Взято тут: "STM32 External Interrupt" |
Спрощений варіант роботи за допомогою переривань було розглянуто в "методичному" пості: "Зовсім просто про далекомір HC-SR04 із GPIO/HAL", тут дотримуватимуся більш систематичного, але дещо заскладного для конкретної простої задачі підходу.
Для відліку часу -- як під час подання сигналу на TRIG, так і для заміру тривалості ECHO, використано TIM1:
- Trig знаходиться на його каналі 3 -- пін PA10,
- Echo під'єднано до PA6.
Логіка роботи програми наступна:
- За допомогою таймера на Trig генерується імпульс потрібної довжини.
- Лічильник цього таймера використовується для заміру часу, а його переривання переповнення -- для фіксації таймауту, факту, що далекомір не реагує і слід спробувати наново.
- Коли Trig перейшов з 1 в 0 -- починаємо чекати на Echo (а якщо не перейшов -- повідомляємо про помилку).
- Для цього дозволяємо переривання по зростанню (по фронту) на PA6.
- Якщо переривання сталося -- фіксуємо момент та чекаємо на переривання про спаду.
- Якщо воно сталося, забороняємо переривання від Echo (PA6) та виводимо результат.
- Якщо таймаут стався раніше, ніж на лінії Echo почався та закінчився імпульс, повідомляємо про помилку.
Стан програми задається, як і раніше, значеннями із відповідного enum:
typedef enum state_t{IDLE_S, TRIGGERING_S, WAITING_FOR_ECHO_START_S, WAITING_FOR_ECHO_STOP_S, TRIG_NOT_WENT_LOW_S, ECHO_TIMEOUT_S, ECHO_NOT_WENT_LOW_S, READING_DATA_S, ERROR_S} state_t; volatile state_t state; volatile uint32_t measured_time;
Переривання
- "ARM. Учебный Курс. Прерывания и NVIC — приоритетный контроллер прерываний"
- "ARM. Учебный курс. Внешние прерывания"
- «The Insider's Guide To The STM32 ARM Based Microcontroller» від Hitex -- легко знаходиться в Інтернеті.
- Joseph Yiu, «The Definitive Guide to the ARM Cortex-M3» -- зокрема, включає огляд підсистеми обробки переривань (NVIC), легко знаходиться в Інтернеті.
- "STM32 External Interrupt"
- GP Inputs as external Interrupt (EXTI) -- для зовсім лінивих.
STM32F303 має 36 ліній! З них, на жаль, до пінів під'єднано тих же 16, решта -- до внутрішньої периферії:
(с) STMicroelectronics |
Мультиплексори ліній переривань. |
Тобто, лінії EXTI0 відповідає пін 0 будь-якого, але одного, із портів. Не можна одночасно реагувати на зміну стану, скажімо, PA0 i PB0. Аналогічно, EXTI1 відповідає пін 1 котрогось із портів і т.д. Котрий порт працює із даною лінією, визначається одним із чотирьох регістрів AFIO_EXTICRx.
Перш ніж змінювати його вміст, слід ввімкнути тактування блоку альтернативних функцій пінів -- AFIO:
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
У нас Echo далекоміра приєднане до PA6, тому кажемо, що шоста лінія отримує сигнал із PortA (див. функцію echoExtiInit()):
AFIO->EXTICR[1] |= AFIO_EXTICR2_EXTI6_PA;
У відповідь на виникнення сигналу на лінії переривання, мікроконтролер може:
- Згенерувати, власне, переривання -- викликати код обробника.
- Створити подію -- встановити той чи інший прапорець (на який, у свою чергу, може прореагувати інша периферія, наприклад, АЦП здійснити перетворення).
Первинною є подія -- кожне переривання генерується у відповідь на подію, але не кожна подія генерує переривання. Крім того, є можливість згенерувати переривання програмно, див. далі.
Біти регістру EXTI_IMR -- Interrupt mask register, дозволяють чи забороняють переривання від відповідних ліній. (В STM32F3 таких регістрів два -- EXTI_IMR1 і EXTI_IMR2, бо ліній переривань більше ніж 32):
Регістр EXTI_IMR для STM32F100. (c) STMicroelectronics |
У нашому випадку дозволяємо переривання на лінії 6:
EXTI->IMR |= EXTI_IMR_MR6;
Аналогічний регістр (регістри) --- EXTI_EMR, є і для генерації подій.
Регістри EXTI_RTSR (Rising trigger selection register) і EXTI_FTSR (Falling trigger selection register) керують спрацюванням по фронту та по спаду сигналу. Кожній лінії відповідає по біту в цих регістрах. Якщо встановити 1 в обидва, переривання буде і по фронту сигналу і по спаду. В STM32F3 їх теж по два: EXTI_RTSR1, EXTI_RTSR2, EXTI_FTSR1, EXTI_FTSR2.
В коді використано 4 наступних очевидних допоміжних функції:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void enable_capture_falling() { EXTI->FTSR |= (EXTI_FTSR_TR6); // Falling trigger selection register } void enable_capture_rising() { EXTI->RTSR |= (EXTI_RTSR_TR6); // Rising trigger selection register } void disable_capture_falling() { EXTI->FTSR &= ~(EXTI_FTSR_TR6); // Falling trigger selection register } void disable_capture_rising() { EXTI->RTSR &= ~(EXTI_RTSR_TR6); // Rising trigger selection register } |
Дуже важливим є регістр EXTI_PR -- після виникнення зовнішнього переривання, мікроконтролер встановлює відповідний біт цього регістра в 1. Очищати його слід програмно, наприклад, в обробнику переривання, інакше після його завершення, обробник буде знову викликано. УВАГА! Для очищення бітів цього регістра слід в них записувати 1!
Згадана вище можливість реалізувати переривання програмно, реалізується регістром EXTI_SWIER (Software interrupt event register), запис у відповідний біт якого дає той же ефект, що виникнення цього переривання.
Отож, послідовність дій, щоб дозволити зовнішнє переривання:
Згадана вище можливість реалізувати переривання програмно, реалізується регістром EXTI_SWIER (Software interrupt event register), запис у відповідний біт якого дає той же ефект, що виникнення цього переривання.
Отож, послідовність дій, щоб дозволити зовнішнє переривання:
- Дозволити тактування AFIO та порту, на пін якого реагуватимемо.
- В регістрі AFIO->EXTICR вказати, а котрий порт нас цікавить.
- Дозволити відповідне переривання в EXTI->IMR.
- Встановити реакцію на фронт чи/і на спад сигналу в регістрах EXTI->RTSR і EXTI->FTSR.
- Чекати на виникнення переривання. Пам'ятати в обробнику очистити відповідний біт у EXTI->PR.
Обробники EXTI
На кожну із перших 5-ти ліній, EXTI0-EXTI5, приходиться по обробнику -- "слабкій" (weak) функції із іменем виду EXTIx_IRQHandler(), наприклад EXTI2_IRQHandler(). Якщо написати таку функцію (котра нічого не приймає і не повертає), вона викликатиметься у відповідь на переривання.Нагадаю, переривання ARM Cortex M викликаються так, що їх обробником може бути звичайна функція С. Говорячи більш технічно, при виклику процесор автоматично зберігає всі scratch-регістри, визначені ARM C ABI а при поверненні із обробника -- відновлює їх. Порівняйте із написанням IRQ для x86, хто ще пам'ятає. :-)Ситуація для ліній із більшими номерами трішки складніша -- є по одному обробнику для всіх ліній від 5 до 9 -- EXTI9_5 та, аналогічно, єдиний EXTI15_10, для ліній 10..15 (зверніть увагу на зворотній порядок номерів у назві). Наша лінія -- 6-та, тому обробник виглядатиме так:
1 2 3 4 5 6 7 | void EXTI9_5_IRQHandler(void) { if (EXTI->PR & EXTI_PR_PR6) { EXTI->PR |= EXTI_PR_PR6; // This bit is cleared by // writing a 1 into the bit! ................................. } } |
Налаштування таймера
Детально налаштування відповідних можливостей таймерів розглядалися в "Таймери STM32 -- ШІМ/CMSIS" та "Таймери STM32 -- відлік часу/CMSIS". Тут лише коротко:
- Функція baseTimersInit() встановлює відлік кожної мікросекунди та Capture-compare подію на каналі CH3 через 12 мкс, задаючи цим тривалість імпульсу. Період таймера -- максимальний, відлік вверх, біт автоматичної зупинки, OPM, встановлено -- щоб таймер не подав ще один імпульс, поки наша програма чимось іншим зайнята, даремно ганяючи далекомір.
- trigChannelInit() налаштовує 3-й канал (який керую Trig), але поки не дозволяє вивід на ньому! Полярність -- active high, PWM1. Вона ж пам'ятає встановити для таймера TIM1 біт MOE -- Master Output Enable, без нього нічого не буде! (Про цей біт див. також тут "Далекомір HC-SR04 -- два таймери/CMSIS", після слів "TIM1 є просунутим таймером").
- Функція initTimers() викликає дві попередні, а також дозволяє переривання від події CC3 та переповнення.
- Функції TIM1_CC3_output_enable() і TIM1_CC3_output_disable() використовуються в коді, щоб дозволяти чи забороняти відповідний канал.
Обробник переривання захоплення зразу і забороняє свій канал; переривання по переповненню встановлює, який же таймаут відбувся, в залежності від поточного стану програми. Їх повний текст -- див. нижче.
Програма
Після всіх попередніх постів та пояснень, детально розбирати код програми особливого сенсу немає. Просто наведу його нижче.
Скачати демонстраційний проект можна тут.
Наступного разу зробимо це ж, за допомогою Cube/HAL, а поки --
Дякую за увагу!
main.c -- повний текст
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | #include <stdint.h> #include <stdio.h> #include <stdbool.h> #include <stm32f10x.h> #include <semihosting.h> #include <inttypes.h> volatile uint32_t curTicks; #ifdef __cplusplus extern "C" { #endif void SysTick_Handler(void) { curTicks++; /* Зроблено ще один тік */ } #define MICROSECONDS_GRANULARITY 100 // Потрібна частота спрацювань таймера SysTick в герцах #define FREQ ((1000000)/(MICROSECONDS_GRANULARITY)) #define TICKS ((SystemCoreClock)/(FREQ)) /*! Затримка в мікросекундах, грануляція -- microseconds_granularity мікросекунд, * одну не витягує сам контролер. 24МГц -- один такт це 42нс=0.042мкс */ inline static void delay_some_ms(uint32_t mks) { uint32_t ticks = mks / MICROSECONDS_GRANULARITY; int stop_ticks = ticks + curTicks; while (curTicks < stop_ticks) { }; } //=============================================================== void enable_capture_falling() { EXTI->FTSR |= (EXTI_FTSR_TR6); // Falling trigger selection register } void enable_capture_rising() { EXTI->RTSR |= (EXTI_RTSR_TR6); // Rising trigger selection register } void disable_capture_falling() { EXTI->FTSR &= ~(EXTI_FTSR_TR6); // Falling trigger selection register } void disable_capture_rising() { EXTI->RTSR &= ~(EXTI_RTSR_TR6); // Rising trigger selection register } inline void TIM1_start() { // TIM1->CNT = 0; // In OPM mode update event clears it TIM1->CR1 |= TIM_CR1_CEN; } inline void TIM1_stop() { TIM1->CR1 &= ~TIM_CR1_CEN; } inline void TIM1_CC3_output_enable() { TIM1->CCER |= (TIM_CCER_CC3E); // Capture/Compare 3 output enable -- PA10 } inline void TIM1_CC3_output_disable() { TIM1->CCER &= ~(TIM_CCER_CC3E); // Capture/Compare 3 output disable -- PA10 } //=============================================================== const int TOO_FAR_TIMEOUT = 38000; typedef enum state_t { IDLE_S, TRIGGERING_S, WAITING_FOR_ECHO_START_S, WAITING_FOR_ECHO_STOP_S, TRIG_NOT_WENT_LOW_S, ECHO_TIMEOUT_S, ECHO_NOT_WENT_LOW_S, READING_DATA_S, ERROR_S } state_t; volatile state_t state = IDLE_S; volatile uint32_t measured_time; //==================================================================== //=ISR=and=tools====================================================== //==================================================================== volatile uint16_t echo_start_ticks; volatile uint16_t echo_finish_ticks; void EXTI9_5_IRQHandler(void) { if (EXTI->PR & EXTI_PR_PR6) { EXTI->PR |= EXTI_PR_PR6; // This bit is cleared by writing a 1 into the bit! switch (state) { case WAITING_FOR_ECHO_START_S: { echo_start_ticks = TIM1->CNT; state = WAITING_FOR_ECHO_STOP_S; disable_capture_rising(); enable_capture_falling(); break; } case WAITING_FOR_ECHO_STOP_S: { echo_finish_ticks = TIM1->CNT; measured_time = echo_finish_ticks - echo_start_ticks; state = READING_DATA_S; disable_capture_falling(); break; } default: puts("Unexpected signal on EXTI"); // Задовга операція, щоб тут залишати, якщо часто виконуватиметься } } } void TIM1_CC_IRQHandler(void) { if (TIM1->SR & TIM_SR_CC3IF) { TIM1->SR &= ~TIM_SR_CC3IF; TIM1_CC3_output_disable(); } } void TIM1_UP_TIM16_IRQHandler(void) { if (TIM1->SR & TIM_SR_UIF) // Перевіряємо джерело { TIM1->SR &= ~TIM_SR_UIF; //Очищаємо прапорець переривання switch (state) { case WAITING_FOR_ECHO_START_S: state = ECHO_TIMEOUT_S; break; case WAITING_FOR_ECHO_STOP_S: state = ECHO_NOT_WENT_LOW_S; break; case IDLE_S: default: ; //puts("Unexpected status"); } } } #ifdef __cplusplus } #endif void enableClock(); void initGPIO(); void baseTimersInit(); void trigChannelInit(); void echoChannelsInit(); void initTimers(); void on_error(const char* text, bool hang) { GPIOC->BSRR = GPIO_BSRR_BS8; // Синім позначатимемо помилку puts(text); if (hang) { while (1) ; } else { // resetTimer(); state = IDLE_S; } } int main(void) { enableClock(); initGPIO(); initTimers(); echoExtiInit(); // Перевіримо, чи лінія Echo в нулі поки ми ще не подали імпульс if (GPIOA->IDR & GPIO_IDR_IDR6 /* (1 << 2) */) { on_error("Error -- Echo line is high, though no impulse was given.", true); } // Ініціалізуємо таймер SysTick if (SysTick_Config(TICKS)) { on_error("Error -- SysTick failed to start.", true); } puts("Starting"); NVIC_ClearPendingIRQ(EXTI9_5_IRQn); NVIC_EnableIRQ(EXTI9_5_IRQn); while (1) { TIM1_CC3_output_enable(); TIM1_start(); NVIC_DisableIRQ(EXTI9_5_IRQn); state = WAITING_FOR_ECHO_START_S; NVIC_EnableIRQ(EXTI9_5_IRQn); enable_capture_rising(); while (TIM1->CCER & TIM_CCER_CC3E) // Поки вивід дозволено -- чекаємо ; if (GPIOA->IDR & GPIO_IDR_IDR10) { state = TRIG_NOT_WENT_LOW_S; puts("Trigger does not went low!"); NVIC_DisableIRQ(EXTI9_5_IRQn); state = IDLE_S; NVIC_EnableIRQ(EXTI9_5_IRQn); continue; } else { while (state != READING_DATA_S && state != ECHO_TIMEOUT_S && state != ECHO_NOT_WENT_LOW_S); NVIC_DisableIRQ(EXTI9_5_IRQn); state_t state_copy = state; state = IDLE_S; NVIC_EnableIRQ(EXTI9_5_IRQn); if (GPIOA->IDR & GPIO_IDR_IDR6 || state == ECHO_NOT_WENT_LOW_S) { puts("Echo line does not went low!"); if (state_copy == ECHO_NOT_WENT_LOW_S) puts("\tConfirmed from interrupt"); } if (state_copy == ECHO_TIMEOUT_S) { printf("Echo timeout!\n"); } else { if (measured_time > TOO_FAR_TIMEOUT) { printf("Tooo far -- no echo received, wating for %"PRIu32" mks\n", measured_time); } else { uint32_t distance_mm = (measured_time*10)/58; printf("Distance: %"PRIu32" mm, measured time: %"PRIu32" us\n",distance_mm, measured_time); } } } delay_some_ms(500000 / 10); } } void enableClock() { // Дозволяємо тактування порту C, на ньому -- світлодіоди плати STM32VLDiscovery // PC8 -- blue, PC9 -- green RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // Дозволяємо тактування порту A RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Дозволяємо тактування порту B RCC->APB2ENR |= RCC_APB2Periph_TIM1; // Дозволяємо тактування TIM1 RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // Вмикаємо мультиплексор переривань } void initGPIO() { //! PA10 -- TIM1_CH3, TRIG GPIOA->CRH &= ~GPIO_CRH_CNF10_0; // 10 -- AF PP (Alternative function -- push-pull) GPIOA->CRH |= GPIO_CRH_CNF10_1; GPIOA->CRH |= GPIO_CRH_MODE10; //Встановити обидва біти MODE для піна 1 -- швидкість 50MHz GPIOA->CRH &= ~GPIO_CRH_CNF8_0; // 10 -- AF PP (Alternative function -- push-pull) GPIOA->CRH |= GPIO_CRH_CNF8_1; GPIOA->CRH |= GPIO_CRH_MODE8; //Встановити обидва біти MODE для піна 1 -- швидкість 50MHz //! PA6 -- TIM3_CH1, ECHO GPIOA->CRL |= GPIO_CRL_CNF6_0; // 01 -- Input floating GPIOA->CRL &= ~GPIO_CRL_CNF6_1; GPIOA->CRL &= ~GPIO_CRL_MODE6; // Має бути 00 -- Input //! PA0 -- TIM2_CH1, візуальна віддаль, яскравістю LED GPIOA->CRL &= ~GPIO_CRL_CNF0_0; // 10 -- AF PP (Alternative function -- push-pull) GPIOA->CRL |= GPIO_CRL_CNF0_1; GPIOA->CRL |= GPIO_CRL_MODE0; //Встановити обидва біти MODE для піна 1 -- швидкість 50MHz //! Порти світлодіодів - теж на вивід. Звертаємося до старших пінів, тому CRH -- High GPIOC->CRH &= ~GPIO_CRH_CNF8; GPIOC->CRH |= GPIO_CRH_MODE8; GPIOC->CRH &= ~GPIO_CRH_CNF9; GPIOC->CRH |= GPIO_CRH_MODE9; } void baseTimersInit() { const int delay = 12; TIM1->PSC = SystemCoreClock / 1000000 - 1; //! По завершенню циклу таймер зупиняється, але перед тим переводить у //! високий стан! Тому слід використовувати PWM2 //! І тому, якщо робити паузу хоча б +3, після імпульсу, //! далекомір встигав спрацювати, а якщо +1 -- частіше ні, +2 -- частіше так. TIM1->ARR = UINT16_MAX; TIM1->CCR3 = delay; // TRIG -- 12 мкс TIM1->CR1 &= ~TIM_CR1_DIR; // Відлік -- вверх. (Default) TIM1->CR1 |= TIM_CR1_OPM; } void trigChannelInit() { // Канал TRIG -- CH3 //TIM1->CCER |= (TIM_CCER_CC3E); // Capture/Compare 3 output enable -- PA10 TIM1->CCER &= ~TIM_CCER_CC3P; // Полярність -- active high (Default) TIM1->CCMR2 |= (TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2); // PWM1 -- OCxM=0b110 TIM1->CCMR2 &= ~TIM_CCMR2_OC3M_0; //TIM1->CCMR2 |= (TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2); // PWM2 -- OCxM=0b111 TIM1->CCMR2 &= ~TIM_CCMR2_CC3S; // Зануляємо -- на вивід. (Default) TIM1->CCMR2 |= TIM_CCMR2_OC3PE; // Preload для CCR3, вимагається Datasheet для PWM TIM1->CR1 |= TIM_CR1_ARPE; // Preload для ARR, вимагається Datasheet для PWM TIM1->BDTR |= TIM_BDTR_MOE; } void echoExtiInit() { AFIO->EXTICR[1] |= AFIO_EXTICR2_EXTI6_PA; EXTI->IMR |= EXTI_IMR_MR6; } void initTimers() { baseTimersInit(); trigChannelInit(); TIM1->DIER |= (TIM_DIER_CC3IE | TIM_DIER_UIE); NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); NVIC_EnableIRQ(TIM1_CC_IRQn); } |
Немає коментарів:
Дописати коментар