发新帖我要提问
12
返回列表
打印
[其他ST产品]

HAL之modbus

[复制链接]
楼主: 逢dududu必shu
手机看帖
扫描二维码
随时随地手机跟帖
21
逢dududu必shu|  楼主 | 2022-11-30 19:55 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
回复数据解析


01-从机返回给主机自己的地址,说明这就是主机查的从机
06-功能码,代表修改单个寄存器功能,主机发啥功能码,从机就必须回什么功能码;
00 00-代表修改的起始寄存器地址.说明是0x0000.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
48 0A-循环冗余校验,是modbus的校验公式,从首个字节开始到48前面为止;

如果回复的一样,说明这个数据是修改成功的;如果功能码不是06,而是别的,说明从机回复的数据有误,主机可以做相应的处理。

如果我要修改多个寄存器,难道用06发好几次,这样不会太傻了吗?所以Modbus RTU协议包含了修改连续多个寄存器的方法,就是功能码为0x10;这个大家自己去查询,基本和上面的数据格式差不多。

使用特权

评论回复
22
逢dududu必shu|  楼主 | 2022-11-30 19:55 | 只看该作者
注意,ModBus只是一个软件协议,传输时可以通过串口来进行通信。

为了提升传输效率,可以结合DMA使用。

使用特权

评论回复
23
逢dududu必shu|  楼主 | 2022-11-30 19:57 | 只看该作者
MX配置

配置串口

使用特权

评论回复
24
逢dududu必shu|  楼主 | 2022-11-30 19:58 | 只看该作者
配置DMA

发送和接收都采用DMA

开启空闲中断

使用特权

评论回复
25
逢dududu必shu|  楼主 | 2022-11-30 19:59 | 只看该作者
关键代码
/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/
#define FunctionCode_Read_Register                 (uint8_t)0x03
#define FunctionCode_Write_Register         (uint8_t)0x06
#define Modbus_Order_LENGTH           (uint8_t)8

/* Private variables----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/      
static void Protocol_Analysis(UART_t*);  //协议分析

static void Modbus_Read_Register(UART_t*);   //读寄存器
static void Modbus_Wrtie_Register(UART_t*);  //写寄存器

/* Public variables-----------------------------------------------------------*/
Modbus_t  Modbus =
{
        1,
       
        Protocol_Analysis
};

/*
        * [url=home.php?mod=space&uid=139335]@name[/url]   Protocol_Analysis
        * [url=home.php?mod=space&uid=247401]@brief[/url]  协议分析
        * @param  UART -> 串口指针
        * @retval None      
*/
static void Protocol_Analysis(UART_t* UART)
{
        UART_t* const  COM = UART;
        uint8_t i = 0,Index = 0;
       
  //串口3停止DMA接收
        HAL_UART_DMAStop(&huart3);
       
        //过滤干扰数据,首字节为modbus地址,共8字节
        for(i=0;i<UART3_Rec_LENGTH;i++)
        {
                //检测键值起始数据Modbus.Addr
                if(Index == 0)
                {
                        if(*(COM->pucRec_Buffer+i) != Modbus.Addr)
                                continue;
                }
               
                *(COM->pucRec_Buffer+Index) = *(COM->pucRec_Buffer+i);

                //已读取7个字节
                if(Index == Modbus_Order_LENGTH)
                        break;
               
                Index++;
        }
       
        //计算CRC-16
        CRC_16.CRC_Value   =  CRC_16.CRC_Check(COM->pucRec_Buffer,6); //计算CRC值
        CRC_16.CRC_H       = (uint8_t)(CRC_16.CRC_Value >> 8);
        CRC_16.CRC_L       = (uint8_t)CRC_16.CRC_Value;
       
        //校验CRC-16
        if(((*(COM->pucRec_Buffer+6) == CRC_16.CRC_L) && (*(COM->pucRec_Buffer+7) == CRC_16.CRC_H))
                                                                                                                                                                                                ||
           ((*(COM->pucRec_Buffer+6) == CRC_16.CRC_H) && (*(COM->pucRec_Buffer+7) == CRC_16.CRC_L)))
  {
                //校验地址
                if((*(COM->pucRec_Buffer+0)) == Modbus.Addr)
                {
                        //处理数据
                        if((*(COM->pucRec_Buffer+1)) == FunctionCode_Read_Register)
                        {
                                Modbus_Read_Register(COM);
                        }
                        else if((*(COM->pucRec_Buffer+1)) == FunctionCode_Write_Register)
                        {
                                Modbus_Wrtie_Register(COM);
                        }       
                }
        }
       
        //清缓存
        for(i=0;i<UART3_Rec_LENGTH;i++)
        {
                *(COM->pucRec_Buffer+i) = 0x00;
        }
}

