STM32标准库编程与51单片机直接写寄存器的区别和联系

简介:

        在学完51单片机之后,我们去学习32的时候,会发现编程的方法有很大的区别,让人非常的不适应,但是通过不断的调用相应外设的库函数之后,你也可以去编程STM32,来实现功能,但是你真的了解标准库吗?不少人只会局限在调用标准库,不论你是学完标准库还是初学标准库,都很有必要了解以下标准库的原理。

 寄存器:

我们首先要明确我们编程到底在编些什么?

单片机寄存器在单片机编程中起着至关重要的作用,它们是与单片机硬件紧密关联的特殊存储单元,用于控制各种硬件功能和状态。单片机寄存器与编程之间的联系主要体现在以下几个方面:

1. 硬件控制:单片机寄存器直接控制着单片机的各种硬件功能,如输入/输出端口、定时器、串口通信等。通过编程操作这些寄存器,可以实现对硬件的控制和配置。

2. 状态监测:单片机寄存器中存储着各种硬件的状态信息,如中断标志、定时器计数值、输入端口状态等。通过编程读取这些寄存器的值,可以实时监测硬件的状态,并根据需要进行相应的处理。

3. 中断处理:单片机中断是一种重要的事件处理机制,通过编程配置中断寄存器,可以实现对各种中断事件的响应和处理。例如,可以通过设置中断使能位和中断优先级来控制中断的触发和处理顺序。

4. 外设通信:单片机通常需要与外围设备进行通信,如传感器、执行器、显示器等。编程时需要操作相应的寄存器来配置通信接口和协议,以实现与外设的数据交换和控制。

5. 优化性能:直接操作寄存器可以提高程序的执行效率和性能,因为与使用高级函数库相比,直接操作寄存器可以减少额外的开销和延迟,从而更好地满足实时性要求。

因此,单片机编程中的许多操作都是通过操作寄存器来实现的,程序员需要深入了解单片机寄存器的功能和用法,以充分利用单片机的硬件资源,并编写出高效可靠的程序。

标准库的本质:

        别看标准库调用一大堆函数,其实你不断的通过拆开函数的一层层嵌套,最后还是发现,他还是通过写单片机的寄存器,来实现功能。

寄存器映射:

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

我们在编程的时候,可以通过他们的地址找到他们,然后来操作他们(通过 C 语言对它们进行数据的读和写)。

        储存器区域功能划分:

        在这 4GB 的地址空间中, ARM 已经粗线条的平均分成了 8 个块,每块 512MB ,每个块也都规定了用途,具体分类见表格存储器功能分类 。每个块的大小都有 512MB ,显然这是非常大的,芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的一部分而 已。

        在这 8 Block 里面,有 3 个块非常重要,也是我们最关心的三个块。 Block0用来设计成内部 FLASH,Block1 用来设计成内部 RAM,Block2 用来设计成片上的外设, 下面我们简单的介绍下这三个 Block 里面的具体区域的功能划分。 其中Block2我们需要特别的去留意。
        Block2 用于设计片内的外设,根据外设的总线速度不同, Block 被分成了 APB 和 AHB 两部分, 其中 APB 又被分为 APB1 和 APB2, 所以为什么我们写调用库的时候,会有APB1 和APB2以及AHB,因为他们外设所在的地方不同。

        在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit ,每一个 单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。 我们可以找到每个单元的起 始地址,然后通过 C 语言指针的操作方式来访问这些单元.如果每次都是通过这种地址的方式 来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
        比如,我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x40010C0C (至于这个地址如何 找到可以先跳过,后面我们会有详细的讲解),ODR 寄存器是 32bit ,低 16bit 有效,对应着 16 个 外部 IO ,写 0/1 对应的的 IO 则输出低 / 高电平。现在我们通过 C 语言指针的操作方式,让 GPIOB的16 IO 都输出高电平。
        
*(unsigned int*)(0x4001 0C0C) = 0xFFFF;
        0x4001 0C0C 在我们看来是 GPIOB 端口 ODR 的地址,但是在编译器看来,这只是一个普通的变 量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针, 即 (unsigned int *)0x4001 0C0C ,然后再对这个指针进行 * 操作。
        刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存器的方式来操作。
// GPIOB 端口全部输出 高电平
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
GPIOB_ODR = 0xFF;

        记住绝对地址或许有点难,但是记住个寄存器的名字, 似乎简单不少,我们这里直接通过宏定义,实现直接往寄存器赋值。本质还是通过寄存器的地址,通过指针来对该寄存器进行读写。

         stm32外设地址映射

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

        总线基地址  :

表格 总线基地址 的“相对外设基地址偏移”即该总线地址与“片上外设”基地址 0x4000 0000 的差值。

       外设基地址:

这里面我们以 GPIO 这个外设来讲解外设的基地址,GPIO 属于高速的外设,挂载到APB2总线 上,具体见表格外设 GPIO 基地址

         外设寄存器:

        在 XX 外设的地址范围内,分布着的就是该外设的寄存器。以 GPIO 外设为例, GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电平。
        GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit ,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。这里我们以GPIOB 端口为例,来说明 GPIO 都有哪些寄存器,具体见表格 GPIOB 端口的寄存器地址列表

c语言对寄存器的封装:

        可以说,只要你看懂这个,那么你几乎就能知道标准库大体是怎么写出来的。

封装总线和外设基地址:

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

 

        首先定义了“片上外设”基地址 PERIPH_BASE ,接着在 PERIPH_BASE 上加入各个总线的地址偏移,得到 APB1APB2 总线的地址 APB1PERIPH_BASEAPB2PERIPH_BASE, 在其之上加入外设地址的偏移,得到 GPIOA-G 的外设地址,最后在外设地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具体地址,就可以用指针读写。

 

封装寄存器列表:

        用上面的方法去定义地址,还是稍显繁琐,例如 GPIOA-GPIOE 都各有一组功能相同的寄存器, 如 GPIOA_ODR/GPIOB_ODR/GPIOC_ODR 等等,它们只是地址不一样,但却要为每个寄存器都定义它的地址。为了更方便地访问寄存器,我们引入 C 语言中的 结构体 语法对寄存器进行封装这就是为什么,标准库要定义这么多结构体。
 /* GPIO 寄存器列表 */typedef struct 
{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; uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
这段代码用 typedef 关键字声明了名为 GPIO_TypeDef 的结构体类型,结构体内有 7 个成员变量, 变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间是连续的,其中 32位的变量占用 4 个字节, 16 位的变量占用 2 个字节。
        也就是说,我们定义的这个GPIO_TypeDef ,假如这个结构体的首地址为 0x4001 0C00(这也是第一个成员变量 CRL 的地址),那么结构体中第二个成员变量 CRH 的地址即为 0x4001 0C00 +0x04,加上的这个 0x04,正是代表 CRL 所占用的 4 个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给。这样的地址偏移与 STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器,

 

这 段 代 码 先 用 GPIO_TypeDef 类 型 定 义 一 个 结 构 体 指 针 GPIOx , 并让指针指向地址
GPIOB_BASE(0x4001 0C00) ,使用地址确定下来,然后根据 C 语言访问结构体的语法,用 GPIOx- >ODR 及 GPIOx->IDR 等方式读写寄存器。

 

最后,我们更进一步,直接使用宏定义好 GPIO_TypeDef 类型的指针,而且指针指向各个 GPIO
端口的首地址,使用时我们直接用该宏访问寄存器即可.
 /* 使用 GPIO_TypeDef 把地址强制转换成指针 */#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)#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)/* 使用定义好的宏直接访问 *//* 访问 GPIOB 端口的寄存器 */GPIOB->BSRR = 0xFFFF; //通过指针访问并修改 GPIOB_BSRR 寄存器GPIOB->CRL = 0xFFFF; //修改 GPIOB_CRL 寄存器GPIOB->ODR =0xFFFF; //修改 GPIOB_ODR 寄存器uint32_t temp;temp = GPIOB->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中/* 访问 GPIOA 端口的寄存器 */GPIOA->BSRR = 0xFFFF;GPIOA->CRL = 0xFFFF;GPIOA->ODR =0xFFFF;uint32_t temp;temp = GPIOA->IDR; //读取 GPIOA_IDR 寄存器的值到变量 temp 中

总结:

因为32的寄存器太多也太复杂,如果说像51单片机来直接对寄存器编程,会非常的麻烦,需要不断的去查询地址,和各个寄存器和各个位的功能,虽然说,他为我们提供了便利,但是,真正的学会这个单片机,还是需要去查阅芯片手册,看看各个寄存器的功能以及单片机整体的架构。

这篇只是大概告诉读者标准库是如何封装寄存器来对stm32中的寄存器进行读写,真正的去看懂标准库和标准库的函数,还是需要读者仔细的去研究标准库的每行代码。

最后,十分感谢野火科技,这些知识点都是在他那里学来的,也很推荐大家去他那里进行学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/2059.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL的基础语句

1、select语句 select colums from table_name 2、条件语句 #查询出查询出用户id为1和3的用户记录 IN 操作符允许我们在 WHERE 子句中规定多个值。 select * from student where id in (1,3) #查询出所有姓王的同学 模糊查询 like 通配符(% 任意多个字符 _单个字符) #下例…

如何使用渐变块创建自定义聊天机器人

如何使用渐变块创建自定义聊天机器人 文章目录 如何使用渐变块创建自定义聊天机器人一、介绍二、参考示例1、一个简单的聊天机器人演示2、将流式传输添加到您的聊天机器人3、喜欢/不喜欢聊天消息4、添加 Markdown、图像、音频或视频 一、介绍 **重要提示:**如果您刚…

软考高级架构师:AI 通俗讲解负载测试、压力测试、强度测试、容量测试和可靠性测试

在软件工程领域,测试是一个确保软件质量和性能的关键步骤。负载测试、压力测试、强度测试、容量测试和可靠性测试都是性能测试的不同类型,它们的目的和方法有所不同。 下面我将通过简单的比喻和解释,帮助您理解这些测试之间的区别。 负载测试…

跳跃游戏 II (贪心, 动态规划)

题目描述(力扣45题) : 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n 返回到…

使用Unity扫描场景内的二维码,使用插件ZXing

使用Unity扫描场景内的二维码&#xff0c;使用插件ZXing 使用Unity扫描场景内的二维码&#xff0c;ZXing可能没有提供场景内扫描的方法&#xff0c;只有调用真实摄像机扫描二维码的方法。 实现的原理是&#xff1a;在摄像机上添加脚本&#xff0c;发射射线&#xff0c;当射线打…

【面试八股总结】Linux系统下的I/O多路复用

参考资料 &#xff1a;小林Coding、阿秀、代码随想录 I/O多路复用是⼀种在单个线程或进程中处理多个输入和输出操作的机制。它允许单个进程同时监视多个文件描述符(通常是套接字)&#xff0c;一旦某个描述符就绪&#xff08;一般是读就绪或者写就绪&#xff09;&#xff0c;能够…

分享三个转换速度快、准确率高的视频转文字工具

想要直接将视频转换成文字&#xff0c;转换工具很重要&#xff01;给大家分享三个转换速度快、准确率高的视频转文字工具&#xff0c;轻松完成转换。 1.网易见外 https://sight.youdao.com/ 网易家的智能转写翻译服务工作站&#xff0c;网页端就可以直接使用&#xff0c;支持视…

Spring Bean依赖注入-Spring入门(二)

1、SpringBean概述 在Spring中&#xff0c;一切Java对象都被视为Bean&#xff0c;用于实现某个具体功能。 Bean的依赖关系注入的过程&#xff0c;也称为Bean的装配过程。 Bean的装配方式有3种&#xff1a; XML配置文件注解Java类 Spring中常用的两种装配方式分别是基于XML的…

Codeforces Round 821 (Div. 2) D2. Zero-One

题目 #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e5 5, inf 1e18, maxm 4e4 5; const int N 1e6;c…

【MySQL】InnoDB与MyISAM存储引擎的区别与选择

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。 存储引擎是基于表的&#xff0c;而不是基于库的&#xff0c;所以存储引擎也可被称为表类型。我们可以在创建表的时候&#xff0c;来指定选择的存储引擎&#xff0c;如果没有指定将自动选择默认的存储引擎。…

【工具-PyCharm】

工具-PyCharm ■ PyCharm-简介■ PyCharm-安装■ PyCharm-使用■ 修改主题■ 设置字体■ 代码模板■ 解释器配置■ 文件默认编码■ 快捷键■ 折叠■ 移动■ 注释■ 编辑■ 删除■ 查看■ 缩进■ 替换 ■ PyCharm-简介 官方下载地址 Professional&#xff1a;专业版&#xff0…

python--使用pika库操作rabbitmq实现需求

Author: wencoo Blog&#xff1a;https://wencoo.blog.csdn.net/ Date: 22/04/2024 Email: jianwen056aliyun.com Wechat&#xff1a;wencoo824 QQ&#xff1a;1419440391 Details:文章目录 目录正文 或 背景pika链接mqpika指定消费数量pika自动消费实现pika获取队列任务数量pi…

JavaScript(二)

JavaScript的语法 1.JavaScript的大小写 在JavaScript中&#xff0c;大小写是敏感的&#xff0c;这意味着大小写不同的标识符被视为不同的变量或函数。例如&#xff0c;myVariable 和 myvariable 被视为两个不同的变量。因此&#xff0c;在编写JavaScript代码时&#xff0c;必…

如何在PostgreSQL中创建并使用窗口函数来进行复杂的分析查询?

文章目录 解决方案1. 了解窗口函数的基本概念2. 常用的窗口函数3. 使用示例示例 1&#xff1a;计算每行销售数据的累计销售额示例 2&#xff1a;计算每行销售数据相对于前一行销售额的增长率 结论 PostgreSQL 提供了一套强大的窗口函数&#xff08;Window Functions&#xff09…

MQTT Broker 白皮书:全面实用的 MQTT Broker 选型指南

在智能数字化时代&#xff0c;家居设备、工厂传感器、智能汽车、能源电力计量表等各类设备都已变身为新型的智能终端。为了满足这些海量且持续增长的智能设备之间对于实时、可靠的消息传递的需求&#xff0c;MQTT Broker 消息代理或消息中间件扮演了至关重要的角色。作为新一代…

STM32H750外设ADC之模拟窗口看门狗

目录 概述 1 相关寄存器 2 功能描述 3 AWDx 标志和中断 4 模拟看门狗 1 4.1 模拟看门狗 1 说明 4.2 模拟看门狗通道选择 4.3 阀值选择 5 模拟看门狗 2和3 6 ADCx_AWDy_OUT 信号输出生成 6.1 功能介绍 6.2 输出信号案例 7 模拟看门狗 1、 2、 3 比较 概述 本文主…

Opencv_3_图像对象的创建与赋值

ColorInvert.h 如下&#xff1a; #include <opencv.hpp> using namespace std; #include <opencv.hpp> using namespace cv; using namespace std; class ColorInvert{ public : void mat_creation(); }; ColorInvert.cpp 文件如下&#xff1a; #include &q…

解决宝塔面板无法访问(无法访问或拒绝链接)

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;Linux ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 问题如下&#xff1a; 本人设置了授权IP&#xff0c;但是有些问题&#xff0c;所以是打算取消授权IP 重…

Spring Boot 自动装配执行流程

Spring Boot 自动装配执行流程 Spring Boot 自动装配执行流程如下&#xff1a; Spring Boot 启动时会创建一个 SpringApplication实例&#xff0c;该实例存储了应用相关信息&#xff0c;它负责启动并运行应用。实例化 SpringApplication 时&#xff0c;会自动装载META-INF/spr…

Linux文件chattr/lsattr/Linux权限(搭建权限测试环境实战)引申到内部原理及Linux删除系统文件原理-7539字详谈

企业高薪思维: 每一个阶段什么时候是最重要的&#xff1f;&#xff08;快速定位&#xff09; 1.学习最重要的事情 &#xff08;学生阶段&#xff0c;找工作前阶段&#xff09; 2.家庭&#xff0c;女朋友 &#xff08;工作阶段/学生阶段&#xff0c;学习不受到影响&#xff09; …