MMEx with SPI Slave to allow legacy devices to communicate with modern media such as USB, SD cards, the internet and all of the mbed\'s other interfaces

Dependencies:   NetServices MSCUsbHost mbed TMP102 SDFileSystem

spissp/spissp.cpp

Committer:
DeMein
Date:
2011-02-27
Revision:
0:67a55a82ce06

File content as of revision 0:67a55a82ce06:

/* MMEx for MBED - SPI interfacing and ringbuffer functions
 * Copyright (c) 2011 MK
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
  \file spissp.cpp
  \brief SPI interfacing and ringbuffer functions
*/

#include "spissp.h"

 /** Library for the interrupt driven SPI SLave
 *
 */
spislirq *spislirq::instance;

 /** Library for the interrupt driven SPI SLave
 *
 */
spislirq::spislirq (int PortNum): smldl(p11, p12, p13, p14), 
                                  pc(USBTX, USBRX, "pc"), _led4(LED4)
  { }

/** initialization of SPI Slave and buffers
 *  
 */
void spislirq::init()   {
    uint32_t tmp;

    smldl.format(8,3);             // SPI format: 8 bits, mode 3
                                   // mode | POL PHA
                                   // -----+--------
                                   //   0  |  0   0
                                   //   1  |  0   1
                                   //   2  |  1   0
                                   //   3  |  1   1
    smldl.frequency(1000000);      // more or less universal speed at 1 MHz
                                   // determines time out IRQ time!

    // initialize buffers
    rx_in = 0;
    rx_out = 0;
    tx_in = 0;
    tx_out = 0;

    // add code here to set the SSP interrupt register and enable interrupts
    // setup IMSC Interrupt Mask Set/Clear Register
    // only enable interrupts for RX FIFO half full and RX timeout
    tmp = (SSP_IMSC_RT | SSP_IMSC_RX) & SSP_IMSC_BITMASK;

    LPC_SSP0->IMSC = tmp;

    // attach our interrupt service routine
    instance = this;
    NVIC_SetVector(SSP0_IRQn, (uint32_t)&_spi_isr);
    enable_irq();
 }

/** disable SSP0 interrupts
 *  will discard the byte if buffer full
 */
 void spislirq::disable_irq(void)
{
    NVIC_DisableIRQ(SSP0_IRQn);
}

/** enable SSP0 interrupts
 *  will discard the byte if buffer full
 */
void spislirq::enable_irq(void)
{
    NVIC_EnableIRQ(SSP0_IRQn);
}

/** instantiation of isr
 *  will discard the byte if buffer full
 */
void spislirq::_spi_isr(void)
{
    instance->spi_isr();
}

/** add one byte to the transmit buffer, no blocking, no protocol
 *  will discard the byte if buffer full
 *
 *  @param c byte to send
 *
 */
void spislirq::txx_add(unsigned char c) {
  if (!tx_full()) {
    txxbuf[tx_in] = c;
    tx_in = (tx_in + 1) % bufsize;
  }
}

/** SPI SLave interrupt service routine
 *
 */
void spislirq::spi_isr(void)
{
  uint16_t inp;

  _led4 = !_led4;   // visualize we are in the isr

  // we get in the ISR when there is at least one input char pending
  // IRQ sources: - RX FIFO at least half full (4 chars)
  //              - RX FIFO not empty and not read from a time-out period


  // first we check if there is enough room left in the RX buffer
  // if there is less than 10% left we signal this to the SPI Master
  // is is very unlikely that the TX buffer is full when there is risk for
  // overrun, but we can always send the '>B' the next time, as there will
  // be more than 20 free chars in the buffer
  if (rx_perc() > 90) {
    txx_add('>');
    txx_add('B');
  }

  // now do the real work, just like in spi_rw():
  // 1. fill output FIFO with data from TX buffer

  while ((!tx_empty()) && tx_fifo_notfull) {
    // we can indeed try to transmit data
    // put as much data as possible in the transmit FIFO
    LPC_SSP0->DR =  (uint16_t)txxbuf[tx_out];
    tx_out = (tx_out + 1) % bufsize;
  }

  // in case the TX FIFO and output buffer are empty, add 0x00
  if (tx_empty() && tx_fifo_empty) {
    LPC_SSP0->DR =  (uint16_t)0x00;
  }

  if irq_RTMIS {
    // interrupt on timeout, clear RTIC to clear interrupt
    LPC_SSP0->ICR = (SSP_ICR_RT & SSP_ICR_BITMASK);
  }

  // we must ensure that always the complete RX FIFO is read
  // when the RX buffer is really full, characters are discarded
  while (rx_fifo_notempty) {
    inp = LPC_SSP0->DR;
    if ((inp != 0x00) && !rx_full()) {
      // received value only interesting when not 0x00
      rxxbuf[rx_in] = inp;
      rx_in = (rx_in + 1) % bufsize;
    }
  }
}


