博客專欄

        EEPW首頁 > 博客 > 按這個步驟 STM32即可完美控制 NeoPixels

        按這個步驟 STM32即可完美控制 NeoPixels

        發布人:12345zhi 時間:2023-10-12 來源:工程師 發布文章

        問:玩轉STM32 - 使用 STM32 來控制 NeoPixels

        目前,諸如 Arduino 和 Feather 等高級開發平臺已經提供了出色的支持,可以通過易于使用的庫和普遍使用的示例代碼與NeoPixel LED 、燈帶、矩陣 等相連接。然而,更高級的平臺(例如 STM32 開發板 )通常缺乏相同水平的支持。因此,希望將NeoPixels整合到項目中的 開發人員需要全面了解NeoPixel通信協議以及如何克服它所帶來的挑戰。

        圖片

        NeoPixels

        Adafruit 推出的極受歡迎的可尋址全彩LED燈“NeoPixels”系列分為RGB和RGBW兩個種類。盡管二者都將紅、綠和藍色LED與驅動器芯片相集成,但RGBW組件還集成了第四個純白色的LED??梢允褂妙愃频膯尉€串行接口來控制這兩種類型的NeoPixel,其時間值和數據結構僅存在微小的差異。

        WS2812

        RGB NeoPixels實際上是WS2812智能控制LED,包括數據信號輸入引腳(DIN)和數據信號輸出引腳(DOUT)。這允許多個LED級聯并且只用一個數據線進行控制。鏈中的第一個LED負責處理從MCU接收到的前三個字節數據,然后將后續的數據簡單地轉發給DOUT引腳,該引腳可以連接到另一個LED的DIN引腳。LED將以此方式繼續向下傳遞數據,直到它們接收到復位信號為止(即,DIN線在一段時間內持續保持低電平狀態)。傳輸的字節按照圖1所示的協議進行組織。第一個字節(G7-G0)表示綠色LED的8位PWM強度,其中0x00是完全關閉,0xFF是完全打開。類似地,第二個字節(R7-R0)用于控制紅色LED的強度,第三個字節(B7-B0)用于控制藍色LED的強度。

        圖片

        圖 1 : WS2812 LED的3字節數據協議的結構

        這些24位數據都是通過改變方波的脈沖寬度來進行編碼的,如圖2所示。請注意,無論發送代碼0還是代碼1,方波的周期仍保持在1.25μs。對于WS2812,使數據線保持低電平至少50μs即可生成復位信號。另請注意,圖2中顯示的計時值具有±0.15μs的公差。

        圖片

        圖2:WS2812 LED的0和1位的計時圖

        一種截然不同的組件,NeoPixels的RGBW種類實際上是SK6812智能控制LED,采用與WS2812 LED相同的運作原理。然而,由于它們包含第四個LED,因此實施了圖3所示的4字節數據協議。與圖1相比,唯一的區別在于數據的串聯字節(W7-W0),該字節指定了白色LED的8位PWM強度。

        圖片

        圖 3 : SK6812 LED的4字節數據協議的結構。

        圖4展示了SK6812控制信號的時間值,同樣與WS2812略有差別(不過仍在±0.15μs的公差范圍內)。請注意,這兩種代碼的方波周期均保持不變,都為1.2μs。此外,SK6812的復位信號長度為80μs ,而非50μs。

        圖片

        圖4:SK6812 LED的0位和1位的計時圖。

        步驟

        由于NeoPixel的控制信號對計時要求非常嚴格,因此除非使用匯編語言,否則無法通過簡單的比特帶寬方法產生此信號。雖然還有許多其他方法可以利用各種MCU外設、外部硬件或其組合來生成該信號,但其中最直接的方法是配置MCU定時器來生成PWM輸出信號。這是因為,如上一部分中所述,NeoPixel控制信號只是一種固定頻率的PWM信號,采用不同的占空比表示0位和1位。為了以與傳輸協議相同的速率高效地在這兩個占空比之間進行切換,還必須配置DMA流來管理更新。盡管這種方法可能是內存效率最低的方式,但它易于理解、CPU高效并且易于實施(得益于STM32Cube環境)。

        以下應用程式利用STM32CubeIDE(版本1.8.0)、NUCLEO-F401RE開發板和RGBW 5x8 NeoPixel Shield實現上述的方法。不過,這些步驟可以輕松地推廣到任何STM32 MCU/板和NeoPixel產品上。假定我們已經創建了一個STM32CubeIDE項目。如需使用其他IDE,你可以改為使用獨立的STM32CubeMX代碼配置器工具,將項目導出到所需的開發平臺上。

        1.配置PWM

        a. 先打開STM32CubeMX配置.ioc 文件(如果還未打開的話)。隨后,STM32CubeIDE將切換到*器件配置工具(*Device Configuration Tool ) 視圖,供你配置MCU。

        b. 將定時器通道備用功能分配給選定的GPIO引腳,以與NeoPixel進行連接。所選定時器通道應該能夠生成PWM輸出。圖5顯示了我的項目中的相關部分,我選擇了引腳PB10,并將它分配給定時器2、通道3(TIM2_CH3)功能。

        圖片

        圖5:將連接到DIN的GPIO引腳配置為定時器通道

        c. 從左側的組件列表中選擇上一步中確定的定時器外設,以打開模式和配置(*Mode and Configuration ) 面板。在模式(*Mode ) 面板中,選擇“內部時鐘”作為時鐘源,并從適當的定時器通道的下拉列表中選擇“PWM生成CHx”。在圖6中,定時器2、通道3已設為“PWM生成CH3”模式,因為我在上一步中選擇了TIM2_CH3備用功能。請注意,在完成此步驟后,關聯的GPIO引腳應在引腳排列視圖中從黃色變為綠色。

        d. 在定時器的*配置(*Configuration ) 面板中,驗證“預分頻器”和“脈沖”值是否都設置為0。計數器周期,即自動重載寄存器(ARR),需要進行設置以得到所需的PWM周期(如果使用RGB WS2812 LED,則為1.25μs;如果使用RGBW SK6812 LED,則為1.2μs)。這將取決于定時器外設輸入的速率。只需將所需的PWM周期除以時鐘周期,并減去1即可得到此值(減去1是因為定數器從0開始)。就我的器件而言,該公式得出的ARR值為99.8,我將其四舍五入為100(圖6)。請參見下文,了解有關計算理想ARR值的詳細說明。

        圖片

        圖6:將所選定時器通道配置為PWM輸出

        計算ARR值

        假設定時器“預分頻器”值設為0,可以很容易的計算出ARR值

        圖片

        具體來說,ARR值等于PWM信號周期除以定時器外設的時鐘信號周期。我們知道,根據使用的NeoPixel類型不同,TPWM可以是1.25μs或1.2μs(例如本例中,TPWM=1.2μs)。要確定Ttimer,你需要查閱器件的規格書,確定定時器外設連接到哪個總線。規格書可以在ST的網站上找到或STM32CubeIDE會隨附提供:選擇幫助>目標器件文檔和資源( Help > Target Device Docs and Resources ) 。然后,在MCU 選項卡下選擇規格書,如圖7所示。

        圖片

        圖 7 : 查找器件規格書

        在我使用的MCU(STM32F401RE)規格書中,器件框圖中顯示我的定時器(TIM2)已連接到APB1(見圖8)。

        圖片

        圖 8 : STM32F401xD/xE的部分框圖(源自DS10086)

        圖9介紹了:通過切換到STM32CubeIDE中的*時鐘配置(*Clock Configuration)選項卡,我們可以發現TIM2的時鐘頻率為84MHz

        圖片

        圖片

        圖 9 : 確定定時器時鐘頻率

        因此,

        圖片

        為了使PWM周期盡可能接近NeoPixel控制信號的周期,我們四舍五入至最接近的整數并得到 ARR=100 。

        2.配置DMA

        a. 從組件列表中選擇DMA外設。

        b. 在配置(Configuration) 面板的DMA1 選項卡下,點擊添加 ( Add ) 按鈕。在下拉菜單中,選擇你的定時器/通道組合。在我的項目中,我選擇了“TIM2_CH3/UP”。

        c. 針對該新的DMA請求,將方向改為“內存到外設”。

        d. 同時,將優先級改為“非常高”。

        e. 驗證默認的DMA請求設置是否與圖10中顯示的相匹配。

        f. 保存.ioc 文件,以生成項目代碼。

        圖片

        圖 10 : 配置DMA流,以便有效更新PWM信號的占空比

        3.編寫代碼

        在main.c 文件中,按從上到下的順序編寫,本部分展示了一個簡單的示例應用,用于測試NeoPixel LED的全彩能力。此處提供了兩個版本的main() 函數,一個用于RGB WS2818 LED,另一個用于RGBW SK6812 LED。

        a. 在main.c 文件的私有typedef部分,你可以創建一個新的數據類型,以便輕松訪問單個LED顏色值以及整個NeoPixel數據結構(如圖1和圖3所示)。列表1提供了RGB和RGBW NeoPixel組件的typedef。此代碼應粘貼在/* USER CODE BEGIN PTD */ 和/* USER CODE END PTD */ 注釋之間。

        列表 1 : 為RGB WS2812和RGBW SK6812 LED自定義數據類型

        typedef union

        {

        struct

        {

        uint8_t b;

          uint8_t r;

        uint8_t g;

        } color;

        uint32_t data;

        } PixelRGB_t;

        typedef union

        {

        struct

        {

        uint8_t w;

        uint8_t b;

        uint8_t r;

        uint8_t g;

        } color;

        uint32_t data;

        } PixelRGBW_t;

        b. 更改“脈沖”寄存器(也稱為CCRx)的值,這樣可以改變PWM波形的占空比。因此,我們必須計算適當的CCRx值,以實現使用的NeoPixels所需的代碼0和代碼1方波(無論是在圖2還是圖4中所示的那些)。對于RGB WS2812 LED,這些值計算如下:

        ZERO=(ARR+1)(0.32)

        ONE=(ARR+1)(0.64)

        對于RGBW SK6812 LED,其計算過程稍有不同。

        ZERO=(ARR+1)(0.25)

        ONE=(ARR+1)(0.5)

        當然,這些計算出的值應該四舍五入到最接近的整數。在 main.c 文件的私有定義部分,為每個值創建一個#define指令(請參見以下圖11中的示例)。

        c. 除了CCRx值之外,還應在私有定義部分中定義控制的NeoPixel LED數量和DMA緩沖區大小。如圖11所示,只需將LED的數量乘以相應的NeoPixel數據結構中的位數即可(回想圖1和圖3)。還必須分配一個額外的緩沖區元素,因為最后一個CCRx值應為零(復位信號)。

        圖片

        圖 11 : WS2812和SK6812 LED的私有定義

        d. 將列表2中提供的DMA完成回調函數添加到/* USER CODE BEGIN 0 /和/ USER CODE END 0*/之間的私有用戶代碼部分。務必將 TIM_CHANNEL_x 更改為步驟1c中配置的通道。

        列表 2 : HAL_TIM_PWM_PulseFinishedCallback() 函數的實施

        void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)

        {

        HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_x);

        }

        e. 最后,必須將應用代碼添加到main() 函數中。列表3提供了一個使用WS2812 LED的示例main() 函數,而列表4提供了使用SK6812 LED的類似示例main() 函數。請注意,HAL_TIM_PWM_Start_DMA() 函數的TIM_CHANNEL_x 參數必須再次進行修改,以匹配步驟1c中配置的通道。

        列表 3 : RGB WS2812 LED的示例main() 函數

        int main(void)

        {

        /* USER CODE BEGIN 1 */

        PixelRGB_t pixel[NUM_PIXELS] = {0};

        uint32_t dmaBuffer[DMA_BUFF_SIZE] = {0};

        uint32_t *pBuff;

        int i, j, k;

        uint16_t stepSize;

        /* USER CODE END 1 */

        /* MCU Configuration--------------------------------------------------------*/

        /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

        HAL_Init();

        /* USER CODE BEGIN Init */

        /* USER CODE END Init */

        /* Configure the system clock */

        SystemClock_Config();

        /* USER CODE BEGIN SysInit */

        /* USER CODE END SysInit */

        /* Initialize all configured peripherals */

        MX_GPIO_Init();

        MX_USART2_UART_Init();

        MX_DMA_Init();

        MX_TIM2_Init();

        /* USER CODE BEGIN 2 */

        /* USER CODE END 2 */

        /* Infinite loop */

        /* USER CODE BEGIN WHILE */

        k = 0;

        stepSize = 4;

        while (1)

        {

        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */

        for (i = (NUM_PIXELS - 1); i > 0; i--)

        {

        pixel[i].data = pixel[i-1].data;

        }

        if (k < 255)

        {

        pixel[0].color.g = 254 - k; //[254, 0]

        pixel[0].color.r =  k + 1;  //[1, 255]

        pixel[0].color.b = 0;

        }

        else if (k < 510)

        {

        pixel[0].color.g = 0;

        pixel[0].color.r = 509 - k; //[254, 0]

        pixel[0].color.b = k - 254; //[1, 255]

        j++;

        }

        else if (k < 765)

        {

        pixel[0].color.g = k - 509; //[1, 255];

        pixel[0].color.r = 0;

        pixel[0].color.b = 764 - k; //[254, 0]

        }

        k = (k + stepSize) % 765;

        // not so bright

        pixel[0].color.g >>= 2;

        pixel[0].color.r >>= 2;

        pixel[0].color.b >>= 2;

        pBuff = dmaBuffer;

        for (i = 0; i < NUM_PIXELS; i++)

        {

        for (j = 23; j >= 0; j--)

        {

        if ((pixel[i].data >> j) & 0x01)

        {

        *pBuff = NEOPIXEL_ONE;

        }

        else

        {

        *pBuff = NEOPIXEL_ZERO;

        }

        pBuff++;

        }

        }

        dmaBuffer[DMA_BUFF_SIZE - 1] = 0; // last element must be 0!

        HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_x, dmaBuffer, DMA_BUFF_SIZE);

        HAL_Delay(10);

        }

        /* USER CODE END 3 */

        }

        列表 4 : RGBW SK6812 LED的示例main() 函數

        int main(void)

        {

        /* USER CODE BEGIN 1 */

        PixelRGBW_t pixel[NUM_PIXELS] = {0};

        uint32_t dmaBuffer[DMA_BUFF_SIZE] = {0};

        uint32_t *pBuff;

        int i, j, k;

        uint16_t stepSize;

        /* USER CODE END 1 */

        /* MCU Configuration--------------------------------------------------------*/

        /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

        HAL_Init();

        /* USER CODE BEGIN Init */

        /* USER CODE END Init */

        /* Configure the system clock */

        SystemClock_Config();

        /* USER CODE BEGIN SysInit */

        /* USER CODE END SysInit */

        /* Initialize all configured peripherals */

        MX_GPIO_Init();

        MX_USART2_UART_Init();

        MX_DMA_Init();

        MX_TIM2_Init();

        /* USER CODE BEGIN 2 */

        /* USER CODE END 2 */

        /* Infinite loop */

        /* USER CODE BEGIN WHILE */

        k = 0;

        stepSize = 4;

        while (1)

        {

        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */

        for (i = (NUM_PIXELS - 1); i > 0; i--)

        {

        pixel[i].data = pixel[i-1].data;

        }

        if (k < 255)

        {

        pixel[0].color.g = 254 - k; //[254, 0]

        pixel[0].color.r =  k + 1; //[1, 255]

        pixel[0].color.b = 0;

        pixel[0].color.w = 0;

        }

        else if (k < 510)

        {

        pixel[0].color.g = 0;

        pixel[0].color.r = 509 - k; //[254, 0]

        pixel[0].color.b = k - 254; //[1, 255]

        pixel[0].color.w = 0;

        j++;

        }

        else if (k < 765)

        {

        pixel[0].color.g = 0;

        pixel[0].color.r = 0;

        pixel[0].color.b = 764 - k; //[254, 0]

        pixel[0].color.w = k - 509; //[1, 255]

        }

        else if (k < 1020)

        {

        pixel[0].color.g = k - 764; //[1, 255]

        pixel[0].color.r = 0;

        pixel[0].color.b = 0;

        pixel[0].color.w = 1019 - k; //[254, 0]

        }

        k = (k + stepSize) % 1020;

        // 50% brightness

        pixel[0].color.g >>= 2;

        pixel[0].color.r >>= 2;

        pixel[0].color.b >>= 2;

        pixel[0].color.w >>= 2;

        pBuff = dmaBuffer;

        for (i = 0; i < NUM_PIXELS; i++)

        {

        for (j = 31; j >= 0; j--)

        {

        if ((pixel[i].data >> j) & 0x01)

        {

        *pBuff = NEOPIXEL_ONE;

        }

        else

        {

        *pBuff = NEOPIXEL_ZERO;

        }

        pBuff++;

        }

        }

        dmaBuffer[DMA_BUFF_SIZE - 1] = 0; // last element must be 0!

        HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_x, dmaBuffer, DMA_BUFF_SIZE);

        HAL_Delay(10);

        }

        /* USER CODE END 3 */

        }

        該項目現在應該能夠成功構建,并支持你在器件上運行代碼了。

        結論

        使用邏輯分析儀捕獲了上面提供的RGB和RGBW配置生成的控制信號。分別如圖12和圖13中所示。請注意,它們與圖2和圖4中指定的預期輸出相匹配。

        圖片

        圖 12 : 生成的WS2812控制信號(正在發送0b0011……)

        圖片

        圖 13 : 生成的SK6812控制信號(正在發送0b0010……)

        *博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。



        關鍵詞: STM32 NeoPixels

        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 密云县| 天镇县| 永兴县| 涿鹿县| 赞皇县| 舞阳县| 枞阳县| 阿合奇县| 东阿县| 湘乡市| 花莲市| 盱眙县| 铁岭县| 琼中| 江津市| 易门县| 靖远县| 北安市| 崇礼县| 乌拉特后旗| 万荣县| 扎赉特旗| 东兰县| 麻城市| 三河市| 博客| 贺州市| 郎溪县| 历史| 广安市| 明水县| 平昌县| 秦皇岛市| 金门县| 思茅市| 中西区| 彭水| 泊头市| 积石山| 徐汇区| 衡水市|