锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

STM32F103寄存器方式点亮LED流水灯

时间:2023-02-09 22:30:00 54c8分类电阻器

文章目录

      • 一.STM32F存储器映射和寄存器映射103系列芯片
        • 1.存储器映射
        • 2.寄存器映射
          • 2.1STM外设地址映射32
          • 2.2C语言包装寄存器
      • 二.GPIO端口的初始化设置
      • 三.以 STM32最小系统核心板(STM32F103C8T6) 面板板 3只红绿蓝LED 建造电路,使用GPIOB、GPIOC、GPIOD控制这三个端口LED灯(最高时钟2Mhz),轮流闪烁,间隔1秒。
        • 1.STM32F103C8T6
      • 四.参考链接

一.STM32F存储器映射和寄存器映射103系列芯片

1.存储器映射

存储器本身没有地址信息,其地址由芯片制造商或用户分配给存储器 这个过程叫做存储器映射,具体见下图。若将另一个地址分配给存储器,则称为存储器重映射。

img

存储区域功能划分

在这 4GB 在地址空间中,ARM 粗线的平均分为 8 个块,每块 512MB,每个 块也规定了用途,具体分类见表格 6-1。每个块的大小都有 512MB,显然,这是非常大的。芯片片制造商在每个块的范围内设计具有自己特点的外设时,它们可能无法完成,只使用它们 只是部分。

存储功能分类

序号 用途 地址范围
Block0 Code 0x0000 0000 ~ 0x1FFF FFFF(512MB)
Block1 SRAM 0x2000 0000 ~ 0x3FFF FFFF(512MB)
Block2 片上外设 0x4000 0000 ~ 0x5FFF FFFF(512MB)
Block3 FSMC的bank1~bank2 0x6000 0000 ~ 0x7FFF FFFF(512MB)
Block4 FSMC的bank3~bank4 0x8000 0000 ~ 0x9FFF FFFF(512MB)
Block5 FSMC寄存器 0xA000 0000 ~ 0xCFFF FFFF(512MB)
Block6 没有使用 0xD000 0000 ~ 0xDFFF FFFF(512MB)
Block7 Cortex-M3内部外设 0xE000 0000 ~ 0xFFFF FFFF(512MB)

在这 8个 Block里面,有 三块很重要,也是我们最关心的三块。Block0用来设计 成内部 FLASH,Block1 用于内部设计 RAM,Block2 用于设计片上的外设。让我们在下面 简单介绍这三个 Block 里面的具体区域的功能划分。

存储器 Block0 内部区域功能划分

Block0 主要用于设计片 FLASH,我们使用的 STM32F103ZET6(霸道)和 STM32F103VET6(指南者)的 FLASH 都是 512KB,属于大容量。芯片内部集成更大 的 FLASH 或者 SRAM 这意味着芯片成本的增加,通常集成在芯片中 FLASH 不会太大,ST 512可以在追求性价比的同时实现KB,其实是良心之举。Block内部区域的功能划分见下表。

用途说明 地址范围
Block0 预留 0x1FFE C008 ~ 0x1FFF FFFF
Block0 选项字节:用于配置读写保护 BOR 等级、软件/硬件看门狗和器具 零件在待机或停止模式下复位。 芯片意外定后,我们可以从 RAM 内部启动修改相应的部分 寄存器位。
Block0 系统存储器:存储在里面ST出厂时 烧 写 好 的 isp 自 举 程 序 ( 即 Bootloader),用户无法改变 这部分程序需要下载。 0x1FFF F000- 0x1FFF F7FF
Block0 预留 0x0808 0000 ~ 0x1FFF EFFF
Block0 FLASH:我们的程序就在这里。 0x0800 0000 ~ 0x0807 FFFF (512KB)
Block0 预留 0x0008 0000 ~ 0x07FF FFFF
Block0 取决于 BOOT引脚,为 FLASH、系 统存储器、SRAM 的别名。 0x0000 0000 ~ 0x0007 FFFF

存储器Block1内部区域功能划分

Block1 用 于 设 计 片 内 的 SRAM 。 我 们 使 用 的 STM32F103ZET6 ( 霸 道 ) 和 STM32F103VET6(指南) SRAM 都是 64KB,Block 具体下表显示了内部区域的功能划分。

用途说明 地址范围
Block1 预留 0x2001 0000 ~ 0x3FFF FFFF
Block1 SRAM 64KB 0x2000 0000 ~0x2000 FFFF

