OpenHarmony設備開發(五)-超聲波傳感器
前言
在之前學習了很多新的有趣知識,本次學習了超聲波模組,利用已學的知識給遙控小車加上一個簡單的自動避障功能。
有了超聲波模組,以后可以開發更有意思的小實驗,例如簡單的無人操控小車、簡單的量尺…
準備
創建文件夾和相關的代碼文件。
其中WIFI_car保存網絡連接遙控小車的代碼,WIFI_hcsr04保存超聲波模組相關的代碼。
編輯BUILD.gn,加入相關頭文件的引用路徑(小tips:不知道路徑的頭文件可以用Ctrl+P搜索)。
static_library("WIFI_car_demo") {
sources = [
"WIFI_car.c",
"WIFI_hcsr04.c",
]
include_dirs = [
"http://utils/native/lite/include",
"http://kernel/liteos_m/components/cmsis/2.0",
"http://base/iot_hardware/peripheral/interfaces/kits",
"http://ohos_bundles/@ohos/device_soc_hisilicon/hi3861v100/sdk_liteos/include",
"http://ohos_bundles/@ohos/device_soc_hisilicon/hi3861v100/sdk_liteos/third_party/lwip_sack/include",
]
}
編輯app路徑下的BUILD.gn,使我們的遙控小車能被系統編譯加載。
格式是:"文件夾名:靜態庫名"。
超聲波模組
本次樣例學習的是HC-SR04超聲波模組。
超聲波模組一共有4個引腳,分別為Vcc、 Trig(控制端)、 Echo(接收端)、 GND;其中VCC、GND接上5V電源。
HC-SR04超聲波距離傳感器的核心是兩個超聲波傳感器。一個Trig(控制端)控制發出的超聲波信號,將電信號轉換為40 KHz超聲波脈沖。Echo(接收端)監聽發射的脈沖。
初始化
通過查詢原理圖,可以得知小車連接超聲波的GPIO口為7和8。
其中GPIO7是Trig(輸入口),GPIO8是Echo(輸出口)。
首先我們要對這兩個GPIO口進行初始化。
GPIO口初始化老三套:IO口初始化、IO口復用、IO口輸入輸出方向。
int hcsr_gpio_init(void)
{
IoTGpioInit(GPIO_Trig);
hi_io_set_func(GPIO_Trig, 0); //0是普通IO口的意思
IoTGpioSetDir(GPIO_Trig, IOT_GPIO_DIR_OUT);
IoTGpioInit(GPIO_Echo);
hi_io_set_func(GPIO_Echo, 0);
IoTGpioSetDir(GPIO_Echo, IOT_GPIO_DIR_IN);
}
編寫檢測距離函數
我們要檢測距離,首先要懂得超聲波傳感器的原理。
超聲波傳感器通過Trig高電平發送聲波,Echo接收超聲波,通過計算發送和接收到的時間差,輔以聲波的速度,得以計算出小車和障礙之間的距離。
程序設計流程:
- 發送trig高電平。
- 等待20us,trig設置為低電平。
- Echo接收到了高電平,計時高電平時間,高電平持續時間就是超聲波從發射到返回的總時間。
其中為了減少干擾,可以先發送trig高電平50us,再將trig置為低電平,Echo接收到的數據從高電平降為低電平,便可開始。
為了準確性,還可以檢測數次距離,再對其數據做平均值以及除錯(當聲波沒有被反射回來,則回波信號將在38毫秒后超時并返回低電平。因此38 ms的脈沖表示在傳感器范圍內沒有阻塞。)
本次樣例只簡單編寫了一個檢測函數:
float GetDistance(void)
{
IotGpioValue value = IOT_GPIO_VALUE0;
float distance = 0.0;
int flag = 0;
static unsigned long start_time = 0, time = 0;
//發送聲波
IoTGpioSetOutputVal(GPIO_Trig, IOT_GPIO_VALUE1);
hi_udelay(20);
IoTGpioSetOutputVal(GPIO_Trig, IOT_GPIO_VALUE0);
while (1)
{//不斷檢測聲波
IoTGpioGetInputVal(GPIO_Echo, &value);
//記錄下高電平持續時間
if (value == IOT_GPIO_VALUE1 && flag == 0)
{
start_time = hi_get_us();
flag = 1;
}
if (value == IOT_GPIO_VALUE0 && flag == 1)
{
time = hi_get_us() - start_time;
start_time = 0;
break;
}
}
distance = time * 0.034 / 2;
printf("distance is %f\r\n", distance);
return distance;
}
避障線程
簡單的避障功能需要以下幾點需求:
- 避障線程的優先級需要高于遙控的優先級,這樣當快要撞上障礙時能保證自動避障能正常運行。
- 需要設計一個信號量,信號量的作用是保證兩個或多個關鍵代碼不被并發調用,在這里是為了避免避障線程和遙控線程并發調用。本次樣例使用一個sem_d變量代替了信號量,sem_d為1時使遙控功能睡眠。
- 不斷判斷距離是否小于特定值,當小于特定值做出相對應的避障。
void hcsr04_avoid(void)
{
float distance = 0;
//io口初始化
hcsr_gpio_init();
while (1)
{
//獲取距離信息
distance=GetDistance();
if (distance < 20)
{
printf("Distance <20!!!");
sem_d = 1;
car_stop();
car_backward();
sleep(1);
car_stop();
sem_d = 0;
}
}
}
void hcsr04_demo(void)
{
osThreadAttr_t attr;
attr.name = "hcsr04_Task";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 4096;
attr.priority = 24;
if (osThreadNew((osThreadFunc_t)hcsr04_avoid, NULL, &attr) == NULL)
{
printf("[hcsr04_Task] Falied to create hcsr04_Task!\n");
}
}
網絡連接遙控小車
源代碼文件
為了加入超聲波傳感器線程,我們可以在小車IO口之后啟動超聲波線程(也可直接用SYS_RUN()啟動線程,此處因為遙控之前用不到避障,因而延遲啟動線程):
extern void hcsr04_demo(void);
hcsr04_demo();
信號量的使用:
首先創建全局變量,記得要在頭文件中extern該變量,WIFI_hcsr04.c才能使用到該變量。
當sem_d為1時,休眠遙控代碼,直至sem_d為0。
int sem_d=0;
while(1)
{
if(sem_d==1)
{
continue;
}
//遙控小車代碼...
}
頭代碼文件
此處代碼為了WIFI_hcsr04.c能調用到一些需要用到的函數和變量。
void car_stop(void);
void car_backwward(void);
extern int sem_d;
其它
效果
問題
測試了幾輪下來,發現了有以下問題。
- 在測試中偶爾會遇到棧溢出的情況,原因尚未找到。
- 超聲波傳感器的精確度不高,容易會誤判障礙物(虛無的障礙emm)。
當前解決方案:
- 加大線程的棧大小(無法從根本解決)。
- 找到棧溢出原因,設計超時機制,避免內存不斷堆積。
- 設計檢測距離的算法,對獲取的數據進行取平均值以及除去最大最小。
float distance_sum[5];
float distance = 0;
//獲取數據
for (int i = 0; i < 5; i++)
{
distance_sum[i] = GetDistance();
}
int max_id = 0;
int min_id = 0;
//記錄最大最小的數組下標
for (int i = 1; i < 5; i++)
{
if (distance_sum[i] > distance_sum[max_id])
{
max_id = i;
}
if (distance_sum[i] < distance_sum[min_id])
{
min_id = i;
}
}
for (int i = 0; i < 5; i++)
{
if (i != max_id && i != min_id)
{
distance += distance_sum[i];
}
}
distance /= 3.0;