From a4c683fb39c46c4a0382866e7ca1ab65a524fcf4 Mon Sep 17 00:00:00 2001 From: "R. Steve McKown" Date: Wed, 27 Oct 2010 17:54:17 -0600 Subject: [PATCH] USCI I2C support. Current limitations and features: * Master mode only. * No multi-master bus configurations. * Will automatically reset a hung bus, if for example the master reset in the middle of a transaction and a slave currently is driving SDA low. * Repeated starts are supported and can be very time efficient. * An I2C transactions may be processed with multiple I2CPacket.read() or I2CPacket.write() commands, as appropriate. The first must include I2C_START and the last must include I2C_STOP. * Conditions exist during read where the hardware may clock out an extra byte from the slave before stop. This byte is not passed to the user. --- tos/chips/msp430/usci/HplMsp430UsciInt0P.nc | 10 +- tos/chips/msp430/usci/HplMsp430UsciInt1P.nc | 6 +- tos/chips/msp430/usci/HplMsp430UsciReg.nc | 23 +- tos/chips/msp430/usci/HplMsp430UsciRegP.nc | 39 +- tos/chips/msp430/usci/Msp430I2CB0C.nc | 89 ++++ tos/chips/msp430/usci/Msp430I2CB1C.nc | 89 ++++ tos/chips/msp430/usci/Msp430I2CP.nc | 457 ++++++++++++++++++++ tos/chips/msp430/usci/Msp430Usci.h | 3 - 8 files changed, 691 insertions(+), 25 deletions(-) create mode 100644 tos/chips/msp430/usci/Msp430I2CB0C.nc create mode 100644 tos/chips/msp430/usci/Msp430I2CB1C.nc create mode 100644 tos/chips/msp430/usci/Msp430I2CP.nc diff --git a/tos/chips/msp430/usci/HplMsp430UsciInt0P.nc b/tos/chips/msp430/usci/HplMsp430UsciInt0P.nc index 9d75d9d9..d802a0bf 100644 --- a/tos/chips/msp430/usci/HplMsp430UsciInt0P.nc +++ b/tos/chips/msp430/usci/HplMsp430UsciInt0P.nc @@ -106,9 +106,13 @@ implementation * modes. */ TOSH_SIGNAL(USCIAB0TX_VECTOR) { - if (READ_FLAG(UC0IFG & UC0IE, UCB0RXIE)) - signal IntB.rx(UCB0RXBUF); /* I2C receive */ - else if (READ_FLAG(UC0IFG & UC0IE, UCA0TXIFG)) + if (READ_FLAG(UC0IFG & UC0IE, UCB0RXIE)) { + /* I2C receive. Do not read UCB0RXBUF here, as the code receiving + * IntB.rx() may first need to set stop and/or start bits. The receiver + * must read UCB0RXBUF. + */ + signal IntB.rx(0); + } else if (READ_FLAG(UC0IFG & UC0IE, UCA0TXIFG)) signal IntA.tx(); else if (READ_FLAG(UC0IFG & UC0IE, UCB0TXIFG)) signal IntB.tx(); diff --git a/tos/chips/msp430/usci/HplMsp430UsciInt1P.nc b/tos/chips/msp430/usci/HplMsp430UsciInt1P.nc index 45eada48..1e18eb70 100644 --- a/tos/chips/msp430/usci/HplMsp430UsciInt1P.nc +++ b/tos/chips/msp430/usci/HplMsp430UsciInt1P.nc @@ -100,7 +100,11 @@ implementation */ TOSH_SIGNAL(USCIAB1TX_VECTOR) { if (READ_FLAG(UC1IFG & UC1IE, UCB1RXIE)) - signal IntB.rx(UCB1RXBUF); /* I2C receive */ + /* I2C receive. Do not read UCB1RXBUF here, as the code receiving + * IntB.rx() may first need to set stop and/or start bits. The receiver + * must read UCB1RXBUF. + */ + signal IntB.rx(0); else if (READ_FLAG(UC1IFG & UC1IE, UCA1TXIFG)) signal IntA.tx(); else if (READ_FLAG(UC1IFG & UC1IE, UCB1TXIFG)) diff --git a/tos/chips/msp430/usci/HplMsp430UsciReg.nc b/tos/chips/msp430/usci/HplMsp430UsciReg.nc index cf49708a..b3a876fc 100644 --- a/tos/chips/msp430/usci/HplMsp430UsciReg.nc +++ b/tos/chips/msp430/usci/HplMsp430UsciReg.nc @@ -224,28 +224,18 @@ interface HplMsp430UsciReg { async command void assignIrrctl(uint8_t value); /** - * Manipulate bits in the UCxxI2COA register. See the methods that - * manipulate UCxxCTL0 for more information. These methods perform no - * operation on providers implementing a USCI_Ax device. + * Access to the UCBxI2COA register. */ async command volatile uint8_t* ptrI2Coa(); - async command uint8_t getI2Coa(uint8_t mask); - async command void setI2Coa(uint8_t mask); - async command void clrI2Coa(uint8_t mask); - //async command void assignI2Coa(uint8_t mask, uint8_t value); - async command void assignI2Coa(uint8_t value); + async command uint16_t readI2Coa(); + async command void assignI2Coa(uint16_t addr); /** - * Manipulate bits in the UCxxI2COA register. See the methods that - * manipulate UCxxCTL0 for more information. These methods perform no - * operation on providers implementing a USCI_Ax device. + * Access to the UCBxI2SA register. */ async command volatile uint8_t* ptrI2Csa(); - async command uint8_t getI2Csa(uint8_t mask); - async command void setI2Csa(uint8_t mask); - async command void clrI2Csa(uint8_t mask); - //async command void assignI2Csa(uint8_t mask, uint8_t value); - async command void assignI2Csa(uint8_t value); + async command uint16_t readI2Csa(); + async command void assignI2Csa(uint16_t addr); /** * Manipulate bits in the UCxxIE register. See the methods that manipulate @@ -265,4 +255,5 @@ interface HplMsp430UsciReg { async command bool getIfgRx(); async command void clrIfgRx(); async command bool getIfgTx(); + async command void clrIfgTx(); } diff --git a/tos/chips/msp430/usci/HplMsp430UsciRegP.nc b/tos/chips/msp430/usci/HplMsp430UsciRegP.nc index 5cd2e6c2..32f3de12 100644 --- a/tos/chips/msp430/usci/HplMsp430UsciRegP.nc +++ b/tos/chips/msp430/usci/HplMsp430UsciRegP.nc @@ -236,8 +236,38 @@ implementation RENDER_A(Abctl); RENDER_A(Irtctl); RENDER_A(Irrctl); - RENDER_B(I2Coa); - RENDER_B(I2Csa); + + /* RENDER_B(I2Coa); */ + async command volatile uint8_t* Registers.ptrI2Coa() + { + return &UCBxI2Coa; + } + + async command uint16_t Registers.readI2Coa() + { + return UCBxI2Coa; + } + + async command void Registers.assignI2Coa(uint16_t addr) + { + UCBxI2Coa = addr; + } + + /* RENDER_B(I2Csa); */ + async command volatile uint8_t* Registers.ptrI2Csa() + { + return &UCBxI2Csa; + } + + async command uint16_t Registers.readI2Csa() + { + return UCBxI2Csa; + } + + async command void Registers.assignI2Csa(uint16_t addr) + { + UCBxI2Csa = addr; + } /* RENDER(Ie); */ async command bool Registers.getIeRx() @@ -285,4 +315,9 @@ implementation { return READ_FLAG(UCxxIfg, UCxxTXIFG); } + + async command void Registers.clrIfgTx() + { + CLR_FLAG(UCxxIfg, UCxxTXIFG); + } } diff --git a/tos/chips/msp430/usci/Msp430I2CB0C.nc b/tos/chips/msp430/usci/Msp430I2CB0C.nc new file mode 100644 index 00000000..9b76c0f3 --- /dev/null +++ b/tos/chips/msp430/usci/Msp430I2CB0C.nc @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2008, Titanium Mirror, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the Technische Universität Berlin nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * This configuration provides the interface for using USCI_B0 in its I2C + * mode. + * + * @author R. Steve McKown + */ + +#if !defined(__MSP430_HAS_USCI_AB0__) +#error "Target does not have a USCI_B0 peripheral (I2C)" +#endif + +#include "I2C.h" +#include "Msp430Usci.h" + +generic configuration Msp430I2CB0C() { + provides { + interface Resource; + interface ResourceRequested; + /* interface SomethingToSetOwnAddress */ + /* interface SomethingToDenoteMode */ + interface I2CPacket as I2CBasicPacket; + interface I2CPacket as I2CExtdPacket; + interface ArbiterInfo; /* ??? */ + } + uses interface AsyncConfigure as Configure; +} +implementation { + enum { + CLIENT_ID = unique(MSP430_USCIB0_RESOURCE) + }; + + components new Msp430I2CP() as I2CP; + I2CBasicPacket = I2CP; + I2CExtdPacket = I2CP; + Configure = I2CP; + + components Msp430UsciB0C as UsciC; + Resource = UsciC.Resource[CLIENT_ID]; + ResourceRequested = UsciC.ResourceRequested[CLIENT_ID]; + ArbiterInfo = UsciC.ArbiterInfo; + I2CP -> UsciC.Registers; + I2CP -> UsciC.Interrupts[CLIENT_ID]; + I2CP -> UsciC.ArbiterInfo; + UsciC.ResourceConfigure[CLIENT_ID] -> I2CP; + + components HplMsp430GeneralIOC as IOC; + I2CP.SCL -> IOC.UCB0SCL; + I2CP.SDA -> IOC.UCB0SDA; + +#if 0 /* FIXME: no virtualized alarm for msp430 */ + comonents new SomeVirtualizedAlarmClientC() as AlarmC; + I2CP.Alarm -> AlarmC; +#endif + + components BusyWaitMicroC; + I2CP.BusyWait -> BusyWaitMicroC; + + components Msp430CounterMicroC; + I2CP.Counter -> Msp430CounterMicroC; +} diff --git a/tos/chips/msp430/usci/Msp430I2CB1C.nc b/tos/chips/msp430/usci/Msp430I2CB1C.nc new file mode 100644 index 00000000..47cab8d0 --- /dev/null +++ b/tos/chips/msp430/usci/Msp430I2CB1C.nc @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2008, Titanium Mirror, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the Technische Universität Berlin nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * This configuration provides the interface for using USCI_B1 in its I2C + * mode. + * + * @author R. Steve McKown + */ + +#if !defined(__MSP430_HAS_USCI_AB1__) +#error "Target does not have a USCI_B1 peripheral (I2C)" +#endif + +#include "I2C.h" +#include "Msp430Usci.h" + +generic configuration Msp430I2CB1C() { + provides { + interface Resource; + interface ResourceRequested; + /* interface SomethingToSetOwnAddress */ + /* interface SomethingToDenoteMode */ + interface I2CPacket as I2CBasicPacket; + interface I2CPacket as I2CExtdPacket; + interface ArbiterInfo; /* ??? */ + } + uses interface AsyncConfigure as Configure; +} +implementation { + enum { + CLIENT_ID = unique(MSP430_USCIB1_RESOURCE) + }; + + components new Msp430I2CP() as I2CP; + I2CBasicPacket = I2CP; + I2CExtdPacket = I2CP; + Configure = I2CP; + + components Msp430UsciB1C as UsciC; + Resource = UsciC.Resource[CLIENT_ID]; + ResourceRequested = UsciC.ResourceRequested[CLIENT_ID]; + ArbiterInfo = UsciC.ArbiterInfo; + I2CP -> UsciC.Registers; + I2CP -> UsciC.Interrupts[CLIENT_ID]; + I2CP -> UsciC.ArbiterInfo; + UsciC.ResourceConfigure[CLIENT_ID] -> I2CP; + + components HplMsp430GeneralIOC as IOC; + I2CP.SCL -> IOC.UCB1SCL; + I2CP.SDA -> IOC.UCB1SDA; + +#if 0 /* FIXME: no virtualized alarm for msp430 */ + comonents new SomeVirtualizedAlarmClientC() as AlarmC; + I2CP.Alarm -> AlarmC; +#endif + + components BusyWaitMicroC; + I2CP.BusyWait -> BusyWaitMicroC; + + components Msp430CounterMicroC; + I2CP.Counter -> Msp430CounterMicroC; +} diff --git a/tos/chips/msp430/usci/Msp430I2CP.nc b/tos/chips/msp430/usci/Msp430I2CP.nc new file mode 100644 index 00000000..8ce827d0 --- /dev/null +++ b/tos/chips/msp430/usci/Msp430I2CP.nc @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2008, Titanium Mirror, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the Technische Universität Berlin nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * USCI I2C implementation. Currently supports only single master operation. + * Repeated start operations are supported, as are using multiple I2CPacket + * read or write commands to satisfy a single I2C read or write transaction. + * + * @author R. Steve McKown + */ + +generic module Msp430I2CP() { + provides { + interface I2CPacket as I2CBasicPacket; + interface I2CPacket as I2CExtdPacket; + interface ResourceConfigure; + } + uses { + interface HplMsp430UsciReg as Registers; + interface HplMsp430UsciInt as Interrupts; + interface HplMsp430GeneralIO as SDA; + interface HplMsp430GeneralIO as SCL; + interface AsyncConfigure as Configure; + interface ArbiterInfo; + interface BusyWait; + interface Counter; + } +} +implementation { + enum { + /* Activity timing */ + SCL_WAIT_TIME = 1024, /* micros */ + BUSY_CHECK_TIME = 1024, /* micros */ + START_CHECK_TIME = 102, /* micros */ + STOP_CHECK_TIME = 102, /* micros */ + + /* Setting new I2C flags is dangerous; manually ensure the bit definitions + * below do not overlap with those defined in tos/types/I2C.h. + */ + I2C_EXTENDED = 0x10, + }; + + i2c_flags_t m_flags; + uint8_t* m_buf; + uint16_t m_len; + uint16_t m_pos; + + inline void setSDA() + { + call SDA.makeInput(); + } + + inline void clrSDA() + { + call SDA.makeOutput(); + } + + inline void setSCL() + { + call SCL.makeInput(); + } + + inline void clrSCL() + { + call SCL.makeOutput(); + } + + bool idleBus() + { + /* Look for bus idle when the I2C pins are in IO mode. */ + return (call SDA.get() && call SCL.get()); + } + + bool resetBus() + { + /* When the I2C pins are in IO mode, verify the I2C bus is idle or attempt + * to get the bus into an idle state. This code is only valid if we are + * the only master on the I2C bus and is not suitable for multi-master + * setups. + * + * Return TRUE if the bus was idle or was successfully brought to idle. + * Return FALSE if the bus could not be made idle in a reasonable time. + */ + if (idleBus()) + return TRUE; + else { + uint16_t i; + + /* Wait a bit if SCL is low. A save might be clock stretching. */ + i = call Counter.get(); + while (call Counter.get() - i <= SCL_WAIT_TIME) { + if (!(call SCL.get())) + return FALSE; + } + + /* If SDA is low, clock SCL in an attempt to release it. */ + for (i = 0; i < 10 && !call SDA.get(); i++) { + clrSCL(); + call BusyWait.wait(10); + setSCL(); + call BusyWait.wait(10); + } + if (!idleBus()) + return FALSE; + + /* Drive a stop condition on the bus to stop any active slaves. */ + clrSCL(); + clrSDA(); + call BusyWait.wait(10); + setSCL(); + call BusyWait.wait(10); + setSDA(); + call BusyWait.wait(10); + return idleBus(); + } + } + + bool isConfigured() + { + return !(call Registers.getCtl1(UCSWRST)); + } + + /* TRUE if a transaction (we initiated, single master) is in progress. */ + bool isBusy() + { + return m_len; + } + + /* Wait for an I2C start, if in progress, to complete. Return FALSE if a + * start is pending and did not finish within the allotted time. Return TRUE + * if no start was pending, or it finished within the allotted time. + */ + bool waitStart() + { + uint16_t t0 = call Counter.get(); + + do { + if (!(call Registers.getCtl1(UCTXSTT))) + return TRUE; + } while (call Counter.get() - t0 <= START_CHECK_TIME); + return FALSE; + } + + /* Wait for an I2C stop, if in progress, to complete. Return FALSE if a + * stop is pending and did not finish within the allotted time. Return TRUE + * if no stop was pending, or it finished within the allotted time. + */ + bool waitStop() + { + uint16_t t0 = call Counter.get(); + + do { + if (!(call Registers.getCtl1(UCTXSTP))) + return TRUE; + } while (call Counter.get() - t0 <= STOP_CHECK_TIME); + return FALSE; + } + + async command void ResourceConfigure.configure() + { + atomic { + const msp430_usci_i2c_t* config = call Configure.get(); + + call Registers.setCtl1(UCSWRST); + + /* Several conditions might find a slave driving the bus at this point. + * One such condition is a badly timed PUC of this slave. Use resetBus() + * to attempt rectification of such conditions. + */ + if (!resetBus()) + return; + + /* Configure USCI registers. I2C requires UCMODE_3 and UCSYNC */ + call Registers.assignCtl0(config->ctl0 | UCMODE_3 | UCSYNC); + call Registers.assignCtl1(config->ctl1 | UCSWRST); + call Registers.assignBr0(config->brx & 0xff); + call Registers.assignBr1(config->brx >> 8); + call Registers.assignMctl(0); + call Registers.assignI2Coa(config->i2coa); + call Registers.assignI2Csa(0); + + /* Configure pins for I2C */ + call SDA.selectModuleFunc(); + call SCL.selectModuleFunc(); + + /* Clear interrupts; we'll add them as needed */ + call Registers.assignI2Cie(0); + call Registers.clrIeRx(); + call Registers.clrIeTx(); + + /* Enable the device */ + call Registers.clrCtl1(UCSWRST); + } + } + + void signalDone() + { + error_t error = (m_pos == m_len) ? SUCCESS : FAIL; + uint16_t len = m_len; + uint16_t addr; + + /* No more I2C interrupts until the next I2C request arrives */ + call Registers.assignI2Cie(0); + call Registers.clrIeTx(); + call Registers.clrIeRx(); + + /* Wait for stop to finish, if it is in progress. */ + if (!waitStop()) + error = FAIL; + + m_len = 0; + addr = call Registers.readI2Csa(); + switch ((call Registers.getCtl0(UCSLA10) ? 2 : 0) + /* extended addr */ + (call Registers.getCtl1(UCTR) ? 1 : 0)) { /* write */ + case 0: + atomic signal I2CBasicPacket.readDone(error, addr, len, m_buf); + break; + case 1: + atomic signal I2CBasicPacket.writeDone(error, addr, len, m_buf); + break; + case 2: + atomic signal I2CExtdPacket.readDone(error, addr, len, m_buf); + break; + case 3: + atomic signal I2CExtdPacket.writeDone(error, addr, len, m_buf); + break; + } + } + + async command void ResourceConfigure.unconfigure() + { + atomic { + /* Signal done if a pending operation */ + if (isBusy()) + signalDone(); + + /* Disable the device */ + call Registers.setCtl1(UCSWRST); + + /* Clear interrupts and interrupt flags. */ + call Registers.assignI2Cie(0); + call Registers.clrIeRx(); + call Registers.clrIfgRx(); + + /* Restore I2C pins to IO function */ + call SDA.selectIOFunc(); + call SCL.selectIOFunc(); + } + } + + error_t read(i2c_flags_t flags, uint16_t addr, uint8_t len, uint8_t* rxBuf) + { + /* From the TI family guide SDAU144D, page 17-17: + * "Data is received from the slave as long as UCTXSTP or UCTXSTT is not + * set. If UCBxRXBUF is not read the master holds the bus during reception + * of the last data bit and until the UCBxRXBUF is read." + * + * Since the I2C bus can only be suspended during read by *not* reading a + * byte out of RXBUF, the only reasonable command after a read() that + * did not include I2C_STOP is a read that does not include I2C_START. A + * read that includes I2C_START, or a write (which by definition must + * include I2C_START) will effectively create the situation where the + * prior read caused the slave to send one more byte than was delivered via + * readDone() to the user code. In many cases, this won't be a problem, as + * triggering the slave to read an extra byte will do no harm. But this + * may not always be the case, if for example the slave maintains state + * that is changed upon the read of that final byte the user code never + * sees. + */ + if (!isConfigured() || isBusy() || len == 0 || !rxBuf) + return FAIL; + + m_flags = flags; + m_len = len; + m_buf = rxBuf; + m_pos = 0; + +#if 0 /* FIXME: No virtualized alarm for msp430 */ + /* We require an alarm because we aren't looking at UCALIFG, arbitration + * lost. If this were to happen, the state machine hangs. + */ + call Alarm.start(I2C_TIMEOUT); +#endif + if (m_flags & I2C_EXTENDED) + call Registers.setCtl0(UCSLA10); + else + call Registers.clrCtl0(UCSLA10); + call Registers.assignI2Csa(addr); + call Registers.clrCtl1(UCTR); + if (m_flags & I2C_START) { + call Registers.clrIfgRx(); /* see above re: suspending reads */ + call Registers.setCtl1(UCTXSTT); + } + if (m_len == 1 && (m_flags & I2C_STOP)) { + /* UCTXSTP must assert before hw finishes clocking in the last byte. + * FIXME: in reading a single I2C trx using multiple I2C...read() calls, + * too much time spent by the user processing the readDone() can cause + * the I2C hardware to clock an extra byte's worth of clocks, thereby + * issuing 1 byte more of read with the slave, before the stop event. + */ + waitStart(); + call Registers.setCtl1(UCTXSTP); + } + call Registers.setI2Cie(UCNACKIE); + call Registers.setIeRx(); + return SUCCESS; + } + + async command error_t I2CBasicPacket.read(i2c_flags_t flags, uint16_t addr, + uint8_t length, uint8_t* data) + { + atomic return read(flags & ~I2C_EXTENDED, addr, length, data); + } + + async command error_t I2CExtdPacket.read(i2c_flags_t flags, uint16_t addr, + uint8_t length, uint8_t* data) + { + atomic return read(flags | I2C_EXTENDED, addr, length, data); + } + + error_t write(i2c_flags_t flags, uint16_t addr, uint8_t len, uint8_t* txBuf) + { + if (!isConfigured() || isBusy() || len == 0 || !txBuf) + return FAIL; + + m_flags = flags; + m_len = len; + m_buf = txBuf; + m_pos = 0; + +#if 0 /* FIXME: No virtualized alarm for msp430 */ + /* We require an alarm because we aren't looking at UCALIFG, arbitration + * lost. If this were to happen, the state machine hangs. + */ + call Alarm.start(I2C_TIMEOUT); +#endif + if (m_flags & I2C_EXTENDED) + call Registers.setCtl0(UCSLA10); + else + call Registers.clrCtl0(UCSLA10); + call Registers.assignI2Csa(addr); + call Registers.setCtl1((m_flags & I2C_START) ? UCTR + UCTXSTT : UCTR); + call Registers.setI2Cie(UCNACKIE); + call Registers.setIeTx(); + return SUCCESS; + } + + async command error_t I2CBasicPacket.write(i2c_flags_t flags, uint16_t addr, + uint8_t length, uint8_t* data) + { + atomic return write(flags & ~I2C_EXTENDED, addr, length, data); + } + + async command error_t I2CExtdPacket.write(i2c_flags_t flags, uint16_t addr, + uint8_t length, uint8_t* data) + { + atomic return write(flags | I2C_EXTENDED, addr, length, data); + } + +#if 0 /* FIXME: need a virtualized alarm for msp430 */ + event void Alarm.fired() + { + if (m_flags & I2C_STOP) { + call Registers.setCtl1(UCTXSTP); + call Registers.clrIfgTx(); + } + signalDone(); + } +#endif + + async event void Interrupts.tx() + { + if (m_pos == m_len) { + if (m_flags & I2C_STOP) { + call Registers.setCtl1(UCTXSTP); + call Registers.clrIfgTx(); + } + signalDone(); + } else + call Registers.setTxbuf(m_buf[m_pos++]); + } + + async event void Interrupts.rx(uint8_t nobyte) + { + if (m_len - m_pos == 2 && (m_flags & I2C_STOP)) { + /* As soon as we read RXBUF, the hw will begin clocking in the next byte. + * To guarantee that a slow uC can still set UCTXSTP before the last + * byte is fully clocked, we set it before the second to last byte is + * read from RXBUF, when the hw has I2C communications suspended. + */ + call Registers.setCtl1(UCTXSTP); + } + m_buf[m_pos++] = call Registers.getRxbuf(); + if (m_pos == m_len) + signalDone(); + } + + async event void Interrupts.i2cNack() + { + call Registers.setCtl1(UCTXSTP); + call Registers.clrStat(UCNACKIFG); + signalDone(); + } + + default async event void I2CBasicPacket.readDone(error_t error, uint16_t addr, + uint8_t length, uint8_t* data) {} + default async event void I2CBasicPacket.writeDone(error_t error, + uint16_t addr, uint8_t length, uint8_t* data) {} + default async event void I2CExtdPacket.readDone(error_t error, uint16_t addr, + uint8_t length, uint8_t* data) {} + default async event void I2CExtdPacket.writeDone(error_t error, uint16_t addr, + uint8_t length, uint8_t* data) {} + + default async command const msp430_usci_i2c_t* Configure.get() + { + const static msp430_usci_i2c_t def = { + ctl0: UCSYNC | UCMODE_3 | UCMST, /* I2C master */ + ctl1: UCSWRST | UCSSEL_3, /* I2C clock source is SMCLK */ + brx: 10, /* I2C clock=SMCLK/10; ~95KHz if SMCLK=2^20Hz */ + ren: USCI_REN_NONE + }; + + return &def; + } + + async event void Interrupts.i2cStart() {} + async event void Interrupts.i2cStop() {} + async event void Interrupts.i2cCal() {} + async event void Interrupts.brk() {} + async event void Counter.overflow() {} +} diff --git a/tos/chips/msp430/usci/Msp430Usci.h b/tos/chips/msp430/usci/Msp430Usci.h index 31ba96ae..575889d1 100644 --- a/tos/chips/msp430/usci/Msp430Usci.h +++ b/tos/chips/msp430/usci/Msp430Usci.h @@ -130,10 +130,7 @@ typedef struct { uint8_t ctl0; uint8_t ctl1; uint16_t brx; - uint8_t i2cie; uint8_t i2coa; - uint8_t i2csa; - bool uclisten; msp430_ren_t ren; } msp430_usci_i2c_t; -- 2.39.2