SSブログ

NEKO BENTO のソースコード [DIY電気]

過去記事でも3回ご紹介してる「猫用自動給餌器をリモコン制御に改造」の続編
「PetSafe おるすばんフィーダー デジタル2食分 バージョン2」という製品を改造しての改造シリーズ第4弾はプログラムソースコードの公開となる
PICマイコンを組み込んでおり、C言語のソースコードである

Summary:
犬猫の餌を入れておいて希望する時間後に蓋が開く商品であるが、ゆういちの希望する仕様ではなかったので改造した
マイコンは割り込みルーチンで時刻を持つ、また家電リモコンからの毎日時刻校正コマンドを受信するため時刻は正確である
左蓋、右蓋のOPEN時刻をセット可能、家電リモコン(スマホから操作)でOPENすることも可能
一か月以上もRUNして問題ないためバグは究極にゼロに近いと思っている

外観はこんな商品
シンプルはゆういちの好みである
DSC_0523.JPG

左右の蓋が開いた状態
DSC_0525.JPG

後ろには、黄色いスイッチ2つとAC/電池切り替えスイッチ増設
DSC_0524.JPG

手に持っているのがオリジナル基板
取付済みが自作基板
DSC_0522.JPG

自作基板は、Fusion PCBにガーバーデーターを送って製作した
MicrochipのPIC16F88マイコン搭載である
DSC_0526.JPG

表示器は小型のOLEDを採用している
PICマイコンが持つフォントデーターは234バイト、文字は[0]~[9], [A]~[Z], [:],[?],[-]、最低限のフォントしか持たない
DSC_0528.jpg

その自作基板の回路図はEagleで設計
nekobento_sch.png

プリント基板設計もEagle
シルク印刷で示すJP1,JP3はOLEDの表示モジュールのマウントパターン
基板表裏どちらでもマウントできるようにしている
nekobento_board.png

1980年代制御プログラムをコーディングしていた、
当時勤めていた会社の上司からプログラミングに対してコメントを受けることもあった
上司はアランドロンだった
ゆういちが上司はアランドロンと似ていると言い出したのではない、誰かが言ったので上司とアランドロンはヒモ付いただけである
アランドロンはゆういちに教えた、メインプログラムはグルグル回してタイマーで判断して複数ある処理プロセスに飛ばせばマルチタスクが実現する・ボンジュールと・・・
アランドロンから伝授した手法はゆういちが作り続けてきた多くのソフトに利用させていただいた
ゆういちのメインルーチンは必ず
while(1){ } である
特にこの手法を必要としないソフトも、while(1){ }、まずはこの手法を利用する
なぜならタスクを増やしたい場合に柔軟に対応できるからだ
このソフトもその手法を少しだけ利用させていただいている

NEKO_BENTOメインプログラム For CCSC Compiler
/**********************************************
   TITLE: NEKO BENTO

   FILENAME: neko_bento.c
   TARGET DEVICE: PIC16F88, 10MHz X'tal
   DATE:  2019.05.11 - 2023.12.14

   VERSION  00 2019.08.04 OLED表示機能
   VERSION  01 2019.09.19 FOR NEW PCB(Fusion PCB)
   VERSION  02 2019 11.02 TIME CLOCK ADJUST
   VERSION  03 2022 01.02 起動時黄色ボタン押していたら時刻入力をスキップ

   AUTHOR: Yuichi
***********************************************/

#include <16f88.h>
#use delay(clock=10000000)

#FUSES  HS,NOWDT,NOPROTECT,PUT,NOMCLR,NOBROWNOUT,NOLVP
#use i2c(master,sda=PIN_B1,scl=PIN_B4,FAST)
#ignore_warnings 203
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>

#define TIMEADJ 187  //LARGE NUMBERS=FAST CLOCK  SMALL NUMBERS:CLOCK is Delayed
                    //大きい数字は時計が進む、小さい数字は時計が遅れる
#define EEPROM_SAVED 0xcc
// OKNG
#define OK          0
#define NG          1

//24Hを超える(単位は分)、60*24-1=1439 以上は無効とするため
//1440以上であればいいが、切りのいい1500としている
#define INVALID_TIME 1500

// AポートのI/Oビット配置
// 上からLSBで割り当てればよい
// plビットをHIGHにするには PORTA.pl=1 とすればいい
struct porta_pin_map1 {      // This structure is overlayed
    BOOLEAN Infrared_rx;
    BOOLEAN pushsw;
    BOOLEAN left;
    BOOLEAN right;
    BOOLEAN pl;       //pilot lamp
} PORTA;
struct porta_pin_map2 {      // This structure is overlayed
    BOOLEAN reserve2;
    BOOLEAN sda;
    BOOLEAN reserve3;
    BOOLEAN reserve4;
    BOOLEAN scl;
    BOOLEAN reserve5;
} PORTB;

