stm32寄存器操作点亮流水灯


用Proteus 设计一个STM32最小系统板+LED流水灯实验原理图,仿真运行。以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只_(或更多)红绿蓝LED 搭建电路,使用GPIOA、GPIOB、GPIOC这3个端口控制LED灯,轮流闪烁,间隔时长1秒。

1)写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数;

2)用C语言寄存器方式编程实现,代码须有详细注解。

3)STM32最小系统核心板子出厂时已经焊接好了1个led灯(标注了PC13处),一般可通过此灯的点亮让编程者验证自己烧录的代码是否正常运行了。请查阅最小版电路原理图和相关资料,将这个灯也用在流水灯中,重编新程序。

一、stm32C8T6介绍

STM32F103C8T6是一款由意法半导体公司(ST)推出的基于Cortex-M3内核的32位微控制器,硬件采用LQFP48封装,属于ST公司微控制器中的STM32系列。除了被我们熟知的STM32,ST公司还有SPC5X系列、STM8系列等,具体参数如下:
在这里插入图片描述

二、GPIOx端口的各寄存器地址和详细参数

1.地址查找

·第一步,找到GPIOB的基地址
也就是找到GPIOB的小区。结论是,所有GPIOB相关的寄存器,都住在0x4001 0C00到0x4001 0FFF范围内。
在这里插入图片描述
·第二步,找到端口输入寄存器的地址偏移
找到存储数据的那个屋子,结论是0x4001 0C00+8 = 0x4001 0C08
在这里插入图片描述
·第三步,找到知道数据的那个人
PB3的数据位于从右往左数第4个。
在这里插入图片描述
而这个寄存器的位数是32位(虽然高16位没有用到),这就是32位的单片机的意思。每个寄存器都占据4字节,32位。
PB3的输入数据位于0x4001 0C08这个地址上,这个地址上存放数据的右起第4个位就是PB3引脚对应的高低电平。
直接访问这个地址:

 unsigned int *pGPIOB_IDR = (unsigned int *)0x40010C08;
 unsigned char PB3 = *pGPIOB_IDR & 0x8;//取出从右往左数的第4位

2.直接操作寄存器来点亮LED

使用LED在PB8。

·配置时钟使能。
参考手册,搜索"时钟",在表1里可以看到。
时钟控制名字叫做RCC,属于AHB总线。GPIOB属于APB2。
在这里插入图片描述
下图系统结构可以看到时钟的从属关系,可以看出AHB总线包含RCC时钟控制,GPIO是属于APB2的。
在这里插入图片描述
GPIO端口B的地址从0x4001 0C00开始。接下来只寻找时钟使能寄存器的地址:
复位和时钟控制RCC的地址从0x4002 1000开始;
可以在6.3.7小节找到APB2外设时钟使能寄存器(RCC_APB2ENR),偏移地址是0x18,所以APB2的地址就是0x4002 1018。
看手册RCC_APB2ENR,位3是IOPBEN,名字是IO端口B时钟使能,就是我们想要的。把RCC_APB2ENR的位3赋值为1,就是开启GPIOB时钟。
在这里插入图片描述
·配置为通用输出
既然叫做IO,那么肯定就是可以输入,可以输出。
控制LED需要输出高电平或是低电平,所以需要配置为输出。
由于STM32的每个IO都需要4个位来配置,所以一个32位的寄存器最大只能配置8个IO(32位的单片机的寄存器就是32位的)。STM32中,用端口配置低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用端口配置高寄存器(GPIOx_CRH)来配置引脚Px8-Px15。
配置引脚PB8,使用的寄存器是GPIOB_CRH。下面我们来寻找这个寄存器的地址。
在这里插入图片描述
关于此寄存器的说明位于8.2.2小节。先看标题GPIOx,表示不管是PA,PB还是PE,都能用。
偏移地址是0x04,意思是在基地址的基础上再加0x04,所以,对于GPIOB来说就是0x4001 0c04。如果配置PB0-PB7,那么需要的寄存器是低位的寄存器GPIOB_CRL,它的地址是0x4001 0c00。我们需要配置的寄存器是GPIOB_CRH。
找到需要操作的寄存器后,把它配置为通用输出。
复位值是0x4444 4444,并不是0x0000 0000。所谓的复位值,就是指如果没有操作这个寄存器时,寄存器存放的默认值。复位值按位拆分0x4 = 0b0100,0x表示16进制,0b表示二进制,也就是默认CNF 01,MODE 00,是浮空输入。
我们需要的是输出高低电平,所以要设置为输出。输出模式有:
在这里插入图片描述
推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。
开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。
开漏是需要外接上拉电阻才可以输出高电平的,这里并不适合。所以需要设置为推挽输出。
所以配置为输出模式,通用推挽输出。所以设置GPIOB_CRH的MODE8与CNF8为0b0011,即0x3。此寄存器中其它的位暂时不做修改,使用默认值,也就是GPIOB_CRH设置为:0x4444 4443。
点亮LED需要输出低电平,我们需要输出0,得知地址的偏移是0x0C,所以这个数据寄存器的地址就是0x4001 0C0C,把第8位写为0。
在这里插入图片描述
在这里插入图片描述
·使用直接赋值的方式写寄存器的地址

    int main(void)
    {
        unsigned int *pRCC_APB2ENR = (unsigned int *)0x40021018;
        unsigned int *pGPIOB_CRH = (unsigned int *)0x40010c04;
        unsigned int *pGPIOB_ODR = (unsigned int *)0x40010c0c;
        *pRCC_APB2ENR = 0x00000008;
        *pGPIOB_CRH = 0x44444443;
        *pGPIOB_ODR = 0x00000000;
         return 0;             
    }

