看這篇文章之前要對Modbus協議要有一個簡單的了解,本篇文章以STM32單片機為例寫一個簡易版的從機Modbus.
Modbus通信機制需要單片機兩個外設資源:串口和定時器。
設一個向上計數的定時器,計數周期為3.5個字符的時間。3.5個字符時間如何計算請參考這個https://zhidao.baidu.com/question/2266066387336737428.html
其實這個時間設長一點也沒關系,比如設個50ms,100ms甚至是1s,如果設為1s,主機的Modbus發送兩幀數據的間隔就不能低於1s,看完從機的具體實現就會明白為什么了。
用Stm32CubeIDE配置一個50ms中斷的定時器
再配置串口
串口中斷回調函數如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { /* Prevent unused argument(s) compilation warning */ UNUSED(huart); /* NOTE: This function Should not be modified, when the callback is needed, the HAL_UART_TxCpltCallback could be implemented in the user file */ if(huart->Instance == huart1.Instance) { Rx_Buf[RxCount++] = aRx1Buffer;//把接收到的數據存入接收緩存數組中 __HAL_TIM_SET_COUNTER(&htim7,0);//只要有數據進來就清空定時器計數器,如果沒有新的數據進來即從此刻開始計時,50ms后進入定時器中斷回調函數,此時意味着接收完一幀數據。 HAL_TIM_Base_Start_IT(&htim7);//啟動定時器 HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRx1Buffer, 1); //再開啟接收串口中斷 } }
在定時器中斷回調函數如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == htim7.Instance) { SLAVE_RS485_SEND_MODE;//485切換成發送模式,不再接受新的串口中斷 HAL_TIM_Base_Stop_IT(&htim7);//停止定時器 MB_Parse_Data();// 提取數據幀,進行解析數據幀 MB_Analyze_Execute();//對接收到的數據進行分析並執行 RxCount = 0;//清空接收計數 SLAVE_RS485_RECEIVE_MODE;//數據處理完畢后,再重新接收串口中斷 } }
串口引腳接了485芯片,SLAVE_RS485_SEND_MODE和SLAVE_RS485_RECEIVE_MODE就是普通的IO口,用於控制485芯片發送與接收模式
#define SLAVE_RS485_SEND_MODE HAL_GPIO_WritePin(USART1_EN_GPIO_Port, USART1_EN_Pin, GPIO_PIN_SET) #define SLAVE_RS485_RECEIVE_MODE HAL_GPIO_WritePin(USART1_EN_GPIO_Port, USART1_EN_Pin, GPIO_PIN_RESET)
接下來看提取數據幀函數
/* 提取數據幀,進行解析數據幀 */ void MB_Parse_Data() { PduData.Code = Rx_Buf[1]; // 功能碼 PduData.Addr = ((Rx_Buf[2]<<8) | Rx_Buf[3]);// 寄存器起始地址 PduData.Num = ((Rx_Buf[4]<<8) | Rx_Buf[5]);// 數量(Coil,Input,Holding Reg,Input Reg) PduData._CRC = MB_CRC16((uint8_t*)&Rx_Buf,RxCount-2); // CRC校驗碼 PduData.byteNums = Rx_Buf[6]; // 獲得字節數 }
PduData是一個結構體如下:
/* 類型定義 ------------------------------------------------------------------*/ typedef struct { __IO uint8_t Code ; // 功能碼 __IO uint8_t byteNums; // 字節數 __IO uint16_t Addr ; // 操作內存的起始地址 __IO uint16_t Num; // 寄存器或者線圈的數量 __IO uint16_t _CRC; // CRC校驗碼 __IO uint8_t *ValueReg; // 10H功能碼的數據 }PDUData_TypeDef;
MB_CRC16校驗函數請參考:https://www.cnblogs.com/lizhiqiang0204/p/12122928.html
接下來看最重要的對接收到的數據進行分析並執行,我們以簡單的寫線圈寄存器為例
/** * 函數功能: 對接收到的數據進行分析並執行 * 輸入參數: 無 * 返 回 值: 異常碼或0x00 * 說 明: 判斷功能碼,驗證地址是否正確.數值內容是否溢出,數據沒錯誤就發送響應信號 */ uint8_t MB_Analyze_Execute(void ) { uint16_t ExCode = EX_CODE_NONE; uint16_t tem_crc; if(PduData._CRC !=((Rx_Buf[RxCount-1])<<8 | Rx_Buf[RxCount-2])) { /* Modbus異常響應 */ ExCode = EX_CODE_02H; // 異常碼02H return ExCode; } /* 根據功能碼分別做判斷 */ switch(PduData.Code) { /* ---- 01H 02H 讀取離散量輸入(Coil Input)---------------------- */ case FUN_CODE_01H: break; case FUN_CODE_02H: break; case FUN_CODE_03H: break; case FUN_CODE_04H: break; case FUN_CODE_05H: /* 寫入一個線圈值 */ if(PduData.Num == 0xFF00) { usCoilBuf[PduData.Addr] = 1;//把這個PduData.Addr地址的線圈寄存器寫1 } else { usCoilBuf[PduData.Addr] = 0;//把這個PduData.Addr地址的線圈寄存器寫0 } MB_SendRX();//把接收到的數據原封不動的發送出去(即應答) break; } /* 數據幀沒有異常 */ return ExCode; // EX_CODE_NONE }
uint8_t usCoilBuf[100] ;//定義100個線圈寄存器 uint16_t usRegInputBuf[100] ;//定義100個輸入寄存器 uint16_t usRegHoldingBuf[100] ;//定義100個保持寄存器 __IO uint8_t Rx_Buf[256]; // 接收緩存,最大256字節 __IO uint8_t Tx_Buf[256]; // 發送緩存,最大256字節 __IO uint16_t RxCount = 0; // 接收字符計數
數據分析執行函數MB_Analyze_Execute除了驗證校驗位,還應該對寄存器的地址進行檢查是否越位。上面的執行函數MB_Analyze_Execute只是對功能碼05H寫單個線圈寄存器進行回應操作,接下來分別補充10H寫多個保持寄存器和04H讀多個輸入寄存器的回應操作。
case FUN_CODE_10H://寫入多個保持寄存器 for(int i = 0;i < PduData.Num;i++) { usRegHoldingBuf[PduData.Addr + i] = (Rx_Buf[i*2+7] << 8) | (Rx_Buf[i*2+8] << 0); } //響應寫多個保持寄存器 Tx_Buf[0] = Rx_Buf[0];//從機地址 Tx_Buf[1] = Rx_Buf[1];//功能碼 Tx_Buf[2] = Rx_Buf[2];//起始地址高位 Tx_Buf[3] = Rx_Buf[3];//起始地址低位 Tx_Buf[4] = Rx_Buf[4];//數量高位 Tx_Buf[5] = Rx_Buf[5];//數量低位 tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,6); // CRC校驗碼 Tx_Buf[6] = (uint8_t)tem_crc;//CRC高位 Tx_Buf[7] = (uint8_t)(tem_crc >> 8);//CRC低位 HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, 8, HAL_MAX_DELAY); break;
case FUN_CODE_04H://寫多個輸入寄存器 Tx_Buf[0] = Rx_Buf[0];//從機地址 Tx_Buf[1] = PduData.Code;//功能碼 Tx_Buf[2] = PduData.Num * 2;//發送字節數 for(uint8_t i = 0; i <PduData.Num;i++) { //把對應地址的輸入寄存器寫入發送緩沖數組中 Tx_Buf[i*2+3] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 8); Tx_Buf[i*2+4] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3); // CRC校驗碼 Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc; Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8);//填寫校驗位 HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY);//串口發送 break;

/* * cus_modbus.c * * Created on: Dec 25, 2019 * Author: LiZhiqiang */ #include "stm32g0xx_hal.h" #include "cus_modbus.h" #include "usart.h" uint16_t usDiscreteInputStart ; uint8_t usDiscreteInputBuf[DISCRETE_INPUT_NDISCRETES/8] ; uint16_t usCoilStart ; uint8_t usCoilBuf[COIL_NCOILS/8] ; uint16_t usRegInputStart ; uint16_t usRegInputBuf[REG_INPUT_NREGS] ; uint16_t usRegHoldingStart ; uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] ; __IO uint8_t Rx_Buf[256]; // 接收緩存,最大256字節 __IO uint8_t Tx_Buf[256]; // 發送緩存,最大256字節 __IO uint8_t tmp_Rx_Buf; // 臨時接收緩存 __IO uint16_t RxCount = 0; // 接收字符計數 __IO uint8_t Addr_Slave = 3;//從機地址 PDUData_TypeDef PduData; // CRC 高位字節值表 static const uint8_t auchCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 } ; // CRC 低位字節值表 static const uint8_t auchCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 }; void MB_10H_WR_NReg(uint16_t* _AddrOffset,uint16_t _RegNum , uint8_t* _Datebuf); //把接受到的數據再回復出去 void MB_SendRX() { HAL_UART_Transmit(&huart1, (uint8_t*)&Rx_Buf, RxCount, HAL_MAX_DELAY); } /* 函數體 --------------------------------------------------------------------*/ /** * 函數功能: Modbus CRC16 校驗計算函數 * 輸入參數: pushMsg:待計算的數據首地址,usDataLen:數據長度 * 返 回 值: CRC16 計算結果 * 說 明: 計算結果是高位在前,需要轉換才能發送 */ uint16_t MB_CRC16(uint8_t *_pushMsg,uint8_t _usDataLen) { uint8_t uchCRCHi = 0xFF; uint8_t uchCRCLo = 0xFF; uint16_t uIndex; while(_usDataLen--) { uIndex = uchCRCLo ^ *_pushMsg++; uchCRCLo = uchCRCHi^auchCRCHi[uIndex]; uchCRCHi = auchCRCLo[uIndex]; } return (uchCRCHi<<8|uchCRCLo); } /* 提取數據幀,進行解析數據幀 */ void MB_Parse_Data() { PduData.Code = Rx_Buf[1]; // 功能碼 PduData.Addr = ((Rx_Buf[2]<<8) | Rx_Buf[3]);// 寄存器起始地址 PduData.Num = ((Rx_Buf[4]<<8) | Rx_Buf[5]);// 數量(Coil,Input,Holding Reg,Input Reg) PduData._CRC = MB_CRC16((uint8_t*)&Rx_Buf,RxCount-2); // CRC校驗碼 PduData.byteNums = Rx_Buf[6]; // 獲得字節數 PduData.ValueReg = (uint8_t*)&Rx_Buf[7]; // 寄存器值起始地址 PduData.PtrCoilOffset = PduData.PtrCoilbase + PduData.Addr; // 離散量的內存起始地址 PduData.PtrHoldingOffset = PduData.PtrHoldingbase + PduData.Addr; // 保持寄存器的起始地址 } /** * 函數功能: 對接收到的數據進行分析並執行 * 輸入參數: 無 * 返 回 值: 異常碼或0x00 * 說 明: 判斷功能碼,驗證地址是否正確.數值內容是否溢出,數據沒錯誤就發送響應信號 */ uint8_t MB_Analyze_Execute(void ) { uint16_t ExCode = EX_CODE_NONE; uint16_t tem_crc; MB_Parse_Data(); /* 校驗功能碼 */ if( IS_NOT_FUNCODE(PduData.Code) ) // 不支持的功能碼 { /* Modbus異常響應 */ ExCode = EX_CODE_01H; // 異常碼01H return ExCode; } if(PduData._CRC !=((Rx_Buf[RxCount-1])<<8 | Rx_Buf[RxCount-2])) { /* Modbus異常響應 */ ExCode = EX_CODE_02H; // 異常碼02H return ExCode; } /* 根據功能碼分別做判斷 */ switch(PduData.Code) { case FUN_CODE_01H://讀線圈寄存器 Tx_Buf[0] = Rx_Buf[0];//從機地址 Tx_Buf[1] = PduData.Code;//功能碼 if(PduData.Num % 8 == 0)//如果讀取線圈的數量是8的整數倍,則返回字節數Tx_Buf[2] = PduData.Num / 8 Tx_Buf[2] = PduData.Num / 8; else//如果不是8的整數倍,則加一 Tx_Buf[2] = PduData.Num / 8 + 1; for(int i = 0; i < Tx_Buf[2];i++) { Tx_Buf[3 + i] = (usCoilBuf[PduData.Addr+7 + i*8] << 7) | (usCoilBuf[PduData.Addr +6 +i*8] << 6) |(usCoilBuf[PduData.Addr+5 + i*8] << 5) | (usCoilBuf[PduData.Addr +4 +i*8] << 4) |(usCoilBuf[PduData.Addr+3 + i*8] << 3) | (usCoilBuf[PduData.Addr +2 +i*8] << 2) |(usCoilBuf[PduData.Addr+1 + i*8] << 1) | (usCoilBuf[PduData.Addr +0 +i*8] << 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,Tx_Buf[2]+3); // CRC校驗碼 Tx_Buf[Tx_Buf[2]+3] = (uint8_t)tem_crc; Tx_Buf[Tx_Buf[2]+4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, Tx_Buf[2] +5, HAL_MAX_DELAY); break; case FUN_CODE_02H://讀離散輸入寄存器,其實讀離散輸入寄存器和讀線圈寄存器類似 Tx_Buf[0] = Rx_Buf[0];//從機地址 Tx_Buf[1] = PduData.Code;//功能碼 if(PduData.Num % 8 == 0)//如果讀取線圈的數量是8的整數倍,則返回字節數Tx_Buf[2] = PduData.Num / 8 Tx_Buf[2] = PduData.Num / 8; else//如果不是8的整數倍,則加一 Tx_Buf[2] = PduData.Num / 8 + 1; for(int i = 0; i < Tx_Buf[2];i++) { Tx_Buf[3 + i] = (usDiscreteInputBuf[PduData.Addr+7 + i*8] << 7) | (usDiscreteInputBuf[PduData.Addr +6 +i*8] << 6) |(usDiscreteInputBuf[PduData.Addr+5 + i*8] << 5) | (usDiscreteInputBuf[PduData.Addr +4 +i*8] << 4) |(usDiscreteInputBuf[PduData.Addr+3 + i*8] << 3) | (usDiscreteInputBuf[PduData.Addr +2 +i*8] << 2) |(usDiscreteInputBuf[PduData.Addr+1 + i*8] << 1) | (usDiscreteInputBuf[PduData.Addr +0 +i*8] << 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,Tx_Buf[2]+3); // CRC校驗碼 Tx_Buf[Tx_Buf[2]+3] = (uint8_t)tem_crc; Tx_Buf[Tx_Buf[2]+4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, Tx_Buf[2] +5, HAL_MAX_DELAY); break; case FUN_CODE_03H://響應讀保持寄存器 Tx_Buf[0] = Rx_Buf[0]; Tx_Buf[1] = PduData.Code; Tx_Buf[2] = PduData.Num * 2; for(uint8_t i = 0; i <PduData.Num;i++) { Tx_Buf[i*2+3] = (uint8_t)(usRegHoldingBuf[PduData.Addr+i] >> 8); Tx_Buf[i*2+4] = (uint8_t)(usRegHoldingBuf[PduData.Addr+i] >> 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3); // CRC校驗碼 Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc; Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY); break; case FUN_CODE_04H://響應讀輸入寄存器 Tx_Buf[0] = Rx_Buf[0]; Tx_Buf[1] = PduData.Code; Tx_Buf[2] = PduData.Num * 2; for(uint8_t i = 0; i <PduData.Num;i++) { Tx_Buf[i*2+3] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 8); Tx_Buf[i*2+4] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3); // CRC校驗碼 Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc; Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY); break; case FUN_CODE_05H: /* 寫入一個線圈值 */ if(PduData.Num == 0xFF00) { usCoilBuf[PduData.Addr] = 1; } else { usCoilBuf[PduData.Addr] = 0; } MB_SendRX();//返回發送的指令作為響應 break; case FUN_CODE_06H://寫單個保持寄存器 usRegHoldingBuf[PduData.Addr] = (Rx_Buf[4] << 8) | (Rx_Buf[5] << 0); MB_SendRX();//返回發送的指令作為響應 break; case FUN_CODE_10H://寫入多個保持寄存器 for(int i = 0;i < PduData.Num;i++) { usRegHoldingBuf[PduData.Addr + i] = (Rx_Buf[i*2+7] << 8) | (Rx_Buf[i*2+8] << 0); } //響應寫多個保持寄存器 Tx_Buf[0] = Rx_Buf[0];//從機地址 Tx_Buf[1] = Rx_Buf[1];//功能碼 Tx_Buf[2] = Rx_Buf[2];//起始地址高位 Tx_Buf[3] = Rx_Buf[3];//起始地址低位 Tx_Buf[4] = Rx_Buf[4];//數量高位 Tx_Buf[5] = Rx_Buf[5];//數量低位 tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,6); // CRC校驗碼 Tx_Buf[6] = (uint8_t)tem_crc;//CRC高位 Tx_Buf[7] = (uint8_t)(tem_crc >> 8);//CRC低位 HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, 8, HAL_MAX_DELAY); break; } /* 數據幀沒有異常 */ return ExCode; // EX_CODE_NONE } /** * 函數功能: 寫,讀N個寄存器 * 輸入參數: _AddrOffset:偏移地址,_RegNum:寄存器數量,_Datebuf:數據指針 * 返 回 值: 異常碼:04H或NONE * 說 明: 在_AddrOffset所指向的空間里寫入_RegNum*2個數據,並且讀取驗證是否寫入成功 */ void MB_10H_WR_NReg(uint16_t* _AddrOffset,uint16_t _RegNum , uint8_t* _Datebuf) { uint16_t i = 0; uint16_t Value = 0; for(i=0;i<_RegNum;i++) { Value = (uint16_t)((*_Datebuf<<8 ) | (*(_Datebuf+1))); *_AddrOffset++ = Value ; _Datebuf+=2; } MB_SendRX(); }

/* * cus_modbus.h * * Created on: Dec 24, 2019 * Author: LiZhiqiang */ #ifndef INC_CUS_MODBUS_H_ #define INC_CUS_MODBUS_H_ #include "stm32g0xx_hal.h" #include "cmsis_os.h" /* 類型定義 ------------------------------------------------------------------*/ typedef struct { __IO uint8_t Code ; // 功能碼 __IO uint8_t byteNums; // 字節數 __IO uint16_t Addr ; // 操作內存的起始地址 __IO uint16_t Num; // 寄存器或者線圈的數量 __IO uint16_t _CRC; // CRC校驗碼 __IO uint8_t *ValueReg; // 10H功能碼的數據 __IO uint8_t *PtrCoilbase; // Coil和Input內存首地址 __IO uint8_t *PtrCoilOffset; // Coil和Input偏移內存首地址 __IO uint16_t *PtrHoldingbase; // HoldingReg內存首地址 __IO uint16_t *PtrHoldingOffset;// HoldingReg內存首地址 }PDUData_TypeDef; /* 宏定義 --------------------------------------------------------------------*/ #define MB_SLAVEADDR 0x0001 #define MB_ALLSLAVEADDR 0x00FF #define FUN_CODE_01H 0x01 // 功能碼01H #define FUN_CODE_02H 0x02 // 功能碼02H #define FUN_CODE_03H 0x03 // 功能碼03H #define FUN_CODE_04H 0x04 // 功能碼04H #define FUN_CODE_05H 0x05 // 功能碼05H #define FUN_CODE_06H 0x06 // 功能碼06H #define FUN_CODE_10H 0x10 // 功能碼10H /* 本例程所支持的功能碼,需要添加新功能碼還需要在.c文件里面添加 */ #define IS_NOT_FUNCODE(code) (!((code == FUN_CODE_01H)||\ (code == FUN_CODE_02H)||\ (code == FUN_CODE_03H)||\ (code == FUN_CODE_04H)||\ (code == FUN_CODE_05H)||\ (code == FUN_CODE_06H)||\ (code == FUN_CODE_10H))) #define EX_CODE_NONE 0x00 // 異常碼 無異常 #define EX_CODE_01H 0x01 // 異常碼 #define EX_CODE_02H 0x02 // 異常碼 校驗錯誤 #define EX_CODE_03H 0x03 // 異常碼 #define EX_CODE_04H 0x04 // 異常碼 /* ----------------------- modbus reg lengh Defines ------------------------------------------*/ /* ----------------------- modbus 各個寄存器數據長度,允許用戶調用的數據----------------------*/ #define DISCRETE_INPUT_START 1 #define DISCRETE_INPUT_NDISCRETES 96 #define COIL_START 1 #define COIL_NCOILS 96 #define REG_INPUT_START 1 #define REG_INPUT_NREGS 100 #define REG_HOLDING_START 1 #define REG_HOLDING_NREGS 100 /* ----------------------- modbus Static variables defines------------------------------------*/ extern uint16_t usDiscreteInputStart ; extern uint8_t usDiscreteInputBuf[DISCRETE_INPUT_NDISCRETES/8] ; extern uint16_t usCoilStart ; extern uint8_t usCoilBuf[COIL_NCOILS/8] ; extern uint16_t usRegInputStart ; extern uint16_t usRegInputBuf[REG_INPUT_NREGS] ; extern uint16_t usRegHoldingStart ; extern uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] ; extern __IO uint8_t Addr_Slave; extern __IO uint8_t Rx_Buf[256]; // 接收緩存,最大256字節 extern __IO uint8_t Tx_Buf[256]; // 接收緩存,最大256字節 extern __IO uint8_t tmp_Rx_Buf; // 接收緩存 extern __IO uint16_t RxCount; // 接收字符計數 extern PDUData_TypeDef PduData; void MB_Parse_Data(); uint8_t MB_Analyze_Execute(void ); #endif /* INC_CUS_MODBUS_H_ */
設定從機地址的話,只需要在定時器回調函數中限制一下即可

/* USER CODE BEGIN 1 */ __IO uint8_t Addr_Slave = 3;//設定從機地址 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == htim7.Instance) { SLAVE_RS485_SEND_MODE;//485切換成發送模式,不再接受新的串口中斷 HAL_TIM_Base_Stop_IT(&htim7);//停止定時器 if(Rx_Buf[0] == Addr_Slave)//只處理本從機的命令 { MB_Analyze_Execute();//對接收到的數據進行分析並執行 } RxCount = 0;//清空接收計數 SLAVE_RS485_RECEIVE_MODE;//數據處理完畢后,再重新接收串口中斷 } } /* USER CODE END 1 */