ラズピコrustでmicro:bitと連携させてみた

ラズパイ財団プロダクト同士でなんか作ってみようと思って、micro:bitをフロントエンドとしてmic入力とLED出力を使った、音声のレベル表示(一応LED25個で25レベルの表示ができます)をやってみた

macro:bit (p1) –> ラズピコADC1(平均化処理)-> PWM出力およそ22KHzをrcでローパス -> micro:bit(p2)という流れ

<micro:bit側のコード>

from microbit import *

def show_bar(level):  # level: 0-25
    display.clear()
    height = level // 5  # 5段に分割
    width = level % 5    # 各段の幅
    
    # 下からバー積み上げ
    for y in range(5):
        if 4-y < height:
            for x in range(5):
                display.set_pixel(x, 4-y, 9)
        elif 4-y == height:
            for x in range(width):
                display.set_pixel(x, 4-y, 9)

while True:
    mic_level = microphone.sound_level()
    pin1.write_analog(mic_level * 4)
    
    control_level = pin2.read_analog()
    # 0-25に変換
    bar_level = min(control_level // 40, 25) 
    show_bar(bar_level)
    sleep(10)

<ラズピコ側>

Cargo.toml

embedded-halの1.0系は0.2と互換なし、embeded-halはobject指向言語で言うところのinterfaceでrp-235x-halは実装、つまりプロセッサが変わっても共通だろうinterfaceをembedded-halはtraitの集合として持っている


[package]
edition = "2024"
name = "rust_starter"
version = "0.1.0"
license = "MIT or Apache-2.0"


[dependencies]
cortex-m = "0.7.7"
cortex-m-rt = "0.7.5"
rp235x-hal = "0.3"
embedded-hal = "0.2"
#embedded-hal = "1.0.0" does not support rp235x-hal yet
defmt = "1.0.1"
defmt-rtt = "1.0.1"
panic-probe = { version = "1.0", features = ["print-defmt"] }

[build-dependencies]
regex = "1.11.0"

[target.'cfg( target_arch = "arm" )'.dependencies]
panic-probe = { version = "1", features = ["print-defmt"] }

[target."thumbv8m.main-none-eabihf".dependencies]
rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] }

<Rustコード>

Lチカコードを修正

#![no_std]
#![no_main]

use defmt_rtt as _;
use hal::pac;

use embedded_hal::adc::OneShot;
use embedded_hal::blocking::delay::DelayUs;
use embedded_hal::PwmPin;

#[cfg(target_arch = "riscv32")]
use panic_halt as _;
#[cfg(target_arch = "arm")]
use panic_probe as _;

// Alias for our HAL crate
use hal::entry;

#[cfg(rp2350)]
use rp235x_hal as hal;

#[cfg(rp2040)]
use rp2040_hal as hal;

// use bsp::entry;
// use bsp::hal;
// use rp_pico as bsp;

/// The linker will place this boot block at the start of our program image. We
/// need this to help the ROM bootloader get our code up and running.
/// Note: This boot block is not necessary when using a rp-hal based BSP
/// as the BSPs already perform this step.
#[unsafe(link_section = ".boot2")]
#[used]
#[cfg(rp2040)]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;

/// Tell the Boot ROM about our application
#[unsafe(link_section = ".start_block")]
#[used]
#[cfg(rp2350)]
pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe();

/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz.
/// Adjust if your board has a different frequency
const XTAL_FREQ_HZ: u32 = 12_000_000u32;
const DELAY: u32 = 1000; // μs delay
const SAMPLES: u32 = 250;

