abner_ma 发表于 2025-8-29 17:38

【APM32E030R Micro-EVB开发板评测】modbus总线读取风速仪



APM32E03X作为国产MCU,相较于同级别的ST、NXP等国际大厂产品,具有价格优势,这对于需要大规模部署的Modbus节点(如传感器、执行器、采集模块)来说至关重要。极致的成本优势。Cortex-M0+内核:48MHz的主频对于处理Modbus RTU/ASCII协议绰绰有余。MODBUS协议是一种开放式通信协议,具有传输速度快、传输距离长、传输速率高、传输可靠性高等特点。由于MODBUS协议的通信方式具有广泛的适用性,因此适用于各种工业自动化领域,如工业控制、自动化设备、数据采集等。Modbus协议本身不算复杂,主要的开销在于CRC计算和串口通信,M0+内核完全可以高效处理。
   丰富的UART资源:APM32E030提供多个UART接口,可以轻松实现,一个UART用于Modbus通信(连接RS-485芯片)。另一个UART用于打印调试信息(连接USB转TTL),非常方便开发。可以同时实现多个Modbus主站或从站。
   低功耗特性 该芯片具有多种低功耗模式(睡眠、停机、待机)。对于电池供电或需要节能的Modbus设备,可以在空闲时进入低功耗模式,当串口收到数据时再通过中断唤醒,极大地降低了平均功耗。

风速仪:

硬件


    APM32E030      MAX485       风速仪
   PA9(TX)→    DI   PA10(RX) →    RO   PA8      →    DE/RE      (控制收发方向)    3.3V   →    VCC    GND      →    GND            A      →   A            B      →   B


"modbus.C

#include "modbus.h"
#include "apm32e0xx_uart.h"
#include "apm32e0xx_gpio.h"
#include "apm32e0xx_rcm.h"
#include "delay.h"// 假设已实现延时函数

// 控制RS485收发方向的引脚定义
#define MODBUS_DIR_PORT    GPIOA
#define MODBUS_DIR_PIN   GPIO_PIN_8

uint8_t txBuffer;   // Modbus发送缓冲区
uint8_t rxBuffer;// Modbus接收缓冲区

// 计算CRC16校验
uint16_t Modbus_CRC16(uint8_t *data, uint8_t len)
{
    uint16_t crc = 0xFFFF;
    uint8_t i, j;
   
    for (i = 0; i < len; i++)
    {
      crc ^= data;
      for (j = 0; j < 8; j++)
      {
            if (crc & 0x0001)
            {
                crc >>= 1;
                crc ^= 0xA001;
            }
            else
            {
                crc >>= 1;
            }
      }
    }
    return crc;
}

// 初始化UART和RS485控制引脚
void Modbus_Init(uint32_t baudrate)
{
    GPIO_Config_T gpioConfig;
    UART_Config_T uartConfig;
   
    // 使能时钟
    RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOA);
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_UART1);
   
    // 配置UART1引脚
    // PA9 - UART1_TX
    gpioConfig.mode = GPIO_MODE_AF_PP;
    gpioConfig.speed = GPIO_SPEED_50MHz;
    gpioConfig.pin = GPIO_PIN_9;
    GPIO_Config(GPIOA, &gpioConfig);
   
    // PA10 - UART1_RX
    gpioConfig.mode = GPIO_MODE_IN_FLOATING;
    gpioConfig.pin = GPIO_PIN_10;
    GPIO_Config(GPIOA, &gpioConfig);
   
    // 配置RS485方向控制引脚PA8
    gpioConfig.mode = GPIO_MODE_OUT_PP;
    gpioConfig.speed = GPIO_SPEED_50MHz;
    gpioConfig.pin = GPIO_PIN_8;
    GPIO_Config(MODBUS_DIR_PORT, &gpioConfig);
    GPIO_ResetBit(MODBUS_DIR_PORT, MODBUS_DIR_PIN);// 初始为接收模式
   
    // 配置UART1
    uartConfig.baudRate = baudrate;
    uartConfig.wordLength = UART_WORD_LEN_8B;
    uartConfig.stopBits = UART_STOP_BIT_1;
    uartConfig.parity = UART_PARITY_NONE;
    uartConfig.hwFlowControl = UART_HW_FLOW_CTRL_NONE;
    uartConfig.mode = UART_MODE_TX_RX;
    UART_Config(UART1, &uartConfig);
   
    // 使能UART
    UART_Enable(UART1);
}

