Hi3861开发板使用与第六届全国大学生嵌入式竞赛海思赛道

Eric Zhang Lv3

参考资料

海思技术论坛注册:https://developer.hisilicon.com/forum/all

嵌入式物联网学习资料总入口:https://developer.hisilicon.com/postDetail?tid=0206112614830760003

海思技术论坛https://developer.hisilicon.com/forum/all

2023海思嵌入式大赛学习文档仓库Gitee:https://gitee.com/HiSpark/HiSpark_NICU2023

HUAWEI_Cloud与Android Studio开发联动:https://blog.csdn.net/weixin_43351158/article/details/124476656

工程结构分析

工程目录

1
2
3
4
5
6
7
8
9
.
└── applications
└── sample
└── wifi-iot
└── app
│── my_first_app //业务my_first_app文件夹
│ │── hello_world.c //业务代码
│ └── BUILD.gn //编译脚本
└── BUILD.gn

hello_world.c文件代码结构

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include "ohos_init.h"
#include "ohos_types.h"

void HelloWorld(void)
{
printf("___________>>>>>>>>>>>>>>>>>>>> [DEMO] Hello world.\n");
}
SYS_RUN(HelloWorld);

SYS_RUN定义在ohos_init.h文件中,是HarmonyOS启动恢复模块接口启动业务

用于将业务构建成静态库的BUILD.gn文件代码结构

路径:./applications/sample/wifi-iot/app/my_first_app/BUILD.gn(与下面的模块编译build.gn不一样,请格外注意)

1
2
3
4
5
6
7
8
9
10
11
//目标业务
static_library("myapp") {
//源文件路径
sources = [
"hello_world.c"
]
//头文件路径
include_dirs = [
"//utils/native/lite/include"
]
}

指定需参与构建的特性模块的BUILD.gn文件代码结构

路径:./applications/sample/wifi-iot/app/BUILD.gn

1
2
3
4
5
6
7
8
9
import("//build/lite/config/component/lite_component.gni")


lite_component("app") {
features = [
"my_first_app:myapp",
]

}

my_first_app是相对路径,指向./applications/sample/wifi-iot/app/my_first_app/BUILD.gn。

myapp是目标,指向./applications/sample/wifi-iot/app/my_first_app/BUILD.gn中的static_library("myapp")。

Hi3861其他代码结构

Hi3861平台配置文件

hi3861平台配置文件位于:vendor_pegasus.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
{
"product_name": "wifiiot_hispark_pegasus",
"ohos_version": "OpenHarmony 1.0",
"device_company": "hisilicon",
"board": "hispark_pegasus",
"kernel_type": "liteos_m",
"kernel_version": "",
"subsystems": [
{
//应用子系统,存放了hi3681编写的应用程序代码
"subsystem": "applications",
"components": [
{ "component": "wifi_iot_sample_app", "features":[] }
]
},
{
//硬件驱动子系统,存放了hi3681的硬件驱动代码
"subsystem": "iot_hardware",
"components": [
{ "component": "iot_controller", "features":[] }
]
},
{
//
"subsystem": "hiviewdfx",
"components": [
{ "component": "hilog_lite", "features":[] }
]
},
{
"subsystem": "distributed_schedule",
"components": [
{ "component": "samgr_lite", "features":[] }
]
},
{
"subsystem": "security",
"components": [
{ "component": "hichainsdk", "features":[] },
{ "component": "deviceauth_lite", "features":[] },
{ "component": "huks", "features":
[
"disable_huks_binary = false",
"disable_authenticate = false",
"huks_use_lite_storage = true",
"huks_use_hardware_root_key = true",
"huks_config_file = \"hks_config_lite.h\"",
"huks_mbedtls_path = \"//device/hisilicon/hispark_pegasus/sdk_liteos/third_party/mbedtls/include/\""
]
}
]
},
{
"subsystem": "startup",
"components": [
{ "component": "bootstrap_lite", "features":[] },
{ "component": "syspara_lite", "features":
[
"enable_ohos_startup_syspara_lite_use_thirdparty_mbedtls = false"
]
}
]
},
{
"subsystem": "communication",
"components": [
{ "component": "wifi_lite", "features":[] },
{ "component": "softbus_lite", "features":[] },
{ "component": "wifi_aware", "features":[]}
]
},
{
"subsystem": "update",
"components": [
{ "component": "ota_lite", "features":[] }
]
},
{
"subsystem": "iot",
"components": [
{ "component": "iot_link", "features":[] }
]
},
{
"subsystem": "utils",
"components": [
{ "component": "file", "features":[] },
{ "component": "kv_store", "features":[] },
{ "component": "os_dump", "features":[] }
]
},
{
"subsystem": "vendor",
"components": [
{ "component": "hi3861_sdk", "target": "//device/hisilicon/hispark_pegasus/sdk_liteos:wifiiot_sdk", "features":[] }
]
}
],
"third_party_dir": "//device/hisilicon/hispark_pegasus/sdk_liteos/third_party",
"product_adapter_dir": "//vendor/hisilicon/hispark_pegasus/hals"
}

hi3861用的是liteos-m内核,但是目前hi3681的liteos-m被芯片rom化了,固化在芯片内部了。所以在harmonyOS代码是找不到hi3861的内核部分。

Hi3861启动流程

第一个入口函数

路径:._pegasus_liteos_app_main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
hi_void app_main(hi_void)
{
#ifdef CONFIG_FACTORY_TEST_MODE
printf("factory test mode!\r\n");
#endif

const hi_char* sdk_ver = hi_get_sdk_version();
printf("sdk ver:%s\r\n", sdk_ver);

hi_flash_partition_table *ptable = HI_NULL;

peripheral_init();
peripheral_init_no_sleep();

#ifndef CONFIG_FACTORY_TEST_MODE
hi_lpc_register_wakeup_entry(peripheral_init);
#endif

hi_u32 ret = hi_factory_nv_init(HI_FNV_DEFAULT_ADDR, HI_NV_DEFAULT_TOTAL_SIZE, HI_NV_DEFAULT_BLOCK_SIZE);
if (ret != HI_ERR_SUCCESS) {
printf("factory nv init fail\r\n");
}

/* partion table should init after factory nv init. */
ret = hi_flash_partition_init();
if (ret != HI_ERR_SUCCESS) {
printf("flash partition table init fail:0x%x \r\n", ret);
}
ptable = hi_get_partition_table();

ret = hi_nv_init(ptable->table[HI_FLASH_PARTITON_NORMAL_NV].addr, ptable->table[HI_FLASH_PARTITON_NORMAL_NV].size,
HI_NV_DEFAULT_BLOCK_SIZE);
if (ret != HI_ERR_SUCCESS) {
printf("nv init fail\r\n");
}

#ifndef CONFIG_FACTORY_TEST_MODE
hi_upg_init();
#endif

/* if not use file system, there is no need init it */
hi_fs_init();

(hi_void)hi_event_init(APP_INIT_EVENT_NUM, HI_NULL);
hi_sal_init();
/* 此处设为TRUE后中断中看门狗复位会显示复位时PC值,但有复位不完全风险,量产版本请务必设为FALSE */
hi_syserr_watchdog_debug(HI_FALSE);
/* 默认记录宕机信息到FLASH,根据应用场景,可不记录,避免频繁异常宕机情况损耗FLASH寿命 */
hi_syserr_record_crash_info(HI_TRUE);

hi_lpc_init();
hi_lpc_register_hw_handler(config_before_sleep, config_after_sleep);

#if defined(CONFIG_AT_COMMAND) || defined(CONFIG_FACTORY_TEST_MODE)
ret = hi_at_init();
if (ret == HI_ERR_SUCCESS) {
hi_at_sys_cmd_register();
}
#endif

/* 如果不需要使用Histudio查看WIFI驱动运行日志等,无需初始化diag */
/* if not use histudio for diagnostic, diag initialization is unnecessary */
/* Shell and Diag use the same uart port, only one of them can be selected */
#ifndef CONFIG_FACTORY_TEST_MODE

#ifndef ENABLE_SHELL_DEBUG
#ifdef CONFIG_DIAG_SUPPORT
(hi_void)hi_diag_init();
#endif
#else
(hi_void)hi_shell_init();
#endif

tcpip_init(NULL, NULL);
#endif

ret = hi_wifi_init(APP_INIT_VAP_NUM, APP_INIT_USR_NUM);
if (ret != HISI_OK) {
printf("wifi init failed!\n");
} else {
printf("wifi init success!\n");
}
app_demo_task_release_mem(); /* 释放系统栈内存所使用任务 */

#ifndef CONFIG_FACTORY_TEST_MODE
app_demo_upg_init();
#ifdef CONFIG_HILINK
ret = hilink_main();
if (ret != HISI_OK) {
printf("hilink init failed!\n");
} else {
printf("hilink init success!\n");
}
#endif
#endif
OHOS_Main();
}

最后一行调用OHOS_Main()代码:

路径:._pegasus_liteos_app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void OHOS_Main()
{
#if defined(CONFIG_AT_COMMAND) || defined(CONFIG_FACTORY_TEST_MODE)
hi_u32 ret;
ret = hi_at_init();
if (ret == HI_ERR_SUCCESS) {
hi_u32 ret2 = hi_at_register_cmd(G_OHOS_AT_FUNC_TBL, OHOS_AT_FUNC_NUM);
if (ret2 != HI_ERR_SUCCESS) {
printf("Register ohos failed!\n");
}
}
#endif
OHOS_SystemInit();
}

OHOS_SystemInit函数进行鸿蒙系统的初始化。

路径:D:_test_lite_init.c

1
2
3
4
5
6
7
8
9
10
void OHOS_SystemInit(void)
{
MODULE_INIT(bsp);
MODULE_INIT(device);
MODULE_INIT(core);
SYS_INIT(service);
SYS_INIT(feature);
MODULE_INIT(run);
SAMGR_Bootstrap();
}

主要是初始化了一些相关模块、系统,包括有bsp、device(设备)。其中最终的是MODULE_INIT(run);负责调用所有run段的代码:前面application中使用SYS_RUN()宏设置的函数名

一、GPIO的使用

API说明

1. IoTGpioInit()

作用:初始化指定的IO口 示例:

1
2
// LED3的GPIO初始化 GPIO initialization of LED3
IoTGpioInit(IOT_IO_NAME_GPIO_9);
依赖库:src_hardware_gpio.h, 说明如下
1
2
3
4
5
6
7
8
9
10
/**
* @brief Initializes a GPIO device.
*
* @param id Indicates the GPIO pin number.
* @return Returns {@link IOT_SUCCESS} if the GPIO device is initialized;
* returns {@link IOT_FAILURE} otherwise. For details about other return values, see the chip description.
* @since 2.2
* @version 2.2
*/
unsigned int IoTGpioInit(unsigned int id);

2. IoSetFunc()

作用:配置指定IO的复用功能 示例:

1
2
// 设置GPIO9的管脚复用关系为GPIO Set the pin reuse relationship of GPIO9 to GPIO
IoSetFunc(IOT_IO_NAME_GPIO_9, IOT_IO_FUNC_GPIO_9_GPIO);
依赖库:src-iot_demo_iot_gpio_ex.c 说明如下:
1
2
3
4
5
6
7
unsigned int IoSetFunc(unsigned int id, unsigned char val)
{
if (id >= HI_GPIO_IDX_MAX) {
return IOT_FAILURE;
}
return hi_io_set_func((hi_io_name)id, val);
}
### 3. IoTGpioSetDir() 作用:设置指定IO的管脚方向 示例:
1
2
// GPIO方向设置为输出 GPIO direction set to output
IoTGpioSetDir(IOT_IO_NAME_GPIO_9, IOT_GPIO_DIR_OUT);
依赖库:src_hardware_gpio.h, 说明如下
1
2
3
4
5
6
7
8
9
10
11
/**
* @brief Sets the direction for a GPIO pin.
*
* @param id Indicates the GPIO pin number.
* @param dir Indicates the GPIO input/output direction.
* @return Returns {@link IOT_SUCCESS} if the direction is set;
* returns {@link IOT_FAILURE} otherwise. For details about other return values, see the chip description.
* @since 2.2
* @version 2.2
*/
unsigned int IoTGpioSetDir(unsigned int id, IotGpioDir dir);
### 4. IoTGpioSetOutputVal() 作用:设置指定IO的输出电平 示例:
1
2
3
4
5
6
  /* 设置GPIO09输出低电平
* Set GPIO09 output high level to turn on red traffic light LED 3
* VALUE0: low level
* VALUE1: high level
*/
IoTGpioSetOutputVal(IOT_IO_NAME_GPIO_9, IOT_GPIO_VALUE0);

二、GPIO使用进阶

API说明

1. IoSetPull()

作用:设置指定IO的上下拉电阻 示例:

1
2
3
4
5
/*
* 设置GPIO0为上拉功能
* Set GPIO0 as pull-up function
*/
IoSetPull(IOT_IO_NAME_GPIO_0, IOT_IO_PULL_UP);
依赖库:src-iot_demo_iot_gpio_ex.c 对第二个参数说明如下:
1
2
3
4
5
6
7
8
/** No pull */
IOT_IO_PULL_NONE,
/** Pull-up */
IOT_IO_PULL_UP,
/** Pull-down */
IOT_IO_PULL_DOWN,
/** Maximum value */
IOT_IO_PULL_MAX
### 2. IoTGpioRegisterIsrFunc()

作用:设置指定GPIO的中断功能 示例:

1
2
3
4
5
6
7
/*
* 使能GPIO0的中断功能, 上升沿触发中断,LeftBCounterHandler为中断的回调函数
* Enable the interrupt function of GPIO0, the rising edge triggers
* the interrupt, and LeftBCounterHandler is the interrupt callback function
*/
IoTGpioRegisterIsrFunc(IOT_IO_NAME_GPIO_0, IOT_INT_TYPE_EDGE,
IOT_GPIO_EDGE_RISE_LEVEL_HIGH, LeftBCounterHandler, NULL);
依赖库:src_hardware_gpio.h, 第二个参数IotGpioIntType intType说明如下
1
2
3
4
/** Level-sensitive interrupt */
IOT_INT_TYPE_LEVEL = 0,
/** Edge-sensitive interrupt */
IOT_INT_TYPE_EDGE
第三个参数IotGpioIntPolarity intPolarity说明如下:
1
2
3
4
/** Interrupt at a low level or falling edge */
IOT_GPIO_EDGE_FALL_LEVEL_LOW = 0,
/** Interrupt at a high level or rising edge */
IOT_GPIO_EDGE_RISE_LEVEL_HIGH
第四个参数GpioIsrCallbackFunc func,为自己编写的中断触发时的回调函数名,第五个参数char *arg为中断回调函数中使用的参数的指针。

IoTGpioGetInputVal()

作用:读取指定GPIO的电平 示例:

1
2
3
4
5
/*
* 获取GPIO8的输入电平状态
* Get the input level status of GPIO8
*/
IoTGpioGetInputVal(IOT_IO_NAME_GPIO_8, &value);
依赖库:src_hardware_gpio.h, IOT_GPIO_VALUE1为高电平,IOT_GPIO_VALUE0为低电平。

I2C总线开发

1. I2C总线开发基础

I2C通信中,主机通过时钟线SCL发送时钟信号,通过数据线SDA发送数据(包括从机地址、指令、数据包等),在发送完一帧数据后,需要等待从机的响应,才能继续发送下一帧数据,因此I2C属于同步通信。

2. 单I2C轮询

(1)IoTI2cInit()

作用:以指定的波特率初始化I2C设备。 示例:

1
2
3
4
#define IOT_I2C_IDX_BAUDRATE (400 * 1000) //指定的I2C波特率
#define CW2015_I2C_IDX 0 //I2C设备ID
//初始化I2C设备0,并指定波特率为400k
IoTI2cInit(CW2015_I2C_IDX, IOT_I2C_IDX_BAUDRATE);
依赖库:src_hardware_i2c.h

(2)IoTI2cDeinit()

作用:关闭/去初始化指定的I2C设备。 定义:

1
2
unsigned int IoTI2cDeinit(unsigned int id);
//参数:I2C设备ID
依赖库:src_hardware_i2c.h

(3)IoTI2cWrite()

作用:向指定的I2C设备写入数据。 示例:

1
2
3
4
5
6
7
8
#define LSM6DS_I2C_IDX 0

