ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Proteus仿真STM32水库预警系统:从电路搭建到代码调试全攻略

Proteus仿真STM32水库预警系统:从电路搭建到代码调试全攻略

简介:这是一份面向嵌入式初学者的STM32水库预警系统Proteus仿真完整工程,基于STM32F103C8单片机,集成步进电机、雨量、流速、压力、风速等多类传感器,配套OLED显示与高水位蜂鸣器、电机阀门联动逻辑,适合课设、毕设或Proteus仿真练习者参考。压缩包共279个文件,含Keil5工程源码、Proteus仿真电路及工程辅助文件,整体大小8.57MB,目录中标准库驱动、应用代码与构建产物分层清晰,便于直接打开仿真和烧录调试。已有181人学习,内容围绕题目要求完整覆盖水位设定、水泵运行、声光报警和阀门控制等场景,并附有程序源码、仿真电路文件及CSDN原文讲解链接,可帮助理解传感器采集、外设驱动和状态控制逻辑,减少从零搭建环境的时间成本。

1. Proteus 仿真 STM32:为什么我劝你先别急着买开发板

做嵌入式的人大多有个执念:要玩 STM32 就得真金白银买块开发板,再配个 ST-Link 下载器,接一堆杜邦线。但我的实际经验是,在水库预警这类需要传感器配合、逻辑链条长的场景里,Proteus 仿真反而比实物调试更高效——至少能省下至少两三百块的物料成本,更重要的是,你能在烧板子之前把整个控制逻辑、水位阈值、报警响应全部跑通。很多老师傅嘴上不说,但私下做算法验证的时候,用的都是这套仿真链路。这篇笔记我拆一个基于 STM32 的水库预警系统 Proteus 仿真工程,从器件选型、原理图搭建到代码烧录调试,一条线走下来,最后把仿真里最容易翻车的几个坑单独拉出来讲。


2. 水库预警模型拆解:传感器、控制器、执行器一个都不能少

2.1 整个系统在干什么:从水位采集到声光报警的完整链路

水库预警系统的核心逻辑不复杂:检测当前水位,超过阈值就报警。但真正做工程化的时候,链路拉长之后问题就来了——水位怎么测、用什么传感器、信号要不要处理、报警以什么形式呈现、参数能不能现场改。这套仿真的价值在于,它把这条链路完整打通了,不是仅仅一个电平翻转实验。

我拆过不少 Proteus 仿真工程,这套水库预警项目的基本构成是三层:感知层负责采集水位信号,控制器层用 STM32F103R6 处理数据并做阈值判断,执行层通过蜂鸣器、LED 灯和 LCD 显示屏输出状态。感知层的信号在仿真里面通常是用电位器模拟传感器的电压输出,走 ADC 通道采样;如果你要模拟超警戒水位的场景,直接把电位器拧到对应电压就行,不需要真的去接一个水位传感器。

2.2 Proteus 仿真与实物调试的本质差异:你写的代码不是白写的

很多新手在 Proteus 里第一次跑通点灯实验之后,会陷入一种「仿真跑通、实物必挂」的困惑。原因在于仿真环境帮你隐去了很多硬件细节——晶振起振时间、引脚驱动能力、电源纹波、下载器连接时序,这些在 Proteus 里统统不存在。但那不代表仿真无用,恰恰相反,仿真里你能确定的是自己的逻辑、代码、寄存器配置这些纯数字层面的东西。你是代码逻辑错了,还是硬件连接错了,这一步分清楚了,才能真正减少实物调试时间。

我的一般做法是:先在 Proteus 里把功能全部调通,包括 LCD 显示的乱码、ADC 采样的跳变、报警的响应延迟这些问题,再去碰实物。这样你带着一套验证过的代码上板,剩下的工作量就被压缩到了电源、晶振、下载器这几个点上了。

2.3 器件选型清单与替代方案:F103R6 和 F103C8 怎么选

这套工程的主控用 STM32F103R6,这是 STM32F1 系列里性价比比较高的型号,48 引脚 LQFP 封装,64KB Flash,10 个 ADC 通道,在 Proteus 的器件库里的名字是 STM32F103R6。如果你手头现有的 Proteus 库里找不到 R6,直接用 STM32F103C8 也是兼容的,只是 Flash 和引脚数量有差异,核心的 ADC 和 GPIO 部分配置逻辑一样。选型的时候注意看库文件里芯片的引脚定义,不然后面连线会错位。

执行层的器件选择比较常规:LCD 用 LM016L(这就是常见的 1602 字符屏,在 Proteus 里叫这个名字),蜂鸣器用仿真库里的 BUZZER,LED 用普通红绿黄三色,限流电阻选 220Ω 左右即可。唯一需要留意的是,Proteus 里的 BUZZER 元件默认驱动电流较大,直接用 STM32 的引脚驱动会顶不住,后面我会讲怎么加三极管。


3. STM32F103R6 + LCD1602 + 蜂鸣器:Proteus 电路搭建全过程

3.1 从零搭电路:晶振、复位、电源,一个引脚都不能少

先把最小系统的三个基本模块搭起来:8MHz 晶振并联两个 20pF 电容接到 OSC_IN 和 OSC_OUT 引脚;复位电路用一个 10kΩ 电阻上拉到 3.3V,再串联一个 100nF 电容到地,NRST 引脚接在电阻和电容的连接点;电源引脚 VDDA、VSSA、VDD、VSS 全部接好,VCAP 引脚接一个 2.2μF 电容到地。这一步很多人都觉得是浪费时间,但漏掉任何一个,仿真跑起来要么芯片不工作,要么电压异常。

电源设计上,Proteus 里直接设置电源电压为 3.3V 就行,默认的 VCC 是 5V,这个别搞混了。如果你用的是 Proteus 8.x 版本,电源符号的电压值可以双击修改,我这里直接用 +3.3V 的电源端子。注意整个电路里所有的参考地必须统一,否则 ADC 采样的值会非常离谱。

# 在 Proteus 原理图编辑模式下 # 选择元件 -> 输入型号关键字 -> 双击放置 # 例如:STM32F103R6、LM016L、BUZZER、LED-RED、RESISTOR

这里说一下元件放置顺序的讲究。先把 STM32F103R6 放在原理图正中央,然后围绕它排布外设——左侧放电源和晶振,右侧放 LCD,下方放 ADC 输入(电位器),上方放蜂鸣器和 LED,这样走线的时候不会交叉混乱。放置完元件之后,执行一次电气规则检查(ERC),把悬空引脚和未连接的警告处理掉,这一步在工程上叫「建立连接基线」,后面所有调试都建立在这条干净的基线上。

3.2 LM016L 接法:4 线还是 8 线?用 4 线省下一个口

LCD1602 的接法在 Proteus 里有两种:8 位并行和 4 位并行。我强烈建议用 4 位模式,因为 STM32F103R6 的引脚本身就紧张,省下 4 个 GPIO 留给别的模块用。4 位模式下只接 DB4-DB7 这四个数据引脚,RS、RW、E 这三个控制引脚照常。

具体的引脚分配我建议这样:RS 接 PA0,RW 接 PA1,E 接 PA2,DB4-DB7 接 PA3-PA6。这里有个设计上的细节,把 LCD 全部放在 PA 口,是为了方便后面写代码的时候统一初始化 GPIO。数据引脚顺序要保持连续,这样后面代码里端口数据位的操作可以做得比较简洁。

// LCD1602 4位模式初始化代码片段 void LCD_Init_pins(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); }

这段代码把 PA0-PA6 全部配置为推挽输出,50MHz 速度。RS、RW、E 三个控制脚和数据脚一并初始化,减少后续重复初始化的开销。4 线模式下 LCD 驱动器内部是先接收高 4 位再接收低 4 位,所以写数据的时候要分两次,这个细节后面讲驱动代码时再说。

3.3 水位信号从哪里来:电位器模拟传感器输出

水位传感器在 Proteus 仿真里没有现成的型号可以直接调用,工程上的做法是用一个 10kΩ 电位器(POT-HG)分压来模拟传感器的电压输出。电位器中心抽头接 STM32 的 ADC 输入引脚 PA7,另外两端分别接 3.3V 和 GND。螺电位器的旋钮就是水位的旋钮,拧到不同的角度对应不同的电压,也就是不同的水位。

为什么要用电压模拟而不是直接用数字量?因为真实的水位传感器(比如投入式液位变送器)输出的本来就是 4-20mA 电流或者 0-5V 电压,通过采样电阻转换成电压之后进 ADC。Proteus 里用电位器模拟的是这个电压信号,不是传感器本身。这样的设计还有一个好处:你可以随时拧电位器来测试不同水位下的程序响应,不需要修改电路。

# 元件选择指引 # POT-HG: 电位器, 阻值选 10k # 中心抽头 -> PA7 (ADC 输入) # 两端 -> +3.3V 与 GND

电位器选 10k 是兼顾功耗和输入阻抗的折中。阻抗太小会拉低电源电压,太大则和 STM32 ADC 的内部采样电容形成 RC 延迟,导致采样值稳定慢。10k 这个量级在 Proteus 仿真里不会出问题,在实际电路里也算是个比较通用的取值。

3.4 蜂鸣器驱动:为什么 STM32 引脚直接推不动蜂鸣器

这个坑我见过很多人踩。STM32 的 GPIO 在推挽输出模式下最大能提供的电流大约是 20mA(绝对最大额定值),而 Proteus 里 BUZZER 元件的工作电流需求通常在 30mA 以上,直接接的话电压会被拉低,蜂鸣器只发出很微弱的声音或者干脆无声。解决办法是加一个 NPN 三极管(2N2222 或 BC547)做开关驱动。

电路接法:STM32 的 PB0 引脚通过一个 1kΩ 基极电阻接到三极管的基极,发射极接地,集电极接蜂鸣器的负极,蜂鸣器的正极接 3.3V 电源。这样当 PB0 输出高电平时,三极管导通,蜂鸣器通电发声;输出低电平时三极管截止,蜂鸣器不响。这个电路本质上是一个共射极开关电路。

// 蜂鸣器控制宏定义 #define BEEP_ON() GPIO_SetBits(GPIOB, GPIO_Pin_0) #define BEEP_OFF() GPIO_ResetBits(GPIOB, GPIO_Pin_0)

三极管的基极电阻不能省略,也不能选太小。1k 的取值保证 STM32 引脚输出高电平时基极电流在 3mA 左右,三极管饱和导通,同时不会给 STM32 引脚造成过载。如果你手上只有 10k 的电阻也能工作,只是基极电流更小,三极管可能工作在放大区而非饱和区,蜂鸣器响度会下降。


4. 水位阈值判断与预警状态机:从 ADC 采到值到触发报警的完整逻辑

4.1 ADC 配置细节:12 位分辨率、规则通道还是注入通道

STM32F103 内部 ADC 是 12 位分辨率的,也就是说模拟电压被分成 4096 个离散的档位。3.3V 参考电压下,每个 ADC 单位对应约 0.8mV 的电压变化——如果你要用这套仿真的结果指导真实的水库水位测量,这个精度指标就意味着 1cm 的水位变化至少需要 8mV 的电压变化才能被分辨出来。

大多数场景下用规则通道就够了。注入通道的区别在于它具备抢占优先级,可以在规则通道转换过程中插队——对于水位检测这种慢速变化的信号,没必要用注入通道,规则通道配合 DMA 或者查询方式完全够用。我这里为了方便看效果,直接用查询方式读取,不用 DMA 和中断,这样代码逻辑对新手来说比较直白。

// ADC1 初始化: PA7 作为模拟输入 void ADC1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // PA7 配置为模拟输入, 不是推挽输出也不是浮空 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 只转换一个通道 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); // 设置采样顺序和采样周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); // 校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }

ADC 初始化里有几个值得展开的点。第一,PA7 要配置为模拟输入模式,如果你保留默认的推挽输出配置,ADC 读取的值会完全不对;第二,采样周期选 55.5 个时钟周期,这是精度和速度的折中——采样时间太长会拖慢转换速度,太短在高阻抗信号源下采样电容充不满,精度下降;第三,转换结束后的校准流程不能省略,芯片内部电容的充放电特性有差异,不校准的话 ADC 的零点会偏移几十个 LSB。

4.2 一阶滤波:为什么 AD 值不能直接拿来做水位判断

ADC 直接读取的数值天然带着噪声。在 Proteus 仿真里,噪声的来源主要是电位器接触电阻的波动和 ADC 本身的量化误差;在真实场景里,传感器的信号噪声更大,尤其是液位变送器的输出容易叠加 50Hz 工频干扰。如果你直接把原始 ADC 值拿来比较阈值,会出现水位明明没变,报警器却偶尔响一下的诡异现象。

解决的办法有硬件和软件两条路。硬件上用 RC 低通滤波器,但时间常数不好选;软件上用一阶低通滤波,也就是滑动平均的变体,代码量少,效果立竿见影。一阶滤波的公式是Y[n] = α·X[n] + (1-α)·Y[n-1],α 越接近 1,滤波结果越灵敏,跟踪越快,但平滑效果越差;α 越小,结果越平滑,但响应越迟钝。

// 一阶低通滤波, 返回滤波后的 ADC 值 uint16_t ADC_Filter(uint16_t new_value, uint16_t *filtered_value) { // alpha = 0.3 左右, 用移位代替浮点运算 // 相当于 filtered = (new_value * 3 + filtered * 5) / 8 *filtered_value = (*filtered_value * 5 + new_value * 3) >> 3; return *filtered_value; }

这里用移位运算替代浮点计算,是因为 STM32F103 没有 FPU(硬件浮点单元),跑浮点运算全部要编译器转成软件模拟,效率低不少。>> 3等于除以 8,整个表达式相当于 α = 0.375 的滤波系数,在无浮点环境下实现浮点滤波效果。每次 ADC 转换完成之后调用一次这个函数,传入新的原始值和上次的滤波结果。注意filtered_value这个参数是每次调用的持久化状态,所以调用它的变量建议定义为 static 类型的。

4.3 阈值分级:警惕、警戒、危险三档怎么设

水库预警系统不能只做一个「超了就叫」的开关量报警。我做过几个类似的监测项目,实际使用中操作人员需要一个渐变的过程判断——水位持续上升时,从警惕到警戒再到危险,是一步步逼近的,提前看到警戒状态就能争取处置时间。我把水位阈值分成三档。

在这个仿真工程里,ADC 满量程 4095 对应水位满量程,按百分比设定三档阈值:警惕线 70%(约 ADC 值 2867)、警戒线 85%(约 ADC 值 3480)、危险线 95%(约 ADC 值 3890)。比例关系的换算方式:ADC 阈值 = 4095 × 水位百分比。如果你要适配真实传感器,只要把传感器量程对应的电压范围带入这个比例就行。

// 水位状态枚举和阈值定义 typedef enum { WARNING_LEVEL_NORMAL, // 正常 WARNING_LEVEL_ALERT, // 警惕 WARNING_LEVEL_CRITICAL, // 警戒 WARNING_LEVEL_DANGER // 危险 } WarningLevel; #define ADC_THRESHOLD_ALERT 2867 // 70% #define ADC_THRESHOLD_CRITICAL 3480 // 85% #define ADC_THRESHOLD_DANGER 3890 // 95%

状态判断的逻辑我用了一个状态机,而不是简单的if-else if链。两种写法的区别在于:状态机天然支持状态保持,比如说当前处于警戒状态,水位回落到警惕线以下时,系统不会立刻降到正常,而是会先回到警惕状态——这在现实中有意义,防止水位在阈值附近来回波动时报警反复触发。

