- 外设寄存器结构体定义
- 构建库函数
- 定义GPIO_TypeDef
- 定义初始化结构体 GPIO_InitTypeDef
- 小知识
- include文件的一个不利之处在于一个头文件可能会被多次包含,为了说明这种错误,考虑下面的代码:
#include “x.h”
#include “x.h”
显然,这里文件x.h被包含了两次,没有人会故意编写这样的代码。但是下面的代码:
#include “a.h”
#include “b.h”
看上去没什么问题。如果a.h和b.h都包含了一个头文件x.h。那么x.h在此也同样被包含了两次,只不过它的形式不是那么明显而已。如果头文件被多次引用,会出现下列提示
error: #256: invalid redeclaration of type name “GPIO_TypeDef” (declared at line 42)
外设寄存器结构体定义
上一节中我们在操作寄存器的时候,操作的是都寄存器的绝对地址,如果每个外设寄存器都这样操作,那将非常麻烦。
我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占 32 个字节,这种方式跟结构体里面的成员类似。所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可 。
在工程中的“stm32f10x.h”文件中,我们使用结构体封装 GPIO 及 RCC 外设的的寄存器。结构体成员的顺序按照寄存器的偏移地址从低到高排列,成员类型跟寄存器类型一样。
stm32f10x.h
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18)//#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0x00)//#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0x04)//#define GPIOB_IDR *(unsigned int*)(GPIOB_BASE + 0x08)//#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0x0C)//#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE + 0x10)//#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE + 0x14)//#define GPIOB_LCKK *(unsigned int*)(GPIOB_BASE + 0x18)typedef 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 LCKK;}GPIO_TypeDef;//声明了一个结构体,结构体中有很多成员,#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE) //强制转换成结构体类型指针
构建库函数
在文件夹下新建gpio.c和.h文件
添加.h文件
双击Source Grouup,添加.c文件
stm32f10x_gpio.c
#include "stm32f10x_gpio.h"void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin){GPIOx->BSRR |= GPIO_Pin;}void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin){GPIOx->BRR |= GPIO_Pin;}
stm32f10x_gpio.h
把stm32f10x_gpio.c程序中定义的置位、复位函数的声明部分放到gpio.h中,还有GPIO的引脚号定义。
#ifndef __STM32F10X_GPIO_H#define __STM32F10X_GPIO_H#include "stm32f10x.h"/* GPIO引脚号定义*/#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)bvoid GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin) ; //置位函数声明,记得加;号void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin); //复位函数声明#endif /*__STM32F10X_GPIO_H */
main.c
#include"stm32f10x.h"#include"stm32f10x_gpio.h"int main(void){#if 0//打开GPIOB端口时钟*(unsigned int *)0x40021018 |= (1<<3);//配置IO口为输出*(unsigned int *)0x40010C00 |= ((1)<<(4*0));*(unsigned int *)0x40010C00 |= ((1)<<(4*1));*(unsigned int *)0x40010C00 |= ((1)<<(4*5));//配置ODR寄存器*(unsigned int *)0x40010C0C &= ~(1<<0);*(unsigned int *)0x40010C0C &= ~(1<<1);*(unsigned int *)0x40010C0C &= ~(1<<5);#elif 0RCC_APB2ENR |= (1<<3);GPIOB_CRL &= ~((0x0f)<<(4*0));//端口配置寄存器清零GPIOB_CRL |= (1<<(4*0));GPIOB_ODR &= ~(1<<0);#elif 1//打开GPIO端口B的时钟RCC->APB2ENR |= (1<<3);//配置IO口为输出GPIOB->CRL &= ~((0x0f)<<(4*0)); //端口配置寄存器清零GPIOB->CRL |= (1<<(4*0));//控制ODR寄存器//GPIOB->ODR &= ~(1<<0);//GPIOB->ODR |= (1<<0);GPIO_SetBits(GPIOB,GPIO_Pin_0);GPIO_ResetBits(GPIOB,GPIO_Pin_0);#endif}void SystemInit(void){//函数体为空,目的为骗过编译器不报错}
定义GPIO_TypeDef
定义初始化结构体 GPIO_InitTypeDef
详见零死角玩转STM32—指南者(野火)
文件地址:G:\开发板学习\野火指南者F103
小知识
头文件多次引用
include文件的一个不利之处在于一个头文件可能会被多次包含,为了说明这种错误,考虑下面的代码:
#include “x.h”
#include “x.h”
显然,这里文件x.h被包含了两次,没有人会故意编写这样的代码。但是下面的代码:
#include “a.h”
#include “b.h”
看上去没什么问题。如果a.h和b.h都包含了一个头文件x.h。那么x.h在此也同样被包含了两次,只不过它的形式不是那么明显而已。如果头文件被多次引用,会出现下列提示
error: #256: invalid redeclaration of type name “GPIO_TypeDef” (declared at line 42)
多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头文件,因此要发现重复包含并不容易。要解决这个问题,我们可以使用条件编译。如果所有的头文件都像下面这样编写:
#ifndef HEADERNAME_H **(前面加下滑线,是为了区别普通的头文件宏定义)**
#define __HEADERNAME_H
…
#endif
那么多重包含的危险就被消除了。当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H被定义为1。如果头文件被再次包含,通过条件编译,它的内容被忽略。符号_HEADERNAME_H按照被包含头文件的文件名进行取名,以避免由于其他头文件使用相同的符号而引起的冲突。
但是,你必须记住预处理器仍将整个头文件读入,即使这个头文件所有内容将被忽略。由于这种处理将托慢编译速度,所以如果可能,应该避免出现多重包含。
使用#ifndef只是防止了头文件被重复包含(其实本例中只有一个头件,不会存在重复包含的问题),但是无法防止变量被重复定义。
配置上拉输入/下拉输入

在CRL寄存器中,输入模式下的10为上拉/下拉输入模式,具体是上拉还是下拉由固件库软件实现。
实现方式:
配置GPIO端口工作模式时,在gpio.h中,对GPIO 工作模式枚举定义。
当bit5为1时,配置成下拉输入。下拉输入为复位,配置BRR(端口清除寄存器),如PB0为下拉输入,则使BRR的BR0为1,则PB0=0.
当bit6为1时,配置成上拉输入。下拉输入为置位,配置BSRR(端口设置/清除寄存器),如PB0为上拉输入,则使BRR的BR0为1,则PB0=1.
gpio.c中对应代码
// 判断是否为下拉输入模式if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD){// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0GPIOx->BRR = (((uint32_t)0x01) << pinpos);}else{// 判断是否为上拉输入模式if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU){// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1GPIOx->BSRR = (((uint32_t)0x01) << pinpos);}

C语言条件编译
C 语言条件编译1 /*2 * C 语言知识,条件编译3 * #if 为真4 * 执行这里的程序5 * #else6 * 否则执行这里的程序7 * #endif8 */

