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

【经验分享】STM32——GPIO

[复制链接]
STMCU小助手 发布时间:2022-1-29 00:49
对于初学者而言,最简单的是对芯片上的IO进行操作,我们学习ARM时候,第一个工程就是点亮LED,STM32F103ZET6通用输入输出接口(General-Purpose Inputs/Outputs),每个GPIO都可以由软件配置成输出(推免或开漏)、输入(带或不带上拉或下拉)或复用的外设功能端口。多数GPIO引脚都与数字或模拟的复用外设共用。具体的细节请参考Datasheet。
     回到MDK开发平台,现在要在main.c中加入相关代码了。代码清单如下:
  1. #include "stm32f10x_lib.h"; u1 l3 J7 |' q( M4 j+ }
  2.   / f( |+ A% m+ t+ _
  3. int main()) I$ [2 V. T/ d0 c+ j5 B+ }3 H( W
  4. {
    3 N4 v- g; m/ d" C# ^. s4 D
  5.        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOB, ENABLE);     //开启外设时钟
    : z' T, w) `3 x0 a0 ]) ?4 o# f6 u# k
  6.        GPIOD->CRL = 0x33333333;                           //设置端口配置寄存器
    8 h* l5 @) x, m# z
  7.        GPIOB->CRL = 0x33333333;5 {9 g/ P' Z9 Z2 c. O
  8.        while(1)
    7 Q5 l' Z$ w! i! I
  9.        {            " r2 M: G1 d9 ]
  10.               GPIOD->ODR = 0xffffffbf;                       //设置端口输出寄存器
    * W( p: U: D0 W+ h5 m8 a
  11.               for(i=0;i<1000000;i++);                                   //延时! u: w4 |9 n$ V7 K
  12.               GPIOD->ODR = 0xffffffff7;1 z. R" S) {9 f9 D+ i% x
  13.               for(i=0;i<1000000;i++);; v+ g9 L/ w: {! u/ Y' g
  14.               GPIOD->ODR = 0x00000000;
    3 u$ q3 A% S) N7 H: J( ~) A* X$ s
  15.               GPIOB->ODR = 0xffffffff;; k- o( ?, a) {, v3 r  u. Y2 `
  16.               for(i=0;i<1000000;i++);1 M5 v  f3 q7 h, K0 `9 x
  17.               GPIOB->ODR = 0x00000000;; P3 n6 t( H7 w0 o: P7 N
  18.        }- Q7 o" K5 o3 ~6 g
  19. }/ 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的时候,我们需要先开启它的时钟,具体用到的是函数库里面的函数:
  1. 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
  1. #include "stm32f10x.h"
    $ I0 h- }7 i9 a# O
  2. static u8  fac_us=0; //us延时倍乘数     * J) a# i( k6 o9 B% e, h8 |
  3. static u16 fac_ms=0; //ms延时倍乘数,在ucos下代表每个节拍的ms数
    , J" w; l9 a, [" {/ B- r
  4. void delay_init(void);+ c. x+ E( k" k( H0 Z. f
  5. void delay_ms(u16 nms);/ g" P8 p: w# }& c3 {, T! @; g- o) U4 Z
  6. void LED_Init(void);//初始化* `# }. z0 |  R% t# X5 P* r% c2 L
  7. int main(void)' o) x% p* e5 d4 _
  8. {
    . s# @$ j. S9 K) g/ P  T5 k
  9.         2 g$ y; L" j% _( k
  10.        delay_init();          //初始化延时函数' B5 G" D: V) ?/ O' Q, A
  11.        LED_Init();        //初始化LED端口/ A1 l; o' f3 y, c7 }8 \, o
  12.      3 p% |! l! ~* W. I* ^0 g% ~
  13.      : ]+ n0 Y, W6 G
  14.     while(1)
    + N# n- {2 S6 f: Y) I
  15.     {
    - t7 V% Z0 v& V6 D- {
  16.     GPIO_ResetBits(GPIOG,GPIO_Pin_13);  //亮  等同LED0=0;
    : k# I! h- B5 Q( `' c
  17.     GPIO_SetBits(GPIOG,GPIO_Pin_14);    //灭  等同LED1=1;
    ; C, ~+ X3 u+ ]$ {0 R
  18.     GPIO_SetBits(GPIOG,GPIO_Pin_15);    //灭  等同LED2=1;, ^+ \% V, N, ]
  19.     delay_ms(500);              //延时500ms: T+ P% v: L+ y- w. D9 h
  20.     GPIO_SetBits(GPIOG,GPIO_Pin_13);  + ?# s, S. _$ @0 d# v9 L
  21.     GPIO_ResetBits(GPIOG,GPIO_Pin_14); ! }7 H2 L" L. Z* K6 Y
  22.     GPIO_SetBits(GPIOG,GPIO_Pin_15);   
    ; @- G  r' A1 q9 G; @
  23.     delay_ms(500);                     . v/ E9 m* a! G& C* ~" x1 M! b
  24.     GPIO_SetBits(GPIOG,GPIO_Pin_13);   
    . R1 R, b1 @4 L* r7 u
  25.     GPIO_SetBits(GPIOG,GPIO_Pin_14);   4 `, k1 ?; H  D2 H
  26.     GPIO_ResetBits(GPIOG,GPIO_Pin_15);
    * ?9 W1 T( G) z) s; n5 q4 R9 Y" ^4 |
  27.     delay_ms(500);  N  P! F5 i: e2 C; o9 h: J
  28.     }) u! J& O& n: \2 V5 K& X- Y! j
  29. }
    3 M9 w, H+ y! R# \8 p; g; e
  30. void LED_Init(void)   //LED对应IO初始化
    $ Z( K2 \- W, C
  31. {       , V# w: v. J' J7 Z+ [
  32.   GPIO_InitTypeDef  GPIO_InitStructure;6 K8 ^* D" }0 @2 k4 N& y
  33.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);  //使能GPIO时种
    ' B" A6 W, P. ]* M0 |( }' j& I
  34.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;        //LED0,LED1,LED2对应IO口* ~0 J9 b3 E' w+ \
  35.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       //推免输出  v( V5 M" |5 G- r3 P% b% Q: W
  36.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      //IO速度为50Mhz
    6 q" B  f5 e6 C3 a. T' W1 B
  37.   GPIO_Init(GPIOG, &GPIO_InitStructure);             //初始化GPIO: X, V2 L% r* O6 ]0 j9 }* S3 \
  38.   GPIO_SetBits(GPIOG,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);    //GPIOG13,G14,G15设置高,灯灭
    0 M$ u+ w7 D$ a1 k3 h0 q" _! ~

  39. ( \* S5 d. d5 f& j- w- m- l/ |
  40. void delay_init()   //延时函数初始化
    # ^: A6 F3 c7 T7 t8 K# |# ]
  41. {
    9 G3 T5 z) l+ O4 w
  42.     SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);- _# M- `2 ?6 o/ \
  43.     fac_us=SystemCoreClock/8000000;
    - \8 @* W# o' `: A" g* Q% V
  44.     fac_ms=(u16)fac_us*1000; //每个ms需要的systick时钟数 6 g8 h% `6 N( V9 Q. K' [0 C# b6 d0 `
  45. }      1 Z. R: _4 k9 T/ z. r- [
  46. void delay_ms(u16 nms) //延时nms
    . O$ o- Y6 i: M/ B1 C
  47. {               
    9 \& }* p, ~" |# K$ g2 B; i
  48.     u32 midtime;          ( _- e1 G! ~3 U5 A
  49.     SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
    3 l9 r, k3 c6 V8 M
  50.         //SysTick->LOAD为24位寄存器,所以最大延时为:nms<=0xffffff*8*1000/SYSCLK对72M条件下,nms<=1864ms# u5 q) X& \9 a% B! x
  51.     SysTick->VAL =0x00;           //清空计时器÷9 j& ?+ f5 Z  G  X! y% G
  52.     SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数
    6 r. {9 N# F  U% c5 r
  53.     do* i( L! F3 m8 Z3 p" U
  54.     {! }2 e$ P8 M$ z, {0 g4 |
  55.         midtime=SysTick->CTRL;
    2 V; d1 o, r2 e8 W6 e# M* X
  56.     }8 F2 q' ^: P- R- i' q3 a
  57.     while((midtime&0x01)&&!(midtime&(1<<16)));//等待时间到达
    3 d: `2 Y3 l, x5 [& w
  58.     SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
    & T& C/ K9 V' d! M9 R& W- o2 `5 A: o
  59.     SysTick->VAL =0X00;       //清空计数器           0 u7 G2 w" O" O# V; d$ e# g2 c
  60. }  
复制代码
$ [, 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
  1. int main(void)
    ) w$ i1 A, \( T5 B: F
  2. {% k7 |+ L) ]: a5 |
  3.   
    4 `1 S  d0 h% L( ~& U7 W
  4.     delay_init();         //初始化延时函数, C5 M  M( i, G  u8 M1 o5 ^# h" J. C
  5.     LED_Init();       //初始化LED端口, c* B6 W- C5 b; v6 s  i( y
  6.      . m/ x/ a3 F; J0 c5 \3 E
  7.   while(1)/ S9 n  k8 Q! {5 R) z, ]) Q' J, J5 X
  8.     {
    : P0 c, N% y8 J7 Q
  9.     GPIO_bits_OUT(GPIOG,13,3,0x0006);+ j3 h( M6 G5 H/ ?
  10.            delay_ms(500);+ o8 @8 m- \5 i% f/ O) h$ e$ ^
  11.       GPIO_bits_OUT(GPIOG,13,3,0x0005);
    8 B) ]7 M8 Q( r0 o+ }: Q) H. P- W( B
  12.         delay_ms(500);
    6 x3 O( ?& w4 G% K) O
  13.       GPIO_bits_OUT(GPIOG,13,3,0x0003);% p/ \% q" H) ^% U  k
  14.         delay_ms(500);
    . Z8 ?( b1 n$ U% {* `
  15.     }
    1 n1 t1 `3 l* M$ W
  16. }0 T. B7 r1 `# Z  @  z3 L

  17. 8 c. Y/ D) g) g( f- C0 H
  18. 4 y4 o1 ]( M' q
  19. /*以下:*GPIOx:对应的IO口,start_bit:并行输出的起始值,bit_size:并行输出的位数*/2 q( X* F% s8 \0 V

  20. 2 q/ E, p# X5 b
  21. void GPIO_bits_OUT(GPIO_TypeDef* GPIOx, u8 start_bit, u8 bit_size,u16 outdata)9 ~, `  c% y9 @9 ~
  22. {
    + M4 s1 g: z. c2 S, r5 ^
  23.   u8 i=0;
    8 ~8 ]% a$ l* Z0 |% ^, A8 L' C
  24.     u16 bu1=0;u16 middata=1;+ k! r) w& T9 i& T. H% }
  25. 4 \( y6 l6 Q( L3 b' m+ Y
  26.     if( bit_size>(16-start_bit) )& z% r2 l: e  |! ?1 v
  27.      bit_size=16-start_bit;3 [6 G8 C; b- p* a& b* D: m
  28.      
    0 x5 i% g& c! R3 E$ C
  29.     i=start_bit;# ~/ G3 J- ^2 X. l4 ~0 t( {
  30.     if(i>0)
    . G9 O1 r* }1 {  N" p7 E
  31.          {
    # `8 ]& V& R3 T2 w( L
  32.              while(i--); w! Q8 A) v. ^' V- ^: m
  33.          { bu1+=middata; middata*=2;}: b/ V. L# n" G: ~% U  w
  34.          }' u. z- o7 y5 [8 b
  35.      ; Q6 r$ |6 `5 H6 ]- }0 }& s  N
  36.    assert_param(IS_GPIO_ALL_PERIPH(GPIOx));& e# A. s4 N( a
  37.    
    7 [& {) f1 K9 H2 i2 D/ M
  38.      GPIOx->ODR&=(  ( (0xffff<<bit_size) <<start_bit ) |bu1   );
    ) s; d/ u/ r# ?) E6 N7 X. k3 h
  39.      GPIOx->ODR|=(outdata<<start_bit);     ' }$ ?' L& f! G1 I2 `; k2 C
  40. }
复制代码
! d0 M( r. R, |: V' T
2、通过位带操作实现IO口控制;
3、通过库函数直接操作实现IO控制;

! I% i& k6 _& T
1294708-20180814163013681-282670528.png

. 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
赞 收藏 评论0 发布时间:2022-1-29 00:49

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版