ラベル PIC24F の投稿を表示しています。 すべての投稿を表示
ラベル PIC24F の投稿を表示しています。 すべての投稿を表示

土曜日, 12月 07, 2019

小型マイコンを使うということ -- 今までのまとめにかえて

自分のブログなので,自分のまとめとしておく。

PIC, AT, STMといったいわゆる小型のマイコンを,趣味,興味から使い始めてみて2ヶ月ほど。その間に,ありがたいことで,職場でも使わせていただく機会があり,いろいろ勉強になった数ヶ月だった。

なんだかんだと,PICを一番使っている。特に,内蔵モジュールのPWMとコンパレータを使った案件は楽しかった! まさにPICを使いこなしている,という雰囲気だ。
ただ,XC8/XC16と,MPLAB IDEには手を焼く。
まず,XC8は整数の扱いが他のコンパイラーと少し違う? 計算が正しいか,入念なチェックが必要。
XC16+MCCは無愛想で,生成されたサンプルがさっぱり理解できない...
とか,細かいところはやはり不満。
そして,ICSPによるプログラム書き込みはいいけど,そのままそれをシリアルモニターできないのはツラい。別途シリアル回線を準備するか,OLED等で状況表示,スイッチ等で動作変更が必要となる。
使う用途は限定されるが,MPLAB IDEのsimulationは,それなりに使える。上記,「コンパイラーは正しく計算しているか?」の検証は,ある程度できる。

STM32 Nucleo + mbed では,上記のようなPICの不満は解消される。
ハードウエア設定はほぼ皆無でよく,サンプルを見てソースコードを埋め込めば,割とあっさりと動くようになる。USBで接続したまま,mbedのweb上のコンパイラーからダウンロードされた .bin を,ターゲットにコピーするようにすれば,即,実行される。さらにUSBを接続したままで,パソコン上でシリアルターミナルソフトで状況確認が容易である。
惜しいのは,大掛かりなmbedの影響か,CPUの能力を100%引き出せてはいないのではないか,ということ。STM32であれば,ST純正の開発環境を使えば少しは違うのかと思うが,まだ試せていない。
職場でも使ってみた。いい感じではあったが,残念ながら,私の接続ミスでターゲットを壊してしまった。反省...

古いSTM32も,mbedで.binをつくり,STLINKでターゲットに書いて実行させることはできるようになった。ただしこちらも壊してしまい,深くは検証できていない。また反省...




アトメルのCPU + Arduinoの環境,小規模な制御は,上記の流れから「かなりいい線」ではないかと思っている。シリアルを接続したまま,書き込みと,そのままArduino IDEのシリアルモニターが使える。Arduino言語はそれなりによくできていて,参考になるコードもいっぱい。コーディングはしやすい。
惜しいのは,クラウドベースの開発環境が無いこと。mbedの「場所を問わずに開発」はよくできている。
それはともかく,もう少し使ってみよう,ということで,職場でも使えそうなこれを発注してみた。

https://www.dfrobot.com/product-1075.html


職場では,ブレッドボードは使わない。実験基板に,あるいは直接はんだ付けしてテストする。小規模な方が,組み込むにせよ,外でテストするにしても,有利である。
USBからプログラム,シリアルモニターが使えそうなので,開発や,状況確認も簡単そうだ。

さて,届くまで楽しみに,別のボードで遊んでいよう。

火曜日, 11月 12, 2019

働くPIC

そうそう,こういうの,やりたかったんだ。

 micro:bitから基準電圧を出すために,PWMの波形を確認。

pins.analogSetPeriod(AnalogPin.P1, 100)
というコードで,1/100usec = 10kHzの矩形波をP1から出力している。これをPIC24Fの簡易オシロで確認した。

これなら,1k+10uFで,十分簡易DCにできそうだ。

土曜日, 11月 09, 2019

PIC24FV16KM202 EEPROMを使う

簡易オシロスコープの設定を記憶させるために,EEPROMを使うことを目論んだ。
いつも通りMCCで生成し,サンプルを見ながら仕込むが,どうも固まってしまう。

webで調査,以下のページの関数を頂きました。コメント送信欄がありませんでしたので,この場をお借りしてお礼申し上げます。

http://jr4pdp.blog.enjoy.jp/myblog/2015/06/pic24fv32keepro-1fc7.html

関数はコピペでOK,MCCの[MEMORY]も不要でした。

eeWrite(0,(uint16_t(書きたいデータ)); で書き込み。
(uint16_tの変数)= eeRead(0); で最初のアドレスの16ビット読み込み。

注意としては,MPLAB ideでビルドしてプログラムを焼き込むと,データはHで埋めつくされちゃうということ。これがわからず,しばらく悩んだ...

プログラムを焼き込んで動作したら,MPLAB ideを終了, チップ単品でON/OFFさせても,しっかり覚えていることを確認。よかった...

PIC24FV16KM202 w/microstick 簡易オシロスコープ 途中だけど

趣味,勉強も兼ねて,作ってみた。プログラム組んでみた。自分自身で出す1kHz/50% PWMを直結して読んでみた。あまりテストしていないので,まだまだ誤りが多いと思うが...

  • 実験値,100k sample/sec ぐらいが限度だった。
  • 5V ADC を 1ch 読んでグラフ化。手を抜くため,分圧,増幅等しない。他の5V/3.3Vマイコンの波形が少し見られれば良いとする。
  • 表示領域は128*16ドット。OLED用のバッファにデータを仮置き。つまりAD値を8ビットに圧縮。有効数字は約2桁。たぶんこれで十分。
  • AD値は256回読む。簡易トリガー判定(下記)を行い,そのうち128回分のデータをグラフ化。
  • 横時間軸はスイッチで加減できるようにする。手を抜くため,単純に2倍または1/2にする。
  • 簡易トリガー
    • 最初の立ち上がりのみ判定。
    • 判定値はAD値の半分固定。
    • 見つかったら,トリガーポイントを表示領域の中央にする。見つからなかったら最初の128個のデータを表示。
  • 簡易周波数測定
    • 最初の2回の立ち上がり時刻の差をとり,計算。表示するプログラムにまだバグがある。(kHz以上がうまく表示できない)
と,まあ,我ながらかなりの「手抜き仕様」になっている。

C言語の,データ型の変換やら,グローバル変数の扱いやらに手を焼きながら,これだけでも数日かかってしまった。まあ,これが楽しいのだけど。

/**
  Section: Included Files
*/
 


#include "mcc_generated_files/system.h"
#include "mcc_generated_files/mcc.h"

#include "oled.h"

/*
                         Main application
 */


#define FCY 16000000UL
#include <libpic30.h>


// #define address 0x3C


unsigned int ADC_COUNT = 256;
unsigned int ADC_SKIP_INIT = 0x08;
unsigned int ADC_SKIP = 0x08;

int PREV_SEC = 0;

extern char prev_key_state;
extern char current_key_state;
extern char key_data;
extern char pressed_key;
extern char _KEY_STATE;

extern unsigned char OLED_buffer[128 * 32 / 8];

char d1[30]="";

void My_TMR1_ISR(void) { // read AD value and store
    ADC_SKIP--;
    if (!ADC_SKIP) {
        if (ADC_COUNT) {
            ADC_COUNT--;
            OLED_buffer[255-ADC_COUNT]=(ADC1_ConversionResultGet())>>2;
        }
        /*
        sprintf(d1,"ADC_COUNT=%3d",ADC_COUNT);
        OLED_string(d1,0,0);
        OLED_write();
         */
        ADC_SKIP=ADC_SKIP_INIT;
    }
}


void show_status(void) {
    // sprintf(d1,"Last ADC %5d",ADC1_ConversionResultGet());
    // OLED_string(d1,0,16);
  
    // TMR1 period 5us -> 200kHz sampling
    //

    switch (ADC_SKIP_INIT) {
        case 2:
            sprintf(d1,"100kHz/s   ");
            break;
        case 4:
            sprintf(d1,"50kHz/s    ");
            break;
        case 8:
            sprintf(d1,"25kHz/s    ");
            break;
        case 16:
            sprintf(d1,"12.5kHz/s  ");
            break;
        default:
            sprintf(d1,"SKIP %4d",ADC_SKIP_INIT);
            break;
    }
    OLED_string(d1,0,16);
    OLED_write();
  
}

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

    TMR1_Stop();

    __delay_ms(500);

    OLED_Init(0x3C);
    OLED_clear();
  
    //           12345678901234567890
    OLED_string("  Loading...", 0, 0);
    OLED_string("    PIC24FV16KM202", 0, 8);
    OLED_string("   with OLED 128*64.", 0, 24);
    OLED_write();

    __delay_ms(500);

    OLED_clear();
    OLED_write();
      
  
    TMR1_SetInterruptHandler(My_TMR1_ISR) ;
    TMR1_Start();
  
    show_status();
  
    while(1) {
      
        // switch
      
        if (_KEY_STATE==3) {
            key_data=current_key_state;
            _KEY_STATE=0; // means clear
            if (key_data==3) {
                switch (pressed_key) {
                    case 3:
                        break;
                    case 1:
                        //           12345678901234567890
                        // OLED_string("RA1 pressed.        ",0,24);
                        // OLED_write();
                        pressed_key=3;
                        // RA1 program
                        TMR1_Stop();
                        if (ADC_SKIP_INIT<0x10000) {ADC_SKIP_INIT=ADC_SKIP_INIT<<1;}
                        show_status();
                        ADC_SKIP=ADC_SKIP_INIT;
                        ADC_COUNT=256;
                        TMR1_Start();
                        break;
                    case 2:
                        //           12345678901234567890
                        // OLED_string("RB3 pressed.        ",0,24);
                        // OLED_write();
                        pressed_key=3;
                        // RB3 program
                        TMR1_Stop();
                        if (ADC_SKIP_INIT>2) {ADC_SKIP_INIT=ADC_SKIP_INIT>>1;}
                        show_status();
                        ADC_SKIP=ADC_SKIP_INIT;
                        ADC_COUNT=256;
                        TMR1_Start();
                        break;
                    case 0:
                        //           12345678901234567890
                        // OLED_string("RA1 and RB3 pressed.",0,24);
                        // OLED_write();

                        // RA1 and RB3 program
                        pressed_key=3;
                      
                        TMR1_Stop();
                        show_status();
                        TMR1_Start();
                      
                        break;
                }
            } else {
                pressed_key = pressed_key & key_data;
            }
        }
      
        /* sprintf(d1," AD = %5d",ADC1_ConversionResultGet());
        OLED_string(d1,0,8);
        OLED_write();
         */
      
        if (!ADC_COUNT) {
         
            TMR1_Stop();
          
            unsigned char i=0;
          
            // find trigger point
            // trigger result -> OFFSET.
          
            unsigned char OFFSET=0;
            unsigned char OFFSET_1=0;
          
            for(i=63; i<191; i++) {
                if ( ((unsigned char)(OLED_buffer[i])<127) & ((unsigned char)(OLED_buffer[i+1])>127) ) {
                    if (OFFSET == 0) {
                        OFFSET=i;
                    } else if (OFFSET_1 == 0) {
                        OFFSET_1=i;
                    }
                }
            }
          
            // print measured freq
          
            uint32_t freq;
            uint8_t freq_unit;
          
            if ( (OFFSET>0) && (OFFSET_1>OFFSET) ) {
                // period = (OFFSET_1-OFFSET) * ADC_SKIP_INIT * 5us
             
                sprintf(d1,"%u",(OFFSET_1-OFFSET) * ADC_SKIP_INIT * 5);
                sprintf(d1,"%s[us]",d1);
                // OLED_string(d1,0,24);
              
                // freq to display STILL BUG!!!
              
                freq=100000000/(5*(OFFSET_1-OFFSET)*ADC_SKIP_INIT); // freq=(real freq)*100
                if (freq>100000) { freq_unit='k'; freq=freq/1000; } else {freq_unit=' '; }
                if (freq>100000) {
                    sprintf(d1,"%lu.%lu",freq/1000,freq-1000*freq/1000);
                } else if (freq>10000) {
                    sprintf(d1,"%lu",freq/100);
                } else {
                    sprintf(d1,"%lu",freq);
                }
                // sprintf(d1,"%lu",freq);
                sprintf(d1,"%s%c[Hz]",d1,freq_unit);
                OLED_string(d1,0,24);
            }
          
            // data shift using OFFSET.
            // At the same time, measure the AD value.
          
            if (OFFSET>0) {
                for(i=0; i<127; i++) {
                    OLED_buffer[i]=OLED_buffer[i+OFFSET-63];
                }
            }
          
            // convert to graph
          
            unsigned int bar,a,b,c,prev_a;
          
            for(i=0; i<127; i++) {
                a=(unsigned int)((unsigned char)OLED_buffer[i]);
                if (i>0) { b=prev_a; } else { b=a; }
                prev_a=a;
                // sprintf(d1,"a=%1x,b=%x,i=%x  ",a,b,i); OLED_string(d1,0,16); OLED_write(); __delay_ms(100);
                if (b>a) { c=b; b=a; a=c; }
              
                a = (unsigned int)((0x8000)>>((unsigned char)(a>>4)));
                b = (unsigned int)((0x8000)>>((unsigned char)(b>>4)));
              
                // sprintf(d1,"a=%1x,b=%x,i=%x  ",a,b,i); OLED_string(d1,0,24); OLED_write(); __delay_ms(100);

                a= ~(a-1);
                b=(b-1)|b;
              
                // sprintf(d1,"a=%1x,b=%x,i=%x  ",a,b,i); OLED_string(d1,0,24); OLED_write(); __delay_ms(100);
              
                OLED_buffer[i]=((a&b) & 0xFF);
                OLED_buffer[i+128]=(a&b)>>8;
            }
          
            OLED_write();
            ADC_COUNT=256;
            TMR1_Start();

            /*
            sprintf(d1,"KEY_STATE = %d",_KEY_STATE);
            _KEY_STATE=0;
            OLED_string(d1,0,16);
            OLED_write();
             */
        }
      
        // __delay_ms(500);
      
        // switch test
        // _LATA0=_RA1;
        _LATA0=(_RA1)*(_RB3); // means LED=ON when RA1 and/or RB3 are pressed
        // _LATA0=_RB6;



    };

    return 1;
}

