freeRTOS 零基础上手

FreeRTOS CMSIS
robe_zhang
发布时间: 2018-10-28
阅读: 4125

最近发现一个好东西,可以用一种全新的接口,0基础上手freeRTOS系统,

对,没错,0基础上手freeRTOS系统,那就是CMSIS_RTOS标准接口,其实是把 freeRTOS 中间件封装成符合 cmsis 标准的接口,好处也太很明显了:

1, cmsis_os 接口很好用,不要多少基础就能自己使用,0基础使用也不难。稍后看代码示例

2, 用户使用更方便了,使用同一套接口可以在所有RTOS 上编程。本来就应该是这样的,操作系统只是一种实现,多种实现方式多个RTOS,应用层面只关注RTOS 的功能,调用标准的与具体实现无关的cmsis_os 接口,具体怎么实现以及RTOS的接口,那是RTOS的事情。愿景还是很美好的,现实还是需要学习freeRTOS代码,可是cmsis_os 是会给你精神希望的,哈哈。

3, 从freeRTOS迁移到另一个 RTOS更方便,只要使用cmsis接口写的用户代码不用动,直接到cobemx中去掉当前的 freeRTOS,勾选其他 RTOS后生成工程,直接编译即可。如果cube库支持其他RTOS的话,当前还不支持。

进入本文主题,看怎么用这一套接口。

本文按照cmsis功能模块分成多个部分,分别列出各个部分接口,和使用方法,后面还有示例代码。

本文中的进程/线程/任务,都是一个概念,等同freeRTOStaskcmsis 中的 thread

本文注解全部是笔者自己做的,表述不一定严谨,仅供参考。





任务有关的接口:

osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument); //创建进程

osStatus osThreadTerminate (osThreadId thread_id);             //终止进程

osThreadId osThreadGetId (void)                                          //获取进程id/句柄/handler,和 Linux getpid差不多

osPriority osThreadGetPriority (osThreadId thread_id)            //获取进程优先级

osThreadState osThreadGetState(osThreadId thread_id)        //获取进程状态/运行/阻塞/就绪/挂起/删除

osStatus osThreadIsSuspended(osThreadId thread_id)           //进程是否挂起

osStatus osThreadResume (osThreadId thread_id)                 //进程恢复

osStatus osThreadResumeAll (void)                                       //所有进程恢复

osStatus osThreadSetPriority (osThreadId thread_id, osPriority priority)       //设置进程优先级

osStatus osThreadSuspend (osThreadId thread_id)                //挂起进程

osStatus osThreadSuspendAll (void)                                      //挂起所有进程

osStatus osThreadYield (void)                                               //调度

 

结构体:

typedef struct os_thread_def  {

  char           *name;        ///< Thread name

  os_pthread    pthread;      ///< start address of thread function

  osPriority      tpriority;    ///< initial thread priority

  uint32_t       instances;    ///< maximum number of instances of that thread function

  uint32_t       stacksize;    ///< stack size requirements in bytes; 0 is default stack size

#if( configSUPPORT_STATIC_ALLOCATION == 1 )

  uint32_t      *buffer;  ///< stack buffer for static allocation; NULL for dynamic allocation

  osStaticThreadDef_t    *controlblock;   ///< control block to hold thread's data for static allocation; NULL for dynamic allocation

#endif

} osThreadDef_t;

 

示例代码:

void led1_function(void const * argument);                           //声明进程函数

osThreadId led1handles;                                                       //定义进程句柄

osThreadDef(led1, led1_function, osPriorityNormal, 0, 128);   //定义进程结构体

led1handles = osThreadCreate(osThread(led1), NULL);          //创建进程

void led1_function(void const * argument)                            //把进程函数实现了

{

for(;;)

{

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_7);

osDelay(300);

}

}

osThreadTerminate (led1handles);                          //删除函数

演示:

以下是先打印所有进程,然后删除led1进程,然后再打印所有任务,就没有 led1 theread

2.png




时钟有关:

osTimerId osTimerCreate (const osTimerDef_t *timer_def, os_timer_type type, void *argument)      //创建时钟

osStatus osTimerDelete (osTimerId timer_id)                                //删除时钟

osStatus osTimerStart (osTimerId timer_id, uint32_t millisec)         //启动时钟

osStatus osTimerStop (osTimerId timer_id)                                   //停止时钟

 

示例代码:

osTimerId timer1_handle;           //定义句柄

osTimerId timer2_handle;

void timer1_function(void const * argument);          //声明函数

void timer2_function(void const * argument);

osTimerDef(timer1, timer1_function);                //定义timer1结构体

osTimerDef(timer2, timer2_function);

timer1_handle = osTimerCreate(osTimer(timer1), osTimerPeriodic, NULL);          //创建时钟,两个参数不一样,periodic 是连续循环时钟,once 是之调用一次,时钟就停了,时钟还在,下次调用,启用载运行一次,又停了。

timer2_handle = osTimerCreate(osTimer(timer2), osTimerOnce, NULL);

void timer1_function(void const * argument)           //时钟回调函数

{

       ;     //随便写点东西

}

void timer2_function(void const * argument)

{

       ;     //随便写点东西

}

创建的时钟不是单独的一个任务,所有创建的时钟,都是有系统的timer svc 时钟系统服务进程调用时钟回调函数实现的,如下图。用户代码写到回调函数中,如上代码注释位置。

3.png




系统相关:

static int inHandlerMode (void)         //是否工作在线程模式

static osPriority makeCmsisPriority (unsigned portBASE_TYPE fpriority) //freeRTOS 优先级转为CMSIS

static unsigned portBASE_TYPE makeFreeRtosPriority (osPriority priority) //CMSIS优先级转为freeRTOS

int32_t osKernelRunning(void)                                                            //内核/系统是否运行

osStatus osKernelStart (void)                                                              //启动内核/系统

uint32_t osKernelSysTick(void)                                                            //获取系统心跳数

 

osKernelStart(void):这个函数会创建idel 任务,创建 tmr svc 任务,开启调度系统自动运行。

Cobemx 生成的工程,会在main函数中的 while(1) 大循环前,最后一句,就是这个函数。

osKernelStart(void) while(1) 之间不允许有用户代码

4.png



这是idle tmr svc 进程,从序号能看出来创建顺序;

5.png



延时相关:

osStatus osAbortDelay(osThreadId thread_id)         //中止延迟

osStatus osDelay (uint32_t millisec)           //系统延时,ms,不占用CPU

osStatus osDelayUntil (uint32_t *PreviousWakeTime, uint32_t millisec)         //延时到

 

osDelay  osDelayUntil : 这两个函数是有区别的,如下图;

注释掉的两个语句是比较耗时间的,实际运行时候,上面这个led1_function进程循环更快,因为osDelayUntil函数延时的起点是ticks变量的值,也就是耗时间比较多的 timer2_function()函数运行的同时,计数值也在累加,即使timer2_function()函数耗时200ms,这个大循环仍然按照 400ms周期循环。

osDelay  延时,是这个语句需要消耗 400ms,再加上耗时间比较长的timer2_function() 函数运行时间假如是200ms,下面这个led2_function进程大循环一次需要 400+200ms60ms,循环的更慢。

这两个函数都是freeRTOS 原生特性

6.png


两个函数的用法,如上图所示,比较简单

timer2_function()函数其实是我的时钟回调函数,占用cpu 时间比较多,临时拿来当作耗时用的。自己可以随便写,for()嵌套循环耗时也行




消息队列:

osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id)     //创建消息队列

osStatus osMessageDelete (osMessageQId queue_id)                 //删除一条消息

osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec);           //获取消息

osEvent osMessagePeek (osMessageQId queue_id, uint32_t millisec);          //获取消息不删除,详细看下面测试

osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)            //发送消息

 

uint32_t osMessageWaiting(myQueue01Handle);                 //获取队列中有几条消息

uint32_t osMessageAvailableSpace(osMessageQId queue_id);      //获取队列可用空间

 

消息结构体:

typedef struct os_messageQ_def  {

  uint32_t                queue_sz;    ///< number of elements in the queue

  uint32_t                item_sz;    ///< size of an item

#if( configSUPPORT_STATIC_ALLOCATION == 1 )

  uint8_t                 *buffer;      ///< buffer for static allocation; NULL for dynamic allocation

  osStaticMessageQDef_t   *controlblock;     ///< control block to hold queue's data for static allocation; NULL for dynamic allocation

#endif

  //void                       *pool;    ///< memory array for messages

} osMessageQDef_t;

示例代码:

int queue_data_put=21;

char queue_buffer[0x40];

osEvent queue_event;

int count=0;

void keyup_press_callback(void)

{

       osMessagePut (myQueue01Handle, ++queue_data_put, 0);

       memset(queue_buffer,0,0x40);

       sprintf(queue_buffer,"put queue a message:%d.\r\n",queue_data_put);

       while(!(HAL_UART_Transmit_IT(&huart1, (uint8_t *)queue_buffer, sizeof(queue_buffer))==HAL_OK)){};   

       HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);

}

 

