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)
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
По факту, же, оказалось, что при если выставить AF_SEL = 1;, для пинов, использованных для ШИМ в предыдущем примере, то импульсы пропадают. Напротив, любые комбинации AF_S0 и AF_S1 при AF_SEL = 0; не влияют на доступ к GPIO — как генерировало, так и генерирует. Еще один вопрос к дефолтным значениям регистров по версии RM считаю закрытым. (Тяжко будет без отладчика раскурить китайца :))

На этом на сегодня все, в следующий раз разберемся с I2C, тем более, что по нему уже есть наработки.
Всем спасибо за просмотр!
  • +3
  • 24 января 2022, 21:28
  • finskiy
  • 2
Файлы в топике: IR&LED.zip, SoftPWM.zip

Комментарии (1)

RSS свернуть / развернуть
Спасибо за примеры. Вполне юзабельный контроллер получается, по цене значительно дешевле STM32, как бы не ругали китайцев. Мне вот еще непонятно про вывод 14. На W806 он NC, а на W801 там WiFi антенна торчит. Может быть и в 806 RF часть есть? Про I2S тоже не всё очевидно…
0
  • avatar
  • Alm
  • 28 января 2022, 05:32
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.