/**
 End of File
*/

TMR1は5usecで割り込み。これを指定されたスキップ値で無視して,ADを取り込んでいる。

本当は,時間が長い時は,トリガーオフで「ロール」つまり逐一表示していくほうがいいのだけど,手を抜いて全てバッファで読み込むだけにしている。

水曜日, 11月 06, 2019

XC8 と XC16 で複数ソースファイルに使える「グローバル変数」の定義の仕方が違う?

前提として,
  • main.c
  • 再利用できそうな関数を記述した my_lib.c とそのヘッダーファイル my_header.h
  • main.c と my_lib.c で共通で使う変数 my_c
があるものとする。このいわゆる「グローバル変数」である my_c の定義,以下のように書かないと,エラーになったり,局所的なファイルになったりする。

XC8
my_header.h
extern char my_c = 'H'; // グローバル変数

void my_init(void); // my_lib.c で記述し,main.c から呼ばれる関数
void my_print(void); // my_lib.c で記述し,main.c から呼ばれる関数
my_lib.c
#include "my_header.h"

void my_init(void) {
    // my_c を使って関数記述
}

void my_print(void) {
    // my_c を使って関数記述
}
main.c
#include "my_header.h"

void main(void)
{
    // my_c, my_init, my_print を使う
}

XC8の別の方法
my_header.h
// extern char my_c = 'H'; // グローバル変数 これは不要

