首页 > 技术知识 > 正文

基础知识点

串口中断种类

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

串口接收中断

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

串口空闲中断

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

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

信号量

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

二值信号量

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

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

接下来的程序思路如下:

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

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(“cant 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; }

实验结果

通过串口发送复制hello或复制led 80 5,可以看到想要的处理结果:

复制receive:hello hello~~~~~~~~~~ receive:led 80 5 get led: 80, 5

审核编辑:汤梓红

猜你喜欢