I2C
The phyCORE-RT1170 module provides support for Inter-Integrated Circuit (I2C) communication, enabling connections with a variety of external I2C-compatible devices. This standard two-wire bus is commonly used for low-speed communication between integrated circuits on the same board or across short distances.
The i.MX RT1170 processor includes two I2C controllers. For in-depth technical information, refer to the I2C Interface chapter in the Hardware Manual.
I2C Buses
The phyBOARD-Atlas RT1170 exposes two I2C buses through the carrier board:
lpi2c2
lpi2c5
Each bus is connected to specific onboard peripherals, as shown below.
Interface |
Address (7-bit) |
---|---|
EEPROM |
0x50 |
EEPROM Identification Page |
0x58 |
Interface |
Address (7-bit) |
---|---|
Audio Codec |
0x18 |
Accelerometer |
0x69 |
Accessing I2C Devices via Shell
To interact with I2C devices from the command line, begin by scanning the bus for connected peripherals. The example below scans the lpi2c5
bus:
uart:~$ i2c scan lpi2c5
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- 18 -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
1 devices found on lpi2c5
In this case, the scan reveals that the audio codec is available at address 0x18
. To read a register from this device, use the i2c read
command followed by the bus name, device address, and register offset:
uart:~$ i2c read lpi2c5 0x18 0
00000000: 00 00 01 56 00 11 04 00 00 00 00 01 01 00 80 80 |...V.... ........|
Note
Direct register access via the I2C shell commands is primarily intended for debugging. For production use, it is strongly recommended to access I2C devices through dedicated drivers whenever possible.
EEPROM Uptime Counter Example
The uptime_counter() function implements a simple persistent uptime counter using the Zephyr EEPROM API. It reads a struct from EEPROM at startup, verifies its integrity using a magic number, and then increments a stored uptime value every minute, writing it back to EEPROM.
EEPROM API usage
The code begins by retrieving the EEPROM device from the devicetree using the DEVICE_DT_GET_OR_NULL() macro. This references the eeprom0 alias, which must be defined in the board’s devicetree:
#define EEPROM0_NODE DT_ALIAS(eeprom0)
static const struct device *eeprom = DEVICE_DT_GET_OR_NULL(EEPROM0_NODE);
Before accessing the EEPROM, the function checks if the device is ready using device_is_ready(). If the device is not ready, it logs an error and exits.
if (!device_is_ready(eeprom)) {
LOG_ERR("Device \"%s\" is not ready.", eeprom->name);
return;
}
To read previously stored values, the eeprom_read() function is used. It attempts to read a packed struct from offset 0 in EEPROM:
ret = eeprom_read(eeprom, UPTIME_COUNTER_OFFSET, &values, sizeof(values));
If the read fails, the function logs an error and returns. If the read is successful, it checks the magic field to determine whether the EEPROM is empty (0xFFFFFFFF) or has valid data (UPTIME_COUNTER_MAGIC). If the magic value does not match either expected case, the function logs an error and exits.
Once the structure is initialized or validated, the program logs the current stored uptime and enters a loop. Every minute, it writes the updated struct back to EEPROM using eeprom_write():
ret = eeprom_write(eeprom, UPTIME_COUNTER_OFFSET, &values, sizeof(values));
This loop continues indefinitely, with the seconds field in the struct being incremented by 60 seconds after each successful write. This ensures that the counter value is regularly updated and preserved across reboots.
Source Code
#include <zephyr/kernel.h>
#include <zephyr/drivers/eeprom.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(uptime_counter, CONFIG_APP_LOG_LEVEL);
#define SLEEP_TIME_S 60
/* size of stack area used by each thread */
#define UPTIME_COUNTER_STACKSIZE 1024
/* scheduling priority used by each thread */
#define UPTIME_COUNTER_PRIORITY 5
#define UPTIME_COUNTER_OFFSET 0
#define UPTIME_COUNTER_MAGIC 0xA3EE9703
#define UPTIME_COUNTER_EMPTY 0xFFFFFFFF
struct perisistant_values {
uint32_t magic;
uint64_t seconds;
} __attribute__((packed));
/*
* Get EEPROM from the devicetree eeprom0 alias. This is mandatory.
*/
#define EEPROM0_NODE DT_ALIAS(eeprom0)
#if !DT_NODE_HAS_STATUS_OKAY(EEPROM0_NODE)
#error "Unsupported board: eeprom0 devicetree alias is not defined"
#endif
static const struct device *eeprom = DEVICE_DT_GET_OR_NULL(EEPROM0_NODE);
void uptime_counter(void)
{
struct perisistant_values values;
int ret;
if (!device_is_ready(eeprom)) {
LOG_ERR("Device \"%s\" is not ready.", eeprom->name);
return;
}
ret = eeprom_read(eeprom, UPTIME_COUNTER_OFFSET, &values, sizeof(values));
if (ret != 0) {
LOG_ERR("Couldn't read EEPROM: %d", ret);
return;
}
if (values.magic == UPTIME_COUNTER_EMPTY) {
LOG_INF("EEPROM is empty.");
values.magic = UPTIME_COUNTER_MAGIC;
values.seconds = 0;
} else if (values.magic != UPTIME_COUNTER_MAGIC) {
LOG_ERR("The EEPROM is either not empty or the required MAGIC header is missing.");
return;
}
LOG_INF("Device uptime: %lld hours and %lld minutes", values.seconds / 3600,
(values.seconds % 3600) / 60);
while (1) {
ret = eeprom_write(eeprom, UPTIME_COUNTER_OFFSET, &values,
sizeof(values));
if (ret != 0) {
LOG_ERR("Couldn't write eeprom: %d", ret);
return;
}
k_sleep(K_SECONDS(SLEEP_TIME_S));
values.seconds += SLEEP_TIME_S;
}
return;
}
K_THREAD_DEFINE(uptime_counter_tid, UPTIME_COUNTER_STACKSIZE, uptime_counter,
NULL, NULL, NULL, UPTIME_COUNTER_PRIORITY, 0, 0);
For more information see the TODO! file.