Modbus 传感器开发:STM32F030 串口编程

目录

一、前言

在低成本 Modbus 传感器开发场景中,STM32F030CCT6 搭配 RS485 通信是核心方案之一。本次笔记聚焦 STM32F030 的串口编程,重点讲解 RS485 控制引脚的配置逻辑、基于面向对象思想的串口设备封装,以及适配 FreeRTOS 的串口收发函数实现,解决低成本芯片下 RS485 方向切换、多串口代码复用的核心问题。

二、RS485 控制引脚核心原理

本开发板选用低成本的 SIT3088ETK RS485 转换芯片,需通过 GPIO 引脚手动控制数据收发方向:

  • 接收数据时:控制引脚设置为低电平
  • 发送数据时:控制引脚设置为高电平
  • 空闲状态:默认设置为低电平,保持接收状态。

补充:此前 H5 芯片方案使用 MAX13487EESA 芯片,支持自动切换收发方向,无需额外控制引脚;本次为适配低成本 Modbus 传感器需求,选用需手动控制的 SIT3088ETK 芯片,需重点处理方向切换逻辑。

三、CubeMX 工程配置

1. 串口与 GPIO 配置

  • 使能串口 1,确认默认引脚与硬件原理图匹配;
  • 配置 PA8 为GPIO_Output模式,作为 RS485 控制引脚;

配置截图如下:

2. 系统功能配置

  • 使能 FreeRTOS,为串口中断收发提供任务调度与同步机制;
  • SYS 模块中使能Debug Serial Wire,保留调试功能;

3. 工程文件整理

复制 Module_driver 文件夹到工程目录,仅保留uart_device.cuart_device.h文件,精简工程结构,聚焦串口设备封装逻辑。

4. 编程工具配置

使用 Source Insight 进行代码开发:新建工程→添加uart_device.c/.h等核心文件→同步工程索引,便于代码编辑与函数跳转。

注:代码原作者韦东山,仅作学习记录

四、串口设备封装:面向对象编程实现

1. 串口设备结构体初始化

uart_device.c中先定义串口 1 设备结构体,预留私有数据字段(后续绑定 RS485 控制引脚、串口句柄等信息):

c 复制代码
#include <stdio.h>
#include <string.h>
#include "uart_device.h"

// 定义串口1设备结构体,xxx为私有数据占位符,后续绑定具体设备信息
struct UART_Device g_uart1_dev = {"uart1", stm32_uart_init, stm32_uart_send, stm32_uart_recv, stm32_uart_flush, xxx};

// 串口设备列表(示例,需根据实际工程调整)
static struct UART_Device *g_uart_devices[] = {&g_uart2_dev, &g_uart4_dev, &g_usbserial_dev};

// 根据设备名称查找对应的串口设备结构体
struct UART_Device *GetUARTDevice(char *name)
{
	int i = 0;
	for (i = 0; i < sizeof(g_uart_devices)/sizeof(g_uart_devices[0]); i++)
	{
		if (!strcmp(name, g_uart_devices[i]->name))
			return g_uart_devices[i];
	}
	
	return NULL;
}

2. 串口设备核心结构体定义

uart_device.h中定义UART_Device结构体,封装串口的名称、操作函数与私有数据,体现面向对象编程思想:

c 复制代码
struct UART_Device {
    char *name; // 设备名称(如"uart1")
	int (*Init)( struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit); // 初始化函数
	int (*Send)( struct UART_Device *pDev, uint8_t *datas, uint32_t len, int timeout); // 发送函数
	int (*RecvByte)( struct UART_Device *pDev, uint8_t *data, int timeout); // 字节接收函数
	int (*Flush)(struct UART_Device *pDev); // 缓冲区刷新函数
	void *priv_data; // 私有数据(存储串口句柄、RS485引脚等专属信息)
};

3. 串口私有数据结构体定义

定义UART_Data结构体存储串口 1 的专属信息(句柄、RS485 引脚、收发队列 / 信号量等),实现不同串口的差异化管理:

c 复制代码
// 串口私有数据结构体:存储串口专属的硬件信息与同步对象
struct UART_Data{
	UART_HandleTypeDef *huart; // 串口句柄
	GPIO_TypeDef* GPIOx_485; // RS485控制引脚GPIO组
	uint16_t GPIO_Pin_485; // RS485控制引脚编号
	QueueHandle_t xRxQueue; // 接收队列(FreeRTOS)
	QueueHandle_t xTxSem; // 发送信号量(FreeRTOS)
	uint8_t rxdata; // 接收缓存字节
};

// 初始化串口1私有数据:绑定串口1句柄、PA8为RS485控制引脚
static struct UART_Data g_uart1_data = {
	&huart1,
	GPIOA,
	GPIO_PIN_8,
};

补充:私有数据字段的设计核心价值在于 ------ 串口 1、2、4 可复用同一套收发函数逻辑,仅通过私有数据区分硬件参数,无需为每个串口编写独立代码。

五、串口核心函数实现:适配 RS485 与 FreeRTOS

仿照串口 2/4 的函数逻辑,基于私有数据实现串口 1 的核心操作函数,重点增加 RS485 控制引脚的方向切换:

1. 初始化函数:创建同步对象 + 配置 RS485 初始状态

c 复制代码
static int stm32_uart_init(struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit)
{
	// 获取串口1私有数据
	struct UART_Data * uart_data = pDev->priv_data;
	// 首次初始化时创建接收队列与发送信号量
	if (!uart_data->xRxQueue)
	{
		uart_data->xRxQueue = xQueueCreate(200, 1); // 创建接收队列(200字节)
		uart_data->xTxSem = xSemaphoreCreateBinary( ); // 创建发送完成信号量

		// RS485初始状态:低电平,保持接收模式
		HAL_GPIO_WritePin(uart_data->huart, uart_data->GPIO_Pin_485, GPIO_PIN_RESET);
		
		// 启动串口中断接收
		HAL_UART_Receive_IT(uart_data->huart, &uart_data->rxdata, 1);
	}
	return 0;
}