// 发送Modbus请求帧
void Modbus_SendRequest(uint8_t addr, uint8_t func, uint16_t regAddr, uint16_t regCnt)
{
    uint16_t crc;
   
    // 构建请求帧
    txBuffer = addr;                // 从机地址
    txBuffer = func;                // 功能码
    txBuffer = (regAddr >> 8) & 0xFF;// 寄存器地址高8位
    txBuffer = regAddr & 0xFF;       // 寄存器地址低8位
    txBuffer = (regCnt >> 8) & 0xFF;   // 寄存器数量高8位
    txBuffer = regCnt & 0xFF;      // 寄存器数量低8位
   
    // 计算CRC
    crc = Modbus_CRC16(txBuffer, 6);
    txBuffer = crc & 0xFF;          // CRC低8位
    txBuffer = (crc >> 8) & 0xFF;   // CRC高8位
   
    // 切换到发送模式
    GPIO_SetBit(MODBUS_DIR_PORT, MODBUS_DIR_PIN);
   
    // 发送数据
    for (uint8_t i = 0; i < 8; i++)
    {
      UART_TxData(UART1, txBuffer);
      while (UART_ReadStatusFlag(UART1, UART_FLAG_TXBE) == RESET);
    }
   
    // 等待发送完成
    while (UART_ReadStatusFlag(UART1, UART_FLAG_TC) == RESET);
   
    // 切换回接收模式
    GPIO_ResetBit(MODBUS_DIR_PORT, MODBUS_DIR_PIN);
}

// 接收Modbus响应帧
uint8_t Modbus_ReceiveResponse(uint8_t *buf, uint8_t maxLen, uint32_t timeout)
{
    uint8_t len = 0;
    uint32_t tickStart = Delay_GetTick();// 获取当前滴答计数
   
    while (1)
    {
      // 检查超时
      if (Delay_GetTick() - tickStart > timeout)
      {
            return 0;// 超时
      }
      
      // 检查是否有数据接收
      if (UART_ReadStatusFlag(UART1, UART_FLAG_RXBNE) != RESET)
      {
            buf = UART_RxData(UART1);
            
            // 对于读取保持寄存器的响应,长度是固定的:地址(1)+功能码(1)+数据长度(1)+数据(n)+CRC(2)
            if (len >= 3 && len == (buf + 5))
            {
                break;// 接收完成
            }
            
            // 防止缓冲区溢出
            if (len >= maxLen)
            {
                break;
            }
      }
    }
   
    return len;
}

// 读取风速值
uint8_t Modbus_ReadWindSpeed(float *windSpeed)
{
    uint8_t len;
    uint16_t crc, receivedCrc;
   
    // 发送读取请求
    Modbus_SendRequest(WIND_SENSOR_ADDR, MODBUS_READ_HOLDING_REG,
                      WIND_SPEED_REG_ADDR, WIND_SPEED_REG_CNT);
   
    // 等待响应(超时时间500ms)
    len = Modbus_ReceiveResponse(rxBuffer, sizeof(rxBuffer), 500);
   
    // 检查是否超时
    if (len == 0)
    {
      return MODBUS_TIMEOUT;
    }
   
    // 检查CRC
    crc = Modbus_CRC16(rxBuffer, len - 2);
    receivedCrc = (rxBuffer << 8) | rxBuffer;
    if (crc != receivedCrc)
    {
      return MODBUS_CRC_ERROR;
    }
   
    // 检查从机地址和功能码
    if (rxBuffer != WIND_SENSOR_ADDR || rxBuffer != MODBUS_READ_HOLDING_REG)
    {
      // 检查是否是错误响应
      if (rxBuffer == (MODBUS_READ_HOLDING_REG | 0x80))
      {
            return MODBUS_RESP_ERROR;// 从机返回错误
      }
      return MODBUS_RESP_ERROR;
    }
   
    // 检查数据长度是否正确
    if (rxBuffer != 2 * WIND_SPEED_REG_CNT)
    {
      return MODBUS_RESP_ERROR;
    }
   
    // 解析风速值(假设是16位无符号整数,单位0.01m/s)
    uint16_t windRaw = (rxBuffer << 8) | rxBuffer;
    *windSpeed = windRaw * 0.01f;// 转换为m/s
   
    return MODBUS_NO_ERROR;
}
   main函数

#include "apm32e0xx.h"
#include "modbus.h"
#include "delay.h"
#include <stdio.h>

float windSpeed = 0.0f;
uint8_t modbusStatus;

int main(void)
{
    // 初始化系统时钟
    SystemInit();
   
    // 初始化延时函数(基于SysTick)
    Delay_Init();
   
    // 初始化Modbus(风速仪常用波特率:9600)
    Modbus_Init(9600);
   
    while (1)
    {
      // 读取风速
      modbusStatus = Modbus_ReadWindSpeed(&windSpeed);
      
      // 处理读取结果
      if (modbusStatus == MODBUS_NO_ERROR)
      {
            // 成功读取到风速,可在这里处理数据
   printf("Wind Speed: %.2f m/s\r\n", windSpeed);
      }
      else
      {
            // 处理错误
            switch (modbusStatus)
            {
                case MODBUS_TIMEOUT:
                  // 超时错误处理
                  break;
                case MODBUS_CRC_ERROR:
                  // CRC校验错误处理
                  break;
                case MODBUS_RESP_ERROR:
                  // 响应错误处理
                  break;
            }
      }
      
      // 1秒读取一次
      Delay_Ms(1000);
    }
}
   

DawnFervor 发表于 2025-8-30 13:47

楼主这是干什么工作的?居然还有风速仪
页: [1]
查看完整版本: 【APM32E030R Micro-EVB开发板评测】modbus总线读取风速仪