【嵌入式基础梳理#12】风压计Modbus框架示例
一、风压计简介
风压(wind pressure)由于通风管道的阻挡,使四周空气受阻,动压下降,静压升高。侧面和背面产生局部涡流,静压下降,动压升高。和远处未受干扰的气流相比,这种静压的升高和降低统称为风压。
在航空航天领域,“风压” 本质上是气流与飞行器表面相互作用产生的压力(包括静压、动压及压力差)的宏观表现,其分布和变化直接影响飞行器的气动性能、结构设计、飞行控制及任务安全。
风压计是一种用于测量气流对物体表面产生压力(即风压)的仪器,广泛应用于气象、航空航天、建筑、能源、环境监测等多个领域。其核心功能是通过感知流体(通常是空气)的压力变化,将物理量转化为可读取的电信号或机械指示,从而实现对风压的定量测量。
某款硬件层采用485,软件层采用Modbus-RTU通信的风压计通信规律如下图所示:
二、主机端(控制器)的Modbus-RTU驱动框架
驱动框架难度不大,但是有一个核心问题:
由于Modbus的报文不带长度信息或结束符,我们该如何判断一个报文发送结束呢?
因为大多Modbus设备都是一问一答的,我们利用这个特点,可以设计一个简单的规则。
(1)UART中断负责读取485单字节信号并装进队列,每处理一次就置位计时变量
(2)定时器负责监测计时变量,若信号超过3~7ms(因多种因素决定,需要调)则判定为超时,开始强制解析信号,解析完毕后再次发请求。(直接用systick就行,一个中断1ms)
利用定时器+串口中断协同的方法,就可以判断何时一个报文传输完毕了。
三、代码示例
(1)main.c
/* 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);//把读到的一个字节写入fifoHAL_UART_Receive_IT(&huart2,&rxbuf,1);//重新开启串口接收}
}//中断优先级 串口 》 systickint 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
#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 32typedef 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; // 取地址高8modbus1.Sendbuf[i++] = reg_add & 0xFF; // 取地址低8modbus1.Sendbuf[i++] = (reg_num >> 8) & 0xFF; // 读取寄存器数目高8modbus1.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(环形队列)
内容不做过多解释,详见其他数据结构教程。
#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;
}