C语言的使用技巧--在IO操作中的移位和快速配置

news2025/1/17 0:20:48

在WB32F103(ARM cortex m3内核,96Mhz)的gpio初始化中有一段代码,充分的结合了硬件特征并使用C语言的技巧来快速的配置对应的GPIO的功能,堪称经典和楷模,代码异常简洁,执行速度快,配置任意IO方便快捷。
我们先来看看这一段源代码:

void GPIO_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint32_t PinConfig)
{
  uint32_t tmp = PinConfig;
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  GPIOx->CFGMSK = ~GPIO_Pin;
  GPIOx->MODER = ((tmp >> 28) & 0x3) * 0x55555555U;
  GPIOx->OTYPER = ((tmp >> 24) & 0x1) * 0xFFFFFFFFU;
  GPIOx->OSPEEDR = ((tmp >> 20) & 0x3) * 0x55555555U;
  GPIOx->PUPDR = ((tmp >> 16) & 0x3) * 0x55555555U;

  tmp = (tmp & 0xF) * 0x11111111U;
  GPIOx->AFRL = tmp;
  GPIOx->AFRH = tmp;
}

相应的调用方式如下:

GPIO_Init(GPIOB, GPIO_Pin_8|GPIO_Pin_9, GPIO_MODE_IN | GPIO_PUPD_UP|GPIO_SPEED_LOW);

一条简单的函数调用代码,就可以完成对同一组IO的多个相同功能IO的同时配置,代码不可谓不经典和优雅,高效易读易懂。
但是很多人在阅读GPIO_Init函数的源代码的时候,对里面的一些运算操作确感觉到懵懵懂懂,理解不了,搞不明白。下面我们结合该硬件的特征讲解和说明一下(注意:不同的MCU的硬件寄存器会有不同的操作方式,需要紧密结合你使用的MCU的硬件寄存器的特征来理解),可以起到举一反三和触类旁通的作用:
要完成这个“一步到位”的操作,首先要对相关的宏进行合理的有技巧的定义,看看相关IO操作的宏定义如下:

/** @defgroup GPIO_MODE_define
  * @{
  */
#define GPIO_MODE_IN          0x00000000U     /*!< Input mode */
#define GPIO_MODE_OUT         0x10000000U     /*!< Output mode */
#define GPIO_MODE_AF          0x20000000U     /*!< Alternate function mode */
#define GPIO_MODE_ANA         0x30000000U     /*!< Analog mode */
/**
  * @}
  */
  
  /** @defgroup GPIO_OTYPE_define
  * @{
  */
#define GPIO_OTYPE_PP         0x00000000U     /*!< Output push-pull */
#define GPIO_OTYPE_OD         0x01000000U     /*!< Output open-drain */
/**
  * @}
  */
  /** @defgroup GPIO_SPEED_define
  * @{
  */
#define GPIO_SPEED_LOW        0x00100000U     /*!< Low speed */
#define GPIO_SPEED_HIGH       0x00000000U     /*!< High speed */
/**
  * @}
  */
  /** @defgroup GPIO_PUPD_define
  * @{
  */
#define GPIO_PUPD_NOPULL      0x00000000U     /*!< No pull resistor */
#define GPIO_PUPD_UP          0x00010000U     /*!< Pull up resistor enabled */
#define GPIO_PUPD_DOWN        0x00020000U     /*!< Pull down resistor enabled */
/**
  * @}
  */
 /** @defgroup GPIO_Pin_sources 
  * @{
  */
#define GPIO_PinSource0            ((uint8_t)0x00)
#define GPIO_PinSource1            ((uint8_t)0x01)
#define GPIO_PinSource2            ((uint8_t)0x02)
#define GPIO_PinSource3            ((uint8_t)0x03)
#define GPIO_PinSource4            ((uint8_t)0x04)
#define GPIO_PinSource5            ((uint8_t)0x05)
#define GPIO_PinSource6            ((uint8_t)0x06)
#define GPIO_PinSource7            ((uint8_t)0x07)
#define GPIO_PinSource8            ((uint8_t)0x08)
#define GPIO_PinSource9            ((uint8_t)0x09)
#define GPIO_PinSource10           ((uint8_t)0x0A)
#define GPIO_PinSource11           ((uint8_t)0x0B)
#define GPIO_PinSource12           ((uint8_t)0x0C)
#define GPIO_PinSource13           ((uint8_t)0x0D)
#define GPIO_PinSource14           ((uint8_t)0x0E)
#define GPIO_PinSource15           ((uint8_t)0x0F)
/**
  * @}
  */

