小專案:藍芽模組點亮RGB三色燈

良许發表於2024-02-09

在之前的教程中,我們學習了藍芽模組的原理,並動手寫了驅動,實現了串列埠的接收和傳送。本次我們就來教大家如何使用藍芽串列埠控制燈。這是一個簡單的示例,展示瞭如何將藍芽通訊與硬體控制相結合,實現遠端控制的功能。你也可以擴充套件這個示例,新增更多的指令和功能,以滿足自己的需求。

1. 原始碼下載及前置閱讀

本文首發 良許嵌入式網https://www.lxlinux.net/e/ ,歡迎關注!

本文所涉及的原始碼及安裝包如下(由於平臺限制,請點選以下連結閱讀原文下載):

https://www.lxlinux.net/e/stm32/bluetooth-rgb-led.html

如果你是嵌入式開發小白,那麼建議你先讀讀下面幾篇文章。

前期教程,沒看過的小夥伴可以先看下。

2. 專案需求

實現目標是我們有一個三色 LED 燈,手機連上藍芽後,向藍芽串列埠傳送關鍵詞 green 則綠燈亮,再次傳送 green 則綠燈滅,黃燈和紅燈的關鍵詞是 yellow、red ,效果類似。

3. 程式設計實戰

3.1 硬體接線

本教程使用的硬體如下:

  • 微控制器:STM32F103C8T6
  • 藍芽模組:HC-08
  • 小燈:三色 LED 燈模組
  • 串列埠:USB 轉 TTL
  • 燒錄器:ST-LINK V2
HC-08LEDSTM32USB 轉 TTL
VCC 3.3
RXD A2
TXD A3
GND G
RA5
YA6
GA7
GNDG
A10TX
A9RX
GGND

燒錄的時候接線如下表,如果不會燒錄的話可以看我之前的文章 STM32下載程式的五種方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to...

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

接好如下圖:

3.2 LED邏輯程式碼

LED 燈的程式碼簡簡單單,只要進行一下三個燈的初始化就行。

void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED1_GPIO_CLK_ENABLE();                                 /* LED1時鐘使能 */
    LED2_GPIO_CLK_ENABLE();                                 /* LED2時鐘使能 */
    LED3_GPIO_CLK_ENABLE();                                 /* LED3時鐘使能 */

    gpio_init_struct.Pin = LED1_GPIO_PIN;                   /* LED1引腳 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推輓輸出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);       /* 初始化LED1引腳 */

    gpio_init_struct.Pin = LED2_GPIO_PIN;                   /* LED2引腳 */
    HAL_GPIO_Init(LED2_GPIO_PORT, &gpio_init_struct);       /* 初始化LED2引腳 */
    
    gpio_init_struct.Pin = LED3_GPIO_PIN;                   /* LED3引腳 */
    HAL_GPIO_Init(LED3_GPIO_PORT, &gpio_init_struct);       /* 初始化LED3引腳 */

    LED1(0);                                                /* 關閉 LED1 */
    LED2(0);                                                /* 關閉 LED2 */
    LED3(0);                                                /* 關閉 LED3 */
}

LED 的 .h檔案:

#ifndef _LED_H
#define _LED_H
#include "sys.h"


/******************************************************************************************/
/* 引腳 定義 */

#define LED1_GPIO_PORT                  GPIOA
#define LED1_GPIO_PIN                   GPIO_PIN_7
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口時鐘使能 */

#define LED2_GPIO_PORT                  GPIOA
#define LED2_GPIO_PIN                   GPIO_PIN_6
#define LED2_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口時鐘使能 */

#define LED3_GPIO_PORT                  GPIOA
#define LED3_GPIO_PIN                   GPIO_PIN_5
#define LED3_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PB口時鐘使能 */

/******************************************************************************************/
/* LED埠定義 */
#define LED1(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED2(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED3(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

/* LED取反定義 */
#define LED1_TOGGLE()   do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)        /* 翻轉LED1 */
#define LED2_TOGGLE()   do{ HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_GPIO_PIN); }while(0)        /* 翻轉LED2 */
#define LED3_TOGGLE()   do{ HAL_GPIO_TogglePin(LED3_GPIO_PORT, LED3_GPIO_PIN); }while(0)        /* 翻轉LED3 */

