element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Smart Security and Surveillance
  • Challenges & Projects
  • Design Challenges
  • Smart Security and Surveillance
  • More
  • Cancel
Smart Security and Surveillance
Forum Getting btstack to work via Arduino
  • News
  • Projects
  • Forum
  • DC
  • Leaderboard
  • Files
  • Members
  • More
  • Cancel
  • New
Join Smart Security and Surveillance to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • Replies 0 replies
  • Subscribers 50 subscribers
  • Views 77 views
  • Users 0 members are here
  • ble
  • btstack
  • arduino
  • MAX32630FTHR#
Related

Getting btstack to work via Arduino

BigG
BigG 20 days ago

This was purely an agentic AI exercise as I was curious to learn how fast I could get my AI agent to modify an Arduino version of bluekitchen's btstack I had found online. I had no way of testing this btstack version with other MCU's so thought to give it a go anyway.

Well, it took a couple of hours to crack it, with the majority time spent testing 2 examples. The impressive part is that it started by (re?)creating a "HAL" of sorts for the MAX32630FTHR in seconds once I had pointed it to be previous manual attempt of uploading the service pack and configuring the RX and TX pins correctly. It worked first time without error.

Header: hal_max32630fthr.h

#ifndef HAL_MAX32630FTHR_H
#define HAL_MAX32630FTHR_H

#include <stdint.h>

#if defined __cplusplus
extern "C" {
#endif

void max32630fthr_bluetooth_setup(void);
void max32630fthr_bluetooth_set_baud(uint32_t baud);

#if defined __cplusplus
}
#endif

#endif

Source: hal_max32630fthr.cpp

#include "hal_max32630fthr.h"

#if defined(ARDUINO_MAX32630FTHR) || defined(TARGET_MAX32630FTHR) || defined(MAX32630FTHR) || defined(MAX32630)

#include <Arduino.h>
#include "pwrseq_regs.h"
#include "rtc_regs.h"
#include "ioman_regs.h"
#include "gpio_regs.h"

// Pin Definitions for MAX32630FTHR
#define BLE_SHUTDOWN_PIN  P1_6   // nSHUTD (Active Low)
#define M4_RTS_OUT_TO_BLE P0_2   // M4 RTS Output
#define BLE_RTS_IN_TO_M4  P0_3   // BLE Module RTS Input (M4 CTS)
#define M4_TX_PIN         P0_0   // UART0 TX (Mapping B)
#define M4_RX_PIN         P0_1   // UART0 RX (Mapping B)

extern "C" void max32630fthr_bluetooth_setup(void) {
    // --- 1. SET VOLTAGES ---
    useVDDIO(BLE_SHUTDOWN_PIN); 
    useVDDIO(M4_TX_PIN); 
    useVDDIO(M4_RX_PIN);
    useVDDIO(M4_RTS_OUT_TO_BLE); 
    useVDDIO(BLE_RTS_IN_TO_M4);

    // --- 2. INITIALIZE HARDWARE ---
    // Configure 32.768 kHz Clock (P1.7)
    MXC_RTCCFG->clk_ctrl |= MXC_F_RTC_CLK_CTRL_NANO_EN;
    MXC_RTCCFG->osc_ctrl |= MXC_F_RTC_OSC_CTRL_OSC_WARMUP_ENABLE;
    MXC_RTCTMR->prescale  = MXC_V_RTC_PRESCALE_DIV_2_0; 
    MXC_PWRSEQ->reg4     |= MXC_F_PWRSEQ_REG4_PWR_PSEQ_32K_EN;

    // Prepare Pins: Set M4 RTS LOW BEFORE the module wakes up to signal "Ready"
    pinMode(M4_RTS_OUT_TO_BLE, OUTPUT);
    digitalWrite(M4_RTS_OUT_TO_BLE, LOW); 
    
    pinMode(BLE_RTS_IN_TO_M4, INPUT); 
    pinMode(M4_RX_PIN, INPUT);        

    // Pulse Reset (nSHUTD)
    pinMode(BLE_SHUTDOWN_PIN, OUTPUT);
    digitalWrite(BLE_SHUTDOWN_PIN, LOW);
    delay(100);
    digitalWrite(BLE_SHUTDOWN_PIN, HIGH);
    
    // Give the TI Controller time to wake up and stabilize
    delay(500); 
}

