Структура CoX |
COX
CooCox, автори CoIDE, розробили також свою бібліотеку роботи із периферією, аналог SPL -- COX. Її перевага -- вона платформонезалежна (кросплатформова), працює і з контролерами ARM Cortex від інших виробників -- підтримуються пристрої від Nuvoton, NXP, ST, TI, Holtek, FreeScale, тоді як код із попередніх постів ([1], [2]), працюватиме лише на STM32 від STMicroelectronics.
Апаратна конфігурація та ж, що й в інших GPIO-прикладах:
- PB1 -- Trig
- PB2 -- Echo
- PC8 -- синій світлодіод
- PC9 -- зелений світлодіод
Проект буде на чистому C, (попередні проекти тривіально переносяться на С++), щоб не
вдаватися в деталі додавання підтримки C++ до StartUp коду COX. Правда,з елементами C99 -- не маю терпцю на чистий 89-й... (За потреби, особливих складностей в підключенні С++ немає,
процедура схожа на вже описану).
В 2012 CoX, доданий із CoIDE, працював успішно і зразу. Зараз -- не працює, бракує ряду важливих файлів (судячи по всьому, вони мали б бути в окремій компоненті, специфічній для мікроконтролера, яка відсутня в репозиторії -- є лише щось схоже на неї, для єдиної моделі контролерів, Cox_LPC17xx_Port). Якщо скачати із github, найсвіжішу версію (2014), пише, що вона теж 2.2.1.0, як і та, що в проекті 2012-го року, хоча файли сильно відрізняються. Додана із репозиторію (не функціональна), каже, що вона -- 2.3. Сходу повних її текстів, хоча б -- для STM32F1xx, не знайшов, тому, так як заглиблюватися немає бажання (за фактичною неактуальністю), взяв версію із github. Для цього слід до проекту додати вміст директорії cox-master/CoX/CoX_Peripheral/CoX_Peripheral_STM32F1xx/libcox та файл cox-master/CoX/CoX_Peripheral/CoX_Peripheral_STM32F1xx/startup/src/startup_coide.c. (Звичайно, краще в проекті створити відповідні групи.)
Зауваження: Старіші версії COX були побудовані без врахування того, що може відбуватися оптимізація, ввімкнення її призводитиме до проблем. Автори обіцяють це виправити, але поки краще використовувати "-O0" -- без оптимізації. При тому, на жаль, компілятор не матиме нагоди викинути зайві змінні, не виділяючи під них пам'ять. Хоча, примітивні приклади працюють і з оптимізацією. Може вже виправили?..
Також, колись доводилося перевіряти, і, за потреби, збільшувати стек у файлі startup_coide.c -- STACK_SIZE. (Для printf 100 байт замало, але 100 слів -- 400 байт, мало б вистачити).
Увага: з-під COX printf по semihosting, якщо не під'єднано дебаггер, зависає при спробі flush-нутися, тобто натрапивши на символ /n або коли виведено достатньо велику порцію інформації, щоб переповнити якісь внутрішні буфери. З-під дебаггера все працює успішно. Ймовірно, справа в тому, що COX не викликає CMSIS-івської функції ініціалізації SystemInit(), а все робить сама, і щось недоналаштовує, або налаштовує на інші значення по замовчуванню. Детальніше розбиратися не став.
Отож, після всіх передмов, переходячи до суті.
Перший крок -- ініціалізувати бібліотеку.
xSysCtlClockSet(24000000, xSYSCTL_OSC_MAIN | xSYSCTL_XTAL_8MHZ);
В принципі, цей виклик не є аж таким обов'язковим,
але він сильно допомагає бібліотеці, зокрема визначитися із режимом
тактування і частотою (які програмно визначити складно). (Деталі
конкретних констант див. документацію та xsysctl.h). Увага! xSYSCTL_XTAL_8MHZ -- не частота мікроконтролера, а частота кварцу! Для STM32VLDisocovery -- 8МГц, а сам контролер працює, максимум, на 24МГц.
Ініціалізація
таймера SysTick розбита на декілька етапів -- встановити період, дозволити йому відлік і дозволити його переривання:
xSysTickPeriodSet(TICKS); xSysTickEnable(); xSysTickIntEnable();
Далі на черзі -- ввімкнення тактування портів:
xSysCtlPeripheralEnable(xSYSCTL_PERIPH_GPIOB);
Для конфігурування пінів використовується наступна функція:
xGPIODirModeSet( xGPIO_PORTB_BASE, xGPIO_PIN_1, xGPIO_DIR_MODE_OUT );
Для конфігурування на вивід використовуємо константу xGPIO_DIR_MODE_OUT, на ввід -- xGPIO_DIR_MODE_IN:
xGPIODirModeSet( xGPIO_PORTB_BASE, xGPIO_PIN_2, xGPIO_DIR_MODE_IN );
Такий виклик достатньо універсальний,
архітектуро-незалежний, але якщо потрібно скористатися можливостями
конкретного контролера більш повно -- вийти за межами "спільного знаменника" різних сімейств мікроконтролерів, то можна трішки по іншому:
xGPIODirModeSet( xGPIO_PORTB_BASE, xGPIO_PIN_2, GPIO_TYPE_IN_FLOATING | GPIO_IN_SPEED_FIXED );
Власне, така конфігурація, більш точно відповідає попереднім прикладам.
xGPIODirModeSet можна передавати декілька пінів одночасно, xGPIO_PIN_x це біт-маски:
xGPIODirModeSet( xGPIO_PORTC_BASE, xGPIO_PIN_8 | xGPIO_PIN_9, xGPIO_DIR_MODE_OUT );
Читання пінів здійснюється так:
xGPIOPinRead(xGPIO_PORTB_BASE,xGPIO_PIN_2)
Вона бере всі значення пінів порту і робить
AND з переданою маскою, зокрема у наведеному прикладі, біт з номером
xGPIO_PIN_2 буде 1 або 0, в залежності від значення входу, решта - нуль.
Запис в порт здійснюється аналогічно:
xGPIOPinWrite( xGPIO_PORTB_BASE, xGPIO_PIN_1, 1 );
xGPIOPinWrite( xGPIO_PORTB_BASE, xGPIO_PIN_1, 0 );
Ця функція теж приймає біт-маску пінів, тому може оперувати з кількома пінами одночасно.
Програма залишиться майже такою ж, як і раніше:
#include <stdint.h> //#include <inttypes.h> #include <stdio.h> #include <semihosting.h> #include <xhw_types.h> #include <xhw_memmap.h> #include <xcore.h> #include <xsysctl.h> #include <xgpio.h> volatile uint32_t curTicks; #ifdef __cplusplus extern "C"{ #endif void SysTickIntHandler(void) { curTicks++; /* Зроблено ще один тік */ } #ifdef __cplusplus } #endif // C вимагає, щоб ініціалізатори були літералами, тому нижче не const а define // Ця змінна не оголошена, якщо не використовувати CMSIS #define SystemCoreClock 24000000 #define MICROSECONDS_GRANULARITY 2 // Потрібна частота спрацювань таймера SysTick в герцах #define FREQ (1000000/MICROSECONDS_GRANULARITY) #define TICKS (SystemCoreClock/FREQ) /*! Затримка в мікросекундах, грануляція -- microseconds_granularity мікросекунд, * одну не витягує сам контролер. 24МГц -- один такт це 42нс=0.042мкс */ inline static void delay_some_us(uint32_t mks) { uint32_t iticks=mks/MICROSECONDS_GRANULARITY; int stop_ticks=iticks+curTicks; while (curTicks < stop_ticks) {}; } int main(void) { // Налаштувати тактування. Не обов'язково -- вказані значення є // і по замовчуванню, але паралельно їх використовує COX // Інакше їй довелося б здогадуватися xSysCtlClockSet(SystemCoreClock, xSYSCTL_OSC_MAIN | xSYSCTL_XTAL_8MHZ); // Ввімкнути тактування порту B xSysCtlPeripheralEnable(xSYSCTL_PERIPH_GPIOB); // Ввімкнути тактування порту C xSysCtlPeripheralEnable(xSYSCTL_PERIPH_GPIOC); // Конфігурування Trig xGPIODirModeSet( xGPIO_PORTB_BASE, xGPIO_PIN_1, xGPIO_DIR_MODE_OUT ); // Конфігурування Echo // xGPIO_DIR_MODE_IN по замовчуванню -- GPIO_TYPE_IN_WPU_WPD | GPIO_IN_SPEED_FIXED // Це не те що треба, тому "вручну": xGPIODirModeSet( xGPIO_PORTB_BASE, xGPIO_PIN_2, GPIO_TYPE_IN_FLOATING | GPIO_IN_SPEED_FIXED ); //xGPIODirModeSet( xGPIO_PORTB_BASE, xGPIO_PIN_2, xGPIO_DIR_MODE_IN ); // Конфігурування світлодіодних виводів: xGPIODirModeSet( xGPIO_PORTC_BASE, xGPIO_PIN_8 | xGPIO_PIN_9, xGPIO_DIR_MODE_OUT ); // Ініціалізація SysTick -- встановити період, дозволити таймер, //дозволити переривання від нього xSysTickPeriodSet(TICKS); xSysTickEnable(); xSysTickIntEnable(); // Перевіримо, чи лінія Echo в нулі поки ми ще не подали імпульс if( xGPIOPinRead(xGPIO_PORTB_BASE,xGPIO_PIN_2)!=0 ) { // Помилка -- імпульсу не було, а на Echo вже одиниця xGPIOPinWrite( xGPIO_PORTC_BASE, xGPIO_PIN_8, 1 ); // Синім світлодіодом позначатимемо помилку while (1); } uint32_t starting_ticks; uint32_t timeout_ticks; uint32_t distance_mm; int are_echoing=0; while(1) { //! Якщо не подавали сигнал - вперед, подаємо if(!are_echoing) { // Пауза між вимірами, двічі по 1/4 секунди delay_some_us(500000/2); // Гасимо світлодіоди xGPIOPinWrite( xGPIO_PORTC_BASE, xGPIO_PIN_8 | xGPIO_PIN_9, 0 ); delay_some_us(500000/2); are_echoing=1; // Посилаємо імпульс xGPIOPinWrite( xGPIO_PORTB_BASE, xGPIO_PIN_1, 1 ); delay_some_us(12); // 12 ms xGPIOPinWrite( xGPIO_PORTB_BASE, xGPIO_PIN_1, 0 ); // Починаємо заміряти час timeout_ticks=curTicks; }else{ // Якщо ж подали -- чекаємо на імпульс і заміряємо його тривалість int measuring=0; uint32_t measured_time; // Скільки чекати, поки не вирішити, що імпульсу вже не буде uint32_t usoniq_timeout = 100000; while( (curTicks - timeout_ticks) < usoniq_timeout ) { // Перевіряємо логічний рівень на Echo if( xGPIOPinRead(xGPIO_PORTB_BASE,xGPIO_PIN_2)!=0) { // Якщо раніше сигналу не було, починаємо заміряти його тривалість if(!measuring) { starting_ticks=curTicks; measuring=1; } }else{ // Якщо сигнал на Echo був і зник -- заміряємо його тривалість if(measuring) { // Результат буде в міліметрах measured_time = (curTicks - starting_ticks)*10*MICROSECONDS_GRANULARITY; distance_mm = measured_time/58; // Повідомляємо про успішний вимір зеленим світлодіодом xGPIOPinWrite( xGPIO_PORTC_BASE, xGPIO_PIN_9, 1 ); break; } } } if(! measuring) { // Не отримали сигналу, повідомляємо про помилку і пробуємо ще xGPIOPinWrite( xGPIO_PORTC_BASE, xGPIO_PIN_8, 1 ); printf("Echo signal not arrived in time\n"); }else{ printf("Distance: %u, measured time: %u\n",distance_mm, measured_time/10); } are_echoing=0; } } }
Будова StartUp-файлу близька до описаної в старому пості: CooCox CoIDE, по версії 1.5.
Підсумовуючи, ідея бібліотеки, як мінімум -- цікава. Однак, через хаос із версіями та певну закинутість авторами, сумніваюся, чи вартує у неї заглиблюватися. Якщо потрібно схожого рівня кросплатформовість, можна на котрусь із RTOS із драйверами периферії, звернути увагу (наприклад, ChibiOS як загальну, MIOSIX-2 як спеціалізовану для сімейства STM32).
Проект можна скачати тут.
Немає коментарів:
Дописати коментар