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

[复制链接]
180|1
龙鳞铁碎牙 发表于 2025-8-30 09:51 | 显示全部楼层 |阅读模式
本帖最后由 龙鳞铁碎牙 于 2025-8-30 09:53 编辑

上一篇帖子中我已经从无到有的完整实现了modbus slave从站,这篇帖子就来从0到1来实现modbus master主站。
Modbus 协议广泛应用在工业机器人和电机控制领域,比如机械臂,6轴或者8轴机器人领域,很多都是使用modbus来进行通信的。自从 1979 年出现工业串行链路的事实标准以来,MODBUS 使成千上万的自动化设备能够通信。
网上有很多开源的modbus协议栈,比较有名的像libmodbus, freemodbus,mbus等等,这些协议栈都是老外写的,都是别人自做的,本来学好一个协议栈最好的方法就是自己手写代码来实现完整的功能,这样才能锻炼提高自己的编码水平和能力。所以这期我就来手写一个modbus从站来代替这些开源的协议,做点与众不同的事情。多做原创!!!!
首先来分析modbus的数据帧格式:

5821768b255c58fa8f.png

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

5393068b255e4f0eb5.png

modbus 主站master和从站不同
主要是给从站发数据,等待从站回复数据。
好了,直接上核心代码
1.这是主要函数
1073068b2570996526.png 1621868b257128678a.png
2.
3665468b2572dae6ad.png
3.
5404068b2573a4b5a6.png
4.
2715368b2574731f00.png
5.
5989968b257531c74a.png
6.
3472668b2576120d32.png
7.
2851768b2576d0449f.png
8.
3402568b257792cbef.png
9.
6448768b257849555b.png
10.
891068b257a1b47d7.png
11.

9936268b257b0526ab.png
主函数核心代码如下:
/*!
* @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 */

4097268b257caee748.png

烧录代码到板子,打开modbusslave软件
4086168b258057790f.png 9839368b258186e4c6.png


抓取报文

00019-Tx:01 04 04 00 00 00 00 FB 84
000020-Rx:01 04 00 02 00 02 D0 0B



修改

5557268b258dec94cb.png


000034-Rx:01 04 00 02 00 02 D0 0B
000035-Tx:01 04 04 00 01 00 02 2B 85
发现读取数据正常,至此modbus master主机完成


1608768b2582fb314d.png
FrostShimmer 发表于 2025-8-30 11:15 | 显示全部楼层
我感觉封装的也不好!
但也想不出来更好的实现方式
您需要登录后才可以回帖 登录 | 注册

本版积分规则

22

主题

54

帖子

0

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