/******************************************************************************************/
/* 外部介面函式*/
void led_init(void);                                                                            /* LED初始化 */

#endif

3.3 藍芽收發

藍芽收發我們在【手把手教你玩轉藍芽模組(原理+驅動):https://www.lxlinux.net/e/stm32/bluetooth-turorial.html】有詳細教程,在這裡就簡單帶過+淺淺複習下,沒看過或者忘記了的小夥伴可以點選連結看看。

藍芽模組透過串列埠與 MCU 進行通訊,所以第一步需要先做好串列埠的配置。

關於串列埠的配置,我寫過一篇文章手把手教你玩串列埠,大家可以移步下文檢視:

【STM32串列埠接收不定長資料(接收中斷+超時判斷):https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-usin...

具體程式碼如下:

UART_HandleTypeDef bt_uart_handle;

uint8_t bt_uart_rx_buf[BT_RX_BUF_SIZE];
uint8_t bt_uart_tx_buf[BT_TX_BUF_SIZE];
uint16_t bt_uart_rx_len = 0;

void bt_init(uint32_t baudrate)
{
    bt_uart_handle.Instance          = BT_INTERFACE;                 /* BT */
    bt_uart_handle.Init.BaudRate     = baudrate;                     /* 波特率 */
    bt_uart_handle.Init.WordLength   = UART_WORDLENGTH_8B;           /* 資料位 */
    bt_uart_handle.Init.StopBits     = UART_STOPBITS_1;              /* 停止位 */
    bt_uart_handle.Init.Parity       = UART_PARITY_NONE;             /* 校驗位 */
    bt_uart_handle.Init.Mode         = UART_MODE_TX_RX;              /* 收發模式 */
    bt_uart_handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;          /* 無硬體流控 */
    bt_uart_handle.Init.OverSampling = UART_OVERSAMPLING_16;         /* 過取樣 */
    HAL_UART_Init(&bt_uart_handle);                                  /* 使能BT */
}

void bt_rx_clear(void)
{
    memset(bt_uart_rx_buf, 0, sizeof(bt_uart_rx_buf));              //清空接收緩衝區
    bt_uart_rx_len = 0;                                             //接收計數器清零
}

void BT_IRQHandler(void)
{
    uint8_t receive_data = 0;   
    if(__HAL_UART_GET_FLAG(&bt_uart_handle, UART_FLAG_RXNE) != RESET){      //獲取接收RXNE標誌位是否被置位
        if(bt_uart_rx_len >= sizeof(bt_uart_rx_buf))                        //如果接收的字元數大於接收緩衝區大小,
            bt_uart_rx_len = 0;                                             //則將接收計數器清零
        HAL_UART_Receive(&bt_uart_handle, &receive_data, 1, 1000);          //接收一個字元
        bt_uart_rx_buf[bt_uart_rx_len++] = receive_data;                    //將接收到的字元儲存在接收緩衝區
    }

    if (__HAL_UART_GET_FLAG(&bt_uart_handle, UART_FLAG_IDLE) != RESET)      //獲取接收空閒中斷標誌位是否被置位
    {
        printf("recv: %s\r\n", bt_uart_rx_buf);                             //將接收到的資料列印出來
        control_led();                                                      //檢測是否有LED關鍵詞
        bt_rx_clear();
        __HAL_UART_CLEAR_IDLEFLAG(&bt_uart_handle);                         //清除UART匯流排空閒中斷
    }
}

透過這幾個函式,我們就可以讀取藍芽返回的資料,並儲存在陣列 bt_uart_rx_buf 裡。

如果需要透過串列埠向藍芽模組傳送資料,可以使用下面函式:

void bt_send(char *fmt, ...)
{
    va_list ap;
    uint16_t len;
    
    va_start(ap, fmt);
    vsprintf((char *)bt_uart_tx_buf, fmt, ap);
    va_end(ap);
    
    len = strlen((const char *)bt_uart_tx_buf);
    HAL_UART_Transmit(&bt_uart_handle, bt_uart_tx_buf, len, HAL_MAX_DELAY);
}

其實是否向藍芽模組傳送資料並不影響我們的實現效果,留著的目的一方面為了讓大家複習一下,另一方面可以看出藍芽模組是否在正常工作。

至此,藍芽模組的初始化、傳送、接收部分就做好了。

3.4 LED控制

檢測藍芽串列埠是否接收到 LED 關鍵詞,如果有就反轉 LED 燈狀態。

void control_led()
{
    if(strstr((const char *)bt_uart_rx_buf, "green") != NULL)           //如果接收到關鍵詞"green"
        LED1_TOGGLE();                                                  // 翻轉LED1
    if(strstr((const char *)bt_uart_rx_buf, "yellow") != NULL)          //如果接收到關鍵詞"yellow"
        LED2_TOGGLE();                                                  // 翻轉LED2
    if(strstr((const char *)bt_uart_rx_buf, "red") != NULL)             //如果接收到關鍵詞"red"
        LED3_TOGGLE();                                                  // 翻轉LED3
}

3.5 主函式

在 main 函式里,我們可以先呼叫 bt_init() 函式進行初始化,然後呼叫 bt_send() 函式傳送資料。

int main(void)
{
    HAL_Init();                                 /* 初始化HAL庫 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 設定時鐘,72M */
    delay_init(72);                             /* 初始化延時函式 */
    usart_init(115200);                         /* 串列埠1波特率設為115200 */
    bt_init(9600);                              /* 串列埠2波特率設為9600 */
    led_init();

    printf("藍芽控制燈……\r\n");

    while(1)
    {
        bt_send("bt send\r\n");
        delay_ms(1000);
    }
}

4. 執行過程

將硬體連好,把串列埠插到電腦 USB 口。

接著我們開啟電腦串列埠軟體。設定串列埠助手波特率 115200(你們不一定要用我這款,隨便的串列埠助手都可以),選擇串列埠號,最後開啟串列埠開始準備接收資料。

這個串列埠工具接收的是 MCU 串列埠 1 的資料,也就是 log 。藍芽接收到資料後,我們使用串列埠 1 列印到下面的串列埠助手裡。

燒錄程式碼,串列埠輸出如下:

然後開啟手機藍芽助手準備開始除錯,(如果有提示下載彈窗的話,點選「下載好了」即可),點選藍芽模組開始連線。沒有藍芽助手的同學,可以在前文找到下載地址。

<img src="https://lxlinux.superbed.verylink.top/item/6577cbd2c458853aef969a92.jpg" alt="img" style="zoom: 50%;" />

<img src="https://lxlinux.superbed.verylink.top/item/6577cc42c458853aef980238.jpg" alt="img" style="zoom: 50%;" />

到這裡,我們就完成了 MCU 透過藍芽將資料透傳到手機 APP(藍芽助手)。

當然,我們也可以透過手機 APP 向藍芽傳送資料,MCU 接收到透傳的資料之後透過串列埠助手列印在電腦上。

比如我們給藍芽模組傳送資料 green 、yellow、red。

可以看到串列埠助手成功接收到了 green 、yellow、red,這些資料。

我們的三個小燈也開啟了。(我的小綠燈不是很亮,用舊了,嘻嘻)

再次傳送關鍵詞即可關對應的燈。當然,一次傳送 「green yellow red」,就可以控制三個小燈一起反轉。

總結

祝賀大家成功點燈!當然,除了控制燈的開關,藍芽串列埠還可以應用於更廣泛的場景,如個人電子裝置、智慧家居控制、健康醫療裝置等等。隨著技術的不斷進步,藍芽技術將持續演進,並在更多領域發揮作用。希望本文能夠為你提供了一個初步的瞭解,並激發你進一步深入研究和應用藍芽技術的興趣。感謝各位看官,love and peace!

另外,想進大廠的同學,一定要好好學演算法,這是面試必備的。這裡準備了一份 BAT 大佬總結的 LeetCode 刷題寶典,很多人靠它們進了大廠。

刷題 | LeetCode演算法刷題神器,看完 BAT 隨你挑!

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

推薦閱讀:

歡迎關注我的部落格:良許嵌入式教程網,滿滿都是乾貨!

相關文章