OpenHarmony轻量和小型系统开发例程

时间:2022-12-29 01:25:36

(目录) 本章学习GPIO基础操作,包含输入输出、ADC、PWM。 视频链接

1.GPIO基本操作

1.1 GPIO基本输出、输入

GPIO常用函数总结:

函数 功能 依赖
IoTGpioInit(unsigned int id) 初始化指定的IO //base/iot_hardware/peripheral/interfaces/kits/iot_gpio.h
hi_io_set_func(unsigned int id, unsigned char val) 配置指定IO的复用功能 //device/hisilicon/hispark_pegasus/sdk_liteos/include/hi_io.h
IoTGpioSetDir(unsigned int id, IotGpioDir dir) 设置指定IO的管脚方向id:指定的IO号dir:GPIO管脚方向(IOT_GPIO_DIR_IN、IOT_GPIO_DIR_OUT) //base/iot_hardware/peripheral/interfaces/kits/iot_gpio.h
IoTGpioSetOutputVal(unsigned int id, IotGpioValue val); 设置指定IO的输出电平id:指定的IO号val:GPIO管脚的输出电平(IOT_GPIO_VALUE0、IOT_GPIO_VALUE1) //base/iot_hardware/peripheral/interfaces/kits/iot_gpio.h
hi_io_set_pull(unsigned int id, IotIoPull val) 设置指定GPIO的上下拉功能id:指定的IO号val:待设置的上下拉状态 //device/hisilicon/hispark_pegasus/sdk_liteos/include/hi_io.h
IoTGpioGetInputVal(unsigned int id, IotGpioValue *val); 读取指定GPIO管脚的高低电平id:指定的IO号val:返回读取的GPIO管脚电平值的指针 //base/iot_hardware/peripheral/interfaces/kits/iot_gpio.h

可以看到复用函数hi_io_set_func、hi_io_set_pull函数并未进一步封装到//base/iot_hardware/peripheral/interfaces/kits/iot_gpio.h中,这个在OpenHarmony2.x中也未做,写起来不太方便,希望后续统一,比如设定为IoTGpioSetFunc()、IoTGpioSetPull()函数,当然也可以自己封装。

案例一: 按键控制LED试验

试验目的:实现按键控制LED亮灭。 学习目标:

  • 了解一个完整的工程
  • 学习配置GPIO,完成基本输入、输出功能 准备工作:小熊派开发板、或者润和hispark_pegasus开发板

1.1.1 新建工程gpio_input_output

在iothardware目录下新建gpio_input_output.c,输入如下程序(以下程序适配hispark_pegasus开发板,如果是小熊派开发板,需要修改按键KEY1 GPIO,#define KEY_TEST_GPIO 11,LED GPIO为#define LED_GPIO_2 2 ,操作接口换一下即可):

/***
 *   user按键控制载板LED
 * LED--GPIO9 默认上拉至V3.3
 * user按键---GPIO5 按下接地
 * 实现功能:
 *      按下user键,LED亮,通过查询GPIO5端的电位控制GPIO9的输出
 * 2023.03.20  By Hellokun
 * OpenHarmony3.0 hi3861_hdu编译通过
 * 
 * */
#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h"
#include "hi_io.h"

#define KEY_TASK_STACK_SIZE 512
#define KEY_TASK_PRIO 25
#define KEY_TEST_GPIO 5 // hispark_pegasus 连接GPIO5 按下user键是 低电平-0
#define LED_GPIO_9 9    //LED 一端通过电阻R6上拉接到V3.3  故按下user键时灯就亮

static void *GpioTask(const char *arg)
{
    (void) arg;
   
    while(1)
    {
        IotGpioValue value = IOT_GPIO_VALUE1;      //==定义存贮电平的变量value 枚举类型有value0-value1
        IoTGpioGetInputVal(KEY_TEST_GPIO,&value);  //==获取GPIO user 按键引脚电平
        IoTGpioSetOutputVal(LED_GPIO_9,value);     //==设置GPIO9引脚的状态
       
    }
    return NULL;
}

static void GpioEntry(void)
{
    osThreadAttr_t attr;

    IoTGpioInit(KEY_TEST_GPIO);                    //==初始化GPIO5
    hi_io_set_func(KEY_TEST_GPIO,HI_IO_FUNC_GPIO_5_GPIO);
    IoTGpioSetDir(KEY_TEST_GPIO,IOT_GPIO_DIR_IN);  //==设置user按键为输入 上拉输入
    hi_io_set_pull(HI_IO_NAME_GPIO_5, HI_IO_PULL_UP);

    IoTGpioInit(LED_GPIO_9);                       //==初始化GPIO9
    IoTGpioSetDir(LED_GPIO_9,IOT_GPIO_DIR_OUT);    //==设置LED接口为输出

    attr.name = "GpioTask";                 //==指定线程运行的任务
    attr.attr_bits = 0U;                    //==
    attr.cb_mem = NULL;                     //==
    attr.cb_size = 0U;                      //==
    attr.stack_mem = NULL;                  //==
    attr.stack_size = KEY_TASK_STACK_SIZE;  //==
    attr.priority = KEY_TASK_PRIO;          //==优先权限 

    if(osThreadNew(GpioTask,NULL,&attr)==NULL)
    {
        printf("[GpioEntry] create GpioTask failed!\n");
    }
}

SYS_RUN(GpioEntry); //==ohos_init.h中定义的宏 让一个函数在系统启动时自动执行   

1.1.2 程序结构说明

从上述按键控制LED程序可以简单总结出一个完整的轻量化系统设备开发流程包含:初始化GPIO、编写业务逻辑、注册任务线程、配置编译运行调试

  • 初始化GPIO:控制LED的GPIO9设置为输出模式,按键接口GPIO5设置为上拉输入模式。使用相关函数要include引用相应的依赖头文件。以GPIO5为例,首先掉用IoTGpioInit初始化接口,然后使用hi_io_set_func函数设置接口为普通GPIO,IoTGpioSetDir函数设置按键接口为输入,最后使用hi_io_set_pull设置接口为上拉(因为按键按下为接地)。其他接口使用可以类比即可。
	IoTGpioInit(KEY_TEST_GPIO);                    //==初始化GPIO5
    hi_io_set_func(KEY_TEST_GPIO,HI_IO_FUNC_GPIO_5_GPIO);
    IoTGpioSetDir(KEY_TEST_GPIO,IOT_GPIO_DIR_IN);  //==设置user按键为输入 上拉输入
    hi_io_set_pull(HI_IO_NAME_GPIO_5, HI_IO_PULL_UP);
  • 编写业务逻辑:业务逻辑根据需求编写即可,业务函数GpioTask代码如下。案例中使用按键控制LED状态,轮询获取按键是否按下,按下则设置LED接口为低/高电平(根据开发板电路确定电平)。需要注意业务函数定义为指针函数,便于osThreadNew线程注册函数调用。
static void *GpioTask(const char *arg)
{
    (void) arg;
    while(1)
    {
        IotGpioValue value = IOT_GPIO_VALUE1;      //==定义存贮电平的变量value 枚举类型有value0-value1
        IoTGpioGetInputVal(KEY_TEST_GPIO,&value);  //==获取GPIO user 按键引脚电平
        IoTGpioSetOutputVal(LED_GPIO_9,value);     //==设置GPIO9引脚的状态  
    }
    return NULL;
}
  • 注册任务线程 :开机后如何才能运行按键控制LED业务呢?需要将业务函数注册到线程任务中去。hi3861运行的是轻量化的系统,有一套自己的运行机制,我们参照led_example官方案例可以探究出使用osThreadNew()、SYS_RUN()配合可注册任务,还有其他方式后续讲解。按键控制LED案例中,在函数GpioEntry中创建了一个osThreadAttr_t attr任务对象,配置任务名称GpioTask、优先级、分配的空间大小等,然后调用osThreadNew(GpioTask,NULL,&attr)指定了业务函数GpioTask。最后在整个程序的最后一行调用SYS_RUN(GpioEntry); 让GpioEntry函数在系统启动时自动执行,函数中的任务是GpioTask函数,也即是按键控制LED业务。
static void GpioEntry(void)
{
    osThreadAttr_t attr;
    attr.name = "GpioTask";                 //==指定线程运行的任务
    attr.attr_bits = 0U;                    //==
    attr.cb_mem = NULL;                     //==
    attr.cb_size = 0U;                      //==
    attr.stack_mem = NULL;                  //==
    attr.stack_size = KEY_TASK_STACK_SIZE;  //==
    attr.priority = KEY_TASK_PRIO;          //==优先权限 

    if(osThreadNew(GpioTask,NULL,&attr)==NULL)
    {
        printf("[GpioEntry] create GpioTask failed!\n");
    }
}

SYS_RUN(GpioEntry); //==ohos_init.h中定义的宏 让一个函数在系统启动时自动执行   

1.1.3 编译验证

通过上述讲解,相信对一个完整轻量化设备开发有了一定的了解。下面编译gpio_input_output工程验证按键控制LED是否成功。如何才能让我们的工程参与编译呢?

  • 配置编译运行调试:参考上一篇环境搭建可知,修改BUILD.gn即可,因为我们的工程和led_example都在iothardware目录下,所以只需修改该目录下的BUILD.gn文件,注释掉led_example.c,添加我们的工程即可,修改如下:
static_library("led_example") {
    sources = [
        # "led_example.c",
        "gpio_input_output.c"
    ]
    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/kal/cmsis",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include", #添加hi_io。h依赖路径
    ]
}

编译运行即可,编译成功后,按下开发板按键测试即可。 OpenHarmony轻量和小型系统开发例程

1.2 GPIO输出标准PWM

hi3861芯片支持4路PWM, 本节学习如何使用hi3861的标准PWM。hi3861PWM通道:pwm0 -pwm1 -pwm2 -pwm3 -pwm4 -pwm5,均是复用接口。PWM相关函数:

函数 功能 依赖
IoTPwmInit(unsigned int port) 初始化PWM端口,Port:指定的PWM端口 //base/iot_hardware/peripheral/interfaces/kits/iot_pwm.h
IoTPwmStart(unsigned int port, unsigned short duty, unsigned int freq) 启动PWM输出。Port:指定的PWM端口duty:指PWM信号输出的占空比。该值范围为1到99freq:指PWM信号输出的频率。取值范围为:[1, 65535] //base/iot_hardware/peripheral/interfaces/kits/iot_pwm.h
IoTPwmStop(unsigned int port) 停止PWM信号输出 //base/iot_hardware/peripheral/interfaces/kits/iot_pwm.h

案例二: PWM呼吸灯实验

本案例通过PWM实现LED呼吸灯效果。使用第一通道(pwm0)的输出, 查阅《Hi3861V100/Hi3861LV100/Hi3881V100 WiFi芯片 用户指南》表6.3-Hi3861引脚复用功能表可知pwm0可在GPIO7、GPIO9两个引脚配置。这里选GPIO9,因为载板LED接了GPIO9,方便测试。

1.2.1 开发准备

  • 一块hi3861开发板(润和、小熊派均可)

1.2.2 PWM软件开发

开发流程总结:配置GPIO9 为PWM0通道、循环变化占空比实现LED呼吸效果、注册任务线程

  • 配置PWM0通道:初始化GPIO为PWM输出,初始化PWM0,开启PWM输出。如下:
    IoTGpioInit(PWM0_TEST_GPIO); 
    hi_io_set_func(PWM0_TEST_GPIO,HI_IO_FUNC_GPIO_9_PWM0_OUT); //==初始化 GPIO9 的pwm复用功能
    IoTPwmInit(PWM0); //==初始化pwm0
    IoTPwmStart(PWM0,50,80000); //==配置pwm0输出参数:占空比50%、频率160M/80000=2KHz 
  • 循环变化占空比:业务代码是不断改变占空比数值。
static void PwmdemoTask(void *arg)
{
    (void) arg;
    int val=1;
    while(1)
    {
         for (val=99;val>1;val-=5)
        {
            IoTPwmStart(PWM0,val,3200000);
            osDelay(10);
        }
        osDelay(10);
        for (val=1;val<99;val+=5)
        {
            IoTPwmStart(PWM0,val,3200000);
            osDelay(10);
        }
        osDelay(100);
        IoTPwmStop(PWM0);
    }
}
  • 注册任务线程:操作和案例一类似,不再赘述。完整代码如下:
/***
 *              Hi3861 gpio输出pwm(gpio复用功能)
 * 通道:pwm0 -pwm1 -pwm2 -pwm3 -pwm4 -pwm5
 * 一共6个pwm通道,这里测试第一通道(pwm0)的输出,
 * 查阅《Hi3861V100/Hi3861LV100/Hi3881V100 WiFi芯片 用户指南》表6.3-Hi3861引脚复用功能表可知
 * pwm0可在GPIO7、GPIO9两个引脚配置。这里选GPIO9,因为载板led接了GPIO9,方便测试
 * 实现功能:
 *      控制外接led亮度呼吸效果
 * 2023.03.20 By HelloKun  
 * OpenHarmony3.0 hi3861_hdu编译通过
 * 
 */ 
#include <stdio.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"

#include "iot_gpio.h"
#include "hi_io.h"
#include "iot_pwm.h"
#include "hi_time.h"

#define PWM_TASK_STACK_SIZE 512
#define PWM_TASK_PRIO 25
#define PWM0_TEST_GPIO 9
#define PWM0 0  //== hi_pwm.h 中定义了枚举类型 HI_PWM_PORT_PWM0 0
static void PwmdemoTask(void *arg)
{
    (void) arg;
    int val=1;
    while(1)
    {
         for (val=99;val>1;val-=5)
        {
            IoTPwmStart(PWM0,val,3200000);
            osDelay(10);
        }
        osDelay(10);
        for (val=1;val<99;val+=5)
        {
            IoTPwmStart(PWM0,val,3200000);
            osDelay(10);
        }
        osDelay(100);
        IoTPwmStop(PWM0);
    }
}
static void PwmdemoEntry(void)
{
    osThreadAttr_t attr;
    IoTGpioInit(PWM0_TEST_GPIO); 
    hi_io_set_func(PWM0_TEST_GPIO,HI_IO_FUNC_GPIO_9_PWM0_OUT); //==初始化 GPIO9 的pwm复用功能
    IoTPwmInit(PWM0); //==初始化pwm0
    IoTPwmStart(PWM0,50,80000); //==配置pwm0输出参数:占空比50%、频率160M/80000=2KHz 
    attr.name = "PwmdemoTask";              //==指定线程运行的任务
    attr.attr_bits = 0U;                    //==
    attr.cb_mem = NULL;                     //==
    attr.cb_size = 0U;                      //==
    attr.stack_mem = NULL;                  //==
    attr.stack_size = PWM_TASK_STACK_SIZE;  //==
    attr.priority = PWM_TASK_PRIO;          //==优先权限 
    if(osThreadNew(PwmdemoTask,NULL,&attr)==NULL){
        printf("[PwmdemoEntry] creat PwmdemoTask failed!\n");
    }
}
SYS_RUN(PwmdemoEntry);

1.2.3 编译验证

修改BUILD.gn文件,添加gpio_pwm.c参与编译。

static_library("led_example") {
    sources = [
        # "led_example.c",
        #"gpio_input_output.c",
        #  "gpio_adc.c",
        "gpio_pwm.c"
    ]
    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/kal/cmsis",
        "//base/iot_hardware/peripheral/interfaces/kits",     
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include", 
    ]
}

运行结果如图: OpenHarmony轻量和小型系统开发例程

1.3 GPIO模拟输出PWM

本节单独讲模拟PWM输出的实现思路。因为查看iot_pwm.h可知,hi3861无法输出1/20ms频率的方波,无法控制数字舵机,这种情况下只有通过GPIO模拟PWM输出。思想是结合GPIO基本输出和延时函数,人为控制GPIO输出电平和周期。

案例三:数字舵机控制实验

思路: 配置GPIO为输出、循环输出模拟方波、注册任务线程

  • 循环输出模拟PWM:PWM的本质是一定时间间隔的高低电平,可以在20ms的时间内,先输出高电平,延时一定时间后输出低电平,循环该操作可得到模拟的PWM信号。具体实现如下:
/** * @brief  Servo  control *
 @param servoID number of servo (任意GPIO) 如7-8-9-10 * 
 @param angle  input value: 0-20000 *              
*/
void My_servo(uint8_t servoID,int angle)
{   	
     int j=0;	
     for (j=0;j<5;j++)
      {		
           GpioSetOutputVal(servoID, 1);		
            hi_udelay(angle); //angle ms		
           GpioSetOutputVal(servoID, 0);		
           hi_udelay(20000-angle);//	
       }//20ms 控制舵机			
} 

其中GpioSetOutputVal(servoID, 1);用于输出的GPIO需要初始化为输出。

1.4 GPIO实现ADC复用

本节了解 hi3861-ADC 的使用方法,解决如何配置一个 GPIO 实现 AD 转换的问题。 AD 转换用途很广,在模拟量采集场景必不可少。后续实验中使用到的人体红外传感器、光 敏电阻以及 MQ2 燃气传感器都会使用到 ADC 功能。 ADC相关函数整理如下:

函数 功能 依赖
hi_adc_read(hi_adc_channel_index channel, hi_u16 *data, hi_adc_equ_model_sel equ_model,hi_adc_cur_bais cur_bais, hi_u16 delay_cnt) 根据输入参数从指定的ADC通道读取一段采样数据。channel:表示指定的ADC通道;data:表示指向存储读取数据的地址的指针;equ_model表示方程模型;cur_bais表示模拟功率控制模式;delay_cntt表示从重置到转换开始的时间计数(一次计数是334ns,其值需在0~0xFF0之间) ./iot_adc.h

hi3861有ADC0-ADC6七个通道,是GPIO的复用功能。对应的GPIO和ADC通道如下表: OpenHarmony轻量和小型系统开发例程

案例四:电压采集实验

本实验是熟悉 GPIO 的ADC功能,采集连接到同一个 GPIO 口的三个按键按下对应的电 压值。为了明确是哪一个按键按下,除了在调试口使用 printf 函数查看采集的电压外,我们还使用核心板上的 LED 不同闪烁模式以区别。

1.4.1 硬件准备

本实验需要使用到hi3861核心板(带一个user按键)、底板、oled显示板(带有两个按键s1、s2 )。安装方式参考下图。 OpenHarmony轻量和小型系统开发例程 值得注意的是本实验中使用到三个按键连接到同一个GPIO口---GPIO5,对应ADC2通道。参考hi3861核心板资料,每个按键不同状态对应的ADC值整理如下:user按键[5,228]、S1按键[228,455]、S2按键[455,682]、无按键按下[1422,1820],以此区分按键状态。 当然可以只使用一块核心板也可以测试ADC功能,通过串口输出ADC值查看功能是否正常。

1.4.2 软件设计

思路是:设置GPIO5为ADC功能、读取ADC数值、不同按键按下LED不同状态、注册任务线程

  • 初始化GPIO5为ADC复用功能
IoTGpioInit(KEY_TEST_GPIO);                          //==初始化GPIO5
hi_io_set_func(KEY_TEST_GPIO,HI_IO_FUNC_GPIO_5_GPIO);//=作为普通 io 接口IoTGpioSetDir(KEY_TEST_GPIO,IOT_GPIO_DIR_IN); //==io 方向-输入
hi_io_set_pull(KEY_TEST_GPIO, HI_IO_PULL_UP); //==上拉输入
  • 读取ADC转换值
//读取电压值
unsigned short int button_adc_get(void)
{
    unsigned short int data;
    if (hi_adc_read( HI_ADC_CHANNEL_6, &data, HI_ADC_EQU_MODEL_1, HI_ADC_CUR_BAIS_DEFAULT, 0) == 0) {  
        data =  (float)data * 1.8 * 4 / 4096.0;
        printf("ADC2 %.2f \n",data);
        return data;    
    }
}
  • 注册任务线程:与案例一操作类似,不再赘述。
  • 完整代码如下
/**
 *          Hi3861: gpio ADC功能
 *  读取 GPIO5 不同按键电压(主板有user按键,oled拓展板有S1、S2)
 *  控制led,GPIO9-0 灯亮
 * 查阅润和Hi3861硬件资料可知:
 *        1)一共8个ADC通道,ADC0--ADC7  但通道7为参考电压,不能adc转换。
        * 2)GPIO5---ADC2 第3通道
        * 3)各个按键ADC值上下限如下:
        * GPIO5 ---user按键 [5,228]
        *       ---S1 按键  [228,455]
        *       ---S2 按键  [455,682]
        *       ---无按键按下[1422,1820] 也就是GPIO是上拉输入
 * 思路: ①轮询按键方式读取(调用内核接口创建线程任务)
 *        ②中断方式读取
 * 这里只演示第①种方式。
 * Code By: HelloKun 2023.03.21
 * OpenHarmony3.0 hi3861_hdu编译通过
 * */

#include <stdio.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"  //==系统依赖 包括usleep()

#include "iot_gpio.h"  //==IoTGpioInit()、IoTGpioSetDir()、IoTGpioSetOutPutVal();
#include "hi_io.h"     //==hi_io_set_func()、hi_io_set_pull()
#include "hi_adc.h"    //==hi_adc_read()

#define KEY_TASK_STACK_SIZE 512
#define KEY_TASK_PRIO 24
#define KEY_TEST_GPIO 5 //连接GPIO5 
#define LED_GPIO_9 9    //LED一端通过电阻R6上拉接到V3.3

typedef enum {         //==枚举电压范围
    ADC_USR_MIN = 5,
    ADC_USR_MAX = 228,
    ADC_S1_MIN,  //==229
    ADC_S1_MAX  = 512,
    ADC_S2_MIN,  //==513
    ADC_S2_MAX  = 854
}AdcValue;

typedef enum {  //==对应的按键标识
    SSU_NONE,   //==0
    SSU_USER,
    SSU_S1,
    SSU_S2      //==3
}KeyCode;

//读取电压值
unsigned short int button_adc_get(void)
{
    unsigned short int data;
    if (hi_adc_read( HI_ADC_CHANNEL_6, &data, HI_ADC_EQU_MODEL_1, HI_ADC_CUR_BAIS_DEFAULT, 0) == 0) {  
        data =  (float)data * 1.8 * 4 / 4096.0;
        printf("ADC2 %.2f \n",data);
        return data;    
    }
}

//判断具体是哪个按键按下
unsigned short int button_pressed_check(unsigned short int data)
{
    KeyCode ret = SSU_NONE;
    if ((ADC_USR_MIN <= data) && (data <= ADC_USR_MAX))  ret = SSU_USER;
    if ((ADC_S1_MIN <= data) && (data <= ADC_S1_MAX))  ret = SSU_S1;
    if ((ADC_S2_MIN <= data) && (data <= ADC_S2_MAX))  ret = SSU_S2;
    if (ret != SSU_NONE) {
        return ret;
    }  
    else return 0; //==按键按下是1-2-3 返回0说明无任何按键按下
}

//循环函数
static void *GpioADCTask(const char *arg)
{
    (void) arg;
    while(1)
    {
        button_adc_get();  //获取adc值
        int key_status =button_pressed_check(button_adc_get()); //判断是哪个按下
        printf("key_status: %d \n",key_status); 
        switch (key_status){
             case SSU_NONE:  //无按键按下-led灭
                    IoTGpioSetOutputVal(LED_GPIO_9,1);   break; 
             case SSU_USER:  //==USER键-led闪烁2下后灭
                    IoTGpioSetOutputVal(LED_GPIO_9,0); 
                    usleep(300000);  
                    IoTGpioSetOutputVal(LED_GPIO_9,1); 
                    usleep(300000);  
                    IoTGpioSetOutputVal(LED_GPIO_9,0); 
                    usleep(300000);  
                    IoTGpioSetOutputVal(LED_GPIO_9,1);  break; 
             case SSU_S1: //S1-led闪一下灭
                    IoTGpioSetOutputVal(LED_GPIO_9,0); 
                    usleep(30000);  
                    IoTGpioSetOutputVal(LED_GPIO_9,1);    break;       
             case SSU_S2: //S2按下-led一直亮  
                    IoTGpioSetOutputVal(LED_GPIO_9,0);   break;
             defualt : //无  led不亮
                    IoTGpioSetOutputVal(LED_GPIO_9,1);   break; 
        }
        usleep(100); //==轮询时间控制 */
    }
    return NULL;
}

//==任务入口函数
void GpioADCEntry(void)
{
    IoTGpioInit(KEY_TEST_GPIO);                          //==初始化GPIO5
    hi_io_set_func(KEY_TEST_GPIO,HI_IO_FUNC_GPIO_5_GPIO);//=作为普通 io 接口
    IoTGpioSetDir(KEY_TEST_GPIO,IOT_GPIO_DIR_IN); //==io 方向-输入
    hi_io_set_pull(KEY_TEST_GPIO, HI_IO_PULL_UP); //==上拉输入
    
    IoTGpioInit(LED_GPIO_9);
    IoTGpioSetDir(LED_GPIO_9,IOT_GPIO_DIR_OUT);         //==载板led初始化

    osThreadAttr_t attr; //==创建任务
    attr.name = "GpioADCTask";                 //==指定线程运行的任务
    attr.attr_bits = 0U;                    //==
    attr.cb_mem = NULL;                     //==
    attr.cb_size = 0U;                      //==
    attr.stack_mem = NULL;                  //==
    attr.stack_size = KEY_TASK_STACK_SIZE;  //==
    attr.priority = KEY_TASK_PRIO;          //==优先权限 

    if (osThreadNew((osThreadFunc_t)GpioADCTask, NULL, &attr) == NULL) {
        printf("[GpioADCEntry] Falied to create GpioADCTask!\n");
    }
}
SYS_RUN(GpioADCEntry); //==ohos_init.h中定义的宏 让一个函数在系统启动时自动执行
  • 编译运行,修改iothardware目录下的BUILD.gn文件,添加我们的工程即可,修改如下:
static_library("led_example") {
    sources = [
        # "led_example.c",
        # "gpio_input_output.c",
	"gpio_adc.c",
    ]
    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/kal/cmsis",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include", #添加hi_io。h依赖路径
    ]
}

试验现象,不同按钮按下输出ADC值不一样: OpenHarmony轻量和小型系统开发例程

2.硬件通信

2.1 串口通信

2.1.1 Uart函数接口

涉及串口相关的函数主要是初始化、发送/读取数据、去初始化函数整理如下:

函数 描述 依赖
IoTUartInit(unsigned int id, const IotUartAttribute *param) 初始化指定的UART端口id: 表示UART设备的端口号param:表示指向UART属性的指针 //base/iot_hardware/peripheral/interfaces/kits/iot_uart.h
IoTUartRead(unsigned int id, unsigned char *data, unsigned int dataLen) 从UART设备中读取指定长度的数据id: 表示UART设备的端口号data: 表示指向要读取数据的起始地址的指针dataLen: 表示要读取的字节数 //base/iot_hardware/peripheral/interfaces/kits/iot_uart.h
IoTUartWrite(unsigned int id, const unsigned char *data, unsigned int dataLen) 将指定长度的数据写入UART设备 //base/iot_hardware/peripheral/interfaces/kits/iot_uart.h
IoTUartDeinit(unsigned int id) 去初始化指定的UART端口 /base/iot_hardware/peripheral/interfaces/kits/iot_uart.h

另外,查看iot_uart.h文件可知,其只是对hi_uart.h中定义的函数进行了再一次封装,hi_uart.h中还定义了限时读取函数:

/**
* @ingroup  iot_uart
* @brief  Reads data in specified timeout time.CNomment:在指定超时时间内读取数据。CNend
*
* @par 描述:
*           Reads data in specified timeout time.CNomment:在指定超时时间内读取数据。CNend
*           if Reads all data before timeout, function will return.
CNomment:超时前读取完成所有数据后,函数会立即返回。CNend
*
* @attention This API must be used after the hi_uart_open function is called.
CNcomment:须在调用完hi_uart_init函数之后使用。CNend
* @param  id        [IN] type #hi_uart_idx,UART port id. CNcomment:UART端口号。CNend
* @param  data      [OUT] type #hi_u8*,Start address of the data to be read.CNcomment:读到数据的首地址。CNend
* @param  data_len  [IN] type #hi_u32,Number of bytes to be read.CNcomment:要读取数据的字节数。CNend
* @param  timeout_ms  [IN] type #hi_u32,timeout.CNcomment:超时时间。CNend
*
* @retval #>=0 Number of bytes that are actually read.CNcomment:实际读到数据的字节数。CNend
* @retval #HI_ERR_FAILURE  Data read error.CNcomment:读数据失败。CNend
* @par 依赖:
*            @li hi_uart.h:Describes UART APIs.CNcomment:UART相关接口。CNend
* @see  hi_uart_write。
*/
hi_s32 hi_uart_read_timeout(hi_uart_idx id, hi_u8 *data, hi_u32 len, hi_u32 timeout_ms);

串口主要参数有波特率、串口号、奇偶校验等使用一个IotUartAttribute结构体定义,iot_uart.h中具体定义如下:

/**
 * @brief Defines basic attributes of a UART port.
 *
 * @since 2.2
 * @version 2.2
 */
typedef struct {
    /** Baud rate */
    unsigned int baudRate;
    /** Data bits */
    IotUartIdxDataBit dataBits; 
    /** Stop bit */
    IotUartStopBit stopBits; 
    /** Parity */
    IotUartParity parity; 
    /** Rx block state */
    IotUartBlockState rxBlock;
    /** Tx block state */
    IotUartBlockState txBlock;
    /** Padding bit */
    unsigned char pad;
} IotUartAttribute;

2.1.2 串口蓝牙案例

下面我们做一个简单的串口通信实验,将一串自定义数据通过Hi3861的UART1发送到蓝牙模块(或者其他开发板)。开发流程有以下几步:配置并开启串口、编写发送\读取任务逻辑、开启任务线程、编译运行测试。 开发准备:两块hi3861开发板或者hi3861开发板和带串口的设备(如HC05串口蓝牙模块)

  • 第一步:配置并开启串口。在iothardware下新建文件uart_bluetooth.c文件。由于默认UART 0为调试串口对应的是GPIO03和GPIO04,一般情况下不修改;与其他设备通信使用UART 1或者UART 2,本实验使用的是UART1,对应的就是GPIO00(UART1的TX)和GPIO01(UART1的RT),首先我们将GPIO0、GPIO1的复用为串口,波特率设置为115200,8个数据位,1个停止位,无校验。具体实现如下:
#define BLUE_RX_1 1
#define BLUE_TX_0 0
int Bluetooth_Init()
{
    // 初始化GPIO
    IoTGpioInit(HI_IO_NAME_GPIO_0);
    hi_io_set_func(BLUE_TX_0, HI_IO_FUNC_GPIO_0_UART1_TXD);
    IoTGpioInit(HI_IO_NAME_GPIO_1);
    hi_io_set_func(BLUE_RX_1, HI_IO_FUNC_GPIO_1_UART1_RXD);

    // 配置UART 1参数
    IotUartAttribute my_param; //={115200,8,1,HI_UART_PARITY_NONE,0,0};
     /** Baud rate */
    my_param.baudRate=115200;
    /** Data bits */
    my_param.dataBits=8;
    /** Stop bit */
    my_param.stopBits=1;
    /** Parity */
    my_param.parity=HI_UART_PARITY_NONE;
    return  IoTUartInit(HI_UART_IDX_1, &my_param);  
}
  • 第二步:编写发送\读取任务逻辑。测试逻辑较为简单,先将字符串“Hello OpenHarmony”发送出去,然后调用读取接口获取读到的数据。具体实现如下:
// 串口蓝牙数据交互
static void BluetoothTask(void *arg)
{
    char send_data[] = "Hello OpenHarmony";
    IoTUartWrite(HI_UART_IDX_1, (unsigned char*)send_data, strlen(send_data));

    char get_data[8] = {0};
    int data_len = 8;
    while (1)
    {
        usleep(500000); // 0.5s
        ret = IoTUartRead(HI_UART_IDX_1, get_data, data_len); 
        if (ret == 0)
            printf("get_data = %s\n", get_data);
        else
            printf("get_data Falile\r\n");
    }
}
  • 第三步:开启任务线程。开启串口任务线程和前面几节实验教程类似,这里给出蓝牙实验uart_bluetooth.c完整代码:
#include <stdio.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"

#include <string.h>
#include "hi_io.h"  // 定义了GPIO编号、描述GPIO功能
#include "hi_uart.h"
#include "iot_uart.h"

#define BLUE_RX_1 1
#define BLUE_TX_0 0

int Bluetooth_Init()
{
    // 初始化GPIO
    IoTGpioInit(HI_IO_NAME_GPIO_0);
    hi_io_set_func(BLUE_TX_0, HI_IO_FUNC_GPIO_0_UART1_TXD);
    IoTGpioInit(HI_IO_NAME_GPIO_1);
    hi_io_set_func(BLUE_RX_1, HI_IO_FUNC_GPIO_1_UART1_RXD);

    // 配置UART 1参数
    IotUartAttribute my_param; //={115200,8,1,HI_UART_PARITY_NONE,0,0};
     /** Baud rate */
    my_param.baudRate=115200;
    /** Data bits */
    my_param.dataBits=8;
    /** Stop bit */
    my_param.stopBits=1;
    /** Parity */
    my_param.parity=HI_UART_PARITY_NONE;
    return  IoTUartInit(HI_UART_IDX_1, &my_param); 
}
//测试蓝牙
static void BluetoothTask(void *arg)
{
    (void)arg;
    sleep(2);
    int ret = 1;
    ret = Bluetooth_Init();
    if (ret != 0)
    {
        printf("Uart1 init failed! \n");
        return;
    }
    char send_data[] = "Hello OpenHarmony";
    IoTUartWrite(HI_UART_IDX_1, (unsigned char*)send_data, strlen(send_data));

    char get_data[8] = {0};
    int data_len = 8;
    while (1)
    {
        usleep(500000); // 0.5s
        ret = IoTUartRead(HI_UART_IDX_1, get_data, data_len); 
        if (ret == 0)
            printf("get_data = %s\n", get_data);
        else
            printf("get_data Falile\r\n");
    }
}

static void BluetoothDemo(void)
{
    osThreadAttr_t attr;
    attr.name = "BluetoothTask";
    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(BluetoothTask, NULL, &attr) == NULL)
    {
        printf("[BlueDemo] Falied to create BluetoothTask!\n");
    }
}
APP_FEATURE_INIT(BluetoothDemo);
  • 第四步:编译运行测试。因为我们的工程在iothardware目录下,所以只需修改该目录下的BUILD.gn文件,添加我们的工程即可,修改如下:
static_library("led_example") {
    sources = [
        # "led_example.c",
        # "gpio_input_output.c",
        # "gpio_adc.c",
        # "gpio_pwm.c",
        "uart_bluetooth.c",
    ]
    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/kal/cmsis",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include", 
    ]
}

连接蓝牙模块(或者串口模块)RT、TX要交叉。我这里连接了HC05蓝牙模块,然后使用手机连接蓝牙,互相发送数据。测试效果如下:

2.1.3 串口使用注意事项

  • 接收经常卡死怎么办: 使用hi_uart_read_timeout(在 hi_uart.h中)
  • 使用多个串口 :同时使用多个串口在实际使用中可能会遇到无法读取数据或者卡死状态(阻塞了),这里有两个解决方案:开启多线程(不同线程开启不同串口)、读取数据用限时读取hi_uart_read_timeout()

2.2 I2C通信

2.2.1 I2C函数接口

I2C是常用硬件接口,MPU6050、温湿度传感器等会使用到。I2C的原理可见Analog Dialogue I2C。I2C相关函数接口整理如下:

函数 描述 依赖
IoTI2cInit(unsigned int id, unsigned int baudrate) 以指定的波特率初始化I2C设备。id:I2C设备ID baudrate:指定的I2C波特率 //base/iot_hardware/peripheral/interfaces/kits/iot_i2c.h
IoTI2cWrite(unsigned int id, unsigned short deviceAddr, const unsigned char *data, unsigned int dataLen) 将数据写入I2C设备 //base/iot_hardware/peripheral/interfaces/kits/iot_i2c.h
IoTI2cRead(unsigned int id, unsigned short deviceAddr, unsigned char *data, unsigned int dataLen); 从I2C设备读取数据 //base/iot_hardware/peripheral/interfaces/kits/iot_i2c.h
IoTI2cSetBaudrate(unsigned int id, unsigned int baudrate) 设置I2C设备的波特率 //base/iot_hardware/peripheral/interfaces/kits/iot_i2c.h

上述IoTI2cRead、IoTI2cWrite实质是分别把hi_i2c_wirte()、hi_i2c_read()函数进行一层封装,实际建议直接使用hi_i2c_wirte()、hi_i2c_read() 【在hi_i2c.h文件中有定义编写程序,发送数据更灵活一些。 另外,在hi_i2c.h中定义了发送的数据类型,可以看到是一个结构,接收时可以不用指定send_buf、send_len两个变量,同理发送时可以不用指定另外两个变量。

/**
 * @ingroup iot_i2c
 *
 * I2C TX/RX data descriptor. CNcomment:I2C发送/接收数据描述符。CNend
 */
typedef struct {
    hi_u8*  send_buf;        /**< Data TX pointer. The user needs to ensure that no null pointer is transferred.
                                CNcomment:数据发送指针CNend */
    hi_u32  send_len;        /**< Length of sent data (unit: byte).
                                CNcomment:发送数据长度(单位:byte)CNend */
    hi_u8*  receive_buf;     /**< Data RX pointer. CNcomment:数据接收指针CNend */
    hi_u32  receive_len;     /**< Length of received data (unit: byte).
                                CNcomment:接收数据长度(单位:byte)CNend */
} hi_i2c_data;

2.2.2 温湿度传感器案例

在本实验中我们将编写程序,获取环境温湿度,检测可燃气体,采集的数据通过串口输出,当温度高于 25 摄氏度时,实验板上的蜂鸣器鸣响。其中温湿度传感器为 AHT20,使用i2c 协议传输数据到 hi3861 核心板,可燃气体传感器输出模拟量,需要使用 ADC 功能采集数据;无源蜂鸣器使用 PWM 信号驱动。开发流程总结为:了解AHT20 数据手册、配置i2C接口、编写连接设备收发数据的任务逻辑、注册线程任务、运行调试

  • 第一步,硬件连接 :本实验使用 hi3861 核心板、底板、环境监测板,硬件连接如下图: OpenHarmony轻量和小型系统开发例程 需要注意的是,每个传感器已经通过底板连接到固定 GPIO,对应的连接关系整理如下:
 蜂鸣器——PWM 控制声音的频率和音量
 GPIO09: PWM0
 MQ2 燃气传感器——ADC 读取模拟值
 GPIO11: ADC5
 AHT20 温湿度传感器——I2C 接口,设备地址 0x38
 GPIO13: I2C0_SDA
 GPIO14: I2C0_SCL

软件分为两个部分,第一部分是获取温湿度传感器 AHT20数据。新建如下三个文件:

applications\sample\wifi-iot\app\iothardware\aht20.h :声明相关函数。
applications\sample\wifi-iot\app\iothardware\aht20.c :初始化传感器,读取并处理数据。
applications\sample\wifi-iot\app\iothardware\environment.c :本实验任务在此文件开启线程。
  • 第二步,查询AHT20数据手册 每条指令对 AHT20 有什么样效果,需要查看 AHT20 的手册,当然作为软件开发而言,我们不用重复造*,可以直接使用别人整理出的资料进行程序编写。AHT20 常用的指令在 aht20.h 中声明,整理如下【AHT20 的资料可以参考教程附带的资料,也可以自行查找】:
#define AHT20_SCL 14
#define AHT20_SDA 13                
#define AHT20_I2C_IDX 0              //--i2c0
#define AHT20_I2C_BAUDRATE 400*1000  //--i2c波特率400K
#define AHT20_ADDR 0x38
#define AHT20_READ  ((0x38<<1)|0x1)  //--SDA-读
#define AHT20_WRITE ((0x38<<1)|0x0)  //--SDA-写
#define AHT20_INIT_CMD 0xBE          //--初始化(校准)命令
#define AHT20_INIT_CMD_B1 0x08
#define AHT20_INIT_CMD_B2 0x00
#define AHT20_TRAG_CMD 0xAC          //--触发测量
#define AHT20_TRAG_CMD_B1 0x33
#define AHT20_TRAG_CMD_B2 0
#define AHT20_RESET_CMD 0xBA         //--软复位(不断电复位)
#define AHT20_STATUS 0x71            //--获取状态位
#define AHT20_WAIT_TIME 100         //复位、传感器测量数据等待时间 ms
  • 第三步,配置i2c接口,获取数据。先来看如何初始化接口以及编写读取发送数据的函数。具体实现如下 aht20.c 文件的部分源码:
//使能 i2c-0
void AHT20_I2C_Init(void)
{
    IoTGpioInit(AHT20_SDA);
    hi_io_set_func(AHT20_SDA,HI_IO_FUNC_GPIO_13_I2C0_SDA);
    IoTGpioInit(AHT20_SCL);  
    hi_io_set_func(AHT20_SCL,HI_IO_FUNC_GPIO_14_I2C0_SCL);
    hi_i2c_init(AHT20_I2C_IDX,AHT20_I2C_BAUDRATE); 
}
// 读取数据
static uint32_t AHT20_Read(uint8_t *data, uint32_t dataLen)
{
  hi_i2c_idx id = AHT20_I2C_IDX;
  hi_i2c_data i2cData;
  i2cData.receive_buf = data;
  i2cData.receive_len = dataLen;
  i2cData.send_buf = NULL;
  i2cData.send_len = 0;
  uint32_t result;
  result = hi_i2c_read((hi_i2c_idx)id,AHT20_READ,&i2cData);
  if(result != IOT_SUCCESS){
    printf("AHT20_Read() Failed ,%0X\n",result);
    return result; }
  return IOT_SUCCESS ;
}
// 发送数据
static uint32_t AHT20_Write(uint8_t *data, uint32_t dataLen)
{
  hi_i2c_idx id = AHT20_I2C_IDX;
  hi_i2c_data i2cData;
  i2cData.receive_buf = NULL;
  i2cData.receive_len = 0;
  i2cData.send_buf = data;
  i2cData.send_len = dataLen;
  uint32_t result;
  result = hi_i2c_write((hi_i2c_idx)id,AHT20_WRITE,&i2cData);
  if(result != IOT_SUCCESS){
    printf("AHT20_Write() Failed ,%0X\n",result);
    return result; }
  return IOT_SUCCESS ;
}

当编写基本的数据发送读取函数后,可调用其发送指令,例如复位、获取传感器状态等。

//获取状态
static uint32_t AHT20_Status(void)
{
  uint8_t statuscmd={AHT20_STATUS};
  return AHT20_Read(&statuscmd,sizeof(statuscmd));
}
//软复位
static uint32_t AHT20_Reset(void)
{
  uint8_t reset = {AHT20_RESET_CMD};
  return AHT20_Write(&reset,sizeof(reset));
}
//校准
static uint32_t AHT20_Initcmd(void)
{
  uint8_t initialcmd[] ={AHT20_INIT_CMD, AHT20_INIT_CMD_B1,AHT20_INIT_CMD_B2};
  return AHT20_Write(&initialcmd,sizeof(initialcmd));
}
//触发测量
uint32_t AHT20_StartMeasure(void)
{
  uint8_t startcmd[] ={AHT20_TRAG_CMD, AHT20_TRAG_CMD_B1,AHT20_TRAG_CMD_B2};
  return AHT20_Write(&startcmd,sizeof(startcmd));  
}
  • 第四步,编写处理逻辑,开启任务线程 。在获取数据到数据后,判断温度是否大于报警温度值,是则让蜂鸣器鸣响。将该任务向系统开一个线程。environment.c文件中的源码如下:

   / *程序功能:
    1. 读取燃气传感器的ADC值;
    2. 读取AHT20温湿度传感器的数值;
    3. 温度、湿度上升(哈一口气)蜂鸣器叫几声.
   基于hi3861_hdu开发 编译运行通过
*/

#include "aht20.h"
#define BEEP_PWM  0
#define BEEP_IO   9
#define MQ2_IO    11

static void EnviroTask(void *arg)
{
    (void) arg;
    unsigned int data;
    float temp;
    float humi;
    while(1)
    {  
        if(hi_adc_read(HI_ADC_CHANNEL_5,&data,HI_ADC_EQU_MODEL_1,HI_ADC_CUR_BAIS_DEFAULT, 0)==0) 
        { printf("燃气MQ2 data:%d\n",data);}

       AHT20_Calibrate();
       AHT20_StartMeasure();
       AHT20_GetMeasureResult(&temp, &humi);

       printf(" 温度temp:%.3f ",temp);
       printf(" 湿度humi:%.3f\n",humi);
       if(temp>25) //温度大于25摄氏度,蜂鸣器叫
       {
          IoTPwmStart(BEEP_PWM,90,40000);
       }
       else{
         IoTPwmStop(BEEP_PWM);
       }
    } 
}

static void EnviroEntry(void)
{
    IoTGpioInit(BEEP_IO);
    hi_io_set_func(BEEP_IO,HI_IO_FUNC_GPIO_9_PWM0_OUT);
    IoTPwmInit(BEEP_PWM);
   
    IoTGpioInit(MQ2_IO);
    hi_io_set_func(MQ2_IO,HI_IO_FUNC_GPIO_5_GPIO);
   
    AHT20_I2C_Init();

    osThreadAttr_t attr;  
    attr.name = "EnviroTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.priority = 24;
    attr.stack_size = 4096;
    if(osThreadNew(EnviroTask,NULL,&attr)==NULL)
    {
        printf("[EnviroEntry] Failed to create EnviroTask!\n ");
    }
}
APP_FEATURE_INIT(EnviroEntry);
  • 第五步,编译运行。修改iothardware目录下的BUILD文件,这次有两个.c文件参与编译。aht20.h文件和.c在一个目录下,可以不用再次指定依赖路径。 上述1-4步完整源码见附件。BUILD.gn如下:
static_library("led_example") {
    sources = [
        # "led_example.c",
        # "gpio_input_output.c",
        # "gpio_adc.c",
        # "gpio_pwm.c",
        # "uart_bluetooth.c",
        "aht20.c",
        "environment.c",
    ]
    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/kal/cmsis",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include", 
    ]
}

运行结果如下图: OpenHarmony轻量和小型系统开发例程 可以用手触摸或者对着温湿度传感器哈气,可以看到数值在变化,燃气传感器会微微发热,也有数值,条件允许可以点一根蜡烛,然后熄灭它,刚熄灭后的的白烟可以让MQ2传感器的数值发生变化。至此该实验全部结束。

