使用GPIO点亮一个LED灯

Day 1 使用GPIO点亮一个LED灯

一. GPIO

1. 介绍

叫通用输入输出口,可以用来连接各种硬件设备,实现功能的实现,或者数据的传输/交互

2. 结构

在这里插入图片描述

  • 上半部分(输入功能)
    输出缓冲器被禁止,施密特触发输入被激活,根据输入配置(上拉,下拉或浮动)的不同,弱上拉和下拉电阻被连接, 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器,对输入数据寄存器的读访问可得到I/O状
    在这里插入图片描述

  • 下半部分(输出功能)
    输出缓冲器被激活
    ─ 开漏模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将端口置于高阻状态(PMOS从不被激活)。
    ─ 推挽模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将激活P-MOS。
    ● 施密特触发输入被激活
    ● 弱上拉和下拉电阻被禁止
    ● 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
    ● 在开漏模式时,对输入数据寄存器的读访问可得到I/O状态
    ● 在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的值。
    在这里插入图片描述

3. 八种模式

  • 输入浮空 :意思就是浮在空中,输入的电平信号具有不确定性,可能是低电平也可能是高电平

  • 输入上拉 :将输入电平拉高,使其变为高电平

  • 输入下拉 :将输入电平拉低,使其变为低电平

  • 模拟输入 :将模拟信号传输至模拟输入处,等待处理

  • 开漏输出 :开漏输出(Open-Drain Output)是指输出引脚可以连接到负载(通常是电阻和/或其他器件),但无法提供高电平(高电压)信号。输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行.

  • 推挽式输出 : 能够提供高电平和低电平。能够利用三极管放大特性直接驱动负载
    在这里插入图片描述

  • 推挽式复用功能

  • 开漏复用功能
    在这里插入图片描述

二.点亮LED配置步骤/过程

打开时钟->初始化GPIO->配置GPIO的参数->在while循环内实现亮灭控制
在这里插入图片描述

#include "stm32f10x.h"                  // Device header

int main(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
    //配置RCC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	//初始化GPIA
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	while(1)
	{
    //实现LED灯的亮灭
		GPIO_ResetBits(GPIOA,GPIO_Pin_0);
		Delay_ms(200);
		GPIO_SetBits(GPIOA,GPIO_Pin_0);
		Delay_ms(200);
	}
}

遇到问题或疑问

  • 为什么要初始化时钟?

原因在于外围设备的寄存器需要时钟才能工作。你可以把外设当做一个设备,而这个设备需要给它提供电源(时钟)才能工作。

  • 输入输出模式英文怎么选?
    • GPIO_Mode_AF_OD:复用开漏输出
    • GPIO_Mode_AF_PP: 复用推挽输出
    • GPIO_Mode_AIN:模拟输入
    • GPIO_Mode_IN_FLOATING:浮空输入
    • GPIO_Mode_IPD:下拉输入
    • GPIO_Mode_IPU:上拉输入
    • GPIO_Mode_Out_OD:开漏输出
    • GPIO_Mode_Out_PP: 推挽输出

三. 代码逻辑及底层函数分析

  • 定义一个初始化GPIOA的结构体GPIO_InitStructure;
  • 初始化时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd函数解析
/**
  * @brief  Enables or disables the High Speed APB (APB2) peripheral clock.
  * @param  RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
  *   This parameter can be any combination of the following values:
  *     @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
  *          RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
  *          RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
  *          RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
  *          RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
  *          RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
  *          RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11     
  * @param  NewState: new state of the specified peripheral clock.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    RCC->APB2ENR |= RCC_APB2Periph;
  }
  else
  {
    RCC->APB2ENR &= ~RCC_APB2Periph;
  }
}

typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState; //FunctionalState是枚举类型起的别名,用于表示DISABLE/ENABLE两种状态

assert_param()断言函数解析
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the 
   Standard Peripheral Library drivers code */
/* #define USE_FULL_ASSERT    1 */

/* Exported macro ------------------------------------------------------------*/
#ifdef  USE_FULL_ASSERT

/**
  * @brief  The assert_param macro is used for function's parameters check.
             //assert_param宏用于检查函数的参数   
  * @param  expr: If expr is false, it calls assert_failed function which reports 
  *         the name of the source file and the source line number of the call 
  *         that failed. If expr is true, it returns no value.
            //如果表达式错误(有语法问题),调用assert_failed 函数,它会打印出
             //出错的源文件和发送错误的行号。如果表达式正确,不返回值。
  * @retval None
  */
  #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)
#endif /* USE_FULL_ASSERT */

这段函数主要表示:如果定义了USE_FULL_ASSERT,运行else上面的部分,其中assert_param(expr)作用是是,expr为真,运行(void)0;否则运行assert_failed((uint8_t *)__FILE__, __LINE__);如果没定义了USE_FULL_ASSERT,运行else中的#define assert_param(expr) ((void)0);


补充:

  1. 三目运算符:A ? B : C.A为真,执行B;为假,执行C

  2. (void)0 与 0的区别?

    (void)0 表示NULL/空。 //assert_param(expr) ((void)0)表示啥都不干

    0 表示一种状态,在判断中表示假;如果用于显示LED表示熄灭状态

  3. 内置宏:用于对各个不同操作系统兼容使用,相互之间可以调用(如)FILE, LINE);

