Недавно писал про свою новую ля-лярдуину. Давайте помигаем светодиодами. А чтобы это сделать, необходимо скачать родную IDE - Simplicity Studio на основе народной Eclipse. Необходимо поставить SDK Gecko, а также файлы поддержки того МК (или Starter kit-а), на котором будет создан проект. У меня это SLSTK3402A на основе EFM32PG12. Ядро МК Cotex-M4, ОЗУ 256кБ, flash 1024кБ. Внутренний RC осциллятор тактирует до 38МГц, внешний до 40МГц. Вместе с SDK идет библиотека emlib, которая облегчает работу с периферией. Блоки периферии собраны в структуры, а поля структуры - это регистры блоков. Первое - инициализируем системные часы, а также dcdc регулятор, который оптимизирует энергопотребление: Код (C++): EMU_DCDCInit_TypeDef dcdcInit = EMU_DCDCINIT_DEFAULT; CMU_HFXOInit_TypeDef hfxoInit = CMU_HFXOINIT_DEFAULT; CHIP_Init(); EMU_DCDCInit(&dcdcInit); CMU_HFXOInit(&hfxoInit); CHIP_Init(); - функция, устраняющая найденные ошибки (errata). Она из набора SDK. Далее включаем тактирование. Нам необходимо включить системный такт и блок ввода-вывода: Код (C++): CMU_ClockEnable(cmuClock_HF, cmuSelect_HFXO); CMU_ClockEnable(cmuClock_GPIO, true); Теперь, когда GPIO тактируется, мы можем с ним поиграться. Светодиоды - а их на плате два - подключены к пинам 4 и 5 порта F. Следующей функцией мы переводим эти пины на выход в состоянии "1": Код (C++): GPIO_PinModeSet(gpioPortF, 4, gpioModePushPull, 1); GPIO_PinModeSet(gpioPortF, 5, gpioModePushPull, 1); Для реализации функции Delay(), нам необходимо настроить системный таймер на прерывание каждые 1мс: Код (C++): if (SysTick_Config(CMU_ClockFreqGet(cmuClock_CORE) / 1000)) { while (1); } Если что-то пошло не так, то дальнейшее выполнение программы бессмысленно, поэтому тупо повисаем. Ну а дальше мигаем светодиодами в бесконечном цикле: Код (C++): { GPIO_PinOutToggle(gpioPortF, 4); Delay(100); GPIO_PinOutToggle(gpioPortF, 5); Delay(1000); } Delay() самописная: Код (C++): void Delay (uint32_t dlyTicks) { uint32_t curTicks; curTicks = msTicks; while ((msTicks - curTicks) < dlyTicks); } msTicks объявлена глобально: Код (C++): volatile uint32_t msTicks; и наращиваем её прерывании: Код (C++): SysTick_Handler не забыть подключить заголовки: Код (C++): #include "em_chip.h" #include "em_gpio.h" #include "em_cmu.h" #include "em_emu.h" Это очень тупой подход, т.к. всё основное время процессор будет находится в функции Delay(). Но есть приложения, где именно такие ожидания являются нормой или даже необходимой мерой. Это операционная система. В нашем же случае это не просто операционная система, а операционная система реального времени или ОСРВ (в англоязычной литературе RTOS). Зачем оно? МК с таким ядром на борту будут нагружать многочисленными задачами, среди которых это и взаимодействие с пользователем, и общение по сетям, вывод на внешние устройства и прочее. Если программировать линейным кодом, то будет сложно сделать так, чтобы при обработке и малого и большого количества информации, все остальные задачи также успевали обрабатывать поступающую информацию, при этом не теряя её и не искажая.
Сорри, что-то я не увидел здесь RTOS. Вы уверены, что она присутствует? Что вот это мигание определено как задача (поток)? Терзают смутные сомнения...
Итак, давайте писать код, для которого функция ожидания жизненно необходима. Т.к. у меня SiLabs, то ОС будет Micrium. Это вытесняющая ОС. Т.е. в ней присутствует планировщик, который распределяет процессорное время между задачами в соответствии с их приоритетами. Для чего в таких задачах необходимы функции ожидания? Допустим, у нас две задачи с разными приоритетами и обе задачи полностью используют отведённое им время. В этом случае задача с низким приоритетом никогда не исполнится, т.к. всё время будет исполняться задача с высоким приоритетом. В такой ситуации есть два пути решения: давать задачам небольшую паузу, чтобы планировщик смог вставить в очередь все задачи; второй путь - использование карусели (round-robin). У нас задача совсем простая - помигать светодиодами. Т.к. период мигания у нас очень длинный по сравнению с тем, как планировщик переключает задачи, то будем использовать первый путь. Напишем функции наших задач. Одна задача будет переключать один светодиод, другая - второй. Код первой задачи: Код (C++): void LED0Tgl_TaskCode (void *p_arg) { RTOS_ERR err; const OS_TICK delay = 501; while (DEF_ON) { GPIO_PinOutToggle(gpioPortF, LED0_PIN); OSTimeDly(delay, OS_OPT_TIME_DLY, &err); if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE) { /// } } } OSTimeDly() - это наш delay(), который сообщает планировщику и приостановке исполнения задачи на время delay. Вторая задача аналогична первой Код (C++): void LED1Tgl_TaskCode (void *p_arg) { RTOS_ERR err; const OS_TICK delay = 500; while (DEF_ON) { GPIO_PinOutToggle(gpioPortF, LED1_PIN); OSTimeDly(delay, OS_OPT_TIME_DLY, &err); if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE) { /// } } } Теперь инициализируем наши GPIO ножки Код (C++): void InitGPIO (void) { CMU_ClockEnable(cmuClock_GPIO, true); GPIO_PinModeSet(gpioPortF, 4, gpioModePushPull, 1); GPIO_PinModeSet(gpioPortF, 5, gpioModePushPull, 1); } Не забываем, что в ARM архитектуре вся периферия сразу после подачи питания не тактируется. Поэтому необходимо выбрать источник тактирования и разрешить тактирование. Для того. чтобы планировщик в ОС исправно работал. ему необходим источник системных квантов. В micrium последней редакции для этого используется SleepTimer. В качестве SleepTimer на моём чипе используется таймер реального времени RTCC. А вот чтобы не писать длинную простыню инициализации этого таймера, в состав Micrium включена вспомогательная функция BSP_SystemInit() - она инициализирует не только таймер RTCC, но и главный системный тактовый сигнал. После этого необходимо инициализировать саму ОС, создать задачи и запустить их. Код (C++): int main (void) { RTOS_ERR err; BSP_SystemInit(); OSInit(&err); APP_RTOS_ASSERT_DBG((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), 1); InitGPIO(); CPU_Init(); OSTaskCreate(&LED0TGL_TASK_TCB, "Toggle LED0", LED0Tgl_TaskCode, (void*)0, LEDTGL_TASK_PRIORITY, &LED0TGL_TASK_STK[0], (LEDTGL_TASK_STK_SIZE/10), LEDTGL_TASK_STK_SIZE, 0, 0, (void*)0, OS_OPT_TASK_STK_CHK+OS_OPT_TASK_STK_CLR, &err); APP_RTOS_ASSERT_DBG((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), 1); OSTaskCreate(&LED1TGL_TASK_TCB, "Toggle LED1", LED1Tgl_TaskCode, (void*)0, LEDTGL_TASK_PRIORITY, &LED1TGL_TASK_STK[0], LEDTGL_TASK_STK_SIZE/10, LEDTGL_TASK_STK_SIZE, 0, 0, (void*)0, OS_OPT_TASK_STK_CHK+OS_OPT_TASK_STK_CLR, &err); OSStart(&err); APP_RTOS_ASSERT_DBG((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), 1); }
Посмотрим на энергопотребление. Видим, что в простое наш код потребляет аж 1.3 mA! По сути, это ничем не отличается от простого линейного программирования. которое было в примере выше. Чтобы снизить энергопотребление, необходимо МК перевести в один из режимов энергосбережения. Из коробки Micrium поддерживает все режимы вплоть до EM2. Вот его и будем использовать как самый энергоэффективный. Для того. чтобы в него попасть. необходимо поймать момент простоя системы. Для этого предусмотрен хук void OSIdleEnterHook(). Отлавливаем его и пишем код: Код (C++): void OSIdleEnterHook (void) { EMU_EnterEM2(true); } Смотрим теперь: 1 мкА в простое - это уже уровень PicoPower AVR, MSP430 с включенным вачдогом.
если правильно понимаю, тут не имеет значения. Сравнивается код тупой программы мигания диодами с кодом программы с ртос, где задержка реализуется временем процесса. И то, и то имеет место жить, но во 2-м случае код получается лаконичнее. Только паровоз не удержался и переключил своё внимание на потребление и получилось не сравнение задержек в ртос и без ртос, а сравнение потреблений) но уверен, статья ещё не знакончена (выводов нет). Имхо, если я правильно понял значение OSTimeDly.
Возникает вполне резонный вопрос: раз у нас ОС, то как посмотреть на параметры наших задач в процессе исполнения? Ведь в любой ОС есть такой инспектор: "диспетчер задач", "top/htop". Ну так и для нашей RTOS такой инструмент найдётся. Один из них - uc/Probe. Он платный. Но любезно работает бесплатно аж целую минуту. Загружаем, кидаем интерактивный элемент Micrium OS Kernel на поле рабочей области и жмём кнопку RUN. И получаем данные для изучения: Первые две задачи - это наши. Видим, что требуемый и используемый размер стек составляет 80 байт на задачу. Разумеется, я уже запускал этот инструмент, "подсмотрел" и вписал в код необходимый размер стека. Ниже идет задача системного тика - по этому тику планировщик переключает контексты (задачи). Размер стека для этой задачи жестко определён по умолчанию. Но его можно изменить. Правда для этого необходимо написать побольше кода, в котором необходимо переопределить абсолютно все параметры. Ну и последняя задача служебная - она обеспечивает вывод данных наружу для обеспечения работы нашего диспетчера задач. В Release её можно убрать, чтобы не отнимала время. Среди прочих параметров видим загрузку процессора - 0.31%!
При кооперативной многозадачности планировщик максимум что может сделать, так это определить порядок выполнения задач, а за процессорным временем следить не сможет. Т.к. при такой многозадачности любая задача возьмёт столько процессорного времени, сколько пожелает. Но судя по наличию Kernel's Timer Task похоже, что Паровозз чего-то напутал...