/// Entry point to our bare-metal application.
///
/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables and the spinlock are initialised.
///
/// The function configures the rp2040 and rp235x peripherals, then toggles a GPIO pin in
/// an infinite loop. If there is an LED connected to that pin, it will blink.
#[entry]
fn main() -> ! {
    // 1. Peripherals取得
    let mut pac = pac::Peripherals::take().unwrap();

    // 2. クロック・ウォッチドッグ初期化
    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
    let clocks = hal::clocks::init_clocks_and_plls(
        XTAL_FREQ_HZ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    // 3. GPIO初期化
    let sio = hal::Sio::new(pac.SIO);
    let pins = hal::gpio::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks);

    // 4. ADC設定
    let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS);
    let mut adc_pin = hal::adc::AdcPin::new(pins.gpio26).unwrap();

    // 5. PWM設定
    let pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS);
    let mut pwm = pwm_slices.pwm0;
    pwm.set_ph_correct();
    pwm.set_top(4095);
    pwm.enable();

    // GP0 = PWM Slice 0 Channel A(alomost 20KHz duty 99% @3.3v full scale)

    let mut pwm_channel = pwm.channel_a;
    let _pwm_pin = pins.gpio0.into_function::<hal::gpio::FunctionPwm>(); // GP0 as a PWM output

    loop {
        let mut sum: u32 = 0;

        // measure "samples" time and make average
        for _ in 0..SAMPLES {
            let v: u16 = adc.read(&mut adc_pin).unwrap();
            sum += v as u32;
        }

        let avg = (sum / SAMPLES) as u16;
        pwm_channel.set_duty(avg);
        delay.delay_us(DELAY);
    }
}

/// Program metadata for `picotool info`
#[unsafe(link_section = ".bi_entries")]
#[used]
pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [
    hal::binary_info::rp_cargo_bin_name!(),
    hal::binary_info::rp_cargo_version!(),
    hal::binary_info::rp_program_description!(c"Blinky Example"),
    hal::binary_info::rp_cargo_homepage_url!(),
    hal::binary_info::rp_program_build_attribute!(),
];

// End of file

動いてるところの動画は以下に、

まだ周辺回路サポートのcrateは途上ではあるけれども、組み込みでも普通に使えるようになってきたのは大きな進歩

 

admin

ラズピコでRust

ラズベリー財団がVScodeのpcio拡張機能でRust(実はZephyrも)サポートするようになったので動かしてみた

<構成と環境>

・ラズピコ2(無線なし)

・M4 MacBook Pro 32GB

・純正デバッグプローブ

 

<手順>

すでにインストール済みのpico拡張機ののメニューにはRustプロジェクト作成のメニューが出ています

ここでNew Rust Projectを選択するとLチカのサンプル、実は初代ラズピコやラズピコのRISCコア対応にもなってます、が開かれます

ラズピコ2にデバッガー接続、ピン立っていないので追加で半田付けして接続

接続確認は、

% probe-rs list
The following debug probes were found:
[0]: Debug Probe _CMSIS_DAP_ -- 2e8a:000c:E663AC91D3826F39 (CMSIS-DAP)

のように見えればつながっています

 

この状態でほぼすぐにデバッグができました、Runはファイルモードでしか機能しませんがデバッグは上位互換なので問題なし、デバッグだと.uf2のファイルも必要なくて、バイナリそのままがラズピコに書き込まれます

これ、デバッガーでブレークポイント設定して止めたとこ

 

めちゃくちゃ簡単に環境構築できるから、まだ無線関連のcrateは不備だけど、もうRustでできることはRust以外の選択は、ラズピコではないと思う

 

admin

ソーラー充電のログ取ってみた結果

ソーラーシステムの電圧ログを取ってみる

この記事のフォローになります

8日間程度のデータですが、

 

こんな感じです、ソーラー電圧は日中の光を受けて電力発生している時間帯にはピークが見えます、この先継続しても12.4V程度は下回りそうにないので十分に効果はあると言えそうです、発電量が一番低下するのは太陽の高度から冬至の頃だからその頃に再度ログ取ってみようかと思う

但し真夏の太陽と違って、晴れてもバッテリーを満充電にするようなパワーはない模様

 

admin

lightsleep有効での電池電圧変化(ラズピコrp2040データロガー)

ようやくそれらしいデータが取れた、何しろ省電力有効にすると電池寿命長くなるから、時間かかると言えば当然と言えば当然ですが

<条件>

・電池:NiH電池の二直、容量1000mAH

・ラズピコ:rp2040のWi-Fi無しモデル

・サンプル周期:10分間隔で電池電圧をSDカードに記録

電池の放電電圧の開始電圧は比較のため合わせてあります(省電力有効は満充電から、省電力無効は放電途中からの測定だから)

SDカードのファイルの見え方、

