/* * i2c.c - functions to work with HW I2C * * Copyright 2015 Edward V. Emelianoff * * 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){ }