自己写库—构建库函数雏形

摘要:
构建库函数的过程是定义寄存器结构,编写端口设置重置函数,编写GPIO初始化结构和初始化函数的摘要,以及如何提高程序的可移植性。在上一篇文章中,我们写过使用寄存器映射来实现小灯的照明,但我们发现,每次我们都要查看参考手册来查找寄存器的内存地址并定义别名,这肯定非常麻烦。所以我们需要编写端口设置和重置函数来快速赋值。写入端口重置功能。我们创建一个新的头文件stm32f10x_ Gpio。h和stm32f10x_ gpio。c文件用于设置和寻址。

构建库函数的过程

  1. 寄存器结构体定义
  2. 编写端口置位复位函数
  3. 编写GPIO初始化结构体和初始化函数
  4. 总结及如何提高程序的可移植性

我们上篇文章写到用寄存器映射,实现点亮小灯,但是我们发现每次我们都要查看参考手册找到寄存器的内存地址并且定义别名,这样必然很麻烦。

      我们可以义一个结构体,结构体中内容与外设中寄存器的排列顺序是一样的(外设中的寄存器的偏移地址正好为4个字节递增的,我们按递增的顺序在结构体中起别名定义4个字节外设的寄存器,这样就实现了一一对应),这是我们只要将外设基址强制类型转换为结构体类型的指针,这样这个指针就指向结构体定义的那块内存单元。

过程:

寄存器结构体定义实现点亮

头文件:stm32f10x.h

#ifndef __STM32F10X_H
#define __STM32F10X_H

#define  PERIPH_BASE               ((unsigned int)0x40000000)
#define  APB1PERIPH_BASE           PERIPH_BASE
#define  APB2PERIPH_BASE          (PERIPH_BASE + 0x10000)
#define  AHBPERIPH_BASE           (PERIPH_BASE + 0x20000)

#define  RCC_BASE                (AHBPERIPH_BASE + 0x1000) //RCC的基地址
#define  GPIOC_BASE              (APB2PERIPH_BASE + 0x1000)//GBIOC的基地址

ypedef unsigned int      uint32_t;
typedef unsigned short    uint16_t;

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


typedef struct
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;


#define GPIOC   ((GPIO_TypeDef*)GPIOC_BASE)  //指针 重定义
#define RCC     ((RCC_TypeDef*)RCC_BASE)//指针 重定义

#endif

main.c函数中

#include "stm32f10x.h"
int main (void)
{
       // 打开 GPIOB 端口的时钟
       RCC->APB2ENR|=  ( 1 << 4 );
      //配置PC2 IO口为通用推挽输出,速度为10M
       GPIOC->CRL&=~ ( 0x0f << (4*2) );//GPIOC IO 4位清空
       GPIOC->CRL|=  ( 1 << (4*2) );// 通用推挽输出,速度为10M
      // 控制 ODR 寄存器
       GPIOC->ODR&= ~(1<<2);//开
      // GPIOC->ODR|= (1<<2);  //关
}

我们通过上面的代码可以实现点亮小灯,但是我们发现再给相应的寄存器赋值时,使用左移的这种方法显然不怎么方便每次都要查询参考手册,查看寄存器的定义结构才能进行相应的赋值。所有我们要编写端口置位和复位函数来快捷进行赋值。

编写端口置位复位函数

我们新建一个头文件 stm32f10x_gpio.h和stm32f10x_gpio.c文件用于置位和址位。

stm32f10x_gpio.h的内容:

#ifndef __STM32F10X_GPIO_H
#define __STM32F10X_GPIO_H
#include "stm32f10x.h"

#define GPIO_Pin_0    ((uint16_t)0x0001)  /*!< 选择Pin0 */    //(00000000 00000001)b
#define GPIO_Pin_1    ((uint16_t)0x0002)  /*!< 选择Pin1 */    //(00000000 00000010)b
#define GPIO_Pin_2    ((uint16_t)0x0004)  /*!< 选择Pin2 */    //(00000000 00000100)b
#define GPIO_Pin_3    ((uint16_t)0x0008)  /*!< 选择Pin3 */    //(00000000 00001000)b
#define GPIO_Pin_4    ((uint16_t)0x0010)  /*!< 选择Pin4 */    //(00000000 00010000)b
#define GPIO_Pin_5    ((uint16_t)0x0020)  /*!< 选择Pin5 */    //(00000000 00100000)b
#define GPIO_Pin_6    ((uint16_t)0x0040)  /*!< 选择Pin6 */    //(00000000 01000000)b
#define GPIO_Pin_7    ((uint16_t)0x0080)  /*!< 选择Pin7 */    //(00000000 10000000)b

#define GPIO_Pin_8    ((uint16_t)0x0100)  /*!< 选择Pin8 */    //(00000001 00000000)b
#define GPIO_Pin_9    ((uint16_t)0x0200)  /*!< 选择Pin9 */    //(00000010 00000000)b
#define GPIO_Pin_10   ((uint16_t)0x0400)  /*!< 选择Pin10 */   //(00000100 00000000)b
#define GPIO_Pin_11   ((uint16_t)0x0800)  /*!< 选择Pin11 */   //(00001000 00000000)b
#define GPIO_Pin_12   ((uint16_t)0x1000)  /*!< 选择Pin12 */   //(00010000 00000000)b
#define GPIO_Pin_13   ((uint16_t)0x2000)  /*!< 选择Pin13 */   //(00100000 00000000)b
#define GPIO_Pin_14   ((uint16_t)0x4000)  /*!< 选择Pin14 */   //(01000000 00000000)b
#define GPIO_Pin_15   ((uint16_t)0x8000)  /*!< 选择Pin15 */   //(10000000 00000000)b
#define GPIO_Pin_All  ((uint16_t)0xFFFF)  /*!< 选择全部引脚*/ //(11111111 11111111)b

void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits( GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin );


#endif

stm32f10x_gpio.c的内容:

#include "stm32f10x_gpio.h"

void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
   //BSRR的低16位 0-15 用于给相应的GPIOx的0-15个引脚置位
   //我们宏定义GPIO_Pin0-15对应的值
   GPIOx->BSRR |= GPIO_Pin; 
}
void GPIO_ResetBits( GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin )
{
    //BRR的低16位 0-15用于给相应的GPIOx的0-15个引脚清除
    GPIOx->BRR |= GPIO_Pin;
}

main函数中:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
int main (void)
{
       // 打开 GPIOB 端口的时钟
       RCC->APB2ENR|=  ( 1 << 4 );
      //配置PC2 IO口为通用推挽输出,速度为10M
       GPIOC->CRL&=~ ( 0x0f << (4*2) );//GPIOC IO 4位清空
       GPIOC->CRL|=  ( 1 << (4*2) );// 通用推挽输出,速度为10M
      // 控制 ODR 寄存器
          //GPIOC->ODR&= ~(1<<2);//开
          // GPIOC->ODR|= (1<<2);  //关
    GPIO_SetBits(GPIOC,GPIO_Pin_2);
    //GPIO_ResetBits(GPIOC,GPIO_Pin_2);

}

 

编写GPIO初始化结构体和初始化函数

     定义位操作函数后,控制 GPIO 输出电平的代码得到了简化,但在控制 GPIO 输出电平前还需要初始化 GPIO 引脚的各种模式(例如GPIOC->CRL&=~ ( 0x0f << (4*2) );我们不查看参考手册很难知道1<<4是什么意思),这部分代码涉及的寄存器有很多,我们希望初始化 GPIO 也能以如此简单的方法去实现。为此,我们先根据 GPIO 初始化时涉及到的初始化参数以结构体的形式封装起来,声明一个名为 GPIO_InitTypeDef 的结构体类型。

     这个结构体中包含了初始化 GPIO 所需要的信息,包括引脚号、工作模式、输出速率。设计这个结构体的思路是:初始化 GPIO 前,先定义一个这样的结构体变量,根据需要配置 GPIO 的模式,对这个结构体的各个成员进行赋值,然后把这个变量作为GPIO 初始化函数”的输入参数,该函数能根据这个变量值中的内容去配置寄存器,从而实现 GPIO 的初始化。

stm32f10x_gpio.h继续添加内容,添加内容为:

typedef enum
{  //枚举类型 后边是,而不是;
  GPIO_Speed_10MHz = 1,         // 10MHZ        (01)b
  GPIO_Speed_2MHz,              // 2MHZ         (10)b
  GPIO_Speed_50MHz              // 50MHZ        (11)b
}GPIOSpeed_TypeDef;

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;

typedef struct
{
	uint16_t GPIO_Pin;
	uint16_t GPIO_Speed;
	uint16_t GPIO_Mode;
}GPIO_InitTypeDef;
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

    

       上面定义的结构体很直接,美中不足的是在对结构体中各个成员赋值实现某个功能时,还需要查询手册的寄存器说明,我们不希望每次用到的时候都要去查询手册, 我们可以使用 C 语言中的枚举定义功能,根据手册把每个成员的所有取值都定义好。 GPIO_Speed 和 GPIO_Mode 这两个成员对应的寄存器是 CRL 和 CRH 这两个端口配置寄存器。

        关于这两个枚举类型的值如何跟端口控制寄存器里面的说明对应起来,我们简单分析下。有关速度的枚举类型有(01)b 10MHZ、 (10)b 2MHZ 和(11)b 50MHZ,这三个值跟寄存器说明对得上,很容易理解。至于模式的枚举类型的值理解起来就比较绕,这让很多人费了脑筋,下面我们通过一个表格来梳理下,好帮助我们理解,具体见图 9-6.1

      如果但从这些枚举值的十六进制来看,很难发现规律,转化成二进制之后,就比较容易发现规律。 bit4 用来区分端口是输入还是输出, 0 表示输入, 1 表示输出, bit2 和 bit3 对应寄存器的 CNFY[1:0]位,是我们真正要写入到 CRL 和 CRH 这两个端口控制寄存器中的值。bit0 和 bit1 对应寄存器的 MODEY[1:0]位,这里我们暂不初始化在 GPIO_Init()初始化函数中用来跟 GPIOSpeed 的值相加即可实现速率的配置。有关具体的代码分析见 GPIO_Init()库函数。 其中在下拉输入和上拉输入中我们设置 bit5 和 bit6 的值为 01 和 10 来以示区别。

      如果不使用枚举类型,仍使用“uint16_t”类型来定义结构体成员,那么成员值的范围就是 0-255,而实际上这些成员都只能输入几个数值。所以使用枚举类型可以对结构体成员起到限定输入的作用,只能输入相应已定义的枚举值。

stm32f10x_gpio.c继续添加内容,添加内容为:

 定义 GPIO 初始化函数 :对初始化结构体赋值后,把它输入到 GPIO 初始化函数,由它来实现寄存器配置。 




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;
  }
}

 

自己写库—构建库函数雏形第2张

 

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

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

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

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

main.c中的内容:

void Delay(uint32_t count)
{
	for( ; count !=0; count-- );
}
int main (void)
{



        RCC->APB2ENR  |=  ( (1) << 4 );

	GPIO_InitStructure.GPIO_Pin = LED_G_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(LED_G_GPIO_PORT, &GPIO_InitStructure); //GPIO_InitStructure地址的内容传递进去



        while(1)
      {
		GPIO_SetBits(GPIOC,GPIO_PIN_2);
		Delay(0xFFFF);
		GPIO_ResetBits(GPIOC,GPIO_PIN_2);
		Delay(0xFFFF);
      }
}

为了提高程序的可以移植性,我们可以继续这样宏定义(main.c中):

#define   LED_G_GPIO_PORT                   GPIOB
 #define   LED_G_GPIO_PIN                    GPIO_Pin_0
Technorati Tags:

将 GPIO_SetBits和 GPIO_ResetBits的参数进行替换。

 

免责声明:文章转载自《自己写库—构建库函数雏形》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇一种机械延时防盗锁(已申请专利专利号:201110153992)初学Oracle下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

人工智能深度学习入门练习之(5)求和

代码实现: importtensorflow.compat.v1 as tf tf.disable_v2_behavior() #使用静态图模式运行以下代码 assert tf.__version__.startswith('2.') #1.创建计算图阶段#创建2个输入端子,指定类型和名字 a_ph = tf.placeholder(tf.float32...

Golang 语言中的内置函数 make 和 new

Golang 语言中的内置函数 make 和 new 都是用作变量初始化,但是它们初始化变量的方式不同。关于它们之间的区别,我们可以简述为 make 返回类型是引用类型,new 返回类型是指针类型。本文我们首先分别介绍二者,然后再介绍二者的区别。02内置函数 make关于内置函数 make,官方的介绍是 make 内置函数仅用作分配内存空间并初始化 slic...

MySQL的安装、配置文件和初始化

MySQL基于源代码的安装 本文以MySQL5.1.73+Centos6.5作为安装演示,其他版本安装方法大同小异。首先下载MySQL5.1.73的源代码包。STEP01 创建MySQL运行期用户和用户组,当前步骤也可以在安装成功之后做: [root@localhost objs]# groupadd mysql [root@localhost objs]...

C++ 中的 const、引用和指针的深入分析

1,关于 const 的疑问: 1,const 什么时候为只读变量,什么时候是常量; 1,const 从 C 到 C++ 进化的过程中得到了升级,const 在 C++ 中不仅仅像在 C 中声明一个只读变量,其在 C++ 中完全可以得到一个常量; 2,const 常量的判别准则: 1,只有用字面量初始化的 const 常量才会进入符号表; 1,这里是字面量...

pthread 的几个结构体

http://blog.csdn.net/yangzhongxuan/article/details/7397139 /* Copyright (C) 2002,2003,2004,2005,2006,2007 Free Software Foundation, Inc.This file is part of the GNU C Library.Cont...

03课堂问题总结归纳

1、早期我们经常这样定义变量“int value=100;”,而前面的示例中这样定义变量“MyClass obj=new MyClass();”,这两种方式定义的变量是一样的吗? 不一样,前者为原始数据类型;后者为引用类型的变量,又简称对象变量。当声明一个对象的变量时,实际上并没有创建一个对象,此变量=null,定义一个原始数据类型时的变量时会马上给其分配内...