STMCU小助手
发布时间:2023-2-23 22:10
|
我当时不理解,觉得领导挑人随心情,不体恤我们这些招聘的锻炼干部的辛苦。过第二天到了济南,老大跟我说:“这些人招聘进来,都是要给你做下属的,如果很难带出来,也是给你自己找麻烦。” 不过现在想来,觉得不无道理。 前期经典文章软硬兼修 % G d9 Z3 E; @- m& x" v 我们通过这篇文章,从芯片内部原理、电路设计、软件开发、软件运行,全流程看一下GPIO初始化、工作的全过程。帮助很多朋友只做MCU硬件的,又很想提升的朋友,进行学习和理解。 【1、熟悉芯片的内部结构和工作原理】 任何开发,我们都应该去看厂家的Datasheet和设计参考先,其他的教材、文档、书籍、网站、博客,都是基于原厂的资料进行编辑的。 所以我们应该下载并仔细阅读下面两个文档:
/ H# {' B9 m, u+ w* t1 z X7 p& R2 ]1 A STM32实物图:
STM32的144个管脚,除了为芯片供电的电源、GND、时钟、复位管脚之外,几乎所有管脚都可以用作GPIO 9 R6 z5 A3 ]1 O+ G& r 2. STM32,LQFP144PIN的引脚分布图:
STM32F103ZET6:共144个引脚,7组IO口,每组16个IO口 7*16=112个IO口(这7组IO口分别为GPIOA,GPIOB…GPIOG)( _" b S* A6 I+ }& L, q例如 GIOA包含PA0,PA1,PA2…PA15,每组16个IO口。从硬件上面,按照:A、B、C、D、E、F、G分为7组。 每组有16个管脚。
- w' l+ M9 ]% V; K9 [$ x 0 ^% n- p! C r5 M5 L" J: P stm32的GPIO的配置模式有好几种,包括: 4种输入模式输入浮空 输入上拉 输入下拉 模拟输入 ) P: g1 w: {9 z2 B0 k8 U 4种输出模式 开漏输出 开漏复用功能% G6 A, }: R# N3 t0 l% C# x 推挽输出 D V6 j9 d! ` 推挽复用功能 可配置3种最大翻转速度 2MHz 10MHz: M8 e0 S+ r+ X6 H 50MHz0 ]" q: b5 |2 q L* \4 g3 |0 x 1、模拟输入; 部分管脚可以用作ADC的输入管脚,需要通过软件进行配置。 当我们把对应的GPIO配置成ADC的功能。 5 y* l W/ B9 v3 Q
则信号接到GPIO的管脚,会被MCU内部集成的ADC进行检测。 . c/ m k2 U3 Z/ g, {) k
从上图我们可以看到,我觉得模拟输入最重要的一点就是,他不经过输入数据寄存器,所以我们无法通过读取输入数据寄存器来获取模拟输入的值,我觉得这一点也是很好理解的,因为输入数据寄存器中存放的不是0就是1,而模拟输入信号不符合这一要求,所以自然不能放进输入数据寄存器。该输入模式,使我们可以获得外部的模拟信号。
如果信号不作为模拟信号输入,可以作为数字信号输入。 数字信号输入时,可以配置上下拉电阻:高阻状态、无上下拉,为浮空输入。 上拉电阻打开,则为上拉输入、如果下拉电阻打开,则为下拉输入。 + {3 C/ ^( F7 @4 T$ c( H+ H
施密特触发器为截止状态/ F) n F% H4 ?0 k7 _ 通过模拟输入通道输入到CPU; C" ^0 y- `# n1 k. P IO口外部电压为模拟量(电压形式非电平形式),作为模拟输入范围一般为0~3.3V 2 p; y: Y+ {9 h" m 2、 浮空输入;
该输入状态,我的理解是,它的输入完全由外部决定,我觉得在数据通信中应该可以使用该模式。应为在数据通信中,我们直观的理解就是线路两端连接着发送端和接收断,他们都需要准确获取对方的信号电平,不需要外界的干预。所以我觉得这种情况适合浮空输入。比如我们熟悉的I2C通信的输入状态。 4 i6 m+ L. }8 \1 l& y
1)外部通过IO口输入电平,外部电平通过上下拉部分(浮空模式下都关闭,既无上拉也无下拉电阻)( p$ K& w4 J5 u 2)传输到施密特触发器(此时施密特触发器为打开状态) 3)继续传输到输入数据寄存器IDR& S8 [9 R7 s# `4 A( C0 C6 v 4)CPU通过读输入数据寄存器IDR实现读取外部输入电平值。在输入浮空模式下可以读取外部输入电平
3、上拉输入; 上拉输入就是在输入电路上使用了上拉电阻。这种模式的好处在于我们什么都不输入时,由于内部上拉电阻的原因,我们的处理器会觉得我们输入了高电平,这就避免了不确定的输入。这在要求输入电平只要高低两种电平的情况下是很有用的。
和输入浮空模式相比较,不同之处在于内部有一个上拉电阻连接到VDD(输入上拉模式下,上拉电阻开关接通,阻值约30-50K) 外部输入通过上拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取。
4、下拉输入; 和上拉输入类似,不过下拉输入时,在外部没有输入时,我们的处理器会觉得我们输入了低电平。
外部输入通过下拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取 7 ?3 {5 H$ ]1 j7 j4 f 5、开漏输出; 开漏输出,输出端相当于三极管的集电极,所以适合与做电流驱动的应用。要得到高电平,需要上拉电阻才可以。(例如模拟软件实现I2C的输出)
2,联通到输出控制电路(也就是ODR的电平)* O9 [/ I( F) }, `: ~ 3,ODR电平通过输出控制电路进入N-MOS管 & Q+ ^9 V/ j+ ? [3 B -ODR输出1: N-MOS截止,IO端口电平不会由ODR输出决定,而由外部上拉/下拉决定。+ i2 s V3 a/ Y& e# ` 在输出状态下,输出的电平可以被读取,数据存入输入数据寄存器,由CPU读取,实现CPU读取输出电平。2 L) S$ ~6 J9 Y# w8 K/ I. ^' b; d' F 所以,当N-MOS截止时,如果读取到输出电平为1,不一定是我们输出的1,有可能是外部上拉产生的1。% ~7 {. H+ Y+ k1 t" c; t* G 0 | j7 N' _2 ]1 q. s& N: s -ODR输出0:! X- N) ~7 C" Q: b9 Q! s* NN-MOS开启,IO端口电平被N-MOS管拉倒VSS,使IO输出低电平。& P' D5 ^+ s7 `8 H 此时输出的低电平同样可以被CPU读取到。 6、 推挽输出; 推挽输出使用了推挽电路,结合推挽电路的特性,它是由两个MOSFET组成,一个导通的同时,另外一个截至,两个MOSFET分别连接高低电平,所以哪一个导通就会输出相应的电平。推挽电路速度快,输出能力强,直接输出高电平或者低电平。 5 L! y7 U$ ], a% h
开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的, C8 d" ]8 J1 ?3 Y1 O6 b9 b) } 开漏推挽输出模式的输出电平是由复用功能外设输出决定的 其他与开漏输出模式相似: 控制电路输出为1:N-MOS截止,IO口电平由外部上拉/下拉决定 控制电路输出为0:N-MOS开启,IO口输出低电平
7、复用功能的开漏输出; 我们使用了某个硬件接口,但是这个接口需要开漏输出,则GPIO处于这个状态。例如我们使用片内外设功能(I2C的SCL,SDA),即我们口头说的硬件I2C,需要输出是个开漏,且使用了MCU的GPIO复用功能。 / L4 s* i- W% r: p3 m& f
与开漏输出相比较: 输出控制寄存器部分相同7 y Z6 J, h# i; D8 l- h 输出驱动器部分加入了P-MOS管部分# W/ P1 Q* I$ v0 L% i" d) h3 }6 W 当输出控制电路输出1时: P-MOS管导通N-MOS管截止,被上拉到高电平,IO口输出为高电平1 当输出控制电路输出0时: P-MOS管截止N-MOS管导通,被下拉到低电平,IO口输出为低电平0; i' }8 E" Q+ ] g/ d$ T 同时IO口输出的电平可以通过输入电路读取
8、 复用推挽输出; 我们使用了某个硬件接口,但是这个接口需要推挽输出,则GPIO处于这个状态。例如我们使用片内外设功能(SPI接口的管脚)。
开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的9 \ r f! _& ~ 开漏推挽输出模式的输出电平是由复用功能外设输出决定的 5 `7 { J$ [3 g" N8 N) Y- ~+ Z 注:推挽输出和开漏输出的区别推挽输出:! [# }$ g4 \* ~3 k2 n 可以输出强高/强低电平,可以连接数字器件, E4 L# N) c( X0 u' T9 O5 ^( A 开漏输出: 只能输出强低电平(高电平需要依靠外部上拉电子拉高),适合做电流型驱动,吸收电流能力较强(20ma之内) ; v7 Z* h8 T( r l4 G6 ?- J 总结一下,在STM32中选用IO模式: (1)模拟输入_AIN ——应用ADC模拟输入 (2)浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1 (3)带上拉输入_IPU——IO内部上拉电阻输入 (4)带下拉输入_IPD—— IO内部下拉电阻输入 (5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能9 K9 D) r/ ^' V7 F$ x- Z (6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的' N. u: c; D4 o3 y, | (7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA) (8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS) 每组GPIO包含系列7个寄存器(7组GPIO共包含7*7=49个寄存器)5 f& B4 N' d0 |3 O" [ 两个32位配置寄存器7 u% N9 I: i) s- P1 Z GPIOx_CRL 低16位8 s9 W D6 N. x9 g GPIOx_CRH 高16位 两个32位数据寄存器 GPIOx_IDR 输入数据寄存器 GPIOx_ODR 输出数据寄存器9 r) U7 o7 C- G 一个32位置位/复位寄存器 GPIOx_BSRR 一个16位复位寄存器 GPIOx_BRR 一个32位锁定寄存器+ l8 J! x4 K0 ~6 F2 B GPIOx_LCKR 六,STM32-IO口相关寄存器讲解 1,端口配置寄存器:, a% o5 y+ I# v% y% x! C STM32每组GPIO位16个IO口,每4位控制一个IO口,所以32位控制8个IO口 分为低16位:GPIOx_CRL和高16位:GPIOx_CRH共32位控制一组GPIO的16个IO口
( g( C4 @, k- L/ l$ ^! D0 W 如图:以端口配置寄存器低16位为例,每四位控制一个IO口(高16位同理); L$ u. K" Z( A8 a7 s8 aMODEx的2位 : 配置IO口输出/输出模式(1种输出+3种不同速度的输出模式)( D& m' d f; B* ~( ^, ] CNFx的2位 : 配置IO口输入/输出状态下(由MODEx控制)的输入/输出模式
6 c) H3 I+ I6 Q1 Z) p 关于上拉/下拉的控制我们将在下面-数据寄存器-中介绍ODR输出寄存器时详细说明$ i! b, P2 @& t& e* U6 b2,数据寄存器(以输入数据寄存器GPIOx_IDR为例)1 Z X8 D* J% ~7 @% S* i 每一组IO口都具有一个GPIOx_IDR的32位寄存器(实际只使用低16位,高16位保留),即16位控制16个IO口,每一位控制一个
这里我们已经了解了输入/输出数据寄存器,现在说下上面提到的问题: 当IO口配置为输入模式且配置为上拉/下拉输入模式(即MODEx=00 CNFx=10时),ODR决定到底是上拉还是下拉2 p( O1 r! S! `$ S& { 1)当输出模式时,ODR为输出数据寄存器* t) ^# g9 L& u' m6 M5 B U: i$ U3 K 2)当输入模式时,ODR用作区分当前位输入模式到底是上拉输入(ODRx=0)还是下拉输入(ODRx=1) / X0 O& `- [. u' w - h& S" Q4 s- ^: z; Q% S 3,端口位设置/清除寄存器(GPIOx_BSRR)
: E6 L: P. O* \ BSRR寄存器作用:9 Q. y! T* o( p0 A9 W; mBSRR寄存器为32位寄存器,低16位BSx为设置为(1设置0不变),高16位BRx为重置位(1:清除0:不变) % {( a9 J' Q, Z. d7 H! z . j% p& z+ i3 I/ s! f0 B# ] 当然,最终的目的还是通过BSRR间接设置ODR寄存器,改变IO口电平 8 B$ M3 |( W4 n) j: ]8 ?" V 6 z2 I6 K- [; B" H , O4 f$ K, G2 ] 4,端口位清除寄存器(GPIOx_BRR)
一般我们使用BSRR低16位和BRR的低16位(STM32F4系列取消了BSRR的高16位) / [1 o! _% s: Y0 u" b! [6 u9 {9 p 5,锁存寄存器:使用较少暂不分析2 {7 R6 _2 _* w0 w1 _ 七,端口的复用和重映射 1,端口的复用:4 C" i& Y5 S0 {0 V 大部分IO口可复用为外部功能引脚,参考芯片数据手册(IO口复用和重映射)
! Y) w7 N* e, L# D$ r- A; v 例如:STM32F103ZET6的PA9和PA10引脚可复用为串口发送和接收功能引脚,也可复用为定时器1的通道2和通道3 端口复用的作用:最大限度的利用端口资源 0 S2 a9 Q7 ~0 L3 C* P* e' I" m! ?2 |; m0 }0 p( ?* v 2,端口的重映射:
端口重映射的作用:方便布线 3,STM32所有的IO口都可作为中断输入(51单片机只有2个端口可以作为外部中断输入)# T( ?# s, a* Q# m/ L; l% } 【3、GPIO软件运行的过程】 首先,我们打开iBox开发板的例程LED_DEMO,点击软件上方的“Start/stop Debug Session”按钮,如下图:
7 R0 I+ r0 m# `, |' b 程序首先从主函数开始。我们点击左上角的step或快捷键F11, ; c( v" v' l3 M
就会发现左边黄色的箭头移动到主函数位置: , @; H% q" A/ M$ ^! F
. \7 y" x* L. j' p0 V+ w9 V 继续F11,我们发现黄色箭头移至下图所示位置:
上面的函数是使能GPIOE端口的时钟的。GPIOE是属于APB2这条高速总线上的,所以用函数RCC_APB2PeriphClockCmd(xxx, xxx) 来启动对应的I/O端口。GPIO只有在时钟上得以启动,我们才能使用它们。 我们再次点击,发现进入了RCC_APB2PeriphClockCmd ( xxx , xxx)函数里面。
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));8 w2 D. _9 }' C1 g, `, d( S, o+ { assert_param(IS_FUNCTIONAL_STATE(NewState));这两行是用来检测形参是否是指定范围内的值。继续F11会发现黄色光标跳到了"RCC->APB2ENR |= RCC_APB2Periph;"。这里就是把RCC_APB2Periph_GPIOE的值赋给RCC_APB2ENR。在此暂时不对此函数做详细介绍。 & P2 {' S' N4 ]2 G9 t$ N9 W 接下来的四次单步运行,我们会发现程序跳回了主函数。黄色箭头会依次在下图中四行代码中移动。
我们逐行分析:
第一行的作用是选择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 上拉输入
第二行的作用是选择IO端口号。此处我们选择了GPIOE的9、10、12口。GPIO_Pin_x其x的值可以是0~15,如果我们同时选择多个IO口,可以用上图方式用“|”隔开,也可以用GPIO_Pin_All来选择GPIOE组下的所有端口。
第三行的作用是设置IO端口的速度。端口的速度只有在IO端口被设置为输出的时候才需要设置。如果为输入模式则不需要设置。端口速度可以2/10/50MHz。上面我们设置IO端口工作方式为推挽输出,所以此处需要设置IO端口的速度。
第四行的作用是运行GPIO的初始化库函数。把上面三条的设置内容写入到IO端口对应的寄存器当中。其中第一个参数是我们要写入的哪一组端口。第二个参数就是对这一组端口的设置内容,也就是我们刚才设置的结构体内容。最终完成三个端口的初始化。 , u& a+ T6 ^- K2 S2 n 再次单步执行,我们发现此程序跳到了GPIO_Init()函数。
此函数后续我们会详细介绍,此处暂不做介绍。 , h( z( e9 I% D- Q0 J6 b, A# n2 } 直接step out(Ctrl+F11)跳到下一步。我们发现程序回到了主函数如下图所示位置。 ; ]- z8 k5 O4 C* [
此处用到了函数GPIO_SetBits。此函数中只有两个参数:第一个参数是选择GPIO的组(此处我们选择了GPIOE);第二个参数是选择GPIO的Pin(此处我们选择了9、10、12)。此函数是专门用来将IO口置位(输出高电平)。下图代码将GPIOE组下的9、10、12号端口设为高电平。函数执行至此,我们可以观察到iBox上的三个指示灯全部亮了起来。 与GPIO_SetBits对应的函数为GPIO_ResetBits。此函数的功能为清楚指定数据端口位,也就是用来将IO口清零的函数。使用方法与GPIO_SetBits相同。操作IO口的方法还有很多种,后面我们会逐步介绍。 继续单步运行程序,发现黄色箭头跳转到下图所示位置: ( K- g" z6 |/ X) F
% A4 u/ u8 D: d6 M4 j6 m B" q5 L GPIOx->BSRR是端口位设置/清除寄存器。其作用为将端口位设置或清除。 继续单步运行,程序再次跳回主函数如下图所示的位置:
其中“for(; ;)”可以理解为“while(1)”。相比之下for式死循环更加高效一些。 即不设初值,不判断条件,循环变量不增值,无终止的循环,程序会一直执行大括号里面的内容。 上图大括号里面的第一行用到了GPIO_SetBits,后面的两个参数分别为GPIO_LED_PORT和GPIO_LED_ALL我们右键选择“Go To Definition Of 'GPIO_LED_PORT',就会跳转到下图所示的位置: - T; P, F) C6 `
不难看出,这里定义了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闪灯的效果。 转载自:硬十 0 \7 ]4 `: N1 p5 ]$ y) P; z |
【STM32H735G-DK 测评】STM32H735G-DK External Loder 编译和测试
Keil-MDK 在线调试设置断点的高级用法
请教下STM32N6570 怎么把分辨率改成320x240并可以全屏显示
【福利三:逢7发帖赢大礼】想要充分利用STM32H743VI片内的RAM空间(但并不顺利)
基于stm32平台上的IC-MU磁绝对值编码器SPI通讯和码盘数据处理
使用STM32 MCU和MPU连接PDM数字麦克风
分享STM32资料-采用 STM32U031R8 MCU 的 STM32 Nucleo-64 开发板,官方资料,数据手册等资料
HRTIM 指南
使用 STM32 的 DWT 单元监控内存
DAC,COMP,HRTIM Fault 功能的使用
微信公众号
手机版