空のループまたは割り込みのないSTM32上のWS2812Bプロトコル。 そして、正しい虹を作る方法





HabréにはWS2112B RGB LEDの操作に関する記事がすでにいくつかありますが、何らかの理由で、それらはすべてかなり古い方法を使用してビットシーケンスを形成します。 この方法では、空のプログラムサイクルを使用して正確な時間間隔を形成します。 おそらくこれはArduinoを使用するコストですが、もちろん、長い間STM32に代表されるARM Cortex-M4に切り替えており、Arduinoをより美しくする余裕があります。



したがって、「プロトコル」WS2112Bを思い出させてください。







WS2112BのLEDストリップには、ストリップの最初のLEDに接続されたDINが1つしかありません。 図に示すように、特殊なパルスシーケンスエンコーディングビットが提供されます。 各LEDには1つのデジタル出力があります。DOUTはストリップ上の次のLEDのDIN入力に接続されています。 各LEDは24ビットを送信する必要があります(色ごとに8ビット:赤R、緑G、青B)。 したがって、すべてのLEDに点火するには、24 * Nビットを送信する必要があります。ここで、Nはストリップ内のLEDの数です。



ビットを受信すると、LEDは点灯し、新しいビットシーケンスを受信するまで静的に点灯します。 各ビットシーケンスは、ログにDINを設定することから始まります。 少なくとも50μsの時間はゼロ。



ご覧のとおり、ビットは十分に短いパルスでエンコードされており、公差は厳密です。 リセットまたは不良ビットが誤って生成されないように、少なくともすべての割り込みを禁止するには、ソフトウェア遅延でそれらを形成しようとするマイクロコントローラーが必要です。 CPU時間リソースもここで無駄になります。100個のLEDを点灯するには、プロセッサが3ミリ秒動作する必要があります。 100 Hzの周波数でLEDのステータスを更新する場合、そのような「プロトコル」はプロセッサ時間の30%かかります。



SPIインターフェースを使用してビットストリームをWS2112Bに送信するための提案があります。 しかし、ここでの障害は、システムバスのクロック周波数への準拠が不十分であり、パルスの持続時間に大きな誤差があることです。



一方、STM32および一般的に、すべてのCortex-Mチップにはダイレクトメモリアクセス(DMA)の優れたメカニズムがあります。 パルス幅変調モードでタイマーを使用してビットを生成でき、DMAを使用して後続の各ビットをRAMから抽出できます。



以下の図は、STM32F407VET6チップでのDMAとTIM4タイマーの相互作用を示しています。 デバッグは、このようなチップを使用して産業用コントローラーで実行されましたが、STM32ファミリーのどのチップでも同じ成功を収めてすべてを繰り返すことができます。 この場合、GPIOBのピン8が私にとって自由だったので、それを利用しました。







次は、タイマーとコントローラーの初期化テキストです。
#define BIT(n) (1u << n) #define LSHIFT(v,n) (((unsigned int)(v) << n)) #define LEDS_NUM 80 #define COLRS 3 INT16U DMA_buf[LEDS_NUM+2][COLRS][8]; /*------------------------------------------------------------------------------ Timer4          PCLK1 72 MHz  3     Compare    DMA  CCR3     ------------------------------------------------------------------------------*/ void Timer4_init(void) { TIM_TypeDef *tim = TIM4; RCC_TypeDef *rcc = RCC; rcc->APB1RSTR |= BIT(2); //   4 rcc->APB1RSTR &= ~BIT(2); rcc->APB1ENR |= BIT(2); //    4 tim->CR1 = BIT(7); // 1: TIMx_ARR register is buffered. tim->CR2 = 0; tim->PSC = 0; //    72  tim->ARR = 90 - 1; //    1.25  tim->CCMR2 = 0 + LSHIFT(6, 4) // OC3M: Output compare 3 mode | 110: PWM mode 1 - In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1 else inactive. + LSHIFT(1, 3) // OC3PE: Output compare 3 preload enable + LSHIFT(0, 0) // CC3S: Capture/Compare 3 selection | 00: CC3 channel is configured as output ; tim->CNT = 0; tim->CCR3 = 0; tim->DIER = BIT(11); // Bit 11 CC3DE: Capture/Compare 3 DMA request enable.   DMA tim->CR1 |= BIT(0); //   tim->CCER = BIT(8); //   ,     DMA } /*------------------------------------------------------------------------------   2 DMA1 Stream 7           WS2812B   TMR4     PWM ------------------------------------------------------------------------------*/ void DMA1_Stream7_Mem_to_TMR4_init(void) { DMA_Stream_TypeDef *dma_ch = DMA1_Stream7; RCC_TypeDef *rcc = RCC; rcc->AHB1ENR |= BIT(21); //  DMA1 dma_ch->CR = 0; //   dma_ch->PAR = (unsigned int)&(TIM4->CCR3) + 1; //     ADC dma_ch->M0AR = (unsigned long)&DMA_buf; dma_ch->NDTR = (LEDS_NUM + 2) * COLRS * 8; dma_ch->CR = LSHIFT(2, 25) + // CHSEL[2:0]: Channel selection | 010: channel 2 selected LSHIFT(0, 23) + // MBURST: Memory burst transfer configuration | 00: single transfer LSHIFT(0, 21) + // PBURST[1:0]: Peripheral burst transfer configuration | 00: single transfer LSHIFT(0, 19) + // CT: Current target (only in double buffer mode) | 0: The current target memory is Memory 0 (addressed by the DMA_SxM0AR pointer) LSHIFT(0, 18) + // DBM: Double buffer mode | 0: No buffer switching at the end of transfer LSHIFT(3, 16) + // PL[1:0]: Priority level | 11: Very high. PL[1:0]: Priority level LSHIFT(0, 15) + // PINCOS: Peripheral increment offset size | 0: The offset size for the peripheral address calculation is linked to the PSIZE LSHIFT(1, 13) + // MSIZE[1:0]: Memory data size | 00: 8-bit. Memory data size LSHIFT(1, 11) + // PSIZE[1:0]: Peripheral data size | 00: 8-bit. Peripheral data size LSHIFT(1, 10) + // MINC: Memory increment mode | 1: Memory address pointer is incremented after each data transfer (increment is done according to MSIZE) LSHIFT(0, 9) + // PINC: Peripheral increment mode | 0: Peripheral address pointer is fixed LSHIFT(1, 8) + // CIRC: Circular mode | 1: Circular mode enabled LSHIFT(1, 6) + // DIR[1:0]: Data transfer direction | 01: Memory-to-peripheral LSHIFT(0, 5) + // PFCTRL: Peripheral flow controller | 1: The peripheral is the flow controller LSHIFT(1, 4) + // TCIE: Transfer complete interrupt enable | 1: TC interrupt enabled LSHIFT(0, 3) + // HTIE: Half transfer interrupt enable | 0: HT interrupt disabled LSHIFT(0, 2) + // TEIE: Transfer error interrupt enable | 0 : TE interrupt disabled LSHIFT(0, 1) + // DMEIE: Direct mode error interrupt enable | 0: Direct mode error interrupt disabled LSHIFT(0, 0) + // EN: Stream enable | 1: Stream enabled 0; dma_ch->FCR = LSHIFT(0, 7) + // FEIE: FIFO error interrupt enable LSHIFT(1, 2) + // DMDIS: Direct mode disable | 1: Direct mode disabled.           LSHIFT(1, 0) + // FTH[1:0]: FIFO threshold selection | 01: 1/2 full FIFO 0; dma_ch->CR |= BIT(0); // 1: Stream enabled }
      
      











この初期化の後、RAMにあるDMA_bufアレイから8 GPIOBの外部出力へのビットストリームの自動転送が開始されます。 50マイクロ秒のリセット一時停止が自動的に生成されます。 プロセッサは転送に参加せず、割り込みは使用されません。 LEDを点灯するには、対応するオフセットでDMA_buf配列に対応するワードを書き込むだけです。 これは、プロジェクトでLEDstrip_set_led_state関数によって行われます。



これは、このメカニズムがプロセッサにまったく影響を与えないということではありません。 彼の仕事はやや遅くなっています。 RAMとシステムバスへのDMA共有アクセスと共有するため。 しかし、測定では、この場合のこのスローダウンは0.2%を超えないことが示されました。



プロジェクトを作成するために、開発環境MDK-ARM Professionalバージョン:4.72.1.0が使用されました。 プロセッサの周波数は144 MHz、PCLK1の周波数は72 MHzです。 STM32 MCU Discovery Kitsシリーズボードに簡単に移植できます。 プロジェクト全体がここに投稿されています。



このプロジェクトでは、STのライブラリまたは他のサードパーティライブラリを使用しませんでした。 プロジェクトは非常にコンパクトで、すべてがレジスタへの直接アクセスを介して記述されているため、テキストが短く、明確になり、他の開発環境への転送が容易になります。



そして虹について



実際には、RGBカラー形式(ビット表現-00000000 RRRRRRRR GGGGGGGG BBBBBBBBB)で単語のバイトを単純に線形にインクリメントするため、100個のLEDを持つLEDストリップに美しい虹を描くことは不可能です。 この虹は明るさを調整するのがさらに難しく、RGB情報を使用して32ビットの単語を簡単に操作できます。 そのような操作には、HSV形式を使用します。 たとえば、虹全体は、Hコンポーネントを単純に線形にインクリメントすることで表されます。 次に、HSVをRGBに変換し、LEDに出力します。

プロジェクトには2つのHSVからRGBへのコンバーターがあります。1つは整数で、もう1つは浮動小数点計算を使用しています。 視覚的には、違いは見られませんでした。 はい、残念ながら、STM32には区別できる場所はありません。



All Articles