目录

一、前言

开关量传感器是工业场景中最基础的一类传感器,核心对应 LED、按键、继电器等简单 GPIO 外设的状态采集与控制。本次基于 STM32F030 开发板设计开关量传感器程序,沿用已验证的通信架构(PC→STM32H5→STM32F030),通过 PC 端 Modbus Poll 软件实现对 F030 开关量硬件的远程采集与控制,完成从寄存器规划到代码落地的全流程设计。

二、开关量传感器基础:硬件与通信架构

开关量传感器的硬件连接保持与此前一致:PC 通过 USB 连接 STM32H5 开发板,H5 通过 RS485 连接 F030 开发板,H5 仍承担 RS485-USB 的转接作用,保障 PC 端 Modbus Poll 软件与 F030 传感器的通信链路。

资料来源韦东山

开关量传感器硬件示意图如下:

![[屏幕截图 2026-02-19 095911.png]]

三、硬件引脚与寄存器映射规划

F030 开发板上开关量模块的引脚、工作电平与寄存器类别对应关系如下表,CubeMX 中需根据寄存器类别(DI 为输入、DO 为输出)配置对应 GPIO 的工作模式:

模块 功能 引脚 工作电平 寄存器类别
SWITCH 开关量模块 KEY1 PA3 DI
KEY2 PA4 DI
KEY3 PA5 DI
K1_CTRL PB5 DO
K2_CTRL PB4 DO
LED1 PB11 DO
LED2 PB12 DO
LED3 PB13 DO

四、点表设计:通信规则标准化

为明确 PC 与 F030 开关量传感器的通信规则,设计如下点表,清晰定义设备地址、寄存器地址、类别及功能:

设备地址 寄存器地址 寄存器类别 用途 描述
01H 0000H DI 读取按键 KEY1 1 - 被按下
01H 0001H DI 读取按键 KEY2 1 - 被按下
01H 0002H DI 读取按键 KEY3 1 - 被按下
01H 0000H DO 控制蜂鸣器 1 1 - 响
01H 0001H DO 控制蜂鸣器 2 1 - 响
01H 0002H DO 控制 LED1 1 - 亮
01H 0003H DO 控制 LED2 1 - 亮
01H 0004H DO 控制 LED3 1 - 亮

五、F030 程序实现:寄存器与 GPIO 联动

1. 宏定义设计

通过宏定义区分不同设备逻辑,同时根据硬件实际数量定义寄存器相关宏,实现代码的可复用性与适配性:

/* USER CODE BEGIN PD */

// 启用开关量传感器功能(宏定义便于切换不同传感器逻辑)
#define USE_SWITCH_SENSOR 1
#ifdef USE_SWITCH_SENSOR

// 从机地址(对应点表中的01H)
#define SLAVE_ADDR 1
// DO寄存器(线圈状态)数量:5个(2个蜂鸣器+3个LED)
#define NB_BITS             5
// DI寄存器(离散输入状态)数量:3个(3个按键)
#define NB_INPUT_BITS       3 
// 保持寄存器数量:0(本传感器未使用)
#define NB_REGISTERS        0
// 输入寄存器数量:0(本传感器未使用)
#define NB_INPUT_REGISTERS  0  


#endif

2. 核心任务实现

在默认 FreeRTOS 任务中实现 Modbus 从机逻辑,核心流程为:读取 GPIO 状态更新 DI 寄存器,根据 DO 寄存器值控制 GPIO 输出,代码及关键注释如下(未修改原有代码逻辑):

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
    // 存储GPIO读取状态
    GPIO_PinState val;
	// Modbus请求报文缓冲区
	uint8_t *query;
	// Modbus RTU上下文
	modbus_t *ctx;
	// 函数返回值,判断操作是否成功
	int rc;
	// Modbus寄存器映射结构体
	modbus_mapping_t *mb_mapping;
	
	// 创建串口1的Modbus RTU上下文:波特率115200、无校验、8数据位、1停止位
	ctx = modbus_new_st_rtu("uart1", 115200, 'N', 8, 1);
	// 设置从机地址(匹配点表中的01H)
	modbus_set_slave(ctx, SLAVE_ADDR);
	// 分配请求报文缓冲区(FreeRTOS内存接口)
	query = pvPortMalloc(MODBUS_RTU_MAX_ADU_LENGTH);

	// 初始化寄存器映射:基于宏定义的寄存器数量配置
	mb_mapping = modbus_mapping_new_start_address(0,
												  NB_BITS,        // DO寄存器数量
												  0,
												  NB_INPUT_BITS,  // DI寄存器数量
												  0,
												  NB_REGISTERS,   // 保持寄存器数量
												  0,
												  NB_INPUT_REGISTERS); // 输入寄存器数量
	
	// 建立Modbus RTU连接
	rc = modbus_connect(ctx);
	if (rc == -1) {
		//fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
		modbus_free(ctx);
		vTaskDelete(NULL);; // 连接失败则删除任务
	}

	// 无限循环接收并响应PC端Modbus请求
	for (;;) {
		do {
			// 接收PC端请求报文,过滤无效请求(返回0的查询)
			rc = modbus_receive(ctx, query);
			/* Filtered queries return 0 */
		} while (rc == 0);
 
		/* The connection is not closed on errors which require on reply such as
		   bad CRC in RTU. */
		// 非CRC错误则继续循环,不退出
		if (rc == -1 && errno != EMBBADCRC) {
			/* Quit */
			continue;
		}

        /* update values of registers
         * a. read gpio
         * b. update reg
         */
#ifdef USE_SWITCH_SENSOR
        // 读取KEY1(PA3)状态,更新DI寄存器0号
        val = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3);
        if (val == GPIO_PIN_RESET) // 按键按下(低电平)
        {
            mb_mapping->tab_input_bits[0] = 1;
        }
        else // 按键未按下(高电平)
        {
            mb_mapping->tab_input_bits[0] = 0;
        }

        // 读取KEY2(PA4)状态,更新DI寄存器1号
        val = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4);
        if (val == GPIO_PIN_RESET)
        {
            mb_mapping->tab_input_bits[1] = 1;
        }
        else
        {
            mb_mapping->tab_input_bits[1] = 0;
        }

        // 读取KEY3(PA5)状态,更新DI寄存器2号
        val = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5);
        if (val == GPIO_PIN_RESET)
        {
            mb_mapping->tab_input_bits[2] = 1;
        }
        else
        {
            mb_mapping->tab_input_bits[2] = 0;
        }

#endif

		// 解析请求并回复PC端(包含更新后的DI寄存器数据)
		rc = modbus_reply(ctx, query, rc, mb_mapping);
		if (rc == -1) {
			//break;
		}

#ifdef USE_SWITCH_SENSOR
        // 根据DO寄存器0号值控制蜂鸣器1(PB5):1-响(高电平)、0-停(低电平)
		if (mb_mapping->tab_bits[0])
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); //switch1
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); //switch1

        // 根据DO寄存器1号值控制蜂鸣器2(PB4):1-响(高电平)、0-停(低电平)
		if (mb_mapping->tab_bits[1])
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_SET); //switch2
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_RESET); //switch2

		// 根据DO寄存器2号值控制LED1(PB11):1-亮(低电平)、0-灭(高电平)
		if (mb_mapping->tab_bits[2])
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); //LED1
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET); //LED1

		// 根据DO寄存器3号值控制LED2(PB12):1-亮(低电平)、0-灭(高电平)
		if (mb_mapping->tab_bits[3])
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); //LED2
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); //LED2

		// 根据DO寄存器4号值控制LED3(PB13):1-亮(低电平)、0-灭(高电平)
		if (mb_mapping->tab_bits[4])
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); //LED3
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); //LED3
#endif
	}

	// 释放资源(实际循环不会执行到此处)
	modbus_mapping_free(mb_mapping);
	vPortFree(query);
	modbus_close(ctx);
	modbus_free(ctx);

	vTaskDelete(NULL);
    
  /* USER CODE END StartDefaultTask */
}

六、功能验证:Modbus Poll 控制硬件

STM32H5 端代码无需修改,仍承担 RS485-USB 的转接功能;将 F030 端编译后的程序烧录至开发板,通过 PC 端 Modbus Poll 软件按照点表规则改写 / 读取寄存器值:

  • 读取 DI 寄存器(0000H-0002H):可获取 KEY1-KEY3 的按下状态;
  • 改写 DO 寄存器(0000H-0004H):可控制蜂鸣器 1/2 的启停、LED1-LED3 的亮灭。

七、总结

  1. 开关量传感器程序核心是 GPIO 与 Modbus 寄存器的联动:DI 寄存器同步 GPIO 输入(按键),DO 寄存器控制 GPIO 输出(LED / 继电器);
  2. 宏定义设计实现了代码的适配性,可快速切换不同传感器逻辑;
  3. 点表是 PC 与传感器通信的核心规则,需严格匹配寄存器地址与硬件功能。

八、结尾

本次完成了 STM32F030 开关量传感器的全流程程序设计,实现了 PC 端 Modbus Poll 软件对 GPIO 外设的远程采集与控制,这套设计思路可直接复用至各类工业开关量模块的开发。掌握开关量传感器的设计逻辑,是进阶开发温湿度、环境检测等复杂传感器的基础。感谢各位的阅读,持续关注本系列笔记,一起探索更多工业级 Modbus 传感器的开发技巧!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