实验1---基于寄存器地址&标准外设库的LED流水灯

基于寄存器地址&标准外设库的LED流水灯

一.嵌入式基础

实验准备
配置stm32
1)打开keil5软件,点击project,New project,选择存放工程的文件夹(我的文件夹已经提前命名“stm study”),在该文件夹里面新建文件light
在这里插入图片描述
保存以后,接着选择芯片STM32F103C8系列,点击OK,叉掉弹出窗口。然后打开1-1led_light文
件夹,继续新建三个文件“Start,Library、User”,然后打开固件库,配置这三个文件夹
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2)文件建立完毕以后,大体开发环境已经搭成,回到keil软件,点击工程文件管理按钮(形状是三个箱子),把默认的组删掉,新建start、Library、User,然后分别选中,点击Add Files,添加文件,路径就是(1)中配置的文件,注意添加start文件时,先选后缀为md的文件,再去添加其他的.c和.文件,其他两个全选添加
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
从右边可以看到添加的文件分别是什么(大家可以进行对照),最后点击ok,文件添加成功!
(3)之后点击魔术棒选项,打开C/C++,在include paths栏,把添加的文件夹路径都添加进来,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后确定,OK,至此工程选项就配置好了。

二.流水灯实验

实验任务1:以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只_(或更多)红绿蓝LED 搭建电路,使用GPIOA/GPIOB/GPIOC端口控制LED灯,轮流闪烁,间隔时长1秒。
1)写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数;
2)用C语言寄存器方式编程实现,代码须有详细注解。
3)STM32最小系统核心板子出厂时已经焊接好了1个led灯(标注了PC13处),一般可通过此灯的点亮让编程者验证自己烧录的代码是否正常运行了。请查阅最小版电路原理图和相关资料,将这个灯也用在流水灯中,重编新程

(1)程序设计思路
使能GPIO时钟
默认的时钟都是关闭的。配置STM32的任何资源前,都必须首先使能时钟。
GPIOA~C的时钟是能寄存器为RCC_APB2ENR,由于RCC寄存器地址为0x4002 1000,RCC_APB2ENR的偏移地址为0x18,因此实际RCC_APB2ENR地址为:
0x4002 1000+0x18=0x4002 1018
使用GPIOA/GPIOB/GPIOC端口控制LED灯,需打开GPIO时钟:

//打开gpio A端口的时钟
*(unsigned int *)0x40021018 |=(1<<2);
//打开gpio B端口的时钟
*(unsigned int *)0x40021018 |=(1<<3);
//打开gpio C端口的时钟
*(unsigned int *)0x40021018 |=(1<<4);

GPIO口配置
因为STM32中一个寄存器只有32位,一个输出引脚占4位,所以一个寄存器中只能放8个引脚的数据。而一个GPIO下有16个引脚,所以就有端口配置低寄存器(GPIOx_CRL)和端口配置高寄存器(GPIOx_CRH)之分。

端口配置低寄存器配置输出模式

端口输入数据寄存器和端口输出数据寄存器

PA1,PB1需要用端口配置低寄存器(GPIOx_CRH)。端口配置低寄存器(GPIOx_CRH)的偏移地址为0x00,而GPIOA的基地址为0x4001 0800,GPIOA的基地址为0x4001 0C00,所以GPIOA_CRH的地址为0x4001 0800,GPIOB_CRH的地址为0x4001 0C00。

端口输出数据寄存器(GPIOx_ODR)的偏移地址为0x0C,而GPIOA的基地址为0x4001 0800,GPIOA的基地址为0x4001 0C00,所以GPIOA_ODR的地址为0x4001080C,GPIOB_ODR的地址为0x40010C0C。

将PA1,PB1配置成通用推挽输出模式,结合图可知,CNF0配置成00,MODE0配置成11即可。

PC14,需要用端口配置高寄存器(GPIOx_CRH)。端口配置高寄存器(GPIOx_CRH)的偏移地址为0x04,而GPIOC的基地址为0x4001 1000,所以GPIOC_CRH的地址为0x4001 1004。

端口输出数据寄存器(GPIOx_ODR)的偏移地址为0x0C,而GPIOC的基地址为0x4001 1000,所以GPIOC_ODR的地址为0x4001100C。

将PC14配置成通用推挽输出模式,结合图可知,CNF13配置成00,MODE13配置成11即可。

*(unsigned int *)0x40010800 &= 0xFFFFFFF0; //设置位 清零
*(unsigned int *)0x40010800 |= 0x00000030; //PA1推挽输出,速度为50MHz
	
*(unsigned int *)0x40010C00 &= 0xFFFFFF0F; //设置位清零
*(unsigned int *)0x40010C00 |= 0x00000030; //PB1推挽输出,速度为50MHz
	
*(unsigned int *)0x40011004 &= 0xFF0FFFFF; //设置位清零
*(unsigned int *)0x40011004 |= 0x00300000; //PC14推挽输出,速度为50MHz 

流水灯设计
红灯对应 PA1 引脚、绿灯对应 PB1 引脚、蓝灯对应 PC14 引脚,红绿蓝轮流闪烁,间隔时长1秒。

*(unsigned int *)0x4001080C=0x1<<1;	 // 将GPIOA的Pin_1置为高电平
*(unsigned int *)0x40010C0C=0x1<<1;	 // 将GPIOB的Pin_1置为高电平
*(unsigned int *)0x4001100C=0x1<<14; // 将GPIOC的Pin_14置为高电平

while (1)
{

    // 红灯亮:GPIOA的Pin_1置低,其他两个引脚置高
    *(unsigned int *)0x4001080C=0x0<<1;	  // 将GPIOA的Pin_1置为低电平     
	*(unsigned int *)0x40010C0C=0x1<<1;	  // 将GPIOB的Pin_1置为高电平     
	*(unsigned int *)0x4001100C=0x1<<14;  // 将GPIOC的Pin_14置为高电平
    Delay_ms(1000); // 延时1秒
    
    // 绿灯亮:GPIOB的Pin_1置低,其他两个引脚置高
    *(unsigned int *)0x4001080C=0x1<<1;   // 将GPIOA的Pin_1置为高电平
    *(unsigned int *)0x40010C0C=0x0<<1;   // 将GPIOB的Pin_1置为低电平 
	*(unsigned int *)0x4001100C=0x1<<14;  // 将GPIOC的Pin_14置为高电平
    Delay_ms(1000); // 延时1秒
    
    // 蓝灯亮:GPIOC的Pin_14置低,其他两个引脚置高
    *(unsigned int *)0x4001080C=0x1<<1;   // 将GPIOA的Pin_1置为高电平
    *(unsigned int *)0x40010C0C=0x1<<1;   // 将GPIOB的Pin_1置为高电平
	*(unsigned int *)0x4001100C=0x0<<14;  // 将GPIOC的Pin_14置为低电平
    Delay_ms(1000); // 延时1秒
}

(2)
寄存器流水灯实现
代码实现
整体代码如下:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{
    // 打开GPIO A端口的时钟
    *(unsigned int *)0x40021018 |= (1 << 2);
    // 打开GPIO B端口的时钟
    *(unsigned int *)0x40021018 |= (1 << 3);
    // 打开GPIO C端口的时钟
    *(unsigned int *)0x40021018 |= (1 << 4);

	*(unsigned int *)0x40010800 &= 0xFFFFFFF0; //设置位 清零
	*(unsigned int *)0x40010800 |= 0x00000030; //PA1推挽输出,速度为50MHz
		
	*(unsigned int *)0x40010C00 &= 0xFFFFFF0F; //设置位清零
	*(unsigned int *)0x40010C00 |= 0x00000030; //PB1推挽输出,速度为50MHz
		
	*(unsigned int *)0x40011004 &= 0xFF0FFFFF; //设置位清零
	*(unsigned int *)0x40011004 |= 0x00300000; //PC14推挽输出,速度为50MHz 	
		
	
    *(unsigned int *)0x4001080C=0x1<<1;	 		 // 将GPIOA的Pin_1置为高电平
    *(unsigned int *)0x40010C0C=0x1<<1;	  		 // 将GPIOB的Pin_1置为高电平
	*((unsigned int *)0x4001100C) &= ~(1 << 14);	//只需要清一位,不能直接像上面一样直接与,因为十六进制中一下改变4位
	*(unsigned int *)0x4001100C=0x1<<14;		 // 将GPIOC的Pin_14置为高电平
	
    while (1)
    {

        // 红灯亮:GPIOA的Pin_1置低,其他两个引脚置高
        *(unsigned int *)0x4001080C=0x0<<1;	  // 将GPIOA的Pin_1置为低电平     
		*(unsigned int *)0x40010C0C=0x1<<1;	  // 将GPIOB的Pin_1置为高电平     
		*(unsigned int *)0x4001100C=0x1<<14;  // 将GPIOC的Pin_14置为高电平
        Delay_ms(1000); // 延时1秒
        
        // 绿灯亮:GPIOB的Pin_1置低,其他两个引脚置高
        *(unsigned int *)0x4001080C=0x1<<1;   // 将GPIOA的Pin_1置为高电平
        *(unsigned int *)0x40010C0C=0x0<<1;   // 将GPIOB的Pin_1置为低电平 
		*(unsigned int *)0x4001100C=0x1<<14;  // 将GPIOC的Pin_14置为高电平
        Delay_ms(1000); // 延时1秒
        
        // 蓝灯亮:GPIOC的Pin_14置低,其他两个引脚置高
        *(unsigned int *)0x4001080C=0x1<<1;   // 将GPIOA的Pin_1置为高电平
        *(unsigned int *)0x40010C0C=0x1<<1;   // 将GPIOB的Pin_1置为高电平
		*(unsigned int *)0x4001100C=0x0<<14;  // 将GPIOC的Pin_14置为低电平
        Delay_ms(1000); // 延时1秒
    }
}

实验结果如下
在这里插入图片描述

(3)点亮pc13
代码实现
将芯片自带PC13中的LED灯加入流水灯中,红灯对应 PA1 引脚、绿灯对应 PB1 引脚,红绿灯和PC13的LED灯轮流闪烁,间隔时长1秒。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{
    // 打开GPIO A端口的时钟
    *(unsigned int *)0x40021018 |= (1 << 2);
    // 打开GPIO B端口的时钟
    *(unsigned int *)0x40021018 |= (1 << 3);
    // 打开GPIO C端口的时钟
    *(unsigned int *)0x40021018 |= (1 << 4);

	*(unsigned int *)0x40010800 &= 0xFFFFFFF0; //设置位 清零
	*(unsigned int *)0x40010800 |= 0x00000030; //PA1推挽输出,速度为50MHz
		
	*(unsigned int *)0x40010C00 &= 0xFFFFFF0F; //设置位清零
	*(unsigned int *)0x40010C00 |= 0x00000030; //PB1推挽输出,速度为50MHz
		
	*(unsigned int *)0x40011004 &= 0xFF0FFFFF; //设置位清零
	*(unsigned int *)0x40011004 |= 0x00300000; //PC推挽输出,速度为50MHz 	
		
	
    *(unsigned int *)0x4001080C=0x1<<1;	 		 // 将GPIOA的Pin_1置为高电平
    *(unsigned int *)0x40010C0C=0x1<<1;	  		 // 将GPIOB的Pin_1置为高电平
	*(unsigned int *)0x4001100C=0x1<<13;		 // 将GPIOC的Pin_14置为高电平
    while (1)
    {		
        // 红灯亮:GPIOA的Pin_1置低,其他引脚置高
        *(unsigned int *)0x4001080C=0x0<<1;	  // 将GPIOA的Pin_1置为低电平     
		*(unsigned int *)0x40010C0C=0x1<<1;	  // 将GPIOB的Pin_1置为高电平     
		//*(unsigned int *)0x4001100C=0x1<<14;  // 将GPIOC的Pin_14置为高电平
        Delay_ms(1000); // 延时1秒
        
        // 绿灯亮:GPIOB的Pin_1置低,其他引脚置高
        *(unsigned int *)0x4001080C=0x1<<1;   // 将GPIOA的Pin_1置为高电平
        *(unsigned int *)0x40010C0C=0x0<<1;   // 将GPIOB的Pin_1置为低电平 
		//*(unsigned int *)0x4001100C=0x1<<14;  // 将GPIOC的Pin_14置为高电平		
        Delay_ms(1000); // 延时1秒
        
        // PC13灯亮:GPIOC的Pin_13置低
        *(unsigned int *)0x4001080C=0x1<<1;   // 将GPIOA的Pin_1置为高电平
        *(unsigned int *)0x40010C0C=0x1<<1;   // 将GPIOB的Pin_1置为高电平
		*((unsigned int *)0x4001100C) &= ~(1 << 13);	//只需要清一位,不能直接像上面一样直接与,因为十六进制中一下改变4位
		*(unsigned int *)0x4001100C=0x0<<13;  // 将GPIOC的Pin_13置为高电平		
		Delay_ms(1000); // 延时1秒
		*((unsigned int *)0x4001100C) &= ~(1 << 13);	//只需要清一位,不能直接像上面一样直接与,因为十六进制中一下改变4位
		*(unsigned int *)0x4001100C=0x1<<13;  // 将GPIOC的Pin_13置为高电平	
    }
}

在这里插入图片描述

实验任务2:在实验1的基础上,改用标准外设库方式使用某个端口GPIOx端口管脚控制几个LED灯,轮流闪烁,间隔时长1秒。
1)写出工程项目创建文件夹、添加STM32标准外设库文件(.c,.h)的详细过程;
2)LED灯的亮/灭周期是通过软件循环延时完成的,其准确周期大致是多少呢?
在没有示波器条件下,可以使用Keil的软件仿真逻辑分析仪功能观察管脚的时序波形,更方便动态跟踪调试和定位代码故障点。 请用此功能观察GPIO端口的输出波形,并分析时序状态正确与否、高低电平转换周期(LED闪烁周期)实际为多少。

(1)先前嵌入式基础中已经详细描述过程
标准库流水灯实现
代码实现
设计思路与寄存器一致,使用库函数进行实现,GPIOA引脚端口0-7管脚控制LED灯,轮流闪烁,间隔时长1秒。
设计代码如下:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main(void)
{
	 // 使能GPIOA
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	 // 定义GPIO初始化结构体
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;// 设置GPIO模式为推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;// 设置要初始化的GPIO引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 设置GPIO速度为50MHz
	
	 // 根据上面的配置初始化GPIOA
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	while (1)
	{
       for (int i = 0; i < 8; i++)
       {
            // 将相应引脚设置为高电平以熄灭LED
            GPIO_SetBits(GPIOA,GPIO_Pin_All);
            // 将相应引脚设置为低电平以点亮LED
            GPIO_ResetBits(GPIOA, 1 << i);
            Delay_ms(1000);      
        }
	}
}

实验结果如下在这里插入图片描述

(2)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由图可以计算出低电平持续时间为0.667s
高电平持续时间为3.335s
我设置的周期为6s,但是仿真得到的低电平持续时间0.667s,高电平持续时间为3.335s.,周期为为4s上下,有误差

实验总结
通过本次围绕 STM32F103C8T6 芯片开展的实验,完成了两项具有递进性的实验任务,深入探索了利用该芯片控制 LED 灯实现轮流闪烁效果的不同方法以及相关开发流程与调试技巧。
在实验任务 1 中,采用 C 语言寄存器方式对 GPIO 端口进行操作,从最基础的层面深入了解了芯片的硬件架构与寄存器配置逻辑。掌握了如何依据数据手册查找各 GPIO 端口寄存器的地址、理解每一位在配置引脚工作模式、输出速度等方面所代表的含义,并通过精准的位运算来初始化相应引脚,使其能够按照预期输出高低电平以控制 LED 灯的亮灭。尤其是将出厂自带的位于 PC13 处的 LED 灯融入流水灯逻辑的过程,进一步强化了对特定引脚配置及整体控制流程的熟悉程度。这一过程让我深刻认识到寄存器操作虽然较为底层且繁琐,但却能最大程度地实现对硬件的精准控制,同时也意识到任何细微的位操作失误都可能导致功能无法正常实现,需要足够的细心和耐心。
实验任务 2 则在此基础上,引入了 STM32 标准外设库的使用方式,体验到了库函数带来的便利性和代码的可读性、可维护性的提升。在工程项目创建、添加库文件以及配置开发环境的过程中,明白了合理的项目文件架构对于大型工程开发的重要性,清晰的文件夹划分和正确的文件引入路径设置能够确保后续编程及编译工作顺利进行。而利用 Keil 的软件仿真逻辑分析仪功能观察 GPIO 端口输出波形这一环节,更是为调试代码提供了有力的手段,让我学会在没有硬件示波器时,如何通过软件工具去分析代码执行的时序状态、验证 LED 灯亮灭周期是否符合预期,及时发现并定位代码中诸如延时不准确、GPIO 配置冲突等潜在问题。
心得与体会
硬件与软件结合的重要性:这次实验让我切实体会到嵌入式开发中硬件和软件紧密结合的特点。仅仅掌握编程语言是远远不够的,还需要深入理解芯片的硬件结构、引脚功能以及寄存器配置等底层知识,才能编写出有效的控制代码实现期望的硬件行为。例如,在配置 GPIO 引脚为推挽输出模式时,只有明白这种模式下芯片内部电路的工作原理,才能理解为何要设置相应寄存器的某些位为特定的值,进而在软件代码中正确操作。
编程思维的拓展:从寄存器方式到标准外设库方式的转变,使我认识到在嵌入式编程领域,根据不同的应用场景和需求可以选择不同的编程方法。寄存器方式更适合对性能和硬件资源进行极致优化以及特定的底层调试需求;而标准外设库方式则能提高开发效率,降低代码编写的复杂性,尤其适用于快速实现功能原型和大型项目的迭代开发。这让我学会了根据具体项目要求灵活切换编程思维,选择最合适的工具和方法来解决问题。
总的来说,本次 STM32 实验是一次非常有意义且收获满满的学习经历,不仅让我在专业知识和技能方面得到了锻炼和提升,更培养了我的问题解决能力、严谨的编程态度以及对嵌入式开发领域的浓厚兴趣,为我今后进一步深入学习和从事相关工作打下了坚实的基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值