電池駆動のデータロガーを作ってみる(4) — machine.lightsleepは動かない

タイトル通りですが、最初の割り込みは上がってきますが、次の割り込みは上がっってこない、おそらくmicroPythonのファームの問題だろうと思うから、やはり実装はpico-sdkでやるけれどもその前に電源電流測定をしてみる

構成は電池はNiH二直に2.2Ωの電流測定用の抵抗経由でラズピコのVSYSに供給

VBUSとVSUSを今は計測してますが、USBは未接続なのでVBUSはほぼ0Vになってます

抵抗(2.2Ω)の両端をテスターで測定すると48mV程度、つまりos.sleep状態の電流はおよそ22mA消費している、十秒に一回はSD書き込みで跳ね上がる

VBUS/VSYSの計測値は以下、USBは未接続だからほぼ0V(VSYSは2.2Ω抵抗をシリーズでその分ドロップ(2.2*22mV)してるはず)

0.01,2.55

0.01,2.55

抵抗両端の電圧測定、つまり電流波形はこんな感じ、1000mAHの電池なら一日半ぐらいは動作可能、ほぼ予測通り

 

<現時点のPythonコード>

RTC関連のクラスがあるので、SD書き込み関連のクラスを追加して見通しをよくした、前回との違いはSDカードの電源供給を書き込み時点だけオンにしている点、時間待ち0.2秒は今少し短くても良さそうだ、システムはアクティブなので信号線からの回り込みでSDカードの電源線の電圧は2V以上はあるからSDへのダメージ対策として、SDへの書き込み終わったらSPIを無効化後に入力High-Zにして回り込みをソフト的に抑制するようにした

#!/usr/bin/python
# -*- coding: utf-8 -*-
from machine import Pin, I2C, ADC, SPI
import time
import binascii
import machine, sdcard, uos

I2C_PORT = 0
I2C_SDA = 20
I2C_SCL = 21
SD_ON = 22
ALARM_PIN = 3
alarm_triggered = False

adc0 = ADC(Pin(26))  # ADC0
adc1 = ADC(Pin(27))  # ADC1

# -----------------------------
# RTC ds3231 class
# -----------------------------
class ds3231():
    w  = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
    address = 0x68
    start_reg = 0x00
    alarm1_reg = 0x07
    control_reg = 0x0e
    status_reg = 0x0f

    def __init__(self,i2c_port,i2c_scl,i2c_sda):
        self.bus = I2C(i2c_port,scl=Pin(i2c_scl),sda=Pin(i2c_sda))
        self.alarm_pin = Pin(ALARM_PIN, Pin.IN, Pin.PULL_UP)
        self.alarm_pin.irq(handler=self.alarm_irq_handler, trigger=Pin.IRQ_FALLING)

    def set_time(self,new_time):
        hour = new_time[0] + new_time[1]
        minute = new_time[3] + new_time[4]
        second = new_time[6] + new_time[7]
        week = "0" + str(self.w.index(new_time.split(",",2)[1])+1)
        year = new_time.split(",",2)[2][2] + new_time.split(",",2)[2][3]
        month = new_time.split(",",2)[2][5] + new_time.split(",",2)[2][6]
        day = new_time.split(",",2)[2][8] + new_time.split(",",2)[2][9]
        now_time = binascii.unhexlify((second + " " + minute + " " + hour + " " + week + " " + day + " " + month + " " + year).replace(' ',''))
        self.bus.writeto_mem(int(self.address),int(self.start_reg),now_time)

    def alarm_irq_handler(self, pin):
        global alarm_triggered
        alarm_triggered = True

    def set_alarm_time(self, alarm_time):
        self.alarm_pin = Pin(ALARM_PIN, Pin.IN, Pin.PULL_UP)
        self.alarm_pin.irq(handler=self.alarm_irq_handler, trigger=Pin.IRQ_FALLING)
        # ステータスフラグクリア
        status = rtc.bus.readfrom_mem(rtc.address, rtc.status_reg, 1)
        rtc.bus.writeto_mem(rtc.address, rtc.status_reg, bytes([status[0] & 0xFE]))
        # コントロールレジスタ設定
        self.bus.writeto_mem(self.address, self.control_reg, b'\x07')
        # アラーム時刻設定
        hour = alarm_time[0] + alarm_time[1]
        minute = alarm_time[3] + alarm_time[4]
        second = alarm_time[6] + alarm_time[7]
        date = alarm_time.split(",", 2)[2][8] + alarm_time.split(",", 2)[2][9]
        now_time = binascii.unhexlify((second + minute + hour + date).replace(' ', ''))
        self.bus.writeto_mem(self.address, self.alarm1_reg, now_time)

    def get_date(self):
        t = self.bus.readfrom_mem(self.address, self.start_reg, 7)
        year = 2000 + bcd2dec(t[6])
        month = bcd2dec(t[5])
        day = bcd2dec(t[4])
        return year, month, day