uint32_t retval = IoTI2cWrite(LSM6DS_I2C_IDX, LSM6DS_WRITE_ADDR, buffer, buffLen);

//参数1:I2C设备id
//参数2:I2C设备(从机)地址
//参数3:指向要写入的数据的指针
//参数4:要写入的数据的长度
依赖库:src_hardware_i2c.h

(4)IoTI2cRead()

作用:从指定的I2C设备读取数据。 注:未找到相应的示例,猜测该函数可能在驱动层面使用 定义:

1
2
3
4
5
6
unsigned int IoTI2cRead(unsigned int id, unsigned short deviceAddr,
unsigned char *data, unsigned int dataLen);
//参数1:I2C设备id
//参数2:I2C设备(从机)地址
//参数3:指向要读取的数据的指针
//参数4:要读取的数据的长度
依赖库:src_hardware_i2c.h

(5)IoTI2cSetBaudrate()

作用:设置指定I2C设备的波特率。 定义:

1
2
3
unsigned int IoTI2cSetBaudrate(unsigned int id, unsigned int baudrate);
//参数1:I2C设备id
//参数2:指定的波特率
依赖库:src_hardware_i2c.h

3. I2C中断触发

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
195 void I2CBusExampleEntry(void)
196 {
197 osThreadAttr_t attr;
198
199 I2CBusInit();
200 IoTWatchDogDisable();
201
202 attr.name = "NFCTask";
203 attr.attr_bits = 0U;
204 attr.cb_mem = NULL;
205 attr.cb_size = 0U;
206 attr.stack_mem = NULL;
207 attr.stack_size = 5 * 1024; // 5*1024
208 attr.priority = osPriorityNormal;
209
210 // NFC
211 if (osThreadNew((osThreadFunc_t)NFCTask, NULL, &attr) == NULL) {
212 printf("[NFCTask] Failed to create NFCTask!\n");
213 }
214
215 // 电量监测
216 attr.name = "CW2015Task";
217 if (osThreadNew((osThreadFunc_t)CW2015Task, NULL, &attr) == NULL) {
218 printf("[CW2015Task] Failed to create CW2015Task!\n");
219 }
220
221 // 功能按键
222 attr.name = "FunKeyTask";
223 if (osThreadNew((osThreadFunc_t)FunKeyTask, NULL, &attr) == NULL) {
224 printf("[FunKeyTask] Failed to create FunKeyTask!\n");
225 }
226
227 // 陀螺仪
228 attr.name = "LSM6DSTask";
229 if (osThreadNew((osThreadFunc_t)LSM6DSTask, NULL, &attr) == NULL) {
230 printf("[LSM6DSTask] Failed to create LSM6DSTask!\n");
231 }
232 }
233
234 APP_FEATURE_INIT(I2CBusExampleEntry);
软件烧录成功后,按一下开发板的RESET按键复位开发板,可以在OLED屏幕上面看到陀螺仪方向角实时数据以及电池电量数据,当手机靠近小车的NFC模块时,NFC会拉起手机的淘宝应用,且可以通过S1、S2、S3功能按键来控制三个交通灯的亮灭,说明我们使用I2C总线,实现同时轮询多个设备的实验成功。

PWM接口开发

1. IoTPwmInit()

作用:初始化指定PWM端口 示例:

1
2
3
 #define IOT_PWM_PORT_PWM1   1
// 初始化PWM1 Initialize PWM1
IoTPwmInit(IOT_PWM_PORT_PWM1);
依赖库:src_hardware_pwm.h 返回值:
IOT_SUCCESS:初始化成功 IOT_FAILURE:初始化失败

2. IoTPwmStart()

作用:启动PWM信号输出 示例:

1
2
3
4
5
6
7
#define IOT_PWM_PORT_PWM2   2
#define IOT_FREQ 65535
#define IOT_DUTY 50
IoTPwmStart(IOT_PWM_PORT_PWM2, IOT_DUTY, IOT_FREQ);
//参数1:指定的PWM端口
//参数2:PWM输出占空比,范围1-99
//参数3:PWM输出频率,范围1-65535
依赖库:src_hardware_pwm.h

3. IoTPwmStop()

作用:停止PWM信号输出 示例:

1
2
#define IOT_PWM_PORT_PWM2   2
IoTPwmStop(IOT_PWM_PORT_PWM2);
依赖库:src_hardware_pwm.h

IoTPwmDeinit()

作用:释放/去初始化指定PWM端口 示例:

1
2
#define IOT_PWM_PORT_PWM2   2
IoTPwmDeinit(IOT_PWM_PORT_PWM2);
依赖库:src_hardware_pwm.h

GPIO模拟PWM信号

Sg92r舵机原理

Sg92舵机的控制需要产生一个20ms的脉冲信号,以0.5ms到2.5ms高电平来控制舵机的角度;高电平持续时间对应角度如下表所示。

高电平持续时间 角度
0.5ms
1.0ms 45°
1.5ms 90°
2.0ms 135°
2.5ms 180°

代码核心

1
2
3
4
5
6
7
8
9
30 void SetAngle(unsigned int duty)
31 {
32 unsigned int time = FREQ_TIME;
33
34 IoTGpioSetOutputVal(IOT_IO_NAME_GPIO_2, IOT_GPIO_VALUE1);
35 hi_udelay(duty);
36 IoTGpioSetOutputVal(IOT_IO_NAME_GPIO_2, IOT_GPIO_VALUE0);
37 hi_udelay(time - duty);
38 }

ADC开发

1.AdcRead()

作用:根据输入参数从指定的ADC通道读取一段采样数据 示例:

1
2
3
4
5
6
ret = AdcRead(idx, &data, IOT_ADC_EQU_MODEL_4, IOT_ADC_CUR_BAIS_DEFAULT, 0xff);
//参数1:表示指定的ADC通道
//参数2:表示指向存储读取数据的地址的指针
//参数3:eqmodel表示方程模型、
//参数4:curBais表示模拟功率控制模式
//参数5:rstCnt表示从重置到转换开始的时间计数(一次计数是334ns,其值需在0~0xFF0之间)
依赖库:src-iot_demo_adc.h

UART开发

1. IoTUartInit()

作用:初始化指定UART端口 示例:

1
2
3
4
5
6

ret = IoTUartInit(HI_UART_IDX_1, &uart_attr);
if (ret != IOT_SUCCESS) {
printf("Init Uart1 Falied Error No : %d\n", ret);
return;
}
依赖库:src_hardware_uart.h

三、定时器使用——以频率测量为例

主要函数

1. osTimerId_t periodic_tid = osTimerNew(cb_timeout_periodic, osTimerPeriodic, NULL, NULL);

作用:创建一个新的定时器,命名为periodic_tid,其变量名称为osTimerId_t。

参数说明:

  1. cb_timeout_periodic:回调函数;
  2. osTimerPeriodic:定时器类型;
  3. NULL:(第一个)回调函数参数;
  4. NULL:(第二个)定时器属性

periodic_tid常用于确保定时器已创建成功,如下所示:

1
2
3
4
5
6
7
8
9
if (periodic_tid == NULL) 
{
printf("[Timer Test] osTimerNew(periodic timer) failed.\r\n");
return;
}
else
{
printf("[Timer Test] osTimerNew(periodic timer) success, tid: %p.\r\n",periodic_tid);
}

2. osTimerStart(periodic_tid, 100);

说明: 创建一个100个时钟周期调用一次回调函数cb_timeout_periodic的定时器 参数说明: 1. periodic_tid:定时器ID; 2. 100:定时器周期,单位为时钟周期,即100个时钟周期调用一次回调函数

3. void cb_timeout_periodic(void)

说明:定时器回调函数,无输入参数,无返回值。

频率测量原理

定时器每记过1个中断执行周期,期间开启上升沿输入捕获记录从低到高电平的变化个数num。由定时器中断执行周期/num可得到一个上升沿所占用的时钟周期,取倒数即可得到频率。

问题来了,Hi3861的系统时钟我找不到接口(骂骂咧咧.gif)

四、对华为云板端互联代码的分析:app_demo_iot.c

1. static void DemoMsgRcvCallBack(int qos, char topic, char payload)

代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void DemoMsgRcvCallBack(int qos, char *topic, char *payload)
{
const char *requesID;
char *tmp;
IoTCmdResp resp;
printf("RCVMSG:QOS:%d TOPIC:%s PAYLOAD:%s\r\n", qos, topic, payload);
/* app 下发的操作 */
TrafficLightMsgRcvCallBack(payload);
tmp = strstr(topic, CN_COMMAND_INDEX);
if (tmp != NULL) {
// /< now you could deal your own works here --THE COMMAND FROM THE PLATFORM
// /< now er roport the command execute result to the platform
requesID = tmp + strlen(CN_COMMAND_INDEX);
resp.requestID = requesID;
resp.respName = NULL;
resp.retCode = 0; ////< which means 0 success and others failed
resp.paras = NULL;
(void)IoTProfileCmdResp(CONFIG_DEVICE_PWD, &resp);
}
}

1
2
3
4
5
6
7
8
9
10
11
static void TrafficLightMsgRcvCallBack(char *payload)
{
printf("PAYLOAD:%s\r\n", payload);
if (strstr(payload, TRAFFIC_LIGHT_CMD_CONTROL_MODE) != NULL) {
if (strstr(payload, TRAFFIC_LIGHT_RED_ON_PAYLOAD) != NULL) { // RED LED
RedLight();
} else if (strstr(payload, TRAFFIC_LIGHT_RED_OFF_PAYLOAD) != NULL) {
RedOff();
}
}
}

解析: 1. 该函数为回调函数,需要传入三个参数:qos、topic、payload,分别为服务质量、消息主题、消息内容(负载)。 2. 调用TrafficLightMsgRcvCallBack函数处理消息内容。 3. tmp = strstr(topic, CN_COMMAND_INDEX);用于在topic里查找 CN_COMMAND_INDEX 宏定义的字符串。如果找到了,说明这是一个来自平台的命令。 如果是,那么将计算请求ID的位置并将其存储在 requesID 变量。 4. IoTCmdResp resp;用于创建一个 IoTCmdResp 类型的结构体 resp 并设置其字段。其中,requestID 字段设置为之前计算出的请求ID,respName 字段设置为NULL,retCode 字段设置为0表示成功,paras 字段设置为NULL。

让我们看看IoTCmdResp里面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
int retCode; //响应代码。值为0表示成功,而其他值表示失败
const char *respName; //指向常量字符字段的指针,表示响应名称。
const char *requestID;//指向常量字符字段的指针,表示消息命令指定的请求ID。
IoTProfileKV *paras; //指向 IoTProfileKV 结构的指针,表示命令参数。
}IoTCmdResp;

//IoTProfileKV的定义:
typedef struct {
void *nxt; ///< ponit to the next key
const char *key;
const char *value;
int iValue;
float LedValue;
IoTDataType type;
}IoTProfileKV;
## 2. traffic light:1.control module

代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void IotPublishSample(void)
{
IoTProfileService service;
IoTProfileKV property;
printf("traffic light:control module\r\n");
memset_s(&property, sizeof(property), 0, sizeof(property));
property.type = EN_IOT_DATATYPE_STRING;
property.key = "ControlModule";
if (g_lightStatus == 0) {
property.value = "RED_LED_ON";
} else {
property.value = "RED_LED_OFF";
}
memset_s(&service, sizeof(service), 0, sizeof(service));
service.serviceID = "TrafficLight";
service.serviceProperty = &property;
IoTProfilePropertyReport(CONFIG_DEVICE_ID, &service);
}
IoTProfilePropertyReport代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//根据给定的设备 ID 和有效负载报告 IoT 设备的配置文件属性。
int IoTProfilePropertyReport(char *deviceID, IoTProfileService *payload)
//接受两个参数:一个 deviceID 字符串和一个指向 IoTProfileService 类型的指针 payload。
{
int ret = -1;
char *topic;
char *msg;
//定义了两个局部变量 topic 和 msg,并将它们初始化为 NULL
if ((deviceID == NULL) || (payload == NULL) || (payload->serviceID == NULL) || (payload->serviceProperty == NULL)) {
return ret;
}//检查输入参数是否有效。如果任何一个输入参数为 NULL,则函数返回 -1。
topic = MakeTopic(CN_PROFILE_TOPICFMT_PROPERTYREPORT, deviceID, NULL);
if (topic == NULL) {
return ret;
}
//调用 MakeTopic 函数,使用给定的格式字符串、设备 ID 和服务 ID 生成一个主题字符串。如果生成的主题字符串为 NULL,则函数返回 -1。
msg = MakeProfilePropertyReport(payload);
if ((topic != NULL) && (msg != NULL)) {
ret = IotSendMsg(0, topic, msg);
}//调用 MakeProfilePropertyReport 函数,使用给定的有效负载生成一条消息字符串。如果生成的主题和消息字符串都不为 NULL,则函数调用 IotSendMsg 函数发送消息。

hi_free(0, topic);
cJSON_free(msg);
//使用 hi_free 和 cJSON_free 函数释放分配给主题和消息字符串的内存
return ret;
}

解析: line 4:用 memset_s 函数将 property 变量的内存清零 line 5:设置其类型为字符串类型,名为 “ControlModule” if:判断g_lightstatus的值并根据情况设置property的value字段 line 12:使用 memset_s 函数将 service 变量的内存清 line 13-14:设置服务 ID 为 “TrafficLight”,并将其服务属性指向 property 变量。 line 15:调用 IoTProfilePropertyReport 函数,使用设备 ID 和服务信息来上报物联网设备的状态信息。

3.demo main task entry

代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// /< this is the demo main task entry,here we will set the wifi/cjson/mqtt ready ,and
// /< wait if any work to do in the while
static void DemoEntry(void)
{
ConnectToHotspot(); //调用 ConnectToHotspot 函数来连接热点
RedLedInit(); //调用 RedLedInit 函数来初始化红色 LED 灯
CJsonInit(); //调用 CJsonInit 函数来初始化 CJson 库。
IoTMain(); //调用 IoTMain 函数来启动物联网主程序。
IoTSetMsgCallback(DemoMsgRcvCallBack); //调用 IoTSetMsgCallback 函数来设置消息回调函数为 DemoMsgRcvCallBack,详情见条目1
TaskMsleep(30000); // 30000 = 3s连接华为云
/* 主动上报 */
while (1) {
// here you could add your own works here--we report the data to the IoTplatform(在这里你可以添加自己的工作--我们将数据上报到物联网平台)
TaskMsleep(TASK_SLEEP_1000MS);
// know we report the data to the iot platform(将数据上报到物联网平台)
IotPublishSample();
}
}
IoTSetMsgCallback代码:
1
2
3
4
5
int IoTSetMsgCallback(FnMsgCallBack msgCallback)
{
g_ioTAppCb.msgCallBack = msgCallback;
return 0;
}

运行示例程序的输出结果:

1
2
3
4
5
6
7
traffic light:control module //执行IotPublishSample函数,

SNDMSG:QOS:0 TOPIC:$oc/devices/6457560a4f1d680324508724_123456789/sys/properties/report PAYLOAD:{"services":[{"service_id":"TrafficLight","properties":{"ControlModule":"RED_LED_OFF"}}]}

MSGSEND:SUCCESS

ProcessQueueMsg

华为云端操作

ref:https://gitee.com/HiSpark/HiSpark_NICU2023/blob/master/4.5%20%E7%89%A9%E8%81%94%E7%BD%91%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91.md#452%E5%8D%8E%E4%B8%BA%E4%BA%91%E7%9A%84%E6%99%BA%E8%83%BD%E4%BA%A4%E9%80%9A%E7%81%AF%E4%B8%8A%E6%8A%A5

1.创建产与相关属性

点击左边任务栏的“产品”同时选择控制台地址为“北京四”,然后点击最右上角的“创建产品”(产品名称可自定义)。注意:创建产品时如果所属空间为NULL,请先实名注册。

Alt text

创建成功后,点击查看详情,点击“自定义模型”创建用户自己的模型:

根据自己需要自己定义,如为模型添加服务:“TrafficLight”,服务类型:“TrafficLight”,服务描述:“交通灯”,点击确定;新增属性为属性名称:“ControlModule”,数据类型:“String”,访问权限:“可读,可写”,长度:“255”,点击确定;新增命令为命令名称:“ControlModule”,新增参数:“TrafficLight”,数据类型:“String”,长度:“255”。

命令下发是 平台向设备下发设备控制命令 ,需要设备及时将命令的执行结果返回给平台。如果设备没有回应,平台会认为命令执行超时. 而响应参数是指 设备在收到平台下发的命令后,返回给平台的执行结果。 这些响应参数可以携带设备执行操作成功或失败后的响应参数.

点击左边任务栏的“设备”,然后注册设备图,注册创建的产品,用户根据自己需要随意填写,填写完成后,可以看到设备状态显示为未注册。

配置完成,如下:

2.工程移植

将oc_demo里的关于设备响应的函数抹去,因为此次工程只需要设备上报的功能。上报函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

/* sensor:1.Temp_ControlModule */
void IotPublishSample_Temp_ControlModule(void)
{
IoTProfileService service;
IoTProfileKV property;
printf("Temp:control module\r\n");
memset_s(&property, sizeof(property), 0, sizeof(property));
property.type = EN_IOT_DATATYPE_STRING;
property.key = "Temp_ControlModule";
property.value = (char)temperature;
memset_s(&service, sizeof(service), 0, sizeof(service));
service.serviceID = "Temp";
service.serviceProperty = &property;
IoTProfilePropertyReport(CONFIG_DEVICE_ID, &service);
}

/* traffic light:1.control module */
void IotPublishSample_Humi_ControlModule(void)
{
IoTProfileService service;
IoTProfileKV property;
printf("Humi:control module\r\n");
memset_s(&property, sizeof(property), 0, sizeof(property));
property.type = EN_IOT_DATATYPE_STRING;
property.key = "Humi_ControlModule";
property.value = (char)humidity;
memset_s(&service, sizeof(service), 0, sizeof(service));
service.serviceID = "Humi";
service.serviceProperty = &property;
IoTProfilePropertyReport(CONFIG_DEVICE_ID, &service);
}

/* traffic light:1.control module */
void IotPublishSample_Gas_ControlModule(void)
{
IoTProfileService service;
IoTProfileKV property;
printf("Gas:control module\r\n");
memset_s(&property, sizeof(property), 0, sizeof(property));
property.type = EN_IOT_DATATYPE_STRING;
property.key = "Gas_ControlModule";
property.value = (char)gasSensorResistance;
memset_s(&service, sizeof(service), 0, sizeof(service));
service.serviceID = "Gas";
service.serviceProperty = &property;
IoTProfilePropertyReport(CONFIG_DEVICE_ID, &service);
}

// /< this is the demo main task entry,here we will set the wifi/cjson/mqtt ready ,and
// /< wait if any work to do in the while
static void DemoEntry(void)
{
ConnectToHotspot();
CJsonInit();
IoTMain();
TaskMsleep(30000); // 30000 = 3s连接华为云
/* 主动上报 */
while (1) {
// here you could add your own works here--we report the data to the IoTplatform
TaskMsleep(TASK_SLEEP_1000MS);
// know we report the data to the iot platform
IotPublishSample_Temp_ControlModule();
IotPublishSample_Humi_ControlModule();
IotPublishSample_Gas_ControlModule();
}
}
### 3. 云设备的激活

利用https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/ 生成Clientld,Username,Password。

将下图地方设备ID复制到DeviceId,密钥复制到DeviceSecret,点击Generate即可。

本次工程生成后的结果:

ClientId:64a98f799415fc7a573be8f0_Temp_Humi_Gas_Detector_0_0_2023070818 Username:64a98f799415fc7a573be8f0_Temp_Humi_Gas_Detector Password:d500d9f5e8b5ad551b4b90181d8f22f0166b8afa91540e8b4bd995648665e483

修改applications/sample/wifi-iot/app/oc_demo/目录下iot_config.h中,WiFi名称和WiFi密码,搜索字段CONFIG_AP_SSID:代表WiFi名称;CONFIG_AP_PWD:代表WiFi密码(板子需要连接的WiFi)。

1
2
3
4
// /<CONFIG THE WIFI
/* Please modify the ssid and pwd for the own */
#define CONFIG_AP_SSID "MIX4" // WIFI SSID
#define CONFIG_AP_PWD "ez20020328" // WIFI PWD

修改applications/sample/wifi-iot/app/oc_demo/目录下iot_config.h中搜索CONFIG_DEVICE_ID和CONFIG_DEVICE_PWD,CONFIG_CLIENTID这三个字段。CONFIG_DEVICE_ID和CONFIG_DEVICE_PWD,CONFIG_CLIENTID,三个字段配置参数见上:

1
2
3
4
5
6
7
// /<Configure the iot platform
/* Please modify the device id and pwd for your own */
// 设备ID名称,请参考华为物联网云文档获取该设备的ID。例如:60790e01ba4b2702c053ff03_helloMQTT
#define CONFIG_DEVICE_ID "64a98f799415fc7a573be8f0_Temp_Humi_Gas_Detector"
// 设备密码,请参考华为物联网云文档设置该设备密码。例如:hispark2021
#define CONFIG_DEVICE_PWD "d500d9f5e8b5ad551b4b90181d8f22f0166b8afa91540e8b4bd995648665e483"
#define CONFIG_CLIENTID "64a98f799415fc7a573be8f0_Temp_Humi_Gas_Detector_0_0_2023070818"

在华为云如下界面点击总览,可以看到平台接入地址,点击进去之后可以看到设备接入MQTT字样如下图所示,复制“xxxxxxx.iot-mqtts.cn-north-4.myhuaweicloud.com”字段,将该字段写到./applications/sample/wifi-iot/app/oc_demo/iot_main.c中CN_IOT_SERVER字段中。具体修改如下图所示。

报错?

=======KERNEL PANIC=======

**********syserr info start********** kernel_ver : Hi3861V100 R001C00SPC025,2020-09-03 18:10:00 **********Exception Information********** PC Task Name : IOTDEMO PC Task ID = 9 Cur Task ID = 9 Task Stack Size = 0x1000 Exception Type = 0x5 **********reg info********** mepc = 0x3fb732 mstatus = 0x1880 mtval = 0x1e mcause = 0x5 ccause = 0x7 ra = 0x4b0ab2 sp = 0xfc5a0 gp = 0x11a9c0 tp = 0x78b11c76 t0 = 0x4b0a9a t1 = 0xf t2 = 0x6d65545f s0 = 0x11d000 s1 = 0x0 a0 = 0x1e a1 = 0x0 a2 = 0x4 a3 = 0x1e a4 = 0xd00a0dff a5 = 0x11d000 a6 = 0xfed01 a7 = 0x35313439 s2 = 0x1e s3 = 0xfc774 s4 = 0x11111111 s5 = 0x10101010 s6 = 0x9090909 s7 = 0x8080808 s8 = 0x7070707 s9 = 0x6060606 s10 = 0x5050505 s11 = 0x4040404 t3 = 0x39376638 t4 = 0x39613436 t5 = 0x1ce t6 = 0x4c61b8 **********memory info********** Pool Addr = 0xe9d80 Pool Size = 0x2e840 Fail Count = 0x0 Peek Size = 0x16ca4 Used Size = 0x15c48 **********task info********** Name : IOTDEMO ID = 9 Status = 0x14 Stack Index = 0x8 Stack Peak = 0xb84 Stack Size = 0x1000 SP = 0x11a860 Stack : 0xfb7e0 to 0xfc7e0 Real SP = 0xfc5a0 Stack Overflow = 0 **********track_info********** current_item:0x1 item_cnt:10 Index TrackType TrackID CurTime Data1 Data2 0001 0065 0009 0x10fa 0xd99ec 0x3f5e78 0002 0065 0001 0x10f8 0x3f5e78 0xd99ec 0003 0016 0007 0x10f8 0xd99ec 0x0 0004 0065 0000 0x10f9 0xd99ec 0x3f5e78 0005 0065 0007 0x10f9 0x3f5e78 0x3f5e78 0006 0065 0001 0x10f9 0x3f5e78 0xd99ec 0007 0016 0034 0x10f9 0xd99ec 0x0 0008 0065 0007 0x10f9 0xd99ec 0x3f5e78 0009 0065 0001 0x10f9 0x3f5e78 0xd99ec 0010 0016 0007 0x10f9 0xd99ec 0x0 **********Call Stack********** Call Stack 0 -- 4b0da0 addr:fc66c Call Stack 1 -- 4b5108 addr:fc68c Call Stack 2 -- 4b514c addr:fc6ac Call Stack 3 -- 4b51a0 addr:fc6cc Call Stack 4 -- 4b5224 addr:fc6ec Call Stack 5 -- 4b525c addr:fc70c Call Stack 6 -- 4b52e0 addr:fc73c Call Stack 7 -- 4b4882 addr:fc76c Call Stack 8 -- 4b49d0 addr:fc7ac Call Stack 9 -- 3f78c0 addr:fc7cc Call Stack 10 -- 3f5e24 addr:fc7dc **********Call Stack end**********

🤔内核被我写爆了???

经过多方排查,发现property.value只能传进字符串,不能识别的格式,会导致内核崩溃。(虽然工程编译没有问题,其背后原因不得而知,可能需要请教海思的工程师)

修复之后: # 作品设想

1. 作品功能

A.基础设施智能化建设解决方案

将小区分为社区门口、单元门、电梯间、入户门、停车场、公共区域、设备间7 个重点区域,对门禁、道闸、对讲、梯控、视频监控、周界防范、信息发布、停车管理、设备检测等进行基础设施智能化开发与建设,为智慧社区整体方案提供支撑,基础设施见图: fig01

最终敲定功能: 小区设备综合管控:供电监测、水暖系统、排水系统、通风系统的设备情况实时监测 人脸、车牌识别: 电梯设备状态监控: 与市气象站数据整合,小区植被喷淋系统

主要工作:Hi3861开发板学习、各种传感器选型、解决方案敲定等。

B.物业综合服务平台

该平台用于:完成人员管理、养老服务、卫生服务、信息发布、收缴费管理、报事报修、车辆管理、便民服务等功能开发;为物业提供社区基础数据、日常业务管理工具,通过数据共享和应用聚合向政府提供社区管理通道、向居民提供社区服务。平台服务与应用见图4: fig02

主要工作:华为云服务的学习与配置,物业综合服务平台的开发。

项目故事

物业<->USER/业主<->政府/社区 双向的故事 双向的沟通交流,贴近居民生活,提高居民生活质量。

海思CAMP

项目管理方法

传感器选型

电能监控模块

艾锐达IM1253B交直流电压电流功率电量测量电能采集电测计量模块 ref:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.7b90523cKNbIy1&id=571489221834&ns=1&abbucket=0#detail

流量计模块

系统时钟拿不到就是寄,不用了((╯‵□′)╯︵┻━┻)

  • 标题: Hi3861开发板使用与第六届全国大学生嵌入式竞赛海思赛道
  • 作者: Eric Zhang
  • 创建于 : 2023-03-27 06:53:39
  • 更新于 : 2023-12-03 23:43:18
  • 链接: https://ericzhang1412.github.io/2023/03/27/03-Hi3861-Embedded-Competition/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论