開箱實作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

  1. CLK_EnableModuleClock(LLSI0_MODULE); // Enable LLSI0 module clock
  2. // T_Period = 1250ns; T_T0H = 400ns; T_T1H = 850ns; T_ResetPeriod = 50000ns; LED count = 32
  3. LLSI_Open(LLSI0, LLSI_MODE_SW, LLSI_FORMAT_RGB, HCLK_CLK, 1250, 400, 850, 50000, 0, LLSI_IDLE_LOW);
  4. 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會認為資料尚未備齊,不會將最後一顆的白光給送出去。所以才要填入第五筆資料「騙」它,將最後一顆的資料送出去。

  1. // 0xWWBBRRGG
  2. LLSI_WRITE_DATA(LLSI0, 0x000000FF);
  3. LLSI_WRITE_DATA(LLSI0, 0x0000FF00);
  4. LLSI_WRITE_DATA(LLSI0, 0x00FF0000);
  5. LLSI_WRITE_DATA(LLSI0, 0xFF000000);
  6. LLSI_WRITE_DATA(LLSI0, 0x00000000);

用LLSI製作跑馬燈效果

幸好這次用的NeoPixel Ring是24顆RGBW,是3和4的公倍數,在LLSI_Open()時將LED數量填入32,製作RGBW跑馬燈效果就不用額外去處理。要玩效果,程式的架構就不再是一顆一顆點了,而是變成這樣:

  1. 開一個陣列g_au32frameBuffer[]當成”影格”
  2. 開啟閾值中斷,當LLSI FIFO中的內容低過設定的數量,通知CPU將接下來的資料補充進去
  3. 開啟”影格”結束中斷,當所有LED的資料都傳送完畢產生中斷,將旗標g_u32FrameEnd設為’1’,並更新圖案樣式g_u32PatternToggle
  4. 於主程式中判斷旗標,當前一”偵”傳送完畢,即可運算新的圖案更新至”影格中”

從這段範例就可以明顯感受到使用LLSI控制ARGB LED的便利性了,將圖案填入g_au32frameBuffer之後,由LLSI硬體自動產生時序,也會在FIFO低於下限時硬體自動產生中斷,通知CPU補充彈匣!保留了許多CPU資源。

透過下面三行程式碼,可以開啟上述兩種中斷功能:

  1. /* Set TX FIFO threshold */
  2. LLSI_SetFIFO(LLSI0, 2);
  3. NVIC_EnableIRQ(LLSI0_IRQn);
  4. /* Enable Transmit FIFO Threshold Interrupt and Frame End Interrupt */
  5. LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK | LLSI_FEND_INT_MASK);

中斷服務程式:

  1. void LLSI0_IRQHandler()
  2. {
  3. if (LLSI_GetIntFlag(LLSI0, LLSI_TXTH_INT_MASK))
  4. {
  5. while(LLSI_GET_TX_FIFO_FULL_FLAG(LLSI0) == 0) // Fill up the TX FIFO
  6. {
  7. if (g_u32DataCount == (TEST_COUNT - 1))
  8. LLSI_SET_LAST_DATA(LLSI0); // Before writing last word data to LLSI_DATA, user must write LDT = 1
  9. if (g_u32DataCount < TEST_COUNT)
  10. LLSI_WRITE_DATA(LLSI0, g_au32frameBuffer[g_u32DataCount++]);
  11. else
  12. break;
  13. }
  14.  
  15. if(g_u32DataCount >= TEST_COUNT)
  16. {
  17. LLSI_DisableInt(LLSI0, LLSI_TXTH_INT_MASK);
  18. }
  19. }
  20.  
  21. if (LLSI_GetIntFlag(LLSI0, LLSI_FEND_INT_MASK)) // FENDIF will be set to 1 when LLSI transfer last pixel data.
  22. {
  23. g_u32FrameEnd = 1;
  24. g_u32PatternToggle++;
  25. LLSI_ClearIntFlag(LLSI0, LLSI_FEND_INT_MASK);
  26. }
  27. }

主程式更新圖案的算法:

  1. if (g_u32FrameEnd == 1) // Wait for FEND_INT
  2. {
  3. u32Tmp = g_u32PatternToggle % 4;
  4. /* Generate a new frame */
  5. for (int i=0; i<TEST_COUNT; i++)
  6. {
  7. if (i % 4 == u32Tmp)
  8. g_au32frameBuffer[i] = 0xFF000000 >> (u32Tmp * 8);
  9. else
  10. g_au32frameBuffer[i] = 0x00000000;
  11. }
  12.  
  13. g_u32FrameEnd = 0;
  14. g_u32DataCount = 0;
  15.  
  16. LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK);
  17. CLK_SysTickDelay(50000);
  18. }

成果展示影片

文章最後邀請讀者加入新唐論壇,除了能一起參與技術交流之外,還能領到新唐原廠線上商店的9折優惠券,想玩玩看最新的LLSI獨家專利技術嗎?敬請把握機會了!

https://forum.nuvoton.com/zh/programs-events/seminars-events/14354

完整程式碼:

  1. #include <stdio.h>
  2. #include "NuMicro.h"
  3.  
  4. #define HCLK_CLK 72000000
  5. #define TEST_COUNT 24
  6.  
  7. // Color format is 0xWWBBRRGG for the Adafruit NeoPixel Ring 24 x RGBW (2862)
  8. volatile uint32_t g_au32frameBuffer[24] = {
  9. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  10. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  11. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  12. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
  13. };
  14. volatile uint32_t g_u32PatternToggle = 0;
  15. volatile uint32_t g_u32DataCount = 0;
  16. volatile uint32_t g_u32FrameEnd = 0;
  17.  
  18. void LLSI0_IRQHandler()
  19. {
  20. if (LLSI_GetIntFlag(LLSI0, LLSI_TXTH_INT_MASK))
  21. {
  22. while(LLSI_GET_TX_FIFO_FULL_FLAG(LLSI0) == 0) // Fill up the TX FIFO
  23. {
  24. if (g_u32DataCount == (TEST_COUNT - 1))
  25. LLSI_SET_LAST_DATA(LLSI0); // Before writing last word data to LLSI_DATA, user must write LDT = 1
  26. if (g_u32DataCount < TEST_COUNT)
  27. LLSI_WRITE_DATA(LLSI0, g_au32frameBuffer[g_u32DataCount++]);
  28. else
  29. break;
  30. }
  31.  
  32. if(g_u32DataCount >= TEST_COUNT)
  33. {
  34. LLSI_DisableInt(LLSI0, LLSI_TXTH_INT_MASK);
  35. }
  36. }
  37.  
  38. if (LLSI_GetIntFlag(LLSI0, LLSI_FEND_INT_MASK)) // FENDIF will be set to 1 when LLSI transfer last pixel data.
  39. {
  40. g_u32FrameEnd = 1;
  41. g_u32PatternToggle++;
  42. LLSI_ClearIntFlag(LLSI0, LLSI_FEND_INT_MASK);
  43. }
  44. }
  45.  
  46. void SYS_Init(void)
  47. {
  48. /*---------------------------------------------------------------------------------------------------------*/
  49. /* Init System Clock */
  50. /*---------------------------------------------------------------------------------------------------------*/
  51. /* Enable HIRC clock */
  52. CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);
  53.  
  54. /* Wait for HIRC clock ready */
  55. CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);
  56.  
  57. /* Set core clock to HCLK_CLK Hz */
  58. CLK_SetCoreClock(HCLK_CLK);
  59.  
  60. /* Enable UART0 module clock */
  61. CLK_EnableModuleClock(UART0_MODULE);
  62.  
  63. /* Select UART0 module clock source as HIRC/2 and UART0 module clock divider as 1 */
  64. CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC_DIV2, CLK_CLKDIV0_UART0(1));
  65.  
  66. /* Enable LLSI0 module clock */
  67. CLK_EnableModuleClock(LLSI0_MODULE);
  68.  
  69. /*---------------------------------------------------------------------------------------------------------*/
  70. /* Init I/O Multi-function */
  71. /*---------------------------------------------------------------------------------------------------------*/
  72. /* Set PB multi-function pins for UART0 RXD and TXD */
  73. SYS->GPB_MFPH = (SYS->GPB_MFPH & (~(UART0_RXD_PB12_Msk | UART0_TXD_PB13_Msk))) | UART0_RXD_PB12 | UART0_TXD_PB13;
  74.  
  75. /* Set PC multi-function pins for LLSI0 */
  76. SET_LLSI0_OUT_PC5();
  77. }
  78.  
  79. void UART0_Init()
  80. {
  81. /*---------------------------------------------------------------------------------------------------------*/
  82. /* Init UART */
  83. /*---------------------------------------------------------------------------------------------------------*/
  84. /* Reset UART0 module */
  85. SYS_ResetModule(UART0_RST);
  86.  
  87. /* Configure UART0 and set UART0 Baudrate */
  88. UART_Open(UART0, 115200);
  89. }
  90.  
  91. void LLSI_Init(void)
  92. {
  93. /*---------------------------------------------------------------------------------------------------------*/
  94. /* Init LLSI */
  95. /*---------------------------------------------------------------------------------------------------------*/
  96. /* Configure as software mode, RGB output format, 6 pixels in a frame and idle output low */
  97. /* Set clock divider. LLSI clock rate = 72MHz */
  98. /* Set data transfer period. T_Period = 1250ns */
  99. /* Set duty period. T_T0H = 400ns; T_T1H = 850ns */
  100. /* Set reset command period. T_ResetPeriod = 50000ns */
  101. LLSI_Open(LLSI0, LLSI_MODE_SW, LLSI_FORMAT_RGB, HCLK_CLK, 1250, 400, 850, 50000, 32, LLSI_IDLE_LOW);
  102.  
  103. /* Set TX FIFO threshold */
  104. LLSI_SetFIFO(LLSI0, 2);
  105.  
  106. /* Enable reset command function */
  107. LLSI_ENABLE_RESET_COMMAND(LLSI0);
  108.  
  109. NVIC_EnableIRQ(LLSI0_IRQn);
  110. }
  111.  
  112. void main(void)
  113. {
  114. uint32_t u32Tmp;
  115. /* Unlock protected registers */
  116. SYS_UnlockReg();
  117.  
  118. /* Init System, IP clock and multi-function I/O. */
  119. SYS_Init();
  120.  
  121. /* Lock protected registers */
  122. SYS_LockReg();
  123.  
  124. /* Init UART0 for printf */
  125. UART_Open(UART0, 115200);
  126.  
  127. printf("\n\nCPU @ %d Hz\n", SystemCoreClock);
  128.  
  129. /* Init LLSI */
  130. LLSI_Init();
  131.  
  132. /* Enable Transmit FIFO Threshold Interrupt and Frame End Interrupt */
  133. LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK | LLSI_FEND_INT_MASK);
  134.  
  135. LLSI_WRITE_DATA(LLSI0, g_au32frameBuffer[g_u32DataCount++]); // Write the first word to trigger TXTH_INT
  136.  
  137. while(1)
  138. {
  139. if (g_u32FrameEnd == 1) // Wait for FEND_INT
  140. {
  141. u32Tmp = g_u32PatternToggle % 4;
  142. /* Generate a new frame */
  143. for (int i=0; i<TEST_COUNT; i++)
  144. {
  145. if (i % 4 == u32Tmp)
  146. g_au32frameBuffer[i] = 0xFF000000 >> (u32Tmp * 8);
  147. else
  148. g_au32frameBuffer[i] = 0x00000000;
  149. }
  150.  
  151. g_u32FrameEnd = 0;
  152. g_u32DataCount = 0;
  153.  
  154. LLSI_EnableInt(LLSI0, LLSI_TXTH_INT_MASK);
  155. CLK_SysTickDelay(50000);
  156. }
  157. }
  158. }


留言

這個網誌中的熱門文章

無法被取代的指針型三用電表(一):前言

關於新唐科技NuMicro ISP的介紹和使用方式

新唐火神板開箱實作(一):NuMaker-Volcano與NuEclipse IDE入門篇