Вступление
Схема термостата
Требования к устройству
Перед написанием программы я сформулировал к термостату ряд требований:- термостат должен измерять и отображать на lcd текущее значение температуры
- значение поддерживаемой температуры должно задаваться двумя значениями – нижним и верхним
- термостат должен иметь простой пользовательский интерфейс для установки крайних значений поддерживаемой температуры.
Распределение ресурсов микроконтроллера
Подход к написанию программы
Первое с чем нужно определиться, это в каких состояниях может находиться устройство. У нашего устройства будет три состояния:
Основное
Установка нижнего порога температуры
Установка верхнего порога температуры
Далее нужно определить количество событий. В нашем случае их четыре:
Нажатие кнопки Enter
Нажатие кнопки Up
Нажатие кнопки Down
Прерывание таймера Т1
И состояниям и событиям присваиваются коды. В программе я вынес их в отдельный файл – list-event.h.
//коды событий
#define KEY_NULL 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_ENTER 3
#define EVENT_TIMER 4
//коды состояний
#define GENERAL 0
#define SET_TEMPERATURE_LOW 1
#define SET_TEMPERATURE_HI 2
//обработчик события - таймер
void HandlerEventTimer(void) {}
//обработчик кнопки Enter
void HandlerEventButEnter(void) {}
//обработчик кнопки Up
void HandlerEventButUp(void) {}
//обработчик кнопки Down
void HandlerEventButDown(void) {}
//массив указателей на функции-обработчики
__flash void (*FuncAr[])(void) =
{
HandlerEventButUp,
HandlerEventButDown,
HandlerEventButEnter,
HandlerEventTimer
};
Код события “нажатие кнопки UP” равен 1, поэтому его обработчик в массиве первый!
#pragma vector = TIMER1_COMPA_vect
__interrupt void Timer1CompA(void)
{
ES_PlaceEvent(EVENT_TIMER);
}
#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0Comp(void)
{
BUT_Debrief();
}
.....
void BUT_Debrief(void)
{
unsigned char key = 0;
//последовательный опрос выводов мк
if (BitIsClear(PIN_BUTTON, UP))
key = KEY_UP;
else if (BitIsClear(PIN_BUTTON, DOWN))
key = KEY_DOWN;
else if (BitIsClear(PIN_BUTTON, ENTER))
key = KEY_ENTER;
else {
key = KEY_NULL;
}
//если во временной переменной что-то есть
if (key) {
//и если кнопка удерживается долго
//записать ее номер в буфер
if (comp == THRESHOLD) {
comp = THRESHOLD+10;
ES_PlaceEvent(key);
return;
}
else if (comp < (THRESHOLD+5)) comp++;
}
else comp=0;
}
Дошли до обработчиков событий.
void HandlerEventButEnter(void)
{
switch (state){
case GENERAL:
GUI_SelectTempLow();
state = SET_TEMPERATURE_LOW;
break;
case SET_TEMPERATURE_LOW:
GUI_SelectTempHi();
state = SET_TEMPERATURE_HI;
break;
case SET_TEMPERATURE_HI:
GUI_General();
state = GENERAL;
break;
default:
break;
}
}
//обработчик кнопки Up
void HandlerEventButUp(void)
{
switch (state){
case GENERAL:
state = GENERAL;
break;
case SET_TEMPERATURE_LOW:
GUI_IncTempLow();
state = SET_TEMPERATURE_LOW;
break;
case SET_TEMPERATURE_HI:
GUI_IncTempHi();
state = SET_TEMPERATURE_HI;
break;
default:
break;
}
}
Но поскольку в некоторых случаях выполняется пустая работа, сокращаем обработчик до:
//обработчик кнопки Up
void HandlerEventButUp(void)
{
switch (state){
case SET_TEMPERATURE_LOW:
GUI_IncTempLow();
break;
case SET_TEMPERATURE_HI:
GUI_IncTempHi();
break;
default:
break;
}
}
Обработчик нажатия кнопки DOWN выглядит аналогично, поэтому его не привожу.
И последний обработчик.
void HandlerEventTimer(void)
{
GUI_Control();
}
#define TEMP_LOW 0
#define TEMP_HI 2
unsigned char stateTerm = TEMP_LOW;
void GUI_Control(void)
{
//считываем результат и отображаем на lcd
unsigned char curTemp = ADC_GetBuf();
LCD_Goto(7,0);
BCD_3Lcd(curTemp);
//управление нагревателем
switch(stateTerm){
case TEMP_LOW:
if (curTemp <= tempHi){
LCD_Goto(12, 0);
LCD_WriteData('+');
PORTD |= (1<<TR);
}
else stateTerm = TEMP_HI;
break;
case TEMP_HI:
if (curTemp >= tempLow) {
LCD_Goto(12, 0);
LCD_WriteData('-');
CONTR_PORT &= ~(1<<TR);
}
else stateTerm = TEMP_LOW;
break;
default:
break;
}
//запускаем АЦП
ADC_StartConv();
}
На этом все. Остальная часть программы не должна вызвать затруднений.
Полный текст модуля event-system.c
Заключение
В программе термостата не реализовано сохранение температурных порогов в EEPROM и защита от установки неправильных значений. То есть пользователь вполне может установить верхний температурный порог меньше нижнего. Устранение недочетов предлагаю в качестве домашнего задания тем, кому будет интересно доработать проект.
Продолжение следует...
Файлы
Проект для IAR`aПроект для CodeVision
Проект для Proteus`a
Comments
становится более привычным !
Статья заслуживает уважительного изучения.Очень и очень интересно.
Небольшой багреперорт к проекту для WinAVR:
1. adc.c, строка 31: условие должно быть (count == 7)
2. interface.c, строка 108: должно быть не PORTD а CONTR_PORT
if (count == 8){
...adcValueBuf = adcValue>>3;
...adcValue = 0;
...count = 0;
}else{
...count++;//ин кр. счетчик
...ADC_StartCon v();//запускаем преобр.
}
2. Да, не заметил.
До кода, который вы привели есть строчки
static unsigned int adcValue = 0;
static unsigned char count = 0;
adcValue += ADCH;
Значит, при значениях count 0-8 к acdvalue буду добавлятся значения с АЦП. Т.е. будет сумма 9ти значений, а не 8ми.
Другими словами, первое преобразование запускается из GUI_Control(), и еще 8 из прерывания.
Если все же не верите, прогоните в дебагере этот кусок кода.
например Микроменю (http://easyelectronics.ru/organizaciya-drevovidnogo-menyu.html)
Я это пока прикручиваю, но может быть есть какой-нибудь элегантный способ?
http://chipenable.ru/index.php/programming-c/73-sobytijnaja-sistema-na-tablice.html
Компилятор WinAVR-20100110 Кто-то еще пробовал собрать проект под WinAVR?
Code:
pFunc = (void*)pgm_read_word_n ear(&(FuncAr[event-1]));
Code:
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
static unsigned int count = 0;
TCNT0=0x7D;
switch(count){
case 9:
BUT_Debrief();
break;
case 249:
ES_PlaceEvent(EVENT_TIMER);
count = 0; // обнуляем в последнем case
break;
default:
break;
}
count++;
}
Для тех кто говорит что switch в прерывании огромная трата тактов... Пример для наглядности, у меня в виде ассемблерной вставки.
З.Ы. Применил в своем проекте, остался доволен, спасибо!
Вопрос - А можно ли так расширить обработчики, чтобы они, например, возвращали значение?
Code:
unsigned int Handle (void)
Таблица __flash void (*FuncAr[])(voi d)- это в принципе всего лишь список указателей. Почему не позволить добавлять, например, один параметр?
Я понимаю, что интерфейсная часть всех функций должна быть одинакова. Диспетчер ведь не имеет информации о том, какая функция запускается.
При этом появляется новая функциональност ь, которая может быть использована, например, при построении иерархического дерева взаимодействия диспетчеров на различных уровнях работы программы.
void (*FuncAr[])(voi d) PROGMEM=
{
HandlerEventButUp,
HandlerEventButDown,
HandlerEventButEnter,
HandlerEventTimer
};
error: variable must be const in order to be put into read-only section by means of '__attribute__( (progmem))'
RSS feed for comments to this post