Skip to content

Commit

Permalink
Add S110 version 8.0.0 SoftDevice for nrf51822
Browse files Browse the repository at this point in the history
The nrf51822 chip is still widely available, for example in the BBC
micro:bit. Therefore it's a good idea to support it too.

Unfortunately, Nordic decided to change the API in some significant ways
so many parts are not compatible between S110 for nrf51 and the other
nrf52* SoftDevices.
  • Loading branch information
aykevl committed May 31, 2020
1 parent c26709f commit f91f73e
Show file tree
Hide file tree
Showing 29 changed files with 11,633 additions and 151 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ smoketest-tinygo:
# Test some more boards that are not tested above.
$(TINYGO) build -o test.hex -size=short -target=pca10056-s140v7 ./examples/advertisement
@md5sum test.hex
$(TINYGO) build -o test.hex -size=short -target=microbit-s110v8 ./examples/advertisement
@md5sum test.hex

smoketest-linux:
# Test on Linux.
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ As you can see above, there is support for some chips from Nordic Semiconductors

* The [nRF52832](https://www.nordicsemi.com/Products/Low-power-short-range-wireless/nRF52832) with the [S132](https://www.nordicsemi.com/Software-and-Tools/Software/S132) SoftDevice (version 6).
* The [nRF52840](https://www.nordicsemi.com/Products/Low-power-short-range-wireless/nRF52840) with the [S140](https://www.nordicsemi.com/Software-and-Tools/Software/S140) SoftDevice (version 7).
* The [nRF51822](https://www.nordicsemi.com/Products/Low-power-short-range-wireless/nRF51822) with the [S110](https://www.nordicsemi.com/Software-and-Tools/Software/S110) SoftDevice (version 8). This SoftDevice does not support all features (e.g. scanning).

These chips are supported through [TinyGo](https://tinygo.org/).

Expand All @@ -36,6 +37,14 @@ After that, don't reset the board but instead flash a new program to it. For exa

Flashing will normally reset the board.

For boards that use the CMSIS-DAP interface (such as the [BBC micro:bit](https://microbit.org/)), this works a bit different. Flashing the SoftDevice is done by simply copying the .hex file to the device, for example (on Linux):

cp path/to/softdevice.hex /media/yourusername/MICROBIT/

Flashing will then need to be done a bit differently, using the CMSIS-DAP interface instead of the mass-storage interface normally used by TinyGo:

tinygo flash -target=microbit-s110v8 -programmer=cmsis-dap ./examples/heartrate

## License

This project is licensed under the BSD 3-clause license, see the LICENSE file for details.
Expand Down
103 changes: 103 additions & 0 deletions adapter_nrf51.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// +build softdevice,s110v8

package bluetooth

/*
// Define SoftDevice functions as regular function declarations (not inline
// static functions).
#define SVCALL_AS_NORMAL_FUNCTION
#include "nrf_sdm.h"
#include "ble.h"
#include "ble_gap.h"
void assertHandler(void);
*/
import "C"

import "unsafe"

//export assertHandler
func assertHandler(pc uint32, line_number uint16, p_file_name *byte) {
println("SoftDevice assert")
}

func (a *Adapter) enable() error {
// Enable the SoftDevice.
errCode := C.sd_softdevice_enable(C.NRF_CLOCK_LFCLKSRC_RC_250_PPM_250MS_CALIBRATION, C.softdevice_assertion_handler_t(C.assertHandler))
if errCode != 0 {
return Error(errCode)
}

// Enable the BLE stack.
enableParams := C.ble_enable_params_t{
gatts_enable_params: C.ble_gatts_enable_params_t{
attr_tab_size: C.BLE_GATTS_ATTR_TAB_SIZE_DEFAULT,
},
}
errCode = C.sd_ble_enable(&enableParams)
return makeError(errCode)
}

func handleEvent() {
id := eventBuf.header.evt_id
switch {
case id >= C.BLE_GAP_EVT_BASE && id <= C.BLE_GAP_EVT_LAST:
gapEvent := eventBuf.evt.unionfield_gap_evt()
switch id {
case C.BLE_GAP_EVT_CONNECTED:
handler := defaultAdapter.handler
if handler != nil {
handler(&ConnectEvent{GAPEvent: GAPEvent{Connection(gapEvent.conn_handle)}})
}
case C.BLE_GAP_EVT_DISCONNECTED:
handler := defaultAdapter.handler
if handler != nil {
handler(&DisconnectEvent{GAPEvent: GAPEvent{Connection(gapEvent.conn_handle)}})
}
case C.BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
// Respond with the default PPCP connection parameters by passing
// nil:
// > If NULL is provided on a peripheral role, the parameters in the
// > PPCP characteristic of the GAP service will be used instead. If
// > NULL is provided on a central role and in response to a
// > BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request
// > will be rejected
C.sd_ble_gap_conn_param_update(gapEvent.conn_handle, nil)
default:
if debug {
println("unknown GAP event:", id)
}
}
case id >= C.BLE_GATTS_EVT_BASE && id <= C.BLE_GATTS_EVT_LAST:
gattsEvent := eventBuf.evt.unionfield_gatts_evt()
switch id {
case C.BLE_GATTS_EVT_WRITE:
writeEvent := gattsEvent.params.unionfield_write()
len := writeEvent.len - writeEvent.offset
data := (*[255]byte)(unsafe.Pointer(&writeEvent.data[0]))[:len:len]
handler := defaultAdapter.getCharWriteHandler(writeEvent.handle)
if handler != nil {
handler.callback(Connection(gattsEvent.conn_handle), int(writeEvent.offset), data)
}
case C.BLE_GATTS_EVT_SYS_ATTR_MISSING:
// This event is generated when reading the Generic Attribute
// service. It appears to be necessary for bonded devices.
// From the docs:
// > If the pointer is NULL, the system attribute info is
// > initialized, assuming that the application does not have any
// > previously saved system attribute data for this device.
// Maybe we should look at the error, but as there's not really a
// way to handle it, ignore it.
C.sd_ble_gatts_sys_attr_set(gattsEvent.conn_handle, nil, 0, 0)
default:
if debug {
println("unknown GATTS event:", id, id-C.BLE_GATTS_EVT_BASE)
}
}
default:
if debug {
println("unknown event:", id)
}
}
}
130 changes: 130 additions & 0 deletions adapter_nrf528xx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// +build softdevice,!s110v8

package bluetooth

/*
// Define SoftDevice functions as regular function declarations (not inline
// static functions).
#define SVCALL_AS_NORMAL_FUNCTION
#include "nrf_sdm.h"
#include "ble.h"
#include "ble_gap.h"
void assertHandler(void);
*/
import "C"

import "unsafe"

//export assertHandler
func assertHandler() {
println("SoftDevice assert")
}

var clockConfig C.nrf_clock_lf_cfg_t = C.nrf_clock_lf_cfg_t{
source: C.NRF_CLOCK_LF_SRC_SYNTH,
rc_ctiv: 0,
rc_temp_ctiv: 0,
accuracy: 0,
}

func (a *Adapter) enable() error {
// Enable the SoftDevice.
errCode := C.sd_softdevice_enable(&clockConfig, C.nrf_fault_handler_t(C.assertHandler))
if errCode != 0 {
return Error(errCode)
}

// Enable the BLE stack.
appRAMBase := uint32(0x200039c0)
errCode = C.sd_ble_enable(&appRAMBase)
return makeError(errCode)
}

func handleEvent() {
id := eventBuf.header.evt_id
switch {
case id >= C.BLE_GAP_EVT_BASE && id <= C.BLE_GAP_EVT_LAST:
gapEvent := eventBuf.evt.unionfield_gap_evt()
switch id {
case C.BLE_GAP_EVT_CONNECTED:
handler := defaultAdapter.handler
if handler != nil {
handler(&ConnectEvent{GAPEvent: GAPEvent{Connection(gapEvent.conn_handle)}})
}
case C.BLE_GAP_EVT_DISCONNECTED:
handler := defaultAdapter.handler
if handler != nil {
handler(&DisconnectEvent{GAPEvent: GAPEvent{Connection(gapEvent.conn_handle)}})
}
case C.BLE_GAP_EVT_ADV_REPORT:
advReport := gapEvent.params.unionfield_adv_report()
if debug && &scanReportBuffer.data[0] != advReport.data.p_data {
// Sanity check.
panic("scanReportBuffer != advReport.p_data")
}
// Prepare the globalScanResult, which will be passed to the
// callback.
scanReportBuffer.len = byte(advReport.data.len)
globalScanResult.RSSI = int16(advReport.rssi)
globalScanResult.Address = advReport.peer_addr.addr
globalScanResult.AdvertisementPayload = &scanReportBuffer
// Signal to the main thread that there was a scan report.
// Scanning will be resumed (from the main thread) once the scan
// report has been processed.
gotScanReport.Set(1)
case C.BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
// Respond with the default PPCP connection parameters by passing
// nil:
// > If NULL is provided on a peripheral role, the parameters in the
// > PPCP characteristic of the GAP service will be used instead. If
// > NULL is provided on a central role and in response to a
// > BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request
// > will be rejected
C.sd_ble_gap_conn_param_update(gapEvent.conn_handle, nil)
case C.BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:
// We need to respond with sd_ble_gap_data_length_update. Setting
// both parameters to nil will make sure we send the default values.
C.sd_ble_gap_data_length_update(gapEvent.conn_handle, nil, nil)
default:
if debug {
println("unknown GAP event:", id)
}
}
case id >= C.BLE_GATTS_EVT_BASE && id <= C.BLE_GATTS_EVT_LAST:
gattsEvent := eventBuf.evt.unionfield_gatts_evt()
switch id {
case C.BLE_GATTS_EVT_WRITE:
writeEvent := gattsEvent.params.unionfield_write()
len := writeEvent.len - writeEvent.offset
data := (*[255]byte)(unsafe.Pointer(&writeEvent.data[0]))[:len:len]
handler := defaultAdapter.getCharWriteHandler(writeEvent.handle)
if handler != nil {
handler.callback(Connection(gattsEvent.conn_handle), int(writeEvent.offset), data)
}
case C.BLE_GATTS_EVT_SYS_ATTR_MISSING:
// This event is generated when reading the Generic Attribute
// service. It appears to be necessary for bonded devices.
// From the docs:
// > If the pointer is NULL, the system attribute info is
// > initialized, assuming that the application does not have any
// > previously saved system attribute data for this device.
// Maybe we should look at the error, but as there's not really a
// way to handle it, ignore it.
C.sd_ble_gatts_sys_attr_set(gattsEvent.conn_handle, nil, 0, 0)
case C.BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST:
// This event is generated by some devices. While we could support
// larger MTUs, this default MTU is supported everywhere.
C.sd_ble_gatts_exchange_mtu_reply(gattsEvent.conn_handle, C.BLE_GATT_ATT_MTU_DEFAULT)
default:
if debug {
println("unknown GATTS event:", id, id-C.BLE_GATTS_EVT_BASE)
}
}
default:
if debug {
println("unknown event:", id)
}
}
}
10 changes: 10 additions & 0 deletions adapter_s110.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build softdevice,s110v8

// This file is necessary to define SVCall wrappers, because TinyGo does not yet
// support static functions in the preamble.

// Discard all 'static' attributes to define functions normally.
#define static

#include "s110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include/nrf_sdm.h"
#include "s110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include/ble.h"
10 changes: 10 additions & 0 deletions adapter_s110.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build softdevice,s110v8

package bluetooth

/*
// Add the correct SoftDevice include path to CFLAGS, so #include will work as
// expected.
#cgo CFLAGS: -Is110_nrf51_8.0.0/s110_nrf51_8.0.0_API/include
*/
import "C"
Loading

0 comments on commit f91f73e

Please sign in to comment.