一、风压计简介
风压(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;
}