組込み系システム開発室by藤原技研M

製作中の組込み機器や気になる開発ツールなどを中心に記事を書いています。

PIC24FJ128GB204用 Flash Memory書き込みプログラム

PIC24FJ128にはEEPROMが無いので、設定値等を記憶しておくことが難しくなります。一つの解決策として任意のプログラムエリアの1ページをFlashメモリに割り当ててそのエリアをEEPROM代わりに使う方法があります。

作成したコードは次の通りです。


//--------------------------------------------
// フラッシュデータ書き込み
//     unsigned int *values: 書き込みたいデータのポインター
//     unsigned int head: 先頭から何ワード目か指定する
//     unsigned int length:書き込みワード数
//     ワード(BP0-BP23:有効、BP24-31:データ無効)
//--------------------------------------------

 //0x1C00-0x2000までをFlashメモリに割り当てる。Hexファイルでは0x3800-0x4000
const __attribute__ ( (section("const_eeprom"), space (prog), address (0x1C00) ) )  unsigned int _flash_datas[512]; //※①


void Flash_Bulk_Write_With_Erase(unsigned int *values, unsigned int head, unsigned int length) {
     unsigned int i=0;
     unsigned int buffer[64];
     unsigned int page = __builtin_tblpage(_flash_datas);
     unsigned int offset = __builtin_tbloffset(_flash_datas);

     // rescue to buffer(一時的な64ワードのバッファにの64ワードのデータを退避する)
     TBLPAG = page;
     for (i=0; i < 64; i++) {
     buffer[i] = __builtin_tblrdl(offset + i*2);
     }

     // bulk over-write(引き渡されたデータをバッファにマージする)
     for (i=0; i < length; i++) {
          buffer[i + head] = values[i];
     }

     // Erase Page(1ページ消去する)
     TBLPAG = page;
     __builtin_tblwtl(offset, 0x0000); // this is important see reference.
     NVMCON = 0x4042; //WREN=1, ERASE=1:消去/プログラミングイネーブルビット,      NVMOP=0010:メモリのページ消去動作
     asm volatile ("disi #5"); //Disable Interrupts For 5 Instructions
     __builtin_write_NVM(); //erase 8line = 64*8
     while(NVMCONbits.WR);

     // Write 64 instructions(バッファからフラッシュメモリにデータを書き込む)
     NVMCON = 0x4001;
     TBLPAG = page;
     for (i=0; i < 64; i++) {
       __builtin_tblwtl(offset + i*2, buffer[i]); // *2 is required because address is 0 2 4 6 ,,,,,
       __builtin_tblwth(offset + i*2, 0xFF);
     }
     asm volatile ("disi #5"); //Disable Interrupts For 5 Instructions
     __builtin_write_NVM();
     while(NVMCONbits.WR);
}

 

//--------------------------------------------
// フラッシュデータ読み出し
// unsigned int address: アドレス
//--------------------------------------------
unsigned int ReadEprom(unsigned int address) {
     unsigned int page = __builtin_tblpage(_flash_datas);
     unsigned int offset = __builtin_tbloffset(_flash_datas);

     TBLPAG = page;
     return __builtin_tblrdl(offset + address*2);
}

================================================

 

※①の所の配列_flash_datasの大きさが96*8=768個では、ページをERASEしたときにページまたがりにより、他のエリアが消去されて、その結果マイコンが暴走しました。色々と検討した結果512個という結論に至りました。Read Device Memory to Fileボタンを押してHexデータダウンロードしてERASEする前と後を比較してみます。

       【ERASE前】               【ERASE/データWR後】

f:id:fujiharagiken:20181024204710p:plain

 

f:id:fujiharagiken:20181024204732p:plain

プログラム上:0x1C00 - 0x2000に割り当てますが、Hexデータでは、アドレスが2倍されているので0x3800 - 0x4000のエリアがERASEできているのが分かります。前後のエリアに悪影響が無いことを確認しています。

 

※参考にしたURL 

yuki-sato.com

 

太陽光発電(オフグリッド)(1)

今年は酷暑なのでエアコン必須です。我が家にはエアコンが各部屋にあり、電気代もバカになりません。そこで、今回作ったのがオフグリッド(売電しない)タイプの太陽光発電システムです。

f:id:fujiharagiken:20180809122332j:plain

300Wのパネルを2枚並列で使っているので、出力は300x2=600Wになります。最近のエアコンは省エネ設計(300W程度)なので、約2台分のエアコンの電気代をカバーすることができます。

また、過剰に発電すると電力計が逆に回るため、CTセンサーを分電盤に取り付け、自動的に出力を制限するリミッターも付けました。

f:id:fujiharagiken:20180809125725j:plain

f:id:fujiharagiken:20180809125753j:plain

発電量をWi-Fiで飛ばしてクラウド上にあげて、モニタできるFi-Fiプラグも購入し接続してあるので出先でも発電状態を確認することができて大変便利です。(白いプラスチック物部分)

 

 

<<部材購入リスト>>

①【単結晶300W】サンテックパワー 太陽光発電パネル STP300S-20/Wfw

 販売元:ソーラーオフ

 製品仕様
 ・セルタイプ:単結晶 156mm×156mm(6インチ)
 ・セルの配列:60枚(6×10)
 ・最大出力(Pmax):300W
 ・最大出力動作電圧(Vmp):32.6V
 ・最大出力動作電流(Imp):9.21A
 ・開放電圧(Voc):39.9V
 ・短絡電流(Isc):9.65A
 ・モジュール変換効率:18.3%
 ・サイズ:L1650mm×W992mm×D35mm
 ・重量:18.3kg

 

②グリットタイインバーター(SUN-1000GTIL2-LCD

 販売元:AliExpress (Sunhome Technology Co.,Ltd)

 製品仕様

 ・タイプ: DC/ACインバーター
 ・入力電圧: 22-60V
 ・出力電圧: 95-140v/185-265v
 ・出力電力: 1000W
 ・重さ: 5.5kg
 ・サイズ: 322*196*88mm


③単管/ジョイント/金具/ビス他

 販売元:ホームセンター・コーナン

 

 

 

■1年後の運用結果

f:id:fujiharagiken:20191012155941p:plain

 

 

 

PIC24FJ128GB204用のアプリの作成(1)

MCCでソースコードを作ります。

コンフィグレーションはUSBブートローダと同じにする必要があります。

設定でデフォルトから変更するのは次の通りです。

f:id:fujiharagiken:20180217170128p:plain

f:id:fujiharagiken:20180217170222p:plain

 

f:id:fujiharagiken:20180216165527p:plain

Generateボタンを押します。

出来上がったCWは以下の通りです。

// CONFIG4
#pragma config DSWDTPS = DSWDTPS1F // Deep Sleep Watchdog Timer Postscale Select bits->1:68719476736 (25.7 Days)
#pragma config DSWDTOSC = LPRC // DSWDT Reference Clock Select->DSWDT uses LPRC as reference clock
#pragma config DSBOREN = ON // Deep Sleep BOR Enable bit->DSBOR Enabled
#pragma config DSWDTEN = ON // Deep Sleep Watchdog Timer Enable->DSWDT Enabled
#pragma config DSSWEN = ON // DSEN Bit Enable->Deep Sleep is controlled by the register bit DSEN
#pragma config PLLDIV = DIVIDE2 // USB 96 MHz PLL Prescaler Select bits->Oscillator input divided by 2 (8 MHz input)
#pragma config I2C1SEL = DISABLE // Alternate I2C1 enable bit->I2C1 uses SCL1 and SDA1 pins
#pragma config IOL1WAY = ON // PPS IOLOCK Set Only Once Enable bit->Once set, the IOLOCK bit cannot be cleared

// CONFIG3
#pragma config WPFP = WPFP127 // Write Protection Flash Page Segment Boundary->Page 127 (0x1FC00)
#pragma config SOSCSEL = OFF // SOSC Selection bits->Digital (SCLKI) mode
#pragma config WDTWIN = PS25_0 // Window Mode Watchdog Timer Window Width Select->Watch Dog Timer Window Width is 25 percent
#pragma config PLLSS = PLL_FRC // PLL Secondary Selection Configuration bit->PLL is fed by the on-chip Fast RC (FRC) oscillator
#pragma config BOREN = ON // Brown-out Reset Enable->Brown-out Reset Enable
#pragma config WPDIS = WPDIS // Segment Write Protection Disable->Disabled
#pragma config WPCFG = WPCFGDIS // Write Protect Configuration Page Select->Disabled
#pragma config WPEND = WPENDMEM // Segment Write Protection End Page Select->Write Protect from WPFP to the last page of memory

// CONFIG2
#pragma config POSCMD = NONE // Primary Oscillator Select->Primary Oscillator Disabled
#pragma config WDTCLK = LPRC // WDT Clock Source Select bits->WDT uses LPRC
#pragma config OSCIOFCN = ON // OSCO Pin Configuration->OSCO/CLKO/RA3 functions as port I/O (RA3)
#pragma config FCKSM = CSDCMD // Clock Switching and Fail-Safe Clock Monitor Configuration bits->Clock switching and Fail-Safe Clock Monitor are disabled
#pragma config FNOSC = FRCPLL // Initial Oscillator Select->Fast RC Oscillator with PLL module (FRCPLL)
#pragma config ALTRB6 = APPEND // Alternate RB6 pin function enable bit->Append the RP6/ASCL1/PMPD6 functions of RB6 to RA1 pin functions
#pragma config ALTCMPI = CxINC_RB // Alternate Comparator Input bit->C1INC is on RB13, C2INC is on RB9 and C3INC is on RA0
#pragma config WDTCMX = WDTCLK // WDT Clock Source Select bits->WDT clock source is determined by the WDTCLK Configuration bits
#pragma config IESO = ON // Internal External Switchover->Enabled

// CONFIG1
#pragma config WDTPS = PS1 // Watchdog Timer Postscaler Select->1:1
#pragma config FWPSA = PR32 // WDT Prescaler Ratio Select->1:32
#pragma config WINDIS = OFF // Windowed WDT Disable->Standard Watchdog Timer
#pragma config FWDTEN = OFF // Watchdog Timer Enable->WDT disabled in hardware; SWDTEN bit disabled
#pragma config ICS = PGx1 // Emulator Pin Placement Select bits->Emulator functions are shared with PGEC1/PGED1
#pragma config LPCFG = OFF // Low power regulator control->Disabled - regardless of RETEN
#pragma config GWRP = OFF // General Segment Write Protect->Write to program memory allowed
#pragma config GCP = OFF // General Segment Code Protect->Code protection is disabled
#pragma config JTAGEN = OFF // JTAG Port Enable->Disabled

 

※CONFIG2のFNOSC = FRC は FRCPLLに手直しが必要になりました(数か月前はうまくいっていたのに原因不明です)

#pragma config FNOSC = FRCPLL  //ハンド修正

 

次の記事は、http://fujiharagiken.hatenablog.com/entry/2018/05/18/213844

キッチンタイマーの製作(6)機能仕様書

機能仕様書が無いとプログラムが作れないので、仮に以下のような仕様にします。

f:id:fujiharagiken:20180125164510p:plain

 f:id:fujiharagiken:20180116135648p:plain

f:id:fujiharagiken:20180130121724p:plain

【main.cを以下のように変更します】

時間設定画面に遷移するようにプログラミングします。

>|c|

int main(void) {
SYSTEM_Initialize();

//タイマー停止
TMR1_Stop();
__delay_ms(40);


// LCD初期化と初期表示処理
WireLcdInit(); // LCD初期化
__delay_ms(1); // ウェイト

gMode = 0;
LcdDisp();

//一定時間初期画面をキープする
__delay_ms(3000);

gMode =1;
int loopCnt = 1;

//永久ループ
while (1) {
SwScan(); // スイッチの取り込みとチャタリング除去処理
switch (loopCnt) {


(以降省略)

||<

 

【スイッチ操作の処理を以下のように変更します】

void swOperation ( void ) {
    // SWに変化があったか判別
    if ( bSwData != bSwDataSave ) {

        if ( ( bSwData & ( SW1 | SW2 ) ) == LOW) { // SW1,SW2同時押し判別
            //分秒スイッチ・オン
            gMin = 0;
            gSec = 0;
            gMode = 1;

            TMR1_Stop(); //タイマーストップ

            beep(70); // ピッ音
            __delay_ms(70);
            beep(70); // ピッ音
            __delay_ms(70);
            beep(70); // ピッ音
            __delay_ms(70);
            beep(210); // ピー音

        } else if ( (bSwData & SW1) == LOW ) { // SW1操作判別
       
            if(gMode == 1) {
                //分スイッチ・オン
                gMin++;
            }
   
            beep(100); // 1KHzで100ms発音処理を追加

        } else if ( (bSwData & SW2) == LOW ) { // SW2操作判別
            if(gMode == 1) {
                //秒スイッチ・オン
                gSec++;
            }
            
            beep(100); // 1KHzで100ms発音処理を追加

        } else if( (bSwData & SW3) == LOW ) { // SW3操作判別
            beep( 300); // 1KHzで100ms発音処理を追加

            if(gMode == 1) {
                gMode = 2;
                TMR1_Start(); //タイマースタート
            } else if(gMode == 2) {
                gMode = 1;
                TMR1_Stop(); //タイマーストップ
            }
        }
    }

    bSwDataSave = bSwData; // 変化検出用旧データを更新
}

 

------------------------------------------------------------------------------------------------------------

LCD表示処理を以下のように変更します】

void LcdDisp (void) {
    char str1[ ] = "Kitchen ";
    char str2[ ] = "Timer ";
    char str3[ ] = " START  ";
    char str5[ ] = "  STOP  ";
    char str6[ ] = "TIME UP ";
    char wk[9] = {'\0'};

    switch (gMode) {
        case 0: //初期画面表示
            //------[ 表示(1行目)]------
            WireLcdDisplay(str1, 8, 0x00);
            
            //------[ 表示(2行目)]------
            WireLcdDisplay(str2, 8, 0x40);
            break;
            
        case 1: // 時間設定画面表示
            //------[ 表示(1行目)]------
            WireLcdDisplay(str5, 8, 0x00);

            //------[ 表示(2行目)]------
            snprintf(wk, 9, " %02d:%02d" , gMin, gSec);
            WireLcdDisplay(wk, 8, 0x40);
            break;

        case 2: // 時間設定画面表示
            //------[ 表示(1行目)]------
            WireLcdDisplay(str3, 8, 0x00);

            //------[ 表示(2行目)]------
            snprintf(wk, 9, " %02d:%02d" , gMin, gSec);
            WireLcdDisplay(wk, 8, 0x40);
            break;

        case 3: // 時間設定画面表示
            //------[ 表示(1行目)]------
            WireLcdDisplay(str6, 8, 0x00);

            //------[ 表示(2行目)]------
            snprintf(wk, 9, " %02d:%02d" , gMin, gSec);
            WireLcdDisplay(wk, 8, 0x40);
            break;

        default:
            break;
    }
}

 

------------------------------------------------------------------------------------------------------------

 

【タイマー割り込み処理を以下のように変更します】

 (省略)

 #include <xc.h>
#include "tmr1.h"

extern int gMin; //変数の外部参照宣言を追加 ※#include "tmr1.h"の下に追加
extern int gSec; //変数の外部参照宣言を追加 ※#include "tmr1.h"の下に追加

 (省略)

void __attribute__ ( (weak) ) TMR1_CallBack(void)
{
    // Add your custom callback code here ※以下のコードを追加
    if(gSec == 0) {
        if(gMin > 0) {
            gMin --;
            gSec = 60;
        }
    }

    if(gSec > 0) {
        gSec --; //カウンタのデクリメント命令を追加
    }

}

------------------------------------------------------------------------------------------------------------

後、タイムアップ時の処理、LEDの点灯処理、時間に関する異常系(60秒以上禁止等)の処理を追加すれば完成です。

 

 

 


 

 

キッチンタイマーの製作(4)パルス出力:スピーカ制御

ボタン操作時や、タイマーアラーム用にビープ音を鳴らす必要があります。鳴らすためには以下の様な点を考慮する必要があります。

 

<出力回路>

ポート出力をHigh,Low繰り返すことでビープ音を鳴らすことができます。ただし、ポートの出力電流は数ミリアンペアなのでスピーカを駆動できませんのでトランジスタやFET等で電流を増幅しスピーカを駆動します。

f:id:fujiharagiken:20171211133542p:plain

<音程>

High、Lowの繰り返し周期で周波数が決まります。

f:id:fujiharagiken:20171211133730p:plain

 

簡易的に発音するなら次の方法があります。

f:id:fujiharagiken:20171220163920p:plain

f:id:fujiharagiken:20171220164514p:plain

【この手法の問題点】

長く発音させると他の処理が止まってしまう、また、高い周波数を発音することができない、という問題がありますが、発音が短時間であったり、高音でなければ使えます。

更に良くしたいのであれば、PWM(Pulse Width Modulation)を使います。

 

 

<PWM方式>

f:id:fujiharagiken:20171211134443p:plain

 

f:id:fujiharagiken:20171211134506p:plain

 

f:id:fujiharagiken:20171211134532p:plain

続きは(5)へ http://fujiharagiken.hatenablog.com/entry/2017/11/07/145039

キッチンタイマーの製作(2)MCCで入出力ポートの追加

マイコンのクロック設定をMCCを用いて設定します。

f:id:fujiharagiken:20171221175336p:plain

 

MCCを用いて入出力ポートの割当を行います。

今回割り当てるポートは

・入力:RA2、RA3、RA4

・出力:RB0、RB1    ※RB0はRP0と同じピン、RB1はRP1と同じピン

Pin Manager.GridウィンドウのPin Module項目のGPIO:input行とRA2,3,4と交差するところをクリックすると鍵がかかる仕組みになっています。。

RB0,RB1は出力ポートで使えるようになっているので今回は触らなくてもOKです。

f:id:fujiharagiken:20171113140749g:plain

Generateボタンをクリックします。

次のような警告がでますが、Yesをクリックします。

f:id:fujiharagiken:20171113140844p:plain

これで入力ポートが3本追加できました。ポートの値を取得する関数

 IO_RA2_GetValue()

 IO_RA3_GetValue()

 IO_RA4_GetValue()

を用いて、ポートの入力がHighかLowを調べることができます。

 

<追加するプログラム>

グローバル変数

//------[ global(広域)変数定義 ]------
unsigned char SW1 = 0x01; // スイッチデータのビット位置
unsigned char SW2 = 0x02; // スイッチデータのビット位置
unsigned char SW3 = 0x04; // スイッチデータのビット位置
unsigned char bSwData; // スイッチデータの格納変数
unsigned char bSwDataOld; // スイッチデータの格納変数(過去)
unsigned char bSwDataSave; // スイッチデータの変化検出用変数

 

【スイッチの取り込みとチャタリング除去処理】

void SwScan(void) {
    unsigned char bWk = 0;

    if (IO_RA2_GetValue() == HIGH) { // HIGHでオンのケース
        bWk |= SW1;
    }

    if (IO_RA3_GetValue() == HIGH) { // HIGHでオンのケース
        bWk |= SW2;
    }

    if (IO_RA4_GetValue() == HIGH) { // HIGHでオンのケース
        bWk |= SW3;
    }


    // スイッチの二度読み
    if (bSwDataOld == bWk) {
        bSwData = bWk; //前回と一致したのでスイッチデータを確定する
    }
    bSwDataOld = bWk; // 今回の値を保存

}

 

【スイッチの変化検出処理関数】

void swOperation (void) {
    // SWに変化があったか判別
    if (bSwData != bSwDataSave) {

        // SW1操作判別
        if ((bSwData & SW1) == LOW) {
            IO_RB0_SetHigh(); // LEDを点灯
        } else {
            IO_RB0_SetLow(); // LEDを消灯
        }
   
        // SW2操作判別
        if ((bSwData & SW2) == LOW) {
            IO_RB1_SetHigh(); // LEDを点灯
        } else {
            IO_RB1_SetLow(); // LEDを消灯
        }

        // SW3操作判別
        if ((bSwData & SW3) == LOW) {

            } else {

        }

    }

    bSwDataSave = bSwData; // 変化検出用旧データを更新
}

【メイン関数】

int main(void){
    // initialize the device
    SYSTEM_Initialize();

    int loopCnt = 1;

    //永久ループ
    while (1) {
        SwScan(); // スイッチの取り込みとチャタリング除去処理
        switch (loopCnt) {
            case 1:
                // SWが押された時のLED点灯処理
                swOperation();
                break;
            case 2:
                // ○○○処理
                break;
            case 3:
                // ○○○処理
                break;
            case 4:
            default:
                loopCnt = 0;
                break;
        }
        loopCnt++; // ループカウンタの更新
        __delay_ms(10); // 10msウェイト

    }

}

 

※__delay_ms(10); // 10msウェイトはエラーが出るので以下の記述をソースプログラムの先頭部に追加しておきます。

    //【定義】を追加

    #define FCY (_XTAL_FREQ*2) 

 

    //【インクルードファイルの指定】を追加

    #include <libpic30.h>  // __delay_ms()使用のため

 

続きは(3)へ http://fujiharagiken.hatenablog.com/entry/2017/10/21/124650