![]() |
15 |
PICによるLEDドライバボードの設計 (8) |
Posts Tagged with "EAGLE"
既に発行済みのブログであっても適宜修正・追加することがあります。We may make changes and additions to blogs already published.
![]() |
11 |
PICによるLEDドライバボードの設計 (7) |
![]() |
Claudeに外部仕様を書いてもらったものを以下に示します。
NY_night LED コントローラ 外部仕様
PIC12F1501 による 2ch LED 調光基板(SMD / JLCPCB PCBA 版)
1. 概要
3Dペーパー製ライトボックスの LED モジュール 2 系統を、PWM で独立に調光する。 モードはタクトスイッチ 1 個で巡回切替。消灯時はマイコンが SLEEP に入る。
| 項目 | 仕様 |
|---|---|
| 制御 IC | PIC12F1501(内蔵 4MHz) |
| 出力 ch 数 | 2(L / R) |
| 出力形式 | N-ch MOSFET ローサイドスイッチ |
| PWM 周波数 | 15.6 kHz |
| 調光分解能 | 8 bit(0〜255) |
| 操作系 | モーメンタリスイッチ ×1 |
2. 電源仕様
| 項目 | 値 |
|---|---|
| 動作電圧 | 3.0 〜 5.5 V |
| 想定電源 | 単4 ×3本(4.5V 公称)/ USB・AC アダプタ 5V |
| 動作電流(基板のみ) | 約 1 mA(LED 電流を除く) |
| 待機電流(SLEEP) | µA オーダー |
| 書込み時の最低電圧 | 2.7 V |
- 電源電圧が変わっても LED の明るさ以外の特性は変化しない。MOSFET のオン抵抗は Vgs = 2.5V でも 48 mΩ で、100 mA 通電時の電圧降下は 5 mV。
- 電池と外部アダプタを同時に接続しないこと。 アダプタから電池へ逆流する。電源切替は基板外で行う(スイッチ付き DC ジャック等)。
- 電圧低下時はブラウンアウトリセットで安全停止する(動作中のみ有効、SLEEP 中は無効)。
3. 外部接続
基板右辺に φ1.0 mm スルーホールのワイヤパッドが 6 個、3.81 mm 等ピッチで 1 列に並ぶ。
| 位置 | 名称 | 内容 |
|---|---|---|
| 上から 1 | VDD | 電源入力 + |
| 上から 2 | GND | 電源入力 − |
| 上から 3 | L_N | 左 ch LED カソード側(MOSFET ドレイン) |
| 上から 4 | L_P | 左 ch LED アノード側(VDD に内部接続) |
| 上から 5 | R_N | 右 ch LED カソード側(MOSFET ドレイン) |
| 上から 6 | R_P | 右 ch LED アノード側(VDD に内部接続) |
電源ペアが上、LED 2 系統が下という並び。各ペアが隣接しているので 2 本組で配線できる。
LED 出力の条件
- LED モジュールは X_P と X_N の間に接続する。
- 基板側に電流制限素子は無い。 LED モジュール内部に抵抗が含まれていること。含まれない裸の LED を直結すると破損する。
- 1 ch あたりの最大電流は 1 A(配線幅 0.4 mm / 1oz 銅による制限)。MOSFET 自体の定格は 5.7 A。
- ローサイドスイッチのため、LED のアノード側は常に VDD 電位。カソード側が接地・開放される。
4. ユーザ操作
基板裏面に実装されたロングステムのタクトスイッチ 1 個。PCB を台紙に取り付け、台紙を貫通させて裏側から押す。
モード遷移
ボタンを離した瞬間に次のモードへ進む。
電源投入 → [1] 交互ゆらぎ 押す → [2] 全点灯 押す → [3] 交互点滅 押す → [0] 消灯(SLEEP) 押す → [1] 交互ゆらぎ …以下巡回
| モード | 動作 |
|---|---|
| 1 交互ゆらぎ | L と R が逆位相の台形波でフェード。2 秒ランプ + 両端 600 ms ホールド、全周期 約 5.2 秒。デューティ範囲 5〜250 / 255 |
| 2 全点灯 | 両 ch フルオン |
| 3 交互点滅 | L と R が 300 ms ごとに交互に点灯(片方点灯中はもう片方消灯) |
| 0 消灯 | 両 ch オフ、マイコン SLEEP |
- 電源投入時はモード 1 から始まる。
- ボタン応答は全モードで 10 ms 以内。チャタリング除去は 30 ms。
- SLEEP からの復帰は短いタップでも確実に反応する。復帰時は必ずモード 1 に入る。
5. 書込みインタフェース
基板上辺の 1×6、2.54 mm ピッチのスルーホール(JP1)に、ピンヘッダを直立で常設実装する。
| ピン | 信号 |
|---|---|
| 1 | MCLR / VPP |
| 2 | VDD |
| 3 | VSS |
| 4 | ICSPDAT (RA0) |
| 5 | ICSPCLK (RA1) |
| 6 | N.C. |
MCLRE = OFFかつLVP = OFFのため、高電圧プログラミング(VPP 約 9V)に対応した書込み器が必要(PICkit 4 で実績あり)。- 書込みは基板側電源(電池等)を接続したまま、書込み器のターゲット給電を OFF にして行う。LED モジュール接続状態でツール給電すると過負荷になる。JP1 の 2 番ピンは電圧検出に使うため接続したままにする。
- SLEEP 中でも書込み可能(VPP 印加でリセットがかかる)。
6. 機構仕様
| 項目 | 値 |
|---|---|
| 基板外形 | 23.7 × 24.7 mm |
| 層構成 | 2 層(表: VDD ベタ + 信号 / 裏: GND ベタ) |
| 板厚 | 1.6 mm |
| 表面の最大部品高さ | 2.5 mm(C2、1210 MLCC)。ほかに JP1 ピンヘッダ(約 9 mm)が立つ |
| 裏面 | タクトスイッチのみ(ロングステム) |
| 取付穴 | 無し |
- 固定は両面テープ。PCB 裏面を台紙表面に接着する(取付穴は使わない設計)。
- 台紙には SW1 本体が入る貫通穴(6×6 mm より大きめ)を開け、SW1 本体を穴に沈めて PCB 面で接着する。SW1 上面だけで接着すると不安定になるため不可。
- SW1 の選定条件: 全高(本体 + ステム)が「両面テープ厚 + 台紙厚 + 押下ストローク 0.25 mm」を超えること。すなわち ステム長 ≥ 両面テープ厚 + 台紙厚 + 0.25 mm − 本体高さ。
- 表面の周囲 1.0 mm 以上は銅箔なし。
7. 組立順序
- JLCPCB より基板到着(表面 SMD 10 点実装済み)
- JP1 にピンヘッダをはんだ付け(常設)
- ICSP でファーム書込み
- 最後に SW1 を裏面にはんだ付け
SW1 を先に付けるとステムが干渉して基板が平らに置けず、以降の裏面作業ができなくなる。
8. 制約事項
- 基板・LED モジュールとも電流制限素子を持たない(実測: 5V・デューティ 255 で 240 mA、上昇傾向)。このためファームウェアが全モード共通のデューティ上限 DUTY_LIMIT(既定 150/255、平均約 140 mA)で制限している。上限値はソース冒頭の #define で変更可。
- 逆接続保護が無い。VDD / GND を逆に繋ぐと破損する。
- 電池とアダプタの同時接続は不可。
- モード状態は電源断で失われる。再投入時は必ずモード 1。
![]() |
10 |
PICによるLEDドライバボードの設計 (5) |
![]() |
JLCPCBのGerber Viewerによる図です。PCBを5枚だけでなく、PCBAも2枚依頼したので、以下は部品も実装した図となっています。

図1103.1 Gerber Viewer
製造はいつものようにJLCPCBに依頼し、待ちが0.8日間、PCBは0.8日間(5枚で2 USD)、PCBAは1.1日間(2枚で21.6 USD)、配送は2.9日(8.92 USD)でした。 最終的に8月4日夜に発注して、8月10日にPCBを香港からの航空便で受け取れたので、オーダーから5.7日(32.52 USD)という感じでした。23.7 × 24.7 mmのPCBです。

図1103.2 完成PCB
図1103.3に、2ch(L-ch/R-ch)がモード1で交互にPWM動作をしている図を示します。光の強さが交互に増減するモードです。

図1103.3 PWM動作
![]() |
8 |
PICによるLEDドライバボードの設計 (5) |
![]() |
Geminiに設計してもらったプログラムをClaudeに修正してもらったものを以下に示します。
/* ============================================================
* NY_night -- PIC12F1501 LED driver
* rev.13: mode 3 changed from simultaneous to L/R alternating blink
*
* 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 alt-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 16000000
/* ---- timing, all expressed in 10 ms ticks ---- */
#define TICK_MS 10
#define FADE_TICKS 7 /* 70 ms per fade step (30 steps = 2.1 s) */
#define BLINK_TICKS 30 /* 300 ms per blink phase */
#define DEBOUNCE_TICKS 3 /* 30 ms settle time */
#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 HOLD_TICKS 60 /* 600 ms dwell at each end (trapezoid) */
/* Perceptual (gamma 2.0) ramp: duty = DUTY_LIMIT * (i / FADE_STEPS)^2.
* Human brightness perception is roughly logarithmic, so a linear duty
* ramp looks flat at the top and collapses abruptly near zero. The
* squared table spends most of its steps in the low-duty region where
* the eye actually sees the change. Regenerate if DUTY_LIMIT changes. */
#define FADE_STEPS 30
static const uint8_t fade_tab[FADE_STEPS + 1] = {
0, 0, 1, 2, 3, 4, 6, 8,
11, 14, 17, 20, 24, 28, 33, 38,
43, 48, 54, 60, 67, 73, 81, 88,
96, 104, 113, 122, 131, 140, 150
};
/* ---- PWM frequency exploration ----------------------------------
* The module's flicker-IC white LEDs are progressively suppressed as
* the PWM frequency rises (3.9 kHz: strong ~10 Hz flicker,
* 15.6 kHz: mostly suppressed). To let the flicker live, choose the
* frequency with the Timer2 prescaler alone; clock and all tick
* timing stay untouched. PR2 = 255 in every case.
*
* T2CON_RUN prescale PWM freq
* 0x04 1:1 15.625 kHz (= rev.9)
* 0x05 1:4 3.906 kHz (= original board feel)
* 0x06 1:16 977 Hz
* 0x07 1:64 244 Hz (floor; below this the PWM
* itself becomes visible)
* ---------------------------------------------------------------- */
#define T2CON_RUN 0x04
#define PR2_VAL 255
#define SET_L(v) (PWM1DCH = (uint8_t)(v)) /* duty ratio = v/256 */
#define SET_R(v) (PWM3DCH = (uint8_t)(v))
#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)
{
SET_L(0);
SET_R(0);
PWM1DCL = 0;
PWM3DCL = 0;
PWM1CON = 0x00;
PWM3CON = 0x00;
T2CONbits.TMR2ON = 0;
LATA = 0x00;
}
static void outputs_on(void)
{
LATA = 0x00;
SET_L(0);
SET_R(0);
PWM1DCL = 0; /* POR value undefined -> would leave
* ~0.3% duty even at DCH = 0 */
PWM3DCL = 0;
PR2 = PR2_VAL;
T2CON = T2CON_RUN; /* prescale select + TMR2ON */
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 = 0x7A; /* 16 MHz internal -> PWM 15.6 kHz.
* Raised from 4 MHz / 3.9 kHz: a white
* LED in the module flickers ~10 Hz
* irregularly, suspected self-flasher IC
* resetting on the 3.9 kHz chopped supply */
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 */
outputs_on(); /* PWM up, both channels at duty 0 */
/* ---------------- state ---------------- */
uint8_t mode = 1; /* 0:sleep 1:fade 2:steady 3:blink */
uint8_t fi = 0; /* index into fade_tab, 0..FADE_STEPS */
int8_t step = 1; /* ramp direction, +1 / -1 */
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);
fi = 0;
step = 1;
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;
fi = 0;
step = 1;
fade_t = 0;
hold_t = 0;
blink_t = 0;
blink_on = 1;
break;
case 1: /* alternating fade, trapezoid,
* perceptual (gamma) ramp */
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 (fi < FADE_STEPS) {
fi++;
if (fi >= FADE_STEPS) hold_t = HOLD_TICKS;
} else {
step = -1;
}
} else {
if (fi > 0) {
fi--;
if (fi == 0) hold_t = HOLD_TICKS;
} else {
step = 1;
}
}
}
/* both channels follow the same perceptual curve, mirrored */
SET_L(fade_tab[fi]);
SET_R(fade_tab[FADE_STEPS - fi]);
break;
case 2: /* steady on */
SET_L(DUTY_LIMIT);
SET_R(DUTY_LIMIT);
break;
case 3: /* alternating blink: L and R
* swap every BLINK_TICKS */
if (++blink_t >= BLINK_TICKS) {
blink_t = 0;
blink_on ^= 1;
}
if (blink_on) { SET_L(DUTY_LIMIT); SET_R(0); }
else { SET_L(0); SET_R(DUTY_LIMIT); }
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 で見積もれる。
![]() |
7 |
PICによるLEDドライバボードの設計 (4) |
![]() |
Studio 500 3D Pop Up Wall Artの修理の続きです。前回のボードは基本的に動作したので、SMD化の改版を行います。改版はAIに手伝ってもらいます。まず製品の外観です。
次にAIに改版してもらった論理図です。部品はSMDタイプを選択させました。電子スイッチは2SCを使用していましたが、CMOSに変更しています。
プルダウン抵抗を100kΩにしたのは、駆動時の無駄電流を抑えるためです。RA2/RA4がHighのとき、470Ωを通してゲートに5Vがかかり、100kΩ経由でGNDへ50µAが流れ続けます。2チャンネルで100µA。単4電池駆動なので、ここは小さいほうが望ましい。
一方でリーク電流に対する引き込み能力としては100kΩで十分です。AO3400Aのゲートリークは最大100nA程度なので、100kΩなら10µVしか持ち上がりません。
なお470Ω(R1/R2)ゲート容量への突入電流を制限してPICのポートを保護し、スイッチングのリンギングを抑えます。100kΩとの分圧比は99.5%なので、オン時のゲート電圧はほぼVDDそのままです。
なおAIの部品配置は配線が省略され適当なので、部品間の配線は人手で接続しています。
次にレイアウト図を示します。人手で部品を配置し、基本的にはEAGLEで自動配線しました。ただし電流が大きいところは太さを変えています。
出来上がりをClaudeにみせたところ、背面GNDと前面GNDの間のVIAが少なく、前面GNDパッド位置でVIAを打ち、背面の大きなGNDプレーンにコンタクトすべきとのことで、VIAを追加しました。今回はSMDなので、前面にVDDとGNDが混在するように変更されたためです。
PCBサイズは23.7 × 24.7 mmとなりました。SMD化により旧ボードと比べて▲53%となっています。
![]() |
29 |
PICによるLEDドライバボードの設計 (3) |
![]() |
Geminiに設計してもらったプログラムを以下に示します。
// --- コンフィギュレーション設定(マイコンの基本動作設定) ---
#pragma config FOSC = INTOSC // 内蔵クロックを使用
#pragma config WDTE = OFF // ウォッチドッグタイマーOFF
#pragma config PWRTE = ON // パワーアップタイマーON
#pragma config MCLRE = OFF // 4番ピンをリセットに使わずGPIO(RA3)として使う
#pragma config CP = OFF // コードプロテクトOFF
#pragma config BOREN = ON // 電圧低下で安全停止
#pragma config CLKOUTEN = OFF // クロック出力OFF
#pragma config LVP = OFF // 低電圧プログラミングOFF(ICSP動作を安定させる)
#include <xc.h>
// 動作クロックの定義(__delay_msの計算に必要)
#define _XTAL_FREQ 4000000
void main(void) {
// --- マイコンの初期化 ---
OSCCON = 0x6A; // 内部クロック4MHzに設定
ANSELA = 0x00; // 全ピンをデジタルモードに設定(スイッチ入力に必須)
TRISA = 0b00100000; // RA5(2番ピン)を入力、他を出力に設定
WPUA = 0b00100000; // RA5を内部プルアップ設定
OPTION_REGbits.nWPUEN = 0; // 内部プルアップ機能を有効化
// PWM(調光)の初期設定
PWM1CON = 0xC0; // PWM1(5番ピン/RA2)を有効化
PWM3CON = 0xC0; // PWM3(3番ピン/RA4)を有効化
T2CON = 0x04; // Timer2 ON, プリスケーラ1:1
PR2 = 255; // PWMの周期設定
// 制御用変数の宣言
unsigned char mode = 1; // 0:消灯, 1:交互ゆらぎ, 2:全点灯, 3:点滅
unsigned char duty_L = 0; // 左側の明るさ用変数
signed char direction = 5; // 明るさの変化量(プラスで明るく、マイナスで暗く)
while(1) {
// --- モード切り替えスイッチ判定(2番ピンをGNDに落とすと反応) ---
if (RA5 == 0) {
__delay_ms(20); // チャタリング防止
if (RA5 == 0) { // 押し続けられているか再確認
while(RA5 == 0); // 指を離すまで待機
__delay_ms(20); // 離した後の安定待ち
mode++; // モードを1つ進める
if (mode > 3) mode = 0; // モード0〜3を循環
// モード切替時に明るさ変数をリセット
duty_L = 0;
direction = 5;
}
}
// --- 各モードの動作実行 ---
switch(mode) {
case 0: // 【全消灯】
PWM1DCH = 0;
PWM3DCH = 0;
break;
case 1: // 【アナログ交互ゆらぎ】★初期起動モード
PWM1DCH = duty_L; // 5番ピン(L)を出力
PWM3DCH = (unsigned char)(255 - duty_L); // 3番ピン(R)は逆位相で出力
// 明るさを変化させる
if (direction > 0) {
if (duty_L < 250) {
duty_L += 5;
} else {
direction = -5; // 上限で反転
}
} else {
if (duty_L > 5) {
duty_L -= 5;
} else {
direction = 5; // 下限で反転
}
}
__delay_ms(40); // 変化のスピード調整(ニューヨークの夜景風)
break;
case 2: // 【全点灯】
PWM1DCH = 255;
PWM3DCH = 255;
break;
case 3: // 【同時点滅】
PWM1DCH = 255;
PWM3DCH = 255;
__delay_ms(300);
PWM1DCH = 0;
PWM3DCH = 0;
__delay_ms(300);
break;
}
__delay_ms(10); // ループの安定用ウェイト
}
}
コンパイル(build)は金槌のアイコンをクリックします。PICへの書き込みはダウンロードのような下向き矢印アイコンをクリックします。読み出しはアップロードのような上向き矢印アイコンをクリックします。書き込んだPICかみ書き込みかは読みだすとわかります。以下はプログラムしたPICをhexで読みだしたファイルの中身です。
:020000040000FA
:100000000128FE2BFF3FFF3FFF3FFF3FFF3FFF3F2A
:10001000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FF0
:10002000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FE0
:10003000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FD0
:10004000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FC0
:
:10061000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FEA
:10062000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FDA
:10063000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FCA
:10064000FF3FFF3FFF3FFF3FFF3FFF3FFF3FFF3FBA
:10065000FF3FFF3FF501F31F2E2B2F2B362BF20907
:10066000F309F20A0319F30A0130F500F11F392BDF
:100670003A2B3F2BF009F109F00A0319F10A70082F
:1006800071040319442B452B6C2B0130F4004F2BC4
:100690000130F035F10D890B492B0130F407F11FC2
:1006A000522B532B482B71087302031D592B7008D2
:1006B0007202031C5C2B5D2B612B7008F202710827
:1006C000F33B0130F136F00C890B622B0130F40260
:1006D000031D6B2B6C2B532B75080319702B712B7F
:1006E000762BF209F309F20A0319F30A7308F100F1
:1006F0007208F0000800203021008C0023008C01DB
:10070000203024008C0021009513043020009C0030
:10071000FF309B00C0302C009300C0309900F801DE
:10072000F90120008C1A952B962BED2B0330F0004D
:100730000030F1007808013EF2000030793DF3000E
:1007400083312A2383317108F9007008F8000230E0
:10075000F7008630F6009930890BAC2BF60BAC2BEA
:10076000F70BAC2B000020008C1EB72BB82BB32B43
:10077000ED2BFF302C009200FF309800912BFA01F6
:100780007A082C0092007A0998000D30F600FC30AF
:10079000890BC82BF60BC82B0130FA077A0A031D08
:1007A000D22BD32BC02BFF30FA007A082C009200FA
:1007B0007A0998000D30F600FC30890BDD2BF60B22
:1007C000DD2B0130FA027A08031DE72BE82BD52B2D
:1007D000912B2C0092019801912B7908003A031972
:1007E000F22B912B7808003A0319B92B013A03191F
:1007F000BF2B033A0319E92B912B012820007B2BF7
:020000040001F9
:04000E00843FFF3FED
:08000000FF3FFF3FFF3FFF3F00
:00000001FF
![]() |
28 |
PICによるLEDドライバボードの設計 (2) |
![]() |
![]() |
26 |
PICによるLEDドライバボードの設計 |
![]() |
Studio 500 3D Pop Up Wall Artの修理をすることになりました。LEDが点滅しなくなったので、点滅回路を作成します。

まずPICを用いて点滅シーケンスを再現します。EAGLEで作成した回路図は以下のようになります。上部のコネクタはin-circuitでPICプログラミングするためのコネクタです。

以下にレイアウト図を示します。

![]() |
15 |
FM-7 ROM吸出し器の改版 (3) |
![]() |
PICの開発環境
早速、開発環境とCコンパイラをインストールし、PICプログラマを購入しました。
- 開発環境: MPLAB X IDE https://wak-tech.com/archives/51←無償、この記事のとおりにインストール
- Cコンパイラ: https://www.microchip.com/mplab/compilers←無償、ここで入手
- Microchip製プログラマPicKit4 ¥5,500 (秋月)

図258.1 PICマイコンプログラマ
対象となるPICマイコンは、秋月電子でも入手可能なPIC12F1501-I/Pとします。
- PICマイコン PIC12F1501-I/P @¥70 (秋月)

Eagleによる設計
PICマイコンをプログラマブルタイマーとして使用した回路図V5を図258.3に示します。回路は多いようですが、ほとんどがコネクタであり、Arduinoが1個と今回追加した8pinのICが全てです。他はZ80コネクタ、ロジアナ用ピンヘッダ、PICプログラム用コネクタから構成されます。


![]() |
13 |
FM-7 ROM吸出し器の改版 |
![]() |
FM-7 ROM吸出し器にDRAMリフレッシュ回路を追加
過去記事において、表記のボードを開発しましたが、DRAMのリフレッシュを失念していました。そこで、前回のオールソフトウエア(?)回路に対して、ハードウエアでリフレッシュ回路を追加します。バイトアクセスはオールソフトウエアで可能なことを実証できたのですが、DRAMリフレッシュは2msec以内に128カラムアドレスにアクセスが必要、という制約があるため、ソフトウエアでは厳しいと思われます。従って、ハードウエアで構成しますが、タイミング回路なので意外に回路規模を必要とします。せっかくArduino 1個で済ませたので、最小の回路構成としたいところです。
アナログタイマーIC 555
最初に思いついたのは小型のタイマーICである555です。ところがこれはアナログICであり、時定数設定のためにRやCを複数必要とします。さらに、リフレッシュ周期だけでなくリフレッシュパルス幅を確保しようとすると、555の2個入りを使用する必要があるだけでなく、外付けCRも倍の数になり、シンプルになりません。設計してみたものの嫌になりました。

プログラマブルタイマーPIC
そこで、プログラマブルタイマーで検討したところ、PICマイコンを使用すれば、開発環境その他は必要となりますが、8pinのIC1個で行けそうです。デジタルのほうがシンプルで、かつ調整も無くて好きです。

PICのリフレッシュアルゴリズム
リフレッシュアルゴリズムは以下のように考えています。
- 6809が\$FD05に1を書き、Z80W信号をLにします(Z80W=$6809/\overline{\text{Z80}}$)。
- ArduinoはPICからのバス権の要求RFREQが無ければ、RFGNTをLにしてバスを獲得します。バスアクセスはQB/EBをアサートすることで行います(RFGNT=$\text{PIC}/\overline{\text{Arduino}}$)。
- Z80W信号がLの際に、PICから周期的にRFREQをHとしてバス権要求をArduinoに通知します。
- Arduinoはバスアクセス中であれば終了後にバス権を放し、PICにRFGNTをHとして通知します。
- PICはRFGNTがHであればリフレッシュ可能と判断し、*REFCKをLとします。
- バス調停のオーバヘッドを削減するため、PICは*REFCKは4回アサートします。その代わり、リフレッシュ周期は1/4の16KHzとなります。
- リフレッシュが終了すると、PICはRFREQをLにします。
- ArduinoはPICがバス権を放したと判断してRFGNTをLにしてバスを獲得します。

ページ:






