当前位置:网站首页>构建库函数的雏形——参照野火的手册
构建库函数的雏形——参照野火的手册
2022-07-06 02:12:00 【Fecter11】
这部分我没按照视频进行(视频中手法流畅节奏快,对我这种慢节奏的略显吃力),所以这里我选择以野火的教学手册进行学习。
自己构建库函数的雏形
定义外设寄存器结构体
我们在操作寄存器的时候,操作的是都寄存器的绝对地址,如果每个外设寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏
移地址,都是在外设基地址上逐个连续递增的,每个寄存器占 32 个字节,这种方式跟结构体里面的成员类似。所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址
(比如:
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 LCKR;
}GPIO_TypeDef;
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)
),
这个结构体的成员就是外设下的所有寄存器,成员的排列顺序跟寄存器的顺序一样。
这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,操作结构体的成员就是直接配置寄存器。
那么首先就是结构体的定义:
#define __IO volatile//volatile 表示易变的变量
typedef struct //定义一个结构体,GPIO的存器部分
{
uint32_t CRL;// 端口配置低寄存器, 地址偏移 0X00
uint32_t CRH;// 端口配置高寄存器, 地址偏移 0X04
uint32_t IDR;// 端口数据输入寄存器, 地址偏移 0X08
uint32_t ODR;// 端口数据输出寄存器, 地址偏移 0X0C
uint32_t BSRR;// 端口位设置/清除寄存器,地址偏移 0X10
uint32_t BRR;// 端口位清除寄存器, 地址偏移 0X14
uint32_t LCKR;// 端口配置锁定寄存器, 地址偏移 0X18
}GPIO_TypeDef; //这是结构体类型名GPIO_TypeDef
typedef struct//定义一个结构体,RCC的存器部分
{
uint32_t CR;//时钟控制寄存器 偏移地址0x00
uint32_t CFGR;// 时钟配置寄存器 地址偏移 0X04
uint32_t CIR;// 时钟中断寄存器, 地址偏移 0X08
uint32_t APB2RSTR;// APB2外设复位寄存器, 地址偏移 0X0C
uint32_t APB1RSTR;// APB1外设复位寄存器,地址偏移 0X10
uint32_t AHBENR;// AHB外设时钟使能寄存器, 地址偏移 0X14
uint32_t APB2ENR;// APB2外设时钟使能寄存器, 地址偏移 0X18
uint32_t APB1ENR;// APB1外设时钟使能寄存器, 地址偏移 0X1C
uint32_t BDCR ;//备份域控制寄存器, 地址偏移 0X20
uint32_t CSR;// 控制/状态寄存器, 地址偏移 0X24
}RCC_TypeDef;//结构体的类型名RCC_TypeDef
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)//先使用结构体指针指向我们的外设基地址,然后使用宏定义重新命名
这段代码在每个结构体成员前增加了一个“__IO”前缀,它的原型在这段代码的第一行,代表了 C 语言中的关键字“volatile”,在 C 语言中该关键字用于表示变量是易变的,要求编译器不要优化。
这些结构体内的成员,都代表着寄存器,而寄存器很多时候是由外设或 STM32 芯片状态修改的,也就是说即使 CPU 不执行代码修改这些变量,变量的值也有可能被外设修改、更新,所以每次使用这些变量的时候,我们都要求 CPU 去该变量的地址重新访问。
若没有这个关键字修饰,在某些情况下,编译器认为没有代码修改该变量,就直接从 CPU 的某个缓存获取该变量值,这时可以加快执行速度,但该缓存中的是陈旧数据,与我们要求的寄存器最新状态可能会有出入。
外设存储器映射
回看上面封装好的寄存器的结构体,我们应该能意识到。在操作寄存器的前提是找到外设的地址。所以我们仍然需要使用宏定义去重新命名。
//首先是片内外设BLOCK2的起始地址
# define PERIPH_BASE ((unsigned int)0x40000000) //如果直接写0x4000 0000那编译器并不会将这个数当做地址,所以我们使用强制类型转化(unsigned int)
//我们门查看参考手册值到GPIO都是挂在APB2这一条总线上的
//我们用BLOCK2的起始地址加上偏移量就能找到总线基地址
# define APB2PERIPH_BASE (PERIPH_BASE + 0x10000 )//通过加上偏移量,就可以得到总线APB2的基地址
//操作GPIO的话还需要配置RCC时钟寄存器,而RCC是挂接在AHB总线上的,这里我们将DMA1的地址作为AHB的总线基地址来使用,偏移量就是0x00020000
# define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
//接下来就需要配置外设地址
//GPIO这些端口都是在APB2这条总线上的
//只需要直接加上他们的偏移量就可以
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
//RCC外设基地址
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
外设声明
定义好外设寄存器结构体,并实现完外设存储器映射后,我们再把外设的基地址强制类型转换成相应外设的寄存器结构体指针,然后再把该指针声明成外设名,这样一来,外设名就跟外设的地址对应起来了,而且该外设名还是一个该外设类型的寄存器结构体指针,通过该指针可以直接操作该外设的全部寄存器。
上面这段文字简单来说就是,将创建好的寄存器结构体,通过结构体指针的形式与外设基地址关联起来,然后使用宏定义重新命名。
// GPIO 外设声明
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
// RCC 外设声明
#define RCC ((RCC_TypeDef *) RCC_BASE)
/*RCC 的 AHB1 时钟使能寄存器地址,强制转换成指针*/
//efine RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18)//0x18是时钟使能寄存器的偏移量
我们回到main.c文件里
/* * C 语言知识,条件编译 * #if 为真 * 执行这里的程序 * #else * 否则执行这里的程序 * #endif */
// 使用寄存器结构体指针点亮 LED
int main(void)
{
#if 0 // 直接通过操作内存来控制寄存器
// 开启 GPIOB 端口时钟
RCC_APB2ENR |= (1<<3);
//清空控制 PB0 的端口位
GPIOB_CRL &= ~( 0x0F<< (4*0));
// 配置 PB0 为通用推挽输出,速度为 10M
GPIOB_CRL |= (1<<4*0);
// PB0 输出 低电平
GPIOB_ODR |= (0<<0);
while (1);
#else // 通过寄存器结构体指针来控制寄存器
// 开启 GPIOB 端口时钟
RCC->APB2ENR |= (1<<3); //IO端口B时钟开启
//清空控制 PB0 的端口位
GPIOB->CRL &= ~( 0x0F<< (4*0));
// 配置 PB0 为通用推挽输出,速度为 10M
GPIOB->CRL |= (1<<4*0);
// PB0 输出 低电平
GPIOB->ODR |= (0<<0);
while (1);
#endif
}
定义位操作函数
在“stm32f10x_gpio.c”文件定义两个位操作函数,分别用于控制引脚输出高电平和低电平。
回顾上面的代码中分别定义了GPIO所对应的结构体,另外还通过宏定义的方式实现存储器的映射(就是通过宏定义与外设地址相关联)。
而我们接下来目的是为了将这两部分封装在一个函数里面。
先看一下代码
//首先是置位函数
/* 函数功能:设置引脚为高电平 参数说明:GPIOx:该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址 GPIO_Pin:选择需要设置的GPIO端口的引脚,我们使用的宏定义GPIO_Pin_0-15 表示GPIOx端口的0-15号引脚。 */
void GPIO_SetBits(GPIO_TypeDef * GPIOx,uint16_t GPIO_pin) //GPIO置位函数。形参包括端口(外设),以及16个io(BSRR的寄存器位,此处置1拉高。操作的是BSy)
{
//置位我们使用的是端口位设置/清除寄存器BSRR。
/* 设置GPIOx的BSRR寄存器的第GPIO_pin 位,使其输出高电平。 宏GPIO_Pin只是对应位位1,其他位都是0,可以直接赋值,也可以使用或等于 */
GPIOx->BSRR |= GPIO_pin; //GPIOx的BSRR,复位值是0x00000000
//GPIOx->BSRR = GPIO_pin;
}
//然后是复位函数
/* 函数功能:设置引脚为低电平 参数说明:GPIOx:该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址 GPIO_Pin:选择要设置的GPIO端口引脚,可输入宏GPIO_Pin_0-15,表示GPIOx端口的0-15号引脚。 */
void GPIO_ResetBits(GPIO_TypeDef * GPIOx,uint16_t GPIO_pin) //GPIO复位函数。形参包括端口(外设),以及16个io(BRR的寄存器位,置1清零)
{
GPIOx->BRR |= GPIO_pin; //这里操作的BRR寄存器,BRR寄存器的特点是置1就会清除对应的ODR位为0。复位值是0x00000000
/* 设置GPIOx端口BRR寄存器的第GPIO_Pin位,使其输出低电平。 宏GPIO_Pin只是对应的位是1,其他位为0,所以可以直接赋值。 /* }
我们现在会看上面两个函数,其主要目的是对GPIOx的寄存器BSRR或者BRR寄存器进行赋值,从而操作引脚输出高电平或者低电平。操作BSRR或着BRR可以实现单独操作某一位。
GPIOx是一个指针变量,通过函数的输入参数我们可以修改他的值,比如赋值GPIOA,GPIOB,GPIOC等等结构体指针的值,而这个函数就可控制这些端口输出。
边栏推荐
- sql表名作为参数传递
- Genius storage uses documents, a browser caching tool
- Paper notes: limit multi label learning galaxc (temporarily stored, not finished)
- [Clickhouse] Clickhouse based massive data interactive OLAP analysis scenario practice
- PHP campus movie website system for computer graduation design
- NumPy 数组索引 切片
- The ECU of 21 Audi q5l 45tfsi brushes is upgraded to master special adjustment, and the horsepower is safely and stably increased to 305 horsepower
- Prepare for the autumn face-to-face test questions
- 一题多解,ASP.NET Core应用启动初始化的N种方案[上篇]
- Computer graduation design PHP animation information website
猜你喜欢
Prepare for the autumn face-to-face test questions
MySQL lethal serial question 1 -- are you familiar with MySQL transactions?
Use the list component to realize the drop-down list and address list
02. Go language development environment configuration
Redis如何实现多可用区?
[community personas] exclusive interview with Ma Longwei: the wheel is not easy to use, so make it yourself!
Social networking website for college students based on computer graduation design PHP
Minecraft 1.18.1、1.18.2模组开发 22.狙击枪(Sniper Rifle)
PHP campus financial management system for computer graduation design
Know MySQL database
随机推荐
How to set an alias inside a bash shell script so that is it visible from the outside?
更改对象属性的方法
Virtual machine network, networking settings, interconnection with host computer, network configuration
Kubernetes stateless application expansion and contraction capacity
D22:indeterminate equation (indefinite equation, translation + problem solution)
The ECU of 21 Audi q5l 45tfsi brushes is upgraded to master special adjustment, and the horsepower is safely and stably increased to 305 horsepower
2022年版图解网络PDF
Use image components to slide through photo albums and mobile phone photo album pages
Genius storage uses documents, a browser caching tool
The ECU of 21 Audi q5l 45tfsi brushes is upgraded to master special adjustment, and the horsepower is safely and stably increased to 305 horsepower
Formatting occurs twice when vs code is saved
Global and Chinese markets of nasal oxygen tubes 2022-2028: Research Report on technology, participants, trends, market size and share
leetcode-2.回文判断
The intelligent material transmission system of the 6th National Games of the Blue Bridge Cup
Minecraft 1.16.5 biochemical 8 module version 2.0 storybook + more guns
Leetcode3. Implement strstr()
Know MySQL database
正则表达式:示例(1)
How does redis implement multiple zones?
Sword finger offer 12 Path in matrix