2.3 SPI通信

2.3.1 SPI函数接口

SPI相关理论可以参考Analoge Dialogue SPI。hi3861支持2路SPI。这里给出相关函数,具体实践可根据需求调用API即可。i2S相关定义在文件src\device\hisilicon\hispark_pegasus\sdk_liteos\include\hi_spi.h中。

// 初始化spi
hi_u32 hi_spi_init(hi_spi_idx spi_id, hi_spi_cfg_init_param init_param, const hi_spi_cfg_basic_info *param);
// 中断模式配置
hi_u32 hi_spi_set_irq_mode(hi_spi_idx id, hi_bool irq_en);
// dma模式配置
hi_u32 hi_spi_set_dma_mode(hi_spi_idx id, hi_bool dma_en);
// 从机写
hi_u32 hi_spi_slave_write(hi_spi_idx spi_id, hi_pvoid write_data, hi_u32 byte_len, hi_u32 time_out_ms);
// 从机度
hi_u32 hi_spi_slave_read(hi_spi_idx spi_id, hi_pvoid read_data, hi_u32 byte_len, hi_u32 time_out_ms);
// 主机写
hi_u32 hi_spi_host_write(hi_spi_idx spi_id, hi_pvoid write_data, hi_u32 byte_len);
// 主机读
hi_u32 hi_spi_host_read(hi_spi_idx spi_id, hi_pvoid read_data, hi_u32 byte_len);

2.4 I2S通信

2.4.1 I2S函数接口

I2S相关理论可以参考Analoge Dialogue I2S。hi3861支持一路I2S。这里给出相关函数,具体实践可根据需求调用API即可。i2S相关定义在文件src\device\hisilicon\hispark_pegasus\sdk_liteos\include\hi_i2s.h中。

/**
* @ingroup iot_i2s
*
* sample rate.
*/
typedef enum {
    HI_I2S_SAMPLE_RATE_8K = 8,
    HI_I2S_SAMPLE_RATE_16K = 16,
    HI_I2S_SAMPLE_RATE_32K = 32,
    HI_I2S_SAMPLE_RATE_48K = 48,
} hi_i2s_sample_rate;

/**
* @ingroup iot_i2s
*
* resolution.
*/
typedef enum {
    HI_I2S_RESOLUTION_16BIT = 16,
    HI_I2S_RESOLUTION_24BIT = 24,
} hi_i2s_resolution;

/**
* @ingroup iot_i2s
*
* I2S attributes.
*/
typedef struct {
    hi_i2s_sample_rate sample_rate;  /**< i2s sample rate, type hi_i2s_sample_rate.CNcomment:采样率,类型为
                                          hi_i2s_sample_rate。CNend */
    hi_i2s_resolution resolution;   /**< i2s resolution, type hi_i2s_resolution.CNcomment:解析度,类型为
                                          hi_i2s_resolution。CNend */
} hi_i2s_attribute;
hi_u32 hi_i2s_init(const hi_i2s_attribute *i2s_attribute);
hi_u32 hi_i2s_read(hi_u8 *rd_data, hi_u32 rd_len, hi_u32 time_out_ms);
hi_u32 hi_i2s_write(hi_u8 *wr_data, hi_u32 wr_len, hi_u32 time_out_ms);

3.无线通信

3.1 Wifi

3.1.1 STA /AP 相关函数

主要学习Hi3861V100的STA和AP模式。常用接口函数如下:

// STA 模式
1.定义: int ConnectToHotspot(WifiDeviceConfig* apConfig);
功能: 连接WiFi
参数: WiFi相关参数比如SSID,passward
返回值: 1:成功
依赖: //foundation/communication/wifi_lite/interfaces/wifiservice
2.定义: void DisconnectWithHotspot(int netId);
**功能: 断开WiFi
参数: WiFi对应的netId
**返回值: 无
依赖: //foundation/communication/wifi_lite/interfaces/wifiservic

// AP模式
1.定义: int StartHotspot(const HotspotConfig* config);
功能: 开启AP
参数: AP参数包括:IP地址,默认网关,子网掩码
**返回值: 0:成功
依赖: //foundation/communication/wifi_lite/interfaces/wifiservice
2.定义: void StopHotspot(void);
功能: 断开AP
参数: 无
返回值: 无
依赖: //foundation/communication/wifi_lite/interfaces/wifiservice

3.1.2 STA\AP demo运行

  • 硬件准备: 一块开发板、可开启热点的手机

第一步,将已有demo移动到app目录下:

  • 将hi3861_hdu_iot_application/src/vendor/hisilicon/hispark_pegasus/demo/wifi_demo 文件夹复制到hi3861_hdu_iot_application/src/applications/sample/wifi-iot/app/目录下。
  • 修改applications/sample/wifi-iot/app/ wifi_demo/目录下的BUILD.gn,如果使用STA模式,将"wifi_starter.c"和"wifi_hotspot_demo.c"使用#注释,如果使用 AP模式,将"wifi_connecter.c"和"wifi_connect_demo.c"使用#注释(同时只能使用一种模式),如下示例使用AP模式:
static_library("wifi_control") {
sources = [
#"wifi_connecter.c",
"wifi_starter.c",
#"wifi_connect_demo.c",
"wifi_hotspot_demo.c",
]

第二步,修改applications/sample/wifi-iot/app/目录下的BUILD.gn,,在features字段中添加wifi_demo: wifi_control。

import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [ "wifi_demo:wifi_control", ]
}

第三步,步骤四:Hi3861V100作为STA模式(参考步骤2修改文件实现demo功能),使用开发板连接手机热点,可以在wifi_connect_demo.c修改热点名称和passward(注意:只支持4G网络),源码中的demo wifi名:“H”,passward:“12345678”,代码如下所示:

static void WifiConnectTask(void)
{
    osDelay(10); /* 10 = 100ms */
    // setup your AP params
    WifiDeviceConfig apConfig = { 0 };
    strcpy(apConfig.ssid, "H"); // 设置wifi ssid "h" Set wifi ssid
    strcpy(apConfig.preSharedKey, "12345678"); // 设置wifi passward "12345678" Set wifi password
    apConfig.securityType = WIFI_SEC_TYPE_PSK;

    int netId = ConnectToHotspot(&apConfig);
    int timeout = 60;
    while (timeout--) {
        printf("After %d seconds I will disconnect with AP!\r\n", timeout);
        /* 100相当于1s,60后WiFi断开 */
        /* 100 is equivalent to 1s, and the WiFi will be disconnected after 60 */
        osDelay(100);
    }
    DisconnectWithHotspot(netId);
}

编译运行之后,设置热点H,passward12345678,复位开发板,打开串口调试Monitor后,可以看到开发板已经连接到手机上。 OpenHarmony轻量和小型系统开发例程 Hi3861作为AP模式(参考步骤一修改BUILD),可以使用手机连接开发板AP,可以在wifi_hotspot_demo.c文件中修改开发板AP的名称和passward。demo AP名称:“HiSpark-AP”,passward为:“12345678”,代码如下所示。

#include "wifi_starter.h"

static void WifiHotspotTask(void)
{
    WifiErrorCode errCode;
    HotspotConfig config = { 0 };

    // 设置AP的配置参数 set configuration parameters for AP
    strcpy(config.ssid, "HiSpark-AP"); // AP :HiSpark-AP
    strcpy(config.preSharedKey, "12345678"); // Password:12345678
    config.securityType = WIFI_SEC_TYPE_PSK;
    config.band = HOTSPOT_BAND_TYPE_2G;
    config.channelNum = 7; /* 通道7 Channel 7 */

    osDelay(10); /* 10 = 100ms */

    printf("starting AP ...\r\n");
    errCode = StartHotspot(&config);
    printf("StartHotspot: %d\r\n", errCode);

    int timeout = 60; /* 60 = 1 minute */
    while (timeout--) {
        printf("After %d seconds Ap will turn off!\r\n", timeout);
        osDelay(100); /* 100 = 1s */
    }

    printf("stop AP ...\r\n");
    StopHotspot();
    printf("stop AP ...\r\n");
    osDelay(10); /* 10 = 100ms */
}

编译运行之后,手机上可以看到HiSpark的无线网络,点击后输入passward即可连接,串口可以看到连接信息。 OpenHarmony轻量和小型系统开发例程

3.2 MQTT

3.2.1 bearpi MQTT使用

mqtt目前已经移植到了OpenHarmony中,在源码vendor目录下bearpi、hihope等公司均有相应的应用demo。这里我先说如何使用bearpi的mqtt。

  • 第一步,拷贝src\vendor\bearpi\bearpi_hm_nano\demo\D5_iot_mqtt文件夹到src\applications\sample\wifi-iot\app\下,该目录(D5_iot_mqtt)
  • 第二步,修改app目录下的BUILD.gn,让该工程参与编译,如下:
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
    features = [
        # "startup",
        # "iothardware:led_example",
            # iothardware指的是工程目录
            # :led_example指的是工程生成的静态库/文件
        # "wifi_demo:wifi_control",
        "D5_iot_mqtt:iot_mqtt",
    ]
}
  • 第三步,打开src\applications\sample\wifi-iot\app\D5_iot_mqtt\iot_mqtt.c文件,修改我们要连接的mqtt服务器(这里可以使用我的服务器120.55.170.12)也可以使用mqtt调试工具EMQX调试。连接的热点我们改为H passward为12345678。
static void MQTTDemoTask(void)
{
    WifiConnect("H", "12345678");
    printf("Starting ...\n");
    int rc, count = 0;
    MQTTClient client;
  • 第四步,如果使用我的服务器调试,那么对应的topic需要修改,如下:
  printf("MQTTSubscribe  ...\n");
  // 下面这行 "substopic" 修改为 "web_fan_btn" 
    rc = MQTTSubscribe(&client, "web_fan_btn", MQTT_QOS, messageArrived);
    if (rc != 0) {
        printf("MQTTSubscribe: %d\n", rc);
        osDelay(MQTT_DELAY_2S);
    }
   ····
   ·····
        message.payloadlen = strlen(payload);
   // 下面这行 "pubtopic" 修改为 "fan"
        if ((rc = MQTTPublish(&client, "fan", &message)) != 0) {
            printf("Return code from MQTT publish is %d\n", rc);
            NetworkDisconnect(&network);
            MQTTDisconnect(&client);
        }
        osDelay(MQTT_DELAY_500_MS);
    }
}
static void MQTTDemo(void)

如果自己设定的mqtt服务器,需要约定好topic。编译运行后,测试如下图所示,点击风扇按钮,开发板可以接收到发送过来的数据。 OpenHarmony轻量和小型系统开发例程OpenHarmony轻量和小型系统开发例程

3.2.2 hihope MQTT使用

这个已经有详细教程,可参考连老师文章,https://ost.51cto.com/posts/10201

4.操作系统

4.1 多线程

4.1.1 多线程相关函数GPI

OpenHarmony是一个可裁剪的操作系统,hi3861运行着裁剪后的轻量化系统,支持多线程。下面先看多线程相关的函数:

1.新建线程
定义: osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, 
const osThreadAttr_t *attr);
功能 创建一个线程并将其添加到活动线程
参数
func:线程函数
argument:参数指针,作为开始参数传递给线程函数
attr:线程属性;NULL:默认值
返回值 创建成功:返回线程ID 创建失败:返回NULL
依赖 //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

2. 其他函数
// 接口名称 		           函数说明 
osThreadGetName 获取指定线程的名字
osThreadGetId 获取当前运行线程的线程ID
osThreadGetState 获取当前线程的状态
osThreadSetPriority 设置指定线程的优先级
osThreadGetPriority 获取当前线程的优先级
osThreadYield 将运行控制转交给下一个处于READY状态的线程
osThreadSuspend 挂起指定线程的运行
osThreadResume 恢复指定线程的运行
osThreadDetach 分离指定的线程(当线程终止运行时,线程存储可以被回收)
osThreadJoin 等待指定线程终止运行
osThreadExit 终止当前线程的运行
osThreadTerminate 终止指定线程的运行
osThreadGetStackSize 获取指定线程的栈空间大小
osThreadGetStackSpace 获取指定线程的未使用的栈空间大小
osThreadGetCount 获取活跃线程数
osThreadEnumerate 获取线程组中的活跃线程数
依赖://third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

4.1.2 多线程点灯实例

下面编写一个多线程实例,一个线程检测按键,当按键按下时,另一个线程实现点亮LED。

  • 第一步,在iothardware目录下,新建osthread_demo.c文件,首先定义线程相关的宏,引入头头文件,并定义一个全局变量value,用于指示按键状态,便于控制LED,在osthread_demo.c中添加如下代码:
// 1. 分别定义按键、LED线程相关宏, 引入头文件
#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h"
#include "hi_io.h"

#define KEY_TASK_STACK_SIZE 512
#define KEY_TASK_PRIO 25
#define LED_TASK_STACK_SIZE 512
#define LED_TASK_PRIO 24

#define KEY_TEST_GPIO 5 // hispark_pegasus 连接GPIO5 按下user键是 低电平-0
#define LED_GPIO_9 9    // LED 一端通过电阻R6上拉接到V3.3  故按下user键时灯就亮

//==定义存贮电平的全局变量value 枚举类型有value0-value1
IotGpioValue value = IOT_GPIO_VALUE1;  
  • 第二步,编写按键检测的线程。流程和第一篇实验雷同,初始化GPIO,编写业务逻辑(检测按键)、注册线程。https://ost.51cto.com/posts/22450#_LED_19, 不详细赘述。下面给出代码,在osthread_demo.c中继续追加如下代码:
// 2.编写按键key业务逻辑
// 获取输入电平 IoTGpioGetInputVal(KEY_TEST_GPIO,&value);
static void *KeyTask(const char *arg)
{
    (void)arg;
    while (1)
    {
        IoTGpioGetInputVal(KEY_TEST_GPIO, &value); //==获取GPIO user 按键引脚电平
    }
    return NULL;
}

// 3.注册线程任务
static void KeyEntry(void)
{
    osThreadAttr_t attr;
    attr.name = "KeyTask";                //==指定线程运行的任务
    attr.attr_bits = 0U;                   //==
    attr.cb_mem = NULL;                    //==
    attr.cb_size = 0U;                     //==
    attr.stack_mem = NULL;                 //==
    attr.stack_size = KEY_TASK_STACK_SIZE; //==
    attr.priority = KEY_TASK_PRIO;         //==优先权限

    // 初始化GPIO5为输入 上拉
    IoTGpioInit(KEY_TEST_GPIO);
    hi_io_set_func(KEY_TEST_GPIO, HI_IO_FUNC_GPIO_5_GPIO);
    IoTGpioSetDir(KEY_TEST_GPIO, IOT_GPIO_DIR_IN);
    hi_io_set_pull(KEY_TEST_GPIO, HI_IO_PULL_UP);

    if (osThreadNew(KeyTask, NULL, &attr) == NULL)
    {
        printf("[KeyEntry] create GpioTask failed!\n");
    }
}
APP_FEATURE_INIT(KeyEntry);
  • 第三步,雷同第二步,编写LED任务线程,初始化LED的GPIO,编写逻辑,由value的值确定led亮灭,最后注册另一个线程。在osthread_demo.c中继续追加如下代码:
// 4.编写LED业务逻辑
// 根据按键状态 设置LED亮灭
static void *LedTask(const char *arg)
{
    (void)arg;
    while (1)
    {
        IoTGpioSetOutputVal(LED_GPIO_9, value);    //==设置GPIO9引脚的状态
    }
    return NULL;
}

在osthread_demo.c中第二步代码基础上添加注册led线程任务

// 4.注册线程任务
static void KeyEntry(void)
{
    osThreadAttr_t attr;
    attr.name = "KeyTask";                //==指定线程运行的任务
    attr.attr_bits = 0U;                   //==
    attr.cb_mem = NULL;                    //==
    attr.cb_size = 0U;                     //==
    attr.stack_mem = NULL;                 //==
    attr.stack_size = KEY_TASK_STACK_SIZE; //==
    attr.priority = KEY_TASK_PRIO;         //==优先权限

    // 初始化GPIO5为输入 上拉
    IoTGpioInit(KEY_TEST_GPIO);
    hi_io_set_func(KEY_TEST_GPIO, HI_IO_FUNC_GPIO_5_GPIO);
    IoTGpioSetDir(KEY_TEST_GPIO, IOT_GPIO_DIR_IN);
    hi_io_set_pull(KEY_TEST_GPIO, HI_IO_PULL_UP);
    if (osThreadNew(KeyTask, NULL, &attr) == NULL)
    {
        printf("[KeyEntry] running\n");
    }

    // 设置LED GPIO9为输出
    IoTGpioInit(LED_GPIO_9);
    hi_io_set_func(LED_GPIO_9, HI_IO_FUNC_GPIO_9_GPIO);
    IoTGpioSetDir(LED_GPIO_9, IOT_GPIO_DIR_OUT);
    if (osThreadNew(LedTask, NULL, &attr) == NULL)
    {
        printf("[LedEntry] create GpioTask failed!\n");
    }
}
SYS_RUN(KeyEntry); //==ohos_init.h中定义的宏 让一个函数在系统启动时自动执行
// APP_FEATURE_INIT(KeyEntry);
  • 第四步,编译运行,修改两处BUILD.gn。 修改src\applications\sample\wifi-iot\app\iothardware\BUILD.gn
static_library("led_example") {
    sources = [
        "osthread_demo.c",
    ]
    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/kal/cmsis",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include", 
    ]
}

修改src\applications\sample\wifi-iot\app\BUILD.gn

import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
    features = [      "iothardware:led_example",   ]
}

编译无误烧录运行后,可以看到和gpio_input_output实验一样的效果,按键按下,led亮。 可以注意到,有两个函数可以添加线程到活动任务中去:

SYS_RUN(KeyEntry); //==ohos_init.h中定义的宏 让一个函数在系统启动时自动执行
APP_FEATURE_INIT(KeyEntry);

4.2 定时器

4.2.1 定时器相关函数

定时器功能可以完成计数、定时任务等。轻量化系统也支持定时器,相关函数如下:

1. 开启定时器
定义: osTimerId_t osTimerNew (osTimerFunc_t func, osTimerType_t type, 
void *argument, const osTimerAttr_t *attr);
功能: 创建并初始化计时器
参数:
func:指向回调函数的指针
type:osTimerOnce表示一次触发, osTimerPeriodic表示周期性行为
argument:计数器回调函数的参数 attr :定时器属性;NULL:默认值
返回值 创建成功:返回定时器ID 创建失败:返回NULL
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
2. 获取定时器名称
定义: const char *osTimerGetName (osTimerId_t timer_id);
功能: 获取定时器的名称
参数: timer_id:定时器ID
返回值: 创建成功:将名称返回以null结尾的字符串 创建失败:返回NULL
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
3.开始定时
定义: osStatus_t osTimerStart (osTimerId_t timer_id, uint32_t ticks);
功能: 启动或重新启动定时器
参数: timer_id:定时器ID 
ticks:CMSIS_RTOS_TimeOutValue定时器的“时间刻度”值。
返回值: 返回指示函数执行状态的状态码
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
4.停止定时
定义: osStatus_t osTimerStop (osTimerId_t timer_id);
功能: 停止定时器
参数: timer_id:定时器ID
返回值: 返回指示函数执行状态的状态码
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
5.检测定时器运行
定义: uint32_t osTimerIsRunning (osTimerId_t timer_id);
功能: 检查定时器是否正在运行
参数: timer_id:定时器ID
返回值: 返回1:正在运行;返回0,:未在运行。
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
6.删除定时
定义: osStatus_t osTimerDelete (osTimerId_t timer_id);
功能: 删除定时器
参数: timer_id:定时器ID
返回值: 返回指示函数执行状态的状态码
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

系统定时器原理(来自:物联网技术及应用实验指导手册):

  • 软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,LiteOS会根据当前系统Tick时间及用户设置的定时时间间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。
  • 当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,看是否有定时器超时,若有则将超过的定时器记录下来。
  • Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下 来的定时器的超时回调函数。

4.2.2 定时开关灯实例

本实验使用定时器功能,每1s切换一次LED状态。

  • 第一步,在iohardware目录下新建timer_led.c文件,定义LED闪烁次数、线程等宏,编写切换LED状态的函数,用于软件定时器的回调。具体代码如下:
 /* 
 * 软件定时器实现LED闪烁5次
 * 
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_watchdog.h"
#include "iot_gpio.h"

#define STACK_SIZE          1024
#define COUNT_STOP          5
#define TIMER_COUNT_NUM     100
#define LED_DELAY          10
#define LED_GPIO_9 9

static int count = 0;   // 定义一个count变量,用于闪烁次数
IotGpioValue value = IOT_GPIO_VALUE1;    // led状态                   

void BlinkLed(void)
{
    count++;
    printf("[Timer_demo] Blink count is %d \r\n", count);
    value = !value;
    IoTGpioSetOutputVal(LED_GPIO_9,value); 
}
  • 第二步,创建周期性的定时器,回调函数是上述BlinkLed函数;然后开启定时,每100个时钟周期执行依次回调函数(一个周期是10ms)。在文件中,追加如下代码:
void TimerThread(const char *arg)
{
    (void)arg;
    osTimerId_t id;
    osStatus_t status;

    // 创建一个周期性的定时器,回调函数是BlinkLed,用于切换LED状态
    id = osTimerNew((osTimerFunc_t)BlinkLed, osTimerPeriodic, NULL, NULL);
    if (id == NULL) {
        printf("[Timer_demo] osTimerNew failed.\r\n");
    } else {
        printf("[Timer_demo] osTimerNew success.\r\n");
    }

    // 开始计时100个时钟周期,一个时钟周期是10ms,100个时钟周期就是1s
    status = osTimerStart(id, TIMER_COUNT_NUM);
    if (status != osOK) {
        printf("[Timer_demo] osTimerStart failed.\r\n");
    } else {
        printf("[Timer_demo] osTimerStart success.\r\n");
    }
}
  • 第三步,当达到指定定时次数后,停止定时,删除定时器。在void TimerThread(const char *arg)函数中继续添加如下代码:
 // 切换5次后,停止闪烁
    while (count < COUNT_STOP) {
        osDelay(LED_DELAY);
    }

    // 停止定时器
    status = osTimerStop(id);
    printf("[Timer_demo] Timer Stop, status :%d. \r\n", status);

    // 删除定时器
    status = osTimerDelete(id);
    printf("[Timer_demo] Timer Delete, status :%d. \r\n", status);
  • 第四步,开启线程,运行任务。
void TimerExampleEntry(void)
{
    osThreadAttr_t attr;
    IoTWatchDogDisable();

    IoTGpioInit(LED_GPIO_9);
    IoTGpioSetDir(LED_GPIO_9,IOT_GPIO_DIR_OUT);         
    attr.name = "TimerThread";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = STACK_SIZE;
    attr.priority = osPriorityNormal;

    // 创建一个线程,并注册一个回调函数 TimerThread,控制LED灯每隔1秒钟闪烁一次
    if (osThreadNew((osThreadFunc_t)TimerThread, NULL, &attr) == NULL) {
        printf("[Timer_demo] osThreadNew Falied to create TimerThread!\n");
    }
}

APP_FEATURE_INIT(TimerExampleEntry);
  • 第五步,编译烧录运行。修改iothardware下的BUILD文件,将timer_led.c添加入sources = [ ]字段中。运行后打开Monitor可以看到count从1-5变化,LED在闪烁,5s后停止闪烁。 OpenHarmony轻量和小型系统开发例程

4.3 中断

4.2.1 中断相关函数

1.清中断
 hi_irq_free(irq_idx);   // 释放中断
参数:中断号ID
依赖:src\device\hisilicon\hispark_pegasus\sdk_liteos\include\hi_isr.h
2.注册中断函数
hi_u32 hi_irq_request(hi_u32 vector, hi_u32 flags, irq_routine routine, hi_u32 param);
  * @param  vector [IN] type #hi_u32,Interrupt ID.CNcomment:中断号。CNend
* @param  flag [IN]   type #hi_u32, attributes like priority,etc.CNcomment:中断优先级等属性。CNend
* @param  routine  [IN] type #irq_routine,Interrupt callback function.CNcomment:中断回调函数。CNend
* @param  param    [IN] type #hi_u32,Parameter transferred to the callback function.
依赖:src\device\hisilicon\hispark_pegasus\sdk_liteos\include\hi_isr.h
3. 使能中断
参数:中断号
hi_void hi_irq_enable(hi_u32 vector);
4.失能中断
hi_void hi_irq_disable(hi_u32 vector);
5.关中断
hi_u32 hi_int_lock(hi_void);
6.恢复中断
hi_void hi_int_restore(hi_u32 int_value);

中断原理(来自:物联网技术及应用实验指导手册):中断是指CPU暂停执行当前程序,转而执行新程序的过程。中断相关的硬件可以划 分为3类:

  • 设备:发起中断的源,当设备需要请求CPU时,产生一个中断信号,该信号连接至中断控制器。
  • 中断控制器:接收中断输入并上报给CPU。可以设置中断源的优先级、触发方式、打开和关闭等 操作。
  • CPU软中断: 由CPU软件发起的中断,一般用于操作系统的模式和线程切换。 使用场景: 当有中断请求产生时,CPU暂停当前的任务,转而去响应外设请求。用户通过中断申请,注册中断 处理程序,可以指定CPU响应中断请求时所执行的具体操作。

4.2.2 中断实例

hi3861_hdu源码中有给定定时器实验案例。下面简要说明如何运行。

  • 第一步 hi3861_hdu_iot_application/src/vendor/hisilicon/hispark_pegasus/demo/interrupt_demo文件夹复制到hi3861_hdu_iot_application/src/applications/sample/wifi-iot/app/目录下。
  • 第二步:修改applications/sample/wifi-iot/app/目录下的BUILD.gn,在features字段中添加interrupt_demo:interrupt_demo。
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [ "interrupt_demo:interrupt_demo", ]
}

打开文件可以看到,demo设定了定时器、获取时钟频率,开启了中断。实现了每一秒中断1次执行中断处理函数,输出一段into the func timer2_irq_handle,这个中断是在3s函数hi_sleep()执行时切入进去的。

// 注册中断函数
    // Register interrupt function
    unsigned int ret = hi_irq_request(TIMER_2_IRQ, HI_IRQ_FLAG_PRI1, timer2_irq_handle, 0);
    if (ret != HI_ERR_SUCCESS) {
        printf("request example irq fail:%x\n", ret);
        return;
    }
    if (g_timer_cnt_cb != 0) {
        dprintf("\n [hi_int_lock] failed\n");
    }
    dprintf("\n [hi_int_lock] success\n\n -Restore the state before shutting down the interrupt-\n");
    hi_int_restore(uvIntSave);  // 中断前完成相关处理
                                // Complete relevant processing before interruption
    if (g_timer_cnt_cb != 0) {
        dprintf("\n [hi_int_restore] failed, timer cnt cb = %d\n", g_timer_cnt_cb);
    }

    tmp = g_timer_cnt_cb;
    ret = hi_irq_enable(irq_idx);   // 使能中断
                                    // Enable interrupt
    if (ret != HI_ERR_SUCCESS) {
        dprintf("failed to hi_irq_enable func ,ret = 0x%x\r\n", ret);
    }
    hi_sleep(TIMER_INTERVAL);       // 进入3s的中断函数
                                    // Interrupt function entering 3s
// 时间中断函数(中断处理函数)
// Time interrupt function
void timer2_irq_handle(unsigned int irqv)
{
    (void)irqv;
    g_timer_cnt_cb++;
    dprintf("\n into the func timer2_irq_handle\n");
    timer_clear(TIMER_ID);  // 清中断
                            // close the interrupt
}

第三步,编译运行。现象是在Monitor看到串口输出两行into the func timer2_irq_handle。 OpenHarmony轻量和小型系统开发例程

本文作者:Hello_Kun

想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com/#bkwz​