本帖最后由 龙鳞铁碎牙 于 2025-8-30 09:53 编辑
上一篇帖子中我已经从无到有的完整实现了modbus slave从站,这篇帖子就来从0到1来实现modbus master主站。
Modbus 协议广泛应用在工业机器人和电机控制领域,比如机械臂,6轴或者8轴机器人领域,很多都是使用modbus来进行通信的。自从 1979 年出现工业串行链路的事实标准以来,MODBUS 使成千上万的自动化设备能够通信。
网上有很多开源的modbus协议栈,比较有名的像libmodbus, freemodbus,mbus等等,这些协议栈都是老外写的,都是别人自做的,本来学好一个协议栈最好的方法就是自己手写代码来实现完整的功能,这样才能锻炼提高自己的编码水平和能力。所以这期我就来手写一个modbus从站来代替这些开源的协议,做点与众不同的事情。多做原创!!!!
首先来分析modbus的数据帧格式:
整个协议栈分为地址域,功能码,数据和差错校验。
MODBUS 数据模型如下
modbus 主站master和从站不同 主要是给从站发数据,等待从站回复数据。 好了,直接上核心代码 1.这是主要函数
2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
主函数核心代码如下:
/*! * @file main.c * * @brief Main program body * * @version V1.0.2 * * @date 2025-05-15 * * @attention * * Copyright (C) 2024-2025 Geehy Semiconductor * * You may not use this file except in compliance with the * GEEHY COPYRIGHT NOTICE (GEEHY SOFTWARE PACKAGE LICENSE). * * The program is only for reference, which is distributed in the hope * that it will be useful and instructional for customers to develop * their software. Unless required by applicable law or agreed to in * writing, the program is distributed on an "AS IS" BASIS, WITHOUT * ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied. * See the GEEHY SOFTWARE PACKAGE LICENSE for the governing permissions * and limitations under the License. */
/* Includes */ #include "main.h" #include "modbus_crc.h" #include "modbusSlave.h" #include "modbus_master.h"
/** @addtogroup Examples @{ */
/** @addtogroup USART_Interrupt @{ */
/** @defgroup USART_Interrupt_Macros Macros @{ */
/* printf function configs to USART1*/ #define DEBUG_USART USART1
/**@} end of group USART_Interrupt_Macros */
/** @defgroup USART_Interrupt_Enumerations Enumerations @{ */
/**@} end of group USART_Interrupt_Enumerations */
/** @defgroup USART_Interrupt_Structures Structures @{ */
/**@} end of group USART_Interrupt_Structures */
/** @defgroup USART_Interrupt_Variables Variables @{ */
/**@} end of group USART_Interrupt_Variables */
/** @defgroup USART_Interrupt_Functions Functions @{ */
/* System tick */ volatile uint32_t sysTick = 0; volatile uint32_t g_tfime_5ms = 0;
volatile uint32_t sysTick20ms = 0;
/* USART Write Data */ void USART_Write(USART_T* usart, uint8_t* dat);
uint8_t U1_RxBuff[RXBUFFLENGTH]; uint16_t U1_Rxlen = 0; uint16_t U1_RxlencntPre = 0;
#define CMD_LEN 64 static char cmd[CMD_LEN]; static uint8_t cmd_index = 0;
//uint8_t RxData[256]; uint8_t TxData[256];
/*! * @brief Main program * * @param None * * @retval None * * @note */ int main(void) { GPIO_Config_T gpioConfig; USART_Config_T usartConfigStruct;
hal_systick_init();
/* Enable GPIO clock */ RCM_EnableAHBPeriphClock(TINY_COM1_TX_GPIO_CLK);
/* Enable COM1 clock */ RCM_EnableAPB2PeriphClock(TINY_COM1_CLK);
/* Enable the BUTTON Clock */ RCM_EnableAHBPeriphClock(KEY1_BUTTON_GPIO_CLK); RCM_EnableAHBPeriphClock(KEY2_BUTTON_GPIO_CLK); RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);
/* Connect PXx to USARTx_Tx */ GPIO_ConfigPinAF(TINY_COM1_TX_GPIO_PORT, TINY_COM1_TX_SOURCE, TINY_COM1_TX_AF);
/* Connect PXx to USARRX_Rx */ GPIO_ConfigPinAF(TINY_COM1_RX_GPIO_PORT, TINY_COM1_RX_SOURCE, TINY_COM1_RX_AF);
/* Configure USART Tx as alternate function push-pull */ gpioConfig.mode = GPIO_MODE_AF; gpioConfig.pin = TINY_COM1_TX_PIN; gpioConfig.speed = GPIO_SPEED_50MHz; gpioConfig.outtype = GPIO_OUT_TYPE_PP; gpioConfig.pupd = GPIO_PUPD_PU; GPIO_Config(TINY_COM1_TX_GPIO_PORT, &gpioConfig);
/* Configure USART Rx as input floating */ gpioConfig.pin = TINY_COM1_RX_PIN; GPIO_Config(TINY_COM1_RX_GPIO_PORT, &gpioConfig);
/* TINY_USARTs configured as follow: */ /* BaudRate = 115200 baud */ usartConfigStruct.baudRate = 115200; /* Receive and transmit enabled */ usartConfigStruct.mode = USART_MODE_TX_RX; /* Hardware flow control disabled (RTS and CTS signals) */ usartConfigStruct.hardwareFlowCtrl = USART_FLOW_CTRL_NONE; /* No parity */ usartConfigStruct.parity = USART_PARITY_NONE; /* One Stop Bit */ usartConfigStruct.stopBits = USART_STOP_BIT_1; /* Word Length = 8 Bits */ usartConfigStruct.wordLength = USART_WORD_LEN_8B; /* USART_Config */ USART_Config(TINY_COM1, &usartConfigStruct);
/* Enable USART_Interrupt_RXBNEIE */ USART_EnableInterrupt(TINY_COM1, USART_INT_RXBNEIE);
NVIC_EnableIRQRequest(TINY_COM1_IRQn, 2);
/* Enable USART */ USART_Enable(TINY_COM1);
RCM_EnableAHBPeriphClock(LED2_GPIO_CLK | LED3_GPIO_CLK); /* LED2 GPIO configuration */ gpioConfig.pin = LED2_PIN; gpioConfig.mode = GPIO_MODE_OUT; gpioConfig.outtype = GPIO_OUT_TYPE_PP; gpioConfig.speed = GPIO_SPEED_50MHz; gpioConfig.pupd = GPIO_PUPD_NO; GPIO_Config(LED2_GPIO_PORT, &gpioConfig);
/* LED3 GPIO configuration */ gpioConfig.pin = LED3_PIN; GPIO_Config(LED3_GPIO_PORT, &gpioConfig);
/* Turn LED2 on */ //GPIO_ClearBit(LED2_GPIO_PORT, LED2_PIN); GPIO_SetBit(LED2_GPIO_PORT, LED2_PIN); /* Turn LED3 off */ GPIO_SetBit(LED3_GPIO_PORT, LED3_PIN); //GPIO_ClearBit(LED3_GPIO_PORT, LED3_PIN);
/* Configure Button pin as input floating */ gpioConfig.mode = GPIO_MODE_IN; gpioConfig.pupd = GPIO_PUPD_PU; gpioConfig.pin = KEY1_BUTTON_PIN; GPIO_Config(KEY1_BUTTON_GPIO_PORT, &gpioConfig);
gpioConfig.pin = KEY2_BUTTON_PIN; GPIO_Config(KEY2_BUTTON_GPIO_PORT, &gpioConfig);
uint8_t state=0; uint16_t Input_Result[2];
ModbusMaster_begin();
while(1) {
uint8_t result; //测试Read Input Registers功能 //从机地址0x01 ,寄存器地址 0x02 ,连续读2个寄存器地址 result = ModbusMaster_readInputRegisters(0x01,0x2, 2); if (result == 0x00) { Input_Result[0] = ModbusMaster_getResponseBuffer(0x00); Input_Result[1] = ModbusMaster_getResponseBuffer(0x01); }
HAL_Delay(100); }
} }
/*! * @brief serial port tramsimt data * * @param pointer to date that need to be sent * * @retval None * * @note */ void USART_Write(USART_T* usart, uint8_t* dat) { while (*dat) { while (USART_ReadStatusFlag(usart, USART_FLAG_TXBE) == RESET);
USART_TxData(usart, *dat++); } }
void Serial_SendByte(uint8_t Byte) { USART_TxData(TINY_COM1, Byte); while (USART_ReadStatusFlag(TINY_COM1, USART_FLAG_TXBE) == RESET); }
void Serial_SendArray(uint8_t *Array, uint16_t Length) { uint16_t i; for (i = 0; i < Length; i ++) { Serial_SendByte(Array); } }
void Serial_SendString(char *String) { uint16_t i; for (i = 0; String != '\0'; i ++) { Serial_SendByte(String); } }
/*! * @brief This function handles USART1 RX interrupt Handler * * @param None * * @retval None * * @NOTE This function need to put into void USART1_IRQHandler(void) */ void USART_Receive_Isr(void) { uint8_t dat;
if (USART_ReadStatusFlag(TINY_COM1, USART_FLAG_RXBNE) == SET) { dat = (uint8_t)USART_RxData(TINY_COM1);
if(U1_Rxlen >= sizeof(U1_RxBuff)) U1_Rxlen = 0; //防止串口被刷爆 U1_RxBuff[U1_Rxlen++] = dat;
//printf("%c", ReceiveTmp); } }
void modbus_slave(void) { if (U1_RxBuff[0] == SLAVE_ID) { switch (U1_RxBuff[1]){ case 0x03: readHoldingRegs(); break; case 0x04: readInputRegs(); break; case 0x01: readCoils(); break; case 0x02: readInputs(); break; case 0x06: writeSingleReg(); break; case 0x10: writeHoldingRegs(); break; case 0x05: writeSingleCoil(); break; case 0x0F: writeMultiCoils(); break; default: modbusException(ILLEGAL_FUNCTION); break; } }
}
void hal_systick_init(void) { SysTick_Config(SystemCoreClock / TICKS_PER_SECONDS); }
uint32_t HAL_GetTick(void) { return sysTick; }
void HAL_Delay(uint32_t Delay) { #define HAL_MAX_DELAY 0xFFFFFFFFU
uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */ if (wait < HAL_MAX_DELAY) { wait++; }
while ((HAL_GetTick() - tickstart) < wait) { } }
#if defined (__CC_ARM) || defined (__ICCARM__) || (defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050))
/*! * @brief Redirect C Library function printf to serial port. * After Redirection, you can use printf function. * * @param ch: The characters that need to be send. * * @param *f: pointer to a FILE that can recording all information * needed to control a stream * * @retval The characters that need to be send. * * @note */ int fputc(int ch, FILE* f) { /* send a byte of data to the serial port */ USART_TxData(DEBUG_USART, (uint8_t)ch);
/* wait for the data to be send */ while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
return (ch); }
#elif defined (__GNUC__)
/*! * @brief Redirect C Library function printf to serial port. * After Redirection, you can use printf function. * * @param ch: The characters that need to be send. * * @retval The characters that need to be send. * * @note */ int __io_putchar(int ch) { /* send a byte of data to the serial port */ USART_TxData(DEBUG_USART, ch);
/* wait for the data to be send */ while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
return ch; }
/*! * @brief Redirect C Library function printf to serial port. * After Redirection, you can use printf function. * * @param file: Meaningless in this function. * * @param *ptr: Buffer pointer for data to be sent. * * @param len: Length of data to be sent. * * @retval The characters that need to be send. * * @note */ int _write(int file, char* ptr, int len) { int i; for (i = 0; i < len; i++) { __io_putchar(*ptr++); }
return len; }
#else #warning Not supported compiler type #endif
void user_uart_clear(void) {
memset(U1_RxBuff, 0, sizeof(U1_RxBuff)); U1_Rxlen = 0;
}
uint8_t user_uart_wait_receive(void) {
if(U1_Rxlen == 0) //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数 return REV_WAIT; if(U1_Rxlen == U1_RxlencntPre) //如果上一次的值和这次相同,则说明接收完毕 { U1_Rxlen = 0; //清0接收计数 return REV_OK; //返回接收完成标志 } U1_RxlencntPre = U1_Rxlen; //置为相同 return REV_WAIT; //返回接收未完成标志
}
void shell_init(void) { printf("\r\n澶氬姛鑳芥寜閿祴璇曞簱\r\n"); }
void shell_process(void) { if(user_uart_wait_receive() == REV_OK) { if(strcmp(U1_RxBuff,"LED2_ON") == 0) { APM_TINY_LEDOn(LED2); printf("Led2 点亮!\n"); } else if(strcmp(U1_RxBuff,"LED2_OFF") == 0) { APM_TINY_LEDOff(LED2); printf("Led2 熄灭!\n"); } else if(strcmp(U1_RxBuff,"LED3_ON") == 0) { APM_TINY_LEDOn(LED3); printf("Led3 点亮!\n"); } else if(strcmp(U1_RxBuff,"LED3_OFF") == 0) { APM_TINY_LEDOff(LED3); printf("Led3 熄灭!\n"); } else if(strcmp(U1_RxBuff,"LED_ON") == 0) { APM_TINY_LEDOn(LED2);APM_TINY_LEDOn(LED3); printf("Led2 Led3 全亮!\n"); } else if(strcmp(U1_RxBuff,"LED_OFF") == 0) { APM_TINY_LEDOff(LED2);APM_TINY_LEDOff(LED3); printf("Led2 Led3 全灭!\n"); }
user_uart_clear(); } HAL_Delay(10); }
/**@} end of group USART_Interrupt_Functions */ /**@} end of group USART_Interrupt */ /**@} end of group Examples */
烧录代码到板子,打开modbusslave软件
抓取报文
00019-Tx:01 04 04 00 00 00 00 FB 84
000020-Rx:01 04 00 02 00 02 D0 0B
修改
000034-Rx:01 04 00 02 00 02 D0 0B
000035-Tx:01 04 04 00 01 00 02 2B 85
发现读取数据正常,至此modbus master主机完成
|