本期主角:cola_os,它是一个300多行代码实现的多任务管理的OS,在很多MCU开发中,功能很简单,实时性要求不强,任务多了管理不当又很乱。
如果使用RTOS显得太浪费,这时候可以尝试使用使用cola_os这类基于软件定时器实现的时间片轮询框架。
license:MulanPSL-1.0(木兰宽松许可证, 第1版)。
cola_os是一份简洁明了的代码,包含很多有用的编程思想,值得通读。下面我们一起来学习一下:
cola_os的分析及使用
其实关于cola_os其实我们前几天的推文中也有做介绍。今天我们再一起来完整地梳理一遍。
cola_os目前的内容如:
1、cola_os
cola_os就是cola_os的任务管理模块。任务使用链表进行管理,其数据结构如:
typedefvoid(*cbFunc)(uint32_tevent); typedefstructtask_s { uint8_ttimerNum;//定时编号 uint32_tperiod;//定时周期 booloneShot;//true只执行一次 boolstart;//开始启动 uint32_ttimerTick;//定时计数 boolrun;//任务运行标志 booltaskFlag;//任务标志是主任务还是定时任务 uint32_tevent;//驱动事件 cbFuncfunc;//回调函数 structtask_s*next; }task_t;
每创建一个任务吗,就是往任务链表中插入一个任务节点。
其创建任务的方法有两种:
创建主循环任务
创建定时任务
两种方式创建,都是会在while(1)循环中调度执行任务函数。
我们可以看看cola_task_loop任务遍历函数,这个函数最终是要放在主函数while(1)中调用的。其内容如:
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; } }
两种方式创建的任务都会在cur->func(events);被调用。不同的就是:遍历执行到定时任务时,需要清掉定时相关标志。
其中,events作为任务函数的参数传入。从cola_task_loop可以看到,事件并未使用到,events无论真还是假,在执行任务函数前,都被清零了。events的功能应该是作者预留的。
创建任务很简单,比如创建一个定时任务:
statictask_ttimer_500ms; //每500ms执行一次 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是基于软件定时器来进行任务调度管理的,需要一个硬件定时器提供时基。比如使用系统滴答定时器,配置为1ms中断一次。
在1ms中断中不断轮询判断定时计数是否到达定时时间:
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; } }
如果到了则将标志cur->run置位,在while大循环中的cola_task_loop函数中如果检测到该标志就执行该任务函数。
2、cola_device
cola_device是硬件抽象层,使用链表来管理各个设备。其借鉴了RT-Thread及Linux相关驱动框架思想。大致内容如:
数据结构如:
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; };
硬件抽象层的接口如:
/* 驱动注册 */ intcola_device_register(cola_device_t*dev); /* 驱动查找 */ cola_device_t*cola_device_find(constchar*name); /* 驱动读 */ intcola_device_read(cola_device_t*dev,intpos,void*buffer,intsize); /* 驱动写 */ intcola_device_write(cola_device_t*dev,intpos,constvoid*buffer,intsize); /* 驱动控制 */ intcola_device_ctrl(cola_device_t*dev,intcmd,void*arg);
首先,在驱动层注册好设备,把操作设备的函数指针及设备名称插入到设备链表中:
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函数如:
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); }
驱动注册好设备之后,应用层就可以根据设备名称来查找设备是否被注册,如果已经注册则可以调用设备操作接口操控设备。比如创建一个定时任务定时反转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也有实现这个功能:
一般的,我们的初始化在主函数中调用,如:
有了自动初始化模块,可以不在主函数中调用,例如:
voidSystemClock_Config(void) { } pure_initcall(SystemClock_Config);
这样也可以调用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)//可用作系统时钟初始化 #definefs_initcall(fn)__define_initcall(fn,1)//tick和调试接口初始化 #definedevice_initcall(fn)__define_initcall(fn,2)//驱动初始化 #definelate_initcall(fn)__define_initcall(fn,3)//其他初始化
在cola_init中,首先是调用不同顺序级别的__define_initcall宏来把函数指针fn放入到自定义的指定的段中。各个需要自动初始化的函数放到指定的段中,形成一张初始化函数表。
__ attribute __ (( __ section __)) 关键字就是用来指定数据存放段。
do_init_call函数在我们程序起始时调用,比如在bsp_init中调用:
voidbsp_init(void) { do_init_call(); }
do_init_call里做的事情就是遍历初始化函数表里的函数:
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$$Limit; fn++) { if(fn) (*fn)(); } for (fn = initcall1init$$Base; fn < initcall1init$$Limit; fn++) { if(fn) (*fn)(); } for (fn = initcall2init$$Base; fn < initcall2init$$Limit; fn++) { if(fn) (*fn)(); } for (fn = initcall3init$$Base; fn < initcall3init$$Limit; fn++) { if(fn) (*fn)(); } }
这里有 initcall0init $$ Base 及 initcall0init Limit这几个initcall_t类型的函数指针数组的声明。它们事先是调用__define_initcall把函数指针fn放入到自定义的指定的段.initcall0init、.initcall1init、.initcall2init、.initcall3init。
initcall0init$$Base与initcall0init$$Limit按照我的理解就是各个初始化函数表的开始及结束地址。从而实现遍历:
for(fn=initcall0init$$Base; fn< initcall0init$$Limit; fn++) { if(fn) (*fn)(); }
例如RT-Thread里的实现也是类似的:
volatileconstinit_fn_t*fn_ptr; for(fn_ptr=&__rt_init_rti_board_start;fn_ptr< &__rt_init_rti_board_end; fn_ptr++) { (*fn_ptr)(); }
关于init自动初始化机制大致就分析这些。
cola_os包含有cola_os任务管理、cola_device硬件抽象层及cola_init自动初始化三大块,这三块内容其实可以单独抽出来学习、使用。
4、cola_os的使用
我们创建两个定时任务:
task0任务:定时500ms打印一次。
task1任务:定时1000ms打印一次。
main.c:
/*Privatevariables---------------------------------------------------------*/ statictask_ttimer_500ms; statictask_ttimer_1000ms; /*USERCODEENDPV*/ /*Privatefunctionprototypes-----------------------------------------------*/ voidSystemClock_Config(void); /*USERCODEBEGINPFP*/ /*Privatefunctionprototypes-----------------------------------------------*/ /*USERCODEENDPFP*/ /*USERCODEBEGIN0*/ //每500ms执行一次 staticvoidtimer_500ms_cb(uint32_tevent) { printf("task0running... "); } //每1000ms执行一次 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*/ }
编译、下载、运行:
从运行结果可以看到,task1的定时周期是task0的两倍,符合预期。
审核编辑:刘清
-
软件定时器
+关注
关注
0文章
18浏览量
6738 -
RTOS
+关注
关注
22文章
811浏览量
119588 -
MCU芯片
+关注
关注
3文章
250浏览量
11434
原文标题:300行代码实现一个多任务OS
文章出处:【微信号:strongerHuang,微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论