ИК датчик движения на STM32

Приветствую, в этой статье вы узнаете: как сделать датчик движения с использованием ИК диода и ИК приемника на STM32 с минимальным использованием ядра (т.е. с максимальной загрузкой периферии) на регистрах, используя таймеры.

В статье максимально подробно рассмотрены вопросы программирования. Материал рассчитан на начальный уровень подготовки, но также и подойдет для более опытных. Часть подробностей скрыта под спойлерами для снижения загруженности статьи.

Оглавление:

Общая картина

Схемы устройств

Работа ИК диода и ИК приемника

Код для передатчика ИК сигнала

Код для приемника ИК сигнала

Полный код

Дополнительно

Заключение

Общая картина

Используются два устройства: передатчик ИК сигнала и приемник ИК сигнала.

zqza70q2gycqy2bwv8en70mhknu.jpeg

Общий принцип работы следующий: передатчик ИК сигнала излучает сигнал инфракрасного диапазона длин волн, а приемник ИК сигнала их принимает. Между этими устройствами образуется «луч», пересечение которого каким-либо объектом фиксируется приемным устройством.

В качестве ИК диода используется TSAL6200, в качестве ИК приемника — TSOP4856.

TSAL6200TSAL6200TSOP4856TSOP4856

Схемы устройств

Опустим цепи питания и другие элементы реальных устройств и рассмотрим только необходимые.

Передатчик ИК сигнала состоит из микроконтроллера STM32L151C8T6, полевого транзистора 2N7002, резистора 1 Ом и ИК диода TSAL6200. Ниже представлена электрическая схема передатчика.

kp_wh5xyorp_pmcmorfwffsc7xy.png

Транзистор необходим для усиления тока, протекающего через ИК диод, так как выходной ток с пина МК ограничен (выходной ток с пина МК STM32L151C8T6 не более 25 мА, максимальный постоянный ток через ИК диод TSAL6200 100 мА).

Выбор транзистора на ваше усмотрение. Здесь выбран транзистор 2N7002, потому что он дешевый и его характеристик достаточно для моего использования. Стоит выбирать транзистор с меньшим пороговым напряжением затвора (Gate Threshold Voltage), так как на затвор вы сможете подать напряжение не более напряжения питания без дополнительных цепей, в нашем случае 3.3 В.

Точный расчет величины сопротивления резистора является трудным, конечно, существуют специальные формулы, но я предлагаю подобрать резистор опытным путем. Так в моем случае используется резистор 1 Ом, амплитуда тока цепи составляет 20 мА, дальность связи при таком токе достигает 14 м в зависимости от конфигурации передаваемого сообщения (количество импульсов в пачке, скважность, об этом ниже). Если вам требуется большая дальность, следует или подобрать резистор поменьше, или увеличить напряжение питания для диода, или выбрать транзистор с меньшим пороговым напряжением и меньшим падением напряжения на сток-истоке.

Приемник ИК сигнала состоит из микроконтроллера STM32L151C8T6, ИК приемника TSOP4856, резистора 100 Ом и конденсатора 0.1 мкФ. Ниже представлена электрическая схема приемника.

844d4885db5bf0c164c3f7b366022605.png

Работа ИК диода и ИК приемника

При протекании тока через диод TSAL6200 излучается ИК сигнал с длиной волны 940 нм. Это излучение необходимо промодулировать меандром с частотой 56 кГц (приемник настроен на данную частоту, см. даташит). Передавать следует пачки таких импульсов с определенной скважностью.

Эти пачки принимается устройством TSOP4856. Пока принимается одна пачка, на выходном выводе приемника низкое значение напряжения, как только на входе нет импульсов, приемник поднимает выходной вывод к напряжению питания. Ниже на картинках представлены временные диаграммы данного процесса. На первой картинке показана работа приемника во время приема одной пачки. На второй картинке показана работа приемника при приеме множества пачек.

e81a4098983dace13c052279b1134b31jtkihcn83c4o8ated2lzvmt73_m.png

Остается только запрограммировать работу передатчика и приемника.

Код передатчика ИК сигнала

Будем использовать таймеры для формирования пачек импульсов с выводом сигнала на конкретный пин. Таким образом, ядро МК только инициализирует эти таймеры, а дальше может заниматься какой-нибудь другой работой.

Выбор пина должен быть обоснован. Необходимо выбирать такой пин, к которому может быть подключен какой-либо канал (кроме четвертого) любого General-purpose таймера. В нашем случае выбран пин PB6, к нему подключается первый канал TIM4. Приемник же будет подключен к пину PB7, второй канал TIM4.

2cku6c5nkawdyrmxv8xf5wwzysw.png

