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 传感器的全流程开发!

相关推荐
芯片和软件研究所3 小时前
【tinyGTC】北斗授时授频 GPSDO 驯服钟的PPS和10M时钟测量
单片机·嵌入式硬件·北斗·时间同步·时频技术·授时·信号测量
Escene20213 小时前
Realtek HoneyGUI (1)
单片机·嵌入式硬件·物联网
xzal124 小时前
python中,turtle基础知识笔记1
笔记·python·turtle
波特率1152005 小时前
FreeRTOS当中的Mail Queue使用教程(CMSIS_v1)
单片机·操作系统·freertos
鱼鳞_6 小时前
Java学习笔记_Day29(异常)
java·笔记·学习
三佛科技-134163842127 小时前
FT32F103系列与APM32F103,STM32F103之间的对比,能否替换?
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
CinzWS7 小时前
A53电源管理(下):DVFS与热管理的硬件实现——ARM芯片的“冷静艺术“
arm开发·嵌入式·芯片验证·原型验证·a53
李永奉7 小时前
杰理可视化SDK开发-蓝牙的可发现可连接和回连
单片机·嵌入式硬件·物联网·语音识别
九成宫7 小时前
IT项目管理期末复习——Chapter 8 项目质量管理
笔记·项目管理·软件工程
Flittly7 小时前
【SpringSecurity新手村系列】(3)自定义登录页与表单认证
java·笔记·安全·spring·springboot