STM32教程第四节:自己编写库函数雏形(第三部分)

目录

前言

STM32第四节:自己写库——构建库函数雏形(第三节)

优化代码

定义初始化结构体 GPIO_InitTypeDef

枚举定义来限定结构体成员

调用GPIO_Init函数

 GPIO_InitTypeDef* GPIO_InitStruct结构体地址变量配置

     小结


前言

        上节课我们讲解了如何实现 RCC 这个外设的寄存器结构体声明,把时钟相关的代码改成寄存器结构体操作的方式,以及编写函数库(端口置位以及复位函数),包括防止重复定义框架·,具体代码以及主函数的更新还有main函数代码的解释。本课我们学习如何更加系统的优化程序以及运用STM32官方给出的标准库。


STM32第四节:自己写库——构建库函数雏形(第三节)


优化代码

        上节课我们编写了我们的固件库函数——置位和清除端口函数,而本节课我们继续完善代码的初始化部分。通过代码我们可知,在初始化模块中不管是二进制还是十六进制,都不能一下让我们看懂,必须返回到参考手册中去查看相关寄存器的解释。把原函数代码再次拷贝一份,将#elif的值改为1,原值改为0。

#elif 1
	//打开 GPIOB 端口的时钟
	RCC->APB2ENR |= ((1)<<3);
	
	//配置IO口为输出
	GPIOB->CRL &= ~((0x0f)<<(4*0));
	GPIOB->CRL |= ((1)<<(4*0));//*0,*1,*5
	
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);

        那么怎么优化代码呢?

定义初始化结构体 GPIO_InitTypeDef

        我们可以定义初始化结构体 GPIO_InitTypeDef。在第一节课的代码中定义位操作函数后,简化了控制GPIO输出电平的代码,但在控制GPIO输出电平前,还有一步是需要初始化GPIO引脚的各种模式,比如推挽输出模式。这部分代码涉及的寄存器有很多,我们希望Init GPIO也能以如此简单的方法去实现。为此,我们先根据 GPIO 初始化时涉及到的初始化参数以结构体的形式封装起来,声明一个名为 GPIO_InitTypeDef 的结构体类型。

typedef struct
{
	uint16_t GPIO_Pin;
	uint16_t GPIO_Speed;
	uint16_t GPIO_Mode;
}GPIO_InitTypeDef;

枚举定义来限定结构体成员

        其中,Pin为寄存器的io口(位数),Speed代表寄存器输出的速度,Mode代表寄存器输出的模式,均用uint16_t(short类型)定义。但是阅读相关手册我们发现,如果端口模式位为输出的话,就只有00,01,11这三种模式输出。可我们用short类型定义的话,就有65536位,为了不犯错,我们可以采用C语言中的枚举定义来限定结构体成员Speed(3)和Mode(8)的模式。

/**
	* GPIO工作速度枚举定义
	*/
typedef enum
{ 
  GPIO_Speed_10MHz = 1,         // 10MHZ        (01)b
  GPIO_Speed_2MHz,              // 2MHZ         (10)b
  GPIO_Speed_50MHz              // 50MHZ        (11)b
}GPIOSpeed_TypeDef;

/**
	* GPIO工作模式枚举定义
	*/
typedef enum
{ GPIO_Mode_AIN = 0x0,           // 模拟输入     (0000 0000)b
  GPIO_Mode_IN_FLOATING = 0x04,  // 浮空输入     (0000 0100)b
  GPIO_Mode_IPD = 0x28,          // 下拉输入     (0010 1000)b
  GPIO_Mode_IPU = 0x48,          // 上拉输入     (0100 1000)b
  
  GPIO_Mode_Out_OD = 0x14,       // 开漏输出     (0001 0100)b
  GPIO_Mode_Out_PP = 0x10,       // 推挽输出     (0001 0000)b
  GPIO_Mode_AF_OD = 0x1C,        // 复用开漏输出 (0001 1100)b
  GPIO_Mode_AF_PP = 0x18         // 复用推挽输出 (0001 1000)b
}GPIOMode_TypeDef;

调用GPIO_Init函数

        除此之外还有一个官方已经写好的函数GPIO_Init函数,有两个形参输入,无返回值。如下述代码所示,第一个形参写的是这种结构体的指针传进来是哪一个GPIO外设,第二个形参编写的一个GPIO_InitTypeDef结构体对应的指针参数,而这个结构体在之前的代码中已经写出来了。

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;
  
/*---------------------- GPIO 模式配置 --------------------------*/
  // 把输入参数GPIO_Mode的低四位暂存在currentmode
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
	
  // bit4是1表示输出,bit4是0则是输入 
  // 判断bit4是1还是0,即首选判断是输入还是输出模式
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
  { 
	// 输出模式则要设置输出速度
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
  // 配置端口低8位,即Pin0~Pin7
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
  {
	// 先备份CRL寄存器的值
    tmpreg = GPIOx->CRL;
		
	// 循环,从Pin0开始配对,找出具体的Pin
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
	 // pos的值为1左移pinpos位
      pos = ((uint32_t)0x01) << pinpos;
      
	  // 令pos与输入参数GPIO_PIN作位与运算,为下面的判断作准备
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
			
	  //若currentpin=pos,则找到使用的引脚
      if (currentpin == pos)
      {
		// pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
        pos = pinpos << 2;
       //把控制这个引脚的4个寄存器位清零,其它寄存器位不变
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
				
        // 向寄存器写入将要配置的引脚的模式
        tmpreg |= (currentmode << pos);  
				
		// 判断是否为下拉输入模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
		  // 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }				
        else
        {
          // 判断是否为上拉输入模式
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
		    // 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
		// 把前面处理后的暂存值写入到CRL寄存器之中
    GPIOx->CRL = tmpreg;
  }
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
  // 配置端口高8位,即Pin8~Pin15
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
		// // 先备份CRH寄存器的值
    tmpreg = GPIOx->CRH;
		
	// 循环,从Pin8开始配对,找出具体的Pin
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08));
			
      // pos与输入参数GPIO_PIN作位与运算
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
			
	 //若currentpin=pos,则找到使用的引脚
      if (currentpin == pos)
      {
		//pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
        pos = pinpos << 2;
        
	    //把控制这个引脚的4个寄存器位清零,其它寄存器位不变
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
				
        // 向寄存器写入将要配置的引脚的模式
        tmpreg |= (currentmode << pos);
        
		// 判断是否为下拉输入模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
		  // 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
         // 判断是否为上拉输入模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
		  // 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
	// 把前面处理后的暂存值写入到CRH寄存器之中
    GPIOx->CRH = tmpreg;
  }
}

 GPIO_InitTypeDef* GPIO_InitStruct结构体地址变量配置

        接下来就要在主函数中写入该GPIO_InitTypeDef* GPIO_InitStruct结构体地址变量,注意该变量要放在尽可能上面,然后打开时钟,进行代码的运行。具体步骤如下述代码所示:

#elif 1

	GPIO_InitTypeDef  GPIO_InitStructure;
	
	//打开 GPIOB 端口的时钟
	RCC->APB2ENR |= ((1)<<3);

	GPIO_SetBits(GPIOB,GPIO_Pin_0);
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);

        紧接着我们来配置该结构体中的具体变量,

        包括(Pin,Mode,Speed)然后再进行GPIO_Init函数的调用完成全部代码:

        在这里,由于之前已经配置了该结构体内容具体的模式代码,所以我们就可以直接输入我们所需要的代码定义就好。然后要注意的是,我们应该在第二个变量前加取地址符&,把地址引入变量中。我们观察这几行代码,如果我们用寄存器来读它们的话,很难读懂。但是我们如果用固件库编程的话,就很好修改且容易读懂。

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

     小结

         到这里我们就完成了所有的代码,具体所有的代码我以后会尽快放到下一个专栏,大家要记得订阅哦!本课我们学习如何更加系统的优化程序以及运用STM32官方给出的标准库,包括如何定义初始化结构体以及枚举定义的运用,还有官方给的GPIO_Init函数和如何配置结构体变量。下节课我们继续学习自己写库——构建库函数雏形的内容。

        创作不易,点个三连吧!!!

作者:Zhang-jk

物联沃分享整理
物联沃-IOTWORD物联网 » STM32教程第四节:自己编写库函数雏形(第三部分)

发表评论