STM32寄存器操作、模板构建

cnblogs 2024-07-18 10:45:02 阅读 64

STM32寄存器操作、模板构建

本文介绍了:外设寄存器查找

① 名称

② 偏移地址

③ 寄存器位表

④ 位功能说明

寄存器基本操作

C语言的置位和清零

具体方法

设置GPIO流程

给寄存器赋值

带参数宏

STM32F1xx

芯片识别

存储器映射

寄存器映射

让GPIOB端口的16个引脚输出高电平,要怎么实现?

STM32寄存器映射

C语言对寄存器的封装

新建寄存器(REG)模板

创建工程

2024年7月18日 发布于博客园, 本文涉及到STM32F4XX和STM32F1XX系列

目录

  • 外设寄存器查找
    • ① 名称
    • ② 偏移地址
    • ③ 寄存器位表
    • ④ 位功能说明
  • 寄存器基本操作
    • C语言的置位和清零
      • 具体方法
    • 设置GPIO流程
    • 给寄存器赋值
    • 带参数宏
  • STM32F1xx
    • 芯片识别
    • 存储器映射
    • 寄存器映射
    • 让GPIOB端口的16个引脚输出高电平,要怎么实现?
    • STM32寄存器映射
    • C语言对寄存器的封装
  • 新建寄存器(REG)模板
    • 创建工程

外设寄存器查找

见<<野火STM32库开发指南P64>>

在XX 外设的地址范围内,分布着的就是该外设的寄存器。以GPIO 外设为例,GPIO 是通用输入输出端口的简称,简单来说就是STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把GPIO 的引脚连接到LED 灯的阴极,LED 灯的阳极接电源,然后通过STM32 控制该引脚的电平,从而实现控制LED 灯的亮灭。

GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为32bit,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。

image

image

① 名称

寄存器说明中首先列出了该寄存器中的名称,<code>“(GPIOx_BSRR)(x=A⋯I)”这段的意思是该寄存器名为“GPIOx_BSRR”其中的“x”可以为A-I,也就是说这个寄存器说明适用于GPIOA、GPIOB 至GPIOI,这些GPIO 端口都有这样的一个寄存器。

② 偏移地址

偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是0x18,从参考手册中我们可以查到GPIOA 外设的基地址为0x4002 0000 ,我们就可以算出GPIOA 的这个GPIOA_BSRR 寄存器的地址为:0x4002 0000+0x18;同理,由于GPIOB 的外设基地址

为0x4002 0400,可算出GPIOB_BSRR 寄存器的地址为:0x4002 0400+0x18 。其他GPIO 端口以此类推即可。

③ 寄存器位表

image

紧接着的是本寄存器的位表,表中列出它的0-31 位的名称及权限。表上方的数字为位编号,中间为位名称,最下方为读写权限,其中w 表示只写,r 表示只读,rw 表示可读写。本寄存器中的位权限都是w,所以只能写,如果读本寄存器,是无法保证读取到它真正内容的。而有的寄存器位只读,一般是用于表示STM32 外设的某种工作状态的,由STM32 硬件自动更改,程序通过读取那些寄存器位来判断外设的工作状态。

④ 位功能说明

位功能是寄存器说明中最重要的部分,它详细介绍了寄存器每一个位的功能。例如本寄存器中有两种寄存器位,分别为BRy 及BSy,其中的y 数值可以是0-15,这里的0-15 表示端口的引脚号,如BR0、BS0 用于控制GPIOx 的第0 个引脚,若x 表示GPIOA,那就是控制GPIOA 的第0 引脚,而BR1、BS1 就是控制GPIOA 第1 个引脚。

其中BRy 引脚的说明是“0:不会对相应的ODRx 位执行任何操作;1:对相应ODRx 位进行复位”。

