time.h 以外の日時フォーマット:FAT

FAT (File Allocation Table) で使用されている日時データと、time.h でおなじみの tm 構造体データを相互変換するサンプルコードです。


FATの日時フォーマット

FATの日時フォーマットは、time_t型と同じく4バイトの整数型です。
しかし、time_t型と違って経過秒ではなく、年月日時分秒の各値を詰めに詰めて保持しています。年は1980年を起点をしているため、各値の必要ビット数は下記のようになります。

項目値の範囲必要ビット数記号
0~127
(1980~2107年)
7ビットY
1~124ビットM
1~315ビットD
0~235ビットH
0~596ビットN
0~596ビットS

これを詰めに詰めるとは、次のように配置することを表します。

  YYYYYYYM MMMDDDDD HHHHHNNN NNNSSSSS S
  ※分かりやすいように8ビット単位で区切っています。

ここで問題となるのが最後の S です。
必要ビット数の合計が33ビットなので、どうしても1ビットあふれてしまいます。そこでこのあふれた1ビットをどうするかというと...

切り捨てます、バッサリと。
その結果どうなるかというと、秒は偶数の値しか保持できなくなります。FATを使用している人は、保存されているファイルの作成日時や更新日時を確認してみてください。
すべて偶数秒になっています。

fattime.h

#ifndef FATTIMEH
#define FATTIMEH

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>
#include <time.h>

//------------------------------------------------------------------------------
// FATフォーマット
//    uint32_t fattime;	(YYYYYYYM MMMDDDDD HHHHHNNN NNNSSSSS)
//
//    年 = 1980年からの経過年数 (0~127 = 1980~2107年)
//    月 = 1~12
//    日 = 1~31
//    時 = 0~23
//    分 = 0~59
//    秒 = 0~58 (偶数秒)
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
bool     Check_Fattime( uint32_t fattime );
uint32_t TmToFattime( const tm *tp );
tm      *FattimeToTm( uint32_t fattime );

#endif

fattime.c

//******************************************************************************
// FAT日時処理
//******************************************************************************

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>
#include <time.h>

#include "fattime.h"
#include "utime.h"

//******************************************************************************
// FAT型の日時を確認
//
// input  : fattime = FAT型
// return : true  = OK
//          false = NG
//******************************************************************************
bool Check_Fattime( uint32_t fattime )
{
    bool result;
    tm  *tp;

    // フォーマットごとに処理を実装していくのは不具合の基になるので、
    // tm型の関数を流用する
    tp     = FattimeToTm( fattime );
    result = Check_Tm( tp );

    return result;
}
//******************************************************************************
// tm型をFAT型に変換
//
// input  : *tp = [in] tm型
// return : FAT型
//******************************************************************************
uint32_t TmToFattime( const tm *tp )
{
    uint32_t fattime;

    fattime = ( uint32_t )( ( uint32_t )( tp->tm_year - 80 ) << 25 ) |
              ( uint32_t )( ( uint32_t )( tp->tm_mon + 1 ) << 21 ) |
              ( uint32_t )( (uint32_t)tp->tm_mday << 16 ) |
              ( uint32_t )( (uint32_t)tp->tm_hour << 11 ) |
              ( uint32_t )( (uint32_t)tp->tm_min << 5 ) |
              ( uint32_t )( (uint32_t)tp->tm_sec >> 1 );

    return fattime;
}
//******************************************************************************
// FAT型をtm型に変換
//
// input  : fattime = FAT型
// return : *tm型
//******************************************************************************
tm *FattimeToTm( uint32_t fattime )
{
    static tm t;

    t.tm_year = (int)( ( fattime >> 25 ) + 80 );
    t.tm_mon  = (int)( ( uint32_t )( fattime >> 21 ) & 0x0000000F ) - 1;
    t.tm_mday = (int)( ( uint32_t )( fattime >> 16 ) & 0x0000001F );
    t.tm_hour = (int)( ( uint32_t )( fattime >> 11 ) & 0x0000001F );
    t.tm_min  = (int)( ( uint32_t )( fattime >> 5 ) & 0x0000003F );
    t.tm_sec  = (int)( ( uint32_t )( fattime << 1 ) & 0x0000003F );

    return &t;
}

time.h 以外の日時フォーマット:BCD (RTC)

RTC (Real Time Clock) でよく使用されるBCDデータと、time.h でおなじみの tm 構造体データを相互変換するサンプルコードです。

RTCとは

一言でいうと時計用のICです。
最初に設定した日時から、1秒周期で時間を刻んでいきます。
単に日時を読みだせるだけでなく、一般的にはタイマーやアラーム機能を内包しており、一定周期や指定した日時にシステムを起動するといった使い方を行ったりもします。

なお、物にもよりますが一般的にはあまり高精度ではありません。温度変化の影響も受けますし。
このため、より高精度に時間管理したい場合は別途GPSモジュールを用意して、GPS衛星からの日時情報で補正することなどを考える必要があります。

事前知識

BCD (bcd.h) については以下を参照ください。
suimin-busoku.hateblo.jp


サンプルコードにでてくる utime.h については、以下を参照ください。
suimin-busoku.hateblo.jp

bcdtime.h

#ifndef BCDTIMEH
#define BCDTIMEH

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>
#include <time.h>

//------------------------------------------------------------------------------
// BCDフォーマット
//    uint8_t bcdtime[BCD_SIZE];
//
//    bcdtime[BCD_YEAR] = 年 (00~99 = 2000~2099年)
//    bcdtime[BCD_MON]  = 月 (1~12)
//    bcdtime[BCD_DAY]  = 日 (1~31)
//    bcdtime[BCD_WEEK] = 曜日 (※1)
//    bcdtime[BCD_HOUR] = 時 (0~23)
//    bcdtime[BCD_MIN]  = 分 (0~59)
//    bcdtime[BCD_SEC]  = 秒 (0~59)
//
//    ※1:RTCのメーカーで変わります
//          EPSON : 01h=日 / 02h=月 / 04h=火 / 08h=水 / 10h=木 / 20h=金 / 40h=土
//          RICHO : 0~6 = ユーザー定義
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// define
//------------------------------------------------------------------------------
enum
{
    BCD_SEC,
    BCD_MIN,
    BCD_HOUR,
    BCD_WDAY,    // なぜか間にあるお邪魔な曜日
    BCD_DAY,
    BCD_MON,
    BCD_YEAR,
    BCD_SIZE
};

//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
bool     Check_Bcdtime( const char bcdtime[] );
uint8_t *TmToBcdtime( const tm *tp );
tm      *BcdtimeToTm( const uint8_t bcdtime[] );

#endif

bcdtime.c

//******************************************************************************
// BCD日時処理
//******************************************************************************

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>
#include <time.h>

#include "bcd.h"
#include "bcdtime.h"
#include "utime.h"

//******************************************************************************
// 日時が正しいかを確認
//
// input  : bcdtime[] = [in] BCD型
// return : true  = OK
//          false = NG
//******************************************************************************
bool Check_Bcdtime( const uint8_t bcdtime[] )
{
    bool result;
    tm  *tp;

    // フォーマットごとに処理を実装していくのは不具合の基になるので、
    // tm型の関数を流用する
    tp     = BcdtimeToTm( bcdtime );
    result = Check_Tm( tp );

    return result;
}
//******************************************************************************
// tm型をBCD型に変換
//
// input  : *tp = [in] tm型
// return : BCD型
//******************************************************************************
uint8_t *TmToBcdtime( const tm *tp )
{
    static uint8_t bcdtime[BCD_SIZE];

    bcdtime[BCD_YEAR] = BinToBcd1( tp->tm_year - 100 );
    bcdtime[BCD_MON]  = BinToBcd1( tp->tm_mon + 1 );
    bcdtime[BCD_DAY]  = BinToBcd1( tp->tm_mday );
    bcdtime[BCD_HOUR] = BinToBcd1( tp->tm_hour );
    bcdtime[BCD_MIN]  = BinToBcd1( tp->tm_min );
    bcdtime[BCD_SEC]  = BinToBcd1( tp->tm_sec );

    // RICHO系で 0=日曜日 とした場合
    bcdtime[BCD_WDAY] = tp->tm_wday;

    return bcdtime;
}
//******************************************************************************
// BCD型をtm型に変換
//
// input  : bcdtime[] = [in] BCD型
// return : *tm型
//******************************************************************************
tm *BcdtimeToTm( const uint8_t bcdtime[] )
{
    static tm t;

    t.tm_year = BcdToBin1( bcdtime[BCD_YEAR] ) + 100;
    t.tm_mon  = BcdToBin1( bcdtime[BCD_MON] ) - 1;
    t.tm_mday = BcdToBin1( bcdtime[BCD_DAY] );
    t.tm_hour = BcdToBin1( bcdtime[BCD_HOUR] );
    t.tm_min  = BcdToBin1( bcdtime[BCD_MIN] );
    t.tm_sec  = BcdToBin1( bcdtime[BCD_SEC] );

    // RICHO系で 0=日曜日 とした場合
    t.tm_wday = bcdtime[BCD_WDAY];

    return &t;
}

