I'm writing a code in C language for my NIOS II processor.
I'm using Ecplipse that making me crazy!
It stuck a lot!!
This part of code should read register using SPI, change the data, write it back and then read it again for a validation.
So the sequence should be SPIread->SPIwrite->SPIread.
When I run it i get SPIread->SPIread->SPIwrite.
The code example:
alt_u32 SpiRead(alt_u8 spiNbytes, alt_u8 spiReg)
{
IOWR_32DIRECT(NRF24_SPI_BASE, 0, 0x1f & spiReg); //set register
IOWR_8DIRECT(NRF24_SPI_BASE, 8, 0x07 & spiNbytes); //Start SPI read
return IORD_32DIRECT(NRF24_SPI_BASE, 12); //return the data
}
void SpiWrite(alt_u32 data,alt_u8 spiNbytes,alt_u8 spiReg)
{
spiReg = 0x1F & spiReg;
spiReg = 0x20 | spiReg;
data = data<<8;
IOWR_32DIRECT(NRF24_SPI_BASE, 0, data | spiReg); //set register
IOWR_8DIRECT(NRF24_SPI_BASE, 8, 0x07 & spiNbytes); //begin write SPI
}
int main(void)
{
alt_u32 dat = 0;
dat = SpiRead(1, 0x06); //read spi
dat = ((dat>>8) & 0x000000f8) | 0x00000005; //change data
SpiWrite(dat, 1, 0x06); //write data back
dat = SpiRead(1, 0x06); //read data to validate
while (1) { } //stay here forever
return 0;
}
If I remove the while loop I get 4 times SPIread and after that 2 times SPIwrite. Each little change may change everything...
My program is 6Kbytes now and I have 18Kbytes dedicated memory (OnChipMemory).
What is wrong, please help!
Dmitry.
Related
I have a PIC24FJ256GA702 communicating with a AD5724RBREZ quad DAC with a SPI link.
The DAC works fine, writing is no problem, but I am stuck on reading back the control register from it.
I get the correct waveform on the PIC pin that I am expecting, and the read routine runs, but it always returns zeros. The waveform is clearly not zero- the waveform on the scope is correct for the 4 channels and the internal reference being enabled.
Scope view of waveforms - blue = clock, yellow = data input from DAC at PIC pin
(The excessive ringing on the scope image is probably caused by a long distance ground connection- in practice these chips are about 25mm apart.)
I thought that the input pin was configured as an analogue, but it was correctly a digital input.
I connected it to a counter based on Timer1, and that counter does count if I try to read the DAC. This suggests that the PPS is working, the pin is not bust, and the the input signal is clean enough to use.
I think it may be a problem with the code or the decode timing of the SPI module, but as shown in the image the data is stable during the clock cycle so I cannot see what is wrong.
I have searched the forums, and it seems most with this problem trace it to analogue functions being enabled but that is not the case here.
Would anyone like to suggest something else to try, or post some working SPI read code if my code is not looking correct?
The code follows-
void AOUT_init(void)
{
//assume PPS is unlocked (it is on reset) - see note below
//setup SPI1 itself
SPI1CON1L = 0x0160; //TX work read 0
//SPI1CON1L = 0x0060; //TX not work
//SPI1CON1L = 0x0120; //TX work, read 0
//SPI1CON1L = 0x0020; //not tried
SPI1CON1H = 0x3000;
SPI1CON2L = 0x0017; //word length (17 hex = 24 bits)
//BRG
SPI1BRGL = 0x0000; //default = no divisor
//PPS - assume unlocked at this point
ANSBbits.ANSB13 = 0;
TRISBbits.TRISB13 = TRIS_INPUT;
//##########################################################################
RPINR20bits.SDI1R = 13; //set SDI1 data input to PIC to RB13
//##########################################################################
TRISBbits.TRISB15 = TRIS_OUTPUT;
RPOR7bits.RP15R = 8; //RB15 to SDI1 clock out from PIC
TRISBbits.TRISB14 = TRIS_OUTPUT;
RPOR7bits.RP14R = 7; //RB14 to SDI1 data out from PIC
//AD5724R has additional lines - not all used in practice
//setup and set initial level here
//AOUT-/LDAC
TRISBbits.TRISB6 = TRIS_OUTPUT;
LATBbits.LATB6 = 1;
//AOUT-/SYNC
TRISBbits.TRISB7 = TRIS_OUTPUT;
LATBbits.LATB7 = 1;
//AOUT-/CLR
TRISBbits.TRISB12 = TRIS_OUTPUT;
LATBbits.LATB12 = 1;
//turn SPI on
SPI1CON1Lbits.SPIEN = 1;
SPI1CON1Lbits.MSTEN = 1; //included in definition above
//now setup the AD chip
//output range set
AOUT_TX(0x0C00,0x0100); //all channels to 10V
//control
AOUT_TX(0x1900,0x0500);
//power control - enable DACs
AOUT_TX(0x1000,0x1F00);
}
The comms routine below is included for completeness- it just controls the other DAC lines. The /SYNC line is doing a chip select function.
static void AOUT_Comms(bool bSync, bool bLDAC, bool bClr)
{
//AOUT-/LDAC
LATBbits.LATB6 = bLDAC;
//AOUT-/SYNC
LATBbits.LATB7 = bSync;
//AOUT-/CLR
LATBbits.LATB12 = bClr;
}
This is write routine which works fine.
void AOUT_TX(uint16_t dataH, uint16_t dataL)
{
//AOUT uses 24 bit data
//this routine handles /SYNC line
//relies on AD chip having much faster response than chip cycle time
//AD chip limits at about 38MHz so much quicker than PIC.
AOUT_Comms(0,1,1);
SPI1BUFL = dataL;
SPI1BUFH = dataH;
while(SPI1STATLbits.SPIBUSY) //wait until sent
{
;
}
AOUT_Comms(1,1,1);
//
}
This is the read routine, which uses the routines above.
void AOUT_read_control(uint16_t ReadH, uint16_t ReadL)
{
uint16_t temp;
//to read, transmit the register to be read, then transmit a dummy command "NOP" to clock the data out.
//send register
AOUT_TX(0x9000,0x0000);
//read out- this is similar to write but reads the received buffer at the end.
//clear buffer
while (SPI1STATLbits.SPIRBF)
{
temp = SPI1BUFL;
temp = SPI1BUFH;
}
AOUT_Comms(0,1,1);
SPI1BUFL = 0;
SPI1BUFH = 0x1800; //nop operation via control register
while(SPI1STATLbits.SPIBUSY) //wait until sent
{
;
}
while (!SPI1STATLbits.SPIRBF)
{
; //hold until something received
}
ReadH = SPI1BUFH;
ReadL = SPI1BUFL; //these read zero
AOUT_Comms(1,1,1);
//
//dummy so can check counter also connected to same pin
temp = TMR1;
temp = TMR1;
}
Update-
I checked the SPI read decode by sending the received words directly to a display as suggested by the comments. I get 0000 for both words.
Also as suggested I connected a logic analyser at the PIC pins, and it decodes both read and write correctly.Logic Analyser view- Ch1/Blue/MOSI writing 0x180000, Ch2/Green/MISP reading 0x00001F, both correct
I'm trying to communicate with a MPU-9250 (Accelerometer and a lot of other stuff) from my PIC16F1789. My functions look like the following:
void i2cWait(){
while((SSP1STAT & 0x04) || (SSP1CON2 & 0x1F));
}
unsigned char i2cReadCycle(unsigned char regAddr){
unsigned char val;
// Start
i2cWait();
SEN = 1;
// Address + Write Bit
i2cWait();
SSPBUF = (slvAdd<<1 | (0b1<<0)); // address slave + write
i2cWait();
//Register address
SSP1BUF = regAddr; // address register + read
i2cWait();
//Start
SEN = 1;
i2cWait();
// Address + Read Bit
SSP1BUF = ((slvAdd<<1) | (0b0<<0)); //Address + read (0)
i2cWait();
// Daten Auslesen
RCEN = 1;
i2cWait();
val = SSP1BUF;
i2cWait();
ACKDT = 1; // set acknowledge Bit (1 = Not Acknowledge, 0 = Acknowledge)
ACKEN = 1; // send acknowledge Bit
// Stop
i2cWait();
PEN = 1;
return val;
}
I've worked with the "Single-Byte Read Sequence" on page 35 of the 9250 Datasheet:
https://cdn.sparkfun.com/assets/learn_tutorials/5/5/0/MPU9250REV1.0.pdf
And the PIC Datasheet:
http://ww1.microchip.com/downloads/en/DeviceDoc/40001675C.pdf
When debugging, the program gets stuck in the i2cWait() after I send the NACK Bit.
It gets stuck because the ACKEN Bit (Bit 4) of the SSPCON2 register (Page 341 of PIC datasheet) doesn't get cleared, so the program gets stuck in the while().
Why doesnt the Bit get cleared by hardware?
It looks like you are using 0b1<<0 for "writing" together with the slave address. However, according to the datasheet of MPU9250REV1.0, it should be 0. Check section 7.4 on page 35.
Although it might be counter-intuitive to set '0' for writes, it makes sense if you think of the "General Call" that ist initiated with the slave address 0b0000000 and the "write" bit 0.
This also means to change the "read" bit in your code to 1.
In the current implementation, mixing up read and write bits, leads to the issue being stuck waiting after setting the NACK.
its my first question here and I hope I'm not stepping on anyone's tows :-)
Currently I'm working on a modular battery management system for my electric scooter, based on a ATtiny85 as a master and multiple ATtiny85 as slaves. Each slave is monitoring one cell (or a array of multiple cells in parallel) and also using this cell to power itself up. For safety it reads the voltage of the battery and reads a temperature sensor of this cell. It sends these two information via a isolated I2C bus to the master, which will analyze it and will send eventual a response if this cell should activate balancing or not.
Therefor I'm using the digispark bootloader and flashing the software using USB. The software itself I'm programming using Arduino IDE.
So far I managed to establish a good connection between the slaves using TinyWireS and the Master using TinyWireM. I'm successfully sending data from multiple slaves to the master and vise versa.
But when I remove one or more slaves from the bus somehow the reading routine will fill the not received data with the previous received data and will not write a empty line or zero.
Here is the relevant routine, where I poll through the slaves addresses and individually ask each slave for a payload of 4 bytes. The slave will separate the measured voltage and the temperature in two bytes each and will send the resulting 4 bytes separately over the I2C bus.
The master will receive the 4 separate bytes and combine them again to voltage and temperature and write them in an array at the position matching its cell-number.
for (int cell_num = 1; cell_num <= CELL_COUNT; cell_num++) //poll data from all cells, start with cell 1 to max
{
int slave_add = 0x3F+cell_num; //connect to cell, adress offset 0x3F, first cell is 0x40
v_data[cell_num] = 0;
t_data[cell_num] = 0;
TinyWireM.requestFrom(slave_add, 4); //request from selected cell 4 bytes
while(TinyWireM.available())
{
v_low = TinyWireM.receive(); //read first byte as low byte of voltage
v_high = TinyWireM.receive(); //read second byte as hight byte of voltage
t_low = TinyWireM.receive(); //read third byte as low byte of temp
t_high = TinyWireM.receive(); //read fourth byte as hight byte of temp
v_data[cell_num] = 0;
t_data[cell_num] = 0;
v_data[cell_num] = (v_high<<8) | v_low; //shift high byte of voltage and combine with low byte
t_data[cell_num] = (t_high<<8) | t_low; //shift high byte of temp and combine with low Byte
v_high = 0;
v_low = 0;
t_high = 0;
t_low = 0;
}
A little example to demonstrate the error:
On the bus are supposed to be 14 slaves (CELL_COUNT = 14) one or more slaves (lets say number 5 & 6) is having an error and is not transmitting the 4 bytes.
So the master is displaying all the data of the slave on a little OLED Display.
Instead of showing a 0 at the line number 5 and 6 the master displays the same value of number 4.
The first half of the display will look something like this:
4125
4035
4156
4137
4137
4137
4089
In addition you can find the full code below:
//1MHz Speed!!!!
#include <TinyWireM.h>
#include "SSD1306_minimal.h"
#define CELL_COUNT 14 //from 1 to cell amout, number of cells on bus
SSD1306_Mini oled;
uint16_t v_data[CELL_COUNT];
uint16_t v_data_max = 0;
uint16_t t_data[CELL_COUNT];
uint16_t t_data_max = 0;
char cast1[8] = {};
char cast2[8] = {};
uint8_t v_low;
uint8_t v_high;
uint8_t t_low;
uint8_t t_high;
void setup()
{
pinMode(1, OUTPUT); //led pinset digispark
digitalWrite(1, LOW); //disable led
oled.init(0x3c); //init oled adress 0x3c
oled.clear(); //clear oled display
oled.startScreen(); //start oled routine to write
TinyWireM.begin(); //start i2c lib for com
}
void loop()
{
v_data_max = 0;
t_data_max = 0;
//read received data
for (int cell_num = 1; cell_num <= CELL_COUNT; cell_num++) //poll data from all cells, start with cell 1 to max
{
int slave_add = 0x3F+cell_num; //connect to cell, adress offset 0x3F, first cell is 0x40
v_data[cell_num] = 0;
t_data[cell_num] = 0;
TinyWireM.requestFrom(slave_add, 4); //request from selected cell 4 bytes
while(TinyWireM.available())
{
v_low = TinyWireM.receive(); //read first byte as low byte of voltage
v_high = TinyWireM.receive(); //read second byte as hight byte of voltage
t_low = TinyWireM.receive(); //read third byte as low byte of temp
t_high = TinyWireM.receive(); //read fourth byte as hight byte of temp
v_data[cell_num] = 0;
t_data[cell_num] = 0;
v_data[cell_num] = (v_high<<8) | v_low; //shift high byte of voltage and combine with low byte
t_data[cell_num] = (t_high<<8) | t_low; //shift high byte of temp and combine with low Byte
v_high = 0;
v_low = 0;
t_high = 0;
t_low = 0;
}
}
for (int cell_num = 1; cell_num <= CELL_COUNT; cell_num++) //pring voltage and temp data to oled
{
oled.startScreen(); //start oled routine to write
if (cell_num<=7) //check if first half of cells (cell number = 14) or second half
{
oled.cursorTo(0,cell_num-1); //jump to right line for cell
}
else
{
oled.cursorTo(66,cell_num-8); //jump to right line for cell
}
oled.printString(itoa(v_data[cell_num], cast1, 10)); //change data from int to str and print on oled
oled.startScreen();
if (cell_num<=7)
{
oled.cursorTo(30,cell_num-1); //jump to right line for cell
}
else
{
oled.cursorTo(96,cell_num-8); //jump to right line for cell
}
oled.printString(itoa(t_data[cell_num], cast2, 10)); //change data from int to str and print on oled
}
delay(5000);
oled.cursorTo(0,0);
oled.clear();
}
I hope someone can point me in the right direction to fix this problem...
I narrowed it down a little more...
In the following code the function TinyWireM is writing the data from the loop befor to the variables v_high, v_low, T_high and t_low if no new data is received. But the weired thing is, that they are not all equal, they all have the exact same value from the loop befor. So v_high (old) = v_high (new) and so on.
How can this be if they are all set using the same function, which should run 4 times, so once for each variable?
Here is the relevant part:
v_low = TinyWireM.receive(); //read first byte as low byte of voltage
v_high = TinyWireM.receive(); //read second byte as hight byte of voltage
t_low = TinyWireM.receive(); //read third byte as low byte of temp
t_high = TinyWireM.receive(); //read fourth byte as hight byte of temp
I found a little inconvenience in the lib TinyWireM.
If you receive some data, the lib will store it in the buffer. If you read the buffer it will show the content. If there is no new data received the buffer will stay as is and you will read the exact same Data if you read the buffer again.
Therefor I changed the TinyWireM.ccp so that the buffer will be deleted if read once.
Now it will show a 0 if you read the buffer again without new received data.
I'm having trouble trying to connect my light sensor (MAX44009) with I2C. I will explain what I've done and I hope someone can help me.
I am connecting this connection card on HMI port of my XMC4400 with 80 ports.
I've connected the sensor according to this table.
SCA - pin37
SCL - pin38
GND - pin 80
3.3 - pin 3.3V of XMC4400
Then I've tried to adapt the I2C Master example (available on DAVE tutorials) for my light sensor. I've created an I2C master app with the following settings:
My main.c is this:
Code:
#include <DAVE.h>
#define IO_EXPANDER_ADDRESS (0x4A)
uint8_t tx_buffer[4] = {0x00,0x01,0x02,0x03};
volatile uint8_t tx_completion_0 = 0;
volatile uint8_t rx_completion_0 = 0;
/* Transmit callback handling */
void tx_callback_0(void)
{
tx_completion_0 = 1;
}
/* Receive callback handling */
void rx_callback_0(void)
{
rx_completion_0 = 1;
}
/* Delay */
void delay(uint32_t counter)
{
volatile uint32_t cnt = counter;
while(--cnt);
}
/*
* For this demo the HMI satellite board for the XMC45 CPU board is required.
* It communicates with the IO expander (U360: PCA9502) found in the mentioned satellite board.
* The demo implements a binary counter using the LEDs attached to the IO expander.
*
*/
int main(void)
{
DAVE_Init();
uint8_t received_data;
uint8_t counter = 0;
/* Write data to reset the LEDs through the IO EXPANDER: DIR and 0xFF */
I2C_MASTER_Transmit(&I2C_MASTER_0,true,IO_EXPANDER_ADDRESS,&tx_buffer[1],2,false);
while(tx_completion_0 == 0);
tx_completion_0 = 0;
while(counter < 255)
{
tx_buffer[3] = ~counter;
counter++;
/* Write data to set the STATE of the IO EXPANDER */
I2C_MASTER_Transmit(&I2C_MASTER_0,true,IO_EXPANDER_ADDRESS,&tx_buffer[3],2,false);
while(tx_completion_0 == 0){
tx_callback_0();
}
tx_completion_0 = 0;
/* Receive the data from the IO EXPANDER */
I2C_MASTER_Receive(&I2C_MASTER_0,true,IO_EXPANDER_ADDRESS,&received_data,2,true,true);
printf("%d", received_data);
while(rx_completion_0 == 0){
rx_callback_0();
}
rx_completion_0 = 0;
/* Check if the received data is correct*/
if(tx_buffer[3] != received_data)
{
// while(1);
}
/* Delay to make visible the change */
delay(0xfffff);
}
while(1);
return 0;
}
I think my callback functions are not working, since it stops every time I execute one I2C function. on this case is on line 108. Plus, sometimes it gives me an error/warning:
No source available on 0x00.
import smbus
import time
# Get I2C bus
bus = smbus.SMBus(1)
# MAX44009 address, 0x4A(74)
# Select configuration register, 0x02(02)
# 0x40(64) Continuous mode, Integration time = 800 ms
bus.write_byte_data(0x4A, 0x02, 0x40)
time.sleep(0.5)
# MAX44009 address, 0x4A(74)
# Read data back from 0x03(03), 2 bytes
# luminance MSB, luminance LSB
data = bus.read_i2c_block_data(0x4A, 0x03, 2)
# Convert the data to lux
exponent = (data[0] & 0xF0) >> 4
mantissa = ((data[0] & 0x0F) << 4) | (data[1] & 0x0F)
luminance = ((2 ** exponent) * mantissa) * 0.045
# Output data to screen
print "Ambient Light luminance : %.2f lux" %luminance
I have this Python code that works fine on my sensor light when I'm using raspberry pi, I've tried to do the same thing on XMC but without success. I hope you can help me.
First of all I would recommend to build a if statement arround your code
like
DAVE_STATUS_t init_status;
init_status = DAVE_Init();
if(init_status == DAVE_STATUS_SUCCESS)
{
... code
}
With this you can check if the DAVE APP is initialized correct. If it is the case you can look further into the callback issue. If not it's a problem with the configuration.
You can find further code exampels by rightlciking on the I2C Master field in teh APP Dependency window -> APP Help. There are examples for the methods provided by the APP.
How do I change slave address of mlx90614 with bcm2835 library? I've tried following code...
int main()
{
// Buffer, where I store data which I'll send
unsigned char buf[6];
// bcm2835 i2c module intialisation code
bcm2835_init();
bcm2835_i2c_begin();
bcm2835_i2c_set_baudrate(25000);
bcm2835_i2c_setSlaveAddress(0x00);
// For debug purposes, I read what reason codes operations give.
bcm2835I2CReasonCodes why;
bcm2835_i2c_begin();
// function which reads and prints what value eeprom address 0x0e has.
// See below the main.
printf("Initial check\n");
check(); // this time it prints a factory default value 0x5a.
// To access eeprom, the command must start with 0x2X, where x determines the
// address, resulting 0x2e.
buf[0] = 0x2e;
// According to datasheet, I first have to clear the address before
// real write operation.
buf[1] = 0x00;
buf[2] = 0x00;
why = bcm2835_i2c_write(buf,3);
reason(why); // resolves and prints the reason code. This time it prints OK
// according to datasheet, eeprom needs 5ms to make a write operation,
// but I give it 2 seconds.
sleep(2);
// Then I check did the value in eeprom 0x0e change. IT DOESN'T!
printf("Check after clear\n");
check();
// Then I try to write a new address to the eeprom but since the clearing
// the register didn't work, this is very unlikely to work either.
buf[0] = 0x2e;
buf[1] = 0x4b;
buf[2] = 0x00;
why = bcm2835_i2c_write(buf,3);
reason(why);
sleep(2);
// The datasheet says that I have to reset the power supply and after that
// the device should respond to the new slave address.
// I do that by pluging off the jumper wires and reconnecting them
// after the program has finnished.
bcm2835_i2c_end();
return 0;
}
// The function I use to determine what the reason code was.
void reason(bcm2835I2CReasonCodes why)
{
printf("Reason is: ");
if(why == BCM2835_I2C_REASON_OK)
{
printf("OK");
}else if(why == BCM2835_I2C_REASON_ERROR_NACK){
printf("NACK");
}else if(why == BCM2835_I2C_REASON_ERROR_CLKT){
printf("Clock stretch");
}else if(why == BCM2835_I2C_REASON_ERROR_DATA ){
printf("Data error");
}else{
printf("Dunno lol");
}
printf("\n");
return;
}
// Here I read eeprom 0x2e.
void check()
{
unsigned char buf[6];
unsigned char reg = 0x2e;
bcm2835I2CReasonCodes why;
// better safe than sorry with the buffer :)
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
why = bcm2835_i2c_write (®, 1);
reason(why);
why = bcm2835_i2c_read_register_rs(®,&buf[0],3);
reason(why);
printf("Buffer values are: %x ; %x ; %x \n", buf[0], buf[1], buf[2]);
}
The output of the program is following:
Initial check
Reason is: OK
Reason is: OK
Buffer values are: 5a ; be ; dc
Reason is: OK
Check after clear
Reason is: OK
Reason is: OK
Buffer values are: 5a ; be ; dc
Reason is: OK
If I run i2cdetect -y 1 after that, the device doesn't appear in the table, but it responds to programs calling it from either 0x00 or 0x5a. After I've used such a program, the i2cdetect detects the device normally from address 0x5a.
So I guess the real question is, why I can't clear and rewrite the eeprom 0x0e?
The description of Mlx90614 SMBus communication can be found below. The most relevat page is IMO the page 19 which actually gives the pseudocode example of what I'm trying to do.
http://www.melexis.com/Assets/SMBus-communication-with-MLX90614-5207.aspx
Here's the datasheet for mlx90614
http://www.melexis.com/Assets/IR-sensor-thermometer-MLX90614-Datasheet-5152.aspx
And here's the documentation for bcm2835
www.airspayce.com/mikem/bcm2835/group__i2c.html
You have to add an Error-Byte. Take a look at this website for an explanation: https://sf264.wordpress.com/2011/03/10/howto-mlx90614-und-pwm/
Calculating CRC-8 for 00002e4b00 gives 0xa3.
I used for calculating CRC-8 this website: http://smbus.org/faq/crc8Applet.htm
I haven't tested this, but I think this should work:
buf[0] = 0x2e;
buf[1] = 0x4b;
buf[2] = 0x00;
buf[3] = 0xa3;
why = bcm2835_i2c_write(buf,4);
Struggled with the exact same problem with my mlx90614s. Here is the write routine I used to solve it (Please note that the bcm2835-library was properly initalized before the call to the routine).
First I called the write routine with "correct" Slaveaddress, command=0x2E (EEPROMAccess | SMBusAddressReg) and data=0x0000 (for erase). The "correct" slave address can be 0x00 or the factory default 0x5a (or whatever is the chip's true address).
After erasing I used the same write routine but now with data=0x005b, to change from the factory default 0x5a to 0x5b, did a Power Off Reset (POR) and the device showed up with its new address (0x5b) using i2cdetect.
uint8_t memWriteI2C16(uint8_t SlaveAddress, uint8_t command, uint16_t data)
{
unsigned char arr[5];
uint8_t status;
//Prepare for CRC8 calc
arr[0] = SlaveAddress<<1; //NB! 7 bit address + a 0 write bit.
arr[1] = command; //Command byte in packet
arr[2] = *((uint8_t *)(&data)); //Extract data low byte
arr[3] = *((uint8_t *)(&data)+1);//Extract data high byte
arr[4] = crc8(&arr[0],4)&0xFF; //Calculate PEC by CRC8
bcm2835_i2c_setSlaveAddress(SlaveAddress);//Transmit address byte to I2C/SMBus
status = bcm2835_i2c_write (&arr[1], 4); //Transmit Command,DataL, DataH and PEC
bcm2835_delay(5); //Delay at least 5ms
return (status);
}
The CRC8 routine I used was:
// Return CRC-8 of the data, using x^8 + x^2 + x + 1 polynomial.
// A table-based algorithm would be faster, but for only a few bytes
// it isn't worth the code size.
// Ref: https://chromium.googlesource.com/chromiumos/platform/vboot_reference/+/master/firmware/lib/crc8.c
uint8_t crc8(const void *vptr, int len)
{
const uint8_t *data = vptr;
unsigned crc = 0;
int i, j;
for (j = len; j; j--, data++) {
crc ^= (*data << 8);
for(i = 8; i; i--) {
if (crc & 0x8000)
crc ^= (0x1070 << 3);
crc <<= 1;
}
}
return (uint8_t)(crc >> 8);
}
In addition: according to the data sheet for the the mlx90614, its default factory state after power up is PWM output. When hooking an mlx90614 in the factory PWM state to the I2C bus on the RPi2, the i2cdetect reports hundreds of I2C devices on the bus. Trying to access the mlx90614 by using the bcm2835-library fails. What is required is to force the mlx90614 out of its PWM-state by holding the SCL low for at least 2ms. Here is what I did:
uint8_t mlx90614SMBusInit()
{
//Hold SCL low for at leat 2ms in order to force the mlx90614 into SMBus-mode
//Ref Melix app note regarding SMBus comm chapter 6.1 and table 5.
uint8_t SCL1 = 3; //BCM2835 pin no 3 -RPi2 and RevB+. Use if i2cdetect -y 1
uint8_t SCL0 = 1; //BCM2835 pin no 1 -RPi2 and RevB+. Use if i2cdetect -y 0
uint8_t SCL;
SCL = SCL1;
bcm2835_gpio_fsel(SCL, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_write(SCL ,LOW);
bcm2835_delay( 3); //Delay >2 ms
bcm2835_gpio_write(SCL ,HIGH);
return (1);
}
However, this only hold until next power up. Hence it is required to write to the pwmctrl-register in mlx90614's eeprom (disable pwm output and force SDA to OpenDrain). I used the write routine as previously described with command=0x22 (i.e. EEPROMAccess | PWMCTRLAddressRegister) and after erasing the pwmctrl-register content, I wrote 0x0200 to it (the frst 3 nibbles was 020 in my devices...). Power Off Reset (POR) and the device started in SMBus-mode (no jamming of the I2C-bus). The mlx90614 is a tricky little component...
Also if you are using I2C-tools package from any linux distribution (in my case I'm using debian distro) you could change the address with the i2cset command (https://manpages.debian.org/buster/i2c-tools/i2cset.8.en.html), here is an example:
#Find your I2C bus in your linux with the command i2cdetect -l
#(in my case is the i2c-1)
i2cdetect -l
i2c-1 i2c bcm2835 I2C adapter I2C adapter
#Write the word 0x0000 to the address 0x2E and append the PEC check byte.
i2cset -y 1 0x5a 0x2E 0x0000 wp
#Write the new address as a word, to the address 0x2E and append the PEC
#check byte. In my case the new address is 0x005c
i2cset -y 1 0x5a 0x2E 0x005c wp
#Perform a power cycle of the Mlx90614 device
#Check the new address with the command i2cdetect -y 1
i2cdetect -y 1