学习原子哥的板子已经两年了,现在也如愿以偿的做相关开发工作。在工作中也学习不少软件方案,上次看论坛里有个网友采用串口空闲中断及DMA接收串口不定长数据,感觉这个非常好用。特意花了点时间写好库,方便与大家交流学习。
一、结构体
[mw_shl_code=c,true]__packed typedef struct
{
u32 USARTX_Bound; //波特率
uint32_t USART_RCC ; //串口时钟时能
USART_TypeDef * USARTX ; //串口
uint8_t USART_IRQChannel; //串口中断线
uint32_t GPIO_RCC_APB2; //GPIO时钟时能
GPIO_TypeDef * GPIO_RX; //GPIO RX端口
uint16_t GPIO_RX_Pin; //引脚
GPIO_TypeDef * GPIO_TX;
uint16_t GPIO_TX_Pin;
DMA_Channel_TypeDef * USART_RX_DMA_Channel; //串口DMA接收通道
uint8_t DMA_IRQChannel ; //DMA中断线
uint32_t DMA_FLAG_TC ; //DMA传输完成标记
}
USERT_Irregular_DMA_typ ; //串口DMA接收不定长数据结构体[/mw_shl_code]
此结构体用于记录此方案所采用的IO、串口、DMA通道等,此结构体的数据一旦确定完成,建议不要改变,此结构体可以直接定义到FLASH中(用关键字 const)
[mw_shl_code=c,true]#define USART_IrrG_DMA_BUFF_LEN 40
//双缓存大小 最大 65535
__packed typedef struct
{
u8 RX_BUFF_Comple : 2 ; //接收完成标记 0 都没有接收完成 bit0 缓存0接收完成 bit1 缓存1接收完成
u8 RX_BUFF0_STA : 1 ; //0 空闲 1 接收完成
u8 RX_BUFF1_STA : 1 ; //0 空闲 1 接收完成
u8 RX_BUFF_Err : 2 ; //0 正常 1 缓存都接收满 未处理
u8 RX_BUFF_Inx : 2 ; //当前正在接收的缓存索引
u16 RX_BUFF0_Size ; //接收到的数据长度
u8 RX_BUFF0[USART_IrrG_DMA_BUFF_LEN] ; //数据缓存
u16 RX_BUFF1_Size ;
u8 RX_BUFF1[USART_IrrG_DMA_BUFF_LEN] ;
const USERT_Irregular_DMA_typ * USERT_Irregular_DMA ;
}
USART_RX_BUFF; [/mw_shl_code]
此结构体用于管理双缓存,并向上层提供双缓存当前状态。
宏 USART_IrrG_DMA_BUFF_LEN 用于设置双缓存长度
二、用户调用函数
此方案有三个函数需要用户调用,但不需要更改。
1、初始化
[mw_shl_code=c,true]void USART_DMA_Init(USART_RX_BUFF *USERT_RX_DMA)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(USERT_RX_DMA->USERT_Irregular_DMA->GPIO_RCC_APB2, ENABLE); //使能GPIO时钟
if(USERT_RX_DMA->USERT_Irregular_DMA->USARTX == USART1)
{
RCC_APB2PeriphClockCmd(USERT_RX_DMA->USERT_Irregular_DMA->USART_RCC, ENABLE); //使能串口1时钟
}
else
{
RCC_APB1PeriphClockCmd(USERT_RX_DMA->USERT_Irregular_DMA->USART_RCC, ENABLE); //使能串口时钟
}
//USART_TX
GPIO_InitStructure.GPIO_Pin = USERT_RX_DMA->USERT_Irregular_DMA->GPIO_TX_Pin ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(USERT_RX_DMA->USERT_Irregular_DMA->GPIO_TX, &GPIO_InitStructure);
//USART_RX
GPIO_InitStructure.GPIO_Pin = USERT_RX_DMA->USERT_Irregular_DMA->GPIO_RX_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(USERT_RX_DMA->USERT_Irregular_DMA->GPIO_RX, &GPIO_InitStructure);
//Usart NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USERT_RX_DMA->USERT_Irregular_DMA->USART_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3 ; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_DeInit(USERT_RX_DMA->USERT_Irregular_DMA->USARTX); //先复位串口
USART_InitStructure.USART_BaudRate = USERT_RX_DMA->USERT_Irregular_DMA->USARTX_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(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, &USART_InitStructure); //初始化串口
USART_ClearITPendingBit(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, USART_IT_IDLE) ; //清除空闲中断
USART_ITConfig(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, USART_IT_IDLE, ENABLE); //开启空闲中断
//DMA 配置--------------------------------------------------------------------------------------------------
USART_DMACmd(USERT_RX_DMA->USERT_Irregular_DMA->USARTX,USART_DMAReq_Rx,ENABLE); //开启DMA接收
MY_USART_Iglr_DMA_CONFIG(USERT_RX_DMA); //DMA配置 先由缓存0 接收
USART_RX_DMA_START(USERT_RX_DMA); //开启DMA
USART_Cmd(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, ENABLE); //使能串口
}[/mw_shl_code]
此函数是根据结构体指针变量 USERT_RX_DMA 的值 来初始化,其中包括时钟、串口、IO、DMA、中断等配置,
无需修改,直接调用
2、串口中断函数
[mw_shl_code=c,true]void USART_RX_DMA_USART_IRQ(USART_RX_BUFF * USERT_RX_DMA) //必须在串口中断函数中调用
{
if(USART_GetITStatus(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, USART_IT_IDLE) != RESET) //如果空闲中断
{
USERT_RX_DMA->USERT_Irregular_DMA->USARTX->DR ; //空读一次
USART_ClearFlag(USART1, USART_IT_IDLE); //清除空闲中断标记
//标记缓存接收完成--------------------------------------------------
USERT_RX_DMA->RX_BUFF_Comple |= (0X01<<USERT_RX_DMA->RX_BUFF_Inx );
if( USERT_RX_DMA->RX_BUFF_Inx == 0) //如果当前是第0个缓存接收
{
//得到缓存长度--------------------------
USERT_RX_DMA->RX_BUFF0_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
USERT_RX_DMA->RX_BUFF_Inx = 1 ; //设置缓存1 接收数据
}
else if( USERT_RX_DMA->RX_BUFF_Inx == 1)
{
//得到缓存长度--------------------------
USERT_RX_DMA->RX_BUFF1_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
USERT_RX_DMA->RX_BUFF_Inx = 0 ; //设置缓存1 接收数据
}
if( USERT_RX_DMA->RX_BUFF_Comple == 0x03 ) //如果缓存0 缓存1 都接收满
{
USERT_RX_DMA->RX_BUFF_Err = 1 ; //错误 缓存满标记
}
else //正常 则开启 DMA接收
{
USART_RX_DMA_START(USERT_RX_DMA); //开启DMA
}
}
}[/mw_shl_code]
此函数必须在对应的串口中断中调用,起作用是用于判断及设置双缓存状态及切换缓存。
无需修改,直接调用。
3、DMA中断函数
[mw_shl_code=c,true]
void USART_RX_DMA_DMA_IRQ(USART_RX_BUFF * USERT_RX_DMA) //必须在DMA中断函数中调用
{
if( DMA_GetFlagStatus(USERT_RX_DMA->USERT_Irregular_DMA->DMA_FLAG_TC) != RESET)
{
DMA_ClearFlag(USERT_RX_DMA->USERT_Irregular_DMA->DMA_FLAG_TC); //清楚接收完成标记
//标记缓存接收完成--------------------------------------------------
USERT_RX_DMA->RX_BUFF_Comple |= (0X01<<USERT_RX_DMA->RX_BUFF_Inx ); //标记缓存接收完成
if( USERT_RX_DMA->RX_BUFF_Inx == 0) //如果当前是第0个缓存接收
{
//得到缓存长度--------------------------
USERT_RX_DMA->RX_BUFF0_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
USERT_RX_DMA->RX_BUFF_Inx = 1 ; //设置缓存1 接收数据
}
else if( USERT_RX_DMA->RX_BUFF_Inx == 1)
{
//得到缓存长度--------------------------
USERT_RX_DMA->RX_BUFF1_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
USERT_RX_DMA->RX_BUFF_Inx = 0 ; //设置缓存1 接收数据
}
if( USERT_RX_DMA->RX_BUFF_Comple == 0x03 ) //如果缓存0 缓存1 都接收满
{
USERT_RX_DMA->RX_BUFF_Err = 1 ; //错误 缓存满标记
}
else //正常 则开启 DMA接收
{
USART_RX_DMA_START(USERT_RX_DMA); //开启DMA
}
}
}
[/mw_shl_code]
必须在串口中断中调用!此函数与串口中断函数功能一致,用于设置双缓存当前状态及切换缓存。
无需修改直接调用。
三、应用例子
先定义一个 USERT_Irregular_DMA_typ 结构体 ,用于加载此双缓存的硬件信息。本例子此结构体定义如下:
[mw_shl_code=c,true]const USERT_Irregular_DMA_typ USERT_Irregular_DMA =
{
115200,
RCC_APB2Periph_USART1,
USART1,
USART1_IRQn,
RCC_APB2Periph_GPIOA,
GPIOA,
GPIO_Pin_10,
GPIOA,
GPIO_Pin_9,
DMA1_Channel5,
DMA1_Channel5_IRQn,
DMA1_FLAG_TC5,
};[/mw_shl_code]
声明保存在FLASH中,用于节约SRAM
再定义一个 USART_RX_BUFF 结构体 ,并把刚才定义的 USERT_Irregular_DMA 变量 加载到结构体中(如果一个个赋值太麻烦),本例子定义如下:
[mw_shl_code=c,true]USART_RX_BUFF USRT_RX_buff = { //串口 DMA接收不定长数据 对象
0,
0,
0,
0,
0,
0,
{0},
0,
{0},
&USERT_Irregular_DMA ,
};[/mw_shl_code]
前5个成员变量用于表示双缓存状态,这里全设为0,接下来4个成员分别为对应缓存接收到的数据长度及接收缓存。最后加载双缓存硬件数据结构体。
主程序如下:
[mw_shl_code=c,true]int main(void)
{
volatile u16 i = sizeof(USART_RX_BUFF) ; //用于查看变量所用SRAM 大小
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
delay_init();
USART_DMA_Init(&USRT_RX_buff) ;
while(1)
{
if(USRT_RX_buff.RX_BUFF_Comple > 0)
{
if(USRT_RX_buff.RX_BUFF_Comple&0x01)
{
//数据处理-------------------------------------------------------------------
printf("缓存0已接收完毕
") ;
printf("接收数据长度为 :%d
",USRT_RX_buff.RX_BUFF0_Size);
printf("数据为: %s
",USRT_RX_buff.RX_BUFF0) ;
//----------------------------------------------------------------------------
USART_DMA_CLR_BUFF(&USRT_RX_buff.RX_BUFF0[0],0,USART_IrrG_DMA_BUFF_LEN) ; //清空缓存
USRT_RX_buff.RX_BUFF_Comple &=~0x01 ; //清楚接收完成标记
}
if(USRT_RX_buff.RX_BUFF_Comple&0x02)
{
//数据处理-----------------------------------------------------
printf("缓存1已接收完毕
") ;
printf("接收数据长度为 :%d
",USRT_RX_buff.RX_BUFF1_Size);
printf("数据为: %s
",USRT_RX_buff.RX_BUFF1) ;
//--------------------------------------------------------------
USART_DMA_CLR_BUFF(&USRT_RX_buff.RX_BUFF1[0],0,USART_IrrG_DMA_BUFF_LEN) ; //清空缓存
USRT_RX_buff.RX_BUFF_Comple &=~0x02 ; //清楚接收完成标记
}
if(USRT_RX_buff.RX_BUFF_Err > 0 ) //如果双缓存 溢出了 接收数据过长 缓存开小了
{
USRT_RX_buff.RX_BUFF_Err = 0 ; //清楚错误标记
printf(" 错误!双缓存溢出!
接收数据过长或者双缓存太小
") ;
//开启DMA -----------------
USART_RX_DMA_START(&USRT_RX_buff); //必须要手动开启 否则无法运行
}
}
}
}
void DMA1_Channel5_IRQHandler(void) //DMA 中断函数
{
USART_RX_DMA_DMA_IRQ(&USRT_RX_buff) ;
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
USART_RX_DMA_USART_IRQ(&USRT_RX_buff) ;
}
[/mw_shl_code]
这里 变量i 用于指示 变量 USART_RX_BUFF 占用 SAM大小。
串口中断函数及DMA中断函数写在 main.c 文件中 是用于方便提示两个函数的调用
注意!
如果接收到的数据长度大于单个缓存长度,则双缓存会关闭,必须要手动开启才能继续运行。这里只把单个缓存长度设为40字节也是为了方便测试出这个情况。
工程代码:
串口DMA接收不定长数据.zip
(341.84 KB, 下载次数: 993)
2016-7-6 09:43 上传
点击文件名下载附件
串口DMA接收不定长数据方案
谢谢论坛中的各位大神的分享,让我受益匪浅!
这个是接收串口的连续的数据的,只要是串口数据就行。串口屏是没问题的
一周热门 更多>