宏里面分别定义了GPIO的pin引脚序号(GPIO_PinSource0-15),输入输入模式配置(输入,输出,特殊功能,模拟),输出类型(推挽,开漏),IO速度(高速,低速),输入电阻配置(无上下拉,上拉,下拉)。他们的定义bit位置是经过精心的安排和计算的(比如不同的功能定义占用的bit位置不重叠,方便进行移位运算,和对应的寄存器的操作有一一的对应关系),以便于后续代码设计和简化代码的操作。好了,准备好这些原材料后,我们具体看看代码的实现过程:

  GPIOx->CFGMSK = ~GPIO_Pin;

这第一行代码,非常关键,要明白它的作用,要对应的查看mcu的规格书,我们发现该mcu有一个操作gpio配置寄存器的特殊功能,其说明如下:
在这里插入图片描述
以上说明再翻译一下:这一行代码的作用,就是允许后续写其他IO配置寄存器的时候,只对本次要配置的gpio的对应bit进行写操作,不影响无需配置的其他bit(后面代码解释再说明)。
对应的寄存器定义如下:
在这里插入图片描述好了,接下来看看第二行代码的作用和操作技巧:

  GPIOx->MODER = ((tmp >> 28) & 0x3) * 0x55555555U;

在这里插入图片描述
在这里插入图片描述

该代码操作的对象是端口模式寄存器,对应的寄存器功能如上图(用一个32bit的寄存器来表示16个io的模式配置,每一个io的模式配置位占2bit,并且按照顺序排列).

#define GPIO_MODE_IN          0x00000000U     /*!< Input mode */
#define GPIO_MODE_OUT         0x10000000U     /*!< Output mode */
#define GPIO_MODE_AF          0x20000000U     /*!< Alternate function mode */
#define GPIO_MODE_ANA         0x30000000U     /*!< Analog mode */

结合前面模式定义的宏来理解:
(tmp >> 28) & 0x3-— 该代码的作用就是取到传入的模式配置数据(模式配置定义在最高4个bit,所以先右移位28bit,然后与3,取出来其值),其结果可能的数据为:0,1,2,3,刚好和寄存器的2个bit的4种组合对应:
00 --对应输入模式
01–对应输出模式
10–复用功能模式
11–模拟模式
比较让人疑惑或者难以理解就是后续这个乘以0x55555555U,要理解这个作用,我们把代码换一种写法来看看:

GPIOx->MODER = 0x55555555U  * ((tmp >> 28) & 0x3);

0x55555555U用二进制来看看是什么样子:0101 0101 0101 0101 0101 0101 0101 0101
我们结合寄存器的每两个bit表示一个gpio的模式配置来看看,也就是对应于每一个gpio的配置位初始值为01,如果把这个01和前面的模式值(((tmp >> 28) & 0x3))进行运算,我们发现得到如下结果:
0101 0101 0101 0101 0101 0101 0101 0101: 和0相乘,结果为0000 0000 0000 0000 0000 0000 0000 0000 ,所有GPIO的配置bit为输入模式(00)
0101 0101 0101 0101 0101 0101 0101 0101: 和1相乘,结果不变,所有GPIO的配置bit为输出模式(01)
0101 0101 0101 0101 0101 0101 0101 0101: 和2相乘,相当于左移1位,结果为:1010 1010 1010 1010 1010 1010 1010 1010 所有GPIO的配置bit为复用功能模式(10)
0101 0101 0101 0101 0101 0101 0101 0101: 和3相乘,结果为:1111 1111 1111 1111 1111 1111 1111 1111 ,所有GPIO的配置bit为模拟模式(11)
这真是一个非常高效和简洁,优雅的设计技巧(包括硬件和软件)。
关键点来了,这个值的修改是对所有16个gpio进行同时操作的,如果我只是设置某一个gpio,会不会影响到其他gpio的配置呢?答案是肯定不会。
回到前面我们看看有一个关键寄存器GPIOx->CFGMSK,英文全称应该是config mask,中文翻译为配置辅助寄存器,直译为配置屏蔽寄存器可能更容易理解一些。
GPIOx->CFGMSK = ~GPIO_Pin;通过前面这一条代码的操作,屏蔽了不需要操作的gpio配置位(也就是说关闭了对无关gpio的bit写的作用),比如你本次只是操作gpio0,这条代码就会把对gpio1-15的操作屏蔽,以后写其他配置寄存器(比如前面的MODER寄存器),就只有gpio0对应的bit起作用,其他bit不会影响原来的值。
接下来的其他几条语句的作用类似,参考规格书就可以分析和看明白。
这再一次说明了一个道理,嵌入式开发,软件和硬件要充分结合,才能设计高效的代码。
根据硬件特征,设计合理和简洁的操作代码,也算是一种算法–一种针对硬件进行操作优化的算法。

