Merge pull request #11 from lebrush/usci-i2c

Extended GenericUSCI to support i2c protocol as well
This commit is contained in:
Joakim Eriksson 2013-08-12 00:14:00 -07:00
commit 562aa94a1e
3 changed files with 647 additions and 28 deletions

View File

@ -0,0 +1,342 @@
/* Copyright (c) 2013, tado° GmbH. Munich, Germany.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of the Institute 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 INSTITUTE 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 INSTITUTE 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 file is part of MSPSim.
*
* Author: Víctor Ariño <victor.arino@tado.com>
*
*/
package se.sics.mspsim.chip;
import se.sics.mspsim.core.GenericUSCI;
import se.sics.mspsim.core.MSP430Core;
import se.sics.mspsim.core.TimeEvent;
import se.sics.mspsim.core.USARTListener;
import se.sics.mspsim.core.USARTSource;
/**
* Simple I2C unit
*
* Any class extending this unit can use the i2c protocol by just implementing
* two methods for reading and writing registers.
*
* @author Víctor Ariño <victor.arino@tado.com>
*/
public abstract class I2CUnit implements USARTListener {
/**
* Auxiliar class for emulating the i2c signaling. This is necessary as
* there's no line messages in the emulator.
*
* @author Víctor Ariño <victor.arino@tado.com>
*/
public class I2CData {
/**
* Several mask for messages. This may not implement a real i2c but is
* useful for message passing
*/
public final static int START = 0x100;
public final static int STOP = 0x200;
public final static int ACK = 0x400;
public final static int NACK = 0x800;
private int data;
public I2CData(int data) {
this.data = data;
}
protected int getData() {
return data & 0xff;
}
protected boolean isStart() {
return (data & START) == START;
}
protected boolean isStop() {
return (data & STOP) == STOP;
}
protected boolean isAck() {
return data == ACK;
}
protected int getAddress() {
if (isStart()) {
return (data & 0xfe) >> 1;
}
return 0;
}
protected boolean isRead() {
if (isStart()) {
return (data & 0x01) != 0x01;
}
return false;
}
}
/**
* Address of the I2C peripheral
*/
protected int busAddress = 0x00;
/**
* String name for logging
*/
protected String name = "Unknown";
/**
* Enable debug
*/
protected boolean DEBUG = false;
/**
* Is a read message or a write
*/
private boolean isRead = false;
/**
* Memory address of the peripheral to read/write
*/
private int registerAddress = 0x00;
/**
* Length in bytes of the bus address
*/
protected int regAddressLen = 1;
/**
* Length in bytes of the buffer value
*/
protected int numBytesTotal = 2;
/**
* Number of received value bytes
*/
private int numBytesRxTx = 0;
/**
* Received bytes of the address
*/
private int regAddressBytesRx = 0;
/**
* Value to read/write
*/
private int value;
/**
* Is the peripheral ready to receive / send data?
*/
private boolean ready = false;
private MSP430Core cpu;
private USARTSource source;
private boolean txScheduled = false;
/**
* Tx Event Handler
*/
private TimeEvent txTrigger = new TimeEvent(0) {
public void execute(long t) {
/* Transmit the next pending byte to the uC */
if (numBytesTotal > numBytesRxTx) {
int tmp = (value >> ((numBytesTotal - numBytesRxTx - 1) * 8)) & 0xff;
log("<sent> " + tmp);
source.byteReceived(tmp);
numBytesRxTx++;
}
txScheduled = false;
}
};
/**
* Class constructor
*
* @param name
* @param address
* @param src
* @param cpu
*/
public I2CUnit(String name, int address, USARTSource src, MSP430Core cpu) {
this.name = name;
busAddress = address;
if (src != null) {
src.addUSARTListener((USARTListener) this);
}
this.cpu = cpu;
source = src;
}
/**
* The microcontroller requested to write a register of the i2c peripheral
*
* @param address
* address of the register
* @param value
* value to write
*/
protected abstract void registerWrite(int address, int value);
/**
* Read a register of the peripheral
*
* @param address
* address of the register to read
* @return write this value
*/
protected abstract int registerRead(int address);
@Override
public void dataReceived(USARTSource source, int data) {
I2CData d = new I2CData(data);
if (d.isStart()) {
if (d.getAddress() != busAddress) {
// Message not for us!
return;
}
isRead = d.isRead();
numBytesRxTx = 0;
ready = true;
log("<start> read?" + isRead);
source.byteReceived(I2CData.ACK);
/*
* Read the register if necessary. When a write reset the memory
* address to avoid strange situations
*/
if (isRead) {
value = registerRead(registerAddress);
scheduleTransmission();
} else {
registerAddress = 0x00;
value = 0;
regAddressBytesRx = 0;
}
return;
}
if (ready) {
/*
* In case of stop condition write the register if necessary and set
* the end of the message acceptance
*/
if (d.isStop()) {
log("<stop>");
if (!isRead && numBytesRxTx > 1) {
registerWrite(registerAddress, value);
}
ready = false;
return;
}
/* If the master transmits and ACK we can transmit the next byte */
if (d.isAck() && isRead) {
log("<ack>");
if (numBytesRxTx < numBytesTotal) {
scheduleTransmission();
}
}
/*
* Write messages are for the address and for the value. First the
* address of the register is received. Afterwards the value itself
*/
if (!isRead) {
if (regAddressBytesRx < regAddressLen) {
log("<addr>");
registerAddress = d.getData();
regAddressBytesRx++;
} else if (numBytesRxTx < numBytesTotal) {
value <<= 8;
value |= d.getData();
numBytesRxTx++;
log("<data>" + value);
} else {
logw("Received more bytes than expected!");
}
source.byteReceived(I2CData.ACK);
}
}
}
/**
* Schedule a transmission with the correct baud rate
*/
private void scheduleTransmission() {
int baudRate = defaultBaudRate;
if (source instanceof GenericUSCI) {
baudRate = ((GenericUSCI) source).getBaudRate();
}
if (!txScheduled && numBytesRxTx < numBytesTotal) {
cpu.scheduleCycleEvent(txTrigger, cpu.cpuCycles + 1);
txScheduled = true;
}
}
/**
* Log a message when the DEBUG flag is set
*
* @param msg
*/
protected void log(String msg) {
if (DEBUG) {
System.out.println("(i2c) " + name + ": " + msg);
}
}
/**
* Log a warning message
*
* @param msg
*/
protected void logw(String msg) {
System.err.println("(i2c) " + name + ": WARNING " + msg);
}
/**
* Default baud rate for the communications
*/
private int defaultBaudRate = 100;
protected void setDefaultBaudRate(int br) {
defaultBaudRate = br;
}
}

View File

@ -1,7 +1,14 @@
package se.sics.mspsim.core;
/*
import se.sics.mspsim.chip.I2CUnit.I2CData;
import se.sics.mspsim.util.RingBuffer;
/**
* GenericUSCI - for newer MSP430's
*
* @author Unknown
* @author Víctor Ariño <victor.arino@tado.com>
*/
public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
@ -14,13 +21,16 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
public static final int STAT = 0x0a;
public static final int RXBUF = 0x0c;
public static final int TXBUF = 0x0e;
// i2C Registers
public static final int I2COA = 0x10;
public static final int I2CSA = 0x12;
// Interrupt related
public static final int IE = 0x1c;
public static final int IFG = 0x1d;
public static final int IV = 0x1e;
// Misc flags
public static final int RXIFG = 0x01;
public static final int TXIFG = 0x02;
@ -43,8 +53,6 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
private int tickPerByte = 1000;
private long nextTXReady = -1;
private int nextTXByte = -1;
private int txShiftReg = -1;
private boolean transmitting = false;
private int ctl0;
@ -56,7 +64,7 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
private int txbuf;
private int stat;
private boolean spiMode = false;
private boolean syncMode = false;
/* always on for now - but SWRST controls it */
private boolean moduleEnabled = true;
@ -70,7 +78,14 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
handleTransmit(t);
}
};
private boolean i2cEnabled;
private boolean i2cTransmitter;
private int i2cSlaveAddress;
private int i2cOwnAddress;
private boolean readyForNextTransmit;
private boolean stopConditionPending;
private RingBuffer<Integer> txBuffer = new RingBuffer<>(100);
public GenericUSCI(MSP430Core cpu, int uartIndex, int[] memory, MSP430Config config) {
super(config.uartConfig[uartIndex].name, cpu, memory, config.uartConfig[uartIndex].offset);
@ -85,14 +100,14 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
}
public void reset(int type) {
nextTXReady = cpu.cycles + 100;
txShiftReg = nextTXByte = -1;
nextTXReady = cpu.cycles + tickPerByte + 100;
transmitting = false;
clrBitIFG(RXIFG);
setBitIFG(TXIFG); /* empty at start! */
stat &= ~USCI_BUSY;
moduleEnabled = true; //false;
updateIV();
txBuffer.clear();
}
void updateIV() {
@ -140,30 +155,37 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
private void handleTransmit(long cycles) {
if (cpu.getMode() >= MSP430Core.MODE_LPM3) {
System.out.println(getName() + " Warning: USART transmission during LPM!!! " + nextTXByte);
System.out.println(getName() + " Warning: USART transmission during LPM!!! ");
}
if (transmitting) {
/* in this case we have shifted out the last character */
USARTListener listener = this.usartListener;
if (listener != null && txShiftReg != -1) {
listener.dataReceived(this, txShiftReg);
if (listener != null && !txBuffer.isEmpty()) {
int t = txBuffer.remove();
listener.dataReceived(this, t);
if (i2cEnabled) {
if ((t & I2CData.START) > 0) {
ctl1 &= ~0x02;
} else if ((t & I2CData.STOP) > 0) {
ctl1 &= ~0x04;
}
}
}
/* nothing more to transmit after this - stop transmission */
if (nextTXByte == -1) {
/* ~BUSY - nothing more to send - and last data already in RX */
stat &= ~USCI_BUSY;
transmitting = false;
txShiftReg = -1;
if (txBuffer.isEmpty()) {
/* ~BUSY - nothing more to send - and last data already in RX */
stat &= ~USCI_BUSY;
transmitting = false;
setBitIFG(TXIFG);
}
}
/* any more chars to transmit? */
if (nextTXByte != -1) {
txShiftReg = nextTXByte;
nextTXByte = -1;
if (!txBuffer.isEmpty()) {
/* txbuf always empty after this */
setBitIFG(TXIFG);
clrBitIFG(TXIFG);
transmitting = true;
nextTXReady = cycles + tickPerByte + 1;
cpu.scheduleCycleEvent(txTrigger, nextTXReady);
@ -215,15 +237,14 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
// address + " = " + data);
switch (address) {
case CTL0:
ctl0 = data;
spiMode = (data & 0x01) > 0;
syncMode = (data & 0x01) > 0;
i2cEnabled = (data & 0x06) == 0x06;
if (DEBUG) log(" write to UxxCTL0 " + data);
break;
case CTL1:
/* emulate the reset */
if ((ctl1 & SWRST) == SWRST && (data & SWRST) == 0)
reset(0);
ctl1 = data;
moduleEnabled = (data & SWRST) == 0;
if (DEBUG) log(" write to UxxCTL1 " + data + " => ModEn:" + moduleEnabled);
@ -239,6 +260,34 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
}
}
updateBaudRate();
/*
* When in I2C mode and an start or stop condition is reached
* just transmit the corresponding signals.
*/
if (i2cEnabled) {
if ((data & 0x04) > 0 && (ctl1 & 0x04) == 0) { // stop condition
txBuffer.add(I2CData.STOP);
}
if ((data & 0x02) > 0 && (ctl1 & 0x02) == 0) {
i2cTransmitter = (data & 0x10) == 0x10;
/* Transmit a start condition START|ADDR|R/W */
int t = I2CData.START;
t |= i2cSlaveAddress << 1;
t |= i2cTransmitter ? 1 : 0;
txBuffer.add(t);
rxbuf = 0;
}
clrBitIFG(TXIFG);
stat |= USCI_BUSY;
if (!transmitting) {
nextTXReady = cycles + 1; //tickPerByte + 3;
cpu.scheduleCycleEvent(txTrigger, nextTXReady);
}
}
ctl1 = data;
break;
case MCTL:
mctl = data;
@ -272,12 +321,11 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
// Schedule on cycles here
// TODO: adding 3 extra cycles here seems to give
// slightly better timing in some test...
nextTXByte = data;
txBuffer.add(data);
if (!transmitting) {
/* how long time will the copy from the TX_BUF to the shift reg take? */
/* assume 3 cycles? */
nextTXReady = cycles + 1; //tickPerByte + 3;
nextTXReady = cycles + tickPerByte + 1; //tickPerByte + 3;
cpu.scheduleCycleEvent(txTrigger, nextTXReady);
}
} else {
@ -285,6 +333,14 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
}
txbuf = data;
break;
case I2CSA:
i2cSlaveAddress = data & 0x3ff;
break;
case I2COA:
i2cOwnAddress = data & 0x3ff;
break;
}
}
@ -313,6 +369,20 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
/* This should be changed to a state rather than an "event" */
/* Force callback since this is not used as a state */
stateChanged(USARTListener.RXFLAG_CLEARED, true);
/* For timing issues we have to send the ACK after reading the
* register */
if (!i2cTransmitter) {
txBuffer.add(I2CData.ACK);
stat |= USCI_BUSY;
if (!transmitting) {
/* how long time will the copy from the TX_BUF to the shift reg take? */
/* assume 3 cycles? */
nextTXReady = cpu.cpuCycles + tickPerByte + 1; //tickPerByte + 3;
cpu.scheduleCycleEvent(txTrigger, nextTXReady);
}
}
return tmp;
case MCTL:
return mctl;
@ -351,9 +421,16 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
//System.out.println(getName() + " byte received: " + b);
if (DEBUG) {
log(" byteReceived: " + b + " " + (char) b);
log("byteReceived: " + b + " " + (char) b);
}
/* Ignore i2c ACK messages */
if (i2cEnabled && b == I2CData.ACK) {
return;
}
rxbuf = b & 0xff;
// Indicate interrupt also!
setBitIFG(RXIFG);
@ -380,5 +457,15 @@ public class GenericUSCI extends IOUnit implements DMATrigger, USARTSource {
return "UTXIE: " + isIEBitsSet(TXIFG) + " URXIE:" + isIEBitsSet(RXIFG) + "\n" +
"UTXIFG: " + ((getIFG() & TXIFG) > 0) + " URXIFG:" + ((getIFG() & RXIFG) > 0);
}
public int getBaudRate() {
return tickPerByte;
}
protected void vlog(String msg) {
if (i2cEnabled) {
System.out.println("USCI "+msg);
}
}
}

View File

@ -0,0 +1,190 @@
package se.sics.mspsim.util;
/******************************************************************************
* File: RingBuffer.java
*
* Author: Keith Schwarz (htiek@cs.stanford.edu)
*
* An implementation of a synchronized queue backed by a ring buffer. This
* functionality and implementation is similar to the ArrayBlockingQueue class,
* but I thought that I'd implement my own version to get a better feel for how
* it works.
*
* A ring buffer is a space-efficient, locality-friendly implementation of a
* FIFO queue. It is implemented as a fixed-sized array that is treated as
* though it wraps around like a ring; it has no well-defined start or end
* point. This array stores two pointers, a read pointer and a write pointer,
* delineating where the next insert should take place and from where the next
* element should be dequeued. For example:
*
* [2] [3] [ ] [ ] [ ] [ ] [0] [1]
* ^ ^
* | |
* write read
*
* When using a ring buffer, one must be careful not to let the read and write
* pointers cross one another. If this happens, future write operations will
* start overwriting old elements that have not yet been consumed. For this
* reason, most ring buffers adopt one of two strategies. First, the ring buffer
* can increase its size whenever it runs out of room. This approach allows the
* buffer to grow arbitrarily large if need be. The second option, and the one
* used in this implementation, is simply to block on a read or write when data
* is not available. This allows the ring buffer to implement the
* producer/consumer pattern fairly easily; any number of threads can begin
* creating data while some number of threads consume it, and at no time are too
* many elements kept in memory waiting to be read.
*/
public final class RingBuffer<T> {
/* The actual ring buffer. */
private final T[] elements;
/* The write pointer, represented as an offset into the array. */
private int offset = 0;
/*
* The read pointer is encoded implicitly by keeping track of the number of
* unconsumed elements. We can then determine its position by backing up that
* many positions before the read position.
*/
private int unconsumedElements = 0;
/**
* Constructs a new RingBuffer with the specified capacity, which must be
* positive.
*
* @param size
* The capacity of the new ring buffer.
* @throws IllegalArgumentException
* If the capacity is negative.
*/
@SuppressWarnings("unchecked")
public RingBuffer(int size) {
/* Validate the size. */
if (size <= 0)
throw new IllegalArgumentException(
"RingBuffer capacity must be positive.");
/* Construct the array to be that size. */
elements = (T[]) new Object[size];
}
/**
* Clear the buffer
*/
@SuppressWarnings("unchecked")
public synchronized void clear() {
offset = 0;
unconsumedElements = 0;
for (int i = 0; i < elements.length; i++) {
elements[i] = null;
}
}
/**
* Appends an element to the ring buffer, blocking until space becomes
* available.
*
* @param elem
* The element to add to the ring buffer.
*/
public synchronized boolean add(T elem) {
/*
* Block until the capacity is nonzero. Otherwise we don't have any space
* to write.
*/
if (unconsumedElements == elements.length)
return false;
/*
* Write the element into the next open spot, then advance the write
* pointer forward a step.
*/
elements[offset] = elem;
offset = (offset + 1) % elements.length;
/*
* Increase the number of unconsumed elements by one, then notify any
* threads that are waiting that more data is now available.
*/
++unconsumedElements;
return true;
}
/**
* Returns the maximum capacity of the ring buffer.
*
* @return The maximum capacity of the ring buffer.
*/
public int capacity() {
return elements.length;
}
/**
* Observes, but does not dequeue, the next available element, blocking until
* data becomes available.
*
* @return The next available element.
*/
public synchronized T peek() {
/* Wait for data to become available. */
if (unconsumedElements == 0)
return null;
/*
* Hand back the next value. The index of this next value is a bit tricky
* to compute. We know that there are unconsumedElements elements waiting
* to be read, and they're contiguously before the write position.
* However, the buffer wraps around itself, and so we can't just do a
* naive subtraction; that might end up giving us a negative index. To
* avoid this, we'll use a clever trick in which we'll add to the index
* the capacity minus the distance. This value must be positive, since the
* distance is never greater than the capacity, and if we then wrap this
* value around using the modulus operator we'll end up with a valid
* index. All of this machinery works because
*
* (x + (n - k)) mod n == (x - k) mod n
*
* And Java's modulus operator works best on positive values.
*/
return elements[(offset + (capacity() - unconsumedElements)) % capacity()];
}
/**
* Removes and returns the next available element, blocking until data
* becomes available.
*
* @return The next available element
*/
public synchronized T remove() {
/* Use peek() to get the element to return. */
T result = peek();
/* Mark that one fewer elements are now available to read. */
--unconsumedElements;
/* Because there is more space left, wake up any waiting threads. */
notifyAll();
return result;
}
/**
* Returns the number of elements that are currently being stored in the ring
* buffer.
*
* @return The number of elements currently stored in the ring buffer.
*/
public synchronized int size() {
return unconsumedElements;
}
/**
* Returns whether the ring buffer is empty.
*
* @return Whether the ring buffer is empty.
*/
public synchronized boolean isEmpty() {
return size() == 0;
}
}