The first two lines are 'are you there' calls to the battery from the Kindle [0xAA+] the '+' is the ACK sent back by the battery. So things are looking good!
This is the Kindle getting the battery to report its voltage.
0x09 Write the following byte to the device's receive buffer: 0x09 is the voltage level address
+ The battery acknowledges that the address has been received.
0xAB The Kindle calls the read address of the battery. The battery reads the data from address 0x09 and places it in its buffer.
+ The battery pulls the clock line high again to signify it has finished writing to buffer(ACK).
0x0E The Kindle's master bus, clocks out the byte from the battery reading a value of 0x0E.
- The Kindle master sends a NACK to say it is done receiving data.
] The stop character signaling end of conversation.
Reading the documentation on SMBus shows that this 0x09 command is comprised of two bytes. As a dead LIPO battery is below 3V the Kindle only cares about the first byte. If you add a second byte, eg. 0x00 to the first byte you get 0x0900 which in decimal is 3580mV which equates to 3.58 V. (yep battery isn't fully charged anymore at this point)
Several Microchip PICs in the Enhanced Mid Range can talk I2C/SMBus without having to bit bash. This uses the MSSP (Master Synchronous Serial Port) which can also do SPI.
For low level simple I/O you can't beat assembly on a PIC to know what is going on. But seeing as you are going to have to write up the I2C protocol in your code and debug it, C is going to be the easier option!
So, without wanting to write from scratch; I poured over several examples of PIC I2C code in C.The examples made no sense and didn't appear to behave as per the SMBus protocol. I still don't quite understand what those code examples are trying to do. Looks like sequentially reading and writing to EEPROM rather than reading and writing to specific addresses on a device. I read Microchip's AN734 and AN736 PDF's and downloaded Microchip example code but they were the same. They don't seem to follow the I2C protocol, and didn't work for me at all.
So I grabbed the best parts of the example code and rewrote it until it did the right job:
Also using a Raspberry Pi with i2cdump installed I was able to read the battery's entire 128 bytes of data:
I pasted the entire data dump into an array in the source code. This should now fool the Kindle into believing the battery is really there, and the PIC will reply with the same data for any request that the battery would have.
/*
* File: kindlebat.c
* Author: Neil
*
* Created on March 25, 2016, 8:50 PM
*/
/* Example of I2C reading 2 bytes (rr) using Bus Pirate
Write address
I2C>[0xaa 0x00 [0xab rr]
I2C START BIT
WRITE: 0xAA ACK
WRITE: 0x00 ACK
I2C START BIT
WRITE: 0xAB ACK
READ: 0xAA
READ: ACK 0xEE
NACK
I2C STOP BIT
Example of writing using Bus Pirate
I2C>[0xaa 0x00 0x10]
I2C START BIT
WRITE: 0xAA ACK
WRITE: 0x00 ACK
WRITE: 0x10 ACK
I2C STOP BIT
*/
// CONFIG1
#pragma config FOSC = INTOSC // INTOSC oscillator: I/O function on CLKIN pin
#pragma config WDTE = ON // Watchdog Timer Enable (WDT enabled)
#pragma config PWRTE = ON // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = ON // MCLR/VPP pin function is MCLR
#pragma config CP = ON // Flash Program Memory Code Protection enabled
#pragma config CPD = ON // Data memory code protection is enabled
#pragma config BOREN = ON // Brown-out Reset enabled
#pragma config CLKOUTEN = OFF // CLKOUT function is disabled.
// I/O or oscillator function on the CLKOUT pin
#pragma config IESO = ON // Internal/External Switch-over mode is enabled
#pragma config FCMEN = ON // Fail-Safe Clock Monitor is enabled
// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection off
#pragma config PLLEN = ON // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON // Stack Overflow/Underflow will cause a Reset
#pragma config BORV = LO // Brown-out Reset Voltage (Vbor),
// low trip point selected.
#pragma config LVP = ON // Low-voltage programming enabled
#include <xc.h>
#include <pic.h>
#include <pic16f1824.h>
#include "uart.h" // Only need for debugging
#define I2C_address 0xAA /* I2C address of the slave node */
#define RX_ELMNTS 128
// array for master to read from
volatile unsigned char I2C_Array[RX_ELMNTS] =
{0x00,0x40,0xb6,0x17,0x26,0x00,0xa1,0x04,0x75,0x0f,0x04,0x63,0xb7,0x0f,
0xd8,0x0f,0xb7,0x0f,0xd8,0x0f,0x67,0x00,0x27,0x09,0xff,0xff,0x67,0x00,0x27,
0x09,0x00,0x00,0x35,0x0e,0x0f,0x00,0x0f,0x00,0x82,0x08,0x02,0x00,0x0c,0x00,
0x63,0xe0,0xa0,0x07,0x21,0x00,0xf8,0x9b,0xc3,0x1b,0xff,0xff,0xa8,0xf6,0xff,0x00,
0xe3,0x03,0x4b,0x4e,0x00,0x00,0x05,0x00,0x00,0x00,0x21,0xa9,0xc7,0x48,0x38,0x85,
0x7f,0x66,0x6c,0x46,0xdf,0x20,0x36,0x09,0x00,0x01,0x52,0xa4,0xcf,0x0f,0x3f,0x24,
0x00,0x00,0x71,0xff,0x91,0xff,0x62,0xff,0x8d,0x04,0xd9,0x05,0x00,0x00,0x00,0x01,
0x00,0x07,0x00,0x00,0x2d,0x78,0x37,0x6d,0x7a,0xbb,0x21,0xa9,0xc7,0x48,0x38,0x85,
0x7f,0x66,0x00,0xa9};
unsigned char dbuf = 0; // index pointer
unsigned char junk = 0; // used to place unnecessary data
unsigned char clear = 0x00;
unsigned int first = 0;
void initialize(void);
void main(void) {
// UART_Init(9600);
initialize();
while(1)
{
asm("CLRWDT"); // clear Watch Dog Timer
}
}
void initialize(void)
{
//uC SET UP
OSCCON = 0b01111010; // Internal OSC @ 16MHZ
OPTION_REG = 0b11010111; // WPU disabled, INT on rising edge, FOSC/4
// Prescaler assigned to TMR0, rate @ 1:256
WDTCON = 0b00010111; // Prescaler 1:65536
// period is 2 sec (RESET value)
PORTC = 0x00; // Clear PORTC
LATC = 0x00; // Clear PORTC latches
ANSELC = 0x00; // Set analogue ports to digital
TRISC = 0b00011011; // Set RC0(SCL), RC1(SDA) as inputs for I2C
//I2C SLAVE MODULE SET UP
SSP1STAT = 0b11000000; // Slew rate control disabled for standard
// speed mode (100 kHz and 1 MHz)
// SMbus input logic
SSP1CON1 = 0b00110110; // Enable serial port, I2C slave mode,
// 7-bit address
SSP1CON2bits.SEN = 0; // Clock stretching is enabled
//SSP1CON2bits.GCEN = 1; // General Call Enable
SSP1CON3bits.BOEN = 1; // SSPBUF is updated and NACK is generated
// for a received address/data byte,
// ignoring the state of the SSPOV bit
// only if the BF bit = 0
SSP1CON3bits.SDAHT = 1; // Minimum of 300 ns hold time on SDA after
// the falling edge of SCL
SSP1CON3bits.SBCDE = 0; // Enable slave bus collision detect interrupts
SSP1ADD = I2C_address; // Load the slave address
SSP1IF = 0; // Clear the serial port interrupt flag
BCL1IF = 0; // Clear the bus collision interrupt flag
BCL1IE = 1; // Enable bus collision interrupts
SSP1IE = 1; // Enable serial port interrupts
PEIE = 1; // Enable peripheral interrupts
GIE = 1; // Enable global interrupts
}//end initialize
/*************************** ISR ROUTINE **************************************/
void interrupt ISR(void)
{
if(SSP1IF) // check to see if SSP interrupt
{
if(!SSP1STATbits.R_nW) // master write (R_nW = 0)
{
// STATE 1
if(!SSP1STATbits.D_nA) // last byte was an address (D_nA = 0)
{
junk = SSP1BUF; // read buffer to clear BF
SSP1CON1bits.CKP = 1; // release CLK
}
// STATE 2
if(SSP1STATbits.D_nA) // last byte was data (D_nA = 1)
{
if (SSP1STATbits.BF){
// first time around data is an index pointer to array
if (first == 0){
dbuf = SSP1BUF;
first++;
}
// second time around data is data - so write to array using previous index
else {
I2C_Array[dbuf] = SSP1BUF;
first = 0;
}
}
if(SSP1CON1bits.WCOL) // Did a write collision occur?
{
SSP1CON1bits.WCOL = 0; // clear WCOL bit
junk = SSP1BUF; // clear SSPBUF
}
SSP1CON1bits.CKP = 1; // release CLK
}
// STATE 5
else {
SSP1CON1bits.CKP = 1;
dbuf = 0;
first = 0;
}
}
// STATE 3
if(SSP1STATbits.R_nW) // Master read (R_nW = 1)
{
if(!SSP1STATbits.D_nA) // last byte was an address (D_nA = 0)
{
junk = SSP1BUF; // dummy read to clear BF bit
SSP1BUF = I2C_Array[dbuf]; // load SSPBUF with data
first = 0;
SSP1CON1bits.CKP = 1; // release CLK
}
// STATE 4
if(SSP1STATbits.D_nA) // last byte was data
{
if (dbuf++ < RX_ELMNTS)
{ // Does data exceed number of allocated bytes?
SSP1BUF = I2C_Array[dbuf++]; // load SSPBUF with data
} // and increment index
else
{
junk = SSP1BUF; // dummy read to clear BF bit
}
first = 0;
SSP1CON1bits.CKP = 1; // release CLK
}
}
}
if(BCL1IF) // Did a bus collision occur?
{
junk = SSP1BUF; // clear SSPBUF
BCL1IF = 0; // clear BCL1IF
SSP1CON1bits.CKP = 1; // Release CLK
}
SSP1IF = 0; // clear SSP1IF
}
It helps to have PICKIT 3 or similar to do in circuit debugging, as it can get quite difficult to see what's happening on the PIC otherwise, I tried to get all this going with a PICKIT 2 but was flying blind.
I included some messages written out to UART to let me know the value of SSP1STAT and the SSP1BUF registers but the PICKIT 3 really helped me the most.
Example of insight I was able to gain from UART debugging the output of SSP1STAT register and the SSP1BUF register for each byte communicated
Next step is to wire the up the USB to power the Kindle rather than attaching a PSU to the battery headers.
Then it's time to delve into some OS hacking on the Kindle and see if I can get an old laptop Wifi card to replace the 3G card inside.