工具Clion和STM32CubeMx
位操作就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。 51 单片机中通过关键字 sbit 来实现位定义, STM32 没有这样的关键字,而是通过访问位带别名区来实现。
在 STM32 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,令一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。
外 设 外 带 区 的 地 址 为: 0X40000000~0X40100000, 大 小 为 1MB, 这 1MB 的 大 小 在103 系 列 大/中/小 容 量 型 号 的 单 片 机 中 包 含 了 片 上 外 设 的 全 部 寄 存 器, 这 些 寄 存 器的 地 址 为: 0X40000000~0X40029FFF。 外 设 位 带 区 经 过 膨 胀 后 的 位 带 别 名 区 地 址 为:
0X42000000~0X43FFFFFF,这个地址仍然在 CM3 片上外设的地址空间中。在 103 系列
大/中小容量型号的单片机里面, 0X40030000~0X4FFFFFFF 属于保留地址,膨胀后的 32MB 位带别名区刚好就落到这个地址范围内,不会跟片上外设的其他寄存器地址重合。
STM32 的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器比特位的效果,
这比 51 单片机强大很多。因为 51 单片机里面并不是所有的寄存器都是可以比特位操作,有些寄存器还是得字节操作,比如 SBUF。虽然说全部寄存器都可以实现比特操作,但我们在实际项目中并不会这么做,甚至不会这么做。有时候为了特定的项目需要,比如需要频繁的操作很多 IO 口,这个时候我们可以考虑把 IO 相关的寄存器实现比特操作。
SRAM 的位带区的地址为: 0X2000 0000~X2010 0000,大小为 1MB,经过膨胀后的位带别名区
地址为: 0X2200 0000~0X23FF FFFF,大小为 32MB。操作 SRAM 的比特位这个用得很少。
位带区的一个比特位经过膨胀之后,虽然变大到 4 个字节,但是还是 LSB 才有效。有人会问这
不是浪费空间吗,要知道 STM32 的系统总线是 32 位的,按照 4 个字节访问的时候是最快的,所
以膨胀成 4 个字节来访问是最高效的。
我们可以通过指针的形式访问位带别名区地址从而达到操作位带区比特位的效果。那这两个地
址直接如何转换,我们简单介绍一下。
对于片上外设位带区的某个比特,记它所在字节的地址为 A, 位序号为 n(0<=n<=7),则该比特在
别名区的地址为:
AliasAddr= =0x42000000+ (A-0x40000000)84 +n*4
0X42000000 是外设位带别名区的起始地址, 0x40000000 是外设位带区的起始地址, (A-
0x40000000)表示该比特前面有多少个字节,一个字节有 8 位,所以 *8,一个位膨胀后是 4 个字
节,所以 *4, n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字节,所以也 *4。
对于 SRAM 位带区的某个比特,记它所在字节的地址为 A, 位序号为 n(0<=n<=7),则该比特在别
名区的地址为:
AliasAddr= =0x22000000+ (A-0x20000000)84 +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 别名
区的起始地址
外设的位带区,覆盖了全部的片上外设的寄存器,我们可以通过宏为每个寄存器的位都定义一个
位带别名地址,从而实现位操作。但这个在实际项目中不是很现实,也很少人会这么做,我们的工程提供了sys.h的文件,里面定义的宏可以对GPIO口的输入和输出进行位操作

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MYXiNB7s-1656512609971)(stm32f103.assets/image-20220629200105438.png)]](https://1000bd.com/contentImg/2023/06/14/234122444.png)
出现芯片型号选择 一般我们直接搜索自己芯片的型号即可
表示警告,对应配置出现问题 点击该选项即可外设配置界面查看
因为我们这里用LED,所以还要配置GPIO引脚


设置高速外部时钟HSE 选择外部时钟源


外部晶振为8MHz
32的时钟树框图 《【STM32】系统时钟RCC详解(超详细,超全面)》

ST-Link 就是 Serial Wire 调试模式,一定要设置!!!
如果不配置 Serial Wire 模式,程序一旦通过 ST-Link 烧录到芯片中,芯片就再也不能被ST-Link 识别了。
ST-Link V2烧录问题(已解决)
设置完MCU的各个配置之后,第三个就是工程文件的设置了






选择自己的配置文件
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmXAxNny-1656512609999)(stm32f103.assets/image-20220629221932932.png)]](https://1000bd.com/contentImg/2023/06/14/234124114.png)
我这里是自定义配置文件,自己新建一个cfg文件:st_my_F1_stlink-v2.cfg
source [find interface/stlink-v2.cfg]
transport select hla_swd
source [find target/stm32f1x.cfg]
(7条消息) 基于Clion IDE + STM32CubeMX搭建STM32开发环境(详细介绍搭建过程)_Ch_champion的博客-CSDN博客_clion搭建stm32开发环境
#include "sys.h"
//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI
void WFI_SET(void)
{
__ASM volatile("wfi");
}
//关闭所有中断
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
}
#ifndef __SYS_H
#define __SYS_H
#include "stm32f1xx_hal.h"
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
//以下为汇编函数
void WFI_SET(void); //执行WFI指令
void INTX_DISABLE(void);//关闭所有中断
void INTX_ENABLE(void); //开启所有中断
#endif
在main.c主函数中的while循环加入下面代码
while (1)
{
/* USER CODE END WHILE */
PCout(13)=1; //PC口13引脚输出,高电平
HAL_Delay(500); //已经在delay.h中初始化
PCout(13)=0; //PC口13引脚输出,低电平
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}

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