源码:
https://download.csdn.net/download/hxkrrzq/88353283
以上源码都是官方资源,可以自行gitbub下载(参见之前笔记)
蓝牙广播格式化
之前的笔记中广播数据是直接使用的十六进制字符串,关于这32bytes数据的格式化在这个例程中,有实例代码。
同时这些例程的readme十分完善。
// Advertising flags (common)
#define ADVERTISE_FLAGS_LENGTH 2
#define ADVERTISE_FLAGS_TYPE 0x01// Bit mask for flags advertising data type
#define ADVERTISE_FLAGS_LE_LIMITED_DISCOVERABLE 0x01
#define ADVERTISE_FLAGS_LE_GENERAL_DISCOVERABLE 0x02
#define ADVERTISE_FLAGS_BR_EDR_NOT_SUPPORTED 0x04// Scan Response
#define ADVERTISE_MANDATORY_DATA_LENGTH 5
#define ADVERTISE_MANDATORY_DATA_TYPE_MANUFACTURER 0xFF// Advertise ID
#define ADVERTISE_COMPANY_ID 0x0047
#define ADVERTISE_FIRMWARE_ID 0x0000// Complete local name
#define ADVERTISE_TYPE_LOCAL_NAME 0x09
#define ADVERTISE_DEVICE_NAME_LEN 10
#define ADVERTISE_DEVICE_NAME "Thermostat"// Helper macro
#define UINT16_TO_BYTES(x) { (uint8_t)(x), (uint8_t)((x) >> 8) }// Default advertising scan response parameters
#define ADVERTISE_SCAN_RESPONSE_DEFAULT \{ \.flags_length = ADVERTISE_FLAGS_LENGTH, \.flags_type = ADVERTISE_FLAGS_TYPE, \.flags = ADVERTISE_FLAGS_LE_GENERAL_DISCOVERABLE \| ADVERTISE_FLAGS_BR_EDR_NOT_SUPPORTED, \.mandatory_data_length = ADVERTISE_MANDATORY_DATA_LENGTH, \.mandatory_data_type = ADVERTISE_MANDATORY_DATA_TYPE_MANUFACTURER, \.company_id = UINT16_TO_BYTES(ADVERTISE_COMPANY_ID), \.firmware_id = UINT16_TO_BYTES(ADVERTISE_FIRMWARE_ID), \.local_name_length = ADVERTISE_DEVICE_NAME_LEN + 1, \.local_name_type = ADVERTISE_TYPE_LOCAL_NAME, \.local_name = ADVERTISE_DEVICE_NAME \}/***************************************************************************//*** @brief* Structure that holds Scan Response data******************************************************************************/
typedef struct {uint8_t flags_length; /**< Length of the Flags field. */uint8_t flags_type; /**< Type of the Flags field. */uint8_t flags; /**< Flags field. */uint8_t mandatory_data_length; /**< Length of the mandata field. */uint8_t mandatory_data_type; /**< Type of the mandata field. */uint8_t company_id[2]; /**< Company ID. */uint8_t firmware_id[2]; /**< Firmware ID */uint8_t local_name_length; /**< Length of the local name field. */uint8_t local_name_type; /**< Type of the local name field. */uint8_t local_name[ADVERTISE_DEVICE_NAME_LEN]; /**< Local name field. */
} advertise_scan_response_t;// The advertising set handle allocated from Bluetooth stack.
static uint8_t advertising_set_handle = 0xff;
static const advertise_scan_response_t adv_scan_response= ADVERTISE_SCAN_RESPONSE_DEFAULT;...
...
...
[设置广播数据]
sc = sl_bt_legacy_advertiser_set_data(advertising_set_handle,0, // 类型是广播,而不是广播回包sizeof(adv_scan_response),(uint8_t *)&adv_scan_response);
蓝牙事件
void sl_bt_on_event(sl_bt_msg_t *evt)
{sl_status_t sc;bd_addr address;uint8_t address_type;uint8_t system_id[8];switch (SL_BT_MSG_ID(evt->header)) {case sl_bt_evt_system_boot_id:
...case sl_bt_evt_connection_opened_id:
...case sl_bt_evt_connection_closed_id:
...///// Add additional event handlers here as your application requires! /////// -------------------------------// Handle configuration characteristics.case sl_bt_evt_gatt_server_attribute_value_id:break;case sl_bt_evt_gatt_server_user_write_request_id:thermostat_process_evt_gatt_server_user_write_request(&(evt->data.evt_gatt_server_user_write_request));break;case sl_bt_evt_gatt_server_user_read_request_id:thermostat_process_evt_gatt_server_user_read_request(&(evt->data.evt_gatt_server_user_read_request));break;case sl_bt_evt_system_external_signal_id:thermostat_process_evt_external_signal(evt->data.evt_system_external_signal.extsignals);break;case sl_bt_evt_system_soft_timer_id:break;// -------------------------------// Default event handler.default:break;}
}
代码中添加了几个蓝牙协议栈的事件,需要搞清楚如何使用:
sl_bt_evt_gatt_server_attribute_value_id
最常见的,手机侧写数据后,会进入此事件,识别写入的value,做出相应动作;
sl_bt_evt_gatt_server_user_write_request_id
简单阅读api.h函数说明后还是不清楚;
参考:
GATT Server and Client Roles,文档中有一段较详细的对比说明:
这个user的意思是,只上报写事件,不直接处理接收到的数据,这个操作粒度比“sl_bt_evt_gatt_server_attribute_value”事件更细。
sl_bt_evt_gatt_server_user_read_request_id
同上
sl_bt_evt_system_soft_timer_id
api文档中:
提示了,此函数超时后会发出此事件。
sl_bt_evt_system_external_signal_id
外部信号是什么?关键的是何时会产生此事件?
同样的搜索api.h和官方帮助文档,都不能获得有效信息,只是说这是外部信号,没找到怎么使用,猜测它是可以自定义的。
继续寻找……
根据api.h文档可以找到“sl_bt_evt_system_external_signal_t”结构体,在接口文档中公开,必然有些地方会使用它。
检索整个蓝牙例程包,发现有多个例程用到了:
具体可以参考例程:bluetooth_ethernet_gateway
最后找到发送信号的函数:
/*** @brief Signal the Bluetooth stack that an external event has happened.** Signals can be used to report status changes from interrupt context or from* other threads to application. Signals are bits that are automatically cleared* after application has been notified.** If the Platform Core Interrupt API has been configured to use the* CORE_ATOMIC_METHOD_BASEPRI as the implementation method of atomic sections,* this function must not be called from an interrupt handler with a priority* higher than CORE_ATOMIC_BASE_PRIORITY_LEVEL.** @param signals is a bitmask defining active signals that are reported back to* the application by system_external_signal-event.* @return SL_STATUS_OK if the operation is successful,* SL_STATUS_NO_MORE_RESOURCE indicating the request could not be processed* due to resource limitation at the moment, or SL_STATUS_INVALID_STATE when* the on-demand start feature is used and the stack is currently stopped.*/
sl_status_t sl_bt_external_signal(uint32_t signals);
反过来,检索sl_bt_external_signal,看看那些地方用到了,怎么使用的。
结果是很多例程使用到了。选“bluetooth_air_quality_monitor”看看:
产生信号的地方:
/***************************************************************************//*** Simple Button* Button state changed callback* @param[in] handle * Button event handle******************************************************************************/
void sl_button_on_change(const sl_button_t *handle)
{// Button released.if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_RELEASED) {if (&sl_button_btn0 == handle) {sl_bt_external_signal(AIR_QUALITY_MONITOR_BUTTON_EVENT);}}
}
接收信号:
/***************************************************************************//*** Bluetooth stack event handler.* This overrides the dummy weak implementation.** @param[in] evt Event coming from the Bluetooth stack.******************************************************************************/
void sl_bt_on_event(sl_bt_msg_t *evt)
{sl_status_t sc;bd_addr address;uint8_t address_type;uint8_t system_id[8];switch (SL_BT_MSG_ID(evt->header)) {// -------------------------------// This event indicates the device has started and the radio is ready.// Do not call any stack command before receiving this boot event!case sl_bt_evt_system_boot_id:.....break;// -------------------------------// This event indicates that a new connection was opened.case sl_bt_evt_connection_opened_id:connection_opened_handler(evt);break;// -------------------------------// This event indicates that a connection was closed.case sl_bt_evt_connection_closed_id:connection_closed_handler(evt);break;// -------------------------------// The parameters eventcase sl_bt_evt_connection_parameters_id:connection_parameters_handler(evt);break;// -------------------------------// The confirm_bonding eventcase sl_bt_evt_sm_confirm_bonding_id:sm_confirm_bonding_handler(evt);break;// -------------------------------// This event triggered after the pairing or bonding procedure is// successfully completed.case sl_bt_evt_sm_bonded_id:app_log("Bluetooth Stack Event : BONDED\r\n");break;// -------------------------------// This event is triggered if the pairing or bonding procedure fails.case sl_bt_evt_sm_bonding_failed_id:sm_bonding_failed_handler(evt);break;// Service the gatt server user write request eventcase sl_bt_evt_gatt_server_user_write_request_id:// Service write handlersair_quality_user_write_callback(evt);break;// Service the gatt server user read request eventcase sl_bt_evt_gatt_server_user_read_request_id:air_quality_user_read_callback(evt);break;// -------------------------------// External signal indication (comes from the interrupt handler)case sl_bt_evt_system_external_signal_id:air_quality_process_event(evt->data.evt_system_external_signal.extsignals);break;///// Add additional event handlers here as your application requires! /////// -------------------------------// Default event handler.default:break;}
}
可知“bluetooth_air_quality_monitor”是一个主机应用,暂且记录下来,以后再去看这些event的用法。
回到【bluetooth_thermostat】应用,关于系统外部信号:
【定义】
#define THERMOSTAT_TIMER_EVENT (1 << 0)
#define THERMOSTAT_BUTTON_EVENT (1 << 1)【发送】/***************************************************************************//*** Callback on button change.******************************************************************************/
void sl_button_on_change(const sl_button_t *handle)
{if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_RELEASED) {if (&sl_button_btn0 == handle) {sl_bt_external_signal(THERMOSTAT_BUTTON_EVENT);}}
}/***************************************************************************//*** Callback on timer period.******************************************************************************/
static void thermostat_periodic_timer_callback(sl_sleeptimer_timer_handle_t *timer, void *data)
{(void) timer;(void) data;sl_bt_external_signal(THERMOSTAT_TIMER_EVENT);
}【接收-处理】蓝牙事件处理中:case sl_bt_evt_system_external_signal_id:thermostat_process_evt_external_signal(evt->data.evt_system_external_signal.extsignals);break;/***************************************************************************//*** Thermostat Application Process External Signal.******************************************************************************/
void thermostat_process_evt_external_signal(uint32_t extsignals)
{if (extsignals & THERMOSTAT_TIMER_EVENT) {thermostat_timer_event_handler();}if (extsignals & THERMOSTAT_BUTTON_EVENT) {thermostat_button_event_handler();}
}
关于延时函数
在程序包中可以看到多个与延时相关的函数:
/***************************************************************************//*** @brief* Delay microseconds* @param[in] us* Microseconds******************************************************************************/
sl_udelay_wait(us)/***************************************************************************//*** @brief* Delay milliseconds* @param[in] ms* Milliseconds******************************************************************************/
sl_sleeptimer_delay_millisecond(ms)/***************************************************************************//*** @brief* Get curent tick count* @return* Current tick count******************************************************************************/
sl_sleeptimer_get_tick_count()/***************************************************************************//*** @brief* Get current tick count in milliseconds unit* @return* Current tick count in milliseconds******************************************************************************/
sl_sleeptimer_tick_to_ms(sl_sleeptimer_get_tick_count())
以上信息:
tick与毫秒需要转化;
sl_udelay_wait似乎不存在?看看是否需要安装组件?
安装完成后,自动添加了微秒延时的代码了(参见下文),注意这是基于核心频率实现的,与ms不同,他是使用sleeptimer时钟实现。
/** @brief* Hardware delay loop** @detail* This is the hardware specific delay loop. It is designed specifically to* execute in 4 or 3 cycles for each iteration depending on the architecture.* Using this information the caller can use the core clock frequency to* calculate the number of loops required in order to delay a specific time* period.* * @param[in] n (r0)* n is the number of loops to execute. Each loop will execute in 4 cycles.* Note that we assume that r0 > 0, so this invariant should be checked by* the caller.*/
sli_delay_loop:subs r0, r0, #1beq doneb.n sli_delay_loop
done:bx lr.end// 以上汇编,摘自驱动文件“sl_udelay_armv6m_gcc.S”void sli_delay_loop(unsigned n);void sl_udelay_wait(unsigned us)
{uint32_t freq_khz;uint32_t ns_period;uint32_t cycles;uint32_t loops;freq_khz = SystemCoreClockGet() / 1000U;if (freq_khz == 0) {EFM_ASSERT(false);return;}ns_period = 1000000U / freq_khz;if (ns_period == 0) {EFM_ASSERT(false);return;}cycles = us * 1000U / ns_period;loops = cycles / HW_LOOP_CYCLE;if (loops > 0U) {sli_delay_loop(loops);}
}
例程中的Flash操作
参考例程的readme:
上电之后,首先初始化数据,从flash读取,如果没有数据则将其设置为缺省值;
读参数操作是在gatt属性读的处理代码中;
写参数操作是在gatt属性写的处理代码中;
代码对nvm3的原生函数进行了一层封装,加入了最大最小值的判定。
初始化:
// Max and min keys for data objects
#define MODE_KEY (NVM3_KEY_MIN)
#define SETPOINT_KEY (NVM3_KEY_MIN + 1)
#define HYSTERESIS_KEY (NVM3_KEY_MIN + 2)
#define LOWER_THRESHOLD_KEY (NVM3_KEY_MIN + 3)
#define UPPER_THRESHOLD_KEY (NVM3_KEY_MIN + 4)
#define THRESHOLD_ALARM_STATUS_KEY (NVM3_KEY_MIN + 5)#define MODE_DEFAULT (0)#define THRESHOLD_ALARM_DEFAULT (1)
#define SETPOINT_VALUE_MIN (-3500)
#define SETPOINT_VALUE_MAX (12000)
#define SETPOINT_VALUE_DEFAULT (2500)#define HYSTERESIS_VALUE_MIN (0)
#define HYSTERESIS_VALUE_MAX (15500)
#define HYSTERESIS_VALUE_DEFAULT (100)#define LOWER_THRESHOLD_VALUE_MIN (-3500)
#define LOWER_THRESHOLD_VALUE_MAX (12000)
#define LOWER_THRESHOLD_VALUE_DEFAULT (0)#define UPPER_THRESHOLD_VALUE_MIN (-3500)
#define UPPER_THRESHOLD_VALUE_MAX (12000)
#define UPPER_THRESHOLD_VALUE_DEFAULT (5000)// Use the default nvm3 handle from nvm3_default.h
#define NVM3_DEFAULT_HANDLE nvm3_defaultHandle// nvm3函数的封装
static void conf_data_u8_init(nvm3_ObjectKey_t key,uint8_t min_value,uint8_t max_value,uint8_t default_value)
{Ecode_t err;uint8_t read_value;// check if the designated keys contain data, and initialize if needed.err = conf_data_u8_read(key, &read_value);if ((err == ECODE_NVM3_OK)&& (read_value >= min_value)&& (read_value <= max_value)) {return;} else {nvm3_deleteObject(NVM3_DEFAULT_HANDLE, key);}// Write default valueerr = nvm3_writeData(NVM3_DEFAULT_HANDLE,key,(unsigned char *)&default_value,sizeof(default_value));
}static void conf_data_i16_init(nvm3_ObjectKey_t key,uint16_t min_value,uint16_t max_value,uint16_t default_value)
{Ecode_t err;int16_t read_value;// check if the designated keys contain data, and initialize if needed.err = conf_data_i16_read(key, &read_value);if ((err == ECODE_NVM3_OK)&& (read_value >= min_value)&& (read_value <= max_value)) {return;} else {nvm3_deleteObject(NVM3_DEFAULT_HANDLE, key);}// Write default valueerr = nvm3_writeData(NVM3_DEFAULT_HANDLE,key,(unsigned char *)&default_value,sizeof(default_value));
}/***************************************************************************//*** Initialize NVM3 config.******************************************************************************/
void user_config_nvm3_init(void)
{Ecode_t err;// This will call nvm3_open() with default parameters for// memory base address and size, cache size, etc.err = nvm3_initDefault();EFM_ASSERT(err == ECODE_NVM3_OK);// Initialize the mode config.conf_data_u8_init(MODE_KEY, 0, 1,MODE_DEFAULT);// Initialize the setpoint config.conf_data_i16_init(SETPOINT_KEY,SETPOINT_VALUE_MIN,SETPOINT_VALUE_MAX,SETPOINT_VALUE_DEFAULT);// Initialize the hysteresis config.conf_data_i16_init(HYSTERESIS_KEY,HYSTERESIS_VALUE_MIN,HYSTERESIS_VALUE_MAX,HYSTERESIS_VALUE_DEFAULT);// Initialize the lower threshold config.conf_data_i16_init(LOWER_THRESHOLD_KEY,LOWER_THRESHOLD_VALUE_MIN,LOWER_THRESHOLD_VALUE_MAX,LOWER_THRESHOLD_VALUE_DEFAULT);// Initialize the upper threshold config.conf_data_i16_init(UPPER_THRESHOLD_KEY,UPPER_THRESHOLD_VALUE_MIN,UPPER_THRESHOLD_VALUE_MAX,UPPER_THRESHOLD_VALUE_DEFAULT);// Initialize the notification enable config.conf_data_u8_init(THRESHOLD_ALARM_STATUS_KEY, 0, 2, THRESHOLD_ALARM_DEFAULT);
}/***************************************************************************//*** Application Init.******************************************************************************/
void thermostat_app_init(void)
{oled_app_init();// Load configuration from NVMuser_config_nvm3_init();hysteresis = user_config_nvm3_get_hysteresis() / 100;setpoint = user_config_nvm3_get_setpoint() / 100;lower_threshold = user_config_nvm3_get_lower_threshold() / 100;upper_threshold = user_config_nvm3_get_uppper_threshold() / 100;is_alarm_enable = user_config_nvm3_get_alarm_status();buzz2_app_init();temphum9_app_init();// Create oled display periodic timersl_sleeptimer_start_periodic_timer_ms(&thermostat_timer,5000,thermostat_periodic_timer_callback,NULL,0,0);
}
读取参数:
/***************************************************************************//*** Get NVM3 Setpoint.******************************************************************************/
int16_t user_config_nvm3_get_setpoint(void)
{int16_t setpoint;Ecode_t err;err = conf_data_i16_read(SETPOINT_KEY, &setpoint);app_assert_s(err == ECODE_NVM3_OK);return setpoint;
}
设置参数:
/***************************************************************************//*** Set NVM3 Setpoint.******************************************************************************/
sl_status_t user_config_nvm3_set_setpoint(int16_t setpoint)
{Ecode_t err;if ((setpoint > SETPOINT_VALUE_MAX)|| (setpoint < SETPOINT_VALUE_MIN)) {app_log("Invalid setpoint config: %d\r\n", setpoint);return SL_STATUS_INVALID_RANGE;}err = nvm3_writeData(NVM3_DEFAULT_HANDLE,SETPOINT_KEY,(unsigned char *)&setpoint,sizeof(setpoint));if (ECODE_NVM3_OK == err) {app_log("Stored setpoint config: %d\r\n", setpoint);return SL_STATUS_OK;} else {app_log("Error storing setpoint config\r\n");return SL_STATUS_FAIL;}
}
大数据量的存储需要什么
flash的操作可以照抄上边的代码。但是还缺少一些基础知识,参考《笔记8》
1. 正确配置nvm3组件参数
缓存大小(Cache Size):减少了键值检索的时间,需要大于等于flash中存在的所有数据对象的数目,包含删除的对象(删除后没有擦除page时)。一个cache占用8byte的RAM,在大数据量多条目存储时,不可能有这么大的ram。
最大数据对象的大小:208 -4096 bytes.。这个影响了nvm3的通用开销。
Flash垃圾回收(Flash repack)的剩余空间阈值:默认值0,和强制垃圾回收的阈值相同,此处不需要自定义。
存储空间大小:单位是byte,最小值是3个page
2. nvm3只有键值对,对key的要求是固定的,必须在0x00000 - 0x0FFFF之内,所以存储的数据条目最多65536条。目前产品设计需求,每分钟一条数据,此处限制了最多45天。
按照500K的存储空间计算,8byte数据长度,可以存多少条数据?
数据object的数据大小事8byte,但是每个对象的开销还有4byte,共12byte。
忽略整个数据的一些共同开销,大约只有几百byte。可以算出500*1024/12 = 42666个
折算到可以存储26天的数据。
3. 需要提供的功能:
数据记录写函数;
数据记录读函数;
能够查找、定位到当前最新的数据,即使重新上电,也能继续记录,这可能需要用到:
nvm3_countObjects()
nvm3_enumObjects()
具体参考:
《nvm3_generic.h》,头文件的最后较大篇幅的使用文档。
蓝牙温控器其他代码
回到主题,蓝牙温控器还有哪些代码值得关注?
该例程最后的温控执行器是用LED模拟的:
static void thermostat_output_control(output_control_t output_control)
{if (output_control == ACTUATOR_ON) {sl_led_turn_on(SL_SIMPLE_LED_INSTANCE(0));} else {sl_led_turn_off(SL_SIMPLE_LED_INSTANCE(0));}
}
温控逻辑在readme中有流程图,这里不需具体分析,控制流所在函数:
static void thermostat_data_process(void)
温控器的传感器数据采集:
mikroe_shtc3_get_temperature_and_humidity(SHTC3_DATA_MODE_NORMAL, &measurement_value);
以上数据采集,温度控制都放在了定时器的回调中:
static void thermostat_timer_event_handler(void)
值得关注的是,该函数并非直接注册为软件定时器的回调函数,这个定时器循环只负责发送“外部信号”,外部信号触发了协议栈的事件,循环执行的代码是在蓝牙协议栈的【sl_bt_evt_system_external_signal_id】处理中进行的,所以此处的疑问是,为什么要兜一圈放到蓝牙协议栈的事件处理中,而不是直接在app_time的定时调用?
需要搞清楚main函数中调用的sl_system_process_action();
void sl_system_process_action(void)
{
sl_platform_process_action(); // 空
sl_service_process_action(); // app_timer此中执行
sl_stack_process_action(); // 蓝牙协议栈此中执行
sl_internal_app_process_action(); // 空
}
sl_stack_process_action(); ---> sl_bt_step();void sl_bt_step(void)
{sl_bt_msg_t evt;sl_bt_run(); // 运行协议栈状态机uint32_t event_len = sl_bt_event_pending_len();// For preventing from data loss, the event will be kept in the stack's queue// if application cannot process it at the moment.if ((event_len == 0) || (!sl_bt_can_process_event(event_len))) {return;}// Pop (non-blocking) a Bluetooth stack event from event queue.sl_status_t status = sl_bt_pop_event(&evt);if(status != SL_STATUS_OK){return;}sl_bt_process_event(&evt); // 将协议栈运行的结果传递出来
}// 分发这些事件
void sl_bt_process_event(sl_bt_msg_t *evt)
{sl_gatt_service_device_information_on_event(evt);sl_bt_on_event(evt);
}
sl_service_process_action()中运行了所有的软件定时器的定时任务。接下来才会进入协议栈的运行和事件分发。相对来说协议栈的事件更加严谨可靠,猜测,协议栈内部的优先级处理可以保证关键蓝牙任务的执行,不会优先响应这个外部信号的事件。如果放到sl_service_process_action()直接运行,那可能会影响蓝牙协议栈的实时性。
总而言之,这个操作如果真有用的话,那一定体现在把何时执行定时任务的控制权全部交给蓝牙协议栈统筹。
(完)