一、Beep控制
原理圖分析:
- 蜂鳴器三極管控制引腳對應 MCU PB8。
- 當前蜂鳴器對應的電路中,三極管是 NPN 三極管,當前【基極】存在小電流,當前三極管導通。要求對應 PB8 引腳對外輸出電壓 / 電流。
- 當前 PB8 輸出高電平,當前三極管導通,BEEP 工作。PB8 提供一個低電平,三極管未導通,BEEP 不工作。當前 PB8 要求的 GPIO 工作模式必須是【推挽模式】。
代碼流程:
- 時鐘使能,RCC->APB2ENR。
- 設置當前 GPIO PB8 工作模式必須是【推挽模式】。需要通過 GPIOB->CRH 配置 PB8 引腳工作模式。需要配置的寄存器位置是【位 3:0】。
- 利用 GPIOx_ODR 控制輸出電平情況。高電平 BEEP 工作,低電平 BEEP 停止工作。
代碼實現:
#include "stm32f10x.h"
/*
STM32 核心頭文件,標準頭文件,當前對應 STM32F10x 系列,
當前使用的芯片是 STM32F103ZET6
開發中,需要使用相關函數,相關類型,相關配置都在當前
頭文件中。
*/
void Delay(u32 tim);
int main(void)
{// 1. 時鐘使能,RCC 控制使用 GPIOB GPIO組RCC->APB2ENR = 0x01 << 3;// 2. 配置 GPIOB --> PB8 推挽模式,需要使用 CRHGPIOB->CRH &= ~(0x0F);GPIOB->CRH |= 0x03; // 推挽模式,同時輸出速度最高位 50MHz/*3. ODR 控制輸出高電平或者低電平高電平 BEEP 工作低電平 BEEP 不工作*/while (1){// 控制輸出高電平GPIOB->ODR |= 0x01 << 8;Delay(200000);// 控制輸出低電平GPIOB->ODR &= ~(0x01 << 8);Delay(200000);}
}
void Delay(u32 tim)
{while (tim--){for (int i = 0; i < 72; i++){}}
}
二、keil的多文件編程
????????將 LED 和 BEEP 控制模式進行函數封裝,同時利用多文件方式,將不同的模塊劃分為不同的文件,從而實現模塊化開發。
需要創建不同模塊的 .c 和 .h。同時完成 Keil5 工程配置。
估計模塊需求創建新文件,需要提供 .c 和 .h:
- 利用 Ctrl + N 創建新文件。
- Ctrl + S 保存當前文件。
- 后續操作如圖。
代碼實現:
led.c:
#include "led.h"/*** @brief LED初始化函數:配置GPIO引腳為推挽輸出模式*/
void Led_Init(void)
{// 1. 使能GPIOB和GPIOE時鐘RCC->APB2ENR |= 0x01 << 3; // GPIOB時鐘使能RCC->APB2ENR |= 0x01 << 6; // GPIOE時鐘使能// 2. 配置PB5為推挽輸出模式(50MHz)GPIOB->CRL &= ~(0x0F << 20); // 清除PB5原有配置GPIOB->CRL |= 0x03 << 20; // 配置PB5為推挽輸出// 3. 配置PE5為推挽輸出模式(50MHz)GPIOE->CRL &= ~(0x0F << 20); // 清除PE5原有配置GPIOE->CRL |= 0x03 << 20; // 配置PE5為推挽輸出// 4. 默認輸出高電平,LED初始狀態為熄滅GPIOB->ODR |= 0x01 << 5; // PB5輸出高電平,LED0滅GPIOE->ODR |= 0x01 << 5; // PE5輸出高電平,LED1滅
}/*** @brief 控制LED0的亮滅* * @param flag 1-點亮LED0,0-熄滅LED0*/
void Led0_Ctrl(u8 flag)
{if (flag){// 輸出低電平,點亮LED0GPIOB->ODR &= ~(0x01 << 5);}else{// 輸出高電平,熄滅LED0GPIOB->ODR |= 0x01 << 5;}
}/*** @brief 控制LED1的亮滅* * @param flag 1-點亮LED1,0-熄滅LED1*/
void Led1_Ctrl(u8 flag)
{if (flag){// 輸出低電平,點亮LED1GPIOE->ODR &= ~(0x01 << 5);}else{// 輸出高電平,熄滅LED1GPIOE->ODR |= 0x01 << 5;}
}/*** @brief 實現LED0和LED1交替閃爍效果*/
void Led_Blink(void)
{Led0_Ctrl(1); // 點亮LED0Led1_Ctrl(0); // 熄滅LED1Delay_ms(500); // 延時500msLed0_Ctrl(0); // 熄滅LED0Led1_Ctrl(1); // 點亮LED1Delay_ms(500); // 延時500ms
}
led.h:
#ifndef _LED_H
#define _LED_H#include "stm32f10x.h"
#include "delay.h"/*** @brief 當前開發板中 DS0 和 DS1 LED 燈配置初始化*/
void Led_Init(void);/*** @brief DS0 對應的 LED0 燈控制函數* * @param flag 控制標志:1-LED0亮,0-LED0滅*/
void Led0_Ctrl(u8 flag);/*** @brief DS1 對應的 LED1 燈控制函數* * @param flag 控制標志:1-LED1亮,0-LED1滅*/
void Led1_Ctrl(u8 flag);/*** @brief LED0 和 LED1 交替閃爍*/
void Led_Blink(void);#endif
三、__Nop()函數實現延時操作
-
delay.h:頭文件
- 使用條件編譯防止重復引用(
#ifndef _DELAY_H
?等) - 聲明了微秒級延時(
Delay_us
)和毫秒級延時(Delay_ms
)函數 - 包含函數注釋,說明功能和參數含義
- 使用條件編譯防止重復引用(
-
delay.c:源文件
- 實現了頭文件中聲明的兩個延時函數
Delay_us
:通過連續調用 72 個?__NOP()
?空操作實現約 1 微秒的延時(基于 STM32F103ZET6 的 72MHz 主頻設計)Delay_ms
:通過循環調用?Delay_us(1000)
?實現毫秒級延時(1 毫秒 = 1000 微秒)
使用時,只需在需要延時的地方調用這兩個函數即可,例如:
Delay_us(500)
:延時 500 微秒Delay_ms(100)
:延時 100 毫秒
????????該延時模塊采用純寄存器級操作,不依賴系統滴答定時器,適合在初始化階段或對延時精度要求不極高的場景使用。
delay.c:
#include "delay.h"/*** @brief 微秒級延時函數* * @param us 要延時的微秒數* @note 基于STM32F103ZET6的72MHz主頻設計,1個__NOP()占用約13.889ns* 72個__NOP()約占用1us(72 * 13.889ns ≈ 1000ns)*/
void Delay_us(u32 us)
{while (us--){// 連續調用72個空操作,實現約1us的延時__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();}
}/*** @brief 毫秒級延時函數* * @param ms 要延時的毫秒數* @note 基于微秒延時函數實現,1ms = 1000us*/
void Delay_ms(u32 ms)
{while (ms--){Delay_us(1000); // 調用1000次微秒延時,實現1ms延時}
}
delay.h:
#ifndef _DELAY_H
#define _DELAY_H
#include "stm32f10x.h"/*** @brief 延時微秒控制函數,延時單位是 us** @param us 延時微秒時間(取值范圍:0~4294967295)*/
void Delay_us(u32 us);/*** @brief 延時毫秒控制函數,延時單位是 ms** @param ms 延時毫秒時間(取值范圍:0~4294967295)*/
void Delay_ms(u32 ms);#endif
四、可編程按鍵控制
原理圖分析:
- 根據原理圖分析,KEY0、KEY1、KEY2 按鍵按下之后,當前電路接地,對應低電平。
- KEY_UP 按鍵按下之后,當前電路接 3.3V,對應高電平。
- 如果想要通過按鍵按下之后的電平切換作為按鍵判斷條件。KEY0、KEY1、KEY2 對應 MCU 引腳默認【高電平】,KEY_UP 對應 MCU 引腳默認【低電平】。
- KEY0、KEY1、KEY2 對應 MCU 引腳為【上拉輸入模式】。
- KEY_UP 對應 MCU 引腳為【下拉輸入模式】。
KEY0、KEY1、KEY2 引腳對應關系:
- KEY0 ==> PE4;KEY1 ==> PE3;KEY2 ==> PE2。
KEY_UP 引腳對應關系:
- KEY_UP ==> PA0。
開發流程:
- 時鐘使能,需要使能的 GPIO 組為 GPIOE 和 GPIOA
- RCC->APB2ENR 使用 GPIOE 和 GPIOA
- 控制 GPIO 工作模式
- PA0【下拉輸入模式】
- PE2 ~ PE4【上拉輸入模式】
- 利用 GPIOx_IDR 寄存器,讀取對應引腳的電平狀態,判斷對應按鍵是否按下。
- PA0 對應 KEY_UP 按下,電平狀態從低電平切換到高電平狀態。
- PE2 ~ PE4 對應 KEY2、KEY1、KEY0 按下,電平狀態從高電平切換到低電平狀態。
寄存器文檔分析:
代碼實現:
key.c:
#include "key.h"/*** @brief 按鍵初始化函數* 配置KEY0(PE4)、KEY1(PE3)、KEY2(PE2)為上拉輸入* 配置KEY_UP(PA0)為下拉輸入*/
void Key_Init(void)
{// 使能GPIOA和GPIOE時鐘RCC->APB2ENR |= (1 << 2); // 使能GPIOA時鐘RCC->APB2ENR |= (1 << 6); // 使能GPIOE時鐘// 配置PE2、PE3、PE4為上拉輸入GPIOE->CRL &= ~(0x0F << (2*4)); // 清除PE2配置GPIOE->CRL &= ~(0x0F << (3*4)); // 清除PE3配置GPIOE->CRL &= ~(0x0F << (4*4)); // 清除PE4配置GPIOE->CRL |= (0x08 << (2*4)); // PE2: 上拉輸入(1000)GPIOE->CRL |= (0x08 << (3*4)); // PE3: 上拉輸入(1000)GPIOE->CRL |= (0x08 << (4*4)); // PE4: 上拉輸入(1000)GPIOE->ODR |= (1 << 2); // PE2上拉GPIOE->ODR |= (1 << 3); // PE3上拉GPIOE->ODR |= (1 << 4); // PE4上拉// 配置PA0為下拉輸入GPIOA->CRL &= ~(0x0F << (0*4)); // 清除PA0配置GPIOA->CRL |= (0x08 << (0*4)); // PA0: 下拉輸入(1000)// PA0默認下拉,無需額外配置ODR
}/*** @brief 獲取按鍵狀態* @return 按鍵值,分別對應KEY0、KEY1、KEY2、KEY_UP或無按鍵*/
u8 Key_GetValue(void)
{// 檢測KEY0(PE4)if (!(GPIOE->IDR & (1 << 4))) // 低電平表示按下{Delay_ms(10); // 消抖if (!(GPIOE->IDR & (1 << 4))){while (!(GPIOE->IDR & (1 << 4))); // 等待釋放return KEY0_VALUE;}}// 檢測KEY1(PE3)if (!(GPIOE->IDR & (1 << 3))) // 低電平表示按下{Delay_ms(10); // 消抖if (!(GPIOE->IDR & (1 << 3))){while (!(GPIOE->IDR & (1 << 3))); // 等待釋放return KEY1_VALUE;}}// 檢測KEY2(PE2)if (!(GPIOE->IDR & (1 << 2))) // 低電平表示按下{Delay_ms(10); // 消抖if (!(GPIOE->IDR & (1 << 2))){while (!(GPIOE->IDR & (1 << 2))); // 等待釋放return KEY2_VALUE;}}// 檢測KEY_UP(PA0)if (GPIOA->IDR & (1 << 0)) // 高電平表示按下{Delay_ms(10); // 消抖if (GPIOA->IDR & (1 << 0)){while (GPIOA->IDR & (1 << 0)); // 等待釋放return KEY_UP_VALUE;}}return NO_KEY_PRESSED; // 無按鍵按下
}
key.h:
#ifndef _KEY_H
#define _KEY_H
#include "stm32f10x.h"
#include "delay.h"// 按鍵值定義
#define KEY0_VALUE (0) // KEY0按鍵對應返回值
#define KEY1_VALUE (1) // KEY1按鍵對應返回值
#define KEY2_VALUE (2) // KEY2按鍵對應返回值
#define KEY_UP_VALUE (3) // 上拉按鍵對應返回值
#define NO_KEY_PRESSED (4) // 無按鍵按下返回值// 函數聲明
void Key_Init(void); // 按鍵初始化函數
u8 Key_GetValue(void); // 獲取按鍵值函數#endif
main.c:
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "delay.h"int main(void)
{// 初始化外設Delay_Init(); // 延時初始化Led_Init(); // LED初始化Key_Init(); // 按鍵初始化while (1){u8 key_value = Key_GetValue(); // 獲取按鍵值// 根據按鍵值控制LEDswitch (key_value){case KEY0_VALUE:Led0_Ctrl(1); // 打開LED0break;case KEY1_VALUE:Led0_Ctrl(0); // 關閉LED0break;case KEY2_VALUE:Led1_Ctrl(1); // 打開LED1break;case KEY_UP_VALUE:Led1_Ctrl(0); // 關閉LED1break;default:// 無按鍵操作時無需處理break;}}
}
五、閃爍燈與呼吸燈
完整代碼:
led.c:
#include "led.h"void Led_Init(void)
{//RCC對PB端口和PE端口時鐘使能RCC->APB2ENR |= (0x01 << 3 | 0x01 << 6);//GPIO寄存器配置GPIOB->CRL &= ~(0xf << 20);GPIOE->CRL &= ~(0xf << 20);GPIOB->CRL |= (0x1 << 20);GPIOE->CRL |= (0x1 << 20);
}
void Led_Ctrl(int value)
{if(value){GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR &= ~(0x1 << 5);}else{GPIOB->ODR |= (0x1 << 5);GPIOE->ODR |= (0x1 << 5);}
}
void Led_Blink(void)
{//低電平LED0燈亮 高電平LED1燈滅 GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR |= (0x1 << 5);Delay_ms(300);//高電平LED0燈滅 低電平LED1燈亮GPIOB->ODR |= (0x1 << 5);GPIOE->ODR &= ~(0x1 << 5);Delay_ms(300);
}
void Led_Breath(void)
{int j = 0;while(1){GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR |= (0x1 << 5);Delay_us(5000 - j);GPIOE->ODR &= ~(0x1 << 5);GPIOB->ODR |= (0x1 << 5);Delay_us(j);j += 10;if(j == 5000){break;}}j = 0;while(1){GPIOE->ODR &= ~(0x1 << 5);GPIOB->ODR |= (0x1 << 5);Delay_us(5000 - j);GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR |= (0x1 << 5);Delay_us(j);j += 10;if(j == 5000){break;}}
}
led.h:
#ifndef __LED_H
#define __LED_H#include "stm32f10x.h"
#include <stdio.h>
#include "delay.h"void Led_Init(void);
void Led_Ctrl(int value);
void Led_Blink(void);
void Led_Breath(void);#endif
main.c:
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"int main(void)
{Led_Init();while(1){//Led_Ctrl(3);Led_Blink();//Led_Breath();}
}
代碼解析:
一、
Led_Blink(void)
?函數:LED 交替閃爍功能該函數實現?兩個 LED 燈(LED0 和 LED1)固定頻率交替亮滅?的效果,具體邏輯如下:
??
初始狀態設置
GPIOB->ODR &= ~(0x1 << 5);
:將 GPIOB 端口的第 5 位(PB5)置為低電平(假設 LED0 接 PB5,低電平點亮),此時 LED0 亮。GPIOE->ODR |= (0x1 << 5);
:將 GPIOE 端口的第 5 位(PE5)置為高電平(假設 LED1 接 PE5,高電平熄滅),此時 LED1 滅。延時保持
Delay_ms(300);
:維持 “LED0 亮、LED1 滅” 的狀態 300 毫秒。狀態反轉
GPIOB->ODR |= (0x1 << 5);
:PB5 置為高電平,LED0 滅。GPIOE->ODR &= ~(0x1 << 5);
:PE5 置為低電平,LED1 亮。再次延時
Delay_ms(300);
:維持 “LED0 滅、LED1 亮” 的狀態 300 毫秒。效果總結:
LED0 和 LED1 以?600 毫秒為一個周期?交替閃爍(各亮 300ms,滅 300ms),狀態切換清晰、節奏固定,類似 “紅綠燈交替” 的效果。二、
Led_Breath(void)
?函數:LED 呼吸燈效果該函數通過?改變 LED 亮滅時間的占空比,實現兩個 LED 燈(LED0 和 LED1)交替 “呼吸”(亮度逐漸變化)的效果,具體分為兩個階段:
階段 1:LED0 逐漸變暗,LED1 逐漸變亮
初始化變量?
j = 0
,通過?while(1)
?循環逐步調整亮滅時間:
- 先讓 LED0 亮(PB5 低電平)、LED1 滅(PE5 高電平),并延時?
5000 - j
?微秒(隨j
增大,此延時逐漸減小)。- 再讓 LED0 滅(PB5 高電平)、LED1 亮(PE5 低電平),并延時?
j
?微秒(隨j
增大,此延時逐漸增大)。- 每次循環?
j += 10
,直到?j = 5000
?時退出循環。過程解析:
- 初始時?
j=0
:LED0 亮 5000us(常亮),LED1 滅 0us(常滅)→ LED0 最亮,LED1 最暗。- 隨著
j
增大:LED0 亮的時間縮短、滅的時間增長 → 亮度逐漸降低;LED1 亮的時間增長、滅的時間縮短 → 亮度逐漸升高。- 結束時?
j=5000
:LED0 亮 0us(常滅),LED1 亮 5000us(常亮)→ LED0 最暗,LED1 最亮。階段 2:LED1 逐漸變暗,LED0 逐漸變亮
?
重置?
j = 0
,再次進入?while(1)
?循環,邏輯與階段 1 對稱:
- 先讓 LED1 亮(PE5 低電平)、LED0 滅(PB5 高電平),延時?
5000 - j
?微秒(隨j
增大而減小)。- 再讓 LED1 滅(PE5 高電平)、LED0 亮(PB5 低電平),延時?
j
?微秒(隨j
增大而增大)。- 直到?
j = 5000
?時退出循環。過程解析:
- 初始時?
j=0
:LED1 常亮,LED0 常滅 → LED1 最亮,LED0 最暗。- 隨著
j
增大:LED1 亮度逐漸降低,LED0 亮度逐漸升高。- 結束時?
j=5000
:LED1 常滅,LED0 常亮 → LED1 最暗,LED0 最亮。效果總結:
?
兩個 LED 燈交替實現 “呼吸” 效果:
- 先 LED0 從最亮逐漸變暗至熄滅,同時 LED1 從熄滅逐漸變亮至最亮;
- 再 LED1 從最亮逐漸變暗至熄滅,同時 LED0 從熄滅逐漸變亮至最亮。
整體效果類似人呼吸時的 “明暗漸變”,過渡平滑自然。
https://github.com/0voice