Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Struggeling with i2c/TWI communication on nRF52 DK (based on nRF 52832) and SH1106 132x64 OLED display #1377

Closed
OevreFlataeker opened this issue Jan 2, 2021 · 8 comments

Comments

@OevreFlataeker
Copy link

I am trying to use the u8g2 library on a nRF52 DK (ARM M4 based) to interface a SH1106 OLED. Before starting I verified the OLED is working perfectly on an Arduino with the following calls:

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

void setup(void) {
  u8g2.begin();
}

void loop(void) {
  u8g2.clearBuffer();					// clear the internal memory
  u8g2.setFont(u8g2_font_ncenB08_tr);	// choose a suitable font
  u8g2.drawStr(0,10,"Hello World!");	// write something to the internal memory
  u8g2.sendBuffer();					// transfer internal memory to the display
  delay(1000);  
}

Then I read through the FAQ and Porting Guides.
I understand I need to implement a GPIO and wait routine and a handler for the HW. I haven't used the TWI peripheral on the nRF chip before but it seems to not be too complicated.

I am able to interrogate the TWI bus and identify the chip at address 0x3c with the nRF -> Timing, Level and general TWI seems to work.

When I then issue the following calls

 u8g2_Setup_sh1106_128x64_noname_f(&u8g2, U8G2_R0, u8x8_HW_com_nrf52832, u8g2_nrf_gpio_and_delay_cb);
 u8g2.u8x8.i2c_address = OLED_ADDR;
 u8g2_InitDisplay(&u8g2);
 u8g2_SetPowerSave(&u8g2,0);

I get a fatal error form the nRF TWI subsystem on the call to u8g2_InitDisplay(), the proplem is a NACK.

I then analyzed the timing and identified the following communication.

I am not sure if this is correct. After the ACK on address 0x3c I get a NACK on the read 0x40 (I read one byte during the ID scan).

i2cdetect

When the actual InitDisplay() is called and I see the following I/O:

startinit

and the output of my code on the console

<info> app: TWI device detected at address 0x3C: 64
<info> app: nrf_delay_ms(100)
<info> app: nrf_delay_ms(100)
<info> app: nrf_delay_ms(100)
<info> app: Dumping send buffer of len 25 ('buf_idx')
<info> app:  AE D5 80 A8 3F D3 00 40|....?..@
<info> app:  8D 14 20 00 A1 C8 DA 12|.. .....
<info> app:  81 CF D9 F1 DB 40 2E A4|.....@..
<info> app:  A6                     |.
<info> app: Now calling 'nrf_drv_twi_tx(&m_twi, 60, buffer, buf_idx, false)'
<info> app: errcode = 0x8202 (unknown error)
<error> app: Fatal error

I've tried to interface the TWI peripheral in blocking and non-blocking mode as blocking should be easier but more CPU consuming. However both showed the same result. The following code shows my blocking I/O attempt. I tried both 100kHz as well as 400 kHz communication. In both attempts it's the same behavior.

#include <stdio.h>
#include "boards.h"
#include "app_util_platform.h"
#include "app_error.h"
#include "nrf_drv_twi.h"

#include "nrf_delay.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#include "u8g2.h"
#include "u8x8.h"

#define TWI_ADDRESSES      127
#define TWI_DONT_ISSUE_STOP   ((bool)false)
#define TWI_ISSUE_STOP   ((bool)true)

#define OLED_I2C_PIN_SCL 27
#define OLED_I2C_PIN_SDA 26
#define OLED_ADDR          (0x3C)


/* Indicates if operation on TWI has ended. */
static volatile bool m_xfer_done = false;
static uint8_t m_sample;
static u8g2_t u8g2;
uint8_t sample_data;
static bool readsomething = false;

#if TWI0_ENABLED
#define TWI_INSTANCE_ID     0
#elif TWI1_ENABLED
#define TWI_INSTANCE_ID     1
#endif

static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

