主题
图文版 · 半小时学完单片机基础
📖 图文版 — 包含完整教程图文内容,适合阅读学习、复习回顾。
52:19电赛零基础AI+速通,一个视频学完单片机。GPIO PWM ADC TIM IRQ I2C SPI UART WDG DMA【BA】
零基础入门单片机,半小时速通GPIO、PWM、ADC、UART、I2C、SPI等核心外设功能
BA-0:半小时学完 单片机基础

简单来讲,单片机就是一个指甲盖大小、几块钱一个,专门控制小电器的迷你小电脑。
它不是你用来打游戏、刷视频的大电脑,而是在各种小电器里的小管家,例如:
•电子表、小风扇
•感应灯、电动车仪表盘
•小玩具、测温枪、遥控车
为啥不用大电脑?
单片机一便宜、二体积小、三低功耗、四功能简单(越简单越稳定越不容易死机)
看这个系列课需要什么基础?
一初中电路的知识、二知道编程是个啥(不咋会也没关系)、三没了
半个小时学到什么?
知道单片机的基本功能,掌握这些内容之后就可以看懂并上手实际项目了

BA-1:电压?!电压到底是什么?

关键在于,电压描述的是一个差值。

为什么亮?
电压产生了电流
为什么不亮?
没有回路,电压产生不了电流
真的有3.7V电压吗?
灯泡两侧的电压计算,发现矛盾:
U=I×R,R是有限值,I=0,U=0≠3.7
因此对标注的电压作出更正:

为什么有-3.7V?
所谓0V,仅仅是我们的定义;
上图只有电压表的差值是真正的电压值。
对于 -3.7V 0V 3.7V,也可以定义为 0V 3.7V 7.4V,
甚至100V 103.7V 107.4V。
取决于系统的哪一处标为GND(Ground)或连接到大地,记为0V。
因此,电压数值的是一个差值,一般是对GND的差值。
如果-3.7V,则表示低于GND处3.7V。
BA-2:电子积木的电压规则:共地

GND表示连接到定义的0V上,所有GND连到一个0V
如果没有共同参考,A认为的2V,B表示不知道是啥。
啥都有可能,状态为:悬空(开路)
因此,为了让整个电子系统以统一参考运行,务必将所有的GND连接在一起,称为:共地

如果失误没有共地,会出现:通信失败、舵机乱动…
新手阶段极其容易忽略这个问题!!!
BA-3:电子积木,启动!

简单来讲,单片机最小系统就是这玩意儿再拆任何一点儿东西就死掉了,真不能拆了。

为了方便开发,把必要的这些内容放在了一块板子上(如下图),称为:最小系统板
我们可以利用这个板子再通过板子上的引脚,使用导线连接到其他模块上实现各种功能。

接下来的内容,我们将主要围绕单片机的这些引脚的功能展开描述。
BA-4-1:GPIO,先读到0和1
简单来讲,这是单片机最最基础的功能,可以读取输入值或者控制输出值。
首先,你可以在代码中设置当前模式为输入模式或者输出模式。
输入模式
输入模式下,可以读取当前引脚是高电平/低电平(高电平[1]一般为单片机的工作电压,多为3.3V或5V,低电平[0]指GND/0V),最常用在按键检测,如图:

但是会发现一个致命问题,按键按下时,引脚为0低电平,但是按键释放时,引脚到按键这段未连接到任何电平,处于悬空!因此读取的值可能是0可也能是1!因此提出上拉电阻、下拉电阻,来解决悬空的问题:

如何加上上拉电阻/下拉电阻?
第一种:直接按照左下角的电路图,在芯片外部连接1kΩ~100kΩ的电阻(常用4.7kΩ 10kΩ)
第二种:现代很多芯片可以内部开启/关闭 上拉/下拉电阻,相当于下方图:

BA-4-2:GPIO,再输出0和1
输出模式
输出模式的本质是:芯片内部有两个开关可以分别控制引脚连接到VCC(一般3.3V或5V)或GND,如下图:

连接到上方VCC电源轨时,引脚输出高电平1
连接到下方GND时,引脚输出低电平0
这种输出方式称为:推挽输出,该输出方式相当于直接连接到电源或地,具备一定的驱动(为元件供电)能力,但是单片机单引脚的最大电流仍有限制,一般最大电流在10mA级。
推挽输出怎么用呢?

例如可通过上方电路实现对LED亮灭的控制:
连接到上方VCC电源轨时,LED的正极为VCC,负极为GND,LED灯亮起;
连接到下方GND时, LED的正极负极均为GND,LED熄灭。
另外还有一种常见的输出方式:开漏输出,只使用一个开关,但是增加一个上拉电阻(也可以芯片内部上拉):

开关未连接时上拉至高电平1。但是上拉电阻1kΩ~100kΩ串接到电路中,导致几乎无法驱动如LED、蜂鸣器等的元件。
开关连接时,输出低电平0。
因此开漏输出只能拉低电平、无法主动输出高电平。
为什么用开漏输出?
实现“线与” 逻辑(多用于通信):多设备输出引脚直接并接在同一总线,任一设备拉低总线即为低电平,所有设备释放才由上拉电阻置高,无输出短路风险。

BA-5:PWM,不只是0和1了,还有0.666
现在的输出模式,引脚只能输出0或1,那么怎么表示0.666这样的值呢?
引入了PWM,通过快速切换0/1,用1所占一个周期的比例表示大小。如下图:


频率较高时,可以近似实现了电压调节,但其本质仍然是快速的脉冲信号。因此可以理解PWM是:用数字信号(只有0/1)模拟出模拟信号(0~1,eg0.666),来传递一个数值信息。
有两个关键性质参数:
频率(1/周期):指一秒钟内信号从高电平到低电平再回到高电平的次数,即一个周期的次数。频率越高,模拟信号越平稳。
可以控制:蜂鸣器音调。
占空比:一个脉冲周期内,高电平时间与整个周期时间的比例。占空比越大,模拟电压越趋近于高电平。
可以控制:LED亮度调整、电机转速控制(需要电机驱动板)、舵机角度控制(下图)。

BA-6:0.666,用TIM产生
单片机怎么输出我想要的0.666脉冲?
首先,大家都能想到的方法是:让程序一直检查时间,达到规定时间输出高电平1/低电平0,但是会有两个问题:
一、PWM可能会到100khz、1Mhz甚至更高,对于CPU的程序去“获取时间、比较时间、更改输出”所耗时间会显著影响PWM精度。
二、程序再反复检查时间的过程中,无法去执行其他主程序任务。
为了解决这个十分棘手的问题,我们在单片机芯片内部设计了外设 (Peripherals)/硬件资源(Hardware),在我们配置好这个外设之后,只要有系统的时钟信号,他就会自己根据配置运行某一特定的功能。我们的主程序可以继续执行其他任务并可以读取外设的状态。
刚刚提到的GPIO(通用输入输出),以及接下来提到的所有内容都是单片机的外设 (Peripherals)/硬件资源(Hardware)

简单来讲,外设像现实中的工具一样,有着对应的特点功能:
例如机械计数器,通过电路的方法实现就是我们的TIM定时器/计数器(Timer/Counter)
关键在于,达到最大值时,会发生进位和溢出。

往TIM配置三个关键设定参数:
预分频值PSC、自动重装值ARR、比较值CCR
分频器可以输入PSC个脉冲再输出一个脉冲。可以降低频率到原来的1/ PSC 。
PSC变小,输出的脉冲频率变大,
PSC变大,输出的脉冲频率变小。
时钟频率→分频器→计数器(TIM)→IO输出

PWM频率 = 主频 ÷ [(PSC+1) × (ARR+1)]
PWM占空比 = CCR ÷ (ARR+1) ×100%
BA-7:ADC,读取0.666,转换为0和1
刚刚我们输出了一个0.666这样的模拟信号,但是实际场景中还需要读取0.666这样的值:

例如在5V单片机中,通过ADC(模数转换器),我们就可以将3.1V的电压值转为2539/4095( 1001 1110 1011 )


有些单片机有ADC_VREF引脚,用于指定ADC的量程/最大值(但是一般仍然不能超过单片机的VCC),如果不接ADC_VREF,使用单片机的VCC作为ADC的最大值。
精度/分辨率(N bit):表示ADC转换结果有2^N个电压台阶,位数越高,可分辨的电压台阶越小,例如上方例子:
12bit的精度的转换结果为2539/4095( 1001 1110 1011 )
8bit的精度的转换结果为158/255( 1001 1110 )
采样率/转换时间:在ADC的内部一般通过逐次逼近的方式得到结果,因此需要一定时间
同时采集多个ADC数值?

值得注意的是,一些单片机的多路 ADC 采用异步采样(一次只能获取一个ADC值)而非同步采样,因此读取两路电压时需要进行通道切换。
受硬件电路特性影响,通道切换后需等待一定时间(如 20μs)再进行 ADC 采集,否则易出现通道间相互干扰,导致采样结果不准确。
BA-8:IRQ,中断一下,请求先看这个
我们按照顺序在讲外设:GPIO、TIM、ADC、I2C、SPI、UART、WDG、DMA,哎!等等打断一下,先看这个!

在主程序正常运行的过程中,通过某个触发条件,暂停主程序,先执行这个触发条件对应的程序,再返回主程序继续。

中断的触发条件可以通过程序设定,有以下类型:

中断的触发的来源是多样的,都可以通过代码进行设定,来实现各种不同的功能:
•外部触发:直接由某个引脚触发,适合用在按键检测、传感器触发; 例如实现:其他程序运行时,如果按键按下则灯亮起。
•计数器TIM触发:当计数器捕获引脚的信号达到该计数器设定的自动重装值ARR时,会清零并产生一次脉冲,该脉冲可触发中断; 例如实现:其他程序运行时,电机每转动一圈(52个脉冲)则灯亮灭翻转一次。
•定时器TIM触发(特殊的计数器):将主频信号(或将主频经过分频的信号),接入计数器,当计数器达到设定的自动重装值ARR时,会清零并产生一次脉冲,该脉冲可触发中断; 例如实现:其他程序运行时,每过500ms则灯亮灭翻转一次。
•UART、DMA、ADC、I2C、SPI硬件触发,用于各种硬件中断的处理。
BA-9:I2C/SPI,少量线,多数据
一大堆01数据,怎么办?

简单来讲,为了满足大量的数据传输需求。引入了通信协议/总线,是指用少量的线按照约定传输大量的数据。其中,I2C只需要两根通信线且支持多个从机,在中低速场景下应用广泛。

通信线:SCL(Clock时钟)\SDA(Data数据),均为开漏输出。
通过I2C一个总线可以和多个设备双向通信。
需要传输大量数据的模块,例如:SSD1306(OLED显示屏)、LCD1602(LCD屏幕)、MPU6050(姿态/加速度传感器)。
另外在单片机上常用的接口是SPI,支持的通信速率更高,但是需要4根信号线。(若只有单向通信则只需要3根)


对于主机:
MOSI (Master Out, Slave In) = TX (Transmit),主机发向从机;
MISO (Master In, Slave Out) = RX (Receive),从机发向主机;
SCK/SCLK/CLK:时钟信号(Clock),由主机发生;
CS/CSn/CE:片选信号(Chip Select// Chip Enable),由主机发生。
如果只需要单向通信,如主机向LCD屏幕发送数据,可以不使用MISO。
对于I2C、SPI,都是串行(一根通信线)同步(有时钟线)通信协议,在使用时,可以直接调用成熟的库函数使用,一般无需使用底层函数。
BA-10:UART,还是太好用了
通信太多线了,我想简单点儿?

I2C、SPI一般用于单片机芯片和各种存储/传感器/执行模块之间的数据读取与输出控制,并且都需要时钟线,配置相对复杂。
如果是单片机对单片机、单片机对电脑、单片机对蓝牙模块的通信,可以使用一种简易的通信协议:UART

只要双方约定好UART的参数,就可以由TX发送到RX接口。因此非常好用。
其实还有 USART ,在原有基础上多了一根时钟线,但在现代设计中,USART被UART取代。
这个好呀,我全用UART行不行?
但是,电脑的接口只有USB接口,并不能直接使用UART通信,这就需要一个中间的转接环节。

USB的协议很复杂,通信只是其中的一部分功能,TTL转USB模块将USB中的通信信息和UART的信息进行协议转换相互转发,同时进行电平转换。
啊?什么是电平?
TTL 用 0V 表示 0、3.3V/5V 表示 1,USB 靠 DP(D+)、DN(D-)两根线的电压差(±3.3V)来表示 0 和 1。
类似的,下载程序也是类似的过程,只不过将UART改为程序下载调试的协议,如SWD(常用,2线)、JTAG(较旧,4~5线)。

BA-11:WDG与DMA,锦上添花
终于,我现在用各种外设设计好了一个可用的设备,我想做的更好,怎么好呢?
一更可靠,二更高效。

可靠是指用WDG(看门狗),顾名思义,一只看门的狗,只不过这狗聪明,见屋里主人挂了就去找医生给主人复活。

本质上,看门狗是一个递减计数器,每次喂狗计数器重置,如果没有喂狗,计数器就会减到0,然后复活主人。
一般看门狗使用独立电路通道、独立内置低速时钟,即使其他核心部分甚至是时钟电路发生致命问题,也可以正常工作。
高效是指用DMA,可以不使用CPU(既不使用主程序,也不使用中断),将RAM与外设设备之间的信息进行复制。
只需要配置好DMA,复制过程将自主完成。


例如可以配置DMA将UART收到的数据实时搬运到某个char数组,将ADC转化后的数值实时更新到某个int变量。主程序直接读取这个char数组/int变量,就是对应外设的实时数据值。
这个DMA怎么这么耳熟?DMA板子?
游戏外挂DMA原理一致,可以绕过电脑CPU(CPU可能运行反作弊程序等),直接读取、修改内存(如血量、位置坐标)。但是显然这是违规操作,还是要适当游戏,热爱生活,享受空气与阳光。
【完】:下一步,跟随教程开始上手实操