☰
STM32点灯实战:从寄存器配置到C++封装GPIO驱动
2026/9/28 13:51:27 网站建设 项目流程

看到有人在评论区问:“都第三篇了,一行代码都没见到,你到底会不会教?”说实话,我特别理解这种心情——我自己学嵌入式的时候也这样,前几章全是概念和背景,翻到第四章才看到第一段代码,当时恨不得把书扔了。不过在嵌入式这个领域,尤其是用C++来写单片机程序,如果你没搞懂“对面是什么硬件、数据手册怎么看、寄存器到底是干嘛的”,直接抄代码,大概率会出现“明明照着唯一答案敲完了,板子就是不干活,哪儿错了都不知道”的尴尬局面。

这篇不一样,我向你保证,从这篇开始,真的要动手写了。咱们用一块最常见的STM32F103最小系统板,走一遍“看原理图、查数据手册、开GPIO时钟、配置引脚模式、写第一个点灯程序”的完整体验。我用的开发环境是VS Code + arm-none-eabi-gcc,不要Keil那种图形化点击,还是那句话,嵌入式C++要的是你能完全掌控代码每一步在干什么,而不是让IDE帮你把活儿都干完了。

1. 先别急着怼我:为什么前三篇没让你写代码

1.1 嵌入式不是应用开发,你面对的是一份数据手册

如果我们是写一个Web服务,你打开IDE、敲几行Java或者Go代码,起一个HTTP端口,浏览器一访问,能跑就行。工具链会帮你屏蔽掉绝大部分底层细节。

但嵌入式开发完全不是这个逻辑。你的C++代码最终编译成一坨二进制文件,烧进芯片里,芯片按照数据手册里定义的电气行为和寄存器位定义,一条一条地执行你的指令。换句话说,你做嵌入式开发时,真正的“接口文档”不是你的IDE帮助文档,而是芯片厂商提供的参考手册(Reference Manual)和数据手册(Datasheet)。

STM32F103的参考手册有上千页,其中很大一部分在讲外设寄存器。如果你不知道这些寄存器的存在、不知道一个外设大概有哪些控制位,那代码对你来说就是魔法。写出来不亮,你根本不知道该去查什么。所以前几篇我花大量篇幅铺垫的“寄存器”概念,本质上是在给你装一个“查手册”的脑回路——这篇实战里,咱们就要地毯式地查一遍手册。

1.2 我踩过的坑说明白了:寄存器就是设备的“遥控器”

我刚学的时候一直纠结一个问题:为什么不能像C++操作类对象一样,直接gpio.setHigh()完事儿?为什么非要写那些又长又难记的寄存器地址?

后来我琢磨明白了,寄存器就是硬件的遥控器。芯片厂商没法预测你要让哪个引脚输出高电平、哪个引脚配置成输入模式,所以他们把控制权全部开放出来,以寄存器的方式交给开发者。每个寄存器就是一组开关,有些开关控制引脚模式,有些控制电平状态,有些控制时钟开关。

遥控器确实没有setHigh()一个按钮来得舒服,但它是最底层的通用语言。你自己封装的setHigh(),底层还是要把寄存器里的某一位改成1。没有这层认知,你以后移植到别的芯片、或者用另一种型号的单片机时,会发现自己除了会调用库函数,什么都不剩。

1.3 什么时候才算准备好了?

很多人卡在“准备”这个阶段,总觉得这里还不懂、那里还模糊,不敢动手。我做个简单判断标准:你知道你的板子用的是哪个型号的芯片,你知道怎么打开VS Code新建一个工程,你知道编译下载烧录这个流程大概长什么样——就这三个条件,够了。剩下的,全在实战里学。

要是最后一个条件还不满足,我强烈建议你花一个小时,先把我前面写过的工具链搭建流程跑一遍。这篇马上就给你完整的工程结构,如果你连编译工具都没装,那才真的是看代码都费劲。

2. 从烂大街的“点灯”说起:它是嵌入式界的Hello World

2.1 为什么人人都拿点灯入门?

点灯看似简单,但它把嵌入式开发最基本的完整链条凑齐了:时钟系统怎么配置、GPIO外设怎么使能、引脚模式怎么设置、输出数据寄存器怎么操作。这几个环节,几乎是所有STM32外设编程的公共前置知识。

