这里为什么说RS485是MODBUS的实现载体呢,因为RS485仅仅只是一种硬件电平标准,我的理解就是在串口的基础上加了电平转换芯片比如MAX485,将串口的TTL电平转换成了RS485差分电平。
贴上代码段
1.RS485——串口2初始化
void RS485_USART2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
//GPIOA2,A3初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//复用模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
//串口初始化配置
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStruct.USART_Parity=USART_Parity_No;
USART_InitStruct.USART_StopBits=USART_StopBits_1;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;
USART_Init(USART2,&USART_InitStruct);
USART_Cmd(USART2,ENABLE);
USART_ClearFlag(USART2, USART_FLAG_TC);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
GPIO_ResetBits(GPIOG,GPIO_Pin_8);//初始化为接收状态
}
因为需要控制芯片引脚收发,所以要利用PG8来控制接收或者发送使能,具体的硬件原理图于正点原子F407的相同,大家可以参考那个。
定时器初始化
void Timer2_Init(void)//84M
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruc;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_TimeBaseInitStruc.TIM_Period=1000-1;//1ms
TIM_TimeBaseInitStruc.TIM_Prescaler=84-1;//84M/84=1MHZ->1us
TIM_TimeBaseInitStruc.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruc.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruc);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
TIM_Cmd(TIM2,ENABLE);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
}
void Timer2_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_InitStruct);
}
发送字节与发送多个字节函数
void RS485_SendByte(u8 bbyte)
{
RS_485_TX;
USART_SendData(USART2, bbyte);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
RS_485_RX;
}
void RS485_SendData(u8 *Sdbuf,u8 len)
{
u8 i;
RS_485_TX;
for(i=0;i<len;i++)
{
USART_SendData(USART2,Sdbuf[i]);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
}
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
RS_485_RX;
}
我认为这些都是基础知识,大家应该首先根据正点原子把能够利用485收发全部实现,然后再这个基础上加入485协议。
接下来就是串口的接收以及定时器中断函数,modbus的复杂主要就麻烦在如何将串口的接收中断与定时器中断配合好,达到完整的接收一帧数据,并且不影响下一帧接受的效果。
void USART2_IRQHandler()
{
u8 res;
if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
{
res =USART_ReceiveData(USART2);
modbus.ReceiveBuff[modbus.ReceiveCount]=res;
modbus.ReceiveCount++;
if(modbus.ReceiveCount==1)
{
modbus.timerun=1;
}
modbus.timecount=0;
}
}
void TIM2_IRQHandler()
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
if(modbus.timerun==1)
{
modbus.timecount++;
}
if(modbus.timecount>=5)
{
modbus.timerun=0;
modbus.timecount=0;
modbus.ReceiveComplete=1;
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
在这里为了方便,定义了一个modbus的结构体
typedef struct
{
u8 Slave_ID; //从机ID
u8 ReceiveBuff[20]; //接收缓存数组
u8 ReceiveCount; //计算接收到的数据有多少字节
u8 timecount; //有多久没有接收到字节,数据断续的时间
u8 timerun; //断续时间是否开始累加
u8 ReceiveComplete; //一帧数据接收完成标志
}MODBUS;
modbus协议主要就是,在接收时有3.5个字符间隔,那么就认为这一帧数据接收结束,可以进行处理并且接收下一帧数据。
在这里定义了一个接收缓存数组,用于将串口2接收到的数据放到缓存数组中。
大概逻辑是这样的,首先主机发送命令后,从机返回一帧数据,当接收到一帧数据的第一个字符时,计时器开始计时,timrun启动,timcount++,如果在3.5个字符内又收到了下一个字节数据,那么timcount就会置0,使他无法到达3.5个字符间隔,如果接收到了,就将数据继续放入到数组中。直到接收完毕,定时器到达3,5个字符间隔,此时接收完成ReceiveComplete=1;然后这个时候为了防止下一帧数据进来把数据覆盖掉,需要把接收缓存数组ReceiveBuff中的数据copy出去,
copy数据的函数
void RS485_Receive_Data(u8 *buf,u8 *len)
{
u8 i;
u8 Temp_len;
Temp_len=modbus.ReceiveCount;
if(modbus.ReceiveComplete==1)
{
for(i=0;i<Temp_len;i++)
{
buf[i]=modbus.ReceiveBuff[i];
}
*len=modbus.ReceiveCount;
modbus.ReceiveCount=0;
}
modbus.ReceiveComplete=0;
}
copy完后主程序处理数据,中断继续接收。
总的程序在这里。有积分的话支持一下,没积分的话评论区留下邮箱,我抽空发你。毕竟自己学习新东西也需要积分。算是形成一个良性循环吧。
程序连接https://download.csdn.net/download/zwbzero/19266609
用到的调试软件链接https://download.csdn.net/download/zwbzero/19066229