extern "C" void max32630fthr_bluetooth_set_baud(uint32_t baud) {
    Serial0.begin(baud);
    // Force IOMAN to Mapping B (Crossover). 
    // Serial0.begin() can revert to Mapping A under the hood. We force it back!
    MXC_IOMAN->uart0_req = 0x11;
    while (MXC_IOMAN->uart0_ack != 0x11) continue;
}

#endif // MAX32630FTHR

Then it was a case of adding in the service pack info for the CC256x.

Finally, it was tweaking a couple of files to align them with the MAX32630FTHR. The main file that was tweaked was bsp_arduino_em9301.cpp.

/**
 * Arduino + Energia Wrapper for BTstack
 */

#if !defined(ARDUINO)
#error "Not compiling for Arduino/Energia"
#endif

#include <Arduino.h>
#ifdef ENERGIA
#include <Energia.h>
#endif
#include <SPI.h>

#include "btstack/hal_uart_dma.h"

#if defined(__arm__)

// --- TRUE UART IMPLEMENTATION FOR ARM / MAX32630FTHR ---

#include "utility/hal_max32630fthr.h"

// Define the hardware serial port connected to your CC256x
#if defined(ARDUINO_MAX32630FTHR) || defined(TARGET_MAX32630FTHR) || defined(MAX32630FTHR) || defined(MAX32630)
#define BT_SERIAL Serial0
#else
#define BT_SERIAL Serial1
#endif

static uint16_t  bytes_to_read = 0;
static uint8_t * rx_buffer_ptr = NULL;

static uint16_t  bytes_to_write = 0;
static uint8_t * tx_buffer_ptr = NULL;

static void dummy_handler(void){};
static void (*rx_done_handler)(void) = dummy_handler;
static void (*tx_done_handler)(void) = dummy_handler;

// Large secondary ring buffer to absorb Arduino loop() / Serial.print() latency
#define RX_RING_BUFFER_SIZE 1024
static uint8_t rx_ring_buffer[RX_RING_BUFFER_SIZE];
static volatile uint16_t rx_ring_head = 0;
static volatile uint16_t rx_ring_tail = 0;

extern "C" __attribute__((weak)) void max32630fthr_bluetooth_setup(void) {}
extern "C" __attribute__((weak)) void max32630fthr_bluetooth_set_baud(uint32_t baud) {
    BT_SERIAL.begin(baud);
}

extern "C" void hal_uart_dma_init(void){
    max32630fthr_bluetooth_setup();
    max32630fthr_bluetooth_set_baud(115200);
}

extern "C" int hal_uart_dma_set_baud(uint32_t baud){
    max32630fthr_bluetooth_set_baud(baud);
    return 0;
}

extern "C" void hal_uart_dma_send_block(const uint8_t *buffer, uint16_t length){
    tx_buffer_ptr = (uint8_t *) buffer;
    bytes_to_write = length;
}

extern "C" void hal_uart_dma_receive_block(uint8_t *buffer, uint16_t length){
    rx_buffer_ptr = buffer;
    bytes_to_read = length;
}

static inline void drain_hardware_buffer(void) {
    // Drain Hardware UART buffer into our massive Ring Buffer to prevent overflows
    while (BT_SERIAL.available() > 0) {
        uint16_t next_head = (rx_ring_head + 1) % RX_RING_BUFFER_SIZE;
        if (next_head != rx_ring_tail) {
            rx_ring_buffer[rx_ring_head] = BT_SERIAL.read();
            rx_ring_head = next_head;
        } else {
            break; // Ring buffer full!
        }
    }
}

extern "C" void hal_uart_dma_process(void){
    // 1. Process Transmit (Blocking write for reliability during init script upload)
    if (bytes_to_write > 0) {
        BT_SERIAL.write(tx_buffer_ptr, bytes_to_write);
        bytes_to_write = 0;
        (*tx_done_handler)();
    }

    // 2. Drain Hardware UART buffer
    drain_hardware_buffer();

    // 3. Process Receive (Feed BTstack from Ring Buffer)
    while (bytes_to_read > 0 && rx_ring_head != rx_ring_tail) {
        *rx_buffer_ptr++ = rx_ring_buffer[rx_ring_tail];
        rx_ring_tail = (rx_ring_tail + 1) % RX_RING_BUFFER_SIZE;
        bytes_to_read--;
        if (bytes_to_read == 0) {
            (*rx_done_handler)();
        }
    }
}

// Override Arduino's weak yield() to aggressively drain the UART buffer
// while the user sketch is blocked on delay() or Serial.print()!
extern "C" void yield(void) {
    drain_hardware_buffer();
}

extern "C" void hal_uart_dma_set_sleep(uint8_t sleep){}
extern "C" void hal_uart_dma_set_csr_irq_handler( void (*csr_irq_handler)(void)){}
extern "C" void hal_uart_dma_set_block_received( void (*block_handler)(void)){ rx_done_handler = block_handler ? block_handler : dummy_handler; }
extern "C" void hal_uart_dma_set_block_sent( void (*block_handler)(void)){ tx_done_handler = block_handler ? block_handler : dummy_handler; }
extern "C" uint32_t hal_time_ms(void){ return millis(); }

#else

// --- ORIGINAL EM9301 SPI IMPLEMENTATION (AVR) ---

#define HAVE_SHUTDOWN

#ifdef ENERGIA

// CMM 9301 Configuration for TI Launchpad
#define PIN_SPI_SCK      7
#define PIN_CS       8
#define PIN_SHUTDOWN 11
#define PIN_IRQ_DATA 13
#define PIN_SPI_MISO     14
#define PIN_SPI_MOSI     15
#else // ARDUINO

// CMM 9301 Configuration for Arduino
#define PIN_IRQ_DATA 2
#define PIN_CS 4
#define PIN_SHUTDOWN 5

// -- SPI defines for Arduino Mega
#ifndef PIN_SPI_MISO
#define PIN_SPI_MISO 50
#endif
#ifndef PIN_SPI_MOSI
#define PIN_SPI_MOSI 51
#endif
#ifndef PIN_SPI_SCK
#define PIN_SPI_SCK  52
#endif

#endif

// rx state
static uint16_t  bytes_to_read = 0;
static uint8_t * rx_buffer_ptr = 0;

// tx state
static uint16_t  bytes_to_write = 0;
static uint8_t * tx_buffer_ptr = 0;

// handlers
static void dummy_handler(void){};
static void (*rx_done_handler)(void) = dummy_handler;
static void (*tx_done_handler)(void) = dummy_handler;

static void bt_setup(void){
    pinMode(PIN_CS, OUTPUT);
    pinMode(PIN_SPI_MOSI, OUTPUT);
    pinMode(PIN_SPI_SCK, OUTPUT);
    pinMode(PIN_SHUTDOWN, OUTPUT);
    pinMode(PIN_IRQ_DATA, INPUT);

    digitalWrite(PIN_CS, HIGH);
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_SHUTDOWN, HIGH);

    // SPI settings are reset in SPI.begin() - calls hang on Arduino Zero, too.
    // SPI.setBitOrder(MSBFIRST);
    // SPI.setDataMode(SPI_MODE0);
    // SPI.end();
}

#ifdef HAVE_SHUTDOWN
static void bt_power_cycle(void){

    // power cycle. set CPU outputs to input to not power EM9301 via IOs
    // pinMode(PIN_SPI_MOSI, INPUT);
    // pinMode(PIN_CS, INPUT);
    pinMode(PIN_CS, OUTPUT);
    pinMode(PIN_SPI_MOSI, OUTPUT);
    pinMode(PIN_SPI_SCK, OUTPUT);
    pinMode(PIN_SHUTDOWN, OUTPUT);
    digitalWrite(PIN_CS, LOW);
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_SPI_SCK, LOW);
    digitalWrite(PIN_SHUTDOWN, HIGH);
    delay(500);

    pinMode(PIN_SPI_MOSI, OUTPUT);
    pinMode(PIN_CS, OUTPUT);
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_CS, HIGH);
    digitalWrite(PIN_SHUTDOWN, LOW);
    delay(1000);
}
#endif

#ifndef HAVE_SHUTDOWN
static void bt_send_illegal(void){
    digitalWrite(PIN_SPI_MOSI, HIGH);
    digitalWrite(PIN_CS, LOW);
    printf("Illegal start\n");
    SPI.begin(); 
    int i;
    for (i=0;i<255;i++){
        SPI.transfer(0xff);
        printf(".");
    }    
    SPI.end(); 
    printf("\nIllegal stop\n");
    digitalWrite(PIN_CS, HIGH);
}

static void bt_flush_input(void){
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_CS, LOW);
    SPI.begin(); 
    while (digitalRead(PIN_IRQ_DATA) == HIGH){
        SPI.transfer(0x00);
    }
    SPI.end(); 
    digitalWrite(PIN_CS, HIGH);
}

static void bt_send_reset(void){
      digitalWrite(PIN_SPI_MOSI, HIGH);
      digitalWrite(PIN_CS, LOW);
      SPI.begin(); 
      SPI.transfer(0x01);
      SPI.transfer(0x03);
      SPI.transfer(0x0c);
      SPI.transfer(0x00);
      SPI.end(); 
      digitalWrite(PIN_CS, HIGH);
}
#endif


static void bt_try_send(void){
    
    if (!bytes_to_write) return;

    // activate module
    pinMode(PIN_SPI_MOSI, OUTPUT);
    digitalWrite(PIN_SPI_MOSI, HIGH);
    digitalWrite(PIN_CS, LOW);

    // module ready
    int tx_done = 0;
    if (digitalRead(PIN_SPI_MISO) == HIGH){
        // printf("Sending: ");

        SPI.begin(); 
        while (bytes_to_write){
            // printf("%02x ", *tx_buffer_ptr);
            SPI.transfer(*tx_buffer_ptr);
            tx_buffer_ptr++;
            bytes_to_write--;
        }
        SPI.end();
        // printf(".\n");
        tx_done = 1;
    } 

    // deactivate module
    digitalWrite(PIN_CS, HIGH);
    digitalWrite(PIN_SPI_MOSI, LOW);
    pinMode(PIN_SPI_MOSI, OUTPUT);

    // notify upper layer
    if (tx_done) {
        (*tx_done_handler)();
    }
}

static int bt_try_read(void){

    // check if data available and buffer is ready
    if (digitalRead(PIN_IRQ_DATA) == LOW) return 0;
    if (bytes_to_read == 0) return 0;

    int num_bytes_read = 0;

    // printf("Reading (%u): ", bytes_to_read);

    // activate module
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_CS, LOW);
    SPI.begin(); 
    do {
        uint8_t byte_read = SPI.transfer(0x00);
        // printf("%02x ", byte_read); 
        *rx_buffer_ptr = byte_read;
        rx_buffer_ptr++;
        bytes_to_read--;
        num_bytes_read++;
    } while (bytes_to_read > 0);
    SPI.end(); 
    digitalWrite(PIN_CS, HIGH);

    // printf("\n"); 

    // notify upper layer
    (*rx_done_handler)();

    return num_bytes_read;
}

extern "C" void hal_uart_dma_init(void){
    bt_setup();

#ifdef HAVE_SHUTDOWN
    bt_power_cycle(); 
#else
    // bring EM9301 into defined state
    bt_send_illegal();
    bt_send_reset();
    bt_flush_input();
#endif
}

extern "C" void hal_uart_dma_set_block_received( void (*block_handler)(void)){
    rx_done_handler = block_handler;
}
extern "C" void hal_uart_dma_set_block_sent( void (*block_handler)(void)){
    tx_done_handler = block_handler;
}

extern "C" void hal_uart_dma_set_csr_irq_handler( void (*csr_irq_handler)(void)){
    // only used for eHCILL
}

extern "C" int  hal_uart_dma_set_baud(uint32_t baud){
	return 0;
}

extern "C" void hal_uart_dma_send_block(const uint8_t *buffer, uint16_t length){
    // printf("send_block, bytes %u\n", length);
    tx_buffer_ptr = (uint8_t *) buffer;
    bytes_to_write = length;
}

extern "C" void hal_uart_dma_receive_block(uint8_t *buffer, uint16_t length){
    rx_buffer_ptr = buffer;
    bytes_to_read = length;
}

extern "C" void hal_uart_dma_set_sleep(uint8_t sleep){
    // not needed for SPI (doesn't need internal clock to work)
}

extern "C" void hal_uart_dma_process(void){
    int num_bytes_read = bt_try_read();
    if (num_bytes_read == 0){
        bt_try_send();
    }
}

extern "C" uint32_t hal_time_ms(void){
    return millis();
}

#endif

Once the btstack library compiled it was onto creating two examples. Here I modified one example (LEPeripheral.ino) and created a new example (LEScanner.ino). The other examples in the btstack library were not tested and would need to be modified to get them to work with the MAX32630FTHR board.

The modified version of `LEPeripheral.ino` basically creates a LED service which allows you to modify the onboard RGB LED's colour, brightness and a blinking pattern. It does this through the use of two characteristics. I tried to add in a button service but the CC256b BLE device did not have enough memory. This was because I was using 128-bit UUID's. It's a well known problem with old (v4.x) BLE devices.

// LE Peripheral Example - Adapted for MAX32630FTHR
#include <BTstack.h>

// Bring in the native C BTstack headers
extern "C" {
    #include "btstack_memory.h"
    #include <btstack/run_loop.h>
    #include <bt_control.h>
    #include "btstack/hal_uart_dma.h"
    #include "att_server.h"
    
    // Expose the TI CC256x controller
    extern bt_control_t * bt_control_cc256x_instance(void);
}

// GATT Characteristic Handles
uint16_t handle_rgb_color;
uint16_t handle_rgb_state;

// State Variables
uint8_t color_r = 0;
uint8_t color_g = 0;
uint8_t color_b = 255; // Default to Blue
uint8_t intensity = 100;

uint8_t state_mode = 0; // 0=Off, 1=Solid, 2=Blink
uint16_t state_period = 1000;
uint8_t state_duty = 50;

// Blink tracking
uint32_t last_blink_time = 0;
bool blink_state = false;

// Update physical RGB LED (Active LOW on MAX32630FTHR)
void updateLEDs(bool is_on) {
    if (!is_on || state_mode == 0) {
        // Active LOW: 255 turns the LED completely off
        analogWrite(RED_LED, 255);
        analogWrite(GREEN_LED, 255);
        analogWrite(BLUE_LED, 255);
    } else {
        // Calculate intensity-scaled values
        uint8_t r = 255 - ((uint16_t)color_r * intensity / 100);
        uint8_t g = 255 - ((uint16_t)color_g * intensity / 100);
        uint8_t b = 255 - ((uint16_t)color_b * intensity / 100);
        
        analogWrite(RED_LED, r);
        analogWrite(GREEN_LED, g);
        analogWrite(BLUE_LED, b);
    }
}

void setup(void){
    Serial.begin(115200);
    while(!Serial) continue;

    Serial.println("\r\nStarting LEPeripheral setup for MAX32630FTHR...");

    pinMode(RED_LED, OUTPUT);
    pinMode(GREEN_LED, OUTPUT);
    pinMode(BLUE_LED, OUTPUT);
    
    updateLEDs(false); // Start OFF

    // set callbacks
    BTstack.setBLEDeviceConnectedCallback(deviceConnectedCallback);
    BTstack.setBLEDeviceDisconnectedCallback(deviceDisconnectedCallback);
    BTstack.setGATTCharacteristicRead(gattReadCallback);
    BTstack.setGATTCharacteristicWrite(gattWriteCallback);

    // --- SETUP GATT DATABASE ---
    
    // 1. rgbService
    BTstack.addGATTService(new UUID("00000001-1111-2222-3333-444444444444"));
    
    handle_rgb_color = BTstack.addGATTCharacteristicDynamic(
        new UUID("00000002-1111-2222-3333-444444444444"), 
        ATT_PROPERTY_READ | ATT_PROPERTY_WRITE_WITHOUT_RESPONSE, 0);
        
    handle_rgb_state = BTstack.addGATTCharacteristicDynamic(
        new UUID("00000003-1111-2222-3333-444444444444"), 
        ATT_PROPERTY_READ | ATT_PROPERTY_WRITE_WITHOUT_RESPONSE, 0);

    // Pass the official CC256X controller flag so it loads the TI Service patch!
    BTstack.setup(BT_CONTROLLER_CC256X);
    BTstack.startAdvertising();
}

void loop(void){
    BTstack.loop();

    // 1. Non-blocking Blink Logic
    if (state_mode == 2) {
        uint32_t now = millis();
        uint32_t on_time = (uint32_t)state_period * state_duty / 100;
        uint32_t off_time = state_period - on_time;
        
        if (blink_state && (now - last_blink_time >= on_time)) {
            blink_state = false;
            last_blink_time = now;
            updateLEDs(false);
        } else if (!blink_state && (now - last_blink_time >= off_time)) {
            blink_state = true;
            last_blink_time = now;
            updateLEDs(true);
        }
    }

}

void deviceConnectedCallback(BLEStatus status, BLEDevice *device) {
    if (status == BLE_STATUS_OK) {
        Serial.println("Device connected!");
    }
}

void deviceDisconnectedCallback(BLEDevice * device){
    Serial.println("Disconnected. Scanning resumes automatically.");
}

uint16_t gattReadCallback(uint16_t value_handle, uint8_t * buffer, uint16_t buffer_size){
    if (value_handle == handle_rgb_color) {
        if (buffer && buffer_size >= 4) {
            buffer[0] = color_r;
            buffer[1] = color_g;
            buffer[2] = color_b;
            buffer[3] = intensity;
        }
        return 4;
    } else if (value_handle == handle_rgb_state) {
        if (buffer && buffer_size >= 4) {
            buffer[0] = state_mode;
            buffer[1] = (state_period >> 8) & 0xFF;
            buffer[2] = state_period & 0xFF;
            buffer[3] = state_duty;
        }
        return 4;
    }
    return 0;
}

int gattWriteCallback(uint16_t value_handle, uint8_t *buffer, uint16_t size){
    if (value_handle == handle_rgb_color && size >= 4) {
        color_r = buffer[0];
        color_g = buffer[1];
        color_b = buffer[2];
        intensity = buffer[3];
        
        Serial.print("RGB updated: "); Serial.print(color_r); Serial.print(","); 
        Serial.print(color_g); Serial.print(","); Serial.print(color_b);
        Serial.print(" @ "); Serial.print(intensity); Serial.println("%");
        
        if (state_mode == 1 || (state_mode == 2 && blink_state)) {
            updateLEDs(true);
        }
    } else if (value_handle == handle_rgb_state && size >= 4) {
        state_mode = buffer[0];
        state_period = (buffer[1] << 8) | buffer[2];
        state_duty = buffer[3];
        
        Serial.print("State updated: Mode="); Serial.print(state_mode); 
        Serial.print(", Period="); Serial.print(state_period); 
        Serial.print("ms, Duty="); Serial.print(state_duty); Serial.println("%");
        
        last_blink_time = millis();
        blink_state = true;
        
        if (state_mode == 0) {
            updateLEDs(false);
        } else {
            updateLEDs(true);
        }
    }
    return 0;
}

The other example was a scanning app. This sketch scanned for a particular 128-bit UUID. This app revealed a few low level issues which were readily resolved.

/* 
 * Stripped down LE Central Scanner
 * Scans for a specific 16-bit Service UUID and retrieves the MAC address.
 * Does NOT initiate a connection.
 */

// Target Luggage Tag Service UUID (16-bit)
// Note. It will also search for iBeacons using same UUID.
// Use the full 128-bit Bluetooth Base UUID to safely bypass the 16-bit parsing bug

#include <BTstack.h>
// Bring in the native C BTstack headers
extern "C" {
    #include "btstack_memory.h"
    #include <btstack/run_loop.h>
    #include <bt_control.h>
    #include "btstack/hal_uart_dma.h"
    
    // Expose the TI CC256x controller and the H4 UART transport
    extern bt_control_t * bt_control_cc256x_instance(void);
    extern hci_transport_t * hci_transport_h4_dma_instance(void);
}

