Posts Tagged with "Design"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.
posted by sakurai on August 8, 2026 #1102

Geminiに設計してもらったプログラムをClaudeに修正してもらったものを以下に示します。

/* ============================================================
 *  NY_night  --  PIC12F1501 LED driver
 *  rev.5 : global duty cap (LED module has no current limit)
 *
 *  RA2 = PWM1 -> R2 -> T2 gate -> L_N   (Left channel)
 *  RA4 = PWM3 -> R1 -> T1 gate -> R_N   (Right channel)
 *  RA5 = mode button (SW1, to GND, internal pull-up)
 *  RA3 = unused input, brought out to JP1 pin 1 (MCLR/VPP)
 *
 *  Modes: 1 alternating fade -> 2 steady -> 3 blink -> SLEEP -> 1 ...
 *  In sleep the core is stopped; a button press wakes it via IOC.
 * ============================================================ */

#pragma config FOSC     = INTOSC   /* internal oscillator                 */
#pragma config WDTE     = OFF      /* watchdog off                        */
#pragma config PWRTE    = ON       /* power-up timer on                   */
#pragma config MCLRE    = OFF      /* pin 4 is RA3, not MCLR              */
#pragma config CP       = OFF      /* code protect off                    */
#pragma config BOREN    = NSLEEP   /* BOR active when running, off asleep */
#pragma config CLKOUTEN = OFF      /* no clock output                     */
#pragma config BORV     = LO       /* low BOR threshold (battery)         */
#pragma config LPBOR    = OFF      /* low-power BOR off                   */
#pragma config LVP      = OFF      /* HV programming via JP1              */

#include 
#include 

#define _XTAL_FREQ 4000000

/* ---- timing, all expressed in 10 ms ticks ---- */
#define TICK_MS         10
#define FADE_TICKS       4     /*  40 ms per fade step   */
#define BLINK_TICKS     30     /* 300 ms per blink phase */
#define DEBOUNCE_TICKS   3     /*  30 ms settle time     */

#define FADE_STEP        5
#define DUTY_LIMIT     150     /* global duty cap, all modes.
                                * LED module has no current limiting of its
                                * own (measured 240 mA and rising at 5 V,
                                * duty 255).  Average LED current scales
                                * linearly:  I = 240 mA x DUTY_LIMIT / 255. */
#define DUTY_MAX       (DUTY_LIMIT - FADE_STEP)
#define DUTY_MIN         5
#define HOLD_TICKS      60     /* 600 ms dwell at each end (trapezoid) */

#define PWM_L  PWM1DCH         /* RA2, pin 5 */
#define PWM_R  PWM3DCH         /* RA4, pin 3 */

#define SW_PRESSED  (RA5 == 0)

/* ------------------------------------------------------------
 *  Output stage on / off
 *  With the PWM modules disabled the pins revert to port
 *  control; LATA = 0 holds both gates low, and R3/R4 back
 *  that up so the MOSFETs cannot float on.
 * ---------------------------------------------------------- */
static void outputs_off(void)
{
    PWM_L   = 0;
    PWM_R   = 0;
    PWM1CON = 0x00;
    PWM3CON = 0x00;
    T2CONbits.TMR2ON = 0;
    LATA    = 0x00;
}

static void outputs_on(void)
{
    LATA    = 0x00;
    PWM_L   = 0;
    PWM_R   = 0;
    PR2     = 255;
    T2CONbits.TMR2ON = 1;
    PWM1CON = 0xC0;
    PWM3CON = 0xC0;
}

/* ------------------------------------------------------------
 *  Sleep until the button is pressed.
 *  GIE stays 0, so the core resumes at the instruction after
 *  SLEEP() instead of vectoring to an ISR -- no ISR needed.
 * ---------------------------------------------------------- */
static void sleep_until_button(void)
{
    outputs_off();

    IOCAF = 0x00;                  /* clear any stale edge flag  */
    IOCANbits.IOCAN5 = 1;          /* wake on RA5 falling edge   */
    INTCONbits.IOCIE = 1;
    INTCONbits.GIE   = 0;

    SLEEP();
    NOP();

    IOCANbits.IOCAN5 = 0;
    INTCONbits.IOCIE = 0;
    IOCAF = 0x00;

    outputs_on();
}

int main(void)
{
    /* ---------------- initialisation ---------------- */
    OSCCON = 0x6A;             /* 4 MHz internal                        */
    ANSELA = 0x00;             /* all pins digital                      */
    LATA   = 0x00;             /* outputs low before they are enabled   */
    TRISA  = 0b00101000;       /* RA5 = button in, RA3 = input only     */
    WPUA   = 0b00101000;       /* pull up RA5 AND RA3                   */
    OPTION_REGbits.nWPUEN = 0; /* enable weak pull-ups globally         */

    T2CON = 0x04;              /* Timer2 on, prescale 1:1               */
    outputs_on();              /* PWM period 3.9 kHz, both channels 0   */

    /* ---------------- state ---------------- */
    uint8_t mode     = 1;      /* 0:sleep  1:fade  2:steady  3:blink */
    uint8_t duty     = 0;
    int8_t  step     = FADE_STEP;

    uint8_t fade_t   = 0;
    uint8_t hold_t   = 0;      /* >0: dwelling at an end of the ramp */
    uint8_t blink_t  = 0;
    uint8_t blink_on = 1;

    uint8_t sw_raw   = 0;      /* 1 = pressed */
    uint8_t sw_prev  = 0;
    uint8_t sw_state = 0;      /* debounced   */
    uint8_t dbnc     = DEBOUNCE_TICKS;

    while (1) {
        /* -------- button: non-blocking, acts on release -------- */
        sw_raw = SW_PRESSED ? 1 : 0;

        if (sw_raw != sw_prev) {
            dbnc = 0;                       /* level changed, restart timer */
        } else if (dbnc < DEBOUNCE_TICKS) {
            if (++dbnc == DEBOUNCE_TICKS) { /* level has settled            */
                if (sw_state == 1 && sw_raw == 0) {
                    mode = (uint8_t)((mode + 1) & 0x03);
                    duty     = 0;
                    step     = FADE_STEP;
                    fade_t   = 0;
                    hold_t   = 0;
                    blink_t  = 0;
                    blink_on = 1;
                }
                sw_state = sw_raw;
            }
        }
        sw_prev = sw_raw;

        /* -------- mode output -------- */
        switch (mode) {

        case 0:                             /* off -> sleep */
            sleep_until_button();

            /* Woken by a press. Go straight to mode 1 so that even a
             * very short tap responds, then swallow this press so the
             * release does not advance the mode a second time.        */
            mode = 1;
            while (SW_PRESSED) {
                __delay_ms(TICK_MS);
            }
            __delay_ms(30);                 /* release bounce */

            sw_raw = sw_prev = sw_state = 0;
            dbnc     = DEBOUNCE_TICKS;
            duty     = 0;
            step     = FADE_STEP;
            fade_t   = 0;
            hold_t   = 0;
            blink_t  = 0;
            blink_on = 1;
            break;

        case 1:                             /* alternating fade, trapezoid */
            if (hold_t > 0) {
                hold_t--;                   /* dwelling at an end: L off with
                                             * R full on, or the reverse     */
            } else if (++fade_t >= FADE_TICKS) {
                fade_t = 0;
                if (step > 0) {
                    if (duty < DUTY_MAX) {
                        duty = (uint8_t)(duty + FADE_STEP);
                        if (duty >= DUTY_MAX) hold_t = HOLD_TICKS;
                    } else {
                        step = -FADE_STEP;
                    }
                } else {
                    if (duty > DUTY_MIN) {
                        duty = (uint8_t)(duty - FADE_STEP);
                        if (duty <= DUTY_MIN) hold_t = HOLD_TICKS;
                    } else {
                        step = FADE_STEP;
                    }
                }
            }
            PWM_L = duty;
            PWM_R = (uint8_t)(DUTY_LIMIT - duty);
            break;

        case 2:                             /* steady on */
            PWM_L = DUTY_LIMIT;
            PWM_R = DUTY_LIMIT;
            break;

        case 3:                             /* simultaneous blink */
            if (++blink_t >= BLINK_TICKS) {
                blink_t  = 0;
                blink_on ^= 1;
            }
            PWM_L = blink_on ? DUTY_LIMIT : 0;
            PWM_R = blink_on ? DUTY_LIMIT : 0;
            break;
        }

        __delay_ms(TICK_MS);
    }
}

NY_night ファームウェア設計概要(内部設計)

PIC12F1501 / XC8、rev.13(最終版)

モード遷移・操作方法・書込みインタフェースなどの外部仕様は spec.md(NY_night LED コントローラ 外部仕様)を参照。 本書はファームウェアの内部構造と実装判断のみを記す。


ステートマシン

10 ms ティックの単一ループ。ブロッキング待ちを排し、フェード・点滅・ボタン判定を すべてティックカウンタで進める。旧版(Gemini 版)にあった __delay_ms(300)while(RA5 == 0); による CPU 占有を廃した。 これによりボタン応答は全モードで 10 ms 以内。

消灯 = SLEEP(消費電流削減)

消灯中は CPU を停止し、待機電流を µA オーダーに落とす。 RA5 の状態変化割り込み(IOC)で復帰するが、GIE = 0 のままにして ISR を書かない (割り込み許可だけ立てておけば SLEEP から復帰でき、割り込みベクタへ飛ばず SLEEP 命令の次から実行が続く仕様を利用)。

SLEEP 前に PWM モジュールを止め、LATA = 0 でゲートを直接 Low に落とす。 基板側の R3 / R4(100 kΩ プルダウン)が二重の保険となり、 ハイインピーダンス期間中も MOSFET が誤オンしない。

BOREN = NSLEEP により、ブラウンアウト検出は動作中のみ有効・SLEEP 中は無効。 BOR 回路自身の消費(数 µA)を待機時から除く。

ボタン処理

  • 毎ティック非同期サンプリング + 30 ms デバウンス(3 ティック連続一致で確定)。
  • モード遷移は離しエッジで確定し、押しっぱなしでの連続遷移を防ぐ。
  • SLEEP 復帰時は「復帰したこと」自体をモード 1 入りのトリガとし、 その押下は飲み込む。短いタップでも確実に反応し、離しエッジで 二重にモードが進むことも防ぐ。

台形フェードの実装

ランプが上限・下限に到達した瞬間にホールドカウンタ hold_t を仕込み、 それが尽きるまでフェード処理をスキップする。ホールド中もボタン判定は 毎ティック回っており、応答性は変わらない。 L と R は PWM_R = 255 - duty の逆位相なので、L が下限ホールドの間 R は上限ホールドになる。

ピン設定の根拠

ピン 役割 設定
RA2 PWM1 → T2 ゲート → L_N 出力
RA4 PWM3 → T1 ゲート → R_N 出力
RA5 モードボタン 入力、内蔵プルアップ
RA3 未使用(JP1 の MCLR/VPP に接続) 入力、内蔵プルアップで固定

RA3 のプルアップは新基板で必須。旧基板と異なり RA3 は未実装スルーホールに しか繋がらず浮くため、固定しないと CMOS 入力の貫通電流が生じる (WPUA = 0b00101000)。

PWM は Timer2 / PR2 = 255、分解能 8 bit(デューティ比 = v/256)。 周波数は Timer2 プリスケーラ 1 箇所(#define T2CON_RUN)で選択でき、 0x04=15.6 kHz / 0x05=3.9 kHz / 0x06=977 Hz / 0x07=244 Hz。 最終値は 0x04(15.6 kHz)。

経緯: モジュール内の白 LED 数個が PWM 駆動下で約 10 Hz の不規則な チラつきを示した。DC 直結テスト(dctest rev.2)で「非常にちらつく」 ことが確認され、フリッカー IC 内蔵 LED の本来動作と確定。PWM 駆動 (平均 59%・断続給電)では IC が電力不足で本来のフリッカーを出せず、 周波数によって誤動作の出方が変わっていただけだった。全開のフリッカー は不要との判断で、最も抑制される 15.6 kHz を最終仕様とした。

調整パラメータ

ソース冒頭の #define で変更する。時間系はすべて 10 ms 単位。

名称 既定値 意味
FADE_TICKS 4 フェード 1 段の間隔(40 ms)
HOLD_TICKS 60 台形の両端ホールド(600 ms、最大 255)
BLINK_TICKS 30 交互点滅の切替間隔(300 ms)
DEBOUNCE_TICKS 3 デバウンス確定時間(30 ms)
FADE_STEP 5 フェード 1 段のデューティ増分
DUTY_MAX / DUTY_MIN 250 / 5 ランプの上下限

モード 3(交互点滅)の実装

rev.13 で同時点滅から L/R 交互に変更。blink_on のトグルで L=DUTY_LIMIT/R=0 と L=0/R=DUTY_LIMIT を入れ替えるだけで、 常にどちらかが点灯しているため暗転しない。

ビルド

  • XC8 Free モードで Flash 約 30% / RAM 約 27%。PRO ライセンス不要。
  • コンフィグは BOREN = NSLEEP / BORV = LO / LPBOR = OFF を含む (XC8 v2.x で確認済み)。

既知の留意点

  • LED モジュールに電流制限が無いことが実測で確定した (5 V・デューティ 255 で 240 mA、温度上昇に伴う増加傾向)。 rev.5 で全モード共通のデューティ上限 DUTY_LIMIT(既定 150)を導入し、 平均電流を約 140 mA に制限。フェードの上限 DUTY_MAX は DUTY_LIMIT - FADE_STEP として連動し、逆位相側も DUTY_LIMIT - duty で生成。 平均電流は 240 mA x DUTY_LIMIT / 255 で見積もれる。

左矢前のブログ 次のブログ右矢


ページ: