当前位置: 首页 > news >正文

阅读HAL源码之重点总结

HAL库的封装特点 

HAL封装中有如下特点(自己总结的):

特定外设要设置的参数组成一个结构体;

特定外设所有寄存器组成一个结构体;

地址基本都是通过宏来定义的,定义了各外设的起始地址,也就是对应寄存器结构体的地址,因为结构体里定义的是32位的地址,所以直接通过寄存器结构体指针->即可访问各寄存器。

实现过程基本都是:先根据要设置的数据赋值给参数结构体,之后根据这些参数来设置寄存器结构体。

最终的目的,都是将特定数据赋值给对应的寄存器。只不过用结构体实现了封装。

示例:

首先定义时钟源结构体的变量以及时钟配置结构体的变量;

然后给各参数赋值。

时钟源类型

HSE状态设置为开启

这三行分别是:HSEON在时钟控制寄存器中所处的位置号、对应的十六进制数、再给到一个宏定义RCC_CR_HSEON

HSE分频值

……

更多对着数据手册的寄存器查看吧。

HAL里函数都是直接调用的方式,并没有将函数都用结构体封装起来,其实多文件本身也可以看做一种封装。 

嵌入式的分层思想 

软件封装,抛去操作系统不说,裸机开发可分为以下三层:

1、寄存器层面的封装;

2、特定硬件层面的封装;

3、业务层面的使用;

以最简单的点亮LED为例说明。

寄存器层面就是指实现了特定GPIO的设置,可以对特定引脚进行一些操作,这一步主要是把芯片操作给封装起来。

特定硬件层面就是根据具体的外设以及其连接方式,实现其功能,比如点亮和熄灭LED,实现特定外设所有可能的操作。也就是说,把电路板调通。

业务层面就是根据业务,需要灯点亮还是熄灭,到这一步,更多的就是要处理业务逻辑了。

HAL初始化

HAL_Init();

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */

配置Flash预取;

配置中断组;

配置滴答时钟;

初始化MSP;

SystemClock_Config();

配置时钟主要包括系统驱动时钟,CPU、AHB和APB总线时钟;

以上,系统初始化完成;

接下来就要初始化自己配置的外设了。

这里以GPIO为例

MX_GPIO_Init()

开启时钟;

将相应的引脚置高或低电平;

引脚相关参数;

接下来就可以编写用户代码了。

HAL编程规范

HAL库使用的编码规范接近于Linux的风格。

学习使用。

基本都是用下划线_分隔;

目录名:大驼峰加下划线;

文件名:全小写加下划线;

注释使用的都是/*   */

且基本每一个函数代码处都有必要的注释

函数名:大驼峰(专有名字全大写)加下划线

变量名:大驼峰,也有大驼峰加下划线的

宏定义:全大写加下划线

十六进制表示的数后基本上都要加UL

十进制表示的数后面基本都要加U

但是如果已经用以下数据类型定义了,则没必要加。

数据类型定义基本都用的是stdint.h里面的

uint8_t

uint16_t

uint32_t

自定义类型名:大驼峰加下划线

程序的大括号在下一行开启

STM32的FLASH编程

STM32的FLASH不但可以存储程序,而且还是可以当EEPROM用。32的FLASH一般都比较大,FLASH的前面部分可以放代码,而最后几页可以存储数据,用于掉电记忆还是挺不错的。


STM32的FLASH是按页类操作的,也就是说每次擦除都必须整页擦除,而不能只擦除一页的一部分,读数据的话不存在这种限制。大容量的芯片每页是2k,而小容量的芯片每页是1k。


STM32的FLASH地址是从0x08000000开始的。比如要操作大容量512k芯片的最后一页FLASH,那么地址是0x08000000+2048*255.其中0x08000000是FLASH的起始地址,512k的芯片共有256页,每页2k,所以地址就是起始地址+前面255页的大小。比如操作64k的小容量的芯片的最后一页,那么地址就是0x08000000+1024*63.因为小容量芯片每页只有1k,所以地址=起始地址+前面63页大小。


在利用FLASH存储数据的过程中发现一个小问题,存储浮点数精度会丢失,所以只好把浮点数放大再存储,读出来的时候再还原回去。在利用库函数操作的时候,发现库函数的底层会把数据强制转换为无符号整型才存储进FLASH里面,但是如果把负数存储进去,读出来的时候还是负数,就好像强制转换语句失效了一样,其实库函数是没问题的,这个和正数负数在内存中的存储结构有关,在这里不过多的解释。
 

在STM32的技术手册里是找不到FLASH的相关寄存器和操作的,在《STM32的FLASH编程》里面才有。操作STM32的FLASH如果用寄存器的话是非常麻烦的,而且还非常容易出错,但是如果用库操作的话会非常简单,比如关键字、延时什么的库都帮我们做好了,我们只要调用接口就行了。 

__IO

在很多类型定义前面,看到了这个标志__IO

一开始我还以为是跟流相关的标志。

直接跳转到原定义,才发现就是一个宏定义

在core_cm3.h中,属于cm3内核里的定义。

而且这个宏定义竟然就是个volatile

__I的含义就是将变量限制在“只读状态”,“只读状态”表示的是“变量的值只能通过读取寄存器的值改变自身,而且可以连续的改变,但是就是不可以人为的在程序中对变量进行改变”。

其实这就是“嵌入式输入的含义”,我们只允许通过读取外设寄存器的值来改变变量,不允许人为的在程序中改变变量的值。

__O的含义是将变量限制在“输出状态”,输出状态有别于输入状态的本质就在于,输出状态去掉了对于变量的const常量限定,使得我们可以通过任意方式(寄存器/人为修改)来改变变量的属性(值和数据类型)进而对变量进行处理。

__IO指的是“该变量可以进行输入输出操作——读/写操作”。它的作用和__O相同。

_weak

函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。

加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。所以我们可以在别的地方定义一个相同名字的函数,而不必也尽量不要修改之前的函数,。
 

可见,弱函数就是个“备胎”,先用着,有了正式的就自然而然地被替换掉了。

断言机制

什么是Assert断言?

编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式

断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。

可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言

同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。 ---来自百度百科

这里的概念,可能不好理解,简单举一个例子来说明吧。

有这么一个数组和函数:

int Array[5] = {0xA1, 0xB2, 0xC3, 0xD4, 0xE5};int Fun(char i)
{return Array[i];
}

如果我们函数中不加Assert断言语句,你觉得直接调用会这个函数会有风险吗? 假如这么调用:

int a;a = Fun(8);

很明显,就这么调用,数组越界会出错,且我们不容易发现错误在哪里。

但是,假如添加有Assert断言语句,错误就能一下找出来。

Assert断言实际应用

其实,Assert断言在很多标准的代码中,基本都有。我们还是拿STM32的代码来说明吧。

不管是STM32标准外设库、还是HAL、LL库源代码里面都有Assert断言机制。

不知道大家有没有注意过assert_param函数


比如在HAL库中:

相信大家都看到过STM32库中的参数断言语句,他的作用就是:

用于检查函数传入参数是否正确

STM32的assert_param参数断言函数默认是没有使能的,如下:

也就是assert_param不起作用。

要想开启,HAL中stm32f1xx_hal_conf.h里:

也可以在CubeMX中选择开启:

如果没有开启断言,那么assert_param就相当于空

#define assert_param(expr) ((void)0U),不起作用。

如果开启断言,则:

#define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) 

如果检测内容为真,则不管,如果检测内容有错误,则输出一些信息。

void assert_failed(uint8_t* file, uint32_t line);

在main.c中,用户可自定义内容:

有错误时,会输出错误在哪个文件的哪一行,方便定位。

我们来看看实际应用吧。

举例:

assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));

注意,不是把函数的参数传进去,而是要传入一个逻辑表达式

这里的逻辑判断函数就是IS_RCC_OSCILLATORTYPE()

问题来了,这个逻辑判断函数是哪里来的呢?

这是个带参宏,在相应外设的头文件中stm32f1xx_hal_rcc.h

无非就是对参数进行判断。

自己定义一些内容都行。

错误处理

断言通常都是检查函数的输入参数的合法性。

另外,如果程序有一些可预见的出错情况,我们可以直接进行错误处理。

可以自定义错误处理函数。

在HAL中,就自己定义了一个,我们可以自己添加内容。

其他

 各端口的引脚定义:

