正文
本期主角:cola_os
cola_os是一個300多行代碼實現(xiàn)的多任務(wù)管理的OS,在很多MCU開發(fā)中,功能很簡單,實時性要求不強,任務(wù)多了管理不當(dāng)又很亂。
如果使用RTOS顯得太浪費,這時候可以嘗試使用使用cola_os這類基于軟件定時器實現(xiàn)的時間片輪詢框架。
倉庫鏈接:
https://gitee.com/schuck/cola_os
license:MulanPSL-1.0(木蘭寬松許可證, 第1版)。
cola_os是一份簡潔明了的代碼,包含很多有用的編程思想,值得通讀。下面我們一起來學(xué)習(xí)一下:
cola_os的分析及使用
其實關(guān)于cola_os其實我們前幾天的推文中也有做介紹。今天我們再一起來完整地梳理一遍。
cola_os目前的內(nèi)容如:
1、cola_os
cola_os就是cola_os的任務(wù)管理模塊。任務(wù)使用鏈表進行管理,其數(shù)據(jù)結(jié)構(gòu)如:
typedefvoid(*cbFunc)(uint32_tevent);
typedefstructtask_s
{
uint8_ttimerNum;//定時編號
uint32_tperiod;//定時周期
booloneShot;//true只執(zhí)行一次
boolstart;//開始啟動
uint32_ttimerTick;//定時計數(shù)
boolrun;//任務(wù)運行標(biāo)志
booltaskFlag;//任務(wù)標(biāo)志是主任務(wù)還是定時任務(wù)
uint32_tevent;//驅(qū)動事件
cbFuncfunc;//回調(diào)函數(shù)
structtask_s*next;
}task_t;
每創(chuàng)建一個任務(wù)嗎,就是往任務(wù)鏈表中插入一個任務(wù)節(jié)點。
其創(chuàng)建任務(wù)的方法有兩種:
- 創(chuàng)建主循環(huán)任務(wù)
- 創(chuàng)建定時任務(wù)
兩種方式創(chuàng)建,都是會在while(1)循環(huán)中調(diào)度執(zhí)行任務(wù)函數(shù)。
我們可以看看cola_task_loop任務(wù)遍歷函數(shù),這個函數(shù)最終是要放在主函數(shù)while(1)中調(diào)用的。其內(nèi)容如:
voidcola_task_loop(void)
{
uint32_tevents;
task_t*cur=task_list;
OS_CPU_SRcpu_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ù)都會在cur->func(events);被調(diào)用。不同的就是:遍歷執(zhí)行到定時任務(wù)時,需要清掉定時相關(guān)標(biāo)志。
其中,events作為任務(wù)函數(shù)的參數(shù)傳入。從cola_task_loop可以看到,事件并未使用到,events無論真還是假,在執(zhí)行任務(wù)函數(shù)前,都被清零了。events的功能應(yīng)該是作者預(yù)留的。
創(chuàng)建任務(wù)很簡單,比如創(chuàng)建一個定時任務(wù):
statictask_ttimer_500ms;
//每500ms執(zhí)行一次
staticvoidtimer_500ms_cb(uint32_tevent)
{
printf("task0running...
");
}
cola_timer_create(&timer_500ms,timer_500ms_cb);
cola_timer_start(&timer_500ms,TIMER_ALWAYS,500);
cola_os是基于軟件定時器來進行任務(wù)調(diào)度管理的,需要一個硬件定時器提供時基。比如使用系統(tǒng)滴答定時器,配置為1ms中斷一次。
在1ms中斷中不斷輪詢判斷定時計數(shù)是否到達定時時間:
voidSysTick_Handler(void)
{
cola_timer_ticker();
}
voidcola_timer_ticker(void)
{
task_t*cur=task_list;
OS_CPU_SRcpu_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ù)中如果檢測到該標(biāo)志就執(zhí)行該任務(wù)函數(shù)。
2、cola_device
cola_device是硬件抽象層,使用鏈表來管理各個設(shè)備。其借鑒了RT-Thread及Linux相關(guān)驅(qū)動框架思想。大致內(nèi)容如:
數(shù)據(jù)結(jié)構(gòu)如:
typedefstructcola_devicecola_device_t;
structcola_device_ops
{
int(*init)(cola_device_t*dev);
int(*open)(cola_device_t*dev,intoflag);
int(*close)(cola_device_t*dev);
int(*read)(cola_device_t*dev,intpos,void*buffer,intsize);
int(*write)(cola_device_t*dev,intpos,constvoid*buffer,intsize);
int(*control)(cola_device_t*dev,intcmd,void*args);
};
structcola_device
{
constchar*name;
structcola_device_ops*dops;
structcola_device*next;
};
硬件抽象層的接口如:
/*
驅(qū)動注冊
*/
intcola_device_register(cola_device_t*dev);
/*
驅(qū)動查找
*/
cola_device_t*cola_device_find(constchar*name);
/*
驅(qū)動讀
*/
intcola_device_read(cola_device_t*dev,intpos,void*buffer,intsize);
/*
驅(qū)動寫
*/
intcola_device_write(cola_device_t*dev,intpos,constvoid*buffer,intsize);
/*
驅(qū)動控制
*/
intcola_device_ctrl(cola_device_t*dev,intcmd,void*arg);
首先,在驅(qū)動層注冊好設(shè)備,把操作設(shè)備的函數(shù)指針及設(shè)備名稱插入到設(shè)備鏈表中:
staticcola_device_tled_dev;
staticvoidled_gpio_init(void)
{
GPIO_InitTypeDefGPIO_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;
}
staticintled_ctrl(cola_device_t*dev,intcmd,void*args)
{
if(LED_TOGGLE==cmd)
{
LED_GREEN_TOGGLE;
}
else
{
}
return1;
}
staticstructcola_device_opsops=
{
.control=led_ctrl,
};
staticvoidled_register(void)
{
led_gpio_init();
led_dev.dops=&ops;
led_dev.name="led";
cola_device_register(&led_dev);
}
cola_device_register函數(shù)如:
intcola_device_register(cola_device_t*dev)
{
if((NULL==dev)||(cola_device_is_exists(dev)))
{
return0;
}
if((NULL==dev->name)||(NULL==dev->dops))
{
return0;
}
returndevice_list_inster(dev);
}
驅(qū)動注冊好設(shè)備之后,應(yīng)用層就可以根據(jù)設(shè)備名稱來查找設(shè)備是否被注冊,如果已經(jīng)注冊則可以調(diào)用設(shè)備操作接口操控設(shè)備。比如創(chuàng)建一個定時任務(wù)定時反轉(zhuǎn)led:
voidapp_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);
}
staticvoidtimer_500ms_cb(uint32_tevent)
{
cola_device_ctrl(app_led_dev,LED_TOGGLE,0);
}
3、cola_init
cola_init是一個自動初始化模塊,模仿Linux的initcall機制。RT-Thread也有實現(xiàn)這個功能:
一般的,我們的初始化在主函數(shù)中調(diào)用,如:
有了自動初始化模塊,可以不在主函數(shù)中調(diào)用,例如:
voidSystemClock_Config(void)
{
}
pure_initcall(SystemClock_Config);
這樣也可以調(diào)用SystemClock_Config。pure_initcall如:
#define__used__attribute__((__used__))
typedefvoid(*initcall_t)(void);
#define__define_initcall(fn,id)
staticconstinitcall_t__initcall_##fn##id__used
__attribute__((__section__("initcall"#id"init")))=fn;
#definepure_initcall(fn)__define_initcall(fn,0)//可用作系統(tǒng)時鐘初始化
#definefs_initcall(fn)__define_initcall(fn,1)//tick和調(diào)試接口初始化
#definedevice_initcall(fn)__define_initcall(fn,2)//驅(qū)動初始化
#definelate_initcall(fn)__define_initcall(fn,3)//其他初始化
在cola_init中,首先是調(diào)用不同順序級別的__define_initcall宏來把函數(shù)指針fn放入到自定義的指定的段中。各個需要自動初始化的函數(shù)放到指定的段中,形成一張初始化函數(shù)表。
__ attribute __ (( __ section __)) 關(guān)鍵字就是用來指定數(shù)據(jù)存放段。
do_init_call函數(shù)在我們程序起始時調(diào)用,比如在bsp_init中調(diào)用:
voidbsp_init(void)
{
do_init_call();
}
do_init_call里做的事情就是遍歷初始化函數(shù)表里的函數(shù):
voiddo_init_call(void)
{
externinitcall_tinitcall0init$$Base[];
externinitcall_tinitcall0init$$Limit[];
externinitcall_tinitcall1init$$Base[];
externinitcall_tinitcall1init$$Limit[];
externinitcall_tinitcall2init$$Base[];
externinitcall_tinitcall2init$$Limit[];
externinitcall_tinitcall3init$$Base[];
externinitcall_tinitcall3init$$Limit[];
initcall_t*fn;
for(fn=initcall0init$$Base;
fnif(fn)
(*fn)();
}
for(fn=initcall1init$$Base;
fnif(fn)
(*fn)();
}
for(fn=initcall2init$$Base;
fnif(fn)
(*fn)();
}
for(fn=initcall3init$$Base;
fnif(fn)
(*fn)();
}
}
這里有 initcall0init $$ Base 及 initcall0init Limit這幾個initcall_t類型的函數(shù)指針數(shù)組的聲明。它們事先是調(diào)用__define_initcall把函數(shù)指針fn放入到自定義的指定的段.initcall0init、.initcall1init、.initcall2init、.initcall3init。
initcall0init$$Base與initcall0init$$Limit按照我的理解就是各個初始化函數(shù)表的開始及結(jié)束地址。從而實現(xiàn)遍歷:
for(fn=initcall0init$$Base;
fnif(fn)
(*fn)();
}
例如RT-Thread里的實現(xiàn)也是類似的:
volatileconstinit_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自動初始化機制大致就分析這些。
cola_os包含有cola_os任務(wù)管理、cola_device硬件抽象層及cola_init自動初始化三大塊,這三塊內(nèi)容其實可以單獨抽出來學(xué)習(xí)、使用。
4、cola_os的使用
我們創(chuàng)建兩個定時任務(wù):
- task0任務(wù):定時500ms打印一次。
- task1任務(wù):定時1000ms打印一次。
main.c:
/*Privatevariables---------------------------------------------------------*/
statictask_ttimer_500ms;
statictask_ttimer_1000ms;
/*USERCODEENDPV*/
/*Privatefunctionprototypes-----------------------------------------------*/
voidSystemClock_Config(void);
/*USERCODEBEGINPFP*/
/*Privatefunctionprototypes-----------------------------------------------*/
/*USERCODEENDPFP*/
/*USERCODEBEGIN0*/
//每500ms執(zhí)行一次
staticvoidtimer_500ms_cb(uint32_tevent)
{
printf("task0running...
");
}
//每1000ms執(zhí)行一次
staticvoidtimer_1000ms_cb(uint32_tevent)
{
printf("task1running...
");
}
intmain(void)
{
/*USERCODEBEGIN1*/
/*USERCODEEND1*/
/*MCUConfiguration----------------------------------------------------------*/
/*Resetofallperipherals,InitializestheFlashinterfaceandtheSystick.*/
HAL_Init();
/*USERCODEBEGINInit*/
/*USERCODEENDInit*/
/*Configurethesystemclock*/
//SystemClock_Config();
/*USERCODEBEGINSysInit*/
/*USERCODEENDSysInit*/
/*Initializeallconfiguredperipherals*/
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/*USERCODEBEGIN2*/
printf("微信公眾號:嵌入式大雜燴
");
printf("cola_ostest!
");
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);
/*USERCODEEND2*/
/*Infiniteloop*/
/*USERCODEBEGINWHILE*/
while(1)
{
/*USERCODEENDWHILE*/
/*USERCODEBEGIN3*/
cola_task_loop();
}
/*USERCODEEND3*/
}
/**
*@briefSystemClockConfiguration
*@retvalNone
*/
voidSystemClock_Config(void)
{
RCC_OscInitTypeDefRCC_OscInitStruct;
RCC_ClkInitTypeDefRCC_ClkInitStruct;
RCC_PeriphCLKInitTypeDefPeriphClkInit;
/**InitializestheCPU,AHBandAPBbussesclocks
*/
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__);
}
/**InitializestheCPU,AHBandAPBbussesclocks
*/
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__);
}
/**Configurethemaininternalregulatoroutputvoltage
*/
if(HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1)!=HAL_OK)
{
_Error_Handler(__FILE__,__LINE__);
}
/**ConfiguretheSystickinterrupttime
*/
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
/**ConfiguretheSystick
*/
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/*SysTick_IRQninterruptconfiguration*/
HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
}
pure_initcall(SystemClock_Config);
SysTick_Handler:
voidSysTick_Handler(void)
{
/*USERCODEBEGINSysTick_IRQn0*/
/*USERCODEENDSysTick_IRQn0*/
cola_timer_ticker();
HAL_IncTick();
HAL_SYSTICK_IRQHandler();
/*USERCODEBEGINSysTick_IRQn1*/
/*USERCODEENDSysTick_IRQn1*/
}
編譯、下載、運行:
從運行結(jié)果可以看到,task1的定時周期是task0的兩倍,符合預(yù)期。
審核編輯:湯梓紅
-
mcu
+關(guān)注
關(guān)注
146文章
17019瀏覽量
350373 -
RTOS
+關(guān)注
關(guān)注
22文章
809瀏覽量
119451 -
代碼
+關(guān)注
關(guān)注
30文章
4753瀏覽量
68368
原文標(biāo)題:300多行代碼實現(xiàn)的一個多任務(wù)OS
文章出處:【微信號:最后一個bug,微信公眾號:最后一個bug】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論