2. 发送函数:切换 RS485 为发送模式 + 中断发送

c 复制代码
static int stm32_uart_send(struct UART_Device *pDev, uint8_t *datas, uint32_t len, int timeout)
{
	// 获取串口1私有数据
	struct UART_Data * uart_data = pDev->priv_data;

	// RS485切换为发送模式:高电平
	HAL_GPIO_WritePin(uart_data->huart, uart_data->GPIO_Pin_485, GPIO_PIN_SET);
	
	// 启动串口中断发送
	HAL_UART_Transmit_IT(uart_data->huart, datas, len);
	
	// 等待发送完成信号量,超时则返回失败
	if (pdTRUE == xSemaphoreTake(uart_data->xTxSem, timeout))
		return 0;
	else
		return -1;
}

3. 接收函数:从 FreeRTOS 队列读取数据

c 复制代码
static int stm32_uart_recv(struct UART_Device *pdev, uint8_t *pData, int timeout)
{
	// 获取串口1私有数据
	struct UART_Data * uart_data = pDev->priv_data;

	// 从接收队列读取1字节数据,超时则返回失败
	if (pdPASS == xQueueReceive(uart_data->xRxQueue, pData, timeout))
		return 0;
	else
		return -1;
}

4. 缓冲区刷新函数:清空接收队列

c 复制代码
static int stm32_uart_flush(struct UART_Device *pdev)
{
	// 获取串口1私有数据
	struct UART_Data * uart_data = pDev->priv_data;

	int cnt = 0;
	uint8_t data;
	// 循环清空接收队列,返回清空的字节数
	while (1)
	{
		if (pdPASS != xQueueReceive(uart_data->xRxQueue, &data, 0))
			break;
		cnt++;
	}
	return cnt;
}

5. 完善串口 1 设备结构体:绑定私有数据

c 复制代码
// 最终版串口1设备结构体:绑定所有操作函数与私有数据
struct UART_Device g_uart1_dev = {"uart1", stm32_uart_init, stm32_uart_send, stm32_uart_recv, stm32_uart_flush, &g_uart1_data};

补充:面向对象封装的核心优势 ------ 串口 1、2、4 共用stm32_uart_init/send/recv/flush函数,仅通过priv_data区分硬件参数,大幅减少代码冗余。

六、中断回调函数:完成收发闭环

1. 发送完成回调:切换 RS485 为接收模式 + 释放信号量

c 复制代码
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	// 判断为串口1发送完成
	if (huart == &huart1)
	{
		// 切换RS485为接收模式:低电平
		HAL_GPIO_WritePin(uart_data->huart, uart_data->GPIO_Pin_485, GPIO_PIN_RESET);
		// 获取串口1私有数据,释放发送完成信号量
		struct UART_Data * uart_data = g_uart1_dev->priv_data;
		xSemaphoreGiveFromISR(uart_data->xTxSem, NULL);
	}
}

2. 接收完成回调:将数据入队 + 重启中断接收

c 复制代码
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	// 判断为串口1接收完成(注:代码中示例为uart2,需根据实际工程调整)
	if (huart == &huart2)
	{
		// 获取串口1私有数据
		struct UART_Data * uart_data = g_uart1_dev->priv_data;
		// 接收数据入队(中断上下文使用FromISR版本)
		xQueueSendFromISR(uart_data->xRxQueue, (const void *)&g_uart2_rx_buf[i], NULL);
		// 重启中断接收,等待下一字节
		HAL_UART_Receive_IT(uart_data->huart, &uart_data->rxdata, 1);
	}	
}

七、总结

  1. STM32F030 串口编程需手动控制 SIT3088ETK 的 RS485 引脚,发送时置高、接收 / 空闲时置低;
  2. 采用面向对象封装思想,通过UART_Device+ 私有数据实现多串口代码复用,降低冗余;
  3. 结合 FreeRTOS 队列 / 信号量实现串口中断收发的同步管理,适配嵌入式实时场景。

八、结尾

本次完成了 STM32F030 串口的面向对象封装与 RS485 适配,这套方案既适配了低成本 Modbus 传感器的硬件需求,又通过代码复用提升了开发效率。掌握该封装思路,可快速适配不同型号串口与 RS485 芯片,大幅降低工业传感器通信层的开发成本。感谢各位的阅读,持续关注本系列笔记,一起完成低成本 Modbus 传感器的全流程开发!

相关推荐
lpfasd1231 小时前
2025年终总结:码途深耕,步履不停|谁在黄金彼岸
笔记
丝斯20112 小时前
AI学习笔记整理(70)——AI+CAE
人工智能·笔记·学习
初夏睡觉2 小时前
笔记(动态规划(引入)1)
笔记·算法·动态规划
热爱生活的猴子2 小时前
二分查找类算法题核心笔记
数据结构·笔记·算法
cameron_tt2 小时前
stm32智能垃圾桶
stm32·单片机·嵌入式硬件
视觉AI2 小时前
USB转网口+Windows共享网络异常:ax650无法上网排查与完美解决
网络·windows·stm32
myron66883 小时前
基于STM32LXXX的模数转换芯片ADC(ADS1110A0IDBVR)驱动C程序设计
c语言·stm32·嵌入式硬件
地球空间-技术小鱼3 小时前
搜罗Linux桌面环境(Desktop Environments)列表
linux·运维·服务器·笔记·学习·ubuntu·debian
蒸蒸yyyyzwd3 小时前
力扣刷题笔记
笔记·算法·leetcode