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的对应关系:
STM32 | STLINK |
---|---|
3.3V | 3.3V |
SWIO | SWDIO |
SWCLK | SWCLK |
GND | GND |
安装完后的样图(选择的 PA0,PB0和PC13引脚
):
三、Keil MDK创建项目
1.新建项目
2.新建文件夹
建一个文件夹(这里我取名为入门点亮LED灯
)
新建工程名Project
3.配置
选择芯片STM32F103C8
点击ok,进入主界面,弹出的窗口叉掉
进入项目文件夹,新建三个文件夹Start
,Library
和User
去官网找到固件库文件或者去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或者不懂的连线都可以在网上搜到别人大佬的细心解答。