使用cola_os软件定时器实现时间片轮询框架

描述

本期主角:cola_os,它是一个300多行代码实现的多任务管理的OS,在很多MCU开发中,功能很简单,实时性要求不强,任务多了管理不当又很乱。

如果使用RTOS显得太浪费,这时候可以尝试使用使用cola_os这类基于软件定时器实现的时间片轮询框架。

license:MulanPSL-1.0(木兰宽松许可证, 第1版)。

cola_os是一份简洁明了的代码,包含很多有用的编程思想,值得通读。下面我们一起来学习一下:

cola_os的分析及使用

其实关于cola_os其实我们前几天的推文中也有做介绍。今天我们再一起来完整地梳理一遍。

cola_os目前的内容如:

RTOS
RTOS

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...
");
}

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也有实现这个功能:

RTOS
一般的,我们的初始化在主函数中调用,如:

RTOS

有了自动初始化模块,可以不在主函数中调用,例如:

 

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 $$ Baseinitcall0init Limit这几个initcall_t类型的函数指针数组的声明。它们事先是调用__define_initcall把函数指针fn放入到自定义的指定的段.initcall0init、.initcall1init、.initcall2init、.initcall3init。

initcall0init$$Baseinitcall0init$$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...
");
}

//每1000ms执行一次
static void timer_1000ms_cb(uint32_t event)
{
    printf("task1 running...
");
}

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("微信公众号:嵌入式大杂烩
");
  printf("cola_os test!
");
  
  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 */
}

 

编译、下载、运行:

RTOS

从运行结果可以看到,task1的定时周期是task0的两倍,符合预期。



审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分