» 電子工作のブログ記事

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

先日作成した変換基板に部品を実装しました。

が……

too-long-width
幅wwwwwwwwwwwwwwww

技適マーク付きの Wi-Fi モジュールを買ってきました。

ただコイツ、2.54mm ピッチでも何でもないので、ブレッドボードで遊ぶためには変換基板が必要です。
ちょっと探してみたところそれ単体で売ってはいないみたいです。
2015/09/11 追記: すみません、ありました。
なので、さくっと作っちゃいましょう。

make-pwb-01
余っている感光基板を見つけました。
100mm × 150mm は少し大きすぎるかなとは思いましたが、変換基盤をたくさん作ることにしました。

make-pwb-02
アートワークはこれ (左)。
Wi-Fi モジュール (中央) の変換基板だけではどうかと思ったので、秋月八潮店限定で売っていた SRAM チップ (右) の変換基板も一緒に作りました。
表面実装のアートワークとはんだ付けの練習がてらという考えです。

さて、ここまではいいのですが、少し問題があります。
まず感光基板。
これの製造年月日が……

make-pwb-03
3年前ですw
また、エッチング液も最後に使用してから 1 年以上は経っていると思います。
うまくいかない前提で作業を開始します。

make-pwb-04
露光環境。
洗濯機の上ですw

make-pwb-05
露光開始。
経過年月を考えて、今回は 45 分と長めに露光することにします。

…………

45 分経ちました。

make-pwb-06
現像液を作ります。
以前は現像剤をすべて使って 200ml の現像液を作っていたのですが、なんとなくもったいないから、また、廃液処理用の酢が残り少ないため、半分の量で作ることにしました。

現像してみると……

make-pwb-07
あらやだ、うまく像ができてるじゃん♪

しかし、ここからもまた問題です。

make-pwb-08
あきらかに悪くなっているエッチング液。
真っ黒だな、おい……w

時間をかけてエッチングすると……

make-pwb-09
キタ (゚∀゚) コレ

ちょっとゴミが残っていそうですが、マイナスドライバーとかで削れば何とかなりそうです。
ちなみに、エッチングのバットに入らなかったので、事前に半分にカットしました。

make-pwb-10
最終工程として、感光剤を取り除くために露光します。

…………

今度は 50 分ほど待ちました。
先ほど使用した現像液に漬けて、過剰なほど現像します (現像っていうか、感光剤を除去する目的ですから)。

make-pwb-11
上手に焼けました~♪

make-pwb-12
細いパターンも痩せずに残っています。

というわけで、失敗前提で作業したのですが、何の問題もなく作成できました。
Wi-Fi モジュールと SRAM チップのひとつずつ使えればいいかな、と思っていたのですが、思わぬ大収穫ですw

なお、サンハヤトさんは感光基板の品質保証期限を 1 年間としています。
この制作事例は「私はうまくいった」という記事にすぎません。
なので、古い基板やエッチング液を使って作業しても成功の保証はどこにもないことに留意してください。

さて、最後に。

make-pwb-13
廃液を処理します。
現像液と同量の酢を投入します。
このあと感光剤が析出したら、ろ過して廃棄します。

このような処理をせず、そのまま下水に流してしまうことは環境保全の観点から断じて許される行為ではありません。
廃液を適切に処理しない人は地球から出て行ってください。

自然放電実験、一週間ほど経ちました。

残電圧

のこり 3.5V。 けっこう持ちますなぁ。

ほーら、ね。 1か月以上も放置してる。
ネタはいくつかあったんですが、投稿が面倒になってましてねw

それはそうと。
最近、電子工作に再々々……ハマりしまして、いろいろ組んでみてます。

いま、電気二重層コンデンサ (別名: スーパーキャパシタ) ってどれくらい持つんだろう、と自然放電の実験なんぞをしております。

テスター棒を箸のように

ふと思ったのですが、テスター棒を箸のように持つのって、日本人特有なものなんでしょうかね?w