2021-04-12 20:21:40 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
2021-04-16 17:51:51 +00:00
|
|
|
* ___ ___
|
|
|
|
* ___| |___| |___ A
|
|
|
|
* ___ ___ _
|
|
|
|
* _| |___| |___| B
|
2021-04-12 20:21:40 +00:00
|
|
|
*
|
2021-04-16 17:51:51 +00:00
|
|
|
* 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.
|
2021-04-12 20:21:40 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
#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"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* GPIO assignments
|
|
|
|
*/
|
|
|
|
#define GP_ENC_A 2
|
|
|
|
#define GP_ENC_B 3
|
2021-04-16 17:51:51 +00:00
|
|
|
#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
|
2021-04-12 20:21:40 +00:00
|
|
|
#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))
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Events: GPIO_IRQ_LEVEL_LOW, GPIO_IRQ_LEVEL_HIGH, GPIO_IRQ_EDGE_FALL, GPIO_IRQ_EDGE_RISE
|
|
|
|
*/
|
|
|
|
#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: +----------------+
|
2021-04-16 17:51:51 +00:00
|
|
|
* |USB 14074.0 920| --> USB mode, 14074.0 kHz, S9+20dB
|
|
|
|
* |Tune Att Fast| --> Menu:Tune, Attenuator, Fast AGC
|
2021-04-12 20:21:40 +00:00
|
|
|
* +----------------+
|
2021-04-16 17:51:51 +00:00
|
|
|
* LEFT and RIGHT buttons (or encoder) are used to navigate sub-menus such as {tune,mode,agc,pre}.
|
|
|
|
* ENTER is used to get into the sub-menu.
|
|
|
|
* ENTER is used again to exit and accept changes or ESCAPE to exit without changes.
|
|
|
|
*
|
|
|
|
* When entered in a submenu:
|
|
|
|
* Menu Values Encoder Enter Escape Left Right
|
|
|
|
* -------------------------------------------------------------------------------------
|
|
|
|
* Mode USB, LSB, AM, CW <value> Accept Exit <value> <value>
|
|
|
|
* Tune Frequency (digit) <value> Accept Exit <=dig dig=>
|
|
|
|
* AGC Fast, Slow, Off <value> Accept Exit <value> <value>
|
|
|
|
* Pre +20dB, 0, -20dB <value> Accept Exit <value> <value>
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* State definitions */
|
2021-04-19 20:11:01 +00:00
|
|
|
#define HMI_S_MENU 0
|
|
|
|
#define HMI_S_TUNE 1
|
|
|
|
#define HMI_S_MODE 2
|
|
|
|
#define HMI_S_AGC 3
|
|
|
|
#define HMI_S_PRE 4
|
|
|
|
#define HMI_NSTATES 5
|
2021-04-16 17:51:51 +00:00
|
|
|
|
2021-04-19 20:11:01 +00:00
|
|
|
/* 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
|
2021-04-16 17:51:51 +00:00
|
|
|
|
2021-04-19 20:11:01 +00:00
|
|
|
/* Sub menu option string sets */
|
|
|
|
#define HMI_NMODE 4
|
|
|
|
#define HMI_NAGC 3
|
|
|
|
#define HMI_NPRE 3
|
|
|
|
char hmi_o_menu[HMI_NSTATES][8] = {"Menu","Tune","Mode","AGC ","Pre "}; // Selected by hmi_state
|
|
|
|
char hmi_o_mode[HMI_NMODE][8] = {"USB", "LSB", "AM ", "CW "}; // Selected by hmi_option/hmi_mode
|
|
|
|
char hmi_o_agc [HMI_NAGC][8] = {"NoGC", "Slow", "Fast"}; // Selected by hmi_option/hmi_agc
|
|
|
|
char hmi_o_pre [HMI_NPRE][8] = {"Off", "Amp", "Att"}; // Selected by hmi_option/hmi_pre
|
|
|
|
|
|
|
|
uint8_t hmi_state, hmi_option; // Current state and option
|
|
|
|
uint8_t hmi_sub[HMI_NSTATES] = {0,4,0,0,0}; // Stored option per state
|
|
|
|
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
|
2021-04-16 17:51:51 +00:00
|
|
|
|
|
|
|
/*
|
2021-04-19 20:11:01 +00:00
|
|
|
* Finite State Machine,
|
|
|
|
* Handle event according to current state
|
2021-04-12 20:21:40 +00:00
|
|
|
*/
|
2021-04-19 20:11:01 +00:00
|
|
|
void hmi_handler(uint8_t event)
|
2021-04-16 17:51:51 +00:00
|
|
|
{
|
2021-04-19 20:11:01 +00:00
|
|
|
switch(hmi_state)
|
2021-04-16 17:51:51 +00:00
|
|
|
{
|
|
|
|
case HMI_S_MENU:
|
2021-04-19 20:11:01 +00:00
|
|
|
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT))
|
|
|
|
hmi_option = (hmi_option<HMI_NSTATES-1)?hmi_option+1:1;
|
|
|
|
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT))
|
|
|
|
hmi_option = (hmi_option>1)?hmi_option-1:HMI_NSTATES-1;
|
|
|
|
if (event==HMI_E_ENTER)
|
|
|
|
{
|
|
|
|
hmi_state = hmi_option; // Enter new submenu
|
|
|
|
hmi_option = hmi_sub[hmi_state]; // Restore option
|
|
|
|
}
|
2021-04-16 17:51:51 +00:00
|
|
|
break;
|
|
|
|
case HMI_S_TUNE:
|
2021-04-19 20:11:01 +00:00
|
|
|
if (event==HMI_E_ENTER)
|
|
|
|
{
|
|
|
|
hmi_sub[hmi_state] = hmi_option; // Store option
|
2021-04-19 20:24:03 +00:00
|
|
|
SI_SETFREQ(0, 2*hmi_freq); // Commit frequency (x2 due to HW used)
|
2021-04-19 20:11:01 +00:00
|
|
|
}
|
|
|
|
if (event==HMI_E_ESCAPE)
|
|
|
|
{
|
|
|
|
hmi_sub[hmi_state] = hmi_option; // Store option
|
|
|
|
hmi_option = hmi_state;
|
|
|
|
hmi_state = HMI_S_MENU; // Leave submenu
|
|
|
|
}
|
|
|
|
if (event==HMI_E_INCREMENT)
|
|
|
|
{
|
|
|
|
hmi_freq += hmi_step[hmi_option];
|
|
|
|
if (hmi_freq > HMI_MAXFREQ) hmi_freq = HMI_MAXFREQ;
|
|
|
|
}
|
|
|
|
if (event==HMI_E_DECREMENT)
|
|
|
|
{
|
|
|
|
hmi_freq -= hmi_step[hmi_option];
|
|
|
|
if (hmi_freq < HMI_MINFREQ) hmi_freq = HMI_MINFREQ;
|
|
|
|
}
|
|
|
|
if (event==HMI_E_RIGHT)
|
|
|
|
hmi_option = (hmi_option<6)?hmi_option+1:0;
|
|
|
|
if (event==HMI_E_LEFT)
|
|
|
|
hmi_option = (hmi_option>0)?hmi_option-1:6;
|
|
|
|
break;
|
2021-04-16 17:51:51 +00:00
|
|
|
case HMI_S_MODE:
|
2021-04-19 20:11:01 +00:00
|
|
|
if (event==HMI_E_ENTER)
|
|
|
|
{
|
|
|
|
// Set Mode
|
|
|
|
hmi_sub[hmi_state] = hmi_option; // Store option
|
|
|
|
hmi_option = hmi_state;
|
|
|
|
hmi_state = HMI_S_MENU; // Leave submenu
|
|
|
|
}
|
|
|
|
if (event==HMI_E_ESCAPE)
|
|
|
|
{
|
|
|
|
hmi_option = hmi_state;
|
|
|
|
hmi_state = HMI_S_MENU; // Leave submenu
|
|
|
|
}
|
|
|
|
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT))
|
|
|
|
hmi_option = (hmi_option<HMI_NMODE-1)?hmi_option+1:0;
|
|
|
|
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT))
|
|
|
|
hmi_option = (hmi_option>0)?hmi_option-1:HMI_NMODE-1;
|
2021-04-16 17:51:51 +00:00
|
|
|
break;
|
|
|
|
case HMI_S_AGC:
|
2021-04-19 20:11:01 +00:00
|
|
|
if (event==HMI_E_ENTER)
|
|
|
|
{
|
|
|
|
// Set AGC
|
|
|
|
hmi_sub[hmi_state] = hmi_option; // Store option
|
|
|
|
hmi_option = hmi_state;
|
|
|
|
hmi_state = HMI_S_MENU; // Leave submenu
|
|
|
|
}
|
|
|
|
if (event==HMI_E_ESCAPE)
|
|
|
|
{
|
|
|
|
hmi_option = hmi_state;
|
|
|
|
hmi_state = HMI_S_MENU; // Leave submenu
|
|
|
|
}
|
|
|
|
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT))
|
|
|
|
hmi_option = (hmi_option<HMI_NAGC-1)?hmi_option+1:0;
|
|
|
|
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT))
|
|
|
|
hmi_option = (hmi_option>0)?hmi_option-1:HMI_NAGC-1;
|
2021-04-16 17:51:51 +00:00
|
|
|
break;
|
|
|
|
case HMI_S_PRE:
|
2021-04-19 20:11:01 +00:00
|
|
|
if (event==HMI_E_ENTER)
|
|
|
|
{
|
|
|
|
// Set Preamp
|
|
|
|
hmi_sub[hmi_state] = hmi_option; // Store option
|
|
|
|
hmi_option = hmi_state;
|
|
|
|
hmi_state = HMI_S_MENU; // Leave submenu
|
|
|
|
}
|
|
|
|
if (event==HMI_E_ESCAPE)
|
|
|
|
{
|
|
|
|
hmi_option = hmi_state;
|
|
|
|
hmi_state = HMI_S_MENU; // Leave submenu
|
|
|
|
}
|
|
|
|
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT))
|
|
|
|
hmi_option = (hmi_option<HMI_NPRE-1)?hmi_option+1:0;
|
|
|
|
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT))
|
|
|
|
hmi_option = (hmi_option>0)?hmi_option-1:HMI_NPRE-1;
|
2021-04-16 17:51:51 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-04-12 20:21:40 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* GPIO IRQ callback routine
|
|
|
|
*/
|
|
|
|
void hmi_callback(uint gpio, uint32_t events)
|
|
|
|
{
|
2021-04-19 20:11:01 +00:00
|
|
|
uint8_t evt=HMI_E_NOEVENT;
|
|
|
|
|
2021-04-12 20:21:40 +00:00
|
|
|
switch (gpio)
|
|
|
|
{
|
2021-04-19 20:11:01 +00:00
|
|
|
case GP_ENC_A: // Encoder
|
|
|
|
if (events&GPIO_IRQ_EDGE_FALL)
|
|
|
|
evt = gpio_get(GP_ENC_B)?HMI_E_INCREMENT:HMI_E_DECREMENT;
|
2021-04-12 20:21:40 +00:00
|
|
|
break;
|
2021-04-16 17:51:51 +00:00
|
|
|
case GP_AUX_0: // Enter
|
2021-04-19 20:11:01 +00:00
|
|
|
if (events&GPIO_IRQ_EDGE_FALL)
|
|
|
|
evt = HMI_E_ENTER;
|
2021-04-12 20:21:40 +00:00
|
|
|
break;
|
2021-04-16 17:51:51 +00:00
|
|
|
case GP_AUX_1: // Escape
|
2021-04-19 20:11:01 +00:00
|
|
|
if (events&GPIO_IRQ_EDGE_FALL)
|
|
|
|
evt = HMI_E_ESCAPE;
|
2021-04-12 20:21:40 +00:00
|
|
|
break;
|
2021-04-16 17:51:51 +00:00
|
|
|
case GP_AUX_2: // Previous
|
2021-04-19 20:11:01 +00:00
|
|
|
if (events&GPIO_IRQ_EDGE_FALL)
|
|
|
|
evt = HMI_E_LEFT;
|
2021-04-12 20:21:40 +00:00
|
|
|
break;
|
2021-04-16 17:51:51 +00:00
|
|
|
case GP_AUX_3: // Next
|
2021-04-19 20:11:01 +00:00
|
|
|
if (events&GPIO_IRQ_EDGE_FALL)
|
|
|
|
evt = HMI_E_RIGHT;
|
2021-04-12 20:21:40 +00:00
|
|
|
break;
|
2021-04-16 17:51:51 +00:00
|
|
|
case GP_PTT: // PTT
|
2021-04-12 20:21:40 +00:00
|
|
|
if (events&GPIO_IRQ_EDGE_FALL)
|
|
|
|
dsp_ptt(true);
|
|
|
|
else
|
|
|
|
dsp_ptt(false);
|
2021-04-19 20:11:01 +00:00
|
|
|
return;
|
2021-04-12 20:21:40 +00:00
|
|
|
default:
|
2021-04-19 20:11:01 +00:00
|
|
|
return;
|
2021-04-12 20:21:40 +00:00
|
|
|
}
|
2021-04-19 20:11:01 +00:00
|
|
|
|
|
|
|
hmi_handler(evt);
|
2021-04-12 20:21:40 +00:00
|
|
|
}
|
|
|
|
|
2021-04-19 20:11:01 +00:00
|
|
|
/*
|
|
|
|
* Initialize the User interface
|
|
|
|
*/
|
2021-04-12 20:21:40 +00:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
// 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, true);
|
|
|
|
|
|
|
|
// 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);
|
2021-04-16 17:51:51 +00:00
|
|
|
|
2021-04-12 20:21:40 +00:00
|
|
|
// Initialize LCD and set VFO
|
2021-04-16 17:51:51 +00:00
|
|
|
hmi_state = HMI_S_TUNE;
|
2021-04-19 20:24:03 +00:00
|
|
|
hmi_option = 4; // Active kHz digit
|
|
|
|
hmi_freq = 7074000UL; // Initial frequency
|
2021-04-12 20:21:40 +00:00
|
|
|
|
2021-04-16 17:51:51 +00:00
|
|
|
SI_SETFREQ(0, 2*hmi_freq); // Set freq to 2*7074 kHz
|
|
|
|
SI_SETPHASE(0, 2); // Set phase to 180deg
|
2021-04-12 20:21:40 +00:00
|
|
|
}
|
|
|
|
|
2021-04-19 20:11:01 +00:00
|
|
|
/*
|
|
|
|
* Redraw the LCD, representing current state
|
|
|
|
*/
|
|
|
|
void hmi_evaluate(void)
|
|
|
|
{
|
|
|
|
char s[20];
|
|
|
|
|
|
|
|
sprintf(s, "%s %7.1f %3d", hmi_o_mode[hmi_sub[HMI_S_MODE]], (double)hmi_freq/1000.0, 920);
|
|
|
|
lcd_writexy(0,0,s);
|
|
|
|
switch (hmi_state)
|
|
|
|
{
|
|
|
|
case HMI_S_MENU:
|
|
|
|
sprintf(s, "=> %s ", hmi_o_menu[hmi_option]);
|
|
|
|
lcd_writexy(0,1,s);
|
|
|
|
lcd_curxy(2, 1, false);
|
|
|
|
break;
|
|
|
|
case HMI_S_TUNE:
|
|
|
|
sprintf(s, "%s %s %s", hmi_o_menu[HMI_S_TUNE], hmi_o_pre[hmi_sub[HMI_S_PRE]], hmi_o_agc[hmi_sub[HMI_S_AGC]]);
|
|
|
|
lcd_writexy(0,1,s);
|
|
|
|
lcd_curxy(4+(hmi_option>4?6:hmi_option), 0, true);
|
|
|
|
break;
|
|
|
|
case HMI_S_MODE:
|
|
|
|
sprintf(s, "=> Mode: %s ", hmi_o_mode[hmi_option]);
|
|
|
|
lcd_writexy(0,1,s);
|
|
|
|
lcd_curxy(9, 1, false);
|
|
|
|
break;
|
|
|
|
case HMI_S_AGC:
|
|
|
|
sprintf(s, "=> AGC: %s ", hmi_o_agc[hmi_option]);
|
|
|
|
lcd_writexy(0,1,s);
|
|
|
|
lcd_curxy(8, 1, false);
|
|
|
|
break;
|
|
|
|
case HMI_S_PRE:
|
|
|
|
sprintf(s, "=> Pre: %s ", hmi_o_pre[hmi_option]);
|
|
|
|
lcd_writexy(0,1,s);
|
|
|
|
lcd_curxy(8, 1, false);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|