三、设计思路

需要在 STM32F103C8T6 上面通过 初始化GPIO 来实现点亮 LED 灯。
外设实现的功能可能是完全不同的,但是,多数情况下,我们在设计程序的时候不需要考虑外设具体如何怎样实现功能,只需要给外设接在哪里、高电平有效还是低电平有效。因此,完成题目要求的时候,只需要找到 GPIOA-5、GPIOB-9、GPIOC-14 的地址,然后通过 GPIO的初始化,控制寄存器将片外引脚(我们称之为 IO口)拉低拉高, 输出高低电平,以控制LED亮灭。

点灯是所有学单片机的人都应该学会的一项技能。C51单片机和 stm32 点灯类似。

51单片机的点灯是,通过控制寄存器将片外引脚(我们称之为IO口)拉低拉高,输出高低电平,以控制LED亮灭。
其过程:单片机给指令->控制寄存器->给IO口电平->控制LED亮灭
stm32的点灯则是,通过使能外设GPIO时钟,发出指令给外设GPIO,外设GPIO收到指令后,着手配置自己的寄存器,然后给IO口模式,让其实现各种功能。其过程:CPU给指令->GPIO收到指令->配置内部寄存器->配置IO口模式(注意是模式)->控制LED亮灭
STM32开发板中包含较多寄存器,实现流水灯操作,需要对相应的引脚进行操作,对相应的引脚进行时钟配置、输入输出模式设置、最大速率设置。
于是利用 STM32F103C8T6 实现流水灯,要经过以下步骤:

·时钟配置
·输入输出模式设置
·最大速率设置
·烧录程序
·运行

四、实现过程

1、时钟配置

找到时钟使能寄存器映射基地址
在这里插入图片描述

找到端口偏移地址以及对应端口所在位置
在这里插入图片描述

·外设时钟使能寄存器,偏移量为0x18,起始地址0x4002 1000,该寄存器地址为0x4002 1018
·使能对应端口时钟
查询数据手册可发现,外设时钟使能寄存器,设偏移量为0x18,起始地址0x4002 1000,该寄存器地址为0x4002 1018

#define RCC_AP2ENR	*((unsigned volatile int*)0x40021018) #时钟使能寄存器

手册RCC_APB2ENR,位3是IOPBEN,名字是IO端口B时钟使能,就是我们想要的。把RCC_APB2ENR的位3赋值为1,就是开启GPIOB时钟

RCC->APB2ENR|=1<<2;			//APB2-GPIOA外设时钟使能
RCC->APB2ENR|=1<<3;			//APB2-GPIOB外设时钟使能	
RCC->APB2ENR|=1<<4;			//APB2-GPIOC外设时钟使能
//这两行代码可以合为 RCC_APB2ENR|=1<<3|1<<4;

在这里插入图片描述

