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

一.STM32F103系列芯片的存储器映射和寄存器映射原理

1.存储器映射

存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址 的过程就称为存储器映射,具体见下图。如果给存储器再分配一个地址就叫存储器重映射。

img

存储器区域功能划分

在这 4GB 的地址空间中,ARM 已经粗线条的平均分成了 8 个块,每块 512MB,每个 块也都规定了用途,具体分类见表格 6-1。每个块的大小都有 512MB,显然这是非常大的,芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的 一部分而已。

存储器功能分类

序号用途地址范围
Block0Code0x0000 0000 ~ 0x1FFF FFFF(512MB)
Block1SRAM0x2000 0000 ~ 0x3FFF FFFF(512MB)
Block2片上外设0x4000 0000 ~ 0x5FFF FFFF(512MB)
Block3FSMC的bank1~bank20x6000 0000 ~ 0x7FFF FFFF(512MB)
Block4FSMC的bank3~bank40x8000 0000 ~ 0x9FFF FFFF(512MB)
Block5FSMC寄存器0xA000 0000 ~ 0xCFFF FFFF(512MB)
Block6没有使用0xD000 0000 ~ 0xDFFF FFFF(512MB)
Block7Cortex-M3内部外设0xE000 0000 ~ 0xFFFF FFFF(512MB)

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

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

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

用途说明地址范围
Block0预留0x1FFE C008 ~ 0x1FFF FFFF
Block0选项字节:用于配置读写保护、 BOR 级别、软件/硬件看门狗以及器 件处于待机或停止模式下的复位。当 芯片不小心被锁住之后,我们可以从 RAM 里面启动来修改这部分相应的 寄存器位。
Block0系统存储器:里面存的是ST出厂时 烧 写 好 的 isp 自 举 程 序 ( 即 Bootloader),用户无法改动。串口 下载的时候需要用到这部分程序。0x1FFF F000- 0x1FFF F7FF
Block0预留0x0808 0000 ~ 0x1FFF EFFF
Block0FLASH:我们的程序就放在这里。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
Block1SRAM 64KB0x2000 0000 ~0x2000 FFFF

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

用途说明地址范围
Block2APB1总线外设0x4000 0000 ~ 0x4000 77FF
Block2APB2总线外设0x4001 0000 ~ 0x4001 3FFF
Block2AHB总线外设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 总线的地址最低,片 上外设从这里开始,也叫外设基地址。

总线基地址:

总线名称总线基地址相对外设基地址的偏移
APB10x4000 00000x0
APB20x4001 00000x0001 0000
AHB0x4001 80000x0001 8000

外设基地址

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

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

外设名称外设基地址相对APB2总线的地址偏移
GPIOA0x4001 08000x0000 0800
GPIOB0x4001 0C000x0000 0C00
GPIOC0x4001 10000x0000 1000
GPIOD0x4001 14000x0000 1400
GPIOE0x4001 18000x0000 1800
GPIOF0x4001 1C000x0000 1C00
GPIOG0x4001 20000x0000 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℃。

核心板原理图:

在这里插入图片描述

由上图可知:

  • 板子供电有两种方式: 通过U3 USB-micro接口提供5V供电,然后经过板载的LDO芯片转为VCC3V3;通过P2 接口,即SWD下载接口中的VCC3V3给核心板供电。
  • 核心板上有两个LED,其中一个为电源指示灯PWR,另外一个LED与PC13引脚相连,当PC13置高时,LED灭;当PC13置低时,LED亮;
  • 核心板上的跳线是为了选择启动模式使用。我们为了让程序以主闪存存储器作为启动区域,需要将BOOT0置低,BOOT1随意,此种启动模式是最常用的用户FLASH启动,为默认启动模式;
  • 核心板上的按键为RESET复位按键;
  • P2接口为SWD下载模式对应的引脚接口;

在这里我们使用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

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要使用STM32F103寄存器方式点亮LED流水灯,需要按照以下步骤进行: 1. 首先,需要配置GPIO引脚为输出模式。可以通过设置GPIOx_CRL或GPIOx_CRH寄存器来实现。例如,如果要使用PA引脚,可以将GPIOA_CRL寄存器的第位和第1位设置为01,表示将PA引脚配置为输出模式。 2. 接下来,需要使用GPIOx_BSRR寄存器来设置或清除引脚的电平。例如,如果要点亮PA引脚上的LED,可以将GPIOA_BSRR寄存器的第位设置为1,表示将PA引脚的电平设置为高电平。 3. 然后,可以使用延时函数来控制LED的亮灭时间。例如,可以使用SysTick定时器来实现延时功能。 4. 最后,可以使用循环语句和位运算符来实现LED流水灯效果。例如,可以使用for循环和左移运算符来实现LED从左到右依次亮起的效果。 需要注意的是,使用寄存器方式编程需要对STM32F103寄存器结构和寄存器位的含义有一定的了解。同时,需要注意寄存器的读写顺序和操作的正确性,以避免出现意外的错误。 ### 回答2: STM32F103是一款高性能、低功耗、易于开发的微控制器,它能为嵌入式设备提供强大的计算和控制能力。在使用STM32F103进行开发时,头文件和寄存器的操作是必不可少的一部分。 很多初学者都想通过点亮LED来入门STM32F103的开发,这里以寄存器方式点亮LED流水灯为例进行讲解: 首先需要初始化GPIO口,确定要控制的IO口和使用的引脚。这里用到了重映射技术,将LED1连接至PD2引脚(具体可以参考datasheet),可以将GPIO口D对应的寄存器地址复制到某个变量用于后续的操作。 代码示例: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO, ENABLE);//使能GPIO时钟 GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO初始化结构体 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_2;//选择PD2引脚 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP;//推挽输出 GPIO_InitStructure.GPIO_Speed= GPIO_Speed_10MHz;//输出速度10MHz GPIO_Init(GPIOD, &GPIO_InitStructure);//将设置好的GPIO配置应用 接下来,可以编写流水灯的代码,通过设置GPIO口输出高低电平,控制LED灯的亮灭。循环体中,分别点亮/熄灭LED,并加上适当的时间延时,从而实现流水灯的效果。 代码示例: while(1) { GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);//将PD2输出高电平,点亮LED1 delay(50);//延时 GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_RESET);//将PD2输出低电平,熄灭LED1 delay(50);//延时 } 代码执行上述代码后,即可实现STM32F103寄存器方式点亮LED流水灯的效果。需要注意的是,该示例代码中的延时函数需要自行编写,建议使用STM32CubeMX来生成延时函数。此外,还需要注意GPIO口的配置以及时钟使能,以免出现硬件问题。 以上就是关于STM32F103寄存器方式点亮LED流水灯的简单介绍与实现步骤。希望本文对初学者入门STM32F103开发有所帮助。 ### 回答3: 首先,启用STM32F103寄存器进行点亮LED流水灯需要进行以下准备步骤: 1. 确认所需引脚和LED的连接方式。此处假设我们将LED连接到引脚PB12,那么需要将PB12设置为输出模式。 2. 配置系统时钟,以便使用定时器来控制LED的闪烁速度。不同的系统时钟配置方式可能会略有不同,但主要是设置时钟源和最终频率。 3. 配置定时器,以便以适当的频率闪烁LED。这通常涉及到设置定时器的时钟源、预分频和计数器值。 4. 配置NVIC(Nested Vectored Interrupt Controller)中断,以便在定时器计数完成时处理中断。这需要设置中断源和优先级,以便定时器中断可以正确地触发。 了解了以上准备工作之后,下面开始实现点亮LED流水灯寄存器方式程序: 1. 在头文件中加入相关寄存器定义,方便后续程序的操作。 2. 在主函数中进行引脚配置: ``` RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //使能PB引脚时钟 GPIOB->CRH &= ~(0xF << 16); //清零位16~19 GPIOB->CRH |= (0x3 << 16); //设置位16~17为01,即输出模式 ``` 3. 配置定时器,以便生成适当的延迟时间: ``` RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; //使能TIM3时钟 TIM3->PSC = 7200 - 1; //预分频器7200,即频率为8KHz TIM3->ARR = 1000 - 1; //计数器自动重载值999,即1s的闪烁周期 TIM3->CR1 |= TIM_CR1_ARPE; //开启自动重载 TIM3->CR1 &= ~(TIM_CR1_DIR); //向上计数 TIM3->CR1 &= ~(TIM_CR1_CMS); //开启边缘对齐模式 TIM3->DIER |= TIM_DIER_UIE; //开启更新事件中断 TIM3->CR1 |= TIM_CR1_CEN; //启动计数器 ``` 4. 配置NVIC中断,以便在定时器计数完成时更新LED的状态: ``` NVIC_EnableIRQ(TIM3_IRQn); //使能TIM3中断 NVIC_SetPriority(TIM3_IRQn, 0); //设置TIM3中断优先级为最高 ``` 5. 在计时器中断处理中更新LED的状态,以实现流水灯效果: ``` void TIM3_IRQHandler(void){ if(TIM3->SR & TIM_SR_UIF){ //判断是否为更新中断 TIM3->SR &= ~(TIM_SR_UIF); //清除更新中断标志 static int count=0; static int flag=1; if(count==0){ GPIOB->ODR |= GPIO_ODR_ODR12; //点亮PB12,LED1亮 flag=1; } else if(count==1){ GPIOB->ODR &= ~(GPIO_ODR_ODR12); //熄灭PB12,LED1灭 GPIOB->ODR |= GPIO_ODR_ODR13; //点亮PB13,LED2亮 } else if(count==2){ GPIOB->ODR &= ~(GPIO_ODR_ODR13); //熄灭PB13,LED2灭 GPIOB->ODR |= GPIO_ODR_ODR14; //点亮PB14,LED3亮 } else if(count==3){ GPIOB->ODR &= ~(GPIO_ODR_ODR14); //熄灭PB14,LED3灭 GPIOB->ODR |= GPIO_ODR_ODR15; //点亮PB15,LED4亮 } else if(count==4){ GPIOB->ODR &= ~(GPIO_ODR_ODR15); //熄灭PB15,LED4灭 GPIOB->ODR |= GPIO_ODR_ODR14; //点亮PB14,LED3亮 } else if(count==5){ GPIOB->ODR &= ~(GPIO_ODR_ODR14); //熄灭PB14,LED3灭 GPIOB->ODR |= GPIO_ODR_ODR13; //点亮PB13,LED2亮 } else if(count==6){ GPIOB->ODR &= ~(GPIO_ODR_ODR13); //熄灭PB13,LED2灭 GPIOB->ODR |= GPIO_ODR_ODR12; //点亮PB12,LED1亮 flag=0; } if(flag){ count++; } else{ count--; } } } ``` 上述代码中,首先判断是否为计数器更新中断,然后根据计数值的不同更新LED的状态,实现流水灯效果。其中,计数值的变化可以通过flag来判断是递增还是递减,以实现LED灯的正向或反向流动。 总体来说,通过以上代码实现了基于STM32F103寄存器点亮LED流水灯,可以调整定时器的时钟源和计数器值来实现不同的闪烁效果。虽然这种方式比较繁琐,但对于有一定经验的开发者来说,可以更精准地控制硬件,实现更高效的程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值