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

一、STM32F103系列芯片

1. 简介

STM32F系列属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3[^1]。
该系列芯片按片内Flash的大小可分为三大类:小容量(16K和32K)、中容量(64K和128K)、大容量(256K、384K和512K)。
芯片集成定时器Timer,CAN,ADC,SPI,I2C,USB,UART等多种外设功能。

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

2. 存储器映射

  • 存储器空间
    Cortex‐M3 支持4GB 存储空间。整块4G存储器开始地址标为0x0000_0000,结束地址为0xFFFF_FFFF,地址的位数是32位,那么2^32=4,294,967,296。
    由于一个基本的存储单元是8bits即1Byte(每个地址对应一个存储单元,这样如果只是访问某一bit就要使用位操作,或者使用位带操作),因此4,294,967,296/1024=4,194,304KB,4,194,304/1024=4096MB,4094/1024=4GB。

  • 存储器映射

这4GB的存储空间被划分成8个块,每一块用来与特定功能完成映射。映射关系如图所示。

(某博主的个人理解:这4GB的空间指的是地址空间,每个地址对应一个具体的设备。CPU并不知道每个设备是什么,它所关心的只有地址,获取相应的地址,然后找到地址对应的存储单元或者寄存器,进行读取或者写入数据即可。4GB是它最大支持的地址数目,但是实际可能没有使用那么多。)

在这里插入图片描述

  • 存储器功能分类
序号 用途 地址范围
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里面,有 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
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 和 APB2,具体见下表。

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

3. 寄存器映射

每个寄存器都是32bit,占用4个Byte即4个存储单元。可以把寄存器看作一个特殊的单元,一个这样的单元占32bit,只要找到这个单元的起始地址就可以对其进行操作。

其映射地址 = 外设总基地址(块基地址)+ 总线相对于外设总基地址的偏移 + 具体外设基地址相对于总线基地址的偏移 + 寄存器相对于具体外设基地址的偏移。

  • 寄存器操作

直接地址操作访问
以GPIOB_ODR寄存器为例:

我们找到 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;

其实以上所讲并不是很清晰明了,笔主在这里推荐一个写得很好的博客 STM32寄存器的简介、地址查找,与直接操作寄存器

二、流水灯

1. 流水灯实验详悉

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

2. Keil新建项目

请添加图片描述

请添加图片描述

在这里插入图片描述
在这里插入图片描述
请添加图片描述

3. 用C语言和汇编语言分别做流水灯

3.1 C语言

  1. 新建项目。
  2. 写入.c文件

注:使用的引脚是PA7,PB9,PC15。如果是不一样的请改为自己相应的引脚。

//--------------APB2使能时钟寄存器------------------------
#define RCC_AP2ENR	*((unsigned volatile int*)0x40021018)
	//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRL	*((unsigned volatile int*)0x40010800)
#define	GPIOA_ORD	*((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH	*((unsigned volatile int*)0x40010C04)
#define	GPIOB_ORD	*((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH	*((unsigned volatile int*)0x40011004)
#define	GPIOC_ORD	*((unsigned volatile int*)0x4001100C)
//-------------------简单的延时函数-----------------------
void  Delay_ms( volatile  unsigned  int  t)
{
    
    
     unsigned  int  i;
     while(t--)
         for (i=0;i<800;i++);
}
//------------------------主函数--------------------------
int main()
{
    
    
	int j=100;
	RCC_AP2ENR|=1<<2;			//APB2-GPIOA外设时钟使能
	RCC_AP2ENR|=1<<3;			//APB2-GPIOB外设时钟使能	
	RCC_AP2ENR|=1<<4;			//APB2-GPIOC外设时钟使能
	//这两行代码可以合为 RCC_APB2ENR|=1<<3|1<<4;
	GPIOA_CRL&=0x0FFFFFFF;		//设置位 清零	
	GPIOA_CRL|=0x20000000;		//PA7推挽输出
	GPIOA_ORD|=1<<7;			//设置初始灯为亮
	
	GPIOB_CRH&=0xFFFFFF0F;		//设置位 清零	
	GPIOB_CRH|=0x00000020;		//PB9推挽输出
	GPIOB_ORD|=1<<9;			//设置初始灯为灭
	
	GPIOC_CRH&=0x0FFFFFFF;		//设置位 清零
	GPIOC_CRH|=0x30000000;   	//PC15推挽输出
	GPIOC_ORD|=0x1<<15;			//设置初始灯为灭	
	while(j)
	{
    
    	
		GPIOA_ORD=0x0<<7;		//PA7低电平	
		Delay_ms(1000000);
		GPIOA_ORD=0x1<<7;		//PA7高电平
		Delay_ms(1000000);
		
		GPIOB_ORD=0x0<<9;		//PB9低电平	
		Delay_ms(1000000);
		GPIOB_ORD=0x1<<9;		//PB9高电平
		Delay_ms(1000000);
		
		GPIOC_ORD=0x0<<15;		//PC15低电平	
		Delay_ms(1000000);
		GPIOC_ORD=0x1<<15;		//PC15高电平
		Delay_ms(1000000);
	}
}

  1. 配置环境
    点击魔法棒:
    请添加图片描述