2038年問題を符号なし32ビット型で切り抜ける

2000年問題の次にくる、大規模障害の温床になりそうなのが2038年問題です。
2038年1月19日3時14分7秒を過ぎると、符号付きの32ビット整数型がオーバーフローして日時が異常になります。
2038年まで20年を切っているため、そろそろ対策を考えることにしました。


対策

よくある簡単な対策が、64ビット版の time.h を使うというものです。
これにより西暦3000億年までカウントできるようになります。一生どころか、地球が消滅している先の未来までカウントし続けることができます。

そんな64ビット版ですが、いろいろと問題もあります。
まずそもそも、64ビット版に非対応の環境だとどうしようもありません。また、日時情報のサイズが4バイトから8バイトに増えます。
パソコン用の環境ならこんなことは問題にもならないかもしれませんが、組み込みの世界ではそうもいきません。フラッシュメモリーに記録できるデータ数が減ってしまいます。メモリーマップの見直しで、余計な不具合が発生してしまうかもしれません。

そういうわけで、日時情報を4バイトのまま延命するために、符号なし32ビット型を採用する案を考えました。
注意点としては、計算を簡略化するために2099年末までしか対応していません。2100年はうるう年ではないため、計算が少し面倒なのです。

utime.h

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdint.h>
#include <time.h>

//------------------------------------------------------------------------------
// tmフォーマット (time.h)
//    tm_sec   : 秒 = 0~61 (うるう秒)
//    tm_min   : 分 = 0~59
//    tm_hour  : 時 = 0~23
//    tm_mday  : 日 = 1~31
//    tm_mon   : 月 = 0~11 (1~12月)
//    tm_year  : 年 = 1900年からの経過年数
//    tm_wday  : 曜日 = 0~6 (日~土曜日)
//    tm_yday  : 1月1日からの経過日数 = 0~365 (0~365日)
//    tm_isdst : 夏時間フラグ [正=夏時間 / 0=夏時間ではない / 負=未使用]
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// define
//------------------------------------------------------------------------------
// エラー
#define TM_ERROR ( (utime_t)0xffffffff )

// time_t の符号なし版
// 1970/01/01 00:00:00 からの経過秒を表す
typedef uint32_t utime_t;

//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
utime_t umktime( tm *tp );
tm *    ulocaltime( const utime_t *ti );

bool    Check_Tm( const tm *tmptr );
uint8_t Get_DayMon( uint16_t year, uint8_t mon );
#endif

utime.c

以降のコードの中に、28年というのがところどころにでてきます。
これはうるう年のくる4の倍数で、かつ曜日がずれないようにするために、「4年×7曜日」にしているためです。

//******************************************************************************
// time.h のラッパー関数
//******************************************************************************

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdint.h>
#include <time.h>

#include "utime.h"

//------------------------------------------------------------------------------
// define
//------------------------------------------------------------------------------
#define DAY_4YEAR  ( 365UL * 4 + 1 )            // 4年間の日数
#define DAY_28YEAR ( DAY_4YEAR * 7UL )          // 28年間の日数
#define DAY_68YEAR ( DAY_4YEAR * 17UL )         // 68年間の日数
#define SEC_1DAY   ( 24UL * 60 * 60 )           // 1日の秒数
#define SEC_28YEAR ( DAY_28YEAR * SEC_1DAY )    // 28年間の秒数
#define SEC_68YEAR ( DAY_68YEAR * SEC_1DAY )    // 68年間の秒数

//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
static uint8_t isLeap( uint16_t year );