/** returns the number of bytes available in the transmit buffer
 *
 *  @return available space in transmit buffer
 */
int spislirq::tx_room() {
  if (tx_in < tx_out) {
    return(tx_out - tx_in - 1);
  } else {
    return(bufsize - tx_in + tx_out - 1);
  }
}

/** returns the percentage used in the transmit buffer
 *
 *  @return percentage from 0 to 100
 */
int spislirq::tx_perc() {
  // returns the percentage used in the transmit buffer
  return(100 * tx_use() / bufsize);
}

/** returns the percentage used in the receive buffer
 *
 *  @return percentage from 0 to 100
 */
int spislirq::rx_perc() {
  // returns the percentage used in the transmit buffer
  return(100 * rx_use() / bufsize);
}


/** returns the amount af actual bytes used in the transmit buffer
 *
 *  @return bytes used in transmit buffer
 */
int spislirq::tx_use() {
  return(bufsize - tx_room() - 1);
}

/** returns the number of bytes available in the receive buffer
 *
 *  @return available space in transmit buffer
 */
int spislirq::rx_room() {
  if (rx_in < rx_out) {
    return(rx_out - rx_in - 1);
  } else {
    return(bufsize - rx_in + rx_out -1);
  }
}

/** returns the amount af actual bytes used in the receive buffer
 *
 *  @return bytes used in receive buffer
 */
int spislirq::rx_use() {
  return(bufsize - rx_room() - 1);
}

/** empties the transmit buffer
 */
void spislirq::flush_tx() {
  tx_out = tx_in = 0;
}

/** empties the receive buffer
 */
void spislirq::flush_rx() {
  rx_out = rx_in = 0;
}

/** check if the TX buffer is empty
 * @return true if the TX buffer is empty
 */
bool spislirq::tx_empty() {
  return (tx_in == tx_out);
}

/** check if the TX buffer is full
 * @return true if the TX buffer is full
 */
bool spislirq::tx_full() {
  return(((tx_in + 1) % bufsize) == tx_out);
}

/** check if the RX buffer is empty
 * @return true if the RX buffer is empty
 */
bool spislirq::rx_empty() {
  return(rx_in == rx_out);
}

/** check if the RX buffer is full
 * @return true if the RX buffer is full
 */
bool spislirq::rx_full() {
  return(((rx_in + 1) % bufsize) == rx_out);
}

/** add one byte to the transmit buffer, no protocol handling is done
 *
 *  @param Bt byte to send
 *  @return 0 when succesfull, will block until there is room in the buffer
 *
 */
int spislirq::tx_add(unsigned char c) {
  while (tx_full()) {
    do_callback();
    wait_ms(1);
  }
  do_callback();
  disable_irq();
  if (!tx_full()) {
    txxbuf[tx_in] = c;
    tx_in = (tx_in + 1) % bufsize;
    enable_irq();
    // DBG_outchar(c);
    return 0;
  } else {
    enable_irq();
    return -1;
  }
}

/** add one byte to the transmit buffer with protocol handling,
 *  function will block until there is room in the transmit buffer
 *
 *  @param Bt byte to send
 */
void spislirq::tx_addp(unsigned char c) {
  // first empty buffer to at least 90%
  switch (c) {
    case c_arrow  : tx_add(c_escape);    // to send the > character
                    tx_add(c_arrow);     // to send the > character
                    break;
    case NULL     : tx_add(c_escape);    // to send the > character
                    tx_add(c_null);      // to send the N character
                    break;
    default       : tx_add(c);          // just send it ...
                    break;
  }
}

/** adds a complete string to the transmit buffer, with protocol processing.
 *  function will block until there is room in the transmit buffer
 *
 *  @param S string to be transmitted
 *  @return always returns 0
 */
int spislirq::tx_string(char S[]) {
  int i = 0;

  while ((S[i] != 0x00)) {
    // add until end of string
    tx_addp(S[i]);
    i++;
  }
  return(0);
}

/** inserts one byte in the receive buffer without protocol processing
 *  used for command batch processing
 *
 *  @return the character read, blocks if RX buffer is empty!
 *
 */