文章为原创,欢迎转载,请注明出处

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

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

相关文章

【Qt6】QWindow类可以做什么

原来的水文标题是“用 VS Code 搞 Qt6”&#xff0c;想想还是直接改为“Qt6”&#xff0c;反正这个用不用 VS Code 也能搞。虽然我知道大伙伴们都很讨厌 CMake&#xff0c;但毕竟这厮几乎成了 C 的玩家规范了。Qt 也算识大体&#xff0c;支持用 CMake 来构建程序。所以&#xf…

“心理健康人工智能产学研创新联盟”揭牌成立|深兰科技

8月14日上午&#xff0c;“2023树洞救援年会”在上海举行&#xff0c;会上举行了“心理健康人工智能产学研创新联盟”的签约和揭牌仪式。“树洞行动救援团”创始人深兰科技科学院智能科学首席科学家、荷兰阿姆斯特丹自由大学人工智能系终身教授黄智生&#xff0c;深兰科技集团创…

ElementUI 树形表格的使用以及表单嵌套树形表格的校验问题等汇总

目录 一、树形表格如何添加序号体现层级关系 二、树形表格展开收缩图标位置放置&#xff0c;设置指定列 三、表单嵌套树形表格的校验问题以及如何给校验rules传参 普通表格绑定如下&#xff1a;这种方法只能校验表格的第一层&#xff0c;树形需要递归设置子级节点prop。 树…

RFID如何在汽车混流生产中进行车辆跟踪?

在汽车混流生产中&#xff0c;RFID技术可以对每个车辆进行唯一标识&#xff0c;从而实现车辆生产全程跟踪。实时确定车辆的位置、状态和生产过程&#xff0c;生产管理系统就能够对生产流程进行实时监控和管理&#xff0c;及时发现和解决问题&#xff0c;提高生产效率和质量。 焊…

SpringBoot之HandlerInterceptor拦截器的使用

&#x1f600;前言 本篇博文是关于拦截器-HandlerInterceptor的使用&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动…

对发卡涡(Hairpin vortex)初步认识

对发卡涡&#xff08;Hairpin vortex&#xff09;初步认识 Hairpin vortex是一种在流体动力学中常见的涡旋结构。它通常形成在流体中的强烈剪切区域&#xff0c;例如在河流、管道或飞机翼等流体流动中。Hairpin vortex的形状类似于一个发夹弯曲的形状&#xff0c;因此得名&…

(7)(7.1) 使用航点和事件规划任务

文章目录 前言 7.1.1 设置Home位置 7.1.2 视频&#xff1a;制作并保存多路点任务 7.1.3 视频&#xff1a;加载已保存的多航点任务 7.1.4 使用说明 7.1.5 提示 7.1.6 自动网格 7.1.7 任务指令 7.1.8 任务结束 7.1.9 任务重置 7.1.10 MIS_OPTIONS 7.1.11 任务再出发 …

【三维重建】【深度学习】【数据集】基于COLMAP制作自己的NeuS(DTU格式)数据集

【三维重建】【深度学习】【数据集】基于COLMAP制作自己的NeuS(DTU格式)数据集 提示:最近开始在【三维重建】方面进行研究,记录相关知识点,分享学习中遇到的问题已经解决的方法。 文章目录 【三维重建】【深度学习】【数据集】基于COLMAP制作自己的NeuS(DTU格式)数据集前言下载…

浅析3D打印技术

目录 1.3D打印的概念 2.3D打印的发展过程 3.3D打印的应用领域 4.3D打印带来的技术变革 1.3D打印的概念 3D打印是一种制造技术&#xff0c;它使用逐层堆叠材料的方式来创建物体。与传统的加工方法相比&#xff0c;3D打印具有很多优势。 在3D打印中&#xff0c;一种叫做CAD&am…

深度解读智能化编码的技术架构与实践案例