Block2 根据外设的总线速度,用于设计片内的外设,Block 被分成了 APB 和 AHB 两部分,其中 APB 又被分为 APB1 和 APB具体见下表。

用途说明 地址范围
Block2 APB1总线外设 0x4000 0000 ~ 0x4000 77FF
Block2 APB2总线外设 0x4001 0000 ~ 0x4001 3FFF
Block2 AHB总线外设 0x4001 8000 ~ 0x5003 FFFF

2.寄存器映射

我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫 寄存器映射?寄存器到底是什么?

在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit, 每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到 每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通 过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的 不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个 给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

比如,我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x4001 0C0C(至于这 个地址如何找到可以先跳过,后面我们会有详细的讲解),ODR 寄存器是 32bit,低 16bit 有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。现在我们通过 C 语言指 针的操作方式,让 GPIOB 的 16 个 IO 都输出高电平。

通过绝对地址访问内存单元

 // GPIOB 端口全部输出 高电平
 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;

0x4001 0C0C在我们看来是 GPIOB端口 ODR的地址,但是在编译器看来,这只是一个 普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把 它转换成指针,即(unsigned int *)0x4001 0C0C,然后再对这个指针进行 * 操作。 刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存 器的方式来操作。

通过寄存器别名方式访问内存单元

// GPIOB 端口全部输出 高电平
#define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)
* GPIOB_ODR = 0xFF;

为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面.

// GPIOB 端口全部输出 高电平
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
GPIOB_ODR = 0xFF;
2.1STM32的外设地址映射

片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1 挂载低速外设,APB2 和 AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地 址,总线基地址也是挂载在该总线上的首个外设的地址。其中 APB1 总线的地址最低,片 上外设从这里开始,也叫外设基地址。

总线基地址:

总线名称 总线基地址 相对外设基地址的偏移
APB1 0x4000 0000 0x0
APB2 0x4001 0000 0x0001 0000
AHB 0x4001 8000 0x0001 8000

外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为 “XX 外设基地址”,也叫 XX 外设的边界地址。

以 GPIO 这个外设来讲解外设的基地址,GPIO 属于高速的外设 ,挂载到 APB2 总线上。

外设名称 外设基地址 相对APB2总线的地址偏移
GPIOA 0x4001 0800 0x0000 0800
GPIOB 0x4001 0C00 0x0000 0C00
GPIOC 0x4001 1000 0x0000 1000
GPIOD 0x4001 1400 0x0000 1400
GPIOE 0x4001 1800 0x0000 1800
GPIOF 0x4001 1C00 0x0000 1C00
GPIOG 0x4001 2000 0x0000 2000

外设寄存器

在 XX 外设的地址范围内,分布着的就是该外设的寄存器。以 GPIO 外设为例,GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,基本功能是控制引脚输 出高电平或者低电平。最简单的应用就是把 GPIO 的引脚连接到 LED 灯的阴极,LED 灯的 阳极接电源,然后通过 STM32 控制该引脚的电平,从而实现控制 LED 灯的亮灭。 GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit,占四个字节, 在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描 述。

2.2C语言对寄存器的封装

封装总线和外设基地址

在编程上为了方便理解和记忆,我们把总线基地址和外设基地址都以相应的宏定义起 来,总线或者外设都以他们的名字作为宏名。

总线和外设基址宏定义:

1 /* 外设基地址 */
2 #define PERIPH_BASE ((unsigned int)0x40000000)
3 
4 /* 总线基地址 */
5 #define APB1PERIPH_BASE PERIPH_BASE
6 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
7 #define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)
8 
9 
10 /* GPIO 外设基地址 */
11 #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
12 #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
13 #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
14 #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
15 #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
16 #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
17 #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
18 
19 
20 /* 寄存器基地址,以 GPIOB 为例 */
21 #define GPIOB_CRL (GPIOB_BASE+0x00)
22 #define GPIOB_CRH (GPIOB_BASE+0x04)
23 #define GPIOB_IDR (GPIOB_BASE+0x08)
24 #define GPIOB_ODR (GPIOB_BASE+0x0C)
25 #define GPIOB_BSRR (GPIOB_BASE+0x10)
26 #define GPIOB_BRR (GPIOB_BASE+0x14)
27 #define GPIOB_LCKR (GPIOB_BASE+0x18)

