PIC16F19155でAliexpressのセグメント液晶を動かす

前回、Aliexpressで買ったセグメント液晶のピン配置を調べてみました。
alasixosaka.hatenablog.com
今回は、そのセグメント液晶を使って時計表示をさせてみます。
Aliexpressのセグメント液晶のうち、小さい方の6桁のやつ使います。

Aliexpressで買った6桁のセグメント液晶

ピン数が足りない?

全部で18ピンです。コモンピンが4つあり、セグメント表示用のピンは14個ということになります。一方、PIC16F19155のスペックですが、4コモンの場合最大で16ピンまでセグメント表示用のピンが使用できます。ですので、スペック上では使えるということになりますが、チャージポンプを使うとSEG10ピンとSEG11ピンにフライバックコンデンサを接続するので使えるピンが2つ減ります。チャージポンプを使う場合、電源の設定によってさらにピン数が減る可能性があります。また、時計合わせの機能を搭載することを考えていて、その場合、外部との通信にUARTピンが2つ必要で、そして、外部機能制御用にIOピンも一つ使う必要がありそうです。それらを考えるとチャージポンプを使わない場合にセグメント用のピンが13個、チャージポンプを使う場合は11個ということになります。
ここでもう一度セグメント液晶のピンを見ると、コロンが2つあって、それぞれひとつづつピンを使っているのでコロンをなくせばピンが2つ浮くことになります。また、時間の10の位は24時間表示だと7セグメント全部使う必要がありますが、12時間表示にしてやれば、表示は1かなしの2種類を表示できればいいことになるので、もう一つピンが浮きます。ですので、チャージポンプを使ってもかろうじて数字だけは表示できそうです。見栄えを考えると時と分の間のコロンはあった方が良いと思われますので、チャージポンプが必須になるかどうかがポイントです。
まだおおざっぱですが、回路図を書いてみました。今回は配線が多いのでいつものFrizingでなく、KiCADを使っています。セグメントLCDの部品図はさすがになかったので自分で作りました。KiCADで新規に部品を作る際はこのサイトを参考にしました。
marunaka-blog.com
また、ライブラリにないパーツもRSコンポーネンツのサイトに載っているものであればダウンロードして使えます。そのやり方はこちらを参考にしました。PIC16F19155はKiCADの標準ライブラリになかったのでRSコンポーネンツのサイトからダウンロードしました。
marunaka-blog.com
一応チャージポンプを使う前提で回路図を描いています。

回路図(KiCADで作成)

RB6とRB7はインラインサーキットプログラム端子と共用していますが、プログラムを書き終われば使えるようになるので、これを外部との通信用の端子としています。図の右の方にジャンパーピンを描いていますが、プログラム時はジャンパーピンをオープンにしてPICKit3と接続できるようにしています。時計として使うときはジャンパーピンをショートします。また、外部回路制御用のIOピンはRA3を使っています。回路図を見てRA5が余っているじゃないかと思われ方もいると思いますが、RA5は入力専用で出力には使えません。Lチカをやった時に最初RA5でうまく動かず、VBATと共用になっているからだと思っていました。しかし、VBATの機能をOFFにしても、出力には使えないようです。MCCの設定ではいかにもGPIOとして入力にも、出力にも使えそうな感じになっているので、出力にも使えるのかと思い試してみましたがうまくいきませんでした。色々調べると、マイクロチップのフォーラムにこの問題が載っていました。
https://www.microchip.com/forums/m1103729.aspx
たしかに、VBAT端子になっているRA5はTRISレジスタや、LATレジスタにはデータシート上も設定できないようになっていておかしいと思っていたのですが、MCCの設定が紛らわしく、入力オンリーというのが正解のようです。

まずはチャージポンプ無しで動かしてみる。

テスト用のプログラムです。RTCCを使って、1秒毎に割り込みをかけ、割り込み処理でRTCCの時、分、秒を読み込んでセグメント液晶に表示します。
コンフィグレーションの部分は省略します。また、液晶表示用の配列は、前々回と同様の方法で作成しました。
alasixosaka.hatenablog.com

int s = 0;
int m = 0;
int h = 0;

void __interrupt() isr(void)
{
    PIR8bits.RTCCIF = 0;
    s = SECONDS;
    m = MINUTES;
    h = HOURS;
    return;
}

void main(void) {
    ANSELA = 0b00000000;  // ?????????????????
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00000000;  // ???????????????(???RA3??????????)
    TRISB = 0x00;
    TRISC  = 0b00000000;  //RC7, RC2をINPUTに
    
    // NOSC HFINTOSC; NDIV 1; 
    OSCCON1 = 0x60;
    // CSWHOLD may proceed; SOSCPWR Low power; 
    OSCCON3 = 0x00;
    // MFOEN disabled; LFOEN disabled; ADOEN disabled; SOSCEN enabled; EXTOEN disabled; HFOEN disabled; 
    OSCEN = 0x08;
    // HFFRQ 1_MHz; 
    OSCFRQ = 0x00;
    // MFOR not ready; 
    OSCSTAT = 0x00;
    // TUN 0; 
    OSCTUNE = 0x00;
    // ACTUD enabled; ACTEN disabled; 
    ACTCON = 0x00;
    
    FVRCONbits.FVREN = 1;   //FVR is enabled
    
    LCDCON = 0b00010100;  //LCD disable, SLPEN, SOSC, 1/4MPLX
    LCDPS = 0b00000000;  //TypeA, LCD inactive, PS = 1/1
    LCDSE0 = 0b11001111;  //SE7, SE6, SE3, SE2, SE1, SE0 on
    LCDSE1 = 0b00000011;  //SE9, SE8 on
    LCDSE2 = 0b11011000;  //SE23, SE22, SE20, SEG19 on
    LCDVCON1 = 0b10000000;  //Charge pump NC, 3.5V range, 3.00V
    LCDVCON2 = 0b00000011;  //FVR + internal resister ladder
    LCDRL = 0b01010000;  //Low Power, Allways B Power
    LCDREF = 0b0000000;  //Maxmum contrast
    LCDCONbits.LCDEN = 1;   //LCD Enable
    
    //__builtin_write_RTCWREN();
    
    RTCCONbits.RTCWREN = 1;

    // Disable RTCC
    RTCCONbits.RTCEN = 0;
    
    RTCCONbits.RTCCLKSEL = 0;
    
    
       // set RTCC time 2022-09-11 21-44-10
    YEAR     = 0x22;   // year
    MONTH    = 0x9;    // month 
    WEEKDAY  = 0x0;    // weekday 
    DAY      = 0x11;    // day
    HOURS    = 0x21;    // hours 
    MINUTES  = 0x44;    // minutes 
    SECONDS  = 0x10;    // seconds 
        
    
    
    
    // set Alarm time 2022-09-11 21-44-10
    ALRMCONbits.ALRMEN = 0;
    // ARPT 0; 
    ALRMRPT = 0x00;
    
    ALRMMTH  = 0x9;  // month 
    ALRMWD   = 0x0; // weekday 
    ALRMDAY  = 0x11;  // day
    ALRMHR   = 0x21; // hours 
    ALRMMIN  = 0x44;  // minutes 
    ALRMSEC  = 0x10;  // seconds 

    // Re-enable the alarm
    ALRMCONbits.ALRMEN = 1;
    
    // AMASK Every 10 Second; CHIME enabled; ALRMEN enabled; 
    ALRMCON = 0xC4;

    
    // CAL 0; 
    RTCCAL = 0x00;
    
    // Enable RTCC
    RTCCONbits.RTCEN = 1;
    while(!RTCCONbits.RTCEN);

    // Disable write operations on RTCC timer registers
    RTCCONbits.RTCWREN = 0;

    // Clear the RTCC interrupt flag
    PIR8bits.RTCCIF = 0;

    // Enable RTCC interrupt
    PIE8bits.RTCCIE = 1;
    
    INTCONbits.PEIE   = 1 ;             // 周辺装置割り込み有効
    INTCONbits.GIE    = 1 ;             // 全割込み処理を許可する
    
    while(1){
        int s1 = s & 0x0F;
        int s10 = s >> 4;
        int m1 = m & 0x0F;
        int m10 = m >> 4;
        if (h>0x19){
            h = h -0x18;
        }else if (h>0x12){
            h = h - 0x12;
        }
        int h1 = h & 0x0F;
        int h10 = h >> 4;
        while(!LCDPSbits.WA){}
        LCDDATA0 = (Num6[s1][0]|Num5[s10][0]|Num4[m1][0]|Num3[m10][0]|Num2[h1][0]|Num1[h10][0]);
        LCDDATA1 = (Num6[s1][1]|Num5[s10][1]|Num4[m1][1]|Num3[m10][1]|Num2[h1][1]|Num1[h10][1]);
        LCDDATA2 = (Num6[s1][2]|Num5[s10][2]|Num4[m1][2]|Num3[m10][2]|Num2[h1][2]|Num1[h10][2]);
        LCDDATA6 = (Num6[s1][3]|Num5[s10][3]|Num4[m1][3]|Num3[m10][3]|Num2[h1][3]|Num1[h10][3]);
        LCDDATA7 = (Num6[s1][4]|Num5[s10][4]|Num4[m1][4]|Num3[m10][4]|Num2[h1][4]|Num1[h10][4]);
        LCDDATA8 = (Num6[s1][5]|Num5[s10][5]|Num4[m1][5]|Num3[m10][5]|Num2[h1][5]|Num1[h10][5]);
        LCDDATA12 = (Num6[s1][6]|Num5[s10][6]|Num4[m1][6]|Num3[m10][6]|Num2[h1][6]|Num1[h10][6]);
        LCDDATA13 = (Num6[s1][7]|Num5[s10][7]|Num4[m1][7]|Num3[m10][7]|Num2[h1][7]|Num1[h10][7]);
        LCDDATA14 = (Num6[s1][8]|Num5[s10][8]|Num4[m1][8]|Num3[m10][8]|Num2[h1][8]|Num1[h10][8]);
        LCDDATA18 = (Num6[s1][9]|Num5[s10][9]|Num4[m1][9]|Num3[m10][9]|Num2[h1][9]|Num1[h10][9]);
        LCDDATA19 = (Num6[s1][10]|Num5[s10][10]|Num4[m1][10]|Num3[m10][10]|Num2[h1][10]|Num1[h10][10]);
        LCDDATA20 = (Num6[s1][11]|Num5[s10][11]|Num4[m1][11]|Num3[m10][11]|Num2[h1][11]|Num1[h10][11]);

        SLEEP();
        NOP();
    }
    return;
}

