Pic, XC8 and I2C - help! - Page 1
Forum section: electronics / general

author: DTJ
author_rank: Super Contributor
timestamp: December 28, 2016, 03:50:37 am
content: I'm battling away with my first use of the I2C interface on a Pic P18LF14k22 trying to read a TSYS01 temperature sensor IC.I'm using MPLABx + XC8 and the XC8 I2C library. There's only the Pic and a single TSYS01 connected together.I've been trying to follow the information given in the TSYS01 application note:http://www.amsys.de/sheets/amsys.de.an.tsys01.pdfFirst I have to reset the TSYS01 then I have to read the calibration coefficients out of it. See images attached.My code compiles and runs ok and I can see what looks like sensible signals on the SCL and SDA lines.To configure the TSYS chip:PS (Pin16) tied high = I2C mode (low is SPI)CSB (Pin2) tied low = so address pin bit should be 1 / high (data sheet shows it as /CSB).When I run my code I read the k0 coefficient high and low bytes as 0xFF - all ones.Should I have a AckI2C() command between the two ReadI2C() commands?Thanks for any help.Here's the pertinent parts of the code:Edit - I changed a couple of PutsI2C() commands to WriteI2C()Code:init_I2C(); //initialise I2C peripheral moduletsys01_reset(); //reset the tsys01 ICtsys01_read_coeffs(); //read the calc coefficients from the TSYS01 sensor//*********************************************************************// Initialise the I2C peripheral module as MASTER, Slew off, 100 kHz//*********************************************************************void init_I2C(void) //initialise I2C peripheral module{CloseI2C(); //close i2c in case it was already open//initialise I2C module for Master mode, slew off & 100KHzOpenI2C(MASTER,SLEW_OFF);SSPADD=39; //100kHz @16MHz clock //I2C Master mode, clock = FOSC/(4 * (SSPADD + 1))// 100000 = 16000000/(4x(39+1)} //close of init_I2C()//*********************************************************************// Reset the tsys01 sensor IC//*********************************************************************void tsys01_reset(void) //reset the tsys01 IC{unsigned char data = 0;data = SSPBUF; //read anything in buffer to clr buffer full statusIdleI2C(); //wait until I2C bus is idleStartI2C(); //send startWriteI2C(0xEE); //write address of TSYS01 = 0xEEIdleI2C();WriteI2C(0x1E); //write reset command 0x1E to I2C slaveIdleI2C();StopI2C();} //close of tsys01_reset()//*********************************************************************// Read 5 x 16 bit coefficients from the TSYS01//double ADC24 = ADC result read from sensor IC//float k4 = k4MSB+k4LSB = IC location 0xA2+0xA3//float k3 = k3MSB+k3LSB = IC location 0xA4+0xA5//float k2 = k2MSB+k2LSB = IC location 0xA6+0xA7//float k1 = k1MSB+k1LSB = IC location 0xA8+0xA9//float k0 = k0MSB+k0LSB = IC location 0xAA+0xAB//*********************************************************************void tsys01_read_coeffs(void) //reads TSYS01 IC coefficients I2C interface{unsigned char data = 0;data = SSPBUF; //read anything in buffer to clr buffer full status//READ coefficient k0IdleI2C(); //wait until I2C bus is idleStartI2C(); //send startWriteI2C(0xEE); //write address of TSYS01 = 0xEEIdleI2C();WriteI2C(0xAA); //command 0xAA to read coeff k0StopI2C();StartI2C(); //send read addressWriteI2C(0xEF); //command to READ data coeff k0, data returned as 2 bytesIdleI2C();k0MSB = ReadI2C(); //read byte1IdleI2C();//should I have an AckI2C() here????????k0LSB = ReadI2C(); //read byte2IdleI2C();StopI2C();} //close of tsys01_read_coeffs()

author: mikerj
author_rank: Super Contributor
timestamp: December 28, 2016, 11:59:56 am
content: When an I2C master is reading data from a slave it must ACK after every byte received apart from the last one which should be a NACK. Effectively an ACK tells the slave that the master wants to read another byte of data.

author: DTJ
author_rank: Super Contributor
timestamp: December 28, 2016, 12:57:45 pm
content: Thanks Mike. I'll add some ACKs / NAK tomorrow and see how it goes.What you say makes sense as I've managed to get it to the point where I get data for the first byte but the second byte is 0xFF.I'm guessing it's 0xFF as it's not returning data and I'm just reading the pull-up value. Adding the ACK should then enable the second byte to be read.Cheers.

author: CJay
author_rank: Super Contributor
timestamp: December 28, 2016, 05:48:47 pm
content: What MikerJ says makes sense, I struggled to get my head round I2C ACK/NACK initially and had similar issues plus the chip I was talking to was battery backed so it was possible to leave it halfway through a read or write and in a state where it was expecting more clocks and would patiently sit there until it received them.Data sheet should specify the protocol, if it just says I2C then the Philips I2C docs are really useful.

author: DTJ
author_rank: Super Contributor
timestamp: December 29, 2016, 10:35:18 am
content: Thanks CJay (& Mike) the data sheet for the TSYS01 IC recommends sending it a reset command prior to measuring which kind of ties in with CJay's warning.I've NEARLY got it all working thanks to you guys. My problem now is more C related - number types I think.I've got a couple of calculations giving the wrong result.I've got the following parameters:k0...k4 these 5 constants are 16 bit values that can range from 0 to 65535ADC24 - this is a 24 bit value read from the sensor that can range from 0 to 16777215ADC16 - this is a 16 bit value derived from ADC24.Temp_C - this is the calculated temp. Type = floatWhen:unsigned int ADC_MSB = 0x0095 = 149unsigned int ADC_MID = 0x001E = 30unsigned int ADC_LSB = 0x0084 = 132The calculation to assemble the 3 bytes into one variable fails. This is what the calc should do:ADC24 = ADC_MSB*256*256 + ADC_MID*256 + ADC_LSB;= 149 x 256 x 256 + 30 x 256 + 132= 9764864 + 7680 + 132= 9772676 = 0x951E84But I am getting a result of 0x45F420 = 4584480This value (ADC16) goes into the temperature calculation later on in the code.I'm sure its something simple but I've tried a few different number type declarations I think should work but they haven't.The code below shows my variable declarations and the code snippet for the calculation:Code://TSYS01 variable declarationsdouble ADC16;double ADC24 = 0; // tsys01 temperature resultunsigned int ADC_MSB = 0; // 3 x 8 bit bytes read from TSYS01 combined = ADC24unsigned int ADC_MID = 0;unsigned int ADC_LSB = 0;float Temp_C; //calculated temperature Celcius//calibration coefficients, 16 bit integersunsigned int k4 = 0;unsigned int k3 = 0;unsigned int k2 = 0;unsigned int k1 = 0;unsigned int k0 = 0;ADC24 = ADC_MSB*256*256 + ADC_MID*256 + ADC_LSB; //calculation - assemble 8 bit bytes into 24 bit ADC valueADC16 = ADC24 / 256; //calculate ADC16// now calculate temp using ADC16 and k0....k4Temp_C = (-2 * k4 * 1E-21 * ADC16*ADC16*ADC16*ADC16) + (4 * k3 * 1E-16 * ADC16*ADC16*ADC16) - (2 * k2 * 1E-11 * ADC16*ADC16) + (k1 * 1E-6 * ADC16) - (1.5 * k0 * 1E-2);

author: AndyC_772
author_rank: Super Contributor
timestamp: December 29, 2016, 11:03:34 am
content: "unsigned int" is 16 bit under XC8. This means that ADC_MSB*256*256 overflows. It doesn't get automatically promoted to a wider type unless you cast it yourself.I've taken to always using the types defined in stdint.h, because they're unambiguous and portable between platforms, so I'd use a uint32_t for the result.Try:ADC24 = (((uint32_t) ADC_MSB) << 16) | (ADC_MID << 8 ) | ADC_LSB;When I get problems like this, I find it helpful to split out the calculation into multiple lines which I can then single step through and see where the problem is happening. For example, you might also like to try:Code:uint32_t adc_24i;adc_24i = ((uint32_t) ADC_MSB) << 16;adc_24i |= ADC_MID << 8;adc_24i |= ADC_LSB;ADC24 = (float) adc_24i;

