PIC24FJの出力コンペアモジュールとLCD3V駆動の実験 [PIC]
PIC24FJには出力コンペアモジュールが内蔵されていて Timer2 または Timer3 のカウント値と比較レジスタ(1個または2個)を使って外部に信号出力したり割り込みを発生させる機能があります。
動作モードとしては
今回はTimer2を使ったデュアル比較の連続パルスモードを使い、5V用LCDモジュールを3.3V電源で動作させる実験を行いました。
[ 前へ ] 連載記事 [ 次へ ]
動作モードとしては
- シングル比較モード
-
デュアル比較モード
- 単一出力パルスモード
- 連続出力パルスモード
-
単純パルス幅変調モード
- フォルト保護入力付き
- フォルト保護入力なし
今回はTimer2を使ったデュアル比較の連続パルスモードを使い、5V用LCDモジュールを3.3V電源で動作させる実験を行いました。
- Timer2の設定
今回は消費電力を削減するためにCLKDIVレジスタ内のRCDIVを2に設定し、内部クロックを2MHz(PLLで4倍なので動作クロックは8MHz)にしています。
Timer2関連のレジスタの設定は次のとおりで1周期を 10KHz にしました。
- T2CON : 0x8000 (TON,Prescale 1:1)
- PR2 : 399(4MHz / (399 + 1) = 10KHz)
因みに config の設定は次のとおり
// CONFIG2 #pragma config POSCMOD = NONE // Primary Oscillator Select #pragma config I2C1SEL = PRI // I2C1 Pin Location Select #pragma config IOL1WAY = OFF // IOLOCK Protection #pragma config OSCIOFNC = ON // Primary Oscillator Output Function #pragma config FCKSM = CSDCMD // Clock Switching and Monitor #pragma config FNOSC = FRCPLL // Oscillator Select (Fast RC #pragma config SOSCSEL = SOSC // Sec Oscillator Select #pragma config WUTSEL = LEG // Wake-up timer Select #pragma config IESO = OFF // Internal External Switch Over Mode // CONFIG1 #pragma config WDTPS = PS1 // Watchdog Timer Postscaler (1:1) #pragma config FWPSA = PR128 // WDT Prescaler (Prescaler ratio of 1:128) #pragma config WINDIS = OFF // Watchdog Timer Window #pragma config FWDTEN = OFF // Watchdog Timer Enable #pragma config ICS = PGx1 // Comm Channel Select #pragma config GWRP = OFF // General Code Segment Write Protect #pragma config GCP = OFF // General Code Segment Code Protect #pragma config JTAGEN = OFF // JTAG Port Enable
- OC1(出力コンペア1)の設定
連続出力パルスモードでの OC1 の出力は 使用するタイマー(今回はTIMER2)のカウント値が OC1RS と一致したとき low になり OC1R と一致した時に high になります。
OC1R はtimer2のカウント最大値にしているので OC1RS の値を変更することで出力される矩形波の duty を変更できます。
- OC1CON : 5(use Timer2,generates continuous output)
- OC1R : 399
- OC1RS : 199(duty 50%)
- PPS(ペリフェラルピンセレクト)の設定
PIC24FJ64GA002 は28ピンのDIPパッケージでピン数が少ないため、PPS機能により、内蔵の機能モジュールの入出力をICの物理的なピンにアサイン可能になっています。
今回は上記で設定した出力コンペアの出力を RP5(14ピン)に出力するようにしました。
レジスタの設定としては RPOR2 レジスタ内の RP5R フィールドに OC1 の機能番号である 18 を設定するだけです。
- 5V用LCDの3V動作実験
5V電源で動作する LCD を 3.3V で動かすためにLCDのコントラスト調整用電源として下に示したチャージポンプ回路でマイナス電圧を発生させている制作例が見受けられます。
人間の目の時間的な周波数特性はあまり高くないので今回は後段の平滑部分(赤い丸で囲んだ部分)を省略して回路を簡略化し、矩形波でコントラスト調整します。
ダイオードが1個減ることにより、負電圧値がVf分高くなるという効果も期待できます。
チャージポンプ回路
また、必要な電圧は -2V 程度あればいいので使用するダイオードはショットキーバリアではなくノーマルのもの(ショットキーより安価、逆電圧時のリーク電流も少ない)にしました。
ショットキーとノーマルの場合の波形を比較したものが次の画像です。黄色がPIC24FJ の出力コンペアモジュールからの出力信号(10KHzの矩形波)で紫色がチャージポンプ回路の出力です。
LCDのコントラスト端子からの流出電流が大きいため逆電圧時のリーク電流が小さいという効果は見られませんがノーマルダイオードでも行けそうです。素子の定数は後述の実験回路を参照してください。
ショットキーバリアダイオード ノーマルダイオード
今回の実験の回路図が下記になります。
回路図では2行表示の LCD ですが、かなり前に秋月電子さんから購入した1行表示の LCD(300円)を使いました。
また、回路図には書いていませんがシリアル接続は RP8:RX(受信)17番ピン、 RP9:TX(送信)18番ピン になります。
シリアルモード : 19200bps、8bit、パリティ無し、Stopビット:1
実験回路(KiCAD)
ロジアナを使うまでもないのですが、LCD 初期化部分の信号を確認した結果が次のキャプチャです。使用しているソフトは pulseview です。
LCD制御信号 (pulseview)
実際に LCD に表示してみると次の写真のように表示できました。写真では少し薄く映っていますがコントラストとしては特に問題ないレベルです(見る角度にも依存しますが・・)
LCD表示例
LCD のコントラスト調整用のマイナス電源を矩形波にしたことで矩形波の duty をソフト制御しコントラスト調整することが可能になります。
Duty を変更した場合のチャージポンプ出力波形が下記になります。紫色がチャージポンプの出力です。
Duty16% Duty70%
それぞれの Duty での LCD 表示は次のようになります。
LCD表示(Duty16%) LCD表示(Duty70%)
いかがでしょうか?5V用LCDを3.3 Vで使用するための部品を半減できた上にコントラスト調整もソフト制御で簡単にできる(コントラスト調整用ボリュームも不要)ようになりました^^
今回の実験で作成したプログラムを末尾に示します(picle言語だけを使っています)。消費電力を抑えるために init() 処理内で PIC24FJ の動作クロックを1/4(32MHz⇒8MHz)に変更しています。
LCD 表示部分は以前 PIC16F88 を使ったツール制作の時に作成したC言語のものを picle に書き換えて使用しました。言語仕様が近いのでコンバージョンは楽です。
実験プログラムの操作としては'1'、'2'のキー入力でそれぞれ duty が減少/増加し、スペースキーでプログラム終了となっています。
PIC24FJ OutputCompare module test(picle言語) # PIC24FJ OutputCompare module test # to use LCD with 3V # written with picle language by skyriver # Ver 0.01 2017/03/18 var _REG,REG,TRISA,LATA,TRISB,LATB; var AD1PCFG; var T2CON,PR2; var bitLCD_E,bitLCD_RS,bitLCD_POW; var LCDPORT; # wait tim x 50us proc LcdWait( tim ) { var i; while ( tim ) { for ( i = 20; i; i=i-1 ) {} tim = tim-1; } } proc LcdWrite( nible ) { LATA[0] = LATA[0] & $fff0 | (nible & $000f); } proc LcdWrCmd( nible ) { LcdWrite( nible ); LCDPORT[0] = LCDPORT[0] & ~bitLCD_RS; LCDPORT[0] = LCDPORT[0] | bitLCD_E; LCDPORT[0] = LCDPORT[0] & ~bitLCD_E; LCDPORT[0] = LCDPORT[0] | bitLCD_RS; LcdWrite( $ff ); # decrease pull up current } proc LcdWrDat( nible ) { LcdWrite( nible ); LCDPORT[0] = LCDPORT[0] |bitLCD_RS; LCDPORT[0] = LCDPORT[0] | bitLCD_E; LCDPORT[0] = LCDPORT[0] & ~bitLCD_E; LcdWrite( $ff ); # decrease pull up current } proc LcdWrCmdWait( nible ) { LcdWait( 1 ); LcdWrCmd( nible ); } proc LcdWrCmdWait8( dat ) { LcdWrCmdWait( dat / 16 ); LcdWrCmd( dat ); } proc LcdWrDatWait8( dat ) { LcdWait( 1 ); LcdWrDat( dat / 16 ); LcdWrDat( dat ); } proc LcdClear() { LcdWrCmdWait8( $01 ); LcdWait( 31 ); } proc LcdSetPos( pos ) { pos = pos | $80; LcdWrCmdWait8( pos ); } proc LcdDspStr( _str ) { while ( _str[0] <> '"' ) { LcdWrDatWait8( _str[0] ); _str = _str + 1; } } proc LcdInit() { LCDPORT[0] = LCDPORT[0] | bitLCD_RS | bitLCD_POW; LcdWait( 200 ); LcdWrCmd( $03 ); LcdWait( 82 ); LcdWrCmd( $03 ); LcdWait( 2 ); LcdWrCmd( $03 ); LcdWrCmdWait( $02 ); LcdWrCmdWait8( $28 ); # set 4bit mode LcdClear(); LcdWrCmdWait8( $06 ); # set entry mode,inc,non-shift LcdWrCmdWait8( $0c ); # display on } proc init() { TRISA = $02c0; LATA = $02c4; TRISB = $02c8; LATB = $02cc; LCDPORT = LATB; AD1PCFG = $032c; T2CON = $0110; PR2 = $010c; bitLCD_E = $0002; bitLCD_RS = $0001; bitLCD_POW = $0080; _REG[$0745] = _REG[$0745] & $f8 | 2; # FRC:2MHz(CLKDIV) REG[$0228/2] = 12; # 19200bps(U1BRG) REG[$0102/2] = 155; # 10ms(PR1) TRISA[0] = $fff0; TRISB[0] = ~(bitLCD_E | bitLCD_RS | bitLCD_POW); AD1PCFG[0] = $ffff; # set digital mode T2CON[0] = $8000; # 1:1,16bit.internal PR2[0] = 399; _REG[$06c5] = 18; # set OC1 to RP5(RPOR2H) REG[$0184/2] = 5; # timer2,dual compare(OC1CON) REG[$0182/2] = PR2[0]; # OC1R REG[$0180/2] = PR2[0]/2;# OC1RS } proc main() { var duty,c; init(); LcdInit(); LcdDspStr( "hello to" ); LcdSetPos( $40 ); LcdDspStr( " picle" ); duty = 50; while ( 1 ) { if ( InpChk_() ) { c = InpChar_() & $ff; if ( c = '1' ) { duty = duty - 2; } else if ( c = '2' ) { duty = duty + 2; } else if ( c = ' ' ) { break; } REG[$0180/2] = REG[$0182/2] * duty / 100; PrnStr_("\nduty : "); PrnDec_( duty ); } } }
[ 前へ ] 連載記事 [ 次へ ]