FreeRTOS下,串口中断与二值信号实现不定长数据接收。
基础知识点
串口中断种类
串口中断属于STM32本身的资源,不涉及到FreeRTOS,但可与FreeRTOS配合使用。
串口接收中断
中断标志为:USART_IT_RXNE,即rx none empty,串口只要接收到数据就触发中断,如果是接收一个字符串,则每接收到一个字符就触发一次中断。
串口空闲中断
中断标志为:USART_IT_IDLE,idle即空闲的意思,串口空闲时触发的中断,当然也不是说串口空闲时就一直触发中断,而实在每个连续的接收完成后,触发中断,如果是接收一个字符串,则接收完整个字符串后,触发一次中断。
所以,这两个中断可以配合使用,串口接收中断实时接收数据,接受完一串数据后,空闲中断被触发,就可以对接收的一串数据分析处理了。这种方式不需要知道每次字符串的具体长度,因而可以接收不定长的串口数据。
信号量
FreeRTOS中的信号量是一种任务间通信的方式,信号量包括:二值信号量、互斥信号量、计数信号量,本次只使用二值信号量。
二值信号量
二值信号量只有两种状态,可以先通俗的理解为它就是个标志,0或1。信号量用于任务间的同步,FreeRTOS是多任务系统,不同任务间可能需要某种同步关系,如串口中断接收完数据后,数据分析处理任务才能拿到数据进行分析,这就是一种同步。
信号量的基本操作有获取信号量和释放信号量,例如:数据分析处理任务需要处理串口数据时,可先尝试获取信号量,若获取不到,也就是信号量是0,则先进入阻塞等待,等待超时可先跳出,之后继续尝试获取信号量。串口空闲中断接受完一串数据后,可执行释放信号量操作,这时,数据分析处理任务就可以获取到信号量,进而可以处理串口数据了,实现了串口数据接收与数据处理的同步。
接下来的程序思路如下:
API函数
创建二值信号量xSemaphoreCreateBinary()
函数原型(tasks.c中):
1
| SemaphoreHandle_t xSemaphoreCreateBinary( void )
|
返回值:
- SemaphoreHandle_t:创建成功的二值信号量句柄,失败返回NULL
释放信号量xSemaphoreGive()
函数原型(tasks.c中):
1
| BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )
|
参数:
返回值:
- 释放成功返回pdPASS,失败返回errQUEUE_FULL
释放信号量(中断函数中)xSemaphoreGiveFromISR()
1 2
| BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t* pxHigherPriorityTaskWoken)
|
参数:
- xSemaphore:同上
- pxHigherPriorityTaskWoken:标记退出此函数后是否需要进行任务切换
返回值:
获取信号量xSemaphoreTake()
函数原型(tasks.c中):
1 2
| BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xBlockTime)
|
参数:
- xSemaphore:要释放的信号量句柄
- xBlockTime:阻塞时间
返回值:
获取信号量(中断函数中)xSemaphoreTakeFromISR()
1 2
| BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, BaseType_t* pxHigherPriorityTaskWoken)
|
参数:
- xSemaphore:同上
- pxHigherPriorityTaskWoken:标记退出此函数后是否需要进行任务切换
返回值:
编程要点
串口中断与释放信号量
串口配置时记得开启两个中断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
void uart_init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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);
USART_Cmd(USART1, ENABLE);
USART_ClearFlag(USART1, USART_FLAG_TC); #if EN_USART1_RX USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=8; NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
#endif }
|
中断服务函数的串口空闲中断,清除标志位只能通过先读SR寄存器,再读DR寄存器清除!
中断中使用信号量释放要使用ISR结尾的函数xSemaphoreGiveFromISR
,否则程序就卡住了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
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; data = USART1->DR; } }
|
获取信号量
编写一个任务来实现串口数据的获取,该任务不断尝试获取信号量,获取成功后,对数据进行处理。
获取信号量xSemaphoreTake
,阻塞(等待时间)10ms,获取不到信号量则向下执行,每个任务都是一个死循环,马上又会进行信号量获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| void print_task(void *pvParameters) { int count=0; BaseType_t err = pdFALSE; int size=50; uint8_t buf[64];
memset(buf,0,size); while(1) { err=xSemaphoreTake(uartSemaphore,10); if(err==pdTRUE) { if(rx_cnt < size) { memcpy(buf,Recv,rx_cnt); count=rx_cnt; } else { memcpy(buf,Recv,size); count=size; } rx_cnt=0; } if(count>0) { count=0; printf("receive:%s",buf);
} } }
|
一个小应用
结合之前文章介绍的字符串操作的相关知识:C语言字符串相关函数使用示例 strtok_r strstr strtok atoi,可以对“命令+参数”型的字符串数据进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| 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) {
break; } } if(i==N) { printf("can't find cmd in funname[]\r\n"); } else { char* para[4]={0}; para[0] = strtok(paras, " "); int j= 1; while(paras != NULL) { para[j++] = strtok(NULL, " "); if(j==4) break; }
struct_dostr1[i].fun(para); }
|
最后的函数执行,是通过定义一个结构体,将字符命令与函数指针对应起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #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~~~~~~~~~~\r\n"); return 0; }
int led(char* p[]) { int p0,p1; p0=atoi(p[0]); p1=atoi(p[1]); printf("get led: %d, %d\r\n",p0,p1); return 0; }
|
实验结果
通过串口发送hello
或led 80 5
,可以看到想要的处理结果:
1 2 3 4
| receive:hello hello~~~~~~~~~~ receive:led 80 5 get led: 80, 5
|
完整工程代码已保存至GitHub:https://github.com/xxpcb/FreeRTOS-STM32F407-examples