基于寄存器&标准外设库的LED流水灯及其keil仿真

STM32F103C8T6实现LED点亮
文章较长,可能有错误,请大家多多指正。
有什么疑问请在评论区留言,源码我之后会放在评论区


实验一:以 STM32最小系统核心板(STM32F103C8T6)+3只LED 搭建电路,使用GPIOA/GPIOB/GPIOC端口控制LED灯,轮流闪烁,间隔时长1秒(标准外设库方式)。并使用Keil的软件仿真逻辑分析仪功能观察管脚的时序波形。

一、使用工具

1.LED发光二极管
2.面包板
3.杜邦线
4.STM32F103C8T6单片机
5.STLINK
6.跳线

二、安装

我这里安装的时候,使用了三个led灯,分别接入了GPIOA的A4,A5,A6三个引脚上。

STM32和STILINK的对应关系:

STM32STLINK
3.3V3.3V
SWIOSWDIO
SWCLKSWCLK
GNDGND

安装完后的样图(选择的 PA0,PB0和PC13引脚):

在这里插入图片描述

三、Keil MDK创建项目

1.新建项目

2.新建文件夹

建一个文件夹(这里我取名为入门点亮LED灯)
在这里插入图片描述

新建工程名Project
在这里插入图片描述

3.配置

选择芯片STM32F103C8
在这里插入图片描述

点击ok,进入主界面,弹出的窗口叉掉
在这里插入图片描述

进入项目文件夹,新建三个文件夹StartLibraryUser
在这里插入图片描述

去官网找到固件库文件或者去csdn搜索一下,有教程,复制粘贴到文件夹下
Start
在这里插入图片描述

Library

在这里插入图片描述
User
在这里插入图片描述

回到keil,点击三个箱子的工程文件管理按钮
在这里插入图片描述

把默认的组叉掉
在这里插入图片描述

点按钮新建三个组,就是上面的文件夹名字
在这里插入图片描述
(1)Start
选中Start,在右边点击Add Files添加文件,打开Start文件夹,文件类型选中所有文件首先添加后缀为md的启动文件
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

然后按住Ctrl,将其他的.c.h文件都选中,添加进去
在这里插入图片描述

最后如图
在这里插入图片描述

(2)Library
同样选择Library,打开Library文件夹,文件类型选中所有文件,全选,添加
在这里插入图片描述

(2)User
Library一样,全选,添加
在这里插入图片描述

最后点击ok,这样工程里的组和文件都添加好了

点击魔术棒按钮,打开工程选项,选择C/C++
在这里插入图片描述
在这里插入图片描述

include Paths栏,点击后面的三个点,把我们自己创建的文件夹路径都添加进来,然后ok
在这里插入图片描述

Define栏上,写上USE_STDPERIPH_DRIVER
在这里插入图片描述

output中勾上Create HEX File,点击ok,这样,项目工程就配置好了
在这里插入图片描述

4.测试代码

在main.c中写代码,先进行编译调试
调试代码如下(示例):

#include "stm32f10x.h"                  // Device header
	
int main(void)
{

	while(1)
	{
		
	}
}

编译测试
在这里插入图片描述

没有错误,说明可以正常运行和写代码
在这里插入图片描述

四、代码撰写

以上讲解了接触了构建项目的操作,在以上现有文件的基础上,我们来进行此次流水灯代码的撰写。

1.增添文件夹HardWare

直接复制上一个基础文件,更名为“LED流水灯”,然后再根目录下多添加HardWare文件夹
在这里插入图片描述

进入keil,将HardWare文件夹添加到工程
在这里插入图片描述
在这里插入图片描述

右击增添新的.c文件和.h文件,分别是LED.c和LED.h
在这里插入图片描述

2.编写代码

LED.c代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"//使用延时函数

//初始化led
void LED_Init(void)
{
	//开启LED相关的GPIO外设时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	

	GPIO_InitTypeDef GPIO_InitStructure;	//定义一个GPIO_InitTypeDef类型的结构体	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//设置引脚模式为通用推挽输出
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;//选择要控制的GPIO引脚
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//设置引脚速率为50MHz
	
	GPIO_Init(GPIOA,&GPIO_InitStructure);//调用库函数,初始化GPIO
	GPIO_Init(GPIOB,&GPIO_InitStructure);//调用库函数,初始化GPIO
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;//选择要控制的GPIO引脚
	GPIO_Init(GPIOC,&GPIO_InitStructure);//调用库函数,初始化GPIO
	
	
	//默认是低电平点亮的,因此先不点亮,将所有端口设置为1
	GPIO_SetBits(GPIOA, GPIO_Pin_0);
	GPIO_SetBits(GPIOB, GPIO_Pin_0);
	GPIO_SetBits(GPIOC, GPIO_Pin_13);
	
}

//流水灯
void LED_WaterLamp(void)
{
	//取反
	GPIO_Write(GPIOA,~0x0010);//0000 0000 0001 0000
	Delay_ms(500);
	GPIO_Write(GPIOA,~0x0020);//0000 0000 0010 0000
	Delay_ms(500);
	GPIO_Write(GPIOA,~0x0040);//0000 0000 0100 0000
	Delay_ms(500);
	GPIO_Write(GPIOA,~0x0080);//0000 0000 1000 0000
	Delay_ms(500);

}

void LED_WaterLamp1(void)
{
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
	GPIO_SetBits(GPIOC,GPIO_Pin_13);
	Delay_ms(1000);
	
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);
	GPIO_SetBits(GPIOA,GPIO_Pin_0);
	GPIO_SetBits(GPIOC,GPIO_Pin_13);
	Delay_ms(1000);
	
	GPIO_ResetBits(GPIOC,GPIO_Pin_13);
	GPIO_SetBits(GPIOA,GPIO_Pin_0);
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
	Delay_ms(1000);

}



//PC_13
void PC_13(void)
{
	RCC->APB2ENR=0x00000010;
	GPIOC->CRH=0x00300000;
	GPIOC->ODR=0x00002000;
	Delay_ms(500);
	GPIOC->ODR=0x00000000;
}

//A0
void LED0_Turn(void)
{
	if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0)//亮
	{
		GPIO_SetBits(GPIOA,GPIO_Pin_0);//灭
	}
	else
	{//灭
		GPIO_ResetBits(GPIOA,GPIO_Pin_0);//亮
	}
}

//A1
void LED1_Turn(void)
{
	if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)==0)//亮
	{
		GPIO_SetBits(GPIOA,GPIO_Pin_1);//灭
	}
	else
	{//灭
		GPIO_ResetBits(GPIOA,GPIO_Pin_1);//亮
	}
}

LED.h代码:

#ifndef __LED_H
#define __LED_H

//流水灯
void LED_Init(void);
void LED_WaterLamp(void);
void LED_WaterLamp1(void);
void PC_13(void);

//按键控制
void LED1_Turn(void);
void LED0_Turn(void);

#endif


main.c代码:

#include "stm32f10x.h"                  // Device header

#include "Delay.h"//使用延时函数
#include "LED.h"
#include "Key.h"
uint8_t a=0;
void test()
{
		a=Key_GetNum();
	
		if(a==1)//B1按键
		{
			LED0_Turn();//A0 LED
		}
		if(a==2)//B11按键
		{
			LED1_Turn();//A1 LED
		}

}

int main(void)
{
	
	LED_Init();	
	while(1)
	{

		LED_WaterLamp1();
	};
	
}

3.运行结果

LED灯成功点亮!

三只流水灯

五、keil仿真

1.keil配置

在这里插入图片描述
在这里插入图片描述

将复制的内容填入下面的Dlalog DLL 和Parameter
在这里插入图片描述

2.开始仿真

回到主界面,点击debug进入调试界面
在这里插入图片描述

点击仿真调试
在这里插入图片描述

点击setup

在这里插入图片描述

输入GPIOA_ODR.0,回车,如果前面步骤都正确的话,就能够正常添加引脚,然后将其他两个引脚也加入进来,同时Display Type选择 Bit

在这里插入图片描述

最后点击全速运行,就能够实时监听引脚的电平变化

在这里插入图片描述

最后监听的流水灯引脚电平变化如下图:
在这里插入图片描述

3.结果分析

观察GPIO端口的输出波形,并分析时序状态正确与否、高低电平转换周期(LED闪烁周期)实际为多少。
发现时延为:1.336s-0.667001s=0.668999s,与设置的时延1s有较大误差
在这里插入图片描述

实验二:用C语言寄存器方式编程实现,写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数

一、创建项目(点亮PA0 PB0 PC13)

新建项目的过程如上了,这里我直接创建了,然后新建一个main.c文件并保存(配置自己按需求调试)
在这里插入图片描述

将其加载进来
在这里插入图片描述

然后是添加启动文件,由于我们的芯片是中容量,因此选择hd.s的
在这里插入图片描述

粘贴到项目下,同理把启动文件加载到项目下:
在这里插入图片描述

二、代码编写
main.c中的代码

int main()
{


}

void SystemInit(void)
{

}

二、流水灯实验过程分析

1.思路

(1)打开对应的时钟
(2)配置输出,确定输出模式(推挽输出,50MHz)
(3)输出低电平(低电平点亮,高电平熄灭)

2.打开 PA0 PB0 PC13 的时钟

RCC外设的起始地址为 0x4002 1000
在这里插入图片描述

APB2外设的偏移地址为 0x18
在这里插入图片描述

所以它的实际地址为 0x4002 1000+0x18=0x4002 1018

转换成单片机能识别的地址:(unsigned int *) 0x4002 1018
取出该地址:*(unsigned int *) 0x4002 1018

由于我们需要使能GPIOA的时钟,查手册1是开启时钟,
因此 *(unsigned int *) 0x4002 1018 |=(1<<2) 在这里插入图片描述
在这里插入图片描述

同理:使能GPIOB的时钟,*(unsigned int *) 0x4002 1018 |=(1<<3);
使能GPIOC的时钟,*(unsigned int *) 0x4002 1018 |=(1<<4);

3.配置输出,确定输出模式

我们需要改变的值是GPIOA的0号引脚,GPIOB的0号引脚以及GPIOC的13号引脚
在这里插入图片描述
在这里插入图片描述

我们的想法是,先将对应的控制位清零(其他位不变),再根据需要设置。

GIOA的基地址是 0x4001 0800,GIOB的基地址是 0x4001 0C00,GIOC的基地址是 0x4001 1000

在这里插入图片描述

对应的控制位清零:

*(unsigned int *) 0x40010800 &= ~0x0F;//GPIOA PA0 清零

*(unsigned int *) 0x40010C00 &= ~0x0F;//GPIOB PB0 清零

*(unsigned int *) 0x40011004 &= ~(0x0F<<20);//GPIOC PC13 需要先加上偏移地址0x04 清零

根据需要设置(推挽输出,50MHz)(00 11):
在这里插入图片描述

进行设置:


*(unsigned int *) 0x40010800 &= ~0x0F;//GPIOA PA0 清零
*(unsigned int *) 0x40010800 |= 3;//设置推挽输出,50MHz

*(unsigned int *) 0x40010C00 &= ~0x0F;//GPIOB PB0 清零
*(unsigned int *) 0x40010C00 |= 3;//设置推挽输出,50MHz

*(unsigned int *) 0x40011004 &= ~(0x0F<<20);//GPIOC PC13 需要先加上偏移地址0x04 清零
*(unsigned int *) 0x40011004 |= (3<<20);//设置推挽输出,50MHz

4.输出低电平

在这里插入图片描述

在这里插入图片描述

//点亮 地址偏移:0Ch
*(unsigned int *) 0x4001080C &= ~1;//PA0引脚设置为低电平
*(unsigned int *) 0x40010C0C &= ~1;//PB0引脚设置为低电平
*(unsigned int *) 0x40011010 &= ~(1<<13);//PC13引脚设置为低电平
	
//熄灭
*(unsigned int *) 0x4001080C |= 1;//PA0引脚设置为高电平
*(unsigned int *) 0x40010C0C |= 1;//PB0引脚设置为高电平
*(unsigned int *) 0x40011010 |=(1<<13);//PC14引脚设置为高电平

5.成功点亮

三只流水灯

流水灯实验中需要用到delay延时函数,这里为了方便起见,我直接在实验一的文件下新建一个main1.c文件进行编写代码,注意要注释main.c的部分,main1.c代码如下:
main1.c代码:

#include "stm32f10x.h"                  // Device header

#include "Delay.h"//使用延时函数


void LED_Init1(void)
{
	*(unsigned int *) 0x40021018 |=(1<<2);//使能GPIOA的时钟
	*(unsigned int *) 0x40021018 |=(1<<3);//使能GPIOB的时钟
	*(unsigned int *) 0x40021018 |=(1<<4);//使能GPIOC的时钟
	
	
	
	*(unsigned int *) 0x40010800 &= ~0x0F;//GPIOA PA0 清零
	*(unsigned int *) 0x40010800 |= 3;//设置推挽输出,50MHz

	*(unsigned int *) 0x40010C00 &= ~0x0F;//GPIOB PB0 清零
	*(unsigned int *) 0x40010C00 |= 3;//设置推挽输出,50MHz

	*(unsigned int *) 0x40011004 &= ~(0x0F<<20);//GPIOC PC13 需要先加上偏移地址0x04 清零
	*(unsigned int *) 0x40011004 |= (3<<20);//设置推挽输出,50MHz
	
	
	//点亮 地址偏移:0Ch
	*(unsigned int *) 0x4001080C &= ~1;//PA0引脚设置为低电平
	*(unsigned int *) 0x40010C0C &= ~1;//PB0引脚设置为低电平
	*(unsigned int *) 0x40011010 &= ~(1<<13);//PC13引脚设置为低电平
	
	
	
	//熄灭
	*(unsigned int *) 0x4001080C |= 1;//PA0引脚设置为高电平
	*(unsigned int *) 0x40010C0C |= 1;//PB0引脚设置为高电平
	*(unsigned int *) 0x40011010 |=(1<<13);//PC14引脚设置为高电平


	

}

void LED_WaterLamp2(void)
{
	
	*(unsigned int *) 0x40011010 &=~(1<<13);//PC13引脚设置为低电平
	*(unsigned int *) 0x4001080C |= 1;//PA0引脚设置为高电平
	*(unsigned int *) 0x40010C0C |= 1;//PB0引脚设置为高电平
	Delay_ms(500);
	
	*(unsigned int *) 0x4001080C &= ~1;//PA0引脚设置为低电平
	*(unsigned int *) 0x40010C0C |= 1;//PB0引脚设置为高电平
	*(unsigned int *) 0x40011010 |= (1<<13);//PC13引脚设置为高电平
	Delay_ms(500);
	
	*(unsigned int *) 0x40010C0C &= ~1;//PB0引脚设置为低电平
	*(unsigned int *) 0x4001080C |= 1;//PA0引脚设置为高电平
	*(unsigned int *) 0x40011010 |=(1<<13);//PC13引脚设置为高电平
	Delay_ms(500);
	

	
}


int main()
{
	 LED_Init1();
	
	
	while(1)
	{
		LED_WaterLamp2();
	};
}


五、总结

不管是用固件库还是寄存器方式,实现流水灯的过程很不容易,中间不管是软件还是实践,均出现了一些细节方面的错误,让我意识到了,硬件这部分需要我们要有足够的耐心和细心,同时要善于查阅资料,有许多不懂的bug或者不懂的连线都可以在网上搜到别人大佬的细心解答。

参考资料

https://blog.csdn.net/ssj925319/article/details/108919862

STM32入门教程-2023持续更新中

KEIL软件仿真

点亮LED(寄存器操作)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菲菲QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值