Author : Dave Sluiter Date : 3/14/2022, initial file. Date : 4/4/2022, cleaned up the formatting this markdown file.
Starter code for the Bluetooth Mesh Assignment based on Gecko SDK 3.2.3
There are 2 Simplicity Studio projects in this repository:
ecen5823-f22-mesh-cp/ - Client-Publisher, this originated as the SiLabs soc_btbesh_switch example. The switch example publishes messages to a Server-Subscriber.
ecen5823-f22-mesh-ss/ - Server-Subscriber, this originated as the SiLabs soc_btbesh_light example. The example subscribes to messages from a Client-Server.
Document soc_btmesh_switch code
This file documents a portion of the the construction of the SiLabs example project: soc_btmesh_switch which was the basis of this starter code for the mesh assignment. Down below are summaries of the changes made to both the soc_btmesh_switch and the sc_btmesh_light example code.
soc_btmesh_switch became ecen5823-f22-mesh-cp (client-publisher) soc_btmesh_light became ecen5823-f22-mesh-cp (server-subscriber)
For soc_btmesh_switch, the relevant installed Software Components are:
Utility / Button Press API for handling button presses of various lengths Has Configure controls for setting the duration of Short, Medium and Long button presses File = app_button_press_config.h
Driver / Button / Simple Button btn0 btn1 Have Configure controls for setting Mode of operation, set to: Interrupt for both, and also assigning the the port and pin numbers to use. PF6 = button 0 PF7 = button 1 File = sl_simple_button_btn0_config.h File = sl_simple_button_btn1_config.h
Driver / Button / Generic Button API
This component provides a generic button API. In addition, a button driver
implementation component such as the Simple Button component should be
included in the project to implement full button handling.
Also has the API documentation, + links to webpage documentation. Not replicating all of that here. Just a few summary notes:
// ### --------------------------------------------------------------
There is currently one type of button supported by the button driver: Simple Button Driver
Both the interrupt and polling methods obtain the button state for the user by calling sl_button_get_state().
// ### --------------------------------------------------------------
Ok, now trace functionality "upwards", starting with
GPIO_EVEN_IRQHandler() GPIO_ODD_IRQHandler()
Application definitions of these functions are defined in : gpiointerrupt.c
/* Array of user callbacks. One for each pin interrupt number. */ static GPIOINT_IrqCallbackPtr_t gpioCallbacks[32] = { 0 };
// For reference // ************************************************************************* void GPIOINT_Init(void) { if (CORE_NvicIRQDisabled(GPIO_ODD_IRQn)) { NVIC_ClearPendingIRQ(GPIO_ODD_IRQn); NVIC_EnableIRQ(GPIO_ODD_IRQn); } if (CORE_NvicIRQDisabled(GPIO_EVEN_IRQn)) { NVIC_ClearPendingIRQ(GPIO_EVEN_IRQn); NVIC_EnableIRQ(GPIO_EVEN_IRQn); } }
// Even // ************************************************************************* void GPIO_EVEN_IRQHandler(void) { uint32_t iflags;
/* Get all even interrupts. */ iflags = GPIO_IntGetEnabled() & _GPIOINT_IF_EVEN_MASK;
/* Clean only even interrupts. */ GPIO_IntClear(iflags);
GPIOINT_IRQDispatcher(iflags); }
// Odd // ************************************************************************* void GPIO_ODD_IRQHandler(void) { uint32_t iflags;
/* Get all odd interrupts. */ iflags = GPIO_IntGetEnabled() & _GPIOINT_IF_ODD_MASK;
/* Clean only odd interrupts. */ GPIO_IntClear(iflags);
GPIOINT_IRQDispatcher(iflags); }
// ************************************************************************* static void GPIOINT_IRQDispatcher(uint32_t iflags) { uint32_t irqIdx; GPIOINT_IrqCallbackPtr_t callback; // defined in gpiointerrupts.h: // typedef void (*GPIOINT_IrqCallbackPtr_t)(uint8_t intNo);
/* check for all flags set in IF register */ while (iflags != 0U) { irqIdx = SL_CTZ(iflags); // turn flag bits into an index
/* clear flag*/
iflags &= ~(1UL << irqIdx);
callback = gpioCallbacks[irqIdx]; // get the address of the callback
if (callback) {
/* call user callback */
callback((uint8_t)irqIdx);
}
} }
So this is the mechanism by which the application ISRs are handled. Now figure out how the addresses of the ISR routines are loaded into gpioCallbacks[32]. This is handled by:
// ************************************************************************* void GPIOINT_CallbackRegister(uint8_t intNo, GPIOINT_IrqCallbackPtr_t callbackPtr) { CORE_ATOMIC_SECTION( /* Dispatcher is used */ gpioCallbacks[intNo] = callbackPtr; ) }
GPIOINT_CallbackRegister() is called from : sl_simple_button.c
// ************************************************************************* sl_status_t sl_simple_button_init(void *context) { sl_simple_button_context_t *button = context;
CMU_ClockEnable(cmuClock_GPIO, true);
GPIO_PinModeSet(button->port, button->pin, SL_SIMPLE_BUTTON_GPIO_MODE, SL_SIMPLE_BUTTON_GPIO_DOUT);
button->state = ((bool)GPIO_PinInGet(button->port, button->pin) == SL_SIMPLE_BUTTON_POLARITY);
if (button->mode == SL_SIMPLE_BUTTON_MODE_INTERRUPT) { GPIOINT_Init(); GPIOINT_CallbackRegister(button->pin, // Callback function that handles the interrupts (GPIOINT_IrqCallbackPtr_t)sli_simple_button_on_change); GPIO_ExtIntConfig(button->port, button->pin, button->pin, true, // rising edge true, // falling edge true); // enable IRQs }
return SL_STATUS_OK; }
We have two paths to follow: a) The initialization code : sl_simple_button_init() b) The callback IRQ handler : sli_simple_button_on_change()
Follow the initialization code
sl_simple_button_init() is referenced from : autogen/sl_simple_button_instances.c
defining/filling out these data structures:
// Button 0 sl_simple_button_context_t simple_btn0_context = { .state = 0, .history = 0, .port = SL_SIMPLE_BUTTON_BTN0_PORT, .pin = SL_SIMPLE_BUTTON_BTN0_PIN, .mode = SL_SIMPLE_BUTTON_BTN0_MODE, };
const sl_button_t sl_button_btn0 = { .context = &simple_btn0_context, .init = sl_simple_button_init, // << callback .get_state = sl_simple_button_get_state, .poll = sl_simple_button_poll_step, .enable = sl_simple_button_enable, .disable = sl_simple_button_disable, };
// Button 1 sl_simple_button_context_t simple_btn1_context = { .state = 0, .history = 0, .port = SL_SIMPLE_BUTTON_BTN1_PORT, .pin = SL_SIMPLE_BUTTON_BTN1_PIN, .mode = SL_SIMPLE_BUTTON_BTN1_MODE, };
const sl_button_t sl_button_btn1 = { .context = &simple_btn1_context, .init = sl_simple_button_init, // << callback .get_state = sl_simple_button_get_state, .poll = sl_simple_button_poll_step, .enable = sl_simple_button_enable, .disable = sl_simple_button_disable, };
const sl_button_t *sl_simple_button_array[] = { &sl_button_btn0, &sl_button_btn1 };
const uint8_t simple_button_count = 2;
void sl_simple_button_init_instances(void) { sl_button_init(&sl_button_btn0); // pass pointer to data structure from above sl_button_init(&sl_button_btn1); // pass pointer to data structure from above }
void sl_simple_button_poll_instances(void) { sl_button_poll_step(&sl_button_btn0); sl_button_poll_step(&sl_button_btn1); }
sl_simple_button_init_instances() is referenced from : autogen/sl_event_handler.c
void sl_driver_init(void) { GPIOINT_Init(); sl_simple_button_init_instances(); // << called here sl_simple_led_init_instances(); }
sl_driver_init() is referenced from : sl_system_init.c
void sl_system_init(void) { sl_platform_init(); sl_driver_init(); // << called here sl_service_init(); sl_stack_init(); sl_internal_app_init(); }
sl_system_init() is referenced from : main.c
// Initialize Silicon Labs device, system, service(s) and protocol stack(s). // Note that if the kernel is present, processing task(s) will be created by // this call. sl_system_init(); // << This is the top of the call-chain for initialization.
Following the callback IRQ handler: sli_simple_button_on_change()
sli_simple_button_on_change() callback is defined in : sl_simple_button.c
/*************************************************************************//
-
An internal callback called in interrupt context whenever a button changes
-
its state. (mode - SL_SIMPLE_BUTTON_MODE_INTERRUPT)
-
@note The button state is updated by this function. The application callback
-
should not update it again.
-
@param[in] interrupt_no Interrupt number (pin number) ******************************************************************************/ static void sli_simple_button_on_change(uint8_t interrupt_no) { for (uint8_t i = 0; i < simple_button_count; i++) {
sl_simple_button_context_t *ctxt = ((sl_simple_button_context_t *)sl_simple_button_array[i]->context);
if ( (ctxt->pin == interrupt_no) && (ctxt->state != SL_SIMPLE_BUTTON_DISABLED) ) {
ctxt->state = ((bool)GPIO_PinInGet(ctxt->port, ctxt->pin) == SL_SIMPLE_BUTTON_POLARITY);
sl_button_on_change(sl_simple_button_array[i]); // << follow this
break; } } }
sl_button_on_change() is defined in : gecko_sdk_3.2.3/.../app_button_press.c
/*************************************************************************//
- This is a callback function that is invoked each time a GPIO interrupt
- in one of the pushbutton inputs occurs.
- @param[in] handle Pointer to button instance
- @note This function is called from ISR context and therefore it is
-
not possible to call any BGAPI functions directly. The button state
-
of the instance is updated based on the state change. The state is
-
updated only after button release and it depends on how long the
-
button was pressed. The button state is handled by the main loop.
******************************************************************************/ void sl_button_on_change(const sl_button_t *handle) { uint32_t t_diff; // If disabled, do nothing if (disabled) { return; } // Iterate over buttons for (uint8_t i = 0; i < SL_SIMPLE_BUTTON_COUNT; i++) {
// If the handle is applicable
if (SL_SIMPLE_BUTTON_INSTANCE(i) == handle) {
// If button is pressed, update ticks
if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
buttons[i].timestamp = sl_sleeptimer_get_tick_count();
} else if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_RELEASED) {
// Check time difference
t_diff = sl_sleeptimer_get_tick_count() - buttons[i].timestamp;
// Set state flag according to the difference
if (t_diff < sl_sleeptimer_ms_to_tick(SHORT_BUTTON_PRESS_DURATION)) {
buttons[i].state = APP_BUTTON_PRESS_DURATION_SHORT;
} else if (t_diff < sl_sleeptimer_ms_to_tick(MEDIUM_BUTTON_PRESS_DURATION)) {
buttons[i].state = APP_BUTTON_PRESS_DURATION_MEDIUM;
} else if (t_diff < sl_sleeptimer_ms_to_tick(LONG_BUTTON_PRESS_DURATION)) {
buttons[i].state = APP_BUTTON_PRESS_DURATION_LONG;
} else {
buttons[i].state = APP_BUTTON_PRESS_DURATION_VERYLONG;
}
} else {
// Disabled state
// Do nothing
}
}//if
}//for }
There is other code that looks at the buttons[i].state values above that results in calling the callback app_button_press_cb() in app.c, which calls sl_btmesh_change_switch_position()
Tracing UPWARDS, how the callback app_button_press_cb() in app.c is called
In file app_button_press.c :
/*************************************************************************//
- Step function for button press ******************************************************************************/ // *** trace this *** void app_button_press_step(void) // <<== { // Iterate over buttons for (uint8_t i = 0; i < SL_SIMPLE_BUTTON_COUNT; i++) { // If the button is pressed if (buttons[i].state != APP_BUTTON_PRESS_NONE) { // Call callback app_button_press_cb(i, buttons[i].state); // <<== params are: button and duration // Clear state buttons[i].state = APP_BUTTON_PRESS_NONE; } } }
// ignore this call for now void button_press_from_cli(sl_cli_command_arg_t *arguments) { uint8_t button_id; uint8_t duration; button_id = sl_cli_get_argument_uint8(arguments, BUTTON_ID_PARAM_IDX); duration = sl_cli_get_argument_uint8(arguments, DURATION_PARAM_IDX); if (duration > APP_BUTTON_PRESS_DURATION_VERYLONG) { duration = APP_BUTTON_PRESS_DURATION_VERYLONG; } if (button_id >= SL_SIMPLE_BUTTON_COUNT) { button_id = SL_SIMPLE_BUTTON_COUNT - 1; } app_button_press_cb(button_id, duration); // <<== params are: button and duration }
Trace app_button_press_step() : called from file : sl_event_handler.c
void sl_service_process_action(void) { app_button_press_step(); // <<== sli_simple_timer_step(); sl_cli_instances_tick(); }
Trace sl_service_process_action() : called from file : sl_system_process_action.c
void sl_system_process_action(void) { sl_platform_process_action(); sl_service_process_action(); // <<== sl_stack_process_action(); sl_internal_app_process_action(); }
Trace sl_system_process_action() : called from main.c ( main while (1) loop )
sl_system_process_action(); // << This is the top of the call-chain for execution/handling
Tracing DOWNWARDS, in app_button_press_cb()
In file app.c, app_button_press_cb()
case APP_BUTTON_PRESS_DURATION_LONG: // Handling of button press greater than 1s and less than 5s if (button == BUTTON_PRESS_BUTTON_0) { sl_btmesh_change_switch_position(SL_BTMESH_LIGHTING_CLIENT_OFF); } else { sl_btmesh_change_switch_position(SL_BTMESH_LIGHTING_CLIENT_ON); }
In file sl_btmesh_lighting_client.c
/*******************************************************************************
- This function change the switch position and send it to the server.
- @param[in] position Defines switch position change, possible values are:
-
- SL_BTMESH_LIGHTING_CLIENT_OFF
-
- SL_BTMESH_LIGHTING_CLIENT_ON
-
- SL_BTMESH_LIGHTING_CLIENT_TOGGLE
******************************************************************************/ void sl_btmesh_change_switch_position(uint8_t position) { if (position != SL_BTMESH_LIGHTING_CLIENT_TOGGLE) { switch_pos = position; } else { switch_pos = 1 - switch_pos; // Toggle switch state }
// Turns light ON or OFF, using Generic OnOff model if (switch_pos) { log(ONOFF_LIGHTING_LOGGING_ON); lightness_percent = LIGHTNESS_PCT_MAX; } else { log(ONOFF_LIGHTING_LOGGING_OFF); lightness_percent = 0; } // Request is sent 3 times to improve reliability onoff_request_count = ONOFF_RETRANSMISSION_COUNT;
send_onoff_request(0); // Send the first request
// If there are more requests to send, start a repeating soft timer // to trigger retransmission of the request after 50 ms delay if (onoff_request_count > 0) { sl_status_t sc = sl_simple_timer_start(&onoff_retransmission_timer, ONOFF_RETRANSMISSION_TIMEOUT, onoff_retransmission_timer_cb, NO_CALLBACK_DATA, true); app_assert_status_f(sc, "Failed to start periodic timer\n"); } } // sl_btmesh_change_switch_position()
/*************************************************************************//
- This function publishes one generic on/off request to change the state
- of light(s) in the group. Global variable switch_pos holds the latest
- desired light state, possible values are:
- switch_pos = 1 -> PB1 was pressed long (above 1s), turn lights on
- switch_pos = 0 -> PB0 was pressed long (above 1s), turn lights off
- param[in] retrans Indicates if this is the first request or a retransmission,
-
possible values are 0 = first request, 1 = retransmission.
- @note This application sends multiple generic on/off requests for each
-
long button press to improve reliability.
-
The transaction ID is not incremented in case of a retransmission.
******************************************************************************/ static void send_onoff_request(uint8_t retrans) { struct mesh_generic_request req; const uint32_t transtime = 0; // using zero transition time by default sl_status_t sc;
req.kind = mesh_generic_request_on_off; req.on_off = switch_pos ? MESH_GENERIC_ON_OFF_STATE_ON : MESH_GENERIC_ON_OFF_STATE_OFF;
// Increment transaction ID for each request, unless it's a retransmission if (retrans == 0) { onoff_trid++; }
// Delay for the request is calculated so that the last request will have // a zero delay and each of the previous request have delay that increases // in 50 ms steps. For example, when using three on/off requests // per button press the delays are set as 100, 50, 0 ms uint16_t delay = (onoff_request_count - 1) * REQ_DELAY_MS;
sc = mesh_lib_generic_client_publish(MESH_GENERIC_ON_OFF_CLIENT_MODEL_ID, BTMESH_LIGHTING_CLIENT_MAIN, onoff_trid, &req, transtime, // transition time in ms delay, NO_FLAGS // flags );
if (sc == SL_STATUS_OK) { log_info(LIGHTING_ONOFF_LOGGING_CLIENT_PUBLISH_SUCCESS, onoff_trid, delay); } else { log_btmesh_status_f(sc, LIGHTING_ONOFF_LOGGING_CLIENT_PUBLISH_FAIL); }
// Keep track of how many requests has been sent if (onoff_request_count > 0) { onoff_request_count--; }
} // send_onoff_request()
mesh_lib_generic_client_publish() is the method (function) that clients uses to publish a generic on/off message.
Summary of modifications to create the client-publisher starter code
All of the models remain. Some functionality from the example code has been disabled or modified.
files = gecko_sdk_3.2.3/app/common/util/app_button_press/app_button_press.c gecko_sdk_3.2.3/app/common/util/app_button_press/app_button_press.h See my initials: DOS and "Student Edits".
Changed sl_button_on_change() to set "pressed" and "released" states. Removed/disabled the measurement of button press code.
file = app.c See my initials: DOS
Modified app_button_press_cb() to only respond to button "press" and button "release"
Changed device from "switch node" to "cli-pub" for client-publisher
Summary of modifications to create the server-sub-scriber starter code
All of the models remain. Some functionality from the example code has been disabled or modified.
file = app.c See my initials: DOS
Changed device from "light node" to "ser-sub" for server-subscriber
file = app_out_lcd.c See my initials: DOS and "Student Edits".
Commented out functionality for sl_btmesh_ctl_on_ui_update(). The CTL models are still there in the publisher, they just can't send any messages.
Modified sl_btmesh_lighting_server_on_ui_update() to have students convert the lightness level into "Button Pressed" and "Button Released" messages on the LCD.
Left the control of the LEDs the unmodified.