
本期主角:cola_os,它是一個(gè)300多行代碼實(shí)現(xiàn)的多任務(wù)管理的OS,在很多MCU開發(fā)中,功能很簡(jiǎn)單,實(shí)時(shí)性要求不強(qiáng),任務(wù)多了管理不當(dāng)又很亂。
如果使用RTOS顯得太浪費(fèi),這時(shí)候可以嘗試使用使用cola_os這類基于軟件定時(shí)器實(shí)現(xiàn)的時(shí)間片輪詢框架。
倉(cāng)庫(kù)鏈接:
https://gitee.com/schuck/cola_os
license:MulanPSL-1.0(木蘭寬松許可證, 第1版)。
cola_os是一份簡(jiǎn)潔明了的代碼,包含很多有用的編程思想,值得通讀。下面我們一起來(lái)學(xué)習(xí)一下:
其實(shí)關(guān)于cola_os其實(shí)我們前幾天的推文中也有做介紹。今天我們?cè)僖黄饋?lái)完整地梳理一遍。
cola_os目前的內(nèi)容如:


cola_os就是cola_os的任務(wù)管理模塊。任務(wù)使用鏈表進(jìn)行管理,其數(shù)據(jù)結(jié)構(gòu)如:
typedef?void?(*cbFunc)(uint32_t?event);
typedef?struct?task_s
{
????uint8_t?????timerNum;????//定時(shí)編號(hào)
????uint32_t????period;??????//定時(shí)周期
????bool????????oneShot;?????//true只執(zhí)行一次
????bool????????start;???????//開始啟動(dòng)
????uint32_t????timerTick;???//定時(shí)計(jì)數(shù)
????bool????????run;?????????//任務(wù)運(yùn)行標(biāo)志?
????bool????????taskFlag;????//任務(wù)標(biāo)志是主任務(wù)還是定時(shí)任務(wù)
????uint32_t????event;???????//驅(qū)動(dòng)事件??
????cbFunc??????func;????????//回調(diào)函數(shù)
????struct?task_s?*next;
}task_t;
每創(chuàng)建一個(gè)任務(wù)嗎,就是往任務(wù)鏈表中插入一個(gè)任務(wù)節(jié)點(diǎn)。
其創(chuàng)建任務(wù)的方法有兩種:
兩種方式創(chuàng)建,都是會(huì)在while(1)循環(huán)中調(diào)度執(zhí)行任務(wù)函數(shù)。
我們可以看看cola_task_loop任務(wù)遍歷函數(shù),這個(gè)函數(shù)最終是要放在主函數(shù)while(1)中調(diào)用的。其內(nèi)容如:
void?cola_task_loop(void)
{
????uint32_t?events;
????task_t?*cur??=?task_list;
????OS_CPU_SR?cpu_sr;
????
????while(?cur?!=?NULL?)
????{
????????if(cur->run)
????????{
????????????if(NULL?!=cur->func)
????????????{
????????????????events?=?cur->event;
????????????????if(events)
????????????????{
????????????????????enter_critical();
????????????????????cur->event?=?0;
????????????????????exit_critical();
????????????????}
????????????????cur->func(events);????????????????
????????????}
????????????if(TASK_TIMER?==?cur->taskFlag)
????????????{
????????????????enter_critical();
????????????????cur->run?=?false;
????????????????exit_critical();
????????????}
????????????if((cur->oneShot)&&(TASK_TIMER?==?cur->taskFlag))
????????????{
???????????????cur->start?=?false;?
????????????}
????????}
????????cur?=?cur->next;
????}
}
兩種方式創(chuàng)建的任務(wù)都會(huì)在cur->func(events);被調(diào)用。不同的就是:遍歷執(zhí)行到定時(shí)任務(wù)時(shí),需要清掉定時(shí)相關(guān)標(biāo)志。
其中,events作為任務(wù)函數(shù)的參數(shù)傳入。從cola_task_loop可以看到,事件并未使用到,events無(wú)論真還是假,在執(zhí)行任務(wù)函數(shù)前,都被清零了。events的功能應(yīng)該是作者預(yù)留的。
創(chuàng)建任務(wù)很簡(jiǎn)單,比如創(chuàng)建一個(gè)定時(shí)任務(wù):
static?task_t?timer_500ms;
//每500ms執(zhí)行一次
static?void?timer_500ms_cb(uint32_t?event)
{
????printf("task0?running...\n");
}
cola_timer_create(&timer_500ms,?timer_500ms_cb);
cola_timer_start(&timer_500ms,?TIMER_ALWAYS,?500);
cola_os是基于軟件定時(shí)器來(lái)進(jìn)行任務(wù)調(diào)度管理的,需要一個(gè)硬件定時(shí)器提供時(shí)基。比如使用系統(tǒng)滴答定時(shí)器,配置為1ms中斷一次。
在1ms中斷中不斷輪詢判斷定時(shí)計(jì)數(shù)是否到達(dá)定時(shí)時(shí)間:
void?SysTick_Handler(void)
{
??cola_timer_ticker();
}
void?cola_timer_ticker(void)
{
????task_t?*cur??=?task_list;
????OS_CPU_SR?cpu_sr;
????while(?cur?!=?NULL?)
????{
????????if((TASK_TIMER?==?cur->taskFlag)&&?cur->start)
????????{
????????????if(++cur->timerTick?>=?cur->period)
????????????{
????????????????cur->timerTick?=?0;
????????????????if(cur->func?!=?NULL)
????????????????{
????????????????????enter_critical();???????????????????
????????????????????cur->run?=?true;
????????????????????exit_critical();
????????????????}
????????????}
????????}
????????cur?=?cur->next;
????}
}
如果到了則將標(biāo)志cur->run置位,在while大循環(huán)中的cola_task_loop函數(shù)中如果檢測(cè)到該標(biāo)志就執(zhí)行該任務(wù)函數(shù)。
cola_device是硬件抽象層,使用鏈表來(lái)管理各個(gè)設(shè)備。其借鑒了RT-Thread及Linux相關(guān)驅(qū)動(dòng)框架思想。大致內(nèi)容如:
數(shù)據(jù)結(jié)構(gòu)如:
typedef?struct?cola_device??cola_device_t;
struct?cola_device_ops
{
????int??(*init)???(cola_device_t?*dev);
????int??(*open)???(cola_device_t?*dev,?int?oflag);
????int??(*close)??(cola_device_t?*dev);
????int??(*read)???(cola_device_t?*dev,?int?pos,?void?*buffer,?int?size);
????int??(*write)??(cola_device_t?*dev,?int?pos,?const?void?*buffer,?int?size);
????int??(*control)(cola_device_t?*dev,?int?cmd,?void?*args);
};
struct?cola_device
{
????const?char?*?name;
????struct?cola_device_ops?*dops;
????struct?cola_device?*next;
};
硬件抽象層的接口如:
/*
????驅(qū)動(dòng)注冊(cè)
*/
int?cola_device_register(cola_device_t?*dev);
/*
????驅(qū)動(dòng)查找
*/
cola_device_t?*cola_device_find(const?char?*name);
/*
????驅(qū)動(dòng)讀
*/
int?cola_device_read(cola_device_t?*dev,??int?pos,?void?*buffer,?int?size);
/*
????驅(qū)動(dòng)寫
*/
int?cola_device_write(cola_device_t?*dev,?int?pos,?const?void?*buffer,?int?size);
/*
????驅(qū)動(dòng)控制
*/
int?cola_device_ctrl(cola_device_t?*dev,??int?cmd,?void?*arg);
首先,在驅(qū)動(dòng)層注冊(cè)好設(shè)備,把操作設(shè)備的函數(shù)指針及設(shè)備名稱插入到設(shè)備鏈表中:
static?cola_device_t?led_dev;
static?void?led_gpio_init(void)
{
????GPIO_InitTypeDef?GPIO_InitStructure;
????RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC,?ENABLE);
????GPIO_InitStructure.GPIO_Pin?=?PIN_GREENLED;????????????????????????????
????GPIO_InitStructure.GPIO_Mode?=?GPIO_Mode_OUT;?????????????????????
????GPIO_InitStructure.GPIO_Speed?=?GPIO_Speed_50MHz;??????????????????
????GPIO_InitStructure.GPIO_OType?=?GPIO_OType_PP;?????????????????????
????GPIO_InitStructure.GPIO_PuPd?=?GPIO_PuPd_NOPULL;??????????????????
????GPIO_Init(PORT_GREEN_LED,?&GPIO_InitStructure);
????LED_GREEN_OFF;
}
static?int?led_ctrl(cola_device_t?*dev,?int?cmd,?void?*args)
{
????if(LED_TOGGLE?==?cmd)
????{
????????LED_GREEN_TOGGLE;
????}
????else?
????{
????????
????}
????return?1;
}
static?struct?cola_device_ops?ops?=
{
????.control?=?led_ctrl,
};
static?void?led_register(void)
{
????led_gpio_init();
????led_dev.dops?=?&ops;
????led_dev.name?=?"led";
????cola_device_register(&led_dev);
}
cola_device_register函數(shù)如:
int?cola_device_register(cola_device_t?*dev)
{
????if((NULL?==?dev)?||?(cola_device_is_exists(dev)))
????{
????????return?0;
????}
????if((NULL?==?dev->name)?||??(NULL?==?dev->dops))
????{
????????return?0;
????}
????return?device_list_inster(dev);
}
驅(qū)動(dòng)注冊(cè)好設(shè)備之后,應(yīng)用層就可以根據(jù)設(shè)備名稱來(lái)查找設(shè)備是否被注冊(cè),如果已經(jīng)注冊(cè)則可以調(diào)用設(shè)備操作接口操控設(shè)備。比如創(chuàng)建一個(gè)定時(shí)任務(wù)定時(shí)反轉(zhuǎn)led:
void?app_init(void)
{
????app_led_dev?=?cola_device_find("led");
????assert(app_led_dev);
????cola_timer_create(&timer_500ms,timer_500ms_cb);
????cola_timer_start(&timer_500ms,TIMER_ALWAYS,500);
}
static?void?timer_500ms_cb(uint32_t?event)
{
????cola_device_ctrl(app_led_dev,LED_TOGGLE,0);
}
cola_init是一個(gè)自動(dòng)初始化模塊,模仿Linux的initcall機(jī)制。RT-Thread也有實(shí)現(xiàn)這個(gè)功能:

一般的,我們的初始化在主函數(shù)中調(diào)用,如:

有了自動(dòng)初始化模塊,可以不在主函數(shù)中調(diào)用,例如:
void?SystemClock_Config(void)
{
}
pure_initcall(SystemClock_Config);
這樣也可以調(diào)用SystemClock_Config。pure_initcall如:
#define??__used??__attribute__((__used__))
typedef?void?(*initcall_t)(void);
#define?__define_initcall(fn,?id)?\
????static?const?initcall_t?__initcall_##fn##id?__used?\
????__attribute__((__section__("initcall"?#id?"init")))?=?fn;?
#define?pure_initcall(fn)???????__define_initcall(fn,?0)?//可用作系統(tǒng)時(shí)鐘初始化??
#define?fs_initcall(fn)?????????__define_initcall(fn,?1)?//tick和調(diào)試接口初始化
#define?device_initcall(fn)?????__define_initcall(fn,?2)?//驅(qū)動(dòng)初始化
#define?late_initcall(fn)???????__define_initcall(fn,?3)?//其他初始化
在cola_init中,首先是調(diào)用不同順序級(jí)別的__define_initcall宏來(lái)把函數(shù)指針fn放入到自定義的指定的段中。各個(gè)需要自動(dòng)初始化的函數(shù)放到指定的段中,形成一張初始化函數(shù)表。
__ attribute __ (( __ section __)) 關(guān)鍵字就是用來(lái)指定數(shù)據(jù)存放段。
do_init_call函數(shù)在我們程序起始時(shí)調(diào)用,比如在bsp_init中調(diào)用:
void?bsp_init(void)
{
????do_init_call();
}
do_init_call里做的事情就是遍歷初始化函數(shù)表里的函數(shù):
void?do_init_call(void)
{
????extern?initcall_t?initcall0init$$Base[];
????extern?initcall_t?initcall0init$$Limit[];
????extern?initcall_t?initcall1init$$Base[];
????extern?initcall_t?initcall1init$$Limit[];
????extern?initcall_t?initcall2init$$Base[];
????extern?initcall_t?initcall2init$$Limit[];
????extern?initcall_t?initcall3init$$Base[];
????extern?initcall_t?initcall3init$$Limit[];
????
????initcall_t?*fn;
????
????for?(fn?=?initcall0init$$Base;
????????????fn?????????????fn++)
????{
????????if(fn)
????????????(*fn)();
????}
????
????for?(fn?=?initcall1init$$Base;
????????????fn?????????????fn++)
????{
????????if(fn)
????????????(*fn)();
????}
????
????for?(fn?=?initcall2init$$Base;
????????????fn?????????????fn++)
????{
????????if(fn)
????????????(*fn)();
????}
????
????for?(fn?=?initcall3init$$Base;
????????????fn?????????????fn++)
????{
????????if(fn)
????????????(*fn)();
????}
}
這里有 initcall0init $$ Base 及 initcall0init Limit這幾個(gè)initcall_t類型的函數(shù)指針數(shù)組的聲明。它們事先是調(diào)用__define_initcall把函數(shù)指針fn放入到自定義的指定的段.initcall0init、.initcall1init、.initcall2init、.initcall3init。
initcall0init$$Base與initcall0init$$Limit按照我的理解就是各個(gè)初始化函數(shù)表的開始及結(jié)束地址。從而實(shí)現(xiàn)遍歷:
????for?(fn?=?initcall0init$$Base;
????????????fn?????????????fn++)
????{
????????if(fn)
????????????(*fn)();
????}
例如RT-Thread里的實(shí)現(xiàn)也是類似的:
????volatile?const?init_fn_t?*fn_ptr;
????for?(fn_ptr?=?&__rt_init_rti_board_start;?fn_ptr?&__rt_init_rti_board_end;?fn_ptr++)
????{
????????(*fn_ptr)();
????}
關(guān)于init自動(dòng)初始化機(jī)制大致就分析這些。
cola_os包含有cola_os任務(wù)管理、cola_device硬件抽象層及cola_init自動(dòng)初始化三大塊,這三塊內(nèi)容其實(shí)可以單獨(dú)抽出來(lái)學(xué)習(xí)、使用。
下面我們基于小熊派IOT開發(fā)板來(lái)簡(jiǎn)單實(shí)踐實(shí)踐。

我們創(chuàng)建兩個(gè)定時(shí)任務(wù):
main.c:
/*?Private?variables?---------------------------------------------------------*/
static?task_t?timer_500ms;
static?task_t?timer_1000ms;
/*?USER?CODE?END?PV?*/
/*?Private?function?prototypes?-----------------------------------------------*/
void?SystemClock_Config(void);
/*?USER?CODE?BEGIN?PFP?*/
/*?Private?function?prototypes?-----------------------------------------------*/
/*?USER?CODE?END?PFP?*/
/*?USER?CODE?BEGIN?0?*/
//每500ms執(zhí)行一次
static?void?timer_500ms_cb(uint32_t?event)
{
????printf("task0?running...\n");
}
//每1000ms執(zhí)行一次
static?void?timer_1000ms_cb(uint32_t?event)
{
????printf("task1?running...\n");
}
int?main(void)
{
??/*?USER?CODE?BEGIN?1?*/
?
??/*?USER?CODE?END?1?*/
??/*?MCU?Configuration----------------------------------------------------------*/
??/*?Reset?of?all?peripherals,?Initializes?the?Flash?interface?and?the?Systick.?*/
??HAL_Init();
??/*?USER?CODE?BEGIN?Init?*/
??/*?USER?CODE?END?Init?*/
??/*?Configure?the?system?clock?*/
//??SystemClock_Config();
??/*?USER?CODE?BEGIN?SysInit?*/
??/*?USER?CODE?END?SysInit?*/
??/*?Initialize?all?configured?peripherals?*/
??MX_GPIO_Init();
??MX_DMA_Init();
??MX_USART1_UART_Init();
??/*?USER?CODE?BEGIN?2?*/
??printf("微信公眾號(hào):嵌入式大雜燴\r\n");
??printf("cola_os?test!\r\n");
??
??cola_timer_create(&timer_500ms,timer_500ms_cb);
??cola_timer_start(&timer_500ms,TIMER_ALWAYS,500);
??cola_timer_create(&timer_1000ms,timer_1000ms_cb);
??cola_timer_start(&timer_1000ms,TIMER_ALWAYS,1000);
??/*?USER?CODE?END?2?*/
??/*?Infinite?loop?*/
??/*?USER?CODE?BEGIN?WHILE?*/
??while?(1)
??{
??/*?USER?CODE?END?WHILE?*/
??/*?USER?CODE?BEGIN?3?*/
????cola_task_loop();
??}
??/*?USER?CODE?END?3?*/
}
/**
??*?@brief?System?Clock?Configuration
??*?@retval?None
??*/
void?SystemClock_Config(void)
{
??RCC_OscInitTypeDef?RCC_OscInitStruct;
??RCC_ClkInitTypeDef?RCC_ClkInitStruct;
??RCC_PeriphCLKInitTypeDef?PeriphClkInit;
????/**Initializes?the?CPU,?AHB?and?APB?busses?clocks?
????*/
??RCC_OscInitStruct.OscillatorType?=?RCC_OSCILLATORTYPE_MSI;
??RCC_OscInitStruct.MSIState?=?RCC_MSI_ON;
??RCC_OscInitStruct.MSICalibrationValue?=?0;
??RCC_OscInitStruct.MSIClockRange?=?RCC_MSIRANGE_6;
??RCC_OscInitStruct.PLL.PLLState?=?RCC_PLL_ON;
??RCC_OscInitStruct.PLL.PLLSource?=?RCC_PLLSOURCE_MSI;
??RCC_OscInitStruct.PLL.PLLM?=?1;
??RCC_OscInitStruct.PLL.PLLN?=?40;
??RCC_OscInitStruct.PLL.PLLP?=?RCC_PLLP_DIV7;
??RCC_OscInitStruct.PLL.PLLQ?=?RCC_PLLQ_DIV2;
??RCC_OscInitStruct.PLL.PLLR?=?RCC_PLLR_DIV2;
??if?(HAL_RCC_OscConfig(&RCC_OscInitStruct)?!=?HAL_OK)
??{
????_Error_Handler(__FILE__,?__LINE__);
??}
????/**Initializes?the?CPU,?AHB?and?APB?busses?clocks?
????*/
??RCC_ClkInitStruct.ClockType?=?RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
??????????????????????????????|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
??RCC_ClkInitStruct.SYSCLKSource?=?RCC_SYSCLKSOURCE_PLLCLK;
??RCC_ClkInitStruct.AHBCLKDivider?=?RCC_SYSCLK_DIV1;
??RCC_ClkInitStruct.APB1CLKDivider?=?RCC_HCLK_DIV1;
??RCC_ClkInitStruct.APB2CLKDivider?=?RCC_HCLK_DIV1;
??if?(HAL_RCC_ClockConfig(&RCC_ClkInitStruct,?FLASH_LATENCY_4)?!=?HAL_OK)
??{
????_Error_Handler(__FILE__,?__LINE__);
??}
??PeriphClkInit.PeriphClockSelection?=?RCC_PERIPHCLK_USART1;
??PeriphClkInit.Usart1ClockSelection?=?RCC_USART1CLKSOURCE_PCLK2;
??if?(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit)?!=?HAL_OK)
??{
????_Error_Handler(__FILE__,?__LINE__);
??}
????/**Configure?the?main?internal?regulator?output?voltage?
????*/
??if?(HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1)?!=?HAL_OK)
??{
????_Error_Handler(__FILE__,?__LINE__);
??}
????/**Configure?the?Systick?interrupt?time?
????*/
??HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
????/**Configure?the?Systick?
????*/
??HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
??/*?SysTick_IRQn?interrupt?configuration?*/
??HAL_NVIC_SetPriority(SysTick_IRQn,?0,?0);
}
pure_initcall(SystemClock_Config);
SysTick_Handler:
void?SysTick_Handler(void)
{
??/*?USER?CODE?BEGIN?SysTick_IRQn?0?*/
??/*?USER?CODE?END?SysTick_IRQn?0?*/
??cola_timer_ticker();
??HAL_IncTick();
??HAL_SYSTICK_IRQHandler();
??/*?USER?CODE?BEGIN?SysTick_IRQn?1?*/
??/*?USER?CODE?END?SysTick_IRQn?1?*/
}
編譯、下載、運(yùn)行:

從運(yùn)行結(jié)果可以看到,task1的定時(shí)周期是task0的兩倍,符合預(yù)期。
聲明:本文素材來(lái)源網(wǎng)絡(luò),版權(quán)歸原作者所有。如涉及作品版權(quán)問題,請(qǐng)與我聯(lián)系刪除。
------------?END?------------

●專欄《嵌入式工具》
●專欄《嵌入式開發(fā)》
●專欄《Keil教程》
●嵌入式專欄精選教程
關(guān)注公眾號(hào)回復(fù)“加群”按規(guī)則加入技術(shù)交流群,回復(fù)“1024”查看更多內(nèi)容。

