【单片机/嵌入式】【梁山派】学习日志03:寄存器和库函数介绍

寄存器和库函数介绍

资料下载:

https://www.gd32mcu.com/cn/download/5?kw=GD32F4

https://www.gd32mcu.com/cn/download/6?kw=GD32F4

(1)下载GD32F4xx用户手册

 

(2)下载GD32F450xx数据手册

 

(3)下载GD32F4xx固件库使用指南

 

一、寄存器

【官方介绍】https://lckfb.com/docs/lckfb_lspi/#/004%E5%AF%84%E5%AD%98%E5%99%A8%E5%92%8C%E5%BA%93%E5%87%BD%E6%95%B0%E4%BB%8B%E7%BB%8D?id=_112%e5%ad%98%e5%82%a8%e5%99%a8%e6%98%a0%e5%b0%84%e8%a1%a8

1.认识存储器

存储器按使用类型可分为只读存储器(ROM)随机存储器(RAM)

存储器是许多存储单元的集合,主要是用来存储程序和各种数据信息的部件。

2.存储器映射

存储器本身是不具有地址的,是一块具有特定功能的内存单元,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就叫做存储区映射

给内存单元分配地址之后,就可以通过指针去操作内存地址

3.存储器映射表

GD32是一个32位的单片机,它的地址范围2的32次方,也就是4GB地址空间

2^32bit=2^(2+10+10+10)bit=4*1024*1024*1024bit

为了降低不同客户在相同应用时的软件复杂度,存储映射是按Cortex-M4处理器提供的规则预先定义的。在存储器映射表中,一部分地址空间由Arm Cortex-M4的系统外设所占用,且不可更改。其余部分地址空间可由芯片供应商定义使用。

关于存储映射表的内容,大家可以去查看用户手册的第38页。

 

8位16进制,即8*4=32位。

4.寄存器映射

寄存器是具有特定功能的内存单元,通过操作这些内存单元可以驱动外设工作

寄存器按功能又可分为指令寄存器地址寄存器数据寄存器,处理器可以使用相互独立的总线读取指令和加载/存储数据

 

程序存储器,数据存储器,寄存器和I / O端口都在同一个线性的4 GB的地址空间之内。

每一个寄存器都对应不同的功能,操作相应的寄存器就可以配置不同的功能

如果我们要控制某个外设工作,那我们可以找到这个单元的起始地址,然后通过c语言指针的方式来访问这些内存单元

但通常我们会给这个特殊的内存单元取一个名字,这个已经分配好地址有特定功能的内存单元别名的过程就叫寄存器映射,这个别名就是我们所说的寄存器。

寄存器重映射给寄存器再分配一个地址的过程叫做寄存器重映射。

 

5.总线基地址

系统不同模块间的通信总线

总线的工作方式就是将数据和地址从一个外设搬到另一个外设上

片上外设区域分为三条总线,分别为AHB总线、APB1总线和APB2总线。

AHB总线最高时钟可达200MHZ;

APB1总线最高时钟可达50MHZ;

APB2总线时钟最高可达100MHZ。

根据外设速度的不同,不同的总线挂载着不同的外设

总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址

关于总线上挂载的外设更多详细信息请查看数据手册的第11

 

总线基地址和地址范围如下

 

6.外设基地址

每个总线上都挂载着很多外设,这些外设也都有自己的地址范围。

关于GPIO这个外设的地址映射信息如下表所示。

7.外设寄存器地址

在外设的地址范围内,分布着该外设的寄存器。

以GPIO外设为例,GPIO外设地址范围内有很多个寄存器,每一个都有特定的功能通过操作对应的寄存器来配置GPIO的功能(按照芯片手册,向其中输入不同功能对应的数值)每个寄存器都为32位,占4个字节,这里我们以GPIOA端口的寄存器进行介绍。

根据不同外设,可设置相对于基地址的地址偏移。

8.如何操作寄存器

如果如我们想让GPIOA端口的16个引脚都置1,我们需要去配置端口输出寄存器GPIOx_OCTL。通过查找用户手册177页可以知道这个寄存器的地址偏移量为0x14,GPIOA端口的基地址为0x4002 0000,所以GPIOA_OCTL寄存器的地址为0x4002 0000 + 0x14 = 0x4002 0014,那我们就是对这个地址进行操作。操作如下:

 

通过上图可以了解到要想使能所有引脚配置为1,只需要将OCTLy(y=0..15)对应的位置1即可。也就是配置GPIOA_OCTL寄存器的高16位为0(高位保留),低16位为1(进行配置),换成十六进制就是0x0000FFFF

8.1通过绝对地址访问内存单元

说明:

(unsigned int*)的作用是将0x40020014这个立即数强制类型转化为无符号整形地址,告诉编译器这是一个地址。

