Bluetooth UART support for the Adafruit BluefruitLE SPI, for the University of York Engineering Stage 1 project
Diff: Adafruit_BluefruitLE_SPI.cpp
- Revision:
- 0:a80552d32b80
- Child:
- 1:6ff0eee2da9c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Adafruit_BluefruitLE_SPI.cpp Sat Feb 06 20:58:22 2021 +0000 @@ -0,0 +1,562 @@ +/**************************************************************************/ +/*! + @file Adafruit_BluefruitLE_SPI.cpp + @author hathach, ktown (Adafruit Industries), ajp109 (University of York) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2015, Adafruit Industries (adafruit.com) + 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 copyright holders 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 ''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 HOLDER 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. +*/ +/**************************************************************************/ +#include "mbed.h" +#include "Adafruit_BluefruitLE_SPI.h" +#include <stdlib.h> + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +/******************************************************************************/ +/*! + @brief Instantiates a new instance of the Adafruit_BluefruitLE_SPI class + + @param[in] mosi + The MOSI pin for the SPI interface + @param[in] miso + The MISO pin for the SPI interface + @param[in] sck + The SCK pin for the SPI interface + @param[in] cs + The CS pin for the SPI interface + @param[in] irq + The IRQ pin - this must be a HW interrupt pin + @param[in] rstPin + The RESET pin (optional) +*/ +/******************************************************************************/ +Adafruit_BluefruitLE_SPI::Adafruit_BluefruitLE_SPI(PinName mosi, PinName miso, PinName sclk, PinName cs, PinName irq, PinName rst) : + m_rx_fifo(m_rx_buffer, sizeof(m_rx_buffer), 1, true), + m_spi(mosi, miso, sclk, cs), + m_irq(irq), + m_rst(rst) +{ + _physical_transport = BLUEFRUIT_TRANSPORT_HWSPI; + + m_spi.frequency(4000000); + // Mode 0, MSB-first is default + + if (m_rst.is_connected()) { + m_rst = true; + } + + m_tx_count = 0; + + m_mode_switch_command_enabled = true; +} + + +/******************************************************************************/ +/*! + @brief Initialize the HW to enable communication with the BLE module + + @return Returns 'true' if everything initialised correctly, otherwise + 'false' if there was a problem during HW initialisation. If + 'irqPin' is not a HW interrupt pin false will be returned. +*/ +/******************************************************************************/ +bool Adafruit_BluefruitLE_SPI::begin(bool v, bool blocking) +{ + _verbose = v; + + bool isOK; + + // Always try to send Initialize command to reset + // Bluefruit since user can define but not wiring RST signal + isOK = sendInitializePattern(); + + // use hardware reset if available + if (m_rst.is_connected()) { + // pull the RST to GND for 10 ms + m_rst = false; + thread_sleep_for(10); + m_rst = true; + + isOK= true; + } + + _reset_started_timestamp = Kernel::Clock::now(); + + // Bluefruit takes 1 second to reboot + if (blocking) { + thread_sleep_for(1000); + } + + return isOK; +} + +/******************************************************************************/ +/*! + @brief Uninitializes the SPI interface +*/ +/******************************************************************************/ +void Adafruit_BluefruitLE_SPI::end(void) +{ } + +/******************************************************************************/ +/*! + @brief Handle direct "+++" input command from user. + User should use setMode instead +*/ +/******************************************************************************/ +void Adafruit_BluefruitLE_SPI::simulateSwitchMode(void) +{ + _mode = 1 - _mode; + + char ch = '0' + _mode; + m_rx_fifo.write(&ch); + m_rx_fifo.write_n("\r\nOK\r\n", 6); +} + +/******************************************************************************/ +/*! + @brief Simulate "+++" switch mode command +*/ +/******************************************************************************/ +bool Adafruit_BluefruitLE_SPI::setMode(uint8_t new_mode) +{ + // invalid mode + if ( !(new_mode == BLUEFRUIT_MODE_COMMAND || new_mode == BLUEFRUIT_MODE_DATA) ) return false; + + // Already in the wanted mode + if ( _mode == new_mode ) return true; + + // SPI use different SDEP command when in DATA/COMMAND mode. + // --> does not switch using +++ command + _mode = new_mode; + + // If we're entering DATA mode, flush any old response, so that it isn't + // interpreted as incoming UART data + if (_mode == BLUEFRUIT_MODE_DATA) sync(); + + return true; +} + +/******************************************************************************/ +/*! + @brief Enable/disable recognition of "+++" switch mode command. + Usage of setMode is not affected. +*/ +/******************************************************************************/ +void Adafruit_BluefruitLE_SPI::enableModeSwitchCommand(bool enabled) +{ + m_mode_switch_command_enabled = enabled; +} + +/******************************************************************************/ +/*! + @brief Send initialize pattern to Bluefruit LE to force a reset. This pattern + follow the SDEP command syntax with command_id = SDEP_CMDTYPE_INITIALIZE. + The command has NO response, and is expected to complete within 1 second +*/ +/******************************************************************************/ +bool Adafruit_BluefruitLE_SPI::sendInitializePattern(void) +{ + return sendPacket(SDEP_CMDTYPE_INITIALIZE, NULL, 0, 0); +} + +/******************************************************************************/ +/*! + @brief Send out an packet with data in m_tx_buffer + + @param[in] more_data + More Data bitfield, 0 indicates this is not end of transfer yet +*/ +/******************************************************************************/ +bool Adafruit_BluefruitLE_SPI::sendPacket(uint16_t command, const uint8_t* buf, uint8_t count, uint8_t more_data) +{ + // flush old response before sending the new command, but only if we're *not* + // in DATA mode, as the RX FIFO may containg incoming UART data that hasn't + // been read yet + if (more_data == 0 && _mode != BLUEFRUIT_MODE_DATA) sync(); + + sdepMsgCommand_t msgCmd; + + msgCmd.header.msg_type = SDEP_MSGTYPE_COMMAND; + msgCmd.header.cmd_id_high = (command >> 8) & 0xFF ; + msgCmd.header.cmd_id_low = command & 0xFF; + 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); + + // Starting SPI transaction + m_spi.select(); + + 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 + m_spi.deselect(); + thread_sleep_for(SPI_DEFAULT_DELAY_US); + m_spi.select(); + } + + bool result = !tt.expired(); + if ( result ) { + // transfer the rest of the data + spixfer((void*) (((uint8_t*)&msgCmd) +1), sizeof(sdepMsgHeader_t)+count-1); + } + + m_spi.deselect(); + + return result; +} + +/******************************************************************************/ +/*! + @brief Print API. Either buffer the data internally or send it to bus + if possible. \r and \n are command terminators and will force the + packet to be sent to the Bluefruit LE module. + + @param[in] c + Character to send +*/ +/******************************************************************************/ +ssize_t Adafruit_BluefruitLE_SPI::_putc(int c_int) +{ + uint8_t c = c_int & 0xFF; + if (_mode == BLUEFRUIT_MODE_DATA) { + sendPacket(SDEP_CMDTYPE_BLE_UARTTX, &c, 1, 0); + getResponse(); + 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) { + // +++ command to switch mode + if (m_mode_switch_command_enabled && memcmp(m_tx_buffer, "+++", 3) == 0) { + simulateSwitchMode(); + } else { + sendPacket(SDEP_CMDTYPE_AT_WRAPPER, m_tx_buffer, m_tx_count, 0); + } + m_tx_count = 0; + } + } + // More than max packet buffered --> send with more_data = 1 + else if (m_tx_count == SDEP_MAX_PACKETSIZE) { + sendPacket(SDEP_CMDTYPE_AT_WRAPPER, m_tx_buffer, m_tx_count, 1); + + m_tx_buffer[0] = c; + m_tx_count = 1; + } + // Not enough data, continue to buffer + else { + m_tx_buffer[m_tx_count++] = c; + } + + if (_verbose) ::printf("%c", (char) c); + + return 1; +} + +/******************************************************************************/ +/*! + +*/ +/******************************************************************************/ +ssize_t Adafruit_BluefruitLE_SPI::write(const void *v_buf, size_t size) +{ + uint8_t *buf = (uint8_t *)v_buf; + if ( _mode == BLUEFRUIT_MODE_DATA ) { + if (m_mode_switch_command_enabled && + (size >= 3) && + !memcmp(buf, "+++", 3) && + !(size > 3 && buf[3] != '\r' && buf[3] != '\n') ) { + simulateSwitchMode(); + } else { + size_t remain = size; + while(remain) { + size_t len = min(remain, SDEP_MAX_PACKETSIZE); + remain -= len; + + sendPacket(SDEP_CMDTYPE_BLE_UARTTX, buf, (uint8_t) len, remain ? 1 : 0); + buf += len; + } + + getResponse(); + } + + return size; + } + // Command mode + else { + size_t n = size; + while (size--) { + _putc(*buf++); + } + return n; + } +} + +/******************************************************************************/ +/*! + @brief Check if the response from the previous command is ready + + @return 'true' if a response is ready, otherwise 'false' +*/ +/******************************************************************************/ +int Adafruit_BluefruitLE_SPI::available(void) +{ + if (! m_rx_fifo.empty() ) { + return m_rx_fifo.count(); + } + + if ( _mode == BLUEFRUIT_MODE_DATA ) { + // DATA Mode: query for BLE UART data + sendPacket(SDEP_CMDTYPE_BLE_UARTRX, NULL, 0, 0); + + // Waiting to get response from Bluefruit + getResponse(); + + return m_rx_fifo.count(); + } else { + return (m_irq.read()); + } +} + +/******************************************************************************/ +/*! + @brief Get a byte from response data, perform SPI transaction if needed + + @return -1 if no data is available +*/ +/******************************************************************************/ +int Adafruit_BluefruitLE_SPI::_getc(void) +{ + uint8_t ch; + + // try to grab from buffer first... + if (!m_rx_fifo.empty()) { + m_rx_fifo.read(&ch); + return (int)ch; + } + + if ( _mode == BLUEFRUIT_MODE_DATA ) { + // DATA Mode: query for BLE UART data + sendPacket(SDEP_CMDTYPE_BLE_UARTRX, NULL, 0, 0); + + // Waiting to get response from Bluefruit + getResponse(); + } else { + // COMMAND Mode: Only read data from Bluefruit if IRQ is raised + if ( m_irq.read() ) getResponse(); + } + + return m_rx_fifo.read(&ch) ? ((int) ch) : EOF; + +} + +/******************************************************************************/ +/*! + @brief Flush current response data in the internal FIFO + + @return -1 if no data is available +*/ +/******************************************************************************/ +int Adafruit_BluefruitLE_SPI::sync(void) +{ + m_rx_fifo.clear(); + return Adafruit_BLE::sync(); +} + +/******************************************************************************/ +/*! + @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 Adafruit_BluefruitLE_SPI::getResponse(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; + memclr(&msg_response, sizeof(sdepMsgResponse_t)); + + if ( !getPacket(&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 + // delayMicroseconds(SPI_DEFAULT_DELAY_US); + } + + return true; +} + +/******************************************************************************/ +/*! + @brief Perform a single SPI SDEP transaction and is used by getReponse to + get a full response composed of multiple packets. + + @param[in] buf + Memory location where payload is copied to + + @return number of bytes in SDEP payload +*/ +/******************************************************************************/ +bool Adafruit_BluefruitLE_SPI::getPacket(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.read() ) { + if (tt.expired()) return false; + } + + sdepMsgHeader_t* p_header = &p_response->header; + + 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 + m_spi.deselect(); + thread_sleep_for(1); + m_spi.select(); + } 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. + + m_spi.deselect(); + // wait for the clock to be enabled.. +// while (!digitalRead(m_irq)) { +// if ( tt.expired() ) break; +// } +// if (!digitalRead(m_irq)) break; + thread_sleep_for(1); + m_spi.select(); + } + } 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 = (p_header->cmd_id_high << 8) | 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); + + m_spi.deselect(); + + return result; +} + +/******************************************************************************/ +/*! + +*/ +/******************************************************************************/ +void Adafruit_BluefruitLE_SPI::spixfer(void *buff, size_t len) +{ + uint8_t *p = (uint8_t *)buff; + + while (len--) { + p[0] = spixfer(p[0]); + p++; + } +} + +/******************************************************************************/ +/*! + +*/ +/******************************************************************************/ +uint8_t Adafruit_BluefruitLE_SPI::spixfer(uint8_t x) +{ + uint8_t reply = m_spi.write(x); + //SerialDebug.println(reply, HEX); + return reply; +}