Icm-42670-p Driver

1 view
Skip to first unread message

Rolan Sacco

unread,
Aug 3, 2024, 6:01:35 PM8/3/24
to dresesbuweb

I will ask my colleague to look at your schematics on Monday, but before that, can you please show some code snippets and the pins output when you send the command to the sensor to know the status of the WHOAMI register?

There is a ready made driver for this sensor in our newer nRF Connect SDK, zephyr based solution. I do not see that this new version of motion sensor has no support on our older SDK (at least no one verified that the SPI drivers work with this sensor on the older SDK)

We're not going to write an entire driver, merely the first step: the hello world of driver writing: reading the device ID of the sensor. This version is labelled easy, because we explain the code fragments, and you only have to copy and paste the fragments into the right place. Use this version if you have very little previous experience with Rust, if these workshops are your first in the embedded domain, or if you found the hard version too hard. You can work in the same file with either version.

i2c-driver/src/icm42670p_solution.rs provides the solution to this exercise. If you want to run it, the imports need to be changed in main.rs and lib.rs. The imports are already there, you only need to comment the current imports out and uncomment the solutions as marked in the line comments.

To use a peripheral sensor first you must get an instance of it. The sensor is represented as a struct that contains both its device address, and an object representing the IC bus itself. This is done using traits defined in the embedded-hal crate. The struct is public as it needs to be accessible from outside this crate, but its fields are private.

We add an impl block that will contain all the methods that can be used on the sensor instance. It also defines the Error Handling. In this block, we also implement an instantiating method. Methods can also be public or private. This method needs to be accessible from outside, so it's labelled pub. Note that written this way, the sensor instance takes ownership of the IC bus.

We define a read and a write method, based on methods provided by the embedded-hal crate. They serve as helpers for more specific methods and as an abstraction that is adapted to a sensor with 8-bit registers. Note how the read_register() method is based on a write_read() method. The reason for this lies in the characteristics of the IC protocol: We first need to write a command over the IC bus to specify which register we want to read from. Helper methods can remain private as they don't need to be accessible from outside this crate.

In this specific context, we are using an external device (since it is a sensor, even if it is on the same PCB). It is addressable by I2C, and we are reading and writing to its register addresses. The addresses each identify a unique location that contains some information. In this case, we want the address for the location that contains the current temperature, as read by the sensor.

In order to use CAN with FreeRTOS, I have replaced the interrupt handler with my own that includes a semaphore for task switching. I've modified the function MCAN_TransferCreateHandle in fsl_mcan.c in the SDK as follows:

I also have added the header for my code file to fsl_mcan.c. I'm doing this because s_mcanIsr is defined within the module as a static variable and is used by other driver functions. I really want to do this in a way that does not modify the fsl_mcan driver. Is there another way to do this without modifying the driver?

The wrappers provide an API which blocks the calling task until the I/O operation completes and allows other tasks to run. This is achieved by using the asynchronous API of the underlying driver along with RTOS task synchronization object.

For my driver I have to read multiple registers of the Gyro therefore I use the HAL_I2C_MEM_READ function of the STM32 HAL which is in the HAL generated from CubeMX. I noticed that I get random Communication Errors so I have written a simple testcase in which I only read the Device ID of the Gyro in a while loop. Here is the pseudo code of this testcase so you have an idea how it works:

