mirror of https://github.com/ARMmbed/mbed-os.git
Merge pull request #12456 from jeromecoutant/PR_COMPONENT_BLUENRG
COMPONENT_BlueNRG_MS creation : ST Bluetooth Low Energy modulepull/12896/head
commit
3c89556cbd
|
@ -0,0 +1,624 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017-2020 ARM Limited
|
||||||
|
* Copyright (c) 2017-2020 STMicroelectronics
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// drivers
|
||||||
|
#include "drivers/DigitalOut.h"
|
||||||
|
#include "drivers/SPI.h"
|
||||||
|
#include "drivers/InterruptIn.h"
|
||||||
|
|
||||||
|
// platform
|
||||||
|
#include "platform/mbed_wait_api.h"
|
||||||
|
|
||||||
|
// FEATURE_BLE/targets/TARGET_CORDIO
|
||||||
|
#include "CordioBLE.h"
|
||||||
|
#include "CordioHCIDriver.h"
|
||||||
|
#include "CordioHCITransportDriver.h"
|
||||||
|
#include "hci_api.h"
|
||||||
|
#include "hci_cmd.h"
|
||||||
|
#include "hci_core.h"
|
||||||
|
#include "dm_api.h"
|
||||||
|
#include "bstream.h"
|
||||||
|
#include "hci_mbed_os_adaptation.h"
|
||||||
|
|
||||||
|
// rtos
|
||||||
|
#include "Thread.h"
|
||||||
|
#include "Semaphore.h"
|
||||||
|
#include "Mutex.h"
|
||||||
|
|
||||||
|
#define HCI_RESET_RAND_CNT 4
|
||||||
|
|
||||||
|
#define VENDOR_SPECIFIC_EVENT 0xFF
|
||||||
|
#define EVT_BLUENRG_MS_INITIALIZED 0x0001
|
||||||
|
#define ACI_READ_CONFIG_DATA_OPCODE 0xFC0D
|
||||||
|
#define ACI_WRITE_CONFIG_DATA_OPCODE 0xFC0C
|
||||||
|
#define ACI_GATT_INIT_OPCODE 0xFD01
|
||||||
|
#define ACI_GAP_INIT_OPCODE 0xFC8A
|
||||||
|
|
||||||
|
#define PUBLIC_ADDRESS_OFFSET 0x00
|
||||||
|
#define RANDOM_STATIC_ADDRESS_OFFSET 0x80
|
||||||
|
#define LL_WITHOUT_HOST_OFFSET 0x2C
|
||||||
|
#define ROLE_OFFSET 0x2D
|
||||||
|
|
||||||
|
#define SPI_STACK_SIZE 1024
|
||||||
|
|
||||||
|
namespace ble {
|
||||||
|
namespace vendor {
|
||||||
|
namespace bluenrg_ms {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BlueNRG_MS HCI driver implementation.
|
||||||
|
* @see cordio::CordioHCIDriver
|
||||||
|
*/
|
||||||
|
class HCIDriver : public cordio::CordioHCIDriver {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construction of the BlueNRG_MS HCIDriver.
|
||||||
|
* @param transport: Transport of the HCI commands.
|
||||||
|
* @param rst: Name of the reset pin
|
||||||
|
*/
|
||||||
|
HCIDriver(cordio::CordioHCITransportDriver &transport_driver, PinName rst) :
|
||||||
|
cordio::CordioHCIDriver(transport_driver), rst(rst) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCIDriver::do_initialize
|
||||||
|
*/
|
||||||
|
virtual void do_initialize()
|
||||||
|
{
|
||||||
|
bluenrg_ms_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCIDriver::get_buffer_pool_description
|
||||||
|
*/
|
||||||
|
ble::vendor::cordio::buf_pool_desc_t get_buffer_pool_description()
|
||||||
|
{
|
||||||
|
// Use default buffer pool
|
||||||
|
return ble::vendor::cordio::CordioHCIDriver::get_default_buffer_pool_description();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCIDriver::start_reset_sequence
|
||||||
|
*/
|
||||||
|
virtual void start_reset_sequence()
|
||||||
|
{
|
||||||
|
reset_received = false;
|
||||||
|
bluenrg_ms_initialized = false;
|
||||||
|
enable_link_layer_mode_ongoing = false;
|
||||||
|
/* send an HCI Reset command to start the sequence */
|
||||||
|
HciResetCmd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCIDriver::do_terminate
|
||||||
|
*/
|
||||||
|
virtual void do_terminate()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCIDriver::handle_reset_sequence
|
||||||
|
*/
|
||||||
|
virtual void handle_reset_sequence(uint8_t *pMsg)
|
||||||
|
{
|
||||||
|
uint16_t opcode;
|
||||||
|
static uint8_t randCnt;
|
||||||
|
|
||||||
|
/* if event is a command complete event */
|
||||||
|
if (*pMsg == HCI_CMD_CMPL_EVT) {
|
||||||
|
/* parse parameters */
|
||||||
|
pMsg += HCI_EVT_HDR_LEN;
|
||||||
|
pMsg++; /* skip num packets */
|
||||||
|
BSTREAM_TO_UINT16(opcode, pMsg);
|
||||||
|
pMsg++; /* skip status */
|
||||||
|
|
||||||
|
/* decode opcode */
|
||||||
|
switch (opcode) {
|
||||||
|
case HCI_OPCODE_RESET: {
|
||||||
|
/* initialize rand command count */
|
||||||
|
randCnt = 0;
|
||||||
|
reset_received = true;
|
||||||
|
// bluenrg_ms_initialized event has to come after the hci reset event
|
||||||
|
bluenrg_ms_initialized = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ACL packet
|
||||||
|
case ACI_WRITE_CONFIG_DATA_OPCODE:
|
||||||
|
if (enable_link_layer_mode_ongoing) {
|
||||||
|
enable_link_layer_mode_ongoing = false;
|
||||||
|
aciSetRole();
|
||||||
|
} else {
|
||||||
|
aciGattInit();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACI_GATT_INIT_OPCODE:
|
||||||
|
aciGapInit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACI_GAP_INIT_OPCODE:
|
||||||
|
aciReadConfigParameter(RANDOM_STATIC_ADDRESS_OFFSET);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACI_READ_CONFIG_DATA_OPCODE:
|
||||||
|
// note: will send the HCI command to send the random address
|
||||||
|
set_random_static_address(pMsg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_SET_RAND_ADDR:
|
||||||
|
HciSetEventMaskCmd((uint8_t *) hciEventMask);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_SET_EVENT_MASK:
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeSetEventMaskCmd((uint8_t *) hciLeEventMask);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_SET_EVENT_MASK:
|
||||||
|
// Note: the public address is not read because there is no valid public address provisioned by default on the target
|
||||||
|
// You can enable it thanks to json "valid-public-bd-address" config value
|
||||||
|
#if MBED_CONF_BLUENRG_MS_VALID_PUBLIC_BD_ADDRESS == 1
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciReadBdAddrCmd();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_READ_BD_ADDR:
|
||||||
|
/* parse and store event parameters */
|
||||||
|
BdaCpy(hciCoreCb.bdAddr, pMsg);
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
#endif
|
||||||
|
HciLeReadBufSizeCmd();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_READ_BUF_SIZE:
|
||||||
|
/* parse and store event parameters */
|
||||||
|
BSTREAM_TO_UINT16(hciCoreCb.bufSize, pMsg);
|
||||||
|
BSTREAM_TO_UINT8(hciCoreCb.numBufs, pMsg);
|
||||||
|
|
||||||
|
/* initialize ACL buffer accounting */
|
||||||
|
hciCoreCb.availBufs = hciCoreCb.numBufs;
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeReadSupStatesCmd();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_READ_SUP_STATES:
|
||||||
|
/* parse and store event parameters */
|
||||||
|
memcpy(hciCoreCb.leStates, pMsg, HCI_LE_STATES_LEN);
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeReadWhiteListSizeCmd();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_READ_WHITE_LIST_SIZE:
|
||||||
|
/* parse and store event parameters */
|
||||||
|
BSTREAM_TO_UINT8(hciCoreCb.whiteListSize, pMsg);
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeReadLocalSupFeatCmd();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_READ_LOCAL_SUP_FEAT:
|
||||||
|
/* parse and store event parameters */
|
||||||
|
BSTREAM_TO_UINT16(hciCoreCb.leSupFeat, pMsg);
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
hciCoreReadResolvingListSize();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_READ_RES_LIST_SIZE:
|
||||||
|
/* parse and store event parameters */
|
||||||
|
BSTREAM_TO_UINT8(hciCoreCb.resListSize, pMsg);
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
hciCoreReadMaxDataLen();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_READ_MAX_DATA_LEN: {
|
||||||
|
uint16_t maxTxOctets;
|
||||||
|
uint16_t maxTxTime;
|
||||||
|
|
||||||
|
BSTREAM_TO_UINT16(maxTxOctets, pMsg);
|
||||||
|
BSTREAM_TO_UINT16(maxTxTime, pMsg);
|
||||||
|
|
||||||
|
/* use Controller's maximum supported payload octets and packet duration times
|
||||||
|
* for transmission as Host's suggested values for maximum transmission number
|
||||||
|
* of payload octets and maximum packet transmission time for new connections.
|
||||||
|
*/
|
||||||
|
HciLeWriteDefDataLen(maxTxOctets, maxTxTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_WRITE_DEF_DATA_LEN:
|
||||||
|
if (hciCoreCb.extResetSeq) {
|
||||||
|
/* send first extended command */
|
||||||
|
(*hciCoreCb.extResetSeq)(pMsg, opcode);
|
||||||
|
} else {
|
||||||
|
/* initialize extended parameters */
|
||||||
|
hciCoreCb.maxAdvDataLen = 0;
|
||||||
|
hciCoreCb.numSupAdvSets = 0;
|
||||||
|
hciCoreCb.perAdvListSize = 0;
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeRandCmd();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_READ_MAX_ADV_DATA_LEN:
|
||||||
|
case HCI_OPCODE_LE_READ_NUM_SUP_ADV_SETS:
|
||||||
|
case HCI_OPCODE_LE_READ_PER_ADV_LIST_SIZE:
|
||||||
|
if (hciCoreCb.extResetSeq) {
|
||||||
|
/* send next extended command in sequence */
|
||||||
|
(*hciCoreCb.extResetSeq)(pMsg, opcode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HCI_OPCODE_LE_RAND:
|
||||||
|
/* check if need to send second rand command */
|
||||||
|
if (randCnt < (HCI_RESET_RAND_CNT - 1)) {
|
||||||
|
randCnt++;
|
||||||
|
HciLeRandCmd();
|
||||||
|
} else {
|
||||||
|
signal_reset_sequence_done();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* vendor specific event
|
||||||
|
*/
|
||||||
|
if (pMsg[0] == VENDOR_SPECIFIC_EVENT) {
|
||||||
|
/* parse parameters */
|
||||||
|
pMsg += HCI_EVT_HDR_LEN;
|
||||||
|
BSTREAM_TO_UINT16(opcode, pMsg);
|
||||||
|
|
||||||
|
if (opcode == EVT_BLUENRG_MS_INITIALIZED) {
|
||||||
|
if (bluenrg_ms_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bluenrg_ms_initialized = true;
|
||||||
|
if (reset_received) {
|
||||||
|
aciEnableLinkLayerModeOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void aciEnableLinkLayerModeOnly()
|
||||||
|
{
|
||||||
|
uint8_t data[1] = { 0x01 };
|
||||||
|
enable_link_layer_mode_ongoing = true;
|
||||||
|
aciWriteConfigData(LL_WITHOUT_HOST_OFFSET, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void aciSetRole()
|
||||||
|
{
|
||||||
|
// master and slave, simultaneous advertising and scanning
|
||||||
|
// (up to 4 connections)
|
||||||
|
uint8_t data[1] = { 0x04 };
|
||||||
|
aciWriteConfigData(ROLE_OFFSET, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void aciGattInit()
|
||||||
|
{
|
||||||
|
uint8_t *pBuf = hciCmdAlloc(ACI_GATT_INIT_OPCODE, 0);
|
||||||
|
if (!pBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hciCmdSend(pBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void aciGapInit()
|
||||||
|
{
|
||||||
|
uint8_t *pBuf = hciCmdAlloc(ACI_GAP_INIT_OPCODE, 3);
|
||||||
|
if (!pBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pBuf[3] = 0xF;
|
||||||
|
pBuf[4] = 0;
|
||||||
|
pBuf[5] = 0;
|
||||||
|
hciCmdSend(pBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void aciReadConfigParameter(uint8_t offset)
|
||||||
|
{
|
||||||
|
uint8_t *pBuf = hciCmdAlloc(ACI_READ_CONFIG_DATA_OPCODE, 1);
|
||||||
|
if (!pBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pBuf[3] = offset;
|
||||||
|
hciCmdSend(pBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t N>
|
||||||
|
void aciWriteConfigData(uint8_t offset, uint8_t (&buf)[N])
|
||||||
|
{
|
||||||
|
uint8_t *pBuf = hciCmdAlloc(ACI_WRITE_CONFIG_DATA_OPCODE, 2 + N);
|
||||||
|
if (!pBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pBuf[3] = offset;
|
||||||
|
pBuf[4] = N;
|
||||||
|
memcpy(pBuf + 5, buf, N);
|
||||||
|
hciCmdSend(pBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hciCoreReadResolvingListSize(void)
|
||||||
|
{
|
||||||
|
/* if LL Privacy is supported by Controller and included */
|
||||||
|
if ((hciCoreCb.leSupFeat & HCI_LE_SUP_FEAT_PRIVACY) &&
|
||||||
|
(hciLeSupFeatCfg & HCI_LE_SUP_FEAT_PRIVACY)) {
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeReadResolvingListSize();
|
||||||
|
} else {
|
||||||
|
hciCoreCb.resListSize = 0;
|
||||||
|
|
||||||
|
/* send next command in sequence */
|
||||||
|
hciCoreReadMaxDataLen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hciCoreReadMaxDataLen(void)
|
||||||
|
{
|
||||||
|
/* if LE Data Packet Length Extensions is supported by Controller and included */
|
||||||
|
if ((hciCoreCb.leSupFeat & HCI_LE_SUP_FEAT_DATA_LEN_EXT) &&
|
||||||
|
(hciLeSupFeatCfg & HCI_LE_SUP_FEAT_DATA_LEN_EXT)) {
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeReadMaxDataLen();
|
||||||
|
} else {
|
||||||
|
/* send next command in sequence */
|
||||||
|
HciLeRandCmd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bluenrg_ms_reset()
|
||||||
|
{
|
||||||
|
/* Reset BlueNRG_MS SPI interface. Hold reset line to 0 for 1500ms */
|
||||||
|
rst = 0;
|
||||||
|
wait_us(1500);
|
||||||
|
rst = 1;
|
||||||
|
|
||||||
|
/* Wait for the radio to come back up */
|
||||||
|
wait_us(100000);
|
||||||
|
}
|
||||||
|
|
||||||
|
mbed::DigitalOut rst;
|
||||||
|
bool reset_received;
|
||||||
|
bool bluenrg_ms_initialized;
|
||||||
|
bool enable_link_layer_mode_ongoing;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport driver of the ST BlueNRG_MS shield.
|
||||||
|
* @important: With that driver, it is assumed that the SPI bus used is not shared
|
||||||
|
* with other SPI peripherals. The reasons behind this choice are simplicity and
|
||||||
|
* performance:
|
||||||
|
* - Reading from the peripheral SPI can be challenging especially if other
|
||||||
|
* threads access the same SPI bus. Indeed it is common that the function
|
||||||
|
* spiRead yield nothings even if the chip has signaled data with the irq
|
||||||
|
* line. Sharing would make the situation worse and increase the risk of
|
||||||
|
* timeout of HCI commands / response.
|
||||||
|
* - This driver can be used even if the RTOS is disabled or not present it may
|
||||||
|
* may be usefull for some targets.
|
||||||
|
*
|
||||||
|
* If The SPI is shared with other peripherals then the best option would be to
|
||||||
|
* handle SPI read in a real time thread woken up by an event flag.
|
||||||
|
*
|
||||||
|
* Other mechanisms might also be added in the future to handle data read as an
|
||||||
|
* event from the stack. This might not be the best solution for all BLE chip;
|
||||||
|
* especially this one.
|
||||||
|
*/
|
||||||
|
class TransportDriver : public cordio::CordioHCITransportDriver {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct the transport driver required by a BlueNRG_MS module.
|
||||||
|
* @param mosi Pin of the SPI mosi
|
||||||
|
* @param miso Pin of the SPI miso
|
||||||
|
* @param sclk Pin of the SPI clock
|
||||||
|
* @param irq Pin used by the module to signal data are available.
|
||||||
|
*/
|
||||||
|
TransportDriver(PinName mosi, PinName miso, PinName sclk, PinName ncs, PinName irq)
|
||||||
|
: spi(mosi, miso, sclk), nCS(ncs), irq(irq), _spi_thread(osPriorityNormal, SPI_STACK_SIZE, _spi_thread_stack)
|
||||||
|
{
|
||||||
|
_spi_thread.start(mbed::callback(this, &TransportDriver::spi_read_cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~TransportDriver() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCITransportDriver::initialize
|
||||||
|
*/
|
||||||
|
virtual void initialize()
|
||||||
|
{
|
||||||
|
// Setup the spi for 8 bit data, low clock polarity,
|
||||||
|
// 1-edge phase, with an 8MHz clock rate
|
||||||
|
spi.format(8, 0);
|
||||||
|
spi.frequency(8000000);
|
||||||
|
|
||||||
|
// Deselect the BlueNRG_MS chip by keeping its nCS signal high
|
||||||
|
nCS = 1;
|
||||||
|
|
||||||
|
wait_us(500);
|
||||||
|
|
||||||
|
// Set the interrupt handler for the device
|
||||||
|
irq.mode(PullDown); // set irq mode
|
||||||
|
irq.rise(mbed::callback(this, &TransportDriver::HCI_Isr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCITransportDriver::terminate
|
||||||
|
*/
|
||||||
|
virtual void terminate() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CordioHCITransportDriver::write
|
||||||
|
*/
|
||||||
|
virtual uint16_t write(uint8_t type, uint16_t len, uint8_t *pData)
|
||||||
|
{
|
||||||
|
// repeat write until successfull. A number of attempt or timeout might
|
||||||
|
// be useful
|
||||||
|
while (spiWrite(type, pData, len) == 0) { }
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t spiWrite(uint8_t type, const uint8_t *data, uint16_t data_length)
|
||||||
|
{
|
||||||
|
static const uint8_t header_master[] = {
|
||||||
|
0x0A, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
uint8_t header_slave[] = { 0xaa, 0x00, 0x00, 0x00, 0x00 };
|
||||||
|
uint16_t data_written = 0;
|
||||||
|
uint16_t write_buffer_size = 0;
|
||||||
|
|
||||||
|
_spi_mutex.lock();
|
||||||
|
|
||||||
|
/* CS reset */
|
||||||
|
nCS = 0;
|
||||||
|
|
||||||
|
/* Exchange header */
|
||||||
|
for (uint8_t i = 0; i < sizeof(header_master); ++i) {
|
||||||
|
header_slave[i] = spi.write(header_master[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header_slave[0] != 0x02) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_buffer_size = header_slave[2] << 8 | header_slave[1];
|
||||||
|
|
||||||
|
if (write_buffer_size == 0 || write_buffer_size < (data_length + 1)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
spi.write(type);
|
||||||
|
|
||||||
|
data_written = data_length;
|
||||||
|
for (uint16_t i = 0; i < data_length; ++i) {
|
||||||
|
spi.write(data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
nCS = 1;
|
||||||
|
|
||||||
|
_spi_mutex.unlock();
|
||||||
|
|
||||||
|
return data_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t spiRead(uint8_t *data_buffer, const uint16_t buffer_size)
|
||||||
|
{
|
||||||
|
static const uint8_t header_master[] = {0x0b, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
uint8_t header_slave[5] = { 0xaa, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
uint16_t read_length = 0;
|
||||||
|
uint16_t data_available = 0;
|
||||||
|
|
||||||
|
nCS = 0;
|
||||||
|
|
||||||
|
/* Read the header */
|
||||||
|
for (size_t i = 0; i < sizeof(header_master); i++) {
|
||||||
|
header_slave[i] = spi.write(header_master[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header_slave[0] != 0x02) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_available = (header_slave[4] << 8) | header_slave[3];
|
||||||
|
read_length = data_available > buffer_size ? buffer_size : data_available;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < read_length; ++i) {
|
||||||
|
data_buffer[i] = spi.write(0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
nCS = 1;
|
||||||
|
|
||||||
|
return read_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* might be split into two parts: the IRQ signaling a real time thread and
|
||||||
|
* the real time thread reading data from the SPI.
|
||||||
|
*/
|
||||||
|
void HCI_Isr(void)
|
||||||
|
{
|
||||||
|
_spi_read_sem.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void spi_read_cb()
|
||||||
|
{
|
||||||
|
uint8_t data_buffer[256];
|
||||||
|
while (true) {
|
||||||
|
_spi_read_sem.acquire();
|
||||||
|
|
||||||
|
_spi_mutex.lock();
|
||||||
|
while (irq == 1) {
|
||||||
|
uint16_t data_read = spiRead(data_buffer, sizeof(data_buffer));
|
||||||
|
on_data_received(data_buffer, data_read);
|
||||||
|
}
|
||||||
|
_spi_mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsafe SPI, does not lock when SPI access happens.
|
||||||
|
*/
|
||||||
|
::mbed::SPI spi;
|
||||||
|
mbed::DigitalOut nCS;
|
||||||
|
mbed::InterruptIn irq;
|
||||||
|
rtos::Thread _spi_thread;
|
||||||
|
uint8_t _spi_thread_stack[SPI_STACK_SIZE];
|
||||||
|
rtos::Semaphore _spi_read_sem;
|
||||||
|
rtos::Mutex _spi_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bluenrg_ms
|
||||||
|
} // namespace vendor
|
||||||
|
} // namespace ble
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cordio HCI driver factory
|
||||||
|
*/
|
||||||
|
ble::vendor::cordio::CordioHCIDriver &ble_cordio_get_hci_driver()
|
||||||
|
{
|
||||||
|
static ble::vendor::bluenrg_ms::TransportDriver transport_driver(
|
||||||
|
MBED_CONF_BLUENRG_MS_SPI_MOSI,
|
||||||
|
MBED_CONF_BLUENRG_MS_SPI_MISO,
|
||||||
|
MBED_CONF_BLUENRG_MS_SPI_SCK,
|
||||||
|
MBED_CONF_BLUENRG_MS_SPI_NCS,
|
||||||
|
MBED_CONF_BLUENRG_MS_SPI_IRQ
|
||||||
|
);
|
||||||
|
static ble::vendor::bluenrg_ms::HCIDriver hci_driver(
|
||||||
|
transport_driver,
|
||||||
|
MBED_CONF_BLUENRG_MS_SPI_RESET
|
||||||
|
);
|
||||||
|
return hci_driver;
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
# BlueNRG_MS
|
||||||
|
|
||||||
|
BLE API wrapper Library for BlueNRG (Bluetooth Low Energy)
|
||||||
|
|
||||||
|
Maybe a simple table like this could help:
|
||||||
|
|
||||||
|
|Name|Type|Bluetooth compliance|Status|Used in shields & boards|Link|
|
||||||
|
|-----------|----------|-----|-|-|-|
|
||||||
|
|SPBTLE-RF |Module |v4.1 |Not recommended for new designs |X-NUCLEO-IDB05A1, DISCO-L475VG-IOT01A, DISCO-L562QE | https://www.st.com/en/wireless-transceivers-mcus-and-modules/spbtle-rf.html |
|
||||||
|
|BlueNRG-M0 |Module |v4.2 |Active (included in ST's Longevity Program) |No | https://www.st.com/en/wireless-transceivers-mcus-and-modules/bluenrg-m0.html |
|
||||||
|
|BlueNRG-MS |Processor |v4.2 |Active (included in ST's Longevity Program) |X-NUCLEO-IDB05A2 (coming soon) | https://www.st.com/en/wireless-transceivers-mcus-and-modules/bluenrg-ms.html |
|
||||||
|
|
||||||
|
|
||||||
|
It uses ARM Cordio stack instead of the ST BlueNRG stack.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
- deprecated ST BLE port: https://github.com/ARMmbed/ble-x-nucleo-idb0xa1
|
||||||
|
- deprecated X-NUCLEO-IDB05A1 BlueNRG : https://github.com/ARMmbed/cordio-ble-x-nucleo-idb0xa1
|
||||||
|
|
||||||
|
|
||||||
|
## Boards
|
||||||
|
|
||||||
|
### X-NUCLEO-IDB05A1
|
||||||
|
|
||||||
|
Bluetooth Low Energy Nucleo Expansion Board:
|
||||||
|
|
||||||
|
https://developer.mbed.org/components/X-NUCLEO-IDB05A1-Bluetooth-Low-Energy/
|
||||||
|
|
||||||
|
### DISCO-L475VG-IOT01A
|
||||||
|
|
||||||
|
STM32L4 Discovery kit IoT node, low-power wireless, Bluetooth V4.1 module (SPBTLE-RF)
|
||||||
|
|
||||||
|
https://os.mbed.com/platforms/ST-Discovery-L475E-IOT01A/
|
||||||
|
|
||||||
|
|
||||||
|
### DISCO-L562QE
|
||||||
|
|
||||||
|
STM32L562E-DK Discovery kit with Bluetooth V4.1 low energy module and Arm Cortex-M33 with TrustZone
|
||||||
|
|
||||||
|
https://os.mbed.com/platforms/ST-Discovery-L562QE/
|
||||||
|
|
||||||
|
|
||||||
|
## Driver configuration
|
||||||
|
|
||||||
|
In order to use the BlueNRG-MS module together with other targets,
|
||||||
|
you may need to override default settings in your local `mbed_app.json` file
|
||||||
|
|
||||||
|
### ST-DISCO boards
|
||||||
|
|
||||||
|
Default settings for `DISCO-L475VG-IOT01A` and `DISCO-L562QE` are configured
|
||||||
|
|
||||||
|
### Arduino Connector Compatibility Warning
|
||||||
|
|
||||||
|
Default Arduino connection is using:
|
||||||
|
|
||||||
|
```
|
||||||
|
"SPI_MOSI": "D11",
|
||||||
|
"SPI_MISO": "D12",
|
||||||
|
"SPI_nCS": "A1",
|
||||||
|
"SPI_RESET": "D7",
|
||||||
|
"SPI_IRQ": "A0",
|
||||||
|
"SPI_SCK": "D3",
|
||||||
|
"valid-public-bd-address": false
|
||||||
|
```
|
||||||
|
|
||||||
|
X-NUCLEO-IDB05A1 is Arduino compatible with an exception: instead of using pin **D13** for the SPI clock, pin **D3** is used.
|
||||||
|
The default configuration for this library is having the SPI clock on pin **D3**.
|
||||||
|
|
||||||
|
To be fully Arduino compatible, X-NUCLEO-IDB05A1 needs a small HW patch.
|
||||||
|
|
||||||
|
For X-NUCLEO-IDB05A1 this patch consists in removing zero resistor **R4** and instead soldering zero resistor **R6**.
|
||||||
|
|
||||||
|
In case you patch your board, then you also have to configure this library to use pin **D13** to drive the SPI clock.
|
||||||
|
To this aim you need to update your local mbed_app.json file with:
|
||||||
|
|
||||||
|
```
|
||||||
|
"target_overrides": {
|
||||||
|
"XXXX": {
|
||||||
|
"bluenrg_ms.SPI_SCK": "D13"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use pin **D13** for the SPI clock, please be aware that on some STM32 Nucleo boards you may **not** drive the LED,
|
||||||
|
otherwise you will get a conflict: the LED on STM32 Nucleo boards is connected to pin **D13**.
|
||||||
|
|
||||||
|
Referring to the current list of tested platforms (see [X-NUCLEO-IDB05A1](https://developer.mbed.org/components/X-NUCLEO-IDB05A1-Bluetooth-Low-Energy/) page),
|
||||||
|
the patch is required for
|
||||||
|
- [ST-Nucleo-F103RB](https://developer.mbed.org/platforms/ST-Nucleo-F103RB/)
|
||||||
|
- [ST-Nucleo-F302R8](https://developer.mbed.org/platforms/ST-Nucleo-F302R8/)
|
||||||
|
- [ST-Nucleo-F411RE](https://developer.mbed.org/platforms/ST-Nucleo-F411RE/)
|
||||||
|
- [ST-Nucleo-F446RE](https://developer.mbed.org/platforms/ST-Nucleo-F446RE/)
|
||||||
|
- [FRDM-K64F](https://developer.mbed.org/platforms/FRDM-K64F/)
|
||||||
|
|
||||||
|
### Target Configuration
|
||||||
|
|
||||||
|
To use that library, the target requires some extra configuration in the application `mbed_app.json`. In the `target_overides` section:
|
||||||
|
|
||||||
|
* BLE feature has to be enabled
|
||||||
|
|
||||||
|
```json
|
||||||
|
"target.features_add": ["BLE"]
|
||||||
|
```
|
||||||
|
|
||||||
|
* BlueNRG module has to be enabled
|
||||||
|
|
||||||
|
```json
|
||||||
|
"target.components_add": ["BlueNRG_MS"]
|
||||||
|
```
|
||||||
|
|
||||||
|
* Extra labels have to be defined to include the cordio stack and this library:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"target.extra_labels_add": ["CORDIO"]
|
||||||
|
```
|
||||||
|
|
||||||
|
As an example, the target overide section for the `NUCLEO_F401RE` would be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"NUCLEO_F401RE": {
|
||||||
|
"target.features_add": ["BLE"],
|
||||||
|
"target.components_add": ["BlueNRG_MS"],
|
||||||
|
"target.extra_labels_add": ["CORDIO"]
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "bluenrg_ms",
|
||||||
|
"config": {
|
||||||
|
"SPI_MOSI": "D11",
|
||||||
|
"SPI_MISO": "D12",
|
||||||
|
"SPI_nCS": "A1",
|
||||||
|
"SPI_RESET": "D7",
|
||||||
|
"SPI_IRQ": "A0",
|
||||||
|
"SPI_SCK": "D3",
|
||||||
|
"valid-public-bd-address": {
|
||||||
|
"help": "Read the BD public address at startup",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_overrides": {
|
||||||
|
"K64F": {
|
||||||
|
"SPI_SCK": "D13"
|
||||||
|
},
|
||||||
|
"DISCO_L475VG_IOT01A": {
|
||||||
|
"SPI_MOSI": "PC_12",
|
||||||
|
"SPI_MISO": "PC_11",
|
||||||
|
"SPI_nCS": "PD_13",
|
||||||
|
"SPI_RESET": "PA_8",
|
||||||
|
"SPI_IRQ": "PE_6",
|
||||||
|
"SPI_SCK": "PC_10"
|
||||||
|
},
|
||||||
|
"DISCO_L562QE": {
|
||||||
|
"SPI_MOSI": "PG_4",
|
||||||
|
"SPI_MISO": "PG_3",
|
||||||
|
"SPI_nCS": "PG_5",
|
||||||
|
"SPI_RESET": "PG_8",
|
||||||
|
"SPI_IRQ": "PG_6",
|
||||||
|
"SPI_SCK": "PG_2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5627,6 +5627,7 @@
|
||||||
},
|
},
|
||||||
"DISCO_L475VG_IOT01A": {
|
"DISCO_L475VG_IOT01A": {
|
||||||
"components_add": [
|
"components_add": [
|
||||||
|
"BlueNRG_MS",
|
||||||
"QSPIF",
|
"QSPIF",
|
||||||
"FLASHIAP"
|
"FLASHIAP"
|
||||||
],
|
],
|
||||||
|
@ -5635,6 +5636,7 @@
|
||||||
],
|
],
|
||||||
"core": "Cortex-M4F",
|
"core": "Cortex-M4F",
|
||||||
"extra_labels_add": [
|
"extra_labels_add": [
|
||||||
|
"CORDIO",
|
||||||
"MX25R6435F",
|
"MX25R6435F",
|
||||||
"STM32L4",
|
"STM32L4",
|
||||||
"STM32L475xG",
|
"STM32L475xG",
|
||||||
|
@ -5676,6 +5678,9 @@
|
||||||
"USBDEVICE",
|
"USBDEVICE",
|
||||||
"MPU"
|
"MPU"
|
||||||
],
|
],
|
||||||
|
"features": [
|
||||||
|
"BLE"
|
||||||
|
],
|
||||||
"release_versions": [
|
"release_versions": [
|
||||||
"2",
|
"2",
|
||||||
"5"
|
"5"
|
||||||
|
@ -10539,6 +10544,7 @@
|
||||||
"inherits": ["FAMILY_STM32"],
|
"inherits": ["FAMILY_STM32"],
|
||||||
"core": "Cortex-M33FE",
|
"core": "Cortex-M33FE",
|
||||||
"extra_labels_add": [
|
"extra_labels_add": [
|
||||||
|
"CORDIO",
|
||||||
"STM32L5",
|
"STM32L5",
|
||||||
"STM32L562xx",
|
"STM32L562xx",
|
||||||
"MX25LM51245G"
|
"MX25LM51245G"
|
||||||
|
@ -10549,6 +10555,7 @@
|
||||||
"PMOD"
|
"PMOD"
|
||||||
],
|
],
|
||||||
"components_add": [
|
"components_add": [
|
||||||
|
"BlueNRG_MS",
|
||||||
"FLASHIAP"
|
"FLASHIAP"
|
||||||
],
|
],
|
||||||
"macros_add": [
|
"macros_add": [
|
||||||
|
@ -10579,6 +10586,9 @@
|
||||||
"FLASH",
|
"FLASH",
|
||||||
"QSPI"
|
"QSPI"
|
||||||
],
|
],
|
||||||
|
"features": [
|
||||||
|
"BLE"
|
||||||
|
],
|
||||||
"detect_code": ["0854"],
|
"detect_code": ["0854"],
|
||||||
"bootloader_supported": true,
|
"bootloader_supported": true,
|
||||||
"mbed_rom_start": "0x08000000",
|
"mbed_rom_start": "0x08000000",
|
||||||
|
|
Loading…
Reference in New Issue