Ієрархія засобів програмування, орієнтована на SPL. Клікабельно. (c) STMicroelectronics |
Зауваження -- не плутайте SPL із STL з С++ -- стандартною бібліотекою шаблонів. ;-)
Повторимо
те ж, що і в попередньому пості, засобами Standard Peripherals Library, SPL. Апаратна конфігурація та ж:
- PB1 -- Trig
- PB2 -- Echo
- PC8 -- синій світлодіод
- PC9 -- зелений світлодіод
STM32 SPL
Всі необхідні компоненти вже підключено раніше -- на жаль, репозиторій CoIDE трохи змішує частини CMSIS i SPL. Нам будуть потрібні дві підсистеми, робота із якими входить до SPL: RCC -- Reset
and clock control для керування тактуванням, GPIO для роботи з пінами. В
головну програму, main.c, слід додати:
#include <stm32f10x_gpio.h> #include <stm32f10x_rcc.h>
Керування тактуванням здійснюється спеціальною функцією:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
якій передається назва периферії, тактування якої
слів ввімкнути (ENABLE) чи вимкнути (DISABLE), в даному випадку:
RCC_APB2Periph_GPIOB -- тактування порту B, під'єднаного до шини APB2.
Ініціалізація SysTick та ж, SysTick_Config з CMSIS -- цей таймер належить універсальному ядру "кортексів".
Для
ініціалізації портів використовується спеціальна структура,
GPIO_InitTypeDef, яку слід наповнити налаштуваннями та передати
GPIO_Init:
GPIO_InitTypeDef TrigPin_InitStruct; //PB1 TrigPin_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; TrigPin_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //Out - Push-Pull TrigPin_InitStruct.GPIO_Pin = GPIO_Pin_1; GPIO_Init(GPIOB, &TrigPin_InitStruct); GPIO_InitTypeDef EchoPin_InitStruct; //PB2 EchoPin_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; EchoPin_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; EchoPin_InitStruct.GPIO_Pin = GPIO_Pin_2; GPIO_Init(GPIOB, &EchoPin_InitStruct);
- Ця структура має поля із доволі очевидними назвами -- GPIO_Speed, GPIO_Mode, GPIO_Pin.
- Швидкості задаються символами типу, GPIO_Speed_50MHz.
- Режим -- GPIO_Mode_Out_PP для виводу Push-Pull, GPIO_Mode_IN_FLOATING для входу, що плаває (не є притягнутим до 0 чи 1).
- Піни, які слід сконфігурувати, задаються біт-масками типу GPIO_Pin_2, їх можна об'єднувати за допомогою побітового OR (АБО), наприклад: GPIO_Pin_8 | GPIO_Pin_9.
- GPIO_Init(), крім вказівника на структуру з конфігураційною інформацією отримує також порт, якому належать піни для конфігурування, наприклад GPIOB.
Зауваження 1: структури для конфігурування різної периферії бажано спочатку ініціалізувати відповідними функціями, які для всіх полів вкажуть розумні значення за замовчуванням. Є така функція і для GPIO_InitTypeDef:void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
В прикладі вище її не застосовував, так як, все рівно, всі поля задавалися вручну, однак краще не опускати їх виклики -- позбавляє багатьох проблем.
Зауваження 2: існує обернена до GPIO_Init функція:void GPIO_DeInit(GPIO_TypeDef* GPIOx);
яка повертає всі регістри, що керують портом, до значень по замовчуванню. За рештою цікавих функцій та констант, що стосуються GPIO -- див. stm32f10x_gpio.h.
Щоб встановити пін в 1, чи 0, використовується, відповідно:
GPIO_SetBits(GPIOB, GPIO_Pin_1); GPIO_ResetBits(GPIOB, GPIO_Pin_1);
Як на мене, такий код очевидніший, ніж аналогічний, з CMSIS:
GPIOB->BSRR = GPIO_BSRR_BS1;
GPIOB->BSRR = GPIO_BSRR_BR1;
(А про зрозумілість CMSIS- та STL- варіантів коду ініціалізації і мова не йде, хоча останній трохи багатослівніший.)
Прочитати значення на "ніжці":
GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)
Повертає вона 0 або 1 (точніше, рекомендується використовувати символи Bit_SET і Bit_RESET, які якраз рівні 0 і 1 відповідно).
У всьому решта програма абсолютно тотожна попередній:
#include <stdint.h> #include <stdio.h> #include <stdbool.h> #include <stm32f10x.h> #include <stm32f10x_gpio.h> #include <stm32f10x_rcc.h> #include <semihosting.h> volatile uint32_t curTicks; #ifdef __cplusplus extern "C"{ #endif void SysTick_Handler(void) { curTicks++; /* Зроблено ще один тік */ } #ifdef __cplusplus } #endif /*! В чистому С90 ініціалізатори глобальних змінних самі мають бути * "зовсім-зовсім" константами -- заданими define, а не const type * Для простоти використовую define. * Див. також про ініціалізацію статичних змінних в С++ тут: * http://indrekis2.blogspot.com/2015/11/c-arm-gcc-stm32-coide.html */ // const uint32_t microseconds_granularity=2; #define MICROSECONDS_GRANULARITY 2 // Потрібна частота спрацювань таймера SysTick в герцах #define FREQ ((1000000)/(MICROSECONDS_GRANULARITY)) // const uint32_t freq=1000000/microseconds_granularity; #define TICKS ((SystemCoreClock)/(FREQ)) //const uint32_t ticks=SystemCoreClock/freq; /*! Затримка в мікросекундах, грануляції -- microseconds_granularity мікросекунд, * одну не витягує сам контролер. 24МГц -- один такт це 42нс=0.042мкс */ inline static void delay_some_us(uint32_t mks) { uint32_t ticks=mks/MICROSECONDS_GRANULARITY; int stop_ticks=ticks+curTicks; while (curTicks < stop_ticks) {}; } //! Затримка в мікросекундах inline static void delay_some_us1 (uint32_t dlyTicks) { // Взято з leaflabs Maple dlyTicks *= 6; //STM32_DELAY_US_MULT; /* Невелика і не дуже точна поправка на час виклику фунції */ dlyTicks--; asm volatile(" mov r0, %[dlyTicks] \n\t" "1: subs r0, #1 \n\t" " bhi 1b \n\t" : : [dlyTicks] "r" (dlyTicks) : "r0"); } // Підрахунок тактів // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337g/BABBCJII.html // mov - 2 (не в циклі!) // subs - 1 // bhi - 1+2 якщо відбувається перехід // всього в циклі -- 4 // 1 такт = 1/24 мкс = 41.6нс // 4 такти -- 0.167 мкс // коеф. = 1/0.167 = 6 // 1 мкс - 24 такти // Див. також https://my.st.com/public/STe2ecommunities/mcu/Lists/STM32Discovery/DispForm.aspx?ID=3100&RootFolder=%2Fpublic%2FSTe2ecommunities%2Fmcu%2FLists%2FSTM32Discovery%2FSTM32F0Discovery%20timing%20issue // Апаратний лічильник: http://electronix.ru/forum/lofiversion/index.php/t99506.html // однак в STM32F100RB відсутній int main(void) { // HC-SR04 підключено до PB1 (Trig) i PB2 (Echo) // Дозволяємо тактування порту B RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // Дозволяємо тактування порту C, на ньому -- світлодіоди плати STM32VLDiscovery // PC8 -- blue, PC9 -- green RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //! Створювати кілька екземплярів цієї структури немає потреби. //! Можна модифікувати одну і ту ж, але так наочніше. GPIO_InitTypeDef TrigPin_InitStruct; //PB1 GPIO_InitTypeDef EchoPin_InitStruct; //PB2 GPIO_InitTypeDef LEDs_InitStruct; // PC8/PC9 TrigPin_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; TrigPin_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //Out - Push-Pull TrigPin_InitStruct.GPIO_Pin = GPIO_Pin_1; GPIO_Init(GPIOB, &TrigPin_InitStruct); EchoPin_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; EchoPin_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; EchoPin_InitStruct.GPIO_Pin = GPIO_Pin_2; GPIO_Init(GPIOB, &EchoPin_InitStruct); LEDs_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; LEDs_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; LEDs_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &LEDs_InitStruct); // Перевіримо, чи лінія Echo в нулі поки ми ще не подали імпульс if( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)!=0 ) { // Помилка -- імпульсу не було, а на Echo вже одиниця GPIO_SetBits(GPIOC, GPIO_Pin_8); // Синім світлодіодом позначатимемо помилку while (1); } // Ініціалізуємо таймер SysTick if (SysTick_Config (TICKS)) { // Помилка -- таймер не ініціалізувався GPIO_SetBits(GPIOC, GPIO_Pin_8); // Синім позначатимемо помилку printf("Error -- SysTick failed to start.\n"); while(1); // Зависаємо } uint32_t starting_ticks; uint32_t timeout_ticks; uint32_t distance_mm; bool are_echoing=false; while(1) { //! Якщо не подавали сигнал - вперед, подаємо if(!are_echoing) { // Пауза між вимірами, двічі по 1/4 секунди delay_some_us(500000/2); // Гасимо світлодіоди GPIO_ResetBits(GPIOC, GPIO_Pin_8); GPIO_ResetBits(GPIOC, GPIO_Pin_9); delay_some_us(500000/2); are_echoing=true; // Посилаємо імпульс GPIO_SetBits(GPIOB, GPIO_Pin_1); delay_some_us(12); // 12 ms GPIO_ResetBits(GPIOB, GPIO_Pin_1); // Починаємо заміряти час timeout_ticks=curTicks; }else{ // Якщо ж подали -- чекаємо на імпульс і заміряємо його тривалість bool measuring=false; uint32_t measured_time; // Скільки чекати, поки не вирішити, що імпульсу вже не буде uint32_t usoniq_timeout = 100000; while( (curTicks - timeout_ticks) < usoniq_timeout ) { // Перевіряємо логічний рівень на Echo //printf("B->IDR %X\n",GPIOB->IDR); if( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)!=0) { // Якщо раніше сигналу не було, починаємо заміряти його тривалість if(!measuring) { starting_ticks=curTicks; measuring=true; } }else{ // Якщо сигнал на Echo був і зник -- заміряємо його тривалість if(measuring) { // Результат буде в міліметрах measured_time = (curTicks - starting_ticks)*10*MICROSECONDS_GRANULARITY; distance_mm = measured_time/58; // Повідомляємо про успішний вимір зеленим світлодіодом GPIO_SetBits(GPIOC, GPIO_Pin_9); break; } } } if(!measuring) { // Не отримали сигналу, повідомляємо про помилку і пробуємо ще GPIO_SetBits(GPIOC, GPIO_Pin_8); printf("Echo signal not arrived in time\n"); }else{ printf("Distance: %u mm, measured time: %lu us\n",distance_mm, measured_time/10); } are_echoing=false; } } }
Скачати проект можна тут.
Видно, що SPL -- більш багатослівний, але значно легший в читанні і дещо легший в написанні. Хоча, повноцінним шаром абстракції, здатним сховати всі хитрощі ігрищ з бітами регістрів, не є. Важливий аспект -- порівняння розмірів коду для CMSIS, SPL та інших підходів, буде в одному із наступних постів.
Видно, що SPL -- більш багатослівний, але значно легший в читанні і дещо легший в написанні. Хоча, повноцінним шаром абстракції, здатним сховати всі хитрощі ігрищ з бітами регістрів, не є. Важливий аспект -- порівняння розмірів коду для CMSIS, SPL та інших підходів, буде в одному із наступних постів.
Немає коментарів:
Дописати коментар