вівторок, 23 лютого 2016 р.

Далекомір HC-SR04 -- використовуючи GPIO/CoX

Структура CoX
Мушу зізнатися, написано цю серію постів було ще в 2012-му році. На фоні тодішньої ситуації, бібліотека периферії від CooCox - розробників CoIDE, виглядала перспективною. Зараз я в цьому вже дуже сумніваюся -- вона, на жаль, практично закинута. Однак, викидати тодішній приклад шкода, тому ось.

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).

Проект можна скачати тут.

Немає коментарів:

Дописати коментар