Output中:
请添加图片描述

Dialog DLL:DARMSTM.DLL
Parameter:-pSTM32F103C8
请添加图片描述

  1. 连上串口之后要把板子上的boot0置1,boot1置0,并且要在烧录之前按下reset键

请添加图片描述

  1. FlyMcu串口调试

将板子及线路连接好,网上很多教程,建议多看看,但不要看太杂。

看图!

在这里插入图片描述

  1. 实验结果

请添加图片描述

3.2 汇编语言

  1. 新建项目,与前面一致 ,稍有不同在于不选择Starup,不然会烧录不成功。

在这里插入图片描述

  1. 写入.c文件
RCC_APB2ENR EQU 0x40021018;配置RCC寄存器,时钟,0x40021018为时钟地址
GPIOC_CRH EQU 0x40011004;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04 
GPIOC_ORD EQU 0x4001100c;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOA_CRL EQU 0x40010800;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04 
GPIOA_ORD EQU 0x4001080C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOB_CRH EQU 0x40010C04;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04 
GPIOB_ORD EQU 0x40010C0C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
Stack_Size EQU  0x00000400;栈的大小
;分配一个stack段,该段不初始化,可读写,按8字节对齐。分配一个大小为Stack_Size的存储空间,并使栈顶的地址为__initial_sp。
                AREA    STACK, NOINIT, READWRITE, ALIGN=3 ;NOINIT: = NO Init,不初始化。READWRITE : 可读,可写。ALIGN =3 : 2^3 对齐,即8字节对齐。
Stack_Mem       SPACE   Stack_Size
__initial_sp




                AREA    RESET, DATA, READONLY

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                    
                    
                AREA    |.text|, CODE, READONLY
                    
                THUMB
                REQUIRE8
                PRESERVE8
                    
                ENTRY
Reset_Handler 
				bl LED_Init;bl:带链接的跳转指令。当使用该指令跳转时,当前地址(PC)会自动送入LR寄存器
MainLoop        BL LED_ON_C
                BL Delay
                BL LED_OFF_C
                BL Delay
				BL LED_ON_A
                BL Delay
                BL LED_OFF_A
                BL Delay
				BL LED_ON_B
                BL Delay
                BL LED_OFF_B
                BL Delay
                
                B MainLoop;B:无条件跳转。
