文章目录

  • 一、地址和寄存器映射
  • 二、GPIO地址映射
  • 1.总线基地址
  • 2. 外设基地址
  • 3.外设寄存器
  • 三、C 语言对寄存器的封装
  • 1.封装总线和外设基地址
  • 2.封装寄存器列表
  • 四、GPIO端口的初始化及相关原理
  • 1.时钟配置
  • 2.输入输出模式设置与最大速率设置
  • 3.控制引脚输出电平
  • 五、实现过程
  • 1.准备材料
  • 2.创建工程
  • 3.main.c具体代码
  • 1)C语言编程实现流水灯
  • 2)汇编语言实现流水灯
  • 4.烧录
  • 5.接线
  • 六、实验效果
  • 七、总结
  • 八、参考资料


一、地址和寄存器映射

在STM32F103芯片中,包括外设在内的被控单元如FLASH、RAM等功能部件件共同排列在一个4GB的地址空间内,我们在编程的时候可以通过他们的地址找到他们,然后操作他们进行数据的读和写。

存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射,具体见下图。

存储器映射图

stm32g0 浮点运算时间_1024程序员节


GPIO(General-purpose input/output)是通用输入输出端口的简称,位于储存器Block2的APB2总线外设上,是 STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电平。芯片架构简图

stm32g0 浮点运算时间_stm32_02


在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

二、GPIO地址映射

1.总线基地址

片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设, APB1挂载低速外设, APB2 和 AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地址,是挂载在该总线上的首个外设的地址。

stm32g0 浮点运算时间_gpio_03

2. 外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围。

stm32g0 浮点运算时间_gpio_04

3.外设寄存器

寄存器的位置都以相对该外设基地址的偏移地址来描述。

stm32g0 浮点运算时间_stm32_05


在编程的时候我们需要反复的查阅外设的寄存器说明《STM32F10xx 参考手册》中具体章节的寄存器描述部分。

如何计算一个寄存器的地址?
以GPIOA_BSRR 为例, GPIOA 外设的基地址为 0x40010800 ,我们就可以算出 GPIOA的这个 GPIOA_BSRR 寄存器的地址为: 0x40010800+0x18(相对于GPIOA基址的偏移)。

三、C 语言对寄存器的封装

1.封装总线和外设基地址

把总线基地址和外设基地址都以相应的宏定义起来,总线或者外设都以他们的名字作为宏名,方便理解和记忆。

/* 外设基地址 */
#define PERIPH_BASE ((unsigned int)0x40000000)

/* 总线基地址 */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)


/* GPIO 外设基地址 */
#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)


/* 寄存器基地址,以 GPIOB 为例 */
#define GPIOB_CRL (GPIOB_BASE+0x00)
#define GPIOB_CRH (GPIOB_BASE+0x04)
#define GPIOB_IDR (GPIOB_BASE+0x08)
#define GPIOB_ODR (GPIOB_BASE+0x0C)
#define GPIOB_BSRR (GPIOB_BASE+0x10)
#define GPIOB_BRR (GPIOB_BASE+0x14)
#define GPIOB_LCKR (GPIOB_BASE+0x18

首先定义了“片上外设”基地址PERIPH_BASE,接着在 PERIPH_BASE 上加入 各 个 总 线 的 地 址 偏 移 , 得 到 APB1 、 APB2 总 线 的 地 址 APB1PERIPH_BASE APB2PERIPH_BASE,在其之上加入外设地址的偏移,得到外设地址,最后在外设地址上加入各寄存器的地址偏移,得到特定寄存器的地址。

控制 GPIOB_BSRR 引脚 0 输出高电平(BSRR 寄存器的 BR0 置 1)

*(unsigned int *)GPIOB_BSRR = 0x01<<0

2.封装寄存器列表

typedef unsigned int uint32_t; /*无符号 32 位变量*/
typedef unsigned short int uint16_t; /*无符号 16 位变量*/

/* GPIO 寄存器列表 */
typedef struct {
uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
} GPIO_TypeDef;

C 语言的语法规定,结构体内变量的存储空间是连续的,其中 32 位的变量占用 4 个字节, 16 位的变量占用 2 个字节,根据上述定义,各成员变量之间的相对于基地址的偏移就隐藏在变量类型里了。

GPIO_TypeDef 结构体成员的地址偏移图

stm32g0 浮点运算时间_stm32g0 浮点运算时间_06


通过结构体指针访问寄存器

GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx
GPIOx = GPIOB_BASE; //把指针地址设置为宏 GPIOH_BASE 地址 
GPIOx->IDR = 0xFFFF;
GPIOx->ODR = 0xFFFF;

