地獄の3時間走

またしてもLSD失敗。

最近電子工作ネタばかり書いていたので、久々にトレーニングの話を。
といっても、タイトルの通り思い通りにはいかなかったという話です。
そもそも、昨日(10/1)は比良トレイルにエントリーしていたんです。夏に行った、武奈ヶ岳が山頂からの景色が全く拝めず、もう一度行ってみたいと思い申し込んだんですが、諸般の事情で参加を取りやめることに。
その事情というのが、家族がコロナにかかったということで、その日から、出勤は約1週間停止になり、在宅勤務の日々。
そして、今日がその出勤停止明けの日ということで、でも日曜日なのでお休みです。
なので、レースに参加する代わりと言っては何ですが、近所の河原でLSDでもしようかと走ったわけです。
最初は、比良トレイルが29kmなので、平地だし30kmと思ったんですが、家に引きこもりの日々を1週間続けたということを考えて、25kmくらい、3時間を目標に走ることに。

出だしのペースは良かった(?)が

走り始めは体が軽く、想定ペースが7-8分/kmのところを、6分台前半。ちょっと早すぎるかと思いペースダウンしてみたものの、それでも6分30秒くらい。想定よりだいぶ早く途中でへばるかなと思いつつ、まあいいか、行けるところまで行って見ようという感じでそのまま走った。2週間前に15kmの坂道を問題なく走れていたので結構自信があったのもある。ところが、やっぱり引きこもりの日々は予想以上に体力を奪っていたようで、折り返したあたりから徐々におかしくなり始めた。
折り返して気づいたのは、前半は追い風だったこと。追い風+下りということで結構ペースが上がってしまっていたらしい。それでも、折り返した直後は7.5分/kmくらいでそんなに悪いペースではない。ところがすぐに、股関節と左の膝が痛くなり始めた。はじめは何とかごまかして走っていたが、徐々に痛みに耐えられなくなってついに歩き出すことに。

ここからが地獄の始まり。

もう半分以上来てしまっているし、駅からは結構離れているところなので、このまま河原を歩いてでも帰るしかない。スマホも持ってきていなかったので、バスを検索することもできず。もっと短い距離にしておけばよかったと後悔したが後の祭り。
歩いては、少し走って、痛くなったらまた歩いてを繰り返しだましだまし進んでいく。はじめは歩く分には痛みを全く感じなかったが残りが3kmくらいの所で、歩いていても痛みを感じるようになり完全に走るのを諦め、歩いて帰ることに。しかし、行きと違って足取りは重い。
なんとかかんとか家にたどり着いたが、到着したときは、夏に60km夜間歩行してゴールした時のような疲労感だった。
それにしても、ずっと家にいるだけでこんなに体力が落ちるとは、今日は本当に恐ろしい目にあった。おまけに、夕方には思いっきり血尿が出た。予想以上に疲労していたようだ。来月には六甲山全縦が待っているのにこんなんで大丈夫だろうか心配になってきた。

歩くだけでも日ごろの運動は大切。

トレランを始めてから基本的には週末のみそれも1日だけのトレーニングで何とか済ませてきた。しかし、改めて考えてみると、日々通勤で結構な距離を歩いているので、それがいいトレーニングになっていたのだということ。だいたい、13000-15000歩位歩くので、歩くだけでもいい運動になっていたんだということを実感した。

PIC16F19155で電子ペーパーを動かす(その1)

PICを使って電子ペーパーの時計を作ろうという企画です(うまくいかどうか?)。
まず、電子ペーパーの動作を確認するために、電子ペーパーのメーカーであるWaveShareのサンプルプログラムをArduinoで動かして、それを自分なりにアレンジして数字を書き換えるという動作をやってみました。
Arduino電子ペーパーを動かす記事の第一回はこちら。
alasixosaka.hatenablog.com
次に、PIC16F19155を使いこなすために、PICの基本的な機能、RTCC、SPI、EUARTなどを使ったテストを行いました。
PIC16F19155を使ったテストの第一回はこちら。
alasixosaka.hatenablog.com
今回から、PICで電子ペーパーを動かしてみるシリーズになります。最終的に時計を作成しようと思っていますが、さてどうなりますか。
第一回は、PICにArduinoで動かしていたプログラムを移植することから始めます。すなわち、電子ペーパーに小さな数字を書いてみるプログラムです。
配線です。

PIC16F19155と電子ペーパーを繋ぐ

PIC側は、前回同様SPIにMSPP1を使いました。今回は、電子ペーパーが受け側だけですので、MISOは繋いでいません。
それぞれの端子の割り当ても前回同様で、
MOSIがRC4、CLKがRB0、CSがRC3です。
電子ペーパーと接続するためには、それ以外に、DC、RST、BUSYの3端子を繋ぐ必要があるので、DCはRB1、RSTはRB2、BUSYはRC7に繋ぎました。
RC7だけは入力端子です。
なお、上の図で電子ペーパーはFrizingにパーツがなかったので、LCDモジュールの絵で代用しています。右から順に、VCC、GND、DIN(MOSI)、CLK、CS、DC、RST、BUSYです。
プログラムの全文です。
プログラムは、Arduinoのプログラムをほぼそのまま移植したので説明するまでもないと思います。どちらもC系の言語で書かれているので対して苦労はしませんでした。今回は、テストが目的なので、フォントサイズの小さな0と1を交互に表示するプログラムになっています。SPIのモードは0です。

#include <xc.h>
#include<stdbool.h>

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits->Oscillator not enabled
#pragma config RSTOSC = HFINT1    // Power-up default value for COSC bits->HFINTOSC (1MHz)
#pragma config CLKOUTEN = OFF    // Clock Out Enable bit->CLKOUT function is disabled; i/o or oscillator function on OSC2
#pragma config VBATEN = OFF    // VBAT Pin Enable bit->VBAT functionality is disabled
#pragma config LCDPEN = OFF    // LCD Charge Pump Mode bit->LCD Charge Pump is disabled.
#pragma config CSWEN = ON    // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON    // Fail-Safe Clock Monitor Enable bit->FSCM timer enabled

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit->MCLR pin is Master Clear function
#pragma config PWRTE = OFF    // Power-up Timer selection bits->PWRT disable
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit->ULPBOR disabled
#pragma config BOREN = ON    // Brown-out reset enable bits->Brown-out Reset Enabled, SBOREN bit is ignored
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices
#pragma config ZCD = OFF    // Zero-cross detect disable->Zero-cross detect circuit is disabled at POR.
#pragma config PPS1WAY = OFF    // Peripheral Pin Select one-way control->The PPSLOCK bit can be set and cleared repeatedly by software
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable bit->Stack Overflow or Underflow will cause a reset

// CONFIG3
#pragma config WDTCPS = WDTCPS_31    // WDT Period Select bits->Divider ratio 1:65536; software control of WDTPS
#pragma config WDTE = OFF    // WDT operating mode->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits->window always open (100%); software control; keyed access not required
#pragma config WDTCCS = SC    // WDT input clock selector->Software Control

// CONFIG4
#pragma config BBSIZE = 512    // Boot Block Size Selection bits->Boot Block Size (Words) 512
#pragma config BBEN = OFF    // Boot Block Enable bit->Boot Block disabled
#pragma config SAFEN = OFF    // SAF Enable bit->SAF disabled
#pragma config WRTAPP = OFF    // Application Block Write Protection bit->Application Block NOT write-protected
#pragma config WRTB = OFF    // Boot Block Write Protection bit->Boot Block NOT write-protected
#pragma config WRTC = OFF    // Configuration Register Write Protection bit->Configuration Words NOT write-protected
#pragma config WRTD = OFF    // Data EEPROM Write Protection bit->Data EEPROM NOT write-protected
#pragma config WRTSAF = OFF    // Storage Area Flash Write Protection bit->SAF NOT write-protected
#pragma config LVP = OFF    // Low Voltage Programming Enable bit->High Voltage on MCLR/Vpp must be used for programming

// CONFIG5
#pragma config CP = OFF    // UserNVM Program memory code protection bit->UserNVM code protection disabled

#define _XTAL_FREQ 1000000

#define CS RC3
#define DC RB1
#define RST RB2
#define BUSY RC7

#define EPD_WIDTH       128
#define EPD_HEIGHT      296

unsigned char WS_20_30[159] =
{                      
0x80, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40, 0x0,  0x0,  0x0,
0x10, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20, 0x0,  0x0,  0x0,
0x80, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x40, 0x0,  0x0,  0x0,
0x10, 0x66, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x20, 0x0,  0x0,  0x0,
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
0x14, 0x8,  0x0,  0x0,  0x0,  0x0,  0x1,          
0xA,  0xA,  0x0,  0xA,  0xA,  0x0,  0x1,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x14, 0x8,  0x0,  0x1,  0x0,  0x0,  0x1,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,          
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0,  0x0,  0x0,      
0x22, 0x17, 0x41, 0x0,  0x32, 0x36
};  

unsigned char _WF_PARTIAL_2IN9[159] =
{
0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0A,0x0,0x0,0x0,0x0,0x0,0x2,  
0x1,0x0,0x0,0x0,0x0,0x0,0x0,
0x1,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
0x22,0x17,0x41,0xB0,0x32,0x36,
};

unsigned char zero[12] ={
// @192 '0' (7 pixels wide)
  //0x00, //        
  0xFF,
  //0x38, //   ###  
  0xC7,
  //0x44, //  #   # 
  0xBB,
  //0x44, //  #   # 
  0xBB,
  //0x44, //  #   # 
  0xBB,
  //0x44, //  #   # 
  0xBB,
  //0x44, //  #   # 
  0xBB,
  //0x44, //  #   # 
  0xBB,
  //0x38, //   ###  
  0xC7,
  //0x00, //        
  0xFF,
  //0x00, //        
  0xFF,
  //0x00, //        
  0xFF,
};

unsigned char one[12] = {
// @204 '1' (7 pixels wide)
  //0x00, //        
  0xFF,
  //0x30, //   ##   
  0xCF,
  //0x10, //    #   
  0xEF,
  //0x10, //    #   
  0xEF,
  //0x10, //    #   
  0xEF,
  //0x10, //    #   
  0xEF,
  //0x10, //    #   
  0xEF,
  //0x10, //    #   
  0xEF,
  //0x7C, //  ##### 
  0x83,
  //0x00, //        
  0xFF,
  //0x00, //        
  0xFF,
  //0x00, //
  0xFF,        
};

void SendCommand(char data){
    DC = 0;
    CS = 0;              // CSを「0」にする
    SSP1BUF = data;    // データの送信を開始
    while(!SSP1IF);          // 送信完了を待つ
    SSP1IF=0;
    CS = 1;
}

void SendData(char data){
    DC = 1;
    CS = 0;              // CSを「0」にする
    SSP1BUF = data;    // データの送信を開始
    while(!SSP1IF);          // 送信完了を待つ
    SSP1IF=0;
    CS = 1;
}

void Reset(void){
    RST = 1;
    __delay_ms(20);
    RST = 0;
    __delay_ms(5);
    RST = 1;
    __delay_ms(20);
}

void WaitUntilIdle(void){
    while(1){
        if(BUSY==0)
        break;
        __delay_ms(5);
    }
    __delay_ms(5);
}

void SetMemoryArea(int x_start, int y_start, int x_end, int y_end){
    SendCommand(0x44);
    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
    SendData((x_start >> 3) & 0xFF);
    SendData((x_end >> 3) & 0xFF);
    SendCommand(0x45);
    SendData(y_start & 0xFF);
    SendData((y_start >> 8) & 0xFF);
    SendData(y_end & 0xFF);
    SendData((y_end >> 8) & 0xFF);
}

void SetMemoryPointer(int x, int y){
    SendCommand(0x4E);
    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
    SendData((x >> 3) & 0xFF);
    SendCommand(0x4F);
    SendData(y & 0xFF);
    SendData((y >> 8) & 0xFF);
    WaitUntilIdle();
}

void SetLut(unsigned char *lut){
    unsigned char count;
    SendCommand(0x32);
    for(count=0; count<153; count++) 
        SendData(lut[count]); 
    WaitUntilIdle();
}

void SetLut_by_host(unsigned char *lut){
    SetLut((unsigned char *)lut);
    SendCommand(0x3f);
    SendData(*(lut+153));
    SendCommand(0x03);  // gate voltage
    SendData(*(lut+154));
    SendCommand(0x04);  // source voltage
    SendData(*(lut+155)); // VSH
    SendData(*(lut+156)); // VSH2
    SendData(*(lut+157)); // VSL
    SendCommand(0x2c);    // VCOM
    SendData(*(lut+158));
}

void Init(void){
    Reset();
    WaitUntilIdle();
    SendCommand(0x12);
    WaitUntilIdle();

    SendCommand(0x01);//Driver output control      
    SendData(0x27);
    SendData(0x01);
    SendData(0x00);
  
    SendCommand(0x11); //data entry mode       
    SendData(0x03);

    SetMemoryArea(0, 0, EPD_WIDTH-1, EPD_HEIGHT-1);

    SendCommand(0x21); //  Display update control
    SendData(0x00);
    SendData(0x80); 

    SetMemoryPointer(0, 0);
    WaitUntilIdle();

    SetLut_by_host(WS_20_30);
}

void SetFrameMemory_Base(int width, int height) {
    SetMemoryArea(0, 0, width - 1, height - 1);
    SetMemoryPointer(0, 0);
    SendCommand(0x24);
    /* send the image data */
    for (int i = 0; i < width / 8 * height; i++) {
        //SendData(pgm_read_byte(&image_buffer[i]));
        SendData(0xFF);
    }
    SendCommand(0x26);
    /* send the image data */
    for (int i = 0; i < width / 8 * height; i++) {
        //SendData(pgm_read_byte(&image_buffer[i]));
        SendData(0xFF);
    }
}

void DisplayFrame(){
    SendCommand(0x22);
    SendData(0xc7);
    SendCommand(0x20);
    WaitUntilIdle();
}

void SetFrameMemory_Partial(
    const unsigned char* image_buffer,
    int x,
    int y,
    int image_width,
    int image_height
) {
    int x_end;
    int y_end;

    if (
        image_buffer == NULL ||
        x < 0 || image_width < 0 ||
        y < 0 || image_height < 0
    ) {
        return;
    }
    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
    x &= 0xF8;
    image_width &= 0xF8;
    //if (x + image_width >= this->width) {
    //    x_end = this->width - 1;
    //} else {
    x_end = x + image_width - 1;
    //}
    //if (y + image_height >= this->height) {
    //    y_end = this->height - 1;
    //} else {
    y_end = y + image_height - 1;
    //}

    RST = 0;
    __delay_ms(2);
    RST = 1;
    __delay_ms(2);
  
    SetLut(_WF_PARTIAL_2IN9);
    SendCommand(0x37); 
    SendData(0x00);  
    SendData(0x00);  
    SendData(0x00);  
    SendData(0x00); 
    SendData(0x00);   
    SendData(0x40);  
    SendData(0x00);  
    SendData(0x00);   
    SendData(0x00);  
    SendData(0x00);

    SendCommand(0x3C); //BorderWavefrom
    SendData(0x80); 

    SendCommand(0x22); 
    SendData(0xC0);   
    SendCommand(0x20); 
    WaitUntilIdle();  
  
    SetMemoryArea(x, y, x_end, y_end);
    SetMemoryPointer(x, y);
    SendCommand(0x24);
    /* send the image data */
    for (int j = 0; j < y_end - y + 1; j++) {
        for (int i = 0; i < (x_end - x + 1) / 8; i++) {
            SendData(image_buffer[i + j * (image_width / 8)]);
        }
    }
}

void DisplayFrame_Partial(void) {
    SendCommand(0x22);
    SendData(0x0F);
    SendCommand(0x20);
    WaitUntilIdle();
}

void main(void) {
    ANSELA = 0b00000000;  // ?????????????????
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00000000;  // ???????????????(???RA3??????????)
    TRISB = 0x00;
    TRISC  = 0b10000100;  //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;
    
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    SSP1DATPPS = 0x12;              // MISO : RC2
    RB0PPS = 0x13;                  // SCK : RB0
    RC4PPS = 0x14;                  // MOSI : RC4
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
    
    SSP1STAT = 0b01000000;  //SMP: middle/ CKE: 1
    SSP1CON1 = 0b00100000;  //SSPEN/ CKP: 0/ SPIMaster Fosc/4
    // CKE=1, CKP=0でモード0
    
    Init();
    int width = 128;
    int height = 24;

    SetFrameMemory_Base(width, 296);
    DisplayFrame();
    
    width = 8;
    height = 12;
    bool i = false;
    while(1){
        if (i) {
            SetFrameMemory_Partial(zero, 60, 72, width, height);
        }else {
            SetFrameMemory_Partial(one, 60, 72, width, height);
        }
            DisplayFrame_Partial();
    
        i = !i;
    }
    
    return;
}

プログラムの詳細は、Arduinoを使ったテストの所で書いていますので詳細はそちらをご覧ください。
ここでは、ざっと電子ペーパーを動かすための概略のみを書いておきます。
この電子ペーパーは、マイコンなどとSPIで接続して使います。上にも書いたように、通常のSPI接続端子以外に、DC、RST、BUSYの3端子を接続します。
このうち、DCはこれから送るデータがコマンドなのかデータなのかを電子ペーパー側に知らせるもので、DCをLowにするとコマンド、Highにするとデータということになります。
コマンドを送る関数が、void SendCommand(char data)関数で、引数がコマンドです。データを送る関数は、void SendData(char data)関数で、引数が1byte分のデータです。複数のデータを送るときはこのコマンドを繰り返します。やっていることはDCピンの値が違う以外同じで、SPIでデータをシリアルで送るだけです。
RSTは、電子ペーパーをハード的にリセットするための端子(のようです)。電子ペーパーの初期化の時以外に、部分的に書き換える時にも使っています。void Reset(void)関数でリセット動作を行っています。
BUSYは、電子ペーパーが動作中を示すピンで、動作中はBUSYピンが立った状態になります。void WaitUntilIdle(void)関数でBUSYピンの状態を確認し、動作の終了を待っています。
プログラムでは、お決まりのPICの設定とSPIの初期設定に続いて、まず電子ペーパーを初期化し、それから、無限ループに入って電子ペーパーを部分的に書き換え数字の0と1を交互に表示しています。ピンの設定は、RC3(MISO:今回は使ってない)とRC7が入力ピンで、それ以外はデジタルの出力ピンに設定しています。PICの動作クロックは1MHz、SPIのモードは上にも書いたように0を使います。
無限ループの中でブール変数iを交互にTrue、Falseと書き換えているので、ブール変数を使うために冒頭で #include<stdbool.h> とブール変数を使うためのモジュールをインクルードしています。

今回のプログラムはめずらしく一発で動きました。
でもなんだか動きがおかしい。
電源を入れて30秒ほどはうんともすんとも動かないが、そこからおもむろに動き出した、電子ペーパーのイニシャライズが始まる。Arduinoでやった時はすぐに動いたのに、どこかで引っかかっている様子。
原因はわからない。プログラムには止まるようなところは特にないはずなのだが。可能性としては、電子ペーパーの方がBUSYシグナルを出していてそこでプログラムが止まっていることくらい。まあ、それはともかく、動くことは動く。しかもイニシャライズの時だけなので最初の1回だけなのだから。あまり気にしないことにしよう。
次は、RTCCを使って、10秒間隔で割り込みをかけて、表示を動かしてみたいと思います。

PIC16F19155を使ってみる(その7)

