|
本帖最后由 zhdzhd-174422 于 2018-12-31 21:24 编辑 # ~2 o4 `# `) Q 7 G! U; M4 U1 @$ }7 _. F 第8章 使用寄存器点亮LED灯 本章实验点亮2组GPIO端口中的2个LED灯。 / }" h( ~) j3 h4 D) S9 Q: S
2 q" A% q6 O- ^/ ]0 {; S9 u. ?+ \ STM32F103ZET6 - 一共有144脚 - 一共有7组IO口 - 每组IO口有16个IO - 一共16X7=112个IO,分别是GPIOA,GPIOB...GPIOG。 1 ~1 w. l' e. [/ { e) k5 t/ B* {( t
, M0 N4 U- p* ? GPIO 有8 种工作模式,分为4种输入模式和4种输出模式,用代码表示如下:6 b( j+ m# e, P -----------------------------------------------------------------------------------------------------& @! j# H3 J/ }2 B' B$ Z typedef enum { GPIO_Mode_AIN = 0x0, // 模拟输入 GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 GPIO_Mode_IPD = 0x28, // 下拉输入$ Z5 Z0 b: d3 F6 y GPIO_Mode_IPU = 0x48, // 上拉输入 GPIO_Mode_Out_OD = 0x14, // 开漏输出 GPIO_Mode_Out_PP = 0x10, // 推挽输出 GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 GPIO_Mode_AF_PP = 0x18 // 复用推挽输出8 F+ o5 B$ m% z5 Z4 i, m } GPIOMode_TypeDef;% }- a5 P9 ^4 u8 N) b8 |9 t# | S \9 O c) I0 N( r9 m3 P9 _ ---------------------------------------------------------------------------------------------------- 2 n1 r7 y- u) B 有3种最高切换频率: -2MHZ+ c. v0 K4 z; J. n -10MHz3 |) i4 B# K* y& _9 o. H -50MHz 4 N& h. f9 W9 v# }' | 每组IO口包含7个寄存器,可以控制GPIO的16个IO口。* {8 J. a4 X+ C1 b$ y- F1 w / i2 L# g6 _6 i8 Y( J7 f - GPIOx_CRL 端口配置低寄存器/ R2 [3 p, B1 Y/ r: Z - GPIOx_CRH 端口配置高寄存器* E$ c9 ~$ S, g, {4 ~- u* i - GPIOx_IDR 端口输入寄存器8 H7 `) h9 F# t7 a! @1 j6 T - GPIOx_ODR 端口输出寄存器 - GPIOx_BSRR 端口位设置/清除寄存器 - GPIOx_BRR 端口位清除寄存器 - GPIOx_LCKR 端口配置锁存寄存器 / [. A* x0 x" x7 ]1 J 用C语言把上面的寄存器地址转换成指针代码如下:(x代表A---G) ---------------------------------------------------------------------------------------------------------------------- #define GPIOx_CRL *(unsigned int*)(GPIOB_BASE+0x00)) p5 j9 }) A9 h( w/ @: S9 h #define GPIOx_CRH *(unsigned int*)(GPIOB_BASE+0x04) #define GPIOx_IDR *(unsigned int*)(GPIOB_BASE+0x08)0 w$ B7 g \# p: G- ? i #define GPIOx_ODR *(unsigned int*)(GPIOB_BASE+0x0C) #define GPIOx_BSRR *(unsigned int*)(GPIOB_BASE+0x10). n1 W" A! E' J0 M$ k z% C! f1 ] #define GPIOx_BRR *(unsigned int*)(GPIOB_BASE+0x14) #define GPIOx_LCKR *(unsigned int*)(GPIOB_BASE+0x18) ---------------------------------------------------------------------------------------------------------------------- GPIOB_BASE从前面一章的学习我们知道是GPIO的外设基地址,而GPIO都是挂载到APB2总线上的,故各端口的基地址代码如下: ---------------------------------------------------------------------------------------------------------------------- #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00); }& e) O7 K6 ~6 w3 M" i: ? #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)6 E7 O+ W9 _8 r #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400) #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800) #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00) #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000) ----------------------------------------------------------------------------------------------------------------------8 O" s: A+ E e) n, p; F& w$ w 我用的是原子的精英板,板载有2个LED,分别是LED0和LED1,对应芯片的PB5和PE5脚。
所有的 GPIO都挂载到 APB2 总线上,具体的时钟由 APB2外设时钟使能寄存器(RCC_APB2ENR)来控制,见下图:
+ p% Q5 b' }/ B2 s1 n. g @' r 3 ]7 }" E' R7 S: ^2 a5 x% f 开启时钟端口的代码如下: RCC_APB2ENR |= (1<<3); //开启GPIOB端口的时钟 # ~- v+ t" m/ n" R; Y, E& d J- Y RCC_APB2ENR |= (1<<6); //开启GPIOB端口的时钟 5 {$ v6 G. R/ d7 D `* r 在输出模式时,对端口位设置/清除寄存器 BSRR 寄存器、端口位清除寄存器 BRR 和ODR 寄存器写入参数即可控制引脚的电平状态,其中操作 BSRR 和 BRR 最终影响的都是ODR 寄存器,然后再通过 ODR寄存器的输出来控制 GPIO,直接操作 ODR寄存器来控制 GPIO 的电平。代码如下: : y F1 S) w. |$ V/ R" B GPIOB_ODR &= ~(1<<0); GPIOE_ODR &= ~(1<<0);* M# b2 q. V: C2 w8 z3 W" ?" P9 J4 J
r" H) J) n5 D3 a* b! o 通过上面 配置引脚模式,开启时钟,控制引脚电平这三步,我们实现了控制2个 LED 的基本步骤和代码,另外有个需要注意的事项就是:" z [* t) B) Z$ l8 t7 h , J& W# F. e5 l# C# f) d l 我们在 main中添加如下函数: ----------------------------------------------------------------------------------------------------------------------$ c" f0 `) u' W7 U& v // 函数为空,目的是为了骗过编译器不报错. Q3 e4 W9 |% M ?- W! D) F void SystemInit(void) { } * U* N& w4 J" \6 w8 Z% A- U ----------------------------------------------------------------------------------------------------------------------" ~% d( U( A7 Z$ w 如果不添加上面的函数,编译时会出现如下错误:' u1 ^: d( ?" L' o “Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x.o)”% `$ C: Z+ K$ m4 D* n 错误提示 SystemInit 没有定义。 P0 A0 ~- ?" w3 v 从分析启动文件时我们知道,Reset_Handler 调用了该函数用来初始化 SMT32 系统时钟,为了简单起见,我们在 main 文件里面定义一个SystemInit 空函数,什么也不做,为的是骗过编译器,把这个错误去掉。关于配置系统时钟我们在后面再写。当我们不配置系统时钟时,STM32 会把 HSI 当作系统时钟,HSI=8M,由芯片内部的振荡器提供。这时再编译就没有错了,完美解决。4 X" [* @5 u t& M 还有一个方法就是在启动文件statrup_stm32f10x.hd.s中把有关SystemInit 的代码注释掉也可以(首先要把此文件的“只读”属性取消,在下面红色的语句前面增加“;”即可注释掉,绿色保留)。5 x. b: W& j9 W' Z3 c" V * s A! X6 @; e ; Reset handler Reset_Handler PROC& _7 L+ F t* K# }1 A7 p EXPORT Reset_Handler [WEAK]& e, G6 T" T% O1 x( N+ l2 ~ IMPORT __main IMPORT SystemInit2 E0 L2 m! k: C LDR R0, =SystemInit) n, n# X7 k) L( E( Y0 |$ E/ @ BLX R0 # @9 }- v8 e* F8 z% n6 q5 U8 Y/ D LDR R0, =__main BX R0 ENDP
如果是初学,还是建议在main.c中增加void SystemInit(void)函数比较方便,不要去动启动文件。 现在完整组织一下用 STM32 控制2个 LED 的代码: --------------------------------------------------------------------------------------------------------------* `# t8 P& S8 {/ v3 J int main(void), e2 B; x+ V1 K# l) A+ w% r( S& c { - j% @3 S! j0 t! { RCC_APB2ENR |= (1<<3); //开启 GPIOB 端口时钟* l) L, x1 e( h5 p4 V : A! R& L( e. [* v8 K- |& l RCC_APB2ENR |= (1<<6); //开启 GPIOE 端口时钟* |0 f1 T& P& x" o& N( D ) s2 n0 C' k' B) S5 [, ]! G+ ]% m' B # w( u( Z8 X/ y5 W& w; k I I GPIOB_CRL &= ~( 0x0F<< (4*5)); //清空控制 PB5 的端口位 GPIOB_CRL |= (1<<4*5); //配置 PB5 为通用推挽输出,速度为 10M: h; W% D, {: |6 o3 I GPIOB_ODR |= (0<<0); //PB5 输出 低电平! @/ T9 ^& O* i% ^" N8 V' n! b/ y : P' r7 @0 {$ W+ W2 A5 u GPIOE_CRL &= ~( 0x0F<< (4*5)); //清空控制 PE5 的端口位 GPIOE_CRL |= (1<<4*5); //配置 PE5 为通用推挽输出,速度为 10M5 T- w# S4 [4 `: m7 ^ GPIOE_ODR |= (0<<0); //PE5 输出 低电平5 U. a* l" I p1 t* g9 p 5 m; G0 [5 U$ `: M; V6 T2 ? while (1); } //切记此处要加回车 ' y& V7 l1 b% U" Z) d void SystemInit(void) { } //切记此处要加回车- L6 ?- {( F; V, \2 x ! p+ B9 _9 x1 O; F o" z) ^/ A7 o ' \: G7 Z3 y0 v2 K' v ---------------------------------------------------------------------------------------------------------------------- 将上面的代码写入上章创建的空的main.c中。& \5 m1 w) c* |, `
将上面配置GPIOB和GPIOE寄存器要使用的代码写入上次创建的空文件stm32f10x.h中: ----------------------------------------------------------------------------------------------------------------------* W2 |( a8 I7 z$ T+ W " v; N9 x7 o$ H4 E5 |! k #define PERIPH_BASE ((unsigned int)0x40000000) #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)+ B% ?& ` Y# _: Q* y $ ~8 [5 O1 @) l# ~8 ^ #define RCC_BASE (AHBPERIPH_BASE + 0x1000) #define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18) 0 e$ r9 b9 W* r1 S$ B$ j9 Z/ S #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)4 X4 Q, Z. N6 ]# [! o" T #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800) * ]8 x6 M8 g0 y" Q( j" }. f7 { # @8 N9 N4 ]3 Z) i$ O #define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0x00)) v, |+ W- s4 {. S- q #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0x0C) * f. Q, H9 X5 e0 _1 `" r #define GPIOE_CRL *(unsigned int*)(GPIOE_BASE + 0x00) #define GPIOE_ODR *(unsigned int*)(GPIOE_BASE + 0x0C) + ]9 Z, x! b* w% | k p) J" O% J ----------------------------------------------------------------------------------------------------------------------
6 h/ g' R8 [- [0 X 编译通过,见下图:' R* Z7 p3 f6 \ Q) ~& v
( T1 T h" T8 s 下载测试,效果如下: % {/ i) z3 [8 Y9 c; Q' w! s( n
眼看2019年就要来了,2018马上要悄悄的离我们而去了,回想今年一年在论坛混的日子,感觉收获颇丰,感谢各位管管和坛友们的支持和帮助,祝大家2019年工作顺利,财运恒通,身体健康!祝论坛在2019年越办越旺,给广大坛友带来更多的福利!!谢谢!' U+ h' C/ w# O" G* R 6 s8 G0 X8 c2 O4 K6 v( e - |- S1 O! t6 z( j3 w 7 `) u- k, a1 m1 J3 b6 A # e- V" p m8 r' G7 O% x5 Y3 [ ) u) K" u' B2 D / q6 D; H1 D8 u9 I" X; ?" D |
谢谢分享 |
基本讲解 |
| 学得挺认真的嘛 |
| 占个楼 |
|
学习学习 |
DMA 原理从 0 到 1:为什么它能解放 CPU?一文讲透 DMA 的工作流程
【福利三:雨露均沾·逢7狂欢】之五:STM32系统时钟PLL故障排除1例
【福利三:雨露均沾·逢7狂欢】之三:AI帮我查BUG
福利三:雨露均沾·逢7狂欢】之二:STM32F407VG串口通信乱码故障的排除
基于STM32F103的I2C主从机通信
OpenBLT移植到STM32F103战舰开发板上适用于所有STM32F103系列的Bootloader
TFT LCD 与 FSMC 的硬件连接大容量 STM32F10xxx FSMC 接口
单片机:初学者该了解的STM32F103基础知识
2025软件工具兔哥知道
STM32之继电器模块
微信公众号
手机版