0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FreeRTOS串口中断接收不定长的数据与二值信号量的使用

码农爱学习 来源:码农爱学习 作者:码农爱学习 2022-09-26 09:02 次阅读

基础知识点

串口中断种类

串口中断属于STM32本身的资源,不涉及到FreeRTOS,但可与FreeRTOS配合使用。

串口接收中断

中断标志为:USART_IT_RXNE,即rx none empty,串口只要接收到数据就触发中断,如果是接收一个字符串,则每接收到一个字符就触发一次中断

串口空闲中断

中断标志为:USART_IT_IDLE,idle即空闲的意思,串口空闲时触发的中断,当然也不是说串口空闲时就一直触发中断,而实在每个连续的接收完成后,触发中断,如果是接收一个字符串,则接收完整个字符串后,触发一次中断

所以,这两个中断可以配合使用,串口接收中断实时接收数据,接受完一串数据后,空闲中断被触发,就可以对接收的一串数据分析处理了。这种方式不需要知道每次字符串的具体长度,因而可以接收不定长的串口数据

信号

FreeRTOS中的信号量是一种任务间通信的方式,信号量包括:二值信号量、互斥信号量、计数信号量,本次只使用二值信号量。

二值信号量

二值信号量只有两种状态,可以先通俗的理解为它就是个标志,0或1。信号量用于任务间的同步,FreeRTOS是多任务系统,不同任务间可能需要某种同步关系,如串口中断接收完数据后,数据分析处理任务才能拿到数据进行分析,这就是一种同步。

信号量的基本操作有获取信号量释放信号量,例如:数据分析处理任务需要处理串口数据时,可先尝试获取信号量,若获取不到,也就是信号量是0,则先进入阻塞等待,等待超时可先跳出,之后继续尝试获取信号量。串口空闲中断接受完一串数据后,可执行释放信号量操作,这时,数据分析处理任务就可以获取到信号量,进而可以处理串口数据了,实现了串口数据接收与数据处理的同步

接下来的程序思路如下:

pYYBAGMwYwaAMgtaAAD5P19tOnw458.png

API函数

创建二值信号量xSemaphoreCreateBinary()

函数原型(tasks.c中):

SemaphoreHandle_t xSemaphoreCreateBinary( void )

返回值:

SemaphoreHandle_t:创建成功的二值信号量句柄,失败返回NULL

释放信号量xSemaphoreGive()

函数原型(tasks.c中):

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )

参数

xSemaphore:要释放的信号量句柄

返回值:

释放成功返回pdPASS,失败返回errQUEUE_FULL

释放信号量(中断函数中)xSemaphoreGiveFromISR()

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
                                  BaseType_t* pxHigherPriorityTaskWoken)

参数:

xSemaphore:同上

pxHigherPriorityTaskWoken:标记退出此函数后是否需要进行任务切换

返回值:

同上

获取信号量xSemaphoreTake()

函数原型(tasks.c中):

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
                           TickType_t xBlockTime)

参数:

xSemaphore:要释放的信号量句柄

xBlockTime:阻塞时间

返回值:

获取成功返回pdTRUE,失败返回pdFALSE

获取信号量(中断函数中)xSemaphoreTakeFromISR()

BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore,
                                  BaseType_t* pxHigherPriorityTaskWoken)

参数:

xSemaphore:同上

pxHigherPriorityTaskWoken:标记退出此函数后是否需要进行任务切换

返回值:

同上

编程要点

串口中断与释放信号量

串口配置时记得开启两个中断。

//=======================================
//初始化IO 串口1 
//bound:波特率
//=======================================
void uart_init(u32 bound)
{
	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟

	//串口1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1

	//USART1端口配置
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10

	//USART1 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
	USART_Init(USART1, &USART_InitStructure); //初始化串口1

	USART_Cmd(USART1, ENABLE);  //使能串口1 

	USART_ClearFlag(USART1, USART_FLAG_TC);
	
#if EN_USART1_RX	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);

	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=8;//抢占优先级8
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;		//子优先级0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

#endif
	
}

中断服务函数的串口空闲中断,清除标志位只能通过先读SR寄存器,再读DR寄存器清除!

中断中使用信号量释放要使用ISR结尾的函数xSemaphoreGiveFromISR,否则程序就卡住了。