LED_Init;LED初始化
                PUSH {
    
    R0,R1, LR};R0,R1,LR中的值放入堆栈
                
                LDR R0,=RCC_APB2ENR;LDR是把地址装载到寄存器中(比如R0)ORR R0,R0,#0x1c;ORR 按位或操作,11100将R0的第二位置1,其他位不变
                LDR R1,=RCC_APB2ENR
                STR R0,[R1];STR是把值存储到寄存器所指的地址中,将r0里存储的值给rcc寄存器
				;上面一部分汇编代码是控制时钟的
				
				
                ;初始化GPIOA部分
                LDR R0,=GPIOA_CRL
                BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与
                LDR R1,=GPIOA_CRL
                STR R0,[R1]
                ;上面的代码是初始化CPIOC_CRH
                LDR R0,=GPIOA_CRL
                ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
                LDR R1,=GPIOA_CRL
                STR R0,[R1]
				;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
                ;将PC15置1
                MOV R0,#0x80; 二进制为0b1000 0000 ,7位就是a7引脚的输出电压
                LDR R1,=GPIOA_ORD ;由r1控制ord寄存器
                STR R0,[R1] ;将ord寄存器的值变为r0的值
				
				 ;初始化GPIOB部分
                LDR R0,=GPIOB_CRH
                BIC R0,R0,#0xffffff0f;BIC 先把立即数取反,再按位与,用的是b9,所以把第二位置零
                LDR R1,=GPIOB_CRH
                STR R0,[R1]
                ;上面的代码是初始化CPIOC_CRH
                LDR R0,=GPIOB_CRH
                ORR R0,#0x00000020;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
                LDR R1,=GPIOB_CRH
                STR R0,[R1]
				;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
                ;将PC15置1
                MOV R0,#0x200; 二进制为0b10 0000 0000,16位就是b9引脚的输出电压
                LDR R1,=GPIOB_ORD ;由r1控制ord寄存器
                STR R0,[R1] ;将ord寄存器的值变为r0的值
				
				 ;初始化GPIOC部分
                LDR R0,=GPIOC_CRH
                BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与,就是将三十二位全部置零
                LDR R1,=GPIOC_CRH
                STR R0,[R1]
                ;上面的代码是初始化CPIOC_CRH
                LDR R0,=GPIOC_CRH
                ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
                LDR R1,=GPIOC_CRH
                STR R0,[R1]
				;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
                ;将PC15置1
                MOV R0,#0x8000; 二进制为0b1000 0000 0000 0000,16位就是c15引脚的输出电压
                LDR R1,=GPIOC_ORD ;由r1控制ord寄存器
                STR R0,[R1] ;将ord寄存器的值变为r0的值
             
                POP {
    
    R0,R1,PC};将栈中之前存的R0,R1,LR的值返还给R0,R1,PC
LED_ON_A;亮灯
                PUSH {
    
    R0,R1, LR}    
                
                MOV R0,#0x00 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
                LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1];将r0的值赋予在GPIOC_ORD中
             
                POP {
    
    R0,R1,PC}
             
LED_OFF_A;熄灯
                PUSH {
    
    R0,R1, LR}    
                
                MOV R0,#0x80 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
                LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
             
                POP {
    
    R0,R1,PC}  
LED_ON_B;亮灯
                PUSH {
    
    R0,R1, LR}    
                
                MOV R0,#0x000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
                LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1];将r0的值赋予在GPIOC_ORD中
             
                POP {
    
    R0,R1,PC}
             
LED_OFF_B;熄灯
                PUSH {
    
    R0,R1, LR}    
                
                MOV R0,#0x200 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
                LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
             
                POP {
    
    R0,R1,PC}  
LED_ON_C;亮灯
                PUSH {
    
    R0,R1, LR}    
                
                MOV R0,#0x0000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
                LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1];将r0的值赋予在GPIOC_ORD中
             
                POP {
    
    R0,R1,PC}
             
LED_OFF_C;熄灯
                PUSH {
    
    R0,R1, LR}    
                
                MOV R0,#0x8000 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
                LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
             
                POP {
    
    R0,R1,PC}             
             
Delay
                PUSH {
    
    R0,R1, LR}
                
                MOVS R0,#0
                MOVS R1,#0
                MOVS R2,#0
                
DelayLoop0        
                ADDS R0,R0,#1

                CMP R0,#330
                BCC DelayLoop0
                
                MOVS R0,#0
                ADDS R1,R1,#1
                CMP R1,#330
                BCC DelayLoop0 ;无进位

                MOVS R0,#0
                MOVS R1,#0
                ADDS R2,R2,#1
                CMP R2,#15
                BCC DelayLoop0
                
                
                POP {
    
    R0,R1,PC}    
                NOP
				END

  1. 烧录步骤与前面一致。
  2. 实验结果:

请添加图片描述

小小的总结

其实实验并不是很复杂,但是在连接实验线路(最小核心板及串口之间)连接时会出现很多小问题。首先可能是器件(面包板、STM32最小核心板、串口等)自身的问题,再者线路连接的稳定问题,在实验过程中需要极大限度的耐心。好好做,加油呀!

参考文献

  1. STM32F103的存储器映射&寄存器映射
  2. 百度百科——STM32F103
  3. STM32F103寄存器方式点亮LED流水灯
  4. STM32寄存器的简介、地址查找,与直接操作寄存器
  5. STM32F103C8T6实现流水灯(c语言和汇编两个版本)

猜你喜欢

转载自blog.csdn.net/YouthBlood9/article/details/120858250