#byte PORTA = 0x05
#byte PORTB = 0x06

struct font_bit{
    BOOLEAN b0;
    BOOLEAN b1;
    BOOLEAN b2;
    BOOLEAN b3;
    BOOLEAN b4;
    BOOLEAN b5;
    BOOLEAN b6;
    BOOLEAN b7;
};

#include "hinagata.h"     // 型宣言
#include "Infrared_rx.h"     // Infrared Rmote cont

//割り込みでカウントアップする変数
int16 g_209ms_count = 0;

// get_4_number()で返される値
struct g_four_digit{  //リモコンボタン4桁で時間入力
  int16   data;  //分
  BOOLEAN timeout;  //timeoutしたらTRUE
} g_four_digit;

// 蓋を開ける時間 0時0分から何分後に蓋を開けるかの設定
// 0-1439 は有効
//   INVALID_TIME は無効
int16 g_cover[2];

// Clock
// このプログラムでは時と分をそれぞれカウントせず、分だけカウントしている
// 0~1439分で24時間
int16   g_minutes=0;  // 分カウント

// cover open flag
BOOLEAN g_open[2] = {FALSE,FALSE};  //蓋が開いたら一分間だけTRUEになる

// OLED光ってるSTATUS
BOOLEAN g_oled = FALSE;
// OLEDをSLEEPにするための時間計測
int16   g_oled_cnt = 0;

// 6*8 bitmap font
// Alphaは大文字のみ
BYTE const font[39][6]={
{0x7C,0xFE,0x82,0xFE,0x7C,0x00}, //0
{0x00,0x04,0xFE,0xFE,0x00,0x00}, //1
{0xE4,0xF2,0xB2,0xBE,0x9C,0x00}, //2
{0x44,0xC6,0x92,0xFE,0x6C,0x00}, //3
{0x1C,0x12,0xFE,0xFE,0x10,0x00}, //4
{0x4E,0xCE,0x8A,0xFA,0x72,0x00}, //5
{0x7C,0xFE,0x8A,0xFA,0x70,0x00}, //6
{0x00,0x02,0xF2,0xFE,0x0E,0x00}, //7
{0x6C,0x9E,0xBA,0xF2,0x6C,0x00}, //8
{0x1C,0xBE,0xA2,0xFE,0x7C,0x00}, //9
{0xC0,0x72,0x4E,0x7E,0xF8,0xE0}, //A 10
{0xFE,0xFE,0x92,0x92,0xFE,0x6C}, //B
{0x7C,0xFE,0x82,0x82,0xEE,0x6C}, //C
{0xFE,0xFE,0x82,0xC6,0x7C,0x38}, //D
{0xFE,0xFE,0x92,0x92,0x92,0x00}, //E
{0xFE,0xFE,0x12,0x12,0x12,0x00}, //F
{0x7C,0xFE,0x82,0xD2,0x76,0xF4}, //G
{0xFE,0xFE,0x10,0x10,0xFE,0xFE}, //H
{0x00,0x00,0xFE,0xFE,0x00,0x00}, //I
{0x60,0xE0,0x82,0xFE,0x7E,0x00}, //J
{0xFE,0xFE,0x38,0xF4,0xE2,0x00}, //K 20
{0xFE,0xFE,0x80,0x80,0xC0,0x00}, //L
{0xFE,0x1E,0x78,0xE0,0x18,0xFE}, //M
{0xFE,0x1C,0x38,0x70,0xFE,0x00}, //N
{0x7C,0xFE,0x82,0x82,0xFE,0x7C}, //O
{0xFE,0xFE,0x22,0x22,0x3E,0x1C}, //P
{0x3C,0x7E,0x42,0xE2,0xFE,0xBC}, //Q
{0xFE,0xFE,0x12,0x32,0xFE,0xDC}, //R
{0x4C,0x9E,0xBA,0xF2,0x64,0x00}, //S
{0x02,0x02,0xFE,0xFE,0x02,0x02}, //T
{0x7E,0xFE,0xC0,0x80,0x7E,0x00}, //U 30
{0x0E,0x3E,0xF0,0xC0,0x70,0x0E}, //V
{0x1E,0xFE,0xE0,0x7C,0xE0,0x3E}, //W
{0x8E,0x7E,0x38,0xFC,0xE2,0x00}, //X
{0x06,0x1E,0xF8,0xF0,0x18,0x06}, //Y
{0x82,0xE2,0xFA,0xBE,0x8E,0x82}, //Z
{0x00,0x00,0x24,0x24,0x00,0x00}, //:
{0x0C,0x0E,0xA2,0xB2,0x1E,0x0C}, //?
{0x00,0x10,0x10,0x10,0x10,0x00}, //-
};

// OLED PRINT POSITION
int oled_x=0;
int oled_y=0;

/*---------------------------------------------
   Timer interrupt interval 209ms
   0.4us*8*(65535-7)=209ms
---------------------------------------------*/
#INT_TIMER1
void watch_handler( void )
{
    // set_timer1(0)だとフルカウントする。0->FFFF->0 で割り込み発生
    // set_timer1(12)だと12少ないカウント、12->FFFF->0 で割り込み発生
    set_timer1( TIMEADJ ); // 65535-11
    ++g_209ms_count; // 209msごとにカウントアップ
    if(g_209ms_count > 286 ){ // 209ms*286=60sec
       g_209ms_count = 0;
       g_open[0]=g_open[1]=FALSE; // Cover open flag clear
       ++g_minutes;
       if( g_minutes > 1439 ){  // 24Hour
          g_minutes = 0;
       }
    }
}

/*---------------------------------------------
   main module
---------------------------------------------*/
void main( void ){
    int32 scode;
    char title[20];
    BOOLEAN select_time = 0;
    sprintf( title , "\fNEKO\nBENTO %s", "03"); //VERSION HERE
    set_tris_a( 0x03 );     // BIT0(IR) BIT1(SW) IS IN
    set_tris_b( 0x12 );     // SDA, SCL is INPUT
    PORTA = 0;
    PORTB = 0;
    if( read_eeprom( 0 ) != EEPROM_SAVED ){ // Never save
        g_cover[0] = INVALID_TIME; //Left
        g_cover[1] = INVALID_TIME; //Right
    }else{
        g_cover[0] = make16( read_eeprom(2), read_eeprom(1) );
        g_cover[1] = make16( read_eeprom(4), read_eeprom(3) );
    }
    delay_ms(300);
    oled_init();
    oled_resume( TRUE ); //NO SLEEP
    printf( oled_putc, "%s", title );
    delay_ms( 1000 );
    // If not Push Yellow Double Switchs
    if( PORTA.pushsw != 0 ){
        printf( oled_putc, "\f SET TIME\n ? " );
        sets_clock();
    }
    //delay_ms( 1000 );
    printf( oled_putc, "\f START");
    setup_timer_1(T1_INTERNAL | T1_DIV_BY_8 ); // 8bit max 3.2us
    PORTA.pl = 1; //START NOTICE
    set_timer1( TIMEADJ );
    enable_interrupts( INT_TIMER1 );
    enable_interrupts( GLOBAL );
    while(1){
        // このループは約2msぐらい
        scode = Infrared_read();
        ++g_oled_cnt;  //時間計測
        // IOのアサート
        if( scode != 0 ){
            if( scode == PS2_L1 ){ // LEFT COVER [L1]
                oled_resume( TRUE );
                printf( oled_putc, "\f L OPEN" );
                open_bento( 0 );
            }else if( scode == PS2_R1 ){ // RIGHT COVER [R1]
                oled_resume( TRUE );
                printf( oled_putc, "\f R OPEN" );
                open_bento( 1 );
            }else if ( scode == PS2_ENTER ){ // 両方開ける[ENTER]
                oled_resume( TRUE );
                printf( oled_putc, "\fALL OPEN" );
                open_bento( 0 );
                open_bento( 1 );
            }else if( scode == PS2_L3 ){ // set left time [L3]
                oled_resume( TRUE );
                printf( oled_putc, "\f L OPEN\n ? " );
                get_4_number();
                if( g_four_digit.timeout == FALSE ){
                    g_cover[0] = g_four_digit.data;
                    save_data();
                    printf( oled_putc, "\f LEFT\n  SAVED");
                }
            }else if( scode == PS2_R3 ){ // set right time [R3]
                oled_resume( TRUE );
                printf( oled_putc, "\f R OPEN\n ? " );
                get_4_number();
                if( g_four_digit.timeout == FALSE  ){
                    g_cover[1] = g_four_digit.data;
                    save_data();
                    printf( oled_putc, "\f RIGHT\n  SAVED");
                }
            }else if( scode == PS2_TIME ){ // セットしたLR蓋が開く時刻表示と現在時刻表示
                oled_resume( TRUE );
                select_time ^= 1;
                if( select_time == 1 ){
                    printf( oled_putc, "\f L " );
                    if( g_cover[0] == INVALID_TIME ){
                        printf( oled_putc, "--:--\n" );
                    }else{
                        printf( oled_putc, "%02ld:%02ld\n", g_cover[0]/60, g_cover[0]%60 );
                    }
                    printf( oled_putc, " R " );
                    if( g_cover[1] == INVALID_TIME ){
                        printf( oled_putc, "--:--" );
                    }else{
                        printf( oled_putc, "%02ld:%02ld",  g_cover[1]/60, g_cover[1]%60 );
                    }
                }else{
                    printf( oled_putc, "\fCUR TIME\n %02ld:%02ld", g_minutes/60 , g_minutes%60 );
                }
            }else if( scode == PS2_AUDIO ){ // 現在時刻を設定する [2] [0] [5] [2] -> 20時52分
                oled_resume( TRUE );
                printf( oled_putc, "\f SET TIME\n ? " );
                sets_clock();
                printf( oled_putc, "\f START");
            }else if( scode == PS2_PROGRAM ){ // Time Adjust 3時00分にセットする
                                           // スマート家電リモコンから定期的にコマンドが来る
                g_209ms_count=47;
                g_minutes = 180;
            }else if( scode == PS2_TITLE ){ // DISPLAY VERSION
                oled_resume( TRUE );
                printf( oled_putc, "%s", title );
            }
            delay_ms( 300 );
        }//if
        // TimerでLEFT蓋を開ける
        if( g_minutes == g_cover[0] && g_open[0] == FALSE ){
            open_bento( 0 );
            g_open[0] = TRUE;  //蓋が開いているフラグは一分後にタイマー割り込みでリセットされる
        }
        // TimerでRIGHT蓋を開ける
        if( g_minutes == g_cover[1] && g_open[1] == FALSE ){
            open_bento( 1 );
            g_open[1] = TRUE;
        }
        // リモコンが押されなくなって一定時間経過したら
        if( g_oled_cnt > 8000 ){
            oled_resume( FALSE );  // SLEEP MODE
        }
        if( PORTA.pushsw == 0 ){
            oled_resume( TRUE );
            printf( oled_putc, "\fALL OPEN" );
            open_bento( 0 );
            open_bento( 1 );
            delay_ms( 3000 );
        }
    }//while
}
/*---------------------------------------------
   SAVE DATA
---------------------------------------------*/
void save_data( void )
{
    disable_interrupts( INT_TIMER1 );
    disable_interrupts( GLOBAL );
    write_eeprom(0,EEPROM_SAVED );
    write_eeprom(1,g_cover[0] & 0xff );
    write_eeprom(2,(g_cover[0] >> 8) & 0xff );
    write_eeprom(3,g_cover[1] & 0xff );
    write_eeprom(4,(g_cover[1] >> 8) & 0xff );
    delay_ms(10);
    enable_interrupts( INT_TIMER1 );
    enable_interrupts( GLOBAL );
}
/*---------------------------------------------
   MANUAL SET CLOCK
---------------------------------------------*/
void sets_clock( void )
{
    get_4_number();
    if( g_four_digit.timeout == FALSE && g_four_digit.data != INVALID_TIME){
        g_minutes = g_four_digit.data;
        g_209ms_count = 0;
    }
}

