>[success] **技術支持說明:**
>**1**.一般以自主學習為主
> **2**.可到官方問答社區中提問:[**去提問**](https://bbs.csdn.net/forums/nb-iot)
> **3**.工程師**會盡快**解答社區問題,但他們是一線開發,【**難以保證**】解答時效,解答辛苦,感謝理解!
<br/>
## **LED控制原理**
**計算機的邏輯層與電路層**
在計算機邏輯層中,一般是使用二進制數字(0或1)來控制對象或表示對象的狀態。在計算機電路層上,一般是用高電平(3.3 v)來表示邏輯上的1,用低電平(0 v)來表示邏輯上的0。
###
**CPU的IO口**
CPU的IO口(引腳)可以理解為從CPU里面引出來的一根導線,用于連接外部的設備。通俗地講,一個CPU會有多個IO口,每個IO口有兩種工作模式,分別是輸出信號模式和接收信號模式。
###
在輸出信號模式時,我們可以通過代碼來控制這個IO口輸出的電平狀態,這個電平狀態有高電平(3.3 v)或低電平(0 v)。在輸入信號模式時,我們可以通過代碼來檢測這個IO口是處于高電平還是低電平狀態。
###
**LED簡介**
LED是Light Emitting Diode的縮寫,中文意思是“發光二極管”,是一種能夠把電能轉化為可見光的元器件。STM32開發板配備了一盞可以給開發者使用的LED,如圖所示。

此LED的原理圖如下圖所示。

其中的D2表示LED,其右端接地(GND),因此右端的電壓為0v。LED的左端依次連接著R6和PB1。PB1是STM32F030的一個IO口,能夠輸出高電平(3.3v)或低電平(0v)。R6是一個穩壓電阻,用于防止電路的電壓過大而燒壞LED。當PB1輸出高電平時,LED左端電壓為3.3v,右端電壓為0v,左端和右端形成了3.3v的電壓差,因此LED被點亮;反之,當PB1輸出低電平時,LED左端和右端電壓均為0v,因此LED被熄滅。
>[danger] 如您因缺少硬件原理相關知識導致未能看懂本圖,需先補充相關知識。
<br/>
## **架構設計**
在學習使用LED燈之前,先簡單講解一下嵌入式系統的常用架構設計,這對以后開發嵌入式軟件有莫大的好處,希望讀者好好學習。
###
常見的小型嵌入式系統可以劃分為硬件、標準庫、HAL/BSP和上層應用,如圖所示。

* 硬件是指主控芯片以及相關外圍設備等,例如本課程配套的開發板。
* 標準庫是指有主控芯片配套的驅動程序庫,例如本課程用到的ST標準庫。
* 上層應用是指由應用軟件開發者針對各種實際的應用需求而開發出來的應用軟件。
有架構思想的工程師通常會結合硬件資源在標準庫和上層應用之間構建HAL(Hardware Abstraction Layer,硬件抽象層)。構建HAL的好處在于極大地方便應用層使用各種硬件資源,例如方便地控制LED的開關、接收按鍵信息,以及獲取溫濕度傳感器數值等。有些資源較為豐富的硬件設備會為開發者提供BSP(Board Support Package),其作用跟HAL也是類似的。
<br/>
**LED HAL API 設計**
為LED應用設計HAL 前,需要先思考一下應用開發者需要對LED進行什么操作。一般地,應用開發者需要打開、關閉、反轉和初始化LED,因此HAL可以提供以下API供應用層調用:
* Init : 初始化
* SetOn : 打開LED
* SetOff : 關閉LED
* Toggle : 反轉LED,即如果當前LED是打開的,那么就關閉,如果是關閉的,那就打開。
因此LED的HAL抽象定義如圖所示。

API是Application Programming Interface的簡稱,中文意思是應用編程接口,開發者可以使用API來實現各種操作,例如使用上述的SetOn來打開LED、使用SetOff來關閉LED等。對于初學者來說,這是一個不容易理解概念,可以先跟隨本課程一步一步學習,在學習的過程中逐漸地領悟它的含義。
<br/>
## **實現LED HAL API**
學習完架構設計思想后,接下來實現LED應用的HAL。
<br/>
#### **準備工作**
在上節課移植好的模板工程的根目錄創建兩個空文件,分別為hal\_led.h和hal\_led.c文件,如圖所示。

###
然后打開文件管理器,選擇STM32F030→User,然后點擊Add Files,把hal\_led.c文件添加進工程中,如圖所示。

<br/>
由于hal_led.c文件將會使用到ST標準庫,因此也需要把相關的標準庫文件添加進工程中,步驟如下:
1. 打開文件管理器,如圖所示。

###
2. 選擇STM32F030→StdPeriph_Driver,然后點擊Add Files...

###
3. 進入到如下目錄中,
```
Libraries\\TM32F0xx\_StdPeriph\_Driver\\src
```
###
4. 添加 stm32f0xx_gpio.c 和 stm32f0xx_rcc.c文件,如圖所示。

###
5. 添加完成后,點擊 "Close"按鈕。
<br/>
由于配套開發板的資源與ST默認的有所不同,因此需要修改文件 system_stm32f0xx.c 文件中的 SetSysClock 函數,如下圖所示。

###
修改后的代碼如下:
```
static void SetSysClock(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK configuration ----------------------------------------*/
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer and set Flash Latency */
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;
/* PLL configuration = HSE * 6 = 48 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL6);
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL)
{
}
}
else
{
/* Enable Prefetch Buffer and set Flash Latency */
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;
// PLL configuration = (HSI/2) * 12 = 48 MHz
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_12); // 8M/2 * 12 = 48M
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while ((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // PLL as system clock
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL)
{
}
}
}
```
>[warning] 上述代碼較為復雜,讀者暫時無需理解。
準備工作至此完成,接下開始編寫代碼實現LED的HAL。
<br/>
#### **編寫源代碼**
在頭文件hal_led.h中定義前面設計的各個API,代碼如下:
```
#ifndef __HAL_LED_H__
#define __HAL_LED_H__
/*定義LED燈連接的GPIO口B1*/
#define HAL_LED_PORT GPIOB
#define HAL_LED_PIN GPIO_Pin_1
#define HAL_LED_CLOCK RCC_AHBPeriph_GPIOB
void halLedInit(void);//初始化LED燈
void halLedSetOn(void);//打開LED燈
void halLedSetOff(void);//關閉LED燈
void halLedToggle(void);//反轉LED燈
#endif /* #ifndef __HAL_LED_H__ */
```
###
上述代碼中使用到GPIO口B1,這是由于配套的開發板板載的LED是與STM32F030的B1連接的。如果開發者需要外接其它的LED,從而使用了其他的GPIO,那么直接修改此定義即可,例如修改為B2的代碼如下:
```
#define HAL_LED_PORT GPIOB
#define HAL_LED_PIN GPIO_Pin_2
```
<br/>
接著,在hal_led.c文件中,實現各個API,代碼如下:
```
#include "hal_led.h"
#include "stm32f0xx_gpio.h"
/*
*初始化LED燈,在使用LED燈前必須調用此函數
*/
void halLedInit()
{
GPIO_InitTypeDef ledGPIO;//定義配置
ledGPIO.GPIO_Mode = GPIO_Mode_OUT;//配置該GPIO為輸出模式
ledGPIO.GPIO_Speed = GPIO_Speed_2MHz;//速率為2MHz
ledGPIO.GPIO_Pin = HAL_LED_PIN;//引腳
RCC_AHBPeriphClockCmd(HAL_LED_CLOCK, ENABLE);//使用LED時鐘
GPIO_Init(HAL_LED_PORT, &ledGPIO);//初始化指定的GPIO
}
/*
*打開LED燈
*/
void halLedSetOn()
{
//置位指定的GPIO
GPIO_SetBits(HAL_LED_PORT, HAL_LED_PIN);
}
/*
*關閉LED燈
*/
void halLedSetOff()
{
//重置指定的GPIO
GPIO_ResetBits(HAL_LED_PORT, HAL_LED_PIN);
}
/*
*反轉LED燈
*/
void halLedToggle()
{
//讀取指定GPIO的狀態
uint8_t level;
level = GPIO_ReadInputDataBit(HAL_LED_PORT, HAL_LED_PIN);
if (level == 0)//如果為0,則表示該LED燈關閉
GPIO_SetBits(HAL_LED_PORT, HAL_LED_PIN);
else
GPIO_ResetBits(HAL_LED_PORT, HAL_LED_PIN);
}
```
其中的stm32f0xx\_gpio.h文件是由ST標準庫提供的頭文件,提供了GPIO相關的配置API。
<br/>
## **使用 HAL API**
編寫好LED的HAL后,LED的使用非常簡單。在配套工程的main.c文件中添加如下代碼:
```
#include "main.h"
#include "hal_led.h"
/*
* 延時函數
*/
static void delay()
{
for (uint32_t i = 0; i < 6553000; i++);
}
int main(void)
{
halLedInit();//初始化LED等
while (1)
{
halLedToggle();//反轉LED燈的狀態
delay();//延時
}
}
```
上述代碼在初始化LED后,不斷地反轉LED燈的狀態,達到了閃爍LED的效果。
<br/>
## **測試驗證**
1.編譯整個工程,生成Hex文件,如圖所示。

###
2.把該Hex文件燒錄到配套的開發板中,可以看到LED不斷地閃爍。
<br/>
<br/>
## **商務合作**
如有以下需求,可掃碼添加管理員好友,注明“**商務合作**”
* 項目定制開發,技術范圍:**NB-IoT**、**CATn(4G)**、**WiFi**、**ZigBee**、**BLE Mesh**以及**STM32**、**嵌入式Linux**等;
* 入駐平臺,成為講師;
* 接項目賺外快;
* 善學坊官網:[www.sxf-iot.com](https://www.sxf-iot.com/)

(非商務合作**勿擾**,此處**非**技術支持)
- 課程簡介
- 配套資源下載
- 配套開發套件簡介
- 簡介
- 硬件組成 & 技術參數
- STM32 Pro 主板 原理圖 & PCB圖
- STM32 Std 主板 原理圖 & PCB圖
- 板載設備使用說明
- STM32開發指南
- 1. 搭建開發環境
- 1.1 Keil MDK簡介與安裝
- 1.2 STM32 Pack 簡介與安裝
- 1.3 CH34x 驅動簡介與安裝
- 1.4 ISP 串口下載工具
- 1.5 串口調試工具
- 2. STM32 開發基礎
- 2.1 新建工程
- 2.2 實現第1個程序
- 2.3 Hex 文件燒錄詳解
- 3. 移植官方標準工程模板
- 4. GPIO實驗:LED
- 5. 系統延時應用
- 6. GPIO實驗:按鍵
- 7. GPIO中斷實驗——按鍵觸發實驗
- 8. 使用定時器TIM3
- 9. 串口通信實驗
- 10. ADC 實驗
- 11. OLED顯示器實驗
- 12. SDK 設計思想
- 13. SDK 架構解析
- 14. 多任務應用實驗
- 15. 輸入型任務:按鍵輸入實驗
- 16. 輸入型任務:串口接收實驗
- 17. 高精度溫濕度傳感器實驗
- 進階課程