» プログラミングのブログ記事

いま、野球が好きな友人のために投球スピードを測る装置を作っています。
スピードガンは高いので代わりとなる装置を自作してみていますw
その過程を動画にしていますので、よかったら見ていってください。

んで、そのプログラムのソースコードを以下に掲載します。
疑問点等ありましたら、このブログか動画のコメント欄でご質問いただければ、可能な限り回答いたします。

/*
 * psmts01.cpp
 *
 * Created: 2021/06/03 23:29:02
 *  Author: piske
 */ 

// CPU の速度を指定します。
#define F_CPU 16000000UL

// ライブラリをインクルードします。
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay_basic.h>
#include <util/delay.h>

// Arduino の millis() の仕組みから転用 ここから
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)

volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;

ISR(TIMER0_OVF_vect)
{
    // copy these to local variables so they can be stored in registers
    // (volatile variables must be read from memory on every access)
    unsigned long m = timer0_millis;
    unsigned char f = timer0_fract;
    
    m += MILLIS_INC;
    f += FRACT_INC;
    if (f >= FRACT_MAX) {
        f -= FRACT_MAX;
        m += 1;
    }
    
    timer0_fract = f;
    timer0_millis = m;
    timer0_overflow_count++;
}

unsigned long millis()
{
    unsigned long m;
    uint8_t oldSREG = SREG;
    
    // disable interrupts while we read timer0_millis or we might get an
    // inconsistent value (e.g. in the middle of a write to timer0_millis)
    cli();
    m = timer0_millis;
    SREG = oldSREG;
    
    return m;
}
// 転用ここまで

// ビット操作のマクロ定義
#define sbi(PORT,BIT) PORT |= _BV(BIT)
#define cbi(PORT,BIT) PORT &=~_BV(BIT)

// 設定距離の EEPROM アドレス
#define EEPROM_DISTANCE 0x0000

// 押下継続フラグ
#define PRESS_DISTANCE_DOWN 0
#define PRESS_DISTANCE_UP 1
#define PRESS_MEASURE 2

// 関数プロトタイプの定義
void show7Seg(unsigned char digit, unsigned char pattern);

// 7 セグメント点灯パターンの定義
unsigned char segments[] = {0b11110011,
                            0b01100000,
                            0b11010101,
                            0b11110100,
                            0b01100110,
                            0b10110110,
                            0b10110111,
                            0b11100010,
                            0b11110111,
                            0b11110110};

// 7 セグメント点灯パターンの定義 (配線間違えていないバージョン)
//unsigned char segments[] = {0b11111100,
//                              0b01100000,
//                              0b11011010,
//                              0b11110010,
//                              0b01100110,
//                              0b10110110,
//                              0b10111110,
//                              0b11100100,
//                              0b11111110,
//                              0b11110110};

// メイン関数
int main(void)
{
    unsigned char distance;        // 設定距離
    unsigned short speed;        // 測定速度
    unsigned long pitchTime;    // 投球時間
    unsigned char pressFlg;        // 押下継続フラグ
    
    // ポート方向と内部プルアップを設定します。
    DDRB = 0b00111111;
    PORTB = 0b11000000;
    DDRD = 0b00000000;
    PORTD = 0b01000000;
    
    // タイマーの設定をします。
    TIMSK = (1<<TOIE0);
    TCCR0A = (0<<COM0A1) | (0<<COM0A0) | (0<<COM0B1) | (0<<COM0B0) | (0<<WGM01) | (0<<WGM00);
    TCCR0B = (0<<WGM02) | (0<<CS02) | (1<<CS01) | (1<<CS00);
    
    sei();
    
    // 設定距離を EEPROM から読み込みます。
    eeprom_busy_wait();
    distance = eeprom_read_byte(EEPROM_DISTANCE);
    
    // 設定距離が範囲外の場合、初期値を設定します。
    if (distance < 1 || distance > 99)
    {
        distance = 15;
    }
    
    // グローバル変数を初期化します。
    speed = 0;
    pitchTime = 0;
    pressFlg = 0;
    
    // メイン ループ
    while(1)
    {
        // 設定距離の桁を分けます。
        unsigned char distance2 = distance / 10;
        unsigned char distance1 = distance - (distance2 * 10);
        
        // 設定距離を表示します。
        show7Seg(0, segments[distance1]);
        
        if (distance2 > 0)
        {
            show7Seg(1, segments[distance2]);
        }
        
        
        // 測定速度の桁を分けます。
        unsigned char speed3 = speed / 100;
        unsigned char speed2 = (speed - (speed3 * 100)) / 10;
        unsigned char speed1 = speed - (speed2 * 10) - (speed3 * 100);

        // 測定速度を表示します。
        if (speed1 > 0 || speed2 > 0 || speed3 > 0)
        {
            show7Seg(2, segments[speed1]);    // 手元表示器用
            show7Seg(5, ~segments[speed1]);    // 主表示器用
        }
        
        if (speed2 > 0 || speed3 > 0)
        {
            show7Seg(3, segments[speed2]);    // 手元表示器用
            show7Seg(6, ~segments[speed2]);    // 主表示器用
        }
        
        if (speed3 > 0)
        {
            show7Seg(4, segments[speed3]);    // 手元表示器用
            show7Seg(7, ~segments[speed3]);    // 主表示器用
        }
        
        
        // 設定距離ダウンを検出します。
        if (bit_is_clear(PINB, PB6))
        {
            // 押下継続でない場合
            if (bit_is_clear(pressFlg, PRESS_DISTANCE_DOWN)) {
                // 設定距離が 1m より大きい場合、1m ダウンします。
                if (distance > 1)
                {
                    distance--;
                    
                    // 設定距離を EEPROM に保存します。
                    eeprom_busy_wait();
                    eeprom_write_byte(EEPROM_DISTANCE, distance);
                }
                
                // 押下継続フラグを立てます。
                sbi(pressFlg, PRESS_DISTANCE_DOWN);
                
                // 100マイクロ秒待ちます (チャタリング対策)。
                _delay_us(100);
            }
        }
        else
        {
            // 押下継続フラグを落とします。
            cbi(pressFlg, PRESS_DISTANCE_DOWN);
            
            // 100マイクロ秒待ちます (チャタリング対策)。
            _delay_us(100);
        }
        
        
        // 設定距離アップを検出します。
        if (bit_is_clear(PINB, PB7))
        {
            // 押下継続でない場合
            if (bit_is_clear(pressFlg, PRESS_DISTANCE_UP)) {
                // 設定距離が 99m より小さい場合、1m アップします。
                if (distance < 99)
                {
                    distance++;
                    
                    // 設定距離を EEPROM に保存します。
                    eeprom_busy_wait();
                    eeprom_write_byte(EEPROM_DISTANCE, distance);
                }
                
                // 押下継続フラグを立てます。
                sbi(pressFlg, PRESS_DISTANCE_UP);
                
                // 100マイクロ秒待ちます (チャタリング対策)。
                _delay_us(100);
            }
        }
        else
        {
            // 押下継続フラグを落とします。
            cbi(pressFlg, PRESS_DISTANCE_UP);
            
            // 100マイクロ秒待ちます (チャタリング対策)。
            _delay_us(100);
        }
        
        
        // 測定ボタンを検出します。
        if (bit_is_clear(PIND, PD6))
        {
            // 押下継続でない場合
            if (bit_is_clear(pressFlg, PRESS_MEASURE)) {
                if (pitchTime == 0)
                {
                    // 測定中でない場合、測定速度をクリアし、投球時間を保持します。
                    speed = 0;
                    pitchTime = millis();
                }
                else
                {
                    // 測定中の場合、現在の時間 (捕球時間) と投球時間の差から測定速度を求めます。
                    speed = (double) distance * 3600 / (millis() - pitchTime);
                    
                    // 測定速度が 999km/h を超える場合、999km/h に設定します。
                    if (speed > 999)
                    {
                        speed = 999;
                    }
                    
                    // 投球時間に 0 を設定します (測定中でないことを表します)。
                    pitchTime = 0;
                }
                
                // 押下継続フラグを立てます。
                sbi(pressFlg, PRESS_MEASURE);
                
                // 100マイクロ秒待ちます (チャタリング対策)。
                _delay_us(100);
            }
        }
        else
        {
            // 押下継続フラグを落とします。
            cbi(pressFlg, PRESS_MEASURE);
            
            // 100マイクロ秒待ちます (チャタリング対策)。
            _delay_us(100);
        }
    }
}

// 7 セグメント表示器に数字を表示します。
void show7Seg(unsigned char digit, unsigned char pattern)
{
    unsigned char i;
    
    // シフト レジスタにパターンを送り込みます。
    for (i = 0; i < 8; i++)
    {
        if ((pattern & 1) == 0)
        {
            sbi(PORTB, PB5);
        }
        
        sbi(PORTB, PB4);
        cbi(PORTB, PB4);
        cbi(PORTB, PB5);
        
        pattern = pattern >> 1;
    }
    
    // ライン デコーダに値を設定します。
    for (i = 0; i < 3; i++)
    {
        if ((digit >> i & 1) == 1)
        {
            sbi(PORTB, PB3 - i);
        }
        else
        {
            cbi(PORTB, PB3 - i);
        }
    }
    
    // ライン デコーダのイネーブルをオンにし、100マイクロ秒待ちます。
    sbi(PORTB, PB0);
    _delay_us(100);
    
    // ライン デコーダのイネーブルをオフにします。
    cbi(PORTB, PB0);
}

自分の日記を読み返してみて、少し恥ずかしくなりました。
5年前の記事で偉そうにに語っていた件です。

「天ぷら」変数の名前を「tempura」と表記するのが正しいと主張していましたが、もっといい例がありました。

「店舗コード」変数

です。

この変数に名前をつけるとき、以下のような揺らぎが発生します。

・tenpo_cd
・tempo_cd
・shop_cd
・store_cd
・tenpo_code
・tempo_code
・shop_code
・store_code

要は、「店舗」を「tenpo」にするのか「tempo」にするのか「shop」にするのか「store」にするのか。
また、「コード」を「cd」にするのか「code」にするのかで上記のパターンが生まれるわけです。

こうなってくると個人の感性の問題としか言いようがないのではないでしょうか。
店舗を「shop」か「store」のどちらかに訳すかなんて、最たる例だと思います。
しかも、「店舗」を「tempo」としてしまうと、「音楽のテンポ」を想起してしまうのが問題となりそうです。

結局は、信頼できるドキュメントを横に置いておいてコピペするのが最強なのではないでしょうかw

他社 (他者) が作ったソースに手を加えるときに、非常にイライラすることがあります。

たとえば論理名で「種別」というフィールド名があったとします。
私ならそれを英訳したエンティティ名をつけます。

 ・type

ただ、このままだと VB.net など一部の言語の予約キーワードとなってしまうので、接頭辞をつけたりはします。
(商品種別の場合、item_type や itemType など)

そのままローマ字化する人が多くいますが、

 ・syubetsu
 ・shubetsu
 ・syubetu
 ・shubetu

ご覧のとおり、4種類もの揺らぎが発生してしまいます。
sy と sh の揺らぎは仕方ない (でもできるだけ sh にしてほしい……と思ってます) としても、tsu と tu の揺らぎは私個人的には許せません。
なぜならば、「つ」は「tu」で入力することができますが、「tu」は「てゅ」と読まれてしまうからです。
(「syubetu」と書いた場合「しゅべてゅ」と読まれてしまいます。 非常にかっこ悪いですね)

同様に、chi と ti の揺らぎも許せません。
「Tiba pref.」は「てぃば県」です。

つまり何が言いたいのかというと……
ローマ字化日本語を変数名に使うのはある意味仕方がないとは思うのですが、せめて可読な形にしてほしいということです。
前出の「tiba」は、普段そのように入力している方であればすぐに「千葉」と読めるかと思われますが、「ち」を「chi」と入力する人はすぐにはピンとこないのではないでしょうか。

そのような理由もあり、私はできるだけローマ字化日本語をエンティティ名や変数名に含めることを避けております。

余談ですが、じゃあ「天ぷら」とか「新聞」とかはどうするんだ、ということになると思います。
これらは、可読な形で表記すると「tempura」と「shimbun」となります。
「新聞」は迷わず「news_paper」としますが、「天ぷら」は迷いますね。
私ならおそらく「tenpura」にしてしまうでしょう。
……ていうか天ぷらなんて変数名付けねえよw

オナ禁タイマーのソリューション エクスプローラ

うーん、古い!

オナ禁タイマーのソースを見直してみた感想です。

文字列関係だけで言っても
 ・TEXT() マクロで切られてたり、そうでなかったり。
 ・lstr~系の関数が _s じゃなかったり。
 ・保存されている文字列が UNICODE じゃなかったり。

こりゃー、時間かかりそうだな……w