STM32(1)——GPIO

 

重新审视STM32(1)——GPIO

第一次学习stm32时什么都不会,51也不懂,C语言模模糊糊不成体统连什么是硬件什么单片机的问题都回答不上来,“51已为神,32掌控万物”是对我那是鄙陋的想法,

后赶鸭子上架,匆匆忙忙,有幸学习了正点原子的教程,但仅限于应用,恍恍惚惚缺乏细节的理解。再往后慢慢用起来FPGA,了解时序,也用上其他的硬件。。。。。对许多问题有了自己的思考和想法,不再像以前一样用黑科技的眼光看待库函数,看待那一串串的0011

由于是纯野生的小白,很难有科班的理解,仍有欠妥当之处,望海涵。

如果真要说起stm32从gpio并不合适,绕开M3内核和指令集讲述文题终究浮于表面,在此一说希望看客有兴趣的话去看一看M3的权威指南,本人汇编水平和对ARM架构的理解不足深入探讨这些问题。故从应用下手——第一篇GPIO

硬件设备:正点原子战舰V3 STM32F103zet6

 GPIO

(讲解顺序

  1 库函数

  2寄存器

  3位带操作

库函数

GPIO_Init()

万物离不开init

来看

 1 void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
 2 /*其中第一个参数为那组引脚,每组拥有16个引脚,每组都具有不同的寄存器配置地址,第二个参数是一个数据结构,也就是将基本配置信息放在这个数据结构里面,再将这个结构传入函数进行配置*/
 3 //其中数据机构可以表示为如下
 4 typedef struct
 5 {
 6   uint16_t GPIO_Pin; //引脚号
 7   GPIOSpeed_TypeDef GPIO_Speed; //配置速度
 8   GPIOMode_TypeDef GPIO_Mode; //工作模式
 9 }GPIO_InitTypeDef;
10 
11 //其中配置模式和工作模式为GPIOSpeed_TypeDef和GPIOMode_TypeDef的枚举变量
12 //其次是模式定义
13 typedef enum
14 { GPIO_Mode_AIN = 0x0, //模拟输入
15 GPIO_Mode_IN_FLOATING = 0x04, //浮空输入模式, 默认
16 GPIO_Mode_IPD = 0x28, //上拉/下拉输入模式
17 GPIO_Mode_IPU = 0x48, //保留
18 GPIO_Mode_Out_OD = 0x14, //通用开漏输出
19 GPIO_Mode_Out_PP = 0x10, //通用推挽输出
20 GPIO_Mode_AF_OD = 0x1C, //复用(开漏)输出
21 GPIO_Mode_AF_PP = 0x18 //复用(推挽)输出
22 }GPIOMode_TypeDef;
23 //最后是速度定义
24 typedef enum 25 { 26 GPIO_Speed_10MHz = 1, 27 GPIO_Speed_2MHz, 28 GPIO_Speed_50MHz 29 }GPIOSpeed_TypeDef;

三大定义支撑起stm32IO口的半壁江山啊,可这如何实现的呢?

我们先看参数信息,第一个

GPIO_TypeDef* GPIOx

typedef struct
{
  __IO uint32_t CRL;
  __IO uint32_t CRH;
  __IO uint32_t IDR;
  __IO uint32_t ODR;
  __IO uint32_t BSRR;
  __IO uint32_t BRR;
  __IO uint32_t LCKR;
} GPIO_TypeDef;

显而易见,可以理解为这是在往函数里加载寄存器

下一个

GPIO_InitStruct
typedef struct
{
  uint16_t GPIO_Pin;             /*!< Specifies the GPIO pins to be configured.
                                      This parameter can be any value of @ref GPIO_pins_define */

  GPIOSpeed_TypeDef GPIO_Speed;  /*!< Specifies the speed for the selected pins.
                                      This parameter can be a value of @ref GPIOSpeed_TypeDef */

  GPIOMode_TypeDef GPIO_Mode;    /*!< Specifies the operating mode for the selected pins.
                                      This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;


GPIO_Init()程序
在看主程序之前我们需要了解一下几点
1 assert_param()这个宏定义在上面(我省了)在下面都需要,我们看看它是什么
 #define assert_param(expr) ((void)0)
仅仅定义了一个null????其实不然,这是一种对于应用这并没有什么用的断言机制,我列出它的完整代码
#ifdef  USE_FULL_ASSERT

  #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
  void assert_failed(uint8_t* file, uint32_t line);
#else
  #define assert_param(expr) ((void)0)

这是和有意思的调试程序,用于设计者debug的东西。如果宏为1那么该函数如果返回1则该函数无效,程序继续执行,如果返回为0,则这里执行出了问题返回所在位置(看见那个三目运算符就明白了)

那么这就意味着带 assert_param()的函数无效执行。

 

2由下图寄存器,我么了解到 一组io口需要  MODE+CNF来配置,且各占两位,32位正好满足 低8位的配置 (4*8)

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
 /*初始化各个变量*/
  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
  uint32_t tmpreg = 0x00, pinmask = 0x00;
  //currentmode 用于存放临时的LCIR
  //currentpin 用于存放配置的引脚位
  //pinpos 用于存放当前操作的引脚号
  //pos 存放当前操作的引脚位
  //tmreg 当前的CIR
  //pinmask


  //判断参数
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
  /*---------------------------- 模式配置 -----------------------*/
  //取出配置信息里面的模式信息并且取它的低4位
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
  if ((((uint32_t)GPIO_OInitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) //输出模式
  {

    //判断参数
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    
    //将速度信息放入currentmode低二位
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) //引脚有定义
  {
    //当前的CRL保存
    tmpreg = GPIOx->CRL;

    //循环低八位引脚
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      //当前是那个引脚,那个位置1
      pos = ((uint32_t)0x01) << pinpos;

      //读取引脚信息里面的当前引脚
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
      if (currentpin == pos)     //如果当前引脚在配置信息里存在
      {
        pos = pinpos << 2; //pos=引脚号x2
        pinmask = ((uint32_t)0x0F) << pos; //1111<<引脚号x2,根据 CRL 的结构很容易理解
        tmpreg &= ~pinmask; //当前应该操作的CRL位清0
        tmpreg |= (currentmode << pos); //设置当前操作的 CRL 位
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) // 端口置为高电平
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)    // 端口清0
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }
若是高八位,则处理方式相同,注意BSRR和BRR加八位

其实我们在配置io口时并不需要那么繁琐的 结构体配置,参考初始化代码,我们可以把要配置的io作为参数直接配置 做一个简单的API,

 1 //GPIO端口配置函数(该函数没有使用库函数, 使用时也无须包含stm32f10x_gpio.h)
 2 //
 3 //输入参数:
 4 //GPIOx:端口组,如GPIOD
 5 //Pin: 端口引脚(可以同时配置多个引脚,如GPIO_Pin_13|GPIO_Pin_5
 6 //Mode: 端口模式(模式和速度合一),请使用模式和速度的宏定义相加或直接填写等值的数值, 
 7 //      如Mode_IPD,或者Mode_Out_PP+Speed_50MHz
 8 //      函数不检查该参数,请勿超出这8种的范围自创模式!!! 
 9 //返回值: 无. 
10 //
11 //使用示例:  My_GPIO_Init(GPIOD, GPIO_Pin_5, Mode_Out_OD+Speed_50MHz); 即可设置PD5为开漏输出
12 void My_GPIO_Init(GPIO_TypeDef* GPIOx, u16 Pin, u8 Mode)
13 {
14     u8  i, pos; 
15     u32 currentmode,tmpreg, pinmask;
16     
17  /*---------------------------- GPIO 模式计算 -----------------------*/
18   currentmode = (u32)(Mode & 0x0F); //取模式参数低4位存入currentmode
19   if (!(Mode & 0x10)) //如果Mode第5位不为1,则为输入模式
20    { 
21     currentmode &= 0x0c;  //输入模式下,确保低2位为0(输出模式下的端口速度项)
22    }
23 
24     /*---------------------------- GPIO CRL 配置 ------------------------*/
25   if (Pin & 0x00ff)//如果低8位有引脚需要配置
26   {
27     tmpreg = GPIOx->CRL;//取CRL原有值
28     for (i = 0; i < 8; i++)
29     {
30       if ((Pin>>i)&0x01)/* 找到一个需要配置的引脚 */
31       {
32         pos = i << 2;//BIT位置计算,每线配置位占4BIT(即pos=i*4)
33         /* 清相应的BIT位 */
34         pinmask = ((u32)0x0F) << pos;
35         tmpreg &= ~pinmask;
36         /* 写相应的BIT位 */
37         tmpreg |= (currentmode << pos);
38         
39         if(Mode == Mode_IPD)/* 对于下拉输入模式,则复位端口 */ 
40                     GPIOx->BRR = (u32)0x01 << i;
41         else if(Mode == Mode_IPU) /* 对于上拉输入模式,则置位端口 */
42                     GPIOx->BSRR =(u32)0x01 << i;
43        }
44     }
45     GPIOx->CRL = tmpreg;//完成CRL配置
46   }
47  
48     /*---------------------------- GPIO CRH 配置 ------------------------*/
49      if (Pin & 0xff00)//如果高8位有引脚需要配置
50   {
51     tmpreg = GPIOx->CRH;//取CRH原有值
52     for (i=0; i<8; i++)
53     {
54       if((Pin>>(i+8))&0x01)/* 找到一个需要配置的引脚 */
55       {
56         pos = i << 2;//BIT位置计算,每线配置位占4BIT(即pos=i*4)
57         /* 清相应的BIT位 */
58         pinmask = ((u32)0x0F) << pos;
59         tmpreg &= ~pinmask;
60         /* 写相应的BIT位 */
61         tmpreg |= (currentmode << pos);
62         
63         if(Mode == Mode_IPD)/* 对于下拉输入模式,则复位端口 */
64                     GPIOx->BRR = (u32)0x01<<(i+8);
65         else if(Mode == Mode_IPU)/* 对于上拉输入模式,则置位端口 */
66                     GPIOx->BSRR =(u32)0x01<<(i+8);
67        }
68     }
69     GPIOx->CRH = tmpreg;//完成CRH配置
70   }
71 }
原作者http://www.openedv.com/forum.php?mod=viewthread&tid=274724&extra=

相对于库函数简单了许多,作为方便记得添加头文件(stm32f10x_gpio.h)或者直接将相关参数添加到你的头文件中。原作者直接把该函数放进sys.h

 1 #define Pin_0                 ((uint16_t)0x0001)  /*!< Pin 0 selected */
 2 #define Pin_1                 ((uint16_t)0x0002)  /*!< Pin 1 selected */
 3 #define Pin_2                 ((uint16_t)0x0004)  /*!< Pin 2 selected */
 4 #define Pin_3                 ((uint16_t)0x0008)  /*!< Pin 3 selected */
 5 #define Pin_4                 ((uint16_t)0x0010)  /*!< Pin 4 selected */
 6 #define Pin_5                 ((uint16_t)0x0020)  /*!< Pin 5 selected */
 7 #define Pin_6                 ((uint16_t)0x0040)  /*!< Pin 6 selected */
 8 #define Pin_7                 ((uint16_t)0x0080)  /*!< Pin 7 selected */
 9 #define Pin_8                 ((uint16_t)0x0100)  /*!< Pin 8 selected */
10 #define Pin_9                 ((uint16_t)0x0200)  /*!< Pin 9 selected */
11 #define Pin_10                ((uint16_t)0x0400)  /*!< Pin 10 selected */
12 #define Pin_11                ((uint16_t)0x0800)  /*!< Pin 11 selected */
13 #define Pin_12                ((uint16_t)0x1000)  /*!< Pin 12 selected */
14 #define Pin_13                ((uint16_t)0x2000)  /*!< Pin 13 selected */
15 #define Pin_14                ((uint16_t)0x4000)  /*!< Pin 14 selected */
16 #define Pin_15                ((uint16_t)0x8000)  /*!< Pin 15 selected */
17 #define Pin_All               ((uint16_t)0xFFFF)  /*!< All pins selected */
18 
19 #define    Mode_AIN                            0x0   //模拟输入
20 #define    Mode_IN_FLOATING            0x04    //浮空输入
21 #define    Mode_IPD                            0x28    //下拉输入
22 #define    Mode_IPU                            0x48    //上拉输入
23 
24 #define    Mode_Out_OD                        0x14    //开漏输出
25 #define    Mode_Out_PP                        0x10    //推挽输出
26 #define    Mode_AF_OD                        0x1C    //复用开漏
27 #define    Mode_AF_PP                        0x18    //复用推挽    
28 
29 #define    Speed_10MHz                        0x1
30 #define    Speed_2MHz                        0x2
31 #define    Speed_50MHz                        0x3
头文件

GPIO_DeInit()的处理方式也是非常的简单粗暴 寻找GPIOX 调用函数 ,而函数本质就是时钟复位

 

 

寄存器

 

看完库函数的IF IFIF.....自我感觉很辣眼睛,寄存器的直接访问似乎清爽了许多

stm32的每组IO口含下面7个寄存器。也就是7个寄存器,
一共可以控制一组GPIO的16个IO口

两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),

两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),

一个32位置位/复位寄存器(GPIOx_BSRR),

一个16位复位寄存器(GPIOx_BRR)

一个32位锁定寄存器(GPIOx_LCKR)。

模式有

─ 输入浮空 ─ 输入上拉 ─ 输入下拉 ─ 模拟输入 ─ 开漏输出 ─ 推挽式输出 ─ 推挽式复用功能

 

 我们所有和io口有关的寄存器全部挂载在APB2总线

使用CRL CRH时我们发现上下拉的配置与库函数不同

库函数明确了:

GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,

而寄存器却:

10:上拉/下拉输入模式

11:保留

上下拉 是给IO一个默认的状态 比如控制EN的话,那么高有效的我们就下拉,低有效的话我们就上拉

为了证明,用库函数分别设置上拉下拉,仿真结果如下

 

操控寄存器我们谈谈ODR和BRR,BSRR

ODR:ODy: 1:对应位设置为1;

        0:对应位清除为0;

BRR:1:对应的ODRy位为0,

       0:时无影响;

BSRR:BSy:0:对对应的ODRy位不产生影响

        1:设置对应的ODRy位为1

    BRy:0:对对应的ODRy位不产生影响

        1:清除对应的ODRy位为0;

        (BSy优先级高于BRy)

BSRR,BRR寄存器虽然繁琐,但使用时没有被中断打断的风险。也就不需要关闭中断。

,许多人觉得操作寄存器影响效率且难受。

我在这里郑重介绍下,stm32f10x.h 头文件,看

 

 

stm32官方已经将每一个位都定义好了,例如我们要操控PB.5设置为推挽50M输出:

GPIOB->CRL &=~(GPIO_CRL_MODE5 | GPIO_CRL_CNF5);

GPIOB->CRL |= GPIO_CRL_MODE5_1;
GPIOB->CRL |= GPIO_CRL_MODE5_0;

首先将不相干位寄存器清零以免造成不必要的麻烦

找到MODE5这是5口速度   ok  50MHZ输出为 0x11位    使用或运算

找到CNF 这是输出模式 推挽输出为0x00 就什么都不做了

这种方法对寄存器操作不熟练的人好用,可以算是杂糅了寄存器和库函数的操作,但似乎没多少人用直接忽视了

 

位带操作

 说位带操作绕不开一张图

 

 

 位带操作我们只需记住使用那个公式就行了,对于意义可以这样理解,我们把一位膨胀到32个位,我的理解和链表一样,在这放的就是个地址,地址上的东西是32位的(这样理解欠妥,但大体无碍),我们为了像51一样直接进行io操作引入了位带操作,本来需要读——改——写的操作直接一步到位,

避免因读——改——写中间留有两个能被中断的空当,于是可能会出现紊乱。就像看书,没有目录的话我一页一页的翻找,有目录,我就直接按照目录找到我要的那页。

 

posted @ 2020-04-15 00:24  forup  阅读(498)  评论(0)    收藏  举报