- 外设寄存器结构体定义
- 构建库函数
- 定义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)b
void 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 0
RCC_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可对引脚置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);
}
C语言条件编译
C 语言条件编译
1 /*
2 * C 语言知识,条件编译
3 * #if 为真
4 * 执行这里的程序
5 * #else
6 * 否则执行这里的程序
7 * #endif
8 */