The retVal_e contains the status of the communication. If its all fine the loop waits for 10 ms and will do another read. If there is a error the error counter will be incremented and a print will appear on the console. In the first place the osDelay was set to 1 ms in this testcase I roughly get 200 errors in 30 s but our company has a zero error tolerance so this is inacceptable. After this I tested the other I2C devices on the PCB if there will be also errors. On the I2C1 Interface we have a MS5837 Pressure sensor and a RX8804 real time clock. I tested both devices in a quit similar way --> polling some data in a while loop. The communication with both devices was flawless even if I have no delay at all just the read function in the while loop. We also got an Ina231 power monitor on the I2C3 Interface. I tested this device too but this communication was again flawless even without a delay. For you to notice both I2C Interfaces (I2C1 and I2C3) are configured in Fast Mode with the Standard Timing (tr and tf = 0 ns) and the Analog Filter enabled while the digital Filter coefficient 0. Also both interfaces use a DMA communication for streams that are bigger than 10 Bytes. Therefore the testcases didn't used the DMA (only a single register read in the while loop). Back to the I2C2 Interface. I looked with a very fast scope on the bus and the positive voltage and saw that the pullup resistor was a bit to large because the clk high phases were nearly spikes so I changed the 4.7k Pullups to 1.8k Pullups. Thereafter the bus looked really nice and the serial encryption of the scope recognized the correct communication sequence (I attached a pic of the normal sequence (device address: 0x68, register address: 0x75, device ID: 0x47). I also get much less errors roughly 10 errors in 30 s (with 1 ms delay). I set a breakpoint to the faulty communication so I could observe the bus in the scope in case of a communication error. Then a communication error occurred the bus looks still very fine and no significant change to the previous sequences could be seen therefore I assume the fault is in the MPU or in the software and not in the gyro or on the bus.

Trough some Debugging I located the error at the waiting for some flags. Over a period of 10 minutes I could capture 3 timeouts for the RXNE flag in the HAL_I2C_Mem_Read() function. I also received 3 timeouts for the TXIS flag in the I2C_RequestMemoryRead() which is called in the HAL_I2C_Mem_Read() function. Also in the I2C_RequestMemoryRead() function I got 6 timeouts for the TC flag. Each time the communication on the bus looks good but the MPU didn't get the right sample.

I already looked up the errata sheet of the STM32 MP153A (ES0438 Rev 7). There is a case (2.19.1) of wrong data sampling if the data setup time is shorter than one I2C kernel clock period. As a workaround I increased the I2CClk Frequency from the previous 64 MHz (Clk src HSI) to 100.5 MHz (Clk src PCLK1) and changed the Timing respectively. This workaround did not help and the errors did not vanish. I even tried a much slower I2C Kernel Clk with 4 MHz (Clk src CSI) but this also did not work. In the next case (2.19.2) there are bus errors with the consequence that the BERR flag is set. I checked this flag but this flag is never set when I get an error. The next case (2.19.3) is not relevant because we don't have a multi master bus. Also the next case (2.19.4) is not relevant because we have clock stretching enabled. The last case (2.19.5) speaks from a stall after the transfer of the first byte if the ratio of the I2c APB Clock and the I2c Kernel Clock is between 1.5 and 3.0. The Workaround is the changing of this ratio to be higher than 3 or to be lower than 1.5. The APB Clk is set to 100.5 MHz and can not be changed due to the project requirements but I already increased the I2C Kernel Clk to 100.5 MHz and 4 MHz (see above) therefore the ratio is either 1.0 or 25.1 and also this workaround did not help.

I made the observation if I increase the delay in the while loop from 1 ms to 10 ms or even 100 ms I get no errors within 5 min. I even tested the communication with a delay from 10 ms within 30 min in two runs. In the first run I didn't get an error at all and in the second run I get a single error. For these tests I changed the I2C kernel clk back to the previously 64 MHz. This seems to be a workaround but we have a zero error tolerance and we need a communication rate of at least 200 Hz so the delay of 10 ms is already to long.

Thank you for the input but I think I found the fault in the voltage supply for the Gyro. The chip gets a VDD Voltage of 3.3V and a VDDIO Voltage of 1.8V. I looked with a scope on both voltages. The Voltage for the IO Pins of the gyro had a ripple of 330 mVpp which equals to a ratio of 18% ripple. In the first instance I added some extra capacitors. The ripple is now at roughly 50mVpp I also have reduced the pullups to 1.5k. I tested this setup over the weekend. There was 263 errors over the entire weekend in 2.37 billion communications this equals to an error rate of 0.111ppm. I think with some extra smoothing this will get even better.

It should be also worth to discuss with sensor manufacturer as from your message above, the issue might be that the sensor does not answer (or give bad answer) if asked too frequently. There might be some limitations or configuration to do on their side.

c80f0f1006
Reply all
Reply to author
Forward
0 new messages