Modbus 协议广泛应用在工业机器人和电机控制领域,比如机械臂,6轴或者8轴机器人领域,很多都是使用modbus来进行通信的。自从 1979 年出现工业串行链路的事实标准以来,MODBUS 使成千上万的自动化设备能够通信。网上有很多开源的modbus协议栈,比较有名的像libmodbus, freemodbus,mbus等等,这些协议栈都是老外写的,都是别人自做的,本来学好一个协议栈最好的方法就是自己手写代码来实现完整的功能,这样才能锻炼提高自己的编码水平和能力。所以这期我就来手写一个modbus从站来代替这些开源的协议,做点与众不同的事情。多做原创!!!!首先来分析modbus的数据帧格式:
整个协议栈分为地址域,功能码,数据和差错校验。
MODBUS 数据模型如下
主要功能码如下:
01 (0x01)读线圈 02 (0x02)读离散量输入 03 (0x03)读保持寄存器 04(0x04)读输入寄存器 05 (0x05)写单个线圈 06 (0x06)写单个寄存器 15 (0x0F) 写多个线圈 16 (0x10) 写多个寄存器 主要就是上面这8种功能码,就可以满足99%以上的modbus应用领域了。
好了,下面来看核心代码实现: 然后进入这8个功能码,解析实现每种功能,非常的简单。 1,(0x01)读线圈 2.(0x02)读离散量输入
(0x03)读保持寄存器
(0x04)读输入寄存器
(0x05)写单个线圈
(0x06)写单个寄存器
(0x0F) 写多个线圈
(0x10) 写多个寄存器
主函数main中添加功能函数
/*! * * * * * @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"
/** @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);
while (1) {
if(user_uart_wait_receive( ) == REV_OK) { modbus_slave(); user_uart_clear(); } 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 */
编译烧录代码
打开modbuspoll软件 1.读取线圈
000038-Tx:01 01 00 00 00 0A BC 0D 000039-Rx:01 01 02 49 00 8E 6C
2.读离散量输入
000050-Tx:01 02 00 00 00 0A F8 0D
000051-Rx:01 02 02 49 00 8E 28
3。读保持寄存器
000058-Tx:01 03 00 00 00 0A C5 CD
000059-Rx:01 03 14 00 00 04 57 08 AE 0D 05 11 5C 15 B3 1A 0A 1E 61 22 B8 27 0F 33 77
4。读输入寄存器
000064-Tx:01 04 00 00 00 0A 70 0D
000065-Rx:01 04 14 00 00 04 57 08 AE 0D 05 11 5C 15 B3 1A 0A 1E 61 22 B8 27 0F 05 91
5。写单个线圈
000074-Tx:01 05 00 00 00 00 CD CA
000075-Rx:01 05 00 00 00 00 CD CA
6。写单个寄存器
000080-Tx:01 06 00 00 00 00 89 CA
000081-Rx:01 06 00 00 00 00 89 CA
7。写多个线圈
000110-Tx:01 0F 00 00 00 01 01 00 2E 97
000111-Rx:01 0F 00 00 00 01 94 0B
8。写多个寄
存器
000112-Tx:01 10 00 00 00 02 04 00 00 00 00 F3 AF
000113-Rx:01 10 00 00 00 02 41 C8
所有modbus slave的8种功能全部测试合格!!!!
|