10月7日の午後から今日の午後までの実行結果、大勢に影響ないけどファイルの日付がなぜこうなるかは理解できていない

青は省電力がほぼ効かない状態での測定、緑はlighsleepをデータサンプリング間隔(10分)で設定してとったデータ

電流測定では22mA vs 4mAだったけど、電池電圧の変化はそれ以上に見えてます、なぜだろう?

 

admin

そこにも落とし穴あったか、(ラズピコmicroPython)

ラズピコのmicroPythonで以下のようなコードではRTC割り込み受け付けられません

つまりlight sleep()で無限に待つ設定にして、RTC割り込みを受けようとしてもラズピコのmicroPython実装では、lightsleep中は他の割り込み受け付けは機能しません

    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 > 2.0:
                    logger.write(rtc, value1, value2)
                machine.lightsleep()
    except KeyboardInterrupt:
        print("terminated")

従って、やりたい機能を実装しようとすると、

machine.lightsleep()でRTC割り込みと同等の値(最大値)を設定してやらないとダメ、電池寿命計ろうと思って無限待ちにしたら最初のRTC割り込みしか実行されず、それ以降のRTC割り込みは発生しなくなってしまった

まあラズピコの実装レベルはその程度のようです

 

admin

1-wireの温度センサーを使う

ラズピコのデータロガーの使い道で温度の記録は主要な用途かも知れないけど、屋外で使うには防水性が必要

ds18x20は1-wire製品で単品では防水性はないけれども、それを金属管に入れて防水性を確保したものが安く販売されているので購入、Amazonで3本セットで販売のもの

SHT30との測定値の差を見るために以下のコードを使用、GP2は推奨の4.7KΩでプルアップ、プルアップしなくてもそれらしい値は出てくるけど、本来的にはかりそめだと思った方が良い、特に複数本つなぐとそれなりに静電容量も増加するし

import time
import machine, onewire, ds18x20
from machine import Pin, I2C

# I2C1 (SDA=GP6, SCL=GP7)
i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=100000)

# --- SHT30 定義 ---
SHT30_ADDR = 0x44
SHT30_CMD_MEASURE = b'\x2C\x10' # set to highspeed response mode

def read_sht30():
    i2c.writeto(SHT30_ADDR, SHT30_CMD_MEASURE)
    time.sleep(0.008)
    data = i2c.readfrom(SHT30_ADDR, 6)
    temp_raw = data[0] << 8 | data[1]
    hum_raw  = data[3] << 8 | data[4]
    temperature = -45 + (175 * temp_raw / 65535)
    humidity    = 100 * hum_raw / 65535
    return temperature, humidity

# 1-Wire の設定(例: GP2)
dat = machine.Pin(2)

# 1-Wire バス作成
ow = onewire.OneWire(dat)
ds = ds18x20.DS18X20(ow)

# バス上のデバイスをスキャン(複数のDS18B20が繋がっている場合あり)
roms = ds.scan()
print('Found DS devices:', roms)

while True:
    # 温度変換開始
    ds.convert_temp()
    time.sleep_ms(750)   # 最大分解能(12bit)の変換待ち時間
    
    for rom in roms:
        temp = ds.read_temp(rom)
        print('Device', rom, 'Temp={:.1f} C'.format(temp))
    
    print('-' * 40)

    try:
        t, h = read_sht30()
        print("SHT30   Temp={:.1f} C  Hum={:.0f} %".format(t, h))
        print("-" * 40)
    except Exception as e:
        print("Error:", e)
    time.sleep(2)

<実行結果>

今回使用のセンサーのIDもプリントアウトするようにしてます、複数箇所の測定をする時にはセンサーで見て分かる識別手段が必要

測定値で1.5℃ぐらい差があるけども、これはアプリによっては許容範囲、おそらく3本それぞれでもばらつきはあるはず

ロガー自体をケースに入れて、センサーを複数本使ってログを取るような形式になるはず

 

admin

Grove shieldでi2c接続

Switch scienceで販売されてる、ラズピコ用のグローブシールド

https://www.switch-science.com/products/7109?_pos=11&_sid=85b9241fd&_ss=r

数年前に買ったけど初めて使ってみた、但しちゃんとした回路図が見つからないから実物で追っかけないとi2cの接続先わからなかった