// 水位状态判断 (状态机版本) WarningLevel Get_WarningLevel(uint16_t adc_value) { static WarningLevel current_level = WARNING_LEVEL_NORMAL; switch(current_level) { case WARNING_LEVEL_NORMAL: if(adc_value >= ADC_THRESHOLD_ALERT) current_level = WARNING_LEVEL_ALERT; break; case WARNING_LEVEL_ALERT: if(adc_value >= ADC_THRESHOLD_CRITICAL) current_level = WARNING_LEVEL_CRITICAL; else if(adc_value < ADC_THRESHOLD_ALERT - 100) current_level = WARNING_LEVEL_NORMAL; break; case WARNING_LEVEL_CRITICAL: if(adc_value >= ADC_THRESHOLD_DANGER) current_level = WARNING_LEVEL_DANGER; else if(adc_value < ADC_THRESHOLD_CRITICAL - 100) current_level = WARNING_LEVEL_ALERT; break; case WARNING_LEVEL_DANGER: if(adc_value < ADC_THRESHOLD_DANGER - 100) current_level = WARNING_LEVEL_CRITICAL; break; default: current_level = WARNING_LEVEL_NORMAL; break; } return current_level; }

注意这里的回差设计(-100 LSB)。如果没有这个回差,水位在阈值附近抖动时,状态会在两档之间来回跳,直观表现就是 LCD 上的状态闪烁、蜂鸣器响一下停一下。加上回差之后,状态切换需要跨越一个明显的区间才能发生,系统行为就稳定了。这个设计直接来源于工业仪表里的滞回比较器思想,是从工程实践里带过来的处理手法。


5. 避坑指南:Proteus 仿真水库预警系统最常见的四个疑难问题

5.1 LCD 显示乱码

现象:LM016L 上电之后显示的不是文字,而是一堆方块或者乱码。原因分析下来有三个层面:第一种是数据线和控制线接错了,4 位模式下 DB4-DB7 的接线顺序和驱动代码不匹配;第二种是初始化时序问题,LCD 需要较长的上电延时,你把延时写太短了;第三种是电平不匹配,LM016L 的逻辑电平是 5V,STM32 是 3.3V,在仿真里可能不明显,但在实物上是要处理的。

解决:先接成 8 位模式做调试,确认 LCD 本身没问题之后再改回 4 位。初始化程序的延时建议至少 50ms,每次写命令之后延时 5ms,写数据之后延时 2ms。在 Proteus 里,时序问题比实物更容易被忽略,因为仿真执行速度可能比真实硬件快很多,代码里如果延时不足,LCD 根本反应不过来。

5.2 ADC 采样值溢出或一直为 4095

现象:ADC 采样的值始终是满量程 4095。这个问题在 Proteus 里极其常见,原因不是程序写错了,而是原理图里 PA7 引脚的电气连接有问题。最常见的情况是模拟输入引脚没有接对,或者电源+3.3V符号没有正确放置。Proteus 里电源符号默认电压是 5V,如果你直接用默认的电源符号,ADC 参考电压就被拉到了 5V,采样值自然会偏大。

解决:双击电源符号,把 Voltage 属性改成 3.3V。同时检查 ADC 的参考电压引脚(VREF+)有没有接对,仿真环境下一般默认接 VDDA,但你不要以为默认就存在,得确认电路里真的有这个连接。另外用ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)检查转换有没有完成,避免 EOC 标志没等就读取数据寄存器,读到的是上一次转换的残留值。

5.3 仿真速度极慢,改一下参数要等几秒

现象:Proteus 仿真运行时,转动电位器旋钮之后,LCD 上的数值要好几秒才更新。原因有两层:一是仿真中如果主频设置的太高,每条指令的翻译执行都会拖慢整体仿真速度;二是代码里面可能在 while 循环里塞入了很长的延时,或者 LCD 采用的是逐字符刷新而非整屏刷新。

解决:把 STM32 的晶振频率设置从 72MHz 改到 8MHz,仿真速度会有数倍提升。二是代码层面,不要在 ADC 查询的循环里加过长的Delay()。我的做法是设置一个 100ms 的节拍标志,在 SysTick 中断里置位,主循环检测到标志才去采集 ADC 并刷新 LCD,这样既能保障响应速度,又能防止 LCD 反复刷屏拖慢仿真。