/*---------------------------------------------
   OPEN BENTO COVER
---------------------------------------------*/
void open_bento( BOOLEAN flag )
{
   if( flag == 0 ){
       PORTA.left = 1;
   }else{
       PORTA.right = 1;
   }
   delay_ms( 300 );    // wait
   PORTA.left = 0;
   PORTA.right = 0;
   delay_ms( 600 );
}
/*---------------------------------------------
   Input 4 botton
   現在時刻設定、蓋を開ける時刻設定で使用される
   From PS2 [0]-[9]
   Return 0-1439, INVALID_TIME
          タイムアウトしたフラグで返す
 --------------------------------------------*/
void get_4_number( void )
{
    long  loop_count=0;  //タイムアウト計測用
    int32 scode; //リモコンコード
    int   digit=0; //桁ポインタ
    long  data[5]; //桁ごとの分データ
    long  num;  //数字ボタンに対応する数値が入る
    int   lutable[11] = {1,2,3,4,5,6,7,8,9,0}; //scodeの4LSBでこのLUTを見る
    long  minutes_data[5] ={600,60,10,1}; //Digitごとの単位
    delay_ms( 300 );
    printf( oled_putc, "----" );  //数字が入力されていない状態表示
    oled_x -= 4;    //表示位置を4文字分戻す
    PORTA.pl = 0;
    while(1){
        ++loop_count;  //この関数のTimeout計測
        scode = Infrared_read();
        if( scode != 0 ){
            if( scode >= PS2_1 && scode <= PS2_0 ){
                num = lutable[scode & 0x0f];  // covert to num
                if( num <= digit_max(digit,num) ){ //入力数値の制限
                    data[digit] = ( num * minutes_data[digit] );
                    printf( oled_putc, "%ld",num );
                    ++digit;  // next digit
                    delay_ms( 300 );
                }
            }
            if( scode == PS2_LEFT ){  //BACK SPACE
                if( digit != 0 ){ // digit 0 以外で動作するように
                    --oled_x;
                    printf( oled_putc, "-" );
                    --oled_x;
                    --digit;
                    delay_ms( 300 );
                }
            }
            // TIMER CANCEL
            // タイマーでNEKO弁当は開けない
            if( scode == PS2_L3 || scode == PS2_R3 ){
               delay_ms( 300 );
               g_four_digit.timeout = FALSE;
               g_four_digit.data = INVALID_TIME; //INVALID_TIME is Invalid
               PORTA.pl = 1;
               return;
            }
            loop_count=0;
        }
        // Sets Success
        // 4桁入力されたらこの関数を終える
        if( digit == 4 ){
            g_four_digit.timeout = FALSE;
            g_four_digit.data = data[0]+data[1]+data[2]+data[3];  //最終データ、分データ
            PORTA.pl = 1;
            return;
        }
        // PL blink while RUN this function
        if( loop_count > 77 ){
            PORTA.pl ^= 1;
            loop_count = 0;
        }
#if 0
        // time out 10sec
        if( loop_count > 2307 ){  // 2307 maybe 6s
            g_four_digit.timeout = TRUE;
            return;
        }
#endif
    }
}
/*---------------------------------------------
 Digit MAX
 時刻を4桁入力する際にそれぞれの桁の最大値を返す
 24:00が最大なので、最初の桁は最大が[2]である
 最初の桁に[2]を入力した場合は次の桁の最大とは[3]となる
 最初の桁に[1]を入力した場合は次の桁の最大とは[9]となる
---------------------------------------------*/
int digit_max( int digit, int num )
{
    int max;
    static int digit0num;
    switch( digit ){
      case 0:
         max = 2;
         digit0num = num;
         break;
      case 1:
         max = (digit0num == 2) ? 3 : 9;  // digit0によって最大値が変わる
         break;
      case 2:
         max = 5;  //Minutesの10の桁は最大5
         break;
      case 3:
         max = 9;  //Minutesの1の桁は最大9
         break;
      default:
         break;
    }
    return( max );
}

