You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
816 lines
24 KiB
816 lines
24 KiB
/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* |
|
|
|
(c) 2017 night_ghost@ykoctpa.ru |
|
|
|
* I2CDriver.cpp --- AP_HAL_F4Light I2C driver. |
|
* |
|
*/ |
|
#pragma GCC optimize ("O2") |
|
|
|
#include <AP_HAL/AP_HAL.h> |
|
#include <AP_Param_Helper/AP_Param_Helper.h> |
|
|
|
#include "I2CDevice.h" |
|
#include <i2c.h> |
|
|
|
using namespace F4Light; |
|
|
|
extern const AP_HAL::HAL& hal; |
|
|
|
F4Light::Semaphore I2CDevice::_semaphores[3]; // 2 HW and 1 SW |
|
|
|
const timer_dev * I2CDevice::_timers[3] = { // one timer per bus for all devices |
|
TIMER4, // for bus 0 so not will be used on AirbotV2 boards when it used for PPM_IN |
|
TIMER10, |
|
TIMER9, |
|
}; |
|
|
|
bool I2CDevice::lateInitDone=false; |
|
|
|
|
|
I2CDevice * I2CDevice::devices[MAX_I2C_DEVICES]; // links to all created devices |
|
uint8_t I2CDevice::dev_count; // number of devices |
|
|
|
#ifdef I2C_DEBUG |
|
I2C_State I2CDevice::log[I2C_LOG_SIZE] IN_CCM; |
|
uint8_t I2CDevice::log_ptr=0; |
|
#endif |
|
|
|
|
|
|
|
void I2CDevice::lateInit() { |
|
lateInitDone=true; |
|
} |
|
|
|
|
|
I2CDevice::I2CDevice(uint8_t bus, uint8_t address) |
|
: _bus(bus) |
|
, _address(address) |
|
, _retries(5) |
|
, _lockup_count(0) |
|
, _initialized(false) |
|
, _slow(false) |
|
, _failed(false) |
|
, need_reset(false) |
|
, _dev(NULL) |
|
{ |
|
|
|
|
|
// store link to created devices |
|
if(dev_count<MAX_I2C_DEVICES){ |
|
devices[dev_count++] = this; // links to all created devices |
|
} |
|
} |
|
|
|
I2CDevice::~I2CDevice() { |
|
for(int i=0;i<dev_count;i++){ |
|
if(devices[i] == this){ |
|
devices[i] = NULL; |
|
} |
|
} |
|
} |
|
|
|
|
|
void I2CDevice::init(){ |
|
if(!lateInitDone) { |
|
((HAL_F4Light&) hal).lateInit(); |
|
} |
|
|
|
if(need_reset) _do_bus_reset(); |
|
|
|
if(_failed) return; |
|
|
|
if(_initialized) return; |
|
|
|
const i2c_dev *dev=NULL; |
|
|
|
switch(_bus) { |
|
case 0: // this is always internal bus |
|
#if defined(I2C1_SDA) && defined(I2C1_SCL) && !defined(BOARD_I2C1_DISABLE) |
|
_offs =0; |
|
#if defined(BOARD_I2C_BUS_SLOW) && BOARD_I2C_BUS_SLOW==0 |
|
_slow=true; |
|
#endif |
|
|
|
#if defined(BOARD_SOFT_I2C) || defined(BOARD_SOFT_I2C1) |
|
if(s_i2c==NULL) s_i2c = new Soft_I2C; |
|
{ // isolate p_sda & p_scl |
|
const stm32_pin_info &p_sda = PIN_MAP[_I2C1->sda_pin]; |
|
const stm32_pin_info &p_scl = PIN_MAP[_I2C1->scl_pin]; |
|
s_i2c->init_hw( |
|
p_scl.gpio_device, p_scl.gpio_bit, |
|
p_sda.gpio_device, p_sda.gpio_bit, |
|
_timers[_bus] |
|
); |
|
} |
|
#else |
|
dev = _I2C1; |
|
#endif |
|
break; |
|
#else |
|
return; |
|
#endif |
|
|
|
case 1: // flexi port - I2C2 |
|
#if defined(I2C2_SDA) && defined(I2C2_SCL) && !defined( BOARD_I2C2_DISABLE) && !defined(BOARD_HAS_UART3) // in this case I2C on FlexiPort will be bus 2 |
|
|
|
_offs = 2; |
|
#if defined(BOARD_I2C_BUS_SLOW) && BOARD_I2C_BUS_SLOW==1 |
|
_slow=true; |
|
#endif |
|
|
|
#if defined(BOARD_SOFT_I2C) || defined(BOARD_SOFT_I2C2) |
|
if(s_i2c==NULL) s_i2c = new Soft_I2C; |
|
{ // isolate p_sda & p_scl |
|
const stm32_pin_info &p_sda = PIN_MAP[_I2C2->sda_pin]; |
|
const stm32_pin_info &p_scl = PIN_MAP[_I2C2->scl_pin]; |
|
s_i2c->init_hw( |
|
p_scl.gpio_device, p_scl.gpio_bit, |
|
p_sda.gpio_device, p_sda.gpio_bit, |
|
_timers[_bus] |
|
); |
|
} |
|
#else |
|
dev = _I2C2; |
|
#endif |
|
break; |
|
#else |
|
return; // not initialized so always returns false |
|
#endif |
|
|
|
case 2: // this bus can use only soft I2C driver |
|
#if defined(BOARD_I2C_BUS_SLOW) && BOARD_I2C_BUS_SLOW==2 |
|
_slow=true; |
|
#endif |
|
|
|
#ifdef BOARD_I2C_FLEXI |
|
if(hal_param_helper->_flexi_i2c){ // move external I2C to flexi port |
|
#if defined(BOARD_SOFT_I2C) || defined(BOARD_SOFT_I2C3) |
|
if(s_i2c==NULL) s_i2c = new Soft_I2C; |
|
{ // isolate p_sda & p_scl |
|
const stm32_pin_info &p_sda = PIN_MAP[_I2C2->sda_pin]; |
|
const stm32_pin_info &p_scl = PIN_MAP[_I2C2->scl_pin]; |
|
s_i2c->init_hw( |
|
p_scl.gpio_device, p_scl.gpio_bit, |
|
p_sda.gpio_device, p_sda.gpio_bit, |
|
_timers[_bus] |
|
); |
|
} |
|
#else |
|
dev = _I2C2; |
|
#endif |
|
} else |
|
#endif |
|
{ // external I2C on Input port |
|
#if defined(BOARD_SOFT_SCL) && defined(BOARD_SOFT_SDA) |
|
if(s_i2c==NULL) s_i2c = new Soft_I2C; |
|
s_i2c->init_hw( |
|
PIN_MAP[BOARD_SOFT_SCL].gpio_device, PIN_MAP[BOARD_SOFT_SCL].gpio_bit, |
|
PIN_MAP[BOARD_SOFT_SDA].gpio_device, PIN_MAP[BOARD_SOFT_SDA].gpio_bit, |
|
_timers[_bus] |
|
); |
|
#endif |
|
} |
|
break; |
|
|
|
// TODO |
|
#if defined(I2C3_SDA) && defined(I2C3_SCL) |
|
#endif |
|
|
|
default: |
|
return; |
|
} |
|
_dev = dev; // remember |
|
|
|
|
|
if(_dev) { |
|
i2c_init(_dev, _offs, _slow?I2C_250KHz_SPEED:I2C_400KHz_SPEED); |
|
|
|
}else if (s_i2c){ |
|
s_i2c->init( ); |
|
|
|
if(_slow) { |
|
s_i2c->set_low_speed(true); |
|
} |
|
} |
|
else { // neither hardware nor software initalization was successful |
|
return; |
|
} |
|
_initialized=true; |
|
} |
|
|
|
|
|
|
|
void I2CDevice::register_completion_callback(Handler h) { |
|
if(h && _completion_cb) {// IOC from last call still not called - some error occured so bus reset needed |
|
_completion_cb=0; |
|
_do_bus_reset(); |
|
} |
|
|
|
_completion_cb=h; |
|
} |
|
|
|
|
|
|
|
|
|
bool I2CDevice::transfer(const uint8_t *send, uint32_t send_len, uint8_t *recv, uint32_t recv_len) |
|
{ |
|
|
|
uint16_t retries=_retries; |
|
|
|
again: |
|
|
|
|
|
uint32_t ret=0; |
|
uint8_t last_op=0; |
|
|
|
if(!_initialized) { |
|
init(); |
|
if(!_initialized) return false; |
|
} |
|
|
|
|
|
if(!_dev){ // no hardware so use soft I2C |
|
|
|
if(recv_len) memset(recv, 0, recv_len); // for DEBUG |
|
|
|
if(recv_len==0){ // only write |
|
ret=s_i2c->writeBuffer( _address, send_len, send ); |
|
}else if(send_len==1){ // only read - send byte is address |
|
ret=s_i2c->read(_address, *send, recv_len, recv); |
|
} else { |
|
ret=s_i2c->transfer(_address, send_len, send, recv_len, recv); |
|
} |
|
|
|
if(ret == I2C_NO_DEVICE) |
|
return false; |
|
|
|
if(ret == I2C_OK) |
|
return true; |
|
|
|
if((_retries-retries) > 0) { // don't reset and count for fixed at 2nd try errors |
|
_lockup_count ++; |
|
last_error = ret; |
|
|
|
if(!s_i2c->bus_reset()) return false; |
|
} |
|
|
|
_dev->state->busy = false; |
|
|
|
if(retries--) goto again; |
|
|
|
return false; |
|
} // software I2C |
|
|
|
// Hardware |
|
#ifdef I2C_DEBUG |
|
{ |
|
I2C_State &sp = log[log_ptr]; // remember last operation |
|
sp.st_sr1 = _dev->I2Cx->SR1; |
|
sp.st_sr2 = _dev->I2Cx->SR2; |
|
} |
|
#endif |
|
|
|
// 1st wait for bus free |
|
uint32_t t=Scheduler::_micros(); |
|
while(_dev->state->busy){ |
|
hal_yield(0); |
|
if(Scheduler::_micros() - t > 5000) { |
|
// grab_count++; |
|
break; |
|
} |
|
} |
|
_dev->state->busy=true; |
|
|
|
if(recv_len==0) { // only write |
|
last_op=1; |
|
ret = i2c_write(_address, send, send_len); |
|
} else { |
|
last_op=0; |
|
ret = i2c_read( _address, send, send_len, recv, recv_len); |
|
} |
|
|
|
|
|
#ifdef I2C_DEBUG |
|
I2C_State &sp = log[log_ptr]; // remember last operation |
|
|
|
sp.time = Scheduler::_micros(); |
|
sp.bus =_bus; |
|
sp.addr =_address; |
|
sp.send_len = send_len; |
|
sp.recv_len = recv_len; |
|
sp.ret = ret; |
|
sp.sr1 = _dev->I2Cx->SR1; |
|
sp.sr2 = _dev->I2Cx->SR2; |
|
sp.cr1 = _dev->I2Cx->CR1; |
|
sp.state = _state; |
|
sp.pos = I2C_FINISH; |
|
if(log_ptr<I2C_LOG_SIZE-1) log_ptr++; |
|
else log_ptr=0; |
|
#endif |
|
|
|
|
|
|
|
if(ret == I2C_PENDING) return true; // transfer with callback |
|
|
|
if(ret == I2C_OK) { |
|
_dev->state->busy=false; |
|
return true; |
|
} |
|
|
|
// something went wrong and completion callback never will be called, so release bus semaphore |
|
if(_completion_cb) { |
|
_completion_cb = 0; // to prevent 2nd bus reset |
|
register_completion_callback((Handler)0); |
|
} |
|
|
|
if(ret == I2C_ERR_STOP || ret == I2C_STOP_BERR || ret == I2C_STOP_BUSY) { // bus or another errors on Stop, or bus busy after Stop. |
|
// Data is good but bus reset required |
|
need_reset = true; |
|
_initialized=false; // will be reinitialized at next transfer |
|
|
|
_dev->I2Cx->CR1 |= I2C_CR1_SWRST; // set for some time |
|
|
|
// we not count such errors as _lockup_count |
|
|
|
Revo_handler h = { .mp=FUNCTOR_BIND_MEMBER(&I2CDevice::do_bus_reset, void) }; // schedule reset as io_task |
|
Scheduler::_register_io_process(h.h, IO_ONCE); |
|
|
|
_dev->state->busy=false; |
|
return true; // data is OK |
|
} |
|
|
|
if(ret != I2C_NO_DEVICE) { // for all errors except NO_DEVICE do bus reset |
|
|
|
if(ret == I2C_BUS_BUSY) { |
|
_dev->I2Cx->CR1 |= I2C_CR1_SWRST; // set SoftReset for some time |
|
hal_yield(0); |
|
_dev->I2Cx->CR1 &= (uint16_t)(~I2C_CR1_SWRST); // clear SoftReset flag |
|
_dev->I2Cx->CR1 |= I2C_CR1_PE; // enable |
|
} |
|
|
|
if((_retries-retries) > 0 || ret==I2C_BUS_ERR){ // not reset bus or log error on 1st try, except ArbitrationLost error |
|
last_error = ret; // remember |
|
last_error_state = _state; // remember to show |
|
if(last_op) last_error+=50; // to distinguish read and write errors |
|
|
|
_lockup_count ++; |
|
_initialized=false; // will be reinitialized at next transfer |
|
|
|
_do_bus_reset(); |
|
|
|
if(_failed) { |
|
_dev->state->busy=false; |
|
return false; |
|
} |
|
} |
|
} |
|
_dev->state->busy=false; |
|
|
|
if(retries--) goto again; |
|
|
|
return false; |
|
} |
|
|
|
|
|
void I2CDevice::do_bus_reset(){ // public - with semaphores |
|
if(_semaphores[_bus].take(HAL_SEMAPHORE_BLOCK_FOREVER)){ |
|
_do_bus_reset(); |
|
_semaphores[_bus].give(); |
|
} |
|
} |
|
|
|
void I2CDevice::_do_bus_reset(){ // private |
|
_dev->state->busy=true; |
|
_dev->I2Cx->CR1 &= (uint16_t)(~I2C_CR1_SWRST); // clear soft reset flag |
|
|
|
if(!need_reset) return; // already done |
|
|
|
i2c_deinit(_dev); // disable I2C hardware |
|
if(!i2c_bus_reset(_dev)) { |
|
_failed = true; // can't do it in limited time |
|
} |
|
need_reset = false; // done |
|
_dev->state->busy=false; |
|
} |
|
|
|
|
|
bool I2CDevice::read_registers_multiple(uint8_t first_reg, uint8_t *recv, |
|
uint32_t recv_len, uint8_t times){ |
|
|
|
while(times--) { |
|
bool ret = read_registers(first_reg, recv, recv_len); |
|
if(!ret) return false; |
|
recv += recv_len; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
enum I2C_state { |
|
I2C_want_SB=0, |
|
I2C_want_ADDR, // 1 |
|
I2C_want_TXE, // 2 |
|
I2C_want_RX_SB, // 3 |
|
I2C_want_RX_ADDR,// 4 |
|
I2C_want_RXNE, // 5 |
|
I2C_done // 6 |
|
} ; |
|
|
|
|
|
/* |
|
moved from low layer to be properly integrated to multitask |
|
|
|
*/ |
|
|
|
|
|
|
|
/* Send a buffer to the i2c port */ |
|
uint32_t I2CDevice::i2c_write(uint8_t addr, const uint8_t *tx_buff, uint8_t len) { |
|
|
|
uint32_t ret = wait_stop_done(true); |
|
if(ret!=I2C_OK) return ret; |
|
|
|
_addr=addr; |
|
_tx_buff=tx_buff; |
|
_tx_len=len; |
|
_rx_len=0; // only write |
|
|
|
|
|
i2c_set_isr_handler(_dev, Scheduler::get_handler(FUNCTOR_BIND_MEMBER(&I2CDevice::isr_ev, void))); |
|
|
|
_state = I2C_want_SB; |
|
_error = I2C_ERR_TIMEOUT; |
|
|
|
// Bus got! enable Acknowledge for our operation |
|
_dev->I2Cx->CR1 |= I2C_CR1_ACK; |
|
_dev->I2Cx->CR1 &= ~I2C_NACKPosition_Next; |
|
|
|
// need to wait until transfer complete |
|
uint32_t t = hal_micros(); |
|
uint32_t timeout = i2c_bit_time * 9 * (len+1) * 8 + 100; // time to transfer all data *8 plus 100uS |
|
|
|
_task = Scheduler::get_current_task();// if function called from task - store it and pause |
|
|
|
EnterCriticalSection; |
|
// Send START condition |
|
_dev->I2Cx->CR1 |= I2C_CR1_START; |
|
_dev->I2Cx->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN; // Enable interrupts |
|
|
|
if(_task) Scheduler::task_pause(timeout); |
|
LeaveCriticalSection; |
|
|
|
if(_completion_cb) return I2C_PENDING; |
|
|
|
while (hal_micros() - t < timeout && _error==I2C_ERR_TIMEOUT) { |
|
hal_yield(0); |
|
} |
|
|
|
if(_error==I2C_ERR_TIMEOUT) finish_transfer(); |
|
|
|
return _error; |
|
} |
|
|
|
uint32_t I2CDevice::i2c_read(uint8_t addr, const uint8_t *tx_buff, uint8_t txlen, uint8_t *rx_buff, uint8_t rxlen) |
|
{ |
|
uint32_t ret = wait_stop_done(false); // wait for bus release from previous transfer and force it if needed |
|
if(ret!=I2C_OK) return ret; |
|
|
|
_addr=addr; |
|
_tx_buff=tx_buff; |
|
_tx_len=txlen; |
|
_rx_buff=rx_buff; |
|
_rx_len=rxlen; |
|
|
|
|
|
i2c_set_isr_handler(_dev, Scheduler::get_handler(FUNCTOR_BIND_MEMBER(&I2CDevice::isr_ev, void))); |
|
|
|
_state = I2C_want_SB; |
|
_error = I2C_ERR_TIMEOUT; |
|
|
|
_dev->I2Cx->CR1 &= ~I2C_NACKPosition_Next; // I2C_NACKPosition_Current |
|
_dev->I2Cx->CR1 |= I2C_CR1_ACK; // enable Acknowledge for our operation |
|
|
|
uint32_t t = hal_micros(); |
|
uint32_t timeout = i2c_bit_time * 9 * (txlen+rxlen) * 8 + 100; // time to transfer all data *8 plus 100uS |
|
_task = Scheduler::get_current_task(); // if function called from task - store it and pause |
|
|
|
EnterCriticalSection; |
|
#ifdef I2C_DEBUG |
|
{ |
|
I2C_State &sp = log[log_ptr]; // remember last operation |
|
|
|
sp.time = Scheduler::_micros(); |
|
sp.cr1 = _dev->I2Cx->CR1; |
|
sp.sr1 = _dev->I2Cx->SR1; |
|
sp.sr2 = _dev->I2Cx->SR2; |
|
sp.state = _state; |
|
sp.pos = I2C_START; |
|
if(log_ptr<I2C_LOG_SIZE-1) log_ptr++; |
|
else log_ptr=0; |
|
} |
|
#endif |
|
|
|
_dev->I2Cx->CR1 |= I2C_CR1_START; // Send START condition |
|
if(_task) Scheduler::task_pause(timeout); |
|
_dev->I2Cx->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN; // Enable interrupts |
|
|
|
#ifdef I2C_DEBUG |
|
I2C_State &sp = log[log_ptr]; // remember last operation |
|
|
|
sp.time = Scheduler::_micros(); |
|
sp.cr1 = _dev->I2Cx->CR1; |
|
sp.sr1 = _dev->I2Cx->SR1; |
|
sp.sr2 = _dev->I2Cx->SR2; |
|
sp.state = _state; |
|
sp.pos = I2C_START; |
|
if(log_ptr<I2C_LOG_SIZE-1) log_ptr++; |
|
else log_ptr=0; |
|
#endif |
|
LeaveCriticalSection; |
|
|
|
if(_completion_cb) return I2C_PENDING; |
|
|
|
// need to wait until DMA transfer complete |
|
while (hal_micros() - t < timeout && _error==I2C_ERR_TIMEOUT) { |
|
hal_yield(0); |
|
} |
|
|
|
if(_error==I2C_ERR_TIMEOUT) finish_transfer(); |
|
|
|
return _error; |
|
} |
|
|
|
void I2CDevice::isr_ev(){ |
|
bool err; |
|
|
|
// get err parameter |
|
asm volatile("MOV %0, r1\n\t" : "=rm" (err)); |
|
|
|
uint32_t sr1itflags = _dev->I2Cx->SR1; |
|
uint32_t itsources = _dev->I2Cx->CR2; |
|
|
|
if(err){ |
|
|
|
/* I2C Bus error interrupt occurred ----------------------------------------*/ |
|
if(((sr1itflags & I2C_BIT_BERR) != RESET) && ((itsources & I2C_IE_ERR) != RESET)) { /* Clear BERR flag */ |
|
_dev->I2Cx->SR1 = (uint16_t)(~I2C_BIT_BERR); // Errata 2.4.6 |
|
} |
|
|
|
/* I2C Arbitration Loss error interrupt occurred ---------------------------*/ |
|
if(((sr1itflags & I2C_BIT_ARLO) != RESET) && ((itsources & I2C_IE_ERR) != RESET)) { |
|
_error = I2C_BUS_ERR; |
|
|
|
/* Clear ARLO flag */ |
|
_dev->I2Cx->SR1 = (uint16_t)(~I2C_BIT_ARLO); // reset them |
|
} |
|
|
|
/* I2C Acknowledge failure error interrupt occurred ------------------------*/ |
|
if(((sr1itflags & I2C_BIT_AF) != RESET) && ((itsources & I2C_IE_ERR) != RESET)) { |
|
/* Clear AF flag */ |
|
_dev->I2Cx->SR1 = (uint16_t)(~I2C_BIT_AF); // reset it |
|
|
|
if(_state == I2C_want_ADDR) { // address transfer |
|
_error = I2C_NO_DEVICE; |
|
} else if(_state == I2C_want_RX_ADDR) { // restart |
|
_error = I2C_ERR_REGISTER; |
|
} else { |
|
_error = I2C_ERROR; |
|
} |
|
|
|
_dev->I2Cx->CR1 |= I2C_CR1_STOP; /* Generate Stop */ |
|
} |
|
|
|
#ifdef I2C_DEBUG |
|
I2C_State &sp = log[log_ptr]; // remember last operation |
|
|
|
sp.time = Scheduler::_micros(); |
|
sp.sr1 = sr1itflags; |
|
sp.sr2 = _dev->I2Cx->SR2; |
|
sp.cr1 = _dev->I2Cx->CR1; |
|
sp.state = _state; |
|
sp.pos = I2C_ERR; |
|
if(log_ptr<I2C_LOG_SIZE-1) log_ptr++; |
|
else log_ptr=0; |
|
#endif |
|
|
|
if(_error) { // смысла ждать больше нет |
|
finish_transfer(); |
|
} |
|
}else{ |
|
|
|
/* SB Set ----------------------------------------------------------------*/ |
|
if(((sr1itflags & I2C_BIT_SB & I2C_BIT_MASK) != RESET) && ((itsources & I2C_IE_EVT) != RESET)) { |
|
// Send address for write |
|
if(_tx_len){ |
|
i2c_send_address(_dev, _addr<<1, I2C_Direction_Transmitter); |
|
_state = I2C_want_ADDR; |
|
} else { |
|
i2c_send_address(_dev, _addr<<1, I2C_Direction_Receiver); |
|
_state = I2C_want_RX_ADDR; |
|
} |
|
|
|
_dev->I2Cx->CR1 &= (uint16_t)(~I2C_CR1_STOP); /* clear STOP condition - just to touch CR1*/ |
|
} |
|
/* ADDR Set --------------------------------------------------------------*/ |
|
else if(((sr1itflags & I2C_BIT_ADDR & I2C_BIT_MASK) != RESET) && ((itsources & I2C_IE_EVT) != RESET)) { |
|
/* Clear ADDR register by reading SR1 then SR2 register (SR1 has already been read) */ |
|
|
|
if(_tx_len) { // transmit |
|
// all flags set before |
|
_state = I2C_want_TXE; |
|
}else { // receive |
|
_dev->I2Cx->CR2 |= I2C_CR2_ITBUFEN; // enable RXNE interrupt |
|
if(_rx_len == 1) { // Disable Acknowledge for 1-byte transfer |
|
_dev->I2Cx->CR1 &= ~I2C_CR1_ACK; |
|
} else { |
|
_dev->I2Cx->CR1 |= I2C_CR1_ACK; |
|
} |
|
_state = I2C_want_RXNE; |
|
} |
|
} |
|
|
|
uint32_t sr2itflags = _dev->I2Cx->SR2; // read SR2 - ADDR is cleared |
|
|
|
if((itsources & I2C_IE_BUF) != RESET ){ // data io |
|
|
|
if((sr1itflags & I2C_BIT_TXE & I2C_BIT_MASK) != RESET) {// TXE set |
|
if((sr2itflags & (I2C_BIT_TRA) & I2C_BIT_MASK) != RESET) { // I2C in mode Transmitter |
|
|
|
if(_tx_len) { |
|
_dev->I2Cx->DR = *_tx_buff++; // 1 byte |
|
_tx_len--; |
|
} else { // tx is over and last byte is sent |
|
_dev->I2Cx->CR2 &= ~I2C_CR2_ITBUFEN; // disable TXE interrupt |
|
} |
|
} |
|
} |
|
if((sr1itflags & I2C_BIT_RXNE & I2C_BIT_MASK) != RESET) { // RXNE set |
|
if(_rx_len && !_tx_len) { |
|
*_rx_buff++ = (uint8_t)(_dev->I2Cx->DR); |
|
_rx_len -= 1; // 1 byte done |
|
|
|
if(_rx_len == 1) { // last second byte |
|
_dev->I2Cx->CR1 &= ~I2C_CR1_ACK; // Disable Acknowledgement - send NACK for last byte |
|
_dev->I2Cx->CR1 |= I2C_CR1_STOP; // Send STOP |
|
} else if(_rx_len==0) { |
|
_error = I2C_OK; |
|
_state = I2C_done; |
|
|
|
finish_transfer(); |
|
} |
|
} else { // fake byte after enable ITBUF |
|
(void)_dev->I2Cx->DR; |
|
} |
|
} |
|
} |
|
if((sr1itflags & I2C_BIT_BTF & I2C_BIT_MASK) != RESET) {// BTF set |
|
if((sr2itflags & (I2C_BIT_TRA) & I2C_BIT_MASK) != RESET) { // I2C in mode Transmitter |
|
// BTF on transmit |
|
if(_rx_len) { |
|
// wait a little - some devices requires time for internal operations |
|
delay_ns100(3); |
|
|
|
// Send START condition a second time |
|
_dev->I2Cx->CR1 |= I2C_CR1_START; |
|
_state = I2C_want_RX_SB; |
|
_dev->I2Cx->CR2 &= ~I2C_CR2_ITBUFEN; // disable TXE interrupt - just for case, should be disabled |
|
} else { |
|
_dev->I2Cx->CR1 |= I2C_CR1_STOP; // Send STOP condition |
|
_error = I2C_OK; // TX is done |
|
_state = I2C_done; |
|
finish_transfer(); |
|
} |
|
|
|
} else { // BTF on receive?? |
|
// |
|
} |
|
} |
|
|
|
#ifdef I2C_DEBUG |
|
I2C_State &sp = log[log_ptr]; // remember last operation |
|
|
|
sp.time = Scheduler::_micros(); |
|
sp.sr1 = sr1itflags; |
|
sp.sr2 = sr2itflags; |
|
sp.cr1 = _dev->I2Cx->CR1; |
|
sp.state = _state; |
|
sp.pos = I2C_ISR; |
|
if(log_ptr<I2C_LOG_SIZE-1) log_ptr++; |
|
else log_ptr=0; |
|
#endif |
|
|
|
} |
|
} |
|
|
|
|
|
void I2CDevice::finish_transfer(){ |
|
|
|
_dev->I2Cx->CR2 &= ~(I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Disable interrupts |
|
i2c_clear_isr_handler(_dev); |
|
_dev->I2Cx->CR1 &= ~(I2C_CR1_STOP | I2C_CR1_START); // clear CR1 - just for case |
|
|
|
Handler h; |
|
if( (h=_completion_cb) ){ // io completion |
|
_completion_cb=0; // only once and before call because handler can set it itself |
|
|
|
revo_call_handler(h, (uint32_t)_dev); |
|
} |
|
|
|
if(_task){ // resume paused task |
|
Scheduler::task_resume(_task); |
|
_task=NULL; |
|
} |
|
} |
|
|
|
uint32_t I2CDevice::wait_stop_done(bool is_write){ |
|
uint32_t sr1; |
|
uint32_t t; |
|
|
|
uint8_t ret; |
|
uint8_t i; |
|
for(i=0; i<10; i++){ |
|
|
|
if( (_dev->I2Cx->CR1 & I2C_CR1_PE) ) { |
|
ret=I2C_OK; |
|
} else { |
|
ret=91; |
|
} |
|
// Wait to make sure that STOP control bit has been cleared - bus released |
|
t = hal_micros(); |
|
while (_dev->I2Cx->CR1 & I2C_CR1_STOP ){ |
|
if((sr1=_dev->I2Cx->SR1) & I2C_BIT_BERR & I2C_BIT_MASK) _dev->I2Cx->SR1 = (uint16_t)(~I2C_BIT_BERR); // Errata 2.4.6 |
|
|
|
if(sr1 & I2C_BIT_ARLO & I2C_BIT_MASK) { // arbitration lost or bus error |
|
_dev->I2Cx->SR1 = (uint16_t)(~I2C_BIT_ARLO); // reset them |
|
ret= I2C_STOP_BERR; // bus error on STOP |
|
break; |
|
} |
|
if(sr1 & I2C_BIT_TIMEOUT & I2C_BIT_MASK) { // bus timeout |
|
_dev->I2Cx->SR1 = (uint16_t)(~I2C_BIT_TIMEOUT); // reset it |
|
ret= I2C_ERR_STOP_TIMEOUT; // STOP generated by hardware |
|
break; |
|
} |
|
|
|
if (hal_micros() - t > I2C_SMALL_TIMEOUT) { |
|
ret=I2C_ERR_STOP; |
|
break; |
|
} |
|
hal_yield(0); |
|
} |
|
|
|
/* wait while the bus is busy */ |
|
t = hal_micros(); |
|
while ((_dev->I2Cx->SR2 & (I2C_BIT_BUSY) & I2C_BIT_MASK) != 0) { |
|
if (hal_micros() - t > I2C_SMALL_TIMEOUT) { |
|
ret=2; // bus busy |
|
break; |
|
} |
|
|
|
hal_yield(0); |
|
} |
|
|
|
|
|
if(ret==I2C_OK) return ret; |
|
|
|
_dev->I2Cx->CR1 |= I2C_CR1_SWRST; // set SoftReset for some time |
|
hal_yield(0); |
|
_dev->I2Cx->CR1 &= (uint16_t)(~I2C_CR1_SWRST); // clear SoftReset flag |
|
_dev->I2Cx->CR1 |= I2C_CR1_PE; // enable |
|
|
|
if(i>1){ |
|
last_error = ret; // remember |
|
if(is_write) last_error+=50; |
|
|
|
_lockup_count ++; |
|
_initialized=false; // will be reinitialized in init() |
|
|
|
need_reset=true; |
|
init(); |
|
|
|
if(!_initialized) return ret; |
|
|
|
} |
|
|
|
} |
|
|
|
return I2C_OK; |
|
} |
|
|
|
|
|
|
|
/* |
|
errata 2.4.6 |
|
Spurious Bus Error detection in Master mode |
|
Description |
|
In Master mode, a bus error can be detected by mistake, so the BERR flag can be wrongly |
|
raised in the status register. This will generate a spurious Bus Error interrupt if the interrupt |
|
is enabled. A bus error detection has no effect on the transfer in Master mode, therefore the |
|
I2C transfer can continue normally. |
|
Workaround |
|
If a bus error interrupt is generated in Master mode, the BERR flag must be cleared by |
|
software. No other action is required and the on-going transfer can be handled normally |
|
|
|
*/
|
|
|