好久不見!最近在研究 OpenHarmony,經過一番折騰,終于打通了南向和北向開發。
如下:
自己做了一個鴻蒙開發板
搞定了 HT30 溫濕度計的驅動
通過 UDP 廣播數據
讓我們一起看看效果吧!
自制的 Neptune 開發板實時更新溫濕度到手機!
這個是我自己做的鴻蒙開發板,里面的核心是 Neptune Wi-Fi 藍牙模塊,通過 IIC 通信連接了一塊 0.96 寸的 OLED 顯示屏以及一個 HT30 溫濕度傳感器。另外,這塊開發板還包括 3 顆 LED 燈,以及相關的串口通信模塊等。
看看這塊 OLED 顯示屏下面寫的什么?
嘻嘻是的!Of course,I Still Love You!致敬一下 StarShip!當然,還有 Powered By OpenHarmony!這個必須有!
接下來,給大家介紹一下這個功能的整個實現過程。
設計開發板
開發板的設計參考了瑞和官方 Neptune 開發板的原理圖。電源模塊和串口通信模塊基本沒有什么改動。
原理圖貢獻給大家:
這里的溫度傳感器模塊用的是 HT30。然后,就是打樣板了:
真的很不容易,被我干翻的板子已經堆成堆了!唉,只能怪自己腦子進水設計失誤,加上焊接技術有點弱。
設計應用程序
①關于 HT30 的驅動程序
由于官方提供的例程是 AHT20 的溫度傳感器的驅動。所以這里還需要針對 HT30 的數據手冊對驅動程序做出一些修改。
看了一下數據手冊。除了 HT30 的 I2C 的地址和 AHT20 不同,溫濕度的數據讀取模式也更加復雜,數據的位數也不同。
因此,設計 HT30 的 I2C 的通信時需要注意一下幾個方面:
溫度數據是由 16bit 的數據位和 8bit 的 CRC 位組成。濕度數據也是一樣的。相比之下,AHT20 的溫濕度數據都是 20bit,而且沒有 CRC 校驗。
HT30 可以開啟 clock stretching 模式。這個模式開啟與否和重復率的設置這個會影響到轉換時間、精度和功耗。
根據這些差異,我自己對 AHT20 的驅動做出了一些修改,形成了 HT30 的驅動。
首先,設置一下 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 // 關閉Clock stretching#define HT30_CMD_LSB 0x16 // 低重復率
這里用的是低重復率和關閉 Clock stretching,這是為了測試的時候讓代碼更加的簡單。童鞋們需要根據自己的實際使用情況做出修改。
最后,設計開始測量和接受測量結果的代碼:
// 開始測量uint32_t HT30_StartMeasure(void)
{
uint8_t clibrateCmd[] = {HT30_CMD_MSB, HT30_CMD_LSB}; 設置MSB和LSB
return HT30_Write(clibrateCmd, sizeof(clibrateCmd));
}
// 接收測量結果,拼接轉換為標準值uint32_t HT30_GetMeasureResult(float* temp, float* humi)
{
uint32_t retval = 0, i = 0;
if (temp == NULL || humi == NULL) {
return WIFI_IOT_FAILURE;
}
// 獲得的返回數據
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;
}
// 獲得溫度數據
uint32_t tempRaw = buffer[0];
tempRaw = (tempRaw 《《 8) | buffer[1];
*temp = tempRaw / (float)HT30_RESOLUTION * 175 - 45;
// 獲得濕度數據
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;
}
這里的溫度和濕度的轉化公式為:
這樣驅動程序就設計好了。
②關于 OLED 的驅動
這里用的是 0.92 寸的 OLED 屏幕,這塊屏幕在 Hi3861 的代碼中是用現成的驅動程序的。所以就不需要自己設計了。
分辨率為 128*64。在官方的驅動程序中,這塊 OLED 有兩種顯示模式:8*16 點陣和 6*8 的點陣。
③選用 TCP 還是 UDP 連接
Neptune 是一款 WiFi 藍牙模塊,這里就通過 WiFi 和我們的手機建立連接。連接的方式有兩種,分別是 TCP 和 UDP。
由于我們的數據并沒有敏感數據,而且丟失其實也不會造成太大影響,因此這里選用了更加簡單的 UDP。
UDP 實際上是可以進行廣播的,如果有多個設備需要接受溫濕度數據的話其實不需要單獨的建立連接,所以更加適合這個場景。
最后,給大家看下最終的業務代碼:
#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的配置參數, 連接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); // 端口號,從主機字節序轉為網絡字節序
if (inet_pton(AF_INET, PARAM_SERVER_ADDR, &toAddr.sin_addr) 《= 0) { // 將主機IP地址從“點分十進制”字符串 轉化為 標準格式(32位整數)
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);
// 每秒測量一次溫濕度數據
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++;
// 將溫濕度數據顯示在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);
// 將溫濕度數據作為UDP的消息發送給手機
static char udpmessage[7] = {0};
snprintf(udpmessage, sizeof(udpmessage), “%04d%02d”, (int)(temp*100), (int)humi);
// UDP socket 是 “無連接的” ,因此每次發送都必須先指定目標主機和端口,主機可以是多播地址
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 函數中創建了 2 個線程,分別是 HT30TestTask 和 GpioTask。前者用于溫濕度測量,后者用于閃爍 LED 燈。GpioTask 沒啥用,只是為了好看而已,各位可以刪掉他沒有關系。
HT30TestTask 中,最終將溫濕度數據以 UDP 的消息發送給 UDP 服務器(也就是手機),而這個數據進行了一次粗包裝:一共是 6 位,前 4 位表示溫度,后四位表示濕度。
例如,“374267”表示 37.42℃ 和相對濕度 67%。這樣,后期鴻蒙應用程序拿到數據后就好處理了。
鴻蒙應用程序的開發
在應用程序端,這里充當了 UDP 服務器。使用 Java 的 API 進行開發的:
getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(new Runnable() {
@Override
public void run() {
try {
// 要接收的報文
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 創建socket并指定端口
DatagramSocket socket = new DatagramSocket(5678);
while (true) {
// 接收socket客戶端發送的數據。如果未收到會一致阻塞
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);
}
});
}
// 關閉socket
// socket.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
});
這段代碼比較簡單:
需要通過 getGlobalTaskDispatcher 獲取全局任務分發器,然后通過異步方法進行網絡連接,否則會拋出 NetworkOnMainThreadException 異常。
獲得到 UDP 報文數據后,通過字符串裁剪和類型轉化等方式將其轉換為浮點型或整型,然后顯示在 mText 組件上。
總結
我自己做的開發板成本是很低的,溫濕度傳感器、OLED 屏幕和 Neptune 模組都是以很低的價格在網上購買的,總成本可能不超過 30 元。這個開發板很小,可以握持在手中隨身攜帶。
不過,在軟件方面,上面的例子充其量算一個 Demo,實際上還有很多工作需要做:
①這里是直接通過 UDP 將開發板和手機連接在一起的,其中的 IP 地址也是硬寫入的。所以如果離開 WiFi 環境,那么手機將不會接收到溫濕度信息。
如果開發者希望遠程獲得溫濕度,那么需要服務器進行中轉。這個中轉技術也不復雜,大家可以思考一下如何實現。
②在應用端,這里的溫濕度是寫在 MainAbilitySlice 中的。其實這種方式也是有待改進的。
至少需要將相關的業務代碼寫到服務中,這樣的話,我們還可以實現高溫預警等功能。如果將其以小卡片的形式顯示在桌面就更好啦!同樣,大家可以思考一下如何實現。
③這塊開發板可以進一步微型化,請大家期待下一個版本!
④在獲取溫濕度數據的時候,我們用了低重復率和關閉 clock stretching 功能。
其實,真正實用化的時候,根據場景的不同大家需要考慮如何配置一下,提高精度的同時降低功耗!
代碼:
https://gitee.com/dongyu1009/neptune-harmony-os-wi-fi-link
視頻演示:
https://harmonyos.51cto.com/show/8232
在這里,為大家貢獻了實例代碼和開發板的原理圖!如果希望進一步研究,點擊“閱讀原文”來一起探究竟吧!責任編輯:haq
-
智能手機
+關注
關注
66文章
18516瀏覽量
180683 -
OLED
+關注
關注
119文章
6214瀏覽量
224546 -
鴻蒙系統
+關注
關注
183文章
2637瀏覽量
66539
原文標題:成本30元,鴻蒙手機知曉家中情況!
文章出處:【微信號:gh_834c4b3d87fe,微信公眾號:OpenHarmony技術社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論