//******************************************************************************
// mktimeのラッパー関数
//
// input  : *tp = [in/out] 日時データ
// return : 経過秒
//******************************************************************************
utime_t umktime( tm *tp )
{
    int     cnt = 0;
    time_t  ssec;
    utime_t usec;

    if ( tp->tm_year > ( 2099 - 1900 ) )
    {
        return TM_ERROR;
    }

    // 2038年以上だとオーバーフローする恐れがあるため、28年分引く
    while ( tp->tm_year >= ( 2038 - 1900 ) )
    {
        cnt++;
        tp->tm_year -= 28;
    }

    ssec = mktime( tp );
    if ( ssec < 0 )
    {
        return TM_ERROR;
    }

    usec = (utime_t)ssec;

    // 引いた分を足す
    usec += SEC_28YEAR * cnt;

    // 引いた分を戻す
    tp->tm_year += 28 * cnt;

    return usec;
}
//******************************************************************************
// localtimeのラッパー関数
//
// input  : *ti = [in] 経過秒
// return : 日時データ
//******************************************************************************
tm *ulocaltime( const utime_t *t )
{
    int cnt = 0;

    tm     *pt;
    utime_t ut;

    ut = *t;

    // 2038年以上だとオーバーフローする恐れがあるため、28年分引く
    // 1970年が起点なので、+68が2038年になる
    while ( ut >= SEC_68YEAR )
    {
        cnt++;
        ut -= SEC_28YEAR;
    }

    pt = localtime( (time_t *)&ut );

    // 引いた分を足す
    pt->tm_year += 28 * cnt;

    return pt;
}
//******************************************************************************
// 日時が正しいかを確認
//
// input  : *tp	= [in] tm型
// return : true  = OK
//          false = NG
//******************************************************************************
bool Check_Tm( const tm *tp )
{
    uint8_t day_max;

    if ( tp->tm_year < 0 )
    {
        return false;
    }
    if ( ( tp->tm_mon < 0 ) || ( tp->tm_mon > 11 ) )
    {
        return false;
    }

    day_max = Get_DayMon( tp->tm_year + 1900, tp->tm_mon + 1 );
    if ( ( tp->tm_mday < 1 ) || ( tp->tm_mday > day_max ) )
    {
        return false;
    }
    if ( ( tp->tm_hour < 0 ) || ( tp->tm_hour > 23 ) )
    {
        return false;
    }
    if ( ( tp->tm_min < 0 ) || ( tp->tm_min > 59 ) )
    {
        return false;
    }
    if ( ( tp->tm_sec < 0 ) || ( tp->tm_sec > 59 ) )
    {
        return false;
    }

    return true;
}
//******************************************************************************
// 月の最終日
//
// input  : year = 年 (西暦)
//          mon  = 月 (1~12)
// return : 最終日
//******************************************************************************
uint8_t Get_DayMon( uint16_t year, uint8_t mon )
{
    static const uint8_t day_mon[2][12] = {
        { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
        { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } };
    uint8_t leap;

    leap = isLeap( year );

    return day_mon[leap][mon - 1];
}
//******************************************************************************
// うるう年かの確認
//
// input  : year = 年 (西暦)
// return : 0=平年 / 1=うるう年
//******************************************************************************
static uint8_t isLeap( uint16_t year )
{
    return ( year % 4 == 0 ) && ( ( year % 100 != 0 ) || ( year % 400 == 0 ) );
}

BCD

BCD (Binary-coded decimal) とは、1桁の10進数を4桁の2進数で表したものです。
7セグのドライバーICの制御や、RTCで使用されています。数値を誤差なく扱えることから、銀行など通貨を扱うシステムでも採用されているという話もあります。

さらにBCDを進化させたDPD (Densely packed decimal) というものもあり、こちらは天下の IEEE 754 でも採用されているとのことです。今回BCDを記事にするにあたって調べていて初めて知ったものなので、そのうち調べてみたいと思います。


BCD対応表

なんの捻りもなく、1桁の数値をそのまま2進数で表現します。

10進数BCD
00000
10001
20010
30011
40100
50101
60110
70111
81000
91001


10進数BCD
12340001 0010 0011 0100

bcd.h

#ifndef BCDH
#define BCDH

//---------------------------------------------------------------------------
// include
//---------------------------------------------------------------------------
#include <stddef.h>
#include <stdint.h>

//---------------------------------------------------------------------------
// function
//---------------------------------------------------------------------------
uint32_t BcdToBin( const uint8_t bcd[], size_t len );
uint32_t BcdToBin1( uint8_t bcd );
void     BinToBcd( uint32_t bin, uint8_t bcd[], size_t len );
uint8_t  BinToBcd1( uint32_t bin );

#endif

bcd.c

//***************************************************************************
// BCD処理
//***************************************************************************

//---------------------------------------------------------------------------
// include
//---------------------------------------------------------------------------
#include <stddef.h>
#include <stdint.h>

#include "bcd.h"

//---------------------------------------------------------------------------
// variable
//---------------------------------------------------------------------------
static const uint8_t m_hex_high[10] = {
    0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90 };
static const uint8_t m_hex_low[10] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };

//***************************************************************************
// BCDからバイナリーに変換
//
// input  : bcd[] = [in] BCD
//          len   = BCD バイト長
// return : バイナリー
//***************************************************************************
uint32_t BcdToBin( const uint8_t bcd[], size_t len )
{
    uint32_t bin = 0;

    for ( size_t i = 0; i < len; i++ )
    {
        bin *= 100;
        bin += BcdToBin1( bcd[i] );
    }

    return bin;
}
//***************************************************************************
// BCDからバイナリーに変換 (1バイト)
//
// input  : bcd = BCD
// return : バイナリー (1バイト)
//***************************************************************************
uint32_t BcdToBin1( uint8_t bcd )
{
    uint8_t  high, low;
    uint32_t bin = 0;

    high = bcd >> 4;
    low  = bcd & 0x0F;
    bin  = high * 10 + low;

    return bin;
}
//***************************************************************************
// バイナリーからBCDに変換
//
// input  : bin = バイナリー
//          bcd = [out] bcd
//          len = BCD バイト長
// return :
//***************************************************************************
void BinToBcd( uint32_t bin, uint8_t bcd[], size_t len )
{
    uint8_t high, low;

    for ( size_t i = 0; i < len; i++ )
    {
        low = bin % 10;
        bin /= 10;
        high = bin % 10;
        bin /= 10;
        bcd[len - i - 1] = m_hex_high[high] | m_hex_low[low];
    }
}
//***************************************************************************
// バイナリーからBCDに変換 (1バイト)
//
// input  : bin = バイナリー
// return : BCD (1バイト)
//***************************************************************************
uint8_t BinToBcd1( uint32_t bin )
{
    uint8_t high, low;

    low = bin % 10;
    bin /= 10;
    high = bin % 10;

    return m_hex_high[high] | m_hex_low[low];
}

さらに変則で...

BCDでは0~9までの数値しか扱いません。
このままだと Ah~Fh が空いていてもったいないよねってことで、私は残りの枠も使うことがあります。

BCD記号内容
1010e指数の e です
1011+符号の + です
1100,カンマです
1101-符号の - です
1110.小数点です
1111NULデータの終わりを表します


10進数 (ではないけど)BCD (長くなるので16進数です)
+1.2345e+01B1 E2 34 5A B0 1F


ちなみに、「+ , - .」の記号は ASCII の順にしたがって割り振っています。
ASCII については下記をどうぞ。


suimin-busoku.hateblo.jp

CRC

CRC (Cyclic Redundancy Check) とは、誤りを検出するための符号です。
データの伝送や記録が、正しく行われているかを確認するために使用します。
パリティーやチェックサムと比べて誤り検出率が高く、USBやCAN、Modbusなど幅広い通信フォーマットに採用されています。

ここでは、CRC値を計算するためのサンプルコードを紹介します。

crc.h

#ifndef CRCH
#define CRCH

//---------------------------------------------------------------------------
// include
//---------------------------------------------------------------------------
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

//---------------------------------------------------------------------------
// define
//---------------------------------------------------------------------------
// シフト方向
typedef enum
{
    SHIFT_LEFT,
    SHIFT_RIGHT
} SHIFT_DIRECTION;

// CRC-8の計算パラメーター
typedef struct
{
    bool    refrect_input;     // 入力反転 [true=Yes / false=No]
    bool    refrect_output;    // 出力反転 [true=Yes / false=No]
    uint8_t polynomial;        // 多項式
    uint8_t initial_value;     // 初期値
    uint8_t final_xor;         // 出力反転

    SHIFT_DIRECTION shift_dir;    // シフト方向
} CRC8_PARAMETER;

