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

本文主要采用寄存器操作,以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只红绿蓝LED 搭建电路,使用GPIOB、GPIOC、GPIOD这3个端口控制LED灯,轮流闪烁,间隔时长1秒
寄存器操作主要帮助大家理解stm32底层是怎么实现的,也是非常重要的!

一、相关寄存器配置介绍

不管是库函数操作还是HAL操作,本质上都是对寄存器的操作。
STM32F103中文教程及参考手册以及后面用到的USB 转串口驱动和mcuisp软件下载链接:https://pan.baidu.com/s/1xqFKQSLsxEjzQrhvaK7bIA
提取码:1234
在提供的“ STM32F103中文教程及参考手册”中看到GPIO寄存器7种类型
在这里插入图片描述
首先看下5.2.1端口配置低寄存器,通过该寄存器具体介绍下。
先解释下(GPIOx_CRL)(x=A…E)的意思。stm32f103引脚有多有少,多则144个,少则48个(本实验用的48脚芯片),所以会把这些引脚分成组,A、B、C、D…每组最多16个引脚,注意,是最多,并不是一定要有16个引脚!
在这里插入图片描述
48引脚的单片机只分成了A、B、C、D(PA、PB、PC、PD)组。GPIO英语的全称是General-purpose input/output,翻译过来就是通用的IO口。GPIOA_CRL的意思是控制A口的CRL寄存器。
在这里插入图片描述
再回到上面的CTRL寄存器图,“偏移地址0x00”。那么偏移是相对于谁偏移的,应该有个基准!先给大家举个例子,要盖一幢商务大楼,外面盖完了如果里面是空的,是不行的,所以要盖一层层,一层层盖了也不行,在每一层隔处一个个房间,这样一幢商务大楼才能使用。我们单片机内存也是,整一大块是不能用的,也要隔成一个个房间才能使用,每个房间相当于寄存器,要么有人(相当于为1),要么没人(相当于为0)。所以要查下GPIOC安排在几楼!

在提供的手册中(P18页)可以看到GPIOC的地址范围是0x4001 1000~0x4001 13FF。所以它的起始地址0x4001 1000,也就是基地址!偏移0x00后可以得出GPIOC->CRL的地址(GPIOC端口的起始地址+偏移地址)为0x40011000
所谓的复位值,就是指如果没有操作这个寄存器时,寄存器存放的默认值。复位值按位拆分0x4 = 0b0100,0x表示16进制,0b表示二进制,也就是默认CNF 01,MODE 00,是浮空输入。
在这里插入图片描述
再回到CTRL寄存器图,看下面的图。一共有32个位,0~31,也就是CTRL寄存器有32位,这也是为什么叫做stm32的原因,以前的51单片机是8位单片机也就是一次性最多能处理8个位。
在这里插入图片描述
看红色框部分(0-3位),CNF0和MODE0。再看4-7位,CNF1和MODE1。说明GPIOC中的第0位需要CNF0和MODE0这4个位来控制,GPIOC中的第1位需要CNF1和MODE1这4个位来控制。具体可以控制哪些呢,在往下看。
在这里插入图片描述
在上图中,以MODE0为例,占了两个位,每个位可以表示0或1,所以可以表示4种情况!一般情况下是输出模式,在下表中的MODEy[1:0]就可以看到③,有输出和输入模式,本次实验就是选择“10”表示输出模式,最大速度为2MHZ,再去看CNFy[1:0],根据输入输出的不同有不同的选择,那么我们刚才选的是输出模式,所以看②,选择“00”,表示“通用推挽输出模式”。
通过以下代码实现通用推挽输出模式:

GPIOC_CRH &= ~(0x0F<<(4*5));
GPIOC_CRH |= (1<<(4*5));

再看5.2.3和5.2.4,一个用于配置输入,一个用于配置输出,那么我们看输出。
在这里插入图片描述
因为每个GPIO组只有16个引脚,所以ODR寄存器只用了低16位,高16位保留。
在这里插入图片描述
这个寄存器功能很简单,控制输出的数据为0或者1 。
所以我们控制LED延时闪烁也很简单,就是控制ODR寄存器先输出1,LED灯亮,延时一段时间,控制ODR寄存器先输出0,LED灯灭,一直循环,就能实现流水灯的效果。
代码如下:

GPIOC_ODR &= ~(1<<13);//配置输出低电平0
GPIOC_ODR |= (1<<13);//配置输出高电平1

相关寄存器介绍就是这些,其他端口寄存器大家有兴趣的话可以自行了解。

二、使用寄存器点亮LED灯

1.工程模板的建立

stm32提供了一个用c语言封装好的固件库,我们要实现什么功能,直接调用相应的库函数即可。
要使用ST固件库,可以建立一个工程模板,方便我们调用函数。
详细建立过程可参考:stm32f103c8t6工程模板的建立

1.1.建立相关文件
新建总文件夹用来存放本次工程的所有程序,然后再建CORE、HARDWARE、OBJ、FWLIB、SYSTEM、USER这六个文件夹。其中,HARDWARE文件夹是用来存放外设硬件代码,OBJ用来存放生成调试代码,FWLIB是各种.c和.h文件,如下图所示:
在这里插入图片描述
其中每个文件夹包含的文件如下:
在这里插入图片描述
其中文件后缀名为.s的文件,s是start的意思,它里边使用汇编语言写好了基本程序,当STM32 芯片上电启动的时候,首先会执行这里的汇编程序,从而建立起 C 语言的运行环境,所以我们把这个文件称为启动文件,可在官方进行下载。
启动文件的功能
1.初始化堆栈指针 SP;
2.初始化程序计数器指针 PC;
3.设置堆、栈的大小;
4.初始化中断向量表;
5.配置外部 SRAM 作为数据存储器(这个由用户配置,一般的开发板可没有外部SRAM);
6.调用 SystemIni() 函数配置 STM32 的系统时钟。
7.设置 C 库的分支入口“__main”(最终用来调用 main 函数);
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.2.建立工程
打开 Keil uVision5 软件,点击菜单栏的Project -> New uVision Project 新建一个工程,将其保存到USER文件夹中:
在这里插入图片描述
芯片型号选择STM32F103C8(根据自有的芯片选择)
在这里插入图片描述
然后会弹出Manage Run-time Environment窗口,选择取消
在这里插入图片描述
1.3.添加项目所需要的分组以及文件
右击Target 1,选择Manage Project Items…
在这里插入图片描述
创建Groups名字,点击右边的Add Files…按钮添加相应的文件
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
添加后可在工作栏中查看
在这里插入图片描述
1.4.配置Options for Target 'Target 1’

点击Target,可以看到STM芯片为STM32F103C8,修改晶振频率值为8在这里插入图片描述
点击Output,其中select folder for objects是选择生成的hex存放的目录,这里选择存放在建立的OBJ文件夹中,Create HEX File用于生成可执行代码文件(可以用编程器写入单片机芯片的 HEX 格式文件,文件的扩展名为.HEX),用于下载到开发板的
在这里插入图片描述
在这里插入图片描述
接着点击C/C++选项,将其中的Define设置为USE_STDPERIPH_DRIVER,STM32F10X_MD,然后点击右下方的添加按钮添加Include Paths的路径
在这里插入图片描述
注:USE_STDPERIPH_DRIVER,STM32F10X_MD两个定义之间应该使用英文逗号隔开,而不是点
添加的路径为之前新建的文件夹内容
在这里插入图片描述
至此,就基本上完成了工程模板的建立。

2.配置GPIO端口

GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
GPIO端口的初始化设置三步骤:

  • 时钟配置
  • 输入输出模式设置
  • 最大速率设置

2.1.配置时钟使能
为什么配置时钟?为了省电,默认的时钟都是关闭的。配置STM32的任何资源前,都必须首先使能时钟。

 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  //开启 GPIOB 端口时钟

2.2.初始化结构体
库函数中提供了一个结构体来配置GPIO端口的输入输出模式设置 、 最大速率设置等。
定义的结构体如下:

// @file    stm32f10x_gpio.h
typedef struct
{
  uint16_t GPIO_Pin;           /*!< 选择要配置的 GPIO 引脚 */

  GPIOSpeed_TypeDef GPIO_Speed;  /*!< 选择 GPIO 引脚的速率 */

  GPIOMode_TypeDef GPIO_Mode;    /*!< 选择 GPIO 引脚的工作模式 */
}GPIO_InitTypeDef;

这个结构体中包含了初始化 GPIO 所需要的信息,包括引脚号、工作模式、输出速率。
设计这个结构体的思路是:初始化 GPIO 前,先定义一个这样的结构体变量,根据需要配置 GPIO 的模式,对这个结构体的各个成员进行赋值,然后把这个变量作为“GPIO 初始化函数”的输入参数,该函数能根据这个变量值中的内容去配置寄存器,从而实现 GPIO 的初始化。
2.3.配置输入输出模式
配置 端口位为通用推挽输出,速度为 2M

    GPIO_InitTypeDef   GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;  			//输出模式为通用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4 ;             		//选定输出端口为GPIO_Pin_4
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;				//输出速度为2M
	
	GPIO_Init(GPIOA,&GPIO_InitStruct);

至此一个GPIOB_Pin_4配置完毕,我们总算可以控制一个 LED 了。

3.主要函数

3.1.led.h函数

#ifndef _LED_H
#define _LED_H

#include "stm32f10x.h"
void LED_R_TOGGLE(void);
void LED_G_TOGGLE(void);
void LED_Y_TOGGLE(void);
void LED_Init(void);
#endif

3.2.led.c函数

#include "led.h"
#include "delay.h"

void LED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);  //打开外设GPIOB的时钟
	
	GPIO_InitTypeDef   GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;  			//输出模式为通用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4 ;             //选定端口为GPIO_Pin_4
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;				//输出速度为2M
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;  			//输出模式为通用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10 ;             //选定端口为GPIO_Pin_1
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;				//输出速度为2M
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;  			//输出模式为通用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_14 ;             //选定端口为GPIO_Pin_14
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;				//输出速度为2M
	GPIO_Init(GPIOC,&GPIO_InitStruct);
}

void LED_R_TOGGLE(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_4);
	delay_ms(500);
	GPIO_ResetBits(GPIOA,GPIO_Pin_4);	
}
void LED_G_TOGGLE(void)
{
	GPIO_SetBits(GPIOB, GPIO_Pin_10);
	delay_ms(500);
	GPIO_ResetBits(GPIOB,GPIO_Pin_10);
}
void LED_Y_TOGGLE(void)
{
	GPIO_SetBits(GPIOC, GPIO_Pin_14);	
	delay_ms(500);
	GPIO_ResetBits(GPIOC,GPIO_Pin_14);
}

3.3.delay.h函数

#ifndef __DELAY_H
#define __DELAY_H 			   
#include "sys.h"  
	 
void delay_init(void);
void delay_ms(u16 nms);
void delay_us(u32 nus);

#endif

3.4.delay.c函数
本实验调用的是正点原子写好的延时函数用于实现延时1s后三种LED灯轮流闪烁,这里就不多做介绍,若感兴趣,可以自行尝试编写。

#include "delay.h"
// 	 
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif 

static u8  fac_us=0;							//us延时倍乘数			   
static u16 fac_ms=0;							//ms延时倍乘数,在ucos下,代表每个节拍的ms数
	
	
#if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
#ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
#endif

//支持UCOSIII
#ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
#endif

//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedLock(&err);							//UCOSIII的方式,禁止调度,防止打断us延时
#else											//否则UCOSII
	OSSchedLock();								//UCOSII的方式,禁止调度,防止打断us延时
#endif
}

//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{	
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedUnlock(&err);						//UCOSIII的方式,恢复调度
#else											//否则UCOSII
	OSSchedUnlock();							//UCOSII的方式,恢复调度
#endif
}

//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
	OS_ERR err; 
	OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);	//UCOSIII延时采用周期模式
#else
	OSTimeDly(ticks);							//UCOSII延时
#endif 
}
 
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{	
	if(delay_osrunning==1)						//OS开始跑了,才执行正常的调度处理
	{
		OSIntEnter();							//进入中断
		OSTimeTick();       					//调用ucos的时钟服务程序               
		OSIntExit();       	 					//触发任务切换软中断
	}
}
#endif
		   
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	u32 reload;
#endif
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
	fac_us=SystemCoreClock/8000000;				//为系统时钟的1/8  
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	reload=SystemCoreClock/8000000;				//每秒钟的计数次数 单位为M  
	reload*=1000000/delay_ostickspersec;		//根据delay_ostickspersec设定溢出时间
												//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
	fac_ms=1000/delay_ostickspersec;			//代表OS可以延时的最少单位	   

	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
	SysTick->LOAD=reload; 						//每1/delay_ostickspersec秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    

#else
	fac_ms=(u16)fac_us*1000;					//非OS下,代表每个ms需要的systick时钟数   
#endif
}								    

#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;					//LOAD的值	    	 
	ticks=nus*fac_us; 							//需要的节拍数	  		 
	tcnt=0;
	delay_osschedlock();						//阻止OS调度,防止打断us延时
	told=SysTick->VAL;        					//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;		//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;				//时间超过/等于要延迟的时间,则退出.
		}  
	};
	delay_osschedunlock();						//恢复OS调度									    
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{	
	if(delay_osrunning&&delay_osintnesting==0)	//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
	{		 
		if(nms>=fac_ms)							//延时的时间大于OS的最少时间周期 
		{ 
   			delay_ostimedly(nms/fac_ms);		//OS延时
		}
		nms%=fac_ms;							//OS已经无法提供这么小的延时了,采用普通方式延时    
	}
	delay_us((u32)(nms*1000));					//普通方式延时  
}
#else //不用OS时
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; 					//时间加载	  		 
	SysTick->VAL=0x00;        					//清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数	  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
	SysTick->VAL =0X00;      					 //清空计数器	 
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864 
void delay_ms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;				//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;							//清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
	SysTick->VAL =0X00;       					//清空计数器	  	    
} 
#endif 

3.5.main.c函数

#include  "stm32f10x.h"
#include "delay.h"
#include "led.h"
int main(void)
{			  
	LED_Init();	
	delay_init();	                //使用系统滴答定时器、延时初始化
	
	while(1)						//循环亮起
	{
		LED_R_TOGGLE();
		delay_ms(500);				//红灯亮后延时1s
		LED_G_TOGGLE();
		delay_ms(500);				//绿灯亮后延时1s
		LED_Y_TOGGLE();
		delay_ms(500);				//黄灯亮后延时1s
	}
}

4.编译生成HEX文件

点击左上角的编译按钮进行编译,可以看到生成了hex 文件
在这里插入图片描述
打开建立的OBJ文件夹也可以看见存放有生成的.hex文件
在这里插入图片描述

5.电路搭建

本次实验是在面包板上使用c8t6来控制红、绿、黄三个灯轮流闪烁,因此需熟悉面包板的使用,关于面包板的使用简介可参考:https://blog.csdn.net/countofdane/article/details/82561478
于是搭建的电路图如下:
在这里插入图片描述
注意使用管脚时不要插错,否则实验会不成功

6.用串口下载程序

其中实验用到的STM32 开发板用的 USB 转串口的驱动芯片是 CH340,要使用串口得先在电脑中安装 USB 转串口驱动—CH340 版本,下载链接详见前面。

如果 USB 转串口驱动安装成功,USB 线跟板子连接没有问题,在计算机->管理->设备管理器->端口中可识别到串口。
在这里插入图片描述
用 USB 线连接电脑和开发板的 USB 转串口接口:USB TO UART,给开发板上电。
打开 mcuisp 软件,配置如下:
①搜索串口,设置波特率 115200(尽量不要设置的太高)
②选择要下载的HEX 文件
③校验、编程后执行
④DTR 低电平复位,RTS 高电平进入 bootloader
⑤开始编程,如果出现一直连接的情况,按一下开发板的复位键即可
在这里插入图片描述
开始编译后的下载成功后的提示如下图,则说明HEX文件已经成功被烧录到芯片中。
在这里插入图片描述

7.实验结果

在这里插入图片描述
可以看见从红、绿、黄灯轮流闪烁,间隔时间1s(因格式帧间隔时间短,故看不出来),实验成功。

三、小结

通过这次使用C语言,在Keil 5上编译生成HEX文件并使用STM32寄存器方式进行的点亮LED流水灯实验,让我有了一番不一样的体验,虽然在过程中遇到了一些硬件上的问题,但询问同学后还是得以解决,让我明白了在仿真器上操作和使用硬件进行操作还是存在区别的,使用硬件进行操作的过程中会学到更多的基础知识,能进一步的学习和理解STM32F103系列芯片的地址映射和寄存器映射原理,了解GPIO端口的初始化设置三步骤,总之收获很大。
另外若你感兴趣的话,可以将程序代码改为汇编语言进行LED点灯尝试,鉴于小编能力有限,这里就不多做说明。

四、参考文献

1.https://blog.csdn.net/athen21/article/details/84070379
2.https://blog.csdn.net/weixin_42827999/article/details/101699674
3.https://blog.csdn.net/weixin_52288941/article/details/120741229?utm_source=app&app_version=4.16.0&code=app_1562916241&uLinkId=usr1mkqgl919blen
4.https://blog.csdn.net/geek_monkey/article/details/86291377
5.https://blog.csdn.net/daniaoxp/article/details/100176967

  • 8
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答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流水灯,可以调整定时器的时钟源和计数器值来实现不同的闪烁效果。虽然这种方式比较繁琐,但对于有一定经验的开发者来说,可以更精准地控制硬件,实现更高效的程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值