这里的“复位”是将该位设置为0 的意思,而“置位”表示将该位设置为1;说明中的ODRx 是另一个寄存器的寄存器位,我们只需要知道ODRx 位为1 的时候,对应的引脚x 输出高电平,为0 的时候对应的引脚输出低电平即可(感兴趣的读者可以查询该寄存器GPIOx_ODR 的说明了解)。所以,如果对BR0 写入“1”的话,那么GPIOx 的第0 个引脚就会输出“低电平”,但是对BR0 写入“0”的话,却不会影响ODR0 位,所以引脚电平不会改变。要想该引脚输出“高电平”,就需要对“BS0”位写入“1”,寄存器位BSy 与BRy 是相反的操作。

image

具体查对应的手册和系统库函数的封装, 注意, 要看数据手册, 查找对应的资源才能知道具体的位置. 重点是掌握方法.

寄存器基本操作

C语言的置位和清零

若直接赋值0或1, 会将所有位都变为0或1

例如:<code>*(unsigned int*)(0x40010C0C)=1; 或 *(unsigned int*)(0x40010C0C)=0;

任何数&1,值不变 任何数&0,均为0

故而只对目标位操作

//清零 &=~

//置位 |=

具体方法

/*对某寄存器某些位清零*/

// 配置IO口为输出

GPIOB_CRL &= ~( (0x0f) << (4*0) );//对寄存器清零. 0x0f是0000 1111, 因为要取反, 要把哪一位清零就用1去清零

/*对某寄存器某1位置位*/

//写入值

GPIOB_CRL |= ( (1) << (4*0) );

/*对某寄存器某1位置零*/

// 控制 ODR 寄存器

GPIOB_ODR &= ~(1<<0);

/* 置位操作 */

a|=(1<<?);

/* 清零操作 */

a&=~(1<<?);

设置GPIO流程

打开GPIOB端口的时钟-->配置端口配置低寄存器的端口模式和速率-->控制ODR寄存器的高低电平

//打开GPIOB端口的时钟(看2.3和第六章6.3.7)

//RCC外设地址0x4002 1000位于AHB总线, 该外设下的APB2寄存器,APB2外设时钟使能寄存器偏移地址为0x18

//让该寄存器的对应IOPB端口时钟开启, 位于该寄存器的第3位, 即左移3位

*(unsigned int *)0x40021018 |= ((1)<<3);

//配置IO口位输出

//配置8.2.1端口配置低寄存器的端口模式和速率

//因为4个为1组,所以控制PB0则为第0组,需要左移0组

*(unsigned int *)0x40010C00 |= ((1)<<(4*0));

//控制ODR寄存器的高低电平,此处为置零

*(unsigned int *)0x40010C0C &= ~((1)<<0);

给寄存器赋值

方法1:宏定义直接赋值

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

//GPIOB的端口全部输出为高电平

*(unsigned int*)(0x40010C0C)=0xFFFF;

/*

(0x40010C0C)是GPIOB输出数据寄存器ODR的地址, 是一个立即数

(unsigned int*)(0x40010C0C) 将立即数强制类型转换为指针, 指向地址

*(unsigned int*)(0x40010C0C) 对该地址所指向的区域进行操作

*(unsigned int*)(0x40010C0C)=0xFFFF; 从该地址开始往后数32位,均为1(十六进制0xffff)

*/

/*通过宏定义,寄存器别名的方式访问内存单元*/

// GPIOB 端口全部输出 高电平

#define GPIOB_ODR (unsignedint*)(0x40010C0C)//给该地址定义个宏

* GPIOB_ODR = 0xFF;//对宏进行操作

/*为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面*/

// GPIOB 端口全部输出 高电平

#define GPIOB_ODR *(unsignedint*)(0x40010C0C)

GPIOB_ODR = 0xFF;

方法2: 采用移位的方式

* (unsigned int * )0x40010c0c &=~(1<<0);

#define PERIPH_BASE ((unsigned int)0x40000000)

#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)

#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)

#define GPIOB_ODR *(unsignedint*)(GPIOB_BASE+0x0C)//ODR的绝对地址

// PB0输出输出低电平(清零) 将目标位清零 &= ~(1<<目标位)

GPIOB_ODR &= ~(1<<0);

/*

等效于 GPIOB_ODR = GPIOB_ODR & ~(1<<0);

根据优先级, 先计算括号内(1<<0)

0001<<0--->0001

取反~

~0001--->1110

计算按位与

GPIOB_ODR & 1110 若原来值为1000

则 1000 & 1110

得 1000

*/

// PB0输出输出高电平 将目标位拉高 |= ~(1<<目标位)

GPIOB_ODR |= (1<<0);

按位逻辑运算符: A &= B 等效于 A = A&B

移位运算符:

左移 1<<2

  • 把十进制数1转换为二进制 0000 0001
  • 整体左移2位 0000 0100 , 左侧移除的丢失, 空位补0

右移需要区分符号位, 结果看机器 (1000 1010)>>2

  • //转换为二进制
  • 无符号位 整体右移2位 0010 0010 , 右侧移除的丢失, 空位补0
  • 有符号位 整体右移2位 0010 0010或1110 0010 , 右侧移除的丢失, 空位补符号位

或不用宏定义

* (unsigned int *)0x40010C0C &=~(1<<0);//将第一位置零

带参数宏

#define ON 1 //代参数宏

#define OFF 0

// \ C语言里面叫续行符,后面不能有任何的东西

//带参宏的主体

#define LED_G(a) if(a) \

GPIO_ResetBits(LED_G_GPIO_PORT, LED_G_GPIO_PIN); \

else GPIO_SetBits(LED_G_GPIO_PORT, LED_G_GPIO_PIN);

void LED_GPIO_Config(void);

STM32F1xx

寄存器 需要反复看书籍第六章

芯片识别

1、如何看芯片丝印

2、懂得如何辨别正方向

image

若有2个点,则看小的点,逆时针。

若没有点,那么正看丝印,左上方为第一脚,逆时针数。

image

STM32芯片架构简图

image

flash存储程序,SRAM存储变量

IP厂商(内核厂商):ARM

SOC厂商(芯片厂商):ST


STM32F10xx系统框图

image

在参考手册的存储架构章节中。

驱动单元(CPU,内核):ARM公司设计

被动单元:外设:ST公司设计

ICode 中的I 表示Instruction,即指令。我们写好的程序编译之后都是一条条指令,存放在FLASH中,内核要读取这些指令来执行程序就必须通过ICode 总线,它几乎每时每刻都需要被使用,它是专门用来取指的。


DCode 总线:DCode 中的D 表示Data,即数据,那说明这条总线是用来取数的。我们在写程序的时候,数据有常量和变量两种,常量就是固定不变的,用C 语言中的const 关键字修饰,是放到内部的FLASH当中的,变量是可变的,不管是全局变量还是局部变量都放在内部的SRAM。因为数据可以被Dcode 总线和DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数。

系统总线system 系统总线主要是访问外设的寄存器,我们通常说的寄存器编程,即读写寄存器都是通过这根系统总线来完成的。

DMA 总线:DMA 总线也主要是用来传输数据,这个数据可以是在某个外设的数据寄存器,可以在SRAM,可以在内部的FLASH。因为数据可以被Dcode 总线和DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数

image

APB1低速总线,APB2高速总线

存储器映射

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

2的32次方是4G。

图在Datasheet search site的Memory mapping章节

image

重点学习Block2

image

image

image

image

寄存器映射

image

看STM32参考手册中文版第八章

image

地址偏移是相对于端口基地址偏移,见第二章存储器组织。

让GPIOB端口的16个引脚输出高电平,要怎么实现?

<code>/*通过绝对地址访问内存单元*/

//GPIOB的端口全部输出为高电平

*(unsigned int*)(0x40010C0C)=0xFFFF;

/*

(0x40010C0C)是GPIOB输出数据寄存器ODR的地址, 是一个立即数

(unsigned int*)(0x40010C0C) 将立即数强制类型转换为指针, 指向地址

*(unsigned int*)(0x40010C0C) 对该地址所指向的区域进行操作

*(unsigned int*)(0x40010C0C)=0xFFFF; 从该地址开始往后数32位,均为1(十六进制0xffff)

*/

/*通过宏定义,寄存器别名的方式访问内存单元*/

// GPIOB 端口全部输出 高电平

#define GPIOB_ODR (unsignedint*)(0x40010C0C)//给该地址定义个宏

* GPIOB_ODR = 0xFF;//对宏进行操作

/*为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面*/

// GPIOB 端口全部输出 高电平

#define GPIOB_ODR *(unsignedint*)(0x40010C0C)

GPIOB_ODR = 0xFF;

什么是寄存器?

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

什么叫存储器映射?

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

STM32寄存器映射

image

image

image

如何计算?以GPIOB为例

GPIOB的外设基地址为:0x4001 0C00

因为端口输入数据寄存器(GPIOx_IDR) 地址偏移为:0x08

所以(GPIOx_IDR) 的绝对地址为 0x4001 0C08

所以我们只需要+4就能得到下一个寄存器的地址, 一个字节有8位, 4个字节即32位

CRL 端口配置低寄存器

CRH 端口配置高寄存器

IDR 输入数据寄存器

ODR 数据输出寄存器

BSRR和BRR是配置某端口的某一个位的

LCKR 锁定寄存器

重点看第85页

C语言对寄存器的封装

在编程上为了方便理解和记忆,我们把总线基地址和外设基地址都以相应的宏定义起来,总线或

者外设都以他们的名字作为宏名

代码清单: 寄存器-4 总线和外设基址宏定义

<code>/* 外设基地址*/

#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,在其之上加入外设地址的偏移,得到GPIOA-G 的外设地址,最后在外设地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具体地址,就可以用指针读写

重点看64页

让PB0输出低/高电平,要怎么实现?

#define PERIPH_BASE ((unsigned int)0x40000000)

#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)

#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)

#define GPIOB_ODR *(unsignedint*)(GPIOB_BASE+0x0C)//ODR的绝对地址

// PB0输出输出低电平(清零)

GPIOB_ODR &= ~(1<<0);//(1<<0)将1左移0位 0000 0000变成0000 0001,别的为0

/*

先计算左边 0000 0001 取反后 1111 1110

再与原来的值0000 1000 按位与

结果 0000 0000

*/

// PB0输出输出高电平

GPIOB_ODR |= (1<<0);

/*

|=是相加的意思

原来0000 1000

相加0000 0001 先计算出1<<0的结果,再位相加

结果0000 1001

*/

使用结构体封装寄存器列表?

image

image

-> 和结构体的. 的作用是类似的, 都是对结构体成员的访问, 可以见C结构体

image

这部分工作都由固件库帮我们完成了

新建寄存器(REG)模板

创建工程

image

工程命名--不要中文

image

选择对应芯片型号

image

跳过软件包

image

导入启动文件,文件路径,在固件库中:

<code>C:\MYDATA~1\网课资料\野火指~1\A盘(~1\1-程序~1\1-_野~1\1-书籍~1\0【固~1.0\STM32F~1.0\【固件~1.0\LIBRAR~1\CMSIS\CM3\DEVICE~1\ST\STM32F~1\startup\arm\

根据Flash大小选择对应启动文件:

image

image

拷贝启动文件到工程项目文件夹中

image

双击导入文件

image

记事本新建一个main.c文件并导入

image

设置代码大小

image

image

<code>#include "stm32f10x.h"//<>是表示头文件在软件安装根目录下, ""表示先在工程目录下寻找,再去软件安装根目录寻找

int main (void)

{

}

void SystemInit(void)

{

//编译时, 会先在启动文件的汇编中初始化时钟, 采用的插入方式. 函数体为空,目的是为了骗过编译器不报错

}

修改名称

间断双击target,重命名

image

修改生成的目标工程文件名称,存放在项目的objects文件夹中。

image

hex是通过串口下载的可执行文件,

axf是通过编译器下载的可执行文件

image

**烧录设置 ** 连接仿真器和单片机 上电

image

image

image

image

然后编译后,选择download下载

这个是可以上兼容的,即在同一内核版本下,F103,引脚少的工程项目在引脚多的芯片上可以直接烧录,无需修改。因为引脚少的芯片的所有引脚在引脚多的芯片上都有。

移植工程,需要修改芯片型号,然后重新配置仿真器:

image

image

关闭C语言语法的动态检查:

image



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。