资讯动态

单片机编程从入门到进阶:100个实例代码详解与实战应用

发布时间:2026/8/24 2:25:34 来源:尧图企业网站定制
1. 项目概述与价值定位最近在整理资料时翻出了自己早年学习单片机时积攒和编写的几百个实例代码。从最基础的LED闪烁到后来涉及通信协议、传感器驱动、电机控制等复杂应用这些代码片段就像一块块积木构成了我对嵌入式世界的认知框架。我决定把这些“积木”系统地整理出来形成一个从入门到进阶的实例库今天分享的就是这个系列的第一部分1-100例。无论你是刚接触51或STM32的新手还是想寻找特定功能参考的开发者这个集合都希望能为你提供一个扎实的、可直接复用的代码基础。单片机编程的精髓在于“动手”看一百遍理论不如调通一个实例我的目标就是把这些经过实际验证的“砖瓦”递到你手里让你能更快地搭建起自己的项目大厦。这个实例大全的核心价值在于其系统性和实用性。它不是零散的知识点堆砌而是按照由浅入深、由核心到外设的逻辑进行编排。前期的例子会聚焦于GPIO、定时器、中断这些单片机的心脏功能确保基础牢靠中后期则会逐步引入ADC/DAC、PWM、各类通信接口UART、I2C、SPI以及具体传感器、执行器的驱动。每一个实例都力求完整、独立包含清晰的注释、可运行的代码以及关键参数的说明你完全可以将其复制到自己的工程中稍作修改即可运行。在分享具体代码之前我想先聊聊如何最高效地使用这些实例不要仅仅满足于“烧录进去能跑”更重要的是理解其背后的硬件原理和编程思想尝试修改参数、组合功能甚至故意制造一些“Bug”去调试这个过程积累的经验远比代码本身更宝贵。2. 实例库的整体架构与学习路径设计2.1 实例分类逻辑与难度阶梯我将这100个实例分成了五大模块构成了一个清晰的学习路径。第一个模块是“单片机认知与GPIO操控”实例1-20这是所有故事的起点。内容从搭建开发环境Keil/IAR/Arduino IDE、创建第一个工程、编写下载程序开始紧接着就是经典的“Hello World”——LED闪烁。但这里不会止步于一个灯的亮灭我们会迅速扩展到流水灯、按键控制LED、按键消抖处理、蜂鸣器驱动等。这个模块的目标是让你彻底掌握单片机最基础的输入输出理解高低电平、灌电流与拉电流、上下拉电阻等概念并养成良好的代码注释和模块化编程习惯。第二个模块是“定时器与中断系统深度解析”实例21-40。定时器是单片机的“脉搏”中断则是其“神经反应”。这个模块将从最基础的定时器查询方式实现延时开始逐步深入到定时器中断实现精确定时、外部中断响应按键、定时器捕获模式测量脉冲宽度、PWM波生成与控制电机转速等。我会重点讲解不同工作模式如模式1、模式2下的寄存器配置差异以及中断服务函数编写的注意事项比如现场保护、避免耗时操作。掌握这个模块你就能让单片机“准时”且“灵敏”地处理任务了。第三个模块是“数据转换与模拟信号处理”实例41-60。单片机是数字世界的王者但现实世界是模拟的。这个模块聚焦于ADC模数转换器和DAC数模转换器的应用。我们会学习如何读取电位器的电压值、LM35温度传感器的输出以及如何用DAC生成一个正弦波。这里会涉及分辨率、参考电压、采样率等关键参数的选择以及软件滤波如均值滤波、中值滤波来提升ADC读取稳定性的技巧。理解这部分单片机就拥有了感知世界的能力。第四个模块是“通信协议与数据交换实战”实例61-85。单片机很少孤军奋战。UART、I2C、SPI是它与传感器、模块、其他单片机或电脑对话的三大语言。我们将通过实例实现单片机与电脑串口助手通信、驱动OLED屏幕通常用I2C或SPI、读取温湿度传感器DHT11/DHT22单总线、操作EEPROM存储器I2C等。我会详细解析每种协议的时序图讲解如何用软件模拟协议当硬件资源不足时以及如何处理通信中的错误和超时。第五个模块是“综合应用与项目雏形”实例86-100。这里我们会把前面学到的“积木”搭建成有趣的小项目。例如结合ADC和PWM做一个可调光台灯利用定时器和GPIO做一个数码管或LCD显示的简易时钟通过UART和上位机如Python编写的简单程序实现数据监控甚至是一个用超声波测距模块和舵机实现的简易避障小车雏形。这个模块旨在启发你的项目思维展示如何将分散的知识点融合解决实际问题。2.2 开发环境与硬件准备建议工欲善其事必先利其器。对于这100个实例在硬件上我建议你至少准备一套核心板加一个基础功能扩展板。对于初学者一款经典的STC89C51或STC12C5A60S2开发板是完全足够的它们价格低廉资料海量非常适合入门GPIO、定时器、中断、UART。当你需要学习ADC、更高级定时器、多路PWM时可以转向STM32F103C8T6蓝色药丸板这类Cortex-M3内核的板子其性能更强外设更丰富且STM32的HAL/LL库或标准库让开发更高效。当然Arduino Uno基于ATmega328P也是一个极佳的起点其生态丰富有大量现成库能让你快速看到成果建立信心。在软件环境上51系列我推荐使用Keil C51它几乎是行业标准对中文支持友好。STM32则可以使用Keil MDKARM版或STM32CubeIDE后者是ST官方推出的免费集成环境集成了图形化配置工具能自动生成初始化代码大大降低了入门门槛。对于所有平台都强烈建议你安装并学会使用串口调试助手如SSCOM、XCOM这是你与单片机对话、观察程序运行状态、调试问题的“眼睛”其重要性不亚于编程软件本身。注意在选择开发板时务必确认其引脚的兼容性和电压水平常见为5V或3.3V。连接外部传感器模块时电压不匹配是导致芯片损坏的最常见原因之一。一个简单的USB转TTL串口模块如CH340G、CP2102系列也是必备的它负责程序下载和串口通信。3. 核心基础实例详解1-20例3.1 开发环境搭建与首个程序“点灯”万事开头难但第一个实例我们必须把它走通。这里以Keil5和STC89C51开发板为例。首先去Keil官网下载并安装C51版本的Keil uVision5。安装完成后新建一个工程选择正确的单片机型号如STC89C51RC。接着新建一个.c文件写入你的第一个程序。#include REGX51.H // 包含51单片机寄存器定义头文件 #include INTRINS.H // 包含空操作指令_nop_()的头文件 // 定义一个简单的延时函数通过循环消耗CPU时间来产生延时 void Delay_ms(unsigned int ms) { unsigned int i, j; for(i0; ims; i) for(j0; j114; j); // 这个循环次数基于11.0592MHz晶振粗略计算不精确 } void main() { // 主函数程序入口 while(1) { // 无限循环 P1 0xFE; // 假设LED接在P1.0口低电平点亮。0xFE二进制为1111 1110即P1.0输出0 Delay_ms(500); // 延时约500毫秒 P1 0xFF; // P1口全部输出高电平LED熄灭 Delay_ms(500); // 再延时500毫秒 } }这段代码的功能是让接在P1.0口的LED以1秒的周期闪烁。编译无误后我们需要使用STC-ISP这样的下载软件选择正确的串口号、单片机型号打开编译生成的.hex文件点击“下载/编程”按钮然后给开发板重新上电冷启动程序便会烧录进去。看到LED开始规律闪烁的那一刻你的单片机世界就正式点亮了。实操心得这个简单的延时函数Delay_ms是通过空循环实现的在while(j114)这个内层循环中每次循环执行几条指令总共执行114次再乘以外层循环的ms次就形成了毫秒级的延时。但要注意这种延时极不精确且会在延时期间独占CPU导致单片机无法执行其他任务。在实际项目1-10例中我们就会将其改进为定时器中断延时这是嵌入式编程思维的第一个重要升级。3.2 GPIO输入与按键消抖实战控制LED输出是第一步读取外部输入如按键是第二步也是人机交互的基础。假设我们有一个按键接在P3.2口这也是外部中断0的引脚但我们先以查询方式使用。目标按下按键使接在P2.0的LED状态翻转。#include REGX51.H sbit LED P2^0; // 将LED定义为P2.0位 sbit KEY P3^2; // 将按键定义为P3.2位 void Delay_ms(unsigned int ms) { /* 同上略 */ } void main() { LED 1; // 初始化LED熄灭 while(1) { if(KEY 0) { // 检测按键是否按下假设低电平有效 Delay_ms(10); // 延时10ms避开机械按键的抖动期 if(KEY 0) { // 再次确认按键确实被按下 LED ~LED; // LED状态取反 while(!KEY); // 等待按键释放避免长按导致连续翻转 Delay_ms(10); // 按键释放消抖 } } } }这就是经典的“软件消抖”逻辑。机械按键在闭合和断开的瞬间金属触点会因为弹性产生一系列快速的通断即抖动持续约5-10ms。如果不处理一次物理按压会被单片机误判为多次按下。我们的对策是第一次检测到低电平后不立即行动而是延时10ms左右待抖动期过去再次检测引脚电平。如果仍然是低电平则确认为有效按键执行动作最后等待按键释放并再次消抖。实例11-15会围绕不同按键扫描方式如矩阵键盘、中断方式按键检测展开但消抖的思想是共通的。4. 定时器与中断系统实例解析21-40例4.1 定时器中断实现精准延时与时钟如前所述循环延时会阻塞CPU。使用定时器中断可以在后台计时前台CPU可以自由执行其他任务这是多任务系统的雏形。我们以51单片机的定时器0模式116位自动重装为例实现一个1ms的中断并在此基础上构建一个毫秒计时器。#include REGX51.H unsigned int ms_count 0; // 毫秒计数全局变量 // 定时器0初始化函数目标1ms中断一次假设晶振11.0592MHz void Timer0_Init() { TMOD 0xF0; // 清零T0的控制位高4位是T1的我们不动 TMOD | 0x01; // 设置T0为模式116位定时器需手动重装初值 // 计算初值机器周期 12 / 11.0592MHz ≈ 1.085us // 需计时1ms 1000us所需机器周期数 1000 / 1.085 ≈ 921 // 定时器从初值加到溢出65536所以初值 65536 - 921 64615 - 0xFC67 TH0 0xFC; // 装入初值高8位 TL0 0x67; // 装入初值低8位 ET0 1; // 允许定时器0中断 EA 1; // 开启总中断 TR0 1; // 启动定时器0 } // 定时器0中断服务函数 void Timer0_ISR() interrupt 1 { // 模式1下中断后定时器值归零需要手动重装初值 TH0 0xFC; TL0 0x67; ms_count; // 毫秒计数器加1 } void main() { Timer0_Init(); while(1) { // 主循环可以自由执行其他任务如扫描按键、刷新显示等 // 需要延时的地方可以判断ms_count if(ms_count 1000) { // 例如每1000ms1秒执行一次 // 执行你的任务比如让LED闪烁 ms_count 0; // 重置计数器注意这不是精确的1秒因为主循环检测有延迟 } } }这个实例是理解中断机制的关键。interrupt 1是编译器识别的关键字表示这是定时器0的中断服务函数。当中断发生时CPU会暂停当前任务跳转到这个函数执行执行完毕后再返回原任务。实例25-30会深入探讨如何利用这个ms_count构建一个实时时钟RTC并处理时分秒的进位以及如何用数码管或LCD将其显示出来。4.2 PWM波生成与电机调速应用PWM脉冲宽度调制是控制模拟输出如LED亮度、电机速度的利器。其核心是产生一个固定频率的方波并通过改变高电平在一个周期内的占比占空比来等效输出不同的平均电压。我们可以用定时器中断来精确控制PWM的周期和占空比。#include REGX51.H sbit PWM_OUT P1^0; // PWM输出引脚 unsigned char PWM_Duty 50; // 占空比范围0-100 unsigned char timer_tick 0; // 定时器中断计数 void Timer0_Init() { /* 初始化定时器产生一个固定时间基比如100us中断一次 */ } void Timer0_ISR() interrupt 1 { // ... 重装初值 ... timer_tick; if(timer_tick 100) { // 假设100个中断为一个PWM周期 (100 * 100us 10ms, 频率100Hz) timer_tick 0; } // 根据占空比控制输出电平 if(timer_tick PWM_Duty) { PWM_OUT 1; // 高电平 } else { PWM_OUT 0; // 低电平 } } void main() { Timer0_Init(); while(1) { // 可以通过按键或串口命令来改变PWM_Duty的值从而动态调整亮度或速度 // 例如PWM_Duty 20; // 20%亮度/低速 // PWM_Duty 80; // 80%亮度/高速 } }在实例35-40中我们会将PWM应用于直流电机驱动通过L298N或TB6612FNG驱动模块和舵机控制舵机需要的是周期20ms脉宽0.5ms-2.5ms的PWM信号。对于舵机需要更精确的定时器控制通常会使用定时器的PWM硬件输出模式如果单片机支持这比软件模拟更稳定、更节省CPU资源。5. 模数转换与通信协议实例精讲41-85例5.1 ADC读取与软件滤波处理我们以STM32的ADC读取一个电位器电压为例并加入软件滤波。这里使用STM32CubeMX生成初始化代码框架并重点讲解应用层逻辑。// 假设使用STM32Cube HAL库ADC1通道0PA0已配置为单次转换模式 #include main.h #include adc.h #define ADC_SAMPLE_TIMES 10 // 采样次数 uint16_t Get_ADC_Average(void) { uint32_t adc_sum 0; uint16_t adc_value; for(uint8_t i0; iADC_SAMPLE_TIMES; i) { HAL_ADC_Start(hadc1); // 启动ADC转换 if(HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { // 等待转换完成超时10ms adc_value HAL_ADC_GetValue(hadc1); // 获取转换结果 adc_sum adc_value; } HAL_Delay(1); // 稍作延时避免连续采样干扰 } return (uint16_t)(adc_sum / ADC_SAMPLE_TIMES); // 返回平均值 } float Get_Voltage(void) { uint16_t adc_avg Get_ADC_Average(); // 假设参考电压Vref3.3VADC为12位0-4095 float voltage (float)adc_avg / 4095.0f * 3.3f; return voltage; // 单位伏特 } void main(void) { // ... 系统初始化包括ADC初始化 ... while (1) { float current_voltage Get_Voltage(); // 现在current_voltage就是PA0引脚上稳定的电压值了 // 可以将其通过串口发送到电脑或者用于控制PWM占空比实现一个可调光LED // 例如PWM_Duty (current_voltage / 3.3f) * 100; HAL_Delay(100); } }这个实例展示了均值滤波它能有效抑制随机干扰。在实例45-50中我们还会接触到中值滤波对采样值排序取中间值对突发性尖峰干扰效果好、一阶滞后滤波软件实现低通滤波器等。同时会结合具体传感器如通过ADC读取光敏电阻值判断环境亮度读取NTC热敏电阻值并通过查表法计算温度。5.2 UART串口通信与数据解析串口是单片机调试和通信的“大动脉”。实例60-70会详细讲解如何实现可靠的串口收发包括中断接收、环形缓冲区、以及命令解析。以下是一个基于STM32 HAL库的串口中断接收与回显示例// 在main.c中 uint8_t uart_rx_buf[256]; // 接收缓冲区 uint16_t uart_rx_index 0; // 缓冲区索引 // 在main函数初始化部分开启串口接收中断 HAL_UART_Receive_IT(huart1, uart_rx_buf[uart_rx_index], 1); // 串口接收中断回调函数HAL库中重写此函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { // 检查接收到的字符例如以回车换行(\r\n)作为一帧结束 if(uart_rx_buf[uart_rx_index] \n) { // 找到帧尾处理一帧数据 uart_rx_buf[uart_rx_index] \0; // 添加字符串结束符 // 例如回显接收到的数据 HAL_UART_Transmit(huart1, (uint8_t*)Echo: , 6, 100); HAL_UART_Transmit(huart1, uart_rx_buf, uart_rx_index, 100); HAL_UART_Transmit(huart1, (uint8_t*)\r\n, 2, 100); // 这里可以添加更复杂的命令解析例如判断是否以“SET LED”开头等 uart_rx_index 0; // 重置索引准备接收下一帧 } else { uart_rx_index; if(uart_rx_index 256) uart_rx_index 0; // 防止缓冲区溢出 } // 重新开启接收中断等待下一个字节 HAL_UART_Receive_IT(huart1, uart_rx_buf[uart_rx_index], 1); } }这个框架实现了基本的“指令-响应”交互。在更复杂的实例中我们会构建一个命令解析器能够识别如LED ON、PWM 50、GET TEMP这样的字符串命令并执行相应的操作这为单片机与上位机如PC、手机APP协同工作奠定了基础。5.3 I2C驱动OLED屏幕显示I2C是一种两线制的同步串行通信协议广泛用于连接低速外设。SSD1306驱动的0.96寸OLED屏是常见的I2C设备。实例71-75会从零开始用软件模拟I2C时序不依赖硬件I2C外设来驱动它。首先我们需要实现基础的I2C起始、停止、发送一个字节、接收一个字节带ACK的函数。这里以51单片机模拟为例sbit I2C_SCL P2^1; // 模拟I2C时钟线 sbit I2C_SDA P2^0; // 模拟I2C数据线 void I2C_Start() { I2C_SDA 1; I2C_SCL 1; Delay_us(5); // 短暂延时满足时序 I2C_SDA 0; Delay_us(5); I2C_SCL 0; // 钳住总线准备发送数据 } void I2C_Stop() { I2C_SDA 0; I2C_SCL 1; Delay_us(5); I2C_SDA 1; Delay_us(5); } bit I2C_WriteByte(unsigned char dat) { unsigned char i; bit ack; for(i0; i8; i) { I2C_SDA (dat 0x80) ? 1 : 0; // 从最高位开始发送 dat 1; I2C_SCL 1; Delay_us(5); I2C_SCL 0; } // 读取ACK I2C_SDA 1; // 释放SDA线准备接收 I2C_SCL 1; Delay_us(5); ack I2C_SDA; // 读取从机ACK电平0为应答 I2C_SCL 0; return ack; }有了这些底层函数我们就可以按照SSD1306的数据手册编写初始化序列、发送命令和数据、以及清屏、画点、显示字符和字符串的函数。最终我们将实现一个OLED_ShowString(x, y, *str)这样的函数在主循环中调用它来显示变量、状态等信息。这个过程虽然繁琐但能让你彻底吃透I2C协议和点阵屏的驱动原理。6. 综合应用与项目实战86-100例6.1 简易数字电压表设计这个项目将综合运用ADC、OLED显示和按键。功能是测量外部输入电压0-3.3V并实时显示在OLED屏幕上通过按键可以切换显示模式如实际电压值、柱状图等。核心逻辑如下初始化配置ADC如STM32的ADC1通道、OLED、按键GPIO。主循环循环检测按键根据按键切换display_mode0数值1柱状图。调用Get_Voltage()函数获取当前电压值。根据display_mode调用不同的OLED显示函数。模式0OLED_ShowString(0, 0, Voltage:);然后OLED_ShowFloat(0, 2, voltage, 2);显示两位小数。模式1计算电压值对应的柱状图长度bar_length (voltage / 3.3) * 128;假设屏幕宽128像素然后调用画线或画矩形函数绘制柱状图。优化可以加入量程切换通过分压电阻、数据保持HOLD功能、最大值/最小值记录等。这个项目麻雀虽小五脏俱全涵盖了数据采集、处理、人机交互和显示是一个非常好的综合性练习。6.2 基于超声波模块的测距仪使用HC-SR04超声波模块结合定时器捕获功能实现距离测量并显示。HC-SR04模块有4个引脚VCC, GND, Trig触发, Echo回响。工作原理单片机给Trig引脚一个至少10us的高电平脉冲模块自动发出8个40kHz的超声波。当接收到回波时Echo引脚会输出一个高电平其持续时间与距离成正比。距离 (高电平时间 * 声速340m/s) / 2。// 伪代码逻辑 float Get_Distance(void) { // 1. 触发Trig拉高10us以上然后拉低 TRIG 1; Delay_us(15); TRIG 0; // 2. 等待Echo变高并开始计时可以用外部中断上升沿触发或轮询 while(ECHO 0); // 等待Echo变高 // 启动一个定时器或清零一个定时计数器 // 3. 等待Echo变低停止计时 while(ECHO 1); // 等待Echo变低 // 停止定时器获取计数值 time_us // 4. 计算距离distance_cm (time_us / 1000000.0) * 34000 / 2; // 简化time_us单位是微秒声速34000 cm/s 距离 time_us * 0.017 float distance_cm time_us * 0.017f; if(distance_cm 400) distance_cm 400; // 限制最大量程 return distance_cm; }在实例95中我们会将这个测距功能与舵机结合让舵机带动超声波模块左右扫描实现一个简易的雷达效果并将扫描到的距离信息通过串口发送到电脑用上位机软件绘制出简单的距离-角度图。7. 常见问题排查与调试心得实录7.1 程序下载失败问题排查表现象可能原因排查步骤开发板连接电脑后无反应设备管理器无串口驱动未安装或安装错误USB线仅供电无数据开发板损坏1. 检查开发板型号安装对应USB转串口芯片驱动CH340/CP2102/PL2303。2. 换一根已知好的USB数据线。3. 尝试连接其他USB口或换一台电脑测试。能识别串口但下载软件提示“正在检测目标单片机...”后失败单片机型号选择错误冷启动时序不对波特率过高P1.0/P1.1口被占用1. 确认下载软件中选择的单片机型号与板上型号完全一致如STC89C52RC。2.严格遵循冷启动流程点击下载按钮 - 软件提示“请给MCU上电” -此时给开发板断电再上电。3. 尝试降低下载波特率如从115200降到9600。4. 检查P1.0和P1.1口是否接有外部器件如LED、传感器下载时需断开。程序下载成功但运行不正常如LED不亮程序逻辑错误硬件连接错误晶振未起振1. 使用最简单的LED闪烁测试程序验证。2. 用万用表测量LED两端电压或测量IO口输出电平。3. 检查晶振及匹配电容是否焊接良好。对于STM32检查启动模式引脚BOOT0/BOOT1配置是否正确。调试心得“最小系统测试法”是解决疑难杂症的黄金法则。当项目复杂、问题诡异时请务必回归到最原始的状态拔掉所有外接模块只保留核心板和电源烧录一个最简单的、只控制一个板上LED闪烁的程序。如果这个都失败问题一定在核心板、电源或下载链路。如果成功再逐一接回外设模块每接一个就测试一次能快速定位问题模块。7.2 程序运行异常问题排查表现象可能原因排查思路程序偶尔跑飞或复位电源不稳定看门狗未处理堆栈溢出中断冲突1. 用示波器观察电源电压尤其在电机等大电流设备启动时。2. 检查是否使能了看门狗IWDG/WWDG但未定期“喂狗”。3. 检查局部变量是否过大导致栈溢出或动态内存分配导致堆溢出。4. 检查中断服务函数是否执行时间过长或发生了中断嵌套冲突。串口通信乱码或丢数据波特率不匹配时钟源错误缓冲区溢出电平不匹配1.确保收发双方波特率、数据位、停止位、校验位完全一致。这是最常见原因。2. 检查单片机系统时钟如外部晶振配置是否正确波特率计算依赖于系统时钟。3. 发送速度过快接收方来不及处理。需增加接收缓冲区或降低发送频率。4. 检查TTL3.3V/5V与RS232电平是否混淆两者不能直接连接。ADC采样值跳动大电源噪声参考电压不稳信号源内阻大未滤波1. 在模拟电源AVDD和参考电压VREF引脚加滤波电容如10uF0.1uF。2. 对于高内阻信号源如某些传感器需在ADC输入引脚加一个小的滤波电容如0.1uF到地并降低采样速率。3. 务必在软件中实施滤波算法如均值、中值滤波。I2C/SPI通信失败上拉电阻缺失从机地址错误时序不符合要求总线冲突1. I2C的SDA和SCL线必须接上拉电阻通常4.7kΩ-10kΩ。2. 确认从机设备地址7位或8位格式是否正确注意左移操作。3. 用逻辑分析仪或示波器抓取时序与数据手册对比。检查时钟频率是否过高。4. 确保总线上没有多个主机同时发起通信。7.3 代码编写与优化技巧模块化编程将不同功能LED、按键、UART、OLED写成独立的.c和.h文件。.h文件中声明外部可调用的函数和全局变量.c文件中实现。这样结构清晰便于复用和团队协作。例如oled.c和oled.huart.c和uart.h。使用有意义的变量名和函数名避免使用a,b,temp这种名称。用adc_value、pwm_duty、UART_SendString()这样的名字代码可读性会极大提升。善用宏定义和枚举对于引脚定义、常量值使用宏定义。对于状态机、模式选择使用枚举。这能避免“魔法数字”方便修改。#define LED_PIN GPIO_PIN_0 #define LED_PORT GPIOA #define PWM_MAX_DUTY 100 typedef enum { MODE_IDLE, MODE_MEASURING, MODE_CALIBRATING } SystemMode_t; SystemMode_t current_mode MODE_IDLE;资源与效率权衡在51这类资源紧张的单片机上要慎用浮点数运算非常慢。可以将计算转换为定点数运算。例如电压值用uint16_t类型表示单位为毫伏mV避免浮点。在STM32上如果性能允许使用浮点单元FPU则很方便。状态机思维对于复杂的业务流程如菜单系统、通信协议解析不要用一堆if-else和flag而是使用状态机。定义好状态和事件代码会变得非常清晰和健壮。实例99会专门讲解一个基于状态机的简易菜单系统实现。这100个实例的旅程从点亮一个LED开始到能够独立完成一个小型综合项目我希望它们能成为你单片机学习路上的一块块坚实的垫脚石。代码本身是死的但其中蕴含的硬件操作逻辑、编程思想和调试方法才是活的。我建议你在学习时准备一个笔记本记录下每个实例中遇到的坑、解决的思路、以及灵光一现的优化想法。这些笔记将来会成为你最宝贵的财富。最后嵌入式开发没有捷径就是多看、多写、多调、多思考。当你能够不看任何参考从零搭建环境为一个新的传感器编写驱动并成功读取数据时你就真正入门了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价