前言
在这之前,先说一说我学stm32的历程,已经学习stm32快一年了,由于某些原因,我就有了现在的想法,重头开始回顾stm32的学习,这既回顾我的所学,说不定还能重新学到一些新的东西。stm32的入门注定的一段痛苦的过程,想当初我在入门的时候也是非常痛苦,但是撑过这一阶段,就会发掘到其中的乐趣了。
本人使用的是正点原子的精英板,也是根据正点原子的教学视频和数据手册来学习stm32的,这里强调一下,数据手册非常重要哦,很多问题都可以在数据手册找到对应的结果。同时,学会看数据手册可是非常重要的一个能力,越是深入学习,就越要有自主看数据手册的能力,不仅是板子的数据手册,后面的学习开发也是要看许多数据手册的。
接下来,进入正题
实验目的
通过LED跑马灯实验、蜂鸣器实验和按键实验来初步了解开发板上IO口作为输入和输出使用,同时熟悉IO口的基本配置方法和配置IO口的基本函数的使用。因为这三个实验原理都是差不多的,所以我们只看按键实验,就能举一反三了。
实验内容
按键实验
先来看LED灯在板子上的硬件连接
从图中可以看到KEY1和KEY0分别连接在单片机的PE3和PE4这两个IO口,WK_UP连接在PA0这个IO口上,所以要使用这三个按键,我们首先要配置这三个IO口。这里需要注意的是:KEY0 和 KEY1 是低电平有效的(即按键按下后接的是低电平,IO口检测到低电平),而 WK_UP 是高电平有效的(即按键按下后接的是高电平,IO口检测到的是高电平),并且外部都没有上下拉电阻,所以,需要在内部设置上下拉。
清楚了硬件连接后,下面就是代码部分。
在初始化之前我们需要使能我们用到的IO口对应的时钟。使能IO口的时钟使用的是RCC_APB2PeriphClockCmd()这个函数,第一个参数是时钟。
#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040)
#define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080)
#define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100)
#define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200)
#define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400)
#define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800)
#define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000)
#define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000)
#define RCC_APB2Periph_USART1 ((uint32_t)0x00004000)
#define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000)
#define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000)
#define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000)
#define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000)
#define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000)
#define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000)
#define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000)
第二个参数是时钟的状态 。如果使能时钟就是ENABLE,不使能就是DISABLE。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
RCC_APB2Periph_GPIOE,ENABLE); //使能 PORTA,PORTE 时钟
除此之外还有一个时钟使能函数RCC_AHBPeriphClockCmd(),但是我们这个实验没有用到,使用方法和上面一样,只是这两个函数配置的时钟不同,因为这两个函数的本质就是分别用来配置两个用来配置时钟的寄存器,至于为什么的两个,那当然是因为一个不够用啊,哈哈。
IO口的初始化使用的是GPIO_Init()这个函数,该函数的第一个参数是GPIO的结构体指针变量。选择要初始化的GPIO。
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
根据我们需要用到的引脚,我们用到的是PE和PA,所以我们需要使用两遍这个函数分别对GPIOA和GPIOE进行初始化。
第二个参数就是配置GPIO的结构体指针变量了,这个结构体名称是GPIO_InitTypeDef。要使用这个对GPIO进行配置,我们需要先定义一个这样的结构体。
GPIO_InitTypeDef GPIO_InitStructure;
然后就是对我们定义的这个结构体的各个变量进行配置,最后使用GPIO_Init()函数初始化就可以了。我们先来看看GPIO_InitTypeDef这个结构体有什么变量。
typedef struct
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
结构体 GPIO_InitStructure 的第一个成员变量 GPIO_Pin 用来设置是要初始化哪个或者哪些 IO 口;第二个参数是 IO 口速度设置,有三个可选值,在MDK中是通过枚举类型定义;
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
第三个成员变量 GPIO_Mode 是用来设置对应 IO 端口的输出输入模式,在MDK中也是通过枚举类型定义;
typedef enum
{ GPIO_Mode_AIN = 0x0, //模拟输入
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入
GPIO_Mode_IPD = 0x28, //下拉输入
GPIO_Mode_IPU = 0x48, //上拉输入
GPIO_Mode_Out_OD = 0x14, //开漏输出
GPIO_Mode_Out_PP = 0x10, //通用推挽输出
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
GPIO_Mode_AF_PP = 0x18 //复用推挽
}GPIOMode_TypeDef;
最终在这个实验里面我们的配置如下。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3| GPIO_Pin_4; //选择GPIO的第几个引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度50MHz,输出模式则需要设置速度
GPIO_Init(GPIOE, &GPIO_InitStructure); //根据配置初始化GPIOE
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入
GPIO_Init(GPIOE, &GPIO_InitStructure);
初始化完成,接下来就是我们的按键功能的实现函数了。这里我直接使用正点原子的例程代码。
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1; //按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(10); //去抖动
key_up=0;
if(KEY0==0)
{
return KEY0_PRES;
}
else if(KEY1==0)
{
return KEY1_PRES;
}
else if(WK_UP==1)
{
return WKUP_PRES;
}
}
else if(KEY0==1&&KEY1==1&&WK_UP==0)
{
key_up=1;
}
return 0; //无按键按下
}
我印象中之前在我使用这个按键函数的时候,在某个使用场景下,这个按键的函数是有点bug的,好像是在中断里面,具体的我也忘记了。不过这不重要,因为到了后面,我们的按键都不会这样使用,我们会使用中断或者是状态机,实际使用我个人会更加偏向状态机,因为在实际项目中,中断的使用是“危险”的,中断太多很容易会导致程序出现各种各样的问题。
扯远了,这期就到这里吧,喜欢的小伙伴可以点赞关注收藏哦!!!