实现思路
从机通过USART2经过MAX485模块一个字节一个字节发送到主机--->
主机检测到USART2有数据输入然后DMA把数据转运到接收数据缓冲区--->
从机中一帧的最后一个字节发送完 主机触发USART2的空闲中断--->
最后在USART2的中断服务函数中获取接收数据的字节数
实现思路不是特别复杂,下面我们就在代码的海洋里快乐地玩耍吧
硬件要求
两块STM32F103C8T6核心板+两块MAX485模块
实现的功能是两块STM32F103C8T6核心板分别作为主机和从机,通过MAX485模块实现不定长数据的接收和发送
代码解析
变量以及缓冲区定义
/*接收数据缓冲区,最多接受64个字节*/
u8 ReceiveBuffer[64];
/*每一次不定长帧的长度*/
u8 FrameSize = 0;
/*ReceiveStatus指示数据读取的状态,1表明读取过一次数据,0表明没有读取过数据*/
u8 ReceiveStatus = 0;
/*一次DMA传输转运的最大字节数*/
u16 DMA1_BufferSize=64;
DMA1_BufferSize要大于一帧传输的数据量,不然会造成后续的数据丢失
USART2_IRQHandler(void);
串口中断处理函数
void USART2_IRQHandler(void)
{
/*空闲中断标志位判断*/
if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
{
/*如果没有接收过数据,那么就开始执行if语句*/
if(ReceiveStatus == 0)
{
/*暂时关闭DMA通道,防止执行下面函数的时候受到干扰*/
DMA_Cmd(DMA1_Channel6, DISABLE);
/*获取这一帧的数据长度*/
FrameSize = DMA1_BufferSize - DMA_GetCurrDataCounter(DMA1_Channel6);
/*可以在这里重新设置DMA1_BufferSize,我是没有修改仍然保持64位的*/
DMA_SetCurrDataCounter(DMA1_Channel6,DMA1_BufferSize);
/*重新打开DMA通道*/
DMA_Cmd(DMA1_Channel6, ENABLE);
/*空闲中断的标志位的清除,需要先读SR寄存器,再读DR寄存器*/
USART_ReceiveData(USART2);
/*已经获取到帧的长度,帧的数据也被存在ReceiveBuffer接收数据缓冲区,读取状态置1*/
ReceiveStatus = 1;
}
}
}
1,DMA_GetCurrDataCounter(DMA1_Channel6)函数的功能是计算我们设定的DMA1_BufferSize还剩多少字节没有传输,总的字节数DMA1_BufferSize(64个)减去还没有传输的字节数就等于已经传输的字节数,也就是这一帧的字节数。
2,空闲中断的标志位清除,直接调用USART_ClearITPendingBit是无法清除的
手册中说需要先读USART_SR(对应的函数是USART_GetITStatus(USART2, USART_IT_IDLE)),然后读USART_DR(对应的函数就是USART_ReceiveData(USART2))
RS485_Init(u32 bound);
RS485总线初始化函数
/**
* 函 数:RS485初始化
* 参 数:bound:串口波特率
* 返 回 值:
* 注意事项:
*/
void RS485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA3端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,ENABLE);//复位串口2
RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,DISABLE);//停止复位
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(USART2, &USART_InitStructure); //初始化串口
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); //开启中断
USART_Cmd(USART2, ENABLE); //使能串口
DMA_DeInit(DMA1_Channel6); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART2->DR; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ReceiveBuffer; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存
DMA_InitStructure.DMA_BufferSize = DMA1_BufferSize; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel6, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
DMA_ClearFlag(DMA1_FLAG_TC6); //清除DMA标志位
DMA_ITConfig(DMA1_Channel6, DMA_IT_TE, ENABLE);
DMA_Cmd(DMA1_Channel6, ENABLE); //使能USART2 TX DMA1 所指示的通道
USART_DMACmd(USART2,USART_DMAReq_Rx, ENABLE);
RS485_ReceiveEnable(); //默认RS485总线是工作在接收状态的
}
1,USART_DMACmd(USART2,USART_DMAReq_Rx, ENABLE);这个函数相当于USART外设到存储器的使能函数,这个是必须要开启的。如果是存储器到存储区的DMA传输,就不需要开启。
2,RS485_ReceiveEnable();默认RS485总线是工作在接收状态的哈
RS485_ReceiveEnable(void)和RS485_SendEnable(void);
接收和发送使能函数
/**
* 函 数:接收使能
* 返 回 值:无
* 注意事项:
*/
void RS485_ReceiveEnable(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
/**
* 函 数:发送使能
* 返 回 值:无
* 注意事项:
*/
void RS485_SendEnable(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_0);
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
1,接收使能和发送使能本质上是对MAX485模块上的DE和RE引脚高低电平的控制
DE和RE引脚均为高电平时,模块工作在发送状态。
DE和RE引脚均为低电平时,模块工作在接收状态。
RS485_SendData(u8 *buf,u8 len)和RS485_ReceiveData(u8 *buf);
RS485发送和接收多个字节
/**
* 函 数:RS485发送len个字节.
* 返 回 值:buf:发送区首地址
* 返 回 值:len:发送的字节数
* 注意事项:
*/
void RS485_SendData(u8 *buf,u8 len)
{
int t;
/*开启发送状态*/
RS485_SendEnable();
/*循环发送数据*/
for(t=0;t<len;t++)
{
/*判断移位寄存器为空,然后发送数据*/
while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
USART_SendData(USART2,buf[t]);
}
while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
/*因为默认是工作在接收状态的,所以重新开启接收使能*/
RS485_ReceiveEnable();
}
/**
* 函 数:RS485从数据缓冲区读取len个字节.
* 返 回 值:buf:接收区首地址
* 返 回 值:RS485_RX_CNTn:数据缓冲区接收的数据个数
* 注意事项:
*/
void RS485_ReceiveData(u8 *buf)
{
/*判断接受状态,如果接收过,那么进入if语句*/
if(ReceiveStatus == 1)
{
int i;
for(i=0;i<FrameSize;i++)
{
buf[i]=ReceiveBuffer[i];
}
/*接收完毕,重新把ReceiveStatus置为0*/
ReceiveStatus = 0;
}
1,宇宙无敌天才的你肯定一下就看明白了,连续发送/接收多个字节就是SendData/ReceiveDataT套个循环结构,要注意的是ReceiveData循环的次数是我们中断服务函数获得的帧数据长度FrameSize
RS485.h
#ifndef __RS485_H
#define __RS485_H
#include "sys.h"
extern u8 RS485_RX_BUF[64]; //接收缓冲,最大64个字节
extern u8 RS485_RX_CNT; //接收到的数据长度
extern u8 FrameSize; //接收到的帧的数据长度
void RS485_Init(u32 bound);
void RS485_ReceiveEnable(void);
void RS485_SendEnable(void);
void RS485_SendData(u8 *buf,u8 len);
void RS485_ReceiveData(u8 *buf);
#endif
百度网盘代码链接
有什么问题可以评论区留言,看到会给你回复的