目录
一、背景介绍
二、主机代码
1、串口初始化配置
2、发送函数定义
3、串口接收中断函数定义
4、定时中断(用于主机发送指令)
5、.h文件?
三、从机代码
1、串口初始化配置
2、发送函数定义
3、串口接收中断函数定义
4、.h文件
四、测试结果
五、注意事项
一、背景介绍
? ? ? ? 项目开发需要用到stm32的串口实现485通信,整个调试过程花了一天半,比预想中的长,期间陆续解决了几个小问题,有些是硬件上的问题,最后总算是把整套代码调试通顺。整理了一下,放在这里供有需要的人参考。
? ? ? ? 因为需要实现多个stm32f103芯片之间的数据交互,485通信为半双工模式,因此代码包含了主机和从机两个部分。为了便于多装置组网,整体上采用主机问询-从机应答的模式,保证同一时间网络中只有一个装置发数据,避免发生通信冲突。
? ? ? ? 具体的规约设计需根据实际需求而定,本文尽量采用简单实例,便于清晰展示485通信功能的整体架构和逻辑。
二、主机代码
1、串口初始化配置
void usart2_init(u32 baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
//TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//RN
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = baud;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//长度为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//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); //初始化串口2
USART_Cmd(USART2, ENABLE); //使能串口2
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接收中断
//中断优先级配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
RS485_RN = 0;//初始化时默认为接收模式
}
2、发送函数定义
u8 RS485_Send(u8 *buf,u8 len)
{
u8 i;
for(i=0;i<len;i++)
{
USART_SendData(USART2,buf[i]);
}
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
return 1;
}
3、串口接收中断函数定义
void USART2_IRQHandler(void)
{
u8 readd;
u8 error;
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
{
//检测噪音、帧错误或校验位错误
if(USART_GetFlagStatus(USART2,USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE))
{
error = 1;
}
else
{
error = 0;
}
readd = USART_ReceiveData(USART2); //读取接收到的字节
if((RS485_RX_CNT < 8)&&(error == 0))
{
//按照规约设置,一帧数据包含8字节,逐个接收
RS485_RX_BUFF[RS485_RX_CNT]=res;
RS485_RX_CNT++;
}
//一帧数据接收完毕,按照规约,进行数据整理,根据实际需求设计规约
if(RS485_RX_CNT == 8)
{
RS485_RX_CNT = 0;
/*接收数据整理*/
//发送标志位为1,表示主机数据接收完毕,可以准备发送新指令
RS485_TX_EN = 1;
}
}
}
4、定时中断(用于主机发送指令)
void TIM3_IRQHandler(void)
{
u8 i;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
if(RS485_TX_EN)//如果接收中断结束,表示可以发送新的指令
{
RS485_TX_EN = 0;//置0,因为发送完毕后需要等待从机的返回数据,避免通信冲突
//发送数据赋值,这里仅以简单数组表示
RS485_TX_BUFF[0] = 0x01;
RS485_TX_BUFF[1] = 0x01;
RS485_TX_BUFF[2] = 0x01;
RS485_TX_BUFF[3] = 0x01;
RS485_TX_BUFF[4] = 0x01;
RS485_TX_BUFF[5] = 0x01;
RS485_TX_BUFF[6] = 0x01;
//最后一个字节设置为校验位,生成校验值
RS485_TX_BUFF[7] = 0x00;
for(i=0;i<7;i++)
{
RS485_TX_BUFF[7] += RS485_TX_BUFF[i];
}
//数据发送
RS485_RN = 1;
RS485_Send(RS485_TX_BUFF,8);
RS485_RN = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //??3yTIM3μ??D??′y′|àí??
}
}
5、.h文件?
#ifndef __USART2_H
#define __USART2_H
#include "all.h"
#define RS485_RN PDout(7)
void usart2_init(u32 baud);
u8 RS485_Send(u8 *buf,u8 len);
#endif
三、从机代码
1、串口初始化配置
????????与主机相同
2、发送函数定义
????????与主机相同
3、串口接收中断函数定义
? ? ? ? 基本流程是先接收,然后校验,最后生成返回值并发送。作为从机,不会主动向外发送信息,仅根据接收到的数据按照规约发送相应数据返回给主机。
void USART2_IRQHandler(void)
{
u8 readd;
u8 error;
u8 check_temp = 0;
u8 i;
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
{
//检测噪音、帧错误或校验错误
if(USART_GetFlagStatus(USART2,USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE))
{
error = 1;
}
else
{
error = 0;
}
//读取接收字节
readd = USART_ReceiveData(USART2);
//逐个读取各字节
if((RS485_RX_CNT < 8)&&(error == 0))
{
RS485_RX_BUFF[RS485_RX_CNT]=res;
RS485_RX_CNT++;
}
//8字节读取完毕,进行数据整理,及返回数据发送
if(RS485_RX_CNT == 8)
{
RS485_RX_CNT = 0;
//首先进行数据校验
for(i=0;i<7;i++)
{
check_temp += RS485_RX_BUFF[i];
}
//若校验通过,返回一组数据
if(check_temp == RS485_RX_BUFF[7])
{
//组织返回数据
RS485_TX_BUFF[0] = 0x10;
RS485_TX_BUFF[1] = 0x10;
RS485_TX_BUFF[2] = 0x10;
RS485_TX_BUFF[3] = 0x10;
RS485_TX_BUFF[4] = 0x10;
RS485_TX_BUFF[5] = 0x10;
RS485_TX_BUFF[6] = 0x10;
//生成返回数据的校验值
RS485_TX_BUFF[7] = 0x00;
for(i=0;i<7;i++)
{
RS485_TX_BUFF[7] += RS485_TX_BUFF[i];
}
//数据发送
RS485_RN = 1;
RS485_Send(RS485_TX_BUFF,8);
RS485_RN = 0;
}
//若校验不通过,返回另一组数据
else
{
//组织返回数据
RS485_TX_BUFF[0] = 0x11;
RS485_TX_BUFF[1] = 0x00;
RS485_TX_BUFF[2] = 0x00;
RS485_TX_BUFF[3] = 0x00;
RS485_TX_BUFF[4] = 0x00;
RS485_TX_BUFF[5] = 0x00;
RS485_TX_BUFF[6] = 0x00;
//生成返回数据的校验值
RS485_TX_BUFF[7] = 0x00;
for(i=0;i<7;i++)
{
RS485_TX_BUFF[7] += RS485_TX_BUFF[i];
}
//数据发送
RS485_RN = 1;
RS485_Send(RS485_TX_BUFF,8);
RS485_RN = 0;
}
}
}
}
4、.h文件
? ? ? ? 与主机相同
四、测试结果
? ? ? ? 利用两块带有485接口的开发板进行测试,主机采用调试模式,测试结果如图:
? ? ? ? ?结论:发送数据和接收数据符合预期,通信正确。
五、注意事项
????????1)本设计中的485采用半双工,因此现实中通信规约和主从机的发送接收机制需要重点设计,尤其要考虑发送与接收之间的时延,避免出现通信冲突;
????????2)本文仅验证了包含一台主机和一台从机的简单系统,其在复杂系统中的应用效果有待进一步测试。
|