本期主角: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í)間片輪詢框架。
license:MulanPSL-1.0(木蘭寬松許可證, 第1版)。
cola_os是一份簡(jiǎn)潔明了的代碼,包含很多有用的編程思想,值得通讀。下面我們一起來(lái)學(xué)習(xí)一下:
cola_os的分析及使用
其實(shí)關(guān)于cola_os其實(shí)我們前幾天的推文中也有做介紹。今天我們?cè)僖黄饋?lái)完整地梳理一遍。
cola_os目前的內(nèi)容如:
1、cola_os
cola_os就是cola_os的任務(wù)管理模塊。任務(wù)使用鏈表進(jìn)行管理,其數(shù)據(jù)結(jié)構(gòu)如:
typedefvoid(*cbFunc)(uint32_tevent); typedefstructtask_s { uint8_ttimerNum;//定時(shí)編號(hào) uint32_tperiod;//定時(shí)周期 booloneShot;//true只執(zhí)行一次 boolstart;//開始啟動(dòng) uint32_ttimerTick;//定時(shí)計(jì)數(shù) boolrun;//任務(wù)運(yùn)行標(biāo)志 booltaskFlag;//任務(wù)標(biāo)志是主任務(wù)還是定時(shí)任務(wù) uint32_tevent;//驅(qū)動(dòng)事件 cbFuncfunc;//回調(diào)函數(shù) structtask_s*next; }task_t;
每創(chuàng)建一個(gè)任務(wù)嗎,就是往任務(wù)鏈表中插入一個(gè)任務(wù)節(jié)點(diǎn)。
其創(chuàng)建任務(wù)的方法有兩種:
創(chuàng)建主循環(huán)任務(wù)
創(chuàng)建定時(shí)任務(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)容如:
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ù)都會(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ù):
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是基于軟件定時(shí)器來(lái)進(jìn)行任務(wù)調(diào)度管理的,需要一個(gè)硬件定時(shí)器提供時(shí)基。比如使用系統(tǒng)滴答定時(shí)器,配置為1ms中斷一次。
在1ms中斷中不斷輪詢判斷定時(shí)計(jì)數(shù)是否到達(dá)定時(shí)時(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ù)中如果檢測(cè)到該標(biāo)志就執(zhí)行該任務(wù)函數(shù)。
2、cola_device
cola_device是硬件抽象層,使用鏈表來(lái)管理各個(gè)設(shè)備。其借鑒了RT-Thread及Linux相關(guān)驅(qū)動(dòng)框架思想。大致內(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ū)動(dòng)注冊(cè) */ intcola_device_register(cola_device_t*dev); /* 驅(qū)動(dòng)查找 */ cola_device_t*cola_device_find(constchar*name); /* 驅(qū)動(dòng)讀 */ intcola_device_read(cola_device_t*dev,intpos,void*buffer,intsize); /* 驅(qū)動(dòng)寫 */ intcola_device_write(cola_device_t*dev,intpos,constvoid*buffer,intsize); /* 驅(qū)動(dòng)控制 */ intcola_device_ctrl(cola_device_t*dev,intcmd,void*arg);
首先,在驅(qū)動(dòng)層注冊(cè)好設(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ū)動(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:
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是一個(gè)自動(dòng)初始化模塊,模仿Linux的initcall機(jī)制。RT-Thread也有實(shí)現(xiàn)這個(gè)功能:
一般的,我們的初始化在主函數(shù)中調(diào)用,如:
有了自動(dòng)初始化模塊,可以不在主函數(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)時(shí)鐘初始化 #definefs_initcall(fn)__define_initcall(fn,1)//tick和調(diào)試接口初始化 #definedevice_initcall(fn)__define_initcall(fn,2)//驅(qū)動(dòng)初始化 #definelate_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)用:
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; 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
例如RT-Thread里的實(shí)現(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自動(dòng)初始化機(jī)制大致就分析這些。
cola_os包含有cola_os任務(wù)管理、cola_device硬件抽象層及cola_init自動(dòng)初始化三大塊,這三塊內(nèi)容其實(shí)可以單獨(dú)抽出來(lái)學(xué)習(xí)、使用。
4、cola_os的使用
下面我們基于小熊派IOT開發(fā)板來(lái)簡(jiǎn)單實(shí)踐實(shí)踐。
我們創(chuàng)建兩個(gè)定時(shí)任務(wù):
task0任務(wù):定時(shí)500ms打印一次。
task1任務(wù):定時(shí)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("微信公眾號(hào):嵌入式大雜燴 "); 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*/ }
編譯、下載、運(yùn)行:
從運(yùn)行結(jié)果可以看到,task1的定時(shí)周期是task0的兩倍,符合預(yù)期。
審核編輯:劉清
-
軟件定時(shí)器
+關(guān)注
關(guān)注
0文章
18瀏覽量
6795 -
RTOS
+關(guān)注
關(guān)注
22文章
819瀏覽量
119884 -
MCU芯片
+關(guān)注
關(guān)注
3文章
253瀏覽量
11627
原文標(biāo)題:300行代碼實(shí)現(xiàn)一個(gè)多任務(wù)OS
文章出處:【微信號(hào):strongerHuang,微信公眾號(hào):strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論