Реализация

Нажатие кнопки:

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 будет подпинывать сам себя до тех пор, пока не дойдет до конца массива.


Изменено: