本期主角:cola_os,它是一個300多行代碼實現的多任務管理的OS,在很多MCU開發中,功能很簡單,實時性要求不強,任務多了管理不當又很亂。
如果使用RTOS顯得太浪費,這時候可以嘗試使用使用cola_os這類基于軟件定時器實現的時間片輪詢框架。
倉庫鏈接:
https://gitee.com/schuck/cola_os
license:MulanPSL-1.0(木蘭寬松許可證, 第1版)。
cola_os是一份簡潔明了的代碼,包含很多有用的編程思想,值得通讀。下面我們一起來學習一下:
cola_os的分析及使用
其實關于cola_os其實我們前幾天的推文中也有做介紹。今天我們再一起來完整地梳理一遍。
cola_os目前的內容如:
1、cola_os
cola_os就是cola_os的任務管理模塊。任務使用鏈表進行管理,其數據結構如:
typedef void (*cbFunc)(uint32_t event); typedef struct task_s { uint8_t timerNum; //定時編號 uint32_t period; //定時周期 bool oneShot; //true只執行一次 bool start; //開始啟動 uint32_t timerTick; //定時計數 bool run; //任務運行標志 bool taskFlag; //任務標志是主任務還是定時任務 uint32_t event; //驅動事件 cbFunc func; //回調函數 struct task_s *next; }task_t;
每創建一個任務嗎,就是往任務鏈表中插入一個任務節點。
其創建任務的方法有兩種:
- 創建主循環任務
- 創建定時任務
兩種方式創建,都是會在while(1)循環中調度執行任務函數。
我們可以看看cola_task_loop任務遍歷函數,這個函數最終是要放在主函數while(1)中調用的。其內容如:
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; } }
兩種方式創建的任務都會在cur->func(events);被調用。不同的就是:遍歷執行到定時任務時,需要清掉定時相關標志。
其中,events作為任務函數的參數傳入。從cola_task_loop可以看到,事件并未使用到,events無論真還是假,在執行任務函數前,都被清零了。events的功能應該是作者預留的。
創建任務很簡單,比如創建一個定時任務:
static task_t timer_500ms; //每500ms執行一次 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是基于軟件定時器來進行任務調度管理的,需要一個硬件定時器提供時基。比如使用系統滴答定時器,配置為1ms中斷一次。
在1ms中斷中不斷輪詢判斷定時計數是否到達定時時間:
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; } }
如果到了則將標志cur->run置位,在while大循環中的cola_task_loop函數中如果檢測到該標志就執行該任務函數。
2、cola_device
cola_device是硬件抽象層,使用鏈表來管理各個設備。其借鑒了RT-Thread及linux相關驅動框架思想。大致內容如:
數據結構如:
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; };
硬件抽象層的接口如:
/* 驅動注冊 */ int cola_device_register(cola_device_t *dev); /* 驅動查找 */ cola_device_t *cola_device_find(const char *name); /* 驅動讀 */ int cola_device_read(cola_device_t *dev, int pos, void *buffer, int size); /* 驅動寫 */ int cola_device_write(cola_device_t *dev, int pos, const void *buffer, int size); /* 驅動控制 */ int cola_device_ctrl(cola_device_t *dev, int cmd, void *arg);
首先,在驅動層注冊好設備,把操作設備的函數指針及設備名稱插入到設備鏈表中:
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函數如:
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); }
驅動注冊好設備之后,應用層就可以根據設備名稱來查找設備是否被注冊,如果已經注冊則可以調用設備操作接口操控設備。比如創建一個定時任務定時反轉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); }
3、cola_init
cola_init是一個自動初始化模塊,模仿Linux的initcall機制。RT-Thread也有實現這個功能:
一般的,我們的初始化在主函數中調用,如:
有了自動初始化模塊,可以不在主函數中調用,例如:
void SystemClock_Config(void) { } pure_initcall(SystemClock_Config);
這樣也可以調用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) //可用作系統時鐘初始化 #define fs_initcall(fn) __define_initcall(fn, 1) //tick和調試接口初始化 #define device_initcall(fn) __define_initcall(fn, 2) //驅動初始化 #define late_initcall(fn) __define_initcall(fn, 3) //其他初始化
在cola_init中,首先是調用不同順序級別的__define_initcall宏來把函數指針fn放入到自定義的指定的段中。各個需要自動初始化的函數放到指定的段中,形成一張初始化函數表。
__ attribute __ (( __ section __)) 關鍵字就是用來指定數據存放段。
do_init_call函數在我們程序起始時調用,比如在bsp_init中調用:
void bsp_init(void) { do_init_call(); }
do_init_call里做的事情就是遍歷初始化函數表里的函數:
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 < 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里的實現也是類似的:
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)(); }
關于init自動初始化機制大致就分析這些。
cola_os包含有cola_os任務管理、cola_device硬件抽象層及cola_init自動初始化三大塊,這三塊內容其實可以單獨抽出來學習、使用。
4、cola_os的使用
下面我們基于小熊派IOT開發板來簡單實踐實踐。
我們創建兩個定時任務:
- task0任務:定時500ms打印一次。
- task1任務:定時1000ms打印一次。
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執行一次 static void timer_500ms_cb(uint32_t event) { printf("task0 running...n"); } //每1000ms執行一次 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("微信公眾號:嵌入式大雜燴rn"); printf("cola_os test!rn"); 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 */ }
編譯、下載、運行:
從運行結果可以看到,task1的定時周期是task0的兩倍,符合預期。
聲明:本文素材來源網絡,版權歸原作者所有。如涉及作品版權問題,請與我聯系刪除。
文章鏈接:https://mp.weixin.qq.com/s/wCh2BF96LBN8JtBYhlIiWQ 轉載自:strongerHuang 文章來源:嵌入式大雜燴 ,作者ZhengNL 文章鏈接:300行代碼實現一個多任務OS
版權申明:本文來源于網絡,免費傳達知識,版權歸原作者所有。如涉及作品版權問題,請聯系我進行刪除。