首先定义了 “片上外设”基地址 PERIPH_BASE,接着在 PERIPH_BASE 上加 入各个总线的地址偏移,得到 APB1 、 APB2 总线的地址 APB1PERIPH_BASE 、 APB2PERIPH_BASE,在其之上加入外设地址的偏移,得到 GPIOA-G 的外设地址,最后在 外设地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具体地址,就可 以用指针读写。

使用指针控制BSRR寄存器:

1 /* 控制 GPIOB 引脚 0 输出低电平(BSRR 寄存器的 BR0 置 1) */
2 *(unsigned int *)GPIOB_BSRR = (0x01<<(16+0));
3 
4 /* 控制 GPIOB 引脚 0 输出高电平(BSRR 寄存器的 BS0 置 1) */
5 *(unsigned int *)GPIOB_BSRR = 0x01<<0;
6 
7 unsigned int temp;
8 /* 读取 GPIOB 端口所有引脚的电平(读 IDR 寄存器) */
9 temp = *(unsigned int *)GPIOB_IDR;

该代码使用 (unsigned int ) 把 GPIOB_BSRR 宏的数值强制转换成了地址,然后再用“” 号做取指针操作,对该地址的赋值,从而实现了写寄存器的功能。同样,读寄存器也是用 取指针操作,把寄存器中的数据取到变量里,从而获取 STM32 外设的状态。

封装寄存器列表

为了更方便地访问寄存器,我们引入 C 语言中的结构体 语法对寄存器进行封装。

1 typedef unsigned int uint32_t; /*无符号 32 位变量*/
2 typedef unsigned short int uint16_t; /*无符号 16 位变量*/
3 
4 /* GPIO 寄存器列表 */
5 typedef struct {
6 uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
7 uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
8 uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
9 uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
10 uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
11 uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
12 uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
13 } GPIO_TypeDef;

这段代码用 typedef 关键字声明了名为 GPIO_TypeDef 的结构体类型,结构体内有 7 个 成员变量,变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间 是连续的,其中 32 位的变量占用 4 个字节,16 位的变量占用 2 个字节。

GPIO_TypeDef结构体成员的地址偏移:

也就是说,我们定义的这个 GPIO_TypeDef ,假如这个结构体的首地址为 0x4001 0C00 (这也是第一个成员变量 CRL 的地址), 那么结构体中第二个成员变量 CRH 的地址即为 0x4001 0C00 +0x04 ,加上的这个 0x04 ,正是代表 CRL 所占用的 4 个字节地址的偏移量, 其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给。 这样的地址偏移与 STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体 设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存 器。

通过结构体指针访问寄存器:

1 GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx
2 GPIOx = GPIOB_BASE; //把指针地址设置为宏 GPIOH_BASE 地址
3 GPIOx->IDR = 0xFFFF; 
4 GPIOx->ODR = 0xFFFF; 
5 
6 
7 uint32_t temp;
8 temp = GPIOx->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中

这段代码先用 GPIO_TypeDef 类型定义一个结构体指针 GPIOx,并让指针指向地址 GPIOB_BASE(0x4001 0C00),使用地址确定下来,然后根据 C 语言访问结构体的语法,用 GPIOx->ODR 及 GPIOx->IDR 等方式读写寄存器。 最后,我们更进一步,直接使用宏定义好 GPIO_TypeDef 类型的指针,而且指针指向 各个 GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可。

定义好 GPIO 端口首地址址针:

1 /*使用 GPIO_TypeDef 把地址强制转换成指针*/
2 #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
3 #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
4 #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
5 #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
6 #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
7 #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
8 #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
9 #define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
10 
11 
12 
13 /*使用定义好的宏直接访问*/
14 /*访问 GPIOB 端口的寄存器*/
15 GPIOB->BSRR = 0xFFFF; //通过指针访问并修改 GPIOB_BSRR 寄存器
16 GPIOB->CRL = 0xFFFF; //修改 GPIOB_CRL 寄存器
17 GPIOB->ODR =0xFFFF; //修改 GPIOB_ODR 寄存器
18 
19 uint32_t temp;
20 temp = GPIOB->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中
21 
22 /*访问 GPIOA 端口的寄存器*/
23 GPIOA->BSRR = 0xFFFF; 
24 GPIOA->CRL = 0xFFFF; 
25 GPIOA->ODR =0xFFFF; 
26 
27 uint32_t temp;
28 temp = GPIOA->IDR; //读取 GPIOA_IDR 寄存器的值到变量 temp 中

这里我们仅是以 GPIO 这个外设为例,给大家讲解了 C 语言对寄存器的封装。以此类 推,其他外设也同样可以用这种方法来封装。

