Stm32使用串口空闲中断,基于队列来接收不定长、不定时数据

编程

 

        串口持续地接收不定长、不定时的数据,把每一帧数据缓存下来且灵活地利用内存空间,下面提供一种方式供参考。原理是利用串口空闲中断和DMA,每当对方发来一帧完整的数据后,串口接收开始空闲,触发中断,在中断处理中新建一个接收队列节点,把DMA缓存的数据copy到接收队列里。当需要的时候就从接收队列里提出数据。定期清理队列防止堆空间溢出。

        话不多说,上代码。

 

定义数据结构:

/*USART接收队列*/

typedef struct _USART_REC_Queue

{

u16 index; //序号

char *buf; //链接的字符串

struct _USART_REC_Queue* next; //链接到下一个节点

}USART_REC_Queue;

 

声明全局变量:

#define USART3_REC_len 320                       //单次最大接收数

extern u8 USART3_REC_buf[USART3_REC_len]; //用于DMA的临时数据中转

extern u16 USART3_REC_counter; //接收计数器

extern USART_REC_Queue* USART3_REC_Queue_head; //接收队列固定头节点

extern USART_REC_Queue* USART3_REC_Queue_tail; //始终指向最后一个节点

 

准备阶段:

在启动汇编文件里,把堆空间改大,防止接收一点点数据就内存溢出。

Heap_Size       EQU     0x00004000//默认200字节,改大

 

实例化全局变量:

u8 USART3_REC_buf[320] = {0}; 

u16 USART3_REC_counter = 0;

USART_REC_Queue* USART3_REC_Queue_head = NULL;

USART_REC_Queue* USART3_REC_Queue_tail = NULL;

 

初始化各个硬件,使能了串口接收空闲中断,串口接收DMA,为接收队列头节点分配内存空间:

void USART3_Init(u32 BaudRate)

{

//初始化参数结构体

GPIO_InitTypeDef GPIO_InitStruct; //IO

USART_InitTypeDef USART_InitStruct; //串口

NVIC_InitTypeDef NVIC_InitStruct; //中断控制

DMA_InitTypeDef DMA_InitStruct; //DMA

/*全局指针初始化*/

USART3_REC_Queue_head = USART_REC_Queue_Creat(); //构建串口3接收队列头节点

USART3_REC_Queue_tail = USART3_REC_Queue_head; //构建串口3接收队列尾节点

//RCC使能

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //IO时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //串口3时钟

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //DMA时钟

//PB11 USART1_TXD

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStruct);

//PB10 USART1_RXD

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入

//GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStruct);

//内嵌向量中断控制器初始化

NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级1

NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;//子优先级1

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;//使能IRQ通道

NVIC_Init(&NVIC_InitStruct);

//USART初始化

USART_InitStruct.USART_BaudRate = BaudRate;//波特率 一般9600

USART_InitStruct.USART_WordLength = USART_WordLength_8b;//字节数据格式8位

USART_InitStruct.USART_StopBits = USART_StopBits_1;//一个停止位

USART_InitStruct.USART_Parity = USART_Parity_No;//无奇偶字节校验

USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件流控制

USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式

USART_Init(USART3, &USART_InitStruct);//初始化USART

//USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//使能接收中断

USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);//使能总线空闲中断

USART_Cmd(USART3, ENABLE);//使能串口

DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR); //读取哪一个寄存器

DMA_InitStruct.DMA_MemoryBaseAddr = (u32)(&USART3_REC_buf); //读取到的数据的存放地址

DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //指定外设为源地址

DMA_InitStruct.DMA_BufferSize = USART3_REC_len; //数据存放区大小

DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设寄存器地址是否偏移

DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //数据存放地址是否偏移

DMA_InitStruct.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; //外设数据宽度8位

DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //定义存储器数据宽度8位

DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //正常操作模式

DMA_InitStruct.DMA_Priority = DMA_Priority_High; //通道优先级

DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //是否开启存储器到存储器模式

DMA_Init(DMA1_Channel3, &DMA_InitStruct); //写入设置到DMA1通道

DMA_Cmd(DMA1_Channel3, ENABLE); //使能DMA1通道

USART_DMACmd(USART3, USART_DMAReq_Rx, ENABLE); //注意不要忘了使能串口的DMA功能

}

 

串口中断处理(核心):

void USART3_IRQHandler(void)

{

if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)

{

char *buf_new; //新字符串

USART_REC_Queue* queue_new; //新队列节点

u16 len;

USART3->DR; //读取数据。注意:这句必须要,否则不能够清除中断标志位

USART_ClearITPendingBit(USART3, USART_IT_IDLE); //清中断

DMA_Cmd(DMA1_Channel3, DISABLE); //关闭DMA1通道3

len = USART3_REC_len - DMA_GetCurrDataCounter(DMA1_Channel3); //计算接收长度

buf_new = (char *)malloc((len+5) * sizeof(char)); //为新字符串分配内存,预留空间添加序号

//if(buf_new == NULL) GPIO_SetBits(LedPort, Led1); //内存不够的提示

queue_new = USART_REC_Queue_Creat(); //为新队列节点分配内存

//if(queue_new == NULL) GPIO_SetBits(LedPort, Led2); //内存不够的提示

USART3_REC_counter ++; //计数器加1

queue_new->index = USART3_REC_counter; //新节点的序号

sprintf(buf_new, "#%d:%s", USART3_REC_counter, USART3_REC_buf); //复制缓存到新字符串并添加序号

queue_new->buf = buf_new; //新队列节点链接新字符串

USART3_REC_Queue_tail->next = queue_new; //接收队列尾节点链接新的节点

USART3_REC_Queue_tail = queue_new; //更新尾节点

DMA1_Channel3->CNDTR = USART3_REC_len; //重置DMA1通道3缓存计数器

DMA_Cmd(DMA1_Channel3, ENABLE); //重开DMA1通道3

}

}

 

创建与销毁接收队列节点:

USART_REC_Queue* USART_REC_Queue_Creat(void)

{

USART_REC_Queue* p_temp = (USART_REC_Queue*)malloc(sizeof(USART_REC_Queue));

if(p_temp == NULL) return NULL;

memset(p_temp, 0, sizeof(USART_REC_Queue));

//p_temp->next = NULL;

return p_temp;

}

void USART_REC_Queue_Delete(USART_TypeDef* USARTx)

{

if(USARTx == USART3)

{

USART_REC_Queue* temp;

temp = USART3_REC_Queue_head->next;

if(temp == NULL || temp->next == NULL) return;

free(temp->buf);

USART3_REC_Queue_head->next = temp->next;

free(temp);

}

}

 

 

主函数里每隔5秒刷新显示接收队列的数据,并清理。

while(1)

{

if(tim3_flag == 1)

{

tim3_flag = 0;

USART_REC_Queue_display(USART3);

USART_REC_Queue_Delete(USART3);

}

}

 

在OLED屏上显示接收队列里的数据:

void USART_REC_Queue_display(USART_TypeDef* USARTx)

{

if(USARTx == USART3)

{

USART_REC_Queue* temp = USART3_REC_Queue_head->next;

if(temp == NULL) return; //队列还没生成则返回

OLED_Clear(); //清屏

OLED_ShowString(0, 0, USART3_REC_Queue_head->next->buf); //显示字符串

}

}

以上是 Stm32使用串口空闲中断,基于队列来接收不定长、不定时数据 的全部内容, 来源链接: utcz.com/z/511812.html

回到顶部