はじめに
CH55xでPIC-Writerが出来れば長年続いた鶏卵問題が解決するなぁと漠然と思っていました。いきなり慣れないハード(ch552pのプログラミング)で知らない(タイミングクリティカルな)CPU(PIC16F747)を焼く新しいソフト(PIC-Writerファーム&アプリ)をスクラッチで書くと言うのはハードルが高いです。Arduino UNO(ATmega328P)はUSBネイティブじゃないんで色々と問題アリですが先人達の道標も多く確実性を優先し枯れているarduino unoをチョイスします。どこの御家庭でも必ず何個か転がってますしプロトタイプにArduinoというのは鉄板中の鉄板ですね。まずは先人達が書いてくれたPIC-WriterのソースとPIC16F747のデーターシートと照らし合わせてインプリメントしてプリミティブレイヤーとテスト用ルーチンを書いて足掛かりにしてみます。
ライターの配線

https://ww1.microchip.com/downloads/en/DeviceDoc/30492a.pdf
| Arduino UNO | PIC16F747 | ICSP役割 |
|---|---|---|
| D8 (PB0) | PGD (Pin 40) | データ(双方向) |
| D9 (PB1) | PGC (Pin 39) | クロック |
| D10 (PB2) | VPP (Pin 1) | VPP電圧制御(12.6V) |
| D11 (PB3) | VDD (Pin 20) | VDD制御(5.0V) |
上の写真にもある通りブレッドボード(I35~I37)に2SA1015を1石刺してハイサイド負論理スイッチでVDD制御しています。先人の中にはデジタルアウトをターゲットのVDDに直結している輩もいますしFETでキレイに制御されている方もいらっしゃいます。110円のCH552Pが惜しいですが高くて5VコントロールのFETが買えないので5円の2SA1015をチョイスしました。ちなみにPIC16F747はVDD先で入れっぱなしでも消去、書き込み、読み込みできました。僕はUSBを入れたままターゲットの付け外ししたいんで僕はVDD制御付けてます。面倒ならVDDは5V直結アリです。いろいろイジッてるうちに付けたんで好みですね。VPPは12VレギュレーターのGNDにダイオード(1N5819)を2段噛ませて実測で12.64Vでした。書き込み時にPIC16F747の1ピン実測が12.64Vなのでスイッチで電圧ロスは気にしてません。データーシートの最初のページの右上にVPP=12.75~13.25Vが要求されています。コレで消去、書き込み、読み込みできたのでヨシとしています。ちなみに定電圧電源でVPP=12.0Vでも数回の負荷テスト程度ではエラー無く焼けてました。定格を守ってないし成り行きOKで作ってるんで気持ち悪い人はちゃんとした回路を組んでください。そのうち気が向いたらPWMでチャージポンプ回路でも作ろうかと思います。ブレッドボード(B列)のLEDは動作確認(DEBUG)用なんてそのうち気が向いたら外しちゃいます。PIC18F2550で似せPICkit2を作った時にも書きましたが適切なVPPの確保とスイッチング(今回は2SC1815と2SA1015の2石と抵抗3本でハイサイドスイッチを組んでみました)さえ出来ればPIC-Writerの9割は完成です。
ライターのプログラム
/*
PIC16F747 Single-purpose ICSP writer
Yet Another PIc wriTER
Author: 2ru
*/
#include <Arduino.h>
#define debug 0
#define VDD_IS_NEGATIVE_LOGIC 1 // // 2SA1015を1石でハイサイド負論理スイッチしている
// Hardware layer
// ===== ピン定義 =====
// Arduino → PIC16F747 ICSP役割
#define PGD_BIT 0 // D8 (PB0) PGD (Pin 40) データ(双方向)
#define PGC_BIT 1 // D9 (PB1) PGC (Pin 39) クロック
#define VPP_BIT 2 // D10 (PB2) VPP (Pin 1) VPP電圧制御(12.6V)
#define VDD_BIT 3 // D11 (PB3) VDD (Pin 20) VDD制御(5.0V)
#define PGD_MASK (1 << PGD_BIT)
#define PGC_MASK (1 << PGC_BIT)
#define VPP_MASK (1 << VPP_BIT)
#define VDD_MASK (1 << VDD_BIT)
#define PORT PORTB
#define DDR DDRB
#define PIN PINB
/////////////// ここからprimitve layer
/////////////// ポーティング毎に書き換える必要がある
// 単純に各PINをオンオフするmacro
inline void PGC_H() { PORT |= PGC_MASK; }
inline void PGC_L() { PORT &= ~PGC_MASK; }
inline void PGD_H() { PORT |= PGD_MASK; }
inline void PGD_L() { PORT &= ~PGD_MASK; }
inline void VPP_H() { PORT |= VPP_MASK; } // VPPは2SA1015+2SC1815の2石で
inline void VPP_L() { PORT &= ~VPP_MASK; } // High Voltegeを正論理制御
#if VDD_IS_NEGATIVE_LOGIC
inline void VDD_L() { PORT |= VDD_MASK; } // VDD制御を負論理ならHが0でLが1になる
inline void VDD_H() { PORT &= ~VDD_MASK; } // 制御せずVDDを5Vに直結という力業もアリ
#else
inline void VDD_H() { PORT |= VDD_MASK; } // VDD制御を正論理に
inline void VDD_L() { PORT &= ~VDD_MASK; } // する場合はコッチにする
#endif
// 電源投入直後のPIN初期化
void InitPINmode() {
// 各機能ピンを出力にセットする
DDR |= PGD_MASK | PGC_MASK | VPP_MASK | VDD_MASK;
VPP_L();
VDD_L();
PGC_L();
PGD_L();
}
// bit banging. physical layer
void writeBit(byte b) { // Writer->PICに1bit送信
if (b)
PGD_H();
else
PGD_L();
PGC_H();
PGC_L();
}
byte readBit() { // PIC->Writerに1bit受信
PGC_H();
delayMicroseconds(2);
byte r = (PIN & PGD_MASK) ? 1 : 0;
PGC_L();
return r;
}
// PGD-pin-の入力出力を切り替えるルーチン。インラインの必要はないかな
inline void readPINmode() { // PGDピンを入力に切り替える
DDR &= ~PGD_MASK;
PORT &= ~PGD_MASK;
}
inline void writePINmode() { // PGDピンを出力に切り替える
DDR |= PGD_MASK;
}
/////////////// ここまでがprimitve layer
/////////////// 以下は移植しても書き換える必要がない事を希望
// Transmit ICSP commands via bit-banged physical layer signals.
// bit banging physical layerを使ってPICとICSPコマンドで通信する
uint16_t pc;
void ICSPcmd(byte cmd) {
noInterrupts();
for (byte i = 0; i < 6; i++) {
writeBit(cmd & 1);
cmd >>= 1;
}
PGD_L();
interrupts();
}
// 14bitのパラメータをPICに送信
void ICSPwrite(word d) {
noInterrupts();
writeBit(0);
for (byte i = 0; i < 14; i++) {
writeBit(d & 1);
d >>= 1;
}
writeBit(0);
PGD_L();
interrupts();
}
// PICから14bitのデーターを受信
word ICSPread() {
word data = 0;
readPINmode();
noInterrupts();
readBit();
for (byte i = 0; i < 14; i++)
if (readBit())
data |= 1 << i;
readBit();
writePINmode();
PGD_L();
interrupts();
return data;
}
enum ProgrammingModeSequence {
VDD1st,
VPP1st
};
// HVPモード開始
void enterProgrammingMode(byte flag) {
PGD_L();
PGC_L();
switch (flag) {
case VPP1st:
VPP_H(); // VPPを先に立ち上げる
delay(10);
VDD_H();
break;
case VDD1st:
VDD_H(); // VDDを先に立ち上げる
delay(10);
VPP_H();
}
pc = 0; // exitProgrammingMode()でpc=0してれば無くてもいいはず
}
// HVPモード終了
void exitProgrammingMode() {
PGD_L();
PGC_L();
VPP_L(); // 作法としてVPPを先にoffの方が無難
VDD_L();
pc = 0;
}
/////////////// Host-PCとライター間の interface rayer
/////////////// ポーティング毎に書き換える必要がある
#define NextDataReady() Serial.available() // 何バイト受信バッファにあるか?
#define readByte() Serial.read() // 1バイト受信
#define writeByte(b) Serial.write(b) // 1バイト送信
// 上記interface 3関数を使ってワード送受信のラップレイヤー
void writeWord(word w) {
writeByte((byte)(w & 0xFF)); // 下位バイトを先に書き出す
writeByte((byte)((w >> 8) & 0xFF)); // 上位バイト
}
word readWord() {
byte lo, hi;
while (!NextDataReady())
; // pcからの入力を待つ
lo = readByte(); // 先に来たバイトは下位
while (!NextDataReady())
;
hi = readByte();
return (word)lo | ((word)hi << 8);
}
// ===== ICSP コマンド =====
#define LoadConfiguration 0b000000
#define LoadDataforProgramMemory 0b000010
#define ReadDatafromProgramMemory 0b000100
#define IncrementAddress 0b000110
#define BeginProgrammingExternally 0b011000
#define EndProgramming 0b011110
#define BulkEraseProgramMemory 0b001001
#define BulkEraseDataMemory 0b001011
static word tprog; // 書き込み終了を待つ時間。μ秒単位
// Writerインタープリタからディスパッチされる各機能のhandler レイヤー
// 書き込み番地をadrsに設定する
void goAdress(word adrs) {
if (0x2000 <= adrs) {
ICSPcmd(LoadConfiguration);
ICSPwrite(0x0000);
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(0x3FFF);
pc = 0x2000;
} else if (adrs < pc) { // Program memory 巻き戻す必要がある
VPP_L(); // VPPだけ入れなおす。pc=0に戻される
delay(10);
VPP_H(); // HVPに再入
pc = 0;
}
delay(10); // 安定するまで待ってから
while (pc < adrs) { // ConfigでもProgram memoryでadrsまでpcを進める
ICSPcmd(IncrementAddress);
pc++;
}
writeWord(pc); // エコーバック
}
// チップイレース
void ICSP_BulkErase() {
ICSPcmd(LoadConfiguration);
ICSPwrite(0x0000);
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(0x3FFF);
ICSPcmd(BulkEraseProgramMemory);
ICSPcmd(BulkEraseDataMemory);
delay(10);
ICSPcmd(EndProgramming);
}
// 現在のpc番地から1ワードを読みだす
word pic2data(void) {
word data;
ICSPcmd(ReadDatafromProgramMemory);
data=ICSPread();
ICSPcmd(IncrementAddress); pc++; // アドレスをポストインクリメント
return data;
}
// 現在のpc番地からlenワード長を読みだしてホストに転送
void pic2host(int len) {
for (int i = 0; i < len; i++) {
writeWord(pic2data());
}
}
// 現在のpc番地に1ワード書き込む。
void data2pic(word d) {
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(d);
ICSPcmd(BeginProgrammingExternally);
delayMicroseconds(tprog);
ICSPcmd(EndProgramming);
ICSPcmd(IncrementAddress); pc++; // アドレスをポストインクリメント
}
// 現在のpc番地からlenワード長をホストから読んでPICに書き込む
#define host2pic host2pic2
#if host2pic == host2pic1
void host2pic(int len) {
while (len--) {
data2pic(readWord());
}
}
#endif
#if host2pic == hostpic2
/*
書き込み先が奇数番地ならまず1ワード読んで書く
残りが1ワードなら1ワード読んで書いて終了
2ワード以上残っているなら2ワード読む
まだ2ワード以上残ってるなら2ワードの書き込み待ち時間に次の2ワードを読む
残っていないなら書き込むだけ
1ワードだけ余ったなら最後の1ワードを書く
*/
#define RowSize 2
word buf0, buf1;
//
void ProgEnt2PIC(int tprog) {
ICSPcmd(BeginProgrammingExternally);
delayMicroseconds(tprog);
ICSPcmd(EndProgramming);
ICSPcmd(IncrementAddress);
pc++;
}
void host2pic(word len) {
word f = 0, w = 0, e;
if (len == 0)
return;
if (pc & 1) { // 奇数番地始まりなら
// 書き込み開始は必ず偶数番地になるよう1ワードフェッチして書き込み
data2pic(readWord()); // 1ワードをホストから読んでPICに書き込む
len--; // 書き込み全長そのものを1ワード減らすのでfやwは0のまま
} // 偶数番地始まりになるよう仕切り直した
// ここより偶数番地毎に2ワード単位で書き込むルーチン
// ループに先立ち2ワード単位でプリロードする
// あと1ワードしか残ってないならプリロードしない
if (RowSize <= len) {
buf0 = readWord();
buf1 = readWord();
f = 2; // f=RowSize; w=0;
}
e = len & 0b111111111110; // 先行して長さを偶数に丸めておいた方がループで毎回評価するより若干早い
while (f < e) { // このループの処理速度が2ワードリードより遅いとシステムの受信バッファを喰い尽くす
ICSPcmd(LoadDataforProgramMemory); // プリロードされてるデーターを2ワード単位でPICに送る
ICSPwrite(buf0);
ICSPcmd(IncrementAddress);
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(buf1);
// 実際の書き込み作業
ICSPcmd(BeginProgrammingExternally);
buf0 = readWord(); // 待っている時間にプリフェッチする
buf1 = readWord();
delayMicroseconds(tprog); // ボーレートが速くて生焼けにならぬように最低必要な時間を確保する
ICSPcmd(EndProgramming);
ICSPcmd(IncrementAddress); // 奇数番地は焼いた後でポストインクリメントしないどアドレスが狂う
pc += 2; // 常に偶数番地になるはず
f += 2; // 常にw番地より2番地先のはず
w += 2; // ループ終了時は
}
// 直前のループを早回ししたいので条件の変わる最終ブロックを外に出して以下のコードにする
if (RowSize == f - w) { // フェッチとライトで2番地差があるという事はプリフェッチ済みで未書き込みの2ワードが残っている
ICSPcmd(LoadDataforProgramMemory); // プリロードされてるデーターを2ワード単位でPICに送る
ICSPwrite(buf0);
ICSPcmd(IncrementAddress);
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(buf1);
ICSPcmd(BeginProgrammingExternally);
// さっきのループとここが違う
// データーは2ワード残っていないのでプリフェッチしない
// 1ワードだけ残ってる場合はここで処理せず次の後始末パートに任せる
delayMicroseconds(tprog);
ICSPcmd(EndProgramming);
ICSPcmd(IncrementAddress);
w += 2; // 2ワード書いたので2番地進める。フェッチしてないからfは変えない
pc += 2; // 2ワード書いたので2番地進める
}
// ここから後始末パート
// f=wになったはず。それでもlenに到達していないなら端数(=奇数)データーが残ってるのでライトスルーで1ワードずつ書く
while (w < len) { // if(w < len) {
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(readWord());
ICSPcmd(BeginProgrammingExternally);
delayMicroseconds(tprog);
ICSPcmd(EndProgramming);
ICSPcmd(IncrementAddress);
f++;
w++;
pc++;
}
#if 0
//オーバーラン/アンダーランを確認するためのデバックデーターを書き込む
//どこまで読んだかを書き留める
data2pic(0x123); // 見つけやすいように
data2pic(buf0);
data2pic(buf1);
data2pic(f);
data2pic(w);
data2pic(len);
data2pic(0x111); // デバックデーターが最後までチャンと書けているか分かるように
#endif
}
#endif
#if debug
#define BufSiz 16
word len, data;
word buf[BufSiz];
void maketestdata(int len) {
for (int i = 0; i < len; i++) {
buf[i] = pc + i;
}
}
word readHexWord() {
word v = 0;
for (int i = 0; i < 5; i++) {
if (NextDataReady()) {
char c = readByte();
if (('0' <= c) && (c <= '9'))
v = v * 16 + c - '0';
else if (('A' <= c) && (c <= 'F'))
v = v * 16 + c - 'A' + 10;
else if (('a' <= c) && (c <= 'f'))
v = v * 16 + c - 'a' + 10;
else
break;
}
}
return v;
}
// ===== デバック用HEXダンプ =====
const char asc[] = "0123456789ABCDEF";
void write_hex_byte(byte v) {
writeByte(asc[(v >> 4) & 0x0F]);
writeByte(asc[v & 0x0F]);
}
void write_hex_word(word w) {
write_hex_byte((w >> 8) & 0xFF);
write_hex_byte(w & 0xFF);
}
void dumpBuf(int len) {
for (int i = 0; i < len; i++) {
if (((pc - len + i) % 16) == 0) {
writeByte('\r');
writeByte('\n');
write_hex_word(pc - len);
writeByte(':');
}
writeByte(' ');
write_hex_word(buf[i]);
}
}
#define buf2pic buf2pic2 // 通常は2ワード書き込みを選択する
// 一番単純な1ワード毎の書き込み
// プリミティブだが2ワード毎書き込みより遅い
void buf2pic1(int len) {
for (int i = 0; i < len; i++) {
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(buf[i]);
ICSPcmd(BeginProgrammingExternally);
delayMicroseconds(tprog);
ICSPcmd(EndProgramming);
ICSPcmd(IncrementAddress);
pc++;
}
}
// 2ワード毎書き込み
void buf2pic2(int len) {
if (len == 0)
return;
for (int i = 0; i < len; i++) {
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(buf[i]);
if (pc & 1) { // 奇数ワード目(すなわち2ワード毎)なら
ProgEnt2PIC(tprog); // 書き込み命令を発行
}
ICSPcmd(IncrementAddress);
pc++;
}
if (pc & 1) { // 更新後のpcが奇数という事は
// 最後の1ワードはロードだけでき込まれずここにおちてきているはずなので
ProgEnt2PIC(tprog); // 書き込み命令を発行
}
}
void pic2buf(int len) {
for (int i = 0; i < len; i++) {
ICSPcmd(ReadDatafromProgramMemory);
buf[i] = ICSPread();
ICSPcmd(IncrementAddress);
pc++;
}
}
void host2buf(int len) {
writeWord(len);
for (int i = 0; i < len; i++) {
buf[i] = readWord();
}
}
void hex2buf(int len) {
writeWord(len);
for (int i = 0; i < len; i++) {
buf[i] = readHexWord();
}
}
void buf2host(int len) {
for (int i = 0; i < len; i++) {
writeWord(buf[i]);
}
}
const char msg[] = "Yat Another PIc-wriTER\n";
#endif
// Application layer
void interpreter() {
if (!NextDataReady())
return;
switch (readByte()) {
case '{': enterProgrammingMode(VDD1st); break;
case '}': exitProgrammingMode(); break;
case 'E': ICSP_BulkErase(); break;
case 'R': pic2host(readWord()); break;
case 'W': host2pic(readWord()); break;
case 'G': goAdress(readWord()); break;
case 'T': tprog = readWord(); break;
case '(': VDD_H(); break;
case ')': VDD_L(); break;
#if debug
case '\r':
case '\n':
case ' ':
return;
case '[': VPP_H(); break;
case ']': VPP_L(); break;
case 'g': goAdress(readHexWord()); break;
case 'w':
len = readHexWord();
while (len--) {
data = readHexWord();
ICSPcmd(LoadDataforProgramMemory);
ICSPwrite(data);
ICSPcmd(BeginProgrammingExternally);
delayMicroseconds(tprog);
ICSPcmd(EndProgramming);
ICSPcmd(IncrementAddress);
pc++;
}
break;
case 't': // self-test data write
len = readHexWord();
while (BufSiz <= len) {
maketestdata(BufSiz);
buf2pic(BufSiz);
len -= BufSiz;
}
if (0 < len) {
maketestdata(len);
buf2pic(len);
}
break;
case 'r':
goAdress(readHexWord());
len = readHexWord();
while (BufSiz <= len) {
pic2buf(BufSiz);
dumpBuf(BufSiz);
len -= BufSiz;
}
if (0 < len) {
pic2buf(len);
dumpBuf(len);
}
break;
case 'v':
for (int i = 0; msg[i]; i++)
writeByte(msg[i]);
break;
default:
// 未定義コマンド
writeByte('N');
#endif
// writeByte(c); // 正常に終わった場合はコマンドをエコーバックする
}
}
// ===== Arduino Setup =====
void setup() {
InitPINmode();
exitProgrammingMode();
tprog = 300; // 本来は2ms=2000。実測だと270~280μsが限界値だったので経験的に300にした。
Serial.begin(76800); // 300μsだと80k bps付近が限界値。2msなら16k~20k bps程度が限界値
}
void loop() {
interpreter();
}
ホストPCのプログラム
import serial
import time
import argparse
import os
from datetime import datetime
import sys
# =========================
# HEX → セグメント
# =========================
def parse_hex_segments(filename):
mem = {}
with open(filename) as f:
base = 0
for line in f:
if not line.startswith(':'):
continue
length = int(line[1:3], 16)
addr = int(line[3:7], 16)
rectype= int(line[7:9], 16)
data = bytes.fromhex(line[9:9+length*2])
if rectype == 0x00:
full = base + addr
for i in range(0, length, 2):
w = data[i] | (data[i+1] << 8)
a = (full + i) // 2
mem[a] = w
elif rectype == 0x04:
base = int.from_bytes(data, 'big') << 16
return dict_to_segments(mem)
def dict_to_segments(mem):
if not mem:
return [], {}
segs = []
keys = sorted(mem.keys())
start = keys[0]
cur = [mem[start]]
prev = start
for k in keys[1:]:
if k == prev + 1:
cur.append(mem[k])
else:
segs.append((start, cur))
start = k
cur = [mem[k]]
prev = k
segs.append((start, cur))
return segs, mem
# =========================
# PIC Writer
# =========================
class PICWriter:
def __init__(self, port, baud=76800):
self.ser = serial.Serial(port, baud, timeout=1, write_timeout=1)
time.sleep(2)
self.ser.reset_input_buffer()
def send(self, s):
if isinstance(s, str):
s = s.encode()
self.ser.write(s)
def write_word(self, w):
self.ser.write(bytes([w & 0xFF, (w >> 8) & 0xFF]))
def read_word(self):
b = self.ser.read(2)
if len(b) < 2:
raise IOError("timeout")
return b[0] | (b[1] << 8)
def enter(self):
self.send('{')
def exit(self):
self.send('}')
def erase(self):
self.send('E')
def set_tprog(self, t):
print(f"SET TPROG = {t} us")
self.send('T')
self.write_word(t)
def go(self, addr):
self.send('G')
self.write_word(addr)
echo = self.read_word()
print(f"GO={echo:04X}")
def write_block(self, addr, data):
self.go(addr)
start = time.perf_counter()
self.send('W')
self.write_word(len(data))
print(f"write_block() addr={addr:04X} len={len(data):04X}")
for w in data:
self.write_word(w)
end = time.perf_counter()
print(f"{end - start:.6f} 秒")
def read_block(self, addr, length):
self.go(addr)
self.send('R')
self.write_word(length)
data = []
for _ in range(length):
data.append(self.read_word())
return data
# =========================
# 書き込み+検証
# =========================
def program_and_verify(pic, hexfile):
segs, mem = parse_hex_segments(hexfile)
print("ERASE")
pic.enter()
pic.erase()
pic.exit()
print("WRITE START")
pic.enter()
for addr, data in segs:
pic.write_block(addr, data)
pic.exit()
print("WRITE DONE")
print("VERIFY START")
pic.enter()
for addr, data in segs:
read = pic.read_block(addr, len(data))
for i, w in enumerate(data):
if read[i] != w:
print(f"VERIFY ERROR at {addr+i:04X}: {read[i]:04X} != {w:04X}")
pic.exit()
return False
pic.exit()
print("VERIFY OK")
return True
# =========================
# ベリファイのみ
# =========================
def verify_only(pic, hexfile):
segs, mem = parse_hex_segments(hexfile)
print("VERIFY ONLY START")
pic.enter()
for addr, data in segs:
read = pic.read_block(addr, len(data))
for i, w in enumerate(data):
if read[i] != w:
print(f"VERIFY ERROR at {addr+i:04X}: {read[i]:04X} != {w:04X}")
pic.exit()
return False
pic.exit()
print("VERIFY OK")
return True
# =========================
# メイン
# =========================
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", required=True)
parser.add_argument("-w", "--write")
parser.add_argument("-v", "--verify-only")
parser.add_argument("-b", "--baud", type=int, default=76800)
parser.add_argument("-t", "--tprog", type=int)
parser.add_argument("-B", "--background", nargs='?', const=1.0, type=float)
parser.add_argument("-e", "--erase", action="store_true")
parser.add_argument("-r", "--read", nargs='+')
# VDD制御
parser.add_argument("--vdd-on", action="store_true")
parser.add_argument("--vdd-off", action="store_true")
args = parser.parse_args()
pic = PICWriter(args.port, args.baud)
# ===== 単独VDD制御 =====
if args.vdd_on and not args.background:
print("VDD ON")
pic.send('(')
return
if args.vdd_off:
print("VDD OFF")
pic.send(')')
return
# ===== verify only =====
if args.verify_only:
ok = verify_only(pic, args.verify_only)
sys.exit(0 if ok else 1)
# ===== erase only =====
if args.erase and not args.write and not args.read:
print("chip erase")
pic.enter()
pic.erase()
pic.exit()
return
# ===== 書き込み =====
if args.write:
if args.tprog:
pic.enter()
pic.set_tprog(args.tprog)
pic.exit()
# -------- 通常モード --------
if args.background is None:
program_and_verify(pic, args.write)
return
# -------- バックグラウンド --------
interval = args.background
print(f"Background mode start (interval={interval}s)")
last_written_time = 0
while True:
try:
mtime = os.path.getmtime(args.write)
if mtime > last_written_time:
now = datetime.now()
print(now.strftime("update %Y年%m月%d日%H時%M分%S秒"))
# ★ 書き込み前:VDD OFF
pic.send(')')
ok = program_and_verify(pic, args.write)
# ★ 成功時のみ、かつ --vdd-on指定時のみON
if ok and args.vdd_on:
print("VDD ON")
pic.send('(')
if ok:
last_written_time = time.time()
time.sleep(interval)
except KeyboardInterrupt:
print("\nExit background mode")
break
except Exception as e:
print(f"ERROR: {e}")
time.sleep(2)
return
# ===== 読み込み =====
if args.read:
if len(args.read) == 1:
addr = 0
length = int(args.read[0], 0)
elif len(args.read) == 2:
addr = int(args.read[0], 0)
length = int(args.read[1], 0)
else:
print("ERROR")
return
print(f"read addr={addr:04X} len={length:04X}")
pic.enter()
data = pic.read_block(addr, length)
for i in range(0, len(data), 16):
line = data[i:i+16]
print(f"{addr+i:04X}: " +
" ".join(f"{w:04X}" for w in line))
pic.exit()
return
print("No operation specified")
if __name__ == "__main__":
main()
まとめ
焼き方の自主練習としてはこんなもんですね。テストデーターで純粋な物理的書き込み速度も試してみたら4Kword(14bit/1word)を1秒強で焼けます。UARTからランスルーだと経験的に76800bpsは通りますが115200bpsはコケます。書き込み実験でtprog=300μ秒弱が限界値でしたので2ワード毎1回のフラッシュレートとすると4Kワードの書き込み理論値は1秒強です。フロー制御をしてないんでフラッシュしている時間より転送レートが早いとWriterがPCに置いてかれてプロトコルフェーズエラー起こすはずです。本来はリングバッファでも作ってネゴシエーションすりゃいいんだろうけどプロトタイプなんで読み書き消去が出来れば十分です。ライトバックキャッシュしてPCを早めにフリーにしても物理的に焼けなきゃ意味ないしオーバーヘッドを考えれば下手にネゴシエーションするより書きっぱなしでボーレートとかウエイトをハードバインドで調整して結果OK的に動けば自分用のライターなんでヨシです。4ワード読む時間を計測してtprogからロスタイムを減算するとかしてたんですけど、むしろ遅くなるんで削っちゃいました。