你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32从原理图PCB到移植RTOS之GPIO

[复制链接]
STMCU小助手 发布时间:2023-1-16 16:00
关于为什么我们软硬件都要懂?2010年,我在华为时,暂时脱产去参与招聘工作;为了为本部门招聘更多的人,争夺HC号(招聘入职的名额),所以非常卖力的希望每个来面试的,符合硬杠杠的面试者都能通过层层面试。
7 X1 b9 W  p9 ~) _5 `7 Q& ?9 @
因为华为招聘的时候,低级别招聘时有很多硬性条件,比如:学校要满足要求、不能频繁跳槽、不能有留级、复读;要过技术面试、集体面试、心理测评,最终送给终面官做终面;然后送人力资源审核,总部审核。
我们招聘锻炼干部,为了完成招聘任务,都是技面放水、集面指导、心理测评暗示;拼命把 学校满足要求的人送到终面。

5 P: n: ?: T7 w8 ]
有次在青岛的时候,当天通过面试的人很少。终于逮到一个学校勉强满足要求的面试者,技术有点烂。我们一路放水。最终把他送到部门老大那里做终面。
结果,没过一会部门老大出来跟我说:“让他走……”。
我把哪位来面试的小伙子送走了之后,问老大:“刚刚那个硬性条件都满足要求哎,为啥不要啊?”

1 J1 M2 g5 ?- a% P4 Q6 |8 W
老大叼根烟,跟我说:“一个搞单片机的,搞三年了的,只会画电路,不会写代码。。。一是学习能力弱、二是不好学、三是现有技能基础太弱。我们招聘过来虽然是来也只是画电路的,但是电路复杂度比简单的单片机系统复杂得多。一个只能画MCU电路的,都不知道学习代码,得弱到什么程度。不要不要!”
+ b' T2 B- V, V# I$ m3 Q- `
我当时不理解,觉得领导挑人随心情,不体恤我们这些招聘的锻炼干部的辛苦。过第二天到了济南,老大跟我说:“这些人招聘进来,都是要给你做下属的,如果很难带出来,也是给你自己找麻烦。”
不过现在想来,觉得不无道理。
) V# u6 J3 S4 k
我们通过这篇文章,从芯片内部原理、电路设计、软件开发、软件运行,全流程看一下GPIO初始化、工作的全过程。帮助很多朋友只做MCU硬件的,又很想提升的朋友,进行学习和理解。

8 Z& j: b1 c7 Y* J+ F
【1、熟悉芯片的内部结构和工作原理】
任何开发,我们都应该去看厂家的Datasheet和设计参考先,其他的教材、文档、书籍、网站、博客,都是基于原厂的资料进行编辑的。
3 R+ k- A) q0 P) S, J; w2 J
STM32实物图:
   微信图片_20230115165702.jpg

. n, ~7 Y0 X* B' [3 g
STM32的144个管脚,除了为芯片供电的电源、GND、时钟、复位管脚之外,几乎所有管脚都可以用作GPIO

( ~3 Q8 G, X8 G0 [
 2. STM32,LQFP144PIN的引脚分布图:
微信图片_20230115165659.jpg

' [2 @) {8 E0 B6 K& m" V
- O$ i' S9 X/ R; }
STM32F103ZET6:共144个引脚,7组IO口,每组16个IO口( ?9 n. h. P" W5 A
7*16=112个IO口(这7组IO口分别为GPIOA,GPIOB…GPIOG)例如GIOA包含PA0,PA1,PA2…PA15,每组16个IO口。
从硬件上面,按照:A、B、C、D、E、F、G分为7组。* z7 _& v. }( J
每组有16个管脚。

  F5 w" h# T+ h1 I0 M$ t0 ~
IO口的基本结构和工作方式
+ ?# \* X( ^- L7 |! V2 v
微信图片_20230115165656.jpg

, |% i) t0 A9 P2 z, y9 \
stm32的GPIO的配置模式有好几种,包括:
 4种输入模式   输入浮空   输入上拉   输入下拉   模拟输入 4种输出模式   开漏输出   开漏复用功能   推挽输出   推挽复用功能
2 a  Z) b* g! |, z" q: w
 可配置3种最大翻转速度   2MHz   10MHz   50MHz
) m, d. V7 u) ~: f

