mirror of
https://github.com/arduino/Arduino.git
synced 2024-12-12 23:08:52 +01:00
77179d78d0
From the data sheet TXCOMP is only set after a stop or repeated start and address change, but EOSACC is set for a stop or repeated start when the address remains the same. This change removes the check for TXCOMP when not idle, to support repeated starts for both RX and TX.
415 lines
11 KiB
C++
415 lines
11 KiB
C++
/*
|
|
* TwoWire.h - TWI/I2C library for Arduino Due
|
|
* Copyright (c) 2011 Cristian Maglie <c.maglie@arduino.cc>
|
|
* All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
extern "C" {
|
|
#include <string.h>
|
|
}
|
|
|
|
#include "Wire.h"
|
|
|
|
static inline bool TWI_FailedAcknowledge(Twi *pTwi) {
|
|
return pTwi->TWI_SR & TWI_SR_NACK;
|
|
}
|
|
|
|
static inline bool TWI_WaitTransferComplete(Twi *_twi, uint32_t _timeout) {
|
|
uint32_t _status_reg = 0;
|
|
while ((_status_reg & TWI_SR_TXCOMP) != TWI_SR_TXCOMP) {
|
|
_status_reg = TWI_GetStatus(_twi);
|
|
|
|
if (_status_reg & TWI_SR_NACK)
|
|
return false;
|
|
|
|
if (--_timeout == 0)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline bool TWI_WaitByteSent(Twi *_twi, uint32_t _timeout) {
|
|
uint32_t _status_reg = 0;
|
|
while ((_status_reg & TWI_SR_TXRDY) != TWI_SR_TXRDY) {
|
|
_status_reg = TWI_GetStatus(_twi);
|
|
|
|
if (_status_reg & TWI_SR_NACK)
|
|
return false;
|
|
|
|
if (--_timeout == 0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool TWI_WaitByteReceived(Twi *_twi, uint32_t _timeout) {
|
|
uint32_t _status_reg = 0;
|
|
while ((_status_reg & TWI_SR_RXRDY) != TWI_SR_RXRDY) {
|
|
_status_reg = TWI_GetStatus(_twi);
|
|
|
|
if (_status_reg & TWI_SR_NACK)
|
|
return false;
|
|
|
|
if (--_timeout == 0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool TWI_STATUS_SVREAD(uint32_t status) {
|
|
return (status & TWI_SR_SVREAD) == TWI_SR_SVREAD;
|
|
}
|
|
|
|
static inline bool TWI_STATUS_SVACC(uint32_t status) {
|
|
return (status & TWI_SR_SVACC) == TWI_SR_SVACC;
|
|
}
|
|
|
|
static inline bool TWI_STATUS_GACC(uint32_t status) {
|
|
return (status & TWI_SR_GACC) == TWI_SR_GACC;
|
|
}
|
|
|
|
static inline bool TWI_STATUS_EOSACC(uint32_t status) {
|
|
return (status & TWI_SR_EOSACC) == TWI_SR_EOSACC;
|
|
}
|
|
|
|
static inline bool TWI_STATUS_NACK(uint32_t status) {
|
|
return (status & TWI_SR_NACK) == TWI_SR_NACK;
|
|
}
|
|
|
|
TwoWire::TwoWire(Twi *_twi, void(*_beginCb)(void)) :
|
|
twi(_twi), rxBufferIndex(0), rxBufferLength(0), txAddress(0),
|
|
txBufferLength(0), srvBufferIndex(0), srvBufferLength(0), status(
|
|
UNINITIALIZED), onBeginCallback(_beginCb), twiClock(TWI_CLOCK) {
|
|
}
|
|
|
|
void TwoWire::begin(void) {
|
|
if (onBeginCallback)
|
|
onBeginCallback();
|
|
|
|
// Disable PDC channel
|
|
twi->TWI_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
|
|
|
|
TWI_ConfigureMaster(twi, twiClock, VARIANT_MCK);
|
|
status = MASTER_IDLE;
|
|
}
|
|
|
|
void TwoWire::begin(uint8_t address) {
|
|
if (onBeginCallback)
|
|
onBeginCallback();
|
|
|
|
// Disable PDC channel
|
|
twi->TWI_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
|
|
|
|
TWI_ConfigureSlave(twi, address);
|
|
status = SLAVE_IDLE;
|
|
TWI_EnableIt(twi, TWI_IER_SVACC);
|
|
//| TWI_IER_RXRDY | TWI_IER_TXRDY | TWI_IER_TXCOMP);
|
|
}
|
|
|
|
void TwoWire::begin(int address) {
|
|
begin((uint8_t) address);
|
|
}
|
|
|
|
void TwoWire::setClock(uint32_t frequency) {
|
|
twiClock = frequency;
|
|
TWI_SetClock(twi, twiClock, VARIANT_MCK);
|
|
}
|
|
|
|
uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint32_t iaddress, uint8_t isize, uint8_t sendStop) {
|
|
if (quantity > BUFFER_LENGTH)
|
|
quantity = BUFFER_LENGTH;
|
|
|
|
// perform blocking read into buffer
|
|
int readed = 0;
|
|
TWI_StartRead(twi, address, iaddress, isize);
|
|
do {
|
|
// Stop condition must be set during the reception of last byte
|
|
if (readed + 1 == quantity)
|
|
TWI_SendSTOPCondition( twi);
|
|
|
|
if (TWI_WaitByteReceived(twi, RECV_TIMEOUT))
|
|
rxBuffer[readed++] = TWI_ReadByte(twi);
|
|
else
|
|
break;
|
|
} while (readed < quantity);
|
|
TWI_WaitTransferComplete(twi, RECV_TIMEOUT);
|
|
|
|
// set rx buffer iterator vars
|
|
rxBufferIndex = 0;
|
|
rxBufferLength = readed;
|
|
|
|
return readed;
|
|
}
|
|
|
|
uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) {
|
|
return requestFrom((uint8_t) address, (uint8_t) quantity, (uint32_t) 0, (uint8_t) 0, (uint8_t) sendStop);
|
|
}
|
|
|
|
uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity) {
|
|
return requestFrom((uint8_t) address, (uint8_t) quantity, (uint8_t) true);
|
|
}
|
|
|
|
uint8_t TwoWire::requestFrom(int address, int quantity) {
|
|
return requestFrom((uint8_t) address, (uint8_t) quantity, (uint8_t) true);
|
|
}
|
|
|
|
uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop) {
|
|
return requestFrom((uint8_t) address, (uint8_t) quantity, (uint8_t) sendStop);
|
|
}
|
|
|
|
void TwoWire::beginTransmission(uint8_t address) {
|
|
status = MASTER_SEND;
|
|
|
|
// save address of target and empty buffer
|
|
txAddress = address;
|
|
txBufferLength = 0;
|
|
}
|
|
|
|
void TwoWire::beginTransmission(int address) {
|
|
beginTransmission((uint8_t) address);
|
|
}
|
|
|
|
//
|
|
// Originally, 'endTransmission' was an f(void) function.
|
|
// It has been modified to take one parameter indicating
|
|
// whether or not a STOP should be performed on the bus.
|
|
// Calling endTransmission(false) allows a sketch to
|
|
// perform a repeated start.
|
|
//
|
|
// WARNING: Nothing in the library keeps track of whether
|
|
// the bus tenure has been properly ended with a STOP. It
|
|
// is very possible to leave the bus in a hung state if
|
|
// no call to endTransmission(true) is made. Some I2C
|
|
// devices will behave oddly if they do not see a STOP.
|
|
//
|
|
uint8_t TwoWire::endTransmission(uint8_t sendStop) {
|
|
uint8_t error = 0;
|
|
// transmit buffer (blocking)
|
|
TWI_StartWrite(twi, txAddress, 0, 0, txBuffer[0]);
|
|
if (!TWI_WaitByteSent(twi, XMIT_TIMEOUT))
|
|
error = 2; // error, got NACK on address transmit
|
|
|
|
if (error == 0) {
|
|
uint16_t sent = 1;
|
|
while (sent < txBufferLength) {
|
|
TWI_WriteByte(twi, txBuffer[sent++]);
|
|
if (!TWI_WaitByteSent(twi, XMIT_TIMEOUT))
|
|
error = 3; // error, got NACK during data transmmit
|
|
}
|
|
}
|
|
|
|
if (error == 0) {
|
|
TWI_Stop(twi);
|
|
if (!TWI_WaitTransferComplete(twi, XMIT_TIMEOUT))
|
|
error = 4; // error, finishing up
|
|
}
|
|
|
|
txBufferLength = 0; // empty buffer
|
|
status = MASTER_IDLE;
|
|
return error;
|
|
}
|
|
|
|
// This provides backwards compatibility with the original
|
|
// definition, and expected behaviour, of endTransmission
|
|
//
|
|
uint8_t TwoWire::endTransmission(void)
|
|
{
|
|
return endTransmission(true);
|
|
}
|
|
|
|
size_t TwoWire::write(uint8_t data) {
|
|
if (status == MASTER_SEND) {
|
|
if (txBufferLength >= BUFFER_LENGTH)
|
|
return 0;
|
|
txBuffer[txBufferLength++] = data;
|
|
return 1;
|
|
} else {
|
|
if (srvBufferLength >= BUFFER_LENGTH)
|
|
return 0;
|
|
srvBuffer[srvBufferLength++] = data;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
size_t TwoWire::write(const uint8_t *data, size_t quantity) {
|
|
if (status == MASTER_SEND) {
|
|
for (size_t i = 0; i < quantity; ++i) {
|
|
if (txBufferLength >= BUFFER_LENGTH)
|
|
return i;
|
|
txBuffer[txBufferLength++] = data[i];
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < quantity; ++i) {
|
|
if (srvBufferLength >= BUFFER_LENGTH)
|
|
return i;
|
|
srvBuffer[srvBufferLength++] = data[i];
|
|
}
|
|
}
|
|
return quantity;
|
|
}
|
|
|
|
int TwoWire::available(void) {
|
|
return rxBufferLength - rxBufferIndex;
|
|
}
|
|
|
|
int TwoWire::read(void) {
|
|
if (rxBufferIndex < rxBufferLength)
|
|
return rxBuffer[rxBufferIndex++];
|
|
return -1;
|
|
}
|
|
|
|
int TwoWire::peek(void) {
|
|
if (rxBufferIndex < rxBufferLength)
|
|
return rxBuffer[rxBufferIndex];
|
|
return -1;
|
|
}
|
|
|
|
void TwoWire::flush(void) {
|
|
// Do nothing, use endTransmission(..) to force
|
|
// data transfer.
|
|
}
|
|
|
|
void TwoWire::onReceive(void(*function)(int)) {
|
|
onReceiveCallback = function;
|
|
}
|
|
|
|
void TwoWire::onRequest(void(*function)(void)) {
|
|
onRequestCallback = function;
|
|
}
|
|
|
|
void TwoWire::onService(void) {
|
|
// Retrieve interrupt status
|
|
uint32_t sr = TWI_GetStatus(twi);
|
|
|
|
if (status == SLAVE_IDLE && TWI_STATUS_SVACC(sr)) {
|
|
TWI_DisableIt(twi, TWI_IDR_SVACC);
|
|
TWI_EnableIt(twi, TWI_IER_RXRDY | TWI_IER_GACC | TWI_IER_NACK
|
|
| TWI_IER_EOSACC | TWI_IER_SCL_WS | TWI_IER_TXCOMP);
|
|
|
|
srvBufferLength = 0;
|
|
srvBufferIndex = 0;
|
|
|
|
// Detect if we should go into RECV or SEND status
|
|
// SVREAD==1 means *master* reading -> SLAVE_SEND
|
|
if (!TWI_STATUS_SVREAD(sr)) {
|
|
status = SLAVE_RECV;
|
|
} else {
|
|
status = SLAVE_SEND;
|
|
|
|
// Alert calling program to generate a response ASAP
|
|
if (onRequestCallback)
|
|
onRequestCallback();
|
|
else
|
|
// create a default 1-byte response
|
|
write((uint8_t) 0);
|
|
}
|
|
}
|
|
|
|
if (status != SLAVE_IDLE && TWI_STATUS_EOSACC(sr)) {
|
|
if (status == SLAVE_RECV && onReceiveCallback) {
|
|
// Copy data into rxBuffer
|
|
// (allows to receive another packet while the
|
|
// user program reads actual data)
|
|
for (uint8_t i = 0; i < srvBufferLength; ++i)
|
|
rxBuffer[i] = srvBuffer[i];
|
|
rxBufferIndex = 0;
|
|
rxBufferLength = srvBufferLength;
|
|
|
|
// Alert calling program
|
|
onReceiveCallback( rxBufferLength);
|
|
}
|
|
|
|
// Transfer completed
|
|
TWI_EnableIt(twi, TWI_SR_SVACC);
|
|
TWI_DisableIt(twi, TWI_IDR_RXRDY | TWI_IDR_GACC | TWI_IDR_NACK
|
|
| TWI_IDR_EOSACC | TWI_IDR_SCL_WS | TWI_IER_TXCOMP);
|
|
status = SLAVE_IDLE;
|
|
}
|
|
|
|
if (status == SLAVE_RECV) {
|
|
if (TWI_STATUS_RXRDY(sr)) {
|
|
if (srvBufferLength < BUFFER_LENGTH)
|
|
srvBuffer[srvBufferLength++] = TWI_ReadByte(twi);
|
|
}
|
|
}
|
|
|
|
if (status == SLAVE_SEND) {
|
|
if (TWI_STATUS_TXRDY(sr) && !TWI_STATUS_NACK(sr)) {
|
|
uint8_t c = 'x';
|
|
if (srvBufferIndex < srvBufferLength)
|
|
c = srvBuffer[srvBufferIndex++];
|
|
TWI_WriteByte(twi, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WIRE_INTERFACES_COUNT > 0
|
|
static void Wire_Init(void) {
|
|
pmc_enable_periph_clk(WIRE_INTERFACE_ID);
|
|
PIO_Configure(
|
|
g_APinDescription[PIN_WIRE_SDA].pPort,
|
|
g_APinDescription[PIN_WIRE_SDA].ulPinType,
|
|
g_APinDescription[PIN_WIRE_SDA].ulPin,
|
|
g_APinDescription[PIN_WIRE_SDA].ulPinConfiguration);
|
|
PIO_Configure(
|
|
g_APinDescription[PIN_WIRE_SCL].pPort,
|
|
g_APinDescription[PIN_WIRE_SCL].ulPinType,
|
|
g_APinDescription[PIN_WIRE_SCL].ulPin,
|
|
g_APinDescription[PIN_WIRE_SCL].ulPinConfiguration);
|
|
|
|
NVIC_DisableIRQ(WIRE_ISR_ID);
|
|
NVIC_ClearPendingIRQ(WIRE_ISR_ID);
|
|
NVIC_SetPriority(WIRE_ISR_ID, 0);
|
|
NVIC_EnableIRQ(WIRE_ISR_ID);
|
|
}
|
|
|
|
TwoWire Wire = TwoWire(WIRE_INTERFACE, Wire_Init);
|
|
|
|
void WIRE_ISR_HANDLER(void) {
|
|
Wire.onService();
|
|
}
|
|
#endif
|
|
|
|
#if WIRE_INTERFACES_COUNT > 1
|
|
static void Wire1_Init(void) {
|
|
pmc_enable_periph_clk(WIRE1_INTERFACE_ID);
|
|
PIO_Configure(
|
|
g_APinDescription[PIN_WIRE1_SDA].pPort,
|
|
g_APinDescription[PIN_WIRE1_SDA].ulPinType,
|
|
g_APinDescription[PIN_WIRE1_SDA].ulPin,
|
|
g_APinDescription[PIN_WIRE1_SDA].ulPinConfiguration);
|
|
PIO_Configure(
|
|
g_APinDescription[PIN_WIRE1_SCL].pPort,
|
|
g_APinDescription[PIN_WIRE1_SCL].ulPinType,
|
|
g_APinDescription[PIN_WIRE1_SCL].ulPin,
|
|
g_APinDescription[PIN_WIRE1_SCL].ulPinConfiguration);
|
|
|
|
NVIC_DisableIRQ(WIRE1_ISR_ID);
|
|
NVIC_ClearPendingIRQ(WIRE1_ISR_ID);
|
|
NVIC_SetPriority(WIRE1_ISR_ID, 0);
|
|
NVIC_EnableIRQ(WIRE1_ISR_ID);
|
|
}
|
|
|
|
TwoWire Wire1 = TwoWire(WIRE1_INTERFACE, Wire1_Init);
|
|
|
|
void WIRE1_ISR_HANDLER(void) {
|
|
Wire1.onService();
|
|
}
|
|
#endif
|