二.GPIO端口的初始化设置

STM32F103ZE的开发板里总共有7组IO口,每组IO口有16个IO,即这块板子总共有112个IO口分别是GPIOA~GPIOG。每个I/O端口位可以自由编程,但I/O端口寄存器必须按32位字节访问,不允许半字或单字节访问。
GPIO的工作模式主要有八种:4种输入方式,4种输出方式,分别为输入浮空,输入上拉,输入下拉,模拟输入;输出方式为开漏输出,开漏复用输出,推挽输出,推挽复用输出。
(1)GPIO_Mode_AIN 模拟输入 (应用ADC模拟输入,或者低功耗下省电)
(2)GPIO_Mode_IN_FLOATING 浮空输入 (浮空就是浮在半空,可以被其他物体拉上或者拉下,可以用于按键输入)
(3)GPIO_Mode_IPD 下拉输入 (IO内部下拉电阻输入)
(4)GPIO_Mode_IPU 上拉输入 (IO内部上拉电阻输入)
(5)GPIO_Mode_Out_OD 开漏输出(开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行)
(6)GPIO_Mode_Out_PP 推挽输出 (推挽就是有推有拉电平都是确定的,不需要上拉和下拉,IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的 )
(7)GPIO_Mode_AF_OD 复用开漏输出(片内外设功能(I2C的SCL,SDA))
(8)GPIO_Mode_AF_PP 复用推挽输出 (片内外设功能(TX1,MOSI,MISO.SCK.SS))

GPIO初始化步骤

①对于单个GPIO口的初始化如下

GPIO_InitTypeDef GPIO_InitStructure;
第一步:使能GPIOA的时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

第二步:设置GPIOA参数:输出OR输入,工作模式,端口翻转速率
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_6| GPIO_Pin_7| GPIO_Pin_8; //设定要操作的管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz

第三步:调用GPIOA口初始化函数,进行初始化。
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA

第四步:调用GPIO-SetBits函数,进行相应为的置位。
GPIO_SetBits(GPIOA,GPIO_Pin_0); //输出高

②对于多个GPIO口的初始化如下

GPIO_InitTypeDef GPIO_InitStructure;
第一步:使能GPIOA,GPIOE的时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

第二步:设置GPIOA,GPIOE参数:输出OR输入,工作模式,端口翻转速率
第三步:调用GPIOA口初始化函数,进行初始化。
第四步:调用GPIO-SetBits函数,进行相应为的置位。

▶把第二、三、四步合并分别设置GPIOA和GPIOE
先设置GPIOA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 第四个口,PA4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOA,&GPIO-InitST); //根据设定参数初始化GPIOA
GPIO_SetBits(GPIOA,GPIO_Pin_4); //输出高

再设置GPIOE
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 第三个口,PE3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOE,&GPIO-InitST); //根据设定参数初始化GPIOE
GPIO_SetBits(GPIOE,GPIO_Pin_3); //输出高

三.以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只红绿蓝LED 搭建电路,使用GPIOB、GPIOC、GPIOD这3个端口控制LED灯(最高时钟2Mhz),轮流闪烁,间隔时长1秒。

1.STM32F103C8T6

简介:

根据STM32&STM8产品型号命名规则(参考:STM32单片机最小系统详解)可知: STM32F103C8T6这个命名中:

  • STM32代表STM32家族,32位MCU;
  • F代表产品类型为基础型;
  • 103代表特定功能为STM32基础型;
  • C代表引脚数为48&49引脚;
  • 8代表内存容量为64KB;
  • T代表封装为QFP;
  • 6代表温度范围为-40到+85℃。

核心板原理图:

由上图可知:

在这里我们使用GPIOA,GPIOB,GPIOC三个端口实现LED流水灯。

第一步:开启GPIOA,GPIOB,GPIOC时钟。

RCC_APB2ENR|=1<<2|1<<3|1<<4;

在这里我们不能直接用运算符“=”直接赋值,这是因为当打开多个时钟时,后面运用“=”会使前一个的赋值被刷新,最后只能成功开启最后一个端口的时钟。

代码实现:

main.c

#include "stm32f10x.h"
//----------------APB2使能时钟寄存器 ---------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
	
extern void led(void);

//延时函数
 void Delay()
 { 
        
   u32 i=0;
   for(;i<5000000;i++);
 }
 
//灯1
 void led1()
 { 
        
    GPIOA_ODR|=1<<4;		//PA4高电平
	 	Delay();
		GPIOA_ODR&=~(1<<4);//PA4低电平,置零使用按位与
		Delay();		
 }
 
//灯2
 void led2()
 { 
        
	  GPIOB_ODR|=1<<5;		//PB4高电平
	 	Delay();
		GPIOB_ODR&=~(1<<5);//PB4低电平,置零使用按位与
		Delay();		
 }
 
//灯3
 void led3()
 { 
        
	 
		GPIOC_ODR|=1<<14;	//PC14高电平
	 	Delay();
		GPIOC_ODR&=~(1<<14);//PC14低电平,置零使用按位与
		Delay();		
 } 
 
 int main(void)
 { 
        	
	 led();
	RCC_APB2ENR|=1<<2|1<<3|1<<4;	//APB2-GPIOA、GPIOB、GPIOC外设时钟使能 
	
	GPIOA_CRL&=0xFFF0FFFF;	//设置位 清零 
	GPIOA_CRL|=0x00020000;	//PA4推挽输出 
	GPIOA_ODR&=~(1<<4);		//设置初始灯为灭 
	
	GPIOB_CRL&=0xFF0FFFFF;	//设置位 清零 
	GPIOB_CRL|=0x00200000;	//PB5推挽输出 
	GPIOB_ODR&=~(1<<5);		//设置初始灯为灭 
	 
	GPIOC_CRH&=0xF0FFFFFF;	//设置位 清零 
	GPIOC_CRH|=0x02000000;	//PC14推挽输出 
	GPIOC_ODR&=~(1<<14);	//设置初始灯为灭 

	while(1){ 
        		
		led1();
		
        led2();
		
        led3();
		}
}

led.s

 AREA MYDATA, DATA
	
 AREA MYCODE, CODE
	ENTRY
	EXPORT led

led
	;使能A,B,C
    ldr r0, =0x40021018
    ldr r1, =0x0000001c
    str r1, [r0]                


	;配置端口A4
	ldr r0, =0x40010800
    ldr r1, [r0]
    bic r1, r1, #0x000f0000
    orr r1, r1, #0x00010000
    str r1, [r0]

	;配置端口B5
    ldr r0, =0x40010c00
    ldr r1, [r0]
    bic r1, r1, #0x00f00000
    orr r1, r1, #0x00100000
    str r1, [r0]
	
	;配置端口C14
	ldr r0, =0x40011004
    ldr r1, [r0]
    bic r1, r1, #0x0f000000
    orr r1, r1, #0x01000000
    str r1, [r0]

	;初始A4亮灯
	ldr r0, =0x4001080c
    ldr r1, =0x00000010
    str r1, [r0]

	ldr r0, =5000000;频率
    ldr r1, =0
	
;循环亮灯
blink
    add r1, r1, #1
    cmp r1, r0
    blt blink
	
	;设置A4灭
	ldr r1, =0x4001080c
    ldr r2, [r1]
    eor r2, r2, #0x00000010
    str r2, [r1]
	
	;设置B5亮
	ldr r1, =0x40010c0c
    ldr r2, [r1]
    eor r2, r2, #0x00000020
    str r2, [r1]
	
	ldr r1, =0

blink1	
	add r1, r1, #1
    cmp r1, r0
    blt blink1
	
	;设置B5灭
	ldr r1, =0x40010c0c
    ldr r2, [r1]
    eor r2, r2, #0x00000020
    str r2, [r1]
	
	;设置C14亮
	ldr r1, =0x4001100c
    ldr r2, [r1]
    eor r2, r2, #0x00004000
    str r2, [r1]
	

	ldr r1, =0

blink2
	add r1, r1, #1
    cmp r1, r0
    blt blink2
	
    ;设置C14灭
	ldr r1, =0x4001100c
    ldr r2, [r1]
    eor r2, r2, #0x00004000
    str r2, [r1]
	
	;设置A4亮
	ldr r1, =0x4001080c
    ldr r2, [r1]
    eor r2, r2, #0x00000010
    str r2, [r1]
	

	ldr r1, =0
    b blink

	
	END

效果:

https://www.bilibili.com/video/BV1p34y1m7Ae/

四.参考链接

http://www.voycn.com/article/stm32rumen-gpiochushihuabuzhou

https://blog.csdn.net/weixin_39710396/article/details/110134356

秉火零死角玩转STM32F103-指南书

https://blog.csdn.net/qq_47281915/article/details/120812867

https://blog.csdn.net/gelad_w/article/details/115555631

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章