def bcd2dec(bcd):
    return ((bcd >> 4) * 10) + (bcd & 0x0F)

# -----------------------------
# SDLoggerVFS class (SPI + Hi-Z安全化)
# -----------------------------
class SDLoggerVFS:
    def __init__(self, spi_id, cs_pin, power_pin):
        self.spi_id = spi_id
        self.cs_pin = cs_pin
        self.power_pin = Pin(power_pin, Pin.OUT)
        self.mount_point = '/sd'
        self.spi = None
        # SPIピン番号
        self.SCK_PIN = 18
        self.MOSI_PIN = 17
        self.MISO_PIN = 16

    def power_on(self):
        self.power_pin.value(0)
        time.sleep(0.2)

    def power_off(self):
        self.power_pin.value(1)

    def spi_init(self):
        self.spi = SPI(self.spi_id, baudrate=1_000_000, polarity=0, phase=0)

    def spi_deinit_and_hiz(self):
        if self.spi:
            self.spi.deinit()
            self.spi = None
        # ピンをHi-Z入力に戻してpull-down
        Pin(self.MOSI_PIN, Pin.IN, Pin.PULL_DOWN)
        Pin(self.MISO_PIN, Pin.IN, Pin.PULL_DOWN)
        Pin(self.SCK_PIN, Pin.IN, Pin.PULL_DOWN)

    def write(self, rtc, value1, value2):
        # SDカード電源ON
        self.power_on()
        # SPI初期化
        self.spi_init()
        # SDCardライブラリ初期化
        sd = sdcard.SDCard(self.spi, Pin(self.cs_pin))
        # マウント
        uos.mount(sd, self.mount_point)

        # ファイル書き込み
        year, month, day = rtc.get_date()
        filename = f"{self.mount_point}/{year:04d}-{month:02d}-{day:02d}.txt"
        with open(filename, 'a') as f:
            f.write(f"{value1},{value2}\n")

        # アンマウント
        uos.umount(self.mount_point)
        # SPI停止 + Hi-Z化
        self.spi_deinit_and_hiz()
        # SDカード電源OFF
        self.power_off()

# -----------------------------
# RTC時間加算用
# -----------------------------
def add_time_period_to_rtc_time(rtc):
    t = rtc.bus.readfrom_mem(rtc.address, rtc.start_reg, 7)
    year = 2000 + bcd2dec(t[6])
    month = bcd2dec(t[5] & 0x1F)
    day = bcd2dec(t[4] & 0x3F)
    hour = bcd2dec(t[2] & 0x3F)
    minute = bcd2dec(t[1] & 0x7F)
    second = bcd2dec(t[0] & 0x7F)
    weekday = t[3] & 0x07
    def is_leap(year):
        return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
    def days_in_month(y, m):
        mdays = [31, 29 if is_leap(y) else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        return mdays[m-1]
    second += 10
    if second >= 60:
        second -= 60
        minute += 1
        if minute >= 60:
            minute = 0
            hour += 1
            if hour >= 24:
                hour = 0
                day += 1
                weekday = weekday + 1 if weekday < 7 else 1 if day > days_in_month(year, month):
                    day = 1
                    month += 1
                    if month > 12:
                        month = 1
                        year += 1
    w = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]
    weekday_str = w[weekday-1]
    alarm_time_str = "{:02d}:{:02d}:{:02d},{},{:04d}-{:02d}-{:02d}".format(
        hour, minute, second, weekday_str, year, month, day)
    return alarm_time_str

