|
|
View previous topic :: View next topic |
Author |
Message |
webgiorgio
Joined: 02 Oct 2009 Posts: 123 Location: Denmark
|
i2c read specific data address |
Posted: Thu Nov 14, 2019 3:38 pm |
|
|
Hello,
I need to read 6 specific memory locations from a i2c 3axis accelerometer.
I ran the i2c scanner ( https://www.ccsinfo.com/forum/viewtopic.php?t=49713 )
and found addresses 0x32 (accelerometer) and 0x3C (magnetometer). It matches with the datasheet info.
https://www.st.com/resource/en/datasheet/lsm303agr.pdf
The data addresses I want to read are: 28, 29, 2A, 2B, 2C, 2D. (page 43)
I followed this guide, https://www.ccsinfo.com/forum/viewtopic.php?t=55088
and implemented the code for the first two bytes, but it doesn't work. I get "-1,0,255" in the serial monitor.
What am I doing wrong?
Code: |
#include <16F1788.h>
#fuses INTRC_IO, PUT, NOWDT, NOLVP, NOPROTECT //INTRC_IO to be able to use A7 and A6 as GPIO
#device ADC=8
#use delay(clock=4000000)
#use rs232(baud=9600,xmit=PIN_C6,rcv=PIN_C7)
#use i2c(Master, sda=PIN_C4, scl=PIN_C3)
void read_inclinometer();
void main(){
setup_oscillator(OSC_4MHZ);
delay_ms(500);
printf("boot ... \n");
while(true)
{
read_inclinometer();
delay_ms(1000);
}
}
#define incl_address 0x32 //32 or 3C
//---------------------------------------
void read_inclinometer(){
int8 ACC_Data0, ACC_Data1, ACC_Data2, ACC_Data3, ACC_Data4, ACC_Data5;
int16 x, y, z;
int status;
i2c_start(); //start
i2c_write(incl_address); //send device address
i2c_write(0x28); //send address of the register I want to read
i2c_start(); //restart
i2c_write(incl_address+1); //send device read address (device address+1)
ACC_Data0 = i2c_read(); //Read bytes (NACK the last byte)
i2c_stop(); //stop
i2c_start();
status=i2c_write(incl_address);
i2c_write(0x29);
i2c_start();
i2c_write(incl_address+1);
ACC_Data1 = i2c_read();
i2c_stop();
x = (int16)(ACC_Data1 << 8) | ACC_Data0;
printf("%d,%d,%Ld\n", ACC_Data0, ACC_Data1, x );
}
|
|
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9226 Location: Greensville,Ontario
|
|
Posted: Thu Nov 14, 2019 4:29 pm |
|
|
I downloaded the sensor datasheet....
If you're trying to follow 'table22' of the device datasheet then you do not need the 2nd i2c_start() function.
Jay |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Nov 14, 2019 11:18 pm |
|
|
You have a problem in two places:
Quote: | ACC_Data0 = i2c_read(); //Read bytes (NACK the last byte)
ACC_Data1 = i2c_read(); |
You say this, but you don't do it. Look in the CCS manual to see how to
do it. Look in the i2c_read() section on page 315.
http://www.ccsinfo.com/downloads/ccs_c_manual.pdf
NACK means "No Ack", or "Do not Ack". |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Fri Nov 15, 2019 2:47 am |
|
|
Also, I2C devices generally support what is known as read/write 'burst'
operation. This device does. With this when you read a register, the
device automatically increments the address being accessed. So to read from
registers 28 & 29, you only need to send the address 28, then perform
two successive reads.
Code: |
//---------------------------------------
void read_inclinometer(){
int8 ACC_Data0, ACC_Data1, ACC_Data2, ACC_Data3, ACC_Data4, ACC_Data5;
int16 x, y, z;
int status;
i2c_start(); //start
i2c_write(incl_address); //send device address
i2c_write(0x28); //send address of the register I want to read
i2c_start(); //restart
i2c_write(incl_address+1); //send device read address (device address+1)
ACC_Data0 = i2c_read(); //Read bytes (NACK the last byte)
//reads register 28
ACC_Data1 = i2c_read(0); //This is where you need the NACK
//This reads register 29.
i2c_stop();
x =make16(ACC_Data1, ACC_Data0);
printf("%d,%d,%Ld\n", ACC_Data0, ACC_Data1, x );
}
|
Also note using the 'Make16' operation. Read the manual for this. It
performs all the operations that were being done (moving the first
value into the MSB, and combining with the second value to return
an int16 result, as efficiently as possible. Your existing code would
not actually work. Bracket in the wrong place:
((int16)ACC_Data1 << 8)
It is ACC_Data1, that needs to be turned into an int16, _before_ the
rotation is performed, _not_ the result of the rotation. Doing it after
the rotation means the data has already been lost.... |
|
|
webgiorgio
Joined: 02 Oct 2009 Posts: 123 Location: Denmark
|
|
Posted: Fri Nov 15, 2019 2:55 am |
|
|
Sorry, it needs to be "i2c_read(0);"
Thanks for pointing out Table 22 (page38). It says to send again the start condition.
I changed the code adding a bit of debugging printf. The i2c communication seems to work but the value I get isn't correct. I should get some unstable number.
Code: |
#include <16F1788.h>
#fuses INTRC_IO, PUT, NOWDT, NOLVP, NOPROTECT //INTRC_IO to be able to use A7 and A6 as GPIO
#device ADC=8
#use delay(clock=4000000)
#use rs232(baud=9600,xmit=PIN_C6,rcv=PIN_C7)
#use i2c(Master, sda=PIN_C4, scl=PIN_C3)
void read_inclinometer();
void main(){
setup_oscillator(OSC_4MHZ);
delay_ms(500);
printf("boot ... \n");
while(true)
{
output_toggle(PIN_A7);
read_inclinometer();
delay_ms(1000);
}
}
#define incl_address 0x32 //32 o 3C
//---------------------------------------
void read_inclinometer(){
int8 ACC_Data0, ACC_Data1, ACC_Data2, ACC_Data3, ACC_Data4, ACC_Data5;
int16 x, y, z;
int1 status;
printf("Read first byte ");
i2c_start(); //start
status=i2c_write(incl_address); //send device address
if(!status) printf("SAK "); else printf("problem ");
status=i2c_write(0x28); //send address of the register I want to read
if(!status) printf("SAK "); else printf("problem ");
i2c_start(); //restart
status=i2c_write(incl_address+1); //send device read address (device address+1)
if(!status) printf("SAK\n"); else printf("problem\n");
ACC_Data0 = i2c_read(0); //Read byte and NACK
i2c_stop(); //stop
printf("Read second byte ");
i2c_start();
status=i2c_write(incl_address);
if(!status) printf("SAK "); else printf("problem ");
status=i2c_write(0x29);
if(!status) printf("SAK "); else printf("problem ");
i2c_start();
status=i2c_write(incl_address+1);
if(!status) printf("SAK\n"); else printf("problem\n");
ACC_Data1 = i2c_read(0);
i2c_stop();
x = (int16)(ACC_Data1 << 8) | ACC_Data0;
printf("%d,%d,%Ld\n\n", ACC_Data0, ACC_Data1, x );
}
|
result:
Code: | Read first byte SAK SAK SAK
Read second byte SAK SAK SAK
0,0,0 |
|
|
|
webgiorgio
Joined: 02 Oct 2009 Posts: 123 Location: Denmark
|
|
Posted: Fri Nov 15, 2019 3:16 am |
|
|
Thanks Ttelmah.
So, I made the code more readable, and read the other registers in burst mode.
Code: | void read_inclinometer(){
int8 ACC_Data0, ACC_Data1, ACC_Data2, ACC_Data3, ACC_Data4, ACC_Data5;
int16 x, y, z;
i2c_start(); //start
i2c_write(incl_address); //send device address
i2c_write(0x28); //send address of the register I want to read
i2c_start(); //restart
i2c_write(incl_address+1); //send device read address (device address+1)
ACC_Data0 = i2c_read(); //Read register 28 and ACK
ACC_Data1 = i2c_read(0); //Read register 29 and NACK
i2c_stop(); //stop
x =make16(ACC_Data1, ACC_Data0);
//x = ((int16)ACC_Data1 << 8) | ACC_Data0;
printf("%d,%d,", ACC_Data0, ACC_Data1);
i2c_start(); //start
i2c_write(incl_address); //send device address
i2c_write(0x2A); //send address of the register I want to read
i2c_start(); //restart
i2c_write(incl_address+1); //send device read address (device address+1)
ACC_Data2 = i2c_read(); //Read register 2A and ACK
ACC_Data3 = i2c_read(); //Read register 2B and ACK
ACC_Data4 = i2c_read(); //Read register 2C and ACK
ACC_Data5 = i2c_read(0); //Read register 2D and NACK
i2c_stop();
printf("%d,%d,%d,%d\n\n", ACC_Data2, ACC_Data3, ACC_Data4, ACC_Data5);
}
|
Still I get 0 for all of them :( |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Fri Nov 15, 2019 3:26 am |
|
|
What value are your pull-up resistors?.
By default the #use will be selecting fast mode at 400KHz. This is only
supported with a reasonably low pull-up. Depends on the length of the
wires and traces, and what connectors are present. Reading '0' is what
typically happens if the pull-ups are not small enough. |
|
|
webgiorgio
Joined: 02 Oct 2009 Posts: 123 Location: Denmark
|
|
Posted: Fri Nov 15, 2019 4:05 am |
|
|
10 kOhm. The problem persist with 1 kOhm.
The circuit is on a breadboard, with 20 cm dupont cable to the LSM303 adapter board. Power is 3.3V from the ICD-U64 programmer.
Regarding #use, I tried 100 kHz and SLOW, but it didn't solve the problem.
I am going to try the device with a ESP8266+Arduino.
I wonder if the device needs to be configured on some other register. I used this chip before with ESP8266+Arduino but I don't remember having changed any configuration.
UPDATE
The module works ok with ESP8266+Arduino.
The Arduino i2c scanner gives me address 0x19 (it is a 7 bit address, 0011001, which is 0x32 if adding a zero at the end 00110010). |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9226 Location: Greensville,Ontario
|
|
Posted: Fri Nov 15, 2019 6:34 am |
|
|
comments...
At 3.3 volts, I usually use 3k3 pullups..
Does the 'module' have it's own pullups ? Some do, some don't.
You may need to turn off any other peripherals that can use the I2C pins.
Since PCM P's I2C scanner program works, the hardware should be OK.
Since the Ardunio code works, I'd print it out and compare to the PIC code. Check each line and see what the diference is, if any.
also
I re-read the datasheet and read that SR is really same as ST, arrgh... I wrongly assumed SR meant Slave Read, not Start Read. I thought ST meant STart.....
Jay |
|
|
webgiorgio
Joined: 02 Oct 2009 Posts: 123 Location: Denmark
|
|
Posted: Fri Nov 15, 2019 7:32 am |
|
|
The board with the inclinometer has no pullups.
The ESP module does not have the pullups in the schematic, but I didn't put external pullups and it worked, so I think it enabled internal pullups automatically on the i2c pins.
From the PIC16F1788 datasheet page 9 (pin allocation table)
http://ww1.microchip.com/downloads/en/DeviceDoc/40001675C.pdf
I see that RC3 RC4 are shared with PSMC, SCK SDI. How do I disable those peripherals?
RB6 RB7 are alternative pins for i2c, but I am using them for ICSP and serial port, so I think the configuration is ok there.
The Arduino code:
0x19 is the inclinometer address (7 bit) which correspond to the 0x32 (8 bit) I used for the PIC.
Code: | int ACC_Data0, ACC_Data1, ACC_Data2, ACC_Data3, ACC_Data4, ACC_Data5;
int x, y, z;
const float alpha = 0.15;
float fx = 0;
float fy = 0;
float fz = 0;
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
Wire.beginTransmission(0x19); // Set accel
Wire.write(0x20); // CTRL_REG1_A register
Wire.write(0x47); // 50 Hz, normal power, all 3 axis enabled
Wire.endTransmission();
Wire.beginTransmission(0x19); // Set accel
Wire.write(0x23); // CTRL_REG4_A register
Wire.write(0x08); // continuous update, littleendian, 2g, high resolution, 4-wire spi
Wire.endTransmission();
}
void loop() {
Wire.beginTransmission(0x19);
Wire.write(0x28);
Wire.endTransmission();
Wire.requestFrom(0x19, (byte)1);
ACC_Data0 = Wire.read();
Wire.beginTransmission(0x19);
Wire.write(0x29);
Wire.endTransmission();
Wire.requestFrom(0x19, (byte)1);
ACC_Data1 = Wire.read();
Wire.beginTransmission(0x19);
Wire.write(0x2A);
Wire.endTransmission();
Wire.requestFrom(0x19, (byte)1);
ACC_Data2 = Wire.read();
Wire.beginTransmission(0x19);
Wire.write(0x2B);
Wire.endTransmission();
Wire.requestFrom(0x19, (byte)1);
ACC_Data3 = Wire.read();
Wire.beginTransmission(0x19);
Wire.write(0x2C);
Wire.endTransmission();
Wire.requestFrom(0x19, (byte)1);
ACC_Data4 = Wire.read();
Wire.beginTransmission(0x19);
Wire.write(0x2D);
Wire.endTransmission();
Wire.requestFrom(0x19, (byte)1);
ACC_Data5 = Wire.read();
x = (int16_t)(ACC_Data1 << 8) | ACC_Data0;
y = (int16_t)(ACC_Data3 << 8) | ACC_Data2;
z = (int16_t)(ACC_Data5 << 8) | ACC_Data4;
Serial.print(x); Serial.print(","); Serial.print(y); Serial.print(","); Serial.println(z);
delay(250);
} |
|
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9226 Location: Greensville,Ontario
|
|
Posted: Fri Nov 15, 2019 7:52 am |
|
|
Good that you checked about the pullups as it eliminates one possible problem.
The Ardunio code has a 'setup' function and the CCS one does not. That could be the problem. I see 'SPI' in the setup commands.. It is possible that the 'default' parameters of the sensor are not allowing it to function as an I2C device. Maybe the default is SPI mode !
You'll need to read the datasheet, make a list of the 'setup' registers and how YOU want them to be, then add code to do so.
As a general comment, never assume 'default' values are correct for YOUR application, always code them yourself.
Jay |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Fri Nov 15, 2019 9:51 am |
|
|
Also, wouldn't it be nice to tell us what inclinometer chip you are using ?
If you bought a board, give a link to the website for it. |
|
|
webgiorgio
Joined: 02 Oct 2009 Posts: 123 Location: Denmark
|
|
Posted: Fri Nov 15, 2019 11:50 am |
|
|
The chip datasheet is linked in the first post ;)
The evaluation board https://www.st.com/en/evaluation-tools/steval-mki172v1.html
I hooked up the scope to SCL SDA, and found that the i2c signals for the ESP improved a lot adding 3,3k pull ups. It worked without anyway.
With the scope connected to the computer (Rigol DK1052E + PulseView) I decoded the communication. Seems to match the C instructions. Any simple way of attaching images here?
Aaaaand it seems temtronic spotted the problem!
It was something I tested earlier today (running the ESP program without the initialization code), but I DIDN'T POWER OFF AND ON THE INCLINOMETER, which kept the initialization.
So I had the impression that it was not needed.
In the ESP program I now removed the setup code and I get 0,0 as I get from the PIC.
So, I am going to implement the chip initialization in the PIC and let you know.
BTW: first time I used it, very cool and handy https://sigrok.org/wiki/Main_Page |
|
|
webgiorgio
Joined: 02 Oct 2009 Posts: 123 Location: Denmark
|
|
Posted: Fri Nov 15, 2019 5:29 pm |
|
|
I start to see varying numbers once I implemented the setup:
Code: | i2c_start(); //start
i2c_write(incl_address); //send device address
i2c_write(0x20); //send address of the register I want to write
i2c_write(0x47); //send data
i2c_stop(); //stop
i2c_start(); //start
i2c_write(incl_address); //send device address
i2c_write(0x23); //send address of the register I want to write
i2c_write(0x08); //send data
i2c_stop(); //stop |
However the burst reading isn't working. On the reads after the first one I get again the value of the first read. It looks like the inclinometer isn't incrementing the pointer to the next register.
Code: | i2c_start(); //start
i2c_write(incl_address); //send device address
i2c_write(0x28); //send address of the register I want to read
i2c_start(); //restart
i2c_write(incl_address+1); //send device read address (device address+1)
ACC_Data0 = i2c_read(); //Read register 28 and ACK
ACC_Data1 = i2c_read(0); //Read register 29 and NACK
i2c_stop(); //stop |
The decoding of the i2c data seems to match the datasheet specification, but the second reply is always identical to the first.
|
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9226 Location: Greensville,Ontario
|
|
Posted: Fri Nov 15, 2019 6:32 pm |
|
|
I think you want to increment the registers within the device NOT the device address.
this line of your code...
Quote: | i2c_write(incl_address+1); //send device read address (device address+1) |
incl_address is really the address of the physical device (the chip), you want to increment the REGISTERS within the device
Jay |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|