蓝桥杯嵌入式_STM32学习_点亮LED_详解

每一款板子的入门都是从LED开始。
接下来,我们就开始学习点亮STM32F103RBT6的LED吧。

原理

首先,打开我们的电路原理图,找到LED——请添加图片描述
这一看,诶,不是很熟悉嘛。
共阳的八个LED通过锁存器连接到H D0、H D1、H D2、H D3、H D4、H D5、H D6、H D7.

再找下H Dx管脚连到哪里——
请添加图片描述
根据上图,我们可以找到,
H D0 ——H D7分别连到M PC8 —— M PC15.
N LE连接到M PD2.

那么,要使得LED亮,则首先LE为高电平,然后H Dx为低电平。即M PD2为高电平,M PCx (x   ∈   \ \in\,   [8,15])为低电平。

代码

以下代码均已实现功能。
//led.h寄存器版

#ifndef __LED_H
#define __LED_H
#include<stm32f10x.h>

//接口宏定义
#define led1 GPIO_Pin_8
#define led2 GPIO_Pin_9
#define led3 GPIO_Pin_10 
#define led4 GPIO_Pin_11 
#define led5 GPIO_Pin_12 
#define led6 GPIO_Pin_13 
#define led7 GPIO_Pin_14 
#define led8 GPIO_Pin_15 
#define ledall GPIO_Pin_All

//函数声明
void LED_Init(void);//LED初始化
void LED_show(unsigned int led,unsigned char state);//LED操作函数

#endif

寄存器方式

地址操作

//LED接口初始化
void led_Init()
{
	*( unsigned int * )0x40021018 |= 3<<4;//开启GPIO C/D 时钟
	
	*(unsigned int *)0x40011400 |= 3<<8;//配置GPIO D
	*(unsigned int *)0x40011400 &= ~(3<<10);
	
	*(unsigned int *)0x40011004 = 0x33333333;//配置GPIO C
	
	*(unsigned int *)0x4001100C |= 0xFF00;//熄灭所有LED
	*(unsigned int *)0x4001140C |= 1<<2;//打开锁存器/使LE为高电平
	*(unsigned int *)0x4001140C &= ~(1<<2);//关闭锁存器
}

//LED显示
//入口参数:led--对应led灯 state--0 亮;1 灭;
void led_disp(unsigned int led,unsigned char state)
{
	if(state==0)//如果state==0
	{
		*(unsigned int *)0x4001140C |= 1<<2;//打开锁存器
		*(unsigned int *)0x4001100C &= ~(1 << (led+8));//led输出低电平
		*(unsigned int *)0x4001140C &= ~(1<<2);//关闭锁存器
	}
	else if(state==1)//如果state==1
	{
		*(unsigned int *)0x4001140C |= 1<<2;//打开锁存器
		*(unsigned int *)0x4001100C |= 1 << (led+8);//led输出高电平
		*(unsigned int *)0x4001140C &= ~(1<<2);//关闭锁存器
	}
}

第一行:*(unsigned int *)0x40021018 |= 3<<4;

STM32与51单片机最大的不同,就是他的每一个外设都要有一个时钟,时钟相当于心脏。
所以,第一步是开启LED的时钟
请添加图片描述

打开STM32参考手册,
点开2.3存储器映像(英文是3.3 Memory map),往下翻,找到图中信息页(或者直接看第28页)。
在这里可以看到,RCC端口是挂载在AHB总线上,GPIO在APB2总线上。请添加图片描述
请添加图片描述
(第一个值为偏移地址,换算公式是:当前寄存器地址为基地址加上偏移地址;
第二个值为复位结果,即默认全为低电平)
请添加图片描述

请添加图片描述
(由于中文版不太清楚,我就主要截取英文版的,中文版的截一部分,大家可以自行在原档案中翻阅,70-71页,中文版,第七章也有描述,但内容是一致的,英文版在109-110页)
所以我们就找到APB2外设时钟使能寄存器的设置说明页。
可以看到,若要使得GPIO C、GPIO D使能,则要让位4和位5置一。
而在这之前,我们需要找到RCC的基地址。
请添加图片描述
我们之前知道RCC在AHB总线上,所以只要在AHB的基地址表格找就行了。
所以RCC_APB2ENR的地址为基地址加偏移地址,为0x4002 1018;
而电脑识别不出这个是一个地址,所以要强制类型转换——
——(unsigned int *)0x40021018;
改变的是寄存器内部的值,所以要取地址——
即为*(unsigned int *)0x40021018 |= 3<<4;

第二、三、四行:
*(unsigned int *)0x40011400 |= 3<<8;
*(unsigned int *)0x40011400 &= ~(3<<10);
*(unsigned int *)0x40011004 = 0x33333333;

GPIO,全称是General-purpose input/output,通用输入输出接口,他输入输出都可以。而控制LED,我们需要设置他为输出模式
请添加图片描述请添加图片描述

请添加图片描述
请添加图片描述
请添加图片描述
(113-114页,中文版;165-166,英文版)
每位IO口分别有四位控制,两位是配置位,两位是模式位,STM32是32位的单片机,每个GPIO_X又有16个端口,所以分为了高低两个寄存器。
端口配置高寄存器是设置8-15的,低寄存器是配置0-7的。
我们要配置的是 PC8-PC15,以及PD2。
所以GPIO D使用端口配置低寄存器,GPIO C使用高寄存器。

我们再来看细节部分——
端口配置位的输出条件是,端口模式位大于0。
这里我们就设置速度为50MHz,为11.

端口配置位的输出模式又有几个模式,我们在这里选择推挽输出(push-pull可输出高电平也可输出低电平),具体每个模式的作用我后面会专门写一个博客,到时候挂在这里(空位)。

再找到APB2的基地址表格,找到GPIO C和GPIO D的基地址——请添加图片描述
所以设置为——

*(unsigned int *)0x40011400 |= 3<<8;//GPIO D 令模式为为11
*(unsigned int *)0x40011400 &= ~(3<<10);//GPIO D 令配置位为00
*(unsigned int *)0x40011004 = 0x33333333;//GPIO C
第五六七行:
*(unsigned int *)0x4001100C |= 0xFF00;
*(unsigned int *)0x4001140C |= 1<<2;
*(unsigned int *)0x4001140C &= ~(1<<2);

配置完输出模式,为了关闭LED,我们就要让LED控制管脚输出高电平,让LE先输出高电平,再输出低电平。
请添加图片描述请添加图片描述

(115页,中文版;167页,英文版)
我们找到端口输出数据寄存器。
我们可以看到,在这里我们只需要把我们需要的数据在此寄存器中清零或置一就好啦。

*(unsigned int *)0x4001100C |= 0xFF00;//1111 1111 0000 0000,使得控制LED的管脚都输出高电平
*(unsigned int *)0x4001140C |= 1<<2;//0000 0000 0000 0100,打开锁存器
*(unsigned int *)0x4001140C &= ~(1<<2);//0000 0000 0000 0000,关闭锁存器

看懂了初始化代码,那显示代码应该也很好理解了,此处便不再赘述。

结构体方式

我们发现,每个寄存器的内存都是按照固定顺序以及固定内存排列,都可以由基地址加上其偏移地址调用,这跟我们之前学过的结构体数据的调用极其相似。
所以,我们也可以用结构体的方式来实现该代码。
请添加图片描述
由基地址表格我们可以发现,基地址的最开头是0x40000000,所以我们从这里开始定义.

//stm32f10x.h(参考资料来源:野火)

#ifndef __STM32F10X_H
#define __STM32F10X_H

// 用来存放STM32寄存器映射的代码

// 外设  perirhral

#define  PERIPH_BASE               ((unsigned int)0x40000000)
#define  APB1PERIPH_BASE           PERIPH_BASE
#define  APB2PERIPH_BASE          (PERIPH_BASE + 0x10000)
#define  AHBPERIPH_BASE           (PERIPH_BASE + 0x20000)


#define  RCC_BASE                (AHBPERIPH_BASE + 0x1000)
#define  GPIOC_BASE              (APB2PERIPH_BASE + 0x1000)
#define  GPIOD_BASE              (APB2PERIPH_BASE + 0x1400)



#define  RCC_APB2ENR            *(unsigned int*)(RCC_BASE + 0x18)

	
typedef unsigned int      uint32_t;
typedef unsigned short    uint16_t;

typedef struct
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;//注意,这里一定要按照顺序,从低位到高位写,地址数是从上往下增加的。


typedef struct
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;


#define GPIOB   ((GPIO_TypeDef*)GPIOB_BASE)//注意,是指针,指针的0x才是地址
#define RCC     ((RCC_TypeDef*)RCC_BASE)

#endif /* __STM32F10X_H */

//stm32f10x_gpio.c

#include "stm32f10x.h"

//LED接口初始化
void led_Init()
{
	RCC->APB2ENR |= 3<<4;//开启GPIO C/D 时钟
	
	GPIOD->CRL |= 3<<8;//配置GPIO D
	GPIOD->CRL &= ~(3<<10);
	
	GPIOC->CRH = 0x33333333;//配置GPIO C
	
	GPIOC->ODR |= 0xFF00;//打开锁存器/使LE为高电平
	GPIOD->ODR |= 1<<2;//熄灭所有LED
	GPIOD->ODR &= ~(1<<2);//关闭锁存器
}

//LED显示
//入口参数:led--对应led灯 state--0 亮;1 灭;
void led_disp(unsigned int led,unsigned char state)
{
	if(state==0)//如果state==0
	{
		GPIOD->ODR |= 1<<2;//打开锁存器
		GPIOC->ODR &= ~(1 << (led+8));//led输出低电平
		GPIOD->ODR &= ~(1<<2);//关闭锁存器
	}
	else if(state==1)//如果state==1
	{
		GPIOD->ODR |= 1<<2;//打开锁存器
		GPIOC->ODR |= 1 << (led+8);//led输出高电平
		GPIOD->ODR &= ~(1<<2);//关闭锁存器
	}
}

固件库方式

终于到固件库了。
固件库也就是用结构体的方式,并将其封装起来,我们只需要疯狂调用就行了,非常方便。

先看代码——

#include <stm32f10x.h>
void LED_Init(void)
{
	//GPIO结构体
	GPIO_InitTypeDef GPIO_InitStruct;
	
	//允许时钟GPIOC与GPIOD
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
	
	//PC8到PC15结构体的配置
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9|\	
                           GPIO_Pin_10|GPIO_Pin_11|\
                           GPIO_Pin_12|GPIO_Pin_13|\
                           GPIO_Pin_14|GPIO_Pin_15;//引脚                           
    //  \ 续行符,后面不可以加任何东西
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//速度
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//输出模式-推挽输出
	GPIO_Init(GPIOC,&GPIO_InitStruct);//初始化GPIOC
	
	//PD2结构体的配置
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_2;//引脚
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//速度
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//输出模式-推挽输出
	GPIO_Init(GPIOD,&GPIO_InitStruct);//初始化GPIOC
	
	GPIO_SetBits(GPIOD,GPIO_Pin_2);//PD2输出高电平
	GPIO_SetBits(GPIOC,ledall);//LED都灭
	GPIO_ResetBits(GPIOD,GPIO_Pin_2);//PD2输出低电平
}
//LED显示
//入口参数:led--对应led灯 state--0 亮;1 灭;
void LED_show(unsigned int led,unsigned char state)
{
	if(state==0)//如果state==0
	{
		GPIO_SetBits(GPIOD,GPIO_Pin_2);//PD2输出高电平
		GPIO_ResetBits(GPIOC,led);//led输出低电平
		GPIO_ResetBits(GPIOD,GPIO_Pin_2);//PD输出低电平
	}
	else if(state==1)//如果state==1
	{
		GPIO_SetBits(GPIOD,GPIO_Pin_2);//PD2输出高电平
		GPIO_SetBits(GPIOC,led);//led输出高电平
		GPIO_ResetBits(GPIOD,GPIO_Pin_2);//PD输出低电平
	}
}

详解:

RCC

首先,我们要使能时钟,打开stm32f10x_rcc.h,拉到最下面,RCC的所有函数声明都在那里(646-709行,很明显的)——请添加图片描述
找到RCC_APB2PeriphClockCmd
APB2:APB2总线
Periph:perirhral 外设
Clock:时钟
Cmd:命令

所以我们就直接调用这个函数。
那么,参数怎么写呢?
先理解一下参数列表——
(uint32_t RCC_APB2Periph, FunctionalState NewState)
RCC_APB2Periph:APB2上的外设
FunctionalState:功能状态
NewState:新的状态值

选中RCC_APB2PeriphClockCmd
右键选择“Go To Difinition Of RCC_APB2PeriphClockCmd”(或选中后直接按F12)
/*
(注:一定要先编译,不然跳不过去)
(还有要勾上 Browse Information)请添加图片描述
*/
请添加图片描述
到达函数定义,往上翻,可以看到这段文字——
brief:简介
param:参数
arg:Autodesk Resource Guide 资源向导,即参数列表
retval:return value 返回值

我们就看param(参数),
第一个说"This parameter can be any combination of the following values:“(这个参数可以是下列值的任意组合),那么我们就知道,我们可以填入的参数就在这里。
我们填RCC_APB2Periph_GPIOCRCC_APB2Periph_GPIOD
第二个说"This parameter can be:”(该参数包括),那么我们就知道,可以填"ENABLE or DISABLE",当然,我们填ENABLE

GPIO

同样的,我们打开stm32f10x_gpio.h,并翻到最下面。请添加图片描述
找到GPIO_Init

接下来分析参数列表——
(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
GPIO_TypeDef* GPIOx:GPIO_TypeDef类型的指针变量
GPIO_InitTypeDef* GPIO_InitStruct:GPIO_InitTypeDef类型的指针变量

同样,找到GPIO_Init的定义——
请添加图片描述
第一个说:“where x can be (A…G) to select the GPIO peripheral.”(其中x可以是(A…G)来选择GPIO外设。)
我们随便在一个文件中输入GPIOB,然后找到他的定义——
请添加图片描述
我们可以看到,GPIOB已经是指针类型了,所以我们写参数可以直接写GPIOB

第二个,说"GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that contains the configuration information for the specified GPIO peripheral"(GPIO_InitStruct:指向GPIO_InitTypeDef结构体的指针,包含指定GPIO外设的配置信息)

但是我们打不开GPIO_InitStruct的定义,说明我们需要自己定义一个GPIO_InitStruct。

接下来我们打开GPIO_InitTypeDef的定义,看看里面有什么——请添加图片描述
ref:reference,参考

是一个结构体,包含三个变量——
GPIO_Pin:“Specifies the GPIO pins to be configured.”(指定要配置的GPIO引脚。)
GPIO_Speed:“Specifies the speed for the selected pins.”(“指定所选引脚的速度。”)
GPIO_Mode:“Specifies the operating mode for the selected pins.”(”指定所选引脚的操作模式。”)

“This parameter can be any value of @ref”(“该参数可以是@ref的任意值”),所以,可以填的参数就在@ref后的定义里面。
选中@ref后的单词,找到其定义就能找到了。请添加图片描述
enum:枚举类型,说明只能填枚举的变量。
所以直接在枚举类型里选就行了。

或许有人会发现,GPIO_pins_define好像找不到定义。
别急,选中,并按下Ctrl+F,Find Next——请添加图片描述
请添加图片描述
这不就找到了吗?

然后就选择这些变量赋值到结构体中,再调用初始化函数就行了。

GPIO_SetBitsGPIO_ResetBits也是一样的,GPIO的函数声明都在stm32f10x_gpio.h的最下面,根据函数的解释写就行了。

就酱。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Moqim Flourite.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值