0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ボタンを押すとランダムな色にLEDが光るプログラムをAIと作ってみた~物理ノイズとM系列乱数、 パワーダウンモードを使った省電力デバイスの作成

0
Posted at

子供はくじ引きが大好きで、自分でくじ引きの箱を作ってしまうほどです。
そんなくじ引き好きの子供のために、3色LEDとAttiny13Aを使ってボタンを押すたびにランダムな色にLEDが点灯するデバイスを作ってみました。
今は2026年なので、Geminiにコーディングを担当してもらいました。


プロジェクト概要

ATTiny13Aを使用し、3色LEDを「じわっと」光らせる3ビット(8通り)の電子サイコロ。単なる擬似乱数ではなく、ADCの浮動電圧(環境ノイズ)をエントロピーとして取り込み、M系列(LFSR)を撹拌することで高いランダム性を実現しています。

1. ハードウェア仕様

  • MCU: ATTiny13A (1.2MHz 内蔵RCオシレータ)
  • 出力: PB0, PB3, PB4 (3色LED / ソフトウェアPWM制御)
  • 入力: PB1 (プッシュボタン / 内部プルアップ)
  • エントロピー源: PB2 (浮動ピン / ADC入力)
  • 電源: パワーダウンモードを活用し、待機電流を極限まで抑制。

2. ソフトウェアの特徴

① 物理ノイズによるエントロピー蓄積

setup_seed() 関数では、どこにも接続されていないPB2ピンの電位をADCで読み取ります。

  • 飽和回避: ADC値が 0 または 1023(VCC/GNDに張り付き)の時は、有効なノイズが得られるまでサンプリングをリトライします。
  • ビット反転: 読み取った値の下位1ビットを用いて、既存のシード(lfsr_state)をXORで反転させます。これにより、前回の状態を活かしつつ物理的な不確実性を累積させます。

② M系列(LFSR)擬似乱数

8ビットの線形帰還シフトレジスタ(多項式: $x^8 + x^6 + x^5 + x^4 + 1$)を採用。

  • 計算負荷が極めて低く、1KBのメモリ制限下で効率的に乱数列を生成します。
  • スリープ復帰ごとに物理ノイズで状態をかき混ぜるため、決定論的な周期性を打破しています。

③ 輝度補正付きソフトウェアPWM

3つのピンをソフトウェア制御することで、滑らかなフェードイン・フェードアウトを実現。

  • 比視感度補正: 赤(R)に対して明るく見えやすい緑(G)と青(B)の最大輝度を係数(SCALE_G, SCALE_B)で制限し、視覚的なバランスを整えています。

④ 鉄壁のチャタリング・誤動作対策

  • PCINT(ピン変化割り込み)の特性を考慮し、スリープ直前に GIFR(割り込みフラグ)を強制クリア。
  • 「ボタンを離すまで待つ」+「ウェイト」の組み合わせにより、ボタン操作1回につき確実に1回だけ演出が実行されるよう制御。

3. ジャックポット演出

抽選結果が 000(通常は消灯)となった場合、特別な「虹色(カラーサイクリング)」演出を実行。RGBの各値を位相をずらして制御し、ゲーミングデバイスのような滑らかな色変化を提供します。

#define F_CPU 1200000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

#define LED_MASK ((1 << PB4) | (1 << PB3) | (1 << PB0))
#define BUTTON_PIN PB1
// --- 輝度調整用パラメータ (0.0〜1.0 のイメージで調整) ---
// 赤(PB0)はそのまま、緑(PB3)と青(PB4)を絞る
#define SCALE_R  255  // 赤はフルパワー
#define SCALE_G  70  // 緑は少し控えめに (0-255で指定)
#define SCALE_B  140  // 青も控えめに (0-255で指定)

// M系列用の状態変数(0以外で初期化が必要)
uint8_t lfsr_state = 0x01; 
uint8_t r_val, g_val, b_val;
uint8_t seed = 0;
int delay_ms=2;

// ADCを使って浮動ピン(PB2)からノイズを読み取りシードを作る
// ADCを使って浮動ピン(PB2)からノイズを読み取り、エントロピーを蓄積する
void setup_seed() {
    ADMUX = (1 << MUX0); // ADC1 (PB2) 選択
    ADCSRA = (1 << ADEN) | (1 << ADPS1) | (1 << ADPS0); 

    // 8ビット分(1バイト分)の有効なエントロピーが溜まるまで繰り返す
    for (uint8_t i = 0; i < 8; i++) {
        uint16_t val;
        
        while (1) {
            ADCSRA |= (1 << ADSC); // 変換開始
            while (ADCSRA & (1 << ADSC)); // 完了待ち
            val = ADC;

            // 飽和チェック: 0(GND付近) または 1023(VCC付近) でなければ採用
            if (val > 0 && val < 1023) {
                break; // 有効なノイズが得られたのでループ脱出
            }
            
            // 飽和している場合は少し待ってからリトライ
            _delay_us(100);
        }

        // ADCの下位1ビットが 1 の場合、現在のビットを反転(XOR)
        if (val & 0x01) {
            lfsr_state ^= (1 << i);
        }
        
        _delay_us(200); // サンプリング間隔を空けて独立性を高める
    }

    ADCSRA &= ~(1 << ADEN); // 終了後、省電力のためADC停止
}

// 8ビットM系列擬似乱数生成器
uint8_t get_lfsr_random() {
    // 多項式: x^8 + x^6 + x^5 + x^4 + 1
    uint8_t bit = ((lfsr_state >> 0) ^ (lfsr_state >> 2) ^ (lfsr_state >> 3) ^ (lfsr_state >> 4)) & 0x01;
    lfsr_state = (lfsr_state >> 1) | (bit << 7);
    return lfsr_state;
}

void soft_pwm_cycle() {
    for (int i = 0; i < 255; i++) {
        uint8_t port_bits = 0;
        if (i < r_val) port_bits |= (1 << PB0);
        if (i < g_val) port_bits |= (1 << PB3);
        if (i < b_val) port_bits |= (1 << PB4);
        PORTB = (PORTB & ~LED_MASK) | port_bits;
        _delay_loop_1(delay_ms); 
    }
}

void fade_normal(uint8_t val) {
    uint8_t m0 = (val & 0x01);
    uint8_t m3 = (val & 0x02);
    uint8_t m4 = (val & 0x04);
    delay_ms=1;
    for (int16_t i = 0; i <= 255; i += 2) {
        // i(0-255)に対して各色の比率を掛けて上限を抑える
        r_val = m0 ? ((uint16_t)i * SCALE_R / 255) : 0;
        g_val = m3 ? ((uint16_t)i * SCALE_G / 255) : 0;
        b_val = m4 ? ((uint16_t)i * SCALE_B / 255) : 0;
        soft_pwm_cycle();
    }
    //_delay_ms(200);
    for (int16_t i = 255; i >= 0; i -= 2) {
        r_val = m0 ? ((uint16_t)i * SCALE_R / 255) : 0;
        g_val = m3 ? ((uint16_t)i * SCALE_G / 255) : 0;
        b_val = m4 ? ((uint16_t)i * SCALE_B / 255) : 0;
        soft_pwm_cycle();
    }
    
}

void jackpot_rainbow() {
    r_val = 255; g_val = 0; b_val = 0;
    delay_ms=1;
    for (int step = 0; step < 6; step++) {
        for (int i = 0; i < 255; i += 2) {
            if (step == 0) g_val = i; 
            else if (step == 1) r_val = 255 - i;
            else if (step == 2) b_val = i;
            else if (step == 3) g_val = 255 - i;
            else if (step == 4) r_val = i;
            else if (step == 5) b_val = 255 - i;
            soft_pwm_cycle();
            //for (uint8_t r = 0; r < 2; r++) soft_pwm_cycle();
        }
    }
    r_val = g_val = b_val = 0;
    soft_pwm_cycle();
}

void go_to_sleep() {
    GIMSK |= (1 << PCIE);
    PCMSK |= (1 << PCINT1);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    GIFR = (1 << PCIF);
    sleep_enable();
    _delay_ms(50); // チャタリング防止
    sei();
    sleep_cpu();
    sleep_disable();
    cli();
}

ISR(PCINT0_vect) {}

int main(void) {
    DDRB |= LED_MASK;
    DDRB &= ~(1 << BUTTON_PIN);
    PORTB |= (1 << BUTTON_PIN);

    while (lfsr_state == 0) {
        setup_seed();
    }
    setup_seed(); // 起動時に一度だけ浮動電圧からシード生成
    setup_seed();
    setup_seed();

    while (1) {
      // --- 1. スリープに入る前にボタンが離れるのを待つ ---
        // これを入れないと、離した瞬間の電圧変化でまた目覚めてしまいます
        _delay_ms(50); // チャタリング防止
        while (!(PINB & (1 << BUTTON_PIN))){
          _delay_ms(50); // 離した直後のノイズも待つ
        }
        
        go_to_sleep();
        
        while (!(PINB & (1 << BUTTON_PIN))){ // チャタリング・長押し対策
          _delay_ms(50);
        }

        uint8_t rnd = get_lfsr_random(); // M系列から取得
        uint8_t result = rnd & 0x07;    // 下位3ビットを使用

        if (result == 0) {
            jackpot_rainbow();
        } else {
            fade_normal(result);
        }
        setup_seed();
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?