How to Use the I2C Port on LattePanda Mu in Ubuntu OS
Youyou 2024-10-08 01:57:35 3548 Views1 Replies Background
LattePanda Mu has multiple I2C Ports, allowing direct communication with I2C devices after coding in the OS. This approach eliminates the need for a USB to I2C adapter or an MCU.
This article demonstrates how to identify BusNumber, program and use LattePanda Mu's I2C pins to read and write the AT24C256 EEPROM Module in an Ubuntu OS.
Preparation
Hardware
- AT24C256 I2C EEPROM Module
Wiring
The Lite Carrier of LattePanda Mu provides four I2C Ports as shown in the diagram below.

Connect the AT24C256 EEPROM Module to any one of the I2C Ports. The pin connections are listed in the table below.
| Lite Carrier I2C Port Pins | AT24C256 Module Pins |
| 3V3 | VCC |
| GND | GND |
| SCL | SCL |
| SDA | SDA |
Software
- i2c-tools
- smbus2
Installation and Initialization
- It is recommended to use the OS versions from the LattePanda Mu OS Compatibility List, as older or other versions may not function correctly.
- This article will use the i2cdetect command to identify the BusNumber of the I2C Port. This command is part of the i2c-tools package, which may not be installed by default on Ubuntu or other Linux distributions, so first, install the i2c-tools package.
sudo apt install i2c-tools
- This article will also use the smbus2 library to program the I2C device, so they need to be installed. It is recommended to install related packages in a virtual environment.
sudo apt install python3-venv
# Create and enter the working directory(the folder name can be customized)
mkdir ~/i2c_project
cd ~/i2c_project
# Create a virtual environment(named .venv), then activate
python3 -m venv .venv
source .venv/bin/activate
pip install smbus2
- By default, ordinary users do not have permission to access I2C devices. Add your user to the i2c group:
sudo usermod -aG i2c $USER
- After executing above command, you must log out and log in again (or restart the OS) for the permission changes of the user group to take effect. Then enter the python virtual environment(.venv) again.
Detailed Steps
Expected Functionality: Write a code to store the value 0x42 at address 0x0000 on an AT24C256 EEPROM chip. Then, read back the value from address 0x0000 and compare it with the stored value to check if they are identical.
- Follow the steps outlined in the previous section to complete the wiring, and install the ubuntu OS and software libraries.
- Verify the BusNumber of the I2C Port
The index number marked on the Lite Carrier I2C Port may not match the BusNumber in the OS, so manual verification is needed. We can use the following command for this purpose.
i2cdetect -y -r BusNumStart testing the BusNum from 0 and increment until you find the 7-bit I2C address 0x50 of the AT24C256.
As shown in the following picture, the BusNumber is 3.

- Use the smbus2 library to write the code.
You can seek AI assistance from GPT or Claude while writing the code, which usually works. The complete sample code is as follows:
from smbus2 import SMBus
import time
DEVICE_ADDRESS = 0x50 # AT24C256 I2C address
MEMORY_ADDRESS = 0x0000 # 16-bit memory address
VALUE_TO_WRITE = 0x42 # Value to write to EEPROM
def write_byte(bus, device_address, memory_address, data):
# Split 16-bit memory address into two bytes
addr_msb = (memory_address >> 8) & 0xFF # Most significant byte
addr_lsb = memory_address & 0xFF # Least significant byte
bus.write_i2c_block_data(device_address, addr_msb, [addr_lsb, data])
time.sleep(0.01) # Allow time for EEPROM to complete the write operation
def read_byte(bus, device_address, memory_address):
# Split 16-bit memory address into two bytes
addr_msb = (memory_address >> 8) & 0xFF # Most significant byte
addr_lsb = memory_address & 0xFF # Least significant byte
bus.write_i2c_block_data(device_address, addr_msb, [addr_lsb])
return bus.read_byte(device_address)
def main():
with SMBus(3) as bus: # Ensure correct bus number
print(f"Writing value 0x{VALUE_TO_WRITE:02X} to address 0x{MEMORY_ADDRESS:04X}")
write_byte(bus, DEVICE_ADDRESS, MEMORY_ADDRESS, VALUE_TO_WRITE)
time.sleep(0.1) # Short delay
print("Reading value back...")
read_value = read_byte(bus, DEVICE_ADDRESS, MEMORY_ADDRESS)
print(f"Read value: 0x{read_value:02X}")
if read_value == VALUE_TO_WRITE:
print("Success! Read value matches written value.")
else:
print("Error: Read value does not match written value.")
if __name__ == "__main__":
main()
- Save the above code as a eeprom_test.py script in the i2c_project folder.
- In the terminal, run the following script using to see the output.
python3 eeprom_test.py

- Use a logic analyzer to observe the waveforms of writing and reading, which is comply with the datasheet specifications.
Write value 0x42 at address 0x0000

Read value from address 0x0000

FAQ
Q. From the waveforms for writing and reading showed by the logic analyzer, and the I2C address of the AT24C256 should be 0x80. Why is it 0x50 here?
A.
I²C Address Format
The I²C bus uses 7-bit addresses, with most devices, including the AT24C256, using a 7-bit address. An additional 1-bit read/write (R/W) bit is added to the 7-bit address, forming an 8-bit data transmission.
Explanation of AT24C256 Address
- 8-bit Address (including R/W bit): The write operation address for the AT24C256 is typically represented as 0xA0.
- 7-bit Address: In reality, the 7-bit address is 0x50. This is because 0xA0 shifted right by one bit (or divided by 2) results in 0x50.
Display Method of i2cdetect CMD
- The i2cdetect displays 7-bit addresses by default. Therefore, the address you detect using i2cdetect is 0x50.
- During actual communication, the I²C bus shifts the 7-bit address left by one bit and adds the read/write bit at the lowest position, making the write operation address 0xA0 and the read operation address 0xA1.
Another example
Read the voltage values of each channel of the ADS1115 16bit ADC module respectively.
from smbus2 import SMBus
import time
DEVICE_ADDRESS = 0x48
REG_CONVERSION = 0x00
REG_CONFIG = 0x01
CONFIG_OS_SINGLE = 0x8000
CONFIG_PGA_6_144V = 0x0000
CONFIG_MODE_SINGLE = 0x0100
CONFIG_DR_128SPS = 0x0080
CONFIG_COMP_DISABLE = 0x0003
MUX_CHANNEL = {
0: 0x4000,
1: 0x5000,
2: 0x6000,
3: 0x7000
}
def write_register(bus, device_address, register, value):
msb = (value >> 8) & 0xFF
lsb = value & 0xFF
bus.write_i2c_block_data(device_address, register, [msb, lsb])
def read_register(bus, device_address, register):
data = bus.read_i2c_block_data(device_address, register, 2)
value = (data[0] << 8) | data[1]
return value
def read_channel(bus, device_address, channel):
config = (
CONFIG_OS_SINGLE |
MUX_CHANNEL[channel] |
CONFIG_PGA_6_144V |
CONFIG_MODE_SINGLE |
CONFIG_DR_128SPS |
CONFIG_COMP_DISABLE
)
write_register(bus, device_address, REG_CONFIG, config)
time.sleep(0.01)
raw = read_register(bus, device_address, REG_CONVERSION)
if raw > 0x7FFF:
raw -= 65536
voltage = raw * 6.144 / 32768.0
return voltage
def main():
with SMBus(3) as bus:
print("Start reading ADS1115...\n")
while True:
v0 = read_channel(bus, DEVICE_ADDRESS, 0)
v1 = read_channel(bus, DEVICE_ADDRESS, 1)
v2 = read_channel(bus, DEVICE_ADDRESS, 2)
v3 = read_channel(bus, DEVICE_ADDRESS, 3)
print(
f"AIN0: {v0:.4f} V | "
f"AIN1: {v1:.4f} V | "
f"AIN2: {v2:.4f} V | "
f"AIN3: {v3:.4f} V"
)
time.sleep(1)
if __name__ == "__main__":
main()
Result:


