STM32F103寄存器方式点亮LED流水灯(软硬结合初尝试)

一、关于STM32芯片寄存器

1.什么是寄存器

提到单片机,就不得不提到寄存器。根据百度百科介绍,寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。

简单来说,寄存器就是存放东西的东西。从名字来看,跟火车站寄存行李的地方好像是有关系的。只不过火车站行李寄存处,存放的行李;寄存器可能存放的是指令、数据或地址。
  存放数据的寄存器是最好理解的,如果你需要读取一个数据,直接到这个寄存器所在的地方来问问他,数据是多少就行了。问寄存器这个动作,叫做访问寄存器。不同的数据会存放在不同的寄存器,例如引脚PA2与PB8的高低电平数据(1或0)肯定放在不同的寄存器里,那么怎么区分不同的寄存器呢?通过地址,不同的寄存器有不同的地址,就像老张行李寄存处在101号店铺,老王行李寄存处在258号店铺。
  指令、地址寄存器与数据寄存器类似,里边存放的都是0和1,毕竟单片机也只认识机器码,机器码都是0或1,只是特别的规定下,数据寄存器里面存放的0和1表示数据,指令寄存器里存放的表示指令。

2.怎么找到某个寄存器的地址

想要找到某个寄存器的地址,可以查看《STM32中文参考手册_V10》,手册可以在这里下载,但是手册中没有直接给出所有的寄存器的地址,需要读者稍加计算,具体可查看这里

3.寄存器映射

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

二、GPIO端口设置

1.GPIO简介

GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32 芯片的 GPIO 被分成很多组,每组有 16 个引脚,如型号为 STM32F103VET6 型号的芯片有 GPIOA、GPIOB、GPIOC至 GPIOE共 5组 GPIO,芯片一共 100个引脚,其中 GPIO就占了一大部分,所有的 GPIO 引脚都有基本的输入输出功能。
最基本的输出功能是由 STM32 控制引脚输出高、低电平,实现开关控制,如把 GPIO引脚接入到 LED灯,那就可以控制 LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。
最基本的输入功能是检测外部输入电平,如把 GPIO 引脚连接到按键,通过电平高低
区分按键是否被按下。

2.GPIO框图剖析

在这里插入图片描述

3.初始化设置概述

1:打开GPIO口的时钟
2:初始化GPIO口(选择推挽输出)
3:设置低电平

三、点亮LED流水灯

1.打开GPIO口的时钟信号

查阅手册查到时钟信号的地址:
在这里插入图片描述
GPIO的地址:
在这里插入图片描述
在这里插入图片描述
则可以计算出时钟寄存器地址为0x40021018,则打开三个IO口的时钟(本例拟打开A、B、C三个IO口)需要将三个位都置1。

#define RCC_APB2ENR (*(unsigned int *)0x40021018)

// 打开时钟
RCC_APB2ENR |= (1<<2);  // 打开 GPIOA 时钟
RCC_APB2ENR |= (1<<3);  // 打开 GPIOB 时钟
RCC_APB2ENR |= (1<<4);  // 打开 GPIOC 时钟

2.GPIO初始化

由 GPIO 的结构决定了 GPIO 可以配置成以下8种模式:
在这里插入图片描述
本例采用推免输出模式,因为在输出模式中,推挽模式时双 MOS 管以轮流方式工作,输出数据寄存器 GPIOx_ODR可控制 I/O 输出高低电平。输出速度可配置,有 2MHz\10MHz\50MHz的选项。此处的输出速度即 I/O 支持的高低电平状态最高切换频率,支持的频率越高,功耗越大,如果功耗要求不严格,把速度设置成最大即可。在输出模式时施密特触发器是打开的,即输入可用,通过输入数据寄存器 GPIOx_IDR可读取 I/O 的实际状态。
具体操作如下:
端口1-7为低,端口8-15为高。每个引脚由四个位控制。
在这里插入图片描述
在这里插入图片描述
对A、B、C端口,代码如下:

#define GPIOA_CRL (*(unsigned int *)0x40010800)
#define GPIOB_CRL (*(unsigned int *)0x40010C00)
#define GPIOC_CRH (*(unsigned int *)0x40011004)

// 配置 GPIO 口为推免输出
// GPIOA----最后四位为0001
GPIOA_CRL |= (1<<0);  // 最后一位变1
GPIOA_CRL &= ~(0xE<<0);  // 倒数2、3、4位变0
// GPIOB----最后四位为0001
GPIOB_CRL |= (1<<0);  // 最后一位变1
GPIOB_CRL &= ~(0xE<<0);  // 倒数2、3、4位变0
// GPIOC----前四位为0001
GPIOC_CRH |= (1<<28);  //  第四位变1
GPIOC_CRH &= ~(0xE0000000);  // 前三位变0

3.设置低电平

在这里插入图片描述
输出高电平则为1,低电平则为0。
对A、B、C端口,代码如下:

#define GPIOA_ODR (*(unsigned int *)0x4001080C)
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)

GPIOA_ODR &= ~(1<<0);  //最后一位变为0
GPIOB_ODR &= ~(1<<0);  //最后一位变为0
GPIOC_ODR &= ~(1<<15); //倒数16位变为0

四、C语言全代码及软硬结合操作

1.创建项目

本例采用Keil5编写程序代码,具体操作见用Keil uVision5创建纯汇编语言的STM32工程
不同之处是:
1)芯片选择STM32F103C8
2)当出现下图时,直接叉掉,不做选择
在这里插入图片描述
在这里插入图片描述
将野火资料中的下图中的文件复制到新建的项目下:
在这里插入图片描述

在这里插入图片描述
右击文件夹,选择Add Existing Files to Group Source Group 1(或双击文件夹),选择All FIles,选择刚刚添加的启动文件,Add,Add之后Close:
在这里插入图片描述
在这里插入图片描述
可以看到Add成功了
在这里插入图片描述
打开魔术棒,如下图所示勾选Create HEX File:
在这里插入图片描述
在这里插入图片描述
在main.c中写入如下代码:

#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOA_BASE 0x40010800

#define RCC_APB2ENR (*(unsigned int *)0x40021018)

#define GPIOB_CRL (*(unsigned int *)0x40010C00)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)

#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
	


void SystemInit(void);
void Delay_ms(volatile  unsigned  int);

void Delay_ms( volatile  unsigned  int  t)
{
     unsigned  int  i;
     while(t--)
         for (i=0;i<800;i++);
}


int main(){
	// 开启时钟
	RCC_APB2ENR |= (1<<3); // 开启 GPIOB 时钟
	RCC_APB2ENR |= (1<<4); // 开启 GPIOC 时钟
	RCC_APB2ENR |= (1<<2); // 开启 GPIOA 时钟
	
	
	// 设置 GPIO 为推挽输出
	// 设置 GPIOB 最后四位为 0001 (B0)
	GPIOB_CRL |= (1<<0);  // 最后一位设置为1
	GPIOB_CRL &= ~(0xE);  // 倒数二、三、四位设置为0
	// 设置 GPIOC 前四位为 0001  (C15)
	GPIOC_CRH |= (1<<28); // 第四位设置为1
	GPIOC_CRH &= ~(0xE0000000);  // 前三位设置为0
	// 设置 GPIOA 最后四位为 0001 (A0)
	GPIOA_CRL |= (1<<0);  // 最后一位设置为1
	GPIOA_CRL &= ~(0xE);  // 倒数二、三、四位设置为0

	
	// 3个LED初始化为不亮(即高点位)
	GPIOB_ODR |= (1<<0);  // 最后一位设置为1
	GPIOC_ODR |= (1<<15); // 倒数第15位设置为1
	GPIOA_ODR |= (1<<0);  // 最后一位设置为1
	
	
	while(1){
		GPIOB_ODR &= ~(1<<0); // 点灯1
		Delay_ms(1000000);
		GPIOB_ODR |= (1<<0);  // 灭灯1
		
		GPIOC_ODR &= ~(1<<15); // 点灯2
		Delay_ms(1000000);
		GPIOC_ODR |= (1<<15);  // 灭灯2
		
		GPIOA_ODR &= ~(1<<0); // 点灯3
		Delay_ms(1000000);
		GPIOA_ODR |= (1<<0);  // 灭灯3
		
	}
	
}

//函数为空,目的是为了骗过编译器不报错
void SystemInit(){
	
}

2.连接电路

根据设计的程序连接电路:
对于USB转TTL模块和stm32f103c8t6连接:
GND — GND
3v3 — 3v3
TXD — A10
RXD — A9
总电路如图所示:
在这里插入图片描述

3.编译程序

点击如图按钮,编译程序,生成hex文件
在这里插入图片描述
将电路板连接到电脑,打开mcuisp,上传HEX文件到stm32f103c8t6上:
在这里插入图片描述
如上图之后,程序就开始跑了,如下图gif所示,依次亮灯:
在这里插入图片描述
值得注意的点是,LED灯的连法是根据程序连的,正极连面包板正极,负极分别接STM芯片的A0、B0、C15。

五、用示波器仿真查看代码是否正确

1.设置魔法棒信息

点开魔法棒,如图设置
在这里插入图片描述
在这里插入图片描述

2.设置示波器

点击如图所示按键
在这里插入图片描述
设置输出口(灯接的芯片位置)
在这里插入图片描述
在这里插入图片描述
因为我的灯连的芯片管脚分别是A0、B0、C15,并且是GPIO口,所以输入(以A0为例):GPIOA_IDR.0
,之后回车,然后依照上图设置bit,颜色。具体输入形式可以参考如何使用Keil5中的虚拟示波器进行软件仿真
:

3.调试运行

点击如图所示按钮
在这里插入图片描述
一段时间后,可以看到Logic Analyzer里面三个波形的显示,观察图像可以看到与代码一致,说明正确执行了。
在这里插入图片描述

六、总结

总结下来,这个对初学者真的有很大的难度,尤其是代码的编写,我就参考了很多大佬写的代码,另外就是参考了代码,还要读懂代码,不然接线就会遇到问题,正负极和哪个接口的确认都是很困扰的,但能够做下来,确实受益匪浅。

七、参考资料

STM32 F103之点亮LED流水灯 (STM32入门学习)
STM32最小系统下载程序方法
STM32串口下载程序
如何使用Keil5中的虚拟示波器进行软件仿真

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值