学习板:STM32F103ZET6
强推系列:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结
STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区
本博总结一下按键实验,设计一个实验,该实验为:
①按下key_up后LED0、LED1交替闪烁,每0.5s闪烁一次,取消按下后,两个灯全灭。
②按下key0后,LED0常亮、蜂鸣器每隔0.5s间断发声。
③按下key2后,LED1常亮,蜂鸣器每隔0.5s间断发声。
④按下key1后,LED0、LED1同时亮,同时灭,且同时灭的时候蜂鸣器发声,同时亮的时候蜂鸣器不发声;间隔1s。
本实验用到LED0、LED1、蜂鸣器、key_up、key0、key1、key2:
①LED0接PB5,且PB5输出低电平时LED0被点亮,PB5只需输出标准高低电平即可,所有采用通用推挽输出。
②LED1接PE5,且PE5输出低电平时LED1被点亮,PE5只需输出标准高低电平即可,所有采用通用推挽输出。
③蜂鸣器接PB8,高电平发声,PB8只需输出标准高低电平即可,所有采用通用推挽输出。
④key_up接PA0,按下后PA0检测到一个高电平输入,为了更容易检测高电平,采用上拉输入模式。
⑤key0接PE4、key1接PE3、key2接PE2;key0~2按下后,对应引脚检测到低电平输入,为了更好的检测低电平,采用下拉输入。
led.h代码:
1 //led.h
2 #ifndef LED_H
3 #define LED_H
4 void LED_Init(void);
5
6 #endif
7
led.c代码:
1 //led.c
2 #include "sys.h"
3 #include "stm32f10x.h"
4 #include "led.h"
5 void LED_Init(void)
6 {
7 GPIO_InitTypeDef GPIO_InitStruct_B;
8 GPIO_InitTypeDef GPIO_InitStruct_E;
9 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE ,ENABLE);
10
11 GPIO_InitStruct_B.GPIO_Mode=GPIO_Mode_Out_PP;
12 GPIO_InitStruct_B.GPIO_Pin=GPIO_Pin_5;
13 GPIO_InitStruct_B.GPIO_Speed=GPIO_Speed_50MHz;
14 GPIO_Init(GPIOB,&GPIO_InitStruct_B);
15
16 GPIO_InitStruct_E.GPIO_Mode=GPIO_Mode_Out_PP;
17 GPIO_InitStruct_E.GPIO_Pin=GPIO_Pin_5;
18 GPIO_InitStruct_E.GPIO_Speed=GPIO_Speed_50MHz;
19 GPIO_Init(GPIOE,&GPIO_InitStruct_E);
20
21 GPIO_SetBits(GPIOB, GPIO_Pin_5);//PB5置高电平
22 //GPIO_WriteBit(GPIOB, GPIO_Pin_5,1);
23 //GPIO_Write(GPIOB,0x0020); //慎用
24 //PBout(5)=1;
25 GPIO_SetBits(GPIOE, GPIO_Pin_5);//PE5置高电平
26 //GPIO_WriteBit(GPIOE, GPIO_Pin_5,1);
27 //GPIO_Write(GPIOE,0x0020); //慎用
28 //PEout(5)=1;
29 }
上面代码中,第7、8行分别定义GPIO_Init()的第二个参数,为了方便理解,直接定义了俩个,其实只需要一个就行了。第21、22、23、24行代码都可以给PB5置高电平,让初始状态下LED0灭。同理25、26、27、28行给PE5置高电平。
再讲一下第23、27行代码的0x0020怎么来的,GPIO_Write()在函数里面用到ODR寄存器,第二个参数就是对ODR寄存器配置的16位数。如下图所示。因为这个函数里面对ODR寄存器的配置:“GPIOx->ODR = PortVal”用的是赋值语句,而不是位或语句,所以这个函数要慎用,之前博客也强调过。比如本实验使用到了PB5、PB8,设置PB5时用GPIO_Write()函数,此时PB5输出高电平、PB8输出低电平;在设置PB8时,再用该函数,使PB8输出低电平,但同时又会设置PB5输出低电平,因为在两个不同的.c文件中不好同时设置,所以会对设置造成紊乱。
蜂鸣器代码上一博客(四)蜂鸣器总结过了,直接附代码:
头文件:beep.h
代码:
//beep.h
#ifndef BEEP_H
#define BEEP_H
void BEEP_Init();
#endif
beep.c代码:
1 //beep.c
2 #include "beep.h"
3 #include "stm32f10x.h"
4 #include "sys.h"
5
6 void BEEP_Init(void)
7 {
8 GPIO_InitTypeDef GPIO_InitStruct;
9 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能GPIOB时钟
10
11 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
12 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;
13 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//PB8 IO配置
14 GPIO_Init(GPIOB,&GPIO_InitStruct);
15
16 GPIO_ResetBits(GPIOB,GPIO_Pin_8); //初始状态置低电平,关闭蜂鸣器
17 //GPIO_WriteBit(GPIOB,GPIO_Pin_8, 0);//初始状态置低电平,关闭蜂鸣器
18 //GPIO_Write(GPIOB,0); //初始状态置低电平,关闭蜂鸣器
19 //PBout(8)=0; //初始状态置低电平,关闭蜂鸣器
20 }
头文件key.h:
#ifndef KEY_H
#define KEY_H
void KEY_Init(void);
#endif
key.c文件
//key.c
#include "stm32f10x.h"
#include "sys.h"
#include "key.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct_A;
GPIO_InitTypeDef GPIO_InitStruct_E;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE , ENABLE);//使能GPIOA和GPIOE(PA0 PE2、3、4)
GPIO_InitStruct_A.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct_A.GPIO_Pin=GPIO_Pin_0 ;
GPIO_InitStruct_A.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct_A);//PA0 key_up 上拉输入
GPIO_InitStruct_E.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStruct_E.GPIO_Pin=GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStruct_E.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStruct_E);//PE2、3、4 key0、key1、key2 下拉输入
GPIO_SetBits(GPIOE,GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}
上述程序中,虽然key是输入,但是将引脚分别置高、低电平,防止复位后或上电后IO引脚都置0引起程序跑飞。
接下来就是主函数了,主函数中实现全部功能:
①按下key_up后LED0、LED1交替闪烁,每0.5s闪烁一次,取消按下后,两个灯全灭。
②按下key0后,LED0常亮、蜂鸣器每隔0.5s间断发声。
③按下key2后,LED1常亮,蜂鸣器每隔0.5s间断发声。
④按下key1后,LED0、LED1同时亮,同时灭,且同时灭的时候蜂鸣器发声,同时亮的时候蜂鸣器不发声;间隔1s。
//main.c
#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "beep.h"
int main(void)
{
LED_Init();
BEEP_Init();
KEY_Init();
delay_init();
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0))//检测key_up按下
{
delay_ms(10); //消抖
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0))//检测key_up按下
{
PBout(5)=0;//点亮LED0
PEout(5)=1;//熄灭LED1
delay_ms(500);
PBout(5)=1; //熄灭LED0
PEout(5)=0;//点亮LED1
delay_ms(500);
}
if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0))//取消按下key_up后俩个灯都灭
{
PBout(5)=1;//熄灭LED0
PEout(5)=1;//熄灭LED1
}
}
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0)//key0按下
{
delay_ms(10);
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0)//key0按下
{
PBout(5)=0;//点亮LED0
PEout(5)=1;//熄灭LED1
PBout(8)=1;//蜂鸣器发声
delay_ms(500);
PBout(8)=0;//蜂鸣器不发声
delay_ms(500);
}
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)!=0)//取消按下key0后都关闭
{
PBout(5)=1;//熄灭LED0
PEout(5)=1;//熄灭LED1
PBout(8)=0;//蜂鸣器不发声
}
}
else if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==0)//key2按下
{
delay_ms(10);
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==0)//key2按下
{
PBout(5)=1; //熄灭LED0
PEout(5)=0;//点亮LED1
PBout(8)=1;//蜂鸣器发声
delay_ms(500);
PBout(8)=0;//蜂鸣器不发声
delay_ms(500);
}
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)!=0)//取消按下key2后都熄灭
{
PBout(5)=1;//熄灭LED0
PEout(5)=1;//熄灭LED1
PBout(8)=0;//蜂鸣器不发声
}
}
else if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==0)//key1按下
{
delay_ms(10);
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==0)//key1按下
{
PBout(5)=0;//点亮LED0
PEout(5)=0;//点亮LLED1
PBout(8)=0; //蜂鸣器不发声
delay_ms(1000);
PBout(5)=1;//熄灭LED0
PEout(5)=1;//熄灭LLED1
PBout(8)=1; //蜂鸣器发声
delay_ms(1000);
}
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)!=0)//取消按下key1后都熄灭
{
PBout(5)=1;//熄灭LED0
PEout(5)=1;//熄灭LED1
PBout(8)=0;//蜂鸣器不发声
}
}
}
}
代码中有注释就不再解释了,有点多…
头文件led.h,与库函数版一样,代码:
//led.h
#ifndef LED_H
#define LED_H
void LED_Init(void);
#endif
led.c编写:
时钟使能,分别为PB5与PE5
RCC->APB2ENR|=1<<3;
RCC->APB2ENR|=1<<6;
//RCC->APB2ENR|=0x0048;
可以用代码中注释部分一步使能时钟。
led.c完整代码:
//led.c
#include "sys.h"
#include "stm32f10x.h"
#include "led.h"
void LED_Init(void)
{
RCC->APB2ENR|=1<<3;
RCC->APB2ENR|=1<<6;
//RCC->APB2ENR|=0x0048;
GPIOB->CRL&=0xff0fffff;//配置PB5、PE5
GPIOB->CRL|=0x00300000;
GPIOE->CRL&=0xff0fffff; //注意一定要清零
GPIOE->CRL|=0x00300000;
//初始熄灭俩个灯
GPIOB->ODR|=0x0020; //ODR寄存器
GPIOE->ODR|=0x0020; //ODR寄存器
//GPIOB->ODR|=1<<5; //ODR寄存器
//GPIOE->ODR|=1<<5; //ODR寄存器
//GPIOB->BSRR=0x00000020; //BSRR寄存器
//GPIOE->BSRR=0x00000020; //BSRR寄存器
//GPIOB->BSRR=1<<5; //BSRR寄存器
//GPIOE->BSRR=1<<5; //BSRR寄存器
}
其中配置PB5、PE5的十六进制数0x00300000由来如下图:
使用BSRR寄存器时0x00000020的由来:
蜂鸣器代码上一博客(四)蜂鸣器总结过了,直接附代码:
beep.h代码:
1 //beep.h
2 #ifndef BEEP_H
3 #define BEEP_H
4 void BEEP_Init(void);
5 #endif
beep.c代码:
1 //beep.c
2 #include "beep.h"
3 #include "sys.h"
4 #include "stm32f10x.h"
5 void BEEP_Init(void)
6 {
7 RCC->APB2ENR|=0x0008; //使能GPIOB时钟
8 //RCC->APB2ENR|=1<<3; //使能GPIOB时钟
9 //RCC->APB2ENR=1<<3; //使能GPIOB时钟
10
11 GPIOB->CRH&=0XFFFFFFF0;
12 GPIOB->CRH|=0x00000003; //配置PB8
13 //GPIOB->CRH=0x00000003; //配置PB8 (不推荐直接赋值)
14
15 GPIOB->ODR&=0xfeff; //ODR寄存器
16 //GPIOB->ODR&=0xfffe<<8; //ODR寄存器
17 //GPIOB->BRR|=0x0100; //BRR寄存器
18 //GPIOB->BRR=0x0100; //BRR寄存器
19 //GPIOB->BRR|=1<<8; //BRR寄存器
20 //GPIOB->BRR=1<<8; //BRR寄存器
21 }
22
头文件key.h:
//key.h
#ifndef KEY_H
#define KEY_H
void KEY_Init(void);
#endif
key.c代码:
//key.c
#include "beep.h"
#include "sys.h"
#include "stm32f10x.h"
#include "key.h"
void KEY_Init(void)
{
RCC->APB2ENR|=0x0044;//FPIOA和GPIOE
GPIOA->CRL&=0xfffffff0;
GPIOA->CRL|=0x00000008; //PA0 上拉输入
GPIOE->CRL&=0XFFF000FF;
GPIOE->CRL|=0x00088800; //PE2、PE3、PE4下拉输入
GPIOA->BRR=0x0001;//PA0初始给低电平
GPIOE->BSRR=0x0000001c;//PE2、3、4初始给高电平
//虽然是上拉、下拉输入,但是上电后IO口电平都清零,防止出现乱跑,所以先将PA0置低电平、PE2、3、4初始给高电平
}
时钟配置的16位数由来:
配置PA0时32位数的由来:
配置PE2…4的32位数的由来:
//main.c
#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "beep.h"
int main(void)
{
LED_Init();
KEY_Init();
BEEP_Init();
delay_init();
while(1)
{
if((GPIOA->IDR&0x0001)!=0)//检测PA0,key_up 第0位有高电平时!0
{
delay_ms(10);
if((GPIOA->IDR&0x0001)!=0)
{
GPIOB->BRR=1<<5;//点亮LED0
GPIOE->BSRR=1<<5;//熄灭LED1
delay_ms(500);
GPIOB->BSRR=1<<5;//熄灭LED0
GPIOE->BRR=1<<5;//点亮LED1
delay_ms(500);
}
if((GPIOA->IDR&0x0001)==0)
{
GPIOB->BSRR=1<<5;//熄灭LED0
GPIOE->BSRR=1<<5;//熄灭LED1
}
}
if((GPIOE->IDR&0x0010)==0)//检测PE4 key0,若按下,第4位为0.与运算后为0
{
delay_ms(10);
if((GPIOE->IDR&0x0010)==0)
{
GPIOB->BRR=1<<5;//点亮LED0
GPIOE->BSRR=1<<5;//熄灭LED1
GPIOB->BSRR&=0;//清零 防止操作PB8时影响上面的LED0点亮操作 当然这里是=1<< 而不是|=1<< 所以可以不清零
GPIOB->BSRR=1<<8;//蜂鸣器发声
delay_ms(500);
GPIOB->BRR=1<<8;//蜂鸣器不发声
delay_ms(500);
}
if((GPIOE->IDR&0010)!=0)
{
GPIOB->BSRR=1<<5;//熄灭LED0
GPIOE->BSRR=1<<5;//熄灭LED1
GPIOB->BRR=1<<8;//蜂鸣器不发声
}
}
if((GPIOE->IDR&0x0004)==0) //key2按下 第2位为0,与运算后为0
{
delay_ms(10);
if((GPIOE->IDR&0x0004)==0)
{
GPIOB->BSRR=1<<5;//熄灭LED0
GPIOE->BRR=1<<5;//点亮LED1
GPIOB->BSRR=1<<8;//蜂鸣器发声
delay_ms(500);
GPIOB->BRR=1<<8;//蜂鸣器不发声
delay_ms(500);
}
if((GPIOE->IDR&0x0004)!=0)
{
GPIOB->BSRR=1<<5;//熄灭LED0
GPIOE->BSRR=1<<5;//熄灭LED1
GPIOB->BRR=1<<8;//蜂鸣器不发声
}
}
if((GPIOE->IDR&0x0008)==0)
{
delay_ms(10);
if((GPIOE->IDR&0x0008)==0)
{
GPIOB->BRR=1<<5;//点亮LED0
GPIOE->BRR=1<<5;//点亮LED1
GPIOB->BRR=1<<8;//蜂鸣器不发声
delay_ms(1000);
GPIOB->BSRR=1<<5;//熄灭LED0
GPIOE->BSRR=1<<5;//熄灭LED1
GPIOB->BSRR=1<<8;//蜂鸣器发声
delay_ms(1000);
}
if((GPIOE->IDR&0x0008)!=0)
{
GPIOB->BSRR=1<<5;//熄灭LED0
GPIOE->BSRR=1<<5;//熄灭LED1
GPIOB->BRR=1<<8;//蜂鸣器不发声
}
}
}
}