いま、野球が好きな友人のために投球スピードを測る装置を作っています。
スピードガンは高いので代わりとなる装置を自作してみています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);
}

トラックバック

このブログ記事に対するトラックバックURL:

コメント & トラックバック

No comments yet.

Comment feed

コメントする