新聞中心
在本系列的 第一 和 第二 部分中討論的大多數(shù)示例都是以某種方式閃爍的 LED。起初它可能很有趣,但是一段時間后變得有些無聊。讓我們做些更有趣的事情……

在伊吾等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務理念,為客戶提供成都做網(wǎng)站、成都網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設 網(wǎng)站設計制作按需開發(fā)網(wǎng)站,公司網(wǎng)站建設,企業(yè)網(wǎng)站建設,高端網(wǎng)站設計,成都全網(wǎng)營銷,成都外貿(mào)網(wǎng)站建設,伊吾網(wǎng)站建設費用合理。
…讓我們點亮更多的 LED!
STM32F030F4P6
WS281x LED
WS281x RGB LED(及其克隆品)非常受歡迎。你可以以單個元素購買、鏈成長條或組裝成矩陣、環(huán)或其他形狀。
WS2812B
它們可以串聯(lián)連接,基于這個事實,你可以只用 MCU 的單個引腳就可以控制一個很長的 LED 燈條。不幸的是,它們的內(nèi)部控制器使用的物理協(xié)議不能直接適用于你在 MCU 中可以找到的任何外圍設備。你必須使用 位脈沖bit-banging或以特殊方式使用可用的外設。
哪種可用的解決方案最有效取決于同時控制的 LED 燈條數(shù)量。如果你必須驅(qū)動 4 到 16 個燈條,那么最有效的方法是 使用定時器和 DMA(請不要忽略這篇文章末尾的鏈接)。
如果只需要控制一個或兩個燈條,請使用可用的 SPI 或 UART 外設。對于 SPI,你只能在發(fā)送的一個字節(jié)中編碼兩個 WS281x 位。由于巧妙地使用了起始位和停止位,UART 允許更密集的編碼:每發(fā)送一個字節(jié) 3 位。
我在 此站點 上找到了有關 UART 協(xié)議如何適用于 WS281x 協(xié)議的最佳解釋。如果你不懂波蘭語,這里是 英文翻譯。
基于 WS281x 的 LED 仍然是最受歡迎的,但市場上也有 SPI 控制的 LED:APA102、SK9822。關于它們的三篇有趣的文章在這里:1、2、3。
LED 環(huán)
市場上有許多基于 WS2812 的環(huán)。我有一個這樣的:
WS2812B
它具有 24 個可單獨尋址的 RGB LED(WS2812B),并暴露出四個端子:GND、5V、DI 和 DO。通過將 DI(數(shù)據(jù)輸入)端子連接到上一個的 DO(數(shù)據(jù)輸出)端子,可以鏈接更多的環(huán)或其他基于 WS2812 的東西。
讓我們將這個環(huán)連接到我們的 STM32F030 板上。我們將使用基于 UART 的驅(qū)動程序,因此 DI 應連接到 UART 接頭連接器上的 TXD 引腳。 WS2812B LED 需要至少 3.5V 的電源。 24 個 LED 會消耗大量電流,因此在編程/調(diào)試期間,最好將環(huán)上的 GND 和 5V 端子直接連接到 ST-LINK 編程器上可用的 GND 和 5V 引腳:
WS2812B
我們的 STM32F030F4P6 MCU 和整個 STM32 F0、F3、F7、L4 系列具有 F1、F4、L1 MCU 不具備的一項重要功能:它可以反轉(zhuǎn) UART 信號,因此我們可以將環(huán)直接連接到 UART TXD 引腳。如果你不知道我們需要這種反轉(zhuǎn),那么你可能沒有讀過我上面提到的 文章。
因此,你不能以這種方式使用流行的 Blue Pill 或 STM32F4-DISCOVERY。使用其 SPI 外設或外部反相器。有關使用 SPI 的 NUCLEO-F411RE,請參見 圣誕樹燈 項目作為 UART + 逆變器的示例或 WS2812示例。
順便說一下,大多數(shù) DISCOVERY 板可能還有一個問題:它們在 VDD = 3V 而不是 3.3V 的情況下工作。 對于高 DI,WS281x 至少要求電源電壓 * 0.7。如果是 5V 電源,則為 3.5V;如果是 4.7V 電源,則為 3.3V;可在 DISCOVERY 的 5V 引腳上找到。如你所見,即使在我們的情況下,第一個 LED 的工作電壓也低于規(guī)格 0.2V。對于 DISCOVERY 板,如果供電 4.7V,它將工作在低于規(guī)格的 0.3V 下;如果供電 5V,它將工作在低于規(guī)格 0.5V 下。
讓我們結(jié)束這段冗長的介紹并轉(zhuǎn)到代碼:
package mainimport ("delay""math/rand""rtos""led""led/ws281x/wsuart""stm32/hal/dma""stm32/hal/gpio""stm32/hal/irq""stm32/hal/system""stm32/hal/system/timer/systick""stm32/hal/usart")var tts *usart.Driverfunc init() {system.SetupPLL(8, 1, 48/8)systick.Setup(2e6)gpio.A.EnableClock(true)tx := gpio.A.Pin(9)tx.Setup(&gpio.Config{Mode: gpio.Alt})tx.SetAltFunc(gpio.USART1_AF1)d := dma.DMA1d.EnableClock(true)tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)tts.Periph().EnableClock(true)tts.Periph().SetBaudRate(3000000000 / 1390)tts.Periph().SetConf2(usart.TxInv)tts.Periph().Enable()tts.EnableTx()rtos.IRQ(irq.USART1).Enable()rtos.IRQ(irq.DMA1_Channel2_3).Enable()}func main() {var rnd rand.XorShift64rnd.Seed(1)rgb := wsuart.GRBstrip := wsuart.Make(24)black := rgb.Pixel(0)for {c := led.Color(rnd.Uint32()).Scale(127)pixel := rgb.Pixel(c)for i := range strip {strip[i] = pixeltts.Write(strip.Bytes())delay.Millisec(40)}for i := range strip {strip[i] = blacktts.Write(strip.Bytes())delay.Millisec(20)}}}func ttsISR() {tts.ISR()}func ttsDMAISR() {tts.TxDMAISR()}//c:__attribute__((section(".ISRs")))var ISRs = [...]func(){irq.USART1: ttsISR,irq.DMA1_Channel2_3: ttsDMAISR,}
導入部分
與前面的示例相比,導入部分中的新內(nèi)容是 rand/math 包和帶有 led/ws281x 子樹的 led 包。 led 包本身包含 Color 類型的定義。 led/ws281x/wsuart 定義了 ColorOrder、Pixel 和 Strip 類型。
我想知道如何使用 image/color 中的 Color 或 RGBA 類型,以及如何以它將實現(xiàn) image.Image 接口的方式定義 Strip。 但是由于使用了 gamma 校正 和 大開銷的 color/draw 包,我以簡單的方式結(jié)束:
type Color uint32type Strip []Pixel
使用一些有用的方法。然而,這種情況在未來可能會改變。
init 函數(shù)
init 函數(shù)沒有太多新穎之處。 UART 波特率從 115200 更改為 3000000000/1390 ≈ 2158273,相當于每個 WS2812 位 1390 納秒。 CR2 寄存器中的 TxInv 位設置為反轉(zhuǎn) TXD 信號。
main 函數(shù)
XorShift64 偽隨機數(shù)生成器用于生成隨機顏色。 XORSHIFT 是目前由 math/rand 包實現(xiàn)的唯一算法。你必須使用帶有非零參數(shù)的 Seed 方法顯式初始化它。
rgb 變量的類型為 wsuart.ColorOrder,并設置為 WS2812 使用的 GRB 顏色順序(WS2811 使用 RGB 順序)。然后用于將顏色轉(zhuǎn)換為像素。
wsuart.Make(24) 創(chuàng)建 24 像素的初始化條帶。它等效于:
strip := make(wsuart.Strip, 24)strip.Clear()
其余代碼使用隨機顏色繪制類似于 “Please Wait…” 微調(diào)器的內(nèi)容。
strip 切片充當幀緩沖區(qū)。 tts.Write(strip.Bytes()) 將幀緩沖區(qū)的內(nèi)容發(fā)送到環(huán)。
中斷
該程序由處理中斷的代碼組成,與先前的 UART 示例 中的代碼相同。
讓我們編譯并運行:
$ egc$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename14088 240 204 14532 38c4 cortexm0.elf$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'
我跳過了 openocd 的輸出。下面的視頻顯示了該程序的工作原理:
讓我們做些有用的事情...
在 第一部分 的開頭,我曾問過:“Go 能深入到多低層,而還能做一些有用的事情?”。 我們的 MCU 實際上是一種低端設備(8 比特的人可能會不同意我的看法),但到目前為止,我們還沒有做任何有用的事情。
所以... 讓我們做些有用的事情... 讓我們做個時鐘!
在互聯(lián)網(wǎng)上有許多由 RGB LED 構(gòu)成的時鐘示例。讓我們用我們的小板子和 RGB 環(huán)制作自己的時鐘。我們按照下面的描述更改先前的代碼。
導入部分
刪除 math/rand 包,然后添加 stm32/hal/exti。
全局變量
添加兩個新的全局變量:btn 和 btnev:
var (tts *usart.Driverbtn gpio.Pinbtnev rtos.EventFlag)
它們將用來處理那些用于設置時鐘的 “按鈕”。我們的板子除了重置之外沒有其他按鈕,但是如果沒有它,我們?nèi)匀豢梢酝ㄟ^某種方式進行管理。
init 函數(shù)
將這段代碼添加到 init 函數(shù):
btn = gpio.A.Pin(4)btn.Setup(&gpio.Config{Mode: gpio.In, Pull: gpio.PullUp})ei := exti.Lines(btn.Mask())ei.Connect(btn.Port())ei.EnableFallTrig()ei.EnableRiseTrig()ei.EnableIRQ()rtos.IRQ(irq.EXTI4_15).Enable()
在內(nèi)部上拉電阻pull-up resistor啟用的情況下,將 PA4 引腳配置為輸入。它已連接至板載 LED,但這不會妨礙任何事情。更重要的是它位于 GND 引腳旁邊,所以我們可以使用任何金屬物體來模擬按鈕并設置時鐘。作為獎勵,我們還有來自板載 LED 的其他反饋。
我們使用 EXTI 外設來跟蹤 PA4 狀態(tài)。它被配置為在發(fā)生任何更改時都會產(chǎn)生中斷。
btnWait 函數(shù)
定義一個新的輔助函數(shù):
func btnWait(state int, deadline int64) bool {for btn.Load() != state {if !btnev.Wait(1, deadline) {return false // timeout}btnev.Reset(0)}delay.Millisec(50) // debouncingreturn true}
它等待 “按鈕” 引腳上的指定狀態(tài),但只等到最后期限出現(xiàn)。這是稍微改進的輪詢代碼:
for btn.Load() != state {if rtos.Nanosec() >= deadline {// timeout}}
我們的 btnWait 函數(shù)不是忙于等待 state 或 deadline,而是使用 rtos.EventFlag 類型的 btnev 變量休眠,直到有事情發(fā)生。你當然可以使用通道而不是 rtos.EventFlag,但是后者便宜得多。
main 函數(shù)
我們需要全新的 main 函數(shù):
func main() {rgb := wsuart.GRBstrip := wsuart.Make(24)ds := 4 * 60 / len(strip) // Interval between LEDs (quarter-seconds).adjust := 0adjspeed := dsfor {qs := int(rtos.Nanosec() / 25e7) // Quarter-seconds since reset.qa := qs + adjustqa %= 12 * 3600 * 4 // Quarter-seconds since 0:00 or 12:00.hi := len(strip) * qa / (12 * 3600 * 4)qa %= 3600 * 4 // Quarter-seconds in the current hour.mi := len(strip) * qa / (3600 * 4)qa %= 60 * 4 // Quarter-seconds in the current minute.si := len(strip) * qa / (60 * 4)hc := led.Color(0x550000)mc := led.Color(0x005500)sc := led.Color(0x000055)// Blend the colors if the hands of the clock overlap.if hi == mi {hc |= mcmc = hc}if mi == si {mc |= scsc = mc}if si == hi {sc |= hchc = sc}// Draw the clock and write to the ring.strip.Clear()strip[hi] = rgb.Pixel(hc)strip[mi] = rgb.Pixel(mc)strip[si] = rgb.Pixel(sc)tts.Write(strip.Bytes())// Sleep until the button pressed or the second hand should be moved.if btnWait(0, int64(qs+ds)*25e7) {adjust += adjspeed// Sleep until the button is released or timeout.if !btnWait(1, rtos.Nanosec()+100e6) {if adjspeed < 5*60*4 {adjspeed += 2 * ds}continue}adjspeed = ds}}}
我們使用 rtos.Nanosec 函數(shù)代替 time.Now 來獲取當前時間。這樣可以節(jié)省大量的閃存,但也使我們的時鐘變成了不知道日、月、年的老式設備,最糟糕的是它無法處理夏令時的變化。
我們的環(huán)有 24 個 LED,因此秒針的顯示精度可以達到 2.5 秒。為了不犧牲這種精度并獲得流暢的運行效果,我們使用 1/4 秒作為基準間隔。半秒就足夠了,但四分之一秒更準確,而且與 16 和 48 個 LED 配合使用也很好。
紅色、綠色和藍色分別用于時針、分針和秒針。這允許我們使用簡單的“邏輯或操作”進行顏色混合。我們 Color.Blend 方法可以混合任意顏色,但是我們閃存不多,所以我們選擇最簡單的解決方案。
我們只有在秒針移動時才重畫時鐘。
btnWait(0, int64(qs+ds)*25e7)
上面的這行代碼等待的正是那一刻,或者是按鈕的按下。
每按一下按鈕就會把時鐘向前調(diào)一調(diào)。按住按鈕一段時間會加速調(diào)整。
中斷
定義新的中斷處理程序:
func exti4_15ISR() {pending := exti.Pending() & 0xFFF0pending.ClearPending()if pending&exti.Lines(btn.Mask()) != 0 {btnev.Signal(1)}}
并將 irq.EXTI4_15: exti4_15ISR 條目添加到 ISR 數(shù)組。
該處理程序(或中斷服務程序)處理 EXTI4_15 IRQ。 Cortex-M0 CPU 支持的 IRQ 明顯少于其較大的同類兄弟處理器,因此你經(jīng)??梢钥吹揭粋€ IRQ 被多個中斷源共享。在我們的例子中,一個 IRQ 由 12 個 EXTI 線共享。
exti4_15ISR 讀取所有掛起的位,并從中選擇 12 個更高的有效位。接下來,它清除 EXTI 中選中的位并開始處理它們。在我們的例子中,僅檢查第 4 位。 btnev.Signal(1) 引發(fā) btnev.Wait(1, deadline) 喚醒并返回 true。
你可以在 Github 上找到完整的代碼。讓我們來編譯它:
$ egc$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename15960 240 216 16416 4020 cortexm0.elf
這里所有的改進只得到 184 個字節(jié)。讓我們再次重新構(gòu)建所有內(nèi)容,但這次在 typeinfo 中不使用任何類型和字段名:
$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc -nf -nt$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename15120 240 216 15576 3cd8 cortexm0.elf
現(xiàn)在,有了千字節(jié)的空閑空間,你可以改進一些東西。讓我們看看它是如何工作的:
我不知道我是怎么精確打到 3:00 的???
以上就是所有內(nèi)容!在第 4 部分(本系列的結(jié)束)中,我們將嘗試在 LCD 上顯示一些內(nèi)容。(LCTT 譯注:然而爛尾了,第三篇寫于 2018 年,整個博客當年就停更了。)
當前文章:Go語言在極小硬件上的運用(三)
轉(zhuǎn)載來于:http://www.5511xx.com/article/djohsic.html


咨詢
建站咨詢