四、GPIO端口的初始化及相关原理

1.时钟配置

由于 STM32的 外设很多,为了降低功耗,每个外设都对应着一个时钟,在芯片刚上电的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC。所有的 GPIO 都挂载到 APB2 总线上,具体的时钟由 APB2 外设时钟使能寄存器(RCC_APB2ENR)来控制。

stm32g0 浮点运算时间_stm32_07


可通过以下代码打开APB2对应的时钟

RCC_APB2ENR |= (1<<3);

2.输入输出模式设置与最大速率设置

在 GPIO 外设中, 控制端口高低控制寄存器 CRH 和 CRL 可以配置每个 GPIO 的工作模式和工作的速度, 每 4 个位控制一个 IO, CRH 控制端口的高八位, CRL 控制端口的低 8 位。

stm32g0 浮点运算时间_单片机_08


stm32g0 浮点运算时间_stm32_09


可通过以下代码把 PBn 配置为通用推挽输出,输出的速度为 10M(0001)

使用位操作可避免影响到寄存器中的其它位,因为寄存器不能按位读写。

GPIOB_CRL &= ~( 0x0F<< (4*n));//清零
 GPIOB_CRL |= (1<<4*n);// 配置 PB0 为通用推挽输出,速度为 10M

3.控制引脚输出电平

这里通过 ODR 寄存器的输出来控制 GPIO。

stm32g0 浮点运算时间_单片机_10


可通过以下代码控制 PBn 输出低电平

GPIOB_ODR &= ~(1<<n);

五、实现过程

1.准备材料

面包板一块、LED灯三个、STM32F103C8T6一块、杜邦线若干、USB转串口一个

2.创建工程

新建文件夹,新建工程,步骤可以看我之前的文章,注意此处使用寄存器方式,因此出现下图时不需要添加库。

stm32g0 浮点运算时间_1024程序员节_11


然后再给工程添加STM32标准库,它是ST 公司提供的标准软件库,包含了

STM32 芯片所有寄存器的控制操作,我们直接学习如何使用 ST 标准库,会极大地方便控制 STM32 芯片。在工程目录下新建下图所列的文件夹。

stm32g0 浮点运算时间_stm32g0 浮点运算时间_12


把下载好的库文件添加到文件夹,其他文件夹暂时为空。

stm32g0 浮点运算时间_gpio_13


stm32g0 浮点运算时间_单片机_14


手动将库文件加上去,按图示步骤,最后一步在文件类型选项选择所有文件然后一个个文件加进去。

stm32g0 浮点运算时间_stm32g0 浮点运算时间_15

stm32g0 浮点运算时间_stm32_16


完成后将输出文件定位到我们的"Listing"文件夹。

stm32g0 浮点运算时间_单片机_17

stm32g0 浮点运算时间_gpio_18


添加处理宏及编译器编译的时候查找的头文件路径。"DEFINE"处填入"STM32F10X_HD, USE_STDPERIPH_DRIVER USE_STDPERIPH_DRIVER "。然后点击Include Paths ”这里添加头文件的路径,如下图所示。

stm32g0 浮点运算时间_stm32_19

3.main.c具体代码

程序框架

stm32g0 浮点运算时间_stm32_20

1)C语言编程实现流水灯
#include "stm32f10x.h"

#define RCC_APB2ENR		*((unsigned volatile int*)0x40021018)

#define GPIOA_CRL		*((unsigned volatile int*)0x40010800)
#define GPIOA_ODR		*((unsigned volatile int*)0x4001080C)

#define DELAY Delay(void);

void LED_Init(void)
{
	RCC_APB2ENR |= 1<<3;//设置时钟
	GPIOA_CRL &=~(0x0F<<(4*1));
	GPIOA_CRL &=~(0x0F<<(4*2));
	GPIOA_CRL &=~(0x0F<<(4*3));//清零
	GPIOA_CRL |=(1<<(4*1));
	GPIOA_CRL |=(1<<(4*2));
	GPIOA_CRL |=(1<<(4*3));//推挽输出,10M
}

void Delay(void)//延时函数
{
    uint16_t i,j;
	for(i=0;i<19601;i++)//延时1s
        {
            for(j=5;j>0;j--);
        }
}
 
int main(void)
{
	LED_Init();//初始化准备工作
	while (1)
	{
		GPIOA_ODR &= ~(1<<1);//A1开
		DELAY;
		DELAY;
		DELAY;
		GPIOA_ODR |= 1<<1;//A1关
		GPIOA_ODR &= ~(1<<2);//A2开
		DELAY;
		DELAY;
		DELAY;
		GPIOA_ODR |= 1<<2;//A2关
		GPIOA_ODR &= ~(1<<3);//A3开
		DELAY;
		DELAY;
		DELAY;
		GPIOA_ODR |= 1<<3;//A3关
	}
}
2)汇编语言实现流水灯
AREA MYDATA, DATA
	
 AREA MYCODE, CODE
	ENTRY
	EXPORT led

led
	;使能A,B,C
    ldr r0, =0x40021018
    ldr r1, =0x0000001c
    str r1, [r0]                


	;配置A4
	ldr r0, =0x40010800
    ldr r1, [r0]
    bic r1, r1, #0x000f0000
    orr r1, r1, #0x00010000
    str r1, [r0]

	;配置B5
    ldr r0, =0x40010c00
    ldr r1, [r0]
    bic r1, r1, #0x00f00000
    orr r1, r1, #0x00100000
    str r1, [r0]
	
	;配置C14
	ldr r0, =0x40011004
    ldr r1, [r0]
    bic r1, r1, #0x0f000000
    orr r1, r1, #0x01000000
    str r1, [r0]

	;A4亮灯
	ldr r0, =0x4001080c
    ldr r1, =0x00000010
    str r1, [r0]

	ldr r0, =5000000;频率
    ldr r1, =0
	
;循环亮灯	
blink
    add r1, r1, #1
    cmp r1, r0
    blt blink
	
	;A4灭灯
	ldr r1, =0x4001080c
    ldr r2, [r1]
    eor r2, r2, #0x00000010
    str r2, [r1]
	
	;B5亮灯
	ldr r1, =0x40010c0c
    ldr r2, [r1]
    eor r2, r2, #0x00000020
    str r2, [r1]
	
	ldr r1, =0

blink1	
	add r1, r1, #1
    cmp r1, r0
    blt blink1
	
	;B5灭灯
	ldr r1, =0x40010c0c
    ldr r2, [r1]
    eor r2, r2, #0x00000020
    str r2, [r1]
	
	;C14亮灯
	ldr r1, =0x4001100c
    ldr r2, [r1]
    eor r2, r2, #0x00004000
    str r2, [r1]
	

	ldr r1, =0

blink2
	add r1, r1, #1
    cmp r1, r0
    blt blink2
	
	;C14灭灯
	ldr r1, =0x4001100c
    ldr r2, [r1]
    eor r2, r2, #0x00004000
    str r2, [r1]
	
	;A4亮灯
	ldr r1, =0x4001080c
    ldr r2, [r1]
    eor r2, r2, #0x00000010
    str r2, [r1]
	

	ldr r1, =0
    b blink

	
	END

4.烧录

要安装一个串口驱动程序和一个串口下载软件,这里用的是CH341和FlyMcu。

stm32g0 浮点运算时间_单片机_21


对于核心板和USB转串口的连接,按照下图,特别注意boot0置1,boot1置0。

stm32g0 浮点运算时间_1024程序员节_22


将USB转串口插到电脑上,点击搜索串口,一般都能匹配到,设置波特率为115200,然后选择生成的hex文件,注意左下方选择’DTR的低电平复位,RTS的高电平进BootLoader’,点击开始编程,出现下图右边界面即烧录成功。

stm32g0 浮点运算时间_1024程序员节_23

5.接线

将电源引导面包板上,并给核心板接地和供电,此时可以把A9、A10拔了,然后将A1、A2、A3的引脚接到LED的负极,它的正极则接高电平。

stm32g0 浮点运算时间_单片机_24

六、实验效果

stm32g0 浮点运算时间_stm32g0 浮点运算时间_25

七、总结

在对GPIO口有了一个系统性的认识后,GPIO口的寄存器操作其实不难,就是找地址的时候有一些麻烦,需要去查手册。而C语言对寄存器的封装以及相关的库函数就可以很好地解决这个问题,我们要知其然还要知其所以然,所以我们还是要了解如何直接操作寄存器来实现对GPIO口的控制,后面才能更好地运用相关知识。这个流水灯也算是我STM32的一个入门了,代码写好了,接线方面也不是很复杂。下篇文章介绍如何利用Keil5的仿真示波器分析代码。

八、参考资料

我的板子不是野火的,但是整个过程中的很多资料比如芯片手册等等都是从下面的网站下载的,参考书籍是《零死角玩转 STM32F103—指南者》,写的很详细,内容安排得很合理,很适合初学者。
野火STM32F103C8T6核心板资料下载