FreeRTOS的接口应用场景

Posted 三明治开发社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FreeRTOS的接口应用场景相关的知识,希望对你有一定的参考价值。

0.前言

  • 本文主要介绍各个api的使用场景。具体的api参数说明以及调用示例由链接方式一带而过。不做过多的赘述。

1.相关API说明以及调用示例

https://blog.csdn.net/sandwich_iot/article/details/121382802

这个页面里有基于涂鸦sdk的操作系统相关接口的api说明以及示例。

2.实时系统和前后台系统(裸机)

2.1.前后台系统

前台指的是中断,后台指的是while(1)循环。

#include <stdio.h>

//中断服务函数
void Timer0_isr(void) interrupt 1 

 


//初始化
void app_init(void)

  


//子任务
void task1(void)

  	printf("task1");


//子任务
void task2(void)

  	printf("task2");  


//子任务
void task3(void)

  	printf("task3");    


int main(void)

  	app_init();
  
  	//主循环
	while(1)
  		task1();
      	
      	task2();
      	
      	task3();
     

特点:

​ 简单,没什么额外的资源消耗。

​ 只有一个死循环。

​ task1,task2 , task3就是按照顺序执行,没有什么优先级概念。

​ 虽然中断机制是可以处理紧急任务的,但是实际使用中我们是要求中断服务函数短小精悍,我们最常在中断服务函数里的做的事情是立个flag,真正的任务处理是放到主循环中处理的。

2.2.实时系统

​ 我们接到一个复杂的需求时我们会经常要求项目经理给子任务派个优先级。我们精力有限,先干最紧急的任务,再干不那么紧急的任务。这样所产生的效率是最高的。映射到程序开发也是如此,让紧急的程序能够先获得资源运行起来,而不是一定要等前一个任务运行完成。实时操作系统就由此而生了。

#include "FreeRTOS.h"
#include "task.h"

void task1(void *pvParameters)

 	while(1) 
  		printf("vTask1");
  		vTaskDelay(1000/portTICK_RATE_MS);
 	

void task2(void *pvParameters)

	 while(1) 
 		 printf("vTask2");
	


int main(void)

    //创建任务
	xTaskCreate(task1,"task1",50,NULL,1,NULL);
 	xTaskCreate(task2,"task2",50,NULL,1,NULL);
    
    //任务开始调度
  	vTaskStartScheduler();
 	
  	while(1);

特点:

​ 相比于前后台系统,上实时操作系统后会有一定的资源消耗。

​ 每个任务内部都是一个无限循环,每个任务更加独立。

​ 提供任务调度机制,支持任务优先级。任务会被打断。

​ 前后台系统 某个任务的delay是“死等” 其他任务也必须等待,操作系统中 某个任务的sleep是任务被挂起,但是其他任务还是可以继续执行的。

3.接口使用场景

3.1.背景

​ 我们以实现一个串口接收发送的功能为背景来讲各个接口的使用的吧

先上需求,我们需要做个基础的串口服务程序,以下是我们需要实现的功能点:

  • 接收数据并且判断帧的完整性。
  • 串口同步发送具有超时重发的功能(超时时间200ms 重发次数3次)<同步发送:简单理解我在调用完发送函数后我就可以通过返回值或者参数知道我发送数据是否有收到ACK。异步发送:简单理解就是在调用完发送函数后,只是将需要发送的数据放到缓存中。不能确保数据真的已经从串口发送出去了。>

3.2.任务(线程/软定时/函数)

先分析一下需求:

  • 首先我们是做“服务程序”,我们是“乙方”,认清自己角色很重要。我们这个程序是会被“甲方”调用或者是我们要给“甲方”反馈结果的。

  • 根据需求规划一下任务。初步规划三个核心任务,串口接收,串口发送,超时检测。

  • 分析一下这三个任务的“主动权”的归属。

    • 串口接收任务,这个属于我们给“甲方”最后结果反馈就好,即把收到的完整数据帧给到甲方就好了,其他事情不需要“甲方”来操心了。任务的调度入口在我们内部,姑且称为“主动任务”。
    • 串口同步发送任务,这个就属于需要“甲方”来调用的。因为我们并不知道在什么时机会发送数据,发什么数据。这个任务的调度是由”甲方“把控,就称为“被动任务”。
    • 超时检测任务,这个任务也是无需“甲方”关心,发送数据的时候会自动开启,这个任务调度入口也算在内部。这个也是属于一个“主动”任务。
  • 分析完任务的“主被动”后,我们就可以思考一下用什么方式处理这三个任务了。

    • 被动任务-串口发送,我们就封装一个对外的函数让“甲方”进行传参调度。每个函数也是一个小任务嘛。
    • 主动任务-串口接收,调度是我们自己把控,并且任务相对复杂,我们就创建一个线程,让接收任务自动运行。
    • 主动任务-超时检测,这个和时间周期有强相关性,并且任务相对简单,调度由我们自己把控,我们就创建一个定时任务。
    • 主动任务和被动任务是可以相互嵌套的。这个主要看你想怎么做任务划分。理清了任务的特性再来选择处理任务的方式。
  • 创建线程是需要消耗不少的资源的,一个任务是否开线程处理,需要考虑任务复杂程度和性价比。

typedef int (*pFrameDataProc)(unsigned char* frame, unsigned int len);

THREAD_HANDLE uart_rx_handle = NULL;

//回调函数
pFrameDataProc   g_rx_proc_cb = NULL;

typedef struct 
    TIMER_ID          timeout_tm;  
UR_SEND_SYN_CTRL_T;

UR_SEND_SYN_CTRL_T g_send_syn_ctrl;

static void uart_recv_process(void)
 
  	while(1) 
  		  //接收数据
          //判断帧的头 长度 校验和等
          //将完整的数据帧通过回调函数返回给”甲方“
  	


static void uart_wait_ack_timout_tm_cb(unsgined int timerID, void* pTimerArg)

            //超时任务


void tuya_uart_service_init(pFrameDataProc process_cb)

    OPERATE_RET op_ret = OPRT_OK;

  	//创建接收任务
    op_ret = tuya_hal_thread_create(&uart_rx_handle, "uart_recv", 1024, TRD_PRIO_2, uart_recv_process, NULL);
    if (op_ret != OPRT_OK) 
        PR_ERR("creat thread task failed, err_num:%d", op_ret);
        return;
    	
  
  	//创建超时检测定时任务
    op_ret = sys_add_timer(uart_wait_ack_timout_tm_cb, NULL, &g_send_syn_ctrl.timeout_tm);
    if(op_ret != OPRT_OK) 
        PR_ERR("sys_add_timer uart_wait_ack_timout_tm_cb failed! op_ret:%d", op_ret);
        return op_ret;
    
  
  	g_rx_proc_cb = process_cb;



//提供同步发送接口
int tuya_uart_send_syn_wait_ack(unsigned char *frame, unsigned int fr_len, unsigned int ack_cmd)

  


3.3.信号量

​ 我们再进一步梳理一下串口发送任务,串口接收任务,超时检测任务的关系。

  • 发送任务里包含了超时检测任务的开启/关闭的控制。
  • 超时检测任务如果检测到超时了, 就需要告知发送任务当前已经超时。发送任务就会累计重发次数,根据次数来决定是再次开启超时检测还是给”甲方“返回发送失败。
  • 串口接收任务,如果收到了对应的应答帧,也需要告知发送任务收到了回复。发送任务就会停止超时检测任务并且给”甲方“。
  • 我们可以利用信号量来达成任务间的同步。
typedef UCHAR_T SND_SYN_STATE;
#define SND_SYN_IDL            0x00
#define SND_SYN_WAIT_ACK       0x01
#define SND_SYN_GET_ACK        0x02
#define SND_SYN_TIME_OUT       0x03

typedef int (*pFrameDataProc)(unsigned char* frame, unsigned int len);

THREAD_HANDLE uart_rx_handle = NULL;
//回调函数
pFrameDataProc   g_rx_proc_cb = NULL

typedef struct 
	SEM_HANDLE        sem;
    SND_SYN_STATE     state;
  	UINT_T            wait_ack;
    TIMER_ID          timeout_tm;  
UR_SEND_SYN_CTRL_T;

UR_SEND_SYN_CTRL_T g_send_syn_ctrl;

UINT_T get_rv_frame_cmd(void)

       ;


static void uart_recv_process(void)
 
  	while(1) 
  		  //接收数据
          //判断帧的头 长度 校验和等
          //将完整的数据帧通过回调函数返回给”甲方“
      
         //判断收到的数据帧的命令字是否是等待的命令字
      	if(g_send_syn_ctrl.wait_ack == get_rv_frame_cmd()) 
           //设置收到ACK状态
           g_send_syn_ctrl.state = SND_SYN_GET_ACK;
           //释放信号量 通知发送任务
           tuya_hal_semaphore_post(g_send_syn_ctrl.sem);
        
  	


static void uart_wait_ack_timout_tm_cb(unsgined int timerID, void* pTimerArg)

    if(SND_SYN_WAIT_ACK == g_send_syn_ctrl.state) 
        //设置超时状态
        g_send_syn_ctrl.state = SND_SYN_TIME_OUT;
        //释放信号量 通知发送任务
        tuya_hal_semaphore_post(g_send_syn_ctrl.sem);
      
        PR_NOTICE("uart_wait_ack_timout_tm_cb");
    


void tuya_uart_service_init(pFrameDataProc process_cb)

    OPERATE_RET op_ret = OPRT_OK;

  	//创建接收任务
    op_ret = tuya_hal_thread_create(&uart_rx_handle, "uart_recv", 1024, TRD_PRIO_2, uart_recv_process, NULL);
    if (op_ret != OPRT_OK) 
        PR_ERR("creat thread task failed, err_num:%d", op_ret);
        return;
    	
  
  	//创建超时检测定时任务
    op_ret = sys_add_timer(uart_wait_ack_timout_tm_cb, NULL, &g_send_syn_ctrl.timeout_tm);
    if(op_ret != OPRT_OK) 
        PR_ERR("sys_add_timer uart_wait_ack_timout_tm_cb failed! op_ret:%d", op_ret);
        return op_ret;
    
  
    //创建信号量
  	op_ret = tuya_hal_semaphore_create_init(&g_send_syn_ctrl.sem,0,1);
    if(op_ret != OPRT_OK) 
        PR_ERR("create semphore err");
        return op_ret;
    


//提供同步发送接口
int tuya_uart_send_syn_wait_ack(unsigned char *frame, unsigned int  fr_len, unsigned int ack_cmd)

    unsigned int retrans_cnt = 0;
  
    //串口发送指令
 	uart_send_data(frame, fr_len);
 
  	//设置等待回复状态
  	g_send_syn_ctrl.state = SND_SYN_WAIT_ACK;
  
  	//开启超时检测任务
  	sys_start_timer(g_send_syn_ctrl.timeout_tm, 200, TIMER_ONCE);
  	  
  	while(1) 
        //等待其他任务释放信号
    	tuya_hal_semaphore_wait(g_send_syn_ctrl.sem);
      
    	switch(g_send_syn_ctrl.state) 
           case SND_SYN_GET_ACK:
                //发送完成
                op_ret = OPRT_OK;
           break;
           case SND_SYN_TIME_OUT:
               //超时次数累加
               retrans_cnt++;
               if(retrans_cnt > 3))   //超时次数超过上线
                   //重置状态
                   g_send_syn_ctrl.state = SND_SYN_IDL;
                   //停止检测任务
                   sys_stop_timer(s_trans_syn_ctrl.snd_syn_tmout);
                   //返回失败
                   op_ret = OPRT_COM_ERROR;
               else 
                   //重发
                   uart_send_data(data, len);
                   //设置等待回复状态 
                   g_send_syn_ctrl.state = SND_SYN_WAIT_ACK;
                   //再次开启超时检测任务
                   sys_start_timer(s_trans_syn_ctrl.snd_syn_tmout, 200, TIMER_ONCE);
               
           break;
           default:
               g_send_syn_ctrl.state = SND_SYN_IDL;
               op_ret = OPRT_COM_ERROR;
           break; 
      
        
    
  
	return op_ret;


3.4.锁机制

​ 我们写的代码服务的”甲方“可能不只一个,有可能存在”甲方1“,”甲方2“。两个”甲方“有可能会同时需要调用发送接口。如”甲1“要发送查询指令,”甲2“要发送控制命令。但是我们的资源只有一个(硬件串口一个,软件变量也只设计了一套)。如果在”甲1“发送查询指令时并且正在等待ack时,”甲2“也发送控制命令,那我们的管理就混乱了。此时我们就可以使用锁,来给我们的资源上锁。让”甲1“,”甲2“严格遵守先来后到的规则,不能插队。

MUTEX_HANDLE      uart_send_mutex;

typedef struct 
	SEM_HANDLE        sem;
    SND_SYN_STATE     state;
  	UINT_T            wait_ack;
    TIMER_ID          timeout_tm;  
UR_SEND_SYN_CTRL_T;

UR_SEND_SYN_CTRL_T g_send_syn_ctrl;

void tuya_uart_service_init(pFrameDataProc process_cb)

    OPERATE_RET op_ret = OPRT_OK;

  	//创建接收任务	
  
  	//创建超时检测定时任务

    //创建信号量
  
    //创建锁
    op_ret = tuya_hal_mutex_create_init(&uart_send_mutex);
    if(OPRT_OK != op_ret) 
        return op_ret;
    
  
  	return op_ret;


//提供同步发送接口
int tuya_uart_send_syn_wait_ack(unsigned char *frame, unsigned int  fr_len, unsigned int ack_cmd)

    unsigned int retrans_cnt = 0;
   
    //上锁
    tuya_hal_mutex_lock(uart_send_mutex);
  
    //串口发送指令
 	uart_send_data(frame, fr_len);
 
  	//设置等待回复状态
  	g_send_syn_ctrl.state = SND_SYN_WAIT_ACK;
  
  	//开启超时检测任务
  	sys_start_timer(g_send_syn_ctrl.timeout_tm, 200, TIMER_ONCE);
  	  
  	while(1) 
        //等待其他任务释放信号
    	tuya_hal_semaphore_wait(g_send_syn_ctrl.sem);
      
    	//根据状态处理
    
  
    //解锁
    tuya_hal_mutex_unlock(uart_send_mutex);
  
	return op_ret;

3.5.队列

如果我再加一个需求:

●串口异步发送数据,每帧的时间间隔要大于200ms。

作为一个优秀的”乙方“是不会让”甲方“操心这个200ms的事情的。”甲方“可以随心所欲的抛数据,我们先把数据缓存起来,然后我们自己按照200ms的节奏将数据发出。此时利用创建队列的方式来缓存这个数据就非常适合。

基于上面的想法,我们可以再创建一个队列,一个入队任务,一个定时发送任务。

MUTEX_HANDLE      uart_send_mutex;

typedef struct 
    P_QUEUE_CLASS     queue;
    TIMER_ID          timer;  
UR_SEND_ASYN_CTRL_T;

//队列成员结构
typedef struct
	unsigned int  len;
    unsigned char data[0];
UR_QUEUE_PROT_T;

UR_SEND_ASYN_CTRL_T g_send_asyn_ctrl;


static void uart_send_from_queue_tm_cb(unsgined int timerID, void* pTimerArg)

    UR_QUEUE_PROT_T *send = NULL;
  
  	//查询队列数量
	if(0 == GetCurQueNum(g_send_asyn_ctrl.queue)) 
        return;
    
  
    //出队
    if(0 == GetQueueMember(g_send_asyn_ctrl.queue,1, (UCHAR_T*)(&send), 1))
        PR_ERR("get queuw member failed!");
        return;
    

    uart_send_data(send->data,send->len);
  
    //释放缓存
  	Free(send);
   
    //输出队列成员
    DelQueueMember(g_send_asyn_ctrl.queue, 1); 


void tuya_uart_service_init(pFrameDataProc process_cb)

    OPERATE_RET op_ret = OPRT_OK;

  	//创建接收任务	
  
  	//创建超时检测定时任务

    //创建信号量
  
    //创建锁
    op_ret = tuya_hal_mutex_create_init(&uart_send_mutex);
    if(OPRT_OK != op_ret) 
        return op_ret;
    

    //创建队列
    g_send_asyn_ctrl.queue = CreateQueueObj(12, sizeof(UR_QUEUE_PROT_T *));
    if(NULL == g_send_asyn_ctrl.queue) 
        PR_ERR("CreateQueueObj failed!");
        return OPRT_MALLOC_FAILED;
    
  
  	//创建定时发送任务
    op_ret = sys_add_timer(uart_send_from_queue_tm_cb, NULL, &g_send_asyn_ctrl.timer);
    if(op_ret != OPRT_OK) 
        PR_ERR("sys_add_timer uart_send_from_queue_tm_cb failed! op_ret:%d", op_ret);
        return op_ret;
     
  
  	return op_ret;


//提供异步发送(入队)
int tuya_uart_send_asyn(unsigned char *frame, unsigned int fr_len)

    UR_QUEUE_PROT_T *send = NULL;
  
    //上锁
    tuya_hal_mutex_lock(uart_send_mutex);
  
    //申请队列成员指向的空间
    send = (UR_QUEUE_PROT_T *)Malloc(SIZEOF(UR_QUEUE_PROT_T)+ len);
    if(NULL == send) 
        PR_ERR("malloc set cmd buf is failed!");
        return OPRT_MALLOC_FAILED;
    
    memset((UCHAR_T *)send, 0x00, SIZEOF(UR_QUEUE_PROT_T)+len);
    
    //填充内容
    send->len = len;
    memcpy(send->data, data, len);
 
    //入队
    ret = InQueue(g_send_asyn_ctrl.queue, (const unsigned char *)(&send),1);
    if(0 == ret) 
		Free(send);
		PR_ERR(" put_in_queue is failed! ret:%d", ret);
        
        //解锁
        tuya_hal_mutex_unlock(uart_send_mutex);
      
        return OPRT_COM_ERROR;
    
  
    //解锁
    tuya_hal_mutex_unlock(uart_send_mutex);
  
	return op_ret;


以上是关于FreeRTOS的接口应用场景的主要内容,如果未能解决你的问题,请参考以下文章

在STM32Cube中使用FreeRTOS:入门体验

解决 HttpServletRequest 流数据不可重复读

RTOS中间件bror&freertos实现bror接口

RTOS中间件bror&freertos实现bror接口

RTOS中间件bror&freertos实现bror接口

RTOS中间件bror&freertos实现bror接口