From 8274cab154f198447f12028e1686bbe2a6de6ebd Mon Sep 17 00:00:00 2001 From: Dien-Nhung Nguyen-Phu Date: Sun, 23 Jun 2024 19:22:20 +0700 Subject: feat: add initial Bluetooth LE module --- src/ble/common.h | 23 +++++ src/ble/peripheral.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++ src/ble/profile.h | 7 ++ src/ble/profile/devinfo.c | 120 ++++++++++++++++++++++ src/ble/profile/legacy.c | 51 ++++++++++ src/ble/profile/utils.h | 22 +++++ src/ble/setup.c | 72 ++++++++++++++ src/ble/setup.h | 8 ++ src/main.c | 16 ++- 9 files changed, 564 insertions(+), 1 deletion(-) create mode 100644 src/ble/common.h create mode 100644 src/ble/peripheral.c create mode 100644 src/ble/profile.h create mode 100644 src/ble/profile/devinfo.c create mode 100644 src/ble/profile/legacy.c create mode 100644 src/ble/profile/utils.h create mode 100644 src/ble/setup.c create mode 100644 src/ble/setup.h (limited to 'src') diff --git a/src/ble/common.h b/src/ble/common.h new file mode 100644 index 0000000..6c03121 --- /dev/null +++ b/src/ble/common.h @@ -0,0 +1,23 @@ +/** + * This file contain high-level data structures for differents BLE chip. + * + */ + +#ifndef __BLE_H__ +#define __BLE_H__ + +#include +#include + +typedef struct { + uint8_t *bytes; + int size; +} byte_t; + +typedef struct { + byte_t val; // Values + uint8_t props; // Properties //TODO: add enum + byte_t uuid; +} ble_char_t; + +#endif /* __BLE_H__ */ diff --git a/src/ble/peripheral.c b/src/ble/peripheral.c new file mode 100644 index 0000000..955799a --- /dev/null +++ b/src/ble/peripheral.c @@ -0,0 +1,246 @@ +#include "CH58xBLE_LIB.h" +#include "setup.h" + +#define ADV_UUID (0xFEE0) + +static uint8 taskid = INVALID_TASK_ID; + +typedef struct +{ + uint16 connHandle; // Connection handle of current connection + uint16 connInterval; + uint16 connSlaveLatency; + uint16 connTimeout; +} peripheralConnItem_t; + +#define SBP_PARAM_UPDATE_DELAY 1600 // Parameter update delay (unit of 0.625ms) +#define SLAVE_LATENCY 0 + +#define MIN_ADV_INTERVAL 100 // Advertising interval (units of 0.625ms) +#define MAX_ADV_INTERVAL 200 // Advertising interval (units of 0.625ms) + +#define MIN_CONN_INTERVAL 20 // Connection interval (units of 1.25ms) +#define MAX_CONN_INTERVAL 100 // Connection interval (units of 1.25ms) + +#define CONN_TIMEOUT 100 // Supervision timeout (units of 10ms) + +// GAP - SCAN RSP data (max size = 31 bytes) +static uint8 scanRspData[] = { + // complete name + 16, // length of this section + GAP_ADTYPE_LOCAL_NAME_COMPLETE, + 'L', 'E','D', ' ', + 'B', 'a','d', 'g', 'e', ' ', + 'M', 'a','g', 'i', 'c', + + // connection interval range + 0x05, // length of this section + GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE, + LO_UINT16(MIN_CONN_INTERVAL), + HI_UINT16(MIN_CONN_INTERVAL), + LO_UINT16(MAX_CONN_INTERVAL), + HI_UINT16(MAX_CONN_INTERVAL), + + // Tx power level + 0x02, // length of this data + GAP_ADTYPE_POWER_LEVEL, + 9 // 9dBm +}; + +// GAP - Advertisement data (max size = 31 bytes) +// keep short, save energy, save the planet +static uint8 advertData[] = { + 0x02, // section length + GAP_ADTYPE_FLAGS, + GAP_ADTYPE_FLAGS_GENERAL | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED, + + // advertise UUID + 0x03, // section length + GAP_ADTYPE_16BIT_MORE, + LO_UINT16(ADV_UUID), + HI_UINT16(ADV_UUID) +}; + +// GAP GATT Attributes +static uint8 devName[GAP_DEVICE_NAME_LEN] = "LED Badge Magic"; + +// Connection item list +static peripheralConnItem_t conn_list; + +static void initConn(peripheralConnItem_t *conn) +{ + conn->connHandle = GAP_CONNHANDLE_INIT; + conn->connInterval = 0; + conn->connSlaveLatency = 0; + conn->connTimeout = 0; +} + +static void processTMOSMsg(tmos_event_hdr_t *pMsg) +{ + switch(pMsg->event) { + default: + break; + } +} + +static void enable_advertising(uint8_t enable) +{ + GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8), &enable); +} + +static void link_onEstablished(gapRoleEvent_t *pe) +{ + gapEstLinkReqEvent_t *e = (gapEstLinkReqEvent_t *)pe; + + // If already connected + if(conn_list.connHandle != GAP_CONNHANDLE_INIT) { + // Only allow 1 connection, so the new connection will be dropped: + GAPRole_TerminateLink(e->connectionHandle); + GAPRole_PeripheralConnParamUpdateReq(e->connectionHandle, + MIN_CONN_INTERVAL, + MAX_CONN_INTERVAL, + SLAVE_LATENCY, + CONN_TIMEOUT, + taskid); + return; + } + conn_list.connHandle = e->connectionHandle; + conn_list.connInterval = e->connInterval; + conn_list.connSlaveLatency = e->connLatency; + conn_list.connTimeout = e->connTimeout; + enable_advertising(FALSE); +} + +static void link_onTerminated(gapRoleEvent_t *pe) +{ + gapTerminateLinkEvent_t *event = (gapTerminateLinkEvent_t *)pe; + GAPRole_TerminateLink(pe->linkCmpl.connectionHandle); + enable_advertising(TRUE); + + if(event->connectionHandle == conn_list.connHandle) { + conn_list.connHandle = GAP_CONNHANDLE_INIT; + conn_list.connInterval = 0; + conn_list.connSlaveLatency = 0; + conn_list.connTimeout = 0; + } else { + // Requested connection is not existed in connection list + } +} + +static void gap_onParamUpdate(uint16 connHandle, uint16 connInterval, + uint16 connSlaveLatency, uint16 connTimeout) +{ + conn_list.connHandle = connHandle; + conn_list.connInterval = connInterval; + conn_list.connSlaveLatency = connSlaveLatency; + conn_list.connTimeout = connTimeout; +} + +static void gap_onStateChange(gapRole_States_t newState, gapRoleEvent_t *pEvent) +{ + switch(newState & GAPROLE_STATE_ADV_MASK) { + case GAPROLE_ADVERTISING: + if(pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT) { + link_onTerminated(pEvent); + } + break; + + case GAPROLE_CONNECTED: + if(pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT) { + link_onEstablished(pEvent); + } + break; + + case GAPROLE_WAITING: + if(pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT) { + link_onTerminated(pEvent); + } else { + // refer to pEvent->gap.opcode + } + break; + + default: + break; + } +} + +// GAP Role Callbacks +static gapRolesCBs_t gap_handlers = { + gap_onStateChange, + NULL, + gap_onParamUpdate +}; + +// Broadcast Callbacks +static gapRolesBroadcasterCBs_t broadcast_handlers = { + NULL, + NULL +}; + +// GAP Bond Manager Callbacks +static gapBondCBs_t bond_managers = { + NULL, + NULL +}; + + +static uint16 peripheral_task(uint8 task_id, uint16 events) +{ + if(events & SYS_EVENT_MSG) { // Handle BLE stack message + uint8 *pMsg = tmos_msg_receive(taskid); + if(pMsg) { + processTMOSMsg((tmos_event_hdr_t *)pMsg); + tmos_msg_deallocate(pMsg); + } + return (events ^ SYS_EVENT_MSG); + } + + return 0; +} + +static void gap_init() +{ + GAPRole_PeripheralInit(); + + static uint8 initial_advertising_enable = TRUE; + static uint16 desired_min_interval = 6; + static uint16 desired_max_interval = 500; + + // Set the GAP Role Parameters + GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8), &initial_advertising_enable); + GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, sizeof(scanRspData), scanRspData); + GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData); + GAPRole_SetParameter(GAPROLE_MIN_CONN_INTERVAL, sizeof(uint16), &desired_min_interval); + GAPRole_SetParameter(GAPROLE_MAX_CONN_INTERVAL, sizeof(uint16), &desired_max_interval); + + // Set the GAP Characteristics + GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, devName); + GAP_SetParamValue(TGAP_DISC_ADV_INT_MIN, MIN_ADV_INTERVAL); + GAP_SetParamValue(TGAP_DISC_ADV_INT_MAX, MAX_ADV_INTERVAL); + + static uint32 passkey = 0; // passkey "000000" + static uint8 pairMode = GAPBOND_PAIRING_MODE_NO_PAIRING; + static uint8 mitm = FALSE; + static uint8 bonding = FALSE; + static uint8 ioCap = GAPBOND_IO_CAP_DISPLAY_ONLY; + GAPBondMgr_SetParameter(GAPBOND_PERI_DEFAULT_PASSCODE, sizeof(uint32), &passkey); + GAPBondMgr_SetParameter(GAPBOND_PERI_PAIRING_MODE, sizeof(uint8), &pairMode); + GAPBondMgr_SetParameter(GAPBOND_PERI_MITM_PROTECTION, sizeof(uint8), &mitm); + GAPBondMgr_SetParameter(GAPBOND_PERI_IO_CAPABILITIES, sizeof(uint8), &ioCap); + GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(uint8), &bonding); + + GAPRole_BroadcasterSetCB(&broadcast_handlers); + + GGS_AddService(GATT_ALL_SERVICES); + GATTServApp_AddService(GATT_ALL_SERVICES); +} + +void peripheral_init() +{ + taskid = TMOS_ProcessEventRegister(peripheral_task); + + initConn(&conn_list); + gap_init(); + + GAPRole_PeripheralStartDevice(taskid, &bond_managers, &gap_handlers); +} diff --git a/src/ble/profile.h b/src/ble/profile.h new file mode 100644 index 0000000..bec1fa1 --- /dev/null +++ b/src/ble/profile.h @@ -0,0 +1,7 @@ +#ifndef __BLE_UART_SERVICE_H__ +#define __BLE_UART_SERVICE_H__ + +int legacy_registerService(); +int devInfo_registerService(); + +#endif /* __BLE_UART_SERVICE_H__ */ diff --git a/src/ble/profile/devinfo.c b/src/ble/profile/devinfo.c new file mode 100644 index 0000000..f29526d --- /dev/null +++ b/src/ble/profile/devinfo.c @@ -0,0 +1,120 @@ +#include "../common.h" +#include "utils.h" + +// TODO: Automatic assign these on build +static const uint8_t systemId_val[] = {0, 0, 0, 0, 0, 0, 0, 0}; +static const uint16_t systemId_UUID = SYSTEM_ID_UUID; + +static const uint8_t modelNumber_val[] = "B1144"; +static const uint16_t modelNumber_UUID = MODEL_NUMBER_UUID; + +const uint16_t serialNumber_UUID = SERIAL_NUMBER_UUID; +static const uint8_t serialNumber_val[] = "N/A"; + +const uint16_t firmwareRev_UUID = FIRMWARE_REV_UUID; +static const uint8_t firmwareRev_val[] = "v0.0.1"; + +const uint16_t hardwareRev_UUID = HARDWARE_REV_UUID; +static const uint8_t hardwareRev_val[] = "221028"; + +const uint16_t softwareRev_UUID = SOFTWARE_REV_UUID; +static const uint8_t softwareRev_val[] = "N/A"; + +uint16_t mfr_name_UUID = MANUFACTURER_NAME_UUID; +static const uint8_t mfr_name_val[] = "FOSSASIA"; + +static const ble_char_t dev_info[7] = { + { + {systemId_val, sizeof(systemId_val)}, + GATT_PROP_READ, { (uint8_t*)&systemId_UUID, 2} + }, { + {modelNumber_val, sizeof(modelNumber_val) - 1}, + GATT_PROP_READ, { (uint8_t*)&modelNumber_UUID, 2} + }, { + {serialNumber_val, sizeof(serialNumber_val) - 1}, + GATT_PROP_READ, { (uint8_t*)&serialNumber_UUID, 2} + }, { + {firmwareRev_val, sizeof(firmwareRev_val) - 1}, + GATT_PROP_READ, { (uint8_t*)&firmwareRev_UUID, 2} + }, { + {hardwareRev_val, sizeof(hardwareRev_val) - 1}, + GATT_PROP_READ, { (uint8_t*)&hardwareRev_UUID, 2} + }, { + {softwareRev_val, sizeof(softwareRev_val) - 1}, + GATT_PROP_READ, { (uint8_t*)&softwareRev_UUID, 2} + }, { + {mfr_name_val, sizeof(mfr_name_val) - 1}, + GATT_PROP_READ, + { (uint8_t*)&mfr_name_UUID, 2}, + } +}; + +const uint16_t service_UUID = DEVINFO_SERV_UUID; +static const gattAttrType_t service = {ATT_BT_UUID_SIZE, (uint8_t *)&service_UUID}; + +static bStatus_t read_handler(uint16_t connHandle, gattAttribute_t *pAttr, + uint8_t *p_value, uint16_t *pLen, uint16_t offset, + uint16_t maxLen, uint8_t method) +{ + uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]); + + if (uuid < SYSTEM_ID_UUID || uuid > MANUFACTURER_NAME_UUID ) { + *pLen = 0; + return ATT_ERR_ATTR_NOT_FOUND; + } + + uint16_t i = uuid - SYSTEM_ID_UUID; + if(offset >= dev_info[i].val.size) { + return ATT_ERR_INVALID_OFFSET; + } else { + *pLen = MIN(maxLen, (dev_info[i].val.size - offset)); + tmos_memcpy(p_value, &dev_info[i].val.bytes[offset], *pLen); + } + return SUCCESS; +} + +gattServiceCBs_t devInfoCBs = { + read_handler, + NULL, + NULL +}; + +int devInfo_registerService() +{ + static gattAttribute_t attr_table[] = { + ATTR_DECLAR(primaryServiceUUID, 2, GATT_PERMIT_READ, &service), + + CHAR_DECLAR(&dev_info[0].props), + CHAR_VAL_DECLAR(dev_info[0].uuid.bytes, dev_info[0].uuid.size, \ + GATT_PERMIT_READ, dev_info[0].val.bytes), + + CHAR_DECLAR(&dev_info[1].props), + CHAR_VAL_DECLAR(dev_info[1].uuid.bytes, dev_info[1].uuid.size, \ + GATT_PERMIT_READ, dev_info[1].val.bytes), + + CHAR_DECLAR(&dev_info[2].props), + CHAR_VAL_DECLAR(dev_info[2].uuid.bytes, dev_info[2].uuid.size, \ + GATT_PERMIT_READ, dev_info[2].val.bytes), + + CHAR_DECLAR(&dev_info[3].props), + CHAR_VAL_DECLAR(dev_info[3].uuid.bytes, dev_info[3].uuid.size, \ + GATT_PERMIT_READ, dev_info[3].val.bytes), + + CHAR_DECLAR(&dev_info[4].props), + CHAR_VAL_DECLAR(dev_info[4].uuid.bytes, dev_info[4].uuid.size, \ + GATT_PERMIT_READ, dev_info[4].val.bytes), + + CHAR_DECLAR(&dev_info[5].props), + CHAR_VAL_DECLAR(dev_info[5].uuid.bytes, dev_info[5].uuid.size, \ + GATT_PERMIT_READ, dev_info[5].val.bytes), + + CHAR_DECLAR(&dev_info[6].props), + CHAR_VAL_DECLAR(dev_info[6].uuid.bytes, dev_info[6].uuid.size, \ + GATT_PERMIT_READ, dev_info[6].val.bytes), + }; + + return GATTServApp_RegisterService(attr_table, + GATT_NUM_ATTRS(attr_table), + GATT_MAX_ENCRYPT_KEY_SIZE, + &devInfoCBs); +} \ No newline at end of file diff --git a/src/ble/profile/legacy.c b/src/ble/profile/legacy.c new file mode 100644 index 0000000..fc775e5 --- /dev/null +++ b/src/ble/profile/legacy.c @@ -0,0 +1,51 @@ +#include "utils.h" + +static const uint16_t ServiceUUID = 0xFEE0; +static const gattAttrType_t service = {2, (uint8_t *)&ServiceUUID}; + +static const uint16_t RxCharUUID = 0xFEE1; +static uint8 RxCharProps = GATT_PROP_WRITE; +static uint8 RxCharVal[16]; + +static gattAttribute_t attr_table[] = { + ATTR_DECLAR(primaryServiceUUID, 2, GATT_PERMIT_READ, &service), + + CHAR_DECLAR(&RxCharProps), + CHAR_VAL_DECLAR(&RxCharUUID, 2, GATT_PERMIT_WRITE, RxCharVal), +}; + +static bStatus_t receive(uint8_t *val, uint16_t len) +{ + /* TODO: implement data receiving here*/ +} + +static bStatus_t write_handler(uint16 connHandle, gattAttribute_t *pAttr, + uint8 *pValue, uint16 len, uint16 offset, uint8 method) +{ + if(gattPermitAuthorWrite(pAttr->permissions)) { + return ATT_ERR_INSUFFICIENT_AUTHOR; + } + + uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]); + if(uuid == RxCharUUID) { + return receive(pValue, len); + } + return ATT_ERR_ATTR_NOT_FOUND; +} + +gattServiceCBs_t service_handlers = { + NULL, + write_handler, + NULL +}; + +int legacy_registerService() +{ + uint8 status = SUCCESS; + + status = GATTServApp_RegisterService(attr_table, + GATT_NUM_ATTRS(attr_table), + GATT_MAX_ENCRYPT_KEY_SIZE, + &service_handlers); + return (status); +} \ No newline at end of file diff --git a/src/ble/profile/utils.h b/src/ble/profile/utils.h new file mode 100644 index 0000000..b52504f --- /dev/null +++ b/src/ble/profile/utils.h @@ -0,0 +1,22 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include "CH58xBLE_LIB.h" + +#define ATTR_DECLAR(uuid, uuid_size, permissions, val) \ + { \ + {(uuid_size), (uint8_t *)(uuid)}, \ + (permissions), \ + 0, \ + (uint8_t *)(val) \ + } + +// Characteristic Declaration +#define CHAR_DECLAR(properties) \ + ATTR_DECLAR(characterUUID, 2, GATT_PERMIT_READ, properties) + +// Characteristic Value Declaration +#define CHAR_VAL_DECLAR(uuid, uuid_size, permissions, val) \ + ATTR_DECLAR(uuid, uuid_size, permissions, val) + +#endif /* __UTILS_H__ */ diff --git a/src/ble/setup.c b/src/ble/setup.c new file mode 100644 index 0000000..7049acb --- /dev/null +++ b/src/ble/setup.c @@ -0,0 +1,72 @@ +#include +#include + +#include "setup.h" +#include "CH58xBLE_LIB.h" +#include "CH58x_common.h" + +#ifndef BLE_BUFF_LEN +#define BLE_BUFF_LEN 27 +#endif +#ifndef BLE_BUFF_NUM +#define BLE_BUFF_NUM 5 +#endif +#ifndef BLE_TX_NUM_EVENT +#define BLE_TX_NUM_EVENT 1 +#endif +#ifndef BLE_TX_POWER +#define BLE_TX_POWER LL_TX_POWEER_6_DBM +#endif +#ifndef BLE_MEMHEAP_SIZE +#define BLE_MEMHEAP_SIZE (1024*6) +#endif +#ifndef CENTRAL_MAX_CONNECTION +#define CENTRAL_MAX_CONNECTION 3 +#endif +#ifndef PERIPHERAL_MAX_CONNECTION +#define PERIPHERAL_MAX_CONNECTION 1 +#endif + +static __attribute__((aligned(4))) uint32_t MEM_BUF[BLE_MEMHEAP_SIZE / 4]; + +static void lsi_calib(void) +{ + Calibration_LSI(Level_128); +} + +void tmos_clockInit(void) +{ + sys_safe_access_enable(); + R8_CK32K_CONFIG &= ~(RB_CLK_OSC32K_XT | RB_CLK_XT32K_PON); + sys_safe_access_enable(); + R8_CK32K_CONFIG |= RB_CLK_INT32K_PON; + sys_safe_access_disable(); + lsi_calib(); + + RTC_InitTime(2020, 1, 1, 0, 0, 0); + TMOS_TimerInit(0); +} + +void ble_hardwareInit(void) +{ + bleConfig_t cfg; + memset(&cfg, 0, sizeof(bleConfig_t)); + + cfg.MEMAddr = (uint32_t)MEM_BUF; + cfg.MEMLen = (uint32_t)BLE_MEMHEAP_SIZE; + cfg.BufMaxLen = (uint32_t)BLE_BUFF_LEN; + cfg.BufNumber = (uint32_t)BLE_BUFF_NUM; + cfg.TxNumEvent = (uint32_t)BLE_TX_NUM_EVENT; + cfg.TxPower = (uint32_t)BLE_TX_POWER; + cfg.ConnectNumber = (PERIPHERAL_MAX_CONNECTION & 3) | (CENTRAL_MAX_CONNECTION << 2); + + cfg.SelRTCClock = 1 << 7; + + cfg.rcCB = lsi_calib; + + uint8_t m[6]; + GetMACAddress(m); + memcpy(cfg.MacAddr, m, 6); + + BLE_LibInit(&cfg); +} diff --git a/src/ble/setup.h b/src/ble/setup.h new file mode 100644 index 0000000..fd65b84 --- /dev/null +++ b/src/ble/setup.h @@ -0,0 +1,8 @@ +#ifndef __HAL_H__ +#define __HAL_H__ + +void tmos_clockInit(void); +void ble_hardwareInit(void); +void peripheral_init(void); + +#endif /* __HAL_H__ */ diff --git a/src/main.c b/src/main.c index d3a959b..613504e 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,9 @@ #include "button.h" #include "fb.h" +#include "ble/setup.h" +#include "ble/profile.h" + #define FB_WIDTH (LED_COLS * 4) #define SCROLL_IRATIO (16) #define SCAN_F (2000) @@ -103,6 +106,16 @@ void poweroff() LowPower_Shutdown(0); } +void ble_start() +{ + ble_hardwareInit(); + tmos_clockInit(); + + peripheral_init(); + devInfo_registerService(); + legacy_registerService(); +} + void handle_mode_transition() { static int prev_mode; @@ -116,8 +129,9 @@ void handle_mode_transition() // Take control of the current fb to display // the Bluetooth animation + ble_start(); while (mode == DOWNLOAD) { - /* Animation and Bluetooth will be placed here */ + TMOS_SystemProcess(); } // If not being flashed, pressing KEY1 again will // make the badge goes off: -- cgit v1.2.3