STM32开发笔记37: 485总线的收发切换时间_STM32库开发实战指南:基于STM32F4

STM32开发笔记37: 485总线的收发切换时间_STM32库开发实战指南:基于STM32F4架构图带位操作原理以往我们在使用暂存器时,都是在操作该暂存器32bits(4bytes)的储存地址,要对其中单一bit进行操作,可以仰赖bitoperation来完成。而bit-banding(位带操作)的目的就是实现直接操作单一比特位为了达成这个目的首先我们有必要理解STM32的特殊存储区段位带区与位带别名区。下图中的红色方框代表位带区的范围,可以看到SRAM中位带区佔有1MB,外设区段部分,1MB的储存范围刚好涵盖了AHB,APB1,APB2,可以说我们使用的大部分外设,例如GPIO,

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

架构图

STM32开发笔记37: 485总线的收发切换时间_STM32库开发实战指南:基于STM32F4

带位操作原理

以往我们在使用暂存器时,都是在操作该暂存器32bits(4bytes)的储存地址,要对其中单一bit进行操作,可以仰赖bit operation来完成。而bit-banding(位带操作)的目的就是实现直接操作单一比特位

为了达成这个目的首先我们有必要理解STM32的特殊存储区段位带区位带别名区

下图中的红色方框代表位带区的范围,可以看到SRAM中位带区佔有1MB,外设区段部分,1MB的储存范围刚好涵盖了AHB, APB1, APB2,可以说我们使用的大部分外设,例如GPIO, UART, SPI等等都在位带区当中

外设的位带区地址范围: 0x40000000~0x400F0000
SRAM的位带区地址范围: 0x20000000~0x200FFFFF

STM32开发笔记37: 485总线的收发切换时间_STM32库开发实战指南:基于STM32F4

位带区不仅可以用来储存暂存器数据外还有一个膨胀32倍位带别名区,这个位带别名区就是操作单一比特位的关键。首先我们来看看为何位带别名区是位带区的32倍

如图所示,每个比特位都可以用一个完整的32bits地址来代替,有就是说一个32bits的暂存器在位带别名区会被扩展成32*32=1024bits,因此位带别名区的储存空间为1MB的位带区空间乘上32等于32MB。由此可知,我们可以直接操作位带别名区地址来达到操作位带区单一比特位的目的

STM32开发笔记37: 485总线的收发切换时间_STM32库开发实战指南:基于STM32F4

需要注意膨胀过后的位带别名区只有LSB(最低位)比特位有效

地址转换

不过要操作位带别名区之前须要先知道位带区与位带别名区之间的关係,幸好地址之间的转换是有迹可循的:

  • 外设地址转换公式: 0x42000000 + (A-0x40000000)*32+n*4
  • SRAM地址转换公式: 0x22000000 + (A-0x20000000)*32+n*4

上述公式可以拆解成: 位带别名区地址 = 位带别名区首地址 + (目标位带区地址 – 位带区首地址)*32 + 第n个比特位*4

该转换公式简单来说就是地址膨胀后的位置运算,各个位带别名区的起始位置为:

  • 外设位带别名区首地址: 0x42000000
  • SRAM位带别名区首地址: 0x22000000

为了在运算当中不用在程式中编写两个不同的运算式,我们试着将两个公式用一条语句来完成:

(bit_band_addr & 0xf0000000) + 0x02000000 + ((bit_band_addr & 0x000fffff << 5) + n << 2)

程式案例

位带操作的实作非常简单,主要就下列4个步骤,其中最重要的就是地址转换以及转换成指标类型

  1. 寻找指定位带区地址
  2. 转换成位带别名地址
  3. 转换成指标类型
  4. 进行赋值

因为我们计算出来的”地址”充其量也就只是一个整数值而已,必须经过类型转换告诉编译器它是一”地址”,引此我们使用macro来处理运算出来的位带别名区数值

下列程式码我们只要使用macroBIT_GPIOH(暂存器地址, 比特位)就可以成功转换成位带别名区地址:

#define BITBAND(addr, bitnum) ((addr&0xf0000000) + 0x2000000 + ((addr&0x000fffff)<<5) + (bitnum<<2))
#define MEM_ADDR(addr) *(volatile uint32_t*)(addr)
#define BIT_GPIOH(addr, n) MEM_ADDR(BITBAND(addr, n))

举个简单的LED操作来说,透过位带别名区也可以控制GPIO的输出,例如以下范例程式码,只要知道别名区地址后,操作方式其实跟普通的暂存器操作无二致:

#include "bsp_led.h"
void GPIO_Initial_API(uint16_t LED){	
    BIT_GPIOH(BIT_MODER, (LED*2)) = SET;
		BIT_GPIOH(BIT_MODER, ((LED*2)+1)) = RESET;
	
		BIT_GPIOH(BIT_TYPER, LED) = GPIO_OType_PP;
	
		BIT_GPIOH(BIT_SPEEDER, (LED*2)) = SET;
		BIT_GPIOH(BIT_SPEEDER, ((LED*2)+1)) = SET;

		BIT_GPIOH(BIT_PuPr, (LED*2)) = SET;
		BIT_GPIOH(BIT_PuPr, ((LED*2)+1)) = RESET;
	
		BIT_GPIOH(BIT_ODR, LED) = SET;
}