; r: y) E( U# V' ]. b# q9 }

1、模拟输入;


8 B5 T" M! m3 U0 ^$ ^5 g

部分管脚可以用作ADC的输入管脚,需要通过软件进行配置。

当我们把对应的GPIO配置成ADC的功能。


9 T% d; J! y: t% }5 ^6 o( _: O8 F3 w

微信图片_20230115165652.jpg


3 |1 C# S: p; J* F, A) T. a! ^/ G4 D3 W) f6 C

则信号接到GPIO的管脚,会被MCU内部集成的ADC进行检测。


* e1 r* Y7 @+ m

微信图片_20230115165648.jpg


: r1 f5 @4 F' N

从上图我们可以看到,我觉得模拟输入最重要的一点就是,他不经过输入数据寄存器,所以我们无法通过读取输入数据寄存器来获取模拟输入的值,我觉得这一点也是很好理解的,因为输入数据寄存器中存放的不是0就是1,而模拟输入信号不符合这一要求,所以自然不能放进输入数据寄存器。该输入模式,使我们可以获得外部的模拟信号。

微信图片_20230115165644.jpg

" g9 I3 K4 M: _  D
* Y0 ~$ ~: G% \/ u  F: T

如果信号不作为模拟信号输入,可以作为数字信号输入。

数字信号输入时,可以配置上下拉电阻:高阻状态、无上下拉,为浮空输入。

上拉电阻打开,则为上拉输入、如果下拉电阻打开,则为下拉输入。

  
微信图片_20230115165636.jpg

$ _0 k0 o8 m9 }3 {8 \2 b
上拉和下拉部分均为关闭状态(AD转换-模拟量转换为数字量)施密特触发器为截止状态通过模拟输入通道输入到CPUIO口外部电压为模拟量(电压形式非电平形式),作为模拟输入范围一般为0~3.3V

1 ^& w& a- Q( y" [9 X7 O# c2 R

2、 浮空输入;

& x8 T3 p8 |" J; k
微信图片_20230115165633.jpg
, t2 @$ h7 {. Z( s

该输入状态,我的理解是,它的输入完全由外部决定,我觉得在数据通信中应该可以使用该模式。应为在数据通信中,我们直观的理解就是线路两端连接着发送端和接收断,他们都需要准确获取对方的信号电平,不需要外界的干预。所以我觉得这种情况适合浮空输入。比如我们熟悉的I2C通信的输入状态。


9 r, |$ {" V  e1 v+ L, L+ e2 C' M% E
微信图片_20230115165629.jpg

7 z* h  c! W+ ?, n1)外部通过IO口输入电平,外部电平通过上下拉部分(浮空模式下都关闭,既无上拉也无下拉电阻)2)传输到施密特触发器(此时施密特触发器为打开状态)3)继续传输到输入数据寄存器IDR4)CPU通过读输入数据寄存器IDR实现读取外部输入电平值。在输入浮空模式下可以读取外部输入电平


0 G& A9 Z. }; K

3、上拉输入;

上拉输入就是在输入电路上使用了上拉电阻。这种模式的好处在于我们什么都不输入时,由于内部上拉电阻的原因,我们的处理器会觉得我们输入了高电平,这就避免了不确定的输入。这在要求输入电平只要高低两种电平的情况下是很有用的。


3 I! X% d% T, D' ?8 G: I0 c. M+ k& _
微信图片_20230115165605.jpg

8 Q+ ?1 S5 ?/ _! C/ a& X
微信图片_20230115165601.jpg

+ m7 _3 ?, x9 m$ M6 M和输入浮空模式相比较,不同之处在于内部有一个上拉电阻连接到VDD(输入上拉模式下,上拉电阻开关接通,阻值约30-50K)外部输入通过上拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取。

