stm32编写Modbus步骤

慈云数据 2024-06-15 技术支持 120 0

1. modbus协议简介:

  modbus协议基于rs485总线,采取一主多从的形式,主设备轮询各从设备信息,从设备不主动上报。

日常使用都是RTU模式,协议帧格式如下所示:

  地址   功能码     寄存器地址       读取寄存器个数        寄存器数据1   .....       CrcL   CrcH

1

2

3

4

5

6

7

8

9

/*

AA      03     00     00        00     0A     DC     16

addr   cmd    regH    regL     lenH  lenL    crcL    crcH     主机发送

AA    03    14 00 00 00 00 00 00 00 00 00 03 00 01 00 00 00 18 00 1C 00 00 81 4B      从机回复

addr  cmd   datelen ....

AA      10    00     0a      00 01      02         00 02       主机修改从机寄存器值

addr   cmd   regH   regL    regNum     datalen     data

*/

   功能码及对应的操作字长:

目前比较简单的实现了读多个保持寄存器,以及写多个保持寄存器,由于不是使用的PLC,所以寄存器地址的划分没有严格按照上表,具体地址后面解释。

2.Modbus协议编写步骤:很多设备厂家都会有自己的modbus协议,大多数都不是很标准

   (1)分析板子的具体信息,编写不同的设备结构体,比如只读的结构体,可读写的结构体,保存配置信息的结构体(当主机发送改变配置信息的消息帧时,会改变相应的变量,并写入flash)

   (2) modbus寄存器映射,定义保持寄存器的指针;

   (2)本此编写采用轮询处理485串口接受到的数据,每次的间隔肯定大于3.5个字符时间(标准的Modbus帧间隔),所以不用但心接受不完整的情况。串口接收完成之后

会首先进行处理在串口数据中找出符合要求,接收正确的数据帧,并记录其功能码,输出帧的真实地址,就可以得到主机想要操作的从机的寄存器地址。

   (3)根据上一步获取的从机寄存器地址,对保持寄存器的指针进行偏移指向,即指向不同信息结构体的首地址,此过程判断寄存器地址是否溢出。

   (4)根据功能码,进行解析操作设备,读写操作就是将寄存器地址里的值直接操作指针读取出/写入。

以上过程都会判断是否错误发生,错误码如下所示:

  (1)0x01 功能码错误,或者不存在

  (2)0x02  寄存器地址超出范围

  (3)0x04 CRC校验错误

错误回复帧的格式为:地址码   功能码|0x80  错误码  CRCL    CRCH

下面就是本次用到的代码,包括将配置信息结构体读写flash:

复制代码

  1 /*******************************************  Modbus  **********************************************/
  2 
  3 uint16_t *Modbus_HoldReg = NULL;//保持寄存器
  4 TRtuCommand g_tCurRtuCmd;
  5 
  6 /*
  7 AA      03     00     00        00     0A     DC     16 
  8 addr   cmd    regH    regL     lenH  lenL    crcL    crcH      读寄存器值
  9 
 10 AA    03    14 00 00 00 00 00 00 00 00 00 03 00 01 00 00 00 18 00 1C 00 00 81 4B 
 11 addr  cmd   datelen ....
 12 
 13 AA      10    00     0a      00 01      02         00 02        写寄存器值
 14 addr   cmd   regH   regL    regNum     datalen     data
 15 */
 16 
 17 
 18 
 19 /*====================================================================
 20   函数名:Modbus_RegMap
 21   功  能:根据读取寄存器的起始地址选择映射对象,将不同的地址映射到
 22                     不同的结构体数据
 23   输入参数说明:
 24   输出参数说明:
 25   返回值说明:无
 26     备 注: 
 27  ====================================================================*/
 28 void Modbus_RegMap(uint16_t wStartAddr)
 29 {
 30     uint16_t woffset = 0;
 31     uint16_t wTemp = 0;
 32     
 33     if((wStartAddr >= REG_BASE_INFO_OFFSET) && (wStartAddr = (REG_BASE_INFO_OFFSET + REG_BASE_INFO_NUM)  && (wStartAddr = REG_CONFIG_INFO_OFFSET) && (wStartAddr = (REG_CONFIG_INFO_OFFSET + REG_CONFIG_INFO_NUM))
 50     {
 51             g_tCurRtuCmd.m_byExceptionCode = 0x02;   //异常码0x02超出寄存器范围
 52     }
 53     g_tCurRtuCmd.m_wStartAddr = woffset;
 54     g_tCurRtuCmd.m_wRegOffsetNum = wTemp;
 55 }
 56 
 57 
 58 /*====================================================================
 59   函数名:DeviceInfoRefresh
 60   功  能:更新设备运行的状态值同时更新modbus寄存器的值
 61   输入参数说明:
 62   输出参数说明:
 63   返回值说明:无
 64     备 注: 
 65  ====================================================================*/
 66 void DeviceInfoRefresh(void)
 67 {
 68 
 69         GetHumiAndTempVlue();
 70     
 71         g_tdeviceinfo.m_wlightStripRly = HAL_GPIO_ReadPin(DOOR_LED_RELAY_GPIO_Port,DOOR_LED_RELAY_Pin);
 72         g_tdeviceinfo.m_wFanRly        = HAL_GPIO_ReadPin(FAN_RELAY_GPIO_Port,FAN_RELAY_Pin);
 73         g_tdeviceinfo.m_wWarningLed1   = HAL_GPIO_ReadPin(WARNING_LED_1_GPIO_Port,WARNING_LED_1_Pin);
 74         g_tdeviceinfo.m_wWarningLed2   = HAL_GPIO_ReadPin(WARNING_LED_2_GPIO_Port,WARNING_LED_2_Pin);
 75     
 76         g_tdeviceinfo.m_wGMvalue       = LightLevelPersenGet();   /* 光照等级 */
 77         g_tdeviceinfo.m_wDoorLimit     = HAL_GPIO_ReadPin(LIMIT_SW_DOOR_GPIO_Port,LIMIT_SW_DOOR_Pin);
 78         g_tdeviceinfo.m_wWaterLimit    = HAL_GPIO_ReadPin(WATER_MARK_GPIO_Port,WATER_MARK_Pin);
 79         g_tdeviceinfo.m_Temp                 = (uint16_t)s_tsht2xInfo.m_fTemp;
 80         g_tdeviceinfo.m_Humi                     = (uint16_t)s_tsht2xInfo.m_fHumi;
 81         g_tdeviceinfo.m_vibration          =  Mma8452StatusGet();
 82 }
 83 
 84 
 85 /*====================================================================
 86   函数名:RtuReceiveHandle
 87   功  能:处理接受到的modbus数据,并读取/设置相应寄存器的值
 88   输入参数说明:
 89     pbydata :串口接收到的数据
 90   输出参数说明:
 91     dwLength :输入数据长度
 92   返回值说明:无
 93     备注:由于modubusRtu函数不支持功能码0x06(写单一寄存器),所以0x06不处理
 94  ====================================================================*/
 95 void RtuReceiveHandle(uint8_t *pbydata,uint32_t dwLength)
 96 {        
 97         uint8_t i;
 98         uint16_t wCrc = 0;
 99         uint16_t wIndex = 0, wRealLength = 0, wStartOff = 0;
100         uint8_t byAddr = (g_tConfigInfo.m_bydeviceAddr) & 0xFF;
101         g_tCurRtuCmd.m_byExceptionCode = 0;
102     
103         if(pbydata == NULL || dwLength == 0)
104         {
105             TCLX_PLATFORM_DIAG(("No data received\n"));
106             return;
107         }
108             
109         for(wIndex = 0; wIndex  8) & 0x00FF;
139                              
140                                 usart_send(USART_485_INDEX, abySendData, g_tCurRtuCmd.m_wRegNum*2 + 5);
141                             }
142                             else
143                             {
144                                          g_tCurRtuCmd.m_byExceptionCode = 0x02;   //异常码,超出寄存范围
145                             }
146                         break;
147                     case 0x06:
148                             
149                             Modbus_HoldReg[g_tCurRtuCmd.m_wStartAddr]  = pbydata[wStartOff + 4]8)&0xFF;
161                             abySendData[7]=(wCrc)&0xFF;
162                             usart_send(USART_485_INDEX, abySendData,8);
163                         break;
164                     
165                     case 0x10:
166                             g_tCurRtuCmd.m_wRegNum = (pbydata[wStartOff + 4] PD7(RE/DE)通用推挽输出//
119         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
120         GPIO_Init(GPIOG,&GPIO_InitStructure);
121         GPIO_ResetBits(GPIOG,GPIO_Pin_9);//默认接收状态
122         
123         USART_DeInit(USART2);//复位串口2
124         USART_InitStructure.USART_BaudRate=RS485_Baudrate;
125         USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
126         USART_InitStructure.USART_WordLength=USART_WordLength_8b;
127         USART_InitStructure.USART_StopBits=USART_StopBits_1;
128         USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//收发模式
129         switch(RS485_Parity)
130         {
131                 case 0:USART_InitStructure.USART_Parity=USART_Parity_No;break;//无校验
132                 case 1:USART_InitStructure.USART_Parity=USART_Parity_Odd;break;//奇校验
133                 case 2:USART_InitStructure.USART_Parity=USART_Parity_Even;break;//偶校验
134         }
135         USART_Init(USART2,&USART_InitStructure);
136         
137         USART_ClearITPendingBit(USART2,USART_IT_RXNE);
138         USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//使能串口2接收中断
139         
140         NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn;
141         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
142         NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
143         NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
144         NVIC_Init(&NVIC_InitStructure);
145         
146         USART_Cmd(USART2,ENABLE);//使能串口2
147         RS485_TX_EN=0;//默认为接收模式
148         
149         Timer7_Init();//定时器7初始化,用于监视空闲时间
150         Modbus_RegMap();//Modbus寄存器映射
151 }
152 
153 //定时器7初始化
154 void Timer7_Init(void)
155 {
156         TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
157         NVIC_InitTypeDef NVIC_InitStructure;
158 
159         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE); //TIM7时钟使能 
160 
161         //TIM7初始化设置
162         TIM_TimeBaseStructure.TIM_Period = RS485_Frame_Distance*10; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
163         TIM_TimeBaseStructure.TIM_Prescaler =7200; //设置用来作为TIMx时钟频率除数的预分频值 设置计数频率为10kHz
164         TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
165         TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
166         TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
167 
168         TIM_ITConfig( TIM7, TIM_IT_Update, ENABLE );//TIM7 允许更新中断
169 
170         //TIM7中断分组配置
171         NVIC_InitStructure.NVIC_IRQChannel =TIM7_IRQn;  //TIM7中断
172         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //先占优先级2级
173         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
174         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
175         NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器                   
176 }
177 
178 
179 
180 //
181 //发送n个字节数据
182 //buff:发送区首地址
183 //len:发送的字节数
184 void RS485_SendData(u8 *buff,u8 len)
185 { 
186         RS485_TX_EN=1;//切换为发送模式
187         while(len--)
188         {
189                 while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);//等待发送区为空
190                 USART_SendData(USART2,*(buff++));
191         }
192         while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
193 }
194 
195 
196 /
197 void USART2_IRQHandler(void)//串口2中断服务程序
198 {
199        
200         u8 res;
201         u8 err;
202      
203         if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
204         {
205                 if(USART_GetFlagStatus(USART2,USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE)) err=1;//检测到噪音、帧错误或校验错误
206                 else err=0;
207                 LED0=0;
208                 res=USART_ReceiveData(USART2); //读接收到的字节,同时相关标志自动清除
209                 
210                 if((RS485_RX_CNT
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon