Has anyone managed to access the IMU on the Waveshare ESP32-C3-LCD-1.47?

Hi. I'm sorta new, though I was here years ago, even I can't remember what my username was back then. Anyway, I 'm lately quite enamored with the development boards that have an integrated display on top. The Waveshare ESP32-C3-LCD-1.47 is one such board. I was able to get the display up and running, but I'd really like to access the built-in IMU. I've been at it for two full days with Google AI doing its best to help me. Neither of us can get past imu.begin, it returns False every time no matter what combination of library, SDA, SCL, and I2C address we try. It must be possible because the board ships with a demo installed that happily displays IMU readings. But I have no idea what the source code for that demo looks like. Google AI seemed to think that maybe the board keeps some peripherals (like the IMU) powered down until told (somehow) to power them up, but none of its many code suggestions ever got us past imu.begin, still always returning false. Any and all advice would be greatly appreciated. Thanks in advance.

what library have you tried?

Waveshare recommends using the FastIMU library

this library adds a few code examples in File->Examples->FastIMU

Hopefully, you've learned a lesson.

Typing in the search phrase "Waveshare ESP32-C3-LCD-1.47 demo code" led to this as the first hit:

Since this has only a binary file for the demo, you may have to contact waveshare for the IMU example code.

Hi time_doc. Thanks very much for replying. Yes, FastIMU is in fact the one I have installed currently (the latest of several). The examples are great for showing how to do sophisticated things with that library, but of course they say nothing about how to get even basic access to the IMU aboard my Waveshare board. I imagine that any of the IMU libraries (eg. the Arduino QMI8658 library) would work well, if I could just get past imu.begin

Hi JRem. Thanks for the reply. If only Google AI could be taught to do the most basic Google search. (joking) :D I do get your point, it's much better at seeming to be helpful (and oh so confident) than actually being helpful. I was rather impressed by its ability to whip up a never-ending series of Arduino sketches for me to try - some of them even compiled, but not one of them ever made it past imu.begin

Have you run the I2C address scanner program to see if it at least detects the I2C address? According to the schematic, the IMU is the QMI8658 and since SA0 is grounded, the 7 bit I2C address should be 0x6B (0xd6 for Arduino).

Datasheet

Also, check that IMU_INT, if used, is correctly identified in the library code. Should be GPIO2 according to the schematic.

JRem: Much to thank you for there. Firstly the datasheet. Now, that information would be more applicable to wiring one up, or writing my own library, but trust me, I'm kicking myself suitably hard for not having thought to read the datasheet already. Alas, my problem seems to be more about how/where the QMI8658 is hiding. Which brings me to your other point. In truth, I have yet to run the scan, but I sure will now, though my optimism is a bit guarded - if the QMI8658 isn't even powered up, the scan's not likely to find it. But I'm definitely going to try it. Could you please explain to me why the address is different for Arduino? I feel like I'm missing something important there. And yes, I'll look for any interrupt use in the library. Looks like I've got my work cut out for me today, but it all makes good sense, so I'm on it. And again, thank you!

The standard Arduino I2C library uses 8 bit I2C addresses (= 7 bit address shifted left 1). The I2C address scanner program will identify all devices on the I2C bus and from the result it should be clear which representation is appropriate.

Many modern MCUs have two or more I2C busses and so two I2C libraries (Wire and Wire1) are commonly used.

According to the schematic the IMU is always powered up.

I see what you mean about the schematic showing the IMU always getting power.

And in the lower right (the table) it makes clear that SDA=4 and SCL=3.

When forced to use those pins, port scanner finds three devices at

0x24, 0x6B, and 0x7E.

It seems rather likely that the one at 0x6B is you-know-who.

So you might think that imu.begin(4,3,QMI8658_ADDRESS_HIGH)

would return true. Alas, it does not.

So let's go into the library, copy that begin code out, line by line,

and see where things go south. Well, quite soon as it turns out.

The first thing the library wants to do is to read the QMI8658_WHO_AM_I register

(it wants a 5). But it doesn't even get as far as checking whether it got a 5.

It doesn't get anything because its readRegister method fails

with its endTransmission returning 4 (which according to Arduino reference

is "Other Error"). That's as far as my efforts got me yesterday.

I'll peck away at it some more today.

What is the value assigned to that symbol? Please post a link to the exact library you are using.

Since the IMU SA0 pin is LOW, QMI8658_ADDRESS_LOW (or similar symbol) might be the correct choice. Better, just use 0x6B instead.


Hi JR, thanks again. Oh I've tried both addresses, as well as 0xD6, and others.
I'm beginning to think there might be some special technique required to
do I2C on ESP32C3 boards, but I don't know, I'm at that grasping-at-straws
stage that comes just before the giving-up stage. :D

Have you contacted Waveshare?

I have tried to get an ESP32-C3-LCD myself to play with it but shipping is delayed

While checking with waveshare they said I need to use their custom software for the onboard IO expander, that is, download the qmi8658.h and qmi8658.c from Waveshare-ESP32-components/sensor/qmi8658 at master · waveshareteam/Waveshare-ESP32-components · GitHub

place the 2 files in the sketch's directory and try something like this (not entirely my code):

edit: I compiled this version on a C3 but without an IMU

#include <Arduino.h>
#include <Wire.h>
#include "qmi8658.h"

#define I2C_SDA_PIN         6 // your setup here
#define I2C_SCL_PIN         7 // your setup here 

#define CH32V003_I2C_ADDR   0x24  // hope your board has the CH32V003 IO expander

qmi8658_dev_t imu_device_ctx;
float ax, ay, az;
float gx, gy, gz;

void writeCH32V003Expander(uint8_t reg, uint8_t data) {
    Wire.beginTransmission(CH32V003_I2C_ADDR);
    Wire.write(reg);
    Wire.write(data);
    if (Wire.endTransmission() != 0) {
        Serial.print("Warning: Communication failed with CH32V003 at register 0x");
        Serial.println(reg, HEX);
    }
}

void initCH32V003HardwareRails() {
    Serial.println("Configuring CH32V003 Expander rails...");
    writeCH32V003Expander(0x03, 0x00);
    delay(10);
    writeCH32V003Expander(0x01, 0xFF);
    delay(100); 
}

void setup() {
    Serial.begin(115200);
    while (!Serial);

    // Start hardware Master I2C bus at fast speed (400kHz)
    Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN, 400000);

    initCH32V003HardwareRails();

    Serial.println("Scanning I2C Bus to verify QMI8658 exposure...");
    uint8_t verified_addr = 0;
    Wire.beginTransmission(0x6B); 
    if (Wire.endTransmission() == 0) {
        verified_addr = 0x6B;
    } else {
        Serial.println("Searching alternative address (0x6A)...");
        Wire.beginTransmission(0x6A);
        if (Wire.endTransmission() == 0) {
            verified_addr = 0x6A;
        }
    }

    if (verified_addr == 0) {
        Serial.println("ERROR: QMI8658 still not detected. Halting execution.");
        while (1) delay(1000);
    }
    Serial.print("SUCCESS: QMI8658 detected on address: 0x");
    Serial.println(verified_addr, HEX);

    // Run official Waveshare C-driver initialization framework
    i2c_master_bus_handle_t i2cmaster_bus_handle = (i2c_master_bus_handle_t)I2C_NUM_0;
    Serial.println("Calling qmi8658_init()...");
    esp_err_t ret = qmi8658_init(&imu_device_ctx, i2cmaster_bus_handle, verified_addr);
    if (ret != ESP_OK) {
        Serial.print("ERROR: qmi8658_init() failed internally. Code: ");
        Serial.println(ret);
        while (1) delay(1000);
    }
    Serial.println("QMI8658 system driver ready!");
}

void loop() {
    qmi8658_read_accel(&imu_device_ctx, &ax, &ay, &az);
    qmi8658_read_gyro(&imu_device_ctx, &gx, &gy, &gz);

    Serial.print("ACC [g] -> X: "); Serial.print(ax, 2);
    Serial.print(" | Y: "); Serial.print(ay, 2);
    Serial.print(" | Z: "); Serial.println(az, 2);

    Serial.print("GYR [dps] -> X: "); Serial.print(gx, 1);
    Serial.print(" | Y: "); Serial.print(gy, 1);
    Serial.print(" | Z: "); Serial.println(gz, 1);
    Serial.println();

    delay(200);
}

Great find, but according to the OP the I2C address scanner detects the IMU without use of the IO expander code from Waveshare. The I2C bus should be all one needs to fully access the IMU.

The schematic is confusing but the table lower right seems to imply that processor GPIO3,4 are equivalent to I2C_SCL,I2C_SDA, which in turn control the IO expander and the IMU.

The files qmi8658.c and qmi8658.h should be very useful.

Hi TimeDoc. Well, you've answered one important question for me - is it even possible to take JR's advice of contacting Waveshare? Apparently so. So thanks for that. Plus, it seems you've already asked the right questions for me. I was about to give up, but I did long think that there was some queer hardware situation relating to that IO expander. So I think maybe I've got just enough safe blood pressure headroom to go in one more time - to try the lead you've so kindly given me. But first I have to drive my wife to an appointment.

Progress, Timey! I was stuck in town most of the day but when I got home I tried some things. I couldn't get the Waveshare QMI8658 driver to compile (and I may yet need it to) but in the short term, I don't need it to. The CH32V003 code you supplied was enough to let my currently installed QMI8658 driver work, it can find and use the IMU! You got past imu.begin! Now as it turns out, I'm not completely out of the woods. I can use the IMU or the LCD - not both; if I try, I get a runtime error via Serial "E (3486) i2c.common: I2C bus id(0) has already been acquired". It seems I need to learn this whole business of acquiring and releasing instances of the I2C bus. Though it might be easier just to figure out how to get the Waveshare driver to compile, because a cursory glance at it makes me think it knows how to handle this bus access. But yeah, big progress, thanks to you!

you are welcome


Hi again. I feel bad for being the cause of you ordering the C3 version of this board. I was eventually able to make it work but I've already ordered the S3 version. Compared to the ESP32-S3 the ESP32-C3 is starved for IO lines, hence the IO expander. In and of itself that's not so bad, but Waveshare's library for the C3 board uses Espressif's painfully bureaucratic I2C library, not Arduino's familiar little Wire library. The QMI8658 library of course uses the latter. The two are not compatible. Once any code has included the Espressif library, any attempt to communicate using the Arduino library will result in strange errors. So without some sort of work-around, one finds oneself able to access the LCD or the IMU, not both. The work-around I wanted to implement was to make the Waveshare code work via the Wire library, but as it turned out, that had me pulling my non-existent hair out. I found it easier to add AuxRead and AuxWrite functions to the Waveshare code and patch the QMI8658 library to use those. That was my work-around. Since you have a C3 board coming (maybe there by now?) I thought I should pass along my little saga. Thanks again.

You sure weren't wrong. It was actually a software problem. The IMU library uses the Arduino Wire library. The LCD library (the Waveshare board library) uses the Espressif I2C library - which doesn't play nice with others. It took a fair bit of fiddling to be able to access both the LCD and the IMU. Thanks again.

Glad you found the problem! I was pretty sure that the Arduino I2C library would work, but did not consider conflict with other software.

Please post working example code, as it could be of great help to others who have that board. The display looks pretty cool.