|
对于初学者而言,最简单的是对芯片上的IO进行操作,我们学习ARM时候,第一个工程就是点亮LED,STM32F103ZET6通用输入输出接口(General-Purpose Inputs/Outputs),每个GPIO都可以由软件配置成输出(推免或开漏)、输入(带或不带上拉或下拉)或复用的外设功能端口。多数GPIO引脚都与数字或模拟的复用外设共用。具体的细节请参考Datasheet。 回到MDK开发平台,现在要在main.c中加入相关代码了。代码清单如下:- #include "stm32f10x_lib.h"; u1 l3 J7 |' q( M4 j+ }
- / f( |+ A% m+ t+ _
- int main()) I$ [2 V. T/ d0 c+ j5 B+ }3 H( W
- {
3 N4 v- g; m/ d" C# ^. s4 D - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOB, ENABLE); //开启外设时钟
: z' T, w) `3 x0 a0 ]) ?4 o# f6 u# k - GPIOD->CRL = 0x33333333; //设置端口配置寄存器
8 h* l5 @) x, m# z - GPIOB->CRL = 0x33333333;5 {9 g/ P' Z9 Z2 c. O
- while(1)
7 Q5 l' Z$ w! i! I - { " r2 M: G1 d9 ]
- GPIOD->ODR = 0xffffffbf; //设置端口输出寄存器
* W( p: U: D0 W+ h5 m8 a - for(i=0;i<1000000;i++); //延时! u: w4 |9 n$ V7 K
- GPIOD->ODR = 0xffffffff7;1 z. R" S) {9 f9 D+ i% x
- for(i=0;i<1000000;i++);; v+ g9 L/ w: {! u/ Y' g
- GPIOD->ODR = 0x00000000;
3 u$ q3 A% S) N7 H: J( ~) A* X$ s - GPIOB->ODR = 0xffffffff;; k- o( ?, a) {, v3 r u. Y2 `
- for(i=0;i<1000000;i++);1 M5 v f3 q7 h, K0 `9 x
- GPIOB->ODR = 0x00000000;; P3 n6 t( H7 w0 o: P7 N
- }- Q7 o" K5 o3 ~6 g
- }/ X" C6 b* \" [: M. b u+ y" H
复制代码
4 A2 Z# c c% `; c 上述代码中,#include "stm32f10x_lib.h"包含了开发stm32f10x系列芯片所需的基本头文件,在进行程序编写的时候,务必要包含此头文件。 RCC_APB2PeriphClockCmd()函数是设置外设时钟。ARM与C51单片机不同的是,不用外设的时候,如IO口、ADC、定时器等等,都是禁止时钟的,以达到节能的目的,只有要用到的外设,才开启它的时钟。因此在需要用到GPIOB和GPIOD的时候,我们需要先开启它的时钟,具体用到的是函数库里面的函数: - void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
复制代码 2 w! x, b" R) v) j9 I! a
其中,第一个参数需要指示要开启什么端口的时钟,RCC_APB2Periph_GPIOx就是开启GPIOx的时钟,第二个参数需要指示是开启还是关闭,ENABLE/DISABLE。 开启外设时钟之后,然后就开始对GPIO的配置寄存器进行设置了。具体设置方式参考《基于MDK的STM32处理器开发应用》一书中,“7.1 通用IO端口”。While循环里面就是给GPIO的端口输出寄存器赋值,由于我手上这款开发板三个LED灯分别接的是PG13、PG14和PG15,所以只要将G端口相应的位上置1就可以了。 编译之后我们发现编译器报错,Undefined symbol RCC_APB2PeriphClockCmd,是因为我们使用了的RCC_APB2PeriphClockCmd()函数在头文件中声明了,却没有在C文件中定义,这个函数在Keil\ARM\RV31\LIB\ST\STM32F10x\stm32f10x_rcc.c中,将这个文件复制到工程的根目录下,然后在屏幕左边的Workspace中添加进来,就可以了。 其实在MDK的库中,还定义了很多宏,可以避免让我们自己去查找相关资料来设置寄存器的各个位。 对于LED的亮灭可用以下代码进行实现:
% ?2 r8 B3 W0 ~/ H, `; V- #include "stm32f10x.h"
$ I0 h- }7 i9 a# O - static u8 fac_us=0; //us延时倍乘数 * J) a# i( k6 o9 B% e, h8 |
- static u16 fac_ms=0; //ms延时倍乘数,在ucos下代表每个节拍的ms数
, J" w; l9 a, [" {/ B- r - void delay_init(void);+ c. x+ E( k" k( H0 Z. f
- void delay_ms(u16 nms);/ g" P8 p: w# }& c3 {, T! @; g- o) U4 Z
- void LED_Init(void);//初始化* `# }. z0 | R% t# X5 P* r% c2 L
- int main(void)' o) x% p* e5 d4 _
- {
. s# @$ j. S9 K) g/ P T5 k - 2 g$ y; L" j% _( k
- delay_init(); //初始化延时函数' B5 G" D: V) ?/ O' Q, A
- LED_Init(); //初始化LED端口/ A1 l; o' f3 y, c7 }8 \, o
- 3 p% |! l! ~* W. I* ^0 g% ~
- : ]+ n0 Y, W6 G
- while(1)
+ N# n- {2 S6 f: Y) I - {
- t7 V% Z0 v& V6 D- { - GPIO_ResetBits(GPIOG,GPIO_Pin_13); //亮 等同LED0=0;
: k# I! h- B5 Q( `' c - GPIO_SetBits(GPIOG,GPIO_Pin_14); //灭 等同LED1=1;
; C, ~+ X3 u+ ]$ {0 R - GPIO_SetBits(GPIOG,GPIO_Pin_15); //灭 等同LED2=1;, ^+ \% V, N, ]
- delay_ms(500); //延时500ms: T+ P% v: L+ y- w. D9 h
- GPIO_SetBits(GPIOG,GPIO_Pin_13); + ?# s, S. _$ @0 d# v9 L
- GPIO_ResetBits(GPIOG,GPIO_Pin_14); ! }7 H2 L" L. Z* K6 Y
- GPIO_SetBits(GPIOG,GPIO_Pin_15);
; @- G r' A1 q9 G; @ - delay_ms(500); . v/ E9 m* a! G& C* ~" x1 M! b
- GPIO_SetBits(GPIOG,GPIO_Pin_13);
. R1 R, b1 @4 L* r7 u - GPIO_SetBits(GPIOG,GPIO_Pin_14); 4 `, k1 ?; H D2 H
- GPIO_ResetBits(GPIOG,GPIO_Pin_15);
* ?9 W1 T( G) z) s; n5 q4 R9 Y" ^4 | - delay_ms(500); N P! F5 i: e2 C; o9 h: J
- }) u! J& O& n: \2 V5 K& X- Y! j
- }
3 M9 w, H+ y! R# \8 p; g; e - void LED_Init(void) //LED对应IO初始化
$ Z( K2 \- W, C - { , V# w: v. J' J7 Z+ [
- GPIO_InitTypeDef GPIO_InitStructure;6 K8 ^* D" }0 @2 k4 N& y
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE); //使能GPIO时种
' B" A6 W, P. ]* M0 |( }' j& I - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15; //LED0,LED1,LED2对应IO口* ~0 J9 b3 E' w+ \
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推免输出 v( V5 M" |5 G- r3 P% b% Q: W
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO速度为50Mhz
6 q" B f5 e6 C3 a. T' W1 B - GPIO_Init(GPIOG, &GPIO_InitStructure); //初始化GPIO: X, V2 L% r* O6 ]0 j9 }* S3 \
- GPIO_SetBits(GPIOG,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //GPIOG13,G14,G15设置高,灯灭
0 M$ u+ w7 D$ a1 k3 h0 q" _! ~
( \* S5 d. d5 f& j- w- m- l/ |- void delay_init() //延时函数初始化
# ^: A6 F3 c7 T7 t8 K# |# ] - {
9 G3 T5 z) l+ O4 w - SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);- _# M- `2 ?6 o/ \
- fac_us=SystemCoreClock/8000000;
- \8 @* W# o' `: A" g* Q% V - fac_ms=(u16)fac_us*1000; //每个ms需要的systick时钟数 6 g8 h% `6 N( V9 Q. K' [0 C# b6 d0 `
- } 1 Z. R: _4 k9 T/ z. r- [
- void delay_ms(u16 nms) //延时nms
. O$ o- Y6 i: M/ B1 C - {
9 \& }* p, ~" |# K$ g2 B; i - u32 midtime; ( _- e1 G! ~3 U5 A
- SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
3 l9 r, k3 c6 V8 M - //SysTick->LOAD为24位寄存器,所以最大延时为:nms<=0xffffff*8*1000/SYSCLK对72M条件下,nms<=1864ms# u5 q) X& \9 a% B! x
- SysTick->VAL =0x00; //清空计时器÷9 j& ?+ f5 Z G X! y% G
- SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
6 r. {9 N# F U% c5 r - do* i( L! F3 m8 Z3 p" U
- {! }2 e$ P8 M$ z, {0 g4 |
- midtime=SysTick->CTRL;
2 V; d1 o, r2 e8 W6 e# M* X - }8 F2 q' ^: P- R- i' q3 a
- while((midtime&0x01)&&!(midtime&(1<<16)));//等待时间到达
3 d: `2 Y3 l, x5 [& w - SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
& T& C/ K9 V' d! M9 R& W- o2 `5 A: o - SysTick->VAL =0X00; //清空计数器 0 u7 G2 w" O" O# V; d$ e# g2 c
- }
复制代码 $ [, Z7 \: R' v8 v
# L6 Y( Y3 o/ {6 Y. @1 ^由于我们使用了GPIO_InitTypeDef类型,所以我们需要找到它的定义,这个定义包含在“…\Keil\ARM\RV31\LIB\ST\STM32F10x\stm32f10x_gpio.c”中,将文件复制到工程根目录下,然后再添加进入工程中,编译才不会报错。 在绝大多数C编译器中,要求所有的变量声明都在执行语句块之前,也就是说如果需要定义的变量需要先在进入main函数一开始就全部定义好,如果执行了某一条语句之后再定义变量的话,就会报错。 5 j0 m& c; O7 x$ T( w" Y" Y; f
补充(一):有三种方式可以控制LED亮灭: 1、通过位段操作实现IO口控制;
, w. U3 X0 V8 O8 d- int main(void)
) w$ i1 A, \( T5 B: F - {% k7 |+ L) ]: a5 |
-
4 `1 S d0 h% L( ~& U7 W - delay_init(); //初始化延时函数, C5 M M( i, G u8 M1 o5 ^# h" J. C
- LED_Init(); //初始化LED端口, c* B6 W- C5 b; v6 s i( y
- . m/ x/ a3 F; J0 c5 \3 E
- while(1)/ S9 n k8 Q! {5 R) z, ]) Q' J, J5 X
- {
: P0 c, N% y8 J7 Q - GPIO_bits_OUT(GPIOG,13,3,0x0006);+ j3 h( M6 G5 H/ ?
- delay_ms(500);+ o8 @8 m- \5 i% f/ O) h$ e$ ^
- GPIO_bits_OUT(GPIOG,13,3,0x0005);
8 B) ]7 M8 Q( r0 o+ }: Q) H. P- W( B - delay_ms(500);
6 x3 O( ?& w4 G% K) O - GPIO_bits_OUT(GPIOG,13,3,0x0003);% p/ \% q" H) ^% U k
- delay_ms(500);
. Z8 ?( b1 n$ U% {* ` - }
1 n1 t1 `3 l* M$ W - }0 T. B7 r1 `# Z @ z3 L
8 c. Y/ D) g) g( f- C0 H- 4 y4 o1 ]( M' q
- /*以下:*GPIOx:对应的IO口,start_bit:并行输出的起始值,bit_size:并行输出的位数*/2 q( X* F% s8 \0 V
2 q/ E, p# X5 b- void GPIO_bits_OUT(GPIO_TypeDef* GPIOx, u8 start_bit, u8 bit_size,u16 outdata)9 ~, ` c% y9 @9 ~
- {
+ M4 s1 g: z. c2 S, r5 ^ - u8 i=0;
8 ~8 ]% a$ l* Z0 |% ^, A8 L' C - u16 bu1=0;u16 middata=1;+ k! r) w& T9 i& T. H% }
- 4 \( y6 l6 Q( L3 b' m+ Y
- if( bit_size>(16-start_bit) )& z% r2 l: e |! ?1 v
- bit_size=16-start_bit;3 [6 G8 C; b- p* a& b* D: m
-
0 x5 i% g& c! R3 E$ C - i=start_bit;# ~/ G3 J- ^2 X. l4 ~0 t( {
- if(i>0)
. G9 O1 r* }1 { N" p7 E - {
# `8 ]& V& R3 T2 w( L - while(i--); w! Q8 A) v. ^' V- ^: m
- { bu1+=middata; middata*=2;}: b/ V. L# n" G: ~% U w
- }' u. z- o7 y5 [8 b
- ; Q6 r$ |6 `5 H6 ]- }0 }& s N
- assert_param(IS_GPIO_ALL_PERIPH(GPIOx));& e# A. s4 N( a
-
7 [& {) f1 K9 H2 i2 D/ M - GPIOx->ODR&=( ( (0xffff<<bit_size) <<start_bit ) |bu1 );
) s; d/ u/ r# ?) E6 N7 X. k3 h - GPIOx->ODR|=(outdata<<start_bit); ' }$ ?' L& f! G1 I2 `; k2 C
- }
复制代码 ! d0 M( r. R, |: V' T
2、通过位带操作实现IO口控制; 3、通过库函数直接操作实现IO控制;
! I% i& k6 _& T
. u/ {- x9 V z2 K3 V' G9 Q补充(二):STM32中GPIO的8种工作模式- 模拟输入 用于模拟量的输入,或者低功耗下省电。
- 下拉输入 端口内下拉电阻电路导通。默认为低电平,外部高电平动作。
- 上拉输入 端口内上拉电阻电路导通,默认为高电平,外部低电平动作。
- 浮空输入 端口内上下MOSFET均不导通,高阻态,输入状态仅由端口决定。
- 开漏输出 端口输出为0时内部下拉电阻电路接地,输出1时端口相当于悬空,即默认只能输出0,如果外部需要输出1,需要外接上拉电阻电路。
- 推挽输出 端口输出为0时内部下拉电阻电路接地,输出1时内部接上拉电阻电路,默认输出为0。
- 复用开漏输出 内部设置同开漏,但被其他外设复用。
- 复用推挽输出 内部设置同推挽,但被其他外设复用。
5 n2 J1 N$ [" h4 m I$ G3 w
小补充: - " ^0 F6 i4 P. |) R; Q) e9 E/ E
- 上拉电阻就是:将一个不确定信号(高或低电平),通过一个电阻与电源VCC相连,固定在高电平;
- 下拉电阻就是:将一个不确定信号(高或低电平),通过一个电阻与地GND相连,固定在低电平。# ^1 u' m) X; O# V6 {7 c( _
# B0 h+ x4 a2 B! ~( u- d
- 作为普通的GPIO输入:根据需要配置该引脚为浮空输入、带弱上拉输入或带弱下拉输入,同时不要使能该引脚对应的所有复用功能模块。
- 作为普通的GPIO输出:根据需要配置该引脚为推免输出或开漏输出,同时不要使能该引脚对应的所有复用功能。
- 作为普通模拟输入:配置该引脚为模拟模拟输入模式,同时不要使能该引脚对应的所有复用功能。
- 作为内置外设的输入:根据需要配置该引脚为浮空输入,带弱上拉输入或带弱下拉输入,同时使能该引脚的复用功能。
- 作为内置外设的输出:根据需要配置引脚为复用推挽输出或复用开漏输出,同时使能该引脚的对应的所有复用功能模块。. ?+ i7 S8 s2 Z8 `
) q! W: @( J" T }$ v! G8 |
j- G# W! |- T1 t |