365 lines
11 KiB
C

/*
* i2c.c - functions to work with HW I2C
*
* Copyright 2015 Edward V. Emelianoff <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
/*
* I2C_FREQR - peripherial input clock (>=1MHz - standard, >=4MHz - fast)
* + clock control & rise time
* I2C_CR1 - enable
* I2C_CR2 - start bit (generate + set SB bit + interrupt if ITEVTEN==1)
* writing can be in 7 & 10 bit modes
*
* ** 7bit transmit**
* 1) start; 2) read SR1 & write address to DR; 3) check ADDR, if ACK received
* it would be 1 - clear it by read SR1 & SR3, wait for TXE==1 & BTF==1 (ready for transmit)
* and send data writing to DR; to send EOT set STOP=1
*
* ** 10bit transmit**
* 1) start; 2) read SR1 & write 11110xx0 (xx - MS bits of address) to DR;
* 3) ADD10=1, clear it by reading SR1 & write last 8 bits of address to DR;
* 4) similar to 3) for 7bits
*
* ** receive **
* 1) clear ADDR after sending address
* 2) set ACK if need to generate ACK bit
* 3) check RXNE or get data by interrupt (if ITEVTEN==1 & ITBUFEN==1)
* in case of byte transfer finished, BTF=1 (to clear it, read SR1 & DR)
* in 10bit receive mode send start & 11110xx1 after sending address
* in 7bit receive mode send address with LSB set
*
* TRA bit indicates I2C mode
*
* ** close **
* Method 1 (higher interrupts):
* 1) reset ACK=0 to generate NACK; 2) to generate Stop/Restart set STOP/START
* all this should be done at second last byte reading (to get a single byte
* do this just after ADDR sent); 3) read last byte
*
* Method 2 (low interrupt priority or polling, valid only for N>2):
* 1) not read DR & wait for RXNE==1&&BTF==1
* 2) clear ACK
* 3) read DR (DATA_N-2) & set STOP/START
* 4) read DR (DATA_N-1), read DR (DATA_N)
*
* to read 1 byte use Method 1;
* to read 2 bytes with Method2, set POS&ACK, wait ADDR, clear ADDR&ACK,
* wait BTF, set STOP, read DR twice
*
*
* *** REGISTERS ***
* I2C_CR1: | NOSTRETCH | ENGC | reserved[5:1] | PE |
* NOSTRETCH - ==1 to slave mode
* ENGC - generall call EN/DIS
* PE - ==1 to enable I2C
*
* I2C_CR2: | SWRST | res[6:4] | POS | ACK | STOP | START |
* SWRST - ==1 to software reset
* POS - ACK position (==0 for current byte, ==1 for next byte)
* ACK - send ACK after reading byte
* STOP - send STOP after reading byte
* START - send repeated START
* DO NOT make any changes with I2C_CR2 when sending START/STOP
* before they'll be cleared by hardware!
*
* I2C_FREQR: | res[7:6] | FREQ[5:0] |
* FREQ - I2C clock frequency (in MHz, from 1 to 24)
*
* I2C_OARL: | ADD[7:1] | ADD0 | - LSB of address
* ADD0 - 0th bit of address in 10bit mode, don't care in 7bit mode
*
* I2C_OARH: | ADDMODE | ADDCONF | res[5:3] | ADD[9:8] | res |
* ADDMODE - 0 for 7bit, 1 for 10bit address
* ADDCONF - must always be written as 1 ("address is set")
*
* I2C_DR - data I/O register
*
* I2C_SR1: | TXE | RXNE | res | STOPF | ADD10 | BTF | ADDR | SB |
* TXE - set when DR is empty for transmission
* RXNE - set when DR not empty in receiver mode
* STOPF - stop detected (slave mode)
* ADD10 - 10 bit header sent (first byte)
* BTF - byte transfer finished (set when NOSTRETCH==0)
* ADDR - address sent (not set if NACK)
* SB - start condition
*
* I2C_SR2: | res[7:6] | WUFH | res | OVR | AF | ARLO | BERR |
* WUFH - wakeup from halt
* OVR - overrun/underrun
* AF - acknowledge failure (must be cleared by software)
* ARLO - arbitration lost (another master on line, clear it by SW)
* BERR - bus error (clear by SW)
*
* I2C_SR3: | res[7:5] | GENCALL | res | TRA | BUSY | MSL |
* GENCALL - general call in slave mode
* TRA - transmitter(1)/receiver(0) flag
* BUSY - bus busy (another communication detected)
* MSL - slave(0)/master(1) mode, set/cleared by HW
*
* I2C_ITR: | res[7:3] | ITBUFEN | ITEVTEN | ITERREN |
* ITBUFEN - enable buffer interrupt
* ITEVTEN - enable event interrupt
* ITERREN - enable error interrupt
*
* I2C_CCRL: | CCR[7:0] |
* SCLH clock in master mode
* standard: T = 2*CCR*tmaster, Tlow = Thigh = CCR*tmaster
* fast: (DUTY==0) T = 3*CCR*tmaster, Thigh = CCR*tmaster, Tlow = 2*Thigh
* (DUTY==1) T = 25*CCR*tmaster, Thigh=9*CCR*tmaster, Tlow=16*CCR*tmaster
* fmaster - clock by I2C_FREQR
* minimum allowed value is 4 (exept FAST DUTY, when it is 1)
*
* I2C_CCRH: | F/S | DUTY | res[5:4] | CCR[11:8] |
* F/S - == 1 in fast mode
* DUTY - (see upper)
* IN standard mode 100kHz is: FREQR=8, CCR=0x28
*
* I2C_TRISER: | res[7:6] | TRISE[5:0] |
* TRISE - maximum rise time (0x09 for standard @100kHz)
*
*/
#include "i2c.h"
#include "ports_definition.h"
static U8 addr7r = 0, addr7w = 0;
extern volatile unsigned long Global_time;
static U16 _c;
#define I2C_WAIT(evt, tmo) do{ \
for(_c = 1000U*tmo; _c && !(evt); _c--); \
if(!_c){ret = I2C_TMOUT; goto eotr;}}while(0)
static U8 _d;
#define I2C_LINEWAIT() do{ for(_d = 0; _d < 16; _d++){\
for(_c = 0; (_c < 60000) && (I2C_SR3 & 2); _c++); \
if(!(I2C_SR3 & 2)) break; I2C_CR2 |= 2;} \
if(_d == 16) return I2C_LINEBUSY; }while(0)
/**
* configure 100kHz speed in standard mode & enable i2c
*/
void i2c_setup(){
// configure pins: PB5 - I2C_SDA; PB4 - I2C_SCL (both opendrain)
PORT(PB, ODR) |= GPIO_PIN4|GPIO_PIN5; // set to 1
PORT(PB, DDR) |= GPIO_PIN4|GPIO_PIN5;
PORT(PB, CR2) |= GPIO_PIN4|GPIO_PIN5; // fast mode
// Don't forget to connect pullup resistor to I2C foots
I2C_FREQR = 8; // 8MHz fmaster
I2C_TRISER = 9; // rise time 1000ns
I2C_CCRL = 80; // 100kHz
I2C_CCRH = 0;
I2C_ITR = 0; // disable all I2C interrupts
I2C_CR2 |= 4; // ACK
I2C_CR1 |= 1; // enable I2C
}
void i2c_set_addr7(U8 addr){
addr7w = addr << 1;
addr7r = addr7w | 1;
}
/**
* send one byte in 7bit address mode
* @param data - data to write
* @param stop - ==1 to send stop event
* @return I2C_OK if success errcode if fails
*/
i2c_status i2c_7bit_send_onebyte(U8 data, U8 stop){
i2c_status ret = I2C_LINEBUSY;
I2C_LINEWAIT();
I2C_CR2 |= 1; // send START
I2C_WAIT((I2C_SR1 & 1), 2); // wait for SB
I2C_DR = addr7w;
ret = I2C_NOADDR;
I2C_WAIT(((I2C_SR1 & 2) || I2C_SR2), 2); // wait for ADDR
if(I2C_SR2){ // NACK or other error
ret = I2C_NACK;
goto eotr;
}
ret = I2C_HWPROBLEM;
// clear ADDR reading SR3
if(!(I2C_SR3 & 4)) goto eotr; // interface is in receiver mode
I2C_WAIT((I2C_SR1 & 0x80), 2); // wait for TXE
I2C_DR = data; // send data
I2C_WAIT(((0x84 == (I2C_SR1 & 0x84)) || I2C_SR2), 15); // wait for TXE & BTF
if(!I2C_SR2) ret = I2C_OK;
else ret = I2C_NACK;
eotr:
I2C_SR2 = 0; // clear all error flagss
if(stop){
I2C_CR2 |= 2; // set STOP
while(I2C_CR2 & 2); // wait for STOP sent
}
return ret;
}
/**
* send datalen bytes over I2C
* @param data - data to write
* @param datalen - amount of bytes in data array
* @param stop - ==1 to send stop event
* return I2C_OK if OK
*/
i2c_status i2c_7bit_send(U8 *data, U8 datalen, U8 stop){
i2c_status ret = I2C_LINEBUSY;
I2C_LINEWAIT();
I2C_CR2 |= 1; // send START
ret = I2C_TMOUT;
I2C_WAIT((I2C_SR1 & 1), 2); // wait for SB
I2C_DR = addr7w;
ret = I2C_NOADDR;
I2C_WAIT(((I2C_SR1 & 2) || I2C_SR2), 2); // wait for ADDR
if(I2C_SR2){ // NACK or other error
ret = I2C_NACK;
goto eotr;
}
ret = I2C_HWPROBLEM;
if(!(I2C_SR3 & 4)) goto eotr; // interface is in receiver mode
while(datalen--){
I2C_WAIT(((I2C_SR1 & 0x80) || I2C_SR2), 2); // wait for TXE
if(I2C_SR2){
ret = I2C_NACK;
goto eotr;
}
I2C_DR = *data++; // send data
}
I2C_WAIT(((0x84 == (I2C_SR1 & 0x84)) || I2C_SR2), 15); // wait for TXE & BTF
if(!I2C_SR2) ret = I2C_OK;
else ret = I2C_NACK;
eotr:
I2C_SR2 = 0; // clear all error flags
if(stop){
I2C_CR2 |= 2; // set STOP
while(I2C_CR2 & 2); // wait for STOP sent
}
return ret;
}
/**
* get one byte by I2C
* @param data - data to read (one byte)
* @param wait - ==1 to wait while LINEBUSY (can send STOP before reading)
* @return I2C_OK if ok || error code
*/
i2c_status i2c_7bit_receive_onebyte(U8 *data, U8 wait){
i2c_status ret = I2C_LINEBUSY;
if(wait)
I2C_LINEWAIT();
I2C_CR2 |= 5; // send START & set ACK
ret = I2C_TMOUT;
I2C_WAIT((I2C_SR1 & 1), 2); // wait for SB
I2C_DR = addr7r; // send address & read bit
ret = I2C_NOADDR;
I2C_WAIT(((I2C_SR1 & 2) || I2C_SR2), 2); // wait for ADDR
if(I2C_SR2){ // NACK or other error
ret = I2C_NACK;
goto eotr;
}
disableInterrupts();
// clear POS|ACK
I2C_CR2 &= ~0x0c;
ret = I2C_HWPROBLEM;
// read SR3 to clear ADDR
if((I2C_SR3 & 4)){
enableInterrupts();
goto eotr; // interface is in transmitter mode
}
// set STOP
I2C_CR2 |= 2;
enableInterrupts();
// wait for RxNE
I2C_WAIT(((I2C_SR1 & 0x40) || I2C_SR2), 2);
if(I2C_SR2){
ret = I2C_NACK;
goto eotr; // error
}
ret = I2C_OK;
// read data clearing RxNE
*data = I2C_DR;
eotr:
I2C_SR2 = 0; // clear all error flags
if(!I2C_CR2 & 2) I2C_CR2 |= 2;
while(I2C_CR2 & 2); // wait for STOP sent
return ret;
}
/**
* receive 2 bytes by I2C
* @param data - data to read (two bytes array, 0 first)
* @param wait - ==1 to wait while LINEBUSY (can send STOP before reading)
* @return I2C_OK if ok || error code
*/
i2c_status i2c_7bit_receive_twobyte(U8 *data, U8 wait){
i2c_status ret = I2C_LINEBUSY;
if(wait)
I2C_LINEWAIT();
I2C_CR2 |= 5; // send START & set ACK
ret = I2C_TMOUT;
I2C_WAIT((I2C_SR1 & 1), 2); // wait for SB
I2C_DR = addr7r; // send address & read bit
// set POS|ACK
I2C_CR2 |= 0x0c;
ret = I2C_NOADDR;
I2C_WAIT(((I2C_SR1 & 2) || I2C_SR2), 2); // wait for ADDR
if(I2C_SR2){ // NACK or other error
ret = I2C_NACK;
goto eotr;
}
disableInterrupts();
ret = I2C_HWPROBLEM;
// read SR3 to clear ADDR
if(I2C_SR3 & 4){
enableInterrupts();
goto eotr; // interface is in transmitter mode
}
// clear ACK
I2C_CR2 &= ~4;
enableInterrupts();
// wait for BTF
I2C_WAIT(((I2C_SR1 & 4) || I2C_SR2), 15);
if(I2C_SR2){
ret = I2C_NACK;
goto eotr;
}
// patch from ERRATA
disableInterrupts();
// set STOP
I2C_CR2 |= 2;
ret = I2C_OK;
// read data
data[0] = I2C_DR;
enableInterrupts();
data[1] = I2C_DR;
eotr:
I2C_SR2 = 0; // clear all error flags
if(!I2C_CR2 & 2) I2C_CR2 |= 2;
while(I2C_CR2 & 2); // wait for STOP sent
// clear POS
I2C_CR2 &= ~8;
return ret;
}
INTERRUPT_HANDLER(I2C_IRQHandler, 19){
}