Сведения о протоколе NEC
В интернете очень много реализации данного протокола. Но я бы хотел поговорить больше о аппаратном захвате импульсов с фотоприемника. Для начала общий вид посылки. Картинки взял с сайта altium, кстати там же есть более подробное описание протокола.
Как видно из рисунка 1 в самом начале посылки идет стартовая последовательность — импульс 9мс и затем пауза длительностью 4,5мс. Затем идет адрес и его инверсное значение, потом команда и ее инверсное значение. Подобное дублирование сделано, как мне кажется для повышенной помехозащищенности. Бывает также и расширенный протокол NEC, где адрес не дублируется, а на месте прямого значения адреса младший байт, а на месте инверсного старший байт.
Биты же в посылке определяются длительностью импульса и паузы. Для ‘1’ и ‘0’ длительность импульса одинаковая и составляет 562.5 мкс, а длина паузы разная 1.6875 мс и 562.5 мкс соответственно.
Если не опустить кнопку на пульте через интервал 108 мс будет отправлен код повтора.
Код повтора представляет из себя импульсы длительностью 9 мс, затем пауза 2,25 и потом импульс 562,5 мкс.
Собственно вот описание общее данного протокола, стоит заметить, что все данные с выхода фотоприемника идут инвертированные.
Аппаратный захват посылок
На практике разберем используя отладочную плату и модуль инфракрасного фотоприемника TSOP2136.
Я буду использовать TIM1 и его первый канал захвата на 40 ноге (PE9).
Итак по сути нам нужно замерять длительность импульса и паузы. У таймеров STm32 есть режим PWM input, который отлично нам подойдет. В таком режиме используются сразу два канала захвата таймера. Второй канал коммутируется к первому внутри самого микроконтроллера.
Рассмотрим режим PWM input более подробно. Как видно из рисунка по фронту входного сигнала сохраняется значение счетного регистра TIM_CNT в регистр захвата 1 (Input Capture 1(IC1)) и тут же его значение обновляется. По спаду входного сигнала значение счетного регистра сохраняется в регистр захвата 2 (Input Capture 2 (IC2)). Таким образом в IC1 получаем общую длину импульса и паузы в терминах протокола NEC, а в IC2 длину импульса. В регистры делителя таймера нужно записать такое значение, чтобы один тик счетчика равнялся 1 мкс. В моем случае тактовая частота TIM1 равна 167Мгц, соответственно и в регистр предделителя запишем 167. А в регистр ARR (Auto-reload register), который определяет до какого значения считает счетчик. Запишем например значение 20000, это даст нам 20 мс до переполнения счетчика. Таким образом в регистрах захвата получим значение в микросекундах, а переполнение получим значительно больше, чем самая длинная комбинация импульса и паузы в посылке. Еще важно выставить бит TIM_CR1_URS в регистре управления CR1, он определяет, что прерывание Update будет происходить только по переполнению счетчика.
SET_BIT(TIM1->CR1, TIM_CR1_URS);//Update прерывание только переполнению
С помощью DMA можно вычитывать значения регистров захвата. По первому прерыванию захвата (Сapture Сompare) разрешить прерывание по обновлению (Update). В самом обработчике прерывания Update выдать сигнал, что пакет принят в буфер и можно его разбирать. Ведь прерывание Update возникнет лишь когда счетчик переполнится, а при получении посылке, такое невозможно, так импульсы идут с периодом меньше чем 20 мс. В обработчике также необходимо запретить прерывание Update и разрешить прерывание Сapture Сompare, т.е. вернуться в исходное состояние.
//Обработчик прерывания по захвату void TIM1_CC_IRQHandler(void) { HAL_TIM_IRQHandler(&htim1); CLEAR_BIT(TIM1->DIER, TIM_DIER_CC1IE);//запретить прерывание по захвату SET_BIT(TIM1->DIER, TIM_DIER_UIE);//разрешить по переполенению } //Обработчик прерывания Update void TIM1_UP_TIM10_IRQHandler(void) { static uint32_t cnt_int=0;//кол-во прерываний в холостую //так как возможно получить прерывание по захвату от помехи //включится прерывание Upadate и будет работать до прихода //очередной посылки от пульта HAL_TIM_IRQHandler(&htim1); Nec._cnt =0xff-DMA2_Stream6->NDTR;//кол-во принятых импульсов и пауз if (cnt_int==5)//если возникло случайное прерывание { SET_BIT(TIM1->DIER, TIM_DIER_CC1IE);//разрешить по захвату CLEAR_BIT(TIM1->DIER, TIM_DIER_UIE);//запретить прерывание Update cnt_int=0; } if (Nec._cnt>=4)//получил посылку можно разбирать { SET_BIT(TIM1->DIER, TIM_DIER_CC1IE);//разрешить по захвату CLEAR_BIT(TIM1->DIER, TIM_DIER_UIE);//запретить прерывание Update HAL_GPIO_TogglePin(GPIOD, LED2_Pin); tim_icc_dma_reload(DMA2_Stream6, 0, Nec._len_pulse_raw, 255);//перезаряжаем DMA cnt_int=0; NEC_decode_callback(&Nec);//Вызов декодера (Неоптимально!!!) } else cnt_int++; }
Теперь стоит поговорить о настройке DMA и таймера. У STM32F4 есть некоторые комбинации потока и канала DMA в которых он может забирать значения сразу нескольких регистров захвата TIM1. Я взял Stream 6 Channel 0. Исходя из таблицы из раздела описания DMA он нам точно подойдет.
DMA настраивается обычным образом: указывается направление чтения из периферии в память, длинна одной посылки полуслово, а вот адрес периферии выбираем DMA address for full transfer (TIMx_DMAR). Как следует из названия там будут находится все необходимые нам данные, но для этого естественно надо произвести кое какие настройки таймера. А именно настроить регистр TIM1&TIM8 DMA control register (TIMx_DCR). Биты 12:8 DBL[4:0]: DMA burst length определяют количество пересылок по DMA через регистр TIM1_DMAR. Биты DBA[4:0]: DMA base address определяют смещение от регистра TIMx_CR1, начиная с которого , будут пересылаться данные в количестве, определенных в DBL.
В нашем примере настройки будут такие DBL=1 это соответствует двум передачам. DBA =13 т.к. смещение в байтах регистра TIM1_CCR1 = 52 (0x34), у нас адресация в 32 битных словах — значит делим на 4, вот отсюда 13. Итак, мы получаем при захвате первым каналом значение TIM1_CCR1, которое поступит в регистр TIM1_DMAR, откуда его заберет DMA, затем по захвату каналом 2 таймера из TIM1_CCR2 . Его значение попадет также в TIM1_DMAR и будет передано по DMA в наш буфер. Но так как в биты DBL у нас определены для двух пересылок, то по следующему захвату в TIM1_DMAR поступит значение из TIM1_CCR1. По сути TIM1_DMAR является неким общим регистром в который в зависимости от настроек мы можем отобразить любой регистр таймера и передать таким образом до 18 значений.
void tim_icc_dma_burst_link(TIM_TypeDef * TIM, DMA_Stream_TypeDef* DMA2_Stream, uint8_t tim_ch, uint8_t dma_ch, uint16_t* rx_buff, uint16_t len) { __HAL_RCC_DMA1_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); DMA2_Stream->CR=dma_ch<<DMA_SxCR_CHSEL_Pos; CLEAR_BIT(DMA2_Stream->CR, DMA_SxCR_EN); SET_BIT(DMA2_Stream->CR, DMA_SxCR_MSIZE_0);//16 бит SET_BIT(DMA2_Stream->CR, DMA_SxCR_PSIZE_0); SET_BIT(DMA2_Stream->CR, DMA_SxCR_MINC); SET_BIT(DMA2_Stream->CR, DMA_SxCR_PSIZE_0); DMA2_Stream->NDTR=len; DMA2_Stream->M0AR=(uint32_t)rx_buff; DMA2_Stream->PAR=(uint32_t)&TIM->DMAR; CLEAR_BIT(TIM->CR1, TIM_CR1_CEN); TIM->DCR|=13<<TIM_DCR_DBA_Pos;//смещнеие на TIM1_CCR1 TIM->DCR|= 1<<TIM_DCR_DBL_Pos;//кол-во пересылок 2 switch(tim_ch) { case 1:SET_BIT(TIM->DIER,TIM_DIER_CC1DE);break; case 2:SET_BIT(TIM->DIER,TIM_DIER_CC2DE);break; case 3:SET_BIT(TIM->DIER,TIM_DIER_CC3DE);break; case 4:SET_BIT(TIM->DIER,TIM_DIER_CC4DE);break; } SET_BIT(TIM->CR1, TIM_CR1_CEN); SET_BIT(DMA2_Stream->CR, DMA_SxCR_EN); }
Заключение
В конечном итоге мы получили следующий алгоритм работы:
- настраиваем DMA, таймер настраиваем на тик 1 мкс и переполнению 20 мс (большим чем период любого импульса данных), разрешаем прерывание по захвату.
- при захвате сигнала в обработчике прерывания TIM1_CC_IRQHandler разрешаем прерывание по переполнению (Update)
- прерывание по переполнению произойдет только тогда, когда импульсы на вход таймера уже не поступают, то есть вся наша посылка за счет DMA уже лежит в буфере и его стоит только декодировать.
К статье прикладываю проект в Keil v.5, проект создан с использованием CubeMX.
Здравствуйте, а не подскажете относительно чего берется «смещение в байтах регистра TIM1_CCR1 = 52 (0x34)»?