def set_alarm():
    alarm_time_later = add_time_period_to_rtc_time(rtc)
    print("Alarm time 10 sec later:", alarm_time_later)
    rtc.set_alarm_time(alarm_time_later)
    rtc.alarm_pin = Pin(ALARM_PIN, Pin.IN, Pin.PULL_UP)

def clear_alarm_flag():
    status = rtc.bus.readfrom_mem(rtc.address, rtc.status_reg, 1)
    rtc.bus.writeto_mem(rtc.address, rtc.status_reg, bytes([status[0] & 0xFE]))

# -----------------------------
# main
# -----------------------------
if __name__ == '__main__':
    rtc = ds3231(I2C_PORT,I2C_SCL,I2C_SDA)
    set_alarm()

    logger = SDLoggerVFS(0, 17, SD_ON)
    value1 = round((adc0.read_u16() >> 4) * 3.3 * 3 / 4096, 2)
    value2 = round((adc1.read_u16() >> 4) * 3.3 * 2 / 4096, 2)
    print(value1, value2)
    logger.write(rtc, value1, value2)

    try:
        while True:
            if alarm_triggered:
                alarm_triggered = False
                clear_alarm_flag()
                time.sleep_ms(5)
                set_alarm()
                value1 = round((adc0.read_u16() >> 4) * 3.3 * 3 / 4096, 2)
                value2 = round((adc1.read_u16() >> 4) * 3.3 * 2 / 4096, 2)
                print(value1, value2)
                if value2 > 1.8:
                    logger.write(rtc, value1, value2)
                time.sleep(10)
    except KeyboardInterrupt:
        print("terminated")

 

admin

 

 

 

電池駆動のデータロガーを作ってみる(3) — SDカードアクセスとタイマー割り込み処理の追加

次の要素はSPIバスにつながるSDカードへのアクセスです

<microPythonのSDカードアクセスライブラリと動作確認>

https://github.com/micropython/micropython-lib/tree/master/micropython

ここから持ってきました、

ライブラリの転送はVScodeでは面倒ぽいので、Thonnyを使って転送、ライブラリは一度ピコに書き込めばVScodeでもそのまま有効です、ただしVScodeですでにUSBシリアル使ってるとThonnyは接続できないから、VScode側は終了しないと競合してしまいます、逆も真也

実行時のVScodeのコンソール出力、上段はmicroSD未挿入だからエラーが出る、カードがラズパイ用のSDカードシステムなので。それらしきファイルが見えます、ファイル形式はおそらくFAT32しか扱えないと思う

———————————————————
MicroPython v1.26.0 on 2025-08-09; Raspberry Pi Pico2 with RP2350
Type "help()" for more information or .help for custom vREPL commands.

>>> 
Traceback (most recent call last):
  File "", line 7, in 
  File "sdcard.py", line 54, in __init__
  File "sdcard.py", line 82, in init_card
OSError: no SD card

>>> 
2025/09/12 17:18:00 Friday
alarm1 time is up
———————————————————

os.listdir(‘/sd’)	の結果を格納して出力(以下のコード)すると、

files = os.listdir('/sd')   # set to mount point

for file in files:
    print(file)

>>> 
overlays
bcm2708-rpi-b-plus.dtb
LICENCE.broadcom
issue.txt

~~~ 以下省略(ラズパイのシステムディスクだからこんな中身)

一応カード検出機能もGPIOに入れてるけど、SDカードにアクセスできなきゃこうなる、実際の運用ではカード検出できなかったら例えばLEDを高速点滅とかの手段だろうね

<タイマー割り込み設定>

タイマーIC(DS3231)のタイマー割り込み設定は絶対時間指定しかできないから、西暦以降全ての指定が必要、ただしタイマーICはデータ形式がBCD(Binary Code Decimal)、例えば十進21は15hではなく21hのような形式だから、内部で都度変換処理が必要、全部生成AIでコード作成したけどね

仮にUSB電源電圧を1分ごとにログするようにしてみた結果はこんな感じ、絶対時間で指定だから、相対時間指定のように段々処理時間が加算されて時間がずれていくようなことはない

Alarm time 1 min later: 20:28:31,Sunday,2025-09-14
5.09
Alarm time 1 min later: 20:29:31,Sunday,2025-09-14
5.09
Alarm time 1 min later: 20:30:31,Sunday,2025-09-14
5.1
Alarm time 1 min later: 20:31:31,Sunday,2025-09-14
5.09
Alarm time 1 min later: 20:32:31,Sunday,2025-09-14
5.1

 

<ファイルへの書き込み>

アナログ的に入力電圧を抵抗分割で1/3しているからリアルの電圧に換算してSDカードに書き込み、書き込みデータの最後の方はこんな風になってます

5.09
5.1
5.09
5.1
5.09
5.1
5.07

次は電源制御だね、広義にはmicroPythonでどこまで省電力動作できるかどうかということ

<今現在のコード>

おそらく冗長になってると思う、

#!/usr/bin/python
# -*- coding: utf-8 -*-
from machine import Pin, I2C, ADC
import time
import binascii
import machine, sdcard, os

#    https://www.waveshare.net/w/upload/0/08/Pico-RTC-DS3231_Sch.pdf
I2C_PORT = 0
I2C_SDA = 20
I2C_SCL = 21
SD_ON = 22

ALARM_PIN = 3
alarm_triggered = False

adc = ADC(Pin(26))  # ADC0 pin


class ds3231():
#            13:45:00 Mon 24 May 2021
#  the register value is the binary-coded decimal (BCD) format
#               sec min hour week day month year
    NowTime = b'\x00\x45\x13\x02\x24\x05\x21'
    w  = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
    address = 0x68
    start_reg = 0x00
    alarm1_reg = 0x07
    control_reg = 0x0e
    status_reg = 0x0f
    
    def __init__(self,i2c_port,i2c_scl,i2c_sda):
        self.bus = I2C(i2c_port,scl=Pin(i2c_scl),sda=Pin(i2c_sda))

    def set_time(self,new_time):
        hour = new_time[0] + new_time[1]
        minute = new_time[3] + new_time[4]
        second = new_time[6] + new_time[7]
        week = "0" + str(self.w.index(new_time.split(",",2)[1])+1)
        year = new_time.split(",",2)[2][2] + new_time.split(",",2)[2][3]
        month = new_time.split(",",2)[2][5] + new_time.split(",",2)[2][6]
        day = new_time.split(",",2)[2][8] + new_time.split(",",2)[2][9]
        now_time = binascii.unhexlify((second + " " + minute + " " + hour + " " + week + " " + day + " " + month + " " + year).replace(' ',''))
        #print(binascii.unhexlify((second + " " + minute + " " + hour + " " + week + " " + day + " " + month + " " + year).replace(' ','')))
        #print(self.NowTime)
        self.bus.writeto_mem(int(self.address),int(self.start_reg),now_time)

    def alarm_irq_handler(self, pin):
        global alarm_triggered
        alarm_triggered = True

    def set_alarm_time(self, alarm_time):
        self.alarm_pin = Pin(ALARM_PIN, Pin.IN, Pin.PULL_UP)
        self.alarm_pin.irq(handler=self.alarm_irq_handler, trigger=Pin.IRQ_FALLING)

    # ステータスフラグクリア
        status = self.bus.readfrom_mem(self.address, self.status_reg, 1)
        self.bus.writeto_mem(self.address, self.status_reg, bytes([status[0] & 0xFE]))

    # コントロールレジスタ設定 (INTCN=1, A1IE=1)
        self.bus.writeto_mem(self.address, self.control_reg, b'\x07')

    # アラーム時刻設定
        hour = alarm_time[0] + alarm_time[1]
        minute = alarm_time[3] + alarm_time[4]
        second = alarm_time[6] + alarm_time[7]
        date = alarm_time.split(",", 2)[2][8] + alarm_time.split(",", 2)[2][9]
        now_time = binascii.unhexlify((second + minute + hour + date).replace(' ', ''))
        self.bus.writeto_mem(self.address, self.alarm1_reg, now_time)

    def get_date(self):
        t = self.bus.readfrom_mem(self.address, self.start_reg, 7)
        year = 2000 + bcd2dec(t[6])
        month = bcd2dec(t[5])
        day = bcd2dec(t[4])
        return year, month, day

# time format conversion
def bcd2dec(bcd):
        return ((bcd >> 4) * 10) + (bcd & 0x0F)

def sdmount_getlist():
    sd = sdcard.SDCard(machine.SPI(0), machine.Pin(17))
    os.mount(sd, '/sd')
    files = os.listdir('/sd')   # set to mount point

    for file in files:
        print(file)

def get_adc_write(filename):
    value = round((adc.read_u16() >> 4) * 3.3 * 3 / 4096, 2)  # calculate real voltage and round to 2 degits below decimal point
    print(value)
    
    with open(filename, 'a') as f:
        f.write("{}\n".format(value))

def add_one_minute_to_rtc_time(rtc):
    # RTCから生の7バイトを取得(BCD形式)
    t = rtc.bus.readfrom_mem(rtc.address, rtc.start_reg, 7)
    
    year = 2000 + bcd2dec(t[6])
    month = bcd2dec(t[5] & 0x1F)
    day = bcd2dec(t[4] & 0x3F)
    hour = bcd2dec(t[2] & 0x3F)
    minute = bcd2dec(t[1] & 0x7F)
    second = bcd2dec(t[0] & 0x7F)
    weekday = t[3] & 0x07  # 1-7
    
    # 月ごとの日数 (うるう年考慮)
    def is_leap(year):
        return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
    def days_in_month(y, m):
        mdays = [31, 29 if is_leap(y) else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        return mdays[m-1]
    
    # 1分加算
    minute += 1
    if minute >= 60:
        minute = 0
        hour += 1
    if hour >= 24:
        hour = 0
        day += 1
        weekday = weekday + 1 if weekday < 7 else 1 if day > days_in_month(year, month):
            day = 1
            month += 1
            if month > 12:
                month = 1
                year += 1
    
    # 曜日文字列
    w = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]
    weekday_str = w[weekday-1]
    
    # alarm_time 文字列形式 'HH:MM:SS,Weekday,YYYY-MM-DD'
    alarm_time_str = "{:02d}:{:02d}:{:02d},{},{:04d}-{:02d}-{:02d}".format(
        hour, minute, second, weekday_str, year, month, day)
    
    return alarm_time_str

# set alrm time period
def set_alarm():
    alarm_time_1min_later = add_one_minute_to_rtc_time(rtc)
    print("Alarm time 1 min later:", alarm_time_1min_later)
    rtc.set_alarm_time(alarm_time_1min_later)

if __name__ == '__main__':

    # SD card power control
    pow_pin = Pin(SD_ON, Pin.OUT)
    pow_pin.value(0)

    sdmount_getlist()
    
    # make RTC instance
    rtc = ds3231(I2C_PORT,I2C_SCL,I2C_SDA)
    #rtc.set_time('15:38:00,Sunday,2025-09-14')
    
    set_alarm()
    
    # create file if not exists or open for append later
    year, month, day = rtc.get_date()
    filename = "/sd/{:04d}-{:02d}-{:02d}.txt".format(year, month, day)

    with open(filename, 'a') as f:
        pass  # to create if not exist

    get_adc_write(filename)
        
    try:
        while True:
            if alarm_triggered:
                alarm_triggered = False
                set_alarm()
                get_adc_write(filename)
    except KeyboardInterrupt:
        print("terminated")

<写真>

表裏です、裏はもりそばです

 

admin