向更智能、更兼容演进。 陈高星&#xff5c;演讲者 大家好&#xff0c;我是阿里云视频云的陈高星&#xff0c;今天和大家分享的主题是“多”维演进&#xff1a;智能化编码架构的研究与实践。 本次分享分为四部分&#xff1a;首先是视频编码与增强方向的业界趋势&#xff0c;其…

Apple Watch 9和Apple Watch 8功能差异对比:预期升级浅析

每年的这个时候,我们都会想知道Apple Watch Series 9和Apple Watch Series 8之间会有什么不同。随着苹果下一代智能手表预计将于9月上市,我们渴望了解该公司即将进行的升级。 Apple Watch Series 8是目前最好的智能手表,但根据Apple Watch Series 9的改进,它可能会成为我们…

为什么C语言全局变量初始化元素必须是常量,而局部变量可以不是常量

前言 &#xff08;1&#xff09;今天看到一个有意思的问题&#xff0c;在交流群中&#xff0c;一位网友问&#xff0c;全局变量为什么不能给变量。会出现initializer element is not constant报错&#xff0c;代码如下 #include <stdio.h>int a 1; int b a1; //这里会报…

Zabbix-6.4.4 邮箱告警SMS告警配置

目录 ​------------------------- # 邮箱告警 ---------------------------------- 1.安装mailx与postfix软件包 2.修改mailx配置文件 3. 创建文件夹 4. 编写mail-send.sh脚本 5. 将该脚本赋予执行权限 6. 进入web界面进行设置—> Alerts —> Media Types 7. 添…

C++11并发与多线程笔记(3)线程传参详解,detach()大坑,成员函数做线程函数

C11并发与多线程笔记&#xff08;3&#xff09;线程传参详解&#xff0c;detach 大坑&#xff0c;成员函数做线程函数 1、传递临时对象作为线程参数1.1 要避免的陷阱11.2 要避免的陷阱21.3 总结 2、临时对象作为线程参数2.1 线程id概念2.2 临时对象构造时机抓捕 3、传递类对象…

数据结构算法--3快速排序

快速排序比冒泡排序&#xff0c;选择排序&#xff0c;插入排序速度都快 快速排序思路&#xff1a; ^取一个元素P&#xff0c;(第一个元素)&#xff0c;使元素P归位。 ^列表被P分成了两部分&#xff0c;左边都比P小&#xff0c;右边都比P大。 ^递归完成排序。 过程: 把5拿出…

FreeRTOS中断优先级测试

目录 资源配置 测试方案 易出 bug 测试代码 资源配置 1、定时器3中断优先级为3 2、定时器4中断优先级为4 3、FreeRTOS中断配置 OS系统管理不高于中断优先级4的中断。 #ifdef __NVIC_PRIO_BITS /* __NVIC_PRIO_BITS 已经在stm32f1xx.h里面定义为4 */#define configPRIO_B…

poetry add scipy || Poetry安装scipy失败

这里出现了一个使用poetry安装scipy失败的报错 poetry add scipy 大致意思是说&#xff0c;找不到需要的库和目前的python在版本上的交集&#xff0c;所以安装不了。 这可能与自己在poetry环境中已经安装的库有关。 但不管怎么说&#xff0c;我的这个3.10版本会装不了都是一…

【STM32RT-Thread零基础入门】 4. 线程介绍(理论)

文章目录 前言一、线程的概念二、线程的调度三、上下文切换四、线程的重要属性1. 线程栈2. 线程的状态3. 线程优先级4. 线程时间片5. 线程的入口函数 五、RT-Thread命令查看系统线程信息总结 前言 前文中的最后一个任务发现&#xff0c;一个main()函数很难同时实现按键功能和闪…

基于浏览器的插件sider,使用chatgpt3.5生成的连接Redis的代码

一.安装 首先在浏览器安装Sider插件&#xff0c;具体安装步骤https://www.423xz.com/gaoxiao/3949.html 收费情况&#xff1a;每天有几次的免费使用chatgpt3.5的次数&#xff0c;chatgpt4需要付费了。 二.使用步骤&#xff1a; 安装后使用&#xff0c;具体步骤&#xff1a;…

20 张图,透析HTTPs五大知识点

本文详细介绍了 HTTPS 相较于 HTTP 更安全的原因&#xff0c;包括对称加密、非对称加密、完整性摘要、数字证书以及 SSL/TLS 握手等内容&#xff0c;图文并茂、理论与实战结合、建议收藏&#xff01; 1. 不安全的 HTTP 近些年来&#xff0c;越来越多的网站使用 HTTPS 协议进行…