
PIC24 based DMX Receiver to 5PWM with CCS Compiler
これまで8bit PIC(PIC16FシリーズやPIC18Fシリーズ)を使ってDMX受信を行ってきたが、制御系を一新する意味で16bit PIC(24FJ64GA002)で設計し直すことにした。8bit PICと16bit PICでは異なる部分が多かったがなんとか移植に成功した。
一時期、ArduinoやmbedとTLC5940を使って実験してみたが、他人の作ったライブラリを自分仕様に合わせるなら、結局自分で開発したほうがベストだなと思った。特にArduinoのPWM周波数は高くても980Hz程度で、今回のPIC24では62.5kHzにしているので差がありすぎる。人間の目には1kHz以上にしておけば気がつかないようだが、Arduinoで複数のLEDを制御した時にちらついたのはPWM周波数が低いことが原因ではないかと思う。
今回のプログラムのDMX受信アルゴリズムはCCSのフォーラムで公開されていたmarkのプログラムを利用している。markのプログラムでは8bit PICの9bit受信モードを使って2個目のストップビットを無視する方法を取っているが、16bit PICでは2bitのストップビットを検出できるのでDMXの仕様である8bitモードのままで受信している。DMX信号をマイコンでどう受信するかについては電装工芸さんが詳しく記述しており、Microchipの公式アプリケーションノート「Using a PIC Microcontroller for DMX512 Communication」でもDMX送受信を扱っているので参考にしている。
コンパイラはCCSのPCD(公式サイト直販でコンパイラのみの場合250ドル)を利用。5つのLEDをハードウェアPWMで制御している。プログラム内のRx_Buffer配列を使えば512個のデータを処理できる。受信テスト用のDMX送信にはEnttec DMX USB Proを利用したが、0〜512CHを問題なく受信することができた。
下図は回路図(クリックして拡大)。
今回PIC24を使う際に感じたPIC18との違い。
<回路に関するもの>
・PIC24は内部では2.5Vで駆動しているが、内蔵レギュレータを使うことで3.3V駆動できる。その場合はVcapに10uFの電解コンデンサをかます。
・5Vトレラント可能なピンは限られているのでデータシートを確認(PIC24FJ64GA002ではRB6〜RB11)
<プログラムに関するもの> ※CCSコンパイラを使う前提
・PIC24では主発振子と副発振子の2つを接続できるので、HSモード(20MHz)の他に、PR(主発振子選択)が必要
・ボーレートを計算する時のFcyは外部発振子の半分になる。今回の回路では20MHzのクリスタルなので、Fcyは10MHzになる。DMX信号は250kbps、BRGH=1、BRG=9となる。(ボーレートの計算式はデータシート参照)
・DMX信号は、8bit、ノンパリティ、2bitストップビットであり、PIC18ではこの2bitストップビットが読めないために9bitモードで受信するか、8bit受信モードで受信して読めないストップビットをTimeBetweenFrameとして処理するしかなかったが、PIC24では2bitストップビットを読めるのでDMX仕様に合わせた受信を行うことができる。
・PIC18のSPENがPIC24ではなくなっている。UARTの許可はUARTENを使う。
・PIC18のCRENがPIC24ではなくなっている。PIC18ではオーバーランエラー時にCRENをクリアして受信バッファをクリアしていた(PIC18のOERRはリードオンリーでCRENのクリアに連動してOERRがクリアされる仕様)。PIC24ではOERRが読み込みとクリアもできるようになっているので、オーバーランエラーの場合はOERRそのものをクリアする。
・PIC18のRCIFの代替はURXDA。※U1RXIFは名前が似ているが用途が異なる。
・PIC24のUART受信割り込みが発生した場合はユーザがソフトウェア上でU1RXIFをクリアする必要がある。割り込み関数を使わずURXDAのみでプログラムを組んだ場合はこの限りではない。
・CCSコンパイラ上でPWMのデューティ値を変数として入力する場合、この変数がshort(int8)であるかint16であるかによってPWMの解像度が自動で変わる仕様になっている。shortの場合は256階調だが、int16では1024階調になる。今回は送信データが256階調のDMX信号を使うので、変数はshortにしている。
私の電子回路とプログラムの知識は全て独学なので、非効率的な所や必要ない命令が含まれているかもしれないが、ひとまず動作するので参考程度にしてください。
//CCS PCD5.028コンパイラ //DMX受信プログラム #include <24FJ64GA002.h> #fuses HS,PR, NOWDT, NOPROTECT,NODEBUG //HSモード、PR(外部主発振器選択) #use delay(clock=20000000) #include <string.h> #include <stdio.h> #CASE //コンパイルをケースセンシティブにする 必須 //Registors Address #word U1MODE = 0x0220 #word U1STA = 0x0222 #word U1RXREG = 0x0226 //UART受信バッファ #word U1BRG = 0x0228 //ボーレートプリスケーラ #word IFS0 = 0x0084 //フラグ用のレジストリ //U1MODE Registor #bit UARTEN = U1MODE.15 //PIC18FのSPENの代わり #bit BRGH = U1MODE.3 #bit STSEL = U1MODE.0 //Stopbit設定 //U1STA Registor #bit OERR = U1STA.1 //オーバーランエラーフラグ #bit URXDA = U1STA.0 //受信バッファフラグ PIC18FのRCIFと同等 //IFS0 Registor #bit U1RXIF = IFS0.11 //UART割り込みフラグ //ピン割り当て #pin_select U1RX = PIN_B9 //UART受信ピン #pin_select OC1 = PIN_B11 //PWMピン #pin_select OC2 = PIN_B12 // #pin_select OC3 = PIN_B13 // #pin_select OC4 = PIN_B14 // #pin_select OC5 = PIN_B15 // #define MAX_PWMS 512 #define START_CH 0 unsigned short Rx_Buffer[MAX_PWMS]; //必ず8bit変数にする PWMの解像度にかかわる int1 Check_levels=0; int i; void Interrupt_USART_Rx(void); /************************************************************************ * メイン関数 * ************************************************************************/ void main (void) { U1BRG = 0x09; //外部主発振子20MHz 250kbps for DMX BRGH = 1; STSEL = 1; //ストップビットを2bitに設定 UARTEN = 1; //UARTの開始 set_tris_b(0b0000001000000000); //UART受信ピン PIN_B9(RP9)だけ入力 //PWM用 setup_compare(1, COMPARE_PWM | COMPARE_TIMER2); //OC1 setup_compare(2, COMPARE_PWM | COMPARE_TIMER2); //OC2 setup_compare(3, COMPARE_PWM | COMPARE_TIMER2); //OC3 setup_compare(4, COMPARE_PWM | COMPARE_TIMER2); //OC4 setup_compare(5, COMPARE_PWM | COMPARE_TIMER2); //OC5 setup_timer2(TMR_INTERNAL | TMR_DIV_BY_1, 255); //タイマーは一つで流用 enable_interrupts(INT_RDA); //UART受信割り込み許可 enable_interrupts(INTR_GLOBAL); //すべての割り込みの許可 PIC24バージョン delay_ms(2000); //Rx_Buffer初期設定 for (i=0; i<MAX_PWMS; i++) { Rx_Buffer[i] = 0; } while (1) { if (Check_levels) { Check_levels=0; set_pwm_duty(1, Rx_Buffer[START_CH]); //RB11 set_pwm_duty(2, Rx_Buffer[START_CH+1]); //RB12 set_pwm_duty(3, Rx_Buffer[START_CH+2]); //RB13 set_pwm_duty(4, Rx_Buffer[START_CH+3]); //RB14 set_pwm_duty(5, Rx_Buffer[START_CH+4]); //RB15 } //END if } //END_WHILE } //END_MAIN /************************************************************************ * UART割り込み関数 * ************************************************************************/ #INT_RDA void Interrupt_USART_Rx(void) { U1RXIF = 0; //割り込み関数が動作したら、すぐに割り込みフラグはクリアする #define WAIT_FOR_NEXT_BYTE 0 #define WAIT_FOR_BREAK 1 #define WAIT_FOR_START 2 #define RECEIVE_DATA 3 static int Rx_State = WAIT_FOR_BREAK; char data; union //受信ステータスタック用の構造体を含む共用体 { unsigned int word; struct { unsigned int URXDA:1; unsigned int OERR:1; unsigned int FERR:1; unsigned int :13; //これ以降は使わないので省略 } bits; } u1sta; static int Rx_Index = 0; while(URXDA) { u1sta.word = U1STA; //OERRとFERRを検出するためのスタック data = (char)U1RXREG; //受信データの読み込み if (u1sta.bits.OERR) { OERR = 0; //PIC18FではCRENのリセットでOERRがクリアされたが、PIC24FではソフトウェアでクリアしてUART受信バッファをクリアする Rx_State = WAIT_FOR_NEXT_BYTE; return; } switch (Rx_State) { case WAIT_FOR_NEXT_BYTE: if (!u1sta.bits.FERR) Rx_State = WAIT_FOR_BREAK; break; case WAIT_FOR_BREAK: if (u1sta.bits.FERR) { if (!data) Rx_State = WAIT_FOR_START; } break; case WAIT_FOR_START: if (u1sta.bits.FERR) Rx_State = WAIT_FOR_NEXT_BYTE; else { if (!data) { Rx_Index = 0; Rx_State = RECEIVE_DATA; } } break; case RECEIVE_DATA: if (u1sta.bits.FERR) { if (!data) Rx_State = WAIT_FOR_START; else Rx_State = WAIT_FOR_NEXT_BYTE; } else { Rx_Buffer[Rx_Index] = data; ++Rx_Index; Check_levels=1; } break; } //END switch } //END while return; } //END