I'm trying to write piece of code, to send data via I2C on my Zynq7020 device. There are 11 register asociated with I2C and I'm prety sure, that I have set this properly. I also double check registers asociated with CPU_1X clock enable a and I2C reset, but they are set properly by default. When I set all data by the code bellow, status register is 0x00000040 and interupt status register is 0x00000000 all the time. I think, there must be some enable register, but I can't find anything in datasheet. Thanks for all replies.
//I2C registers
#define XIICPS_CR 0xE0004000 //Controll register
#define XIICPS_SR 0xE0004004 //Status register
#define XIICPS_ADDR 0xE0004008 //IIC Address register
#define XIICPS_DATA 0xE000400C //IIC data register
#define XIICPS_ISR 0xE0004010 //IIC interrupt status register
#define XIICPS_TRANS_SIZE 0xE0004014 //Transfer Size Register
#define XIICPS_SLV_PAUSE 0xE0004018 //Slave Monitor Pause Register
#define XIICPS_TIME_OUT 0xE000401C //Time out register
#define XIICPS_IMR 0xE0004020 //Interrupt mask register
#define XIICPS_IER 0xE0004024 //Interrupt Enable Register
#define XIICPS_IDR 0xE0004028 //Interrupt Disable Register
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
int main()
{
init_platform();
initI2C();
while(1){
printf("Write start\n\r");
writeI2C(0x6C, 0x00); //I have device with 0x6C adress connected;
printf("Write done\n\r");
}
cleanup_platform();
return 0;
}
void initI2C(){
*((unsigned int*)XIICPS_CR) = 0x0000905E; //((15:14)CLK_A = 2, (13:8)CLK_B = 16, (6)CLR_FIFO=1, (5)MONITOR_MODE=0, (4)HOLD=1, (3)1, (2)1, (1)MASTER=1, (0)rv = 0)
*((unsigned int*)XIICPS_TIME_OUT) = 0x000000FF; //set timeout to 255
*((unsigned int*)XIICPS_IER) = 0x00000000; //no interupts (I'm pretty sure, that this line can't do anything, but it's in datasheet...)
*((unsigned int*)XIICPS_IDR) = 0x000002FF; //no interupts
return;
}
void writeI2C(unsigned addr, unsigned data){
*((unsigned int*)XIICPS_CR) = (*((unsigned int*)XIICPS_CR)|0x00000040)&0xFFFFFFFE; //CLR_FIFO and WRITE_MODE
*((unsigned int*)XIICPS_DATA) = data; //When I debug this with JTAG, I can see XIICPS_TRANS_SIZE increment by this lines. So data goes into FIFO properly, right?
*((unsigned int*)XIICPS_DATA) = data;
*((unsigned int*)XIICPS_DATA) = data;
*((unsigned int*)XIICPS_DATA) = data;
*((unsigned int*)XIICPS_ADDR) = addr;
while(*((unsigned int*)XIICPS_SR) != 0){ //this loop will never exit. Data sits in FIFO forever. No errors in status or interupt status registers, everything looks fine.
print("Wait1...\n\r");
}
while(*((unsigned int*)XIICPS_ISR)&0x00000001 == 0){
print("Wait2...\n\r");
}
return;
}
First I'm noob in this stuff, but learning and really want to get this working. I bought a raspberrypi and a bno055 bosch accelerometer. It comes with a bno055.c, bno055.h and a bno055_support.c file. After getting into programming and c and studying/trying out it seems somehow I need to define how to do I2C read and write. It needs to be setup so you can define the amount of bytes read/written. Below you can find the two functions as predefined :
/* \Brief: The API is used as I2C bus write
* \Return : Status of the I2C write
* \param dev_addr : The device address of the sensor
* \param reg_addr : Address of the first register,
* will data is going to be written
* \param reg_data : It is a value hold in the array,
* will be used for write the value into the register
* \param cnt : The no of byte of data to be write
*/
s8 BNO055_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
s32 BNO055_iERROR = BNO055_INIT_VALUE;
u8 array[I2C_BUFFER_LEN];
u8 stringpos = BNO055_INIT_VALUE;
array[BNO055_INIT_VALUE] = reg_addr;
for (stringpos = BNO055_INIT_VALUE; stringpos < cnt; stringpos++)
array[stringpos + BNO055_I2C_BUS_WRITE_ARRAY_INDEX] =
*(reg_data + stringpos);
}
/*
* Please take the below APIs as your reference for
* write the data using I2C communication
* "BNO055_iERROR = I2C_WRITE_STRING(DEV_ADDR, ARRAY, CNT+1)"
* add your I2C write APIs here
* BNO055_iERROR is an return value of I2C read API
* Please select your valid return value
* In the driver BNO055_SUCCESS defined as 0
* and FAILURE defined as -1
* Note :
* This is a full duplex operation,
* The first read data is discarded, for that extra write operation
* have to be initiated. For that cnt+1 operation done
* in the I2C write string function
* For more information please refer data sheet SPI communication:
*/
return (s8)BNO055_iERROR;
}
/* \Brief: The API is used as I2C bus read
* \Return : Status of the I2C read
* \param dev_addr : The device address of the sensor
* \param reg_addr : Address of the first register,
* will data is going to be read
* \param reg_data : This data read from the sensor,
* which is hold in an array
* \param cnt : The no of byte of data to be read
*/
s8 BNO055_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
s32 BNO055_iERROR = BNO055_INIT_VALUE;
u8 array[I2C_BUFFER_LEN] = {BNO055_INIT_VALUE};
u8 stringpos = BNO055_INIT_VALUE;
array[BNO055_INIT_VALUE] = reg_addr;
/* Please take the below API as your reference
* for read the data using I2C communication
* add your I2C read API here.
* "BNO055_iERROR = I2C_WRITE_READ_STRING(DEV_ADDR,
* ARRAY, ARRAY, 1, CNT)"
* BNO055_iERROR is an return value of SPI write API
* Please select your valid return value
* In the driver BNO055_SUCCESS defined as 0
* and FAILURE defined as -1
*/
for (stringpos = BNO055_INIT_VALUE; stringpos < cnt; stringpos++)
*(reg_data + stringpos) = array[stringpos];
return (s8)BNO055_iERROR;
}
My question is there somebody out, that can coach me through this challenge?
I'm learning about, https://www.kernel.org/doc/Documentation/i2c/dev-interface, but stuck here for the moment. Thx in advance for reading / replying.
I recently wrote a library similar to what you're describing for the MMA8451 i2c accelerometer.
Essentially i2c controllers in Linux get assigned a device node (e.g. /dev/i2c-1). You'll open this device node as a file like this:
int file = open(path, O_RDWR); //path = /dev/i2c-1
Once you have your file handle you can read and write i2c registers using ioctl's. The i2c kernel module supports the I2C_RDWR ioctl which lets you interact with i2c registers.
To read a register you do something like this:
int mma8451_get_i2c_register(int file, unsigned char addr, unsigned char reg, unsigned char *val) {
unsigned char inbuf, outbuf;
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[2];
outbuf = reg;
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = sizeof(outbuf);
messages[0].buf = &outbuf;
messages[1].addr = addr;
messages[1].flags = I2C_M_RD;
messages[1].len = sizeof(inbuf);
messages[1].buf = &inbuf;
packets.msgs = messages;
packets.nmsgs = 2;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
return 0;
}
*val = inbuf;
return 1;
}
To write a register you do something like this:
int mma8451_set_i2c_register(int file, unsigned char addr, unsigned char reg, unsigned char value) {
unsigned char outbuf[2];
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = sizeof(outbuf);
messages[0].buf = outbuf;
outbuf[0] = reg;
outbuf[1] = value;
packets.msgs = messages;
packets.nmsgs = 1;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
return 0;
}
return 1;
}
Edit: The I2C_RDWR ioctl takes a i2c_rdwr_ioctl_data structure as an argument. It's described like this:
Another common data structure is struct i2c_rdwr_ioctl_data
This is the structure as used in the I2C_RDWR ioctl call
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};
(Defined in linux/i2c-dev.h)
This structure points to the array of i2c_msg to process and defines the number of i2c_msg in the array.
Usage:
If the program is to write one byte (example - the index byte), followed by
reading one byte, two struct i2c_msg data structures will be needed.
One for the write, and another for the read. These two data structures should be declared as an array of two i2c_msg data structures. They will be processed in the order they appear in the array.
The i2c_rdwr_ioctl_data structure contains a pointer to an array of i2c_msg structures. These structures contain the actual messages you want to send or receive. For for example my accelerometer in order to read a register I first needed to write the register I wanted to read to the device and then I could read it (hence why there's two i2c_msg's in my read function). If I was simply writing a register I only needed one.
You'll want to refer to the data sheet for your BNO055 to figure out exactly which registers do what.
As for your example, it looks like it comes from bno055_support.c. It looks like this is just a set of stubs you're meant to implement. It looks like it's basically a mock for a real interface. So what's important is the interface, not the actual code (so don't worry about cnt). The important bits are here:
s8 I2C_routine(void)
{
bno055.bus_write = BNO055_I2C_bus_write;
bno055.bus_read = BNO055_I2C_bus_read;
bno055.delay_msec = BNO055_delay_msek;
bno055.dev_addr = BNO055_I2C_ADDR1;
return BNO055_INIT_VALUE;
}
This sets the function pointers on your device structure to the write functions you're going to define and sets the address of your device and delay. From there you need to implement functions that match this interface:
#define BNO055_BUS_WRITE_FUNC(dev_addr, reg_addr, reg_data, wr_len)\
bus_write(dev_addr, reg_addr, reg_data, wr_len)
#define BNO055_BUS_READ_FUNC(dev_addr, reg_addr, reg_data, r_len)\
bus_read(dev_addr, reg_addr, reg_data, r_len)
The functions I gave you above should be pretty close stand-ins. Good luck!
For example
s8 BNO055_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
u8 array[I2C_BUFFER_LEN] = { BNO055_INIT_VALUE };
array[BNO055_INIT_VALUE] = reg_addr;
if (write(file, array, 1) != 1) {
return -1;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
s8 res = read(file, reg_data, cnt);
if (res > 0) return 0;
else return -1;
}
s8 BNO055_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
char buf[1 + cnt];
buf[0] = reg_addr;
memcpy(&buf[1], reg_data, cnt);
if (write(file, buf, cnt+1) != cnt+1) {
return -1;
}
return 0;
}
Here is a simple program which reads and writes I2C registers, using the linux kernel I2C driver. This is tested on a raspberry pi.
#include <stdio.h>
#include <stdint.h> // uint8_t, uint16_t, uint32_t, uint64_t
#include <linux/i2c-dev.h> // I2C_SLAVE
#include <fcntl.h> // open(), O_RDONLY
#include <unistd.h> // read(), write(), usleep()
#include <sys/ioctl.h> // ioctl()
#define I2C_BUS_NUMBER 1
#define I2C_DEVICE_NUMBER 0x1C
#define I2C_REGISTER_NUMBER 4
int open_i2c_device ( int i2c_bus_number , int i2c_device_number ) {
char filename[12] ;
snprintf ( filename , 11 , "/dev/i2c-%i", i2c_bus_number) ;
int file_descriptor = open ( filename, O_RDWR ) ;
if ( file_descriptor < 0 ) {
printf ( "failed to open %s, open() returned %i\n" , filename , file_descriptor ) ;
}
if ( ioctl ( file_descriptor , I2C_SLAVE, i2c_device_number ) < 0 ) {
printf ( "failed to find device %i on i2c bus %i\n" , i2c_device_number , i2c_bus_number ) ;
}
return ( file_descriptor ) ;
}
void i2c_write_register ( int file_descriptor , uint8_t register_to_write_to , uint8_t data_to_write_to_register ) {
uint8_t message[2] ;
message[0] = register_to_write_to ;
message[1] = data_to_write_to_register ;
int i = write ( file_descriptor , message , 2 ) ;
if ( i != 2 ) {
printf ( "error: i2c write returned %i instead of 2\n" , i ) ;
}
}
uint8_t i2c_read_register ( int file_descriptor , uint8_t register_to_read_from ) {
uint8_t message[1] ;
message[0] = register_to_read_from ;
int i = write ( file_descriptor , message , 1 ) ;
if ( i != 1 ) {
printf ( "error: i2c write returned %i instead of 1\n" , i ) ;
}
i = read ( file_descriptor , message , 1 ) ;
if ( i != 1 ) {
printf ( "error: i2c read returned %i instead of 1\n" , i ) ;
}
return ( message[0] ) ;
}
int main () {
int file_descriptor_for_I2C_device = open_i2c_device ( I2C_BUS_NUMBER , I2C_DEVICE_NUMBER ) ;
i2c_write_register ( file_descriptor_for_I2C_device , I2C_REGISTER_NUMBER , 0xA1 ) ;
uint8_t i = i2c_read_register ( file_descriptor_for_I2C_device , I2C_REGISTER_NUMBER ) ;
printf ( "register %02hhx is %02hhx\n" , I2C_REGISTER_NUMBER, i) ;
}
Usually ordinary users are not allowed to access I2C, so you probably need
to run I2C programs with sudo.
You might need to load the linux kernel I2C driver module, and change some
parameters like I2C speed.
Documentation for the I2C linux kernel driver:
https://www.kernel.org/doc/Documentation/i2c/dev-interface
For accessing I2C, you need three numbers: the number of the I2C bus, the
number of the I2C device, and the number of the I2C register. All three of
these numbers are sometimes called address. If someone or some
documentation refers to I2C address, ask which number they are talking
about.
If you do not know what I2C bus number to use, run the command sudo i2cdetect -l
If you do not know what I2C device number to use, run the command sudo i2cdetect -y B,
where B is the I2c bus number.
If you do not know what I2C register number to use, run the command sudo i2cdump -y B D
where B is the I2C bus number and D is the I2C device number.
The I2C protocol includes some special combinations of clock and data pulses
which start a transmission, and which acknowledge a transmission. If a
program is implementing I2C by switching gpio pins between LOW and HIGH, the
program needs to do start and acknowledge. But if a program is using the
I2C linux kernel driver, then the I2C linux kernel driver does start and
acknowledge, so the program should NOT do start and acknowledge.
The linux kernel driver creates a /dev/i2c* for each I2C bus. Your program
should open() the /dev/i2c*, then use ioctl() to associate a single I2C
device number with the I2C bus. So what if you have more than one I2C
device number for an I2C bus? The documentation does not say. Maybe
your program should open() the /dev/i2c* multiple times, then associate
a different I2C device number with each file descriptor. Maybe your
program should change which I2C device number is associated with
the I2C bus with every read or write.
The I2C protocol is that to do a read, first start doing a write, select
which register to write, abort the write without writing any data, then
read.
The I2C protocol is to begin every transmission with a device number. If a
program is implementing I2C by switching gpio pins between LOW and HIGH, the
program needs to begin every transmission with the I2C device number. But
if a program is using the I2C linux kernel driver, then the I2C linux kernel
driver adds the I2C device number to every transmission (which is why the
program must use ioctl() to provide the correct I2C device number to the
I2C linux kernel driver), so the program should NOT begin a transmission
with the I2C device number.
Notice that the I2C protocol and the I2C linux kernel driver protocol are
different. Some details about the I2C protocol do not apply to the
I2C linux kernel driver protocol, and vice versa.
The I2C bus probably runs at a slower speed than the cpu. If a program
writes some data to the I2C controller, it probably takes some time for the
I2C controller to write the data to the I2C bus, and more time to read data
back. So when reading an I2C register, I thought maybe there should be a
delay or timeout. But these functions work without a delay or timeout, so I
guess the linux kernel I2C driver takes care of that. Maybe the linux
kernel driver knows how long I2C access should take, and delays read() for
that amount of time. On a raspberry pi with a 400 khz I2C bus, I found
about 5 milliseconds to read 1 register. I think the code executes faster
than that, so it is probably waiting for the I2C data. If you want your
program to do something else while waiting for I2C data, try multithreading.
Or maybe you could code a loop which checks multiple sensors, and the first
pass through the loop the code sends the request, the second pass through
the loop the code reads the response, the third pass through the loop the
code sends another request, etc.
I'm writing an i386 ELF kernel from scratch. I need to be able to write to the serial port COM1.
I have wrote two functions, serial_init() is called every time I call printk(char* str) which calls for each iteration for each character serial_putc(char c).
#define SERIAL_COM1 (0x03f8)
void serial_putc(char c)
{
char* serial = (char*)SERIAL_COM1;
while ((serial[5] & 0x20) == 0);
serial[0] = c;
}
void serial_init()
{
char* serial = (char*)SERIAL_COM1;
serial[1] = 0x00;
serial[3] = 0x80;
serial[0] = 0x03;
serial[1] = 0x00;
serial[3] = 0x03;
serial[2] = 0xc7;
serial[4] = 0x0b;
}
The line protocol is:
38400 bauds
8 bits per word
No parity check
1 stop bit
I'm using qemu-system-i386 -serial stdio -kernel ./kernel to test my kernel but it doesn't print anything on the output on the serial port.
Since I needed to write outb and inb here is the code:
inline void outb(unsigned int port, unsigned char val)
{
asm volatile ("outb %%al,%%dx": :"d" (port), "a" (val));
}
inline unsigned char inb(unsigned int port)
{
unsigned char ret;
asm volatile ("inb %%dx,%%al":"=a" (ret):"d" (port));
return (ret);
}
I still can't get an ouput.
What am I doing wrong ?
You should look at outb() and inb(), you can't write on your COM1 address like that.
I'm facing a problem with my erasing function.
I try to erase 15 sectors of my Flash in order to put a new binary file.
I don't understand why but my function freeze and i cannot erase all memory i need.
Here is my code if you want to give a try
/*
* bootloader.c
*
* Created on: 9 juin 2015
* Author: tgloaguen
*/
#include "usart.h"
#include "stm32l1xx_flash.h"
#define WRITE_START_ADDR 0x08000000
#define WRITE_END_ADDR 0x0800FFFF
#define FLASH_PAGE_SIZE ((uint16_t)0x100) //If a page is 256 bits
#define MY_BL_FUNCTIONS __attribute__((section(".bootsection")))
void BootLoader(void) MY_BL_FUNCTIONS;
FLASH_Status Flash_Write ( uint32_t StartAddress, uint8_t *p, uint32_t Size ) MY_BL_FUNCTIONS;
uint8_t Flash_Erase() MY_BL_FUNCTIONS;
void Receive_Data(char * buffer,int size)MY_BL_FUNCTIONS;
void Receive_Size(char * buffer, int *sizeData)MY_BL_FUNCTIONS;
void BootLoader(void) {
//clear all ITs
USART_ITConfig( USART1, USART_IT_RXNE, DISABLE );
//SendString("HELLO",USART2);
uint8_t status,i;
char buffer[33];
//en dur
uint16_t *adr = WRITE_START_ADDR;
uint16_t sizeBin = 51400,k = 0,k_hexa = 0x20;
SendString("BOOTLOADER",USART2);
Flash_Erase();
SendString("ERASEOK",USART2);
//if sizeBin ok
}
and the erase function
uint8_t Flash_Erase() {
uint32_t EraseCounter = 0x00, Address = 0x00;//Erase count, write address
uint32_t NbrOfPage = 0x00;//Recording to erase the pages
volatile FLASH_Status FLASHStatus = FLASH_COMPLETE;/*FLASH complete erasure marks*/
/*Unlock FLASH*/
FLASH_Unlock();
/*Calculate the number of FLASH pages need to erase */
NbrOfPage = (WRITE_END_ADDR - WRITE_START_ADDR) / FLASH_PAGE_SIZE;
/* Remove all hang flags */
FLASH_ClearFlag ( FLASH_FLAG_EOP |
FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR |
FLASH_FLAG_SIZERR |
FLASH_FLAG_OPTVERR );
/* Erase the FLASH page*/
for(EraseCounter = 0; (EraseCounter <NbrOfPage) && (FLASHStatus == FLASH_COMPLETE); EraseCounter++)
{
SendString("ok |",USART2);
FLASHStatus = FLASH_ErasePage(WRITE_START_ADDR + (FLASH_PAGE_SIZE * EraseCounter));
}
FLASH_Lock ( );
return (uint8_t)FLASHStatus;
}
In your code you are tring to erase the whole flash, but you are executing your bootloader from flash from final sectors.
As reported by th REF man
During a write/erase operation to the NVM (except Half Page programming or Double-word
erase/write), any attempt to read the same bank of NVM stalls the bus.
Then you have to preserve the space of bootloader changing WRITE_END_ADDR define according to your memory map.
Example: if your bootloader is 4K long and belongs to the last sector (0x0801 F000 - 0x0801 FFFF) then WRITE_END_ADDR must be 0x0801 EFFF.
EDIT
As #Olaf wrote take care about your ISP (Initial Stack Pointer) and IPC (Initial Program Counter) that belong to first 8 bytes in your flash at address 0.
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