好久不見(jiàn)!最近在研究 OpenHarmony,經(jīng)過(guò)一番折騰,終于打通了南向和北向開(kāi)發(fā)。
如下:
自己做了一個(gè)鴻蒙開(kāi)發(fā)板
搞定了 HT30 溫濕度計(jì)的驅(qū)動(dòng)
通過(guò) UDP 廣播數(shù)據(jù)
讓我們一起看看效果吧!
自制的 Neptune 開(kāi)發(fā)板實(shí)時(shí)更新溫濕度到手機(jī)!
這個(gè)是我自己做的鴻蒙開(kāi)發(fā)板,里面的核心是 Neptune Wi-Fi 藍(lán)牙模塊,通過(guò) IIC 通信連接了一塊 0.96 寸的 OLED 顯示屏以及一個(gè) HT30 溫濕度傳感器。另外,這塊開(kāi)發(fā)板還包括 3 顆 LED 燈,以及相關(guān)的串口通信模塊等。
看看這塊 OLED 顯示屏下面寫(xiě)的什么?
嘻嘻是的!Of course,I Still Love You!致敬一下 StarShip!當(dāng)然,還有 Powered By OpenHarmony!這個(gè)必須有!
接下來(lái),給大家介紹一下這個(gè)功能的整個(gè)實(shí)現(xiàn)過(guò)程。
設(shè)計(jì)開(kāi)發(fā)板
開(kāi)發(fā)板的設(shè)計(jì)參考了瑞和官方 Neptune 開(kāi)發(fā)板的原理圖。電源模塊和串口通信模塊基本沒(méi)有什么改動(dòng)。
原理圖貢獻(xiàn)給大家:

這里的溫度傳感器模塊用的是 HT30。然后,就是打樣板了:

真的很不容易,被我干翻的板子已經(jīng)堆成堆了!唉,只能怪自己腦子進(jìn)水設(shè)計(jì)失誤,加上焊接技術(shù)有點(diǎn)弱。
設(shè)計(jì)應(yīng)用程序
①關(guān)于 HT30 的驅(qū)動(dòng)程序
由于官方提供的例程是 AHT20 的溫度傳感器的驅(qū)動(dòng)。所以這里還需要針對(duì) HT30 的數(shù)據(jù)手冊(cè)對(duì)驅(qū)動(dòng)程序做出一些修改。
看了一下數(shù)據(jù)手冊(cè)。除了 HT30 的 I2C 的地址和 AHT20 不同,溫濕度的數(shù)據(jù)讀取模式也更加復(fù)雜,數(shù)據(jù)的位數(shù)也不同。
因此,設(shè)計(jì) HT30 的 I2C 的通信時(shí)需要注意一下幾個(gè)方面:
溫度數(shù)據(jù)是由 16bit 的數(shù)據(jù)位和 8bit 的 CRC 位組成。濕度數(shù)據(jù)也是一樣的。相比之下,AHT20 的溫濕度數(shù)據(jù)都是 20bit,而且沒(méi)有 CRC 校驗(yàn)。
HT30 可以開(kāi)啟 clock stretching 模式。這個(gè)模式開(kāi)啟與否和重復(fù)率的設(shè)置這個(gè)會(huì)影響到轉(zhuǎn)換時(shí)間、精度和功耗。
根據(jù)這些差異,我自己對(duì) AHT20 的驅(qū)動(dòng)做出了一些修改,形成了 HT30 的驅(qū)動(dòng)。
首先,設(shè)置一下 HT30 的地址:
#define HT30_DEVICE_ADDR 0x44#define HT30_READ_ADDR ((HT30_DEVICE_ADDR《《1)|0x1)#define HT30_WRITE_ADDR ((HT30_DEVICE_ADDR《《1)|0x0)
然后,設(shè)置 MSB 和 LSB。
#define HT30_CMD_MSB 0x24 // 關(guān)閉Clock stretching#define HT30_CMD_LSB 0x16 // 低重復(fù)率
這里用的是低重復(fù)率和關(guān)閉 Clock stretching,這是為了測(cè)試的時(shí)候讓代碼更加的簡(jiǎn)單。童鞋們需要根據(jù)自己的實(shí)際使用情況做出修改。
最后,設(shè)計(jì)開(kāi)始測(cè)量和接受測(cè)量結(jié)果的代碼:
// 開(kāi)始測(cè)量uint32_t HT30_StartMeasure(void)
{
uint8_t clibrateCmd[] = {HT30_CMD_MSB, HT30_CMD_LSB}; 設(shè)置MSB和LSB
return HT30_Write(clibrateCmd, sizeof(clibrateCmd));
}
// 接收測(cè)量結(jié)果,拼接轉(zhuǎn)換為標(biāo)準(zhǔn)值uint32_t HT30_GetMeasureResult(float* temp, float* humi)
{
uint32_t retval = 0, i = 0;
if (temp == NULL || humi == NULL) {
return WIFI_IOT_FAILURE;
}
// 獲得的返回?cái)?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ū)動(dòng)程序就設(shè)計(jì)好了。
②關(guān)于 OLED 的驅(qū)動(dòng)
這里用的是 0.92 寸的 OLED 屏幕,這塊屏幕在 Hi3861 的代碼中是用現(xiàn)成的驅(qū)動(dòng)程序的。所以就不需要自己設(shè)計(jì)了。
分辨率為 128*64。在官方的驅(qū)動(dòng)程序中,這塊 OLED 有兩種顯示模式:8*16 點(diǎn)陣和 6*8 的點(diǎn)陣。
③選用 TCP 還是 UDP 連接
Neptune 是一款 WiFi 藍(lán)牙模塊,這里就通過(guò) WiFi 和我們的手機(jī)建立連接。連接的方式有兩種,分別是 TCP 和 UDP。
由于我們的數(shù)據(jù)并沒(méi)有敏感數(shù)據(jù),而且丟失其實(shí)也不會(huì)造成太大影響,因此這里選用了更加簡(jiǎn)單的 UDP。
UDP 實(shí)際上是可以進(jìn)行廣播的,如果有多個(gè)設(shè)備需要接受溫濕度數(shù)據(jù)的話其實(shí)不需要單獨(dú)的建立連接,所以更加適合這個(gè)場(chǎng)景。
最后,給大家看下最終的業(yè)務(wù)代碼:
#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};
// 準(zhǔn)備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連接,這里充當(dāng)了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); // 端口號(hào),從主機(jī)字節(jié)序轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
if (inet_pton(AF_INET, PARAM_SERVER_ADDR, &toAddr.sin_addr) 《= 0) { // 將主機(jī)IP地址從“點(diǎn)分十進(jìn)制”字符串 轉(zhuǎn)化為 標(biāo)準(zhǔ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);
// 每秒測(cè)量一次溫濕度數(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ā)送給手機(jī)
static char udpmessage[7] = {0};
snprintf(udpmessage, sizeof(udpmessage), “%04d%02d”, (int)(temp*100), (int)humi);
// UDP socket 是 “無(wú)連接的” ,因此每次發(fā)送都必須先指定目標(biāo)主機(jī)和端口,主機(jī)可以是多播地址
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);
// 延時(shí)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);
// 溫濕度測(cè)量線程
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);
閱讀代碼時(shí)可以注意一下兩點(diǎn):
在 HT30Test 函數(shù)中創(chuàng)建了 2 個(gè)線程,分別是 HT30TestTask 和 GpioTask。前者用于溫濕度測(cè)量,后者用于閃爍 LED 燈。GpioTask 沒(méi)啥用,只是為了好看而已,各位可以刪掉他沒(méi)有關(guān)系。
HT30TestTask 中,最終將溫濕度數(shù)據(jù)以 UDP 的消息發(fā)送給 UDP 服務(wù)器(也就是手機(jī)),而這個(gè)數(shù)據(jù)進(jìn)行了一次粗包裝:一共是 6 位,前 4 位表示溫度,后四位表示濕度。
例如,“374267”表示 37.42℃ 和相對(duì)濕度 67%。這樣,后期鴻蒙應(yīng)用程序拿到數(shù)據(jù)后就好處理了。
鴻蒙應(yīng)用程序的開(kāi)發(fā)
在應(yīng)用程序端,這里充當(dāng)了 UDP 服務(wù)器。使用 Java 的 API 進(jìn)行開(kāi)發(fā)的:
getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(new Runnable() {
@Override
public void run() {
try {
// 要接收的報(bào)文
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ù)。如果未收到會(huì)一致阻塞
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();
}
}
});
這段代碼比較簡(jiǎn)單:
需要通過(guò) getGlobalTaskDispatcher 獲取全局任務(wù)分發(fā)器,然后通過(guò)異步方法進(jìn)行網(wǎng)絡(luò)連接,否則會(huì)拋出 NetworkOnMainThreadException 異常。
獲得到 UDP 報(bào)文數(shù)據(jù)后,通過(guò)字符串裁剪和類(lèi)型轉(zhuǎn)化等方式將其轉(zhuǎn)換為浮點(diǎn)型或整型,然后顯示在 mText 組件上。
總結(jié)
我自己做的開(kāi)發(fā)板成本是很低的,溫濕度傳感器、OLED 屏幕和 Neptune 模組都是以很低的價(jià)格在網(wǎng)上購(gòu)買(mǎi)的,總成本可能不超過(guò) 30 元。這個(gè)開(kāi)發(fā)板很小,可以握持在手中隨身攜帶。
不過(guò),在軟件方面,上面的例子充其量算一個(gè) Demo,實(shí)際上還有很多工作需要做:
①這里是直接通過(guò) UDP 將開(kāi)發(fā)板和手機(jī)連接在一起的,其中的 IP 地址也是硬寫(xiě)入的。所以如果離開(kāi) WiFi 環(huán)境,那么手機(jī)將不會(huì)接收到溫濕度信息。
如果開(kāi)發(fā)者希望遠(yuǎn)程獲得溫濕度,那么需要服務(wù)器進(jìn)行中轉(zhuǎn)。這個(gè)中轉(zhuǎn)技術(shù)也不復(fù)雜,大家可以思考一下如何實(shí)現(xiàn)。
②在應(yīng)用端,這里的溫濕度是寫(xiě)在 MainAbilitySlice 中的。其實(shí)這種方式也是有待改進(jìn)的。
至少需要將相關(guān)的業(yè)務(wù)代碼寫(xiě)到服務(wù)中,這樣的話,我們還可以實(shí)現(xiàn)高溫預(yù)警等功能。如果將其以小卡片的形式顯示在桌面就更好啦!同樣,大家可以思考一下如何實(shí)現(xiàn)。
③這塊開(kāi)發(fā)板可以進(jìn)一步微型化,請(qǐng)大家期待下一個(gè)版本!
④在獲取溫濕度數(shù)據(jù)的時(shí)候,我們用了低重復(fù)率和關(guān)閉 clock stretching 功能。
其實(shí),真正實(shí)用化的時(shí)候,根據(jù)場(chǎng)景的不同大家需要考慮如何配置一下,提高精度的同時(shí)降低功耗!
代碼:
https://gitee.com/dongyu1009/neptune-harmony-os-wi-fi-link
視頻演示:
https://harmonyos.51cto.com/show/8232
在這里,為大家貢獻(xiàn)了實(shí)例代碼和開(kāi)發(fā)板的原理圖!如果希望進(jìn)一步研究,點(diǎn)擊“閱讀原文”來(lái)一起探究竟吧!責(zé)任編輯:haq
-
智能手機(jī)
+關(guān)注
關(guān)注
66文章
18683瀏覽量
185812 -
OLED
+關(guān)注
關(guān)注
121文章
6346瀏覽量
233102 -
鴻蒙系統(tǒng)
+關(guān)注
關(guān)注
183文章
2642瀏覽量
69600
原文標(biāo)題:成本30元,鴻蒙手機(jī)知曉家中情況!
文章出處:【微信號(hào):gh_834c4b3d87fe,微信公眾號(hào):OpenHarmony技術(shù)社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
冷庫(kù)溫濕度監(jiān)控系統(tǒng)物聯(lián)網(wǎng)解決方案
全無(wú)線物聯(lián)網(wǎng)庫(kù)房溫濕度自動(dòng)監(jiān)控系統(tǒng)設(shè)計(jì)與實(shí)施解決方案
【瑞薩RA6E2地奇星開(kāi)發(fā)板試用】DHT11 測(cè)量溫濕度
溫濕度傳感器HTU31D在溫濕度控器中的應(yīng)用優(yōu)勢(shì)
【上海晶珩睿莓1開(kāi)發(fā)板試用體驗(yàn)】Home Assistant 物聯(lián)網(wǎng)溫濕度計(jì)
一款適用于粉塵、易結(jié)露等惡劣環(huán)境溫濕度監(jiān)控中的溫濕度傳感芯片
常見(jiàn)的溫濕度傳感器類(lèi)型?
溫濕度變送器功能有哪些?一文詳細(xì)解析
智能倉(cāng)儲(chǔ):溫濕度監(jiān)控方案應(yīng)用
用樹(shù)莓派RP2350 DIY 桌面動(dòng)態(tài)溫濕度計(jì)
LoRa無(wú)線技術(shù)的溫濕度監(jiān)測(cè)預(yù)警系統(tǒng)
溫濕度傳感器:核心原理與跨領(lǐng)域應(yīng)用解析
如何操作電子溫濕度記錄儀
通過(guò)手機(jī)端遠(yuǎn)程監(jiān)控冷庫(kù)溫濕度并進(jìn)行遠(yuǎn)程控制
自制鴻蒙Neptune開(kāi)發(fā)板實(shí)時(shí)更新溫濕度到手機(jī)
評(píng)論