void my_init(void); // my_lib.c で記述し,main.c から呼ばれる関数
void my_print(void); // my_lib.c で記述し,main.c から呼ばれる関数
my_lib.c
#include "my_header.h"

char my_c = 'H'; // 実はグローバル変数

void my_init(void) {
    // my_c を使って関数記述
}

void my_print(void) {
    // my_c を使って関数記述
}
main.c
#include "my_header.h"

extern char my_c; // extern宣言必須

void main(void)
{
    // my_c, my_init, my_print を使う
}

XC16

my_header.h
// extern char my_c = 'H'; // グローバル変数 これは不要

void my_init(void); // my_lib.c で記述し,main.c から呼ばれる関数
void my_print(void); // my_lib.c で記述し,main.c から呼ばれる関数
my_lib.c
#include "my_header.h"

char my_c = 'H'; // 実はグローバル変数

void my_init(void) {
    // my_c を使って関数記述
}

void my_print(void) {
    // my_c を使って関数記述
}
main.c

// #include "my_header.h" // これが不要

extern char my_c; // extern宣言必須

void main(void)
{
    // my_c, my_init, my_print を使う
}

うーむ,わけわからん。。。誰か教えてください!(切実)

火曜日, 11月 05, 2019

PIC24FV16KM202 w/microstick SCCPモジュールでPWMテスト