Будем использовать два таймера: TIM4 будет генерировать меандр с частотой 56 кГц, TIM2 будет управлять таймером TIM4, т.е. создавать пачки из меандра. TIM2 будет работать в режиме Master, TIM4 — Slave. Почему именно таймер TIM2? Он выбран исходя из таблицы даташита подключений таймеров друг к другу.

xj908rpqj44vxuporqp8pwi-uwy.png

Приступим к написанию функции инициализации таймеров Tim_Init_Transmitter (). Код написан для пустого проекта. Сначала объявляем функцию, в теле main её инициализируем, а после мэйна прописываем уже саму функцию.

#include "main.h"

void Timer_Init_Transmitter(void);

int main(void)
{
   RCC->ICSCR |= RCC_ICSCR_MSIRANGE_6;   // MSI 4.194 MHz enable

   Timer_Init_Transmitter();

   while(1)
   {

   }
}

void Timer_Init_Transmitter(void)
{

}

RCC->ICSCR |= RCCICSCRMSIRANGE_6 — эта строчка просто устанавливает частоту 4.194 МГц МК. У вас может быть любая другая частота.

Настраиваем пин. Включаем тактирование, включаем альтернативную функцию, выбираем скорость пина повыше (это не является необходимым), выбираем какую именно альтернативную функцию хотим подключить к пину. Под спойлерами описано все подробно.

Подробнее про включение тактирования

В даташите находим регистр RCC_AHBENR. Нужно прописать »1» в поле GPIOBEN.

nv1-udiauudwqobuisrgioidtkq.png

Для этого выбираем в библиотеке CMSIS строчку RCC_AHBENR_GPIOBEN, которая ставит данный бит в единицу.

7lhueqvi03tr_zilts9fcaya-08.png

И изменяем регистр AHBENR следующим образом:

RCC->AHBENR |= RCC_AHBENR_GPIOBEN; //GPIO port B clock enable

Подробнее про включение альтернативной функции

В даташите находим регистр GPIOx_MODER. Нужно прописать »10» в поле MODER6 (для пина PB6).

uvlvdlduxkhnxo3bpzwg7k0kix4.png

Для этого выбираем в библиотеке CMSIS строчку GPIO_MODER_MODER6_1, которая ставит второй бит в единицу.

chn1igbm6thuwuh8cobgd7duxpi.png

В дальнейшем все такие обозначения будут браться аналогично.

И изменяем регистр MODER следующим образом:

GPIOB->MODER |= GPIO_MODER_MODER6_1;   //Alternative function mode enable

Включение высокой скорости для пина делается аналогично включению альтернативной функции.

Подробнее про выбор альтернативной функции

Обозначение альтернативных функций представлено на картинке ниже.

mauwftcdsgjqsdpdqcxr3phui0s.png

Нам нужна альтернативная функция для TIM4 — это AF2.

Стоит отметить, что существует два регистра для выбора альтернативных функций: нижний GPIOx_AFRL для пинов с номерами от 0 до 7 и верхний GPIOx_AFRH для пинов с номерами от 8 до 15. Однако, в библиотеке CMSIS определен сдвоенный регистр AFR[2], прописав в скобки »0», мы выбираем нижний регистр, прописав »1», выбираем верхний регистр.

В даташите находим регистр GPIOx_AFRL.

nmt6hyfhpjyzszi163v2xwpi4pk.png

Нам нужно прописать »0010» в поле AFRL6, для этого нам нужно число в шестнадцатеричной форме 0×2000000,»2» седьмая справа для пина PB6, потому что первые шесть цифр для пинов с номерами от 0 до 5.

И изменяем регистр AFRL следующим образом:

GPIOB->AFR[0] |= 0x2000000;     //Pin PB6 TIM4 alternative function AF2 enable

Получили следующий код для настройки пина внутри нашей функции:

void Timer_Init_Transmitter (void)
{
   //Settings for GPIO PB6
   RCC->AHBENR |= RCC_AHBENR_GPIOBEN;             //GPIO port B clock enable
   GPIOB->MODER |= GPIO_MODER_MODER6_1;           //Alternative function mode enable
   GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6_1;	  //High speed
   GPIOB->AFR[0] |= 0x2000000;                    //Pin PB6 TIM4 alternative function AF2 enable
}

Теперь перейдем к настройке TIM4.

Нам нужно выбрать прескейлер PSC (делитель частоты), рассчитать значения для регистров CCR1 (длительность импульса) и ARR (период импульсов), выбрать режим работы таймера ШИМ, выбрать входной управляющий сигнал от таймера TIM2, выбрать режим для Slave и подать выходной сигнал таймера на пин PB6.

Включение тактирования осуществляется аналогично включению тактирования GPIO.

Подробнее про выбор прескейлера PSC и расчет CCR1 и ARR

Выберем значение преселлектора входной частоты равное 0, в этом случае частота тактирования таймера будет равна частоте МК.

lbnxpaf725wecegb4p0kxypl-r8.png

И изменяем регистр PSC следующим образом:

TIM4->PSC = 0; //Prescaler value

Перейдем к расчету ARR.

w8pur0e0uhklpp4xfbmex_mengq.png

Для расчета ARR нам нужно поделить тактовую частоту таймера 4.194 МГц (у вас своя) на 56 кГц. Получаем 74,89, округляем до целого. Я округлил до 75. И вписываем в регистр ARR:

TIM4->ARR = 75 //Auto-reload value

Осталось рассчитать CCR1.

oaalfkavhugqusuovtg09sjiba4.png

Так как у нас простой меандр, то в регистр CCR1 при режиме ШИМ нужно указать половину от значения ARR:

TIM4->CCR1 = 37;   //Capture/Compare 1 value

Подробнее про выбор режима работы ШИМ таймера

Так как у нас первый канал, то используем регистр CCMR1. Нас интересует в этом регистре поле OC1M. Режим ШИМ позволяет настраивать длительность импульса при фиксированном периоде.

4kv3rmrgstsciqlhl34ctxps7-y.png5j3f48d3tdjtjprjwvdwlbz6g9u.png

Выберем PMW mode 1, вписав две единицы во второй и третий биты, т.е.»110»:

TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1

Подробнее про настройку подачи выходного сигнала на пин PB6

В регистр CCER в поле CC1E нужно вписать »1», включив подачу выходного сигнала на пин.

1e3mzrs_arnvzj1v0wgmonr7djq.png

Вписываем следующую строчку:

TIM4->CCER |= TIM_CCER_CC1E; //OC3 signal is output on the corresponding pin

Подробнее про выбор управляющего сигнала и режима Slave

xj908rpqj44vxuporqp8pwi-uwy.png

Для управления TIM4 таймером TIM2 нужно выбрать входной сигнал ITR1. Для этого нужно вписать в поле TS регистра TIMx_SMCR »001». Также выберем режим для Slave, вписав »101» в поле SMS. В этом режиме пока входной сигнал ITR1 будет высоким, то TIM4 будет работать и выдавать меандр на пин, как только ITR1 станет низким, TIM4 выключается.

49sxkei5hpdffdxp9fkkmet_3t8.pngl0lrmqabop06rqrg8ysucrdaqm4.png

Для этого вписываем:

TIM4->SMCR |= TIM_SMCR_TS_0;    //choosing ITR1

TIM4->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_2; //Gated Mode

Добавим к уже существующему коду, и получим:

void Timer_Init_Transmitter (void)
{
   //Settings for GPIO PB6
   RCC->AHBENR |= RCC_AHBENR_GPIOBEN;             //GPIO port B clock enable
   GPIOB->MODER |= GPIO_MODER_MODER6_1;           //Alternative function mode enable
   GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6_1;	  //High speed
   GPIOB->AFR[0] |= 0x2000000;                    //Pin PB6 TIM4 alternative function AF2 enable

   //Settings for TIM4 - Slave
   RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;            //TIM4 clock enable
   TIM4->PSC = 0;                                 //Prescaler value
   TIM4->ARR = 75;                                //Auto-reload value
   TIM4->CCR1 = 37;                               //Capture/Compare 1 value
   TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1    enable
   TIM4->CCER |= TIM_CCER_CC1E;                   //OC3 signal is output on the corresponding output pin
   TIM4->SMCR |= TIM_SMCR_TS_0;                   //choosing ITR1
   TIM4->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_2; //Gated Mode
   TIM4->CR1 |= TIM_CR1_CEN;                      //TIM4 enable
}

Включение TIM4 можно производить, а можно и нет, так как управляющий сигнал с TIM2, всё равно включит его.

Теперь перейдем к настройке TIM2.

Необходимо подать тактирование на таймер, рассчитать PSC, CCR1 и ARR, выбрать режим работы таймера ШИМ, выбрать какой сигнал будет выходным (входным управляющим для TIM4) и включить таймер.

Включение тактирования аналогично, как и для таймера TIM4.

RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;  //TIM2 clock enable

Подробнее про расчет PSC, CCR1 и ARR для TIM2

Так как по даташиту на диод в пачке следует использовать не менее 10 импульсов (на самом деле можно и меньше, но качество работы может быть не очень хорошим), то выберем тактирование таймера TIM2 в десять раз меньше, чем TIM4.

lbnxpaf725wecegb4p0kxypl-r8.png

Как видим в формуле для расчета частоты тактирования таймера в знаменателе уже есть »+1», т.е. нам нужно записать в регистр PSC »9», чтобы получить частоту в 10 раз меньшую, чем частоту МК.

TIM2->PSC = 9;    //Prescaler value

Теперь рассчитаем значение для CCR1: отправлять будем 10 импульсов, следовательно, нам нужно взять значение ARR для TIM4 (период одного импульса, это 75) и умножить на 10, т.е. получаем 750, однако, у нас есть прескейлер, который делит частоту на 10, т.е. нам нужно поделить 750 на 10, в итоге получаем снова 75 (как и в TIM4, но уже с другим прескейлером). Запишем это значение в регистр CCR1 таймера TIM2.

TIM2->CCR1 = 75;  //Capture/Compare 1 value

Перейдём к расчету ARR: тут всё просто, допустим, хотим «стрелять» пачками со скважностью 11.2, при этом период излучения пачек будет около 2 мс (из расчета, что 1 мс это 4194000/1000 = 4194 тика, и умножаем на 2, получаем округлённо 8400, а с прескейлером 10, получаем 840 тиков), умножаем длительность пачки 75 на 11.2 и получаем 840, как видите, значения совпадают. Запишем это в регистр ARR.

TIM2->ARR = 840;   //Auto-reload value

По даташиту скважность должна быть не менее 2, но при таких значениях стабильность работы будет хуже, и такая передача данных годится только для коротких посылок. Для длинных посылок и больших пачек скважность должна быть больше 4.

Режим работы таймера TIM2 точно такой же, как и у TIM4 — ШИМ.

TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1 enable

Подробнее про выбор выходного сигнала TIM2 (управляющего для TIM4)

Найдем регистр TIMx_CR2.

fnnbqev6tb-ar7xfdqmc5hhhkmg.pngiodyzeflibfrwkbj2ksuqhjr7gk.png

Нам нужно подать сигнал с первого канала (мы же используем CCR1), на выход с таймера TIM2. Этим сигналом является OC1REF. Ниже на картинке можно найти комбинацию битов для этого сигнала — »100».

В поле MMS необходимо вписать »1» в третий бит.

TIM2->CR2 |= TIM_CR2_MMS_2;    //OC1REF signal is used as trigger output (TRGO)

Теперь осталось включить TIM2, записав следующую строчку:

TIM2->CR1 |= TIM_CR1_CEN;    //TIM2 enable

В таком случае при включении МК, диод начнет излучать. Если вы хотите включать диод в определенное время, тогда вам в нашей функции необходимо убрать две строчки с включением таймеров, и просто в нужный момент включить TIM2.

Добавим эти строчки к предыдущим и получим:

void Timer_Init_Transmitter (void)
{
   //Settings for GPIO PB6
   RCC->AHBENR |= RCC_AHBENR_GPIOBEN;             //GPIO port B clock enable
   GPIOB->MODER |= GPIO_MODER_MODER6_1;           //Alternative function mode enable
   GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6_1;	  //High speed
   GPIOB->AFR[0] |= 0x2000000;                    //Pin PB6 TIM4 alternative function AF2 enable

   //Settings for TIM4 - Slave
   RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;            //TIM4 clock enable
   TIM4->PSC = 0;                                 //Prescaler value
   TIM4->ARR = 75;                                //Auto-reload value
   TIM4->CCR1 = 37;                               //Capture/Compare 1 value
   TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1 enable
   TIM4->CCER |= TIM_CCER_CC1E;                   //OC3 signal is output on the corresponding output pin
   TIM4->SMCR &= ~TIM_SMCR_TS;                    //clear bits
   TIM4->SMCR |= TIM_SMCR_TS_0;                   //choosing ITR1
   TIM4->SMCR &= ~TIM_SMCR_SMS;                   //clear bits
   TIM4->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_2; //Gated Mode
   TIM4->CR1 |= TIM_CR1_CEN;                      //TIM4 enable

   //Settings for TIM2 - Master
   RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;            //TIM2 clock enable
   TIM2->PSC = 9;                                 //Prescaler value
   TIM2->ARR = 840;                               //Auto-reload value
   TIM2->CCR1 = 75;                               //Capture/Compare 1 value
   TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1 enable
   TIM2->CR2 |= TIM_CR2_MMS_2;                    //OC1REF signal is used as trigger output (TRGO)
   TIM2->CR1 |= TIM_CR1_CEN;                      //TIM2 enable
}

Теперь у нас ИК диод будет излучать пачки по 10 импульсов, у которых частота 56 кГц, со скважностью 11.2, т.е. с периодом следования пачек в 2 мс. Заметим, что момент пересечения каким-либо объектом внутри периода пачек не определен, т.е. мы можем судить о пересечении ИК луча только по отсутствию следующей пачки. Таким образом, погрешность измерения момента времени пересечения луча составляем 2 мс.

Код приемника ИК сигнала

Для приема ИК сигнала снова будем использовать таймеры, а точнее всего один. В микроконтроллерах STM32 таймеры могут быть не только Master или Slave, а могут быть Master/Slave, т.е. они могут управлять сами собой.

В разделе статьи «Код передатчика ИК сигнала» мы уже выбрали пин PB7 для приемника ИК сигнала. Схему подключения см. выше. К этому пину подключается второй канал TIM4.

Ниже на картинке представлена схема устройства таймера.

x3lqdqg4idehjsvlongscluynde.png

Из схемы видно, что у каждого канала есть вход и выход, у таймера есть входной управляющий сигнал TRGI, также показаны тактирование и устройство Trigger controller.

Принцип работы для нашего проекта заключается в следующем: пусть наш таймер будет просто считать время, допустим, равное 5 периодам излучаемых пачек ИК сигнала, следовательно, в ARR будет число, равное 5 периодам пачек, т.е. 840×5 = 4200. Настроим таймер так, чтобы при приеме каждой пачки таймер перезагружался и начинал считать заново. При достижении таймером числа в регистре ARR таймер выдает прерывание, которое означает, что на вход уже как 5 периодов не приходит пачка, следовательно, луч пересекается каким-то объектом. Вот и вся работа таймера. Осталось настроить TIM4.

Создадим новый проект и функцию инициализации таймера:

#iclude "main.h"

void Timer_Init_Receiver(void);

int main(void)
{
   RCC->ICSCR |= RCC_ICSCR_MSIRANGE_6;    // MSI 4.194 MHz enable

   Timer_Init_Receiver();

   while(1)
   {

   }
}

void Timer_Init_Receiver(void)
{

}

Необходимо настроить пин PB7: включить тактирование порта B, настроить альтернативную функцию пина, настроить скорость и выбрать какую конкретную альтернативную функцию подключить к пину. Всё, как и в предыдущем случае.

void Timer_Init_Receiver(void)
{

//Settings for GPIO PB7
	RCC->AHBENR |= RCC_AHBENR_GPIOBEN;          // GPIO port B clock enable
	GPIOB->MODER |= GPIO_MODER_MODER7_1;        // Alternative function mode enable
	GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7_1;	// High speed
	GPIOB->PUPDR |= GPIO_PUPDR_PUPDR7_0;        // pull-up PB7
	GPIOB->AFR[0] |= 0x20000000;                // Pin PB7 TIM4 alternative function AF2 enable

}

Все настройки аналогичны, как и для передатчика, только альтернативная функция немного отличается,»2» нужно вписать уже на восьмое место справа. Еще необходимо подтянуть пин к плюсу прописав строчку:

GPIOB->PUPDR |= GPIO_PUPDR_PUPDR7_0;

Теперь настроим TIM4.

Включим тактирование таймера. Так как у нас второй канал, то будем использовать CCR2. Прескейлер приравняем к 9, чтобы частота тактирования таймера была как у TIM2 передатчика. Для приемника можно приравнять CCR2 и ARR. Как уже рассчитали ранее, вписываем в эти регистры 4200.

RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;   // TIM4 clock enable

TIM4->PSC = 9;   // Prescaler value

TIM4->ARR = 4200;   // Auto-reload value

TIM4->CCR2 = 4200;    // Capture/Compare 2 value

Далее настроим режим работы таймера.

Нам нужно, чтобы он просто считал, и всё. Для этого в даташите находим TIMx_CCMR1 и в поле OC2M вписываем »000», что соответствует Frozen mode. Изменим регистр, обнулив все биты:

TIM4->CCMR1 &= ~TIM_CCMR1_OC2M;   // Frozen mode enable

Настроим канал на выход, обнулив биты поля CC2S (более подробно написано в даташите), впишем нули в эти биты:

TIM4->CCMR1 &= ~TIM_CCMR1_CC2S;   // Output mode

Теперь обратимся к картинке со схемой устройства таймера (см. выше). Нам необходимо, чтобы входной сигнал с пина PB7 был управляющим для таймера TIM4, таким сигналом является TI2FP2. Проследите путь этого сигнала от TIMx_CH2 до TRGI. Чтобы выбрать данный сигнал в качестве управляющего, необходимо в регистр TIMx_SMCR в поле TS вписать »110». Сразу же выберем режим для Slave: Reset mode, вписав »100» в поле SMS. Для этого пропишем строчки:

TIM4->SMCR |= TIM_SMCR_TS_1 | TIM_SMCR_TS_2;   // Choosing TI2FP2

TIM4->SMCR |= TIM_SMCR_SMS_2;    // Reset mode

Теперь нам нужно определиться, что будет являться триггером обновления таймера: передний или задний фронт входных импульсов (на самом деле не особо важно, но лучше выбрать по переднему фронту, в нашем случае, это фронт спада напряжения). Для этого нужно использовать пару битовых полей регистра CCER: CC2P и CC2NP, они работают в паре, более подробно написано о них в даташите.

1e3mzrs_arnvzj1v0wgmonr7djq.png

Для нашего случая вписываем »1» в CC2P и »0» в CC2NP. Делаем это так:

TIM4->CCER &= ~TIM_CCER_CC2NP;  // This bit is used in conjunction with CC2P.

TIM4->CCER |= TIM_CCER_CC2P;    // Inverted/falling edge

Разрешим прерывание по переполнению. Для этого в регистре TIMx_DIER в поле CC2IE впишем »1».

bwzykzisr2psfz_y56z0hxlie4q.png

Для этого впишем строчку:

TIM4->DIER |= TIM_DIER_CC2IE;  // Capture/Compare 2 interrupt enable

Включаем таймер строчкой:

TIM4->CR1 |= TIM_CR1_CEN;   // TIM4 enable

Не забываем разрешить глобальное прерывание:

NVIC_EnableIRQ(TIM4_IRQn);   // TIM4 global Interrupt enable

Наша функция инициализации таймера выглядит следующим образом:

void Timer_Init_Receiver(void)
{

//Settings for GPIO PB7
	RCC->AHBENR |= RCC_AHBENR_GPIOBEN;           // GPIO port B clock enable
	GPIOB->MODER |= GPIO_MODER_MODER7_1;         // Alternative function mode enable
	GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7_1;	 // High speed
	GPIOB->PUPDR |= GPIO_PUPDR_PUPDR7_0;         // pull-up PB7
	GPIOB->AFR[0] |= 0x20000000;                 // Pin PB7 TIM4 alternative function AF2 enable


	//Settings for TIM4
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;          // TIM4 clock enable
TIM4->PSC = 9;                                 // Prescaler value
TIM4->ARR = 4200;                              // Auto-reload value
TIM4->CCR2 = 4200;                             // Capture/Compare 2 value
	
	TIM4->CCMR1 &= ~TIM_CCMR1_OC2M;              // Frozen mode enable
	TIM4->CCMR1 &= ~TIM_CCMR1_CC2S;              // Output mode
	TIM4->CCER &= ~TIM_CCER_CC2NP;               // This bit is used in conjunction with CC2P.
	TIM4->CCER |= TIM_CCER_CC2P;                 // Inverted/falling edge
	TIM4->SMCR |= TIM_SMCR_TS_1 | TIM_SMCR_TS_2; // Choosing TI2FP2
	TIM4->SMCR |= TIM_SMCR_SMS_2;                // Reset mode
	TIM4->DIER |= TIM_DIER_CC2IE;                // Capture/Compare 2 interrupt enable
	TIM4->CR1 |= TIM_CR1_CEN;                    // TIM4 enable
	NVIC_EnableIRQ(TIM4_IRQn);                   // TIM4 global Interrupt enable
}

Теперь напишем обработчик прерываний:

Первое что нужно сделать, это снять флаг в регистре статуса TIMx_SR:

TIM4->SR &= ~TIM_SR_CC2IF;

Далее можем делать, что хотим. Хоть ставить какой-нибудь флаг, хоть зажигать диод. Допустим будем зажигать светодиод. Настроим пин PB15 на выход, прописав в мэйне:

GPIOB->MODER |= GPIO_MODER_MODER15_0;  // PB15 output mode

Наш обработчик прерываний будет выглядеть следующим образом:

void TIM4_IRQHandler(void)
{
	TIM4->SR &= ~TIM_SR_CC2IF;
	GPIOB->ODR |= GPIO_ODR_ODR_15;  // Led red on
}

Готово! Теперь при пересечении ИК луча каким-либо объектом, на приемнике будет зажигаться светодиод.

Полный код

Код передатчика ИК сигнала

void Timer_Init_Transmitter(void);


int main(void)
{
   RCC->ICSCR |= RCC_ICSCR_MSIRANGE_6;             // MSI 4.194 MHz enable

   Timer_Init_Transmitter();

   while(1)
   {

   }
}

void Timer_Init_Transmitter(void)
{
  //Settings for GPIO PB6
   RCC->AHBENR |= RCC_AHBENR_GPIOBEN;             //GPIO port B clock enable
   GPIOB->MODER |= GPIO_MODER_MODER6_1;           //Alternative function mode enable
   GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6_1;	  //High speed
   GPIOB->AFR[0] |= 0x2000000;                    //Pin PB6 TIM4 alternative function AF2 enable

   //Settings for TIM4 - Slave
   RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;            //TIM4 clock enable
   TIM4->PSC = 0;                                 //Prescaler value
   TIM4->ARR = 75;                                //Auto-reload value
   TIM4->CCR1 = 37;                               //Capture/Compare 1 value
   TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1 enable
   TIM4->CCER |= TIM_CCER_CC1E;                   //OC3 signal is output on the corresponding output pin
   TIM4->SMCR &= ~TIM_SMCR_TS;                    //clear bits
   TIM4->SMCR |= TIM_SMCR_TS_0;                   //choosing ITR1
   TIM4->SMCR &= ~TIM_SMCR_SMS;                   //clear bits
   TIM4->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_2; //Gated Mode
   TIM4->CR1 |= TIM_CR1_CEN;                      //TIM4 enable

   //Settings for TIM2 - Master
   RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;            //TIM2 clock enable
   TIM2->PSC = 9;                                 //Prescaler value
   TIM2->ARR = 840;                               //Auto-reload value
   TIM2->CCR1 = 75;                               //Capture/Compare 1 value
   TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1 enable
   TIM2->CR2 |= TIM_CR2_MMS_2;                    //OC1REF signal is used as trigger output (TRGO)
   TIM2->CR1 |= TIM_CR1_CEN;                      //TIM2 enable
}

Код приемника ИК сигнала

#include "main.h”

void Timer_Init_Receiver(void);

int main(void)
{
   RCC->ICSCR |= RCC_ICSCR_MSIRANGE_6;         // MSI 4.194 MHz enable

  GPIOB->MODER |= GPIO_MODER_MODER15_0;        // PB15 output mode

   Timer_Init_Receiver();

   while(1)
   {

   }
}

void Timer_Init_Receiver(void)
{
//Settings for GPIO PB7
	RCC->AHBENR |= RCC_AHBENR_GPIOBEN;           // GPIO port B clock enable
	GPIOB->MODER |= GPIO_MODER_MODER7_1;         // Alternative function mode enable
	GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7_1;	 // High speed
	GPIOB->PUPDR |= GPIO_PUPDR_PUPDR7_0;         // pull-up PB7
	GPIOB->AFR[0] |= 0x20000000;                 // Pin PB7 TIM4 alternative function AF2 enable


	//Settings for TIM4
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;          // TIM4 clock enable
TIM4->PSC = 9;                                 // Prescaler value
TIM4->ARR = 4200;                              // Auto-reload value
TIM4->CCR2 = 4200;                             // Capture/Compare 2 value
	
	TIM4->CCMR1 &= ~TIM_CCMR1_OC2M;              // Frozen mode enable
	TIM4->CCMR1 &= ~TIM_CCMR1_CC2S;              // Output mode
	TIM4->CCER &= ~TIM_CCER_CC2NP;               // This bit is used in conjunction with CC2P.
	TIM4->CCER |= TIM_CCER_CC2P;                 // Inverted/falling edge
	TIM4->SMCR |= TIM_SMCR_TS_1 | TIM_SMCR_TS_2; // Choosing TI2FP2
	TIM4->SMCR |= TIM_SMCR_SMS_2;                // Reset mode
	TIM4->DIER |= TIM_DIER_CC2IE;                // Capture/Compare 2 interrupt enable
	TIM4->CR1 |= TIM_CR1_CEN;                    // TIM4 enable
	NVIC_EnableIRQ(TIM4_IRQn);                   // TIM4 global Interrupt enable

}

void TIM4_IRQHandler(void)
{
	TIM4->SR &= ~TIM_SR_CC2IF;
	GPIOB->ODR |= GPIO_ODR_ODR_15;               // Led red on
}

Дополнительно

Если хотите, чтобы диод на приемнике светился, когда ИК сигнал принимается, и не светился, когда на входе приемника нет сигнала, то нужно немного изменить код для приемника.

Нужно добавить глобальную переменную, например, написав между «инклюдами» и мэйном такую строчку:

int StatusDiode = 0;   // 0 - diode is off, 1 - diode is on

Эта переменная необходима для запоминания статуса светодиода: выключен или включен.

Далее в функции инициализации таймера нужно изменить строчку с разрешением прерывания по переполнению на разрешение прерывания по триггеру, такой строчкой:

TIM4->DIER |= TIM_DIER_TIE;  // Trigger interrupt enable

И последнее: изменим обработчик прерываний.

Соответственно, если диод был выключен и у нас сработало прерывание по триггеру, то снимаем соответствующий флаг, включаем диод, выключаем разрешение прерывания по триггеру, включаем разрешение прерывания по переполнению, обнуляем счетчик (можно не делать этот пункт) и изменяем статус диода.

Если диод был включен, делаем все наоборот: снимаем соответствующий флаг, выключаем диод, выключаем разрешение прерывания по переполнению, включаем разрешение прерывания по триггеру и изменяем статус диода.