グローバル変数として、h、m、sを定義して、割り込み処理ルーチンでRTCCの時、分、秒の値をそれぞれh、m、sに代入しています。
ループの中でそれぞれを10の位と1の位に分けてs1が秒の1の位、s10が秒の10の位というようにしています。BCDコードなので、0x0Fとアンドを取れば1の位。ビットシフトを4回すると10の位に分かれます。時の部分だけは12時間表示にするために、次のように計算して換算しています。BCDなので20以上の場合と13から19の場合に引く数が異なります。

       f (h>0x19){
            h = h -0x18;
        }else if (h>0x12){
            h = h - 0x12;
        }

それぞれの桁の表示用データは二次元配列に格納されていて、秒の1の位がNum1、10の位がNum2となっていて、Num6まであります。長くなるので中身は省略します。
それぞれの配列の列は12列あって、セグメントをコントロールするレジスタLCDDATA0~LCDDATA20用に割り振られているので、それぞれを取り出してORを取ってレジスタに書き込んでいます。
それ以外の、RTCCの設定などは以前にやっていたことと同じです。チャージポンプはなしでFVRを電源にした設定にしています。

電圧が2.5V以下では動かない。

プログラムを書き込んで正常に動作することを確認したので、電圧を変えて表示がどうなるか見てみました。電源は、Aitendoで買ったDC-DC降圧モジュールというのを使いました。
www.aitendo.com
これにACアダプタから5VのDCを入力し、出力電圧を変えながらどこまで下げられるか調べてみた。

ほぼ予想通りでしたが、FVRを使うと電圧が下がると液晶が薄くなって見づらくなり、2.5Vを下回ると表示が消えPICの動作そのものが停止しました(たぶん)。データシート上は2.3Vまで動くことになっていますのでひょっとしたらマイコン自体は動いているのかもしれませんが、いずれにしても液晶が見えないとどうしようもないので、使える下限は2.5V、しかも液晶が薄くしか表示できないという結果でした。(写真が逆さまです、すいません)

PIC16F19155では2.5Vで液晶が見えなくなる

