基于STM32的RS485传感器数据采集详解(附正点原子部分代码参考)
目前工业上,传感器一般都选RS485,modbus通讯协议,这种通讯方式,有很强的鲁棒性,本篇文章基于原子哥的精英板进行开发。
1、初始化与电脑通信的串口(PA9 PA10)
//初始化USART2
void RS485_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;//PA2(TX)复用推挽输出
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_2);//默认高电平
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;//PA3(RX)输入上拉
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //修改原GPIO_Mode_IPU(输入上拉)->GPIO_Mode_IN_FLOATING(浮空输入)/
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;//修改PG9(RE/DE)通用推挽输出->PD7(RE/DE)通用推挽输出//
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOD,&GPIO_InitStructure);
GPIO_ResetBits(GPIOD,GPIO_Pin_7);//默认接收状态
USART_DeInit(USART2);//复位串口2
USART_InitStructure.USART_BaudRate=RS485_Baudrate;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//收发模式
switch(RS485_Parity)
{
case 0:USART_InitStructure.USART_Parity=USART_Parity_No;break;//无校验
case 1:USART_InitStructure.USART_Parity=USART_Parity_Odd;break;//奇校验
case 2:USART_InitStructure.USART_Parity=USART_Parity_Even;break;//偶校验
}
USART_Init(USART2,&USART_InitStructure);
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//使能串口2接收中断
NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART2,ENABLE);//使能串口2
RS485_TX_EN=1;//模式
Timer7_Init();//定时器7初始化,用于监视空闲时间
//Modbus_RegMap();//Modbus寄存器映射
}
2、采用串口中断将数据保存到数组buff中
void RS485_SendData(u8 *buff,u8 len)
{
RS485_TX_EN=1;//切换为发送模式
while(len--)
{
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);//等待发送区为空
USART_SendData(USART2,*(buff++));
}
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
TX_RX_SET=1; //发送命令完成,定时器T4处理接收到的数据
RS485_TX_EN=0;
}
3、用定时器来配置一帧字节是否结束(空闲时间>指定时间)
void TIM7_IRQHandler(void)
{
if(TIM_GetITStatus(TIM7,TIM_IT_Update)!=RESET)
{
TIM_ClearITPendingBit(TIM7,TIM_IT_Update);//清除中断标志
TIM_Cmd(TIM7,DISABLE);//停止定时器
RS485_TX_EN=1;//默认为发送模式 485模式控制.0,接收;1,发送.
RS485_RxFlag=1;//置位帧结束标记
// errpace=1;
}
}
4、主机发送请求指令(从机地址、功能码 、起始地址、读取个数)
void modbus_rtu(void)
{
static u8 i=0;
static u8 j=0;
switch(i)
{
case 0: //modbus执行命令第一步。
//RS485_TX_Service(); //向从机发送一个请求。就在此时发送完成TX_RX_SET=1 发送命令完成,定时器T4处理接收到的数据
//在此处也可以直接写Master_Service( SlaverAddr, Fuction, StartAddr, ValueOrLenth);
//多次通讯结果可以按照类似的封装进行填写
RS485_TX_Service();
if(TX_RX_SET) i=1; //发送,接受命令切换。 0 发送模式 1 接受模式
state=1;
break;
case 1: //modbus命令执行第二步。
RS485_RX_Service(); //执行数据接收
state=2;
if(ComErr==0) //如果什么错误都没有发生
{
i=2;//完成命令更换功能码!
} //一次通讯已经完成
else //错误接收后再次准备接收
{
i=1;//
j++;//一个命令发送3次没有应答切换下一个命令
if(j>=2)
{
j=0;
i=2;
ComErr=7; //通讯超时
}
}
break;
case 2: //从机地址++
i=0;
state=3;
break;
case 3://功能码,这个是空余出来做报错以及其他处理的
break;
}
}
5、从机响应,通过校验码判断数据是否成功
u16 CRC_Compute(u8 *puchMsg, u16 usDataLen)
{
u8 uchCRCHi = 0xFF ;
u8 uchCRCLo = 0xFF ;
u32 uIndex ;
while (usDataLen--)
{
uIndex = uchCRCHi ^ *puchMsg++ ;
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;
uchCRCLo = auchCRCLo[uIndex] ;
}
return ((uchCRCHi<< 8) | (uchCRCLo)) ;
}//uint16 crc16(uint8 *puchMsg, uint16 usDataLen)
6、处理主机采集到的数据
void USART2_IRQHandler(void)//串口2中断服务程序
{
u8 res;
u8 err;
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
{
if(USART_GetFlagStatus(USART2,USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE)) {err=1;errpace=2;}//检测到噪音、帧错误或校验错误
else err=0;
res=USART_ReceiveData(USART2); //读接收到的字节,同时相关标志自动清除
if((RS485_RX_CNT<2047)&&(err==0))
{
RS485_RX_BUFF[RS485_RX_CNT]=res;
RS485_RX_CNT++;
TIM_ClearITPendingBit(TIM7,TIM_IT_Update);//清除定时器溢出中断
TIM_SetCounter(TIM7,0);//当接收到一个新的字节,将定时器7复位为0,重新计时(相当于喂狗)
TIM_Cmd(TIM7,ENABLE);//开始计时
}
}
}
void Modbus_03_Solve(void)
{
u8 i;
//u8 RegNum;
//RegNum= RS485_RX_BUFF[2]/2;//获取字节数 6---->?
if(1)//寄存器地址+数量在范围内
{
for(i=0;i<20;i++)
{
Master_ReadReg[StartAddr+i]= RS485_RX_BUFF[3+i*2]; /高8位
Master_ReadReg[StartAddr+i]= RS485_RX_BUFF[4+i*2]+(Master_ReadReg[StartAddr+i]<<8);// 低8位+高8位
Master_ReadReg[i]= RS485_RX_BUFF[3+i*2];
Master_ReadReg[i]= RS485_RX_BUFF[4+i*2]+(Master_ReadReg[i]<<8);// 低8位+高8位
}
temp=Master_ReadReg[0];
x_shock=Master_ReadReg[1];
y_shock=Master_ReadReg[2];
z_shock=Master_ReadReg[3];
//volback(message);
ComErr=0;
}
else
{
ComErr=3;
}
TX_RX_SET=0; //命令完成
}
//Modbus功能码05处理程序 ///程序已验证OK
//写单个输出开关量
void Modbus_05_Solve(void)
{
u16 i;
i=ValueOrLenth;
if((i>0&&RS485_RX_BUFF[4]==0XFF&&RS485_RX_BUFF[5]==0X00)||(i==0&&RS485_RX_BUFF[4]==0X00&&RS485_RX_BUFF[5]==0X00))
{
ComErr=0;
}
else
{
ComErr=5;
}
TX_RX_SET=0; //命令完成
}
//Modbus功能码06处理程序 //已验证程序OK
//写单个保持寄存器
void Modbus_06_Solve(void)
{
u16 i; //数据返回校验用
i=(((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if(i==Master_WriteReg[StartAddr])
{
ComErr=0;
}
else
{
ComErr=6;
}
TX_RX_SET=0; //命令完成
}
//Modbus功能码15处理程序 //程序已验证OK
//写多个输出开关量
void Modbus_15_Solve(void)
{
u16 i;//数据返回校验用
i=(((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if(i==ValueOrLenth)
{
ComErr=0;
}
else
{
ComErr=15;
}
TX_RX_SET=0; //命令完成
}
//返回温度值
u16 temperature(void)
{
return temp;
}
//返回x 振动
u16 X_shock(void)
{
return x_shock;
}
//返回y 振动
u16 Y_shock(void)
{
return y_shock;
}
//返回z 振动
u16 Z_shock(void)
{
return z_shock;
}
7、结果展示
有什么疑问或想要完整程序可私聊我 ,平时回消息都比较快