2、输入输出模式设置、最大速率设置

本次实验采用通用推挽输出模式,最高输出时钟频率2Mhz。分别用到GPIOA-5、GPIOB-9、GPIOC-14 这3个引脚上三个引脚。其中A4属于端口配置低寄存器偏移地址为0x00,B9、C14属于端口配置高寄存器偏移地址为0x04。
在这里插入图片描述
在这里插入图片描述

·找到GPIOx端口基地址
在这里插入图片描述

配置对应引脚寄存器,基地址+偏移量
故而配置如下:

//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL		*((unsigned volatile int*)0x40010800)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRH		*((unsigned volatile int*)0x40010C04)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH		*((unsigned volatile int*)0x40011004)

设置输出模式为推挽输出,输出速度为2Mhz
例:将GPIOB-9配置成推挽输出模式,且最大速度为2MHz
首先,其为GPIOB9端口,其属于端口配置高寄存器模块,则由上图可知,CNF9和MODE9位为0,其余位为F,即:GPIOB_CRH&=0xFFFFFF0F;又因其为推挽输出模式,且最大速度为2MHz,所以4位寄存器的配置就是CNF9【00】MODE9【10】,0010换成十进制数就是2,即:GPIOB_CRH|=0x00000020

GPIOA_CRL&=0xFFF0FFFF;		//设置位清零	
GPIOA_CRL|=0X00200000;		//PA5推挽输出,把第23、22、21、20位变为0010
GPIOA->ODR|=1<<5;			//设置PA5初始灯为灭
	
GPIOB_CRH&=0xFF0FFFFF;		//设置位清零	
GPIOB_CRH|=0x00000020;		//PB9推挽输出,把第7、6、5、4变为0010
GPIOB->ODR|=0x1<<9;			//设置初始灯为灭
	 
GPIOC_CRH&=0xFF0FFFFF;		//设置位清零	
GPIOC_CRH|=0x02000000;		//PC14推挽输出,把第27、26、25、24变为0010
GPIOC->ODR|=0x1<<14;			//设置初始灯为灭	

3、代码实现

1.创建新工程

·新建 test2 文件夹 —> 点击 Project 下的 New uVision Project —> 输入文件名 test2
在这里插入图片描述
·选择 STM32F103C8
在这里插入图片描述
·创建项目出现弹窗,勾选 CORE 项,点击 OK 完成创建
在这里插入图片描述

2.添加启动文件

点击 Target1→Source Group1→双击→设置打开文件类型为 Asm Source file→选择 startup_stm32f10x_md.s→点击 Add,如下图所示:
在这里插入图片描述

3.代码编写并编译

(1)加上PC13口前代码

//--------------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_CRH		*((unsigned volatile int*)0x40010C04)
#define	GPIOB_ODR		*((unsigned volatile int*)0x40010C0C)
	
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH		*((unsigned volatile int*)0x40011004)
#define	GPIOC_ODR		*((unsigned volatile int*)0x4001100C)

//函数声明
void Delay_ms(volatile  unsigned  int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);

//时延函数
void Delay_ms( volatile  unsigned  int  t)
{
     unsigned  int  i;
     while(t--)
         for (i=0;i<10000;i++);
}

//控制灯亮灭
void A_LED_LIGHT()
	{
    GPIOA_ODR=0x0<<5;		//PA5低电平
	GPIOB_ODR=0x1<<9;		//PB9高电平
	GPIOC_ODR=0x1<<14;		//PC14高电平	
}
void B_LED_LIGHT()
	{
	GPIOA_ODR=0x1<<5;		//PA5高电平
	GPIOB_ODR=0x0<<9;		//PB9低电平
	GPIOC_ODR=0x1<<14;		//PC14高电平
}
void C_LED_LIGHT()
	{
	GPIOA_ODR=0x1<<5;		//PA5高电平
	GPIOB_ODR=0x1<<9;		//PB9高电平
	GPIOC_ODR=0x0<<14;		//PC14低电平
}

