Topic: tech dcc pc src prev next
tech dcc pc src > menu.c
#include "menu.h"
#include <avr/interrupt.h>
#include <util/delay.h>
#include "lcd.h"
#include "millis.h"
#include "pins.h"
#define ANALOG_DEBOUNCE_LENGTH 3
#define HERMES_LONG_BUTTON_PRESS_MS 1000
#define HERMES_ANALOG_INPUT_MS 100
typedef struct
{
adc_type history[ANALOG_DEBOUNCE_LENGTH];
unsigned char index;
adc_type total;
} analog_debounce;
#define ANALOG_DEBOUNCE_DEFAULT \
{ \
.history = { 0, 0, 0 }, \
.index = 0, \
.total = 0 \
};
// Structure for debouncing the current sense input.
analog_debounce g_current = ANALOG_DEBOUNCE_DEFAULT;
// The topmost view has finished.
bool g_view_finished;
// The topmost view should be redrawn.
bool g_view_redraw;
// The last switch to be pressed (0 for none, 1 to 13 for a switch).
switch_type g_last_switch = 0;
// The last time the screen was fully refreshed (reset and redrawn).
millis_type g_last_screen_refresh = 0;
void record_analog(analog_debounce *debounce, unsigned short int value)
{
debounce->total -= debounce->history[debounce->index];
debounce->history[debounce->index] = value;
debounce->total += value;
debounce->index =
(unsigned char)(debounce->index + 1) % ANALOG_DEBOUNCE_LENGTH;
}
adc_type adc_value()
{
const adc_type adcl_cpy = ADCL;
return (unsigned int)(ADCH << 8) | adcl_cpy;
}
void Menu_Pcint()
{
static unsigned char pin_cache = 0;
const unsigned char reading = HERMES_RE_PIN;
// Ignore all but the rotary encoder pins.
const unsigned char changed = (unsigned char)(
(
reading & (
(1 << HERMES_RE_A_PIN) |
(1 << HERMES_RE_B_PIN)
)
) ^ (pin_cache & (
(1 << HERMES_RE_A_PIN) |
(1 << HERMES_RE_B_PIN)
)
)
);
const bool a_rising =
(reading & (1 << HERMES_RE_A_PIN)) &&
(changed & (1 << HERMES_RE_A_PIN));
const bool b_high = (reading & (1 << HERMES_RE_B_PIN));
if(a_rising && b_high)
g_last_switch = HERMES_RE_CW;
if(a_rising && !b_high)
g_last_switch = HERMES_RE_ACW;
pin_cache = reading;
}
void Menu_RescanKeypad()
{
// The last time each button was pressed down. 0 if the button is not
// pressed down.
static millis_type g_last_button_press[13] = { 0 };
// The action has been triggered for a switch.
static unsigned short g_switch_action_triggered = 0;
unsigned short out = 0;
HERMES_SWSEL_DDR &= (unsigned char)~(
(1 << HERMES_SWSEL1_PIN) | (1 << HERMES_SWSEL2_PIN) |
(1 << HERMES_SWSEL3_PIN)
);
// The four read lines should be configured as inputs with pull up
// resistors.
HERMES_SW_DDR &= (unsigned char)~(
(1 << HERMES_SW1_PIN) | (1 << HERMES_SW2_PIN) |
(1 << HERMES_SW3_PIN) | (1 << HERMES_SW4_PIN)
);
HERMES_SW_PORT |= (unsigned char)(
(1 << HERMES_SW1_PIN) | (1 << HERMES_SW2_PIN) |
(1 << HERMES_SW3_PIN) | (1 << HERMES_SW4_PIN)
);
// The three select lines should be configured as outputs. They are
// pulled high when off.
HERMES_SWSEL_PORT |= (unsigned char)(
(1 << HERMES_SWSEL1_PIN) | (1 << HERMES_SWSEL2_PIN) |
(1 << HERMES_SWSEL3_PIN)
);
//HERMES_SWSEL_DDR |= (
//(1 << HERMES_SWSEL1_PIN) | (1 << HERMES_SWSEL2_PIN) |
//(1 << HERMES_SWSEL3_PIN)
//);
// Pull each select line low in turn.
HERMES_SWSEL_PORT &= (unsigned char)~(1 << HERMES_SWSEL1_PIN);
HERMES_SWSEL_DDR |= (unsigned char)(1 << HERMES_SWSEL1_PIN);
_delay_ms(1);
out |= (unsigned short)(
( (HERMES_SW_PIN & (1 << HERMES_SW1_PIN)) ? 0 : (1 << 0))
| ( (HERMES_SW_PIN & (1 << HERMES_SW2_PIN)) ? 0 : (1 << 1))
| ( (HERMES_SW_PIN & (1 << HERMES_SW3_PIN)) ? 0 : (1 << 2))
| ( (HERMES_SW_PIN & (1 << HERMES_SW4_PIN)) ? 0 : (1 << 3))
);
HERMES_SWSEL_DDR &= (unsigned char)~(1 << HERMES_SWSEL1_PIN);
HERMES_SWSEL_PORT |= (unsigned char)(1 << HERMES_SWSEL1_PIN);
HERMES_SWSEL_PORT &= (unsigned char)~(1 << HERMES_SWSEL2_PIN);
HERMES_SWSEL_DDR |= (unsigned char)(1 << HERMES_SWSEL2_PIN);
_delay_ms(1);
out |= (unsigned short)(
((HERMES_SW_PIN & (1 << HERMES_SW1_PIN)) ? 0 : (1 << 4))
| ((HERMES_SW_PIN & (1 << HERMES_SW2_PIN)) ? 0 : (1 << 5))
| ((HERMES_SW_PIN & (1 << HERMES_SW3_PIN)) ? 0 : (1 << 6))
| ((HERMES_SW_PIN & (1 << HERMES_SW4_PIN)) ? 0 : (1 << 7))
);
HERMES_SWSEL_DDR &= (unsigned char)~(1 << HERMES_SWSEL2_PIN);
HERMES_SWSEL_PORT |= (unsigned char)(1 << HERMES_SWSEL2_PIN);
HERMES_SWSEL_PORT &= (unsigned char)~(1 << HERMES_SWSEL3_PIN);
HERMES_SWSEL_DDR |= (unsigned char)(1 << HERMES_SWSEL3_PIN);
_delay_ms(1);
out |= (unsigned short)(
( (HERMES_SW_PIN & (1 << HERMES_SW1_PIN)) ? 0 : (1 << 8))
| ((HERMES_SW_PIN & (1 << HERMES_SW2_PIN)) ? 0 : (1 << 9))
| ((HERMES_SW_PIN & (1 << HERMES_SW3_PIN)) ? 0 : (1 << 10))
| ((HERMES_SW_PIN & (1 << HERMES_SW4_PIN)) ? 0 : (1 << 11))
);
HERMES_SWSEL_DDR &= (unsigned char)~(1 << HERMES_SWSEL3_PIN);
HERMES_SWSEL_PORT |= (unsigned char)(1 << HERMES_SWSEL3_PIN);
if(!(HERMES_RE_PIN & (1 << HERMES_RE_C_PIN)))
out |= (unsigned short)(1 << 12);
const millis_type current_millis = millis();
for(unsigned char i = 0; i < 13; ++i)
{
// Inputs have a trigger- reset mechanism. g_switch_action_triggered
// is written when the switch action is sent to the menu event loop and
// cleared (reset) when the button is no longer pressed down.
if(
(out & (unsigned short)(1 << i)) &&
!(g_switch_action_triggered & (unsigned short)(1 << i)) &&
g_last_button_press[i] == 0
)
{
// The button has been pressed down initially.
g_last_button_press[i] = current_millis;
}
else if(
(out & (unsigned short)(1 << i)) &&
!(g_switch_action_triggered & (unsigned short)(1 << i)) &&
current_millis - g_last_button_press[i] > HERMES_LONG_BUTTON_PRESS_MS
)
{
// The button has been pressed for a 'long press' time.
g_last_button_press[i] = 0;
g_last_switch = HERMES_SWITCH_LONG(i + 1);
g_switch_action_triggered |= (unsigned short)(1 << i);
}
else if(
!(out & (unsigned short)(1 << i)) &&
(g_switch_action_triggered & (unsigned short)(1 << i))
)
{
// The button has already been triggered and has now been released.
g_switch_action_triggered &= (unsigned short)~(1 << i);
}
else if(!(out & (unsigned short)(1 << i)) && g_last_button_press[i] > 0)
{
// A previously pressed button has been released. The switch
// action has not been triggered.
g_last_button_press[i] = 0;
g_last_switch = HERMES_SWITCH_SHORT(i + 1);
}
}
}
void Menu_Finish()
{
g_view_finished = true;
}
void Menu_Redraw()
{
g_view_redraw = true;
}
void Menu_FakeButton(switch_type sw)
{
g_last_switch = sw;
}
struct view;
struct view
{
void(*update)(void*);
struct view *covering;
void *context;
};
typedef struct view view;
view *g_top_view = 0;
void Menu_EventLoop(
void(*switch_action)(void*, switch_type),
void(*draw)(void*),
void(*update)(void*),
void *context
)
{
static millis_type g_last_current_sense_time = 0;
view v = { .update = update, .covering = g_top_view, .context = context };
g_top_view = &v;
Menu_Redraw();
for(;;)
{
const millis_type cmillis = millis();
// Read from an analog input every HERMES_ANALOG_INPUT_MS.
if(cmillis >= g_last_current_sense_time + HERMES_ANALOG_INPUT_MS)
{
g_last_current_sense_time = cmillis;
const unsigned short int tmp = adc_value();
record_analog(&g_current, tmp);
// Trigger the next analog read.
ADCSRA |= (1 << ADSC);
}
Menu_RescanKeypad();
if(g_last_switch != 0)
{
const switch_type last_switch = g_last_switch;
g_last_switch = 0;
// The rotation of the rotary encoder gets special treatment;
// trigger more events if it is rotated quickly.
if(
HERMES_SWITCH_NO(last_switch) == HERMES_RE_ACW ||
HERMES_SWITCH_NO(last_switch) == HERMES_RE_CW
)
{
// The last time an action was triggered by the rotary encoder.
static millis_type last_re_trigger_ms = 0;
// The number of times an action has been triggered by the rotary encoder
// recently.
static unsigned char recent_re_trigger_count = 0;
// Multiple triggers within this time are considered 'recent' enough to be
// grouped as part of the same input.
static const millis_type RECENT_MS = 250;
unsigned char trigger_repeat = 1;
if(cmillis < last_re_trigger_ms + RECENT_MS)
{
++recent_re_trigger_count;
// Trigger the appropriate action more than once to speed
// up data entry.
if(recent_re_trigger_count > 5)
trigger_repeat = 4;
else if(recent_re_trigger_count > 3)
trigger_repeat = 2;
}
else
recent_re_trigger_count = 0;
last_re_trigger_ms = cmillis;
for(unsigned char i = 0; i < trigger_repeat; ++i)
{
if(switch_action)
(*switch_action)(context, last_switch);
}
}
else if(switch_action)
(*switch_action)(context, last_switch);
}
// Custom function for the topmost view.
if(update)
(*(update))(context);
if(g_view_redraw && draw)
(*(draw))(context);
view *vu = &v;
while((vu = vu->covering))
{
if(vu->update)
vu->update(vu->context);
}
g_view_redraw = false;
if(g_view_finished)
{
g_view_finished = false;
break;
}
}
// Reset the last switch, in case a switch was pressed but not yet
// processed by the view. For example, the locomotive programming view
// hangs for a time before finishing, during which time button presses are
// still recorded via interrupts.
// This assignment stops the button press being picked up by the view below.
g_last_switch = 0;
// Reinstate the view v was covering as the top view.
g_top_view = v.covering;
// Redraw the view that was covered by v.
Menu_Redraw();
}
int Menu_GetInt(const char *question, int def, int max)
{
typedef struct {
// Question to display.
const char *question;
// Highest number permitted.
const int high;
// Number entered.
int number;
// Ask for a yes/no confirmation.
bool confirm_stage;
// Number to return (will be the default number if left unconfirmed).
int result;
} number_select;
number_select context = {
.question = question,
.high = max,
.number = 0,
.confirm_stage = false,
.result = def
};
void button(void *v, const switch_type sw)
{
number_select *context_ = (number_select*)v;
if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
Menu_Finish();
if(context_->confirm_stage)
{
if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL)
{
// Accept the number.
context_->result = context_->number;
Menu_Finish();
}
if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
{
// Reject the number.
Menu_Finish();
}
if(HERMES_SWITCH_NO(sw) == 12)
{
// Amend the number.
context_->confirm_stage = false;
Menu_Redraw();
}
}
else
{
if(HERMES_SWITCH_NO(sw) == HERMES_RE_ACW)
{
if(context_->number == 0)
context_->number = context_->high;
else if(context_->number > 0)
--context_->number;
Menu_Redraw();
}
if(HERMES_SWITCH_NO(sw) == HERMES_RE_CW)
{
if(context_->number == context_->high)
context_->number = 0;
else if(context_->number < context_->high)
++context_->number;
Menu_Redraw();
}
if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL)
{
context_->confirm_stage = true;
Menu_Redraw();
}
}
}
void draw(void *v)
{
number_select *context_ = (number_select*)v;
LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
LCD_Clear();
if(context_->confirm_stage)
{
LCD_PutS(context_->question);
LCD_Move((unsigned char)(15 - LCD_DigitCount(context_->number)), 0);
LCD_PutI(context_->number);
LCD_Move(0, 1);
LCD_PutC(HERMES_LCD_CHAR_UP);
LCD_PutS(" ");
LCD_PutC(HERMES_LCD_CHAR_OK);
LCD_PutS(" ");
LCD_PutC(HERMES_LCD_CHAR_ACW);
}
else
{
LCD_PutS(context_->question);
LCD_Move((unsigned char)(15 - LCD_DigitCount(context_->number)), 0);
LCD_PutI(context_->number);
LCD_Move(0, 1);
LCD_PutC(HERMES_LCD_CHAR_UP);
LCD_PutS(" ");
LCD_PutC(HERMES_LCD_CHAR_ACW);
LCD_PutS(" ");
LCD_PutC(HERMES_LCD_CHAR_CW);
LCD_PutS(" ");
LCD_PutC(HERMES_LCD_CHAR_OK);
}
}
Menu_EventLoop(button, draw, 0, &context);
return context.result;
}
bool Menu_Question(const char *question)
{
typedef struct
{
bool result;
const char *question;
} question_state;
question_state context = {
.result = false,
.question = question
};
void button(void *v, const switch_type sw)
{
question_state *context_ = (question_state*)v;
if(
HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK ||
HERMES_SWITCH_NO(sw) == 10
)
// Decline the question.
Menu_Finish();
if(
HERMES_SWITCH_NO(sw) == HERMES_RE_SEL ||
HERMES_SWITCH_NO(sw) == 11 ||
HERMES_SWITCH_NO(sw) == 12
)
{
context_->result = true;
Menu_Finish();
}
}
void draw(void *v)
{
question_state *context_ = (question_state*)v;
LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
LCD_Clear();
LCD_PutS(context_->question);
LCD_Move(1, 1);
LCD_PutC('X');
LCD_Move(14, 1);
LCD_PutC(HERMES_LCD_CHAR_OK);
}
Menu_EventLoop(button, draw, 0, &context);
return context.result;
}
int Menu_GetOption(
const char *question,
const char **options,
unsigned char def
)
{
typedef struct
{
const char *question;
const char **options;
unsigned char count;
unsigned char index;
int result;
} state;
unsigned char count = 0;
while(options[count])
++count;
state context = {
.question = question,
.options = options,
.count = count,
.index = def,
.result = -1
};
void button(void *v, const switch_type sw)
{
state *context_ = (state*)v;
if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
Menu_Finish();
if(HERMES_SWITCH_NO(sw) == HERMES_RE_ACW)
{
context_->index =
(unsigned char)((context_->count + context_->index - 1) % context_->count);
Menu_Redraw();
}
if(HERMES_SWITCH_NO(sw) == HERMES_RE_CW)
{
context_->index =
(unsigned char)((context_->index + 1) % context_->count);
Menu_Redraw();
}
if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL)
{
context_->result = context_->index;
Menu_Finish();
}
}
void draw(void *v)
{
state *context_ = (state*)v;
Menu_DrawList(
context_->question,
context_->options[context_->index],
context_->index,
context_->count
);
}
Menu_EventLoop(button, draw, 0, &context);
return context.result;
}
bool Menu_Current(adc_type *current)
{
const adc_type c = (g_current.total / ANALOG_DEBOUNCE_LENGTH) *
HERMES_CURRENT_MULTIPLIER;
if(*current != c)
{
*current = c;
return true;
}
return false;
}
void Menu_DrawList(
const char *title,
const char *text,
unsigned char index,
unsigned char count
)
{
LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
LCD_Clear();
LCD_PutS(title);
LCD_PrintListIndex(index + 1, count);
LCD_Move(0, 1);
LCD_PutS(text);
LCD_Move(13, 1);
LCD_PutC(HERMES_LCD_CHAR_L);
LCD_PutC(' ');
LCD_PutC(HERMES_LCD_CHAR_R);
}
switch_type Menu_Notice(const char *text)
{
return Menu_TimedNotice(text, 0);
}
switch_type Menu_TimedNotice(const char *text, millis_type timeout)
{
typedef struct
{
const char *text;
millis_type dismiss;
switch_type result;
} notice_state;
notice_state context = {
.text = text,
.dismiss = (timeout == 0) ? 0 : millis() + timeout,
.result = HERMES_SWITCH_NULL
};
void draw(void *v)
{
notice_state *context_ = (notice_state*)v;
LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
LCD_Clear();
LCD_PutS(context_->text);
if(context_->dismiss == 0)
{
LCD_Move(15, 1);
LCD_PutC(HERMES_LCD_CHAR_OK);
}
}
void button(void *v, const switch_type sw)
{
(void)sw;
notice_state *context_ = (notice_state*)v;
context_->result = sw;
Menu_Finish();
}
void update(void *v)
{
notice_state *context_ = (notice_state*)v;
if(context_->dismiss != 0 && millis() > context_->dismiss)
Menu_Finish();
}
Menu_EventLoop(button, draw, update, &context);
return context.result;
}