Ningkai Wu / Mbed 2 deprecated test_spi_ble

Dependencies:   mbed-rtos mbed

main.cpp

Committer:
wninghj
Date:
2018-04-05
Revision:
0:39b7f3158ebe

File content as of revision 0:39b7f3158ebe:

#include "mbed.h"
#include "rtos.h"
#include "sdep.h"
#include "Adafruit_FIFO.h"
#include <stdio.h>

#define BLUEFRUIT_MODE_COMMAND   HIGH
#define BLUEFRUIT_MODE_DATA      LOW
#define BLE_BUFSIZE              4*SDEP_MAX_PACKETSIZE


#define SPI_IGNORED_BYTE          0xFEu /**< SPI default character. Character clocked out in case of an ignored transaction. */
#define SPI_OVERREAD_BYTE         0xFFu /**< SPI over-read character. Character clocked out after an over-read of the transmit buffer. */
#define SPI_DEFAULT_DELAY_US      50

DigitalOut myled(LED1);

Serial pc(USBTX,USBRX);

SPI spi(p5, p6, p7); // mosi, miso, sclk

DigitalOut cs(p21);
DigitalIn m_irq_pin(p22);
DigitalOut ble_reset(p23);

void enable_spi() {
    cs = 0;
}

void disable_spi() {
    cs = 1;
}

// TX
uint8_t         m_tx_buffer[SDEP_MAX_PACKETSIZE] = {0};
uint8_t         m_tx_count = 0;

// RX
uint8_t         m_rx_buffer[BLE_BUFSIZE * 2] = {0};
Adafruit_FIFO   m_rx_fifo(m_rx_buffer, sizeof(m_rx_buffer), 1, true);

enum BLE_MODE {
    COMMAND = 0,
    DATA
};

BLE_MODE bleMode = COMMAND;


// prototypes
void spixfer(void *buff, size_t len);
uint8_t spixfer(uint8_t x);
bool bleGetResponse(void);
bool sendPacket(uint16_t command, const uint8_t* buf, uint8_t count, uint8_t more_data);
size_t bleWriteChar(uint8_t c);
void bleWrite(char *cmd);


volatile unsigned long _millis = 0;
unsigned long millis(void) {
    return _millis;
}


class TimeoutTimer
{
  private:
    uint32_t start;
    uint32_t interval;

  public:
    TimeoutTimer()              { start = millis(); interval = 0; }
    TimeoutTimer(uint32_t msec) { set(msec); }

    void set(uint32_t msec)     { start = millis(); interval = msec; }
    bool expired(void)  const   { return (millis() - start) >= interval; }
    void restart(void)          { start = millis(); }
    void reset(void)            { start += interval; } // used for periodic invoke to prevent drift
};


uint16_t word(uint8_t h, uint8_t l) {
    uint16_t res = h;
    res <<= 8;
    res |= l;
    return res;
}


uint32_t _timeout = 250;


bool bleGetPacket(sdepMsgResponse_t* p_response)
{
  // Wait until IRQ is asserted, double timeout since some commands take long time to start responding
  TimeoutTimer tt(2*_timeout);
  
  while ( !m_irq_pin ) {
    if (tt.expired()) return false;
  }
  
  sdepMsgHeader_t* p_header = &p_response->header;
  
  enable_spi();

  tt.set(_timeout);

  do {
    if ( tt.expired() ) break;

    p_header->msg_type = spixfer(0xff);

    if (p_header->msg_type == SPI_IGNORED_BYTE)
    {
      // Bluefruit may not be ready
      // Disable & Re-enable CS with a bit of delay for Bluefruit to ready itself
      disable_spi();
      wait_us(50);
      enable_spi();
    }
    else if (p_header->msg_type == SPI_OVERREAD_BYTE)
    {
      // IRQ may not be pulled down by Bluefruit when returning all data in previous transfer.
      // This could happen when Arduino MCU is running at fast rate comparing to Bluefruit's MCU,
      // causing an SPI_OVERREAD_BYTE to be returned at stage.
      //
      // Walkaround: Disable & Re-enable CS with a bit of delay and keep waiting
      // TODO IRQ is supposed to be OFF then ON, it is better to use GPIO trigger interrupt.

      disable_spi();
      // wait for the clock to be enabled..
//      while (!digitalRead(m_irq_pin)) {
//        if ( tt.expired() ) break;
//      }
//      if (!digitalRead(m_irq_pin)) break;
      wait_us(50);
      enable_spi();
    }
  }  while (p_header->msg_type == SPI_IGNORED_BYTE || p_header->msg_type == SPI_OVERREAD_BYTE);

  bool result=false;

  // Not a loop, just a way to avoid goto with error handling
  do
  {
    // Look for the header
    // note that we should always get the right header at this point, and not doing so will really mess up things.
    while ( p_header->msg_type != SDEP_MSGTYPE_RESPONSE && p_header->msg_type != SDEP_MSGTYPE_ERROR && !tt.expired() )
    {
      p_header->msg_type = spixfer(0xff);
    }
    
    if ( tt.expired() ) break;
    
    memset( (&p_header->msg_type)+1, 0xff, sizeof(sdepMsgHeader_t) - 1);
    spixfer((&p_header->msg_type)+1, sizeof(sdepMsgHeader_t) - 1);

    // Command is 16-bit at odd address, may have alignment issue with 32-bit chip
    uint16_t cmd_id = word(p_header->cmd_id_high, p_header->cmd_id_low);

    // Error Message Response
    if ( p_header->msg_type == SDEP_MSGTYPE_ERROR ) break;

    // Invalid command
    if (!(cmd_id == SDEP_CMDTYPE_AT_WRAPPER ||
          cmd_id == SDEP_CMDTYPE_BLE_UARTTX ||
          cmd_id == SDEP_CMDTYPE_BLE_UARTRX) )
    {
      break;
    }

    // Invalid length
    if(p_header->length > SDEP_MAX_PACKETSIZE) break;

    // read payload
    memset(p_response->payload, 0xff, p_header->length);
    spixfer(p_response->payload, p_header->length);

    result = true;
  }while(0);

  disable_spi();

  return result;
}


/******************************************************************************/
/*!

*/
/******************************************************************************/
void spixfer(void *buff, size_t len) {
  uint8_t *p = (uint8_t *)buff;
  while (len--) {
    p[0] = spixfer(p[0]);
    p++;
  }
}

/******************************************************************************/
/*!

*/
/******************************************************************************/
uint8_t spixfer(uint8_t x) {
  return spi.write(x);
}


/******************************************************************************/
/*!
    @brief  Try to perform an full AT response transfer from Bluefruit, or execute
            as many SPI transaction as internal FIFO can hold up.

    @note   If verbose is enabled, all the received data will be print to Serial

    @return
      - true  : if succeeded
      - false : if failed
*/
/******************************************************************************/
bool bleGetResponse(void)
{
  // Try to read data from Bluefruit if there is enough room in the fifo
  while ( m_rx_fifo.remaining() >= SDEP_MAX_PACKETSIZE )
  {
    // Get a SDEP packet
    sdepMsgResponse_t msg_response;
    memset(&msg_response, 0, sizeof(sdepMsgResponse_t));

    if ( !bleGetPacket(&msg_response) ) return false;

    // Write to fifo
    if ( msg_response.header.length > 0)
    {
      m_rx_fifo.write_n(msg_response.payload, msg_response.header.length);
    }

    // No more packet data
    if ( !msg_response.header.more_data ) break;

    // It takes a bit since all Data received to IRQ to get LOW
    // May need to delay a bit for it to be stable before the next try
    wait_us(50);
  }

  return true;
}


void bleWrite(char *cmd) {
    while (*cmd != '\0') {
        bleWriteChar((uint8_t) *cmd);
        cmd += 1;
    }
}


/******************************************************************************/
/*!
    @brief Check if the response from the previous command is ready

    @return 'true' if a response is ready, otherwise 'false'
*/
/******************************************************************************/
int bleAvailable(void)
{
  if (! m_rx_fifo.empty() ) {
    return m_rx_fifo.count();
  }

  if ( bleMode == DATA )
  {
    // DATA Mode: query for BLE UART data
    sendPacket(SDEP_CMDTYPE_BLE_UARTRX, NULL, 0, 0);

    // Waiting to get response from Bluefruit
    bleGetResponse();

    return m_rx_fifo.count();
  }else{
    return m_irq_pin;
  }
}

/******************************************************************************/
/*!
    @brief Get a byte from response data, perform SPI transaction if needed

    @return -1 if no data is available
*/
/******************************************************************************/
int bleRead(void)
{
  uint8_t ch;

  // try to grab from buffer first...
  if (!m_rx_fifo.empty()) {
    m_rx_fifo.read(&ch);
    return (int)ch;
  }

  if ( bleMode == DATA )
  {
    // DATA Mode: query for BLE UART data
    sendPacket(SDEP_CMDTYPE_BLE_UARTRX, NULL, 0, 0);

    // Waiting to get response from Bluefruit
    bleGetResponse();
  }else
  {
    // COMMAND Mode: Only read data from Bluefruit if IRQ is raised
    if ( m_irq_pin ) bleGetResponse();
  }

  return m_rx_fifo.read(&ch) ? ((int) ch) : EOF;

}


uint16_t bleReadLine(char * buf, uint16_t bufsize)
{
  uint16_t timeout = 1000 * 10;
  bool multiline = false;
  uint16_t replyidx = 0;

  while (timeout--) {
    while(bleAvailable()) {
      //pc.printf("SPI: Available\n");
      char c = bleRead();
      //pc.printf("SPI: %d\n", (int) c);
      //SerialDebug.println(c);

      if (c == '\r') continue;

      if (c == '\n') {
        // the first '\n' is ignored
        if (replyidx == 0) continue;

        if (!multiline) {
          timeout = 0;
          break;
        }
      }
      buf[replyidx] = c;
      replyidx++;

      // Buffer is full
      if (replyidx >= bufsize) {
        //if (_verbose) { SerialDebug.println("*overflow*"); }  // for my debuggin' only!
        timeout = 0;
        break;
      }
    }

    // delay if needed
    if (timeout) {
        Thread::wait(1);
    }
  }

  buf[replyidx] = 0;  // null term

  return replyidx;
}

bool waitForOK(void)
{
    // Use temp buffer to avoid overwrite returned result if any
    char tempbuf[BLE_BUFSIZE + 1];

    while (bleReadLine(tempbuf, BLE_BUFSIZE)) {
        // pc.printf("SPI: %s\n", tempbuf);
        if (strcmp(tempbuf, "OK") == 0) return true;
        if (strcmp(tempbuf, "ERROR") == 0) return false;
    }
    return false;
}

bool sendATCommand(char *cmd) {
    bleMode = COMMAND;
    bleWrite(cmd);
    bleWrite("\r\n");
    bool result = waitForOK();
    bleMode = DATA;
    return result;
}

bool sendCommandCheckOK(char *cmd) {
    return sendATCommand(cmd);
}

// =============================================================================

void flush(void)
{
    m_rx_fifo.clear();
}

uint8_t highByte(uint16_t x) {
    return (uint8_t) (x >> 8);
}

uint8_t lowByte(uint16_t x) {
    return (uint8_t) (x & 0xff);
}

bool sendPacket(uint16_t command, const uint8_t* buf, uint8_t count, uint8_t more_data) {
  // flush old response before sending the new command
  if (more_data == 0) flush();

  sdepMsgCommand_t msgCmd;

  msgCmd.header.msg_type    = SDEP_MSGTYPE_COMMAND;
  msgCmd.header.cmd_id_high = highByte(command);
  msgCmd.header.cmd_id_low  = lowByte(command);
  msgCmd.header.length      = count;
  msgCmd.header.more_data   = (count == SDEP_MAX_PACKETSIZE) ? more_data : 0;

  // Copy payload
  if ( buf != NULL && count > 0) memcpy(msgCmd.payload, buf, count);

  enable_spi();

  TimeoutTimer tt(_timeout);

  // Bluefruit may not be ready
  while ( ( spixfer(msgCmd.header.msg_type) == SPI_IGNORED_BYTE ) && !tt.expired() )
  {
    // Disable & Re-enable CS with a bit of delay for Bluefruit to ready itself
    disable_spi();
    wait_us(SPI_DEFAULT_DELAY_US);
    enable_spi();
  }

  bool result = !tt.expired();
  if ( result )
  {
    // transfer the rest of the data
    spixfer((void*) (((uint8_t*)&msgCmd) +1), sizeof(sdepMsgHeader_t)+count-1);
  }

  disable_spi();

  return result;
}

size_t bleWriteChar(uint8_t c) {
  if (bleMode == DATA)
  {
    sendPacket(SDEP_CMDTYPE_BLE_UARTTX, &c, 1, 0);
    bleGetResponse();
    return 1;
  }

  // Following code handle BLUEFRUIT_MODE_COMMAND

  // Final packet due to \r or \n terminator
  if (c == '\r' || c == '\n')
  {
    if (m_tx_count > 0)
    {
      bool result = sendPacket(SDEP_CMDTYPE_AT_WRAPPER, m_tx_buffer, m_tx_count, 0);
      m_tx_count = 0;
      if (!result) {
        return 0;
      }
    }
  }
  // More than max packet buffered --> send with more_data = 1
  else if (m_tx_count == SDEP_MAX_PACKETSIZE)
  {
    bool result = sendPacket(SDEP_CMDTYPE_AT_WRAPPER, m_tx_buffer, m_tx_count, 1);

    m_tx_buffer[0] = c;
    m_tx_count = 1;
    if (!result) {
      return 0;
    }
  }
  // Not enough data, continue to buffer
  else
  {
    m_tx_buffer[m_tx_count++] = c;
  }
  return 1;
}

int32_t readline_parseInt(void)
{
  char buffer[101] = { 0 };
  uint16_t len = bleReadLine(buffer, 100);
  if (len == 0) return 0;

  // also parsed hex number e.g 0xADAF
  int32_t val = strtol(buffer, NULL, 0);

  return val;
}

bool send_arg_get_resp(int32_t *reply) {
    bleWrite("\r\n"); // execute command

    // parse integer response if required
    if (reply)
    {
        (*reply) = readline_parseInt();
    }

    // check OK or ERROR status
    return waitForOK();
}

int32_t sendATCommandIntReply(char *cmd) {
    int32_t reply = 0;
    BLE_MODE current_mode = bleMode;

    // switch mode if necessary to execute command
    bleMode = COMMAND;

    // Execute command with parameter and get response
    bleWrite(cmd);
    send_arg_get_resp(&reply);

    // switch back if necessary
    if ( current_mode == DATA ) bleMode = DATA;
    return reply;
}

bool sendInitializePattern() {
    return sendPacket(SDEP_CMDTYPE_INITIALIZE, NULL, 0, 0);
}

bool isBLEConnected() {
    int32_t connected = 0;
    connected = sendATCommandIntReply("AT+GAPGETCONN");
    return connected;
}

 
void timer_function_millis(void const *n) {
    _millis += 1;
}

int main() {
    // timer to bump _millis
    RtosTimer millis_timer(timer_function_millis, osTimerPeriodic, (void *)0);
    millis_timer.start(1);
    
    pc.baud(115200);
    spi.format(8, 0);
    spi.frequency(4000000);
    
    pc.printf("Prepare to initiaize BLE\n");
    
    // initialize BLE
    cs = 1;
    if (!sendInitializePattern()) pc.printf("BLE Init Failed\n");
    ble_reset = 1;
    ble_reset = 0;
    Thread::wait(10);
    ble_reset = 1;
    Thread::wait(500);
    cs = 1;
    
    pc.printf("Set BLE Name\n");
    
    // change name
    if (!sendCommandCheckOK("AT+GAPDEVNAME=AdafruitDJ")) {
        pc.printf("Could not set device name.\n");
    }
    
    // wait until connected
    while (!isBLEConnected()) {
        Thread::wait(100);
    }
    
    pc.printf("Ready\n");
    
    Thread::wait(1000);
    
    int i = 0;
    while (true) {
        bleMode = DATA;
        // send character
        char str[40] = { 0 };
        sprintf(str, "Hello DJ %d\r\n", i);
        bleWrite(str);
        Thread::wait(1000);
        pc.printf("Send\n");
        i += 1;
    }
    
    Thread::wait(osWaitForever);
}