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

2015年9月29日火曜日

GPIOを操作するマクロを作る

久しぶりにSTM32F1で遊んでいます。
雛形を作りなおしているので、そのついでにGPIOを操作するマクロを作ってみました。
基本的にSTM32F1用。

マクロ

#ifndef __hardware_H
#define __hardware_H

#include "includes.h"

#define     HW(name, x, type)       name##_##x##_##type
#define     HW_OUT(name, x, val)    GPIO_WriteBit(HW(name, x, PORT), HW(name, x, PIN), (val) ? HW(name, x, ACTIVE) : !HW(name, x, ACTIVE))
#define     HW_IN(name, x)          (GPIO_ReadInputDataBit(HW(name, x, PORT), HW(name, x, PIN)) == HW(name, x, ACTIVE))
#define     HW_OT(name, x)          (GPIO_ReadOutputDataBit(HW(name, x, PORT), HW(name, x, PIN)) == HW(name, x, ACTIVE))
#define     HW_TGL(name, x)         HW_OUT(name, x, !HW_OT(name, x));

#define     LED_1_RCC       RCC_APB2Periph_GPIOD
#define     LED_1_PORT      GPIOD
#define     LED_1_PIN       GPIO_Pin_4
#define     LED_1_ACTIVE    0

#define     SW_1_RCC        RCC_APB2Periph_GPIOA
#define     SW_1_PORT       GPIOA
#define     SW_1_PIN        GPIO_Pin_0
#define     SW_1_ACTIVE     1

#endif /* __hardware_H */
LED1はアクティブLOW接続でResetで点灯、Setで消灯。
SW1はアクティブHIGH接続で押された時にSet。

使用例

void LED_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(HW(LED, 1, RCC), ENABLE);

    GPIO_InitStructure.GPIO_Pin = HW(LED, 1, PIN);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(HW(LED, 1, PORT), &GPIO_InitStructure);
}

void SW_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(HW(SW, 1, RCC), ENABLE);

    GPIO_InitStructure.GPIO_Pin = HW(SW, 1, PIN);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(HW(SW, 1, PORT), &GPIO_InitStructure);
}

スイッチを読む場合
if (HW_IN(SW, 1)) { ... } else { ... }

LEDを点灯させる場合
HW_OUT(LED, 1, 1);

LEDを消灯させる場合
HW_OUT(LED, 1, 0);

LEDを切り替える場合
HW_TGL(LED, 1);

もちろんスイッチやLED以外にも使用可能です。
ほとんど便利じゃないし、forで回したりできないので、ベタ書きとほとんど変わらないですが。
あとUnity(テストハーネス)使ってる人はわかると思いますが、マクロでごにょごにょやってるとエラった時にメッセージが非常に難解です。
今回はHWを接頭辞として使っていますが、LED(1, PORT)やSW(1, PIN)のほうが直感的な気がします。

2015年5月6日水曜日

ftoa関数(ひたすらループする)

浮動小数点数を指定した精度で文字列化する関数です

ftoaが本体です
第1引数:変換する値
第2引数:結果を入れる文字配列
第3引数:指定した長さに足りない場合に0を入れるか(偽で0x20埋め 真で0x30埋め)
第4引数:変換結果の最短長さ
第5引数:小数点以下の長さ

またftoafは第1引数にフォーマットの文字列を取り、第2引数に変換する値を取ります
このフォーマットはprintfの%fに使用するフォーマットに近い書式を設定できます

現在のところNaNやInf等の処理は行っていません
またいくつかのフォーマットを指定してsprintfと同じ結果になるように作っていますが、正しく実装されていない部分や、そもそも実装依存で環境によって違うなどの場合があるかもしれません
ループを多用しているので、コンパイラの最適化に依存しているコードでもあります
また内部で浮動小数点を割ったり掛けたりしているので、その間の演算誤差も無視できない場合があります  とりあえず「sprintfとか使えない環境で臨時に使う」程度で


