【嵌入式基础梳理#12】风压计Modbus框架示例

一、风压计简介

风压(wind pressure) 由于通风管道的阻挡,使四周空气受阻,动压下降,静压升高。侧面和背面产生局部涡流,静压下降,动压升高。和远处未受干扰的气流相比,这种静压的升高和降低统称为风压。

在航空航天领域,"风压" 本质上是气流与飞行器表面相互作用产生的压力(包括静压、动压及压力差)的宏观表现,其分布和变化直接影响飞行器的气动性能、结构设计、飞行控制及任务安全。

风压计是一种用于测量气流对物体表面产生压力(即风压)的仪器,广泛应用于气象、航空航天、建筑、能源、环境监测等多个领域。其核心功能是通过感知流体(通常是空气)的压力变化,将物理量转化为可读取的电信号或机械指示,从而实现对风压的定量测量。

某款硬件层采用485,软件层采用Modbus-RTU通信的风压计通信规律如下图所示:

二、主机端(控制器)的Modbus-RTU驱动框架

驱动框架难度不大,但是有一个核心问题:

由于Modbus的报文不带长度信息或结束符,我们该如何判断一个报文发送结束呢?

因为大多Modbus设备都是一问一答的,我们利用这个特点,可以设计一个简单的规则。

(1)UART中断负责读取485单字节信号并装进队列,每处理一次就置位计时变量

(2)定时器负责监测计时变量,若信号超过3~7ms(因多种因素决定,需要调)则判定为超时,开始强制解析信号,解析完毕后再次发请求。(直接用systick就行,一个中断1ms)

利用定时器+串口中断协同 的方法,就可以判断何时一个报文传输完毕了。

三、代码示例

(1)main.c

vbscript 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "Modbus.h"
#include "FIFO.h"
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define WINDINIT 0
#define WINDON 1
//两个状态,风压计关闭和开启
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint8_t rxbuf;
//串口接收中断寄存器
uint8_t WIND_G_STATE;
//风压计状态寄存器
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
Modbus_RTU modbus1 = {0};
//初始化modbus结构体
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_UART4_Init();
  /* USER CODE BEGIN 2 */
	printf("WindSYSTEM-On\r\n");
	FIFO_init();
	//初始化环形队列
	HAL_Delay(100);
	//等待风压计预热
	HAL_UART_Receive_IT(&huart2,&rxbuf,1);
    //打开接收中断
	WIND_G_STATE = WINDON;
    //开启风压信号接收
	Modbus_fun3_Tx(1,33,2);
    //进行第一轮数据请求
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
void HAL_SYSTICK_Callback(){
	if(WIND_G_STATE==WINDON){
    //接收打开则进入
		if(modbus1.ex_time<7){
            //未超时则计时器自增
				modbus1.ex_time++;
		}
		else{
           //如果超时则进入处理
				uint8_t len = RS485_fifo_getusedsize();
              //获取此时缓冲区长度
				uint8_t data[len];
              //按照报文长度开辟一个报文寄存器
				RS485_uart_rx_fifo_read(data, len);
				//从缓冲区获取报文
				int32_t RS485_rxbuf=0;
                //初始化风压数据寄存器
			  Modbus_fun3_Rx(&RS485_rxbuf,data, 1, len, 2);

                //进行强制解析并把解析结果给数据寄存器
                //注意:在严苛应用场合应该根据返回值做调试日志,具体返回规则见后文

//			  RS485_rx_fifo_flush();
			  printf("WindPress: %d\r\n",RS485_rxbuf);
			 //print风压,但是建议有上位机的不用这么做,这样会损失时间
				Modbus_fun3_Tx(1,33,2);
                //继续下一轮请求
				modbus1.ex_time=0;
                //计时器清零
		}
	}	

}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
        //485-》串口接收中断
		if(huart==&huart2){
				modbus1.ex_time=0;
                //每接收一次数据,都要置为计时器
//			printf("INPUT DETECTED: %d\r\n",rxbuf);
//				HAL_UART_Transmit(&huart4,&rxbuf,1,0xff);
				RS485_uart_rx_fifo_write(&rxbuf, 1);
                //把读到的一个字节写入fifo
				HAL_UART_Receive_IT(&huart2,&rxbuf,1);
                //重新开启串口接收
		}
}

//中断优先级 串口 》 systick

int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart4 , (uint8_t *)&ch, 1, 0xFFFF);
    return ch;
}

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

(2)Modbus.c

vbscript 复制代码
#include "Modbus.h"
#include "gpio.h"
#include "usart.h"
#include "stdio.h"

#define RS485_TX_EN HAL_GPIO_WritePin(WIND_EN_GPIO_Port,WIND_EN_Pin,1);
#define RS485_RX_EN HAL_GPIO_WritePin(WIND_EN_GPIO_Port,WIND_EN_Pin,0);

#define M_CRC_8 8
#define M_CRC_32 32

typedef struct
{
   uint32_t poly;           
   uint32_t InitValue;      
   uint32_t xor;             
   uint8_t InputReverse;   
   uint8_t OutputReverse; 
}S_CRC;

const S_CRC crc_16_MODBUS       = {0x8005,      0xffff,     0x0000,     1,   1};


// An highlighted block
void UART_SendByte(uint8_t byte){
		HAL_UART_Transmit(&huart2,&byte,1,0xff);
//	printf("bytesent: %d\r\n",byte);
}

static uint32_t reverse(uint32_t data, uint8_t bit)
{ 
    uint8_t i;
    uint32_t temp = 0;
    uint8_t bitlen = bit;
    for(i = 0; i < bitlen; i++)	
        temp |= ((data>>i) & 0x01) << (bitlen-1-i);
 
    return temp;
}


uint32_t crc_fun(uint8_t *addr, int num, S_CRC type, uint8_t bit)  
{  
    uint8_t i;  
    uint8_t data;
    uint8_t offset = bit - 8;                           
    uint32_t maxbit = 1 << (bit-1);           
    uint32_t crc = type.InitValue;					   
    for (; num > 0; num--)               
    {  
        data = *addr++;
        if(type.InputReverse == 1)
            data = reverse(data, M_CRC_8);		
        crc = crc ^ (data << offset);			
        for (i = 0; i < 8; i++)					
        {  
            if (crc & maxbit)				      
                crc = (crc << 1) ^ type.poly;    
            else                                
                crc <<= 1;                  
        }
    }
    if(type.OutputReverse == 1)             
        crc = reverse(crc, bit);
    
    crc = crc^type.xor;	                          
 
    if (bit < M_CRC_32)
        crc &= ~(0xffffffff << bit);
 
    return(crc);                               
}

void Modbus_fun3_Tx(uint8_t addr, uint16_t reg_add, uint16_t reg_num)
{
  uint16_t i;         
  uint16_t crc;      
  uint8_t j;      

     //初始化Modbus发送寄存器,避免上次发送有余量
  for(i=0; i<32; i++)
  {
    modbus1.Sendbuf[i] = 0;
  }
  i = 0; 

  // 对应0x03功能码的请求结构
  //  设备地址|功能码|16位地址|16位寄存器数目|16位CRC校验码

  modbus1.Sendbuf[i++] = addr;                  // 通讯地址
  modbus1.Sendbuf[i++] = 0x03;                  // 功能码
  modbus1.Sendbuf[i++] = (reg_add >> 8) & 0xFF; // 取地址高8
  modbus1.Sendbuf[i++] = reg_add & 0xFF;        // 取地址低8
  modbus1.Sendbuf[i++] = (reg_num >> 8) & 0xFF; // 读取寄存器数目高8
  modbus1.Sendbuf[i++] = reg_num & 0xFF;        // 读取寄存器数目低8

  // 进行CRC校验 
  crc = crc_fun(modbus1.Sendbuf, i, crc_16_MODBUS, 16);
  modbus1.Sendbuf[i++] = crc & 0xFF;            // CRC?8?
  modbus1.Sendbuf[i++] = (crc >> 8) & 0xFF;     // CRC?8?

  // 485发送功能开启
  RS485_TX_EN;  // ???:DE/RE????

  // 发送串口数据
  for(j=0; j<i; j++)
  {
    UART_SendByte(modbus1.Sendbuf[j]);  // ?????
  }
	//提前开启485接收,因为串口接收在485后,其是被动的
	RS485_RX_EN;
  }

void Modbus_fun3_Rx(int32_t* RS485_rxbuf,uint8_t*data ,uint8_t gadget_adr, uint8_t rx_len, uint8_t reg_num)	{
	uint16_t i;       
  uint16_t crc;       
  uint8_t j;          
    //对接收数据进行CRC校验
  crc = crc_fun(data, rx_len - 2, crc_16_MODBUS, 16);
  if( (data[rx_len-2] != (crc & 0xFF)) || 
      (data[rx_len-1] != (crc >> 8)) )
  {
//		printf("CRC failed!\r\n");
    return;
  }

  //对接收数据来源校验
  if( (data[0] != gadget_adr) || (data[1] != 0x03) )
  { 
//		printf("FUN failed!\r\n");
    return; 
  }

    //对数据位进行强转
    //每个8bit数据都强转成32然后进行移位,即能求出风压大小和符号
		for(j=0; j<reg_num * 2; j++){
    *(RS485_rxbuf) |=((int32_t) data[3 + j] ) << ( 8 * (reg_num * 2 - j - 1) );
		}
}


//发送配置略
void Modbud_fun6_Tx(uint8_t addr, uint16_t reg_add, uint16_t data)  //6??????
{
	uint16_t i;        
  uint16_t crc;       
  uint8_t j;         

  // 
  for(i=0; i<32; i++)
  {
    modbus1.Sendbuf[i] = 0;
  }
  i = 0;  // ????

  modbus1.Sendbuf[i++] = addr;                  // ????
  modbus1.Sendbuf[i++] = 0x06;                  // ???:???????
  modbus1.Sendbuf[i++] = (reg_add >> 8) & 0xFF; // ????????8?
  modbus1.Sendbuf[i++] = reg_add & 0xFF;        // ????????8?
  modbus1.Sendbuf[i++] = (data >> 8) & 0xFF; // ?????8?
  modbus1.Sendbuf[i++] = data & 0xFF;        // ?????8?

  // ??CRC???
  crc = crc_fun(modbus1.Sendbuf, i, crc_16_MODBUS, 16);
  modbus1.Sendbuf[i++] = crc & 0xFF;            // CRC?8?
  modbus1.Sendbuf[i++] = (crc >> 8) & 0xFF;     // CRC?8?

  RS485_TX_EN;

  for(j=0; j<i; j++)
  {
    UART_SendByte(modbus1.Sendbuf[j]);  // ?????
  }
	
	RS485_RX_EN;
}

(3)fifo.c(环形队列)

内容不做过多解释,详见其他数据结构教程。

vbscript 复制代码
#include "FIFO.h"
#include "usart.h"

static struct
{
    uint8_t buf[RS485_UART_RX_FIFO_BUF_SIZE];  /* ?? */
    uint16_t size;                                  /* ???? */
    uint16_t reader;                                /* ??? */
    uint16_t writer;                                /* ??? */
} g_uart_rx_fifo;                                   /* UART??FIFO */

/**
 * @brief       ATK-MS901M UART??FIFO????
 * @param       dat: ?????
 *              len: ????????
 * @retval      0: ??????
 *              1: FIFO??????
 */
uint8_t RS485_uart_rx_fifo_write(uint8_t *dat, uint16_t len)
{
    uint16_t i;
   
    for (i=0; i<len; i++)
    {
        g_uart_rx_fifo.buf[g_uart_rx_fifo.writer] = dat[i];
        g_uart_rx_fifo.writer = (g_uart_rx_fifo.writer + 1) % g_uart_rx_fifo.size;
    }
    
    return 0;
}

/**
 * @brief       ATK-MS901M UART??FIFO????
 * @param       dat: ????????
 *              len: ????????
 * @retval      0: FIFO????
 *              ???: ?????????
 */
uint16_t RS485_uart_rx_fifo_read(uint8_t *dat, uint16_t len)
{
    uint16_t fifo_usage;
    uint16_t i;
    
    /* ??FIFO????? */
    if (g_uart_rx_fifo.writer >= g_uart_rx_fifo.reader)
    {
        fifo_usage = g_uart_rx_fifo.writer - g_uart_rx_fifo.reader;
    }
    else
    {
        fifo_usage = g_uart_rx_fifo.size - g_uart_rx_fifo.reader + g_uart_rx_fifo.writer;
    }
    
    /* FIFO????? */
    if (len > fifo_usage)
    {
        len = fifo_usage;
    }
    
    /* ?FIFO????
     * ???FIFO????
     */
    for (i=0; i<len; i++)
    {
        dat[i] = g_uart_rx_fifo.buf[g_uart_rx_fifo.reader];
        g_uart_rx_fifo.reader = (g_uart_rx_fifo.reader + 1) % g_uart_rx_fifo.size;
    }
    
    return len;
}

/**
 * @brief       ATK-MS901M UART??FIFO??
 * @param       ?
 * @retval      ?
 */
void RS485_rx_fifo_flush(void)
{
    g_uart_rx_fifo.writer = g_uart_rx_fifo.reader;
}

uint16_t RS485_fifo_getusedsize(void){
		if(g_uart_rx_fifo.reader<=g_uart_rx_fifo.writer){
				return (g_uart_rx_fifo.writer - g_uart_rx_fifo.reader);
		}
		else {
				return (g_uart_rx_fifo.size + g_uart_rx_fifo.writer - g_uart_rx_fifo.reader);
		}
}

void FIFO_init(){
		g_uart_rx_fifo.size = RS485_UART_RX_FIFO_BUF_SIZE;       
    g_uart_rx_fifo.reader = 0;                                   
    g_uart_rx_fifo.writer = 0;      
}