4 [4 B% O" w3 F& w$ `/ t

4、下拉输入;

和上拉输入类似,不过下拉输入时,在外部没有输入时,我们的处理器会觉得我们输入了低电平。

3 T  ~6 A" E6 q$ y5 G4 }  D- m& J
微信图片_20230115165557.jpg

3 H5 i. T+ n  q9 Z7 n& |
微信图片_20230115165555.jpg

9 `% J' M) {7 f+ R7 M1 e! ~/ d" Y4 F
        和输入浮空模式相比较,不同之处在于内部有一个下拉电阻连接到VSS(输入下拉模式下,下拉电阻开关接通,阻值约30-50K)        外部输入通过下拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取


" q1 h  Y# ?# V

5、开漏输出;

开漏输出,输出端相当于三极管的集电极,所以适合与做电流驱动的应用。要得到高电平,需要上拉电阻才可以。(例如模拟软件实现I2C的输出)

& Q3 t# y: o2 i
微信图片_20230115165552.jpg
0 U+ ~5 O5 e; m+ g
微信图片_20230115165548.jpg
  
1,CPU写入位设置/清楚寄存器BSRR,映射到输出数据寄存器ODR2,联通到输出控制电路(也就是ODR的电平)9 z! a8 v& e& w" D  u0 U! c3 L* U) T
; Y- ~6 S4 \6 P( J7 }
3,ODR电平通过输出控制电路进入N-MOS管  -ODR输出1:    N-MOS截止,IO端口电平不会由ODR输出决定,而由外部上拉/下拉决定。  在输出状态下,输出的电平可以被读取,数据存入输入数据寄存器,由CPU读取,实现CPU读取输出电平。  所以,当N-MOS截止时,如果读取到输出电平为1,不一定是我们输出的1,有可能是外部上拉产生的1。
% [8 [# q, O! M& F) a" L8 U5 K
  -ODR输出0:   N-MOS开启,IO端口电平被N-MOS管拉倒VSS,使IO输出低电平。   此时输出的低电平同样可以被CPU读取到。
3 |; \: T. x4 R6 R& G

6、 推挽输出;

推挽输出使用了推挽电路,结合推挽电路的特性,它是由两个MOSFET组成,一个导通的同时,另外一个截至,两个MOSFET分别连接高低电平,所以哪一个导通就会输出相应的电平。推挽电路速度快,输出能力强,直接输出高电平或者低电平。

- f! O2 R1 K) I/ n" u
微信图片_20230115165545.jpg
- Q% c, u7 X: t) T: ~+ ^! D. f. C* p
   与开漏输出模式唯一的区别在于输出控制电路之前电平的来源    开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的    开漏推挽输出模式的输出电平是由复用功能外设输出决定的   其他与开漏输出模式相似:    控制电路输出为1:N-MOS截止,IO口电平由外部上拉/下拉决定    控制电路输出为0:N-MOS开启,IO口输出低电平

6 o, U- k6 V5 p

7、复用功能的开漏输出;

我们使用了某个硬件接口,但是这个接口需要开漏输出,则GPIO处于这个状态。例如我们使用片内外设功能(I2C的SCL,SDA),即我们口头说的硬件I2C,需要输出是个开漏,且使用了MCU的GPIO复用功能。

+ t. o/ u/ z* [% f6 \6 G
微信图片_20230115165540.jpg

( G% h( p! o  g$ Q) v0 A* \5 L   与开漏输出相比较:    输出控制寄存器部分相同    输出驱动器部分加入了P-MOS管部分   当输出控制电路输出1时:    P-MOS管导通N-MOS管截止,被上拉到高电平,IO口输出为高电平1   当输出控制电路输出0时:    P-MOS管截止N-MOS管导通,被下拉到低电平,IO口输出为低电平0   同时IO口输出的电平可以通过输入电路读取


* z( M2 X: F: Z" N7 P$ P

8、 复用推挽输出;

我们使用了某个硬件接口,但是这个接口需要推挽输出,则GPIO处于这个状态。例如我们使用片内外设功能(SPI接口的管脚)。

  
微信图片_20230115165535.jpg
/ ~, P2 a: ^2 D- a1 p

  s9 T4 x* [1 g/ l* d
  与推挽输出模式唯一的区别在于输出控制电路之前电平的来源   开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的   开漏推挽输出模式的输出电平是由复用功能外设输出决定的
5 E+ T" H- q8 i1 Q
注:推挽输出和开漏输出的区别   推挽输出:    可以输出强高/强低电平,可以连接数字器件   开漏输出:    只能输出强低电平(高电平需要依靠外部上拉电子拉高),适合做电流型驱动,吸收电流能力较强(20ma之内)

' K' @! s7 t5 g0 T1 b/ P0 v1 |3 B
总结一下,在STM32中选用IO模式:
(1)模拟输入_AIN ——应用ADC模拟输入
(2)浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1' Q0 }" Z1 O' C) r+ h) U
(3)带上拉输入_IPU——IO内部上拉电阻输入
- `# S* w7 W: D1 j( y' B(4)带下拉输入_IPD—— IO内部下拉电阻输入* z  l1 }: @. e4 l" c# R
(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能
; I2 a; U9 P* \9 \(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的) `- T& A# }  n! x4 g' _
(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)8 t- i5 U/ a! ]9 ^0 M* `5 h* z
(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)

8 x8 C7 v- M1 i
【2、STM32-IO口相关寄存器】 每组GPIO包含系列7个寄存器(7组GPIO共包含7*7=49个寄存器) 两个32位配置寄存器  GPIOx_CRL 低16位  GPIOx_CRH 高16位 两个32位数据寄存器  GPIOx_IDR 输入数据寄存器  GPIOx_ODR 输出数据寄存器 一个32位置位/复位寄存器  GPIOx_BSRR 一个16位复位寄存器  GPIOx_BRR 一个32位锁定寄存器  GPIOx_LCKR 六,STM32-IO口相关寄存器讲解 1,端口配置寄存器:  STM32每组GPIO位16个IO口,每4位控制一个IO口,所以32位控制8个IO口  分为低16位:GPIOx_CRL和高16位:GPIOx_CRH共32位控制一组GPIO的16个IO口
  
微信图片_20230115165530.jpg

2 i- l  f$ n) k! ]6 f, _% C& D
  如图:以端口配置寄存器低16位为例,每四位控制一个IO口(高16位同理)  MODEx的2位 : 配置IO口输出/输出模式(1种输出+3种不同速度的输出模式)  CNFx的2位 : 配置IO口输入/输出状态下(由MODEx控制)的输入/输出模式 
以GPIOA_CRL为例,配置IO口PA0 -> MODE0=00(输入模式) CNF0=10(上拉/下拉输入模式) + j, Z- [, ]3 a# e' |( U4 w% r9 q
此种配置下到底是上拉还是下拉输入模式还需由ODR寄存器决定
 
   微信图片_20230115165527.jpg

$ S. _$ q  w; H  w
  关于上拉/下拉的控制我们将在下面-数据寄存器-中介绍ODR输出寄存器时详细说明  2,数据寄存器(以输入数据寄存器GPIOx_IDR为例)  每一组IO口都具有一个GPIOx_IDR的32位寄存器(实际只使用低16位,高16位保留),即16位控制16个IO口,每一位控制一个
  
微信图片_20230115165524.jpg
: p& R# \- F8 F
  如图:IDR寄存器共32位,0~15位代表一组IO口16个IO当前值    这里我们已经了解了输入/输出数据寄存器,现在说下上面提到的问题:  当IO口配置为输入模式且配置为上拉/下拉输入模式(即MODEx=00 CNFx=10时),ODR决定到底是上拉还是下拉   1)当输出模式时,ODR为输出数据寄存器   2)当输入模式时,ODR用作区分当前位输入模式到底是上拉输入(ODRx=0)还是下拉输入(ODRx=1)  3,端口位设置/清除寄存器(GPIOx_BSRR)
  
微信图片_20230115165521.jpg

% Y# w8 I; f2 m: {9 W; g( W
  BSRR寄存器作用:   BSRR寄存器为32位寄存器,低16位BSx为设置为(1设置0不变),高16位BRx为重置位(1:清除0:不变)   当然,最终的目的还是通过BSRR间接设置ODR寄存器,改变IO口电平  4,端口位清除寄存器(GPIOx_BRR)
  
微信图片_20230115165516.jpg
7 F. @. \4 t. K$ Y9 k1 m2 d, E
  GPIOx_BRR寄存器作用同GPIOx_BSRR寄存器高16位  一般我们使用BSRR低16位和BRR的低16位(STM32F4系列取消了BSRR的高16位)  5,锁存寄存器:使用较少暂不分析 七,端口的复用和重映射 1,端口的复用:  大部分IO口可复用为外部功能引脚,参考芯片数据手册(IO口复用和重映射)
  
