定番Cプログラミング技法
◆センサー入力のノイズ除去
#define PALG 0x0F // 入出力論理反転=1
volatile unsigned char PAinbo; // PA input buff old @
volatile unsigned char PAinbn; // PA input buff new
volatile unsigned char PAinb; // PA input buff 確定
//定期タイマー割り込みルーチンでの処理
ISR(TIMER0_OVF_vect) //タイマー0 オーバーフロー割込み
{
TCNT0 = TC; //Restart Timer: 4ms
PAinbo = PAinbn; // 前回入力データを旧データにセーブ
PAinbn = PINA; // ポートを新バッファに入力
PAinbn = PAinbn ^ PALG; // 論理整合 A
if (PAinbn == PAinbo)
{ PAinb = PAinbn; } // 旧=新データなら確定inbufに格納
}
センサーからデータを入力する場合は、ノイズを除去するために、1ms程度の定期タイマー割込みでバッファにポートから入力し、データが2回一致したら確定バッファに取り込みます。
注@: 割込みで取り込む外部変数を宣言する際は必ず、不揮発 volatile を宣言します。
注A: ハード的に負論理な入力はEXORを取り、信号名称に対して正論理に修正します。
◆入力ルーチンをmacro定義にする。
比較的短いまとまった手続きは関数の形ではなくマクロ形式にして使うこともできます。
#define ASW 0x01 // @
#define in_asw() (PAinb & ASW) // A
volatile unsigned char PAinb; // B
if( in_asw() ) // C
{
実行文;
}
注@: 入力信号ビットを定義 (ASWはb0に接続されています。)
注A: 入力をマクロ定義 (PAinbにはノイズ除去されたデータが格納されているものとします)
注B: 入力バッファを外部変数として宣言
注C: 実際に判断をする文です。逆論理の場合は if(! in_asw() ) となります。
◆電源投入時、操作スイッチの状態によってTESTを実行する。
操作スイッチ ASW,BSW,CSW,DSW はマクロ定義されるものとします。
ASWとBSWのみがONしていたらTESTを実行します。
if( in_asw() && in_bsw() && !in_csw() && !in_dsw()
)
{ test(); }
◆出力ルーチンの例
下は負論理出力のON/OFFです。
void on_APL(void)
{ PORTB |= APL; } // ビット=1にしてOFF
void off_APL(void)
{ PORTB &= ~APL; } // ビット=0にしてON
◆EEPROM
// EEPROM 1バイト書込み
void EEPROM_write(unsigned int Address, unsigned char Data)
{
while(EECR & (1<<EEPE)); /* 前に行った書込み完了をチェック */
EEAR = Address; /* アドレスおよびデータレジスタのセット */
EEDR = Data;
EECR |= (1<<EEMPE); /* EEMPE(EEPマスターライトイネーブル)に1をセット*/
EECR |= (1<<EEPE); /* EEPEを1にセットして書込みスタート*/
lptm(5); /* timer */
}
// EEPROM 1バイト読み出し
unsigned char EEPROM_read(unsigned int Address)
{
while(EECR & (1<<EEPE)); /* 前に行った書込み完了をチェック */
EEAR = Address; /* アドレスレジスターセット*/
EECR |= (1<<EERE); /* EEREに1をセットして読み込みスタート*/
lptm(5);
return EEDR; /* データを保持してリターン*/
}
// EEPROM 2バイト書込みファンクション
// 例 EEPROM_int_wr(0,1350); 値1350を0番地に書き込み
void EEPROM_int_wr(unsigned int Address, unsigned int Data)
{
unsigned int t;
t = Data >> 8; //下位アドレスにHデータを書き込み
EEPROM_write(Address,t);
Address++;
EEPROM_write(Address,(uint16_t)Data); // 上位アドレスにLデータ書き込み
}
// EEPROM 2バイト読み出しファンクション
// 例 a = EEPROM_int_rd(0); int型変数aに0番地の値を読み出し
unsigned int EEPROM_int_rd(unsigned int Address)
{
unsigned int t;
t = (uint8_t)EEPROM_read(Address); // 下位アドレスのHデータを読み出し
t = t << 8;
Address++;
t |= (uint8_t)EEPROM_read(Address); // 上位アドレスのLデータ読み出し
return t;
}
◆ EEPROM メモリーチェック
// もし消えていたらデフォルト実行
void ck_eeprom(void)
{
if( EEPROM_read(patn_ad) != 0x55 )
{
EEPROM_write( patn_ad , 0x55); // パターン書き込み
ini_mem(); // デフォルト値書き込み
}
}
◆ エラー番号をエラー履歴へ書き込み
void wr_err(unsigned char en)
{
signed char i;
unsigned char d;
for (i = err_max - 2 ; i >= 0 ; i-- ) // 履歴をプッシュ
{
d = EEPROM_read(err_ad + i);
EEPROM_write( err_ad + i + 1, d );
}
EEPROM_write(err_ad,en); // 新しいエラーを書き込み
}
◆ 1msec×n ループタイマー
void lptm(unsigned ms)
{
volatile int i;
while(ms)
{
for (i = 440; i > 0; i--); // 8MHz
ms--;
}
return;
}
◆バイナリデータを16進アスキー文字に変換
/*---- binary(low 4bit) to hex ascii('0'~'F') -----*/
unsigned char bin_hex(unsigned char data)
{
data &= 0x0f;
if(data > 9)
data += 0x37;
else
data += 0x30;
return(data);
}
/*----- charを 指定バッファに2バイト変換"00"〜"FF" -----*/
/* *buf=low *(buf+1)=high
ex. bin_hex2(0xf2,d);
tx0c(&d[0],1);tx0c(&d[1],1);
*/
void bin_hex2(unsigned char data,char *buf)
{
*(buf+1) = bin_hex(data);
*buf = bin_hex(data/16);
}
/*-----intを指定バッファに4バイト変換"0000"〜"FFFF" -----*/
/* *buf=low ,, *(buf+3)=high
ex. int_hex4(0xf2c3,d);
tx0c(&d[0],1); ,, tx0c(&d[3],1);
*/
void int_hex4(unsigned int data,char *buf)
{
*(buf+3) = bin_hex(data);
*(buf+2) = bin_hex(data/16);
*(buf+1) = bin_hex(data/256);
*buf = bin_hex(data/4096);
}
◆バイナリデータを10進文字に変換
/*----char値を指定するバッファに10進3桁数に変換 "0"〜"255"ゼロサプレス ----*/
/*
ex. static char d[3];
char_deci(120, d);
*/
void char_deci(unsigned char data, char *buf)
{
char i;
*(buf) = (char)(data / 100)| '0';
data %= 100;
*(buf+1) = (char)(data / 10) | '0';
*(buf+2) = (char)(data % 10) | '0'; /* low */
*(buf+3) = 0; // nul
for(i=0;i<2;i++)
{
if(*(buf+i) == '0')
{ *(buf+i) = ' '; }
else
break;
}
}
/*----int値を指定するバッファに10進5桁数に変換 "0"〜"65535"ゼロサプレス ----*/
/*
ex. static char d[5];
int_deci(19765, d);
*/
void int_deci(unsigned int data, char *buf)
{
char i;
*buf = (char)(data / 10000) | '0'; /* high */
data %= 10000;
*(buf+1) = (char)(data / 1000) | '0';
data %= 1000;
*(buf+2) = (char)(data / 100)| '0';
data %= 100;
*(buf+3) = (char)(data / 10) | '0';
*(buf+4) = (char)(data % 10) | '0'; /* low */
*(buf+5) = 0; // nul
for(i=0;i<4;i++)
{
if(*(buf+i) == '0')
{ *(buf+i) = ' '; }
else
break;
}
}
/*----32ビット値を指定するバッファに10進7桁文字変換 ----*/
/*
ex. static char d[7];
uint32_deci(19765, d);
*/
void uint32_deci(uint32_t data, char *buf)
{
*buf = (char)(data / 1000000) | '0'; /* high */
data %= 1000000;
*(buf+1) = (char)(data / 100000) | '0';
data %= 100000;
*(buf+2) = (char)(data / 10000) | '0';
data %= 10000;
*(buf+3) = (char)(data / 1000) | '0';
data %= 1000;
*(buf+4) = (char)(data / 100)| '0';
data %= 100;
*(buf+5) = (char)(data / 10) | '0';
*(buf+6) = (char)(data % 10) | '0'; /* low */
}
◆ 10進7桁 インクリメント カウンタ
/*
例 char d[7]; 最上位桁=d[0]
inc_cnt(d);
*/
void inc_cnt(unsigned char *buf)
{
unsigned char i;
for ( i=0 ; i<7 ;i++ ) // 下の桁から
{
if (++(*(buf + i)) > 9) // +1の結果が9より大きかったら
{*(buf + i) = 0;} // 0にして、次の桁へ
else
{return;} // そうでない場合は終
}
}
◆ buffer を指定文字で埋める
/* IN buff = buffer pointer
moji = 指定文字
leng = 埋める文字数
*/
void fill_buff_b(uchar *buff,uchar moji,ushort leng)
{
do
{ *buff++ = moji; }
while (--leng);
}
◆copy buffer
/*
IN in_buff = copy 先の buffer
out_buff = copy 元の buffer
length = copy する byte 数
*/
void copy_buff_b(uchar *in_buff, uchar *out_buff, ushort length)
{
while(length--)
{ *in_buff++ = *out_buff++;}
}
◆ buffer比較
/* buffer の比較
IN fbuff = 比較するbuffer 1
sbuff = 比較するbuffer 2
leng = 比較する長さ
OUT 0=同じ 1=fbuff > sbuff -1=fbuff < sbuff
*/
char comp_buff_b(uchar *fbuff, uchar *sbuff, ushort leng)
{
for ( ; leng != 0; leng--)
{
if (*fbuff > *sbuff)
{ return(1); }
if (*fbuff++ < *sbuff++)
{ return(-1); }
}
return(0);
}
◆ ASCII to uchar
/* ASCII data(0~9,A~F) をchar dataに変換する
IN buff = 変換する buffer (2byte only)
*/
uchar chg_asc2char(uchar *buff)
{
uchar dt[2];
if (*buff > '9')
{ dt[0] = *buff - 7; }
else
{ dt[0] = *buff; }
buff++;
if (*buff > '9')
{ dt[1] = *buff - 7; }
else
{ dt[1] = *buff; }
return ((dt[0] & 0x0f) * 16 + (dt[1] & 0x0f));
}
◆uchar data to ASCII data
/* uchar data をASCII data に変換する
IN data = 変換するdata
buff = 変換後の stock buffer
*/
void chg_char2asc(uchar data, uchar *buff)
{
*buff = ((data >> 4) & 0x0f) + '0';
if (*buff > '9')
{ *buff += 7; }
buff++;
*buff = (data & 0x0f) + '0';
if (*buff > '9')
{ *buff += 7; }
}
◆ ASCII to num
/* ASCII data をchar data(数字)に変換する
IN buff = 変換する buffer (2byte only)
*/
uchar chg_asc2num(uchar *buff)
{
return((*buff & 0x0f) * 10 + (*(buff + 1) & 0x0f));
}
◆char data to ASCII data
/* char data をASCII 数字 data (2byte only) に変換する
IN data = 変換するdata
buff = 変換後の stock buffer
*/
void chg_num2asc(uchar data, uchar *buff)
{
data %= 100;
*buff++ = (data / 10) | '0';
*buff = (data % 10) | '0';
}
◆ 10進counterをclear
/*
IN buff = counter buffer
length = counter の桁数
*/
void clr_counter(uchar *buff, ushort length)
{
do
{ *buff = '0'; buff++; }
while (--length);
}
◆10進counterを inc
/*
IN buff = counter buff
length = counter の桁数
*/
void inc_counter(uchar *buff, ushort length)
{
do
{
length--;
if (++(*(buff + length)) > '9')
{ *(buff + length) = '0'; }
else
{ return; }
}
while (length);
}
◆ビットスワップ(MSBとLSBの入替)
unsigned swapbit(unsigned char x, char b) //b=ビット長
{
unsigned char r;
r = 0;
while (b--)
{
r <<= 1;
r |= (x & 1);
x >>= 1;
}
return r;
}
◆buff の順番をひっくり返す
void rev_buff(uchar *buff, ushort leng)
{
char a, b, c, d;
for (b = leng / 2, c = 0, d = leng - 1; c < b; c++, d--)
{
a = *(buff + c); *(buff + c) = *(buff + d); *(buff + d) = a;
}
}
◆関数の間接実行
普通、関数は直接呼び出しますが、ポインタを使って配列変数の中から間接的に値を呼び出す方法と似た方法を使い、間接的に関数を呼び出すこともできます。例えば、保守用のテストルーチンの集合から特定のテストを呼び出す場合、if 文を使って分岐構造にすると長い記述になりますが、この手法を使い短かい簡潔な記述にすることができます。
void f1(void); // 関数のプロトタイプ宣言
void f2(void);
void f3(void);
void main()
{
void (*po[])() = { f1 , f2 , f3 }; // 関数の配列を宣言
int i;
i = 2; // i に実行関数の番号をセットして。
(*po[i] )(); // 関数を間接参照で呼び出す。
while(1);
}
void f1() // 各関数のプログラム
{
,,,,,,
}
void f2()
{
,,,,,
}
void f3()
{
,,,,
}
//////////////////////////////////////////////////////////////////////////////////
注) 本項では以下のように型を再定義しています。
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef volatile uchar vuchar;
typedef volatile short vshort;
typedef volatile ushort vushort;
typedef volatile ulong vulong;
余談)
C言語では、改行や連続するスペースやタブの個数には意味がありません。例えば、
@
void func1(void) {
何々;
}
A
void func1(void)
{
何々;
}
B
void func1(void){何々;}
の何れでも構わないわけです。一般的には1番目の記法が多いようですが私個人としては2番目の書き方を使うようにしています。それは、文のブロックの対応が "{" と "}" の位置で簡単に視覚化できるからです。if文などのネスティングが深くなるとタブによる位置付けに頼るところが大きくなります。