Uint8_t david_digikey_coffee_cup_zephyr_i2c_read(uint8_t address)

En el previo artículo se demonstro como escribir a la linea de interfaz I2C al TDK ICM-20948 Sensor vía Qwiic de Sparkfun en una aplicación dentro del sistema operativo Zephyr. En este artículo, la función de leer fue desarrollada aquí en Digikey para el sistema operativo Zephyr. Ya Zephyr provee un mecanismo para muchos sensores usando las herramientas de KConfig and Devicetree, pero este artículo provee una alternativa para aquellos que quieren desarrollar aplicaciones directamente con la interfaz I2C dentro de Zephyr, que en algunos casos en necesario. También el proceso de construcción de la aplicación, con los archivos esenciales y la estructura de directories necesaria será descrita para el Raspberry Pico 2. Primero, se abre un terminal de minicom que se usara luego para ver la salida del Raspberry Pico 2 that que esta corriendo la aplicación dentro del sistema operativo de Zephyr,

$ minicom -D /dev/ttyACM0

Welcome to minicom 2.8

OPTIONS: I18n 
Port /dev/ttyACM0, 12:41:43

Press CTRL-A Z for help on special keys

Dentro del directorio de Zephyr, se crea un directorio llamado myproject, entonces dentro de este directorio se crean estos archivos,

El archivo CMake llamado CMakeLists.txt,

cmake_minimum_required(VERSION 3.25.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(i2c_scanner)
target_sources(app PRIVATE src/main.c)

El archivo de la configuración relacionada al sistema operativo Zephyr llamado prj.conf,

CONFIG_I2C=y
CONFIG_RTIO=y
CONFIG_I2C_RTIO=y
CONFIG_I2C_TARGET=y
CONFIG_I2C_TARGET_BUFFER_MODE=y

Y el Zephyr Overlay file que en este caso se llama RP2350.overlay que define el puerto correspondiente en este caso i2c port 0 de la Raspberry Pico 2,

/ {
    aliases {
        scan-i2c = &i2c0;
    };
};

El programa main.c que se muestra a continuación fué desarrollado aquí en Digikey y reside en un sub-directorio llamado src.

/*Digikey Coffee Cup*/

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/i2c/rtio.h>
#include <string.h>

#define CONFIG_I2C_RTIO_LOOPBACK_DATA_WRITE_MAX_SIZE 1
#define CONFIG_I2C_RTIO_LOOPBACK_DATA_READ_MAX_SIZE 1
#define I2C_TARGET_ADDR 0x69

static const struct device *scan_dev = DEVICE_DT_GET(DT_ALIAS(scan_i2c));

static uint8_t sample_write_data[CONFIG_I2C_RTIO_LOOPBACK_DATA_WRITE_MAX_SIZE];
static uint8_t sample_write_buf[sizeof(sample_write_data)];
static uint32_t sample_write_buf_pos;

static uint8_t sample_read_data[CONFIG_I2C_RTIO_LOOPBACK_DATA_READ_MAX_SIZE];
static uint32_t sample_read_data_pos;
static uint8_t sample_read_buf[sizeof(sample_read_data)];

uint8_t david_digikey_coffee_cup_zephyr_i2c_read(uint8_t address)
{
 
    sample_write_data[0] = address;
    sample_read_buf[0] = 0x00;
    struct i2c_msg msgs[2];
    msgs[0].buf = sample_write_data;
    msgs[0].len = sizeof(sample_write_data);
    msgs[0].flags = I2C_MSG_WRITE;
    msgs[1].buf = sample_read_buf;
    msgs[1].len = sizeof(sample_read_buf);
    msgs[1].flags = I2C_MSG_RESTART | I2C_MSG_READ | I2C_MSG_STOP;

    int ret = i2c_transfer(scan_dev, msgs, ARRAY_SIZE(msgs), I2C_TARGET_ADDR);
    if (ret) {
            return -EIO;
    }
    
    return sample_read_buf[0];
    
}

uint8_t david_digikey_coffee_cup_zephyr_i2c_write(uint8_t address, uint8_t value)
{
    
    uint8_t error = 0u;
    struct i2c_msg msgs[2];
    msgs[0].buf = &address;
    msgs[0].len = 1U;
    msgs[0].flags = I2C_MSG_WRITE;
    msgs[1].buf = &value;
    msgs[1].len = sizeof(value);
    msgs[1].flags = I2C_MSG_WRITE | I2C_MSG_STOP;
    
    error = i2c_transfer(scan_dev, msgs, 2, I2C_TARGET_ADDR);
    if (error == 0) {
            printk("I2C Bus Digikey Coffe Cup Write to Address = 0x%02X, Register=0x%02X Complete\n", address, value);

    }
    else {
            printk("I2C Bus Digikey Coffe Cup Write Error\n");
    }

    return  error;
    
}

int main(void) 
{
    k_sleep(K_SECONDS(1));
    if (!scan_dev) {
        printk("I2C: Device driver not found.\n");
        return -1;
    }

    printk("*** David Digikey Coffee Cup Zephyr I2C ***\n");
    printk("Board:           %s\n", CONFIG_BOARD);
    printk("I2C device:      %s\n", scan_dev->name);
    
    
    uint8_t address = 0x00;
    uint8_t theregister = 0x00;
    uint8_t value = 0x00;
    
    //Select Bank0
    address = 0x7F;
    value = 0x00;
    david_digikey_coffee_cup_zephyr_i2c_write(address, value);
    k_sleep(K_MSEC(10));
    
    while(1)
    {
        //READ WHOAMI
        printk("david_digikey_coffee_cup_zephyr_i2c_read(address)\n");
        address = 0x00;
        theregister = david_digikey_coffee_cup_zephyr_i2c_read(address);
        printk("I2C Bus Digikey Coffe Cup Reading Address = 0x%02X is 0x%02X\n", address, theregister);
        k_sleep(K_MSEC(10));
        
        //READ PWR_MGMT_1 @ 0x06
        address = 0x06;
        theregister = david_digikey_coffee_cup_zephyr_i2c_read(address);
        printk("I2C Bus Digikey Coffe Cup Reading Address = 0x%02X is 0x%02X\n", address, theregister);
        k_sleep(K_MSEC(10));
       
        //WRITE TO PWR_MGMT_1 @ 0x06 (Take device out of sleep mode and auto selects the best available clock source – Phase Lock Loop if ready, else use the Internal oscillator)
        address = 0x06;
        value = 0x01;
        david_digikey_coffee_cup_zephyr_i2c_write(address, value);
        k_sleep(K_MSEC(10));
        
        //READ PWR_MGMT_1 @ 0x06 (To verify that the value was properly written)
        address = 0x06;
        theregister = david_digikey_coffee_cup_zephyr_i2c_read(address);
        printk("I2C Bus Digikey Coffe Cup Reading Address = 0x%02X is 0x%02X\n", address, theregister);
        k_sleep(K_MSEC(10));
        
        //WRITE TO PWR_MGMT_1 @ 0x06 (Place device in sleep mode and auto selects the best available clock source – Phase Lock Loop if ready, else use the Internal oscillator)
        address = 0x06;
        value = 0x41;
        david_digikey_coffee_cup_zephyr_i2c_write(address, value);
        k_sleep(K_MSEC(10));
        
        //READ PWR_MGMT_1 @ 0x06 (To verify that the value was properly written)
        address = 0x06;
        theregister = david_digikey_coffee_cup_zephyr_i2c_read(address);
        printk("I2C Bus Digikey Coffe Cup Reading Address = 0x%02X is 0x%02X\n", address, theregister);
        k_sleep(K_MSEC(500));
    }
    
	return 0;
    
}

El código de la aplicación previa muestra como leer y escribir a un sensor en la linea de I2C bus usando el sistema de tiempo real Zephyr en la Raspberry Pico 2 en diferentes momentos,

El directorio llamado myproject con sus archivos y subdirectoros es como sigue,

|-- CMakeLists.txt
|-- prj.conf
|-- RP2350.overlay
`-- src
    `-- main.c

Ahora en este momento se puede construir la aplicación dentro del sistema operativo Zephyr OS para la Raspberry Pico 2 usando el previo archivo llamado RP2350.overlay como sigue,

(venv) $ west build -p always -b rpi_pico2/rp2350a/m33 -S cdc-acm-console -- -DCONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y -DEXTRA_DTC_OVERLAY_FILE=RP2350.overlay

El Sparkfun 8-channel USB Logic Analyzer previamente descrito aquí Techforum article fue confiurado asi como se muestra en la siguiente foto usando estos cables I2C Qwiic para monitorear la linea de I2C bus usando el voltaje apriopiado, PIN6 (SDA), PIN7 (SCL) (I2C Port 0) y el PIN 38 (GND) en la Raspberry Pico 2 como se muestra aqui,

y la configuración actual en un bread board de Digikey,

El Sparkfun 8-channel USB Logic Analyzer fue conectado al SCL, SDA (señales de la linea I2C) y el GND (Ground) mostrado anteriormente (No la forma optima de conección). Ahora se procede a programar la aplicacción Zephyr OS en la Raspberry Pico 2

(venv) $ west flash --runner uf2

En el terminal minicom se puede observer la salida de Raspberry Pico 2 de esta manera,

david_digikey_coffee_cup_zephyr_i2c_read(address)
I2C Bus Digikey Coffe Cup Reading Address = 0x00 is 0xEA
I2C Bus Digikey Coffe Cup Reading Address = 0x06 is 0x41
I2C Bus Digikey Coffe Cup Write to Address = 0x06, Register=0x01 Complete
I2C Bus Digikey Coffe Cup Reading Address = 0x06 is 0x01
I2C Bus Digikey Coffe Cup Write to Address = 0x06, Register=0x41 Complete
I2C Bus Digikey Coffe Cup Reading Address = 0x06 is 0x41


El Sparkfun 8-channel USB Logic Analyzer confirma que esta corriendo este programa de Zephyr dentro del Raspberry Pico 2 usando solamente la definición de la interfaz I2C para el sistema operativo Zephyr OS overlay funciona. La secuencia apropiada para la interfaz I2CI2C_MSG_WRITE, I2C_MSG_WRITE, I2C_MSG_STOP definida en el main.c desarrollado aqui en Digikey para la función de estribir al interfaz I2C maneja la máquina de estados finitos es apropiadamente seguida. También el I2C_MSG_WRITE, I2C_MSG_RESTART, I2C_MSG_READ, I2C_MSG_STOP de la función de la interfaz I2C de leer (read) de la máquina de estados finitos es apropiadamente seguida. Las siguientes figuras, muestran lo que esta ocurriendo dentro de la linea I2C de el programa dentro de Zephyr en la Raspberry Pico 2 interactuando con el sensor,

La secuencia apropiada de transacciones de I2C read/write (leer/escribir) son confirmadas por el analizador lógico conectado en la linea I2C al sensor. El registro WHOAMI es leido en el programa, y cambios relevantes al registro PWR_MGMT_1 register son realizadas para poner el sensor en modo despierto y dormido (awake/sleep) (en sleep mode toda la sección analoga es desactivada).

El Sparkfun 8-channel USB Logic Analyzer disponible en Digikey, es una herramienta excelente cuando se necesita trabajar con este tipo de interfaz. (como por ejemplo, CAN bus, SPI bus, I2C bus, entre otros, para analizar el tráfico que ocurre.) Que tenga un buen día.

Este artículo se encuentra en idioma inglés aquí.

This article is available in english language here.

1 Like