void key0_press_callback(void)

{

       memset(&queue_event,0,sizeof(osEvent));

       queue_event=osMessagePeek (myQueue01Handle,0);

       memset(queue_buffer,0,0x40);

       sprintf(queue_buffer,"peek queue a message:%d.\r\n",queue_event.value.v);

       while(!(HAL_UART_Transmit_IT(&huart1, (uint8_t *)queue_buffer, sizeof(queue_buffer))==HAL_OK)){};   

       HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);

}

void key1_press_callback(void)

{     

       memset(&queue_event,0,sizeof(osEvent));

       queue_event=osMessageGet (myQueue01Handle,0);

       memset(queue_buffer,0,0x40);

       sprintf(queue_buffer,"get queue a message:%d.\r\n",queue_event.value.v);

       while(!(HAL_UART_Transmit_IT(&huart1, (uint8_t *)queue_buffer, sizeof(queue_buffer))==HAL_OK)){};   

       HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_11);

}

void key2_press_callback(void)

{

       count=osMessageAvailableSpace(myQueue01Handle);

       memset(queue_buffer,0,0x40);

       sprintf(queue_buffer,"queue availd space:%d.\r\n",count);

       while(!(HAL_UART_Transmit_IT(&huart1, (uint8_t *)queue_buffer, sizeof(queue_buffer))==HAL_OK)){};   

//     osTimerStart (timer2_handle, 0);

       HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_12);

演示:初始消息队列可用长度16put 发送一条消息,可用长度减1peek 获取一条消息,因为不删除,所以不减1get获取一条消息减1,他会把消息从队列删除。

7.png



事件中有个结构体,联合体,避免发生错误,需要判断status,我的代码中没有判断,获取队列是0,其实是消息队列空了,发生了错误。

8.png



Staus 是个枚举变量,osOKevent还有三种状态,signalmessagemail,读取消息要根据这个值做相应处理。

9.png






信箱:

void *osMailAlloc (osMailQId queue_id, uint32_t millisec)                     //分配内存

void *osMailCAlloc (osMailQId queue_id, uint32_t millisec)                   //分配内存并清零

osMailQId osMailCreate (const osMailQDef_t *queue_def, osThreadId thread_id)             //创建信箱队列

osStatus osMailFree (osMailQId queue_id, void *mail)                          //释放内存

osEvent osMailGet (osMailQId queue_id, uint32_t millisec)                   //获取消息

osStatus osMailPut (osMailQId queue_id, void *mail)                           //发送消息

 

信箱队列和消息队列完全一模一样,唯一不同的一点是,这个信箱可以传递任何类型的数据,消息队列只能传递uint32_t 类型的数据。

信箱传递的参数是指针,消息传递的参数是变量。其他的完全一样,状态变量,osEvent 都是同样的类型,一样的。

 



互斥量:

osMutexId osMutexCreate (const osMutexDef_t *mutex_def)               //创建互斥量

osStatus osMutexDelete (osMutexId mutex_id)                                    //释放互斥量

osStatus osMutexWait (osMutexId mutex_id, uint32_t millisec)             //获取互斥量

osStatus osMutexRelease (osMutexId mutex_id)                                  //释放互斥量

 

互斥量结构体:

typedef struct os_mutex_def  {

  uint32_t                   dummy;    ///< dummy value.

#if( configSUPPORT_STATIC_ALLOCATION == 1 )

  osStaticMutexDef_t         *controlblock;      ///< control block for static allocation; NULL for dynamic allocation

#endif

} osMutexDef_t;

示例代码:

  osMutexDef(myMutex01);      //定义结构体

  myMutex01Handle = osMutexCreate(osMutex(myMutex01));           //创建互斥量

 

void led1_function(void const * argument)

{

  for(;;)

  {

              while(osMutexWait(myMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_7);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_7);

              while(osMutexRelease(myMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

void led2_function(void const * argument)

{

  for(;;)

  {

              while(osMutexWait(myMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_8);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_8);

              while(osMutexRelease(myMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

void led3_function(void const * argument)

{

  for(;;)

  {

              while(osMutexWait(myMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);

              while(osMutexRelease(myMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

void led4_function(void const * argument)

{

  for(;;)

  {

              while(osMutexWait(myMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);

              while(osMutexRelease(myMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

基于上面这个代码,单片机 stm32f407 上能跑4个任务,能正常实现功能,任务里面需要加一个osThreadYield ()调度才可以。

获取互斥量的多个任务之间优先级必须一样,这个测试压力比较大,5个任务同时获取互斥量,就出现问题了只有两个任务可以正常拿到互斥量相互切换执行,其他任务获取不到互斥量。



递归互斥量:

osMutexId osRecursiveMutexCreate (const osMutexDef_t *mutex_def)       //创建互斥量

osStatus osRecursiveMutexWait (osMutexId mutex_id, uint32_t millisec)      //获取互斥量

osStatus osRecursiveMutexRelease (osMutexId mutex_id)                           //释放互斥量

 

递归互斥量结构体和互斥量一样。

 

示例代码:

  osMutexDef(myRecursiveMutex01);  //定义结构体

  myRecursiveMutex01Handle = osRecursiveMutexCreate(osMutex(myRecursiveMutex01));           //创建递归互斥量

 

void led3_function(void const * argument)

{

  for(;;)

  {

              while(osRecursiveMutexWait(myRecursiveMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);

              while(osRecursiveMutexRelease(myRecursiveMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

void led4_function(void const * argument)

{

  for(;;)

  {

              while(osRecursiveMutexWait(myRecursiveMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);

              while(osRecursiveMutexRelease(myRecursiveMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

void led5_function(void const * argument)

{

  for(;;)

  {

              while(osRecursiveMutexWait(myRecursiveMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_11);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_11);

              while(osRecursiveMutexRelease(myRecursiveMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

void led6_function(void const * argument)

{

  for(;;)

  {

              while(osRecursiveMutexWait(myRecursiveMutex01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_12);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_12);

              while(osRecursiveMutexRelease(myRecursiveMutex01Handle)!=osOK);

              osThreadYield ();

  }

}

递归互斥量,这一段代码,四个 led 灯可以轮番被点亮。第五个任务获取递归信号量,也会出问题。

 



内存池:

osPoolId osPoolCreate (const osPoolDef_t *pool_def)           //创建内存池

osStatus osPoolFree (osPoolId pool_id, void *block)              //释放内存

void *osPoolAlloc (osPoolId pool_id)                                     //分配内存

void *osPoolCAlloc (osPoolId pool_id)                                   //分配内存清零

这几个好简单

 

内存池结构体:

typedef struct os_pool_def  {

  uint32_t                 pool_sz;    ///< number of items (elements) in the pool

  uint32_t                 item_sz;    ///< size of an item

  void                       *pool;    ///< pointer to memory for pool

} osPoolDef_t;

 



二值信号量,计数信号量:

osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)           //创建信号量,二值count=1,计数count=100都可以。

osStatus osSemaphoreDelete (osSemaphoreId semaphore_id)             //删除    

uint32_t osSemaphoreGetCount(osSemaphoreId semaphore_id)         //获取信号值

int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)             //获取

osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)                  //释放

 

二值信号量示例代码:

  osSemaphoreDef(myBinarySem01);

  myBinarySem01Handle = osSemaphoreCreate(osSemaphore(myBinarySem01), 1);

使用二值信号量,一次点亮一盏灯:

void led1_function(void const * argument)

{

  for(;;)

  {

              while(osSemaphoreWait(myBinarySem01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_7);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_7);

              while(osSemaphoreRelease(myBinarySem01Handle)!=osOK);

              osThreadYield ();

  }

}

void led2_function(void const * argument)

{

  for(;;)

  {

              while(osSemaphoreWait(myBinarySem01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_8);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_8);

              while(osSemaphoreRelease(myBinarySem01Handle)!=osOK);

              osThreadYield ();

  }

}

void led3_function(void const * argument)

{

  for(;;)

  {

              while(osSemaphoreWait(myBinarySem01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);

              while(osSemaphoreRelease(myBinarySem01Handle)!=osOK);

              osThreadYield ();

  }

}

void led4_function(void const * argument)

{

  for(;;)

  {

              while(osSemaphoreWait(myBinarySem01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);

    osDelay(500);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);

              while(osSemaphoreRelease(myBinarySem01Handle)!=osOK);

              osThreadYield ();

  }

}

 

计数信号量示例代码:

  osSemaphoreDef(myCountingSem01);

  myCountingSem01Handle = osSemaphoreCreate(osSemaphore(myCountingSem01), 2);

 

使用计数信号量,初始化为2,一次点亮两盏灯:

void led1_function(void const * argument)

{

  for(;;)

  {

              while(osSemaphoreWait(myCountingSem01Handle,0)!=osOK);

              HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_7);

  &n


原创作品,未经权利人授权禁止转载。详情见转载须知 举报文章

点赞 (0)
robe_zhang
评论(0)

登录后可评论,请 登录注册

相关文章推荐
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回

我要举报该内容理由

×
请输入您举报的理由(50字以内)