/*
        * @name   Modbus_Read_Register
        * @brief  读寄存器
        * @param  UART -> 串口指针
        * @retval None      
*/
static void Modbus_Read_Register(UART_t* UART)
{
        UART_t* const  COM = UART;
       
        //校验地址
        if((*(COM->pucRec_Buffer+2) == 0x9C) && (*(COM->pucRec_Buffer+3) == 0x41))
        {
                回应数据
                //地址码
                *(COM->pucSend_Buffer+0)  = Modbus.Addr;
                //功能码
                *(COM->pucSend_Buffer+1)  = FunctionCode_Read_Register;
                //数据长度
                *(COM->pucSend_Buffer+2)  = 8;
                //SHT30温度
                *(COM->pucSend_Buffer+3)  = ((uint16_t)((SHT30.fTemperature+40)*10))/256;
                *(COM->pucSend_Buffer+4)  = ((uint16_t)((SHT30.fTemperature+40)*10))%256;
                //SHT30湿度
                *(COM->pucSend_Buffer+5)  = 0;
                *(COM->pucSend_Buffer+6)  = SHT30.ucHumidity;
                //继电器状态
                *(COM->pucSend_Buffer+7)  = 0;
                *(COM->pucSend_Buffer+8)  = Relay.Status;
                //蜂鸣器状态
                *(COM->pucSend_Buffer+9)  = 0;
                *(COM->pucSend_Buffer+10) = Buzzer.Status;
               
                //插入CRC
                CRC_16.CRC_Value = CRC_16.CRC_Check(COM->pucSend_Buffer,11); //计算CRC值
                CRC_16.CRC_H     = (uint8_t)(CRC_16.CRC_Value >> 8);
                CRC_16.CRC_L     = (uint8_t)CRC_16.CRC_Value;
               
                *(COM->pucSend_Buffer+11) = CRC_16.CRC_L;
                *(COM->pucSend_Buffer+12) = CRC_16.CRC_H;
               
                //发送数据
                UART3.SendArray(COM->pucSend_Buffer,13);
        }
}

/*
        * @name   Modbus_Read_Register
        * @brief  写寄存器
        * @param  UART -> 串口指针
        * @retval None      
*/
static void Modbus_Wrtie_Register(UART_t* UART)
{
        UART_t* const  COM = UART;
        uint8_t i;
       
        回应数据
        //准备数据
        for(i=0;i<8;i++)
        {
                *(COM->pucSend_Buffer+i) = *(COM->pucRec_Buffer+i);
        }
        //发送数据
        UART3.SendArray(COM->pucSend_Buffer,8);
       
        //提取数据
        //校验地址 -> 继电器
        if((*(COM->pucRec_Buffer+2) == 0x9C) && (*(COM->pucRec_Buffer+3) == 0x43))
        {
                //控制继电器
                if(*(COM->pucRec_Buffer+5) == 0x01)
                {
                        Relay.Relay_ON();
                }
                else
                {
                        Relay.Relay_OFF();
                }
        }
       
        //校验地址 -> 蜂鸣器
        if((*(COM->pucRec_Buffer+2) == 0x9C) && (*(COM->pucRec_Buffer+3) == 0x44))
        {
                //控制蜂鸣器
                if(*(COM->pucRec_Buffer+5) == 0x01)
                {
                        Buzzer.ON();
                }
                else
                {
                        Buzzer.OFF();
                }
        }
}
/********************************************************
  End Of File
********************************************************/

使用特权

评论回复
26
逢dududu必shu|  楼主 | 2022-11-30 19:59 | 只看该作者
CRC校验代码
/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/      
static uint16_t CRC_Check(uint8_t*,uint8_t);  //CRC校验

/* Public variables-----------------------------------------------------------*/
CRC_16_t  CRC_16 = {0,0,0,CRC_Check};

/*******************************************************
说明:CRC添加到消息中时,低字节先加入,然后高字
CRC计算方法:
1.预置1个16位的寄存器为十六进制FFFF(即全为1);称此寄存器为CRC寄存器;
2.把第一个8位二进制数据(既通讯信息帧的第一个字节)与16位的CRC寄存器的低
8位相异或,把结果放于CRC寄存器;
3.把CRC寄存器的内容右移一位(朝低位)用0填补最高位,并检查右移后的移出位;
4.如果移出位为0:重复第3步(再次右移一位);
如果移出位为1:CRC寄存器与多项式A001(1010 0000 0000 0001)进行异或;
5.重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;
6.重复步骤2到步骤5,进行通讯信息帧下一个字节的处理;
7.将该通讯信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低
字节进行交换;
********************************************************/
/*
        * @name   CRC_Check
        * @brief  CRC校验
        * @param  CRC_Ptr->数组指针,LEN->长度
        * @retval CRC校验值      
*/
uint16_t CRC_Check(uint8_t *CRC_Ptr,uint8_t LEN)
{
        uint16_t CRC_Value = 0;
        uint8_t  i         = 0;
        uint8_t  j         = 0;
        CRC_Value = 0xffff;
        for(i=0;i<LEN;i++)
        {
                CRC_Value ^= *(CRC_Ptr+i);
                for(j=0;j<8;j++)
                {
                        if(CRC_Value & 0x00001)
                                CRC_Value = (CRC_Value >> 1) ^ 0xA001;
                        else
                                CRC_Value = (CRC_Value >> 1);
                }
        }
        CRC_Value = ((CRC_Value >> 8) +  (CRC_Value << 8)); //交换高低字节
        return CRC_Value;
}
/********************************************************
  End Of File
********************************************************/

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则