举个类比,你在Web开发里写个Hello World,要搞定HTTP协议、路由、响应体——点灯也是这个地位,它让你走通“配置时钟 → 配置外设 → 操作寄存器”这条路,只不过输出结果不是网页,而是LED的亮和灭。

另外,LED点灯还是调试利器。我写过不少项目,串口调试没来得及接、屏幕还没点亮的时候,全靠板子上的LED来判断程序跑到了哪个分支。有经验的工程师甚至用LED的闪烁频率来区分不同的错误状态。

2.2 先看原理图,别急着写代码

写任何嵌入式程序,第一步永远是看板子原理图,不是打开编辑器。你需要确认三件事:LED挂在哪个引脚、引脚通过什么电路接到LED、高电平亮还是低电平亮。

我手头用的这块STM32F103C8T6最小系统板,原理图上标得很清晰:板载LED接在PC13引脚,经过一个限流电阻接到3.3V电源。注意这颗LED的连接方式是共阳极接法——电源从3.3V经过限流电阻和LED,再到PC13引脚。

这意味着什么?当PC13引脚输出低电平时,电流从3.3V经过LED和电阻流到PC13,LED点亮;当PC13输出高电平时,两端电位相同,没有电流流动,LED熄灭。

就这么一个细节,你如果不看原理图就照着网上代码写一个“高电平点亮”,板子上的灯大概率纹丝不动——因为人家的LED可能是共阴极接法。代码语法没毛病,编译也没报错,但就是不符合你的硬件。这类问题,在实操里比复杂的逻辑错误还磨人。

2.3 建立最小工程,先从寄存器操作开始

刚上手的时候,我不建议直接用STM32官方标准外设库或者HAL库,那相当于你还没学会算数就让你用计算器。先来一段纯粹寄存器操作的代码,把底层逻辑走一遍。

以下是这次打定主意要跑起来的最小工程骨架:

// main.cpp - 基于STM32F103C8T6的最小点灯程序 #include "stm32f1xx.h" int main() { // 1. 打开GPIO.Clock,让GPIOC外设开始工作 RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // 2. 配置PC13为通用推挽输出模式,速度设为2MHz GPIOC->CRH &= ~(GPIO_CRH_CNF13_Msk); // 先清空PC13的模式配置位 GPIOC->CRH |= GPIO_CRH_MODE13_0; // 设置输出模式的速度位 GPIOC->CRH &= ~(GPIO_CRH_CNF13_Msk); // 配置为通用推挽输出 while (1) { // 3. 让PC13输出低电平,点亮LED GPIOC->BRR = GPIO_BRR_BR13; // 简单软件延时 volatile int delay = 0; for (delay = 0; delay < 720000; delay++); // 4. 让PC13输出高电平,熄灭LED GPIOC->BSRR = GPIO_BSRR_BS13; for (delay = 0; delay < 720000; delay++); } }

看到没,核心步骤就三步:开时钟、配模式、写数据寄存器。这三步做完,程序就能跑。代码里我把RCC->APB2ENR、GPIOC->CRH、GPIOC->BRR/BSRR这些寄存器地址全部用官方头文件里的宏定义写出来了——这些宏可不是我信手写的,它们都来自ST官方提供的设备头文件。

这里顺便给小白解释一下,STM32为了简化开发,在GPIO端口上给了两组寄存器来改变引脚输出状态:BRR(引脚复位寄存器,写入1就让引脚输出低电平)和BSRR(引脚置位寄存器,写入1就让引脚输出高电平)。理论上一组就够了,为什么做两组?背后的考虑是:如果只用一个ODR寄存器,你要置位某一个引脚,必须先把当前状态读回来、再改对应位、再写回去,这个过程不是原子的——万一中间来了个中断,其他引脚的状态就可能被意外改写。而BRR/BSRR的好处是,你写入的时候直接写要改的那一位,其他位全部写0,完全不影响别的引脚状态,硬件层面就保证了原子性。

这也是我建议你从寄存器开始的原因之一:你只有了解到这一层,才会理解HAL库里HAL_GPIO_WritePin为什么那么设计,脑子里就有了完整的知识体系,而不是调用完了就完了。

3. 时钟树与GPIO配置:点灯背后真正重要的两件事

3.1 为什么所有外设的第一步都是“开时钟”?

