TinyAVR 1-series是Microchip於2018年推出的AVR單片機系列,定位是新一代的8位單片機,ATtiny3217是其中最高端的一款。相比於ATmega328P那個時代的AVR,ATtiny3217不只加強了組件的功能,更是加入了EVSYS(Event System)和CCL(Configurable Custom Logic)這兩大支撐CIP(Core Independent Peripherals)的組件,使得硬件中的消息傳遞十分靈活。對於我來講,有吸引力的是它帶來的可玩性。html
惋惜,ATtiny3217只提供VQFN-24封裝,並且國內渠道不太好買到,另外尚未下載器。第三方開發板目前尚未,官方的則價格很貴,下不了手。ios
WS2812B是Worldsemi(華彩威)的一款內置控制電路的LED,RGB三種顏色均有8位256級亮度。WS2812B的數據信號爲單線歸零碼,帶整形輸出,(理論上)能夠支持無限級聯。單片機PWM控制RGB燈佔用大量定時器資源,以舊AVR型號爲例,RGB三個通道至少須要2個定時器,而定時器總共不過3個。在各類外置控制方案中,WS2812B整合了控制邏輯,更加小巧。算法
WS2812B以5050、燈帶和軟屏等形式出售,很容易得到,本身用5050設計PCB也很方便。編程
有一天我讀到一篇application note,其中有用ATtiny1617(3217同系列)的CCL實現WS2812B的總線。我起初感到十分新奇,在看懂了實現原理以後,我直接拍手叫好——它利用SPI的SCK
和MOSI
信號和一個定時器的波形輸出的邏輯運算得到了能驅動WS2812B的信號。這讓我對ATtiny3217的執念更加深了。sass
下面先來介紹一下今天的出場嘉賓。安全
半年前,趁着能夠用公款的時機,我拔草了種草已久的開發板。app
在某寶買的,一塊那麼小的開發板居然要105元。還有一款ATtiny3217 Xplained Pro,要300+,還不包括擴展板,超出了預算限制。店家只有現貨1塊,隊友買第2塊的時候商家告知要去定貨,因而就退款了。wordpress
板上有兩顆單片機:一個ATSAMD21E18,用做電源控制器、調試器、虛擬串口等;另外一個固然是ATtiny3217啦。工具
沒錯,調試器,這對於AVR是很少見的,由於調試器只有Microchip賣,它又賣得很貴——咱們一般只用USBasp下載器。新的AVR系列都用UPDI(Unified Program and Debug Interface)來調試,包括燒寫,USBasp是不支持的(但好像能支持xmega的PDI),而Curiosity Nano不只能給板上的單片機調試,還能夠經過官方推薦的硬改來調試外部單片機。測試
開發板兩邊的排針孔之間有16 mil的錯位,排針用力插進去就能鏈接牢固,無需焊接。
ATtiny3217雖然從名字上看屬於tiny系列,實際上比做爲mega的ATmega328P和ATmega324PA等老產品強很多,至少跟xmega是一個級別的。在它之上有megaAVR 0-series(以ATmega4809爲表明)系列和DA/DB系列,都是新產品。
ATtiny3217擁有32 KB flash、256字節EEPROM和2 KB SRAM。新產品的EEPROM不是真正的EEPROM,而是在HEF(high-endurance flash)中模擬出來的,由NVMCTRL提供字節粒度的讀寫。(BTW:Microchip的PIC系列先開始這麼作的;EEPROM成本較高,我在多款單片機中看到了用flash取代EEPROM的趨勢。)
CPU方面,0-/1-series都用AVRxt指令集(見AVR® Instruction Set Manual),相比328的AVRe+改進了指令週期數,主要是寫RAM更快,使CALL
(子過程調用)、ST
(寫RAM)、PUSH
(壓棧)、SBI
和CBI
(I/O寄存器的位操做)各減小一個週期。其中PUSH
是最值得關注的,由於它大幅縮短了從事件觸發到用戶中斷代碼開始執行的間隔。(一個不太典型的中斷disassembly見AVR單片機教程——定時器中斷,它不典型在push
太少,通常至少十幾個。)
時鐘終於不用經過熔絲位設置了,CLKCTRL能夠運行時切換時鐘源。中斷也終於有兩個優先級了,但有不少限制。
外設方面,首先是從xmega開始,寄存器就以struct
來組織,好比之前設置PB6
爲輸出是DDRB |= 1 << 6
,如今是PORTB.DIR |= 1 << 6
或PORTB.DIRSET = 1 << 6
。(xmega之前的AVR的寄存器定義是各單片機中作得最差的之一,就算我已經寫過幾十遍定時器1 ms中斷,每次寫以前仍是得查datasheet才能知道WGM0[2:0]的哪一個組合是CTC模式。但凡稍微正常一點的頭文件都會給一個TC0_WGM_CTC
之類的宏吧。
The Amazing $1 Microcontroller:
The worst header files were from the megaAVR, the PSoC 4000S, the Kinetis KE04, the HT-66, the Sanyo LC-87. These header files have zero documentation, no predefined bit offsets, and no bit-addressable register definitions. Their header files are little more than register names attached to addresses.
其實他們明明能夠把這些宏定義補上去的。)
每一個外設都是新的,不只是寄存器組織變了,功能也有很大改進:
GPIO:以DIRSET
等寄存器和虛擬端口兩種方式支持位操做;一些組件的輸入輸出信號對應兩組引腳,能夠總體切換。
定時器:16位TCA做PWM輸出、2個16位TCB主要做輸入、12位TCD生成兩路同步PWM,還有一個16位RTC。
總線:USART中的fractional baud rate generator能夠處理主頻和波特率非整數倍的狀況;SPI有了緩衝區;I²C支持1 MHz的Fm+,主機和從機能夠在兩組引腳上單獨工做。
模擬:雙10位ADC,其中一個會在須要時被電容觸摸控制器佔用,可經過隨機延時消除任意頻率的干擾;三個8位DAC,其中一個能夠輸出到外部;三個模擬比較器。
CIP:CCL用組合與時序邏輯實現事件的組合,EVSYS控制組件之間的鏈接。
針對CIP舉個例子:按鍵按下時觸發ADC轉換,要求按鍵有消抖。常規的作法是每間隔一段時間讀一次按鍵,用必定的算法消抖,判斷按下時開始ADC轉換;而藉助CIP,這個功能能夠這樣實現:
按鍵的電平又GPIO讀入,RTC產生必定頻率的時鐘,二者經過EVSYS接到CCL的LUT上(look-up table,能夠實現任意3輸入的組合邏輯,這裏只用了按鍵一個輸入),LUT輸出接濾波器(filter,其輸出在連續兩次輸入相同時纔會更新),再經過EVSYS接到ADC觸發轉換。這些過程都是不須要CPU干預的,CPU此時應該處於一種睡眠狀態,或在執行其餘耗時的操做。ADC轉換完成後產生中斷,這才須要CPU執行相應代碼。
WS2812B的信號是單線的,一方面這簡化了燈帶的設計,對級聯也比較友好,但另外一方面這種信號不是任何一種常見的總線,也不能由常見總線信號經過簡單變換獲得,這帶來了一些困難。
每一位都是先高電平後低電平,0
和1
的差異在於高低電平的時間不一樣,0
的高電平時間比較短。容許的時間範圍都是比較寬的。一般每一位都是等長的,那麼一位的時間範圍爲1.16 μs到1.38 μs。
每一個燈有4個引腳:VCC
、GND
、DIN
、DO
。DO
上的信號是DIN
信號除了前24個bit之外的部分,這24個bit以綠紅藍、MSB優先的順序鎖存進WS2812B。前一個燈的DO
接後一個的DIN
,如此級聯。
沒有信號時數據線保持低電平,當低電平時間超過280 μs時就會RESET,鎖存的數據更新到亮度上。全部級聯的燈在幾乎同一時刻更新。
若是你之前接觸過WS2812B,可能會以爲以上信息和你記憶中的有一些誤差。的確,上面這份datasheet來自官網,而網上流傳的是以前的版本,外網上比較通用的版本以下:
有人對datasheet描述不明確感到不滿,因而作了個實驗測試高低電平時間的最低條件,並對WS2812B的內部原理做了猜想。實驗結果以下:
首先這不是我想出來的方案,連接在文首。
咱們讓定時器產生兩倍於SCK
頻率的方波WO2
,上升沿對齊;MOSI
設置爲上升沿更新,從SCK
上升沿到下一個上升沿爲一個bit。在這一bit中,高電平佔前1/4爲WS2812B的0
,1/2爲1
。
單片機時鐘頻率爲10 MHz(內部20 MHz,分頻係數2),SCK
頻率爲10 MHz / 16 = 625 kHz,WO2
頻率爲1.25 MHz。這樣算下來t0H = 400 ns,t0L = 1200 ns,t1H = t1L = 800 ns。儘管不符合上述任何一個版本的時序,可是都差得不大,實測能夠工做(我也不知道我買的WS2812B應該參考哪一個時序)。
ATtiny3217的時鐘能夠用程序更改,但仍是有一個參數須要用熔絲位設置——內部RC時鐘是20 MHz仍是16 MHz。出廠默認是20 MHz,因此就不用改了。若是要改的話,在Microchip Studio(原Atmel Studio)的菜單欄Tools/Device Programming裏。
CLKCTRL
寄存器組是被保護起來的,寫入操做須要一個特殊的流程:先向CCP
(configuration change protection)寄存器裏寫IO寄存器對應的key,而後在4週期裏寫被保護的寄存器。
CCP = CCP_IOREG_gc; CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm;
賦值號左邊是寄存器,大部分都是分組的;右邊的_gc
表示group configuration,_bm
表示bit mask,還有_bp
表示bit position。
下面是從iotn3217.h
(咱們仍是應該#include <avr/io.h>
)中截取的幾段,展現了分組的寄存器定義以及相關的宏是如何用標準C語言實現的:
typedef volatile uint8_t register8_t; //-------------------------------------------------------------------------- /* Clock controller */ typedef struct CLKCTRL_struct { register8_t MCLKCTRLA; /* MCLK Control A */ register8_t MCLKCTRLB; /* MCLK Control B */ register8_t MCLKLOCK; /* MCLK Lock */ register8_t MCLKSTATUS; /* MCLK Status */ register8_t reserved_1[12]; register8_t OSC20MCTRLA; /* OSC20M Control A */ register8_t OSC20MCALIBA; /* OSC20M Calibration A */ register8_t OSC20MCALIBB; /* OSC20M Calibration B */ register8_t reserved_2[5]; register8_t OSC32KCTRLA; /* OSC32K Control A */ register8_t reserved_3[3]; register8_t XOSC32KCTRLA; /* XOSC32K Control A */ register8_t reserved_4[3]; } CLKCTRL_t; /* CLKCTRL.MCLKCTRLB bit masks and bit positions */ #define CLKCTRL_PEN_bm 0x01 /* Prescaler enable bit mask. */ #define CLKCTRL_PEN_bp 0 /* Prescaler enable bit position. */ #define CLKCTRL_PDIV_gm 0x1E /* Prescaler division group mask. */ #define CLKCTRL_PDIV_gp 1 /* Prescaler division group position. */ #define CLKCTRL_PDIV0_bm (1<<1) /* Prescaler division bit 0 mask. */ #define CLKCTRL_PDIV0_bp 1 /* Prescaler division bit 0 position. */ #define CLKCTRL_PDIV1_bm (1<<2) /* Prescaler division bit 1 mask. */ #define CLKCTRL_PDIV1_bp 2 /* Prescaler division bit 1 position. */ #define CLKCTRL_PDIV2_bm (1<<3) /* Prescaler division bit 2 mask. */ #define CLKCTRL_PDIV2_bp 3 /* Prescaler division bit 2 position. */ #define CLKCTRL_PDIV3_bm (1<<4) /* Prescaler division bit 3 mask. */ #define CLKCTRL_PDIV3_bp 4 /* Prescaler division bit 3 position. */ /* Prescaler division select */ typedef enum CLKCTRL_PDIV_enum { CLKCTRL_PDIV_2X_gc = (0x00<<1), /* 2X */ CLKCTRL_PDIV_4X_gc = (0x01<<1), /* 4X */ CLKCTRL_PDIV_8X_gc = (0x02<<1), /* 8X */ CLKCTRL_PDIV_16X_gc = (0x03<<1), /* 16X */ CLKCTRL_PDIV_32X_gc = (0x04<<1), /* 32X */ CLKCTRL_PDIV_64X_gc = (0x05<<1), /* 64X */ CLKCTRL_PDIV_6X_gc = (0x08<<1), /* 6X */ CLKCTRL_PDIV_10X_gc = (0x09<<1), /* 10X */ CLKCTRL_PDIV_12X_gc = (0x0A<<1), /* 12X */ CLKCTRL_PDIV_24X_gc = (0x0B<<1), /* 24X */ CLKCTRL_PDIV_48X_gc = (0x0C<<1), /* 48X */ } CLKCTRL_PDIV_t; //-------------------------------------------------------------------------- #define CLKCTRL (*(CLKCTRL_t *) 0x0060) /* Clock controller */
上升沿串出,降低沿採樣,這是SPI mode 1。SCK
頻率爲主頻除以16。
SPI0.CTRLA = SPI_MASTER_bm | SPI_PRESC_DIV16_gc | SPI_ENABLE_bm; SPI0.CTRLB = SPI_SSD_bm | SPI_MODE_1_gc;
SPI發送一字節:向寄存器寫入來發送,輪詢寄存器等待發送完成。
SPI0.DATA = byte; while (!(SPI0.INTFLAGS & SPI_IF_bm)) ;
產生方波一般用CTC(現FRQ)模式,可是極性很差控制(其實如今有CMPnOV
位了),改用PWM。設置PER
爲7
,PWM週期爲8個CPU週期;CMP2
爲4,佔空比爲4 / 8 = 50%。
沒有硬件設施能夠實現定時器和SPI的同步,因此在初始化中先不開啓定時器輸出。
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc; TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP2EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc; TCA0.SINGLE.PER = 7; TCA0.SINGLE.CMP2 = 4;
(TCA有兩種模式:一個16位(single)和兩個8位(split)。你以爲TCA0.SINGLE
和TCA0.SPLIT
是什麼關係呢?)
在SPI發送時要求WO2
和SCK
同步,但此時並不知道計數器CNT
的值,因此把它清零,而後開啓輸出。SPI發送完後再關閉輸出。
void ws2812b_write(uint8_t byte) { TCA0.SINGLE.CNT = 0; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; SPI0.DATA = byte; while (!(SPI0.INTFLAGS & SPI_IF_bm)) ; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc; TCA0.SINGLE.CTRLC = 0; }
LUT
寄存器的8位分別存放IN[2:0]
的8種狀態對應的輸出。根據前面的時序圖,在011
、101
和111
三種狀況下輸出爲1
,LUT
值爲0xA8
。
CCL.LUT1CTRLB = CCL_INSEL1_SPI0_gc | CCL_INSEL0_SPI0_gc; CCL.LUT1CTRLC = CCL_INSEL2_TCA0_gc; CCL.TRUTH1 = 0xA8; CCL.LUT1CTRLA = CCL_OUTEN_bm | CCL_ENABLE_bm; CCL.CTRLA = CCL_RUNSTDBY_bm | CCL_ENABLE_bm;
CCL的寄存器是被ENABLE
保護的,在ENABLE
爲1
時不能更改,所以要先配置其餘寄存器,再enable LUT,最後enable CCL。
並不是每一個信號都能做爲LUT的任意輸入,如SCK
只能接IN0
、MOSI
只能接IN1
,而普通的GPIO則不能直接接進LUT。若是須要的話,能夠把GPIO接到event channel上,設置其用戶爲LUT,再在LUT中選擇對應的EVOUT。若是SCK
要接IN1
而MOSI
接IN0
,只能用EVSYS這種方法,但這沒有任何意義——老是能夠經過修改LUT
達到相同的功能。
(Datasheet中的一些「GPIO」指的是GPIOR(general-purpose I/O registers),咱們講的GPIO叫「PORT」,有些章節裏也叫「GPIO」。)
爲了和application note中一致,SPI0和LUT1的輸出都移到非默認的引腳上,在那裏默認引腳和其餘功能衝突了。Alternative pins經過PORTMUX
配置:
PORTMUX.CTRLA = PORTMUX_LUT1_ALTERNATE_gc; PORTMUX.CTRLB = PORTMUX_SPI0_ALTERNATE_gc;
按鍵在PB7
上,沒有外部上拉電阻,啓用內部上拉電阻(在);LED在PA3
上,LUT1-OUT
即WS2812B的信號在PC1
上,輸出;SCK
、MOSI
、WO2
分別在PC0
、PC2
、PB2
上,爲了用邏輯分析儀觀察波形,也配置爲輸出。
PORTA.DIRSET = PIN3_bm; PORTB.DIRSET = PIN2_bm; PORTB.PIN7CTRL = PORT_PULLUPEN_bm; PORTC.DIRSET = PIN2_bm | PIN1_bm | PIN0_bm;
#include <stdbool.h> #include <avr/io.h> #define F_CPU 10000000 #include <util/delay.h> void ws2812b_write(uint8_t byte) { TCA0.SINGLE.CNT = 0; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; SPI0.DATA = byte; while (!(SPI0.INTFLAGS & SPI_IF_bm)) ; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc; TCA0.SINGLE.CTRLC = 0; } int main() { CCP = CCP_IOREG_gc; CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm; SPI0.CTRLA = SPI_MASTER_bm | SPI_PRESC_DIV16_gc | SPI_ENABLE_bm; SPI0.CTRLB = SPI_SSD_bm | SPI_MODE_1_gc; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc; TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP2EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc; TCA0.SINGLE.PER = 7; TCA0.SINGLE.CMP2 = 4; CCL.LUT1CTRLB = CCL_INSEL1_SPI0_gc | CCL_INSEL0_SPI0_gc; CCL.LUT1CTRLC = CCL_INSEL2_TCA0_gc; CCL.TRUTH1 = 0xA8; CCL.LUT1CTRLA = CCL_OUTEN_bm | CCL_ENABLE_bm; CCL.CTRLA = CCL_RUNSTDBY_bm | CCL_ENABLE_bm; PORTMUX.CTRLA = PORTMUX_LUT1_ALTERNATE_gc; PORTMUX.CTRLB = PORTMUX_SPI0_ALTERNATE_gc; PORTA.DIRSET = PIN3_bm; PORTB.DIRSET = PIN2_bm; PORTB.PIN7CTRL = PORT_PULLUPEN_bm; PORTC.DIRSET = PIN2_bm | PIN1_bm | PIN0_bm; bool prev = 0; while (1) { bool curr = PORTB.IN & PIN7_bm; if (prev && !curr) { for (uint8_t i = 0; i != 24; ++i) ws2812b_write(0x0A); PORTA.OUTTGL = PIN3_bm; } prev = curr; _delay_ms(1); } }
It works!
這是一個字節的波形。WO2
在左右各有一個額外的週期,但這並不影響LUT1-OUT
在閒時爲低電平(idle state = low)。
先別高興得太早,看看這裏最後兩個字節:
兩個字節之間有明顯的間隔,這從代碼裏也能看出來。雖然間隔時間比實測最短的RESET時間9 μs還要短一半,但讓我很不舒服。
ATtiny3217的SPI有一個緩衝字節,利用它或許能夠實現多個字節連續發送:
void ws2812b_write(const uint8_t* byte, uint8_t length) { TCA0.SINGLE.CNT = 3; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; SPI0.INTFLAGS |= SPI_TXCIF_bm; for (const uint8_t* end = byte + length; byte != end; ++byte) { while (!(SPI0.INTFLAGS & SPI_DREIE_bm)) ; SPI0.DATA = *byte; } while (!(SPI0.INTFLAGS & SPI_TXCIF_bm)) ; TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc; TCA0.SINGLE.CTRLC = 0; }
記得在AVR單片機教程——DAC中,USART in SPI mode的緩衝區一方面讓我要額外注意每次都要把UDR0
讀掉以得到新鮮的數據,另外一方面在我須要連續發送兩個字節時相比SPI更節省CPU資源,讓我得以實現音樂播放器。若是要在編程簡單和功能強大之間選擇的話,我仍是會選擇後者。那麼此次ATtiny3217的SPI緩衝區可否讓它勝任WS2812B的連續發送呢?讓咱們來看看波形:
前兩行很符合預期——SCK
信號沒有出現間斷。加入第三行,密集的線條可能迷惑了你的雙眼,可是第四行足夠明顯——第一字節的輸出是正常的,可是第二字節就不對了。究其緣由,是第二字節的第一個SCK
上升沿出如今它原本應該對應的WO2
上升沿和它後面的降低沿中間,換言之SCK
滯後了。
繼續向後觀察,第三、四、5字節都貌似正常,第6字節又出錯了。仔細觀察,第3字節像是降低沿對齊的PWM信號,而第4字節是高電平中心對齊的(center-aligned)。以4字節爲週期,後面重複。事實上,每兩字節之間SCK
低電平延長了2個CPU週期,至關於WO2
信號的90°相位差;這樣週期爲4字節就很好理解了。
因此,以讓我開心爲目的的改進失敗了。
若是你仔細看代碼的話,應該是沒法理解TCA0.SINGLE.CNT = 3;
中的magic number的。的確,這個數是我一點點改直到SCK
和WO2
上升沿對齊這樣試出來的。若是把SPI0.INTFLAGS |= SPI_TXCIF_bm;
這一句移到前面去,這個數就得改爲7
——這說明移的那句須要4個週期來執行。
同理,改進前的TCA0.SINGLE.CNT = 0;
也只是一個巧合,而不是像application note上說的那樣:
Since there is no synchronization between the TCA output and the SPI clock, it is necessary to start and stop the TCA each time data is sent to the LEDs. It is also necessary to clear the TCA CNT register before TCA is started. This is done to make sure that the TCA starts counting from zero each time the LEDs are updated.
很顯然,這樣作是低效的、不安全的:低效在這個magic number須要花工夫去找,不安全在也許改變一下編譯器的優化等級就能讓你花的工夫做廢。
老版本WS2812B的時序能夠大體理解爲1/3和2/3的高電平佔比,而上述方案只能實現分母爲4的佔比。不過就1
而言,3/4比1/2更接近2/3,要作到3/4也只須要把IN[2:0] = 0b110
對應的輸出改爲1
就能夠了。爲何application note不是這樣作的呢?
在1/2的方案中,只要SCK
爲低電平,輸出就是低電平;SCK
的閒時電平是SPI mode能徹底肯定的,於是能保證輸出的閒時電平爲低。在3/4的方案中,三輸入的組合邏輯能夠理解爲輸入有至少兩個高電平時輸出爲高(提問:哪款常見的邏輯IC能實現這樣的功能?);那麼若是數據的LSB爲1
,輸出就徹底跟着WO2
走。而WO2
在SPI發送完後還有一段高電平,除非這一段能被消除,不然3/4方案就是不可行的。
那麼如何消除呢?也能夠像上面那樣搞個magic number,開始發送後等待這麼多個週期,而後關閉TCA輸出。這個數只要在一個[n, n+3]的區間裏便可,沒那麼嚴格。可是,一旦主頻改變,從新找吧!
我開了SCK
等信號的輸出,是爲了看波形,若是不開,那個引腳還能夠用嗎?輸出是不行的,一旦DIR
位爲1
,它輸出的就是SCK
信號;輸入或許能夠。
因此,儘管我只須要SCK
信號在內部使用,它卻必須佔用一個引腳,這好嗎?ATtiny3217一共只有24個pin,儘管有alternative pins,但畢竟總數擺在這,挺容易衝突的。不知Microchip的工程師有沒有思考過這個問題,仍是說tiny系列的應用場景連24 pins都已經嫌多了?或許吧,雖然我捨不得。
那麼如何安排引腳呢?Atmel START是一個在線的工具,幫助你配置引腳、時鐘和各類組件,就像隔壁廠家的某立方體同樣。
最近在作一個涉及WS2812B燈帶的項目。爲了鍛鍊本身,我要把整個寫級聯WS2812B的操做作成無需CPU干預的,這固然離不開DMA。我在網上找到三種方案,但它們都有嚴重的內存overhead,以致於很難把整個燈帶的數據在一次DMA請求中發送出去,至少不划算。
本文的方案則不存在這樣的問題,由於WS2812B的一個字節就對應SPI的一個字節。可是TCA與SPI的同步和SCK
信號在字節間被延長,尤爲是後者,給我澆了一盆冷水。我尚未驗證這種方案,但大機率是不行的,好在我還有別的方案。
你有什麼方案嗎?歡迎在評論區留言。