/*---------------------------------------------
 文字列表示する
 CCSCの特殊な使い方をする printf( vfd_putc, *fmt, hikisuu )
 というようにprintfで使う
 data; 文字
---------------------------------------------*/
void oled_putc( char c )
{
    int array;
    if( c == '\n' ){ //改行
        oled_x = 0;
        oled_y = 1;
        return;
    }
    if( c == '\f' ){ //画面クリア
        oled_clr(0);
        return;
    }
    if( isdigit(c) ){
        array = c - '0'; //数字だったら(0-9に変換)
    }else if( isalpha(c) ){
        array = toupper(c) - 55;
    }else if( c == ':'){
        array = 36;
    }else if( c == '?' ){
        array = 37;
    }else if( c == '-' ){
        array = 38;
    }else{
        array = 50; // Invalid char is SPACE
    }
    char1216( array, oled_x, oled_y );
    ++oled_x;  //次回のためにx表示位置変更
    if( oled_x > 8 ){
        oled_x = 0; //次回のためにx表示位置変更
        oled_y ^= 1; //次回のためにy表示位置変更
                     //yは0と1のみなので反転演算
    }
}

/* ----------------------------------------------------------------
    OLEDの初期化
---------------------------------------------------------------- */
void oled_init(void)
{
   i2c_start();
   i2c_write(0x78); // OLED slave address
   i2c_write(0x00); // Control byte Co=0, D/C#=0
   i2c_write(0x8d); // disable charge pump
   i2c_write(0x10);
   delay_ms(1);
   i2c_write(0xa8); //SET MUX RATIO
   i2c_write(0x3f);
   i2c_write(0xd3); //SET DISPLAY OFFSET
   i2c_write(0x00);
   i2c_write(0x40); //SET DISPLAY START LINE
#if 0
   i2c_write(0xa0); //SET SEGMENT RE-MAP
   i2c_write(0xc0); //SET COM OUTPUT SCAN DIRECTION
   i2c_write(0xda); //SET COM PINS HARDWARE CONFIGURATION
   i2c_write(0x02);
#else
   i2c_write(0xa1); //SET SEGMENT RE-MAP
   i2c_write(0xc8); //SET COM OUTPUT SCAN DIRECTION
   i2c_write(0xda); //SET COM PINS HARDWARE CONFIGURATION
   i2c_write(0x22);
#endif
   i2c_write(0x81); //SET CONTRAST CONTROL
   i2c_write(0x3f);    // MAX 7F
   i2c_write(0xa4); //DISABLE ENTRE DISPLAY ON
   i2c_write(0xa6); //SET NORMAL DISPLAY
   i2c_write(0xd5); //SET OSC FREQUENCY
   i2c_write(0x80);
   i2c_write(0x8d); //ENSBLE CHSRGE PUMP REGULATOR
   i2c_write(0x14);
   i2c_write(0xaf); //DISPLAY ON
   i2c_stop();
}
/* ----------------------------------------------------------------
    OLEDの画面クリア

      data : 埋めるデータ
---------------------------------------------------------------- */
void oled_clr(int data) // OLED 画面消去
{
   int32 i;
   oled_x = oled_y = 0;
   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);
   for(i=0; i<512; i++){ // 128COL * 4page
     i2c_write(data); // Out data
   }
   i2c_stop();
}

