forked from ArjanteMarvelde/uSDR-pico
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhmi.c
424 lines (392 loc) · 12.6 KB
/
hmi.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/*
* hmi.c
*
* Created: Apr 2021
* Author: Arjan te Marvelde
*
* This file contains the HMI driver, processing user inputs.
* It will also do the logic behind these, and write feedback to the LCD.
*
* The 4 auxiliary buttons have the following functions:
* GP6 - Enter, confirm : Used to select menu items or make choices from a list
* GP7 - Escape, cancel : Used to exit a (sub)menu or cancel the current action
* GP8 - Left : Used to move left, e.g. to select a digit
* GP9 - Right : Used to move right, e.g. to select a digit
*
* The rotary encoder (GP2, GP3) controls an up/down counter connected to some field.
* It may be that the encoder has a bushbutton as well, this can be connected to GP4.
* ___ ___
* ___| |___| |___ A
* ___ ___ _
* _| |___| |___| B
*
* Encoder channel A triggers on falling edge.
* Depending on B level, count is incremented or decremented.
*
* The PTT is connected to GP15 and will be active, except when VOX is used.
*
*/
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/timer.h"
#include "hardware/clocks.h"
#include "hardware/gpio.h"
#include "lcd.h"
#include "hmi.h"
#include "dsp.h"
#include "si5351.h"
#include "relay.h"
/*
* GPIO assignments
*/
#define GP_ENC_A 2
#define GP_ENC_B 3
#define GP_AUX_0 6 // Enter, Confirm
#define GP_AUX_1 7 // Escape, Cancel
#define GP_AUX_2 8 // Left move
#define GP_AUX_3 9 // Right move
#define GP_PTT 15
#define GP_MASK_IN ((1<<GP_ENC_A)|(1<<GP_ENC_B)|(1<<GP_AUX_0)|(1<<GP_AUX_1)|(1<<GP_AUX_2)|(1<<GP_AUX_3)|(1<<GP_PTT))
#define GP_MASK_PTT (1<<GP_PTT)
/*
* Event flags
*/
#define GPIO_IRQ_ALL (GPIO_IRQ_LEVEL_LOW|GPIO_IRQ_LEVEL_HIGH|GPIO_IRQ_EDGE_FALL|GPIO_IRQ_EDGE_RISE)
#define GPIO_IRQ_EDGE_ALL (GPIO_IRQ_EDGE_FALL|GPIO_IRQ_EDGE_RISE)
/*
* Display layout:
* +----------------+
* |USB 14074.0 R920| --> mode=USB, freq=14074.0kHz, state=Rx,S9+20dB
* | Fast -10dB| --> ..., AGC=Fast, Pre=-10dB
* +----------------+
* In this HMI state only tuning is possible,
* using Left/Right for digit and ENC for value, Enter to commit change.
* Press ESC to enter the submenu states (there is only one sub menu level):
*
* Submenu Values ENC Enter Escape Left Right
* -----------------------------------------------------------------------------------------------
* Mode USB, LSB, AM, CW change commit exit prev next
* AGC Fast, Slow, Off change commit exit prev next
* Pre +10dB, 0, -10dB, -20dB, -30dB change commit exit prev next
* Vox NoVOX, Low, Medium, High change commit exit prev next
*
* --will be extended--
*/
/* State definitions */
#define HMI_S_TUNE 0
#define HMI_S_MODE 1
#define HMI_S_AGC 2
#define HMI_S_PRE 3
#define HMI_S_VOX 4
#define HMI_S_BPF 5
#define HMI_NSTATES 6
/* Event definitions */
#define HMI_E_NOEVENT 0
#define HMI_E_INCREMENT 1
#define HMI_E_DECREMENT 2
#define HMI_E_ENTER 3
#define HMI_E_ESCAPE 4
#define HMI_E_LEFT 5
#define HMI_E_RIGHT 6
#define HMI_E_PTTON 7
#define HMI_E_PTTOFF 8
#define HMI_NEVENTS 9
/* Sub menu option string sets */
#define HMI_NMODE 4
#define HMI_NAGC 3
#define HMI_NPRE 5
#define HMI_NVOX 4
#define HMI_NBPF 5
char hmi_o_menu[HMI_NSTATES][8] = {"Tune","Mode","AGC","Pre","VOX"}; // Indexed by hmi_state
char hmi_o_mode[HMI_NMODE][8] = {"USB","LSB","AM","CW"}; // Indexed by hmi_sub[HMI_S_MODE]
char hmi_o_agc [HMI_NAGC][8] = {"NoGC","Slow","Fast"}; // Indexed by hmi_sub[HMI_S_AGC]
char hmi_o_pre [HMI_NPRE][8] = {"-30dB","-20dB","-10dB","0dB","+10dB"}; // Indexed by hmi_sub[HMI_S_PRE]
char hmi_o_vox [HMI_NVOX][8] = {"NoVOX","VOX-L","VOX-M","VOX-H"}; // Indexed by hmi_sub[HMI_S_VOX]
char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"};
// Map option to setting
uint8_t hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10};
uint8_t hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40};
uint8_t hmi_state, hmi_option; // Current state and option selection
uint8_t hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state
bool hmi_update;
uint32_t hmi_freq; // Frequency from Tune state
uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments
#define HMI_MAXFREQ 30000000
#define HMI_MINFREQ 100
#define HMI_MULFREQ 1 // Factor between HMI and actual frequency
// Set to 2 for certain types of mixer
#define PTT_DEBOUNCE 3 // Nr of cycles for debounce
int ptt_state; // Debounce counter
bool ptt_active; // Resulting state
/*
* Some macros
*/
#ifndef MIN
#define MIN(x, y) ((x)<(y)?(x):(y)) // Get min value
#endif
#ifndef MAX
#define MAX(x, y) ((x)>(y)?(x):(y)) // Get max value
#endif
/*
* HMI State Machine,
* Handle event according to current state
* Code needs to be optimized
*/
void hmi_handler(uint8_t event)
{
/* Special case for TUNE state */
if (hmi_state == HMI_S_TUNE)
{
if (event==HMI_E_ENTER) // Commit current value
{
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Commit frequency
}
if (event==HMI_E_ESCAPE) // Enter submenus
{
hmi_sub[hmi_state] = hmi_option; // Store selection (i.e. digit)
hmi_state = HMI_S_MODE; // Should remember last one
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
if (event==HMI_E_INCREMENT)
{
if (hmi_freq < (HMI_MAXFREQ - hmi_step[hmi_option])) // Boundary check
hmi_freq += hmi_step[hmi_option]; // Increment selected digit
}
if (event==HMI_E_DECREMENT)
{
if (hmi_freq > (hmi_step[hmi_option] + HMI_MINFREQ)) // Boundary check
hmi_freq -= hmi_step[hmi_option]; // Decrement selected digit
}
if (event==HMI_E_RIGHT)
{
hmi_option = (hmi_option<6)?hmi_option+1:6; // Digit to the right
}
if (event==HMI_E_LEFT)
{
hmi_option = (hmi_option>0)?hmi_option-1:0; // Digit to the left
}
return; // Early bail-out
}
/* Submenu states */
switch(hmi_state)
{
case HMI_S_MODE:
if (event==HMI_E_INCREMENT)
hmi_option = (hmi_option<HMI_NMODE-1)?hmi_option+1:HMI_NMODE-1;
if (event==HMI_E_DECREMENT)
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
case HMI_S_AGC:
if (event==HMI_E_INCREMENT)
hmi_option = (hmi_option<HMI_NAGC-1)?hmi_option+1:HMI_NAGC-1;
if (event==HMI_E_DECREMENT)
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
case HMI_S_PRE:
if (event==HMI_E_INCREMENT)
hmi_option = (hmi_option<HMI_NPRE-1)?hmi_option+1:HMI_NPRE-1;
if (event==HMI_E_DECREMENT)
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
case HMI_S_VOX:
if (event==HMI_E_INCREMENT)
hmi_option = (hmi_option<HMI_NVOX-1)?hmi_option+1:HMI_NVOX-1;
if (event==HMI_E_DECREMENT)
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
case HMI_S_BPF:
if (event==HMI_E_INCREMENT)
hmi_option = (hmi_option<HMI_NBPF-1)?hmi_option+1:HMI_NBPF-1;
if (event==HMI_E_DECREMENT)
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
}
/* General actions for submenus */
if (event==HMI_E_ENTER)
{
hmi_sub[hmi_state] = hmi_option; // Store selected option
hmi_update = true; // Mark HMI updated
}
if (event==HMI_E_ESCAPE)
{
hmi_state = HMI_S_TUNE; // Leave submenus
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
if (event==HMI_E_RIGHT)
{
hmi_state = (hmi_state<HMI_NSTATES-1)?(hmi_state+1):1; // Change submenu
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
if (event==HMI_E_LEFT)
{
hmi_state = (hmi_state>1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
}
/*
* GPIO IRQ callback routine
* Sets the detected event and invokes the HMI state machine
*/
void hmi_callback(uint gpio, uint32_t events)
{
uint8_t evt=HMI_E_NOEVENT;
switch (gpio)
{
case GP_ENC_A: // Encoder
if (events&GPIO_IRQ_EDGE_FALL)
evt = gpio_get(GP_ENC_B)?HMI_E_INCREMENT:HMI_E_DECREMENT;
break;
case GP_AUX_0: // Enter
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_ENTER;
break;
case GP_AUX_1: // Escape
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_ESCAPE;
break;
case GP_AUX_2: // Previous
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_LEFT;
break;
case GP_AUX_3: // Next
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_RIGHT;
break;
default:
return;
}
hmi_handler(evt); // Invoke state machine
}
/*
* Initialize the User interface
*/
void hmi_init(void)
{
/*
* Notes on using GPIO interrupts:
* The callback handles interrupts for all GPIOs with IRQ enabled.
* Level interrupts don't seem to work properly.
* For debouncing, the GPIO pins should be pulled-up and connected to gnd with 100nF.
* PTT has separate debouncing logic
*/
// Init input GPIOs
gpio_init_mask(GP_MASK_IN);
// Enable pull-ups
gpio_pull_up(GP_ENC_A);
gpio_pull_up(GP_ENC_B);
gpio_pull_up(GP_AUX_0);
gpio_pull_up(GP_AUX_1);
gpio_pull_up(GP_AUX_2);
gpio_pull_up(GP_AUX_3);
gpio_pull_up(GP_PTT);
// Enable interrupt on level low
gpio_set_irq_enabled(GP_ENC_A, GPIO_IRQ_EDGE_ALL, true);
gpio_set_irq_enabled(GP_AUX_0, GPIO_IRQ_EDGE_ALL, true);
gpio_set_irq_enabled(GP_AUX_1, GPIO_IRQ_EDGE_ALL, true);
gpio_set_irq_enabled(GP_AUX_2, GPIO_IRQ_EDGE_ALL, true);
gpio_set_irq_enabled(GP_AUX_3, GPIO_IRQ_EDGE_ALL, true);
gpio_set_irq_enabled(GP_PTT, GPIO_IRQ_EDGE_ALL, false);
// Set callback, one for all GPIO, not sure about correctness!
gpio_set_irq_enabled_with_callback(GP_ENC_A, GPIO_IRQ_EDGE_ALL, true, hmi_callback);
// Initialize LCD and set VFO
hmi_state = HMI_S_TUNE;
hmi_option = 4; // Active kHz digit
hmi_freq = 7074000UL; // Initial frequency
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Set freq to 7074 kHz (depends on mixer type)
SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type)
ptt_state = 0;
ptt_active = false;
dsp_setmode(hmi_sub[HMI_S_MODE]);
dsp_setvox(hmi_sub[HMI_S_VOX]);
dsp_setagc(hmi_sub[HMI_S_AGC]);
relay_setattn(hmi_pre[hmi_sub[HMI_S_PRE]]);
relay_setband(hmi_bpf[hmi_sub[HMI_S_BPF]]);
hmi_update = false;
}
/*
* Redraw the display, representing current state
* This function is called regularly from the main loop.
*/
void hmi_evaluate(void)
{
char s[32];
// Print top line of display
sprintf(s, "%s %7.1f %c%3d", hmi_o_mode[hmi_sub[HMI_S_MODE]], (double)hmi_freq/1000.0, (tx_enabled?0x07:0x06), (tx_enabled?0:920));
lcd_writexy(0,0,s);
// Print bottom line of dsiplay, depending on state
switch (hmi_state)
{
case HMI_S_TUNE:
sprintf(s, "%s %s %s", hmi_o_vox[hmi_sub[HMI_S_VOX]], hmi_o_agc[hmi_sub[HMI_S_AGC]], hmi_o_pre[hmi_sub[HMI_S_PRE]]);
lcd_writexy(0,1,s);
lcd_curxy(4+(hmi_option>4?6:hmi_option), 0, true);
break;
case HMI_S_MODE:
sprintf(s, "Set Mode: %s ", hmi_o_mode[hmi_option]);
lcd_writexy(0,1,s);
lcd_curxy(9, 1, false);
break;
case HMI_S_AGC:
sprintf(s, "Set AGC: %s ", hmi_o_agc[hmi_option]);
lcd_writexy(0,1,s);
lcd_curxy(8, 1, false);
break;
case HMI_S_PRE:
sprintf(s, "Set Pre: %s ", hmi_o_pre[hmi_option]);
lcd_writexy(0,1,s);
lcd_curxy(8, 1, false);
break;
case HMI_S_VOX:
sprintf(s, "Set VOX: %s ", hmi_o_vox[hmi_option]);
lcd_writexy(0,1,s);
lcd_curxy(8, 1, false);
break;
case HMI_S_BPF:
sprintf(s, "Band: %d %s ", hmi_option, hmi_o_bpf[hmi_option]);
lcd_writexy(0,1,s);
lcd_curxy(8, 1, false);
default:
break;
}
/* PTT debouncing */
if (hmi_sub[HMI_S_VOX] == 0) // No VOX active
{
gpio_set_dir(GP_PTT, false); // PTT input
if (gpio_get(GP_PTT)) // Get PTT level
{
if (ptt_state<PTT_DEBOUNCE) // Increment debounce counter when high
ptt_state++;
}
else
{
if (ptt_state>0) // Decrement debounce counter when low
ptt_state--;
}
if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debounced level high
ptt_active = false;
if (ptt_state == 0) // Set PTT when debounced level low
ptt_active = true;
}
else
{
ptt_active = false;
gpio_set_dir(GP_PTT, true); // PTT output
}
/* Set parameters corresponding to latest entered option value */
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq);
dsp_setmode(hmi_sub[HMI_S_MODE]);
dsp_setvox(hmi_sub[HMI_S_VOX]);
dsp_setagc(hmi_sub[HMI_S_AGC]);
if (hmi_update)
{
dsp_setmode(hmi_sub[HMI_S_MODE]);
dsp_setvox(hmi_sub[HMI_S_VOX]);
dsp_setagc(hmi_sub[HMI_S_AGC]);
relay_setband(hmi_bpf[hmi_sub[HMI_S_BPF]]);
sleep_ms(1); // I2C doesn't work without...
relay_setattn(hmi_pre[hmi_sub[HMI_S_PRE]]);
hmi_update = false;
}
}