//=======================================
//串口1中断服务程序
//=======================================
void USART1_IRQHandler(void)                	
{
	uint8_t data;//接收数据暂存变量
	BaseType_t xHigherPriorityTaskWoken;

	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断
	{
		data =USART_ReceiveData(USART1);   			
		Recv[rx_cnt++]=data;//接收的数据存入接收数组 
		
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	} 
	
	if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)//空闲中断
	{
		if(uartSemaphore!=NULL)
		{
			//释放二值信号量
			xSemaphoreGiveFromISR(uartSemaphore,&xHigherPriorityTaskWoken);	//释放二值信号量
		}
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
		
		data = USART1->SR;//串口空闲中断的中断标志只能通过先读SR寄存器,再读DR寄存器清除!
		data = USART1->DR;
		//USART_ClearITPendingBit(USART1,USART_IT_IDLE);//这种方式无效
	
		//rx_cnt=0;
	}
} 

获取信号量

编写一个任务来实现串口数据的获取,该任务不断尝试获取信号量,获取成功后,对数据进行处理。

获取信号量xSemaphoreTake,阻塞(等待时间)10ms,获取不到信号量则向下执行,每个任务都是一个死循环,马上又会进行信号量获取。

//打印任务函数
void print_task(void *pvParameters)
{
	int count=0;
	BaseType_t err = pdFALSE;
	
	int size=50;
	uint8_t buf[64];//最多只取前64个数据

	//清空本地接收数组
	memset(buf,0,size);
	
	while(1)
	{
		err=xSemaphoreTake(uartSemaphore,10);	//获取信号量
		if(err==pdTRUE)							//获取信号量成功
		{  
			//printf("%s",Data);
			if(rx_cnt < size)//收到的数据长度在size范围内
			{
				//void *memcpy(void *str1, const void *str2, size_t n)  
				//从存储区 str2 复制 n 个字节到存储区 str1。
				memcpy(buf,Recv,rx_cnt);//有几个复制几个
				count=rx_cnt;
				//printf("%srn", buf);
			}
			else//收到的数据长度太长了
			{
				memcpy(buf,Recv,size);//只复制size个
				count=size;
			}
			rx_cnt=0;
		}
		
		if(count>0)
		{
			count=0;
			printf("receive:%s",buf);
			
			//------------------------------------------------------------------------------
			//这里可以继续对buf进行分析和处理,比如根据buf的不同内容执行不同的小任务

		}
	}
}

一个小应用

结合之前文章介绍的字符串操作的相关知识:,可以对“命令+参数”型的字符串数据进行处理。

//先判断指令名称
char *cmd;//表示命令
char *paras;//表示命令后的参数
cmd = strtok_r((char*)buf, " ", ¶s);//这里有点小问题,不带参数的命令,后面需要一个空格

char *ret;
int i;
for (i = 0; i < N;i++)
{
    ret = strstr(struct_dostr1[i].name, cmd);
    if(ret!=NULL)
    {
//        printf("find cmd in funname[%d]rn", i);
        break;
    }
}
if(i==N)
{
    printf("can't find cmd in funname[]rn");
}
else
{				
    //是有效的指令,继续判断后续参数
    char* para[4]={0};//限定最多接收4个参数
    para[0] = strtok(paras, " ");
    int j= 1;
    while(paras != NULL)//这里有点小问题,不可以提前结束
    {
        para[j++] = strtok(NULL, " ");
        if(j==4)
            break;
    }

    //执行对应的函数
    struct_dostr1[i].fun(para);
}

最后的函数执行,是通过定义一个结构体,将字符命令与函数指针对应起来:

#define N 2
typedef struct struct_dostr
{
char name[32];
int (*fun)(char *argv[]);
}struct_dostr;
	
struct_dostr struct_dostr1[N]={
{"hello",hello},
{"led",  led},	
};

int hello(char* p[])
{
	printf("hello~~~~~~~~~~rn");
	return 0;
}

int led(char* p[])
{
	int p0,p1;
	p0=atoi(p[0]);
	p1=atoi(p[1]);
	
	printf("get led: %d, %drn",p0,p1);
	return 0;
}

实验结果

通过串口发送helloled 80 5,可以看到想要的处理结果:

receive:hello  

hello~~~~~~~~~~

receive:led 80 5

get led: 80, 5

审核编辑:汤梓红

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

    关注

    14

    文章

    1483

    浏览量

    74513
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61347
收藏 人收藏

    评论

    相关推荐

    FreeRTOS串口DMA收发不定长数据

    FreeRTOS例程,介绍串口DMA收发不定长数据
    的头像 发表于 09-26 09:08 3675次阅读
    <b class='flag-5'>FreeRTOS</b><b class='flag-5'>串口</b>DMA收发<b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    CW32L083串口中断+定时器实现不定长数据接收

    CW32L083 用串口中断加定时器中断实现串口不定长数据接收,特别适用于AT指令的
    的头像 发表于 07-12 09:00 1063次阅读
    CW32L083<b class='flag-5'>串口中断</b>+定时器实现<b class='flag-5'>不定长</b><b class='flag-5'>数据</b><b class='flag-5'>接收</b>

    freertos串口接收数据后如何发送给任务?

    最大长度? 如果用信号量,是不是就等于是中断程序接收完后发送信号量通知任务处理数组?这样的
    发表于 04-18 06:36

    stm32串口是如何实现接收不定长度数据的呢

    stm32串口是如何实现接收不定长度数据的呢?串口接收数据一般会采用
    发表于 08-11 08:18

    不定长数据接收的原理是什么?怎么实现串口数据不定长接收

    不定长数据接收的原理是什么?怎么实现串口数据不定长接收
    发表于 11-16 08:11

    如何利用IDLE中断进行串口不定长数据接收

    利用IDLE中断进行串口不定长数据接收有何优势?如何利用IDLE中断进行
    发表于 12-08 07:04

    如何利用串口空闲中断接收不定长数据

    在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲
    发表于 02-22 07:34

    Freertos串口中断中释放信号量,线程捕捉不到是为什么?

    Freertos串口中断中释放信号量,线程捕捉不到
    发表于 10-15 10:40

    stm32 串口接收不定长度数据及黏包处理 + 串口DMA接收

    ,那么stm32串口是如何实现接收不定长度数据的呢? 串口接收数据一般会采用
    发表于 12-23 19:09 26次下载
    stm32 <b class='flag-5'>串口</b><b class='flag-5'>接收</b><b class='flag-5'>不定长度数据</b>及黏包处理 + <b class='flag-5'>串口</b>DMA<b class='flag-5'>接收</b>

    STM32 DMA串口接收不定长数据

    STM32 DMA串口接收不定长数据
    发表于 12-24 18:50 40次下载
    STM32  DMA<b class='flag-5'>串口</b><b class='flag-5'>接收</b><b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    STM32之串口DMA接收不定长数据

    在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据不定长的有如何高效
    发表于 12-24 19:03 30次下载
    STM32之<b class='flag-5'>串口</b>DMA<b class='flag-5'>接收</b><b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    STM32单片机串口空闲中断+DMA接收不定长数据

    在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲
    发表于 12-27 19:24 18次下载
    STM32单片机<b class='flag-5'>串口</b>空闲<b class='flag-5'>中断</b>+DMA<b class='flag-5'>接收</b><b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    单片机接收不定长数据,最优解是DMA+串口空闲中断

    如果单片机不支持串口空闲中断和DMA,可以参考之前写的,串口只用接收中断,完成不定长的分包。这里
    发表于 12-28 19:26 19次下载
    单片机<b class='flag-5'>接收</b><b class='flag-5'>不定长</b>的<b class='flag-5'>数据</b>,最优解是DMA+<b class='flag-5'>串口</b>空闲<b class='flag-5'>中断</b>

    使用UART IDLE中断接收不定长数据

    在本文中,将介绍使用该中断来进行不定长串口数据接收的办法。通过该中断,可以省却用于检测数据传输是
    发表于 02-08 15:29 4次下载
    使用UART IDLE<b class='flag-5'>中断</b><b class='flag-5'>接收</b><b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    使用UART IDLE中断接收不定长数据

    使用UART IDLE中断接收不定长数据
    的头像 发表于 09-18 15:41 599次阅读
    使用UART IDLE<b class='flag-5'>中断</b><b class='flag-5'>接收</b><b class='flag-5'>不定长</b><b class='flag-5'>数据</b>