微信图片_20230115165512.jpg
% D! Z3 R7 I( v
  例如:STM32F103ZET6的PA9和PA10引脚可复用为串口发送和接收功能引脚,也可复用为定时器1的通道2和通道3  端口复用的作用:最大限度的利用端口资源  2,端口的重映射:
  
微信图片_20230115165509.jpg

( E- ^$ I  X- C+ }1 A! P
  串口1默认引脚是PA9,PA10可以通过配置重映射映射到PB6,PB7  端口重映射的作用:方便布线  3,STM32所有的IO口都可作为中断输入(51单片机只有2个端口可以作为外部中断输入)4 x' U4 p2 V6 d. f1 t
9 J4 C* p$ t% ?$ @
【3、GPIO软件运行的过程】
       首先,我们打开iBox开发板的例程LED_DEMO,点击软件上方的“Start/stop Debug Session”按钮,如下图:
% M  ?$ o2 q  k& ^# f0 ~
微信图片_20230115165506.png

2 g# B1 K, z6 i, b' |. {8 R, {
程序首先从主函数开始。我们点击左上角的step或快捷键F11,
0 b9 P+ T9 w3 ?" n, D- N
微信图片_20230115165502.png
; P) c! S% F' ^/ q
就会发现左边黄色的箭头移动到主函数位置:
- q. ?( d3 Z0 a) x9 I
微信图片_20230115165459.jpg
& _+ |# |. P# ^/ [
继续F11,我们发现黄色箭头移至下图所示位置:

3 ?( F6 d9 J5 F2 U$ t
微信图片_20230115165456.png
. O& ~2 ~0 Q* k" O9 }7 M5 p
上面的函数是使能GPIOE端口的时钟的。GPIOE是属于APB2这条高速总线上的,所以用函数RCC_APB2PeriphClockCmd(xxx, xxx) 来启动对应的I/O端口。GPIO只有在时钟上得以启动,我们才能使用它们。

+ L1 m4 y* T: b+ ]$ N
我们再次点击,发现进入了RCC_APB2PeriphClockCmd ( xxx , xxx)函数里面。

