View previous topic :: View next topic |
Author |
Message |
Toast
Joined: 15 Aug 2019 Posts: 16
|
Problem about IMU sensor (ICM-20948) with I2C |
Posted: Thu Aug 15, 2019 7:36 am |
|
|
Hello, I am new to embedded system and I am trying out with the PIC18f4550 and a ICM 20948 modules. So in order to get the sensor reading, I think the single- byte write/read sequence would be used. However, I do not know what actually need to be write on the code in order to get the reading. The below code is my attempt on the single - byte read sequence, hope someone could give me some advice.
Code: |
main()
{
i2c_start(); // Start condition
i2c_write(b1101000); // Problem 1 : not sure if this is the right salve address
i2c_write(cmd); //Problem 2: Is that I should the register in the user bank register map?
i2c_start();
i2c_write(b1101001)
//Problem 3: How should I do the NACK ?
i2c_stop(); // Stop condition
while()
{}
} |
Here is the datasheet for the IMU module :http://www.invensense.com/wp-content/uploads/2016/06/DS-000189-ICM-20948-v1.3.pdf |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9221 Location: Greensville,Ontario
|
|
Posted: Thu Aug 15, 2019 8:58 am |
|
|
First , download and confirm you can run PCM P's 'I2C Scanner' program. It's in the code library.
That PIC is a 5 volt device, the peripheral is a 3 volt device, so unless you have 'logic level 'conversion betwen the devices, it won't operate properly. It 'might' kinda work depending on the I/O pins you select.
You can use the 'L' version of that PIC as it will run on 3 volts.
Jay |
|
|
Toast
Joined: 15 Aug 2019 Posts: 16
|
|
Posted: Thu Aug 15, 2019 9:36 am |
|
|
I had looked at the I2C Scanner program. However, I could not run debug and print the message. Also my board don't have any LCD / any display, would you have any suggest such that I could use the program to check the slave address ? |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9221 Location: Greensville,Ontario
|
|
Posted: Thu Aug 15, 2019 10:31 am |
|
|
Since that PIC has USB, is it connected to a PC ? If not, do you have ONE spare output pin ? That could be connected to a TTL<>USB module and then you could send information from PIC to a terminal program running on the PC.
If that's not possible, do you have a pin that could have an LED-resistor attached? You could modify the I2C scanner program to 'pulse' the LED in a similar fashion that old P.O.S.T. cards did for PCs.
For any kind of sucessful programming ,you really need either serial connection to PC or a 'local' display on the PIC PCB.
Jay |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Aug 15, 2019 10:46 am |
|
|
Here is the data sheet:
https://www.invensense.com/wp-content/uploads/2016/06/DS-000189-ICM-20948-v1.3.pdf
On page 14, it lists the i2c slave address of the device. It's given in 7-bit
format. CCS uses 8-bit format, so left-shift it by 1. This gives possible
i2c addresses of 0xD0 or 0xD2, depending on the state of the AD0 pin.
The AD0 pin is on pin 9 of the ICM chip. It's probably tied to ground, but
that's the kind of thing you can find out by using the i2c scanner program.
Let's do hardware. I assume the ICM-20948 module is running at +3.3v.
Method 1 - Using the hardware i2c module in the 18F4550:
1. Connect SDA on the ICM to SDA on the PIC (PIN_B0).
Connect SCL on the ICM to SCL on the PIC (PIN_B1).
2. Use 3.3K pull-up resistors on SDA and SCL. Connect the pull-ups to 3.3v.
3. Enable the SMBus mode in the hardware i2c controller in the 18F4550
with the following #use i2c() statement:
Code: | #use i2c(Master, I2C1, SMBUS, Slow) |
Method 2 - Use software i2c on the 18F4550:
1. Same as above, but it could use any pins on PortB, not just B0 & B1.
But pins B6 and B7 should be kept for the ICSP programmer's use.
2. Same as above.
3. Use the following #use i2c() statement:
Code: | #use i2c(Master, sda=PIN_B0, scl=PIN_B1, Force_sw, Slow) |
sda and scl could be other pins on PortB (only).
Once you have method 1 or 2 set up, then use the i2c scanner program
to see if it detects the ICM device.
http://www.ccsinfo.com/forum/viewtopic.php?t=49713 |
|
|
Toast
Joined: 15 Aug 2019 Posts: 16
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Aug 15, 2019 12:43 pm |
|
|
It tells you exactly in the Burst Read Sequence diagram. The master sends:
S
AD+W
RA
S
AD+R
Data
Data + NACK // Do NACK by master on last data byte read
P
Translate that to CCS, but only read one byte in this example:
Code: |
#define ICM_WR_ADDR 0xD0
#define ICM_RD_ADDR 0xD1
#define ICM_REG_WHO_AM_I 0x00
int8 result;
i2c_start();
i2c_write(ICM_WR_ADDR);
i2c_write(ICM_REG_WHO_AM_I);
i2c_start();
i2c_write(ICM_RD_ADDR);
result = i2c_read(0); // Do a NACK on the last read operation
i2c_stop();
|
The result from reading the WHO_AM_I register should be 0xEA. |
|
|
Toast
Joined: 15 Aug 2019 Posts: 16
|
|
Posted: Thu Aug 15, 2019 10:24 pm |
|
|
Thanks PCM programmer, it is clear and I understand how it works now.
But if i want to do a self test for gyro module, first it need to be enable the self test at address 02 like stated on page 60 from the datasheet.
However, how do I change particular bits [5:3] without modifying the reserved bit[7:6]? And how do I check if the value is within the correct range ?
Code: |
#define ICM_WR_ADDR 0xD0
#define ICM_RD_ADDR 0xD1
#define ICM_REG_WHO_AM_I 0x00
int8 result;
i2c_start(); //S
i2c_write(ICM_WR_ADDR); //AD+W
i2c_write(0x02); //RA
i2c_write(DATA); //Question stated above
i2c_stop(); //P
|
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Fri Aug 16, 2019 2:47 am |
|
|
Toast wrote: |
How do I change paticular bits [5:3] without modifing the reserved
bit[7:6]? And how do I check if the value is within the correct range ?
|
Read the register in the ICM chip. Put the result into a temporary
variable in your program. Then use the CCS function bit_set() to set
a bit that you want set. Then write the modified temporary variable
back to the register in the ICM chip.
Toast wrote: |
How do I check if the value is within the correct range ?
|
According to the ICM data sheet on page 11, it says:
Quote: |
The self-test response for each gyroscope axis is defined in the
gyroscope specification table:
3.1 GYROSCOPE SPECIFICATIONS
Full-Scale Range: GYRO_FS_SEL=0 ±250 dps
When the value of the self-test response is within the specified min/max
limits, the part has passed self-test. When the self-test response exceeds
the min/max values, the part is deemed to have failed self-test.
|
You can set GYRO_FS_SEL to other values and get a wider range, but
your first test can be with it set = 0. You will need a 'signed int16' variable
to hold values from -250 to +250. |
|
|
Toast
Joined: 15 Aug 2019 Posts: 16
|
|
Posted: Fri Aug 16, 2019 4:03 am |
|
|
In the process, I found that there are some register sharing the same address with different user bank register.
Does it mean that I need to write the command to change the user bank before I write the RA? |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Fri Aug 16, 2019 2:21 pm |
|
|
Toast wrote: |
Does it mean that I need to write the command to change the user bank
before I write the RA?
|
There is a sample driver for the ICM20648, which is apparently a similar
chip to the ICM20948, available on Github:
https://os.mbed.com/teams/SiliconLabs/code/ICM20648/file/296308a935f5/ICM20648.cpp/
Search for these lines:
Quote: |
void ICM20648::read_register
void ICM20648::write_register
|
These are SPI routines, but they do call the select_bank() routine
before they talk to any register. It's very simple to calculate the bank.
You can put the same thing in your driver.
You should write the read_register() and write_register() routines,
except use i2c, as shown in earlier posts in this thread. Incorporate
the bank select code at the start of each routine. |
|
|
Toast
Joined: 15 Aug 2019 Posts: 16
|
|
Posted: Wed Aug 28, 2019 11:34 pm |
|
|
PCM programmer wrote: | It tells you exactly in the Burst Read Sequence diagram. The master sends:
S
AD+W
RA
S
AD+R
Data
Data + NACK // Do NACK by master on last data byte read
P
Translate that to CCS, but only read one byte in this example:
Code: |
#define ICM_WR_ADDR 0xD0
#define ICM_RD_ADDR 0xD1
#define ICM_REG_WHO_AM_I 0x00
int8 result;
i2c_start();
i2c_write(ICM_WR_ADDR);
i2c_write(ICM_REG_WHO_AM_I);
i2c_start();
i2c_write(ICM_RD_ADDR);
result = i2c_read(0); // Do a NACK on the last read operation
i2c_stop();
|
The result from reading the WHO_AM_I register should be 0xEA. |
I have tested the code, however the result is not 0xEA. The below is my code
Code: |
#use i2c(Master,sda=PIN_B2,scl=PIN_B1,force_hw)
#define LED pin_a2
#define ICM_WR_ADDR 0xD0
#define ICM_RD_ADDR 0xD1
#define ICM_REG_WHO_AM_I 0x00
/* Register common for all banks */
#define ICM_REG_BANK_SEL 0x7F
/**************************************************************************//**
* @name ICM20648 register banks
* @{
******************************************************************************/
#define ICM_BANK_0 (0 << 7) /**< Register bank 0 */
#define ICM_BANK_1 (1 << 7) /**< Register bank 1 */
#define ICM_BANK_2 (2 << 7) /**< Register bank 2 */
#define ICM_BANK_3 (3 << 7) /**< Register bank 3 */
/**@}*/
void select_bank(unsigned int8 bank)
{
/* clear R/W bit - write, send the address */
i2c_start(); //S
i2c_write(ICM_WR_ADDR); //AD+W
i2c_write(ICM_REG_BANK_SEL); //RA
i2c_write((unsigned int8)(bank<<4));
i2c_stop();
return;
}
void who_am_i()
{
int8 result;
// get the who am i register value
select_bank(0);
i2c_start();
i2c_write(ICM_WR_ADDR);
i2c_write(ICM_REG_WHO_AM_I);
i2c_start();
i2c_write(ICM_RD_ADDR);
result = i2c_read(0);
i2c_stop();
if(result != 0xEA)
{
output_high(LED);
}
else
{
output_low(LED);
}
////////////////////
}
void main()
{
who_am_i();
while(TRUE)
{
}
}
|
I got the LED turned on so the result is not 0xEA, however i dont have any display hardware so i cannot tell the exact value of the result. I also checked the i2c device with i2c scanner program.
Also, I would like to ask why ICM_WR_ADDR would be 0xD0 and ICM_RD_ADDR would be 0xD1. I am new to pic and not quite understand how it works. My thought is that the slave address of the ICM-20948 is b110100X which is 7bit long, but in the code the address write to i2c is 8bit long, since i do not long which whether the extra bit is append to first or last, i considered both cases. First, bX110 100X which the last four bit will not be 0 or 1. Second, b 1101 00X0 the last four bit will not be 0 or 1 as well. So i am a bit confused and hoping for some helping hands. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Aug 29, 2019 12:10 am |
|
|
CCS uses 8-bit i2c address format. To convert 7-bit to 8-bit format,
shift the 7-bit format left by 1. Then make bit 0 = 0 for an i2c write
and make bit 0 = 1 for an i2c read. |
|
|
Toast
Joined: 15 Aug 2019 Posts: 16
|
|
Posted: Thu Aug 29, 2019 1:08 am |
|
|
PCM programmer wrote: | CCS uses 8-bit i2c address format. To convert 7-bit to 8-bit format,
shift the 7-bit format left by 1. Then make bit 0 = 0 for an i2c write
and make bit 0 = 1 for an i2c read. |
Does it mean that from my code , the i2c_write(ICM_RD_ADDR) should be using i2c_read instead? I am quite confused when should use write and when should use read
Code: | select_bank(0);
i2c_start();
i2c_write(ICM_WR_ADDR);
i2c_write(ICM_REG_WHO_AM_I);
i2c_start();
i2c_write(ICM_RD_ADDR);
result = i2c_read(0);
i2c_stop();
|
Last edited by Toast on Thu Aug 29, 2019 1:13 am; edited 1 time in total |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Aug 29, 2019 1:10 am |
|
|
You send the write address before you write to a register, and you send
the read address before you read a register, just as the code shows. |
|
|
|