[APM32E0] 【APM32E030R Micro-EVB开发板评测】——原创从0到1完整实现modbus slave协议栈

[复制链接]
331|1
龙鳞铁碎牙 发表于 2025-8-30 09:35 | 显示全部楼层 |阅读模式
Modbus 协议广泛应用在工业机器人和电机控制领域,比如机械臂,6轴或者8轴机器人领域,很多都是使用modbus来进行通信的。自从 1979 年出现工业串行链路的事实标准以来,MODBUS 使成千上万的自动化设备能够通信。网上有很多开源的modbus协议栈,比较有名的像libmodbus, freemodbus,mbus等等,这些协议栈都是老外写的,都是别人自做的,本来学好一个协议栈最好的方法就是自己手写代码来实现完整的功能,这样才能锻炼提高自己的编码水平和能力。所以这期我就来手写一个modbus从站来代替这些开源的协议,做点与众不同的事情。多做原创!!!!首先来分析modbus的数据帧格式:

7621568b250d2d4442.png

整个协议栈分为地址域,功能码,数据和差错校验。
MODBUS 数据模型如下


8080268b250eae9265.png

主要功能码如下:

8919768b2510b1db00.png

01 (0x01)读线圈
02 (0x02)读离散量输入
03 (0x03)读保持寄存器
04(0x04)读输入寄存器
05 (0x05)写单个线圈
06 (0x06)写单个寄存器
15 (0x0F) 写多个线圈
16 (0x10) 写多个寄存器
主要就是上面这8种功能码,就可以满足99%以上的modbus应用领域了。
好了,下面来看核心代码实现:
3018968b2515eceac6.png
然后进入这8个功能码,解析实现每种功能,非常的简单。
1,(0x01)读线圈
4991568b25174988b5.png 7066268b25182263d8.png
2.(0x02)读离散量输入
2262168b25198e4962.png 5307368b251b04faf4.png
(0x03)读保持寄存器
130068b251c54b9ad.png 3870568b251cc5cc3c.png
(0x04)读输入寄存器
5248268b251e0b057d.png 1004268b251ea5f9b8.png
(0x05)写单个线圈
2807368b251fc6ac7c.png 8514468b252058c8a7.png
(0x06)写单个寄存器
1150068b25224735fc.png
(0x0F) 写多个线圈
9433168b2523998c32.png 9776568b252459ce64.png
(0x10) 写多个寄存器
2319968b252587f16a.png 5868568b252611af96.png
主函数main中添加功能函数
/*!
* @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"







/** @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 */

9466268b25289f1a87.png
编译烧录代码
2826468b252c443be0.png
打开modbuspoll软件
9519068b252f53b471.png 7244368b2531b1c861.png
1.读取线圈
4090568b25339c888c.png 2393968b2534a057a9.png
9155968b2535499eca.png
000038-Tx:01 01 00 00 00 0A BC 0D
000039-Rx:01 01 02 49 00 8E 6C


2.读离散量输入

2575468b25384dfbe3.png 3688168b25390a3aed.png 7024068b253a2a07fd.png


000050-Tx:01 02 00 00 00 0A F8 0D
000051-Rx:01 02 02 49 00 8E 28


3。读保持寄存器

914168b253c8c5366.png 1042168b253d2acaef.png 3314768b253e09aadf.png


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。读输入寄存器

4284968b25405232c9.png 638868b2540f7b6f1.png 6639268b2541f170f7.png


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。写单个线圈

7437968b2544da7a73.png 8562268b2545a5711c.png 3413068b2546aaa0ab.png


000074-Tx:01 05 00 00 00 00 CD CA
000075-Rx:01 05 00 00 00 00 CD CA


6。写单个寄存器

6583268b2548b46438.png 8949968b25493ca40f.png 3571868b254a3323bb.png


000080-Tx:01 06 00 00 00 00 89 CA
000081-Rx:01 06 00 00 00 00 89 CA


7。写多个线圈



5421568b254cdbf996.png 4209768b254d71e1b2.png 3607968b254e51aad1.png


000110-Tx:01 0F 00 00 00 01 01 00 2E 97
000111-Rx:01 0F 00 00 00 01 94 0B


8。写多个寄 4096968b2550901937.png 存器 2323768b2551271ede.png 536668b2552273d9a.png

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种功能全部测试合格!!!!



DawnFervor 发表于 2025-8-30 13:41 | 显示全部楼层
怎么感觉楼主您这帖子和内容不匹配呀
您需要登录后才可以回帖 登录 | 注册

本版积分规则

22

主题

54

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部