项目介绍
基于EFR32MG24片上系统的温度可视化
- 使用EFR32的BLE_SDK实现蓝牙功能
- 初始化内置温度传感器,并通过蓝牙发送出去
- 使用python的bleak连接下位机EFR32 ,并把数据传递给pyqtgraph
设计思路
Silicon Labs提供的IDE有一个非常强大的且好用的的功能software component,使用它可以很方便完成对板载的led,vcom,温度传感器的驱动文件的添加。使得我们只需关心温度的读取及发送即可。难点就在于上位机通信 ,解析温度数据。
我使用的是bleak(一个异步的BLE库),pyqt5及pyqtgraph图表来制作上位机。
硬件介绍
XG24-EK2703A开发板分成两部分,第一部分是EFM32GG12构成的jlink下载器,支持虚拟串口,在线调试的功能。第二部分是本次任务的芯片EFR32MG24以及两颗用户按键和LED。
软件部分
下位机
- 由IDE在sl_system_init完成对蓝牙协议栈的初始化,对芯片的时钟初始化,堆栈分配
- 在appinit里完成GPIO初始化,温度传感器初始化
- 在sl_system_process_action里完成添加蓝牙任务,启动定时器定时发送温度数据。
- temperature_measurement_val_to_buf数据封装
上位机
- 安装并导入pyqt,asyncio,bleak,pyqtgraph库
- 开启多线程并把bleak蓝牙事务处理单独放在一个线程中,防止主界面卡死
- bleak作为异步蓝牙通信库,使用协程来完成蓝牙数据处理
- 协程是单线程下,提高数据接收,发送效率的机制
- 在处理网络通信,IO事件耗时操作时,不会死等而是去执行其他异步任务
- 利用信号-槽机制把接收的数据发送到pyqtgraph里
- 数据解码并绘图
软件流程图
蓝牙任务创建的流程图
这里面尤其要指出sl_bt_ht_temperature_measurement_indicate完成把原始温度数据封装到长度为5的buff数组中。
还有sl_bt_gatt_server_send_indication()函数是最底层的向上位机发送经过封装后的温度数据的函数。
因此上位机要想得到的正确的数据必须用indication属性来接收,之后要把buff数组内的数据再还原回封装前的值。
上位机,下位机流程图
主要代码和说明
下位机
1.硬件初始化
SL_WEAK void app_init(void)
{
/////////////////////////////////////////////////////////////////////////////
// Put your additional application init code here! 1秒 //
printf("start");
sl_simple_led_context_t led;
led.port=gpioPortA;
led.pin=4;
led.polarity=SL_SIMPLE_LED_POLARITY_ACTIVE_HIGH;
CMU_ClockEnable(cmuClock_GPIO, true);//时钟使能
GPIO_PinModeSet(led.port,
led.pin,
gpioModePushPull,
!led.polarity);//初始化GPIO的配置的
GPIO_PinOutSet(led.port,led.pin);//输出高电平
//sl_iostream_usart_vcom_config 硬件配置
led.pin=7;
GPIO_PinModeSet(led.port,
led.pin,
gpioModePushPull,
!led.polarity);
GPIO_PortOutClear(led.port,led.pin);//输出低电平
sl_sensor_rht_init();//初始化传感器
}
2.传感器初始化
void sl_sensor_rht_init(void)//传感器初始化
{
Ecode_t er;
// Init TEMPDRV
er = TEMPDRV_Init();
if (er != ECODE_EMDRV_TEMPDRV_OK) {
has_tempdrv = false;
return SL_STATUS_INITIALIZATION;
}
// Enable TEMPDRV
er = TEMPDRV_Enable(true);
if (er != ECODE_EMDRV_TEMPDRV_OK) {
TEMPDRV_DeInit();
has_tempdrv = false;
return SL_STATUS_INITIALIZATION;
}
// Reset to base values
temperature_millicelsius = T_MIN;
has_tempdrv = true;
return SL_STATUS_OK;
}
3.获取温度数据
sl_status_t sl_sensor_rht_get(int32_t *t)
{
sl_status_t sc = SL_STATUS_OK;
// Get temperature from TEMPDRV
int32_t temperature_celsius = (int32_t)TEMPDRV_GetTemp();
temperature_millicelsius = temperature_celsius * 1000;
(*t) = temperature_millicelsius;
return sc;
}
4.蓝牙任务
void sl_bt_ht_temperature_measurement_indication_changed_cb(uint8_t connection,
sl_bt_gatt_client_config_flag_t client_config)
{printf("sl_bt_ht_temperature_measurement_indication_changed_cb");
app_connection = connection;//获取蓝牙链接句柄
app_timer_start(&app_periodic_timer,
1 * 1000,
app_periodic_timer_cb,
NULL,
true);//开启定时器
}
//定时器回调 1秒中一次
static void app_periodic_timer_cb(app_timer_t *timer, void *data)
{
GPIO_PinOutToggle(led.port,7);
int32_t temperature = 0;
int tmp_c = 0.0;
// float tmp_f = 0.0;
// Measure temperature; units are % and milli-Celsius.
sl_sensor_rht_get( &temperature);
tmp_c = temperature / 1000;
printf("Temperature____123: %d\n", tmp_c);
sl_bt_ht_temperature_measurement_indicate(app_connection, temperature, false);
}
//蓝牙发送函数
sl_status_t sl_bt_ht_temperature_measurement_indicate(uint8_t connection,
int32_t value,
bool fahrenheit)
{
sl_status_t sc;
uint8_t buf[5] = { 0 };
temperature_measurement_val_to_buf(value, fahrenheit, buf);
sc = sl_bt_gatt_server_send_indication(
connection,
gattdb_ccx24,
sizeof(buf),
buf);
return sc;
}
5.数据封装
static void temperature_measurement_val_to_buf(int32_t value,
bool fahrenheit,
uint8_t *buffer)
{
uint32_t tmp_value = ((uint32_t)value & 0x00ffffffu) \
| ((uint32_t)(-3) << 24);
buffer[0] = 0;
//app_log_info(" tmp_value%d\n", tmp_value);
buffer[1] = tmp_value & 0xff;
// app_log_info(" buffer[1]%d\n", buffer[1]);
buffer[2] = (tmp_value >> 8) & 0xff;
// app_log_info(" buffer[2]%d\n", buffer[2]);
buffer[3] = (tmp_value >> 16) & 0xff;
// app_log_info(" buffer[3]%d\n", buffer[3]);
buffer[4] = (tmp_value >> 24) & 0xff;
// app_log_info(" buffer[4]%d\n", buffer[4]);
}
上位机
1.蓝牙扫描
async def handle_scan(self):
self.log_edit.appendPlainText("Started scanner")
self.devices.clear()
devices = await BleakScanner.discover()
self.devices.extend(devices)
self.devices_combobox.clear()
for i, device in enumerate(self.devices):
self.devices_combobox.insertItem(i, device.name, device)
self.log_edit.appendPlainText("Finish scanner")
2.蓝牙链接建立
async def build_client(self, device):
if self._client is not None:
await self._client.stop()
self._client = QBleakClient(device)
self._client.messageChanged.connect(self.handle_message_changed)
await self._client.start()
await self._client.start()将会开启一个线程 用来处理蓝牙接收任务
3.数据处理
async def start(self):
await self.client.connect()
await self.client.start_notify(UART_TX_CHAR_UUID, self._handle_read)//开启indicate数据接收
def _handle_read(self, _: int, data: bytearray) -> None:
print("received:", data)//数据转换
a= (data[4]<<8)|data[3];
a=(a<<8)|data[2];
a=(a<<8)|data[1];
a=a&0xffffff;
#print("sent:", a)
self.messageChanged.emit(a/1000)数据发送到主线程
def handle_message_changed(self, message):
self.log_edit.appendPlainText(f"msg: "+str(message))
q.put(message)
global i
global length
4.绘图
def update(self):
global i
#dil=[1,0,8,9]
#print(list_number)
for a in range(0,length):
if(q.qsize()<length):
pass
#self.curve.setData(list_number)
self.curve.setData(list(q.queue))//温度曲线数据
if(q.qsize()==50):
q.queue.clear()
#q.queue.clear()
i=i+1
self.curve.setPos(i, 0)
功能展示及说明
如果需要重新连接上位机与开发板,请按板子上的rest键,断开蓝牙。
XG24-EK2703A连接蓝牙后led闪烁
温度数据可视化
本活动的心得体会
1.第一次参加Funpack活动感觉非常有趣,当然也存在一些困难,在群友的集思广益和厂商的丰富资料下,最终得以完成。
2.希望视频资料,中文资料能够再多些,最好有一些零基础的开发资料。
3希望.板子上能够再增加一些屏幕,传感器的外设。