Реализация
Нажатие кнопки:
typedef enum {
MODE_CLOCK,
MODE_TEMP,
MODE_SETUP,
// ...
} CLOCK_MODE_t:
volatile CLOCK_MODE_t mode;
void EXTIA_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(tskState, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
void tskState( void *arguments ) {
// ...
while(1) {
// ...
value = ulTaskNotifyTake( pdTRUE, max_expected_time );
}
}
Считывание температуры:
void tskOneWire(void *arguments) {
// work with DS18B20
semaphore_task_1w = xSemaphoreCreateBinary();
1w_temp_mutex = xSemaphoreCreateMutex();
while (1) {
// read tempearture via 1-Wire
if (xSemaphoreTake(1w_temp_mutex, portMAX_DELAY) == pdTRUE) {
// read new value
xSemaphoreGive(1w_temp_mutex);
}
if (clock_mode == MODE_TEMP)
xSemaphoreGiveFromISR(semaphore_task_out, NULL);
xSemaphoreTake(semaphore_logic_isr, 60*configTICK_RATE_HZ);
}
}
Прерывание от RTC происходит раз в секунду.
void RTC_IRQHandler(void) {
// ...
xSemaphoreGiveFromISR(semaphore_tsk_display, NULL);
// ...
}
void tskDisplay() {
semaphore_tsk_display = xSemaphoreCreateBinary();
while (1) {
xSemaphoreTake(semaphore_tsk_display, 1 * configTICK_RATE_HZ);
if (clock_mode == MODE_TEMP) {
// display the temperature
} else {
// data output
taskENTER_CRITICAL();
{
// SPI ouput should be marked as a critical section,
// because you must make this operation atomic
}
taskEXIT_CRITICAL();
}
}
}
Обработка данных от UART не слишком отличается от задачи, отвечающей за 1-Wire интерфейс, поэтому описание этого участка мы опустим. В дополнительных главах вопрос передачи и приема данных будет рассмотрен отдельно.
Перейдем к функции проигрывания мелодии. Всё, что нам требуется, это запустить проигрывание мелодии в прерывании от блока RTC, срабатывающем на определенное время. Здесь, как и раньше, можно создать задачу, которая будет просыпаться через какое-то время и давать управляющий сигнал, но такой механизм мы уже рассмотрели, поэтому обратимся к программным таймерам, чтобы показать их в действии.
В качестве динамика используется пьезоизлучатель. Принцип его работы прост: если приложить напряжение к его клеммам, внутренняя пластинка деформируется. Подавая импульсы, можно добиться колебаний этой пластины с той же частотой: звук не что иное, как распространение упругих волн в среде. Нота ля в первой октаве имеет частоту 440 Гц. Для простоты мы будем использовать аппаратный таймер, настроенный в режим широтно-импульсной модуляции. Меняя заполнение импульса, можно менять громкость, а частоту следования — частоту звука. Пусть заполнение фиксировано, а частота задается через аргумент функции.
Реализовать проигрывание мелодии через таймер можно по-разному. Мы будем использовать два интервальных таймера и массив с частотами и длительностями (звука и его отсутствия). Создадим массив и необходимые переменные.
#define MARIO_ARRAY_HEIGH7
static uint16_t mario[][] = {
//frequency (Hz),length (ms),delay (ms)
{ 600, 100, 150 },
{ 660, 100, 300 },
{ 660, 100, 300 },
{ 510, 100, 100 },
{ 660, 100, 300 },
{ 770, 100, 550 },
{ 380, 100, 575 },
// ...
};
volatile uint32_t index = 0;
TimerHandle_t xTimerLength = NULL;
TimerHandle_t xTimerDelay = NULL;
Далее создадим два таймера в функции main()
. Для обоих таймеров будем использовать одну и ту же функцию-обработчик — vTimer
.
int main(void) {
// ...
xTimerLength = xTimerCreate("soundLength", mario[0][1], pdFALSE, 0, vTimer);
xTimerDelay = xTimerCreate("silence", mario[0][2], pdFALSE, 0, vTimer);
// ...
}
В прерывании RTC мы обнулим переменную index, зададим время срабатывания таймера и запустим xTimerLength
, который в свою очередь включит генерацию ШИМ с частотой из массива, задаст задержку для таймера xTimerDelay
и запустит его. Когда наступит время, сработает таймер xTimerDelay
и остановит генерацию ШИМ. Там самым мы получим проигрывание одной ноты.

Оформим данное поведение в виде кода.
void vTimer(TimerHandle_t xTimer) {
if (index < MARIO_ARRAY_HEIGH) {
switch(xTimer) {
case xTimerLength:
PWM_SET_FREQ(mario[index][0]);
PWM_ON();
xTimerChangePeriod(xTimerLength, (mario[index][1] + mario[index][2]) / portTICK_RATE_MS, 0);
xTimerChangePeriod(xTimerDelay, mario[index][2] / portTICK_RATE_MS, 0);
xTimerResart(xTimerLength);
xTimerResart(xTimerDelay);
break;
case xTimerDelay:
PWM_OFF();
break;
}
}
index++;
}
Таким образом, таймер xTimerLength
будет подпинывать сам себя до тех пор, пока не дойдет до конца массива.