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
Related
I'm configuring a ICM20948 breakout from primo roni via i2c on a Beagle Bone black, using a minimal linux build using buildroot, via a C program. To do so, i've used this driver for the i2c connection and also this ICM20948 driver as base for the speciffic device, so the target is to merge both.
I've been able to configure some registers (like the sleepmode o lowpowermode) but when setting the fullscale, the values seems not to be changing.
This is more or less the function that pressents the problem
uint16_t set_values(uint16_t register, uint16_t value)
{
unsigned char rxBuffer[1]; // receive buffer
unsigned char txBuffer[2]; // transmit buffer
retval |= ICM_20948_set_bank(2); // Must be in the right bank
txBuffer[0] = register;
retval |= write(device.i2cHandle,txBuffer,1); // device.i2cHandle is the handler obtained when
retval |= read(device.i2cHandle,rxBuffer,1); // opening the /dev/i2c-X
printf("Readed value %x: %x\n",txBuffer[0],rxBuffer[0]);
txBuffer[1] = value;
printf("Intending to write: %x\n", txBuffer[1]);
retval |= write(device.i2cHandle, txBuffer, sizeof(txBuffer));
write(device.i2cHandle,txBuffer,1);
read(device.i2cHandle,rxBuffer,1);
printf("Written value on register %x: %x\n",txBuffer[0],rxBuffer[0]);
}
But the output when doing trying to modify the 0x14 register with the value 0x03 returns what follows:
Readed value 14: 1
Intending to write: 3
Written value on register 14: 1
Any suggestion? Thanks in advance!
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.
I also have a problem with DRDY. I need to include DRDY. The pins for DRDY are RD2 and RD5. They are both inputs.
Here is the information for DRDY.
DRDY Pin
DRDY is an open-drain output (in SPI mode) or bidirectional pin (in UART mode) with an internal 20 k – 50 k pullup
resistor.
Most communications failures are the result of failure to properly observe the DRDY timing.
Serial communications pacing is controlled by this pin. Use of DRDY is critical to successful communications with the
QT1481. In either UART or SPI mode, the host is permitted to perform a data transfer only when DRDY has returned
high. Additionally, in UART mode, the QT1481 delays responses to the host if DRDY is being held low by the host.
After each byte transfer, DRDY goes low after a short delay and remains low until the QT1481 is ready for another
transfer. A short delay occurs before DRDY is driven low because the QT1481 may otherwise be busy and requires
a finite time to respond.
DRDY may go low for a microsecond only. During the period from the end of one transfer until DRDY goes low and
back high again, the host should not perform another transfer. Therefore, before each byte transmission the host
should first check that DRDY is high again.
If the host wants to perform a byte transfer with the QT1481 it should behave as follows:
1. Wait at least 100 µs after the previous transfer (time S5 in Figure 3-2 on page 23: DRDY is guaranteed to go
low before this 100 µs expires).
2. Wait until DRDY is high (it may already be high).
3. Perform the next transfer with the QT1481.
In most cases it takes up to 3 ms for DRDY to return high again. However, this time is longer with some commands
or if the STS_DEBUG setup is enabled, as follows:
0x01 (Setups load): <20 ms
0x02 (Low Level Cal and Offset): <20 ms
Add 15 ms to the above times if the STS_DEBUG setup is enabled.
Other DRDY specifications:
Min time DRDY is low: 1 µs
Max time DRDY is low after reset: 100 ms
The timing diagram is this:
How can implement that?
The code I have written with my friend is written here:
#include <xc.h>
#include "PIC.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
//#include <pic18f45k80.h>
#define MSB 1
#define LSB 0
// SPI PIN CONFIGURATION
#define SCK_TRIS TRISCbits.TRISC3 = 0 ;
#define SDO_TRIS TRISCbits.TRISC5 = 0 ;
#define SDI_TRIS TRISCbits.TRISC4 = 1 ;
#define QTA_SS_TRIS TRISDbits.TRISD4 = 0 ;
#define QTB_SS_TRIS TRISEbits.TRISE2 = 0 ;
#define QTA_SS_LAT_LOW LATDbits.LATD4 = 0 ;
#define QTA_SS_LAT_HIGH LATDbits.LATD4 = 1 ;
#define QTB_SS_LAT_LOW LATEbits.LATE2 = 0 ;
#define QTB_SS_LAT_HIGH LATEbits.LATE2 = 1 ;
#define QTA_DRDY_TRIS TRISDbits.TRISD5 = 1 ;
#define QTB_DRDY_TRIS TRISDbits.TRISD2 = 1 ;
#define QTA_DRDY_LAT_LOW LATDbits.LATD5 = 0 ;
#define QTA_DRDY_LAT_HIGH LATDbits.LAT52 = 1 ;
#define QTB_DRDY_LAT_LOW LATDbits.LAT25 = 0 ;
#define QTB_DRDY_LAT_HIGH LATDbits.LATD2 = 1 ;
#define QTB_DRDY PORTDbits.RD2 ;
#define QTA_DRDY PORTDbits.RD5 ;
// FREQUENCY SELECT
#define _XTAL_FREQ 16000000
// PIN SETUP
void PIN_MANAGER_Initialize(void)
{
/**
LATx registers
*/
LATE = 0x00;
LATD = 0x00;
LATA = 0x00;
LATB = 0b00010000;
LATC = 0x00;
/**
TRISx registers
*/
TRISE = 0x00;
TRISA = 0x08;
TRISB = 0x01;
TRISC = 0b00010000;
TRISD = 0xEF;
PORTC = 0b00010010 ;
/**
ANSELx registers
*/
ANCON0 = 0x00;
ANCON1 = 0x00;
/**
WPUx registers
*/
WPUB = 0x00;
INTCON2bits.nRBPU = 1;
}
// SPI
void SPI_Initialize(void)
{
// SMP Middle; CKE Idle to Active;
SSPSTAT = 0b00000000;
// SSPEN enabled; WCOL no_collision; CKP Idle:High, Active:Low; SSPM FOSC/4; SSPOV no_overflow;
SSPCON1 = 0b00111010;
// SSPADD 0;
SSPADD = 0x00;
ADCON0 = 0 ;
ADCON1 = 0x0F ; //Makes all I/O digital
SCK_TRIS ;
SDO_TRIS ;
SDI_TRIS ;
QTA_SS_TRIS ;
QTB_SS_TRIS ;
QTA_DRDY_TRIS ;
QTB_DRDY_TRIS ;
}
signed char WriteSPI( unsigned char data_out )
{
unsigned char TempVar;
TempVar = SSPBUF; // Clears BF
PIR1bits.SSPIF = 0; // Clear interrupt flag
SSPCON1bits.WCOL = 0; //Clear any previous write collision
SSPBUF = data_out; // write byte to SSPBUF register
if ( SSPCON1 & 0x80 ) // test if write collision occurred
return ( -1 ); // if WCOL bit is set return negative #
else
while( !PIR1bits.SSPIF ); // wait until bus cycle complete
return ( 0 ); // if WCOL bit is not set return non-negative#
}
unsigned char ReadSPI( void )
{
unsigned char TempVar;
TempVar = SSPBUF; // Clear BF
PIR1bits.SSPIF = 0; // Clear interrupt flag
SSPBUF = 0x00; // initiate bus cycle
while(!PIR1bits.SSPIF); // wait until cycle complete
return ( SSPBUF ); // return with byte read
}
unsigned char DataRdySPI( void )
{
if ( SSPSTATbits.BF )
return ( +1 ); // data in SSPBUF register
else
return ( 0 ); // no data in SSPBUF register
}
// SOFTWARE EUART
void out_char(char character, char bit_order){
uint8_t i = 0;
RSOUT = 1 ; // MSB
__delay_ms(1);
RSOUT = 0 ; // START
__delay_us(100);
for (i = 8; i>0; --i){
if (bit_order){ // Bit order determines how you will put the bits, from left to right (MSB) or right to left (LSB)
RSOUT = (character & 0x80) ? 1:0; // in MSB you compare the left-most bit doing an AND with 0x80, and put 1 if true, 0 elsewhere.
character <<= 1; // Shift the character to the left, discrading the bit just sent
} else {
RSOUT = (character & 0x01); // in LSB you compare the right-most bit doing an AND with 0x01, and put 1 if true, 0 else.
character >>= 1; // Shift the character to the right, discrading the bit just sent
}
__delay_us(100);
}
RSOUT = 1 ; // STOP
}
void out_str(char * string, uint8_t len, char bit_order){
uint8_t i = 0;
for (i = 0; i< len; i++){
out_char(string[i], bit_order);
}
}
void SYSTEM_Initialize(void)
{
PIN_MANAGER_Initialize() ;
SPI_Initialize() ;
}
void main(void)
{
SYSTEM_Initialize() ;
while (1)
{
QTB_SS_LAT_LOW ; // Transmit data
char temp ;
WriteSPI(0x0F) ; // Send a byte
while(!DataRdySPI()) ; // wait for a data to arrive
temp = ReadSPI(); // Read a byte from the
QTB_SS_LAT_HIGH ; // Stop transmitting data
__delay_us(100) ;
}
}
No. Do not just write a bunch of code, then see what it does. That kind of shotgun (or, if you prefer, spaghetti-to-the-wall) approach is a waste of effort.
First, drop all those macros. Instead, write comments that describe the purpose of each chunk of code, like the first three assignments in your SPI_Initialize() function.
Next, convert your specification to pseudocode. The format does not matter much, just use something that lets you keep your mind focused on what the purpose is, rather than on the details on how to do it.
The datasheet says that with SPI, there are three outputs from the PIC (^SS, SCK, MOSI on the QT1481), and two inputs (^DRDY and MISO on the QT1481). I'll use those names for the data lines, and for their respective I/O pin names on the PIC.
The setup phase on the PIC should be simple:
Make ^DRDY an input
Make ^SS an output, set it HIGH
Make SCK an output, set it LOW
Make MOSI an output, set it LOW
Make MISO an input
Set up SPI using SCK, MOSI, MISO
Each transfer is a bidirectional one. Whenever you send data, you also receive data. The zero command is reserved for receiving multiple data, says the datasheet. So, you only need a function that sends a byte, and at the same time receives a byte:
Function SPITransfer(command):
Make sure at least 0.1ms has passed since the previous transfer.
Do:
Nothing
While (^DRDY is LOW)
Set ^SS LOW
response = Transfer(command)
Set ^SS HIGH
Return response
End Function
As I understand it, for PICs and properly initialized hardware SPI the response = Transfer(command) line is in C
SSPBUF = command;
while (!DataRdySPI())
;
response = SSPBUF;
You can also bit-bang it, in which case it is (in pseudocode):
response = 0
For bit = 7 down to 0, inclusive:
If (command & 128):
Set MOSI high
Else:
Set MOSI low
End If
Set SCK low
Sleep for a half period
command = command / 2
response = response * 2
If MISO high:
response = response + 1
End If
Set SCK high
Sleep for a half period
End For
but obviously the hardware SPI approach is better.
(When you get this working, you can use the hardware SPI without a wait loop from a timer interrupt, making the communications essentially transparent to the "main operation" of the PIC microcontroller. That requires a slightly different approach, with a command and response queues (of a few bytes), but will make it much easier for the PIC to do actual work, other than just scan the QT1481.)
After a reset, you essentially send 0x0F until you get 0xF0 back:
while (SPITransfer(0x0F) != 0xF0)
;
At this point, you have the steps you need to implement in C. OP also has the hardware (an oscilloscope) to verify their code works.
I am trying to test the i2c communication of the MAX77651 chip before programming it.
So here is my setup to do so:
I have an UMFT4222ev connected to my Linux laptop by USB. This chip has his SCL and SDA linked to the SDA and SCL of my MAX77651 thanks to the Evaluation Kit for the MAX77651. My MAX77651evkit is powered with 3,7V on the Vbatt pin.
I also installed the mraa librarie from git hub and the libft4222. I know mraa is well installed because i tried it with and example.
I was told that the mraa library takes in charge the setup of the FT4222 so i only used mraa functions to make my program.
I searched on the website of Maxim integrated the i2c slave address and one register where i could read data and check if everyting is working. I then read the i2c protocol of communication to read a single register which is available here : https://datasheets.maximintegrated.com/en/ds/MAX77650-MAX77651.pdf at the page 78.
With all those informations I tried to make my "test program". I solved the compiling errors but when I execute the program I can't get what is in the register which should be 0xFF.
Here is my program:
#include "stdio.h"
#include "syslog.h"
#include "string.h"
#include "unistd.h"
#include "mraa/i2c.h"
#include "mraa.h"
#define I2C_ADDR 0x48
int
main(int argc, char *argv[])
{
uint8_t *message;
*message=0XAC;
int i,j,k;
char reg_a_lire = 0x06;
mraa_init();
mraa_i2c_context i2c;
i2c = mraa_i2c_init(0);
mraa_i2c_frequency(i2c,MRAA_I2C_FAST);
mraa_i2c_address(i2c, I2C_ADDR);
mraa_i2c_write_byte(i2c,0x90);
mraa_i2c_read(i2c, message,1);
mraa_i2c_write_byte(i2c,reg_a_lire);
mraa_init();
mraa_i2c_write_byte(i2c,0x91);
mraa_i2c_read(i2c, message,1);
mraa_i2c_read(i2c, message,1);
printf("%02X \n", *message);
mraa_i2c_stop(i2c);
return 0;
}
Here is the actual output :
alex#cyclonit-laptop ~/Test_alex/tests $ ./a.out
AC
And i would like to get FF instead of AC.
I think my error could come from something i missed to initialize the FT4222 or from the MAX77651 which I maybe did nt power up correctly and its not sufficient to put 3,7V on Vbatt. But maybe this is a problem with my program because i don't know much about uint8_t and I made something wrong.
I am hoping someone has experience with FT4222 and/or MAX77651 and can help me.
I think you are confused by pg. 75 of the MAX77651 datasheet.
ADDRESS | 7-BIT SLAVE ADDRESS | 8-BIT WRITE ADDRESS | 8-BIT READ ADDRESS
Main Address | 0x48, 0b 100 1000 | 0x90, 0b 1001 0000 | 0x91, 0b 1001 0001
(ADDR = 1)* | | |
-------------+---------------------+---------------------+-------------------
Main Address | 0x40, 0b 100 0000 | 0x80, 0b 1000 0000 | 0x81, 0b 1000 0001
(ADDR = 0)* | | |
Depending on your view of the I²C specification, you either use the 7-bit address or use both 8-bit addresses. The reason is that the first I²C transaction sends the following 8 bit:
76543210
\_____/\-- R/#W
\------ 7-bit Slave Address
In your case that's 0x48 shifted one bit to the left followed by either a 1 for read mode or a 0 for write mode. Notice that
(0x40 << 1) | 0x00 == 0x80 == 8-bit Write Address (ADDR = 0)
(0x40 << 1) | 0x01 == 0x81 == 8-bit Read Address (ADDR = 0)
(0x48 << 1) | 0x00 == 0x90 == 8-bit Write Address (ADDR = 1)
(0x48 << 1) | 0x01 == 0x91 == 8-bit Read Address (ADDR = 1)
Some people like to use the slave address while other people prefer to give separate addresses so it's clear what the first byte of communication will look like.
So you should remove the extraneous mraa_i2c_write_byte(i2c, ...); lines from your code, as their intention seems to be to send the read or write address to the slave.
Check out the MRAA library API reference. There are functions that abstract reading and writing registers, either byte-wide [8-bit] registers or word-wide [16-bit] registers:
mraa_i2c_read_byte_data
mraa_i2c_read_bytes_data
mraa_i2c_write_byte_data
In both cases you should check the return codes to know if the action succeeded. In your case, your message is likely not altered, because there was a stop/error condition beforehand, because the slave did either not ACK its slave address or it did not ACK the 0x90/0x91 data bytes you mistakenly sent, as they don't show up in the Programmer's Guide as valid addresses.
Another issue is that you try to read register INTM_GLBL (0x06; Global Interrupt Mask Register) using two consecutive read operations. I'm not sure if your intention is to read register 0x06 twice or if you intended to read INT_M_CHG (0x07; Global Interrupt Mask for Charger), because that's not what it will do. Notice the description on pg. 78 of the datasheet:
Note that when the MAX77650/MAX77651 receive a stop
they do not modify their register pointer.
This is typical behavior for I²C slaves that support multi-byte/sequential reads. You will have to issue a sequential read operation for multiple bytes of data if you want to read multiple registers, e.g. using mraa_i2c_read_bytes_data.
Something like the following might get you on the right track. It is supposed to read the CID and CLKS settings and wait forever if a read error occurred. If successful, it will disable charging, all outputs, setup the red LED to blink once every other second and -- as soon as you uncomment it -- transition the On/Off Controller into On Via Software state to enable bias and the LED.
#define MAX77651_SLA 0x48u
#define CNFG_GLBL_REG 0x10u
#define CID_REG 0x11u
#define CNFG_CHG_B_REG 0x19u
#define CNFG_SBB0_B_REG 0x2Au
#define CNFG_SBB1_B_REG 0x2Cu
#define CNFG_SBB2_B_REG 0x2Eu
#define CNFG_LDO_B_REG 0x39u
#define CNFG_LED1_A_REG 0x41u
#define CNFG_LED1_B_REG 0x44u
#define CNFG_LED_TOP_REG 0x46u
int main(int argc, char *argv[]) {
uint8_t data;
int res;
mraa_init();
mraa_i2c_context i2c0;
i2c0 = mraa_i2c_init(0);
mraa_i2c_frequency(i2c0, MRAA_I2C_STD);
mraa_i2c_address(i2c0, MAX77651_SLA);
res = mraa_i2c_read_byte_data(i2c0, CID_REG);
if (res < 0) {
printf("Reading CID_REG failed.\n");
mraa_i2c_stop(i2c0);
while(1);
}
data = res;
printf("CID_REG: CLKS = %02X CID = %02X\n", ((data & 0x70u) >> 4), (data & 0x0Fu));
/* you should check return values here */
mraa_i2c_write_byte_data(i2c0, 0x00u /* CHG_EN = off */, CNFG_CHG_B_REG);
mraa_i2c_write_byte_data(i2c0, 0x04u /* EN_LDO = off */, CNFG_LDO_B_REG);
mraa_i2c_write_byte_data(i2c0, 0x04u /* EN_SBB0 = off */, CNFG_SBB0_B_REG);
mraa_i2c_write_byte_data(i2c0, 0x04u /* EN_SBB1 = off */, CNFG_SBB1_B_REG);
mraa_i2c_write_byte_data(i2c0, 0x04u /* EN_SBB2 = off */, CNFG_SBB2_B_REG);
/* set up red led to toggle every second */
mraa_i2c_write_byte_data(i2c0, 0x17u /* P = 1s, D = 50% */, CNFG_LED1_B_REG);
mraa_i2c_write_byte_data(i2c0, 0x98u /* FS = 6.4mA, BRT = 5.0mA */, CNFG_LED1_A_REG);
mraa_i2c_write_byte_data(i2c0, 0x01u /* EN_LED_MSTR = on */, CNFG_LED_TOP_REG);
// DANGER ZONE: enable only when you know what this is supposed to do
//mraa_i2c_write_byte_data(i2c0, 0x10u /* SBIA_EN = on, SBIA_LPM = normal */, CNFG_GLBL_REG);
mraa_i2c_stop(i2c0);
while(1);
}
I managed to read I2C registers of MAX77651.
First looking at the hardware part I had to make sure that VIO had the right voltage like #FRob said in hid comment.
Then for the software, I stopped using the mraa library because i could'nt control everything. I used the FT4222 library which allowed me to open and initiate the FT4222 device. I took some part of the I2C example in this library for the initialization of the device. Then I noticed that I had to first use the FT4222's write function to send the register i wanted to read , then simply use the read function to get my result. Those are the two steps required.
I won't post the whole program i made as it is mainly taken from the I2C example for initialization but here is the part I added to read my register 0X06 which is supposed to contain 0xFF:
uint8 resultat=0x11;
uint8 *p_resultat=&resultat;
int chiffre;
slaveAddr
uint16 bytesToWrite2 = 1;
uint16 bytesWritten2=1;
uint8 valeur= 0x06; // REGISTER TO READ
uint8 *p_valeur=&valeur;
FT4222_I2CMaster_Write(ftHandle,slaveAddr,
p_valeur,bytesToWrite2,&bytesWritten); //INDICATES WHICH REGISTER TO
// READ
chiffre = FT4222_I2CMaster_Read(ftHandle,
slaveAddr,p_resultat,1, &bytesRead); // READ REGISTER
printf("The content of the register %02X is : %02X \n " ,
valeur,resultat); // DISPLAY RESULT
printf("reading success if : %d = 0 \n " , chiffre);
// READING SUCCESS ?
With this code and the initialization i get the following result :
alex#cyclonit-laptop ~/Downloads/libft4222-1.2.1.4/examples $ ./a.out
Device 0 is interface A of mode-0 FT4222H:
0x0403601c FT4222 A
Chip version: 42220200, LibFT4222 version: 01020104
The content of the register 06 is : FF
reading success if : 0 = 0
Skipping interface B of mode-0 FT4222H.
If someone has the same problem you are free to ask me your question by answering this post.I am very thankful to the people here who helped me!
I am working on a library for controlling the M95128-W EEPROM from an STM32 device. I have the library writing and reading back data however the first byte of each page it not as expected and seems to be fixed at 0x04.
For example I write 128 bytes across two pages starting at 0x00 address with value 0x80. When read back I get:
byte[0] = 0x04;
byte[1] = 0x80;
byte[2] = 0x80;
byte[3] = 0x80;
.......
byte[64] = 0x04;
byte[65] = 0x80;
byte[66] = 0x80;
byte[67] = 0x80;
I have debugged the SPI with a logic analyzer and confirmed the correct bytes are being sent. When using the logic analyzer on the read command the mysterios 0x04 is transmitted from the EEPROM.
Here is my code:
void FLA::write(const void* data, unsigned int dataLength, uint16_t address)
{
int pagePos = 0;
int pageCount = (dataLength + 64 - 1) / 64;
int bytePos = 0;
int startAddress = address;
while (pagePos < pageCount)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_SET); // WP High
chipSelect();
_spi->transfer(INSTRUCTION_WREN);
chipUnselect();
uint8_t status = readRegister(INSTRUCTION_RDSR);
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
printConsole("Wrote byte to ");
printConsoleInt(startAddress + bytePos);
printConsole("with value ");
printConsoleInt(byte);
printConsole("\n");
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI);
chipUnselect();
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_RESET); //WP LOW
bool writeComplete = false;
while (writeComplete == false)
{
uint8_t status = readRegister(INSTRUCTION_RDSR);
if(status&1<<0)
{
printConsole("Waiting for write to complete....\n");
}
else
{
writeComplete = true;
printConsole("Write complete to page ");
printConsoleInt(pagePos);
printConsole("# address ");
printConsoleInt(bytePos);
printConsole("\n");
}
}
pagePos++;
address = address + 64;
}
printConsole("Finished writing all pages total bytes ");
printConsoleInt(bytePos);
printConsole("\n");
}
void FLA::read(char* returndata, unsigned int dataLength, uint16_t address)
{
chipSelect();
_spi->transfer(INSTRUCTION_READ);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address
_spi->transfer(xlow); // part 2 address
for (unsigned int i = 0; i < dataLength; i++)
returndata[i] = _spi->transfer(0x00);
chipUnselect();
}
Any suggestion or help appreciated.
UPDATES:
I have tried writing sequentially 255 bytes increasing data to check for rollover. The results are as follows:
byte[0] = 4; // Incorrect Mystery Byte
byte[1] = 1;
byte[2] = 2;
byte[3] = 3;
.......
byte[63] = 63;
byte[64] = 4; // Incorrect Mystery Byte
byte[65] = 65;
byte[66] = 66;
.......
byte[127] = 127;
byte[128] = 4; // Incorrect Mystery Byte
byte[129} = 129;
Pattern continues. I have also tried writing just 8 bytes from address 0x00 and the same problem persists so I think we can rule out rollover.
I have tried removing the debug printConsole and it has had no effect.
Here is a SPI logic trace of the write command:
And a close up of the first byte that is not working correctly:
Code can be viewed on gitlab here:
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/flash.cpp
Init code of SPI can be seen here in MX_SPI_Init()
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/main.cpp
I have another device on the SPI bus (RFM69HW RF Module) which works as expected sending and receiving data.
The explanation was actually already given by Craig Estey in his answer. You do have a rollover. You write full page and then - without cycling the CS pin - you send INSTRUCTION_WRDI command. Guess what's the binary code of this command? If you guessed that it's 4, then you're absolutely right.
Check your code here:
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
// ...
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI); // <-------------- ROLLOEVER!
chipUnselect();
With these devices, each command MUST start with cycling CS. After CS goes low, the first byte is interpreted as command. All remaining bytes - until CS is cycled again - are interpreted as data. So you cannot send multiple commands in a single "block" with CS being constantly pulled low.
Another thing is that you don't need WRDI command at all - after the write instruction is terminated (by CS going high), the WEL bit is automatically reset. See page 18 of the datasheet:
The Write Enable Latch (WEL) bit, in fact, becomes reset by any of the
following events:
• Power-up
• WRDI instruction execution
• WRSR instruction completion
• WRITE instruction completion.
Caveat: I don't have a definitive solution, just some observations and suggestions [that would be too large for a comment].
From 6.6: Each time a new data byte is shifted in, the least significant bits of the internal address counter are incremented. If more bytes are sent than will fit up to the end of the page, a condition known as “roll-over” occurs. In case of roll-over, the bytes exceeding the page size are overwritten from location 0 of the same page.
So, in your write loop code, you do: for (i = 0; i < 64; i++). This is incorrect in the general case if the LSB of address (xlow) is non-zero. You'd need to do something like: for (i = xlow % 64; i < 64; i++)
In other words, you might be getting the page boundary rollover. But, you mentioned that you're using address 0x0000, so it should work, even with the code as it exists.
I might remove the print statements from the loop as they could have an effect on the serialization timing.
I might try this with an incrementing data pattern: (e.g.) 0x01,0x02,0x03,... That way, you could see which byte is rolling over [if any].
Also, try writing a single page from address zero, and write less than the full page size (i.e. less that 64 bytes) to guarantee that you're not getting rollover.
Also, from figure 13 [the timing diagram for WRITE], it looks like once you assert chip select, the ROM wants a continuous bit stream clocked precisely, so you may have a race condition where you're not providing the data at precisely the clock edge(s) needed. You may want to use the logic analyzer to verify that the data appears exactly in sync with clock edge as required (i.e. at clock rising edge)
As you've probably already noticed, offset 0 and offset 64 are getting the 0x04. So, this adds to the notion of rollover.
Or, it could be that the first data byte of each page is being written "late" and the 0x04 is a result of that.
I don't know if your output port has a SILO so you can send data as in a traditional serial I/O port or do you have to maintain precise bit-for-bit timing (which I presume the _spi->transfer would do)
Another thing to try is to write a shorter pattern (e.g. 10 bytes) starting at a non-zero address (e.g. xhigh = 0; xlow = 4) and the incrementing pattern and see how things change.
UPDATE:
From your update, it appears to be the first byte of each page [obviously].
From the exploded view of the timing, I notice SCLK is not strictly uniform. The pulse width is slightly erratic. Since the write data is sampled on the clock rising edge, this shouldn't matter. But, I wonder where this comes from. That is, is SCLK asserted/deasserted by the software (i.e. transfer) and SCLK is connected to another GPIO pin? I'd be interested in seeing the source for the transfer function [or a disassembly].
I've just looked up SPI here: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus and it answers my own question.
From that, here is a sample transfer function:
/*
* Simultaneously transmit and receive a byte on the SPI.
*
* Polarity and phase are assumed to be both 0, i.e.:
* - input data is captured on rising edge of SCLK.
* - output data is propagated on falling edge of SCLK.
*
* Returns the received byte.
*/
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
uint8_t byte_in = 0;
uint8_t bit;
for (bit = 0x80; bit; bit >>= 1) {
/* Shift-out a bit to the MOSI line */
write_MOSI((byte_out & bit) ? HIGH : LOW);
/* Delay for at least the peer's setup time */
delay(SPI_SCLK_LOW_TIME);
/* Pull the clock line high */
write_SCLK(HIGH);
/* Shift-in a bit from the MISO line */
if (read_MISO() == HIGH)
byte_in |= bit;
/* Delay for at least the peer's hold time */
delay(SPI_SCLK_HIGH_TIME);
/* Pull the clock line low */
write_SCLK(LOW);
}
return byte_in;
}
So, the delay times need be at least the ones the ROM needs. Hopefully, you can verify that is the case.
But, I also notice that on the problem byte, the first bit of the data appears to lag its rising clock edge. That is, I would want the data line to be stabilized before clock rising edge.
But, that assumes CPOL=0,CPHA=1. Your ROM can be programmed for that mode or CPOL=0,CPHA=0, which is the mode used by the sample code above.
This is what I see from the timing diagram. It implies that the transfer function does CPOL=0,CPHA=0:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
This is what I originally expected (CPOL=0,CPHA=1) based on something earlier in the ROM document:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
The ROM can be configured to use either CPOL=0,CPHA=0 or CPOL=1,CPHA=1. So, you may need to configure these values to match the transfer function (or vice-versa) And, verify that the transfer function's delay times are adequate for your ROM. The SDK may do all this for you, but, since you're having trouble, double checking this may be worthwhile (e.g. See Table 18 et. al. in the ROM document).
However, since the ROM seems to respond well for most byte locations, the timing may already be adequate.
One thing you might also try. Since it's the first byte that is the problem, and here I mean first byte after the LSB address byte, the memory might need some additional [and undocumented] setup time.
So, after the transfer(xlow), add a small spin loop after that before entering the data transfer loop, to give the ROM time to set up for the write burst [or read burst].
This could be confirmed by starting xlow at a non-zero value (e.g. 3) and shortening the transfer. If the problem byte tracks xlow, that's one way to verify that the setup time may be required. You'd need to use a different data value for each test to be sure you're not just reading back a stale value from a prior test.