(unsigned int)即(0x40020014)相当于绝对地址,也就是对这个地址对应的内存空间的值,(unsigned int*)(0x40020014) = 0xFFFF;就是相当于对0x40020014这个内存空间赋值为0xFFFF。

8.2通过别名访问内存单元

通过上面的方式确实可以对寄存器地址进行操作,但是操作起来很麻烦,用户也不能清晰的明白这个地址对应的功能。

如果我们给每个地址都起一个名字,这样看到名字就知道这个地址对应什么功能.

二、库函数

2.1为什么要使用库函数

从上一节我们了解到如何去用寄存器驱动外设,但我们也同时了解到GD32的寄存器数量非常多,这么多的寄存器光是定义就需要花费很多的时间,更不用说还要去查找对应的功能,找到对应的地址,然后配置需要的值,这在难度和时间上都是不可取的。为此,库函数就在这种情况下应运而生,库函数能使我们的开发效率大大提高

关于GD32的标准外设库函数,GD32的官方已经给我们开发好了,我们只需要移植到我们的工程使用即可。库函数的使用不需要让我们去了解硬件的机制只需要根据需要的功能去查找对应的函数,然后调用即可,大大降低了开发要求。

2.2库函数简单介绍

从前面的章节可以知道创建的工程模板里面有GD32F4的标准外设库,我们可以打开其中的gd32f4xx_gpio.hgd32f4xx_gpio.c文件进行查看,也可以参考GD32F4xx的固件库使用手册进行参考。

打开模板工程,添加固件库

打开路径,选择文件

Template\Firmware\GD32F4xx_standard_peripheral\Source

点击编译,发现报错了:缺乏rcu(与时钟相关的程序)

在同样的固件库目录下找到rcu文件,并添加进来。再次编译,即可通过。

添加成功

 

打开函数说明(函数头文件.h)

打开头文件,拖到代码的最底下部分,有大量的库函数封装说明

打开固件库手册,找到GPIO其中一个库函数,如下

2.3如何操作库函数

让GPIOA端口的16个引脚都置1,那么使用库函数该如何配置呢?首先,我们知道我们操作的功能是关于GPIO的,那么首先要从GPIO外设库中去寻找。打开gd32f4xx_gpio.h头文件,拉到最下面可以看到所有声明的有关GPIO的API(Application Programming Interface,程序间接口)函数。我们是让GPIOA的16个端口都置1,也就是对端口进行操作,我们寻找对应的功能函数,从函数名可以知道gpio_port_write这个函数是对端口进行写数据操作,我们对16个引脚全部置1不就是相当于对GPIOA这个端口写入数据0xFFFF吗。可见这个功能函数是我们需要的。

根据之前查找固件库手册获悉的函数原型:

void gpio_port_write(uint32_t gpio_periph,uint16_t data);

所以可用如下语句进行配置:

具体实现:

首先是调用gpio_port_write(GPIOA,0xFFFF);

我们进入gpio_port_write函数看一下,这个函数下只写了一行代码GPIO_OCTL(gpio_periph) = (uint32_t)data;

把我们的参数带进入,即为GPIO_OCTL(GPIOA) = (uint32_t)0xFFFF;那我们再看一下GPIO_OCTL(GPIOA)是个什么东西,通过跳转可以知道有这么一个宏定义#define GPIO_OCTL(gpiox) REG32((gpiox) + 0x14U),带入参数即为#define GPIO_OCTL(GPIOA) REG32((GPIOA) + 0x14U)。

把GPIOA的地址带入即为REG32(0x40020014) = (uint32_t)0xFFFF。

在gd32F4xx.h头文件中有这么一个宏定义#define REG32(addr) (*(volatile uint32_t )(uint32_t)(addr))

把REG32(addr) 替换掉就是 ((volatile uint32_t *)(uint32_t)(0x40020014)) = (uint32_t)0xFFFF;看到这个是不是很熟悉了,这不就是我们上一节用寄存器去配置GPIOA端口16个引脚全部置1的例子吗。

说白了,其实库函数就是在寄存器的基础上又封装了一层,使操作起来更简单,最后还是通过寄存器来实现的

三、寄存器和库函数的区别

(1)寄存器更能理解原理,更直观,库函数相对来说屏蔽底层,直接面向应用

(2)使用库函数较寄存器代码量会增大,库函数会把所有情况都考虑到函数里,有时会造成代码的冗余

(3)库函数使用起来相对简单,容易上手,可快速开发应用,大大提高效率。

(4)寄存器占用内存少,速度快,在资源有限或者要求执行速度的情况下寄存器是一个不错的选择。

posted @ 2022-11-03 23:04  U羊U  阅读(723)  评论(0编辑  收藏  举报