UUID targetLuggageTagUUID("0000FEED-0000-1000-8000-00805F9B34FB");

// Hardware definitions
const int LED_PIN = LED_BUILTIN; // Triggers the MAX32630FTHR's onboard LED
uint32_t lastTagSeenTime = 0;

// Globals to pass data from the fast callback to the main loop
bool targetFound = false;
int targetRssi = 0;
char targetMac[18] = {0};

 void setup(void){
    Serial.begin(115200);
    
    pinMode(LED_PIN, OUTPUT);
    
    // Wait for the native USB serial port to connect before booting BTstack
    while(!Serial) continue;

    Serial.println("\r\nStarting LECentral Scanner");
    
    // Only register the advertisement callback (used for scanning)
    BTstack.setBLEAdvertisementCallback(advertisementCallback);
    
    // 1. Disable packet logging now that everything is working!
    // (Printing massive raw hex dumps to USB causes the hardware UART buffer to overflow and drop packets)
    // BTstack.enablePacketLogger();
    // BTstack.enableDebugLogger();

    // 2. Use the official setup (now that we patched BTstack.cpp to use the CC256x!)
    BTstack.setup(BT_CONTROLLER_CC256X);
    
    BTstack.bleStartScanning();
}

void loop(void){
    // Must be called continuously to process Bluetooth events
    BTstack.loop();

    // Turn off LED if we haven't seen the target tag in the last 3 seconds
    if (millis() - lastTagSeenTime > 3000) {
        digitalWrite(LED_PIN, HIGH); // Note: Built-in LEDs are often active-LOW on Maxim boards!
    }

    // Process the discovery safely outside the callback
    if (targetFound) {
        targetFound = false; // clear the flag
        lastTagSeenTime = millis();
        digitalWrite(LED_PIN, LOW); // Turn ON the LED!
        
        // Rough distance calculation (assuming Tx Power at 1 meter is ~ -65 dBm)
        // Distance = 10 ^ ((TxPower - RSSI) / (10 * N)) -> N = Path loss exponent (usually ~2.5)
        float distance = pow(10.0, (-65.0 - targetRssi) / (10.0 * 2.5))*0.6;
        
        static uint32_t lastPrintTime = 0;
        if (millis() - lastPrintTime > 100) {
            lastPrintTime = millis();
            Serial.print("\r\n *** Target Tag Found! MAC: ");
            Serial.print(targetMac);
            Serial.print(" | RSSI: ");
            Serial.print(targetRssi);
            Serial.print(" dBm | Est. Distance: ");
            Serial.print(distance);
            Serial.println(" meters");
        }
    }
}

void advertisementCallback(BLEAdvertisement *bleAdvertisement) {
    bool isTarget = false;

    // 1. Check if the advertisement contains our target as a standard GATT Service UUID
    if (bleAdvertisement->containsService(&targetLuggageTagUUID)) {
        isTarget = true;
    }
    
    // 2. Check if the beacon is an iBeacon, which hides the UUID inside Manufacturer Specific Data
    if (bleAdvertisement->isIBeacon()) {
        if (targetLuggageTagUUID.matches((UUID*)bleAdvertisement->getIBeaconUUID())) {
            isTarget = true;
        }
    }

    if (isTarget) {
        // Quickly copy the data to our globals and get out of the callback!
        targetRssi = bleAdvertisement->getRssi();
        // getAddressString() uses a single static buffer internally in BTstack, 
        // so we copy it to our own safe array using strncpy.
        strncpy(targetMac, bleAdvertisement->getBdAddr()->getAddressString(), sizeof(targetMac));
        targetFound = true; 
    }
}

Here is a video demonstration of this sketch:

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

The whole library can be found here should anyone find it useful - I just compressed it as a zip file rather than uploading it onto Github: https://drive.google.com/file/d/18-V3d7NckCjgbqI09-9MtiYFfE9YE3BZ/view?usp=sharing

I hope you find this useful. Any question please feel free to ask.

  • Sign in to reply
  • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube