Приветствую, в этой статье вы узнаете: как сделать датчик движения с использованием ИК диода и ИК приемника на STM32 с минимальным использованием ядра (т.е. с максимальной загрузкой периферии) на регистрах, используя таймеры.
В статье максимально подробно рассмотрены вопросы программирования. Материал рассчитан на начальный уровень подготовки, но также и подойдет для более опытных. Часть подробностей скрыта под спойлерами для снижения загруженности статьи.
Оглавление:
Работа ИК диода и ИК приемника
Код для передатчика ИК сигнала
Общая картина
Используются два устройства: передатчик ИК сигнала и приемник ИК сигнала.
Общий принцип работы следующий: передатчик ИК сигнала излучает сигнал инфракрасного диапазона длин волн, а приемник ИК сигнала их принимает. Между этими устройствами образуется «луч», пересечение которого каким-либо объектом фиксируется приемным устройством.
В качестве ИК диода используется TSAL6200, в качестве ИК приемника – TSOP4856.
Схемы устройств
Опустим цепи питания и другие элементы реальных устройств и рассмотрим только необходимые.
Передатчик ИК сигнала состоит из микроконтроллера STM32L151C8T6, полевого транзистора 2N7002, резистора 1 Ом и ИК диода TSAL6200. Ниже представлена электрическая схема передатчика.
Транзистор необходим для усиления тока, протекающего через ИК диод, так как выходной ток с пина МК ограничен (выходной ток с пина МК STM32L151C8T6 не более 25 мА, максимальный постоянный ток через ИК диод TSAL6200 100 мА).
Выбор транзистора на ваше усмотрение. Здесь выбран транзистор 2N7002, потому что он дешевый и его характеристик достаточно для моего использования. Стоит выбирать транзистор с меньшим пороговым напряжением затвора (Gate Threshold Voltage), так как на затвор вы сможете подать напряжение не более напряжения питания без дополнительных цепей, в нашем случае 3.3 В.
Точный расчет величины сопротивления резистора является трудным, конечно, существуют специальные формулы, но я предлагаю подобрать резистор опытным путем. Так в моем случае используется резистор 1 Ом, амплитуда тока цепи составляет 20 мА, дальность связи при таком токе достигает 14 м в зависимости от конфигурации передаваемого сообщения (количество импульсов в пачке, скважность, об этом ниже). Если вам требуется большая дальность, следует или подобрать резистор поменьше, или увеличить напряжение питания для диода, или выбрать транзистор с меньшим пороговым напряжением и меньшим падением напряжения на сток-истоке.
Приемник ИК сигнала состоит из микроконтроллера STM32L151C8T6, ИК приемника TSOP4856, резистора 100 Ом и конденсатора 0.1 мкФ. Ниже представлена электрическая схема приемника.
Работа ИК диода и ИК приемника
При протекании тока через диод TSAL6200 излучается ИК сигнал с длиной волны 940 нм. Это излучение необходимо промодулировать меандром с частотой 56 кГц (приемник настроен на данную частоту, см. даташит). Передавать следует пачки таких импульсов с определенной скважностью.
Эти пачки принимается устройством TSOP4856. Пока принимается одна пачка, на выходном выводе приемника низкое значение напряжения, как только на входе нет импульсов, приемник поднимает выходной вывод к напряжению питания. Ниже на картинках представлены временные диаграммы данного процесса. На первой картинке показана работа приемника во время приема одной пачки. На второй картинке показана работа приемника при приеме множества пачек.
Остается только запрограммировать работу передатчика и приемника.
Код передатчика ИК сигнала
Будем использовать таймеры для формирования пачек импульсов с выводом сигнала на конкретный пин. Таким образом, ядро МК только инициализирует эти таймеры, а дальше может заниматься какой-нибудь другой работой.
Выбор пина должен быть обоснован. Необходимо выбирать такой пин, к которому может быть подключен какой-либо канал (кроме четвертого) любого General-purpose таймера. В нашем случае выбран пин PB6, к нему подключается первый канал TIM4. Приемник же будет подключен к пину PB7, второй канал TIM4.
Будем использовать два таймера: TIM4 будет генерировать меандр с частотой 56 кГц, TIM2 будет управлять таймером TIM4, т.е. создавать пачки из меандра. TIM2 будет работать в режиме Master, TIM4 – Slave. Почему именно таймер TIM2? Он выбран исходя из таблицы даташита подключений таймеров друг к другу.
Приступим к написанию функции инициализации таймеров 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.
Для этого выбираем в библиотеке CMSIS строчку RCC_AHBENR_GPIOBEN
, которая ставит данный бит в единицу.
И изменяем регистр AHBENR следующим образом:
RCC->AHBENR |= RCC_AHBENR_GPIOBEN; //GPIO port B clock enable
Подробнее про включение альтернативной функции
В даташите находим регистр GPIOx_MODER. Нужно прописать «10» в поле MODER6 (для пина PB6).
Для этого выбираем в библиотеке CMSIS строчку GPIO_MODER_MODER6_1
, которая ставит второй бит в единицу.
В дальнейшем все такие обозначения будут браться аналогично.
И изменяем регистр MODER следующим образом:
GPIOB->MODER |= GPIO_MODER_MODER6_1; //Alternative function mode enable
Включение высокой скорости для пина делается аналогично включению альтернативной функции.
Подробнее про выбор альтернативной функции
Обозначение альтернативных функций представлено на картинке ниже.
Нам нужна альтернативная функция для TIM4 – это AF2.
Стоит отметить, что существует два регистра для выбора альтернативных функций: нижний GPIOx_AFRL для пинов с номерами от 0 до 7 и верхний GPIOx_AFRH для пинов с номерами от 8 до 15. Однако, в библиотеке CMSIS определен сдвоенный регистр AFR[2], прописав в скобки «0», мы выбираем нижний регистр, прописав «1», выбираем верхний регистр.
В даташите находим регистр GPIOx_AFRL.
Нам нужно прописать «0010» в поле AFRL6, для этого нам нужно число в шестнадцатеричной форме 0x2000000, «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, в этом случае частота тактирования таймера будет равна частоте МК.
И изменяем регистр PSC следующим образом:
TIM4->PSC = 0; //Prescaler value
Перейдем к расчету ARR.
Для расчета ARR нам нужно поделить тактовую частоту таймера 4.194 МГц (у вас своя) на 56 кГц. Получаем 74,89, округляем до целого. Я округлил до 75. И вписываем в регистр ARR:
TIM4->ARR = 75 //Auto-reload value
Осталось рассчитать CCR1.
Так как у нас простой меандр, то в регистр CCR1 при режиме ШИМ нужно указать половину от значения ARR:
TIM4->CCR1 = 37; //Capture/Compare 1 value
Подробнее про выбор режима работы ШИМ таймера
Так как у нас первый канал, то используем регистр CCMR1. Нас интересует в этом регистре поле OC1M. Режим ШИМ позволяет настраивать длительность импульса при фиксированном периоде.
Выберем PMW mode 1, вписав две единицы во второй и третий биты, т.е. «110»:
TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; //Output compare PMW mode 1
Подробнее про настройку подачи выходного сигнала на пин PB6
В регистр CCER в поле CC1E нужно вписать «1», включив подачу выходного сигнала на пин.
Вписываем следующую строчку:
TIM4->CCER |= TIM_CCER_CC1E; //OC3 signal is output on the corresponding pin
Подробнее про выбор управляющего сигнала и режима Slave
Для управления TIM4 таймером TIM2 нужно выбрать входной сигнал ITR1. Для этого нужно вписать в поле TS регистра TIMx_SMCR «001». Также выберем режим для Slave, вписав «101» в поле SMS. В этом режиме пока входной сигнал ITR1 будет высоким, то TIM4 будет работать и выдавать меандр на пин, как только ITR1 станет низким, TIM4 выключается.
Для этого вписываем:
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.
Как видим в формуле для расчета частоты тактирования таймера в знаменателе уже есть «+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.
Нам нужно подать сигнал с первого канала (мы же используем 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.
Ниже на картинке представлена схема устройства таймера.
Из схемы видно, что у каждого канала есть вход и выход, у таймера есть входной управляющий сигнал 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, они работают в паре, более подробно написано о них в даташите.
Для нашего случая вписываем «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».
Для этого впишем строчку:
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;
}
}
Заключение
Таким образом, разработали датчик движения, основанный на работе пересечения каким-либо объектом ИК луча. Система состоит из двух устройств, имеет минимум элементов, проста в реализации, имеет низкую стоимость и высокое быстродействие. При этом ядро МК свободно и может использоваться для других нужд.