mirror of https://github.com/contiki-ng/mspsim
Merge pull request #11 from lebrush/usci-i2c
Extended GenericUSCI to support i2c protocol as well
This commit is contained in:
commit
562aa94a1e
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue