開箱實作NUC1263 MCU!探索新唐獨家LLSI ARGB LED控制介面!
連假前看到新唐在論壇介紹NUC1263這顆MCU,除了CPU是ARMv8的Cortex-M23之外,還有一個新唐專利硬體周邊LLSI,加上億光同學塞了他們新開發的神奇產品給我,腦袋裡面馬上冒出漂亮的應用,於是就上新唐線上商店下單一片NuMaker-NUC1263SD開發板和5顆晶片,沒想到隔天就到了~
新唐NUC1263系列產品帶給您炫彩的ARGB燈效控制 -LLSI介面簡易設計,方便好用-
不過作品還在設計當中,敬請期待。先來用現成的Adafruit NeoPixel Ring試玩一下NUC1263這顆MCU和它的LLSI吧!一樣會在GitHub和留言區提供完整測試程式碼喔!(看到專案名稱,知道我又要做什麼了吧XD)
https://github.com/danchouzhou/LLSIClock/blob/main/firmware/LLSI_RGBW/main.c
先幫讀者整理一下這顆NUC1263重點特色
- Arm Cortex-M23 72 MHz,ARMv8-M架構的CPU
- 內建64 KB Flash和20 KB SRAM
- 工作電壓2.5 V ~ 5.5 V
除了常見的UART、SPI、I2C、ADC、PWM之外,NUC1263還有I3C、USB和4個DAC,其中I3C還可以從獨立VDDIO電源接腳,輸入I3C匯流排上的電壓,讓I3C不用外接level shifter,最低就能支援0.95 V的電壓準位。不過最大的特色還是全新推出的專利獨家功能LLSI。
第一次聽到LLSI?
LLSI全稱是LED Light Strip Interface,其實就是專門為可定址LED (ARGB LED, addressable RGB LED) 設計的控制介面,常見的型號例如WS2812B、SK6812,或者是Adafruit推出的NeoPixel系列,這種介面的好處就是只要一條信號線從第一顆LED的DIN輸入,第一顆的DOUT串到第二顆的DIN,以此類推就可以控制整串的全彩LED。
由於只有一條信號線,ARGB LED的控制介面是利用信號high low時長不同,來代表資料的0和1。新唐將這種控制時序設計成硬體,取了酷酷的名字LLSI並整合進MCU裡面,成為首度推出有硬體控制ARGB LED能力的MCU公司,這也是新唐MCU才有的獨家功能。下圖節錄自NUC1263的手冊:
RGB動態燈光效果的市場趨勢
隨著RGB動態燈光效果的需求越來越受歡迎,已經可以在電競鍵盤、滑鼠、主機板、顯示卡、機殼、風扇、電源、散熱器、耳麥等數不清的電競周邊產品看見RGB動態光效華麗的應用,現在就連Windows 11的設定中,還直接內建調整燈光效果的選項。甚至是在一些電視背面、燈飾也陸續出現RGB LED,來營造各式各樣的居家氛圍。
控制ARGB LED
以往用MCU控制ARGB LED的方式,是去改變GPIO的輸出狀態,期間以NOP指令或是timer delay,以軟體方式刻出ARGB LED的時序。為了達到時序精度要求,在傳輸的過程中CPU不得做其它事情,還得把中斷關閉來保證資料能完整傳輸出去。例如之前porting到新唐MCU的NeoPixel Library即是如此,沒有辦法的辦法。
RGB動態光效越來越受歡迎之外,LED數量、花樣也是越變越多、越來越複雜,軟體刻時序的方式恐怕不再能滿足人們的胃口,韌體設計更是令工程師們頭疼。
世界首創、獨家功能、最新專利!LLSI的優勢
幸好新唐推出了LLSI,使用LLSI時只要將每顆LED色彩資料填入buffer內,硬體會自動幫我們把資料按照ARGB LED的時序丟出去,這就跟一些串列通訊介面的使用邏輯一樣。
NUC1263的LLSI提供了深度16-byte的FIFO buffer,且支援閾值中斷、DMA傳輸,這樣的設計騰出了CPU資源,讓CPU去做原本該做的工作,以及去算那些花花綠綠的燈效;內建的64 KB / 20 KB的記憶體,即使LED數量一多,記憶體還是很夠用!
開箱實作
介紹完LLSI了,接著讓我們深入韌體技術的部份吧!無論您習慣的是Keil MDK、IAR或是新唐自家的NuEclipse開發環境,都可以參考NuMaker-NUC1263SD的手冊來將開發環境設定起來。
https://www.nuvoton.com/export/resource-files/en-us--UM_NuMaker-NUC1263SD_EN_Rev1.00.pdf
用LLSI也能控制RGBW「四色」可定址LED
從NUC1263的手冊中可以發現,LLSI是為RGB三色的可定址LED所設計的,一次以3個bytes為單位送出資料,支援RGB和GRB兩種不同的排列順序。能控制一般的ARGB不稀奇,今天就來挑戰看看能不能用LLSI控制ARGB的變種型號,四色的ARGB”W”,來帶過實作的環節!
為了測試NUC1263的LLSI,從箱子裡挖出一直珍藏著捨不得用的Adafruit NeoPixel Ring - 24 x RGBW Natural White - ~4500K,都還記得是當初在美國念書,從當地的商店Micro Center花二十幾塊美金購買的。它除了常見的RGB三色之外額外多了一個白光,能比RGB調出來的白色有更好的演色性 (CRI)。
用LLSI分別點亮RGBW其中一個顏色
要調色、做效果首先就是要知道一顆LED中的R、G、B、W排列順序以及要怎麼單獨控制,所以就先從單一個顏色開始點亮吧!
LLSI的使用方式很簡單,只要這三行就能啟動LLSI硬體,我使用NUC1263的PC.5來當LLSI0的輸出。由於這次使用的是四色LED,並非LLSI原先設計的三色LED,因此數量的欄位要自己換算一下:
LLSI_PCNT = 24 / 3 x 4 = 32
- CLK_EnableModuleClock(LLSI0_MODULE); // Enable LLSI0 module clock
- // T_Period = 1250ns; T_T0H = 400ns; T_T1H = 850ns; T_ResetPeriod = 50000ns; LED count = 32
- LLSI_Open(LLSI0, LLSI_MODE_SW, LLSI_FORMAT_RGB, HCLK_CLK, 1250, 400, 850, 50000, 0, LLSI_IDLE_LOW);
- SET_LLSI0_OUT_PC5(); // Set PC.5 multi-function pins for LLSI0
下面則是分別點亮四顆LED其中的R、G、B、W,每個顏色占用8-bit,測試後可得知排列順序為0xWWBBRRGG。不過最後為什麼還多送了一筆0x00000000呢?正如剛剛提到「LLSI是為RGB三色的可定址LED所設計的,一次以3個bytes為單位送出資料」,四個RGBW一共16-byte無法整除3,LLSI會認為資料尚未備齊,不會將最後一顆的白光給送出去。所以才要填入第五筆資料「騙」它,將最後一顆的資料送出去。
- // 0xWWBBRRGG
- LLSI_WRITE_DATA(LLSI0, 0x000000FF);
- LLSI_WRITE_DATA(LLSI0, 0x0000FF00);
- LLSI_WRITE_DATA(LLSI0, 0x00FF0000);
- LLSI_WRITE_DATA(LLSI0, 0xFF000000);
- LLSI_WRITE_DATA(LLSI0, 0x00000000);
用LLSI製作跑馬燈效果
幸好這次用的NeoPixel Ring是24顆RGBW,是3和4的公倍數,在LLSI_Open()時將LED數量填入32,製作RGBW跑馬燈效果就不用額外去處理。要玩效果,程式的架構就不再是一顆一顆點了,而是變成這樣:
- 開一個陣列g_au32frameBuffer[]當成”影格”
- 開啟閾值中斷,當LLSI FIFO中的內容低過設定的數量,通知CPU將接下來的資料補充進去
- 開啟”影格”結束中斷,當所有LED的資料都傳送完畢產生中斷,將旗標g_u32FrameEnd設為’1’,並更新圖案樣式g_u32PatternToggle
- 於主程式中判斷旗標,當前一”偵”傳送完畢,即可運算新的圖案更新至”影格中”
從這段範例就可以明顯感受到使用LLSI控制ARGB LED的便利性了,將圖案填入g_au32frameBuffer之後,由LLSI硬體自動產生時序,也會在FIFO低於下限時硬體自動產生中斷,通知CPU補充彈匣!保留了許多CPU資源。
透過下面三行程式碼,可以開啟上述兩種中斷功能:
- /* Set TX FIFO threshold */
- LLSI_SetFIFO(LLSI0, 2);
- NVIC_EnableIRQ(LLSI0_IRQn);
- /* Enable Transmit FIFO Threshold Interrupt and Frame End Interrupt */
- LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK | LLSI_FEND_INT_MASK);
中斷服務程式:
- void LLSI0_IRQHandler()
- {
- if (LLSI_GetIntFlag(LLSI0, LLSI_TXTH_INT_MASK))
- {
- while(LLSI_GET_TX_FIFO_FULL_FLAG(LLSI0) == 0) // Fill up the TX FIFO
- {
- if (g_u32DataCount == (TEST_COUNT - 1))
- LLSI_SET_LAST_DATA(LLSI0); // Before writing last word data to LLSI_DATA, user must write LDT = 1
- if (g_u32DataCount < TEST_COUNT)
- LLSI_WRITE_DATA(LLSI0, g_au32frameBuffer[g_u32DataCount++]);
- else
- break;
- }
- if(g_u32DataCount >= TEST_COUNT)
- {
- LLSI_DisableInt(LLSI0, LLSI_TXTH_INT_MASK);
- }
- }
- if (LLSI_GetIntFlag(LLSI0, LLSI_FEND_INT_MASK)) // FENDIF will be set to 1 when LLSI transfer last pixel data.
- {
- g_u32FrameEnd = 1;
- g_u32PatternToggle++;
- LLSI_ClearIntFlag(LLSI0, LLSI_FEND_INT_MASK);
- }
- }
主程式更新圖案的算法:
- if (g_u32FrameEnd == 1) // Wait for FEND_INT
- {
- u32Tmp = g_u32PatternToggle % 4;
- /* Generate a new frame */
- for (int i=0; i<TEST_COUNT; i++)
- {
- if (i % 4 == u32Tmp)
- g_au32frameBuffer[i] = 0xFF000000 >> (u32Tmp * 8);
- else
- g_au32frameBuffer[i] = 0x00000000;
- }
- g_u32FrameEnd = 0;
- g_u32DataCount = 0;
- LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK);
- CLK_SysTickDelay(50000);
- }
成果展示影片
文章最後邀請讀者加入新唐論壇,除了能一起參與技術交流之外,還能領到新唐原廠線上商店的9折優惠券,想玩玩看最新的LLSI獨家專利技術嗎?敬請把握機會了!
https://forum.nuvoton.com/zh/programs-events/seminars-events/14354
完整程式碼:
- #include <stdio.h>
- #include "NuMicro.h"
- #define HCLK_CLK 72000000
- #define TEST_COUNT 24
- // Color format is 0xWWBBRRGG for the Adafruit NeoPixel Ring 24 x RGBW (2862)
- volatile uint32_t g_au32frameBuffer[24] = {
- 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
- 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
- 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
- 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
- };
- volatile uint32_t g_u32PatternToggle = 0;
- volatile uint32_t g_u32DataCount = 0;
- volatile uint32_t g_u32FrameEnd = 0;
- void LLSI0_IRQHandler()
- {
- if (LLSI_GetIntFlag(LLSI0, LLSI_TXTH_INT_MASK))
- {
- while(LLSI_GET_TX_FIFO_FULL_FLAG(LLSI0) == 0) // Fill up the TX FIFO
- {
- if (g_u32DataCount == (TEST_COUNT - 1))
- LLSI_SET_LAST_DATA(LLSI0); // Before writing last word data to LLSI_DATA, user must write LDT = 1
- if (g_u32DataCount < TEST_COUNT)
- LLSI_WRITE_DATA(LLSI0, g_au32frameBuffer[g_u32DataCount++]);
- else
- break;
- }
- if(g_u32DataCount >= TEST_COUNT)
- {
- LLSI_DisableInt(LLSI0, LLSI_TXTH_INT_MASK);
- }
- }
- if (LLSI_GetIntFlag(LLSI0, LLSI_FEND_INT_MASK)) // FENDIF will be set to 1 when LLSI transfer last pixel data.
- {
- g_u32FrameEnd = 1;
- g_u32PatternToggle++;
- LLSI_ClearIntFlag(LLSI0, LLSI_FEND_INT_MASK);
- }
- }
- void SYS_Init(void)
- {
- /*---------------------------------------------------------------------------------------------------------*/
- /* Init System Clock */
- /*---------------------------------------------------------------------------------------------------------*/
- /* Enable HIRC clock */
- CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);
- /* Wait for HIRC clock ready */
- CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);
- /* Set core clock to HCLK_CLK Hz */
- CLK_SetCoreClock(HCLK_CLK);
- /* Enable UART0 module clock */
- CLK_EnableModuleClock(UART0_MODULE);
- /* Select UART0 module clock source as HIRC/2 and UART0 module clock divider as 1 */
- CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC_DIV2, CLK_CLKDIV0_UART0(1));
- /* Enable LLSI0 module clock */
- CLK_EnableModuleClock(LLSI0_MODULE);
- /*---------------------------------------------------------------------------------------------------------*/
- /* Init I/O Multi-function */
- /*---------------------------------------------------------------------------------------------------------*/
- /* Set PB multi-function pins for UART0 RXD and TXD */
- SYS->GPB_MFPH = (SYS->GPB_MFPH & (~(UART0_RXD_PB12_Msk | UART0_TXD_PB13_Msk))) | UART0_RXD_PB12 | UART0_TXD_PB13;
- /* Set PC multi-function pins for LLSI0 */
- SET_LLSI0_OUT_PC5();
- }
- void UART0_Init()
- {
- /*---------------------------------------------------------------------------------------------------------*/
- /* Init UART */
- /*---------------------------------------------------------------------------------------------------------*/
- /* Reset UART0 module */
- SYS_ResetModule(UART0_RST);
- /* Configure UART0 and set UART0 Baudrate */
- UART_Open(UART0, 115200);
- }
- void LLSI_Init(void)
- {
- /*---------------------------------------------------------------------------------------------------------*/
- /* Init LLSI */
- /*---------------------------------------------------------------------------------------------------------*/
- /* Configure as software mode, RGB output format, 6 pixels in a frame and idle output low */
- /* Set clock divider. LLSI clock rate = 72MHz */
- /* Set data transfer period. T_Period = 1250ns */
- /* Set duty period. T_T0H = 400ns; T_T1H = 850ns */
- /* Set reset command period. T_ResetPeriod = 50000ns */
- LLSI_Open(LLSI0, LLSI_MODE_SW, LLSI_FORMAT_RGB, HCLK_CLK, 1250, 400, 850, 50000, 32, LLSI_IDLE_LOW);
- /* Set TX FIFO threshold */
- LLSI_SetFIFO(LLSI0, 2);
- /* Enable reset command function */
- LLSI_ENABLE_RESET_COMMAND(LLSI0);
- NVIC_EnableIRQ(LLSI0_IRQn);
- }
- void main(void)
- {
- uint32_t u32Tmp;
- /* Unlock protected registers */
- SYS_UnlockReg();
- /* Init System, IP clock and multi-function I/O. */
- SYS_Init();
- /* Lock protected registers */
- SYS_LockReg();
- /* Init UART0 for printf */
- UART_Open(UART0, 115200);
- printf("\n\nCPU @ %d Hz\n", SystemCoreClock);
- /* Init LLSI */
- LLSI_Init();
- /* Enable Transmit FIFO Threshold Interrupt and Frame End Interrupt */
- LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK | LLSI_FEND_INT_MASK);
- LLSI_WRITE_DATA(LLSI0, g_au32frameBuffer[g_u32DataCount++]); // Write the first word to trigger TXTH_INT
- while(1)
- {
- if (g_u32FrameEnd == 1) // Wait for FEND_INT
- {
- u32Tmp = g_u32PatternToggle % 4;
- /* Generate a new frame */
- for (int i=0; i<TEST_COUNT; i++)
- {
- if (i % 4 == u32Tmp)
- g_au32frameBuffer[i] = 0xFF000000 >> (u32Tmp * 8);
- else
- g_au32frameBuffer[i] = 0x00000000;
- }
- g_u32FrameEnd = 0;
- g_u32DataCount = 0;
- LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK);
- CLK_SysTickDelay(50000);
- }
- }
- }
留言
張貼留言