追記:2015-05-06 15-07
一番最後にp--からp++までの間に入っている四捨五入処理ですが、最後の桁が9で切り上げるときに数字がコロンになってしまう問題があります
一番手っ取り早い対策はこのコードをコメントアウトすることです(もちろん四捨五入はされず、小数点以下切り捨てとされます)
数字が9だった場合は上の桁をインクリメントすればいいのですが、例えば999.96を小数点以下1桁まで表示した場合、繰り上げをすると先頭に1桁増やす必要があります
ということで小さな修正では対応できないので、近いうちにちゃんと作りなおそうと思います
追記ここまで

2015年1月15日木曜日

GCCのコンパイラ警告に依存したコードの書き方について

ちょっと気になることが有ったのでまとめ
(諸事情で回線速度が80kbpsくらいしか出てないのでネットの情報を漁ったりはしてません 詳しく書いてるサイトがあったら教えてください)

1. 普通に書く

まずこういうコードを用意します

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1;
    hoge2_t hoge2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

見るからにCのコードですが、g++でビルドします
$ g++ test.c -Wall
という感じで
もちろん警告は出ません
実行すると
$ ./a.exe
type size:8
type.hoge1: hoge1
type.hoge2: hoge2
という感じになります

2. ちょっと端折った書き方をしてみる

列挙型の一部は使用していません switchで使用していない分岐を持つのはコード効率が悪いのでコメントアウトしてみましょう

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1;
    hoge2_t hoge2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    //case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    //case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