電池での駆動を考えると2.5Vで使えないというのは単三2本直列でつないで動かすことを想定すると1本あたり1.25Vまで電圧が下がった時に使えなくなるということなります。1.25Vというとニッケル水素電池の電圧よりも高いので普通の機器は動かすことができる電圧範囲なので結構早めに死んでしまうということになります。やはり、電池駆動を考えると通常の品番でなく、LFシリーズを使う方が良いと思われます。まあ、DC-DCコンバータをかませて3.3Vくらいに昇圧してやるという手もありますが、昇圧コンバーターもチャージポンプを使うので効率を考えると消費電流が増えることになるのでどんなもんですかねというところです。
2.5V以下で動かないのでこのままチャージポンプのテストをしてもあまり意味がないので、手持ちのPIC16LF19176を使って実験してみました。

PIC16LF19176でFVRを使ってみる。

PIC16LF19176は40ピンの大型のチップです。ピン配置はPIC16F19155と大きく変わらず、プログラムもほぼそのまま使えます。IOピンが多いのでピン設定用のレジスタが多いことくらいしか違いはありません。ただ品番がLFなので、使用できる電圧範囲が1.8-3.6Vと低く電池駆動に向いている。逆に5V系のArduinoなどとは直接接続できない。

プログラムの書き込みで苦労する。

PIC16LF19176はノートPCではうまく書き込めないと以前にも書いたが、
alasixosaka.hatenablog.com
今回も書き込みに苦労した。まず、基本のLチカのプログラムとRTCCの割込みを使ったLチカまではノートPCで書き込めた。
しかし、セグメント液晶との配線を行って、液晶を駆動するプログラムを書きこもうとすると電流値が取れないのか書き込みに失敗する。電源を別系統にしてPicKitから供給するのをやめると書き込めるかと思ったが結果は変わらず。ここで、以前はうまくいったデスクトップでやってもよかったが(少し前に書いたようにデスクトップの方のMPLAB XIDEはX16コンパイラをインストールしてから調子が悪い)、それならばと、ZIFを使ったPICの書き込み機を使ってみた。これは、PicKit3(といってもバッタもの)を買った時について来たものだが、今までは使っていなかった。
これを使うと何とか書き込みができるが、それでも不安定で、書き込めたり書き込めなかったり、USBポートを差し替えたり、チップをはめ直したりして何とか書き込めるという状態。チップの個体差もあるのかもしれないが今のところそれは何とも言えない。

ZIFを使ったPICの書き込み機

何とか書き込めたので、動かしてみた。プログラムはほぼそのままで、FVRでの駆動です。
結果はたしかに2V以下まで動くことは動くが、液晶の表示は2.35Vくらいでほとんど読めなくなった。なので、FVRをつかうなら実用的には2.4V以上が必要という感じ。

FVRを使うと2.35Vくらいで液晶がほぼ読めなくなる

PIC16LF19176でチャージポンプを動かしてみる。

それならチャージポンプを使ったらどうかという実験をしてみた。
コンフィグレーションビットのLCDPENをONにし、LCDVCON1をLCDENを1(Low Current)、EN5Vを0(3.5V)、BIASを011(3.1V)に設定。LCDVCON2は0110のチャージポンプオンリーでまず動かしてみた。フライバックコンデンサ1μFをピン35と36の間に接続し、VLCD3(ピン24)に今回も手持ちの0.33μFのコンデンサを接続した。
すると、たしかに電圧を下げても液晶は見えることは見えるが、液晶の表示がちらつく。

チャージポンプオンリーの設定で動かすと液晶がちらつく(写真ではわかりづらいですが)

それなら、ラダー抵抗を使った設定ならと、LCDVCON2を0111にして、チャージポンプ+ラダー抵抗にしてコントラストは最大にしてみた。ついでに電圧設定BIASを3.3V(101)に変更。
すると、ちらつきもなくなり奇麗に表示された。電圧も2Vを下回っても十分に見える。

チャージポンプ+ラダー抵抗の設定だと奇麗に見える

LFを使ってチャージポンプを使うことで電圧が2Vくらいまで使えることがわかった。いちおうこれで時計側の基本回路は何とかなりそう。チップを28ピンのPICLF19155にして新たに購入するか、今手持ちの40ピンのPICLF19176にするかはもう少し検討してみることにする。