从最低位为1,一直到最高位为1,分别对应着每一个引脚。 

相关文章:

阅读HAL源码之重点总结

HAL库的封装特点 HAL封装中有如下特点(自己总结的): 特定外设要设置的参数组成一个结构体; 特定外设所有寄存器组成一个结构体; 地址基本都是通过宏来定义的,定义了各外设的起始地址,也就是对应…...

常见的http请求响应的状态码

常见的http请求响应的状态码 一些常见的状态码为: 200 – 服务器成功返回网页 404 – 请求的网页不存在 503 – 服务不可用 1xx(临时响应) 表示临时响应并需要请求者继续执行操作的状态代码。 代码 说明 100 (继续&#xff09…...

UML类图中的类图、接口图、关联、聚合、依赖、组合概念的解释

文章目录UML类图依赖和关联的主要区别UML类图 类&#xff1a;类有三层结构 第一层&#xff1a;类的名字第二层&#xff1a;类的属性第三层&#xff1a;类的方法 接口&#xff1a;接口跟类相似&#xff0c;不过多了一个<<interface>>来表示它是一个接口 第一层&a…...

【数据库】第九章 关系查询处理与优化

第九章 关系查询处理与优化 索引 索引文件是一种辅助存储结构&#xff0c;其存在与否不改变存储表的物理存储结 构&#xff1b;然而其存在&#xff0c;可以明显提高存储表的访问速度。 索引文件组织方式有两种&#xff1a;(相对照的&#xff0c;主文件组织有堆文件、排序文件、…...

大学物理期末大题专题训练总结-磁学大题

&#xff08;事先声明指的是简单的那个磁学大题&#xff0c;另外一类涉及储存的磁能、磁感应强度分布下次说&#xff09;求个磁通量&#xff0c;求个感应电动势&#xff0c;求个安培力大小......这个感觉是不是像你梦回高中&#xff1f;当然&#xff0c;这一块大题跟高中磁学部…...

聚类算法(上):8个常见的无监督聚类方法介绍和比较

无监督聚类方法的评价指标必须依赖于数据和聚类结果的内在属性&#xff0c;例如聚类的紧凑性和分离性&#xff0c;与外部知识的一致性&#xff0c;以及同一算法不同运行结果的稳定性。 本文将全面概述Scikit-Learn库中用于的聚类技术以及各种评估方法。 本文将分为2个部分&…...

华为OD机试真题Python实现【找到它】真题+解题思路+代码(20222023)

找到它 题目 找到它是个小游戏,你需要在一个矩阵中找到给定的单词 假设给定单词HELLOWORLD,在矩阵中只要能找HELLOWORLD就算通过 注意区分英文字母大小写,并且你只能上下左右行走 不能走回头路 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目…...

English Learning - L2 语音作业打卡 Day4 2023.2.24 周五

English Learning - L2 语音作业打卡 Day4 2023.2.24 周五&#x1f48c; 发音小贴士&#xff1a;&#x1f48c; 当日目标音发音规则/技巧&#xff1a;&#x1f36d; Part 1【热身练习】&#x1f36d; Part2【练习内容】&#x1f36d;【练习感受】&#x1f353;元音 [u:]&#x…...

C#:Krypton控件使用方法详解(第九讲) ——kryptonRadioButton

今天介绍的Krypton控件中的kryptonRadioButton&#xff0c;这是一个单选按钮控件。下面开始介绍这个控件的属性&#xff1a;首先介绍的是外观属性&#xff0c;如下图所示&#xff1a;Cheacked属性&#xff1a;表示设置kryptonRadioButton控件的初始选中状态是什么样的&#xff…...

消失的数字(每日一题)

目录 一、题目描述 二、题目分析 2.1 方法一 2.1.1 思路 2.1.2 代码 2.2 方法二 2.2.1 思路 2.2.2 代码 2.3 方法三 2.3.1 思路 2.3.2 代码 三、完整代码 一、题目描述 oj链接&#xff1a;https://leetcode.cn/problems/missing-number-lcci 数组nums包含从0到n的…...

TypeScript算法基础——TS字符串的常用操作总结:substring、indexOf、slice、replace等

字符串的操作是算法题当中经常碰见的一类题目&#xff0c;主要考察对string类型的处理和运用。 在处理字符串的时候&#xff0c;我们经常会碰到求字符串长度、匹配子字符串、替换字符串内容、连接字符串、提取字符串字符等操作&#xff0c;那么调用一些简单好用的api可以让工作…...

Leetcode100-两数之和

参见官方题解 一、学到的知识 正面寻找两个数之和相加等于某个数&#xff0c;如 ab c&#xff0c;不如反过来寻找 a c - b 正面寻找需要两层 for 循环&#xff0c;把每个数都进行遍历&#xff0c;所以时间复杂度较高 反过来则可以通过维护一个 a 的集合&#xff0c;每次通过…...

4565: 删除中间的*

描述规定输入的字符串中只包含字母和*号&#xff0c;除了字符串前导和尾部的*号之外,将串中其他*号全部删除输入输入数据包括一串字符串&#xff0c;只包含字母和*&#xff0c;总长度不超过80。输出输出删除中间*后的字符串。样例输入*******A*BC*DEF*G****样例输出*******ABCD…...

VUE组件示例说明

<!-- * Author: xxx.xx * Date: 2021-07-20 14:33:41 * LastEditors: xxx.xx * LastEditTime: 2021-07-20 18:22:37 * PageTitle: 上拉加载组件 * Description: 描述... * FilePath: /wxapp-view/components/loadmore.vue --> <template><view class"c-mor…...

Widget中的State-学习笔记

Widget 有 StatelessWidget 和 StatefulWidget 两种类型。StatefulWidget 应对有交互、需要动态变化视觉效果的场景&#xff0c;而 StatelessWidget 则用于处理静态的、无状态的视图展示。StatefulWidget 的场景已经完全覆盖了 StatelessWidget&#xff0c;因此我们在构建界面时…...

股市实战技巧(知行合一)

投资策略 长线&#xff1a;优质核心股票大仓位核心标的票&#xff0c;小仓位短线投资投机小储蓄可加大投机仓位价值投资也要去做仓位控制 行情好&#xff0c;总体大仓位&#xff0c;行情小&#xff0c;小仓位个股根据走势调整个股仓位&#xff08;布林线的20%原则&#xff09;…...

k8s-资源限制-探针检查

文章目录一、资源限制1、资源限制的使用2、reuqest资源&#xff08;请求&#xff09;和limit资源&#xff08;约束&#xff09;3、Pod和容器的资源请求和限制4、官方文档示例5、资源限制实操5.1 编写yaml资源配置清单5.2 释放内存&#xff08;node节点&#xff0c;以node01为例…...

一文让你彻底了解Linux内核文件系统

一&#xff0c;文件系统特点 文件系统要有严格的组织形式&#xff0c;使得文件能够以块为单位进行存储。文件系统中也要有索引区&#xff0c;用来方便查找一个文件分成的多个块都存放在了什么位置。如果文件系统中有的文件是热点文件&#xff0c;近期经常被读取和写入&#xf…...

解决前端组件下拉框选择功能失效问题

问题&#xff1a; 页面下拉框选择功能失效 现象&#xff1a; 在下拉框有默认值的情况下&#xff0c;点击下拉框的其他值&#xff0c;发现并没有切换到其他值 但是在下拉框没默认值的情况下&#xff0c;功能就正常 原因 select 已经绑定选项&#xff08;有默认值&#xff09; 在…...

Linux_vim编辑器入门级详细教程

前言&#xff08;1&#xff09;vim编辑器其实本质上就是对文本进行编辑&#xff0c;比如在.c文件中改写程序&#xff0c;在.txt文件写笔记什么的。一般来说&#xff0c;我们可以在windows上对文本进行编译&#xff0c;然后上传给Linux。但是有时候我们可能只是对文本进行简单的…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

均衡后的SNRSINR

本文主要摘自参考文献中的前两篇&#xff0c;相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程&#xff0c;其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt​ 根发送天线&#xff0c; n r n_r nr​ 根接收天线的 MIMO 系…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

Redis上篇--知识点总结

Redis上篇–解析 本文大部分知识整理自网上&#xff0c;在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库&#xff0c;Redis 的键值对中的 key 就是字符串对象&#xff0c;而 val…...