/* ----------------------------------------------------------------
    OLED resume
      flag:
        true is NORMAL MODE
        false is SLEEP
---------------------------------------------------------------- */
void oled_resume( int flag)
{
    if( flag != g_oled ){
        i2c_start();
        i2c_write(0x78); // OLED slave address
        i2c_write(0x00); // Control byte Co=0, D/C#=0
        if( flag == false ){
            i2c_write( 0xae ); // SLEEP
        }else{
            i2c_write( 0xaf ); // DISPLAY ON
        }
        i2c_stop();
        g_oled = flag;
    }
    g_oled_cnt = 0;
}
/* ----------------------------------------------------------------
    OLED SETS PRINT POSITION
---------------------------------------------------------------- */
void oled_gotoxy( int x, int y)
{
    oled_x = x;
    oled_y = y;
}
/* ----------------------------------------------------------------
     OLEDに一文字出力
       font_code:
          配列を指定(0~)
       cx:
          出力水平位置(0~8)
       cy:
          出力垂直位置(0~1)
---------------------------------------------------------------- */
void char1216( int font_code, int cx, int cy ) // 12x16dot FONT
{
    int x;
    struct font_bit FONT_BIT1;  //before 8bit font
    struct font_bit FONT_BIT2;  //after extent 8bit font
    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(0x22); // Set Page Address
    i2c_write(cy*2); // Page Start
    i2c_write(cy*2+1); // Page End
    i2c_write(0x21);  // Set Column Address
    i2c_write(cx*13);  // Start Address(0-9)
    i2c_write(cx*13+11);  // End Address
    i2c_stop();
    i2c_start();
    i2c_write(0x78); // OLED slave address
    i2c_write(0x40);

    // 6*8 FONTを2倍にする処理、粗い文字となるがFONTデータが少量ですむ
    // MSBはOLED画面下方向
 
    // 1st page
    // FONT_BIT1.b0 -> FONT_BIT2_b0
    // FONT_BIT1.b0 -> FONT_BIT2_b1
    // FONT_BIT1.b1 -> FONT_BIT2_b2
    // FONT_BIT1.b1 -> FONT_BIT2_b3
    // FONT_BIT1.b2 -> FONT_BIT2_b4
    // FONT_BIT1.b2 -> FONT_BIT2_b5
    // FONT_BIT1.b3 -> FONT_BIT2_b6
    // FONT_BIT1.b3 -> FONT_BIT2_b7

    // next page
    // FONT_BIT1.b4 -> FONT_BIT2_b0
    // FONT_BIT1.b4 -> FONT_BIT2_b1
    // FONT_BIT1.b5 -> FONT_BIT2_b2
    // FONT_BIT1.b5 -> FONT_BIT2_b3
    // FONT_BIT1.b6 -> FONT_BIT2_b4
    // FONT_BIT1.b6 -> FONT_BIT2_b5
    // FONT_BIT1.b7 -> FONT_BIT2_b6
    // FONT_BIT1.b7 -> FONT_BIT2_b7

    if( font_code > 49 ){  // SPACE(BLANK) Draw
        for( x=0 ; x<24 ; ++x ){
            i2c_write( 0 );
        }
    }else{
        for( x=0 ; x<6 ; ++x ){
            FONT_BIT1 = font[font_code][x];
            //FONT_BIT1からFONT_BIT2へBITを振り分ける
            FONT_BIT2.b0 = FONT_BIT2.b1 = FONT_BIT1.b0;
            FONT_BIT2.b2 = FONT_BIT2.b3 = FONT_BIT1.b1;
            FONT_BIT2.b4 = FONT_BIT2.b5 = FONT_BIT1.b2;
            FONT_BIT2.b6 = FONT_BIT2.b7 = FONT_BIT1.b3;
            i2c_write( (int)FONT_BIT2 ); //横方向に同FONTを2 DOT
            i2c_write( (int)FONT_BIT2 ); //これで2 DOT分
        }
        for( x=0 ; x<6 ; ++x ){
            FONT_BIT1 = font[font_code][x];
            //FONT_BIT1からFONT_BIT2へBITを振り分ける
            FONT_BIT2.b0 = FONT_BIT2.b1 = FONT_BIT1.b4;
            FONT_BIT2.b2 = FONT_BIT2.b3 = FONT_BIT1.b5;
            FONT_BIT2.b4 = FONT_BIT2.b5 = FONT_BIT1.b6;
            FONT_BIT2.b6 = FONT_BIT2.b7 = FONT_BIT1.b7;
            i2c_write( (int)FONT_BIT2 );
            i2c_write( (int)FONT_BIT2 );
        }
    }
   i2c_stop();
}
//EOF


