• 二、stm32-位带操作点亮LED(附模板及案例程序)


    一、固件库模板

    见博客:stm32f103c8t6新建固件库模板(可自取)

    二、准备资料

    1. 固件库模板
    2. MDK5开发环境
    3. 上一个博客工程--------------一、利用固件库模板点灯(附模板及案例程序)
      实验程序已经发布到百度网盘,本文末有链接可以自取

    三、位带简介

    位操作就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。 51 单片机中通过关
    键字 sbit 来实现位定义, STM32 没有这样的关键字,而是通过访问位带别名区来实现。
    在 STM32 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,令一个是外设区最
    低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别
    名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些
    字时,就可以达到访问位带区某个比特位的目的。

    1.外设位带区

    外 设 外 带 区 的 地 址 为: 0X40000000~0X40100000, 大 小 为 1MB, 这 1MB 的 大 小 在
    103 系 列 大/中/小 容 量 型 号 的 单 片 机 中 包 含 了 片 上 外 设 的 全 部 寄 存 器, 这 些 寄 存 器
    的 地 址 为: 0X40000000~0X40029FFF。 外 设 位 带 区 经 过 膨 胀 后 的 位 带 别 名 区 地 址 为:
    0X42000000~0X43FFFFFF,这个地址仍然在 CM3 片上外设的地址空间中。在 103 系列
    大/中小容量型号的单片机里面, 0X40030000~0X4FFFFFFF 属于保留地址,膨胀后的 32MB 位带
    别名区刚好就落到这个地址范围内,不会跟片上外设的其他寄存器地址重合。
    STM32 的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器比特位的效果,
    这比 51 单片机强大很多。因为 51 单片机里面并不是所有的寄存器都是可以比特位操作,有些寄
    存器还是得字节操作,比如 SBUF。
    虽然说全部寄存器都可以实现比特操作,但我们在实际项目中并不会这么做,甚至不会这么做。
    有时候为了特定的项目需要,比如需要频繁的操作很多 IO 口,这个时候我们可以考虑把 IO 相关
    的寄存器实现比特操作。

    2.SRAM 位带区

    SRAM 的位带区的地址为: 0X2000 0000~X2010 0000,大小为 1MB,经过膨胀后的位带别名区
    地址为: 0X2200 0000~0X23FF FFFF,大小为 32MB。操作 SRAM 的比特位这个用得很少。

    3.位带区和位带别名区地址转换

    位带区的一个比特位经过膨胀之后,虽然变大到 4 个字节,但是还是 LSB 才有效。有人会问这
    不是浪费空间吗,要知道 STM32 的系统总线是 32 位的,按照 4 个字节访问的时候是最快的,所
    以膨胀成 4 个字节来访问是最高效的。
    我们可以通过指针的形式访问位带别名区地址从而达到操作位带区比特位的效果。那这两个地
    址直接如何转换,我们简单介绍一下。

    3.1 外设位带别名区地址

    对于片上外设位带区的某个比特,记它所在字节的地址为 A, 位序号为 n(0<=n<=7),则该比特在
    别名区的地址为:

    AliasAddr= =0x42000000+ (A-0x40000000)84 +n*4

    0X42000000 是外设位带别名区的起始地址, 0x40000000 是外设位带区的起始地址, (A-
    0x40000000)表示该比特前面有多少个字节,一个字节有 8 位,所以 *8,一个位膨胀后是 4 个字
    节,所以 *4, n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字节,所以也 *4。

    3.2 SRAM 位带别名区地址

    对于 SRAM 位带区的某个比特,记它所在字节的地址为 A, 位序号为 n(0<=n<=7),则该比特在别
    名区的地址为:

    AliasAddr= =0x22000000+ (A-0x20000000)84 +n*4
    公式分析同上。

    3.3 统一公式

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

    // 把“位带地址 + 位序号”转换成别名地址的宏
    #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr &
    ,0x00FFFFFF)<<5)+(bitnum<<2))
    
    • 1
    • 2
    • 3

    addr & 0xF0000000 是为了区别 SRAM 还是外设,实际效果就是取出 4 或者 2,如果是外设,则
    取出的是 4, +0X02000000 之后就等于 0X42000000, 0X42000000 是外设别名区的起始地址。如
    果是 SRAM,则取出的是 2, +0X02000000 之后就等于 0X22000000, 0X22000000 是 SRAM 别名
    区的起始地址

    外设的位带区,覆盖了全部的片上外设的寄存器,我们可以通过宏为每个寄存器的位都定义一个
    位带别名地址,从而实现位操作。但这个在实际项目中不是很现实,也很少人会这么做,我们的工程提供了sys.h的文件,里面定义的宏可以对GPIO口的输入和输出进行位操作

    四、 实验步骤

    1.打开工程

    在这里插入图片描述

    2.编写代码

    led.c

    stm32f103c8t6自带一个led灯,使用PC13引脚就行了,

    切记尽量避免使用PB3、PB4,具体看stm32f103c8t6使用PB3和PB4做普通GPIO使用时发现异常

    #include "led.h"	//绑定led.h
    
    void LED_GPIO_Config(void) {
        GPIO_InitTypeDef GPIO_InitStruct; //初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。
        //开启RCC时钟
        RCC_APB2PeriphClockCmd(LED_G_GPIO_CLK, ENABLE);
    
        //配置初始化,推挽输出方式和LED_G_GPIO_PIN管脚、赫兹
        GPIO_InitStruct.GPIO_Pin = LED_G_GPIO_PIN;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        //GPIO口初始化
        GPIO_Init(LED_G_GPIO_PORT, &GPIO_InitStruct);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    led.h

    #ifndef __LED_H_
    #define __LED_H_
    
    #include "stm32f10x.h"
    #include "sys.h"
    
    #define LED_G_GPIO_PIN         GPIO_Pin_13
    #define LED_G_GPIO_PORT        GPIOC
    #define LED_G_GPIO_CLK         RCC_APB2Periph_GPIOC
    //使用位带操作来实现操作某个IO口的 1个位,由sys.h实现
    #define LED 				   PCout(13)
    
    void LED_GPIO_Config(void); //函数定义
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    main.c

    #include "delay.h"
    #include "sys.h"
    #include "usart.h"
    #include "stm32f10x.h"
    #include "led.h"		//引用led文件
    
    int main()
    {
        /********************************************************************************
        *    Delay_init();				  //本实验使用的是SysTick时钟
        *    CPU_TS_TmrInit();      //已经使能宏,不需要初始化
        *    uart1_init(115200);	 	//串口初始化为115200,需要在usart.h中使能
        *    uart3_init(115200);	 	//串口初始化为115200
        ********************************************************************************/
    
        /* 初始化 */
        LED_GPIO_Config();		//初始化led使用的GPIO口
        while (1) {
            PCout(13)=1; 		//PC口13引脚输出,高电平
            Delay_ms(500);		//已经在delay.h中初始化
    			
    		PCout(13)=0;		//PC口13引脚输出,低电平
    		Delay_ms(500);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    PCout();这个函数在sys.h宏定义,
    在这里插入图片描述

    3.编译

    在这里插入图片描述
    可以看到没有错误,也没有警告。

    4. 选择烧录工具并配置MDK

    本文选择的是ST_Link烧录工具
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    成品

    在这里插入图片描述

    工程链接

    链接:https://pan.baidu.com/s/1siTquc6ZntuzaAPWvS7ioA
    提取码:0000

  • 相关阅读:
    C/C++指针入门详解(一)
    走进Oracle世界
    AtCoder ABC239G 最小割集
    第2章 Linux的进程管理
    SSRF绕过思路
    不可谓不“细”阿里内部价值百万“微服务架构精髓”限时开源
    springboot毕设项目高校招生预报管理系统f10sf(java+VUE+Mybatis+Maven+Mysql)
    CTFHUB-WEB-WEB前置技能-HTTP协议-302跳转
    1031 Hello World for U
    SpringBoot框架
  • 原文地址:https://blog.csdn.net/weixin_55999942/article/details/126093022