10. STM32. Програмування STM32F103. TIMER. Захоплення сигналу
Однією з типових задач для мікроконтролера є обробка вхідних сигналів. У STM32 з цією задачею досить вправно справляються таймери загального призначення. Але, перш ніж перейти до розглядання теми захоплення сигналу таймером, спочатку розглянемо ще один приклад, який є продовженням попередньої статті.
Робота з сонаром HC-SR04
У роботі з сонаром HC-SR04 використовується зовнішнє переривання. Докладніше із зовнішніми перериваннями познайомимося трохи пізніше. У цьому прикладі мікроконтролер посилає сонару імпульс (Trigger), який запускає вимірювання. Через деякий час сонар має "підняти" сигнал Echo - саме у цей момент починається відлік часу. А потім сонар сигнал Echo "опускає". У цей момент вимірювання закінчується. З таймера зчитуються показник лічильника і, в залежності від виміряної довжини зворотного імпульсу Echo, вираховується відстань від сонара до перешкоди. Фактично, ми вимірюємо час між двома подіями. Ми це вже робили у попередній статті.Діаграма сигналів HC-SR04:
Схема підключення сонара до тестової плати STM32F103C8:
Текст програми для роботи з сонаром HC-SR04 можете скачати на сторінці з прикладами.
Я не даремно навів цей приклад, бо далі піде мова про захоплення сигналу таймером. Отже, вимірювання довжини імпульсу на вході мікроконтролера - це типова задача і таймери можуть вимірювати їх у більш простий спосіб, до того ж значно точніше і без використання зовнішніх переривань.
Захоплення сигналу
На ноги контролера виведено канали таймерів (Дивись TIMn_CH# ). Де, n - номер таймера # - номер каналу. Наприклад, TIM2_CH1 - перший канал таймеру TIM2.Канали таймерів можуть працювати як входи (для роботи з вхідними сигналами), і як виходи, коли таймери генерують вихідні сигнали.
Ідея захоплення сигналу полягає у тому, що при зміні стану вхідного сигналу таймер зберігає у спеціальний регістр поточне значення лічильника і викликає переривання. Але це ще не все. Можна налаштувати дільник, щоб таймер реагував на кожний n-ний імпульс. Також можна налаштувати фільтр. Фільтр використовується коли сигнал зашумлений. Фільтр працює як зворотній лічильник. Тобто, коли на вході сигнал змінив стан, таймер віднімає від числа зазначеного у фільтрі одиницю і чекає наступної вибірки. Перевірка сигналу повторюється, доки лічильник не дорахує до нуля, і, якщо після цього сигнал залишився не змінним, тоді викликається переривання. Для наочності наведу діаграму. У ній сигнал має шуми і нам треба відфільтрувати ці шуми. На діаграмі зображена робота таймера без фільтра і з фільтром = 5.
Давайте розглянемо приклад розбору PPM сигналу. Якщо Ви не знаєте що таке PPM, це не біда. Це досить специфічна річ. Важливо зрозуміти, що PPM - періодичний сигнал з 8-ми посилок різної довжини. І нам треба виміряти час між кожним з 8-ми імпульсів. Після 8-ми імпульсів іде пауза. По ній ми будемо знати, що посилка з 8-ми імпульсів скінчилась. За нею іде наступна посилка. PPM сигнал виглядає так:
Я наведу код програми, а нижче - пояснення:
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
void SetSysClockTo72(void)
{
ErrorStatus HSEStartUpStatus;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable HSE */
RCC_HSEConfig( RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS)
{
/* Enable Prefetch Buffer */
//FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);
/* Flash 2 wait state */
//FLASH_SetLatency( FLASH_Latency_2);
/* HCLK = SYSCLK */
RCC_HCLKConfig( RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config( RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config( RCC_HCLK_Div2);
/* PLLCLK = 8MHz * 9 = 72 MHz */
RCC_PLLConfig(0x00010000, RCC_PLLMul_9);
/* Enable PLL */
RCC_PLLCmd( ENABLE);
/* Wait till PLL is ready */
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source */
RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while (RCC_GetSYSCLKSource() != 0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock configuration.
User can add here some code to deal with this error */
/* Go to infinite loop */
while (1)
{
}
}
}
volatile uint16_t PPMBuffer[] = {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
volatile uint8_t PPMi = 0;
volatile uint16_t PPMValue_Prev, PPMValue;
void ppm_init() {
GPIO_InitTypeDef gpio_cfg;
GPIO_StructInit(&gpio_cfg);
/* Timer TIM2, channel 1 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//gpio_cfg.GPIO_Mode = GPIO_Mode_IN_FLOATING;
gpio_cfg.GPIO_Mode = GPIO_Mode_IPU;
gpio_cfg.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOA, &gpio_cfg);
/* Timer TIM2 enable clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* Timer TIM2 settings */
TIM_TimeBaseInitTypeDef timer_base;
TIM_TimeBaseStructInit(&timer_base);
timer_base.TIM_Prescaler = 72;
TIM_TimeBaseInit(TIM2, &timer_base);
/* Signal capture settings:
- Channel: 1
- Count: Up
- Source: Input
- Divider: Disable
- Filter: Disable */
TIM_ICInitTypeDef timer_ic;
timer_ic.TIM_Channel = TIM_Channel_2;
//timer_ic.TIM_ICPolarity = TIM_ICPolarity_BothEdge; # !!! BothEdge not supported
timer_ic.TIM_ICPolarity = TIM_ICPolarity_Rising;
timer_ic.TIM_ICSelection = TIM_ICSelection_DirectTI;
timer_ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
timer_ic.TIM_ICFilter = 0;
TIM_ICInit(TIM2, &timer_ic);
/* Enable Interrupt by overflow */
TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE);
/* Timer TIM2 Enable */
TIM_Cmd(TIM2, ENABLE);
/* Enable Interrupt of Timer TIM2 */
NVIC_EnableIRQ(TIM2_IRQn);
}
void TIM2_IRQHandler(void){
volatile uint16_t PPM;
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
{
/* Reset flag */
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
PPMValue_Prev = PPMValue;
PPMValue = TIM_GetCapture2(TIM2);
PPM = (PPMValue >= PPMValue_Prev) ? (PPMValue - PPMValue_Prev) : (UINT16_MAX - PPMValue_Prev + PPMValue);
if (PPM < 3000) { // Pause
PPMBuffer[PPMi] = PPM;
PPMi++;
if (PPMi > 7) {
PPMi = 0;
}
}
else {
PPMi = 0;
}
/* over-capture */
if (TIM_GetFlagStatus(TIM2, TIM_FLAG_CC2OF) != RESET)
{
TIM_ClearFlag(TIM2, TIM_FLAG_CC2OF);
// ...
}
}
}
int main(void)
{
SetSysClockTo72();
ppm_init();
while(1)
{
// You can read PPM data from array PPMBuffer
// For example: PPMBuffer[n]; n - number of channel 0..7
}
}
Роздивимось процедуру ініціалізації таймера TIM2. Спочатку іде стандартна ініціалізація таймера за допомогою структури TIM_TimeBaseInitTypeDef. Нам важливо знати, з якою частотою буде "цокати" таймер, тому обов`язково звертаємо увагу на частоту тактуваня і поділювач (TIM_Prescaler):
/* Timer TIM2 enable clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* Timer TIM2 settings */
TIM_TimeBaseInitTypeDef timer_base;
TIM_TimeBaseStructInit(&timer_base);
timer_base.TIM_Prescaler = 72;
TIM_TimeBaseInit(TIM2, &timer_base);
А вже потім налаштовуємо захоплення сигналу. Для цього використовується структура TIM_ICInitTypeDef. Вона має наступні параметри:
- TIM_Channel - Номер каналу
- TIM_ICPolarity - Визначає активний фронт вхідного сигналу. Тут невеличка засада. TIM_ICPolarity_BothEdge, тобто по наростаючому і спадаючому фронту у STM32F103C8 не спрацює. Або по на наростаючому або по спадаючому фронту. У даному випадку нас цілком влаштовує по фронту. На початку статті я не даремно навів приклад з зовнішніми перериваннями. Саме тому, що таймер саме нашого контролера можна налаштувати на захват або лише фронту або лише спаду, довелося шукати вихід, використовуючи зовнішні переривання.
- TIM_ICSelection - Визначає вхід
- TIM_ICPrescaler - Поділювач вхідного сигналу
- TIM_ICFilter - Фільтр (0x0 ... 0xF)
/* Signal capture settings:
- Channel: 1
- Count: Up
- Source: Input
- Divider: Disable
- Filter: Disable */
TIM_ICInitTypeDef timer_ic;
timer_ic.TIM_Channel = TIM_Channel_2;
//timer_ic.TIM_ICPolarity = TIM_ICPolarity_BothEdge; # !!! BothEdge not supported
timer_ic.TIM_ICPolarity = TIM_ICPolarity_Rising;
timer_ic.TIM_ICSelection = TIM_ICSelection_DirectTI;
timer_ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
timer_ic.TIM_ICFilter = 0;
TIM_ICInit(TIM2, &timer_ic);
Вмикаємо переривання:
/* Enable Interrupt by overflow */
TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE);
/* Timer TIM2 Enable */
TIM_Cmd(TIM2, ENABLE);
/* Enable Interrupt of Timer TIM2 */
NVIC_EnableIRQ(TIM2_IRQn);
Що забули?. Звісно! Треба налаштувати ногу, на яку заведений другий канал другого таймера, на вхід:
/* Timer TIM2, channel 1 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//gpio_cfg.GPIO_Mode = GPIO_Mode_IN_FLOATING;
gpio_cfg.GPIO_Mode = GPIO_Mode_IPU;
gpio_cfg.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOA, &gpio_cfg);
Тепер, при надходженні фронту сигналу, таймер буде зберігати показник лічильника у спеціальний регістр і викликати переривання TIM2_IRQHandler, де буде вираховуватись час між імпульсами і зберігатися дані у масив PPMBuffer:
void TIM2_IRQHandler(void){
volatile uint16_t PPM;
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
{
/* Reset flag */
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
PPMValue_Prev = PPMValue;
PPMValue = TIM_GetCapture2(TIM2);
PPM = (PPMValue >= PPMValue_Prev) ? (PPMValue - PPMValue_Prev) : (UINT16_MAX - PPMValue_Prev + PPMValue);
if (PPM < 3000) { // it was before 10000
PPMBuffer[PPMi] = PPM;
PPMi++;
if (PPMi > 7) {
PPMi = 0;
}
}
else {
PPMi = 0;
}
/* over-capture */
if (TIM_GetFlagStatus(TIM2, TIM_FLAG_CC2OF) != RESET)
{
TIM_ClearFlag(TIM2, TIM_FLAG_CC2OF);
// ...
}
}
}
І, поки ми будемо розбирати та вираховувати наші дані, таймер буде крокувати далі. Навіть, якщо наш контролер був зайнятий і обробив переривання з затримкою, за час якої таймер вже "пішов далі", це не біда. Показник лічильника було відкладено до регістра і він очікує на обробку. Зчитується регістр функцією TIM_GetCapture2(). На відміну від наведеного на початку статті прикладу зі зовнішнім перериванням, цей метод є більш точним, бо на виклик переривання та зчитування показань таймера теж витрачається час. Інколи це буває досить критично. До того ж, не завжди обробка переривань може бути виконана негайно.
У випадку з сонаром використання захоплення сигналу може і не принести помітного ефекту, але при розборі PPM сигналу результати стали помітно менш зашумлені.
Бажаю успіхів!
Дивись також:
- 1. STM32. Програмування STM32F103. Тестова плата. Прошивка через UART та через ST-Link
- 2. STM32. Програмування. IDE для STM32
- 3. STM32. Програмування STM32F103. GPIO
- 4. STM32. Програмування STM32F103. Тактування
- 5. STM32. Програмування STM32F103. USART
- 6. STM32. Програмування STM32F103. NVIC
- 7. STM32. Програмування STM32F103. ADC
- 8. STM32. Програмування STM32F103. DMA
- 9. STM32. Програмування STM32F103. TIMER
- 10. STM32. Програмування STM32F103. TIMER. Захоплення сигналу
- 11. STM32. Програмування STM32F103. TIMER. Encoder
- 12. STM32. Програмування STM32F103. TIMER. PWM
- 13. STM32. Програмування STM32F103. EXTI
- 14. STM32. Програмування STM32F103. RTC
- 15. STM32. Програмування STM32F103. BKP
- 16. STM32. Програмування STM32F103. Flash
- 17. STM32. Програмування STM32F103. Watchdog
- 18. STM32. Програмування STM32F103. Remap
- 19. STM32. Програмування STM32F103. I2C Master
- 20. STM32. Програмування STM32F103. I2C Slave
- 21. STM32. Програмування STM32F103. USB
- 22. STM32. Програмування STM32F103. PWR
- 23. STM32. Програмування STM32F103. Option bytes
- 24. STM32. Програмування STM32F103. Bootloader
- STM32. Скачати приклади
- System Workbench for STM32 Інсталяція на Ubuntu
- Keil uVision5 – IDE для STM32
- IAR Workbench – IDE для STM32
- Керування безколекторним двигуном постійного струму (BLDC) за допомогою STM32
- Керування PMSM за допомогою STM32
Tags
bme280 bmp280 gps mpu-6050 options stm32 ssd1331 ssd1306 eb-500 3d-printer soldering tim mpu-9250 dma watchdog piezo exti web raspberry-pi docker ngnix solar bluetooth foc html css brushless flask dc-dc capture gpio avr rs-232 mpx4115a atmega mongodb st-link barometer pwm nvic git java-script programmator dht11 hih-4000 pmsm encoder max1674 smd sensors rtc adc lcd motor timer meteo examples i2c usb flash sms rfid python esp8266 servo books bldc remap eeprom bkp battery ethernet uart usart displays led websocket nodemcu wifi
Архіви