// CRC-16の計算パラメーター
typedef struct
{
    bool     refrect_input;     // 入力反転 [true=Yes / false=No]
    bool     refrect_output;    // 出力反転 [true=Yes / false=No]
    uint16_t polynomial;        // 多項式
    uint16_t initial_value;     // 初期値
    uint16_t final_xor;         // 出力反転

    SHIFT_DIRECTION shift_dir;    // シフト方向
} CRC16_PARAMETER;

// CRC-32の計算パラメーター
typedef struct
{
    bool     refrect_input;     // 入力反転 [true=Yes / false=No]
    bool     refrect_output;    // 出力反転 [true=Yes / false=No]
    uint32_t polynomial;        // 多項式
    uint32_t initial_value;     // 初期値
    uint32_t final_xor;         // 出力反転

    SHIFT_DIRECTION shift_dir;    // シフト方向
} CRC32_PARAMETER;

//---------------------------------------------------------------------------
// function
//---------------------------------------------------------------------------
void Make_CRC8Table( const CRC8_PARAMETER *param );
void Make_CRC16Table( const CRC16_PARAMETER *param );
void Make_CRC32Table( const CRC32_PARAMETER *param );

uint8_t  Calc_CRC8( const uint8_t buf[], size_t len, const CRC8_PARAMETER *param );
uint16_t Calc_CRC16( const uint8_t buf[], size_t len, const CRC16_PARAMETER *param );
uint32_t Calc_CRC32( const uint8_t buf[], size_t len, const CRC32_PARAMETER *param );
#endif

crc.c

//---------------------------------------------------------------------------
// include
//---------------------------------------------------------------------------
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "crc.h"

//---------------------------------------------------------------------------
// variable
//---------------------------------------------------------------------------
static uint8_t  m_crc8_table[256];
static uint16_t m_crc16_table[256];
static uint32_t m_crc32_table[256];

//---------------------------------------------------------------------------
// function
//---------------------------------------------------------------------------
static uint8_t  Refrect_8bit( uint8_t data );
static uint16_t Refrect_16bit( uint16_t data );
static uint32_t Refrect_32bit( uint32_t data );


//***************************************************************************
// CRC-8テーブルの作成
//
// input  : *param = [in] 計算設定
// return :
//***************************************************************************
void Make_CRC8Table( const CRC8_PARAMETER *param )
{
    uint8_t c, j;

    if ( param->shift_dir == SHIFT_LEFT )
    {
        for ( uint16_t i = 0; i < 256; i++ )
        {
            c = (uint8_t)i;
            for ( j = 0; j < 8; j++ )
            {
                if ( (uint8_t)( c & 0x80 ) == 0U )
                {
                    c <<= 1;
                }
                else
                {
                    c = (uint8_t)( (uint8_t)( c << 1 ) ^ param->polynomial );
                }
            }
            m_crc8_table[i] = c;
        }
    }
    else
    {
        for ( uint16_t i = 0; i < 256; i++ )
        {
            c = (uint8_t)i;
            for ( j = 0; j < 8; j++ )
            {
                if ( (uint8_t)( c & 0x01 ) == 0U )
                {
                    c >>= 1;
                }
                else
                {
                    c = (uint8_t)( param->polynomial ^ (uint8_t)( c >> 1 ) );
                }
            }
            m_crc8_table[i] = c;
        }
    }
}
//***************************************************************************
// CRC-16テーブルの作成
//
// input  : *param = [in] 計算設定
// return :
//***************************************************************************
void Make_CRC16Table( const CRC16_PARAMETER *param )
{
    uint8_t  j;
    uint16_t c;

    if ( param->shift_dir == SHIFT_LEFT )
    {
        for ( uint16_t i = 0; i < 256; i++ )
        {
            c = (uint16_t)( i << 8 );
            for ( j = 0; j < 8; j++ )
            {
                if ( (uint16_t)( c & 0x8000 ) == 0U )
                {
                    c <<= 1;
                }
                else
                {
                    c = (uint16_t)( (uint16_t)( c << 1 ) ^ param->polynomial );
                }
            }
            m_crc16_table[i] = c;
        }
    }
    else
    {
        for ( uint16_t i = 0; i < 256; i++ )
        {
            c = i;
            for ( j = 0; j < 8; j++ )
            {
                if ( (uint16_t)( c & 0x0001 ) == 0U )
                {
                    c >>= 1;
                }
                else
                {
                    c = (uint16_t)( param->polynomial ^ (uint16_t)( c >> 1 ) );
                }
            }
            m_crc16_table[i] = c;
        }
    }
}
//***************************************************************************
// CRC-32テーブルの作成
//
// input  : *param = [in] 計算設定
// return :
//***************************************************************************
void Make_CRC32Table( const CRC32_PARAMETER *param )
{
    uint8_t  j;
    uint32_t c;

    if ( param->shift_dir == SHIFT_LEFT )
    {
        for ( uint32_t i = 0; i < 256; i++ )
        {
            c = (uint32_t)( i << 24 );
            for ( j = 0; j < 8; j++ )
            {
                if ( (uint32_t)( c & 0x80000000 ) == 0UL )
                {
                    c <<= 1;
                }
                else
                {
                    c = (uint32_t)( (uint32_t)( c << 1 ) ^ param->polynomial );
                }
            }
            m_crc32_table[i] = c;
        }
    }
    else
    {
        for ( uint32_t i = 0; i < 256; i++ )
        {
            c = i;
            for ( j = 0; j < 8; j++ )
            {
                if ( (uint32_t)( c & 0x00000001 ) == 0UL )
                {
                    c >>= 1;
                }
                else
                {
                    c = (uint32_t)( param->polynomial ^ (uint32_t)( c >> 1 ) );
                }
            }
            m_crc32_table[i] = c;
        }
    }
}

//***************************************************************************
// CRC-8の計算
//
// input  : buf[]  = [in] データ
//          len    = データ長
//          *param = [in] 計算設定
// return : CRC-8
//***************************************************************************
uint8_t Calc_CRC8( const uint8_t buf[], size_t len, const CRC8_PARAMETER *param )
{
    uint8_t c = param->initial_value;
    uint8_t tmp;

    if ( param->shift_dir == SHIFT_LEFT )
    {
        for ( size_t i = 0; i < len; i++ )
        {
            tmp = buf[i];
            if ( param->refrect_input == true )
            {
                tmp = Refrect_8bit( tmp );
            }
            c = m_crc8_table[c ^ tmp];
        }
    }
    else
    {
        for ( size_t i = 0; i < len; i++ )
        {
            tmp = buf[i];
            if ( param->refrect_input == true )
            {
                tmp = Refrect_8bit( tmp );
            }
            c = (uint8_t)( m_crc8_table[c ^ tmp] ^ c );
        }
    }

    c ^= param->final_xor;

    if ( param->refrect_output == true )
    {
        c = Refrect_8bit( c );
    }

    return c;
}
//***************************************************************************
// CRC-16の計算
//
// input  : buf[]  = [in] データ
//          len    = データ長
//          *param = [in] 計算設定
// return : CRC-16
//***************************************************************************
uint16_t Calc_CRC16( const uint8_t buf[], size_t len, const CRC16_PARAMETER *param )
{
    uint8_t  adr, tmp;
    uint16_t c = param->initial_value;

    if ( param->shift_dir == SHIFT_LEFT )
    {
        for ( size_t i = 0; i < len; i++ )
        {
            tmp = buf[i];
            if ( param->refrect_input == true )
            {
                tmp = Refrect_8bit( tmp );
            }
            adr = (uint8_t)( (uint8_t)( (uint8_t)( c >> 8 ) ^ tmp ) & 0xff );
            c   = (uint16_t)( (uint16_t)( c << 8 ) ^ m_crc16_table[adr] );
        }
    }
    else
    {
        for ( size_t i = 0; i < len; i++ )
        {
            tmp = buf[i];
            if ( param->refrect_input == true )
            {
                tmp = Refrect_8bit( tmp );
            }
            adr = (uint8_t)( (uint8_t)( c ^ tmp ) & 0xFF );
            c   = (uint16_t)( m_crc16_table[adr] ^ (uint16_t)( c >> 8 ) );
        }
    }

    c ^= param->final_xor;

    if ( param->refrect_output == true )
    {
        c = Refrect_16bit( c );
    }

    return c;
}
//***************************************************************************
// CRC-32の計算
//
// input  : buf[]  = [in] データ
//          len    = データ長
//          *param = [in] 計算設定
// return : CRC-32
//***************************************************************************
uint32_t Calc_CRC32( const uint8_t buf[], size_t len, const CRC32_PARAMETER *param )
{
    uint8_t  adr, tmp;
    uint32_t c = param->initial_value;

    if ( param->shift_dir == SHIFT_LEFT )
    {
        for ( size_t i = 0; i < len; i++ )
        {
            tmp = buf[i];
            if ( param->refrect_input == true )
            {
                tmp = Refrect_8bit( tmp );
            }
            adr = (uint8_t)( (uint8_t)( (uint8_t)( c >> 24 ) ^ tmp ) & 0xff );
            c   = (uint32_t)( (uint32_t)( c << 8 ) ^ m_crc32_table[adr] );
        }
    }
    else
    {
        for ( size_t i = 0; i < len; i++ )
        {
            tmp = buf[i];
            if ( param->refrect_input == true )
            {
                tmp = Refrect_8bit( tmp );
            }
            adr = (uint8_t)( (uint8_t)( c ^ tmp ) & 0xFF );
            c   = (uint32_t)( m_crc32_table[adr] ^ (uint32_t)( c >> 8 ) );
        }
    }

    c ^= param->final_xor;

    if ( param->refrect_output == true )
    {
        c = Refrect_32bit( c );
    }

    return c;
}

