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