前回の記事 「XIAOで PIC12F1822ライタの製作 (ameblo.jp)」では

SAMD21G18マイコンを使った無印の Seeeduino XIAO を使用しましたが、

今では XIAOシリーズのラインナップが増えたためか、区別するために

Seeeduino XIAO SAMD21 と後ろにマイコン名を付けているようです。

 

いずれにしても  Seeeduino XIAO SAMD21 では

CircuitPythonでスクリプトを作るにはメモリ容量が不足気味でしたので、

今回は RP2040マイコンを使った Seeeduino XIAO RP2040 を使用して

PIC12F1822ライタを製作しました。

 

■ 前回からの変更点

 

マイコン接続

・ MCLRピンの制御を D7から D6に変更

・ ICSPDATAピンの制御を D9(入力)/D10(出力)から D7(入出力切替)に変更

 

スクリプトファイル

・ イメージファイル(image.hex)のパース処理を実装

  → Intel HEXファイルから専用イメージファイルを作成するための

    事前の変換処理は不要になり、Intel HEXファイルを

    そのまま CIRCUITPYドライブにコピーするだけ済むようにした。

・ ベリファイ処理を実装
  → マイコンにファームを書込んだ後に、改めてマイコンから

    読出したファームとイメージファイルの照合を行えるようにした。

・ コマンド実行状態の表示処理を実装

  → 実行しているコマンドの内容に応じて三色LEDの色を変えて

    表示するようにした。また、コマンド待機中は、

    サポートされたマイコンかどうかを表示するようにした。

 

■ Seeeduino XIAO RP2040と PIC12F1822マイコンの接続

 

Seeeduino XIAO RP2040 - PIC12F1822
3V3 3V3 - 1-pin VDD
GND GND - 8-pin VSS
D6 GPO 10 kΩ 4-pin (RA3) MCLR
D8 GPO 10 kΩ 6-pin (RA1) ICSPCLK
D7 GPO/GPI 10 kΩ 7-pin (RA0) ICSPDATA

 

 

■ CircuitPythonスクリプトのソースコード

 

前回は Mery というフリーウェアのテキストエディタを使って作ったのですが、

今回は Visual Studio Code で作って black という Pythonコードフォーマッターで

整形しました。不要な情報ですね。

 

# -----------------------------------------------------------------------------
# PIC16F1xxx LV-ICSP Programmer by Seeeduino XIAO RP2040 and CircuitPython
#
# Seeeduino XIAO RP2040      Microchip PIC12F1822
#   3V3          -----------   1 VDD
#   GND          -----------   8 VSS
#   D6 : GPO     --- 10k ---   4 RA3: MCLR
#   D8 : GPO     --- 10k ---   6 RA1: ICSPCLK
#   D7 : GPO/GPI --- 10k ---   7 RA0: ICSPDAT
#

import time
import board
import digitalio
import neopixel_write

DEVICE_LIST = {
    0x2700: {  # Device ID
        "P": [0x0000, 0x0800, 0x3FFF],  # Address, Size, Value
        "C": [0x8007, 0x0002, 0x3FFF],  # Address, Size, Value
        "D": [0xF000, 0x0100, 0x00FF],  # Address, Size, Value
        "N": "PIC12F1822",  # Device Name
    }
}


# -----------------------------------------------------------------------------
# Low-Voltage In-Circuit Serial Programming (LV-ICSP) Class


class ICSP:

    WAIT_TCLK = 200e-9  # 200 ns
    WAIT_TDLY = 1e-6  # 1 us
    WAIT_TENT = 1e-3  # 1 ms
    WAIT_TERA = 5e-3  # 5 ms

    COLUMN = 0x10

    def __init__(self, MCLR, ICSPCLK, ICSPDAT):
        self.MCLR = digitalio.DigitalInOut(MCLR)
        self.MCLR.direction = digitalio.Direction.OUTPUT
        self.ICSPCLK = digitalio.DigitalInOut(ICSPCLK)
        self.ICSPCLK.direction = digitalio.Direction.OUTPUT
        self.ICSPDAT = digitalio.DigitalInOut(ICSPDAT)
        self.ICSPDAT.direction = digitalio.Direction.OUTPUT

    # Communication Routine

    def send_bit(self, length, value):
        for i in range(length):
            # TCKH: Min 100 ns
            self.ICSPCLK.value = True
            self.ICSPDAT.value = value & 1
            # TDS: Min 100 ns
            time.sleep(self.WAIT_TCLK)
            # TCKL: Min 100 ns
            self.ICSPCLK.value = False
            # TDH: Min 100 ns
            time.sleep(self.WAIT_TCLK)
            value = value >> 1

    def send_command(self, value):
        self.send_bit(6, value)
        # TDLY: Min 1 us
        time.sleep(self.WAIT_TDLY)

    def send_data(self, value):
        self.send_bit(1, 0)
        self.send_bit(14, value)
        self.send_bit(1, 0)

    def recv_data(self):
        value = 0
        self.send_bit(1, 0)
        self.ICSPDAT.direction = digitalio.Direction.INPUT
        for i in range(14):
            # TCKH: Min 100 ns
            self.ICSPCLK.value = True
            # TCO: Max 80 ns
            time.sleep(self.WAIT_TCLK)
            value |= self.ICSPDAT.value << i
            # TCKL: Min 100 ns
            self.ICSPCLK.value = False
            time.sleep(self.WAIT_TCLK)
        self.ICSPDAT.direction = digitalio.Direction.OUTPUT
        self.send_bit(1, 0)
        return value

    def set_lvp_mode(self):
        # TENTS: Min 100 ns
        self.MCLR.value = True
        time.sleep(self.WAIT_TENT)
        # TENTH: Min 250 us
        self.MCLR.value = False
        time.sleep(self.WAIT_TENT)
        # Key Sequence: 0x4D434850 (MCHP in ASCII)
        self.send_bit(8, 0x50)
        self.send_bit(8, 0x48)
        self.send_bit(8, 0x43)
        self.send_bit(8, 0x4D)
        # Total 33 Clocks
        self.send_bit(1, 0)
        time.sleep(self.WAIT_TENT)

    def set_normal_mode(self):
        self.MCLR.value = True

    # Command Routine

    def run_load_configuration(self):
        self.send_command(0x00)
        self.send_data(0x00)

    def run_load_data_for_program_memory(self, value):
        self.send_command(0x02)
        self.send_data(value)

    def run_load_data_for_data_memory(self, value):
        self.send_command(0x03)
        self.send_data(value)

    def run_read_data_from_program_memory(self):
        self.send_command(0x04)
        return self.recv_data()

    def run_read_data_from_data_memory(self):
        self.send_command(0x05)
        return self.recv_data()

    def run_increment_address(self):
        self.send_command(0x06)

    def run_reset_address(self):
        self.send_command(0x16)

    def run_begin_internally_timed_programming(self):
        self.send_command(0x08)
        # TPINT: Max 5 ms
        time.sleep(self.WAIT_TERA)

    def run_bulk_erase_program_memory(self):
        self.send_command(0x09)
        # TERAB: Max 5 ms
        time.sleep(self.WAIT_TERA)

    def run_bulk_erase_data_memory(self):
        self.send_command(0x0B)
        # TERAB: Max 5 ms
        time.sleep(self.WAIT_TERA)

    # Read Routine

    def read_memory(self, size, run_read_data, show=True):
        base_address = 0
        data = [0] * size
        for address in range(size):
            data[address] = run_read_data()
            next_address = address + 1
            self.run_increment_address()
            if not show:
                continue
            if ((next_address % self.COLUMN) == 0) or (next_address == size):
                column_data = data[base_address:next_address]
                print_data_line(base_address, column_data)
                base_address = next_address
        return data

    def read_program_memory(self, size):
        run_read_data = self.run_read_data_from_program_memory
        self.run_reset_address()
        return self.read_memory(size, run_read_data)

    def read_configulation(self, size, show=True):
        run_read_data = self.run_read_data_from_program_memory
        self.run_load_configuration()
        return self.read_memory(size, run_read_data, show)

    def read_data_memory(self, size):
        run_read_data = self.run_read_data_from_data_memory
        self.run_reset_address()
        return self.read_memory(size, run_read_data)

    # Erase Routine

    def erase_program_memory(self):
        self.run_load_configuration()
        self.run_bulk_erase_program_memory()

    def erase_data_memory(self):
        self.run_bulk_erase_data_memory()

    # Write Routine

    def write_memory(self, data, latch, run_load_data):
        base_address = 0
        size = len(data)
        for address in range(size):
            run_load_data(data[address])
            next_address = address + 1
            if ((next_address % latch) == 0) or (next_address == size):
                self.run_begin_internally_timed_programming()
            self.run_increment_address()
            if ((next_address % self.COLUMN) == 0) or (next_address == size):
                column_data = data[base_address:next_address]
                print_data_line(base_address, column_data)
                base_address = next_address

    def write_program_memory(self, data):
        if data:
            run_load_data = self.run_load_data_for_program_memory
            self.erase_program_memory()
            self.run_reset_address()
            self.write_memory(data, 16, run_load_data)

    def write_configulation(self, data):
        if data:
            run_load_data = self.run_load_data_for_program_memory
            self.run_load_configuration()
            for i in range(7):
                self.run_increment_address()
            self.write_memory(data[0:2], 1, run_load_data)

    def write_data_memory(self, data):
        if data:
            run_load_data = self.run_load_data_for_data_memory
            self.erase_data_memory()
            self.run_reset_address()
            self.write_memory(data, 1, run_load_data)


# -----------------------------------------------------------------------------
# Sub Routine


def hexstr(data):
    return " ".join([("%04X" % value) for value in data])


def print_data_line(address, data):
    print(("%04X:" % address), hexstr(data))


def print_data(data):
    for address in range(0, len(data), ICSP.COLUMN):
        print_data_line(address, data[address : address + ICSP.COLUMN])


def verify_data(memory, config, read_data):
    print("File Data")
    data_file = read_hex_file(file, memory)
    print_data(data_file)
    print("Read Data")
    if config:
        data_read = icsp.read_configulation(11)[7:9]
    else:
        data_read = read_data(memory[1])
    if data_file == data_read:
        print("Verify OK")
    else:
        print("Verify NG")


def read_configulation():
    data = icsp.read_configulation(11, False)
    device_id = data[6] & 0x3FE0
    device_infomation = DEVICE_LIST.get(device_id)
    if device_infomation is None:
        device_name = "(Not Supported)"
    else:
        device_name = "(" + device_infomation["N"] + ")"
    # Print
    print("# Configuration")
    print("User ID Location   :", hexstr(data[0:4]))
    print("Device ID          :", hexstr([device_id]), device_name)
    print("Revision ID        :", hexstr([data[6] & 0x1F]))
    print("Configuration Word :", hexstr(data[7:9]))
    print("Calibration Word   :", hexstr(data[9:11]))
    return device_infomation


def read_hex_file(name, memory):
    memory_address = memory[0]
    memory_size = memory[1]
    memory_buffer = [memory[2]] * memory_size
    extended_linear_address = 0
    # Read File
    file = open(name, "r")
    for line in file:
        line = line.rstrip()
        # Parse Record Structure
        start_code = line[0]  #         # Start code
        byte_count = line[1:3]  #       # Byte count
        address = line[3:7]  #          # Address
        record_type = line[7:9]  #      # Record type
        data = line[9:-2]  #            # Data
        checksum = line[-2:]  #         # Checksum
        # Check
        if start_code != ":":
            print("Invalid Start Code")
            return
        if (int(byte_count, 16) * 2) != len(data):
            print("Invalid Data Length")
            return
        byte_data = [int(line[i : i + 2], 16) for i in range(1, len(line), 2)]
        if sum(byte_data) & 0xFF:
            print("Invalid Checksum")
            return
        # Handle
        if record_type == "00":  #      # Data
            absolute_address = int(extended_linear_address + address, 16) >> 1
            offset_address = absolute_address - memory_address
            if 0 <= offset_address < memory_size:
                for i in range(0, len(data), 4):
                    value = int(data[i + 2 : i + 4] + data[i : i + 2], 16)
                    memory_buffer[offset_address + (i >> 2)] = value
        elif record_type == "04":  #    # Extended Linear Address
            extended_linear_address = line[9:13]
        elif record_type == "01":  #    # End Of File
            break
        else:
            print("Invalid Record Type")
            return
    file.close()
    return memory_buffer


class LED:

    mode = 0

    def __init__(self):
        self.PWR = digitalio.DigitalInOut(board.NEOPIXEL_POWER)
        self.PWR.direction = digitalio.Direction.OUTPUT
        self.PWR.value = True
        self.DAT = digitalio.DigitalInOut(board.NEOPIXEL)
        self.DAT.direction = digitalio.Direction.OUTPUT

    def set_error(self, value):
        self.mode = 2 if value else 1

    def OFF(self):
        data = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
        neopixel_write.neopixel_write(self.DAT, bytearray(data[self.mode]))

    def ON_MODE(self):  # White
        neopixel_write.neopixel_write(self.DAT, bytearray([0x30, 0x30, 0x30]))
        self.mode = 0

    def ON_READ(self):  # Green
        neopixel_write.neopixel_write(self.DAT, bytearray([0x30, 0x00, 0x00]))

    def ON_ERASE(self):  # Yellow
        neopixel_write.neopixel_write(self.DAT, bytearray([0x20, 0x40, 0x00]))

    def ON_WRITE(self):  # Red
        neopixel_write.neopixel_write(self.DAT, bytearray([0x00, 0x60, 0x00]))

    def ON_VERIFY(self):  # Cyan
        neopixel_write.neopixel_write(self.DAT, bytearray([0x20, 0x00, 0x20]))


# -----------------------------------------------------------------------------
# Main Routine


file = "image.hex"

icsp = ICSP(board.D6, board.D8, board.D7)
icsp.set_lvp_mode()
device = read_configulation()

led = LED()
led.set_error(device is None)

while True:
    led.OFF()
    print("")
    print("# PIC16F1xxx LV-ICSP Programmer")
    print("MI/MO    : Enter/Exit LV-ICSP Mode                  (White)")
    if device:
        print("RP/RD/RC : Read   Program/Data/Configuration Memory (Green)")
        print("EP/ED    : Erase  Program/Data               Memory (Yellow)")
        print("WP/WD/WC : Write  Program/Data/Configuration Memory (Red)")
        print("VP/VD/VC : Verify Program/Data/Configuration Memory (Cyan)")
    else:
        print("RC       : Read Configuration Memory                (Green)")
    print("> ", end="")
    text = input().upper()
    if text == "MI":
        led.ON_MODE()
        icsp.set_lvp_mode()
    elif text == "MO":
        led.ON_MODE()
        icsp.set_normal_mode()
        device = None
    elif text == "RC":
        led.ON_READ()
        device = read_configulation()
        led.set_error(device is None)
    elif device is None:
        print("Invalid")
    elif text == "RP":
        led.ON_READ()
        icsp.read_program_memory(device["P"][1])
    elif text == "RD":
        led.ON_READ()
        icsp.read_data_memory(device["D"][1])
    elif text == "EP":
        led.ON_ERASE()
        icsp.erase_program_memory()
    elif text == "ED":
        led.ON_ERASE()
        icsp.erase_data_memory()
    elif text == "WP":
        led.ON_WRITE()
        icsp.write_program_memory(read_hex_file(file, device["P"]))
    elif text == "WD":
        led.ON_WRITE()
        icsp.write_data_memory(read_hex_file(file, device["D"]))
    elif text == "WC":
        led.ON_WRITE()
        icsp.write_configulation(read_hex_file(file, device["C"]))
    elif text == "VP":
        led.ON_VERIFY()
        verify_data(device["P"], False, icsp.read_program_memory)
    elif text == "VD":
        led.ON_VERIFY()
        verify_data(device["D"], False, icsp.read_data_memory)
    elif text == "VC":
        led.ON_VERIFY()
        verify_data(device["C"], True, None)
    elif text == "TF":
        print("Program Memory")
        print_data(read_hex_file(file, device["P"]))
        print("Configuration Memory")
        print_data(read_hex_file(file, device["C"]))
        print("Data Memory")
        print_data(read_hex_file(file, device["D"]))
    else:
        print("Invalid")
    time.sleep(0.1)

 

以上です。

ソースコードを添付します。

 

■ 専用イメージファイルの変換用 PowerShellスクリプト

 

PowerShellスクリプトファイル(image.ps1)と同じフォルダに

Intel HEXイメージファイル(image.hex)を置いて実行すると、

プログラム領域/データ領域/コンフィグレジスタ設定に分けた

3つの専用イメージファイル(.program/.data/.config)が作成されます。

 

アドレス指定が PIC12F1822マイコン専用になっていますので、

他のマイコンの場合は適宜修正する必要があります。

 

# Convert Intel HEX File

$file = "image.hex"
$Buffer = @("3FFF") * 0xF000 + @("00FF") * 0x1000
$ExtendedLinearAddress = 0
Get-Content $file |
Where-Object { $_ -match '^(:)(\w{2})(\w{4})(\w{2})(\w*)(\w{2})$' } |
ForEach-Object {
    $StartCode = $Matches[1]
    $ByteCount = $Matches[2]
    $Address = $Matches[3]
    $RecordType = $Matches[4]
    $Data = @(($Matches[5] | Select-String -AllMatches -Pattern "\w{4}").Matches.Value)
    $Checksum = $Matches[6]
    if ($RecordType -eq "04") {
        $ExtendedLinearAddress = $Data[0]
    } elseif ($RecordType -eq "00") {
        $ActualAddress = [int]"0x$($ExtendedLinearAddress + $Address)" -shr 1
        if ($ActualAddress -ge 0x800) {
        }
        foreach ($Word in $Data) {
            $Buffer[$ActualAddress++] = $Word.Substring(2, 2) + $Word.Substring(0, 2)
        }
    }
}
$Buffer[0x0000..0x07FF] | Set-Content "$file.program"
$Buffer[0x8007..0x8008] | Set-Content "$file.config"
$Buffer[0xF000..0xF0FF] | Set-Content "$file.data"

 

■ LV-ICSPライタの CircuitPythonスクリプト

 

CircuitPythonスクリプトファイル(main.py)を

CIRCUITPYドライブにコピーします。

 

GPIO(digitalio)とウェイト(time.sleep)しか使用していませんので、

他の CircuitPythonが動作するデバイスでも、

GPIOの割当てを変更する程度で動作するのではないかと思います。

 

#------------------------------------------------------------------------------
# PIC16F1xxx LV-ICSP Programmer by Seeeduino XIAO and CircuitPython
#
# Seeeduino XIAO                 Microchip PIC12F1822
#   D7 : SPI.CSn  --------------   4(RA3): nMCLR
#   D8 : SPI.SCK  --------------   6(RA1): ICSPCLK
#   D9 : SPI.MISO --- 4.7k --+--   7(RA0): ICSPDAT
#   D10: SPI.MOSI --- 4.7k --+

import board
import digitalio
import time

#------------------------------------------------------------------------------
# Port Setup Routine

def setup_dio(pin, direction, value = False):
    dio = digitalio.DigitalInOut(pin)
    dio.direction = direction
    if direction == digitalio.Direction.OUTPUT:
        dio.value = value
    return dio

led = setup_dio(board.LED_INVERTED, digitalio.Direction.OUTPUT, True)
MCLR = setup_dio(board.D7, digitalio.Direction.OUTPUT, False)
ICSPCLK = setup_dio(board.D8, digitalio.Direction.OUTPUT, False)
ICSPDAT_in = setup_dio(board.D9, digitalio.Direction.INPUT)
ICSPDAT = setup_dio(board.D10, digitalio.Direction.OUTPUT, False)

#------------------------------------------------------------------------------
# LV-ICSP Routine

def send_bit(bit, value):
    for i in range(bit):
        ICSPCLK.value = True        # TCKH: Min 100ns
        ICSPDAT.value = value & 1   # TDS:  Min 100ns
        ICSPCLK.value = False       # TDH:  Min 100ns
        value = value >> 1          # TCKL: Min 100ns
    time.sleep(0.0001)              # TDLY: Min 1us

def send_command(value):
    send_bit(6, value)

def send_data(value):
    send_bit(1, 0)
    send_bit(14, value)
    send_bit(1, 0)

def recv_data():
    value = 0
    send_bit(1, 0)
    for i in range(14):
        ICSPCLK.value = True        # TCO: Max 80ns
        value |= (ICSPDAT_in.value << i)
        ICSPCLK.value = False
    send_bit(1, 0)
    return value

def set_lvp_mode():
    MCLR.value = True
    time.sleep(0.001)               # TENTS: Min 100ns
    MCLR.value = False
    time.sleep(0.001)               # TENTH: Min 250us
    send_bit(8, 0x50)               # 'P'
    send_bit(8, 0x48)               # 'H'
    send_bit(8, 0x43)               # 'C'
    send_bit(8, 0x4D)               # 'M'
    send_bit(1, 0)                  # 33 Clocks
    time.sleep(0.001)

def run_load_configuration():
    send_command(0x00)
    send_data(0x00)

def run_load_data_for_program_memory(value):
    send_command(0x02)
    send_data(value)

def run_load_data_for_data_memory(value):
    send_command(0x03)
    send_data(value)

def run_read_data_from_program_memory():
    send_command(0x04)
    return recv_data()

def run_read_data_from_data_memory():
    send_command(0x05)
    return recv_data()

def run_increment_address():
    send_command(0x06)

def run_reset_address():
    send_command(0x16)

def run_begin_internally_timed_programming():
    send_command(0x08)
    time.sleep(0.005)               # TPINT: Max 5ms

def run_bulk_erase_program_memory():
    send_command(0x09)
    time.sleep(0.005)               # TERAB: Max 5ms

def run_bulk_erase_data_memory():
    send_command(0x0B)
    time.sleep(0.005)               # TERAB: Max 5ms

#------------------------------------------------------------------------------
# Sub Routine

def hexstr(data):
    return " ".join([("%04X" % value) for value in data])

def read_configulation():
    run_load_configuration()
    data = [0] * 11
    for i in range(11):
        data[i] = run_read_data_from_program_memory()
        run_increment_address()
    print("User ID Location   :", hexstr(data[0:4]))
    print("Device ID          :",
        "%04X (REV: %02X)" % ((data[6] & 0x3FE0), (data[6] & 0x1F)))
    print("Configuration Word :", hexstr(data[7:9]))
    print("Calibration Word   :", hexstr(data[9:11]))

def read_memory(size, function):
    run_reset_address()
    unit = 0x10
    address = 0
    for line in range(size / unit):
        data = [0] * unit
        for index in range(unit):
            data[index] = function()
            run_increment_address()
        print(("%04X:" % address), hexstr(data))
        address += unit

def write_memory(type, name, function):
    run_reset_address()
    unit = 0x10
    address = 0
    data = [0] * unit
    index = 0
    file = open(name, "r")
    for line in file:
        value = int(line.rstrip(), 16)
        function(value)
        if type == 2:
            run_begin_internally_timed_programming()
        data[index] = value
        index += 1
        if index == unit:
            if type == 1:
                run_begin_internally_timed_programming()
            print(("%04X:" % address), hexstr(data))
            address += unit
            index = 0
        run_increment_address()
    if index > 0:
        run_begin_internally_timed_programming()
    file.close()

def write_configulation(name):
    run_load_configuration()
    for i in range(7):
        run_increment_address()
    file = open(name, "r")
    for i in range(2):
        run_load_data_for_program_memory(int(file.readline().rstrip(), 16))
        run_begin_internally_timed_programming()
        run_increment_address()
    file.close()

#------------------------------------------------------------------------------
# Main Routine

file = "image.hex"

set_lvp_mode()
read_configulation()

while True:
    print("")
    print("# PIC16F1xxx LV-ICSP Programmer")
    print("MI/MO    : Enter/Exit LV-ICSP Mode")
    print("RP/RD/RC : Read  Program/Data/Configuration Memory")
    print("EP/ED    : Erase Program/Data               Memory")
    print("WP/WD/WC : Write Program/Data/Configuration Memory")
    print("> ", end = "")
    text = input().upper()
    led.value = False
    if text == "MI":
        set_lvp_mode()
    elif text == "MO":
        MCLR.value = True
    elif text == "RP":
        read_memory(0x800, run_read_data_from_program_memory)
    elif text == "RD":
        read_memory(0x100, run_read_data_from_data_memory)
    elif text == "RC":
        read_configulation()
    elif text == "EP":
        run_load_configuration()
        run_bulk_erase_program_memory()
    elif text == "ED":
        run_bulk_erase_data_memory()
    elif text == "WP":
        run_load_configuration()
        run_bulk_erase_program_memory()
        write_memory(1, file + ".program", run_load_data_for_program_memory)
    elif text == "WD":
        run_bulk_erase_data_memory()
        write_memory(2, file + ".data", run_load_data_for_data_memory)
    elif text == "WC":
        write_configulation(file + ".config")
    else:
        print("Invalid")
    led.value = True

 

以上です。

私は今までワンチップマイコンでは、主に AVRマイコンを使っていまして、

PICマイコンは使ったことがなかったのですが、こちらのサイト

自分で作るDCCデコーダ - ワンコインデコーダ6(Web Nucky)」では、

DCCデコーダ用に PICマイコン(PIC12F1822)が使われていましたので、

それのファームの書込み環境を Seeeduino XIAOで作ってみました。

 

MPLAB PicKit 4MPLAB SNAP などの純正ライタは、

お試しで気軽に買える値段ではないですしね。

 

■ ファームの書込み仕様

 

ファームの書込み仕様は、PIC12F1822マイコンのサイトにあります

PIC12(L)F1822/PIC16(L)F182X Memory Programming Specification」を

見まして、LV-ICSP (Low Voltage In-Circuit Serial Programming)モードのみに

対応することにしました。

 

■ Seeeduino XIAOと PIC12F1822マイコンの接続

 

当初は Seeeduino XIAOの SPI機能を使った通信を考えていましたので

SPIピンと接続しましたが、ICSPの 6ビットのコマンドを

送信できないことが分かり、結局 GPIOによるソフトウェア制御にしたため、

GPIOの割当て設定を変えればどのピンと接続しても大丈夫です。

 

Seeeduino XIAO - WIRE PIC12F1822
3V3 3V3 - RED 1-pin VDD
GND GND - BLACK 8-pin VSS
D7 GPO (SPI.CSn) - YELLOW 4-pin (RA3) nMCLR
D8 GPO (SPI.SCK) - BLUE 6-pin (RA1) ICSPCLK
D9 GPI (SPI.MISO) 4.7 kΩ GREEN 7-pin (RA0) ICSPDATA
D10 GPO (SPI.MOSI) 4.7 kΩ

 

 

■ Seeeduino XIAOのプログラム

 

ファームの書込み環境を準備することは手段であって目的ではないため、

Seeeduino XIAOのプログラムのために別途開発環境のインストールが

必要になることは避けたく、その必要のない CircuitPythonで作成しました。

 

ただ、CircuitPythonのスクリプトファイル(main.py)と、

書込むファームのイメージファイル(image.hex)を

CIRCUITPYドライブにコピーするだけで済むようにしたかったのですが、

途中でメモリ不足になってしまったため、

残念ながら Intel HEXフォーマットのパース処理は削除して、

事前に変換した専用イメージファイルをコピーすることにせざるを

得ませんでした。

 

最終的に書込み作業は下記の流れで行うことになりました。

コピーとシリアルコンソールの作業も PowerShellスクリプトで行えば

よいのですが、そこはやる気がなくなりました。

 

(1) パソコンで PowerShellスクリプトを実行して、

  Intel HEXイメージファイル(.hex)から、PIC12F1822マイコンの

  プログラム領域/データ領域/コンフィグレジスタ設定に分けた

  3つの専用イメージファイル(.program/.data/.config)に変換する。

(2) CircuitPythonのスクリプトファイルと変換した専用イメージファイルを

  Seeeduino XIAOの CIRCUITPYドライブにコピーする。

(3) パソコンのシリアルコンソールで Seeeduino XIAOを操作して、

  PIC12F1822マイコンのファーム書込みを行う。

 

■ 専用イメージファイルの変換

 

元の Intel HEXイメージファイル: image.hex

:020000040000FA
:020000000428D2
:080008008C0123008C01210092
:100010007A30990021000C308C0022009D141D14B0
:10002000250091010830BF23AB00AB1D1C28200028
…
:020000040001F9
:02000E00A40943
:02001000FF3CB3
:10E000000300000000000000FF0000005C009C0016
…

 

変換後の専用イメージファイル

image.hex.program image.hex.data image.hex.config
2804
3FFF
3FFF
3FFF
018C
0023
018C
0021
0003
0000
0000
0000
00FF
0000
005C
009C
…
09A4
3CFF

 

■ Seeeduino XIAOの操作

 

CircuitPythonのスクリプトファイルを CIRCUITPYドライブにコピーすると

自動的にスクリプトが実行され、

PIC12F1822マイコンを LV-ICSPモードへ遷移させてコンフィグ設定を

読み出します。

Adafruit CircuitPython 7.3.2 on 2022-07-20; Seeeduino XIAO with samd21g18
>>>
soft reboot

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
main.py output:
User ID Location   : 3FFF 3FFF 3FFF 3FFF
Device ID          : 2700 (REV: 09)
Configuration Word : 3FFF 3FFF
Calibration Word   : 28F1 1A8C

# PIC16F1xxx LV-ICSP Programmer
MI/MO    : Enter/Exit LV-ICSP Mode
RP/RD/RC : Read  Program/Data/Configuration Memory
EP/ED    : Erase Program/Data               Memory
WP/WD/WC : Write Program/Data/Configuration Memory
> 

 

WP、WD、WCのコマンドを実行して、

プログラム領域/データ領域/コンフィグレジスタ設定を書込みます。

> wp
0000: 2804 3FFF 3FFF 3FFF 018C 0023 018C 0021 307A 0099 0021 300C 008C 0022 149D 141D
0010: 0025 0191 3008 23BF 00AB 1DAB 281C 0020 30FF 009B 0020 2821 0020 307F 009B 0020
0020: 2821 0020 300B 052B 009C 0020 018C 01B7 01BF 0020 301D 23BF 00B0 18B0 284B 0837
0030: 1903 2842 3001 23BF 00A9 0237 1803 283A 0829 00B7 3004 23BF 00AA 0237 1C03 2842
…

> wd
0000: 0003 0000 0000 0000 00FF 0000 005C 009C 0000 0000 0000 0000 0001 0000 0000 0000
0010: 00C0 0080 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 0000 0000 0000
0020: 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0030: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
…

> wc

 

RP、RD、RCのコマンドを実行して、

プログラム領域/データ領域/コンフィグレジスタ設定を読出します。

ベリファイ機能はありませんので自分で確認します。

> rp
0000: 2804 3FFF 3FFF 3FFF 018C 0023 018C 0021 307A 0099 0021 300C 008C 0022 149D 141D
0010: 0025 0191 3008 23BF 00AB 1DAB 281C 0020 30FF 009B 0020 2821 0020 307F 009B 0020
0020: 2821 0020 300B 052B 009C 0020 018C 01B7 01BF 0020 301D 23BF 00B0 18B0 284B 0837
0030: 1903 2842 3001 23BF 00A9 0237 1803 283A 0829 00B7 3004 23BF 00AA 0237 1C03 2842
…

> rd
0000: 0003 0000 0000 0000 00FF 0000 005C 009C 0000 0000 0000 0000 0001 0000 0000 0000
0010: 00C0 0080 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 0000 0000 0000
0020: 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0030: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
…

> rc
User ID Location   : 3FFF 3FFF 3FFF 3FFF
Device ID          : 2700 (REV: 09)
Configuration Word : 09A4 3CFF
Calibration Word   : 28F1 1A8C

 

■ ICSP通信波形

 

最後にオシロで測定した ICSP通信波形です。

CH1(赤色)がクロック ICSPCLK で、CH2(黄色)がデータ ICSPDAT です。

 

PIC12F1822マイコンを LV-ICSPモードへ遷移させるために、

キーシーケンスとして 0x4D434850 ("MCHP"のASCIIコード)のデータを

LSB Firstで送信するのですが、32ビット分のクロックとデータを送信した後に、

クロックをもう 1ビット送信して計 33ビットのクロックが必要なことがミソです。

 

 

 

以上です。

Desktop Stationのサイトで、DSshield2(DSシールド2)と組み合わせて使う

Raspberry Pi Pico (RP2040)用のスケッチがフリーで公開されていましたので、

Seeed XIAO RP2040 で動かしてみました。

 

・電機屋の毎日 - ラズパイPico(RP2040)用のDSシールドスケッチを追加

 

・Desktop Station - DSshield2 - スケッチ(RP2040 Raspberrypi Pico向け)

・Desktop Station - Pico UNO

 

ただ DSshield2は持っていませんので、同等品をユニバーサル基板で製作しました。

といっても簡略化のために、

・モータードライバーは TOSHIBA TB6643KQ を使用

・電流は TB6643KQの GNDに入れたシャント抵抗の電圧で測定

といった構成です。

 

■ DCCコマンドステーションの製作

 

配線図です。

 

 

製作した基板の写真です。

抵抗器は手元にあった値で組み合わせたため、実際には

R1: 4.7kΩ→22kΩ、R2: 1.2kΩ→4.7kΩ、R3: 0.5Ω→1Ωx2、を使いました。

 

 

■ Arduino IDE スケッチ

 

スケッチは、現在公開されている「rev.RP2040.001」をベースにして、

以下の修正を行いました。

 

項目 変更前 変更後
DCC制御出力 GPIO19
GPIO20
GPIO1 (D7)
GPIO0 (D8)
アナログ電圧入力 GPIO27 (ADC1) GPIO27 (ADC1)
アナログ電流入力 GPIO28 (ADC2) GPIO26 (ADC0)
RUN LED GPIO2 GPIO25 (XIAO搭載LED RGB-B)
PWR LED - GPIO16 (XIAO搭載LED RGB-G)
ERR LED - GPIO17 (XIAO搭載LED RGB-R)
LEDの論理 H 点灯 / L 消灯 L 点灯 / H 消灯

 

初回リリースだからか、ハードウェア依存のコードが散在していたため、

以下3つのファイルを修正しました。

・DSCoreM_Type.h

・DSshield2040.ino

・DSCoreM_Common.cpp

 

 

下記のボードを使用してビルドしました。

 

・Preferences - Board Manager URL:

https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json

・Board Manager: Seeed XIAO RP2040 by Seeed Studio version 1.12.0

・Board: Seeed XIAO RP2040

 

XIAO RP2040は、購入後に初めてパソコンに接続するときは

COMポートが表示されず、Arduino IDEでアップロードできずに困っていたところ、

下記サイトを見つけました。

付属の適当なサンプルスケッチを一回書き込むと、

以降、COMポートが表示されるようになりました。

 

Arduino IDEでRaspberry Pi Picoの開発環境を構築 | TomoSoft

 

■ 動作確認

 

モータードライバーの電源は、12Vの ACアダプタから給電しました。

 

XIAO RP2040の USBポートは、
ストロベリーリナックス製の LTM2884 USBアイソレータ 経由でパソコンに接続し、

Desktop Station Software から制御しました。

 

車両の DCCデコーダーは 「ESU 58731 LokSound 5 micro DCC Kato」を搭載し、

ヘッド/テールライトや室内灯については

Kato FL12/FR11を使わず LokSoundから制御しています。

 

 

以上です。

つぶやきです。

 

何の目的の基板かは分かりませんが、回路図を見ると、

74HC595(U1)の SRCLRピン(10ピン)が GNDに接続されています。

 

SRCLRピンが常に Low(GND)だとシフトレジスタがクリアされたままになり、

全く動作しない感じがします。

 

しかも ICの下で GNDピン(8ピン)とつながっているので、

部品実装後に気付くとリワークが大変そうです。

 

あや!!📛さんはTwitterを使っています: 「楽しんだ☺️ https://t.co/C6h92XZZI8」 / Twitter

 

 

以上です。

DesktopStationにおいて DCCサウンドデコーダ「SmileSound」の開発が

進められているようですが、

個人的には、サウンドフローの CSVファイルのフォーマットを

もう少し改善した方がよいかなと思いました。

 

具体的には、

・LABEL文とIF文については独立した列を設けてフローの可読性を高める。

・フラグ目的で数値のパラメータは使わない。

 (PLAY文の loop(0 or 1),immediate(0 or 1)など)

などです。

 

SmileSoundDecoder (desktopstation.net)

DSSP/DesktopStation Sound Programmer – 電機屋の毎日

 

 

例えば、上記の「ats-s.csv」ファイルの場合は、

下記のように記述できた方がよいかなと思います。

 

LABEL IF COMMAND PARAM 1 PARAM 2
START FNC = 0 GOTO START  
PLAY_ON SPD != 0 GOTO RUNNING  
    WAIT 500  
    PLAY ATSALM.WAV 2
    PLAY ATSCHK.WAV 8
PLAY_LOOP FNC = 1 GOTO PLAY_LOOP  
    EXIT    
RUNNING   PLAY ATSALM.WAV 1
  FNC = 0 GOTO PLAY_LOOP2  
    PLAY ATSALM.WAV 2
  FNC = 0 GOTO PLAY_LOOP2  
    PLAY ATSALM.WAV 2
  FNC = 0 GOTO PLAY_LOOP2  
    PLAY ATSALM.WAV 2
  FNC = 0 GOTO PLAY_LOOP2  
    SLIM 0  
    PLAY ATSALM.WAV 0
STOP SPD > 0 GOTO STOP  

 

現実的にこのような改善の有無で何か変わるかと言えば

変わらないかもしれませんが、

少しでもユーザーの裾野を広げるためには、

理解の妨げになる引っ掛かりポイントを減らした方がよいと思います。

 

以上です。

前回に続きます。

鉄道模型スマートフィーダー線路の製作1 | さみしいひとりにっき (ameblo.jp)

 

今回の基板では、24-VQFNパッケージの ATtiny827マイコンを使用するため、

市販の安価な卓上ホットプレートを使用したリフロー実装に挑戦しました。

 

使用したのはプラスモア(plus more) ちょい焼きグリル 「MO-SK001-FT」です。

プレートの内側が約11.5cm角のため 10cm角の基板がちょうど入るサイズで、

温度がやや高めの 265±15℃で、1,600円くらいと安価で、

リフロー実装用に向いていると思って買いました。

 

 

製品を裏返すと四隅にネジカバーがありますので、

隙間にキリを挿してこじると簡単に外れて、ネジが現れます。

 

 

分解は自己責任です。ネジを外してケースを開けると、

写真のようにヒーターが配置されていることが分かります。

内部を確認したら再び組み立てます。

 

後で分かったことですが、

ホットプレートは想像以上に温度が均一にはならないので、

ヒーターのパイプの真上に基板を置くようにした方がよいです。

 

 

続いて、プリント基板にクリームハンダを塗布したかったのですが、

鉛フリータイプのクリームハンダが Amazonになく、

色々検索すると松尾ハンダで個人向けに販売しているようなのですが、

高そうなので、結局 糸ハンダで一つ一つのパッドにハンダを盛りました。

 

その後、チップ抵抗やチップコンデンサは片方の端子のみをハンダ付けし、

SOPの ICはどれか1ピンか2ピンをハンダ付けして仮固定し、

VQFNの ATtiny827だけは乗せるだけにして、

基板をホットプレートに乗せてマスキングテープで固定しました。

フラックスも塗りました。

また、温度計付きデジタルテスタ MS8221Cの熱電対も取り付けました。

 

それで実際にリフローしたのが下記の動画になります。

 

電源スイッチをONすると徐々に温度が上がり、

01:48頃に ATtiny827マイコンがセルフアライメント効果で動き出し、

01:55頃に右端の手前のセラミックコンデンサが動いていることが分かります。

 

しばらくすると自動的にサーモスタットが切れて、

結果的にうまくリフローできた感じがしました(この時は)。

 

 

電源スイッチをOFFして、ほぼ常温に下がるまで待った後に、

ホットプレートから取り外しました。

 

 

 

クリームハンダ塗布用にメタルマスク(ステンシル)も作っていたのですが、

使用せずに終わりました。

 

 

以上です。

鉄道模型スマートフィーダー線路に取り付けるための

鉄道模型信号機をプリント基板で製作しました。

 

信号機の光源となるLEDは、

・輝度調整を抵抗値設定ではなくマイコンで制御可能にするため

・将来的にLEDの数が増えた場合に容易に追加可能にするため

に、マイコン内蔵RGB LEDを使用することにしました。

 

マイコン内蔵RGB LEDもいくつか種類がありますが、

NeoPixel (WS2812B, WS2815Bなど)はデータのタイミング仕様が

許容差 ±150nsなどと厳しいため、1~100usで済む

OptoSupply OST4ML5B32A [I-08346] を使用することにしました。

 

以下、横倒しになっていますが点灯させたときの写真です。

乳白レンズのため光が綺麗に拡散されて見やすいです。

 

 

■ 基板

 

基板仕様

・ 材質: FR-4

・ 層数: 両面

・ 寸法: 100x100mm (組立後は 9x60mm)

・ 板厚: 1.6mm

・ レジスト: 黒色 (つや有り)

・ 表面処理: 鉛フリー半田レベラー

 

基板は Seeed Fusion PCBに発注しました。

Vカットの指示を入れていましたが、基板が 3種類(上段・中段・下段)に

なるため追加費用がかかると連絡があり断念しました。

(実際には 5種類なのに 3種類とみなされたので、

縦のVカットだけは入れてもらえばよかったと後から思いました)

 

 

■ 組立

 

まず生基板の写真です。左側が表、右側が裏です。

 

 

基板の個片はニッパーでカットしてヤスリで整えました。

 

 

LEDは、リードの長さや切欠の向きをシルク表示と合わせますが、

リードが基板の裏側からはみ出さないように、

予めカットしてから基板に挿入して半田付けしました。

その後、小基板と半田付けしました。

 

 

前後の基板は両面テープで貼り合わせて、以下、完成写真です。

 

 

■ 動作確認

 

鉄道模型スマートフィーダー線路はまだできていないため、

DCCポイントデコーダー基板から制御しました。

 

GIF動画の点灯パターン(LEDの設定値)は下記の通りです。

 

(1) 消灯
(2) 青色(R0/G0/B128)、緑色(R0/G32/B0)、赤色(R64/G0/B0)
(3) 紫色(R128/G0/B128)、水色(R0/G128/B128)、黄色(R128/G128/B0)
(4) 緑色(R0/G55/B6)、黄色(R114/G16/B0)、赤色(R128/G0/B0) … 信号機(明)
(5) 緑色(R0/G14/B2)、黄色(R29/G4/B0)、赤色(R32/G0/B0) … 信号機(暗)
(6) 消灯

(7) 信号機(暗)の緑色→赤色→黄色→緑色

 

 

以上です。

現在、鉄道模型スマートフィーダー線路を製作しようとしています。

これは下記の機能を一枚の基板に搭載して道床付線路と一体化させたもので、

賢い高機能フィーダー線路という意味で勝手にそう呼ぶようにしました。

 

今回、基板は KATO 直線線路 62mm<S62>(20-040)に合わせて製作します。

 

■ 鉄道模型スマートフィーダー線路の主な機能

 

・メインマイコン (ATtiny827)

・RS-485通信 x1

・DCCブースター (TB67H450FNG) x1

・RailComディテクター x1

・電流式 在線検知センサー x1

・フォトリフレクタ式 車両検知センサー (TPR-105F) x2

・ポイントドライバ (TB67H450FNG) x1

・信号機ドライバ (マイコン内蔵RGB LED用) x2

 

■ 背景

 

DCCは、ギャップで閉塞区間を設けることなく複数の車両を同時に制御でき、

ポイントや信号機などのアクセサリも線路からの給電で制御できることが

メリットの一つとして謳われていると思います。

 

ですが、そのメリット通りに済むのはミニレイアウトくらいでして、

先日見学しました実際のレイアウトでは、

・線路抵抗による電圧低下対策のために複数のフィーダー配線があり、

・在線検知のためにギャップや S88モジュールへの配線があり、

・DCC/アナログ両対応のためにアクセサリ制御は結局別の配線にしたり、

とにかく配線・配線・配線…が必要なのが現実で、

初心者には実現のハードルが高いのが実態だと感じています。

 

また、某掲示板で頻繁に書き込まれている、

「DCCはとにかく全車両に施工するのが面倒。

新車導入したらまた工事とかやってられんわな」というのも

まさにその通りです。

DCCは集電不良による車両の瞬停も起きやすく

メンテナンスに労力がかかりますので、

一度DCCでレイアウトを構築したら、以後ずっと新たに買う車両を

DCCに改造し続けなければならなくなるのは気が重いです。

 

そんな中で、

RFCダイヤ運転フェスタで使われていたDCC対応自動閉塞キットであったり、

TOMIXの TNOSシステムなどにもインスパイアされて、

鉄道模型スマートフィーダー線路の製作に取り組もうと思ったしだいです。

 

鉄道模型スマートフィーダー線路を分散配置することにより、

・在線検知やアクセサリ制御を含めたトータルでの省配線化

・車両追従しながら閉塞区間ごとに DCC/アナログ(PWM) を個別制御

 (同じレイアウトで DCC車両とアナログ車両を同時に運転が可能)

・リバース線路対応

などを実現していきたいと考えています。
 

■ 回路

 

RS-485通信と RailComデータ受信を行うのに UARTが 2ch必要なため、

メインマイコンは Microchip社製 ATtiny827 (tinyAVR2シリーズ)にしました。

ただ、処理速度が間に合うか心配です。

(本当は ATtiny1627にしたかったのですが Digi-Keyに在庫がなく…)

 

個々の回路ブロックについては今後順次説明していきたいと思います。

 

■ 基板

 

基板パターン設計は KiCad 6 で行いました。

面実装部品はホットプレートによるリフローができるように片面配置にしました。

 

 

 

基板仕様

・ 材質: FR-4

・ 層数: 4層

・ 寸法: 61x100mm (個片サイズ 61x25mmを 4個面付け、Vカット)

・ 板厚: 1.6mm

・ レジスト: つや消し黒色

・ 表面処理: 鉛フリー半田レベラー

 

製造履歴 (Seeed Fusion PCB)

・2022/5/5(木) 午後 … ガーバーデータ送付&発注

・2022/5/12(木) 午後 … 出荷連絡、OCSに引渡し

・2022/5/16(月) 午後 … 佐川急便に引渡し

・2022/5/17(火) 午後 … 到着

 

基板写真

 

 

直線線路への取付イメージ

 

 

以上です。

続いて、イベント「結伝杜公開運転会」に行ってきました。

 

 

 

 

 

 

こちらのポイントは配線されていませんでした。

 

 

 

綺麗なジオラマですね。

 

 

 

 

 

 

バスコレ?も見どころでした。

 

 

以上です。