W806 - GPIO и таймеры. часть 2.

Прикинув, как на практике изобразить работу внешних прерываний, решил подружить W806 с инфракрасным приемником TSOP и помигать светодиодиками при помощи пульта ДУ. Т.к. абсолютное большинство пультов в моем доме работают по протоколу NEC, то его декодирование и будем реализовывать. Вернее, портировать алгоритм, который отлично себя показал в работе на контроллерах STM8. Интересности — под катом.
Итак, соединяем выход приемника с PORTB5, не забываем повесить внешний резистор подтяжки, в моем случае отлично работает 15K, но возможны вариации. Компилируем и зашиваем код:
#include "wm_hal.h"
#define ISR __attribute__((isr)) void
char buffer[32]; // буфер для вывода строк по UART, просто для наглядности
volatile unsigned char rcvd_data[4]; // принятые данные (пакет) от приемника {addr, ~addr, command, ~command}
volatile unsigned char new_command = 0; // команда принята полностью, признак нового пакета
#define INT_IR_GPIO GPIOB // к какому порту подключен IR приемник
#define INT_PIN GPIO_PIN_5 // к какому пину порта (не забываем про внешнюю подтяжку к питанию, 4K7-10K)
#define START_PULSE 12 // длительность стартового бита, мс
#define REPEAT_PULSE 10
#define BIT_ONE 1 // длительность бита "1", в тиках таймера
#define REPEAT_DELAY 3 // сколько импульсов повтора команды пропустить, до того как засчитать это новой командой
ISR GPIOB_IRQHandler(void)
{
static unsigned char repeat_delay = REPEAT_DELAY; // отсчет до начала автоповтора команды
static unsigned char rcvd_bits;
static unsigned char mask;
unsigned char tim_count = TIM->TIM0_CNT; // считываем значение таймера, натиканное от предыдущего прерывания
if(INT_IR_GPIO->MIS & INT_PIN) // реагируем на прерывание не от любого пина, а только от указанного как INT_PIN
{
TIM->CR &= ~(TIM_CR_TIM0_EN); // останавливаем таймер, при этом счетный регистр обнуляется
if(tim_count > START_PULSE) //если это стартовый импульс, 9мс импульс+4,5мс пауза
{
mask = 0x01; // прием начинаем с младшего бита
rcvd_bits = 0; // т.к. это новый пакет, сбрасываем счетчик принятых бит
}
else
{
if(tim_count > REPEAT_PULSE) // если это импульсы повтора команды
{
if(!repeat_delay) // если приняли несколько импульсов повтора, заданных в REPEAT_DELAY
{
new_command = 1; // то считаем что приняли новую команду, с тем же кодом
}
else
{
repeat_delay--; // пропускаем несколько импульсов повтора команды
}
}
else
{
if(tim_count > BIT_ONE) // если длительность превышает длительность единицы
{
rcvd_data[rcvd_bits/8] |= mask; // значит приняли единичку, сохраняем принятый байт в массив пакета, начиная с 0-го байта
}
else
{
rcvd_data[rcvd_bits/8] &= ~mask; // приняли нолик
}
mask = mask<<1; // сдвиг маски,
rcvd_bits++; // увеличиваем количество принятых бит
if(!(rcvd_bits%8)){mask = 0x01;}; // каждые 8 бит обновляем маску
if(rcvd_bits == 32) // если насчитали 32 бит
{
new_command = 1 ; // значит приняли полный пакет от приемника
repeat_delay = REPEAT_DELAY;
}
}
}
TIM->CR |= TIM_CR_TIM0_EN; // запускаем таймер
INT_IR_GPIO->IC |= INT_PIN; // сброс флага прерывания от IR
}
}
int main(void)
{
GPIOB->DIR |= GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2; // пины PORTB0 PORTB1 PORTB2 на выход
// эту секцию можно закомментировать, все работает со значениями регистров по умолчанию
//CLEAR_BIT(NT_IR_GPIO->DIR, INT_PIN); // порт B5 на вход
//CLEAR_BIT(NT_IR_GPIO->PULLUP_EN, INT_PIN);// с подтяжкой к питанию
//CLEAR_BIT(NT_IR_GPIO->IS, INT_PIN); // прерывание по перепаду уровней
//CLEAR_BIT(NT_IR_GPIO->IBE, INT_PIN); // «0» — прерывание по фронту зависит от бита в регистре IEV
//CLEAR_BIT(NT_IR_GPIO->IEV, INT_PIN); // «0» — спадающий фронт
// эту секцию можно закомментировать, все работает со значениями регистров по умолчанию
SET_BIT(INT_IR_GPIO->IC, INT_PIN); // на всякий случай сбрасываем флаг прерывания от IR
SET_BIT(INT_IR_GPIO->IE, INT_PIN); // разрешаем прерывания от IR INPUT
TIM->TIM0_PRD = 20; // период счета таймера, мс, должен быть заведомо больше чем максимальный интервал между спадающими фронтами в протоколе NEC
NVIC_EnableIRQ(GPIOB_IRQn); // включение прерывания от PORTB
while (1)
{
if(new_command == 1) // если обнаружен новый пакет (команда)
{
new_command = 0; // сбрасываем флаг новой команды
sprintf(buffer, "0x%.2X \n\r", rcvd_data[2]); // выводим код команды в UART
printf(buffer); // выводим код команды в UART
GPIOB->DATA ^= GPIO_PIN_0; // переключаем светодиод на PORTB0
if(rcvd_data[2] == 0x13) // кнопка "Ок"
{
GPIOB->DATA ^= GPIO_PIN_2; // переключаем светодиод на PORTB2
}
}
}
}}
Для ленивых (как я), в приложении будут все прошивки, *.fls, для upgrade tools. Можно сразу прошить и посмотреть в работе.
Теперь при приеме любой команды от пульта будет переключаться светодиод на PORTB0, а его коллега на PORTB2 будет реагировать только на команду 0x13. У всех моих пультов этот код привязан к разным кнопкам, в примере используется пульт от спутникового тюнера Viasat, этот код соответствует кнопке «Ок». Для удобства, код нажатой кнопки будет выводится в UART0, его сразу можно будет посмотреть в консоли прошивальщика:

Касательно статусных регистров внешних прерываний, я подсмотрел в HAL, что для определения, от какого пина сработало прерывание GPIO используется регистр MIS, и не стал на этом моменте останавливаться.
Если переключить таймер опроса приемника в режим «микросекунды», то станет возможно более гибко настроить интервалы импульсов, не забыв домножить все значения на 1000.
Продолжим издеваться над таймерами.
В контроллерах серии W80x реализован аппаратный PWM модуль на 5 каналов, с разными вариантами синхронизации работы каналов, но мне интересно было запустить ШИМ на таймерах, которые здесь урезаны по самое не хочу. Для этого один из таймеров будет генерировать тактовые импульсы ШИМ, а два (три, четыре… maybe more) будут использоваться для изменения коэффициента заполнения. Можно даже придумать подобие функции BREAK, для остановки генерации, но естественно, она будет полу-программная, на внешнем прерывании. Мысля пришла в процессе написания заметки, так проверять уже лень.
#include "wm_hal.h"
#define ISR __attribute__((isr)) void
ISR TIM0_5_IRQHandler(void)
{
if(TIM->CR & TIM_CR_TIM0_TIF)
{
GPIOB->DATA &= ~(GPIO_PIN_0);
TIM->CR |= TIM_CR_TIM0_TIF; // сброс флага
}
if(TIM->CR & TIM_CR_TIM4_TIF)
{
GPIOB->DATA &= ~(GPIO_PIN_1);
TIM->CR |= TIM_CR_TIM4_TIF; // сброс флага
}
if(TIM->CR & TIM_CR_TIM5_TIF)
{
TIM->CR |= TIM_CR_TIM0_EN | TIM_CR_TIM4_EN;
GPIOB->DATA |= GPIO_PIN_0 | GPIO_PIN_1 ;
TIM->CR |= TIM_CR_TIM5_TIF; // сброс флага
}
}
int main(void)
{
GPIOB->DIR |= GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2; // пины PORTB0 PORTB1 PORTB2 на выход
TIM->TMR_CONFIG = 1; // частота тактирования блока таймеров 20 МГЦ
TIM->TIM0_PRD = 4900; // период счета таймера0, коэфф. заполнения
TIM->TIM4_PRD = 100; // период счета таймера4, коэфф. заполнения
TIM->TIM5_PRD = 5000; // частота ШИМ
TIM->CR &= ~(TIM_CR_TIM5_MODE | TIM_CR_TIM5_UNIT | TIM_CR_TIM0_UNIT | TIM_CR_TIM4_UNIT);
TIM->CR |= TIM_CR_TIM5_EN | TIM_CR_TIM5_TIE | TIM_CR_TIM0_TIE | TIM_CR_TIM4_TIE;
NVIC_EnableIRQ(TIM_IRQn); // включение прерывания таймеров
while (1)
{}
}
Что из этого получилось — на картинках с осциллографа:

это при
TIM->TMR_CONFIG = 1; // частота тактирования блока таймеров 20 МГЦ
TIM->TIM0_PRD = 600; // период счета таймера
TIM->TIM4_PRD = 350; // период счета таймера
TIM->TIM5_PRD = 1000;

такие результаты дают значения:
TIM->TMR_CONFIG = 9; // частота тактирования блока таймеров 10 МГЦ
TIM->TIM0_PRD = 950; // период счета таймера
TIM->TIM4_PRD = 400; // период счета таймера
TIM->TIM5_PRD = 1000;
При установке коэфф. заполнения менее 1,5%(более 98,5%) синхронизация срывается, сигнал нестабильный:

Изврат, конечно, но для помигать плавненько светодиодом сойдет. При всем при этом процессор работает на дефолтных 80 МГц, времени обработать прерывания — вагон и тележка, видимо тут уже вступают какие-то ограничения задействованной периферии. Разбираться с предельными значениями я оставляю на вашу совесть, и переходим к альтернативным функциям GPIO.
Насчет регистра выбора альтернативной функции GPIO AF_SEL референс-мануал недвусмысленно обещает следующее его значение при старте:

и так описывает его поведение, и связанных с ним регистров AF_S1 и AF_S0

S1.[x] = 0, S0.[x] = 0, alternate function 1 (opt1)По факту, же, оказалось, что при если выставить AF_SEL = 1;, для пинов, использованных для ШИМ в предыдущем примере, то импульсы пропадают. Напротив, любые комбинации AF_S0 и AF_S1 при AF_SEL = 0; не влияют на доступ к GPIO — как генерировало, так и генерирует. Еще один вопрос к дефолтным значениям регистров по версии RM считаю закрытым. (Тяжко будет без отладчика раскурить китайца :))
S1.[x] = 0, S0.[x] = 1, alternate function 2 (opt2)
S1.[x] = 1, S0.[x] = 0, alternate function 3 (opt3)
S1.[x] = 1, S0.[x] = 1, alternate function 4 (opt4)
When [x] = 0, if GPIO_DIR[x] = 0 and GPIO_PULL_EN[x] = 1, the GPIO multiplexing is
opt6 analog IO function
На этом на сегодня все, в следующий раз разберемся с I2C, тем более, что по нему уже есть наработки.
Всем спасибо за просмотр!
- +3
- 24 января 2022, 21:28
- finskiy
- 2
Файлы в топике:
IR&LED.zip, SoftPWM.zip
Комментарии (1)
RSS свернуть / развернуть