//***************************************************************************
// 反転 (8bit)
//
// input  : data = データ
// return : 反転後のデータ
//***************************************************************************
static uint8_t Refrect_8bit( uint8_t data )
{
    uint8_t result = 0;

    for ( uint8_t i = 0; i < 8; i++ )
    {
        if ( (uint8_t)( data & 0x01 ) == 1U )
        {
            result |= (uint8_t)( 1U << ( 7 - i ) );
        }
        data >>= 1;
    }
    return result;
}
//***************************************************************************
// 反転 (16bit)
//
// input  : data = データ
// return : 反転後のデータ
//***************************************************************************
static uint16_t Refrect_16bit( uint16_t data )
{
    uint16_t result = 0;

    for ( uint8_t i = 0; i < 16; i++ )
    {
        if ( (uint16_t)( data & 0x0001 ) == 1U )
        {
            result |= (uint16_t)( 1U << ( 15 - i ) );
        }
        data >>= 1;
    }
    return result;
}
//***************************************************************************
// 反転 (32bit)
//
// input  : data = データ
// return : 反転後のデータ
//***************************************************************************
static uint32_t Refrect_32bit( uint32_t data )
{
    uint32_t result = 0;

    for ( uint8_t i = 0; i < 32; i++ )
    {
        if ( (uint32_t)( data & 0x00000001 ) == 1UL )
        {
            result |= (uint32_t)( 1UL << ( 31 - i ) );
        }
        data >>= 1;
    }
    return result;
}

使用例

uint8_t data[4];

CRC8_PARAMETER crc8_param;

// CRCの計算条件を設定します
crc8_param.polynomial     = 0x07;    // x8 + x2 + x + 1
crc8_param.initial_value  = 0x00;
crc8_param.refrect_input  = false;
crc8_param.refrect_output = false;
crc8_param.final_xor = 0x00;
crc8_param.shift_dir = SHIFT_LEFT;

// CRCを計算する前にテーブルを作成すること
// 一度作成すると、後は使いまわせます
Make_CRC8Table( &crc8_param );

data[0] = 0x31;
data[1] = 0x32;
data[2] = 0x33;
data[3] = Calc_CRC8( data, 3, &crc8_param );

// data[0]~data[3]を通信で伝送したり、メモリーに記録したりする

RAMが少ないときは...

CRC32だと、テーブルだけで1KBも持っていかれます。
このため、RAMが少ないときはテーブルの値を定数化して、プログラムメモリーに配置することを考えてもいいと思います。

// x8 + x2 + x + 1、左シフト
static const uint8_t m_crc8_table[256] = {
    0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
    0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
    0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
    0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
    0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
    0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
    0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
    0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
    0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
    0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
    0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
    0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
    0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
    0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
    0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
    0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
    0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
    0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
    0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
    0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
    0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
    0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
    0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
    0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
    0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
    0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
    0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
    0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
    0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
    0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
    0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
    0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
};