基于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、结果展示

有什么疑问或想要完整程序可私聊我 ,平时回消息都比较快

  • 41
    点赞
  • 139
    收藏
    觉得还不错? 一键收藏
  • 145
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 145
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值