本系列第1部分和第2部分中讨论的大多数示例都以某种方式涉及闪烁LED。一开始可能很有趣,但过了一会儿就会有点无聊。让我们做些更有趣的事情……让我们点亮更多LED!STM32F030F4P6WS281xLEDWS281xRGBLED(及其克隆)非常受欢迎。您可以将它们作为单独的元素购买,将它们串成长条,或将它们组装成矩阵、环或其他形状。WS2812B它们可以串联,基于这样的事实,你可以只用单片机的一个引脚控制一个很长的LED灯条。不幸的是,他们的内部控制器使用的物理协议不能直接适用于您在MCU中找到的任何外围设备。您必须使用bit-banging或以特殊方式使用可用的外围设备。哪种可用解决方案最有效取决于同时控制的LED灯条数量。如果你必须驱动4到16个条带,那么最有效的方法是使用定时器和DMA(请不要忽略本文末尾的链接)。如果您只需要控制一个或两个灯带,请使用可用的SPI或UART外围设备。对于SPI,您只能在发送的一个字节中编码两个WS281x位。由于巧妙地使用了起始位和停止位,UART允许更密集的编码:每发送一个字节3位。我在这个网站上找到了关于UART协议如何适应WS281x协议的最佳解释。如果你不懂波兰语,这里是英文翻译。基于WS281x的LED仍然最受欢迎,但市场上也有SPI控制的LED:APA102、SK9822。这里有三篇关于它们的有趣文章:1、2、3。LED环市场上有许多基于WS2812的环。我有一个这样的:WS2812B,它有24个可单独寻址的RGBLED(WS2812B)并暴露四个端子:GND、5V、DI和DO。通过将DI(数据输入)终端连接到前一个的DO(数据输出)终端,可以链接更多的环或其他基于WS2812的东西。让我们将这个环连接到我们的STM32F030板。我们将使用基于UART的驱动程序,因此DI应连接到UART接头上的TXD引脚。WS2812BLED至少需要3.5V电源。24个LED消耗大量电流,因此在编程/调试期间,最好将环上的GND和5V端子直接连接到ST-LINK编程器上可用的GND和5V引脚:WS2812B我们的STM32F030F4P6MCU和整个STM32F0,F3,F7,L4系列有一个F1,F4,L1MCU所没有的重要特性:它可以反转UART信号,所以我们可以直接将环连接到UARTTXD引脚。如果您不知道我们需要这种反转,那么您可能还没有阅读我上面提到的文章。因此,您不能以这种方式使用流行的BluePill或STM32F4-DISCOVERY。使用其SPI外设或外部反相器。对于使用SPI的NUCLEO-F411RE,请参阅圣诞树灯项目作为UART+逆变器或WS2812示例的示例。顺便说一句,大多数DISCOVERY板可能还有一个问题:它们在VDD=3V而不是3.3V下工作。对于高DI,WS281x至少需要电源电压*0.7。如果5V电源为3.5V,如果4.7V电源为3.3V;在DISCOVERY的5V引脚上找到。如您所见,即使在我们的例子中,第一个LED的工作电压也低于规范0.2V。对于DISCOVERY板,如果提供4.7V,它将在低于规范的0.3V下运行;如果提供5V,它将在低于规范0.5V的电压下运行。让我们结束这个冗长的介绍,进入代码:packagemainimport("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")vartts*usart.Driverfuncinit(){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()}funcmain(){varrndrand.XorShift64rnd.Seed(1)rgb:=wsuart.GRB条带:=wsuart.Make(24)black:=rgb.Pixel(0)for{c:=led.Color(rnd.Uint32()).Scale(127)像素:=rgb.Pixel(c)fori:=rangestrip{strip[i]=pixeltts.Write(strip.Bytes())delay.Millisec(40)}fori:=rangestrip{strip[i]=blacktts.Write(strip.Bytes())delay.Millisec(20)}}}functtsISR(){tts.ISR()}functtsDMAISR(){tts.TxDMAISR()}//c:__attribute__((section(".ISRs")))varISRs=[...]func(){irq.USART1:ttsISR,irq.DMA1_Channel2_3:ttsDMAISR,}importsection与前面的例子相比,importsection中的新内容是rand/math包和带led/ws281x子树的led包led包本身包含Color类型的定义。led/ws281x/wsuart定义了ColorOrder、Pixel和Strip类型。我想知道如何使用image/color中的Color或RGBA类型,以及如何以实现image.Image接口的方式定义Strip。但是通过伽玛校正和昂贵的颜色/绘图包,我最终采用了简单的方法:使用一些有用的方法键入Coloruint32typeStrip[]Pixel。不过,这种情况未来可能会有所改变。init函数init函数没有太多新意。UART波特率从115200更改为3000000000/1390≈2158273,相当于每个WS2812位1390纳秒。CR2寄存器中的TxInv位设置为反转TXD信号。主要函数XorShift64伪随机数生成器用于生成随机颜色。XORSHIFT是目前唯一由math/rand包实现的算法。您必须使用带有非零参数的Seed方法显式初始化它。rgb变量是wsuart.ColorOrder类型,设置为WS2812使用的GRB颜色顺序(WS2811使用RGB顺序)。然后将其用于将颜色转换为像素。wsuart.Make(24)创建一个24像素的初始化条带。它等效于:strip:=make(wsuart.Strip,24)strip.Clear()其余代码使用随机颜色绘制类似于“请稍候...”微调器的内容。条带切片充当帧缓冲区。tts.Write(strip.Bytes())将帧缓冲区的内容发送到环。中断该程序由处理中断的代码组成,与前面的UART示例中的代码相同。让我们编译运行:$egc$arm-none-eabi-sizecortexm0.elftextdatabssdechexfilename140882402041453238c4cortexm0.elf$openocd-d0-finterface/stlink.cfg-ftarget/stm32f0x。cfg-c'初始化;程序cortexm0.elf;重置运行;exit'我跳过openocd的输出。下面的视频展示了这个程序是如何工作的:让我们做一些有用的事情......在第一部分开始时,我问:“Go能走多低,仍然做一些有用的事情?”.我们的MCU确实是一个低端设备(8位的人可能不同意我的看法),但到目前为止我们还没有做任何有用的事情。所以...让我们做点有用的事...让我们制作一个时钟!互联网上有许多由RGBLED制成的时钟示例。让我们用我们的小板和RGB环制作我们自己的时钟。我们如下所述更改以前的代码。导入部分去掉math/rand包,然后添加stm32/hal/exti。全局变量添加两个新的全局变量:btn和btnev:var(tts*usart.Driverbtngpio.Pinbtnevrtos.EventFlag)它们将用于处理那些用于设置时钟的“按钮”。我们的董事会没有重置以外的按钮,但我们仍然可以在没有它的情况下以某种方式进行管理。初始化函数将此代码添加到初始化函数: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()内部上拉时上拉电阻使能,将PA4引脚配置为输入。它连接到板载LED,但这不会妨碍任何事情。更重要的是它在GNDpin旁边,所以我们可以使用任何金属物体来模拟按钮并设置时钟。作为奖励,我们从板载LED获得了额外的反馈。我们使用EXTI外设来跟踪PA4状态。它被配置为在任何更改时生成中断。btnWait函数定义了一个新的辅助函数:funcbtnWait(stateint,deadlineint64)bool{forbtn.Load()!=state{if!btnev.Wait(1,deadline){returnfalse//timeout}btnev.Reset(0)}delay.Millisec(50)//debouncingreturntrue}它等待“按钮”引脚上的指定状态,但只等到截止日期到来。下面是稍微改进的轮询代码:EventFlag类型的btnev变量会休眠,直到发生某些事情。您当然可以使用通道而不是rtos.EventFlag,但后者要便宜得多。main函数我们需要全新的main函数:funcmain(){rgb:=wsuart.GRBstrip:=wsuart.Make(24)ds:=4*60/len(strip)//LED之间的间隔(四分之一秒).adjust:=0adjspeed:=dsfor{qs:=int(rtos.Nanosec()/25e7)//自重置后的四分之一秒。qa:=qs+adjustqa%=12*3600*4//自0:00或12:00以来的四分之一秒。hi:=len(strip)*qa/(12*3600*4)qa%=3600*4//当前小时的四分之一秒。mi:=len(strip)*qa/(3600*4)qa%=60*4//当前分钟的四分之一秒。si:=len(strip)*qa/(60*4)hc:=led.Color(0x550000)mc:=led.Color(0x005500)sc:=led.Color(0x000055)//混合颜色如果手时钟重叠。如果hi==mi{hc|=mcmc=hc}如果mi==si{mc|=scsc=mc}如果si==hi{sc|=hchc=sc}//绘制时钟并写入ring.strip.Clear()strip[hi]=rgb.Pixel(hc)strip[mi]=rgb.Pixel(mc)strip[si]=rgb.Pixel(sc)tts.Write(strip.Bytes())//睡眠直到按下按钮或应该移动秒针。ifbtnWait(0,int64(qs+ds)*25e7){adjust+=adjspeed//睡眠直到按钮被释放或超时。if!btnWait(1,rtos.Nanosec()+100e6){ifadjspeed<5*60*4{adjspeed+=2*ds}continue}adjspeed=ds}}}我们用rtos的.Nanosec函数代替time.Now获取当前时间。这节省了大量的闪存,但也使我们的时钟成为一个不知道日、月、年的老式设备,最糟糕的是,它无法处理夏令时的变化。我们的戒指有24个LED,因此秒针的显示精度可达2.5秒。为了不牺牲这个精度并获得流畅的运行结果,我们使用1/4秒作为基本间隔。半秒就足够了,但四分之一秒更准确,并且适??用于16和48个LED。红色、绿色和蓝色分别用于时针、分针和秒针。这使我们可以使用简单的OR运算来进行颜色混合。我们的Color.Blend方法可以混合任何颜色,但我们没有太多的闪存,所以我们选择最简单的解决方案。我们只在秒针移动时重新绘制时钟。btnWait(0,int64(qs+ds)*25e7)上面的代码行正在等待那个时刻,或者按钮按下。每按一下按钮,时钟就会前进一格。按住按钮一会儿会加快调整速度。中断定义了一个新的中断处理程序:funcexti4_15ISR(){pending:=exti.Pending()&0xFFF0pending.ClearPending()ifpending&exti.Lines(btn.Mask())!=0{btnev.Signal(1)}}并将irq.EXTI4_15:exti4_15ISR条目添加到ISR数组。该处理程序(或中断服务例程)处理EXTI4_15IRQ。Cortex-M0CPU支持的IRQ明显少于其较大的兄弟姐妹,因此您经常会看到一个IRQ被多个中断源共享。在我们的示例中,一个IRQ由12个EXTI线共享。exti4_15ISR读取所有未决位并选择12个更高有效位。接下来,它清除EXTI中的检查位并开始处理它们。在我们的例子中,只检查了第4位。btnev.Signal(1)导致btnev.Wait(1,deadline)唤醒并返回true。你可以在Github上找到完整的代码。让我们编译它:$egc$arm-none-eabi-sizecortexm0.elftextdatabssdechexfilename15960240216164164020cortexm0.elf这里的所有改进只得到184个字节。让我们再次重建一切,但这次在类型信息中没有任何类型和字段名称:$cd$HOME/emgo$./clean.sh$cd$HOME/firstemgo$egc-nf-nt$arm-none-eabi-sizecortexm0.elftextdatabssdechexfilename15120240216155763cd8cortexm0.elf现在,有了KB的免费空间,您可以改进一些东西。让我们看看它是如何工作的:我不知道我是怎么精确到3:00的!?这里的所有都是它的!在第4部分(本系列的结尾)中,我们将尝试在LCD上显示一些内容。(LCTT译注:不过未完,第三篇写于2018年,那一年整个博客停止更新。)
