好久不見!最近在研究 OpenHarmony,經(jīng)過一番折騰,終于打通了南向和北向開發(fā)。
如下:
自己做了一個鴻蒙開發(fā)板
搞定了 HT30 溫濕度計的驅(qū)動
通過 UDP 廣播數(shù)據(jù)
讓我們一起看看效果吧!
自制的 Neptune 開發(fā)板實時更新溫濕度到手機!
這個是我自己做的鴻蒙開發(fā)板,里面的核心是 Neptune Wi-Fi 藍牙模塊,通過 IIC 通信連接了一塊 0.96 寸的 OLED 顯示屏以及一個 HT30 溫濕度傳感器。另外,這塊開發(fā)板還包括 3 顆 LED 燈,以及相關(guān)的串口通信模塊等。
看看這塊 OLED 顯示屏下面寫的什么?
嘻嘻是的!Of course,I Still Love You!致敬一下 StarShip!當然,還有 Powered By OpenHarmony!這個必須有!
接下來,給大家介紹一下這個功能的整個實現(xiàn)過程。
設計開發(fā)板
開發(fā)板的設計參考了瑞和官方 Neptune 開發(fā)板的原理圖。電源模塊和串口通信模塊基本沒有什么改動。
原理圖貢獻給大家:
這里的溫度傳感器模塊用的是 HT30。然后,就是打樣板了:
真的很不容易,被我干翻的板子已經(jīng)堆成堆了!唉,只能怪自己腦子進水設計失誤,加上焊接技術(shù)有點弱。
設計應用程序
①關(guān)于 HT30 的驅(qū)動程序
由于官方提供的例程是 AHT20 的溫度傳感器的驅(qū)動。所以這里還需要針對 HT30 的數(shù)據(jù)手冊對驅(qū)動程序做出一些修改。
看了一下數(shù)據(jù)手冊。除了 HT30 的 I2C 的地址和 AHT20 不同,溫濕度的數(shù)據(jù)讀取模式也更加復雜,數(shù)據(jù)的位數(shù)也不同。
因此,設計 HT30 的 I2C 的通信時需要注意一下幾個方面:
溫度數(shù)據(jù)是由 16bit 的數(shù)據(jù)位和 8bit 的 CRC 位組成。濕度數(shù)據(jù)也是一樣的。相比之下,AHT20 的溫濕度數(shù)據(jù)都是 20bit,而且沒有 CRC 校驗。
HT30 可以開啟 clock stretching 模式。這個模式開啟與否和重復率的設置這個會影響到轉(zhuǎn)換時間、精度和功耗。
根據(jù)這些差異,我自己對 AHT20 的驅(qū)動做出了一些修改,形成了 HT30 的驅(qū)動。
首先,設置一下 HT30 的地址:
#define HT30_DEVICE_ADDR 0x44#define HT30_READ_ADDR ((HT30_DEVICE_ADDR《《1)|0x1)#define HT30_WRITE_ADDR ((HT30_DEVICE_ADDR《《1)|0x0)
然后,設置 MSB 和 LSB。
#define HT30_CMD_MSB 0x24 // 關(guān)閉Clock stretching#define HT30_CMD_LSB 0x16 // 低重復率
這里用的是低重復率和關(guān)閉 Clock stretching,這是為了測試的時候讓代碼更加的簡單。童鞋們需要根據(jù)自己的實際使用情況做出修改。
最后,設計開始測量和接受測量結(jié)果的代碼:
// 開始測量uint32_t HT30_StartMeasure(void)
{
uint8_t clibrateCmd[] = {HT30_CMD_MSB, HT30_CMD_LSB}; 設置MSB和LSB
return HT30_Write(clibrateCmd, sizeof(clibrateCmd));
}
// 接收測量結(jié)果,拼接轉(zhuǎn)換為標準值uint32_t HT30_GetMeasureResult(float* temp, float* humi)
{
uint32_t retval = 0, i = 0;
if (temp == NULL || humi == NULL) {
return WIFI_IOT_FAILURE;
}
// 獲得的返回數(shù)據(jù)
uint8_t buffer[HT30_STATUS_RESPONSE_MAX];
memset(&buffer, 0x0, sizeof(buffer));
for (i = 0; i 《 HT30_MAX_RETRY; i++) {
retval = HT30_Read(buffer, sizeof(buffer)); // recv status command result
if (retval == WIFI_IOT_SUCCESS) {
break;
}
printf(“HT30 device busy, retry %d/%d!
”, i, HT30_MAX_RETRY);
}
//
if (i 》= HT30_MAX_RETRY) {
printf(“HT30 device always busy!
”);
return WIFI_IOT_FAILURE;
}
// 獲得溫度數(shù)據(jù)
uint32_t tempRaw = buffer[0];
tempRaw = (tempRaw 《《 8) | buffer[1];
*temp = tempRaw / (float)HT30_RESOLUTION * 175 - 45;
// 獲得濕度數(shù)據(jù)
uint32_t humiRaw = buffer[3];
humiRaw = (humiRaw 《《 8) | buffer[4];
*humi = humiRaw / (float)HT30_RESOLUTION * 100;
printf(“humi = %04X, %f, temp= %04X, %f
”, humiRaw, *humi, tempRaw, *temp);
return WIFI_IOT_SUCCESS;
}
這里的溫度和濕度的轉(zhuǎn)化公式為:
這樣驅(qū)動程序就設計好了。
②關(guān)于 OLED 的驅(qū)動
這里用的是 0.92 寸的 OLED 屏幕,這塊屏幕在 Hi3861 的代碼中是用現(xiàn)成的驅(qū)動程序的。所以就不需要自己設計了。
分辨率為 128*64。在官方的驅(qū)動程序中,這塊 OLED 有兩種顯示模式:8*16 點陣和 6*8 的點陣。
③選用 TCP 還是 UDP 連接
Neptune 是一款 WiFi 藍牙模塊,這里就通過 WiFi 和我們的手機建立連接。連接的方式有兩種,分別是 TCP 和 UDP。
由于我們的數(shù)據(jù)并沒有敏感數(shù)據(jù),而且丟失其實也不會造成太大影響,因此這里選用了更加簡單的 UDP。
UDP 實際上是可以進行廣播的,如果有多個設備需要接受溫濕度數(shù)據(jù)的話其實不需要單獨的建立連接,所以更加適合這個場景。
最后,給大家看下最終的業(yè)務代碼:
#include “ht30.h”#include 《stdio.h》#include 《unistd.h》#include 《string.h》#include “ohos_init.h”#include “cmsis_os2.h”#include “wifiiot_gpio.h”#include “wifiiot_gpio_ex.h”#include “wifiiot_i2c.h”#include “wifiiot_gpio_w800.h”#include “oled_ssd1306.h”#include “net_params.h”#include “wifi_connecter.h”#include “net_common.h”#define LED_TASK_STACK_SIZE 512#define LED_TASK_PRIO 25enum LedState {
LED_ON = 0,
LED_OFF,
LED_SPARK,
};
enum LedState g_ledState = LED_SPARK;
static void* GpioTask(const char* arg)
{
(void)arg;
while (1) {
switch (g_ledState) {
case LED_ON:
printf(“ LED_ON!
”);
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE0);
osDelay(500);
break;
case LED_OFF:
printf(“ LED_OFF!
”);
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE1);
osDelay(500);
break;
case LED_SPARK:
printf(“ LED_SPARK!
”);
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE0);
osDelay(500);
printf(“ LED_SPARK!2
”);
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE1);
osDelay(500);
break;
default:
osDelay(500);
break;
}
}
return NULL;
}
static void GpioIsr(char* arg)
{
(void)arg;
enum LedState nextState = LED_SPARK;
printf(“ GpioIsr entry
”);
GpioSetIsrMask(WIFI_IOT_GPIO_PB_07, 0);
switch (g_ledState) {
case LED_ON:
nextState = LED_OFF;
break;
case LED_OFF:
nextState = LED_ON;
break;
case LED_SPARK:
nextState = LED_OFF;
break;
default:
break;
}
g_ledState = nextState;
}
void HT30TestTask(void* arg)
{
(void) arg;
int times = 0;
uint32_t retval = 0;
WifiDeviceConfig config = {0};
// 準備AP的配置參數(shù), 連接WiFi
strcpy(config.ssid, PARAM_HOTSPOT_SSID);
strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK);
config.securityType = PARAM_HOTSPOT_TYPE;
osDelay(10);
int netId = ConnectToHotspot(&config);
// 建立UDP連接,這里充當了UDP的客戶端
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // UDP socket
struct sockaddr_in toAddr = {0};
toAddr.sin_family = AF_INET;
toAddr.sin_port = htons(PARAM_SERVER_PORT); // 端口號,從主機字節(jié)序轉(zhuǎn)為網(wǎng)絡字節(jié)序
if (inet_pton(AF_INET, PARAM_SERVER_ADDR, &toAddr.sin_addr) 《= 0) { // 將主機IP地址從“點分十進制”字符串 轉(zhuǎn)化為 標準格式(32位整數(shù))
printf(“inet_pton failed!
”);
goto do_cleanup;
}
// I2C和OLED的初始化。
if (I2cInit(WIFI_IOT_I2C_IDX_0, 200*1000)) {
printf(“HT30 test i2c init failed
”);
}
OledInit();
OledFillScreen(0x00);
OledShowString(0, 0, “** HarmonyOS! **”, 1);
osDelay(400);
OledShowString(0, 1, “** HarmonyOS! **”, 1);
OledShowString(0, 2, “****************”, 1);
OledShowString(0, 3, “****************”, 1);
// 每秒測量一次溫濕度數(shù)據(jù)
while (1) {
retval = HT30_StartMeasure();
printf(“HT30_StartMeasure: %d
”, retval);
float temp = 0.0, humi = 0.0;
retval = HT30_GetMeasureResult(&temp, &humi);
printf(“HT30_GetMeasureResult: %d, temp = %.2f, humi = %.2f
”, retval, temp, humi);
times++;
// 將溫濕度數(shù)據(jù)顯示在OELD屏幕上
static char line1[32] = {0};
snprintf(line1, sizeof(line1), “** times = [%d]”, times);
OledShowString(0, 1, line1, 1);
static char line2[32] = {0};
snprintf(line2, sizeof(line2), “** temp : %.2f”, temp);
OledShowString(0, 2, line2, 1);
static char line3[32] = {0};
snprintf(line3, sizeof(line3), “** humi : %d”, (int)humi);
OledShowString(0, 3, line3, 1);
// 將溫濕度數(shù)據(jù)作為UDP的消息發(fā)送給手機
static char udpmessage[7] = {0};
snprintf(udpmessage, sizeof(udpmessage), “%04d%02d”, (int)(temp*100), (int)humi);
// UDP socket 是 “無連接的” ,因此每次發(fā)送都必須先指定目標主機和端口,主機可以是多播地址
retval = sendto(sockfd, udpmessage, sizeof(udpmessage), 0, (struct sockaddr *)&toAddr, sizeof(toAddr));
if (retval 《 0) {
printf(“sendto failed!
”);
goto do_cleanup;
}
printf(“send UDP message {%s} %ld done!
”, udpmessage, retval);
// 延時1秒
osDelay(500);
}
do_cleanup:
printf(“do_cleanup.。.
”);
close(sockfd);
}
void HT30Test(void)
{
GpioInit();
GpioSetDir(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_DIR_OUTPUT); // output is 0 PB08 control led
GpioSetDir(WIFI_IOT_GPIO_PB_07, WIFI_IOT_GPIO_DIR_INPUT); // input is PB09
IoSetPull(WIFI_IOT_GPIO_PB_07, WIFI_IOT_GPIO_ATTR_PULLHIGH);
GpioRegisterIsrFunc(WIFI_IOT_GPIO_PB_07, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, GpioIsr, NULL);
// 溫濕度測量線程
osThreadAttr_t attr;
attr.name = “HT30Task”;
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 4096;
attr.priority = osPriorityNormal;
if (osThreadNew(HT30TestTask, NULL, &attr) == NULL) {
printf(“[HT30Test] Failed to create HT30TestTask!
”);
}
// OLED閃爍線程
osThreadAttr_t attr2;
attr2.name = “HT30Task2”;
attr2.attr_bits = 0U;
attr2.cb_mem = NULL;
attr2.cb_size = 0U;
attr2.stack_mem = NULL;
attr2.stack_size = 4096;
attr2.priority = osPriorityNormal;
if (osThreadNew(GpioTask, NULL, &attr2) == NULL) {
printf(“[HT30Test] Failed to create HT30TestTask2!
”);
}
}
APP_FEATURE_INIT(HT30Test);
閱讀代碼時可以注意一下兩點:
在 HT30Test 函數(shù)中創(chuàng)建了 2 個線程,分別是 HT30TestTask 和 GpioTask。前者用于溫濕度測量,后者用于閃爍 LED 燈。GpioTask 沒啥用,只是為了好看而已,各位可以刪掉他沒有關(guān)系。
HT30TestTask 中,最終將溫濕度數(shù)據(jù)以 UDP 的消息發(fā)送給 UDP 服務器(也就是手機),而這個數(shù)據(jù)進行了一次粗包裝:一共是 6 位,前 4 位表示溫度,后四位表示濕度。
例如,“374267”表示 37.42℃ 和相對濕度 67%。這樣,后期鴻蒙應用程序拿到數(shù)據(jù)后就好處理了。
鴻蒙應用程序的開發(fā)
在應用程序端,這里充當了 UDP 服務器。使用 Java 的 API 進行開發(fā)的:
getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(new Runnable() {
@Override
public void run() {
try {
// 要接收的報文
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 創(chuàng)建socket并指定端口
DatagramSocket socket = new DatagramSocket(5678);
while (true) {
// 接收socket客戶端發(fā)送的數(shù)據(jù)。如果未收到會一致阻塞
socket.receive(packet);
String receiveMsg = new String(packet.getData(),0,packet.getLength());
System.out.println(“packet:” + packet.getLength());
System.out.println(“packet:” + receiveMsg);
getMainTaskDispatcher().asyncDispatch(new Runnable() {
@Override
public void run() {
long number = Long.parseLong(receiveMsg.substring(0, 6));
float temp = ((float)(number / 100)) / 100;
long humi = number % 100;
mText.setText(“溫度:” + temp + “ 濕度:” + humi);
}
});
}
// 關(guān)閉socket
// socket.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
});
這段代碼比較簡單:
需要通過 getGlobalTaskDispatcher 獲取全局任務分發(fā)器,然后通過異步方法進行網(wǎng)絡連接,否則會拋出 NetworkOnMainThreadException 異常。
獲得到 UDP 報文數(shù)據(jù)后,通過字符串裁剪和類型轉(zhuǎn)化等方式將其轉(zhuǎn)換為浮點型或整型,然后顯示在 mText 組件上。
總結(jié)
我自己做的開發(fā)板成本是很低的,溫濕度傳感器、OLED 屏幕和 Neptune 模組都是以很低的價格在網(wǎng)上購買的,總成本可能不超過 30 元。這個開發(fā)板很小,可以握持在手中隨身攜帶。
不過,在軟件方面,上面的例子充其量算一個 Demo,實際上還有很多工作需要做:
①這里是直接通過 UDP 將開發(fā)板和手機連接在一起的,其中的 IP 地址也是硬寫入的。所以如果離開 WiFi 環(huán)境,那么手機將不會接收到溫濕度信息。
如果開發(fā)者希望遠程獲得溫濕度,那么需要服務器進行中轉(zhuǎn)。這個中轉(zhuǎn)技術(shù)也不復雜,大家可以思考一下如何實現(xiàn)。
②在應用端,這里的溫濕度是寫在 MainAbilitySlice 中的。其實這種方式也是有待改進的。
至少需要將相關(guān)的業(yè)務代碼寫到服務中,這樣的話,我們還可以實現(xiàn)高溫預警等功能。如果將其以小卡片的形式顯示在桌面就更好啦!同樣,大家可以思考一下如何實現(xiàn)。
③這塊開發(fā)板可以進一步微型化,請大家期待下一個版本!
④在獲取溫濕度數(shù)據(jù)的時候,我們用了低重復率和關(guān)閉 clock stretching 功能。
其實,真正實用化的時候,根據(jù)場景的不同大家需要考慮如何配置一下,提高精度的同時降低功耗!
代碼:
https://gitee.com/dongyu1009/neptune-harmony-os-wi-fi-link
視頻演示:
https://harmonyos.51cto.com/show/8232
在這里,為大家貢獻了實例代碼和開發(fā)板的原理圖!如果希望進一步研究,點擊“閱讀原文”來一起探究竟吧!責任編輯:haq
-
智能手機
+關(guān)注
關(guān)注
66文章
18543瀏覽量
180792 -
OLED
+關(guān)注
關(guān)注
119文章
6219瀏覽量
224619 -
鴻蒙系統(tǒng)
+關(guān)注
關(guān)注
183文章
2638瀏覽量
66593
原文標題:成本30元,鴻蒙手機知曉家中情況!
文章出處:【微信號:gh_834c4b3d87fe,微信公眾號:OpenHarmony技術(shù)社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論