Vol.891 29.Mar.2024

眼鏡の修理 ポータブル投光器 PICミニBB(2)_printf関数を使わない

G DIYで眼鏡の修理

by fjk

 老眼鏡(2,980円)の蝶番のネジがいつのまにか外れ、テンプル(柄)がとれてしまった。「買い換え」かなと諦めていたが、蝶番以外の部品は問題ないので、蝶番に使えるネジがないかと探したところ、M1.4のネジが使えることが判り、DIYショップでネジセットを入手(SUS、複数の長さサイズ入りで300円程度)。
 適当な長さのネジを蝶番としてはめてみると、ピッタリ治まり、ダブル・ナットで固定。さらに、ナットの緩み止めに瞬間接着剤で補強し、修理が完了。
 今回はSUSのネジを使ったが、百均の眼鏡を買って、分解し、パーツとすれば、もっと安くできそう。 。


修理した眼鏡(蝶番)


L 充電式COB(Chip_On_Boad)ポータブル投光器

by fjk

 ソースネクストで「COBポータブル投光器(2個セット)」を3,280円(23%引き)で購入した。小型の手持ちライトは持っていないわけではないが、いずれも電池式で、充電式ではない(充電式も持っていたのだが充電できなくなった)。ソーラー充電も出来るので災害時でも使えそう。
 ポータブル投光器は、@USB充電、Aソーラーパネル(充電)付き、B据え置き/吊り下げ、C内側・外側の2つの点灯パターン(同時点灯は不可)で、COBは広範囲にムラ無く照明できるのが特徴。

【主な仕様】

スポット(上)、広域(下)
 
ライト裏面にはソーラー


P PICミニBBシリーズ(2)   〜printf関数を使わない

by fjk

 XC8コンパイラで、数値を文字列に変換する(s)printf関数を使うことが多いが、(s)printf関数はメモリを多く使うことで有名である。例えば、前報(abc890)のスレーブプログラムで、(ROM/RAM)

(1)sprintfを使った場合(2,839/234)
(2)sprintfを使用しないと(1,364/114)

 すなわち、ROMで1,475バイト、RAMで120バイトが、(s)printf文を使うことで増えている。
 また、C90コンパイラでは、itoa()などの文字列変換関数が使えたが、C99ではサポートされていない。 そこで、(s)printf関数を使いたくない場合、以下の2つの方法がある

  1. C99ではなくC90コンパイラを使い、itoaなどの関数を使う。
  2. 自作文字列変換関数を使う。

1.C90コンパイラを使う方法
 C90コンパイラを使うには、Projectを右クリックし、propertiesを選び、XC8 Global OptionのC standerdをC90に設定し、itoa関数などを使う。


C90の設定画面

 なお、C99で強化された主なものは、

  • bool型(trueかfalse)の追加
  • 実行時にサイズを決定可能な配列の作成
  • ブロックの先頭だけでなく、任意位置で変数を宣言
  • 構造体や配列をリテラルとして生成
  • bit幅が規定された整数型が追加(stdint.h)
  • 書き込み先のバッファーのサイズを指定(stdio.h)

 C99はハードウェア毎に異なる部分をなるべく少なくするため(intのビット幅指定)と、セキュリティの強化が図られた。PICでは、C99の浮動小数点は32ビットと固定だがC90では24ビットも選択でき、C99で(バッファーオーバーランエラーを防止するため)使えなくなったいくつかのライブラリ関数(itoa,ltoa,utoaなど)も利用可能となる(printf関数は残されたが)。8ビットPICでは、主に8ビットデータを使うことが多く、搭載メモリも少ないので、C90がよく用いられている。
※参 考:後閑哲也(技術評論社)など

2.自作文字列変換関数を使う方法
 C99のまま、printf関数に置き換わる文字列変換関数(負号無し16ビット)を独自に作成。
 前報(abc890)のキーユニットで、PIC16F18325でコンパイルしてみると、独自関数使用例ではプログラムが2Kバイト未満となったので、PIC16F18323(ROM:2kbyte)に置き換えが可能となった


PIC16F18325でprintfを使用した場合