void twi_init (void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t twi_oled_config = {
       .scl                = OLED_I2C_PIN_SCL,
       .sda                = OLED_I2C_PIN_SDA,
       .frequency          = NRF_DRV_TWI_FREQ_100K,
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
       .clear_bus_init     = false,       
    };

    //err_code = nrf_drv_twi_init(&m_twi, &twi_oled_config, twi_handler, NULL); // Non blocking IO
    err_code = nrf_drv_twi_init(&m_twi, &twi_oled_config, NULL, NULL); // Blocking IO
    APP_ERROR_CHECK(err_code);

    nrf_drv_twi_enable(&m_twi);
}

uint8_t u8g2_nrf_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    switch(msg)
    {   
        case U8X8_MSG_DELAY_MILLI:
            NRF_LOG_INFO("nrf_delay_ms(%d)", arg_int);
            nrf_delay_ms(arg_int);
            break;

        case U8X8_MSG_DELAY_10MICRO:
            NRF_LOG_INFO("nrf_delay_us(%d)", 10*arg_int);
            nrf_delay_us(10*arg_int);
            break;
        
        default:
            u8x8_SetGPIOResult(u8x8, 1); // default return value
            break;  
    }
    return 1;
}

uint8_t u8x8_HW_com_nrf52832(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    uint8_t *data;
    bool res = false;
    ret_code_t err_code;    
    static uint8_t buffer[32];
    static uint8_t buf_idx;
    switch(msg)
    {
        case U8X8_MSG_BYTE_SEND:
            {
            data = (uint8_t *)arg_ptr;      
            while( arg_int > 0 )
              {
                buffer[buf_idx++] = *data;
                data++;
                arg_int--;
              }      
            }
            
            break;  
        case U8X8_MSG_BYTE_INIT:            
            break;
        case U8X8_MSG_BYTE_SET_DC: //Pin Data/Comand           
            break;
        case U8X8_MSG_BYTE_START_TRANSFER:                    
            buf_idx = 0;
            break;
      case U8X8_MSG_BYTE_END_TRANSFER:    
           NRF_LOG_INFO("Dumping send buffer of len %d ('buf_idx')", buf_idx);
           NRF_LOG_HEXDUMP_INFO(buffer, buf_idx);
           uint8_t addr = u8x8_GetI2CAddress(u8x8);
           NRF_LOG_INFO("Now calling 'nrf_drv_twi_tx(&m_twi, %d, buffer, buf_idx, false)'", addr);           
           err_code = nrf_drv_twi_tx(&m_twi, addr, buffer, buf_idx, false);
           NRF_LOG_INFO("errcode = 0x%x (%s)", err_code, strerror(err_code));
           
           NRF_LOG_FLUSH();
           APP_ERROR_CHECK(err_code);
           break;
      default:
           return 0;
    }
    return 1;

}

void print_hello()
{
    u8g2_ClearBuffer(&u8g2);
    u8g2_SetFont(&u8g2, u8g2_font_ncenB08_tr);
    u8g2_DrawStr(&u8g2, 0, 10, "Hello World!");
    u8g2_SendBuffer(&u8g2);
}

/**
 * @brief Function for main application entry.
 */
int main(void)
{    
    ret_code_t err_code;
    uint8_t address;
    
    bool detected_device = false;

    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    twi_init();
    
    for (address = 0x0; address <= 0x7f; address++)
    {
        if (detected_device) break;             
        err_code = nrf_drv_twi_rx(&m_twi, address, &sample_data, sizeof(sample_data));       
        if (err_code == 0)
        {
            detected_device = true;
            NRF_LOG_INFO("TWI device detected at address 0x%x: %u", address, sample_data);
        }
        NRF_LOG_FLUSH();
    }

    if (!detected_device)
    {
        NRF_LOG_INFO("No device was found.");
        NRF_LOG_FLUSH();
        while (true);
    }
            
    u8g2_Setup_sh1106_128x64_noname_f(&u8g2, U8G2_R0, u8x8_HW_com_nrf52832, u8g2_nrf_gpio_and_delay_cb);
    u8g2.u8x8.i2c_address = OLED_ADDR;
    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2,0);
    while (true)
    {        
        nrf_delay_ms(1000);
        print_hello();               
    } 
    
}

I wonder if it is ok that the CLK signal looks to be as if it is a tiny bit shifted? However I don't have any control on this, so I would not know what to do if that is an issue. I wonder if the DNACK is something I need to care for or configure if I need to ignore it or anything?

The code for the u8x8_HW_com_nrf52832 I basically copy/pasted and adapted to the nRF SDK. I am using the nRF SDK 17.02. The description of using the TWI master mode is here: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v14.0.0%2Fgroup__nrf__drv__twi.html

The examples on the SDK page shows a minimal example:

uint32_t err_code;
uint8_t tx_data[] = {'a', 'b', 'c', 'd', 'e'};
const nrf_drv_twi_t twi = NRF_DRV_TWI_INSTANCE(0);
err_code = nrf_drv_twi_init(&twi, NULL, NULL);
APP_ERROR_CHECK(err_code);
nrf_drv_twi_enable(&twi);
err_code = nrf_drv_twi_tx(&twi, SLAVE_ADDRESS, tx_data, sizeof(tx_data), false);
APP_ERROR_CHECK(err_code);

So i wonder what might be wrong with my attempt?

@OevreFlataeker
Copy link
Author

I just compared the i2c traced between Arduino and nRF and saw something but can't make a clue out of it:

Arduino first byte of init sequence:

arduino

nRF first byte of init sequence:

nrf

Why is this 0x00 not send by the library before the 0xae?

Also I wonder why I have to explicitly set the i2c address to 0x3c in the initialization of the library. This is not needed on the Arduino part? If I don't call u8g2_SetI2Caddress(0x3c), the i2c address is preset to 0xff? Am I missing something during the initialization?

u8g2_Setup_sh1106_128x64_noname_f(&u8g2, U8G2_R0, u8x8_HW_com_nrf52832, u8g2_nrf_gpio_and_delay_cb);
u8g2_SetI2CAddress(&u8g2, OLED_ADDR);

@OevreFlataeker
Copy link
Author

OK, ok... call me.... :-D

I should have use u8g2_Setup_sh1106_i2c_128x64_noname_f not u8g2_Setup_sh1106_128x64_noname_f ().
However I still need to initialize the i2c address myself and the display is very distorted.

image

Also it looks as if the spaces between the individual transmissions are HUGE...
image
image

@OevreFlataeker
Copy link
Author

OK the interleave was due to the logging messages... yet the display is still totally distorted...

@OevreFlataeker
Copy link
Author

Problem solved... I had initially commented out the printhello() and apparently the garbled screen was just what was in the displays memory. After activating the printhello() again I see a proper printout! Everything seems to work! Awesome library! Now I'm trying to get it to work in non-blocking mode!

@olikraus
Copy link
Owner

olikraus commented Jan 2, 2021

If I don't call u8g2_SetI2Caddress(0x3c), the i2c address is preset to 0xff?

U8g2 internally works with the left aligned address, this means it not 0x3c, but 0x3c*2. For the uC specific I2C produres you probably need to device the u8g2 internal address by 2:

Wire.beginTransmission(u8x8_GetI2CAddress(u8x8)>>1);

This is also documented in the reference manual:
https://github.com/olikraus/u8g2/wiki/u8x8reference#seti2caddress

There had been a lot of discussion in history regarding the i2c address, so please excause, it the multiplication with two is confusing (see also here: #821)

Why is this 0x00 not send by the library before the 0xae?

To me it is not clear, what you mean by "the library". No doubt, the 0x00 byte MUST be there. This byte is either 0x00 or 0x80 and specifies how to interpret other bytes after this command (see also the controller specification).

Problem solved...

Oh, ah, I just started to read all you posts.
Thanks for working on this. Hope it will be helpful to others also.
Maybe you can post the final code of the two callback functions here.

@OevreFlataeker
Copy link
Author

Love to! Here it is!

oled_u8g2_nrf52_sdk1702.zip

/*
 * u8g2 Demo Application running on Nordic Semiconductors nRF52 DK with nRF SDK 17.02 and TWIM and a SH1106 OLED 128x64 display
 * that can be found on Ebay/Amazon for few EUR.
 * 
 * Created 20200102 by @daubsi, based on the great u8g2 library https://github.com/olikraus/u8g2
 * Probably not the cleanest code but it works :-D
 * 
 * Set up with i2c/TWIM in 400 kHz (100 kHz works equally well) in non-blocking mode
 *
 * Wiring:
 * Connect VCC and GND of the display with VDD and GND on the nRF52 DK
 * Connect SCL to Pin 27 ("P0.27") and SDA to pin 26 ("P0.26") on the nRF52 DK
 * 
 * Settings in sdk_config.h
 *
 * #define NRFX_TWIM_ENABLED 1
 * #define NRFX_TWIM0_ENABLED 0
 * #define NRFX_TWIM1_ENABLED 0
 * #define NRFX_TWIM_DEFAULT_CONFIG_FREQUENCY 104857600
 * #define NRFX_TWIM_DEFAULT_CONFIG_HOLD_BUS_UNINIT 0
 * #define TWI_ENABLED 1
 * #define TWI_DEFAULT_CONFIG_FREQUENCY 104857600
 * #define TWI_DEFAULT_CONFIG_CLR_BUS_INIT 0
 * #define TWI_DEFAULT_CONFIG_HOLD_BUS_UNINIT 0
 * #define TWI0_ENABLED 1
 * #define TWI0_USE_EASY_DMA 1
 * #define TWI1_ENABLED 1
 * #define TWI1_USE_EASY_DMA 1
 *
 * Probably most of those configs are not needed apart from TWI_ENABLED and TWI0_ENABLED but I kept them there
 */

#include <stdio.h>
#include "boards.h"
#include "app_util_platform.h"
#include "app_error.h"
#include "nrf_drv_twi.h"

#include "nrf_delay.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#include "u8g2.h"
#include "u8x8.h"

#define TWI_ADDRESSES      127

#define OLED_I2C_PIN_SCL 27
#define OLED_I2C_PIN_SDA 26
#define OLED_ADDR        0x3C


/* Indicates if operation on TWI has ended. */
static volatile bool m_xfer_done = false;
static uint8_t m_sample;

static u8g2_t u8g2;

static bool readsomething = false;

#if TWI0_ENABLED
#define TWI_INSTANCE_ID     0
#elif TWI1_ENABLED
#define TWI_INSTANCE_ID     1
#endif

static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

/**
 * @brief TWI events handler.
 */
void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
    switch(p_event->type)
    {
        case NRF_DRV_TWI_EVT_ADDRESS_NACK:
        {
            NRF_LOG_ERROR("Got back NACK");
            m_xfer_done = true;            
            break;
        }
        case NRF_DRV_TWI_EVT_DATA_NACK:
        {
            NRF_LOG_ERROR("Got back D NACK");
            m_xfer_done = true;            
            break;
        }        
        case NRF_DRV_TWI_EVT_DONE:
        {
            if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_RX)
            {
                readsomething=true;
            }
            m_xfer_done = true;
            break;
        }        
        default:
        {
            m_xfer_done = false;
            break;
        }
    }    
}

/**
 * @brief UART initialization.
 */
void twi_init (void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t twi_oled_config = {
       .scl                = OLED_I2C_PIN_SCL,
       .sda                = OLED_I2C_PIN_SDA,
       .frequency          = NRF_DRV_TWI_FREQ_400K,
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
       .clear_bus_init     = false,       
    };

    err_code = nrf_drv_twi_init(&m_twi, &twi_oled_config, twi_handler, NULL);
    //err_code = nrf_drv_twi_init(&m_twi, &twi_oled_config, NULL, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_twi_enable(&m_twi);
}

uint8_t u8g2_nrf_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)