前回は、RTCCの内容を読み取ってEUSARTに出力することをやりました。
alasixosaka.hatenablog.com

今回は、SPIのテストです。

SPIの泥沼にはまる。

ところが、2日間ほど泥沼にはまって抜け出せずに苦戦してしまいました。
結論から書くと、PPSの設定ミスと、SPIのモードの理解不足が原因だったようです。
PPSの設定は、EUSARTのテストの時に自分で書いておきながら、ビットロックを解除するのを忘れて出力が出ずに散々悩んでしまいました。
結局MCCの助けを借りて、PPSの設定を行ったのですが、どうもそのおかげでミスしたままのプログラムでSPIの出力が出るようになっていたようです。
ただ、この状態でもSPIモードの設定がまずく、通信できずに悩んでしまいました。
まずやったことですが、手っ取り早く、SPIをテストするために、温度センサーのADT7310を使ってみました。しかし、返ってくる値が0ばかりで全く測定できず。念のため、Arduinoでつないでみると、こちらも最初は0が返ってきました。さてはセンサーが壊れていたのかと思いましたが、こちらのブログを参考に、プルアップ抵抗を繋いでみるとうまく温度が測れました。
www.wsnak.com
なんだそんなことかと、PICの回路にプルアップ抵抗を入れてみたもののこちらは依然として0しか返ってこない。こちらのブログを参考に、センサーをBMP280に変えてみたもののやっぱり0しか返ってこない。
rikeden.net
PICから出力が出てないのではという結論に至り(この結論はこの時点では多分正しい)、Arduinoをスレーブ側にしてPICからの出力をモニターしてみました。
こちらのサイトを参考にしました。
tomosoft.jp
Arduino側でSPIを受信すると割り込みがかかるはずが、かからない。やはり出力がおかしいのではと、今度は、Arduino同士で通信すると正常に通信できている。
(この時点でMCCを一度使っているので実は出力は出ていたはず)。
ここで、原点に立ち返って、SPI通信をもう一度勉強し直し。ようやくモードが違っているのではと気づく。

SPIのモードとは

Arduino関係の記事にはモードについて述べられている記事が少なく、先の参考サイトでも何も書かれておらず、どのモードで通信しているのかわかりませんでした。実は、Arduinoは何も設定しないとモード0と呼ばれるモードで通信するのだそうです。
SPIの通信モードはモード0から3まで4種類あり、また、データを読み込むタイミングがクロックの終わりの方か、中ほどかで2種類あるので、計8種類のモードがあります。
通信モードとその動作については、こちらのサイトに詳しく書いてあります。
stupiddog.jp
PICでの設定ですが、CKEとCKPの2つのビットでモードを設定します。CKEはSSPxSTATの第6ビットにあり、CKPはSSPxCON1の第4ビットにあります。別々のレジスタにあってややこしいです。なんでこんなことをするのかよくわかりませんが、これらのレジスタはI2C通信の時にも使うのでいろいろ都合があるのかもしれません。
CKPはクロックのアイドル状態をHighかLowかを指定するビットで、1ならHighがアイドル、0ならLowがアイドルです。
CKEはクロックがアイドルからアクティブになるときにデータ転送するのか、アクティブからアイドルになるときにデータ転送するのかを決めるビットで、1なら、アクティブからアイドルになるとき、0ならアイドルからアクティブになるときとなります。
SPIモードとの対応は、下記のようになります。
SPIモード   CKE  CKP
  0     1   0
  1     0   0
  2     1   1
  3     0   1
CKEとCKPを2進数に見立てても見事に一致してませんね、とてもややこしい。
もう一つ、サンプリングの位置ですが、これは、SMPというビットで定義します。SMPはSSPxSTATの第7ビットにあり、SMPが1の時がサイクルの後半、0の時がサイクルの半ばということになります。こちらは、だいたいサイクルの半ば(つまりSMPを0にする)で行うことが多いようです。
センサーを動かすサンプルを作った時に、センサーはモード3で動かしていたので、Arduinoとの通信を行うときも、モード3を設定してしまい、それで通信がうまくいきませんでした。モードを0に設定しなおすことでようやく通信ができるようになりました。
プログラムの全文です。なお、モード0に設定しなおして、うまく動いたプログラムは、SSPの設定をミスって、ロック解除をしてませんでしたが、下記のプログラムでは修正してあります。

#include <xc.h>

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits->Oscillator not enabled
#pragma config RSTOSC = HFINT1    // Power-up default value for COSC bits->HFINTOSC (1MHz)
#pragma config CLKOUTEN = OFF    // Clock Out Enable bit->CLKOUT function is disabled; i/o or oscillator function on OSC2
#pragma config VBATEN = OFF    // VBAT Pin Enable bit->VBAT functionality is disabled
#pragma config LCDPEN = OFF    // LCD Charge Pump Mode bit->LCD Charge Pump is disabled.
#pragma config CSWEN = ON    // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON    // Fail-Safe Clock Monitor Enable bit->FSCM timer enabled

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit->MCLR pin is Master Clear function
#pragma config PWRTE = OFF    // Power-up Timer selection bits->PWRT disable
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit->ULPBOR disabled
#pragma config BOREN = ON    // Brown-out reset enable bits->Brown-out Reset Enabled, SBOREN bit is ignored
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices
#pragma config ZCD = OFF    // Zero-cross detect disable->Zero-cross detect circuit is disabled at POR.
#pragma config PPS1WAY = OFF    // Peripheral Pin Select one-way control->The PPSLOCK bit can be set and cleared repeatedly by software
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable bit->Stack Overflow or Underflow will cause a reset

// CONFIG3
#pragma config WDTCPS = WDTCPS_31    // WDT Period Select bits->Divider ratio 1:65536; software control of WDTPS
#pragma config WDTE = OFF    // WDT operating mode->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits->window always open (100%); software control; keyed access not required
#pragma config WDTCCS = SC    // WDT input clock selector->Software Control

// CONFIG4
#pragma config BBSIZE = 512    // Boot Block Size Selection bits->Boot Block Size (Words) 512
#pragma config BBEN = OFF    // Boot Block Enable bit->Boot Block disabled
#pragma config SAFEN = OFF    // SAF Enable bit->SAF disabled
#pragma config WRTAPP = OFF    // Application Block Write Protection bit->Application Block NOT write-protected
#pragma config WRTB = OFF    // Boot Block Write Protection bit->Boot Block NOT write-protected
#pragma config WRTC = OFF    // Configuration Register Write Protection bit->Configuration Words NOT write-protected
#pragma config WRTD = OFF    // Data EEPROM Write Protection bit->Data EEPROM NOT write-protected
#pragma config WRTSAF = OFF    // Storage Area Flash Write Protection bit->SAF NOT write-protected
#pragma config LVP = OFF    // Low Voltage Programming Enable bit->High Voltage on MCLR/Vpp must be used for programming

// CONFIG5
#pragma config CP = OFF    // UserNVM Program memory code protection bit->UserNVM code protection disabled

#define _XTAL_FREQ 1000000

#define CS RC3

void main(void) {
    ANSELA = 0b00000000;  
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00000000;  
    TRISB = 0x00;
    TRISC  = 0b10000100;  //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;
    
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    SSP1DATPPS = 0x12;              // MISO : RC2
    RB0PPS = 0x13;                  // SCK : RB0
    RC4PPS = 0x14;                  // MOSI : RC4
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
    
    SSP1STAT = 0b01000000;  //SMP: middle/ CKE: 1
    SSP1CON1 = 0b00100000;  //SSPEN/ CKP: 0/ SPIMaster Fosc/4
    // CKE=1, CKP=0でモード0
    
    char num = 10;
    while(1){
        CS = 0;              // CSを「0」にする
        SSP1BUF = num;    // データの送信を開始
        while(!SSP1IF);          // 送信完了を待つ
        SSP1IF=0;
        CS = 1;
        RA0 = 1;
        __delay_ms(500);
        RA0 = 0;
        __delay_ms(500);
    }
    return;
}

初期設定以外は非常にシンプルです。コンフィグレーション設定と、IOポートの設定まではいつもとほぼ同じです。今回は、RC2をSPIの入力(MISO)に使うので、TRISCの第2ビットを立てています。第7ビットが立っているのはEUSARTの設定の残りです。気にしないでください。
SPIの設定は、モード0に設定するために、CKEを1、CKPを0にしてあります。MSPは0です。
そして、PPSの設定です。おまじない
PPSLOCK = 0x55;
PPSLOCK = 0xAA;
に続いて、PPSロックビット解除を行い(PPSLOCKbits.PPSLOCKED = 0;)
続いて、PPSの設定を行っています。設定の詳細は下記に記載しました。設定が終われば、またおまじないを書いて、今度はロックビットを立てます。
無限ループの中は、Lチカに加えて、SPIで10を繰り返して出力するようになっています。まず、CSを0にして、スレーブ側に通信開始を通知します。SSP1BUFにデータ(10)を書き込んで、SSP1IFをモニターして、1になる(送信終了)を待ちます。送信が終了したらSSP1IFをクリアして、CSを1にして、Lチカをやってを繰り返します。

SPI通信に使用する端子

SPI通信は、クロック(CLK)、マスターからスレーブ側の通信(MOSI)、スレーブからマスター側への通信(MISO)、チップセレクト(CS)の4端子で行います。
通信の手順は、まずCSをLowにして、スレーブに通信開始を伝えます。そして、マスター側からクロックを送り、それに応じて、マスター、スレーブ双方からデータをシリアルで送ります。従ってスレーブ側からのデータは通信終了と同時にPICの側にデータが送られてきています。PICでは、MSPPがこの機能を担い、PIC側がマスターの場合、SSPxBUFにデータを与えると直ちに通信が始まり、通信が終了した時点でSSPxBUFにスレーブ側からのデータが入っています。今回はマスター側からの送信のみをテストしたので、受信したデータのチェックは行っていません。Arduinoからは、同じデータを返すようになっています。でも同時に返すことはできるのかな? まだ、理解が不十分なようです。
センサーとかだと、コマンドを送って、その時の返ってきたデータは捨てて、次にダミーのコマンドを送ってセンサーからのデータを読み込むという操作をしています。
今回は、CS以外の端子は、SSPを使ってPIC側の端子を次のように設定しました。

SPI端子 PICポート SSP設定
CS    RC3   なし(通常の出力ポート)
CLK    RB0   RB0PPS = 0x13;
MOSI    RC4   RC4PPS = 0x14;
MISO    RC2   SSP1DATPPS = 0x12;
そして、Arduino側は、次のようになります。
CS ピン10
CLK ピン13
MOSI ピン11
MISO ピン12
従って、PICとArduinoの接続は次のようになります。
PIC  Arduino
RC3  ピン10
RB0  ピン13
RC4  ピン11
RC2  ピン12
全体の配線です。

PICとArduinoでSPI通信するときの接続

図には示していませんが、PIC側の電源はArduino側からとりました。Arduinoの5VとGNDをブレッドボードの+ラインと-ラインに繋いでいます。


そして、参考サイトのArduino側のプログラムを少し変えて、SPIのデータを受信したら、シリアルポートに出力するようにしてあります。
Arduino側のスケッチです。

/**********************************************
  Arduino uno SPI(sleave)
**********************************************/
#include <SPI.h>
 
  byte rxdata;
  int flg = 0;
 
void setup() {
  Serial.begin(9600);
  Serial.println("/-----START Arduino uno-----/");
  Serial.println("/----------SPI Slave---------/");
 
  SPCR |= bit(SPE);
  pinMode(MISO, OUTPUT);
  SPI.attachInterrupt();
}
 
void loop() {
  
  if (flg==1){
    Serial.println(rxdata);
    flg = 0;
  }
  
}
 
ISR(SPI_STC_vect) {
  rxdata = SPDR;
  SPDR = rxdata;
  flg = 1;
}

割り込み処理ルーチンで変数flgを立てて、割り込みがあったことをメインルーチンに知らせるようにしています。割り込み処理ルーチン中でシリアル出力をすると動作がおかしくなるそうです。ループ関数の中で変数flgを見て、割り込みがあったらシリアルにデータを出力するようにしています。

これで、Arduinoのシリアルポートから10が連続して出力されました。
ようやく難関のSPIにケリがついたので、次はようやくPICで電子ペーパーを動かす試験をやってみます。

PIC16F19155を使ってみる(その6)

前々回は、スリープモードとRTCCからの割り込みを使ったスリープからの復帰をテストしました。
alasixosaka.hatenablog.com

そして前回は、EUARTを使ったシリアル通信を試してみました。
alasixosaka.hatenablog.com

今回は、RTCCの値を読み込んで、シリアル通信で時刻をPCに送ってみます。
前二回のプログラムを合体させるような感じですので、大枠はほとんどできています。
まず、プログラムの全文です。

#include <xc.h>
#include <PIC16f19155.h>
#include <stdio.h>
#include <string.h>

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits->Oscillator not enabled
#pragma config RSTOSC = HFINT1    // Power-up default value for COSC bits->HFINTOSC (1MHz)
#pragma config CLKOUTEN = OFF    // Clock Out Enable bit->CLKOUT function is disabled; i/o or oscillator function on OSC2
#pragma config VBATEN = OFF    // VBAT Pin Enable bit->VBAT functionality is disabled
#pragma config LCDPEN = OFF    // LCD Charge Pump Mode bit->LCD Charge Pump is disabled.
#pragma config CSWEN = ON    // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON    // Fail-Safe Clock Monitor Enable bit->FSCM timer enabled

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit->MCLR pin is Master Clear function
#pragma config PWRTE = OFF    // Power-up Timer selection bits->PWRT disable
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit->ULPBOR disabled
#pragma config BOREN = ON    // Brown-out reset enable bits->Brown-out Reset Enabled, SBOREN bit is ignored
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices
#pragma config ZCD = OFF    // Zero-cross detect disable->Zero-cross detect circuit is disabled at POR.
#pragma config PPS1WAY = OFF    // Peripheral Pin Select one-way control->The PPSLOCK bit can be set and cleared repeatedly by software
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable bit->Stack Overflow or Underflow will cause a reset

// CONFIG3
#pragma config WDTCPS = WDTCPS_31    // WDT Period Select bits->Divider ratio 1:65536; software control of WDTPS
#pragma config WDTE = OFF    // WDT operating mode->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits->window always open (100%); software control; keyed access not required
#pragma config WDTCCS = SC    // WDT input clock selector->Software Control

// CONFIG4
#pragma config BBSIZE = 512    // Boot Block Size Selection bits->Boot Block Size (Words) 512
#pragma config BBEN = OFF    // Boot Block Enable bit->Boot Block disabled
#pragma config SAFEN = OFF    // SAF Enable bit->SAF disabled
#pragma config WRTAPP = OFF    // Application Block Write Protection bit->Application Block NOT write-protected
#pragma config WRTB = OFF    // Boot Block Write Protection bit->Boot Block NOT write-protected
#pragma config WRTC = OFF    // Configuration Register Write Protection bit->Configuration Words NOT write-protected
#pragma config WRTD = OFF    // Data EEPROM Write Protection bit->Data EEPROM NOT write-protected
#pragma config WRTSAF = OFF    // Storage Area Flash Write Protection bit->SAF NOT write-protected
#pragma config LVP = OFF    // Low Voltage Programming Enable bit->High Voltage on MCLR/Vpp must be used for programming

// CONFIG5
#pragma config CP = OFF    // UserNVM Program memory code protection bit->UserNVM code protection disabled

char str1[7];

void readRTCC(){
    if (RTCCONbits.RTCSYNC==1){
        while(RTCCONbits.RTCSYNC);
    }
    
    char h = HOURS;
    char m = MINUTES;
    char s = SECONDS;
    
    str1[0] = (h >> 4) + 48;
    str1[1] = (h & 0xF) + 48;
    str1[2] = (m >> 4) + 48;
    str1[3] = (m & 0xF) + 48;
    str1[4] = (s >> 4) + 48;
    str1[5] = (s & 0xF) + 48;
    
}


void putch(unsigned char ch) {
    while (!PIR3bits.TX1IF); 
                      //送信終了待ち
                             
    TX1REG = ch;
}

void __interrupt() isr(void)
{
    PIR8bits.RTCCIF = 0;
    readRTCC();
    printf("%s\r\n", str1);
    
    return;
}


void main(void) {
    char num = 10;
   
    ANSELA = 0b00000000;  // ?????????????????
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00001000;  // ???????????????(???RA3??????????)
    TRISB = 0x00;
    TRISC  = 0b10000000;
    
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RC6PPS = 0x0D;     //RC6をTXにする
    RX1PPS = 0x17;     //RC7をRXにする
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
    
     // 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;
    
    
    // SYSCMD SYSCLK enabled; ACTMD ACT enabled; FVRMD FVR enabled; IOCMD IOC enabled; NVMMD NVM enabled; 
    PMD0 = 0x00;
    // TMR0MD TMR0 enabled; TMR1MD TMR1 enabled; TMR4MD TMR4 enabled; TMR2MD TMR2 enabled; 
    PMD1 = 0x00;
    // ZCDMD ZCD enabled; DACMD DAC enabled; CMP1MD CMP1 enabled; ADCMD ADC enabled; CMP2MD CMP2 enabled; RTCCMD RTCC enabled; 
    PMD2 = 0x00;
    // CCP2MD CCP2 enabled; CCP1MD CCP1 enabled; CCP4MD PWM4 enabled; CCP3MD PWM3 enabled; 
    PMD3 = 0x00;
    // CWG1MD CWG1 enabled; UART2MD EUSART2 enabled; MSSP1MD MSSP1 enabled; UART1MD EUSART enabled; 
    PMD4 = 0x00;
    // CLC3MD CLC3 enabled; CLC4MD CLC4 enabled; SMT1MD SMT1 enabled; LCDMD LCD enabled; CLC1MD CLC1 enabled; CLC2MD CLC2 enabled; 
    PMD5 = 0x00;
    
    //__builtin_write_RTCWREN();
    RC1STA   = 0b10010000;      // 非同期送信 9600baud
    TX1STA   = 0b00100100;
    BAUD1CON = 0b00001000;
    SP1BRGH  = 0;
    SP1BRG   = 25;
    
    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 = 0xC8;

    // 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 ;             // 全割込み処理を許可する
    
    RA0 = 0;
    
    while(1){
        
        RA0 = 0;
        __delay_ms(950);            
        RA0 = 1;
        __delay_ms(50);
        
    }
    return;
}

コンフィクレーションの設定は以前と同じです。
割り込みは、RTCCを使って10秒間隔で起こります。
割り込み処理関数は次のようになっています。

void __interrupt() isr(void)
{
    PIR8bits.RTCCIF = 0;
    readRTCC();
    printf("%s\r\n", str1);
    
    return;
}

まず、割り込みフラグをクリアし、readRTCC()で関数を呼び出しています。readRTCCでは、RTCCから時、分、秒を読みだして、配列str1に代入します。
そして、printf文でシリアルポートに出力しています。
readRTCC関数は、

void readRTCC(){
    if (RTCCONbits.RTCSYNC==1){
        while(RTCCONbits.RTCSYNC);
    }
    
    char h = HOURS;
    char m = MINUTES;
    char s = SECONDS;
    
    str1[0] = (h >> 4) + 48;
    str1[1] = (h & 0xF) + 48;
    str1[2] = (m >> 4) + 48;
    str1[3] = (m & 0xF) + 48;
    str1[4] = (s >> 4) + 48;
    str1[5] = (s & 0xF) + 48;
    
}

となっています。まず、RTCSYNCビットを読み込んで、ビットが立っていれば、ビットがクリアされるまで待ちます。ビットが立っているときは桁の繰り上がりが起こることを意味しています。桁上がりの最中にデータを読むと値が狂うので、ビットがクリアされるまで待ちます。
そして、時、分、秒が格納されているレジスタ、HOURS、MINUTES、SECONDSをそれぞれ、変数h、m、sに代入します。それぞれの値は、BCDで2桁になっていますので、右に4ビットシフトすることで10の位を得、0xFとアンドを取ることで1の位を得て、順に配列str1に代入します。シリアル出力はアスキーコードで行いますので、それぞれの数値に48を加えています。また、printfで出力するときに、str1の最後にNULLが必要ですので、配列を定義するときにchar str1[7]として一つ多く配列領域を確保しています。

メインルーチンの無限ループはLチカのみです。LEDが1秒間隔で点滅し、10秒に1回割り込みがかかり、そのたびにシリアルポートから時刻が出力されます。今回はスリープは行っていません。

シリアルポートの出力をTeraTermで見たところ。時、分、秒が6桁で表示されている。

大部時計らしくなってきました。
次は、SPI通信を試してみます。電子ペーパーを駆動するにはSPIで通信する必要があります。
PICでSPI通信をやったことがないのでまずは簡単なテストから初めて、それから電子ペーパーの駆動に移っていきたいと思います。

PIC16F19155を使ってみる(その5)

前回は、RTCCを使った割り込み処理でスリープからの復帰をテストしました。
alasixosaka.hatenablog.com

今回は、EUSARTを使ったシリアル通信のテストを行います。
前回のブログで、シリアル通信は行わないと書いてしまったのに、その舌の根も乾かぬうちにという感じですが、よくよく考えたら、TWE-LITEと通信するときにシリアルで通信する必要がありました。タイムサーバーからのデータをTWE-LITEで経由で受信し時刻合わせをする機能を搭載する予定なので、シリアル通信が必要です。この機能は受信だけでOKですが、RTCCから読みだしたデータをシリアル通信でPCに送るテストをしてみたいので、送信側も実装する必要があり、送信、受信のテストを行いました。

配線です。

右上のピンヘッダはUSBーシリアル変換器との接続のためです。左からGND、ひとつ飛んでVCC、TXD、RXDの順です。
PIC側のピンはRC6がRXD(黄色の線)でUSB-シリアル変換器のTXDと接続、RC7がTXD(緑の線)でUSB-シリアル変換器のRXDと接続します。USB-シリアル変換器を接続した場合は、電源はUSB-シリアル変換器から供給することとし、ブレッドボード上のVCCとGNDのラインにそれぞれ接続しています。今回の追加はこれだけです。

送信テスト

まず、PIC側からの送信のテストを行います。受け手はPCで、USBシリアル変換器を繋いで、TeraTermでモニタしました。
プログラムです。PIC16F19155にはEUSARTが2つあります。今回は1番の方を使いました。

#include <xc.h>
#include <PIC16f19155.h>
#include <stdio.h>

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits->Oscillator not enabled
#pragma config RSTOSC = HFINT1    // Power-up default value for COSC bits->HFINTOSC (1MHz)
#pragma config CLKOUTEN = OFF    // Clock Out Enable bit->CLKOUT function is disabled; i/o or oscillator function on OSC2
#pragma config VBATEN = OFF    // VBAT Pin Enable bit->VBAT functionality is disabled
#pragma config LCDPEN = OFF    // LCD Charge Pump Mode bit->LCD Charge Pump is disabled.
#pragma config CSWEN = ON    // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON    // Fail-Safe Clock Monitor Enable bit->FSCM timer enabled

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit->MCLR pin is Master Clear function
#pragma config PWRTE = OFF    // Power-up Timer selection bits->PWRT disable
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit->ULPBOR disabled
#pragma config BOREN = ON    // Brown-out reset enable bits->Brown-out Reset Enabled, SBOREN bit is ignored
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices
#pragma config ZCD = OFF    // Zero-cross detect disable->Zero-cross detect circuit is disabled at POR.
#pragma config PPS1WAY = OFF    // Peripheral Pin Select one-way control->The PPSLOCK bit can be set and cleared repeatedly by software
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable bit->Stack Overflow or Underflow will cause a reset

// CONFIG3
#pragma config WDTCPS = WDTCPS_31    // WDT Period Select bits->Divider ratio 1:65536; software control of WDTPS
#pragma config WDTE = OFF    // WDT operating mode->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits->window always open (100%); software control; keyed access not required
#pragma config WDTCCS = SC    // WDT input clock selector->Software Control

// CONFIG4
#pragma config BBSIZE = 512    // Boot Block Size Selection bits->Boot Block Size (Words) 512
#pragma config BBEN = OFF    // Boot Block Enable bit->Boot Block disabled
#pragma config SAFEN = OFF    // SAF Enable bit->SAF disabled
#pragma config WRTAPP = OFF    // Application Block Write Protection bit->Application Block NOT write-protected
#pragma config WRTB = OFF    // Boot Block Write Protection bit->Boot Block NOT write-protected
#pragma config WRTC = OFF    // Configuration Register Write Protection bit->Configuration Words NOT write-protected
#pragma config WRTD = OFF    // Data EEPROM Write Protection bit->Data EEPROM NOT write-protected
#pragma config WRTSAF = OFF    // Storage Area Flash Write Protection bit->SAF NOT write-protected
#pragma config LVP = OFF    // Low Voltage Programming Enable bit->High Voltage on MCLR/Vpp must be used for programming

// CONFIG5
#pragma config CP = OFF    // UserNVM Program memory code protection bit->UserNVM code protection disabled


// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

//int LEDflg ;

#define _XTAL_FREQ 1000000

void putch(unsigned char ch) {
    while (!PIR3bits.TX1IF); //送信終了待ち                           
    TX1REG = ch;
}


void main(void) {
    char num = 10;

    ANSELA = 0b00000000;  
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00001000;  
    TRISB = 0x00;
    TRISC = 0x00;
    
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RC6PPS = 0x0D;
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
    
     // 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;
    
    
    // SYSCMD SYSCLK enabled; ACTMD ACT enabled; FVRMD FVR enabled; IOCMD IOC enabled; NVMMD NVM enabled; 
    PMD0 = 0x00;
    // TMR0MD TMR0 enabled; TMR1MD TMR1 enabled; TMR4MD TMR4 enabled; TMR2MD TMR2 enabled; 
    PMD1 = 0x00;
    // ZCDMD ZCD enabled; DACMD DAC enabled; CMP1MD CMP1 enabled; ADCMD ADC enabled; CMP2MD CMP2 enabled; RTCCMD RTCC enabled; 
    PMD2 = 0x00;
    // CCP2MD CCP2 enabled; CCP1MD CCP1 enabled; CCP4MD PWM4 enabled; CCP3MD PWM3 enabled; 
    PMD3 = 0x00;
    // CWG1MD CWG1 enabled; UART2MD EUSART2 enabled; MSSP1MD MSSP1 enabled; UART1MD EUSART enabled; 
    PMD4 = 0x00;
    // CLC3MD CLC3 enabled; CLC4MD CLC4 enabled; SMT1MD SMT1 enabled; LCDMD LCD enabled; CLC1MD CLC1 enabled; CLC2MD CLC2 enabled; 
    PMD5 = 0x00;
    
    RC1STA   = 0b10000000;      // 非同期送信 9600baud
    TX1STA   = 0b00100100;
    BAUD1CON = 0b00001000;
    SP1BRGH  = 0;
    SP1BRG   = 25;
    
    RA0 = 0;
    
    while(1){
      
        RA0 = 0;
        __delay_ms(950);            
        RA0 = 1;
        __delay_ms(50);
        printf("%d\r\n",num++); // 0123・・・と送信
    }
    return;
}

シリアルポートに0,1,2,3と順番にキャラクタを送信するだけのプログラムです。
前回からの変更点だけを記載します。

#include <stdio.h>

ます、stdio.hをインクルードして、printf関数を使えるようにしています。
そして、putch関数を定義します。

void putch(unsigned char ch) {
    while (!PIR3bits.TX1IF); //送信終了待ち                           
    TX1REG = ch;
}

ここでは、TX1IFビットを読んでビットが立っている間はループを繰り返して送信の終了を待ち、送信が終了していたら、TX1REGに出力するデータを書き込みます。
メイン関数の中ですが、まず

 char num = 10;

として、出力するデータをnumという変数で定義しています。これをカウントアップして順次出力することになります。
それからPPSによるピンの設定です。

    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RC6PPS = 0x0D;
    RX1PPS = 0x17;     //RC7をRXにする
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;

データシートを見ると、EUSARTのピンはPPSで設定しなくてもデフォルト設定で使えそうな感じがしたのですが、PPSで設定しないと動きませんでした。
まずPPSのロック解除を行います。RTCCの時と違って、こちらは解除用のおまじないが必要でした。PPSLOCKに0x55と0xAAを順番に書き込みます。
そして、PPSLOCKビットをクリアして書き込みを許可して、RC6をTXDに設定します。PIC16F19155ではTXDは0x0Dになっています。
そして、ついでと言ってなんですが、受信用のRXDをRC7に設定します。こちらは、出力端子の設定と逆で、RX1PPS = 0x17のように、周辺装置側を左に書いて、ピンを右に書きます。RC7は0x17です。
書き込んだら、PPSLOCビットを立てて、またPPSをロックします。
EUSARTの設定の部分です。

    RC1STA   = 0b10000000;      // 非同期送信 9600baud
    TX1STA   = 0b00100100;
    BAUD1CON = 0b00001000;
    SP1BRGH  = 0;
    SP1BRG   = 25;

RC1STAとTX1STAで非同期、ノンパリティ、8ビットに設定しています。TX1STAのビット2を立てているのは、高速モードで動作するという意味です。メインクロックを1MHzと低速にしているので、高速モードにしてやらないとまともな速度で通信ができません。
そして、BAUD1CONレジスタの第3ビットを立てて、ボーレート設定用のレジスタを16ビットで使うことを指示します。ボーレート設定用レジスタはSP1BRGHとSP1BRGで、ここに25を書き込むと約9600ボーで通信します。ちなみに12を書き込むと19200ボーでの通信になります。メインクロックが1MHzの場合は実用的なのはこの2つくらいです。

PIC16F19155のボーレート設定、非同期でハイスピード、16ビットの場合

whileループの中ですが、Lチカの後に一行加えています。

    while(1){
      
        RA0 = 0;
        __delay_ms(950);            
        RA0 = 1;
        __delay_ms(50);
        printf("%d\r\n",num++); // 0123・・・と送信
    }

printf("%d\r\n",num++);として、冒頭で定義した変数numをインクリメントしながらprintfで出力しています。これで、シリアルポートから順番に数字の0、1と送信されます。

TeraTermで受信したところ

受信テスト

こんどはPCからデータを送って、PICで受信してみます。
受信テスト用のプログラムです。
変更した点だけ記載します。
まず、受信用の変数RxDataと、受信を検知した際に立てるフラグ変数Rflgを定義します。

unsigned char RxData, Rflg;

次に、受信用の割り込み関数を定義します。

void __interrupt() isr(void)
{
    PIR3bits.RC1IF = 0;
    if((RC1STAbits.OERR)||(RC1STAbits.FERR)){
        RC1STA = 0;
        RC1STA = 0x90;
    }
    else{
        RxData = RC1REG;
        Rflg = 1;
    }
}

ここでは、まず割り込みフラグRC1IFをクリアし、エラーチェックを行い、エラーならRC1STAレジスタを再設定してリターンします。エラーがなければ、データをRxDataに読み込み、受信用フラグを立ててリターンします。
次にメインルーチンです。RC7を入力に設定する必要があるので、TRISCの設定を下記のように変更します。

    TRISC  = 0b10000000;

それから、受信割り込みを許可するため、下記の4行を追記します。

    PIR3bits.RC1IF = 0;
    PIE3bits.RC1IE = 1;
    INTCONbits.PEIE   = 1 ;             // 周辺装置割り込み有効
    INTCONbits.GIE    = 1 ;             // 全割込み処理を許可する

まず、割り込みフラグRC1IFビットをクリアします、次に、RC1IEフラグを立てて受信割り込みを許可します。それから、作法に従って周辺装置割り込み、全体割り込みの順に許可します。
ループ関数の中ですが、

    while(1){
        for (int i=0; i<10; i++){
            RA0 = 0;
            __delay_ms(950);            
            RA0 = 1;
            __delay_ms(50);
            printf("%d\r\n",num++); // 0123・・・と送信
        }
        Rflg = 0;
        while(1){
            if (Rflg==1){
                Rflg=0;
                RA0 = 0;
                break;
            }
        }
        printf("%d\r\n",RxData);    //受信したデータをそのまま返す
    }
    return;
}

まず、送信時と同じ動作をforループを使って10回繰り返します。そのあと、受信検知用フラグ関数Rflgをクリアして、whileで無限ループに入り受信を待ちます。シリアルポートへ何もデータが送られてこないと永遠に待ち続けます。
ループ内では、Rflgが立っていたら、Rflgをクリアし、無限ループを抜けて、受信したデータをそのまま送信し返します。RA0=0は受信が成立しているかどうかをチェックするためのコマンドです。受信待ちに入る前にLEDは点灯しっぱなしになるので、ちゃんと受信ができていればここでLEDが消灯し、受信できたことが確認できます。
下の写真では、1を送った後に49が返ってきて149と表示されています。これは、1のアスキーコードが49なので、1をteratermから送るとコードの49が送られるためです。2を送ると同様に50を返します。

10回カウントしたら受信を待ち、受信したらそのまま返して、再びカウントアップを続けます。

PIC16F19155を使ってみる(その4)

スリープモードを試す

PICのシリーズが続きます。今回は、スリープをしてみてどのくらい電力消費するかの計測です。
前回はRTCCを使った10秒間隔の割り込みをテストしました。
alasixosaka.hatenablog.com

この時は、スリープを使ってないので、CPUは動作し続けていて電力を消費し続けます。電池駆動を考える時は不要な時はなるべくスリープに入れて電力を消費しないようにします。
PICではスリープの手順は簡単で、コマンドのSLEEP()を使うだけです。一つ注意しないといけないのは、スリープに入る前に1コマンド先まで進むことがあるということです。ですので、念のためスリープコマンドの後にNOP()コマンドを入れておきます。

PMDを設定する

PIC16F19155には、PMD(Peripheral Module Disable)コントロールレジスタというのがあり、不要な周辺装置を無効にすることができます。デフォルトでは有効になっていますので、使わない機能があっても装置が動き続けて電力を消費します。ですので、このレジスタを設定して不要な機能は停止することで消費電力を削減できます。
PMDコントロールレジスタは5つあって、下記のように設定しました。まず、PMD0です。

    // SYSCLKは有効、 ACTを無効、FVRを無効、IOCを無効、NVMを無効 
    PMD0 = 0x65;

SYSCLK、周辺装置でシステムクロックで動作するものはこれを有効にしないと動かない(と思います)。今回は使いませんが、電子ペーパーとの通信にSPIを使いますので有効にします。(SPIはスレーブはマスターのクロックで動作するのでスレーブ動作の時は不要です。マスター動作の時はクロックが必要)
ACTはActive Clock Tuningで、外部クロックで内臓オシレータをチューニングする機能のようです。主にはUSB通信を安定にするためのようです。USBは使いませんというかPIC16F19155にはそもそも搭載されていないので必要ないかと思います。
FVRはFixed Voltage ReferenceでADコンバータを使うときに使うものです。ADCは使わないので無効にします。
IOCはInterrupt on Change Bitで外部ピン割り込みを使うときに外部ピンの変化を監視する機能です。外部ピン割り込みも使いませんので無効にします。
NVMはNon Volatile Memoryのことで、フラッシュメモリにアクセスするときに使うようです。この機能も使わない(たぶん)ので無効にします。
PMD1は、すべてタイマー関連です。タイマーを使う予定はないのですべて無効にします。

    // TMR0は無効、TMR1は無効、TMR4は無効、TMR2は無効
    PMD1 = 0x17;

PMD2は、

    // ZCD は無効。DACは無効、CMP1は無効、ADCは無効、CMP2は無効、RTCCは有効 
    PMD2 = 0x67;

ZCDは交流が0Vを横切るポイントを測定するモジュール、使わないので無効にします。
DACはDAコンバーター、使わないので無効にします。
CMP1、CMP2はコンパレータです。使わないので無効にします。
ADCはADコンバーター、使わないので無効にします。
RTCCは必要ですので有効にします。
PMD3です。PMD3はPWMとCCP関連ですべて無効にします。

    // CCP2は無効、CCP1は無効、PWM4は無効、PWM3は無効
    PMD3 = 0x0F;

PMD4です。

    // CWG1は無効、EUSART2は無効、MSSP1は有効、EUSARTは無効 
    PMD4 = 0xC1;

CWGもPWM関係のモジュールのようです。使用しないので無効にします。
EUSART(EUSART2も)はシリアル通信用モジュールで使用しないので無効にします。
MSSPはI2CやSPI通信を行うモジュールです、後々使用するので有効にします。
PMD5です。

    // CLC3は無効、CLC4は無効、SMT1は無効、LCDは無効、CLC1は無効、CLC2は無効 
    PMD5 = 0x7E;

CLCはConfigure Logic Cellのことで論理回路をソフト的に構成できるモジュールです。今回は使いませんので無効にします。
LCDは液晶ドライバのモジュールです。今回は使用しないので無効にします。
SMTはパルスカウンタです。今回は使用しないので無効にします。
あいまいな記述が多くてすいません。どうもよくわかっていないところが多いもので。

プログラムの全文です。

#include <xc.h>
#include <PIC16f19155.h>

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits->Oscillator not enabled
#pragma config RSTOSC = HFINT1    // Power-up default value for COSC bits->HFINTOSC (1MHz)
#pragma config CLKOUTEN = OFF    // Clock Out Enable bit->CLKOUT function is disabled; i/o or oscillator function on OSC2
#pragma config VBATEN = OFF    // VBAT Pin Enable bit->VBAT functionality is disabled
#pragma config LCDPEN = OFF    // LCD Charge Pump Mode bit->LCD Charge Pump is disabled.
#pragma config CSWEN = ON    // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON    // Fail-Safe Clock Monitor Enable bit->FSCM timer enabled

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit->MCLR pin is Master Clear function
#pragma config PWRTE = OFF    // Power-up Timer selection bits->PWRT disable
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit->ULPBOR disabled
#pragma config BOREN = ON    // Brown-out reset enable bits->Brown-out Reset Enabled, SBOREN bit is ignored
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices
#pragma config ZCD = OFF    // Zero-cross detect disable->Zero-cross detect circuit is disabled at POR.
#pragma config PPS1WAY = OFF    // Peripheral Pin Select one-way control->The PPSLOCK bit can be set and cleared repeatedly by software
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable bit->Stack Overflow or Underflow will cause a reset

// CONFIG3
#pragma config WDTCPS = WDTCPS_31    // WDT Period Select bits->Divider ratio 1:65536; software control of WDTPS
#pragma config WDTE = OFF    // WDT operating mode->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits->window always open (100%); software control; keyed access not required
#pragma config WDTCCS = SC    // WDT input clock selector->Software Control

// CONFIG4
#pragma config BBSIZE = 512    // Boot Block Size Selection bits->Boot Block Size (Words) 512
#pragma config BBEN = OFF    // Boot Block Enable bit->Boot Block disabled
#pragma config SAFEN = OFF    // SAF Enable bit->SAF disabled
#pragma config WRTAPP = OFF    // Application Block Write Protection bit->Application Block NOT write-protected
#pragma config WRTB = OFF    // Boot Block Write Protection bit->Boot Block NOT write-protected
#pragma config WRTC = OFF    // Configuration Register Write Protection bit->Configuration Words NOT write-protected
#pragma config WRTD = OFF    // Data EEPROM Write Protection bit->Data EEPROM NOT write-protected
#pragma config WRTSAF = OFF    // Storage Area Flash Write Protection bit->SAF NOT write-protected
#pragma config LVP = OFF    // Low Voltage Programming Enable bit->High Voltage on MCLR/Vpp must be used for programming

// CONFIG5
#pragma config CP = OFF    // UserNVM Program memory code protection bit->UserNVM code protection disabled

int LEDflg ;

#define _XTAL_FREQ 1000000

void __interrupt() isr(void)
{
    PIR8bits.RTCCIF = 0;
    if (LEDflg == 0) LEDflg = 1 ;
    else             LEDflg = 0 ;
    return;
}

void main(void) {

    ANSELA = 0b00000000;  // ?????????????????
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00001000;  // ???????????????(???RA3??????????)
    TRISB = 0x00;
    TRISC = 0x00;
    
     // 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;
    
    
    // SYSCLKは有効、 ACTを無効、FVRを無効、IOCを無効、NVMを無効 
    PMD0 = 0x65;
    // TMR0は無効、TMR1は無効、TMR4は無効、TMR2は無効
    PMD1 = 0x17;
    // ZCD は無効。DACは無効、CMP1は無効、ADCは無効、CMP2は無効、RTCCは有効 
    PMD2 = 0x67;
    // CCP2は無効、CCP1は無効、PWM4は無効、PWM3は無効
    PMD3 = 0x0F;
    // CWG1は無効、EUSART2は無効、MSSP1は有効、EUSARTは無効 
    PMD4 = 0xD1;
     // CLC3は無効、CLC4は無効、SMT1は無効、LCDは無効、CLC1は無効、CLC2は無効 
    PMD5 = 0x7E;
    
    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 = 0xC8;

    // 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 ;             // 全割込み処理を許可する
    
    RA0 = 0;
    
    while(1){
        if (LEDflg == 0) RA2 = 0 ;    // 10番ピンにLOWを出力する(LED OFF)
        else             RA2 = 1 ;    // 10番ピンにHIGHを出力する(LED ON)
        //RA0 = 0;
        //__delay_ms(950);            
        //RA0 = 1;
        //__delay_ms(50);
        SLEEP();
        NOP();
    }
    return;
}

あまり、大きく変えていません。PMD関連の設定と、ループ関数内でのLチカをやめて、その代わりにスリープコマンドを記述しています。
これで、RTCCで10秒間隔で割り込みがかかり、すぐにスリープに入るという動作を繰り返します。ループ関数内でLEDのオン、オフをしていますので、RA2に繋いだLEDが10秒ごとに点滅を繰り返します。LEDはスリープしていても点灯時はつきっぱなしです。つまり、電流を消費します。ですので、今回のテストプログラムは省電力という観点では良いプログラムではありません。スリープする前にLEDは消灯して消費電力を下げてスリープするのが基本です。今回は、LED点灯時と消灯時の消費電流の差を測るためにあえてこういうプログラムにしてあります。
実際に消費電流を測ってみました。電源電圧は3.3Vにしました。

LED点灯時の消費電流。3.6mA
LED消灯時の電流。65.5μA

ちょっとわかり難いですが右側のLEDがついている状態で3.6mA。消えている状態で65.5μAでした。以外に電流を消費していますねえ。
もう少し工夫の余地があるのかもしれません。データシートを読むと、Low Powe Sleep Modeというのがあるようです。ピンにつながっているLDOを止めることで消費電流を下げることができるようです。また、周辺装置を完全にストップしてしまえばもっと消費電力を下げれそうです。

低消費電力を極める

それでは、RTCC以外の全部の周辺装置をストップして、LDOを止めた状態でどうなるかテストしてみました。
PMDはめんどくさいので下記のように、PMD2以外は0xFFを、PMD2は0x7Fとしました。

    PMD0 = 0xFF;
    PMD1 = 0xFF;
    PMD2 = 0x7F;
    PMD3 = 0xFF;
    PMD4 = 0xFF;
    PMD5 = 0xFF;

LDOを止めるには、

VREGCONbits.VREGPM = 1

と一文を書き加えます。
これで、42.4μAまで下がりました。

RTC以外を停止して42.4μA

LFシリーズなら

部屋をあさっていたら、PIC16LF19157というチップが見つかりました。これは、PIC16F19155と同じシリーズで、40ピンのタイプです。(PIC16F19155は28ピン)。しかも、型番がFではなくLFなので、更に省電力が期待できます。時計を作ろうとしていた時に購入したもののようです。
早速実験してみました。
同じシリーズなのでプログラムはほぼ同じでいけます。ピン数が多いので、ピンの設定の部分だけ書き足しました。
ところが、プログラムを書きこもうとすると電力不足と言われます。こんなメッセージが出ます。
The target circuit may require more power than the debug tool can provide.
An external power supply might be necessary. Connection Failed.
PIC16F19155は何の問題もないに、PICLF19157はエラーになってしまいます。LF品番なので、PicKitから供給する電圧を3.25Vに設定してます。PIC16F19155でも3.25Vでやってみましたが、こちらは何の問題もなく書き込めました。
仕方がないので、電源を3.3VのACアダプタから供給してみました。ところが、今度は
Conection Failed
と出て、うまくいきません。何度やってもダメで、USBポートを変えてもダメでした。
ノートパソコンから繋いでいるのがダメなのかと思い、デスクトップパソコンから繋いでみると、PicKitからの電源供給で問題なく書き込めてしまいました。
ちなみに、ノートPCの方は、Windows11で、MPLAB XIDEのバージョンが6.0、デスクトップの方は、Windows10で、MPLAB XIDEのバージョンが5.2でした。OSの違いなのか、MPLAB XIDEのバージョンの違いなのか、はたまた、デスクトップとノートの違いなのか、ちょっと判らないですが。自分の場合、デスクトップPCの周りはスペースがほとんどないので、マイコンへの書き込みとか電子工作関連はノートPCで行うのが基本なのですが、ちょっと厄介です。
とりあえず、プログラムが書けたので、電流値を測定してみました。

PIC16LF19157のスリープ時の電流。12.7μA

写真のように12.7μAまで下がりました。
かなり下がりましたが、思ったほどではないかなという印象。マイクロチップの資料によると、ディープスリープ時の電流消費は、60nAで、RTCCが900nAということなので、約1μAというのが公式資料のデータです。この値は、おそらく最低電流をとったチャンピオンデータと思われます。最低電圧の1.8Vで動作したときのデータではないかと思われます。
今回は3.3Vで動作させたので、そのあたりの差なのかもしれません。あとは、使ったテスターが中華製なのでどこまで測定値に信頼性があるかという問題もあります。いずれにしても、LFにすることでスリープ時の消費電流も半分以下に抑えられるということがわかりました。まあ、PICの消費電流をいくら抑えても、電子ペーパー側の消費電流が問題になりそうなので、そのあたりを測定してみないとトータルでどのくらい消費するかはわからないですが。

PIC16F19155を使ってみる(その3)

前回は、RTCCモジュールを動かしてみるところまでやりました。
alasixosaka.hatenablog.com

しかし、RTCCモジュールが動いていることは確認できたのですが、アラーム機能が正しく動作しているかは確認できませんでした。
RTCCはのアラームが発報すると割り込みをかけることができるので、割り込み処理でLEDを点滅させてアラームの動作を確認してみます。
また、前回はMCCを使ってプログラムを作りましたが、今回はMCCを使わず、main.cのみでプログラムを完結させてみました。まあ、実のところを言うと、MCCで生成された割り込み処理関数の使い方がいまいちよくわからんかったというのが実態ですが。とくに、引数をどうするかに引っかかってしまいました。ということでブラックボックスのまま先に進んでもどうもいまいちかなと思ったので、MCCが生成するコードは参考程度にして、メインルーチンのみのプログラムにしてみました。
ソースコードはこちらです。

#include <xc.h>
#include <PIC16f19155.h>

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits->Oscillator not enabled
#pragma config RSTOSC = HFINT1    // Power-up default value for COSC bits->HFINTOSC (1MHz)
#pragma config CLKOUTEN = OFF    // Clock Out Enable bit->CLKOUT function is disabled; i/o or oscillator function on OSC2
#pragma config VBATEN = OFF    // VBAT Pin Enable bit->VBAT functionality is disabled
#pragma config LCDPEN = OFF    // LCD Charge Pump Mode bit->LCD Charge Pump is disabled.
#pragma config CSWEN = ON    // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON    // Fail-Safe Clock Monitor Enable bit->FSCM timer enabled

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit->MCLR pin is Master Clear function
#pragma config PWRTE = OFF    // Power-up Timer selection bits->PWRT disable
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit->ULPBOR disabled
#pragma config BOREN = ON    // Brown-out reset enable bits->Brown-out Reset Enabled, SBOREN bit is ignored
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices
#pragma config ZCD = OFF    // Zero-cross detect disable->Zero-cross detect circuit is disabled at POR.
#pragma config PPS1WAY = OFF    // Peripheral Pin Select one-way control->The PPSLOCK bit can be set and cleared repeatedly by software
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable bit->Stack Overflow or Underflow will cause a reset

// CONFIG3
#pragma config WDTCPS = WDTCPS_31    // WDT Period Select bits->Divider ratio 1:65536; software control of WDTPS
#pragma config WDTE = OFF    // WDT operating mode->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits->window always open (100%); software control; keyed access not required
#pragma config WDTCCS = SC    // WDT input clock selector->Software Control

// CONFIG4
#pragma config BBSIZE = 512    // Boot Block Size Selection bits->Boot Block Size (Words) 512
#pragma config BBEN = OFF    // Boot Block Enable bit->Boot Block disabled
#pragma config SAFEN = OFF    // SAF Enable bit->SAF disabled
#pragma config WRTAPP = OFF    // Application Block Write Protection bit->Application Block NOT write-protected
#pragma config WRTB = OFF    // Boot Block Write Protection bit->Boot Block NOT write-protected
#pragma config WRTC = OFF    // Configuration Register Write Protection bit->Configuration Words NOT write-protected
#pragma config WRTD = OFF    // Data EEPROM Write Protection bit->Data EEPROM NOT write-protected
#pragma config WRTSAF = OFF    // Storage Area Flash Write Protection bit->SAF NOT write-protected
#pragma config LVP = OFF    // Low Voltage Programming Enable bit->High Voltage on MCLR/Vpp must be used for programming

// CONFIG5
#pragma config CP = OFF    // UserNVM Program memory code protection bit->UserNVM code protection disabled

int LEDflg ;

#define _XTAL_FREQ 1000000

void __interrupt() isr(void)
{
    PIR8bits.RTCCIF = 0;
    if (LEDflg == 0) LEDflg = 1 ;
    else             LEDflg = 0 ;
    return;
}

void main(void) {

    ANSELA = 0b00000000;  // すべてデジタルに
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00001000;  // MCLR以外は出力に
    TRISB = 0x00;
    TRISC = 0x00;
    
     // 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;
    
    // SYSCMD SYSCLK enabled; ACTMD ACT enabled; FVRMD FVR enabled; IOCMD IOC enabled; NVMMD NVM enabled; 
    PMD0 = 0x00;
    // TMR0MD TMR0 enabled; TMR1MD TMR1 enabled; TMR4MD TMR4 enabled; TMR2MD TMR2 enabled; 
    PMD1 = 0x00;
    // ZCDMD ZCD enabled; DACMD DAC enabled; CMP1MD CMP1 enabled; ADCMD ADC enabled; CMP2MD CMP2 enabled; RTCCMD RTCC enabled; 
    PMD2 = 0x00;
    // CCP2MD CCP2 enabled; CCP1MD CCP1 enabled; CCP4MD PWM4 enabled; CCP3MD PWM3 enabled; 
    PMD3 = 0x00;
    // CWG1MD CWG1 enabled; UART2MD EUSART2 enabled; MSSP1MD MSSP1 enabled; UART1MD EUSART enabled; 
    PMD4 = 0x00;
    // CLC3MD CLC3 enabled; CLC4MD CLC4 enabled; SMT1MD SMT1 enabled; LCDMD LCD enabled; CLC1MD CLC1 enabled; CLC2MD CLC2 enabled; 
    PMD5 = 0x00;
    
    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 = 0xC8;

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

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

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

    // Enable RTCC interrupt
    PIE8bits.RTCCIE = 1;
    
    INTCONbits.PEIE   = 1 ;             // 周辺装置割り込み有効
    INTCONbits.GIE    = 1 ;             // 全割込み処理を許可する
    
    RA0 = 0;
    
    while(1){
        if (LEDflg == 0) RA2 = 0 ;    // RA2番ピンにLOWを出力する(LED OFF)
        else             RA2 = 1 ;    // RA2番ピンにHIGHを出力する(LED ON)
        RA0 = 0;
        __delay_ms(950);            
        RA0 = 1;
        __delay_ms(50);
    }
    return;
}

初期設定の所ですが、ピンの設定までは前回までと同じです。そのあとに、オシレータの設定やらPMDの設定やらがだらだらと書いてありますが、基本的になくても動くと思います。この辺はMCCのコードからとってきたものです。RTCCONbits.RTCWREN = 1;以降がRTCC周りの設定です。
まず、RTCWRENビットを1にします。こうすることでRTCCの各レジスタにアクセスが可能になります。このあたりの設定も実はPIC18F24J50とは異なっています。PIC18F24J50では、RTCERENビットを変更するために、

EECON2 = 0x55;
EECON2 = 0xAA;

の2行が必要です。これは容易にビット変更できないようになっている仕様のようですが、PIC16F1955ではその必要がなく、RTCWRENビットは簡単に変更できるようになっています。
そして、

 RTCCONbits.RTCEN = 0;

として、RTCCをいったんストップしています。

RTCCONbits.RTCCLKSEL = 0;

は、RTCCのクロックソースを選択しています。0で水晶振動子を選択します。
その次が、RTCCのレジスタ設定で

YEAR     = 0x22;   // year
MONTH    = 0x9;    // month 
WEEKDAY  = 0x0;    // weekday 
DAY      = 0x11;    // day
HOURS    = 0x21;    // hours 
MINUTES  = 0x44;    // minutes 
SECONDS  = 0x10;    // seconds 

それぞれのレジスタに値を書き込みます。値は16進数のHEXではなく、BCDで書き込むことになっています。2022年9月11日21時44分10秒に設定しています。(MCCでRTCCのプログラムを作成した日時です)
レジスタへの書き込みも、PIC18F24J50では、ウィンドウレジスターというのを使って書き込む(読みだす)ことになっていますが、PIC16F19155ではウィンドウレジスターはなく、直接各レジスタに書き込むようになっています。
次はアラームの設定です。

ALRMCONbits.ALRMEN = 0;
    // ARPT 0; 
ALRMRPT = 0x00;

まず、ALRMENビットを0にして、アラームを無効にします。
ALRMRPTはアラームのリピート回数ですが、後ほどCHIMEを設定しますので、0回に設定します。
アラームの設定値は、

 ALRMMTH  = 0x9;  // month 
 ALRMWD   = 0x0; // weekday 
 ALRMDAY  = 0x11;  // day
 ALRMHR   = 0x21; // hours 
 ALRMMIN  = 0x44;  // minutes 
 ALRMSEC  = 0x10;  // seconds 

と各種の値を設定していますが、設定値に意味はありません。ここも、MCCが生成したプログラムからそのままコピーしています。
アラームの設定が終わったら

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

ALRMENビットを1にしてアラームを有効にしています。
そして、ALRMCONレジスタに0xC8を書き込んでいます。
第7ビットがさっき立てたALRMENビットで、また1を書き込んでいます。さっきの命令は意味がないように思うのですが、この辺もMCCのコードそのままなので...。第6ビットはCHIMEを設定するビットでビットを立てることでCHIME機能をONにします。こうすることでアラームが永久に繰り返されます。
アラームの間隔を設定するのが、第2ビットから第5ビットで、0010を書き込んでいるので10秒ごとにアラームが発報します。第1ビットと第0ビットは無効です。この設定で10秒間隔のアラームがずっと繰り返されることになります。
次はキャリブレーションの設定で

// CAL 0; 
RTCCAL = 0x00;

キャリブレーションは0に設定しています。キャリブレーションを適切に設定することで月差が数秒程度まで調整することができます。プログラムでどうするかはちょっと難しい問題でしょうが。一定間隔で正しい時刻を入手して、誤差をもとに新しいキャリブレーション値を設定するといったところでしょうか?
次は、

// Enable RTCC
RTCCONbits.RTCEN = 1;
while(!RTCCONbits.RTCEN);

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

で、RTCENビットを立ててRTCCを作動させています。一応、ビットが立ったのを確認するまで待ちます。
そして、RTCWRENビットをクリアして、レジスタへの書き込みを禁止します。
その次が割り込みに関する部分で

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

// Enable RTCC interrupt
PIE8bits.RTCCIE = 1;
    
INTCONbits.PEIE   = 1 ;             // 周辺装置割り込み有効
INTCONbits.GIE    = 1 ;             // 全割込み処理を許可する

まず、RTCCIFを0にして割り込みフラグをクリアします。そして、RTCCIEを立ててRTCC割り込みを許可、そのあとPEIEを立てて周辺装置割り込みを許可、最後にGIE立てて全割り込みを許可しています。
割り込み処理ルーチンは、

void __interrupt() isr(void)
{
    PIR8bits.RTCCIF = 0;
    if (LEDflg == 0) LEDflg = 1 ;
    else             LEDflg = 0 ;
    return;
}

となっていて、割り込みがかかるごとに変数LEDflgを変更しています。
メインルーチンのループの部分は、

while(1){
        if (LEDflg == 0) RA2 = 0 ;    // RA2番ピンにLOWを出力する(LED OFF)
        else             RA2 = 1 ;    // RA2番ピンにHIGHを出力する(LED ON)
        RA0 = 0;
        __delay_ms(950);            
        RA0 = 1;
        __delay_ms(50);
    }

となっていて、LEDflgの値によって、RA2ピンのLEDをオン、オフしています。残りの部分は通常のLチカです。

コンパイラーのエラーではまる。

これでちゃんと動くのですが、最初はコンパイラーがエラーを出してコンパイルしてくれませんでした。理由は、一部のレジスタコンパイラに認識されないためです。インクルードの所で、”#include <PIC16f19155.h>” を書いているのは対策のためです。調子が悪いとこいつがそもそも認識されません。
そして、RTCC関連のレジスタ類は全部認識されません。

コンパイルエラーの状態

ネットでググっていろいろ対策を試みてみたのですが、いずれも効果なく、どうもXC8コンパイラかMPLAB XIDEのどちらかの動作が不安定なためのようです。
新しいプロジェクトを作成し、エラーになったプログラムを丸ごとコピペしたらちゃんと動いたりします。今のところ、これといった対処法がないので、プロジェクトを新規に作成する以外に手がない状態です。
あまり症状がひどい場合は、前のバージョンにダウングレードすることも考えないといけないかもしれません。
次回は、スリープを実行して、どのくらい消費電流を削減できるか試してみようと思っています。