PIC16F18325で独自関数を使用した場合
 
PIC16F18323で独自関数を使用した場合
【独自作成関数】
・16進数(負号無し)文字列変換
void my_utoa(uint8_t *str, uint16_t wd, uint8_t dg)
*str: 変換後に格納される文字列のポインタ
wd: 変換したい負号無し16ビット整数
dg: 変換した16進数の桁数
   (指定桁数が少ない場合、上位の数値は無視)
 
・10進数(負号無し)文字列変換
void my_itoa(uint8_t *str, uint16_t wd)
*str: 変換後に格納される文字列のポインタ
wd: 変換したい負号無し16ビット整数
 
・文字列を逆順に入替
void reverseString(char *str)
*str: 逆順に入れ替えたい文字列
/**************************************
 *   文字列を逆順に入替  
 **************************************/
#include "string.h"

void reverseString(char *str) {
    int length = strlen(str);
    for (int i = 0; i < length / 2; i++) {
        char temp = str[i];
        str[i] = str[length - 1 - i];
        str[length - 1 - i] = temp;
    }
}

/**************************************
 *   10進数文字列変換  uint16 -> str(Dec)
 **************************************/
void my_itoa2(char *str, uint16_t wd){
	for(int i = 0; i < 6; i++){
	  *str++ = '0' + (wd % 10);
	  wd = wd / 10;
	  if(wd == 0)	break;
	}
	*str = '\0';
}

void my_itoa(char *str, uint16_t wd){
	my_itoa2(str, wd);
	reverseString(str);
}
※16進数文字列変換関数はslaveプログラムを参照 10進数値文字列変換の例(今回未使用)

 今回試作したものは、前報(abc890)で製作した物はそのまま残し、全ての回路を新たに作成した。回路および配線はabc890と同じだが、プッシュキーには「ボタン付きタクトスイッチ」(秋月電子、103654、@100円×12個)を使用し、自作した電話機配列の数値シール(A-one_26101)をトップに貼り付けた。
 PIC16F18323を利用した4x3キーユニットで、キー入力と通信テストを行ったところ、LCD画面およびパソコンのテラターム画面に、入力データが正常に表示されることを確認できた。
 なお、キーユニットからUSB送信時に、16進数変換を利用しているが、printf文の4桁16進文字出力とほぼ同じ機能を持つprintx16()関数を作成し、printf文は(コメントとして)使用していない。
 また、キーデータkeyPは 1〜12 から 0〜11 に変更した(bitCount関数でfor文の条件を変更)


I2C接続キーユニット(PIC16F18323)接続例
(ボタン付きスイッチにシールを貼り付け)
(各ユニットはabc890と同じものを再度作成)

USB接続テラターム(windows10)画面例
(画面上:キーユニットUSB接続時例)
(画面下:Base14からUSB接続時例)

★プログラム abc891-18323-slave.c(zip)
/***************************************
 *  keyデータ送信(I2Cスレーブ)
 *  Slave Address = 0x40
 **************************************/
#include "mcc_generated_files/mcc.h"

#define	SCN_DELAY() __delay_ms(30)
#define	KEY_DELAY() __delay_ms(500)

// 変数定義
uint8_t  wState;                // I2C書込みステート番号
uint8_t  rState;                // I2C読込ステート番号
uint16_t rDat;                  // I2C受信16ビットデータ
uint8_t  keyStat;               // キー入力状態
uint16_t keyM;                  // キーマトリクスデータ
uint8_t  keyP;                  // キーデータ(0〜11)
uint16_t keyDat;                // マトリクス+データ
uint8_t  iFlg;                  // I2C受信フラグ
uint8_t  sBuf[8];               // 文字列変換用バッファ
/*
uint8_t  k[] = {'0','1','4','7',
                '.','2','5','8',
               '\r','3','6','9'}; // テンキー配列
*/
uint8_t  k[] = {'*','7','4','1',
                '0','8','5','2',
                '#','9','6','3'}; // 電話機配列

/********************************
 * I2C Read Callback 
 ********************************/
void I2C1_ReadProcess(void) {
    switch (rState) {           // ステートで進む
        case 0:
            rDat = (uint16_t)I2C1_Read();
            rState++;           // 1byte目読込、次へ
            break;
        case 1:
            rDat |= (uint16_t)I2C1_Read() << 8;
            iFlg = 1;
            rState = 0;         // 2byte目読込、最初に
            break;
        default:
            break;
    }
}

/********************************
 * I2C Write Callback 
 ********************************/
void I2C1_WriteProcess(void) {
    switch (wState) {
        case 0:
            I2C1_Write((uint8_t)keyDat);
            wState++;           // keyDatの下位送信、次へ
            break;
        case 1:
            I2C1_Write(keyDat >> 8);
            wState++;           // keyDatの上位送信、次へ
            break;
        case 2:                 // 送信完了
            wState = 0;         // 最初に戻る
            break;
        default:
            break;
    }
}

/*******************************
 * キースキャン
 *******************************/
uint16_t keyScan(){
    uint8_t kt;
    uint16_t kyw;
    X_A0_SetLow();              // X scan
    SCN_DELAY();
    kyw = ~PORTC & 0x0F;
    X_A0_SetHigh();
    Y_A1_SetLow();              // Y scan
    SCN_DELAY();
    kt = ~PORTC & 0x0F;
    kyw |= (uint16_t)kt << 4;
    Y_A1_SetHigh();
    Z_A2_SetLow();              // Z scan
    SCN_DELAY();
    kt = ~PORTC & 0x0F;
    kyw |= (uint16_t)kt << 8;
    Z_A2_SetHigh();    
    return kyw; 
}

uint8_t bitCount(uint16_t w){
    uint16_t wsft = 1;
    uint8_t i;
    for (i = 0; i < 12; i++){
        if(w & wsft)   return i;
        wsft <<= 1;
    }
    return 0;
}

/****************************************
 *   16進数文字変換  uint16 -> str(Hex)
 ****************************************/
uint8_t my_utoa2(uint16_t n){
    n &= 0x0F;
    if(n < 10)  return '0'+ (uint8_t)n;
    else        return 'A'+ ((uint8_t)n - 10);
}

void my_utoa(uint8_t *str, uint16_t wd, uint8_t dg){
    if(dg > 3)  *str++ = my_utoa2(wd >> 12);
    if(dg > 2)  *str++ = my_utoa2(wd >> 8);
    if(dg > 1)  *str++ = my_utoa2(wd >> 4);
    *str++ = my_utoa2(wd);
    *str = '\0';
}

void printx16(uint16_t wd){
    putch('-');
    my_utoa(sBuf,wd,4);
    puts((const char *)sBuf);
    putch('\r');    
}

/*****************************************
 * Main application  
 *****************************************/
void main(void)
{
    SYSTEM_Initialize();

    I2C1_Open();                // I2C1初期化
    //-- Callback関数定義
    I2C1_SlaveSetReadIntHandler(I2C1_ReadProcess);
    I2C1_SlaveSetWriteIntHandler(I2C1_WriteProcess);
    
    //-- 割り込み許可
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();
//    IO_RC5_SetHigh();
 
    /****** メイン関数 *****************/
    X_A0_SetHigh();             // キーピン初期化
    Y_A1_SetHigh();
    Z_A2_SetHigh();
    
    puts("**ready\n\r");
    
    while (1)
    {
        keyM = keyScan();
        if(iFlg){
            printx16(rDat);
//           printf("%4XH\n\r",rDat);
            iFlg = 0;
        }
        if(keyM){
            keyP = bitCount(keyM);
            keyDat = (keyM << 4)+(keyP & 0x0F);
            if(keyStat == 0){
                putch(k[keyP]);
                printx16(keyDat);  // テスト・確認用
//               printf(" %04XH\n\r",keyDat);
            }
            keyStat = 1;
            KEY_DELAY();
        }else{
            keyStat = 0;
        }
    }
}
/*----  End of File  ---*/


※ 本レポートの参考・利用は、あくまでも自己責任でお願いします。


眼鏡の修理 ポータブル投光器 PICミニBB(2)_printf関数を使わない