很多刚入门的人,看代码时候对RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;这句无感,觉得像是个固定仪式。它还真不是仪式,这是STM32低功耗设计的一个关键机制。

STM32的绝大多数外设,默认都是断电省电状态。你不先打开外设时钟,外设的寄存器根本不会被映射到总线地址上,你写任何配置都是无效的。

用生活里的话说,时钟就是外设的供电开关。你往墙上插充电器之前,得先在电闸那里合上对应的空气开关,否则插座就是个摆设。GPIO的时钟挂在APB2总线上,RCC->APB2ENR就是这面电闸。你要用哪个GPIO端口,就把这个端口对应的位置1。

我见过太多入门者在配置外设时漏掉开时钟这一行,查了半天代码逻辑,就是忘了它。养成习惯,以后不管用什么外设,第一件事永远是去RCC寄存器里找这个外设的时钟开关。

3.2 端口配置寄存器CRH/CRL:每一个引脚的“性格”由这4位决定

接下来是GPIO引脚的模式配置。STM32F103的每个端口引脚,由CRL(控制低8个引脚,即PIN0~PIN7)和CRH(控制高8个引脚,即PIN8~PIN15)两个寄存器控制。每个引脚占用4位,这4位又分成两部分:CNF[1:0]配置引脚功能类型,MODE[1:0]配置输入/输出及输出速度。

PC13属于高8位,所以它的控制寄存器在CRH里。对照数据手册的寄存器位定义:

  • MODE13[1:0]:控制引脚是输入还是输出,以及输出模式下的最大翻转速度。10表示输出模式、速度2MHz。
  • CNF13[1:0]:在输出模式下,10表示通用推挽输出(GPIO_PP);在输入模式下,01表示带上拉或下拉。

通用推挽输出是最常用的模式,它意味着引脚既能输出高电平(驱动电流经PMOS到达引脚),又能输出低电平(引脚电流经NMOS流向地),输出高低电平的驱动能力都比较强,驱动LED完全够用。

配置的时候有个细节,就是必须先清空原来的位再写入新值,否则原来的配置会干扰新配置。代码里我用的是标准做法:

GPIOC->CRH &= ~(GPIO_CRH_CNF13_Msk); // 清空并配置为通用推挽输出 GPIOC->CRH |= GPIO_CRH_MODE13_0; // 置位MODE13最低位:输出模式2MHz

这里你可能会问,GPIO_CRH_CNF13_Msk和GPIO_CRH_MODE13_0是从哪儿来的?它们是ST官方的stm32f1xx.h头文件里根据数据手册寄存器定义预生成的位掩码宏。这个头文件就是程序员和数据手册之间的桥梁,它把寄存器地址和位定义翻译成了C语言可以识别的东西。这也是嵌入式C++和普通应用开发的一个不同点:你写代码的过程中,得时不时地翻开芯片的头文件和参考手册,交叉确认自己写的位操作和硬件定义是否一致。

3.3 为什么我选了2MHz而不是最快的50MHz?

我故意把输出速度配成2MHz,是基于实际应用场景考量的。PC13这一路是板载LED,通常通过一个比较小的限流电阻连接到3.3V,电流只有几毫安。LED本身也不是高速信号,2MHz的输出速度在电气性能上完全够用。

更重要的是,输出速度越高,引脚翻转瞬间的电流变化越快,电磁干扰越强,功耗也越高。对于低速信号,盲目追求高速只会引入噪声和过冲问题。这体现的是嵌入式开发的一种思维习惯:不选最强的,选最合适的。

曾经我在一个项目里把所有引脚都配到50MHz,结果PCB走线比较长,信号边沿抖动得特别厉害,后来查资料才知道,IO翻转速度太快导致振铃。从那之后,我都是把速度档位和信号的实际频率挂钩,再也不贪快。

4. 让它动起来:从寄存器到第一款可扩展的C++代码

4.1 体验一把直接在嵌入式里写C++的感觉

上面的代码能跑,但严格来说它还是“穿了一件C++外衣的C语言”——整体思路还是过程式的。既然咱们这篇的主题是嵌入式C++,那咱们就再往前走一步,把这个简单的点灯逻辑,用C++的类封装成更通用的GPIO驱动。

你想想,如果以后一个项目要控制几十个LED,每个LED都像刚才那样写一遍寄存器配置,代码会肿得没法看。而C++的封装能力恰好适合解决这个问题。

举个例子,我们可以写一个简单的LED类:

// led.hpp #pragma once #include "stm32f1xx.h" class Led { public: Led(GPIO_TypeDef* port, uint16_t pin); void init(); void on(); void off(); void toggle(); private: GPIO_TypeDef* _port; uint16_t _pin; };

你可能会问,GPIO_TypeDef*是什么?这是官方头文件里定义的一个结构体类型,代表整个GPIO外设的寄存器组。每个GPIO端口在内存里都有基地址,这段连续的地址被映射成了结构体的各个字段:CRL、CRH、IDR、ODR、BSRR、BRR。只要你拿到一个GPIO端口的基地址指针,就能访问它的所有寄存器。

这样设计的好处是,我们可以把“操作哪个端口”这个可变因素变成构造函数参数:

// led.cpp #include "led.hpp" Led::Led(GPIO_TypeDef* port, uint16_t pin) : _port(port), _pin(pin) {} void Led::init() { // 1. 打开时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // 2. 根据引脚号,选择CRL或CRH(这里简单起见只支持PC13) _port->CRH |= GPIO_CRH_MODE13_0; _port->CRH &= ~GPIO_CRH_CNF13_Msk; } void Led::on() { _port->BRR = (1 << (_pin - 16)); // 写入BRR对应位 } void Led::off() { _port->BSRR = (1 << (_pin - 16)); } void Led::toggle() { if (_port->ODR & (1 << _pin)) { _port->BRR = (1 << (_pin - 16)); } else { _port->BSRR = (1 << (_pin - 16)); } }

然后主函数就优雅多了:

#include "led.hpp" int main() { Led boardLed(GPIOC, 13); boardLed.init(); while (1) { boardLed.on(); volatile int delay; for (delay = 0; delay < 720000; delay++); boardLed.off(); for (delay = 0; delay < 720000; delay++); } }

这种写法的一个直接好处是,LED的开关状态与硬件引脚解耦了,Led类的任何实例都可以点灯,你只要将来传入正确的端口和引脚。从工程化角度讲,这也叫“面向外设编程”。

4.2 把点灯泛化:你的第一个可复用GPIO模块

上面那个Led类已经不错,但它还是绑定了PC13在CRH配置的假设。引入一个中间抽象层,把GPIO初始化和操作做成一个通用类,这样才能算真正开始用C++的思维方式写嵌入式代码。

这里我不给完整代码,核心思路是:

class Gpio { public: enum class Mode { Input, OutputPP, OutputOD, Analog }; Gpio(GPIO_TypeDef* port, uint16_t pin, Mode mode); void write(bool high); bool read() const; private: GPIO_TypeDef* _port; uint16_t _pin; };

构造函数里根据pin的值(0~7还是8~15)选择操作CRL还是CRH,同时清空对应的CNF和MODE位,再根据传入的Mode填好模式配置。write方法里区分BSRR和BRR,read读IDR。

这段代码琢磨透了之后,你会发现一个很重要的规律:STM32整个GPIO体系其实就是那两组寄存器在不同引脚上的排列组合。从此以后你再看任何板子的点灯代码,都有种“一眼看穿”的快感。

顺便说句心里话,这就是我坚持让你从寄存器开始写的原因。HAL库和标准外设库里那些API确实好用,但它们是别人对硬件抽象后的产物,你在使用过程中很难感知“底层发生了什么”。亲手封装一遍,你才能体会到库函数那些参数设计是怎么来的——你以后用任何现成的库,都不再是一个无脑调用者,而是能读懂设计意图的开发者。

4.3 编译烧录过程中的几个实测经验

工程写好了,接下来的工作是编译、烧录、验证。我用的是arm-none-eabi-gcc交叉编译链,VS Code里配好了tasks和launch,整个流程已经相当顺滑。给几个实操建议:

编译命令里要链接一个启动文件startup_stm32f103xb.s,它是芯片上电后最早执行的汇编代码,主要任务是设置栈指针、初始化向量表、跳转到SystemInit函数配置时钟系统、再调用main()。忘了加这个文件,程序连入口都找不到。

烧录用的是ST-Link V2或者USB转TTL串口。用ST-Link的好处是可以直接用STM32 ST-LINK Utility或者OpenOCD下载,还能在线调试。我习惯用st-flash write firmware.bin 0x08000000这种方式,简单粗暴。0x08000000是STM32F103内部Flash的起始地址,所有固件都要烧到这个地址上,程序才能从Flash启动。

首次烧录成功率高,不代表你没问题。最典型的翻车现场是把引脚号搞错了。F103C8T6封装是LQFP48,PC13比较特殊,它在芯片上是个单独引出的引脚(TQFP48的第2脚)。有些国产开发板虽然也叫“STM32F103C8T6最小系统板”,但板上LED可能接到了别的引脚——比如PB12或PA15,得看准原理图,以实物为准,不要默认别人写的例程就是你的硬件。

5. 从点灯到通信:为什么嵌入式里到处都是协议

5.1 你迟早要面对的五种通信协议

点灯跑通之后,嵌入式学习路线上的下一关就是通信——跟传感器说话、跟屏幕说话、跟另一块板子说话。业内经常讲“嵌入式5种通信协议”,指的就是UART、SPI、I2C、CAN、USB。每一种都有自己擅长的场景,也各有各的坑。

这里我提前打个预防针:通信协议的学习,本质上是理解“数据和时钟怎么对齐”的问题。UART靠双方约定的波特率自行同步,SPI靠时钟线SCK带动,I2C靠时钟线和应答信号配合,CAN靠报文的优先级和仲裁,USB靠复杂的枚举和端点机制。

每一种通信方式最终都落到寄存器的配置上。比如UART要设置波特率寄存器BRR、控制寄存器CR1里的UE、TE、RE位,SPI要设置CR1里的MSTR、SPE、BR位。我相信你经过这篇点灯实战,已经不太怕这些寄存器名了。通信协议不过是相似流程的重复:开时钟、配置引脚复用、配置外设模式、操作数据寄存器。

5.2 一个串口通信的原型代码,帮你打个底

为了让你在下一篇系列文章前有个心理准备,咱们用寄存器方式点亮一个最常用的UART发送功能。这里直接给可以抄的代码框架:

void uart_init() { // 1. 打开USART1和GPIOA的时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN; // 2. 配置PA9为复用推挽输出,PA10为浮空输入 GPIOA->CRH &= ~(GPIO_CRH_CNF9_Msk | GPIO_CRH_MODE9_Msk); GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_1; // 复用推挽输出50MHz // 3. 设置波特率115200:USARTDIV = 72MHz / 115200 ≈ 625 USART1->BRR = 625; // 这里简化了,实际要看OVER8位和BRR分频关系 // 4. 使能发送和UART USART1->CR1 |= USART_CR1_TE | USART_CR1_UE; } void uart_send_char(char c) { while (!(USART1->SR & USART_SR_TXE)); USART1->DR = c; }

这个代码框架也许调不通,因为波特率分频值的计算比上面写的要复杂一些——72MHz的F103跑115200,BRR寄存器实际应该写624还是625,取决于你把时钟配置到了多少MHz。这些细节我都会在下一篇好好拆解。这里先让你有个整体印象:串口通信,也不过是“开时钟、配引脚、配外设、操作数据寄存器”的又一次重复。

5.3 AI编程与嵌入式开发的一点点心得

最近AI编程非常火,各种AI代码生成工具在应用开发领域确实效率惊人。但嵌入式领域的情况不太一样,AI生成嵌入式代码的准确率没那么高,主要原因是嵌入式代码高度依赖具体的芯片型号、引脚连接、外部电路和你手上的开发板硬件。

你让AI帮你生成一段STM32的代码,它并看不到你的原理图,回答很可能基于通用模板,给出的引脚配置和时钟配置可能对不上你的板子。到头来你还是得自己搞清楚GPIO时钟是哪条总线、引脚是挂在哪个端口,否则你连AI生成的代码哪儿错了都看不出来。

所以,在我个人看来,AI编程在嵌入式中真正能帮上忙的地方,是帮你快速写出一个项目的代码框架,帮你把寄存器定义从头文件里检索出来,帮你解释报错信息和数据手册段落。把这个工具用好,前面讲的“查手册、查寄存器映射、配时钟启用外设”这些基本功反而更重要——因为你得有能力判断AI给的结果对不对。这就像用计算器算账很快,但算得快的前提是你自己知道公式是对的、数字带对了。基本功永远不能被替代,只是给你省了重复劳动的时间。

6. 常见问题排查实录:我第一次完整体验了一遍点灯翻车现场

6.1 现象一:代码编译通过了,LED死活不亮

大概率是硬件问题没检查到。按这个顺序自查:

  1. 先查电源:用万用表量3.3V和GND,板上有没有电。没电一切免谈。
  2. 再查原理图:LED是不是真的接在PC13?有没有用到跳线帽?有些板子需要跳线来连接LED和引脚。
  3. 查正负极:共阳极接法输出低电平亮,共阴极接法输出高电平亮。如果你用的是别人代码里“高电平亮”的写法,在这个电路里就歇菜了。
  4. 最后查代码里的引脚号:你写的是PC13,看看配置的寄存器位操作是不是真的修改了CRH的第13位对应的那4个bit。

这几个查完,绝大多数问题都能暴露。

6.2 现象二:LED点亮了,但是闪烁频率不对劲

如果你发现灯亮的频率跟预期差很多,多半是时钟频率问题。代码里那个软件延时是按72MHz主频估算的,但如果你没有调用SystemInit配置时钟,芯片可能跑的是内部8MHz的HSI时钟。同样的720000次循环,实际延时时间长了近9倍。

解决方法是确认启动文件里是否调用了SystemInit,或者在main的一开始直接调用SystemInit(),同时确认工程里链接了正确的时钟配置源文件。这里多说一句,很多教程让你建工程时,刻意嘱咐“别用SystemInit”,理由是让初学者手动配置时钟树。但对于目前的你来说,直接用官方的默认配置就好,等后面学到时钟树了,再专门去配置PLL倍频这些高级选项。

6.3 现象三:烧录时报错“No ST-Link detected” / “Flash download failed”

遇到这类问题,先看连接。ST-Link和板子之间的SWD线有没有接对?SWDIO接SWDIO,SWCLK接SWCLK,GND必须共地。很多时候就是杜邦线松了或者接错针脚,导致调试器找不到目标芯片。

如果确认连接没问题,先按一下板子上的复位按钮再烧录一次。有时候芯片程序跑飞了或者进入了低功耗模式,调试接口会暂时失联,复位一下能重新抓住它。还有一个常见原因是供电不足,片外ST-Link虽然能供电,但电流可能不够,换成USB供电同时把ST-Link的供电线断开再试试。

6.4 常见问题速查表

现象最可能的原因排查手段
LED完全不亮时钟没有使能,或引脚配置错误检查RCC->APB2ENR和CRH寄存器
LED微亮但不闪烁软件延时循环变量被优化了检查延时变量是否加了volatile
闪烁速度异常慢/快系统时钟源没配置好确认SystemInit被调用,时钟频率正确
编译报错未定义GPIO_CRH_MODE13_0没包含正确的设备头文件检查stm32f1xx.h或者直接包含stm32f103xb.h
烧录失败SWD接线错误或芯片锁死检查接线,触发复位后再烧录

补充一个看起来很基础但很坑的细节:作为嵌入式初学者,你会反复和“程序能编译”与“硬件能干活”之间的鸿沟作斗争。所有编译器都能通过的代码,在硬件上不一定有意义。这也是嵌入式开发的乐趣所在——你的判断力和动手能力,会在一次次对照原理图排查的过程中慢慢积累起来。

7. 最后再给点实操层面的建议

如果你现在跟我一样,遇到问题之后养成了查手册、对照电路图的习惯,那这篇点灯实战的使命就完成了。作为一个系列文章的第五篇,其实到这里你应该已经具备以下能力:看原理图画出一个引脚对应的寄存器位、看懂时钟使能的作用、理解GPIO配置寄存器的各个位段、会用C++做一个简单的硬件抽象封装。

这些能力不是一个晚上就能固化下来的,后续几篇我会带着你用同样的思路,把串口玩明白,把中断看懂,把定时器用起来,逐渐让嵌入式C++的世界在你面前展开。你写下去就好,前面几篇铺的路现在开始有回报了。这篇已经能让你亲手点亮第一颗LED,下一篇开始,你就能让单片机开口说话了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询