GPIO_Init解析
//这一部分是官方给的函数说明
/**
  * @brief  Initializes the GPIOx peripheral according to the specified
  *         parameters in the GPIO_InitStruct.
  * @param  GPIOx: where x can be (A..G) to select the GPIO peripheral.
  * @param  GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that
  *         contains the configuration information for the specified GPIO peripheral.
  * @retval None
  */

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;
  /* Check the parameters */
//断言函数
  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));  
  
/*---------------------------- GPIO Mode Configuration -----------------------*/
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
//取出地址后四位
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
  { 
    /* Check the parameters */
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    /* Output mode */
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
//判断是否为输出模式,如果是,则检查配置的速度参数是否合法,
/*---------------------------- GPIO CRL Configuration ------------------------*/
  /* Configure the eight low port pins */
//配置低八位GPIO端口 GPIO0~GPIO7地址是从0x0001~0x0080

  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
  {
    tmpreg = GPIOx->CRL;
    //将tmpreg赋予寄存器CRL的初值
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)  //遍历GPIO0~GPIO3
    {
      pos = ((uint32_t)0x01) << pinpos;
      /* Get the port pins position */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
      if (currentpin == pos)  //表明已经遍历到了对应端口位
      {
        // pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
        pos = pinpos << 2;  

        /* Clear the corresponding low control register bits */
        /*以下两个句子,把控制这个引脚的 4 个寄存器位清零,其它寄存器位不变*/
        pinmask = ((uint32_t)0x0F) << pos; 
        tmpreg &= ~pinmask;

        /* Write the mode configuration in the corresponding bits */
        // 向寄存器写入将要配置的引脚的模式
        tmpreg |= (currentmode << pos); 

        /* Reset the corresponding ODR bit */
        // 判断是否为下拉输入模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
           /*下拉输入模式,引脚默认置 0,对 BRR 寄存器写 1 可对引脚置 0*/ 
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          /* Set the corresponding ODR bit */
          // 判断是否为上拉输入模式
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
              /*上拉输入模式,引脚默认值为 1,对 BSRR 寄存器写 1 可对引脚置 1*/
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }
/*---------------------------- GPIO CRH Configuration ------------------------*/
  /* Configure the eight high port pins */
//对GPIO8~15端口操作
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
    tmpreg = GPIOx->CRH;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
    //因为GPIO7的地址是0x08,相当于起始地址是0x08
      pos = (((uint32_t)0x01) << (pinpos + 0x08));  
      /* Get the port pins position */
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* Clear the corresponding high control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
        /* Set the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
    GPIOx->CRH = tmpreg;
  }
}

在这里插入图片描述

这个函数有 GPIOx 和 GPIO_InitStruct 两个输入参数,分别是 GPIO 外设指针和 GPIO初始化结构体指针。分别用来指定要初始化的 GPIO 端口及引脚的工作模式。要充分理解这个 GPIO 初始化函数,得配合我们刚刚分析的 GPIO 引脚工作模式真值表来看。

1)先取得 GPIO_Mode 的值,判断 bit4 是 1 还是 0 来判断是输出还是输入。如果是输出则设置输出速率,即加上 GPIO_Speed 的值,输入没有速率之说,不用设置。

2) 配置 CRL 寄存器。通过 GPIO_Pin 的值计算出具体需要初始化哪个引脚,算出后,然后把需要配置的值写入到 CRL 寄存器中,具体分析见代码注释。这里有一个比较有趣的是上/下拉输入并不是直接通过配置某一个寄存器来实现的,而是通过写BSRR 或者 BRR 寄存器来实现。这让很多只看手册没看固件库底层源码的人摸不着头脑,因为手册的寄存器说明中没有明确的指出如何配置上拉/下拉。

3)配置 CRH 寄存器过程同 CRL。

GPIO_ResetBits()函数解析
/**
  * @brief  Clears the selected data port bits.
  * @param  GPIOx: where x can be (A..G) to select the GPIO peripheral.
  * @param  GPIO_Pin: specifies the port bits to be written.
  *   This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
  * @retval None
  */
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  
  GPIOx->BRR = GPIO_Pin;
}

首先通过断言函数判断两个参数是否合理,然后将GPIO_Pin值写入BRR寄存器中,清除对应的位为0;
GPIO_SetBit函数解析
/**
  * @brief  Sets the selected data port bits.
  * @param  GPIOx: where x can be (A..G) to select the GPIO peripheral.
  * @param  GPIO_Pin: specifies the port bits to be written.
  *   This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
  * @retval None
  */
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  
  GPIOx->BSRR = GPIO_Pin;
}
首先通过断言函数判断两个参数是否合理,然后将GPIO_Pin值写入BSRR寄存器中,清除对应的位为1;

补充:

  1. 这里的BRR和BSRR寄存器分别是端口位清除寄存器和端口位设置/清除寄存器,这两个寄存器可以将每个端口或者说是引脚置高电平或低电平(可以联想51单片机中,对应的每一位想要点亮小灯时,引脚置高电平1时,LED熄灭;置低电平时,LED点亮的实例)。
  2. 上拉/下拉模式就是通过BRR和BSRR寄存器配置的,通过配置BRR寄存器全为0,配置出下拉模式;通过配置BSRR寄存器全为1,配置出上拉模式
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值