int spislirq::rx_add(unsigned char c) {
  while (rx_full()) {
  do_callback();
    wait_ms(1);  // wait until RC buffer full
  }
  disable_irq();
  if (!rx_full()) {
    rxxbuf[rx_in] = c;
    rx_in = (rx_in + 1) % bufsize;
    enable_irq();
    return c;
  }
  enable_irq();
  return c;
}

/** read one byte from the receive buffer without protocol processing
 *
 *  @return the character read, blocks if RX buffer is empty!
 *
 */
int spislirq::rx_read() {
  unsigned char c = 0;

  while (rx_empty()) {
    do_callback();
    wait_us(500);
  }
  disable_irq();
  if (!rx_empty()) {
    c = rxxbuf[rx_out];
    rx_out = (rx_out + 1) % bufsize;
    enable_irq();
    // DBG_inchar(c);
    return c;
  }
  enable_irq();
  return c;
}

/** peeks in receive buffer, reads next pending input char without advancing buffer pointer
 *
 *  @return the character read, blocks if RX buffer is empty!
 *
 */
int spislirq::rx_peek() {
  unsigned char c = 0;

  while (rx_empty()) {
    do_callback();
    wait_us(500);
  }
  disable_irq();
  if (!rx_empty()) {
    c = rxxbuf[rx_out];
  }
  enable_irq();
  return c;
}

/** read one byte, will wait until a valid char is read,
 *  this function is blocking and will process escape characters,
 *  when an escape is seen, will try to read the next char
 *
 *  @param mode command line processing or data read (ignored)
 *  @return character read, or -1 when >F seen, -2 when >I seen
 *
 */
int spislirq::rxx_read(bool mode) {
  unsigned char bt1 = 0;
  unsigned char bt2 = 0;
  unsigned char h1  = 0;
  unsigned char h2  = 0;
  int rslt = 0;

  bt1 = rx_read();

  // we have now a character, check for escape codes
  if (bt1 == c_escape) {
    // this is an escape, now check for the next character
    bt2 = rx_peek();        // peek in buffer, but do not read

    switch (bt2) {
      case c_arrow  : bt2 = rx_read();                   // read the char
                      rslt = c_escape;
                      break;

      case c_eof    : bt2 = rx_read();                   // read the char
                      rslt = -1;                         // <EOF>
                      break;

      case c_return : bt2 = rx_read();                   // read the char
                      rslt = 0x0d;                       // carriage return
                      break;

      case c_null   :
      case c_zero   : bt2 = rx_read();                   // read the char
                      rslt = 0;                          // zero char
                      break;

      case c_hex    :
      case c_hexalt : bt2 = rx_read();                    // read the char
                      // now get next two chars, which must be hex digits
                      h1 = rx_read();                     // get data from buffer
                      h2 = rx_read();                     // get data from buffer

                      rslt = 16 * hex2int(h1) + hex2int(h2); // calculate the result
                      break;

      case c_intrpt : bt2 = rx_read();                   // read the char
                      rslt = -2;                         // interrupt
                      break;

      default       : rslt = bt1;                        // ignore

    }  // end of switch(bt2)

  } else {
    // regular data send, no escape character
    rslt = bt1;
  }
  return rslt;
}

void spislirq::DBG_set(int l) {
  if ((l >= DBG_OFF) && (l <= DBG_FULL)) DBG_level = l;
}


/** show a debug message with a string and a character both a character and hex value
 *
 *  @param src this will typically be the name of the function
 *  @param c character to be shown
 */
void spislirq::DBG_chr(char* src, char c) {
  if (DBG_level != DBG_OFF) {
    int cc = c;
    if (c < 32) c = '.';
    printf("%s : %c [%02x]\n", src, c, cc);
  }
}

/** show one character in hex, input from SPI to the MBED
 *
 *  @param c input to be shown
 */
void spislirq::DBG_inchar(char C) {
  // prints only one char in hex, preceded by a + for input followed by a space
  if (DBG_level == DBG_FULL) {
    printf("+%02x ", C);
  }
}

/** show one character in hex, output from MBED to SPI
 *
 *  @param c output to be shown
 */
void spislirq::DBG_outchar(char C) {
  if (DBG_level == DBG_FULL) {
    printf("-%02x ", C);
  }
}

/** convert a hex char '0' to 'F' in its decimal equivalent
 *
 *  @param C character
 *  @return a value 0 to 15, 0 when C was not in '0'..'F'
 *
 */
int spislirq::hex2int(char C) {
  if ((C >= '0') && (C <= '9')) return(C - '0');
  if ((C >= 'A') && (C <= 'F')) return(C - 'A' + 10);
  return(0);
}