5.4 一上电蜂鸣器就响

现象:程序还没跑起来或者刚上电,蜂鸣器就开始叫。这个原因在于 STM32 上电复位瞬间,GPIO 引脚的电平是不确定的,PB0 可能被默认拉起为高,直接驱动三极管导通。还有个更隐蔽的原因:Proteus 仿真里某些版本的元件模型在电源建立瞬间会有一个瞬态脉冲,直接触发三极管开关。

解决:硬件层面在 PB0 和三极管基极之间加一个 10kΩ 下拉电阻到地,保证上电瞬间基极是确定的低电平;代码层面把 Beep_Init 里初始状态设置为关闭,并且在 main 函数最开始的位置——也就是时钟初始化之前——先把 PB0 复位为低电平。这个「先拉低、再初始化」的顺序在工程上叫输出预置,是一个非常好的习惯,能够规避掉绝大多数外设上电误动作的问题。


6. 进阶玩法:加一个 GSM 模块,把仿真做成远程报警模型

前面的系统只能做到现场声光报警,但真实的水库预警场景,操作人员往往不在现场。这个进阶方案是在仿真的基础上,挂一个模拟的 GSM 模块(Proteus 元件库里有 SIM800 的仿真模型),当水位达到警戒或危险级别时,通过串口发送一条 AT 指令来触发远程报警。仿真环境下不需要真实的 GSM 网络,Proteus 会在串口终端里显示指令的收发过程,你看到 AT+CMGS 指令发出,就说明预警链路已经成功延伸到远程端了。

串口配置上用 USART1,TX 接 PC9,RX 接 PC10,波特率 9600——GSM 模块的默认波特率通常是 9600 或 115200,仿真里用 9600 最稳。代码实现上,检测到水位阈值穿越时,调用Send_SMS_Alert()函数,通过串口发送字符串。在 Proteus 里给 USART1 挂一个 COMPIM(串口物理接口)组件作为虚拟连接,再打开虚拟终端窗口,就能看到 AT 指令输出。

// USART1 初始化: 9600 8N1 void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // TX - PA9 复用推挽, RX - PA10 浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); }

串口初始化里有个细节,复用功能的 GPIO 必须使能 AFIO 时钟,否则 TX 引脚不输出数据。PA9 和 PA10 是 USART1 的默认引脚映射,不需要重映射,在 F103R6 上这个映射关系是固定的。如果你以后要在更大的封装上复用其他引脚,就要查重映射表格。

串口发送数据的代码比较简单,用查询方式等待发送寄存器为空再写数据即可:

// 串口发送单个字符与字符串 void UART_SendChar(char c) { while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, c); } void UART_SendString(char *str) { while(*str) { UART_SendChar(*str++); } }

USART_FLAG_TXE是发送数据寄存器空标志,置位说明可以写入下一个数据。如果你用中断或者 DMA 发送,这里的写法就要改,但查询方式的好处是逻辑清晰、资源占用少,仿真环境里完全够用。真正接 GSM 模块的时候,你还需要处理模块上电后的 AT 握手流程——发AT等待OK返回——那就是往状态机方向走了,目前这套代码留了这个扩展的接口。

最后回到这套仿真的调试习惯上来。从那以后,我每次做 STM32 系统设计,都强制先走一遍 Proteus 仿真链路——把 ADC 采样、滤波、阈值判断、状态机、LCD 显示、蜂鸣器报警全部跑通了才动硬件。不是说不踩硬件的坑了,而是把逻辑层面的坑提前排干净,上板之后剩下的问题就那么几个,排查时间是原来的三分之一。仿真和实物从来不是对立关系,它们各自解决一个层面的问题。这套基于 STM32 的水库预警系统仿真工程,核心价值就是把感知、决策、执行这条链路完整的走一遍,你拿到手之后建议先照抄跑一遍,再改阈值和状态逻辑做二次开发——希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进