author: rstofer
author_rank: Super Contributor
timestamp: December 29, 2016, 04:45:09 pm
content: I think I would use a union of a uint32_t with a struct of 4 uint8_t's such that I avoided all the multiplication or shifting:Code:#ifdef _WIN32#include <Windows.h>#else#include <unistd.h>#endif#include <stdio.h>union Readings_t {uint32_t Value;struct {uint8_t Low;uint8_t Mid;uint8_t High;uint8_t Dummy;} Bytes;};void WinMain(void){union Readings_t Readings;Readings.Bytes.Low = 13;Readings.Bytes.Mid = 12;Readings.Bytes.High = 14;Readings.Bytes.Dummy = 0;printf("Output %d\n",Readings.Value);}ETA: Tweaked the code a bit to make it a little cleaner

author: DTJ
author_rank: Super Contributor
timestamp: December 30, 2016, 02:10:40 am
content: Quote from: AndyC_772 on December 29, 2016, 11:03:34 amThanks Andy, changing the casting of the ADC_MSB to "unsigned short long ADC_MSB = 0;" seems to have fixed the issue. Now that you've pointed it out I can understand why.I modified your suggestion to the format below, much more elegant than what I had originally.ADC24 = (ADC_MSB) << 16) | (ADC_MID << 8 ) | ADC_LSB;ThanksQuote from: rstofer on December 29, 2016, 04:45:09 pmThanks for the reply rstofer - that's quite a bit out of my depth! I'm a novice at C and I'm just hacking my way through!Thanks for all the help guys.

author: rstofer
author_rank: Super Contributor
timestamp: December 30, 2016, 04:58:37 am
content: Quote from: DTJ on December 30, 2016, 02:10:40 amI like to visualize the union by drawing a 4 byte wide uint32_t rectangle over the top of 4 uint8_t blocks set out as contiguout blocks. The 32-bit value exactly overlays the 4 8-bit values. When you grab the incoming bytes, you have to store them somewhere so it might as well be in the struct as Readings.Bytes.<low, mid or high>That way, all the data is aligned without messing around shifting or multiplying. The only trick is to keep the high byte 0 and there is no reason it will ever change. Although I set .dummy = 0, it is easier just to set the entire Value to 0. Once set, it will stay that way because you won't overflow the first 3 bytes.But I do understand! There's a lot of stuff to learn and new stuff comes up all the time. Have fun!

author: DTJ
author_rank: Super Contributor
timestamp: December 30, 2016, 11:01:55 am
content: I like to visualize the union by drawing a 4 byte wide uint32_t rectangle over the top of 4 uint8_t blocks set out as contiguout blocks. The 32-bit value exactly overlays the 4 8-bit values. When you grab the incoming bytes, you have to store them somewhere so it might as well be in the struct as Readings.Bytes.<low, mid or high>That way, all the data is aligned without messing around shifting or multiplying. The only trick is to keep the high byte 0 and there is no reason it will ever change. Although I set .dummy = 0, it is easier just to set the entire Value to 0. Once set, it will stay that way because you won't overflow the first 3 bytes.But I do understand! There's a lot of stuff to learn and new stuff comes up all the time. Have fun!That's really very neat. I did a quick google and I think I can at see how valuable and useful this might be. Thanks I'm going to knock up a quick test program and try it out to get a feel for it.................. and there's always ONE MORE question;Something about this TSYS01 sensor seems odd.After its made a measurement you read a 24 bit result called ADC24 from its ADC.Its read as 3 bytes, high, mid and low.To use this result, the first thing you do is divide it by 256 to get a value called ADC16.I've re read the data sheet a couple of times and I don't understand why they bother with reading the low byte as its blown away with the 'divide by 256'.The data sheet is here, the calculation part is on page 8 of the pdf :http://www.te.com/commerce/DocumentDelivery/DDEController?Action=srchrtrv&DocNm=TSYS01&DocType=Data+Sheet&DocLang=English&DocFormat=pdf&PartCntxt=G-NICO-018