以下のコードの、

i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=100000)

がI2C1の接続先になってます

import time
import machine, onewire, ds18x20
from machine import Pin, I2C

# I2C1 (SDA=GP6, SCL=GP7)
i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=100000)

# --- SHT30 定義 ---
SHT30_ADDR = 0x44
SHT30_CMD_MEASURE = b'\x2C\x10' # set to highspeed response mode

def read_sht30():
    i2c.writeto(SHT30_ADDR, SHT30_CMD_MEASURE)
    time.sleep(0.008)
    data = i2c.readfrom(SHT30_ADDR, 6)
    temp_raw = data[0] << 8 | data[1]
    hum_raw  = data[3] << 8 | data[4]
    temperature = -45 + (175 * temp_raw / 65535)
    humidity    = 100 * hum_raw / 65535
    return temperature, humidity

# --- メイン ---
while True:
    try:
        t, h = read_sht30()
        print("SHT30   Temp={:.1f} C  Hum={:.0f} %".format(t, h))
        print("-" * 40)
    except Exception as e:
        print("Error:", e)
    time.sleep(2)

こんな感じで吐き出してきます

SHT30   Temp=28.5 C  Hum=62 %
----------------------------------------
SHT30   Temp=28.6 C  Hum=62 %
----------------------------------------

アプリケーションは防水機能のある温度センサーが必要で、ポピュラーなのはds18x20の先端がsusパイプで覆われたやつが使えそうです

インターフェースが1-wireなので、増設も並列につなげていけばできるけど読み出し時間はかなりかかるらしい、なぜ増設した時に個体識別できるかはds18x20が出荷時点で固有のIDが付与される、つまりMACアドレスなどと同じ理屈、から可能ということ

 

admin

電池駆動のデータロガーを作ってみる(5) — ラズピコの省電力はイマイチ

結局ラズピコ2は、

① microPythonはlightsleepもdeepsleepも実装が不完全

② pico-sdkはSDカードアクセスライブラリが無いぽい

③ Arduino IDEはi2cの設定Wire()関数でUSBの通信が切れる

ということで、ラズピコ2はまだ環境がイマイチなので、ラズピコでやってみた

<結果>

① ラズピコはおそらく周辺回路のデザインによるだろうけど、lightsleepでもdeepsleepでもスリープ状態の電流は4mA程度(単三電池2本使用で)だから、電池容量1,000mAでほぼ10日がmax

② deepsleep使うアプリ書き込んでしまうと、ThonnyやVScodeのようなUSBシリアル使うツールは使えなくなる、リセットするにはdeskモードにして、

% picotool erase
Erasing:              [==============================]  100%

picotoolで初期化が必要

③ 消費電流に差がなくて、余計な初期化処理が走るdeepsleepにラズピコでは使い道がなさそうだ

ラズピコでもGrove経由で色々なセンサーへの接続はできるので、サンプリング対象を変えればロガーとして使うことはできる

ということで、当初の目論見の数ヶ月を単三電池2本でというのはラズピコでは無知というのが結論、アプリ次第で一週間もてば十分なケースもあるし、電池容量増やせば期間延長もできるわけだけども

 

admin

ラズピコのmicroPython(番外編)

microPython用のIDE、具体的にはThonny、VScodeの拡張機能MicroPicoを使ってのいくつかの気づき

・IDEとはUSB経由での通信だけど、例えばVScode上でのコード実行は実際はRAM上に展開して実行される、従ってラズピコにmain.pyがすでに存在している時には、実行(run)も無効だし、ラズピコ側も動作停止する(USBシリアル使っている時には競合するから、停止ということ)

・ラズピコへのコード(***.py)の転送はThonnyが直感的で便利、但しここでもUSBは共有できないからVScodeは終了しておかないとダメ

・したがって、microPythonへファームさえ書き込んでしまえば、それ以降はIDEにはファイルに見せる操作は不要

ラズピコ2はいろんな機能がいまだサポートされてないようだから、今回のプロジェクトは前世代のラズピコに変えるつもり、それならmicroPythonのmichine.lightsleepやdeepsleepも動作するだろうからそれで十分だよ

 

admin

電池駆動のデータロガーを作ってみる(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