Python example code for using i2c

Has anyone used Python (probably with smbus) to access I2C on the Duo boards?
If so, do you have any example code you can share?
I’ve seen the C code using /dev/i2cdev-x and ioctl, but I would rather use a cleaner interface, such as smbus, with python.
I’ve done this on Raspberry Pi’s, but the tools on the duo boards are so convoluted.

Any help is appreciated.

1 Like

As for the bigger core, have you ever tried MicroPython / CircuitPython? It should do.

Thanks for the reply.
That’s interesting… is there a port of CP for the MilkV?
I use CP on all my other boards (except Raspi)… RP2040, ESP32S3, etc.
Is there a guide/documentation somewhere about porting CP or even MP onto the MilkV boards?

1 Like

Just try looking at this, it is probable that you will like it (but it’s not ideal).

1 Like

Hardware requirements
BMP280 with i2c interface.

Software requirements
python-periphery

Hardware requirements
BMP180 with i2c interface.

Hardware requirements
pcf8574x.

2 Likes

Thanks for the info.
Where do I get the periphery lib?
And how do I install on the duo?
Do I need to add it in build-root or can I just put into the python library?

1 Like

I tried to import pinpong, but it failed.
Do I have use build-root to bake it into an image?

Any help on the config would be appreciated.

1 Like

Which Duo board are you using? What firmware image are you using?

1 Like

Duo256m and Duo running release SD1.1.1.
But I have the build-root SDK, so I can build what ever is necessary.

1 Like

Milk-V Duo (64M), SD image. I built it myself against the official buildroot&SDK (v1.1.1), the only difference is no ION reserved.

Here’s pinpong, with a textbook example you could find by the link I sent earlier:

[root@milkv-duo]~# python3
Python 3.9.5 (default, Feb 13 2024, 18:09:16) 
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
from pinpong.board import Board,Pin

Board("MILKV-DUO").begin()

led = Pin(Pin.D25, Pin.OUT)

while True:
  led.value(1)
  print("1")
  time.sleep(1)

  led.value(0)
  print("0")
  time.sleep(1)>>> 
>>> >>> milkv-duo

  __________________________________________
 |    ____  _       ____                    |
 |   / __ \(_)___  / __ \____  ____  ____ _ |
 |  / /_/ / / __ \/ /_/ / __ \/ __ \/ __ `/ |
 | / ____/ / / / / ____/ /_/ / / / / /_/ /  |
 |/_/   /_/_/ /_/_/    \____/_/ /_/\__, /   |
 |   v0.5.2  Designed by DFRobot  /____/    |
 |__________________________________________|
 
<pinpong.board.Board object at 0x3fb9233ee0>
>>> >>> >>> >>> ... ... ... ... 1
1
1
1
1
1
1
1
1
1
1
1
1
1
^Cexit_handler

Hello,
here you find some i2c test, in python-periphery and bash with mcp23017 with photo and video:
https://github.com/bigjohnson/duo256m/

1 Like

Today tested with python smbus2 reading i2c memory, all work ok.
pip install smbus2

1 Like
  1. install smbus2 with pip3
  2. in milkv duo s use pins pin b20 and b21 (that is i2c bus 4)

bmp sensor example

from smbus2 import SMBus
import time

DEVICE_ADDR = 0x77

def read_signed_16(bus, addr):
    high = bus.read_byte_data(DEVICE_ADDR, addr)
    low = bus.read_byte_data(DEVICE_ADDR, addr+1)
    val = (high << 8) + low
    if val >= 32768:
        val = val - 65536
    return val

def read_unsigned_16(bus, addr):
    high = bus.read_byte_data(DEVICE_ADDR, addr)
    low = bus.read_byte_data(DEVICE_ADDR, addr+1)
    return (high << 8) + low

with SMBus(4) as bus:
    # Read calibration data from EEPROM
    AC1 = read_signed_16(bus, 0xAA)
    AC2 = read_signed_16(bus, 0xAC)
    AC3 = read_signed_16(bus, 0xAE)
    AC4 = read_unsigned_16(bus, 0xB0)
    AC5 = read_unsigned_16(bus, 0xB2)
    AC6 = read_unsigned_16(bus, 0xB4)
    B1 = read_signed_16(bus, 0xB6)
    B2 = read_signed_16(bus, 0xB8)
    MB = read_signed_16(bus, 0xBA)
    MC = read_signed_16(bus, 0xBC)
    MD = read_signed_16(bus, 0xBE)

    # Read raw temperature
    bus.write_byte_data(DEVICE_ADDR, 0xF4, 0x2E)
    time.sleep(0.005)
    UT = (bus.read_byte_data(DEVICE_ADDR, 0xF6) << 8) + bus.read_byte_data(DEVICE_ADDR, 0xF7)

    # Read raw pressure (oss = 0)
    bus.write_byte_data(DEVICE_ADDR, 0xF4, 0x34)
    time.sleep(0.005)
    UP = (bus.read_byte_data(DEVICE_ADDR, 0xF6) << 8) + bus.read_byte_data(DEVICE_ADDR, 0xF7)

    # Calculate true temperature
    X1 = ((UT - AC6) * AC5) >> 15
    X2 = (MC << 11) // (X1 + MD)
    B5 = X1 + X2
    temp = ((B5 + 8) >> 4) / 10.0

    # Calculate true pressure
    B6 = B5 - 4000
    X1 = (B2 * (B6 * B6 >> 12)) >> 11
    X2 = (AC2 * B6) >> 11
    X3 = X1 + X2
    B3 = (((AC1 * 4 + X3) << 0) + 2) >> 2
    X1 = (AC3 * B6) >> 13
    X2 = (B1 * ((B6 * B6) >> 12)) >> 16
    X3 = ((X1 + X2) + 2) >> 2
    B4 = (AC4 * (X3 + 32768)) >> 15
    B7 = (UP - B3) * 50000
    if B7 < 0x80000000:
        p = (B7 * 2) // B4
    else:
        p = (B7 // B4) * 2
    X1 = (p >> 8) * (p >> 8)
    X1 = (X1 * 3038) >> 16
    X2 = (-7357 * p) >> 16
    pressure = p + ((X1 + X2 + 3791) >> 4)

    print(f"Temperature: {temp:.1f} °C")
    print(f"Pressure: {pressure} Pa")

scan i2c

from smbus2 import SMBus

def scan_i2c(bus_number):
    print(f"Scanning I2C bus {bus_number}...")
    devices = []
    with SMBus(bus_number) as bus:
        for address in range(0x03, 0x78):
            try:
                bus.read_byte(address)
                devices.append(address)
            except OSError:
                continue
    if devices:
        print("Found I2C device(s) at address(es):")
        for d in devices:
            print(f"  0x{d:02X}")
    else:
        print("No I2C devices found.")

scan_i2c(4)

use an oled screen (ssd1306)

from smbus2 import SMBus
import time

# OLED I2C config
I2C_ADDR = 0x3C
I2C_BUS = 4
CMD = 0x00
DATA = 0x40

# Minimal 5x7 font (vertical bytes, bit 0 = top)
font5x7 = {
    ' ': [0x00, 0x00, 0x00, 0x00, 0x00],
    '!': [0x00, 0x00, 0x5F, 0x00, 0x00],
    '.': [0x00, 0x60, 0x60, 0x00, 0x00],
    ':': [0x00, 0x36, 0x36, 0x00, 0x00],
    '-': [0x08, 0x08, 0x08, 0x08, 0x08],

    '0': [0x3E, 0x51, 0x49, 0x45, 0x3E],
    '1': [0x00, 0x42, 0x7F, 0x40, 0x00],
    '2': [0x42, 0x61, 0x51, 0x49, 0x46],
    '3': [0x21, 0x41, 0x45, 0x4B, 0x31],
    '4': [0x18, 0x14, 0x12, 0x7F, 0x10],
    '5': [0x27, 0x45, 0x45, 0x45, 0x39],
    '6': [0x3C, 0x4A, 0x49, 0x49, 0x30],
    '7': [0x01, 0x71, 0x09, 0x05, 0x03],
    '8': [0x36, 0x49, 0x49, 0x49, 0x36],
    '9': [0x06, 0x49, 0x49, 0x29, 0x1E],

    'A': [0x7E, 0x11, 0x11, 0x11, 0x7E],
    'B': [0x7F, 0x49, 0x49, 0x49, 0x36],
    'C': [0x3E, 0x41, 0x41, 0x41, 0x22],
    'D': [0x7F, 0x41, 0x41, 0x22, 0x1C],
    'E': [0x7F, 0x49, 0x49, 0x49, 0x41],
    'F': [0x7F, 0x09, 0x09, 0x09, 0x01],
    'G': [0x3E, 0x41, 0x49, 0x49, 0x7A],
    'H': [0x7F, 0x08, 0x08, 0x08, 0x7F],
    'I': [0x00, 0x41, 0x7F, 0x41, 0x00],
    'J': [0x20, 0x40, 0x41, 0x3F, 0x01],
    'K': [0x7F, 0x08, 0x14, 0x22, 0x41],
    'L': [0x7F, 0x40, 0x40, 0x40, 0x40],
    'M': [0x7F, 0x02, 0x04, 0x02, 0x7F],
    'N': [0x7F, 0x04, 0x08, 0x10, 0x7F],
    'O': [0x3E, 0x41, 0x41, 0x41, 0x3E],
    'P': [0x7F, 0x09, 0x09, 0x09, 0x06],
    'Q': [0x3E, 0x41, 0x51, 0x21, 0x5E],
    'R': [0x7F, 0x09, 0x19, 0x29, 0x46],
    'S': [0x46, 0x49, 0x49, 0x49, 0x31],
    'T': [0x01, 0x01, 0x7F, 0x01, 0x01],
    'U': [0x3F, 0x40, 0x40, 0x40, 0x3F],
    'V': [0x1F, 0x20, 0x40, 0x20, 0x1F],
    'W': [0x3F, 0x40, 0x38, 0x40, 0x3F],
    'X': [0x63, 0x14, 0x08, 0x14, 0x63],
    'Y': [0x07, 0x08, 0x70, 0x08, 0x07],
    'Z': [0x61, 0x51, 0x49, 0x45, 0x43],
}


def send_cmd(bus, cmd):
    bus.write_byte_data(I2C_ADDR, CMD, cmd)

def send_data(bus, data):
    for i in range(0, len(data), 16):
        bus.write_i2c_block_data(I2C_ADDR, DATA, data[i:i+16])

def init_display(bus):
    cmds = [
        0xAE, 0xD5, 0x80, 0xA8, 0x3F,
        0xD3, 0x00, 0x40, 0x8D, 0x14,
        0x20, 0x00, 0xA1, 0xC8, 0xDA,
        0x12, 0x81, 0xCF, 0xD9, 0xF1,
        0xDB, 0x40, 0xA4, 0xA6, 0xAF
    ]
    for c in cmds:
        send_cmd(bus, c)

def set_cursor(bus, page, column):
    send_cmd(bus, 0xB0 + page)            # Set page (row)
    send_cmd(bus, 0x00 + (column & 0x0F)) # Lower col nibble
    send_cmd(bus, 0x10 + (column >> 4))   # Upper col nibble

def draw_char(bus, char):
    data = font5x7.get(char.upper(), font5x7[' ']) + [0x00]  # spacing
    send_data(bus, data)

def draw_text(bus, page, col, text):
    set_cursor(bus, page, col)
    for c in text:
        draw_char(bus, c)

def clear_display(bus):
    for page in range(8):
        set_cursor(bus, page, 0)
        send_data(bus, [0x00] * 128)

def main():
    with SMBus(I2C_BUS) as bus:
        init_display(bus)
        clear_display(bus)

        draw_text(bus, page=0, col=0, text="HELLO")
        draw_text(bus, page=1, col=0, text="DUO S")

        time.sleep(10)
        clear_display(bus)

if __name__ == "__main__":
    main()
1 Like