{
    switch(msg)
    {   
        case U8X8_MSG_DELAY_MILLI:
            // NRF_LOG_INFO("nrf_delay_ms(%d)", arg_int);
            nrf_delay_ms(arg_int);
            break;

        case U8X8_MSG_DELAY_10MICRO:
            // NRF_LOG_INFO("nrf_delay_us(%d)", 10*arg_int);
            nrf_delay_us(10*arg_int);
            break;
        
        default:
            u8x8_SetGPIOResult(u8x8, 1); // default return value
            break;  
    }
    return 1;
}

uint8_t u8x8_HW_com_nrf52832(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    uint8_t *data;
    bool res = false;
    ret_code_t err_code;    
    static uint8_t buffer[32];
    static uint8_t buf_idx;
    switch(msg)
    {
      case U8X8_MSG_BYTE_SEND:
      {
            data = (uint8_t *)arg_ptr;      
            while( arg_int > 0 )
            {
              buffer[buf_idx++] = *data;
              data++;
              arg_int--;
            }      
            break;  
      }      
      case U8X8_MSG_BYTE_START_TRANSFER:                    
      {
            buf_idx = 0;            
            m_xfer_done = false;
            break;
      }
      case U8X8_MSG_BYTE_END_TRANSFER:
      {
            // NRF_LOG_INFO("Dumping send buffer of len %d ('buf_idx')", buf_idx);
            // NRF_LOG_HEXDUMP_INFO(buffer, buf_idx);
            uint8_t addr = u8x8_GetI2CAddress(u8x8);
            // NRF_LOG_INFO("Now calling 'nrf_drv_twi_tx(&m_twi, 0x%02x, buffer, buf_idx, false)'", addr);
           
            err_code = nrf_drv_twi_tx(&m_twi, u8x8_GetI2CAddress(u8x8) , buffer, buf_idx, false);
            APP_ERROR_CHECK(err_code);
            while (!m_xfer_done)
            {
                __WFE();
            }
            break;
      }
      default:
            return 0;
    }
    return 1;
}

void print_hello()
{
static int x = 10;
static int y= 0;
    
    u8g2_ClearBuffer(&u8g2);
    u8g2_SetFont(&u8g2, u8g2_font_ncenB08_tr);
    x = (++x % 10);
    y = (++y % 10);
    u8g2_DrawStr(&u8g2, y, x, "Hello World!");
    u8g2_SendBuffer(&u8g2);
}

/**
 * @brief Function for main application entry.
 */
int main(void)
{    
    ret_code_t err_code;
    uint8_t address;
    
    bool detected_device = false;

    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();
    
    twi_init();
    
    readsomething=false;
    for (address = 0x0; address <= TWI_ADDRESSES; address++)
    {
        if (detected_device) break;        
        m_xfer_done = false;        
        err_code = nrf_drv_twi_rx(&m_twi, address, &m_sample, sizeof(m_sample));
        while (!m_xfer_done)
        {
            __WFE();
        }
        if (readsomething)
        {
            detected_device = true;
            NRF_LOG_INFO("TWI device detected at address 0x%x: Read value 0x%02x", address, m_sample);
        }
        NRF_LOG_FLUSH();
    }

    if (!detected_device)
    {
        NRF_LOG_INFO("No device was found.");
        NRF_LOG_FLUSH();
        while (true)
        {
      
        }
    }
           
    
    u8g2_Setup_sh1106_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_HW_com_nrf52832, u8g2_nrf_gpio_and_delay_cb);    
    u8g2_SetI2CAddress(&u8g2, OLED_ADDR);
    
    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2,0);
 
    while (true)
    {        
        nrf_delay_ms(10);
        print_hello();               
    } 
    
}

@OevreFlataeker OevreFlataeker changed the title Struggeling with i2c/TWI communication on nRF 52832 and SH1106 Struggeling with i2c/TWI communication on nRF52 DK (based on nRF 52832) and SH1106 132x64 OLED display Jan 2, 2021
@olikraus
Copy link
Owner

olikraus commented Jan 2, 2021

Thanks!

@OevreFlataeker
Copy link
Author

My pleasure! Thanks for YOUR great work with this library!

Link to my github https://github.com/OevreFlataeker/u8g2_nrf52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants