串口可以配置成用DMA的方式接收数据,不过DMA需要定长才能产生接收中断,如何接收可变长度的数据呢?
方法有以下3种:
1.将RX脚与一路时钟外部引脚相连,当串口一帧发完,即可利用此定时器产生超时中断。这个实时性较高,可以做到1个字节实时监测。
2.不改变硬件,开启一个定时器监控DMA接收,如果超时则产生中断。这个实时性不高,因为超时时间必须要大于需要接收帧的时间,精度不好控制。
3.STM32单片机有的串口可以监测总线是否处于空闲,如果空闲则产生中断。可以用它来监测DMA接收是否完毕。这种方式实时性很高。
串口DMA发送:
发送数据的流程:
前台程序中有数据要发送,则需要做如下几件事
1. 在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。
2. 将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(串口发送DMA和串口接收DAM不是同一个DMA通道)
3. 开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管Keil 是什么状态,DMA总是发送数据。
4. 等待发送完成标志位,即下面的终端服务函数中的第3点设置的标志位。或者根据自己的实际情况来定,是否要一直等待这个标志位,也可以通过状态机的方式来循环查询也可以。或者其他方式。 判断数据发送完成:
启动DMA并发送完后,产生DMA发送完成中断,在中断函数中做如下几件事:
1. 清DMA发送完成中断标志位 2. 关闭串口发送DMA通道
3. 给前台程序设置一个软件标志位,说明数据已经发送完毕
串口DMA接收:
接收数据的流程:
串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。
判断数据数据接收完成:
这里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。这个中断里面做如下几件事:
1.关闭串口接收DMA通道,2点原因:1.防止后面又有数据接收到,产生干扰。2.便于DMA的重新配置赋值,下面第4点。
2. 清除DMA 所有标志位
3. 从DMA寄存器中获取接收到的数据字节数
4.重新设置DMA下次要接收的数据字节数,注意,这里是给DMA寄存器重新设置接收的计数值,这个数量只能大于或者等于可能接收的字节数,否则当DMA接收计数器递减到0的时候,又会重载这个计数值,重新循环递减计数,所以接收缓冲区的数据则会被覆盖丢失。
5. 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如第4条的写入计数值,必须要在关闭DMA的条件进行,否则操作无效。
说明一下,STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。
串口用DMA方式发送和接收,分以下几步:
1)串口初始化
2)DMA初始化
3)发送数据
4)接收数据
我们按部就班:
1) 串口初始化 — 使用串口一
#define DMASIZE 1024
// 配置串口一的发送和接收的GPIO口功能,以及中断
static void _uart1_gpio_init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_USART1 |
RCC_APB2Periph_AFIO, ENABLE) ;
GPIOA-》CRH&=0XFFFFF00F;
GPIOA-》CRH|=0X000008B0;//IO状态设置 10pin_上拉输入 9pin_推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* Configure USART1 Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART1 Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Enable the USART1 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ClearFlag(USART1, USART_FLAG_TC); /* 清发送外城标志,Transmission Complete flag */
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);// 采用空闲中断,目的是在产生空闲中断时,说明接收或者发送已经结束,此时可以读取DMA中的数据了。
//USART_ITConfig(USART1, USART_IT_TC, ENABLE);
//USART_ITConfig(USART1, USART_IT_FE, ENABLE);
}
// 设置对应串口的波特率
static void _uart_setbaudrate(USART_TypeDef* USARTx,u32 value)
{
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate =value;
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(USARTx, &USART_InitStructure);
USART_Cmd(USARTx, ENABLE);
2)初始化DMA
u8 sendbuf[1024];
u8 receivebuf[1024];
static void _uart1_dma_configuration()
{
DMA_InitTypeDef DMA_InitStructure;
/* DMA1 Channel6 (triggered by USART1 Rx event) Config */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 ,
ENABLE);
/* DMA1 Channel5 (triggered by USART1 Rx event) Config */
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;// 初始化外设地址,相当于“哪家快递”
DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf;// 内存地址,相当于几号柜
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据来源,即为收快递
DMA_InitStructure.DMA_BufferSize = DMASIZE ;// 缓存容量,即柜子大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增,即柜子对应的快递不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 内存递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节宽度,即快递运输快件大小度量(按重量算,还是按体积算)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存字节宽度,即店主封装快递的度量(按重量,还是按体质进行封装)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,即满了就不在接收了,而不是循环存储
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;// 优先级很高,对应快递就是加急
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 内存与外设通信,而非内存到内存
DMA_Init(DMA1_Channel5, &DMA_InitStructure);// 把参数初始化,即拟好与快递公司的协议
DMA_Cmd(DMA1_Channel5, ENABLE);// 启动DMA,即与快递公司签订合同,正式生效
/* DMA1 Channel4 (triggered by USART1 Tx event) Config */
DMA_DeInit(DMA1_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; // 外设地址,串口1, 即发件的快递
DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf;// 发送内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 外设为传送数据目的地,即发送数据,即快递是发件
DMA_InitStructure.DMA_BufferSize = 0; //发送长度为0,即未有快递需要发送
DMA_Init(DMA1_Channel4, &DMA_InitStructure);//初始化
USART_ITConfig(USART1, USART_IT_TC, ENABLE);// 使能串口发送完成中断
USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求
}
}
数据发送
流程:串口发送数据,全部数据发送完毕后,会产生一个发送中断,所以
发送数据分为两部分,
A、发送数据
B、中断处理
A、发送数据
u16 Uart_Send_Data(void* buffer, u16 size)
{
if(!size) return 0;// 判断长度是否有效
while (DMA_GetCurrDataCounter(DMA1_Channel4));// 检查DMA发送通道内是否还有数据
if(buffer) memcpy(sendbuf, buffer,(size 》 1024?1024:size));
//DMA发送数据-要先关 设置发送长度 开启DMA
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4-》CNDTR = size;// 设置发送长度
DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送
return size;
}
B、中断处理
1)中断处理相关准备工作
typedef enum _UartEvent_
{
E_uart_0 = 0,// 没有事件
E_uart_tc=0x40, //发送完成
E_uart_idle=0x80, //接收完成
}UartEvent;
u16 receivelen = 0;// 声明接收数据长度
UartEvent event;//申明一个事件参数
//清除DMA 缓存,并终止DMA
void Uart_Dma_Clr(void)
{
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4-》CNDTR=0;
DMA_Cmd(DMA1_Channel5, DISABLE);
DMA1_Channel5-》CNDTR=DMASIZE ;
DMA_Cmd(DMA1_Channel5, ENABLE);
}
// 获取一个事件,事件分为发送完成事件和接收完成事件,可以根据事件进行进行处理
UartEvent Uart_Get_Event(void)
{
UartEvent e;
if(!DMA1_Channel5-》CNDTR) Uart_Dma_Clr();// 如果产生一个事件后,接收数据通道已经没有了缓存空间,进行清除DMA清空
return event;
}
// 清除对应的事件
void Uart_Clr_Event(UartEvent event_in)
{
event&=~event_in;
}
2) 中断处理,当所有数据发送完毕,串口1产生一个发送完成中断
void Uatr1_Back_IRQHandler()
{
u8 tem;
if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)
{
tem=USART1-》SR;//先读SR,然后读DR才能清除
tem=USART1-》DR;
tem=tem;
Uart_Set_Event(E_uart_idle);
receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
}
**if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记**
{
USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记
DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA
DMA1_Channel4-》CNDTR=0; // 清除数据长度
Uart_Set_Event(E_uart_tc); //设置发送完成事件
}
}
4、接收数据
根据上图描述,流程如下:
1、串口接收到数据
2、DMA自动取走数据
3、DMA把数据存到内存receive[1024]中
4、串口接收完毕后会产生一个空闲中断
根据上面流程,我们接收数据需要做到两步:
1)串口产生一个空闲中断后,设置一个接收完成事件
中断处理:
void Uatr1_Back_IRQHandler()
{
u8 tem;
**if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)**
{
tem=USART1-》SR;//先读SR,然后读DR才能清除
tem=USART1-》DR;// 清除DR
tem=tem; // 防止编译器警告
Uart_Set_Event(E_uart_idle);// 设置接收完成(空闲)事件
receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度
USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除空闲中断
}
if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记
{
USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记
DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA
DMA1_Channel4-》CNDTR=0; // 清除数据长度
Uart_Set_Event(E_uart_tc); //设置发送完成事件
}
}
2)接收数据函数检测事件,如果发现是接收完成事件,取走数据,并且做相关清除操作
u8 Uart_Receive_Data(u8*recbuf u16 *revLen)
{
u8 *str;
if( event & E_uart_idle) // 是否产生空闲中断
{
str = Uart_Get_Data(revLen);
memcpy(recbuf,receivebuf,*revLen);
Uart_Clr_Event(E_uart_idle);
Uart_Dma_Clr();
return TRUE;
}
else
{
revLen = 0;
return FALSE;
}
}