これを1.と同じようにビルドしてみましょう
$ g++ main.c -Wall
main.c: 関数 ‘int main()’ 内:
main.c:20:9: 警告: 列挙値 ‘hoge1_4’ は switch 内で取り扱われません [-Wswitch]
  switch (type.hoge1) {
         ^
main.c:28:9: 警告: 列挙値 ‘hoge2_4’ は switch 内で取り扱われません [-Wswitch]
  switch (type.hoge2) {
         ^
警告がでてしまいました
これは便利ですね 列挙型の書き忘れを防ぐことができます

3. 消費メモリをカリカリ削る

しかし実行してみるとtype_t型は8バイトのメモリを使用しています
それぞれの列挙型は4種類しか値を持たないのでそれぞれ2bitあれば事足りるはずです
ということでビットフィールドで2bitを指定しましょう

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1 : 2;
    hoge2_t hoge2 : 2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

同じようにビルドしてみましょう もちろん警告はでません
では実行してみましょう

$ ./a.exe
type size:4
type.hoge1: hoge1
type.hoge2: hoge2

消費メモリは4バイトに減っています
メモリ効率が倍になりました めでたしめでたし

4. 消費メモリをカリカリ削る + 端折った書き方

では、使っていない列挙のcaseをコメントアウトしてみましょう

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1 : 2;
    hoge2_t hoge2 : 2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    //case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    //case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

このようになります
ビルドしてみると
$ g++ main.c -Wall
警告がでません
これはこまった…

メモリ効率を優先すると未使用のcaseに対する警告がでなくなってしまいました

組み込みの場合は消費メモリは減らしたいし、case分岐忘れは致命的だし という場合がありますが、どうしたらいいのでしょう…

というのが今日の本題
本題というかもうこれしかネタが無いのですが、こういう時はどうすればいいんでしょうかねぇ…


ちなみに


#include <stdio.h>
#include <stdint.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    uint8_t hoge1 : 2;
    uint8_t hoge2 : 2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

というコードにすると、実行結果は
$ ./a.exe
type size:1
type.hoge1: hoge1
type.hoge2: hoge2
となります
メモリ消費量が1バイトになりました(もちろんビットフィールドを使っているのでcase忘れ警告はでませんが)

列挙型では4バイトを使ってしまうのは、暗黙的にint(4バイト)を使用し、そのビットを区切って使っているからだと思われます


メモリがカツカツな環境で構造体を使いたい場合はどうすればいいんですかねぇ
enumは#defineと同じような感覚で使って、uint8_tのビットフィールドで使用メモリを減らす case忘れはコーダーが気をつける という感じになるんでしょうか

そもそもコンフィグレジスタの値をtypedefで持とうとしてる時点でメモリ消費量なんて… という感じではありますが

僕の知る限りどのルートを通ってもあっちを立てればこっちが立たずです
なにかいい方法があったら教えてください

2014年9月15日月曜日

strtokっぽい関数

C言語のstring.hにはstrtokという関数があって、文字列を指定した区切り文字で分割することができます
しかしこの関数では区切り文字が連続した場合、その部分をまとめて次のデータに飛んでしまいます
この場合、CSVのヌルデータ等は正常に処理できません
ということでそのような連続した区切り文字が存在する場合も次に飛ばないような関数を作ってみました

#include <stdio.h>
#include <string.h>

char *strtoks(char *s1, const char *s2) {
    static char *str = 0;
    register int i, j;

    if (s1) {
        str = s1;
    } else {
        s1 = str;
    }
    if (!s1) { return(0); }

    j = strlen(s2);

    while (1) {
        if (!*str) {
            str = 0;
            return(s1);
        }

        for (i = 0; i < j; i++) {
            if (*str == s2[i]) {
                *str++ = 0;
                return(s1);
            }
        }

        str++;
    }
}

char *strtokc(char *s1, char s2) {
    static char *str = 0;

    if (s1) {
        str = s1;
    } else {
        s1 = str;
    }
    if (!s1) { return(0); }

    while (1) {
        if (!*str) {
            str = 0;
            return(s1);
        }

        if (*str == s2) {
            *str++ = 0;
            return(s1);
        }

        str++;
    }
}

int main(int argc, char *argv[]) {
    char str1[] = "10,20,30,,50,60,,,90";
    char str2[] = "10,20,30,,50,60,,,90";
    char str3[] = "10,20,30,,50,60,,,90";
    char *p;

    printf("strtok\n");
    p = strtok(str1, ",");
    while (p) {
        static int cnt = 0;
        printf("%d:\"%s\"\n", ++cnt, p);
        p = strtok(0, ",");
    }

    printf("\nstrtoks\n");
    p = strtoks(str2, ",");
    while (p) {
        static int cnt = 0;
        printf("%d:\"%s\"\n", ++cnt, p);
        p = strtoks(0, ",");
    }

    printf("\nstrtokc\n");
    p = strtokc(str3, ',');
    while (p) {
        static int cnt = 0;
        printf("%d:\"%s\"\n", ++cnt, p);
        p = strtokc(0, ',');
    }

    return(0);
}

上記のstrtoksとstrtokcが今回作った関数です
strtoksは複数の区切り文字のいずれかが出てきたらそこで区切ります
strtokcは区切り文字は文字列ではなく文字として渡します そのため若干パフォーマンスが向上します

実行結果は
strtok
1:"10"
2:"20"
3:"30"
4:"50"
5:"60"
6:"90"

strtoks
1:"10"
2:"20"
3:"30"
4:""
5:"50"
6:"60"
7:""
8:""
9:"90"

strtokc
1:"10"
2:"20"
3:"30"
4:""
5:"50"
6:"60"
7:""
8:""
9:"90"

という感じです
strtokではスキップしてしまうところも正常に処理しています
なお、strtoksおよびstrtokcのヌルデータはptr[0] = '\0'となっています
例えば整数型が入っていると仮定し、整数型から変換する関数に渡した場合、場合によっては問題が発生する可能性があります
その可能性が有る場合は最初にヌルデータか否かを確認する必要があります

あと、わざわざ説明する必要もないと思いますが、strtokや上記の関数は受け取ったデータを破壊します
char *str = "123,456,789"; p = strtok(str, ","); とかやらないよーに

2014年4月8日火曜日

atan2の変換テーブル

atan2を変換する整数テーブルを作ってみました

      1 #include <stdio.h>
      2 #include <math.h>
      3 #include <stdint.h>
      4
      5 #define PI (3.14159265358979323846264338327950288)
      6
      7 /*             Row  Col  */
      8 int atan2table[100][100];
      9 const int tableSize   = (int) (sizeof(atan2table   ) / sizeof(atan2table[0][0]));
     10 const int tableColumn = (int) (sizeof(atan2table[0]) / sizeof(atan2table[0][0]));
     11 const int tableRow    = (int)((sizeof(atan2table   ) / sizeof(atan2table[0][0])) /
     12                               (sizeof(atan2table[0]) / sizeof(atan2table[0][0])));
     13
     14 void createTable(void) {
     15     int x, y;
     16
     17     for (x = 0; x < tableRow; x++) {
     18         for (y = 0; y < tableColumn; y++)
     19         { atan2table[x][y] = (int)((atan2(y + 1, x + 1) * (180.0 / PI)) * 1000); }
     20     }
     21 }
     22
     23 int ATAN2(y, x) {
     24     int xp = 1;
     25     int yp = 1;
     26
     27     if (!x && !y)  { return(0); }
     28
     29     if (!x) { if (y > 0) { return(90000); } else { return(-90000); } }
     30     if (!y) { if (x > 0) { return( 0); }    else { return(180000); } }
     31
     32     if (x < 0) { x = -x; xp = -1; }
     33     if (y < 0) { y = -y; yp = -1; }
     34
     35     x--; y--;
     36
     37     if (x >= tableRow)    { return(0); }
     38     if (y >= tableColumn) { return(0); }
     39
     40     int deg = atan2table[x][y];
     41
     42     if (xp == -1) { deg = 180000 - deg; }
     43     if (yp == -1) { deg = -deg; }
     44
     45     return(deg);
     46 }
     47
     48 int main(void) {
     49     int i;
     50
     51     createTable();
     52
     53     for (i = 0; i < 360; i += 5) {
     54         int x = sin(i * (PI / 180.0)) * 95;
     55         int y = cos(i * (PI / 180.0)) * 95;
     56         double _atan2 = atan2(y, x) * (180.0 / PI);
     57         double _ATAN2 = ATAN2(y, x) * 0.001;
     58
     59         printf("deg:%3d, ", i);
     60         printf("x:%3d, y:%3d, ", x, y);
     61         printf("%8.3lf, ", _atan2);
     62         printf("%8.3lf, ", _ATAN2);
     63         printf("%6.3lf, ", _atan2 - _ATAN2);
     64         printf("\n");
     65     }
     66
     67     return(0);
     68 }
     69

入力範囲はx,y共に-100 - +100の間です(デフォルトのテーブルサイズでは)
このコードでは予めmatn.hのatan2でテーブルを作成していますが、組み込み用途向けの場合は予めテーブルをCSVとして作成しておき、配列に読み込んでROMに焼く という方法が使えます
また、入力がテーブルを超えた場合はテーブルに収まるように除算してから処理する というアルゴリズムにすれば精度は落ちますが広い範囲の入力を受けることができます

このコードではハードコードされた値があるため、intの代わりにshortにしようと思うとちょっと面倒ですが、uint16_tでテーブルを作った場合、100x100だと20kByteくらいのROMが必要になります

精度は悪く、ifの連続なので処理速度も期待できませんが、ソフトウェアで浮動小数点を計算するとか、そもそも浮動小数点が使えない環境では使えるかも

2014年3月12日水曜日

xprintfに引数で最小フィールド幅を指定する機能を追加する

stdio.hのprintf関数には最小フィールド幅というものがあります
例えば
printf("%05d", 123);
というコードの場合は"00123"という風に、指定した文字数(今回は5)に足りない場合に空白や0で埋めるという機能です

この機能はもちろんxprintfにもあり、同じように使うことができます

しかし、printfの地味な機能として、この最小フィールド幅を引数で指定することができます
printf("%0*d", 5, 123);
という感じです
最初のサンプルではフォーマットの中に5と指定していた部分をアスタリスクに置き換え、数字を引数として渡しています

この機能をxprintfで使えるようにしてみました

変更方法は
if (!w && c == '*') { w = va_arg(arp, unsigned int); c = *fmt++; }
をvfprintf関数の"/* Minimum width */"というコメントが書いてあるforの次に置くだけです

この機能は数字で最小フィールド幅を指定されていない場合 かつ フォーマットに'*'が書かれていた場合に、引数から最小フィールド幅を取り出す という動作です

あまり使う場面は多くありませんが、たまーに使いたい時があり、結構便利な機能となります

2014年3月10日月曜日

ノンブロッキングでコンソール入力

コンソール入力は通常ブロッキングで行われます
これはscanf等で読み込んだ場合、少なくともEnterを押すまでは処理が停止することを意味します
通常のコンソールプログラミングではコレで問題ありませんが、バックグラウンドで何か処理をしたい場合は困る場合があります

ということでノンブロッキングで読み込む方法
を参考にしました

 11 int CanIRecv(int fd) {
 12     fd_set fdset;
 13     struct timeval timeout;
 14     FD_ZERO(&fdset);
 15     FD_SET(fd, &fdset);
 16     timeout.tv_sec = 0;
 17     timeout.tv_usec = 0;
 18     return(select(fd + 1, &fdset, NULL, NULL, &timeout));
 19 }
 20 
 21 int Recv(int fd, char *buff, int buffSize) {
 22     int i = CanIRecv(fd);
 23     if (i) {
 24         read(fd, buff, buffSize);
 25         buff[strlen(buff) - 1] = '\0';
 26     }
 27     return(i);
 28 }
 29 
 30 int main() {
 31     int cnt = 0;
 32 
 33     while (1) {
 34         char buff[200];
 35         if (Recv(0, buff, sizeof(buff))) {
 36             printf("%d:[%s]\n", cnt, buff);
 37             if (!strcmp(buff, "quit")) { break; }
 38         }
 39         cnt++;
 40     }
 41 
 42     return(0);
 43 }
という感じのコードでテスト
なお<stdio.h>と<sys/time.h>のインクルードが必要

このコードだと文字列の受信はノンブロッキングで行われるため、何も入力しなくてもcntがどんどんインクリメントされていくはずです

結果は
hello
1508070:[hello]
test
2561543:[test]
quit
3515094:[quit]
という感じになり、正常に動作していることが伺えます

ちなみにCanIRecvなどに渡すのはファイルディスクリプタで、POSIXではstdinに0が stdoutに1が stderrに2が割り当てられ、openなどで開くとその後ろが割り当てられます


ということで、マイコンだとよく使ってるノンブロッキングな行入力の使い方でした


MacOSってGCCコンソールでも簡単にネットワークプログラミングができるので、ちょっと遊んでいます
もうちょっと色々できるようになったら XBeeWiFiとか使えば面白いかなーと思っています

2014年3月4日火曜日

FizzBuzz

前回のエントリで文字列を表示するために使った方法の応用編です

      1 #include <stdio.h>
      2
      3 int main(void) {
      4     int end = 36, i;
      5
      6     for (i = 1; i <= end; i++)
      7     printf("%d\n\0  Fizz\n\0Buzz\n\0FizzBuzz\n" + ((!(i % 3) + !(i % 5) * 2)) * 6, i);
      8
      9     return(0);
     10 }

そこそこトリッキーなコードですが、いちおうFizzBuzzとして正常に動作します

C言語の文字列がわかる人ならおそらく説明は必要ないでしょう
また若干ポインタの理解も要求されます

ただ全体的に見てそんなに難しい使い方ではないでしょう

年月日から曜日を計算

西暦と月日から曜日を計算する方法です

      1 #include <stdio.h>
      2
      3 int main(void) {
      4     const char str[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
      5     int y, m, d, Y, C, h;
      6
      7     y = 2014;
      8     m = 3;
      9     d = 4;
     10
     11     if (m == 1) { m = 13; y--; }
     12     if (m == 2) { m = 14; y--; }
     13
     14     C = y / 100;
     15     Y = y % 100;
     16
     17     h = (d + ((26 * (m + 1)) / 10) + Y + (Y / 4) + 5 * C + (C / 4)) % 7;
     18
     19     printf("%d\n", h);
     20     printf("%s\n", str + (((h + 6) % 7) * 4));
     21
     22     return(0);
     23 }

計算をしているのは7行目から17行目までです

strの文字列は曜日を表示するためのもので、ifやswitchを書くのが面倒なときに使う方法です

まずymdに年月日を代入します

そして1月と2月の場合は年をデクリメントしてから12を加算します
今回は1月と2月の判断をifで1個1個処理したので、加算ではなく代入で行いました

次に西暦を上2ケタと下2ケタに分割します
あとは計算式に代入するだけです

実はこの計算式 ページによって式が違っていたりします
結果が同じならどれを使ってもいいのですけども

最後にprintfで表示します
出力される値が土曜日を0として日曜が1 金曜が6 という感じなので少し扱いづらいので、hに6を加算してから7のあまりを取る という方法で日曜を0に移動しています
この曜日を移動するコードは17行目に追加するとスマートに実装できます


このコードを音声認識で起動してGoogle Glassに表示するアプリをかけばケンジ君ごっこができます
ちなみにこれはモジュラ演算ではなく ツェラーの公式という名前です
モジュラはおそらくModularのことで、これは合同式を意味します
これは17行目最後に出てくる%記号の演算処理のことで、あまりを計算しています


上の画像はWikipediaから拾ってきた式ですが
2つ目の式と3つ目の式のmodがModularの意味です
例えばyが2014だった場合 Y = y mod 100 つまりyを100で割ったあまりで、14が入ることになります

この計算式を何に使うのかは正直微妙なところですが、組み込みシステムなどで1秒に1回インクリメントするカウンタを前回のエントリで日付に直してからこの式で曜日を計算して液晶に表示  とかできるかもしれません(ちょっと苦しい)

秒から時分秒への変換 及び年月日時分秒への変換

秒から時分秒や年月日時分秒への変換について
後者はいわゆるUNIX時間を実際の年月日に変換する方法について

時分秒への変換は非常に楽です
void Sec2Time(uint32_t Second, Time_t *time) {
    time->Hour = Second / 3600; Second %= 3600;
    time->Min  = Second / 60;
    time->Sec  = Second % 60;
}
関数名とブロックを除けば3行になります
非常に簡単な処理なので、説明の必要も無いでしょう


次に年月日への変換ですが、これは簡単に実装することができません
数学的にちゃんと考えれば可能かもしれませんが 僕は思いつきませんでした。。(考えるのをやめたとも言う)

なぜ計算で簡単にできないかと言うと、年をまたぐ時間処理はうるう年を考慮に入れる必要があるからです
うるう年ではない場合の1年は31536000秒ですが、うるう年の場合は31622400秒となり、その頻度も「4年に1度」など簡単な処理では対応できないからです

Cでうるう年を判定するためのコードを示します(Wikipediaより)
#define IS_LEAP(year) ((!((year) % 4) && ((year) % 100)) || !((year) % 400))
うるう年の場合は真 うるう年でない場合は偽として展開されます

また
#define TIMEZONE_OFFSET (32400)
という定義も追加しておきます
これはタイムゾーンの時間で、経度が東の場合は正の値 西の場合は負の値になります
今回は日本で使うことを想定し9*60*60の値を設定しました
これを定義ではなくグローバル変数で扱えば動作中に任意のタイムゾーンを設定することもできます
タイムゾーンの設定を0に もしくは処理そのものを削除した場合はUTCとして動作します


関数は
void Sec2DateTime(uint32_t Second, DateTime_t *datetime)
のような感じになっています
第一引数に秒数を与え、第二引数に変換後の値を入れるための構造体のポインタを設定します
この構造体はYear,Mon,Day,Hour,Min,Secの6個のメンバがあります
すべての型は符号なし8bitです
ただし関数の引数を32bitではなく、64bit等で実装する場合はYearを16bitや32bitとして実装する必要があります


さて、実際のコードになるわけですが、まずはローカル変数を1つ準備しておきます
uint8_t IsDec = 0;
これはうるう年の処理に使用する変数です 1bitで十分ですが、最小単位が8bitなのでこの型を使用しました

最初の処理として
    datetime->Year = 0;
    Second += TIMEZONE_OFFSET;
を実行します
これは年の初期化と、タイムゾーンの設定です
年以外の月日時分秒はその都度値を代入しているため、初期化する必要はありません
またタイムゾーンの処理はこの箇所でのみ行っています

次に年の計算ですが
これは上記の通り面倒なのでwhileのループで行っています
    while (1) {
        if (!IS_LEAP(datetime->Year + 1970)) {
            if (Second < 31536000) { break; }
            Second -= 31536000;
        } else {
            if (Second < 31622400) { break; }
            Second -= 31622400;
        }  
        datetime->Year++;
    }
まずうるう年か否かを判定し、その結果にそって秒を判定します
その年1年分にみたない場合はbreakで処理を抜け、十分に時間があるなら1年分の時間を引いてから年をインクリメントします
年は代入ではなくインクリメントで行うため、最初に初期化する必要があるわけです

そして
    uint16_t Days = Second / 86400;
    Second %= 86400;
のコードで秒を日に変換します
秒は1日にみたない時間にします

その後で
    if (Days > (31 + 28) && IS_LEAP(datetime->Year + 1970)) { Days--; IsDec = 1; }
の処理を通します
これによりうるう年でかつ2月29日以降の場合は1日減算されます

次に月の変換です
         if (Days >= 334) { datetime->Mon = 12; Days -= 334; }
    else if (Days >= 304) { datetime->Mon = 11; Days -= 304; }
    else if (Days >= 273) { datetime->Mon = 10; Days -= 273; }
    else if (Days >= 243) { datetime->Mon = 9;  Days -= 243; }
    else if (Days >= 212) { datetime->Mon = 8;  Days -= 212; }
    else if (Days >= 181) { datetime->Mon = 7;  Days -= 181; }
    else if (Days >= 151) { datetime->Mon = 6;  Days -= 151; }
    else if (Days >= 120) { datetime->Mon = 5;  Days -= 120; }
    else if (Days >=  90) { datetime->Mon = 4;  Days -=  90; }
    else if (Days >=  59) { datetime->Mon = 3;  Days -=  59; }
    else if (Days >=  31) { datetime->Mon = 2;  Days -=  31; } 
    else                  { datetime->Mon = 1;               }
月はこのようにif分岐で行います
1月1日から11月30日までの日数以上の時間があるなら12月として
1月1日から10月31日までの日数以上の時間があるなら11月として のように判定します
先ほどうるう年の場合は1日減算したのはこの判定をするためです
そして日は適切に減算しておきます

その後
    datetime->Day  = Days + IsDec + 1;
で日を代入します
うるう年の場合はIsDecが1 それ以外は0なので うるう年でかつ2月29日以降の場合は1が加算されます
また日は0からではなく1からなので、常に1を加算します

残りは最初の関数と同じことをするだけなので簡単です
    datetime->Hour = Second / 3600; Second %= 3600;
    datetime->Min  = Second / 60;
    datetime->Sec  = Second % 60;

これですべての計算は終わりです
なお実際に使用する場合には年に1970を加算して表示してやります
構造体のYearを16bitにする場合はYearの初期化を0ではなく1970で行い、IS_LEAPで1970を加算せずに行うのもいいでしょう

他にも、このコードはまだ最適化の余地が残っているので、そのあたりをいじってみるのもいいと思います

ついでに書くと、この関数は入力が符号なしということからも分かる通り、1970年1月1日より前の時間については計算していません


なお、このコードでいくつかの時間を変換し、正常に値が出力されることを確認していますが、確実に動作するとは限らないことをご了承ください


最後に、細切れではない関数を書いておきます
void Sec2DateTime(uint32_t Second, DateTime_t *datetime) {
    uint8_t IsDec = 0;

    datetime->Year = 0;
    Second += TIMEZONE_OFFSET;

    while (1) {
        if (!IS_LEAP(datetime->Year + 1970)) {
            if (Second < 31536000) { break; }
            Second -= 31536000;
        } else {
            if (Second < 31622400) { break; }
            Second -= 31622400;
        }   
        datetime->Year++;
    }   

    uint16_t Days = Second / 86400;
    Second %= 86400;

    if (Days > (31 + 28) && IS_LEAP(datetime->Year + 1970)) { Days--; IsDec = 1; }

         if (Days >= 334) { datetime->Mon = 12; Days -= 334; }
    else if (Days >= 304) { datetime->Mon = 11; Days -= 304; }
    else if (Days >= 273) { datetime->Mon = 10; Days -= 273; }
    else if (Days >= 243) { datetime->Mon = 9;  Days -= 243; }
    else if (Days >= 212) { datetime->Mon = 8;  Days -= 212; }
    else if (Days >= 181) { datetime->Mon = 7;  Days -= 181; }
    else if (Days >= 151) { datetime->Mon = 6;  Days -= 151; }
    else if (Days >= 120) { datetime->Mon = 5;  Days -= 120; }
    else if (Days >=  90) { datetime->Mon = 4;  Days -=  90; }
    else if (Days >=  59) { datetime->Mon = 3;  Days -=  59; }
    else if (Days >=  31) { datetime->Mon = 2;  Days -=  31; } 
    else                  { datetime->Mon = 1;               }   

    datetime->Day  = Days + IsDec + 1;
    datetime->Hour = Second / 3600; Second %= 3600;
    datetime->Min  = Second / 60;
    datetime->Sec  = Second % 60;
}



2013年7月22日月曜日

Cで固定小数点

C語で固定小数点の文字列化です
string.h関数が必要になります 使ってるのはstrlenだけなので適当に実装してください
関数は2つあります
strAddは再帰を使ってます
場合によっては30回以上再帰します

以下コード

2012年11月4日日曜日

整数を文字列に変換する関数

整数を文字列に変換する関数を作ってみました

通常はsprintfとかでも十分ですが
今作ってる機材でprintf系の関数が使えない事態(ROMとかの問題ではない)が発生したので
後学のためにも作ってみました


微妙に高性能なitoa という感じです
簡単な動作確認はしましたが正常に動く保障はないです
多少高性能な事によるメリットよりも安定性が低いデメリットのほうが上回る程度です


2012年10月31日水曜日

MAX31855Kの計算

MAXIM製の熱電対IC MAX31855K というICがあります

これはK熱電対の温度を計算してデジタル値で取り出せるというIC
冷接点の計算もおおよそやってくれるので
楽に扱うことができます
I/FはSPIっぽい物(一方通行のSPI)なので
SPIモジュールがあるならそれを使えるし
ない場合でもGPIOを操作するだけでいいので簡単です

ICとしては特にクセは無いようなので
ハードウェアの詳細は省略します
とりあえずCSを立ち下げてクロックを32個送りながら読み取って最後にCS立ち上げれば大丈夫です

スイッチサイエンスやストロベリーリナックスで買うことができますが
スイッチサイエンスで僕が買ったものは良い感じに動いてくれませんでした

ストロベリーリナックスで買った物は正常に動いたので

付属の熱電対の相性や微妙なパターンの違いが影響しているようです


さて、このICは固定小数点で出力されます
なので何らかの方法で計算する必要があります
って別にヘンなフォーマットなわけではないのでどうということも無いですが

とりあえず僕が使ったテストコードを置いておきます
VC++2010で作りました
全角文字使ってるので多くの場合は書き換えが必要です


#include <stdio.h>

int MAX31855K_calc(unsigned int 出力値, double *熱電対, double *冷接点) {
    unsigned int 熱電対値 = (出力値 >> 18) & 0x3FFF;
    unsigned int 冷接点値 = (出力値 >>  4) &  0xFFF;
   
    if (出力値 & 0x10000) { return(出力値 & 0x7); }

    if (熱電対値 & 0x2000)
    { *熱電対 = ((熱電対値 ^ 0x3FFF) + 1) * -0.25; }
    else { *熱電対 = 熱電対値 * 0.25; }

    if (冷接点値 &  0x800)
    { *冷接点 = ((冷接点値 ^  0xFFF) + 1) * -0.0625; }
    else { *冷接点 = 冷接点値 * 0.0625; }

    return(0);
}

int main(void) {
    unsigned int 出力値 = 0xFEA4E670; // ICから読み取った32bitデータ
    double 熱電対 = 0, 冷接点 = 0;
    int s;

    s = MAX31855K_calc(出力値, &熱電対, &冷接点);

    printf("%d:%.2f/%.5f\n", s, 熱電対, 冷接点);

    return(0);
}



ビットシフトやXOR等を使っています
出力に浮動小数点を使ってるので
それなりの環境じゃないと満足な結果は得られません

動作としては
まずFaultBitを確認して エラーが発生していればデータ下位3ビットを返します
障害がなければ熱電対と冷接点の温度を取り出し
符号を確認して必要ならビット反転等を処理してから
適切な値を掛けてポインタで指示された変数に結果を入れます
そして戻り値0で戻るだけ

もしも戻り値が0位外ならばその旨を表示するべきです
戻り値&0b001 != 0なら熱電対を検出できていない
戻り値&0b010 != 0なら熱電対がGNDに短絡している
戻り値&0b100 != 0なら熱電対がVCCに短絡している
という感じになります

これは戻り値を見る必要はなく データの下位3bitを比較するだけでもいいです


しっかし ソースコードに全角文字って気持ち悪いね
「全角使える環境ならヘタに変数名考えるより漢字割り当てたほうが楽」
ってことで試してみたけど