STM32第一课:按键控制LED灯和蜂鸣器


功能要求

设备上电,4个灯灭
按键1按下,4个灯灭
按键2按下,4个灯亮
按键3按下,蜂鸣器响
按键4按下,蜂鸣器关闭


一、开发思路

1.打开原理图找到对应模块的引脚,分析电路工作的原理。
2.顺着引脚找到连接芯片的对应位置,看看到底是PX几。
3.打开参考手册找到GPIOX的位置,并顺着GPIOX的位置找到该部分的时钟总线。
4.在参考手册中找到该总线的寄存器,查看如何配置能够开启对应的时钟。
5.配置引脚模式。低八位(0-7)配置GPIOx_CRL,高八位(8-15)配置GPIOx_CRH。
6.通过ODR/IDR,配置默认输出/输入数据。
7.根据以上编写好对应模块的.c和.h文件。
8.在main函数中进行综合编程,以满足功能要求。

二、详细步骤及相关代码

1.查看原理图

找到所需模块的位置并分析工作高低电平。
LED灯模块:
在这里插入图片描述
分析电路:
4个LED灯末端接低电平时开始工作。

按键模块:
在这里插入图片描述
分析电路:
KEY1默认低电平,按下时会产生一个高电平信号
KEY2,KEY3,KEY4均为默认高电平,按下时会产生一个低电平信号。

蜂鸣器模块:
在这里插入图片描述
分析电路:
当输入低电平时电路截止,蜂鸣器不响。
等输入高电平时电路导通,蜂鸣器开始响。

找到芯片上每个模块对应的引脚
1.LED灯:
在这里插入图片描述
2.按键:
在这里插入图片描述在这里插入图片描述
3.蜂鸣器:
在这里插入图片描述

2.各个模块代码的编写

LED灯

从原理图上我们可以得知四个LED灯分别对应的引脚为PE2-PE5,且低电平使能。
此时我们可以打开参考手册找到系统结构,在系统结构中找到控制GPIOE的时钟。
在这里插入图片描述
在这里插入图片描述
由图可知:
控制GPIOE的时钟在APB2总线上。
时钟可以理解为控制GPIOE的开关总闸,若要使用该部分就必须先打开该部分的时钟。

如何打开对应的时钟呢?
首先在芯片手册中找到APB2外设时钟使能寄存器。
在这里插入图片描述
我们可以看到,IOPE的位置在底6位。
该寄存器默认都是0,所有时钟均为关闭状态,置为1时为打开。
所以为了使用PE部分的引脚,我们需要将第6位置为1。
在代码中为:

	RCC->APB2ENR |= 0X01 << 6;

开启完时钟后,为了能够将引脚输出高低电平。
此时我们还要配置引脚模式,引脚为低八位查看数据手册的GPIOx_CRL,引脚为低八位查看数据手册的GPIOx_CRH。
该LED灯为低八位,所以我们查看数据手册的GPIOx_CRL:
在这里插入图片描述
配置模式前我们要简单了解一下各个模式的意思:
输入模式
00:模拟输入模式
在这种模式下,引脚的输入缓冲器被禁用,通常用于模拟信号输入,例如 ADC。

01:浮空输入模式
这是复位后的默认状态。在这种模式下,没有内部上拉或下拉电阻,适用于需要外部电路提供信号驱动的情况。

10:上拉/下拉输入模式
这种模式下,可以使用内部上拉或下拉电阻。具体是上拉还是下拉通过 ODR(输出数据寄存器)来决定:
ODR 为 1 时,上拉。
ODR 为 0 时,下拉。

11:保留
这种模式在输入模式下是保留的,通常不使用。
输出模式

00:通用推挽输出模式(最常用)
在这种模式下,GPIO 引脚可以输出高电平和低电平,适用于大多数需要输出固定电平的场景。

01:通用开漏输出模式
在这种模式下,GPIO 引脚只能拉低电平或者处于高阻状态,适用于需要连接到多路总线的场景,如 I2C。

10:复用功能推挽输出模式
在这种模式下,GPIO 引脚被配置为外围设备的推挽输出,例如 USART 的 TX 引脚。如果外设需要推挽输出,这种模式非常合适。

11:复用功能开漏输出模式
在这种模式下,GPIO 引脚被配置为外围设备的开漏输出,例如 I2C 的 SDA 和 SCL 引脚。这种模式适用于需要外设驱动的开漏输出。

了解以上各种模式之后,就可以得知LED灯引脚只需要配置为0011。(4位配置一个引脚)
放在代码中即为:

//配置PE2--PE5为通用推挽输出
	GPIOE->CRL &=~(0X0F << 20);//PE5
	GPIOE->CRL |= 0X03 << 20;
	GPIOE->CRL &=~(0X0F << 16);//PE4
	GPIOE->CRL |= 0X03 << 16;
    GPIOE->CRL &=~(0X0F << 12);//PE3
	GPIOE->CRL |= 0X03 << 12;
	GPIOE->CRL &=~(0X0F << 8);//PE2
	GPIOE->CRL |= 0X03 << 8;

最后,我们还需要给该LED灯一个初始值,使其初始化为全灭的状态。

//4个引脚均输出高电平
	GPIOE->ODR |= (0x0F << 2);

根据需求整合成一个完整的.c和.h文件:

#include "stm32f10x.h"

void Led_Init()
{
	//配置好模式,然后全灭
	//开APB2时钟
	RCC->APB2ENR |= 0X01 << 6;
  //配置PE2--PE5为通用推挽输出
	GPIOE->CRL &=~(0X0F << 20);//PE5
	GPIOE->CRL |= 0X03 << 20;
	GPIOE->CRL &=~(0X0F << 16);//PE4
	GPIOE->CRL |= 0X03 << 16;
    GPIOE->CRL &=~(0X0F << 12);//PE3
	GPIOE->CRL |= 0X03 << 12;
	GPIOE->CRL &=~(0X0F << 8);//PE2
	GPIOE->CRL |= 0X03 << 8;
	//4个引脚均输出高电平
	GPIOE->ODR |= (0x0F << 2);
	
}
//添加一个开关灯,方便后续使用
void Led1_Ctrl(int flag)
{
  if(!!flag)
	 {
		GPIOE->ODR &= ~(0x0F << 2);
	 }
	else
	 {
		GPIOE->ODR |= (0x0F << 2);
	 }
}
#ifndef _LED_H_
#define _LED_H_

void Led_Init();
void Led1_Ctrl(int flag);

#endif

按键

开时钟:
大致流程跟上述LED灯的差不多,不过KEY的引脚分为PA0和PC4-PC6
所以此时我们要在APB2总线上开启对应A、C两个时钟

//开时钟
	RCC->APB2ENR |= 0x01<<4;//PC
	RCC->APB2ENR |= 0x01<<2;//PA

配置引脚模式:
按键按下时会发出一个电平信号,此时我们需要将模式改为浮空输入(0100)

	//配置模式
	GPIOC->CRL &=~(0X0F << 24);//PC6   key4
	GPIOC->CRL |= 0X04 << 24;
  GPIOC->CRL &=~(0X0F << 20);//PC5   key3
	GPIOC->CRL |= 0X04 << 20;
	GPIOC->CRL &=~(0X0F << 16);//PC4   key2
	GPIOC->CRL |= 0X04 << 16;
	GPIOA->CRL &=~0X0F;//PA0   key1
	GPIOA->CRL |= 0X04;

最后,根据要求我写了一个按下按键返回其对应按键序号的函数方便后续操作。

完整代码如下:

#include "stm32f10x.h"

void key_Init()
{
	//开时钟
	RCC->APB2ENR |= 0x01<<4;//PC
	RCC->APB2ENR |= 0x01<<2;//PA
	//配置模式
	GPIOC->CRL &=~(0X0F << 24);//PC6   key4
	GPIOC->CRL |= 0X04 << 24;
  GPIOC->CRL &=~(0X0F << 20);//PC5   key3
	GPIOC->CRL |= 0X04 << 20;
	GPIOC->CRL &=~(0X0F << 16);//PC4   key2
	GPIOC->CRL |= 0X04 << 16;
	GPIOA->CRL &=~0X0F;//PA0   key1
	GPIOA->CRL |= 0X04;
	
}

int Get_Key_Val(void)
{
	int key_val = 0;
	if(!!(GPIOA->IDR &(0X01 << 0))==1)
		key_val = 1;
	if(!!(GPIOC->IDR &(0X01 << 4))==0)
		key_val = 2;
	if(!!(GPIOC->IDR &(0X01 << 5))==0)
		key_val = 3;
	if(!!(GPIOC->IDR &(0X01 << 6))==0)
		key_val = 4;
	
	return key_val;
}
#ifndef _KEY_H_
#define _KEY_H_

void key_Init();
int Get_Key_Val(void);

#endif

其中,需要注意的是:KEY1按下时发出的是一个高平信号,不要忘记了。

蜂鸣器

开时钟:
该对应引脚为PC0,开启C的时钟即可

	//开时钟
	RCC->APB2ENR |= 0x01<<4;//PC

配置引脚模式:
和LED灯一样,配置成通用推挽输出(0011)就行。

	//配置模式
	GPIOC->CRL &=~(0X0F << 0);//PC0
	GPIOC->CRL |= 0X03 << 0;

最后,为了防止上电后蜂鸣器就响,我们需要设置一个初始值低电平,使其初始化后就关闭。

	GPIOC->ODR &= ~0x01;//默认低电平,关闭

完整代码:

#include "stm32f10x.h"

void Beep_Init()
{
	//开时钟
	RCC->APB2ENR |= 0x01<<4;//PC
	//配置模式
	GPIOC->CRL &=~(0X0F << 0);//PC0
	GPIOC->CRL |= 0X03 << 0;
	GPIOC->ODR &= ~0x01;//默认低电平,关闭
}
#ifndef _BEEP_H_
#define _BEEP_H_

void Beep_Init();

#endif

三、主函数编写

#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "beep.h"

int main()
{
    Led_Init();
    key_Init();
    Beep_Init();
    while(1)
    {
        switch(Get_Key_Val())
          {
               case 1:
               // 全灭
               Led1_Ctrl(0);
               break;
               case 2:
               // 全亮
               Led1_Ctrl(1);
               break;
               case 3:
               // 蜂鸣器响
               GPIOC->ODR |= 0x01; 
               break;
               case 4:
               // 蜂鸣器关
			   GPIOC->ODR &= ~0x01;	
               break;
           }
     }
     return 0;
}

思路解析:
先对各个模块进行初始化操作,完成后进入到while(1)死循环,通过Switch语句和按键反馈函数循环检测按键具体为哪一个。
当按键1按下时,运行预设的LED灯全亮/灭函数(真为全亮)
当按键2按下时,同上给一个真值使其全亮。
当按键3按下时,将PC0引脚置为1,蜂鸣器开始工作。
当按键4按下时,将PC0引脚置为0,蜂鸣器结束工作。


四、总结

堆区(heap):一般由程序员使用malloc或new来进行分配,在适当的时候用free或delete来进行释放。若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。堆栈的大小在编译器编译之后是不知道的,只有运行的时候才知道
栈区(stack):由编译器自动分配和释放,程序员不做干涉。存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。程序的中断,函数的形式参数传递等都需要STACK来实现。所有在处理的函数,包括函数嵌套,递归,等等,都是从这个“栈”里面,来分配的。

完全掌握了LED灯、按键和蜂鸣器模块。
学习了STM32的工作原理,对STM32的开发流程较为熟悉。能够通过看数据手册和原理图进行一些简单模块的开发和使用。

### STM32蜂鸣器驱动实现 要在STM32控制器上实现蜂鸣器功能,可以按照以下方法完成初始化控制逻辑。以下是基于HAL库的代码示例以及说明。 #### 初始设置 在开始编写代码之前,需确保已通过STM32CubeMX或STM32CubeIDE完成了GPIO端口定时器(TIMx)的配置,并生成了相应的HAL库代码[^1]。通常情况下,蜂鸣器连接到某个GPIO引脚并通过PWM信号来调节声音频率。 #### 主要文件修改 ##### 1. `stm32f1xx_hal_msp.c` 文件中的外设初始化 在此文件中定义用于产生音频波形的定时器实例及其通道分配: ```c void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim){ GPIO_InitTypeDef GPIO_InitStruct = {0}; if(htim->Instance==TIM3){ // 假设使用 TIM3 控制 PWM 输出 __HAL_RCC_GPIOA_CLK_ENABLE(); // 启用对应 GPIO 的时钟 /* 配置 PA6 作为 PWM 输出 */ GPIO_InitStruct.Pin = GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); __HAL_AFIO_REMAP_TIM3_PARTIAL(); // 如果需要重映射,则启用此函数 } } ``` 上述代码片段展示了如何将指定的GPIO引脚配置成复用推挽模式以便于发送PWM信号给蜂鸣器设备。 ##### 2. `main.c` 中的应用层编程 接下来,在主程序里创建一个简单的旋律播放循环: ```c #include "main.h" #define NOTE_C4 ((uint16_t)(261)) // C4音符对应的频率值(Hz) #define NOTE_D4 ((uint16_t)(294)) #define NOTE_E4 ((uint16_t)(329)) #define NOTE_F4 ((uint16_t)(349)) // 定义一首短曲子的数据结构 typedef struct { uint16_t frequency; // 当前音符的频率 uint8_t duration; // 持续时间(单位:ms) } MusicNote; MusicNote melody[] = {{NOTE_C4,500},{NOTE_D4,500},{NOTE_E4,500},{NOTE_F4,750}}; int noteCount = sizeof(melody)/sizeof(MusicNote); /** * @brief 根据输入参数设定PWM占空比从而发出相应的声音. * @param htim : TIM句柄指针 * @param freq : 所需产生的正弦波频率 **/ static void SetBuzzerFrequency(TIM_HandleTypeDef* htim,uint16_t freq){ uint32_t periodValue = SystemCoreClock /freq/2 ; // 计算周期寄存器数值 __HAL_TIM_SET_AUTORELOAD(htim,(periodValue)-1); //-1是因为自动加载计数从零开始计算 __HAL_TIM_SET_COMPARE(htim,TIM_CHANNEL_1,(periodValue)>>1); // 设置比较值为一半周期长度得到方波输出 } int main(void){ MX_HAL_Init(); MX_GPIO_Init(); MX_TIM3_Init(); while (1) { for(int i=0;i<noteCount;i++){ SetBuzzerFrequency(&htim3,melody[i].frequency); HAL_Delay(melody[i].duration); // 关闭发声 __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,0); HAL_Delay(100); // 添加短暂静默间隔改善听觉效果 } } } ``` 以上代码实现了利用定时器生成不同频率的脉宽调制(PWM)信号来驱动蜂鸣器发声的功能。每种特定频率代表了一个具体的音乐音阶,而持续时间为每个音符演奏的时间长短提供了依据。 对于有源蜂鸣器来说,只需简单地切换高低电平即可让其工作;而对于无源蜂鸣器则需要提供变化的电信号才能形成可辨识的声音[^2]。 #### 总结 综上所述,借助STMicroelectronics官方提供的硬件抽象层(HAL),开发者能够轻松便捷地操控各种外围器件,其中包括但不限于本案例提到的蜂鸣器应用。通过对定时器资源合理调配并配合恰当算法处理后便可顺利达成预期目标——即令嵌入式系统具备基础声效反馈能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值