//------------------------主函数--------------------------
int main(){

	RCC_APB2ENR|=1<<2;			//APB2-GPIOA外设时钟使能
	RCC_APB2ENR|=1<<3;			//APB2-GPIOB外设时钟使能	
	RCC_APB2ENR|=1<<4;			//APB2-GPIOC外设时钟使能
	

  GPIOA_CRL&=0xFF0FFFFF;		//设置位清零	
  GPIOA_CRL|=0X00200000;		//PA5推挽输出,把第23、22、21、20位变为0010
	
  GPIOB_CRH&=0xFFFFFF0F;		//设置位清零	
  GPIOB_CRH|=0x00000020;		//PB9推挽输出,把第7、6、5、4变为0010
	 
  GPIOC_CRH&=0xF0FFFFFF;		//设置位清零	
  GPIOC_CRH|=0x02000000;		//PC14推挽输出,把第27、26、25、24变为0010

	GPIOA_ODR |= (1<<5);  
	GPIOB_ODR |= (1<<9);      //设置灯的初始状态为灭
	GPIOC_ODR |= (1<<14); 
	
	while(1)
		{
		  A_LED_LIGHT();
		  Delay_ms(60);
		  B_LED_LIGHT();
		  Delay_ms(60);
		  C_LED_LIGHT();
		  Delay_ms(60);
	}

}

(2)加上PC13口后代码

·由于GPIOC已配置寄存器,只需要加上PC13推挽输出,再循环时增添一个PC13灯亮循环即可,由于PC14和PC13共用一个GPIOC,所以需要增加相应与活=或运算。

//--------------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_CRH		*((unsigned volatile int*)0x40010C04)
#define	GPIOB_ODR		*((unsigned volatile int*)0x40010C0C)
	
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH		*((unsigned volatile int*)0x40011004)
#define	GPIOC_ODR		*((unsigned volatile int*)0x4001100C)

//函数声明
void Delay_ms(volatile  unsigned  int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
void C3_LED_LIGHT(void);
//时延函数
void Delay_ms( volatile  unsigned  int  t)
{
     unsigned  int  i;
     while(t--)
         for (i=0;i<10000;i++);
}

//控制灯亮灭
void A_LED_LIGHT()
	{
    GPIOA_ODR &= (0<<5);  //PA5低电平,红灯亮
	GPIOB_ODR |= (1<<9);   //PB8高电平,黄灯灭  
	GPIOC_ODR |= (1<<14); //PC14高电平,绿灯灭
	GPIOC_ODR |= (1<<13);  //PC13,PC13口灯不亮
    		
}
void B_LED_LIGHT()
	{
	GPIOA_ODR |= (1<<5);  //高电平
	GPIOB_ODR &= (0<<9);   //低电平   
	GPIOC_ODR |= (1<<14); //高电平
	GPIOC_ODR |= (1<<13);//高电平
}
void C_LED_LIGHT()
	{
	GPIOA_ODR |= (1<<5);  //高
	GPIOB_ODR |= (1<<9);  //高    
	GPIOC_ODR &= (0<<14); //低
	GPIOC_ODR |= (1<<13); //高
		
	}
void C3_LED_LIGHT()
	{
	GPIOA_ODR |= (1<<5);  //高
	GPIOB_ODR |= (1<<9);    //高  
	GPIOC_ODR = (0<<13);//高
	GPIOC_ODR |= (1<<14); //低
	}

//------------------------主函数--------------------------
int main(){

	RCC_APB2ENR|=1<<2;			//APB2-GPIOA外设时钟使能
	RCC_APB2ENR|=1<<3;			//APB2-GPIOB外设时钟使能	
	RCC_APB2ENR|=1<<4;			//APB2-GPIOC外设时钟使能
	

  GPIOA_CRL&=0xFF0FFFFF;		//设置位清零	
  GPIOA_CRL|=0X00200000;		//PA5推挽输出,把第23、22、21、20位变为0010
	
  GPIOB_CRH&=0xFFFFFF0F;		//设置位清零	
  GPIOB_CRH|=0x00000020;		//PB9推挽输出,把第7、6、5、4变为0010
	 
  GPIOC_CRH&=0xF0FFFFFF;		//设置位清零	
  GPIOC_CRH|=0x02000000;		//PC14推挽输出,把第27、26、25、24变为0010
	
  GPIOC_CRH&=0xFF0FFFFF;		//设置位清零	
  GPIOC_CRH|=0x00200000;		//PC13推挽输出,把第23、22、21、20变为0010

	GPIOA_ODR |= (1<<5);  
	GPIOB_ODR |= (1<<9);      //设置灯的初始状态为灭
	GPIOC_ODR |= (1<<14); 
	GPIOC_ODR |= (1<<13);
	while(1)
		{
		  
		  A_LED_LIGHT();//红灯亮
		  Delay_ms(60);
		  B_LED_LIGHT();//黄灯亮
		  Delay_ms(60);
		  C_LED_LIGHT();//绿灯亮
		  Delay_ms(60);
		  C3_LED_LIGHT();//PC13口亮
		  Delay_ms(60);
	}

}

编译,生成HEX文件
在这里插入图片描述

4、仿真图设计

在这里插入图片描述

5、硬件连接

用杜邦线分别将A5,B9,C14和LED负极连接起来,再将LED正极接+,板子3.3接+
在这里插入图片描述

五、结果实现

1、仿真

2、硬件

总结

这次算是对stm32使用的入门,是第一次通过编写程序实现stm32硬件的应用。这次实验通过简单的寄存器操作,我深刻理解了stm32的工作原理;了解了寄存器的相关介绍;学习了地址查找,直接操作寄存器;FlyMu的使用及ST-link的配置、硬件的连接,收获很大,受益匪浅。

参考:
1.STM32启动文件:startup_stm32f10x_hd.s等启动文件的简单描述
2.STM32F103C8T6寄存器方式借助面包板点亮LED流水灯详解
3.STM32寄存器的简介、地址查找,与直接操作寄存器

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用以下代码来点亮LED灯: ``` section .text global _start _start: mov eax, b00000010 ; 将LED灯的控制位设置为2 mov ebx, b00000000 ; 将LED灯的状态设置为关闭 out x378, eax ; 将控制位写入端口x378 out x378, ebx ; 将状态写入端口x378 mov eax, 1 ; 退出程序 xor ebx, ebx int x80 ``` 这段汇编代码将LED灯的控制位设置为2,状态设置为关闭,然后将控制位和状态写入端口x378,最后退出程序。 ### 回答2: 点亮 LED 灯可以通过控制相应的 GPIO 引脚来实现。下面是使用汇编语言编写一个点亮 LED 灯的程序的示例: ORG 0 ; 设置端口A的引脚为输出 LDI R16, 0xFF OUT DDRB, R16 ; 设置端口A的引脚高电平输出 LDI R17, 0xFF OUT PORTB, R17 END 在这个示例中,我们首先设置了端口B的引脚为输出,这里的端口B可以按照具体的硬件平台进行相应的调整。然后,我们使用 LDI 指令将数值 0xFF 装载到寄存器 R17 中,表示将端口B的引脚设置为高电平输出。最后,我们使用 OUT 指令将寄存器 R17 的内容输出到端口B,从而点亮 LED 灯。 以上是一个基本的汇编语言程序的示例,具体的硬件平台和编程环境可能会有所不同,你需要根据具体的硬件平台和编程环境进行相应的调整。 ### 回答3: 汇编语言可以使用一个标准的端口库来控制硬件,点亮LED灯可以通过控制某个特定的端口来实现。 首先,我们需要确定使用的是哪个端口。每个硬件平台都有不同的IO端口控制方式,因此我们需要查找相关资料并确定要使用的端口。 以8086微处理器为例,如果想要点亮LED灯,我们可以选择将其连接到端口A,其中的位0(低位)控制LED灯的亮灭。通过将位0设置为1,则会点亮LED灯。因此,我们需要向端口A发送一个带有位0为1的数据。 下面是汇编语言代码示例: ``` .model small .stack 100 .data .code main proc MOV AL, 00000001B ; 将AL寄存器设置为二进制 00000001,其中位0为1 OUT 00h, AL ; 将AL的值输出到端口00h,控制LED灯点亮 MOV AH, 04Ch ; 退出程序 INT 21h main endp end main ``` 以上示例代码首先将AL寄存器设置为二进制数00000001,其中位0为1,然后通过OUT指令将AL寄存器的值输出到端口00h。这样就可以点亮LED灯。 请注意,以上代码只是示例,并且使用了8086微处理器的端口控制方式。具体的代码可能会因为使用不同的硬件平台而有所差异。因此,在实际应用中,您需要根据所使用的硬件平台和端口控制方式进行适当的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值