/* * hmi.c * * Created: Dec 2021 * Author: Arjan te Marvelde * * */ #include #include #include "pico/stdlib.h" #include "pico/sem.h" #include "hardware/i2c.h" #include "hardware/gpio.h" #include "hardware/timer.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/pio.h" #include "hardware/dma.h" #include "hmi.h" #include "gen.h" #include "lcd.h" /** Some generic identifiers **/ // Mode strings #define HMI_SQR 0 #define HMI_TRI 1 #define HMI_SAW 2 #define HMI_SIN 3 #define HMI_PUL 4 #define HMI_NMODE 5 uint8_t *hmi_chmode[HMI_NMODE] = { LCD_SQR24X12, LCD_TRI24X12, LCD_SAW24X12, LCD_SIN24X12, LCD_PUL24X12}; // Unit strings #define HMI_USEC 0 #define HMI_MSEC 1 #define HMI_NUNIT 2 uint8_t *hmi_chdur[HMI_NUNIT] = {"usec", "msec"}; // Pulse parameters #define HMI_DUTY 0 #define HMI_RISE 1 #define HMI_FALL 2 #define HMI_NPUL 3 // Additional Pulse parameters uint8_t *hmi_chpul[HMI_NPUL] = {"Duty", "Rise", "Fall"}; /** Channel A/B wave definition structures **/ extern uint8_t sine[GEN_MAXBUFLEN]; // Full sine waveform typedef struct { int mode; // Waveform type float time; // Duration, in seconds int duty; // Duty cycle, percentage of duration int rise; // Rise time, percentage of duration int fall; // Fall time, percentage of duration } ch_t; ch_t hmi_chdef[2]; uint8_t hmi_wave[GEN_MAXBUFLEN]; // Scratch buffer for waveform samples // Generate waveform samples in scratch buffer // Division factor should end-up above 4 to get a <0.1% deviation // so: fsample < fsys/4, implying a bufferlength of maximum time*fsys/4. // This is about 50 samples per usec, but this length should be minimized too // which means that for times smaller than a few usec the frequency will be less accurate. // Simple waveforms can be calculated, for Sine wave there is a lookup-table void hmi_genwave(int ch) { int i; uint32_t d, r, f; float step; wfg_t wf; // Calculate optimum nr of samples wf.len = (uint32_t)((_fsys/1) * hmi_chdef[ch].time); // Calculate required nr of samples wf.len &= ~3; // Multiple of 4 bytes if (wf.len<20) wf.len = 20; // Minimum size if (wf.len>GEN_MAXBUFLEN) wf.len = GEN_MAXBUFLEN; // Maximum size // Fill array switch (hmi_chdef[ch].mode) { case HMI_SQR: memset(&hmi_wave[0],0xff, wf.len/2); // High half samples memset(&hmi_wave[wf.len/2],0x00, wf.len/2); // Low half samples break; case HMI_TRI: step = 255.0/(wf.len/2); // Calculate slope (step per sample) for (i=0; i=(HMI_NPAR+HMI_NPUL*2)) return; // Range check if (hmi_chpar[i].str == NULL) // Character? lcd_putc(hmi_chpar[i].x,hmi_chpar[i].y,'0'+hmi_chpar[i].val,LCD_6X8,invert); else if (i==0) // or Graphic? lcd_putg(hmi_chpar[i].x,hmi_chpar[i].y,hmi_chpar[i].str[hmi_chpar[i].val],invert); else // else String lcd_puts(hmi_chpar[i].x,hmi_chpar[i].y,hmi_chpar[i].str[hmi_chpar[i].val],LCD_6X8,invert); } // Write channel setting at display bottom void hmi_writech(int ch) { int y = (ch?112:98); lcd_clrscr(0,y,128,12); lcd_puts( 4, y , (ch?"B:":"A:"), LCD_8X12, false); lcd_putg( 20, y , hmi_chmode[hmi_chpar[0].val], false); lcd_putc( 54, y+2, '0'+hmi_chpar[1].val, LCD_6X8, false); lcd_putc( 60, y+2, '0'+hmi_chpar[2].val, LCD_6X8, false); lcd_putc( 66, y+2, '0'+hmi_chpar[3].val, LCD_6X8, false); lcd_putc( 72, y+2, '.', LCD_6X8, false); lcd_putc( 78, y+2, '0'+hmi_chpar[4].val, LCD_6X8, false); lcd_putc( 84, y+2, '0'+hmi_chpar[5].val, LCD_6X8, false); lcd_putc( 90, y+2, '0'+hmi_chpar[6].val, LCD_6X8, false); lcd_puts( 98, y+2, hmi_chdur[hmi_chpar[7].val], LCD_6X8, false); } // Init scratch from channel def void hmi_initch(int ch, int par) { ch_t *chdef; uint32_t dur; char s[16]; int i; chdef = &hmi_chdef[ch&1]; if (chdef->time < 1.0e-3) // Range is either 999.999 msec or usec { hmi_chpar[7].val = HMI_USEC; dur = (uint32_t)(chdef->time*1e9); } else if (chdef->time < 1.0) { hmi_chpar[7].val = HMI_MSEC; dur = (uint32_t)(chdef->time*1e6); } hmi_chpar[6].val = dur%10; dur = dur/10; // so only 6 digits are relevant hmi_chpar[5].val = dur%10; dur = dur/10; hmi_chpar[4].val = dur%10; dur = dur/10; hmi_chpar[3].val = dur%10; dur = dur/10; hmi_chpar[2].val = dur%10; dur = dur/10; hmi_chpar[1].val = dur%10; hmi_chpar[0].val = chdef->mode; hmi_chpar[13].val = chdef->fall%10; // For some waveforms this is meaningless hmi_chpar[12].val = chdef->fall/10; hmi_chpar[11].val = chdef->rise%10; hmi_chpar[10].val = chdef->rise/10; hmi_chpar[ 9].val = chdef->duty%10; hmi_chpar[ 8].val = chdef->duty/10; if ((par<0)||(par>=HMI_NPAR+HMI_NPPAR)) return; // Initialize display layout of scratch area lcd_clrscr(32,0,96,32); // Clean title area lcd_puts(44,8,ch?"Chan.B":"Chan.A",LCD_12X16,false); // Write title lcd_hruler(44,24,72); lcd_clrscr(0,32,128,62); // Clean scratch area lcd_puts( 4, 36, "Mode",LCD_8X12,false); // Channel parameter captions lcd_puts( 48, 36, "Time",LCD_8X12,false); lcd_putc( 66, 54, '.', LCD_6X8, false); lcd_puts( 2, 76, "Dut", LCD_6X8, false); lcd_puts( 44, 76, "Ris", LCD_6X8, false); lcd_puts( 86, 76, "Fal", LCD_6X8, false); for (i=0;imode = hmi_chpar[0].val; val = hmi_chpar[1].val; val = val*10 + hmi_chpar[2].val; val = val*10 + hmi_chpar[3].val; val = val*10 + hmi_chpar[4].val; val = val*10 + hmi_chpar[5].val; val = val*10 + hmi_chpar[6].val; if (hmi_chpar[7].val == HMI_USEC) chdef->time = (val+1) * 1.0e-9; else if (hmi_chpar[7].val == HMI_MSEC) chdef->time = (val+1) * 1.0e-6; // TBD Check range // Store shape scratch values switch (hmi_chdef[ch].mode) { case HMI_SQR: hmi_chdef[ch].duty = 50; hmi_chdef[ch].rise = 1; hmi_chdef[ch].fall = 1; break; case HMI_TRI: hmi_chdef[ch].duty = 50; hmi_chdef[ch].rise = 50; hmi_chdef[ch].fall = 50; break; case HMI_SAW: hmi_chdef[ch].duty = 99; hmi_chdef[ch].rise = 99; hmi_chdef[ch].fall = 1; break; case HMI_SIN: hmi_chdef[ch].duty = 50; hmi_chdef[ch].rise = 0; hmi_chdef[ch].fall = 0; break; case HMI_PUL: hmi_chdef[ch].duty = 10*hmi_chpar[ 8].val + hmi_chpar[ 9].val; hmi_chdef[ch].rise = 10*hmi_chpar[10].val + hmi_chpar[11].val; if (hmi_chdef[ch].rise>hmi_chdef[ch].duty) hmi_chdef[ch].rise=hmi_chdef[ch].duty; hmi_chdef[ch].fall = 10*hmi_chpar[12].val + hmi_chpar[13].val; if (hmi_chdef[ch].fall>100-hmi_chdef[ch].duty) hmi_chdef[ch].rise=100-hmi_chdef[ch].duty; break; } return; } /** Menu state machine **/ // The three menu selection keys (TOP, MID, BOT) choose the menu to enter. // Pressing the menu selection key again re-enters the menu, rejecting all changes. // Changes have to be committed by the CENTER key. // The active menu context determines how the navigation keys are interpreted. // Key definitions #define HMI_NOKEY 0x00 #define HMI_BOT 0x01 #define HMI_MID 0x02 #define HMI_TOP 0x04 #define HMI_CENTER 0x08 #define HMI_DOWN 0x10 #define HMI_RIGHT 0x20 #define HMI_UP 0x40 #define HMI_LEFT 0x80 uint8_t keystat; // Menu parameters #define HMI_M_LCR 0x01 #define HMI_M_CHA 0x02 #define HMI_M_CHB 0x03 int hmi_menu = HMI_M_LCR; // Current active menu int par; // Current active menu parameter int channel; // Current active channel void hmi_lcrmenu(int key) { } void hmi_chmenu(int key) { switch(key) { case HMI_UP: hmi_chpar[par].val = (hmi_chpar[par].val0)?hmi_chpar[par].val-1:hmi_chpar[par].max; hmi_writepar( par, true); break; case HMI_LEFT: hmi_writepar( par, false); par = (par>0)?par-1:par; hmi_writepar( par, true); break; case HMI_RIGHT: hmi_writepar( par, false); if (hmi_chpar[0].val == HMI_PUL) // Only change shape when Pulse WF par = (par LCR measurement hmi_menu = HMI_M_LCR; par = 0; // Initialize parameter nr lcd_clrscr(32,0,96,32); // Clean title area lcd_putg(55,8,LCD_CIR16X16,false); // Write title, 3 circles lcd_putg(75,8,LCD_CIR16X16,false); lcd_putg(95,8,LCD_CIR16X16,false); lcd_clrscr(0,32,128,62); // Clean scratch area break; case HMI_MID: // Middle button => channel A hmi_menu = HMI_M_CHA; par = 0; // Initialize parameter nr channel = 0; hmi_initch(channel, par); // Init scratch break; case HMI_BOT: // Bottom button => channel B hmi_menu = HMI_M_CHB; par = 0; // Initialize parameter nr channel = 1; hmi_initch(channel, par); // Init scratch break; } if (hmi_menu == HMI_M_LCR) hmi_lcrmenu(key); else hmi_chmenu(key); } /** Called from main loop at regular intervals **/ void hmi_evaluate() { uint8_t rxdata[4]; uint8_t key; static bool firsttime = true; // Get key status i2c_read_blocking(i2c0, I2C_PCF8574, rxdata, 1, false); // Poll PCF8574 key = rxdata[0] ^ 0xff; // Take XOR for selection if (key == keystat) return; // No change: so bail out if (firsttime) // Initialize screen { lcd_clrscr(0,0,128,128); lcd_putg(0, 0, LCD_UDJAT32, false); lcd_hruler(0, 94, 128); hmi_initch(0, -1); hmi_writech(0); hmi_initch(1, -1); hmi_writech(1); lcd_hruler(0, 126, 128); firsttime = false; } hmi_handler(key); keystat = key; // Remember this key event } void hmi_init() { uint8_t rxdata[4]; // Get key status i2c_read_blocking(i2c0, I2C_PCF8574, rxdata, 1, false); // Get PCF8574 byte keystat = rxdata[0] ^ 0xff; // Initialize keystat hmi_chdef[0].mode = HMI_SQR; // Waveform type hmi_chdef[0].time = 1.001e-6f; // Duration, in seconds hmi_chdef[0].duty = 50; // Duty cycle, percentage of duration hmi_chdef[0].rise = 1; // Rise time, percentage of duration hmi_chdef[0].fall = 1; // Fall time, percentage of duration hmi_chdef[1].mode = HMI_TRI; // Waveform type hmi_chdef[1].time = 1.001e-6f; // Duration, in seconds hmi_chdef[1].duty = 50; // Duty cycle, percentage of duration hmi_chdef[1].rise = 50; // Rise time, percentage of duration hmi_chdef[1].fall = 50; // Fall time, percentage of duration }