大家好,我是ZhengN。
嵌入式大雜燴周記主要是一些實(shí)用項(xiàng)目學(xué)習(xí)分享,每周一篇,每篇一個(gè)主題。
內(nèi)容主要來(lái)源于我們之前收集的資料:
https://gitee.com/zhengnianli/EmbedSummary
本期主角:cola_os
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í)一下:
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)如:
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á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)容如:
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ù)。
2、cola_device
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);
}
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)用,例如:
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 < 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這幾個(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 < initcall0init$$Limit;
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í)、使用。
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:
/* 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ù)期。
本文代碼工程可在本公眾號(hào)后臺(tái)回復(fù)關(guān)鍵詞:cola_os測(cè)試
,即可獲取。
以上就是本次的分享,文章如有錯(cuò)誤,歡迎指出,謝謝!
咱們下期見(jiàn)~
-
mcu
+關(guān)注
關(guān)注
146文章
17148瀏覽量
351212 -
OS
+關(guān)注
關(guān)注
0文章
91瀏覽量
34759 -
代碼
+關(guān)注
關(guān)注
30文章
4788瀏覽量
68616
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論