void LED_Init(void){
		/*Enable peripheral cloack*/
		RCC_AHB1PeriphClockCmd(LED_RCC, ENABLE);
		
		/*Initialize pin to set*/
		GPIO_Initial_API(LED1);
		GPIO_Initial_API(LED2);
		GPIO_Initial_API(LED3);
}

void LED_button_toggle(uint16_t LED, uint8_t key){
	if(key){
		BIT_GPIOH(BIT_ODR, LED) = BIT_GPIOH(BIT_IDR, LED) ^ 0x01;
	}
}

附上header对照

#ifndef __BSP_LED_H_
#define __BSP_LED_H_
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include <stdio.h>
#include <stdint.h>
#include "time.h"

/*Increase portability of bsp_led*/
#define LED_Red_PIN GPIO_Pin_10
#define LED_Green_PIN GPIO_Pin_11
#define LED_Blue_PIN GPIO_Pin_12
#define LED_PORT GPIOH
#define LED_RCC RCC_AHB1Periph_GPIOH

#define LED1 10
#define LED2 11
#define LED3 12

#define SET 1
#define RESET 0

#define BIT_MODER (GPIOH_BASE)
#define BIT_TYPER (GPIOH_BASE + 0x04)
#define BIT_SPEEDER (GPIOH_BASE + 0x08)
#define BIT_PuPr (GPIOH_BASE + 0x0c)
#define BIT_IDR (GPIOH_BASE + 0x10)
#define BIT_ODR (GPIOH_BASE + 0x14)


#define BITBAND(addr, bitnum) ((addr&0xf0000000) + 0x2000000 + ((addr&0x000fffff)<<5) + (bitnum<<2))
#define MEM_ADDR(addr) *(volatile uint32_t*)(addr)
#define BIT_GPIOH(addr, n) MEM_ADDR(BITBAND(addr, n))

extern void LED_Init(void);
extern void LED_button_toggle(uint16_t PIN, uint8_t key);

#endif /*__BSP_LED_H_*/

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/182377.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • C++ 23种设计模式(6)-适配器模式

    C++ 23种设计模式(6)-适配器模式适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。它包括类适配器和对象适配器,本文针对的是对象适配器。举个例子,在STL中就用到了适配器模式。STL实现了一种数据结构,称为双端队列(deque),支持前后两段的插入与删除。STL实现栈和队列时,没有从头开始定义它们,而是直接使用双端队列实现的。这里双端队列就扮演了适配器的角色。队列用到了它的后端插入,前端删除。而栈用到了它的后端插入,后端删除。假设栈和队列都是一种顺序容器,有两种操作:压入和弹出。

    2022年7月25日
    12
  • 浏览器无法复制文字解决办法是什么_粘贴复制浏览器里下载

    浏览器无法复制文字解决办法是什么_粘贴复制浏览器里下载以谷歌浏览器为例:第一步F12:打开开发者选项第二步:F1:设置项第三步:拉到最下面Debugger下的DisableJavaScript,勾选上,然后就可以复制了。注意这时候按钮都失效了,因为我们禁用了脚本。所以复制完成后再把√去掉即可。…

    2022年10月13日
    1
  • python基础知识思维导图总结

    python基础知识思维导图总结今天给同学用思维导图整理了python基础知识,供大家复习参考学习,希望每天都有进步。鸡汤几点:没有目标永远不知道方向在哪?不去尝试永远不知道答案和结果?打破习惯、冲击惯性思维、不断学习,才能使你越来越有价值。行动永远比坐以待毙更幸运。主要内容:一、Python简介二、输入输出三、变量、数据基本类型与操作四、条件判断五、常用数据类型–列表六、常用数据类型–元组tuple、字典dict(全称dictionary)七、常用数据类型–布尔值(True

    2022年10月16日
    3
  • ios事件-触摸事件2(手势 和 pointInSide()、hitTest()、touchesBegan()、touchesMoved()、touchesEnded()的关系)

    ios事件-触摸事件2(手势 和 pointInSide()、hitTest()、touchesBegan()、touchesMoved()、touchesEnded()的关系)ios事件-触摸事件2(手势和pointInSide()、hitTest()、touchesBegan()、touchesMoved()、touchesEnded()、touchesCancelled()的关系)先看效果图本文中,凡是看到xxx(),即表示xxx是一个函数或者方法!!!事件分为事件传递和事件响应,其中,事件响应又称事件处理。具体代码FindViewViewContr…

    2022年7月25日
    7
  • 放出一套完整的进销存软件源码[通俗易懂]

    放出一套完整的进销存软件源码[通俗易懂]商易进销存2.0vb+sqlserver下载地址:http://60.2.39.130/microerp/源码非常完整,从速下载,过期失效,不接受菜鸟们的技术帮助请求。

    2022年5月31日
    206
  • android 抛出FileNotFoundException异常

    android 抛出FileNotFoundException异常大家都知道,Android6.0中,某些权限属于ProtectedPermission,例如:读写手机存储权限,仅仅在AndroidManifest.xml中申明是无法真正获取到权限的,打开手机的权限管理页面,我们可以看见,读写手机存储权限栏是一个问号,这意味着App并未获取到该权限。这是访问手机存储时,会报出类似下面的错误:java.io.FileNotFoundExcept…

    2025年6月26日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号