1 K0 @! Q! r" s9 P/ F) r
微信图片_20230115165451.jpg

  u" V" Z+ Q) b- |
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
* G; @! c% k5 y5 D5 {  f
assert_param(IS_FUNCTIONAL_STATE(NewState));这两行是用来检测形参是否是指定范围内的值。继续F11会发现黄色光标跳到了"RCC->APB2ENR |= RCC_APB2Periph;"。这里就是把RCC_APB2Periph_GPIOE的值赋给RCC_APB2ENR。在此暂时不对此函数做详细介绍。
9 H' i9 m- M& k8 E
接下来的四次单步运行,我们会发现程序跳回了主函数。黄色箭头会依次在下图中四行代码中移动。

; p% O- O# G) P7 J! D  j
微信图片_20230115165447.png

/ p# W) U  O& C3 a) t: Q
我们逐行分析:
微信图片_20230115165445.png

  T4 ^4 [! X4 \/ J$ P- y" G
第一行的作用是选择IO端口的工作方式。由于驱动LED需要较大电流,所以此处采用推挽输出的方式。如果想更改为其他方式,只需将上图等号后面的” GPIO_Mode_Out_PP”改为其他工作方式即可。
GPIO_Mode_Out_PP 推挽输出
GPIO_Mode_Out_OD 开漏输出
GPIO_Mode_AF_PP 复用推挽输出
GPIO_Mode_AF_OD 复用开漏输出
GPIO_Mode_AIN 模拟输入
GPIO_Mode_IN_FLOATING 浮空输入
GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入
( P- C6 V5 g& L3 T5 w: C
微信图片_20230115165440.png
- Y3 S- Y! T/ @/ l1 V, K: C4 N6 p
第二行的作用是选择IO端口号。此处我们选择了GPIOE的9、10、12口。GPIO_Pin_x其x的值可以是0~15,如果我们同时选择多个IO口,可以用上图方式用“|”隔开,也可以用GPIO_Pin_All来选择GPIOE组下的所有端口。

" _  G4 b! W' p0 U& Z# q: j
微信图片_20230115165436.png

) p" I/ G* i  z: B0 C
第三行的作用是设置IO端口的速度。端口的速度只有在IO端口被设置为输出的时候才需要设置。如果为输入模式则不需要设置。端口速度可以2/10/50MHz。上面我们设置IO端口工作方式为推挽输出,所以此处需要设置IO端口的速度。
. q' O) W  r% r  w
微信图片_20230115165433.png

0 K3 ~( O, F" ~. I3 u5 L7 f0 G
第四行的作用是运行GPIO的初始化库函数。把上面三条的设置内容写入到IO端口对应的寄存器当中。其中第一个参数是我们要写入的哪一组端口。第二个参数就是对这一组端口的设置内容,也就是我们刚才设置的结构体内容。最终完成三个端口的初始化。
, `7 H- b- N6 Z! `
再次单步执行,我们发现此程序跳到了GPIO_Init()函数。

; N- |  S  i7 z  [) c# t) i
微信图片_20230115165429.png

4 }6 b% ]: x( T, x% C7 L: H: V
此函数后续我们会详细介绍,此处暂不做介绍。

# B: S% J0 ^6 v  A
直接step out(Ctrl+F11)跳到下一步。我们发现程序回到了主函数如下图所示位置。
9 }  a5 k5 X/ a/ z5 m0 |
微信图片_20230115165426.png
3 X& \" G% H4 [3 b+ l& N. w: z
此处用到了函数GPIO_SetBits。此函数中只有两个参数:第一个参数是选择GPIO的组(此处我们选择了GPIOE);第二个参数是选择GPIO的Pin(此处我们选择了9、10、12)。此函数是专门用来将IO口置位(输出高电平)。下图代码将GPIOE组下的9、10、12号端口设为高电平。函数执行至此,我们可以观察到iBox上的三个指示灯全部亮了起来。
与GPIO_SetBits对应的函数为GPIO_ResetBits。此函数的功能为清楚指定数据端口位,也就是用来将IO口清零的函数。使用方法与GPIO_SetBits相同。操作IO口的方法还有很多种,后面我们会逐步介绍。

) }* v$ P+ n0 S" S* ]! O. Y, x& f
继续单步运行程序,发现黄色箭头跳转到下图所示位置:
& v9 W. N) d. t7 b% a
微信图片_20230115165422.png
! a' c8 }# Y! C6 v' v
GPIOx->BSRR是端口位设置/清除寄存器。其作用为将端口位设置或清除。

6 a/ @/ N7 _0 d, D( ^
继续单步运行,程序再次跳回主函数如下图所示的位置:

; ^, G" V; F/ f) T& m& r: m
微信图片_20230115165417.png

( A5 P8 t0 [7 }$ Y* {- b
其中“for(; ;)”可以理解为“while(1)”。相比之下for式死循环更加高效一些。
即不设初值,不判断条件,循环变量不增值,无终止的循环,程序会一直执行大括号里面的内容。
    上图大括号里面的第一行用到了GPIO_SetBits,后面的两个参数分别为GPIO_LED_PORT和GPIO_LED_ALL我们右键选择“Go To Definition Of 'GPIO_LED_PORT',就会跳转到下图所示的位置:

! O2 k6 P; \+ h% J9 Z! Z
微信图片_20230115165414.png

9 l) Q7 {3 Z  W, p+ w3 n% D
不难看出,这里定义了GPIOE为GPIO_LED_PORT;定义了GPIO_Pin_9、GPIO_Pin_10、GPIO_Pin_11、GPIO_Pin_12为GPIO_LED_ALL。
这样一来,我们就很容易理解这四条语句的含义了:首先用GPIO_SetBits将GPIOE组下的9、10、11、12号端口置高,延时一段时间后,用GPIO_ResetBits将GPIOE组下的9、10、11、12号端口清零,再延时一段时间,由于是在for(;;)中执行,程序会一直在这里循环,这样就实现了LED闪灯的效果。

1 F: T; c# G; T3 a& i0 Z' K+ r
转载自:硬十
7 S5 W1 X6 l" ~& z" t% l
收藏 评论1 发布时间:2023-1-16 16:00

举报

1个回答
晒太阳的懒猫 回答时间:2023-1-16 21:00:11
图文并茂,容易理解,讲得也很好,谢谢!
: L7 K( e9 j. b

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版