赤外線受信サブルーチン For CCSC Compiler
////////////////////////////////////////////////////////////////
//
// Infrared_X.H 
// 赤外線リモコン受信プログラム for ソニー
//
// AUTHOR: Yuichi
//
// NOTE:
//    (1)構造体の宣言例
//      struct porta_pin_map {      // This structure is overlayed
//        BOOLEAN unused1;
//        BOOLEAN Infrared_rx;
//        BOOLEAN sw;
//        BOOLEAN beep;
//        BOOLEAN Infrared_tx;
//        int     unused2 : 3;
//      }PORTA;
//    (2)1つのタイマーを使用
//      setup_timer_0(RTCC_INTERNAL | RTCC_DIV_128 );//12.8us long
//
// FUNCTION:
//   int32 Infrared_read( void );
//   int32 ep_Infrared_read20bit( void );
//   int   ep_Infrared_measure_pulse( void );
//   int   ep_Infrared_measure_leader( void );
//   int   ep_Infrared_measure_hilow( void );
//
////////////////////////////////////////////////////////////////

// Infrared BIT LEVEL
#define Infrared_LOW      0
#define Infrared_HI       1
#define Infrared_LEADER   2
#define Infrared_OUT      3

#define TV_BLUE   0x0f004ba4
#define TV_RED    0x0f004ba5
#define TV_GREEN  0x0f004ba6
#define TV_YELLOW 0x0f004ba7
#define TV_DATA   0x0f004b95
#define TV_TENKEY 0x0f004b8c
#define TV_CH1    0x0c000080
#define TV_CH2    0x0c000081
#define TV_CH3    0x0c000082
#define TV_CH4    0x0c000083
#define TV_CH5    0x0c000084
#define TV_CH6    0x0c000085
#define TV_CH7    0x0c000086
#define TV_CH8    0x0c000087
#define TV_CH9    0x0c000088
#define TV_CH10   0x0c000089
#define TV_CH11   0x0c00008a
#define TV_CH12   0x0c00008b

#define PS2_1     0x14049D00  //Play Station2 リモコン
#define PS2_2     0x14049D01
#define PS2_3     0x14049D02
#define PS2_4     0x14049D03
#define PS2_5     0x14049D04
#define PS2_6     0x14049D05
#define PS2_7     0x14049D06
#define PS2_8     0x14049D07
#define PS2_9     0x14049D08
#define PS2_0     0x14049D09
#define PS2_L1    0x140DAD5A
#define PS2_L2    0x140DAD58
#define PS2_L3    0x140DAD51
#define PS2_R1    0x140DAD5B
#define PS2_R2    0x140DAD59
#define PS2_R3    0x140DAD52
#define PS2_DISPLAY 0x14049D54
#define PS2_MARU    0x140DAD5D //RT
#define PS2_PEKE    0x140DAD5E //RB
#define PS2_SANKAKU 0x140DAD5C //LT
#define PS2_SIKAKU  0x140DAD5F //LB
#define PS2_TIME    0x14049D28
#define PS2_UP      0x14049D79
#define PS2_DOWN    0x14049D7A
#define PS2_LEFT    0x14049D7B
#define PS2_RIGHT   0x14049D7C
#define PS2_START   0x140DAD53
#define PS2_CLEAR   0x14049D0F
#define PS2_ENTER   0x14049D0B
#define PS2_AUDIO   0x14049D64
#define PS2_PROGRAM 0x14049D1F
#define PS2_TITLE   0x14049D1A

/*---------------------------------------------
   Infraredを受信して32bitデーターを返す
   リピート時間制御:繰り返し時間よりも早くこの関数を
   実行すれば即returnする
---------------------------------------------*/
int32 Infrared_read( void )
{
    int32 code;
    if( ep_Infrared_measure_leader() != Infrared_LEADER ){  // リーダー検出
        return(0);
    }
    code = ep_Infrared_read20bit();     // 12-20ビット入力
    return( code );
}

/*---------------------------------------------
  Infraredを受信して32bitデーターを返す
   必ず、ヘッダー受信の後に実行するようにプログラムしなければならない
   global変数 g_Infrared_bit_lengを使う
   RETURN:
     32bit data
     0C 00 00 95  <- POWER ON(12bit)  
     0F 00 4B CA  <- 決定(15bit)
     14 04 9D 32  <- DVD再生(20bit)
     32ビットの上位8ビットはInfraredのビット長を示し、下位24ビットはコード
---------------------------------------------*/
int32 ep_Infrared_read20bit( void )
{
    int   a_cnt, a_data;
    int32 a_Infrared_bit_leng = 0;
    int32 a_code;
    int32 a_shift;
    a_code = 0;
    a_shift = 1;
    a_Infrared_bit_leng = 0;
    for( a_cnt=0 ; a_cnt<20 ; ++a_cnt ){
        a_data = ep_Infrared_measure_hilow();        
        if( a_data == Infrared_OUT ){
            break;  // END
        }
        if( a_data == Infrared_HI ){
            a_code |= a_shift;
        }
        a_shift <<= 1;
        ++a_Infrared_bit_leng;
   }
   // BIT長が12,15,20のいずれかであれば有効とする
   if( a_Infrared_bit_leng == 12 ||
       a_Infrared_bit_leng == 15 ||
       a_Infrared_bit_leng == 20 ){
           a_code |= (a_Infrared_bit_leng << 24);
   }else{
           a_code = 0;
   }
   return( a_code );
}

/*---------------------------------------------
  InfraredのLEADERパルスを検出する
   LEADERパルスは負の2400us幅である。立下りエッジから300us後
   がLOW,600us後がLOW,600us後がLOW,600us後がLOW,600us後がHI
   であればLEADERと判断する。

  "0"     ~~~~|_|~~~~~~~~~~~~~~~~~~~~~~~ width=600us
  "1"     ~~~~|____|~~~~~~~~~~~~~~~~~~~~ width=1200us
  LEADER  ~~~~|__________|~~~~~~~~~~~~~~ width=2400us
---------------------------------------------*/
int   ep_Infrared_measure_leader( void )
{
    int16 uscnt=0;
    if( !PORTA.Infrared_rx ){
        return( Infrared_OUT );
    }
    do{
        if( uscnt > 400 ){         // uscnt400は約800us
            return( Infrared_OUT );   // 800us経過してもLOWにならない
        }
        delay_us(1);
        ++uscnt;
    }while( PORTA.Infrared_rx );        // LOWになったか
    delay_us(300);
    if( PORTA.Infrared_rx == 0 ){
        delay_us(600);
        if( PORTA.Infrared_rx == 0 ){
            delay_us(600);
            if( PORTA.Infrared_rx == 0 ){
                delay_us(600);
                if( PORTA.Infrared_rx == 0 ){
                    delay_us(600);
                    if( PORTA.Infrared_rx == 1 ){
                        // ここまでくると本当のLEADERパルス
                        return( Infrared_LEADER );
                    }
                }
            }
        }
    }
    return( Infrared_OUT );
}
/*---------------------------------------------
  InfraredのHI又はLOWパルスを検出する
   必ずLEADERパルスの直後から呼び出して検出すること
   立下りエッジから900us後にHIであれば"0"パルス、LOWであれば
   "1"パルスと判断する。

  "0"     ~~~~|_|~~~~~~~~~~~~~~~~~~~~~~~ width=600us
  "1"     ~~~~|____|~~~~~~~~~~~~~~~~~~~~ width=1200us
  LEADER  ~~~~|__________|~~~~~~~~~~~~~~ width=2400us
---------------------------------------------*/
int   ep_Infrared_measure_hilow( void )
{
    int  level;
    int16 uscnt=0;
    do{
        if( uscnt > 400 ){         // uscnt400は約800us
            return( Infrared_OUT );   // 800us経過してもLOWにならない
        }
        delay_us(1);
        ++uscnt;
    }while( PORTA.Infrared_rx );        // LOWになったか
    delay_us(900);                   // LOWのなった瞬間から900us後に
    level = PORTA.Infrared_rx;          // HI/LOWを読み込む
    while( !PORTA.Infrared_rx );        // HIになるまでここで待つ
    if( level ){
        return( Infrared_LOW );
    }else{
        return( Infrared_HI );
    }
}

//EOF


ありがとうございますm(__)m For アランドロン
ゆういちの上司はアランドロンの髪型と似てるだけじゃなかったけ?
おわり

過去の記事:
猫用自動給餌器をリモコン制御に改造
猫用自動給餌器をリモコン制御に改造、その2
猫用自動給餌器をリモコン制御に改造、その3


人気ブログランキング
nice!(10)  コメント(0) 

nice! 10

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。