Стоит помнить, что счетчик не останавливается, если не выключить таймер, однако, при прерывании по переполнению он перезагружается (обновляется регистр CNT), а при прерывании по триггеру обновления не происходит, поэтому мы его и обнуляем.

Получим такой обработчик прерываний:

void TIM4_IRQHandler(void)
{
	if (StatusDiode == 0)
	{
	    TIM4->SR &= ~TIM_SR_TIF;
	    GPIOB->ODR |= GPIO_ODR_ODR_15;         // Led red on
	    TIM4->DIER &= ~TIM_DIER_TIE;           // Trigger interrupt disable
	    TIM4->DIER |= TIM_DIER_CC2IE;          // Capture/Compare 2 interrupt enable
	    TIM4->CNT = 0;
	    StatusDiode = 1;
	}
	else
	{
	    TIM4->SR &= ~TIM_SR_CC2IF;
	    GPIOB->ODR &= ~GPIO_ODR_ODR_15;        // Led red off
	    TIM4->DIER &= ~TIM_DIER_CC2IE;         // Capture/Compare 2 interrupt disable
	    TIM4->DIER |= TIM_DIER_TIE;            // Trigger interrupt enable
	    StatusDiode = 0;
	}
}

Полный код для приемника ИК сигнала

#include "main.h”

void Timer_Init_Receiver(void);

int main(void)
{
   RCC->ICSCR |= RCC_ICSCR_MSIRANGE_6;      // MSI 4.194 MHz enable

  GPIOB->MODER |= GPIO_MODER_MODER15_0;     // PB15 output mode

   Timer_Init_Receiver();

   while(1)
   {

   }
}

void Timer_Init_Receiver(void)
{
//Settings for GPIO PB7
	RCC->AHBENR |= RCC_AHBENR_GPIOBEN;           // GPIO port B clock enable
	GPIOB->MODER |= GPIO_MODER_MODER7_1;         // Alternative function mode enable
	GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7_1;	 // High speed
	GPIOB->PUPDR |= GPIO_PUPDR_PUPDR7_0;         // pull-up PB7
	GPIOB->AFR[0] |= 0x20000000;                 // Pin PB7 TIM4 alternative function AF2 enable


	//Settings for TIM4
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;          // TIM4 clock enable
TIM4->PSC = 9;                                 // Prescaler value
TIM4->ARR = 4200;                              // Auto-reload value
TIM4->CCR2 = 4200;                             // Capture/Compare 2 value
	
	TIM4->CCMR1 &= ~TIM_CCMR1_OC2M;              // Frozen mode enable
	TIM4->CCMR1 &= ~TIM_CCMR1_CC2S;              // Output mode
	TIM4->CCER &= ~TIM_CCER_CC2NP;               // This bit is used in conjunction with CC2P.
	TIM4->CCER |= TIM_CCER_CC2P;                 // Inverted/falling edge
	TIM4->SMCR |= TIM_SMCR_TS_1 | TIM_SMCR_TS_2; // Choosing TI2FP2
	TIM4->SMCR |= TIM_SMCR_SMS_2;                // Reset mode
	TIM4->DIER |= TIM_DIER_TIE;                  // Trigger interrupt enable
	TIM4->CR1 |= TIM_CR1_CEN;                    // TIM4 enable
	NVIC_EnableIRQ(TIM4_IRQn);                   // TIM4 global Interrupt enable

}

void TIM4_IRQHandler(void)
{
	if (StatusDiode == 0)
	{
	    TIM4->SR &= ~TIM_SR_TIF;
	    GPIOB->ODR |= GPIO_ODR_ODR_15;           // Led red on
	    TIM4->DIER &= ~TIM_DIER_TIE;             // Trigger interrupt disable
	    TIM4->DIER |= TIM_DIER_CC2IE;            // Capture/Compare 2 interrupt enable
	    TIM4->CNT = 0;
	    StatusDiode = 1;
	}
	else
	{
	    TIM4->SR &= ~TIM_SR_CC2IF;
	    GPIOB->ODR &= ~GPIO_ODR_ODR_15;          // Led red off
	    TIM4->DIER &= ~TIM_DIER_CC2IE;           // Capture/Compare 2 interrupt disable
	    TIM4->DIER |= TIM_DIER_TIE;              // Trigger interrupt enable
	    StatusDiode = 0;
	}
}

Заключение

Таким образом, разработали датчик движения, основанный на работе пересечения каким-либо объектом ИК луча. Система состоит из двух устройств, имеет минимум элементов, проста в реализации, имеет низкую стоимость и высокое быстродействие. При этом ядро МК свободно и может использоваться для других нужд.

© Habrahabr.ru