これもsaharaさんのページをめちゃ参考にしております。

複雑そうに見えたPWM設定,できた。テスト用なので,2秒周期でLEDを光らせるありがちなやつを試した。

MCCで今回はSCCPを追加。

Compare/PWMモードを選ぶ。
Clock SourceはFOSCだと早すぎるので,LPRC=31kにする。
Modeは[Dual Edge Compare]にして,下3つは以下のように設定

Primary Compare Value = 0
Secondary Compare Value = (Time Period)*(Duty)=0xF0(要するに短め)
Time Period = 31k*2 = 0xF230

Saharaさんのページによれば,Primary Compare Valueの値を数えたらHにする,とのことなので,0でOK

Pin Managerで出力ポートを設定する。わりと固定的だった。最初はSCCP4を追加してみたけど,I2Cのポートと重なっていたので,SCCP5に変更した。



月曜日, 11月 04, 2019

PIC24FV16KM202 w/microstick MCCPモジュールで2つ目のタイマーを

I2Cを使っているからか,1個目のタイマーをADCにしたら,他のタイマーが見当たらない。いつものようにsaharaさんのページなどを参照すると,MCCPの機能と一つとして,「もう一つのタイマー」として使えるようだ。

MCCでMCCP1を追加。


あいかわらず無愛想だ... データシートも読む。16Bitだと,複雑なタイマーも仕掛けられるようだが,今回は10msecごとの単純なタイマー割り込みなので,設定が簡単そうな32bitを選択。
Primary Timer Period だけを示せばようだ。

その上,FOSC/2 -> 16MHz がもとのクロックなので,10msec / (1/16MHz) = 160000(dec) = 0x27100 が設定値。

そのタイマー処理だが,TMR1とは違って,main.cの中で定義できそうにない。やむを得ず,mccp1_tmr.c に記載する。

char _KEY_STATE = 0;

void __attribute__ ((weak)) MCCP1_TMR_Timer32CallBack(void)
{
    // Add your custom callback code here
    _KEY_STATE++;
}

そして main.cの処理 共通で使う変数をexternで記載する。これがわからなくて悩んだ... C言語の基本が...

extern char _KEY_STATE;

(中略 RTCCで1秒おきに以下を実行)

            sprintf(d1,"KEY_STATE = %d",_KEY_STATE);
            _KEY_STATE=0;
            OLED_string(d1,0,16);
            OLED_write();

ふう。できた。

PIC24FV16KM202 w/microstick ADCテスト

 PIC16FのXC8と勝手が違っていたので,メモ。

参考リンク

テストとして,プルアップしたスイッチをADCとして読み,読み値をOLEDに表示。

MCCを使ったセットアップ。

saharaさんのページの通り,[Enable Auto Sampling]にチェック。TADが指示された値より大きくなるように,Conversion Clockを設定。Conversion Triggerは[Internal counter...]を選ぶ。

これだけかと思った。プログラムを走らせてみると,どうもRA0の値を読んでいるっぽい。読みたいのはRB3なのに...

というわけで,MCCのADC画面の下,RB3=AN5のところ,[Scan Enable]にチェックを入れる。これが正解だった。複数チャンネルの時はどうするんだろう... 後で考えよう。

main.cでは,ADC1_ConversionResultGet()を使えば良い。
無事,5Vっぽい値が読めている。スイッチを押すと,ゼロになる。

(追記)
タイマー割り込みを使い,どのぐらいたくさんのサンプリングが行えるか実験した。
結果,5usec間隔の206k サンプル/秒 ぐらいが限界点だと思われた。

ADCの設定では1usecで変換が終わることになっているが,無理は禁物...

PLL使用の32MHz動作。検証は以下のmain.c 生存確認のため,Lチカのスイッチテストと,稼働時間が表示されている。

#include "mcc_generated_files/system.h"
#include "mcc_generated_files/rtcc.h"
#include "mcc_generated_files/mcc.h"

#define FCY 16000000UL
#include <libpic30.h>

#define address 0x3C

struct tm CURRENT_RTCC;

unsigned long int ADC_COUNT = 0;
unsigned long int ADC_COUNT_PREV = 0;

int PREV_SEC = 0;


void My_TMR1_ISR(void) {
    ADC1_ConversionResultGet();
    ADC_COUNT++;
}

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


    __delay_ms(500);

    OLED_Init(0x3C);
    OLED_clear();
    OLED_string("This is from my", 0, 0);
    OLED_string(" PIC24FV16KM202", 0, 8);
    OLED_string("with OLED 128*64.", 0, 24);
    OLED_write();

    __delay_ms(500);

    OLED_clear();
    OLED_write();
       
    char d1[30]="";
   
    TMR1_SetInterruptHandler(My_TMR1_ISR) ;
    TMR1_Start();
   
    while(1) {

        RTCC_TimeGet(&CURRENT_RTCC);

    
        if (CURRENT_RTCC.tm_sec != PREV_SEC) {
           
            PREV_SEC=CURRENT_RTCC.tm_sec;
           
            sprintf(d1,"%02d:%02d:%02d",CURRENT_RTCC.tm_hour,CURRENT_RTCC.tm_min,CURRENT_RTCC.tm_sec);
            OLED_string(d1,0,0);

            TMR1_Stop();
            sprintf(d1,"ADC_COUNT = %8lu",ADC_COUNT);
            OLED_string(d1,0,8);
            OLED_write();
            ADC_COUNT=0;
            TMR1_Start();
        }
        

        _LATA0=_RA1; // for switch test

    };

    return 1;
}

火曜日, 10月 29, 2019

PIC24FV16KM202 w/microstick OLEDを使う

(追記始め)
以前の表示だが,かろうじてI2Cが接続できただけで,やっぱりどうもデータが化けている。いろいろデータの送り方を試行錯誤したが,最終的にはsaharaさんのページで紹介されていたマイクロチップのサポートページoled.zipをダウンロード。自分の環境に移植することで,うまく動くようになった。
本体メモリーにバッファを持つタイプ。描画スピードとかは少しずつテストしていこう。

やれやれ。ともかく,目標はクリアした。きれい! うれしい!!

ちなみに移植手段としては,saharaさんのページの通り,I2C初期化はMCCのコードに任せ,送信関数部分のレジスターの定義名などをPIC24FV16KM202用に修正しました。

ああ,振り返ってみると,これだけで動いたんだな。でもいい勉強になった。

あ,横5ドットの文字で,5ドットに続けて次の文字が書かれていたので,1ドット余白を作るようにしました。


amazonで買った,SSD1306の128*32です。
MPLAB X IDE v5.25
XC16 (v1.41) でした。

(追記終わり)

当面の目標であったOLEDだが,やっとPIC24FV16KM202で動き始めた。まだめちゃくちゃな表示だけど,うれしい!



参考にさせて頂いたwebページはたくさんあれど,とりあえず以下3つ。

http://www.eonet.ne.jp/~jr3tgs/oled_test.htm

https://neo-sahara.com/wp/2017/11/16/pic24fj64gb002-ssd1306gm009605/

https://tool-lab.com/make/pic-practice-40/

普通にMCCでMSSPを追加,生成された例をよく読んで,必要なコマンドを書いていったつもりだが,どうにも動かない。I2Cに適切に送り出していない。タイムアウトせず,反応待ちで止まっている感じ。

micro:bitでは,あっけなく表示できたので,OLEDの故障ではない。

MSSP1ではなくてMSSP2を使わないとうまくいかないPICもある,ということで試すもダメ。

そこで,上のSaharaさんのページに,こんなことが書いてあった。

 「基本的に、I2Cの初期化関連はMCCの吐いたコードに任せ、OLEDとのやり取りは参考にするコードの方をなるべく生かす。」

ということで,基本設定はMCCで行い,I2Cの基本はとっても理解しやすかったJR3TGSさんのページから移植することにした。

ビット定義等を修正しつつ,コンパイルエラーをとにかくなくす。動いた!

以下,MCCとソースの覚書

クロックは最速でOK

MSSP1の設定 High Speedで,Baud Rate Generatorの数字を調整して400kに合わせる。


以下をmain.cに記述。赤字は修正したところ。私の環境の都合で,日本語コメントは削除してコンパイルする必要があります。(冒頭の追記参照 結局これは使わず,別のものから移植しました)

void i2c_start() { 
    SSP1CON2bits.SEN = 1;
    while(SSP1CON2bits.SEN);
}


void i2c_stop() {
    SSP1CON2bits.PEN = 1;
    while(SSP1CON2bits.PEN);
}


void i2c_write(unsigned char byte) {
    IFS1bits.SSP1IF = 0; // レジスタ名をPIC24F用に修正

    SSP1BUF = byte;
    while(!IFS1bits.SSP1IF);
    SSP1CON1bits.CKP = 1;
}

void oled_ini(void) {
    i2c_start();
    i2c_write(0x78); // OLED slave address
    i2c_write(0x00); // Control byte Co=0, D/C#=0
    i2c_write(0x8D); // Set charge pump
    i2c_write(0x14); // Enable charge pump
    i2c_write(0xAF); // Display ON
    i2c_stop();


}

void oled_clr(void) {
    unsigned int i;

    i2c_start();
    i2c_write(0x78); // OLED slave address                                                             
    i2c_write(0x00); // Control byte Co=0, D/C#=0                                                      
    i2c_write(0x20); // Set memory addressing mode                                                     
    i2c_write(0x00); // Horizontal addressing mode                                                     
    i2c_write(0x21); // Set column address                                                             
    i2c_write(0x00); // Column start address 0                                                         
    i2c_write(0x7F); // Column end address 127d                                                        
    i2c_write(0x22); // Set page address                                                               
    i2c_write(0x00); // Page start address 0                                                           
    i2c_write(0x03); // Page end address 3d                                                            
    i2c_stop();

    i2c_start();
    i2c_write(0x78); // OLED slave address                                                             
    i2c_write(0x40); // Control byte Co=0, D/C#=1 (The following data bytes are stored at the GDDRAM)  

    for(i=0; i<512; i++) // 128 column * 4page                                                         
        i2c_write(0x00); // filled with 0 (OLED clear)                                                     

    i2c_stop();
}

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

    // sample print A

    i2c_start();
    i2c_write(0x78);
    i2c_write(0x40);
   
    i2c_write(0x7C);
    i2c_write(0x12);
    i2c_write(0x11);
    i2c_write(0x12);
    i2c_write(0x7C);
   
    i2c_stop();
   
   
    while (1) {
        _LATA0=(_RA1); // これはテスト用(ボタンを押したらLED消える)
    }

    return 1;
}

土曜日, 10月 19, 2019

PIC24FV16KM202 スイッチ読み込み(基本的...)

勤務先のソフト屋さんに相談したら,あまりに基本的で怒られそうな案件だ...

だが,自分の大切な記録として残しておく。

設計仕様
  • RA1とRB3に接続した押しボタンスイッチで,PICの動作を変えたりできるようにする。
  • とりあえずテストのため,ボタンが押された状況をUART(printf)で送信。
  • ボタンは「オフトリガー」すなわちある程度押され,離した時に処理を開始する。
  • [RA1] 単独,[RB3] 単独,[RA1] と [RB3] の同時押し,以上3つを判定する。
オフトリガーで同時押しも実装しようとしたのは,micro:bitが良い動きだったから。
ボタンで割り込みがかかるとかっこいいのだが,難しそうなので諦め,mainループ内で判定することにした。

/**
  Section: Included Files
*/
#include "mcc_generated_files/system.h"
#include "mcc_generated_files/mcc.h"
#include <stdio.h>

/*
                         Main application
 */
unsigned long int GLOBAL_TIME = 0;
unsigned long int NEXT_TIME = 0;
char RECEIVED_CHAR = ' ';

char prev_key_state = 3;
char current_key_state = 3;
char key_data = 3;
char pressed_key = 3;

char KEY_STATE = 0;

void My_TMR1_ISR(void)                     
  {
    GLOBAL_TIME++;
    current_key_state = ((_RA1)<<1)|(_RB3);
    if (GLOBAL_TIME>NEXT_TIME) {
        // printf("Interrupted at %lu.\n",NEXT_TIME);
        NEXT_TIME=NEXT_TIME+100; // means 1 sec
        // printf("RA1 state:%d\n",_RA1);
        // printf("RB3 state:%d\n",_RB3);
        // printf("current_key_state = %d.\n",current_key_state);
    }
    switch(KEY_STATE) {
        case 0:
            if (current_key_state != prev_key_state) {
                KEY_STATE=1;
                prev_key_state=current_key_state;
            }
        break;
        case 1:
        case 2:
            if (current_key_state == prev_key_state) {
                KEY_STATE++;                
            } else {
                KEY_STATE=0;
            }
        break;
    }
   }

int main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    TMR1_SetInterruptHandler(My_TMR1_ISR);  
    NEXT_TIME=100;
    // TMR1_Start();
    while (1) {

        // test code
        _LATA0=(_RA1)*(_RB3); // means LED=ON when RA1 and/or RB3 are pressed

        // UART RX
        if (UART1_IsRxReady()) {
            RECEIVED_CHAR=UART1_Read();
            printf("%c was received at %lu.\n",RECEIVED_CHAR,GLOBAL_TIME);
            // printf(" -->> current_key_state = %d.\n",current_key_state);
            // printf(" -->> prev_key_state = %d.\n",prev_key_state);
            // printf(" -->> SW_STATE = %d.\n",KEY_STATE);
        }
        
        // KEY routine
        if (KEY_STATE==3) {
            key_data=current_key_state;
            KEY_STATE=0; // means clear
            // printf("key_data = %d.\n",key_data);
            if (key_data==3) {
                switch (pressed_key) {
                    case 3:
                        break;
                    case 1:
                        printf("RA1 pressed.¥n");
                        // RA1 program
                        pressed_key=3;
                        break;
                    case 2:
                        printf("RB3 pressed.¥n");
                        // RB3 program
                        pressed_key=3;
                        break;
                    case 0:
                        printf("Both RA1 and RB3 pressed.¥n");
                        // RA1 and RB3 program
                        pressed_key=3;
                        break;
                }
            } else {
                pressed_key = pressed_key & key_data;
            }
        }
    }

    return 1;
}



いわゆる「チャタリング処理」を施している。いろいろ試行した結果,20msecとした。タイマーを用いているが,のちのち間違えそうなので,タイマーそのものは10msecごと。3回読んで(つまり20msec間)スイッチの状態が同じなら,チャタリングではないと判定した。

それでも,「同時押し」では,例えば [RA1] 押した -> さらに[RB3] 押した と離散的なデータが取れてしまう。考えた末,やはりオフトリガー,すなわちスイッチが1個,または2個完全に離された状態を判定して,そこまで片方しか押されなかったのか,同時押しだったのかを覚えておくようにした。最後の方の pressed_key = pressed_key & key_data; 

スイッチが2個だけなので,単純にビット操作を使うとうまくいった。

PIC24FV16KM202 w/microstick USBシリアルボードでテスト

以下二つを接続して使うテストを行う。

microstick 5V

秋月さんで購入したUSBシリアル変換ボード

まずは電源接続を確認する。microstick回路図では,CPUはVDDと名付けられたネットで接続されている。


これは,デバッガーブロックのFPF2102というICでON/OFFコントロールされる。
 

FPF2102はオンセミのロードスイッチ。データシートに,VOUTにはVINを超える電圧をかけてはダメ,と明記されている。

したがって,USBモジュールからmicrostickのPICに直接電源を接続しちゃダメ,ということになる。

なので,普通に,microstickにはmicrostickの電源を接続し,モジュールはUSBから電源をもらい,両者は信号ピンとグランドのみを接続する,ということになる。

microstickは5Vでうごき,モジュールは5V入力対応だが,電源OFFは少し心配だ。だが,PICを守るためにも,microstickの電源を先に入れ,モジュールをUSBで接続,という順番を守らなくてはならない。

結果,動いた。

プログラムは前回使ったもの

接続イメージ


組み立てた様子

Mac側はmicro:bitの時と同様