STM32第八节:位带操作——GPIO输出和输入

前言

        我们讲了GPIO的输出,虽然我们使用的是固件库编程,但是最底层的操作是什么呢?对,我们学习过51单片机的同学肯定学习过 sbit 修改某一位的高低电平,从而实现对于硬件的控制。那么我们现在在STM32中有没有相似的操作呢?答案肯定是有的。那么我们今天就来讲讲位带操作。


创作不易,点个三连不迷路!!!

STM32第八节:位带操作——GPIO输出和输入

位带

位带简介

P0=0xFE;    //总线操作sbit LED1 = P0^0;   //位操作
LED1 = 0;

        如此这般,就是总线操作与位操作的区别(在51单片机中)。那么我们在32中该如何操作呢          位操作就是可以单独的对一个bit位进行读和写的过程。51 单片机中通过关键字 sbit 来实现位定义,而 STM32 是通过访问位带别名区来实现。
        在 STM32 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,另一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别
名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些
字时,就可以达到访问位带区某bit位的目的。

        如图所示, 我们以ODR寄存器为例,而位带操作就是把寄存器中的每一个位都重新找了个地址。这个地址在位带别名区内,而且在位带别名区里会膨胀成4个字节,但是操作的时候只有最低位有效(ODR0)。

位带区分布 

         在位带操作中,不止止是片上外设会有位带操作,而且SRAM也会有1MB的位带区,位带区里面的每一个位都可以通过位带别名区的地址来访问(一位为四个字节)。

         结合上述例子,我们知道GPIO_ODR的基地址,那么我们怎么知道每一位所对应的地址呢?那么我们就有了位带区与位带别名区地址转换。

位带区与位带别名区地址转换

位带区地址与位带别名区的地址之间的转换        

         接着我就来给大家讲解一下转换的公式的具体含义及代码展示。

        前一个呢是位带别名区地址,0x 4200 0000 和 0x 2200 0000.接着呢就是(A-0x 2000 0000),这个算出来的是偏移地址(字节)。一个字节有八个位,而一个位是四个字节。n*4为字节的偏移数,n为位号。从公式来看,我们只需要知道A以及n就可以算出位带地址。再编程上来说,可以统一用一个公式表示:

// AliasAddr= =0x42000000+ (A-0x40000000)*8*4 +n*4
// AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4// 把“位带地址 + 位序号”转换成别名地址的宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2))

         为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址 + 位序号”转换成别名区地址统一成一个宏。

        addr & 0xF0000000 是为了区别 SRAM 还是外设,实际效果就是取出 4 或者 2,如果是外设,则取出的是 4,+0X02000000 之后就等于 0X42000000,0X42000000 是外设别名区的起始地址。如果是 SRAM,则取出的是 2,+0X02000000 之后就等于 0X22000000,0X22000000 是 SRAM 别名区的起始地址。addr & 0x00FFFFFF 屏蔽了高三位,相当于减去 0X20000000 或者 0X40000000。

        外设的最高地址是:0X20100000,跟起始地址 0X20000000 相减的时候,总是低5位才有效,所以就把高三位屏蔽掉来达到减去起始地址的效果,具体屏蔽掉多少位跟最高地址有关。SRAM 同理分析。«5 相当于 *8*4,«2 相当于 *4。

        最后我们就可以通过指针的形式操作这些位带别名区地址,最终实现位带区的bit位操作。

// 把一个地址转换成一个指针
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))// 把位带别名区地址转换成指针
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))

编写程序——实现GPIO上的位带输出操作

使用位带的方式访问GPIO的ODR寄存器

        拷贝一份上节课的代码,并稍作修改,使用条件编译使得上一部分代码编译,下一部分代码不编译。我们看到上一部分代码是使得LED2(即绿色)自动亮灭亮灭,中间稍作迟缓:

#include "stm32f10x.h"   // 相当于51单片机中的  #include <reg51.h>
#include "bsp_led.h"
#include "bsp_key.h"void Delay(uint32_t count)
{for(;count!=0;count--);
}int main(void)
{	LED_GPIO_Config();LED_KEY_Config();
#if 1while(1){//GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);LED2(OFF);Delay(0xFFFFF);//GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);LED2(ON);Delay(0xFFFFF);}
#elsewhile(1)                            {	   if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON  ){LED1(ON);}if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON  ){LED2_TOGGLE;}	}
#endif
}

        那么输出怎么写呢?我们现在开始操作ODR寄存器,从公式上看,我们要先写进去通用的公式,然后用n代替参量写一个带参宏出来,然后强制转换为地址,并使用指针操作加上 * 。然后再定义一个GPIOB_ODR_Addr用来表示(GPIOB_BASE+0x0c)基地址加偏移量:

#define GPIOB_ODR_Addr       (GPIOB_BASE+0x0c)
#define PBOut(n)             *(unsigned int*)((GPIOB_ODR_Addr & 0xF0000000)+0x02000000+((GPIOB_ODR_Addr & 0x00FFFFFF)<<5)+(n<<2))

        然后我们就可以在main函数中做修改,如此这般,我们就通过位带操作实现了操作寄存器从而实现LED灯的亮灭:

while(1){PBOut(0) = 1;Delay(0xFFFFF);PBOut(0) = 0;Delay(0xFFFFF);}

完整代码展示

#include "stm32f10x.h"   // 相当于51单片机中的  #include <reg51.h>
#include "bsp_led.h"
#include "bsp_key.h"#define GPIOB_ODR_Addr       (GPIOB_BASE+0x0c)
#define PBOut(n)             *(unsigned int*)((GPIOB_ODR_Addr & 0xF0000000)+0x02000000+((GPIOB_ODR_Addr & 0x00FFFFFF)<<5)+(n<<2))void Delay(uint32_t count)
{for(;count!=0;count--);
}int main(void)
{	LED_GPIO_Config();LED_KEY_Config();#if 1while(1){PBOut(0) = 1;Delay(0xFFFFF);PBOut(0) = 0;Delay(0xFFFFF);}
#elsewhile(1)                            {	   if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==KEY_ON)LED1(ON);if(Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==KEY_ON)LED2_TOGGLE;}
#endif
}

 同样的原理打开别的颜色的灯

        从原理图可知,我们要打开BLUE灯,就是打开PB1口,即把PBOut(0)改为PBOut(1)即可,其他的以此类推。

编写程序——实现GPIO上的位带输入操作

使用位带的方式访问GPIO的IDR寄存器

        输入的话就该操作下面一部分代码了。首先我们要算出IDR的这个第0位地址,为:

#define GPIOA_IDR_Addr       (GPIOA_BASE+0x08)
#define PAin(n)              *(unsigned int*)((GPIOA_IDR_Addr & 0xF0000000)+0x02000000+((GPIOA_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))#define GPIOC_IDR_Addr       (GPIOA_BASE+0x08)
#define PCin(n)              *(unsigned int*)((GPIOC_IDR_Addr & 0xF0000000)+0x02000000+((GPIOC_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))

         那么我们发现我们写的Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN)和Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN)函数就没有用了,我们在这里修改为:

while(1)                            {	   
//		if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==KEY_ON)
//			LED1(ON);
//		if(Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==KEY_ON)
//			LED2_TOGGLE;if(PAin(0)==KEY_ON){while(PAin(0)==KEY_ON);LED1(ON);}if(PCin(13)==KEY_ON){while(PCin(13)==KEY_ON);LED2_TOGGLE;}}

        这里我们使用了两种方式来实现按键控制LED灯的亮灭,分别控制PA0和PC13端口。 

完整代码展示

#include "stm32f10x.h"   // 相当于51单片机中的  #include <reg51.h>
#include "bsp_led.h"
#include "bsp_key.h"#define GPIOA_IDR_Addr       (GPIOA_BASE+0x08)
#define PAin(n)              *(unsigned int*)((GPIOA_IDR_Addr & 0xF0000000)+0x02000000+((GPIOA_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))#define GPIOC_IDR_Addr       (GPIOA_BASE+0x08)
#define PCin(n)              *(unsigned int*)((GPIOC_IDR_Addr & 0xF0000000)+0x02000000+((GPIOC_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))int main(void)
{	LED_GPIO_Config();LED_KEY_Config();while(1)                            {	   if(PAin(0)==KEY_ON){while(PAin(0)==KEY_ON);LED1(ON);}if(PCin(13)==KEY_ON){while(PCin(13)==KEY_ON);LED2_TOGGLE;}}
}

小结

        到这里我们的课程就结束啦,从下一节开始就是中级篇的讲解了,我们下次见咯!


创作不易,点个三连不迷路!!!

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

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

相关文章

前端面试 ===> 【Vue2】

Vue2 相关面试题总结 1. 谈谈对Vue的理解 Vue是一种用于构建用户页面的渐进式JavaScript框架&#xff0c;也是一个创建SPA单页面应用的Web应用框架&#xff0c;Vue的核心是 数据驱动试图&#xff0c;通过组件内特定的方法实现视图和模型的交互&#xff1b;特性&#xff1a;&a…

pkav之当php懈垢windows通用上传缺陷

环境&#xff1a; Windowsnginxphp 一、php源码 <?php //U-Mail demo ... if(isset($_POST[submit])){$filename $_POST[filename];$filename preg_replace("/[^\w]/i", "", $filename);$upfile $_FILES[file][name];$upfile str_replace(;,&qu…

清华把大模型用于城市规划,回龙观和大红门地区成研究对象

引言&#xff1a;参与式城市规划的新篇章 随着城市化的不断推进&#xff0c;传统的城市规划方法面临着越来越多的挑战。这些方法往往需要大量的时间和人力&#xff0c;且严重依赖于经验丰富的城市规划师。为了应对这些挑战&#xff0c;参与式城市规划应运而生&#xff0c;它强…

【文献阅读】A Fourier-based Framework for Domain Generalization(基于傅立叶的领域泛化框架)

原文地址&#xff1a;https://arxiv.org/abs/2105.11120 摘要 现代深度神经网络在测试数据和训练数据的不同分布下进行评估时&#xff0c;存在性能下降的问题。领域泛化旨在通过从多个源领域学习可转移的知识&#xff0c;从而泛化到未知的目标领域&#xff0c;从而解决这一问…

面试复盘记录(数据开发)

一、apple外包1.矩阵顺时针旋转遍历2.两表取差集 二、 一、apple外包 没问理论&#xff0c;就两个算法题。 1.矩阵顺时针旋转遍历 Given an m x n matrix, return all elements of the matrix in spiral order.Example 1:Input: matrix [[1,2,3],[4,5,6],[7,8,9]] Output: …

【LeetCode热题100】141. 环形链表(链表)

一.题目要求 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置…

基于FPGA的图像锐化算法(USM)设计

免费获取源码请关注微信号《FPGA学习笔记册》&#xff01; 1.图像锐化算法说明 图像锐化算法在实际的图像处理应用很广泛&#xff0c;例如&#xff1a;医学成像、工业检测和军事领域等&#xff1b;它的作用就是将模糊的图像变的更加清晰。常用的图像锐化算法有拉普拉斯算子、s…

基于SpringCache实现数据缓存

SpringCache SpringCache是一个框架实现了基本注解的缓存功能,只需要简单的添加一个EnableCaching 注解就能实现缓存功能 SpringCache框架只是提供了一层抽象,底层可以切换CacheManager接口的不同实现类即使用不同的缓存技术,默认的实现是ConcurrentMapCacheManagerConcurren…

SpringBoot(Lombok + Spring Initailizr + yaml)

1.Lombok 1.基本介绍 2.应用实例 1.pom.xml 引入Lombok&#xff0c;使用版本仲裁 <!--导入springboot父工程--><parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version&g…

wy的leetcode刷题记录_Day86

wy的leetcode刷题记录_Day86 声明 本文章的所有题目信息都来源于leetcode 如有侵权请联系我删掉! 时间&#xff1a;2024-3-13 前言 目录 wy的leetcode刷题记录_Day86声明前言2864. 最大二进制奇数题目介绍思路代码收获 3. 无重复字符的最长子串题目介绍思路代码收获 438. 找…

Vite为什么比Webpack快

本文作者为 360 奇舞团前端开发工程师 一.引言 Vite和Webpack作为两个主流的前端构建工具&#xff0c;在近年来备受关注。它们的出现使得前端开发变得更加高效和便捷。然而&#xff0c;随着前端项目规模的不断增大和复杂度的提升&#xff0c;构建工具的性能优化也成为了开发者关…

【Mac】鼠标控制\移动\调整窗口大小BBT|边缘触发调整音量\切换桌面

一直在 win 习惯了通过鼠标的侧键来控制窗口的位置、大小&#xff0c;现在找到心的解决方案了&#xff0c;通过 BBT 设置侧键按下\抬起几颗。 以下解决方案的截图&#xff0c;其中还包括了其他操作优化方案&#xff1b; 滚轮配合 cmd 键调节页面大小&#xff1b;配合 option 键…