10. 中断服务注册 ISR

10.1 ISR 概述

ISR ==> Interrupt Service Register

中断 ==> 当前设备中引脚状态电平改变,触发对应的功能函数。在硬件开发中非常常见。

10.2 触发条件

变化!!!

在 MCU 开发中,常规关注的变化过程为【下降沿】和【上升沿】。在 OpenHarmony 中,可以 利用中断注册服务将中断触发函数,和电平变化的【下降沿】和【上升沿】进行绑定,从而根据电 平变化,触发对应的功能函数。

中断服务注册的功能函数要求

  • 返回值为 void 类型
  • 函数参数为 void * 类型

10.3 功能实现

利用按键实现中断服务函数注册案例,同理可以实现 SR602 人体传感器中断服务注册。

  • 利用 KEY2 进行编程,对应引脚是 IO07 引脚,选择 GPIO 输入工作模式。
  • 通过中断服务注册,在下降沿触发服务,修改 flag ,从而控制 RGB 灯光。
hi_u32 hi_gpio_register_isr_function(hi_gpio_idx id, 
hi_gpio_int_type int_type, 
hi_gpio_int_polarity int_polarity,
gpio_isr_callback func, hi_void *arg
);
typedef enum {
HI_INT_TYPE_LEVEL = 0, /** 电平触发中断CNend
HI_INT_TYPE_EDGE   
/** 边沿触发中断CNend
} hi_gpio_int_type;
hi_gpio_int_type 中断类型,int_type ==> Interrupt Type,在 OHOS 中
中断类型由两大类 
HI_INT_TYPE_LEVEL 电平触发【高电平 or 低电平】
HI_INT_TYPE_EDGE  边沿触发【上升沿 or 下降沿】
typedef enum {
HI_GPIO_EDGE_FALL_LEVEL_LOW = 0,  /** CNcomment:低电平或下降沿触发中断CNend 
HI_GPIO_EDGE_RISE_LEVEL_HIGH      
/** CNcomment:高电平或上升沿触发中断CNend 
} hi_gpio_int_polarity;
hi_gpio_int_polarity 中断极性,配合 hi_gpio_int_type 中断类型,明确当前触发条件
HI_GPIO_EDGE_FALL_LEVEL_LOW  对应低电平或者下降沿触发中断 
HI_GPIO_EDGE_RISE_LEVEL_HIGH 对应高电平或者上降沿触发中断 
四种触发方式
HI_INT_TYPE_LEVEL, HI_GPIO_EDGE_FALL_LEVEL_LOW  低电平触发中断
HI_INT_TYPE_LEVEL, HI_GPIO_EDGE_RISE_LEVEL_HIGH 高电平触发中断
HI_INT_TYPE_EDGE, HI_GPIO_EDGE_FALL_LEVEL_LOW   【下降沿触发中断】
HI_INT_TYPE_EDGE, HI_GPIO_EDGE_RISE_LEVEL_HIGH  【上升沿触发中断】
gpio_isr_callback func
终端服务注册对应的处理函数,要求返回值为 void 类型,参数为 void * 类型
中断服务函数 or 中断函数 or 中断处理函数【快进快出】

11. DTH11 温湿度传感器

11.1 硬件电路中信息的传递方式【重点了解】

  • 规定时间周期内容高低电平变化。
  1. 举例:一秒钟时间,每 125 毫秒完成一次高低电平反馈
  2. 高电平对应 1 低电平对应 0。当前设备 1 秒钟传递一个字节。
  • 图例传递的数据为 1011 0001

  • 根据高电平时间差距作为 1 和 0 的数据传递方式
  1. 举例,如果设备发送 1,高电平持续时间 100 us,设备发送数据 0,高电平持续时间为 50 us,每一个数据之间统一间隔 50 us 的低电平。
  • 图例传递的数据为 1000

  • 两股数据线,分为 A 和 B 两根数据线,参考时间周期,计算 A 和 B 两根数据线的压差,作为 1 和 0 判断
  1. A > B 200mv 认为是 1
  2. B > A 200mv 认为是 0
  • 图例传递的数据为 0011 0001

11.2 DHT11 概述

11.3 DHT11 工作方式

DHT11 和 MCU 进行通信,对应有三个阶段

  • MCU 告知 DHT11 进行数据采样传递,唤醒 DHT11,对应 GPIO 引脚工作模式为输出模式
  • DHT11 告知 MCU 设备已准备就绪,准备开始传递有效数据,对应 GPIO 引脚工作模式为输入模式。
  • DHT11 发送 40 位数据内容【小重点】
  1. 需要完成单一数据位 0 和 1 接收
  2. 每接收 8 个数据位对应一个字节
  3. 5 个字节数据进行校验判断,解析为最终结果。

11.4 DHT11 唤醒和响应

11.5 DHT11 发送 40 位数据

11.6 DHT11 代码实现

IO11 连接 DHT11 对应的 Data 引脚,同时 Hi3861 提供 VCC + GND

IO11 在整个的 DTH11 数据读取过程中,需要进行【输出到输入】模式的转换。

IO11 引脚在空闲状态/初始化状态 ==> GPIO 输出工作模式,默认电平为高电平

IO11 引脚进入到数据接收状态 ==> GPIO 输入工作模式,默认电平为低电平模式/浮空模式 dht11_source.h

#ifndef _DHT11_SOURCE_H
#define _DHT11_SOURCE_H
/*
C 语言标准库三剑客
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
以下是需要导入的 OpenHarmony 相关头文件
ohos_init.h OpenHarmony OS 系统初始化相关头文件
cmsis_os2.h 实时操作系统头文件
hi_timer.h 海思提供的 timer 定时器头文件
*/
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "hi_timer.h"
#include "hi_time.h" // 对应 hi_udelay us 延时函数头文件
#include "hi_errno.h" // 使用 Hi3861 提供的官方错误宏定义
// 海思(OHOS) 提供的标准 IO 头文件,所有对外接口的模式,相关函数
#include "hi_io.h"
// 海思(OHOS) 提供的标准 GPIO 头文件,针对 GPIO 相关控制
#include "hi_gpio.h"
#include "hi_pwm.h"
#define DHT11_PIN HI_IO_NAME_GPIO_11
#define DHT11_PIN_FUNC HI_IO_FUNC_GPIO_11_GPIO
#define TIMEOUT_COUNT (100)
/**
* @brief DHT11 对应引脚初始化函数
*/
void dht11_init(void);
/**
* @brief DTH11 对应引脚修改为输入模式,用于在发送完成起始信号之后
*        
进行数据接收, GPIO 输入模式修改。
*/
void dht11_gpio_input(void);
/**
* @brief DTH11 对应引脚修改为输出模式,DHT11 数据接收完毕,对应引脚恢复到
*         
输出模式空闲状态
*/
void dht11_gpio_output(void);
/**
* @brief MCU 发送起始信号到 DHT11, MCU 对应 IO 口空闲情况下处于高电平状态
*         
1. Tbe 低电平 30 ms
*         
*        
2. Tgo 高电平 35 us
转换 IO 工作状态从输出 ==> 输入状态,释放数据总线。
*/
void dht11_start_signal(void);
/**
* @brief 读取 MCU 对应 DHT11 IO 引脚的电平情况函数,返回值是
*      
hi_gpio_value 类型,对应 HI_GPIO_VALUE0 或者 HI_GPIO_VALUE1
*
* @return 返回值对应 IO 引脚电平情况
*/
hi_gpio_value get_dht11_gpio_input_val(void);
/**
* @brief DHT11 发送响应信号到 MCU 对应引脚,MCU 需要读取对应引脚的电平情况持续时间
*          
进行判断当前 DHT11 响应是否正常
*          
*          
*          
1. Trel DHT11 发送低电平信号,持续时间 88 us
2. Treh DHT11 发送高电压循环,持续时间 92 us
综合考虑,统一持续时间/超时判断时间 Timeout ==> 100 us
*
* @return 响应成功,返回 HI_ERR_SUCCESS,失败返回 HI_ERR_S_FAILURE
*/
hi_s8 dht11_response(void);
/**
* @brief DHT11 读取一位数据内容,数据总量是 40 位
* 
* @return 根据高电平持续时间,TH1 对应 1,TH0 对应 0
*/
hi_u8 dht11_read_bit(void);
/**
* @brief DHT11 读取一个字节数据,数据总量是 5 个字节
* 
* @return 返回是一个字节数据。
*/
hi_u8 dht11_read_byte(void);
/**
* @brief DHT11 传感器数据解析函数,将数据内容通过【校验操作】之后,按照
*          
数据顺序完成温度和湿度数据解析 
* 
* @param tem     
温度整型值
* @param tem_dec 温度小数部分数据,按照整数方式处理,降低 MCU 处理计算压力
* @param hum     
湿度整型值
* @param hum_dec 湿度小数部分数据,按照整数方式处理,降低 MCU 处理计算压力
* @return 读取成功返回 HI_ERR_SUCCESS,失败返回 HI_ERR_S_FAILURE
*/
hi_s8 dht11_read_data(hi_u32 *tem, hi_u32 *tem_dec,
hi_u32 *hum, hi_u32 *hum_dec);
#endif

dht11_source.c

#include "dht11_source.h"
void dht11_init(void)
{
hi_gpio_init();
/*
设置 DHT11 对应引脚初始化为 GPIO 输出工作模式,且默认电平为高电平
*/
hi_io_set_func(DHT11_PIN, DHT11_PIN_FUNC);
hi_gpio_set_dir(DHT11_PIN, HI_GPIO_DIR_OUT);
hi_io_set_pull(DHT11_PIN, HI_IO_PULL_UP);
}
void dht11_gpio_input(void)
{
/*
DHT11 对应引脚修改为 GPIO 输入工作模式,且默认电平为低电平,也可以选择浮空模式,
在 Hi3861 中存在一定的隐患。
*/
hi_gpio_set_dir(DHT11_PIN, HI_GPIO_DIR_IN);
// hi_io_set_pull(DHT11_PIN, HI_IO_PULL_DOWN);
}
void dht11_gpio_output(void)
{
hi_gpio_set_dir(DHT11_PIN, HI_GPIO_DIR_OUT);
// hi_io_set_pull(DHT11_PIN, HI_IO_PULL_UP);
}
void dht11_start_signal(void)
{
/*
1. 明确 MCU 对应 IO 引脚为输出工作模式,且明确为高电平
*/
dht11_gpio_output();
hi_gpio_set_ouput_val(DHT11_PIN, HI_GPIO_VALUE1);
/*
2. Tbe 拉低电平,持续时间 30 ms
hi_void hi_udelay(hi_u32 us);
hi3861 提供的延时函数,单位 us
*/
hi_gpio_set_ouput_val(DHT11_PIN, HI_GPIO_VALUE0);
hi_udelay(30 * 1000);
/*
3. Tgo 拉高电平,持续时间 35 us
*/
hi_gpio_set_ouput_val(DHT11_PIN, HI_GPIO_VALUE1);
hi_udelay(35);
/*
4. MCU 对应 DHT11 IO 引脚拉低电平,且修改为输入模式,释放数据总线
*/
hi_gpio_set_ouput_val(DHT11_PIN, HI_GPIO_VALUE0);
dht11_gpio_input();
}
hi_gpio_value get_dht11_gpio_input_val(void)
{
hi_gpio_value value = HI_GPIO_VALUE0;
hi_gpio_get_input_val(DHT11_PIN, &value);
return value;
}
hi_s8 dht11_response(void)
{
hi_u32 timeout = 0;
/*
1. 【等低】 ==> 滤高
过滤 MCU 或者 DHT11 剩余或者相关干扰的高电平信号,明确进入到
DHT11 响应信号开始阶段 Trel
*/
    while (get_dht11_gpio_input_val() == HI_GPIO_VALUE1)
    {
        // 延时 1 us
        hi_udelay(1);
        timeout += 1;
        if (TIMEOUT_COUNT == timeout)
        {
            // 表示当前 DHT11 响应超时
            return HI_ERR_S_FAILURE; // #define HI_ERR_S_FAILURE (-1)
        }
    }
    timeout = 0;
    /*
    2. 【等高】 ==> Trel 过程
        DHT11 开始发送低电平信号到 MCU ,MCU 根据低电平持续时间进行判断,是否
        出现异常情况。
    */
    while (get_dht11_gpio_input_val() == HI_GPIO_VALUE0)
    {
        // 延时 1 us
        hi_udelay(1);
        timeout += 1;
        if (TIMEOUT_COUNT == timeout)
        {
            // 如果 Trel 持续时间超出 100 us,可以认为 DHT11 异常
            return HI_ERR_S_FAILURE;
        }
    }
    timeout = 0;
    /*
    3. 【等低】 ==> Treh 过程
        DHT11 开始发送高电平信号到 MCU,MCU 读取对应 IO 进行持续高电平时间判断
    */
    while (get_dht11_gpio_input_val() == HI_GPIO_VALUE1)
    {
        // 延时 1 us
        hi_udelay(1);
        timeout += 1;
        if (TIMEOUT_COUNT == timeout)
        {
            // 如果 Treh 持续时间超出 100 us,可以认为 DHT11 异常
            return HI_ERR_S_FAILURE;
        }
    }
    return HI_ERR_SUCCESS; // #define HI_ERR_SUCCESS 0
}
hi_u8 dht11_read_bit(void)
{
    hi_u32 timeout = 0;
    /*
    1. 【等高】 ==> Tlow
        DHT11 发送 40 位数据的固定间隔低电平,等待出现高电平情况,进入
        TH0 或者 TH1 分析
    */
    while (get_dht11_gpio_input_val() == HI_GPIO_VALUE0)
    {
        // 延时 1 us
        hi_udelay(1);
        timeout += 1;
        if (TIMEOUT_COUNT == timeout)
        {
            return HI_ERR_S_FAILURE;
        }
    }
    /*
    2. 【延时控制】 ==> 【重点】
        如果以上 while 循环正常结束,表示当前 DHT11 Tlow 阶段结束,同时
        DHT11 发送高电平数据
        延时控制 40 ~ 50 us 之后,MCU 再次读取 DHT11 数据线电平情况
            TH0 23 ~ 27 us
            TH1 68 ~ 74 us
    */
    hi_udelay(40);
    timeout = 0;
    /*
    3. 【读取判断】
        3.1 如果 MCU 读取到的电平情况为低电平,对应当前 DHT11 发送数据 0
        3.2 如果 MCU 读取到电平情况下为高电平
            进入【等低】 ==> 过滤必要的 TH1 高电平数据,且控制时间。
    */
    if (get_dht11_gpio_input_val() == HI_GPIO_VALUE1)
    {
        // 3.2 MCU 读取到电平情况为高电平,需要对高电平信号进行过滤
        while (get_dht11_gpio_input_val() == HI_GPIO_VALUE1)
        {
            hi_udelay(1);
            timeout += 1;
            if (TIMEOUT_COUNT == timeout)
            {
                return HI_ERR_S_FAILURE;
            }
        }
        // 以上 while 过滤正常结束,返回 1
        return 1;
    }
    else
    {
        // 3.1 MCU 读取到的电平情况为低电平,直接返回 0,对应 TH0
        return 0;
    }
}
hi_u8 dht11_read_byte(void)
{
}
hi_u8 byte = 0;
for (size_t i = 0; i < 8; i++)
{
byte <<= 1;
byte |= dht11_read_bit();
}
return byte;
hi_s8 dht11_read_data(hi_u32 *tem, hi_u32 *tem_dec,
hi_u32 *hum, hi_u32 *hum_dec)
{
hi_u8 dht11_sensor_data[5] = {0};
for (size_t i = 0; i < 5; i++)
{
dht11_sensor_data[i] = dht11_read_byte();
}
if (dht11_sensor_data[0] + dht11_sensor_data[1] + dht11_sensor_data[2] + 
dht11_sensor_data[3] != dht11_sensor_data[4])
{
return HI_ERR_S_FAILURE;
}
*hum = dht11_sensor_data[0];
*hum_dec = dht11_sensor_data[1];
*tem = dht11_sensor_data[2];
*tem_dec = dht11_sensor_data[3];
return HI_ERR_SUCCESS;
}

dht11_demo.c

/*
C 语言标准库三剑客
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
以下是需要导入的 OpenHarmony 相关头文件
ohos_init.h OpenHarmony OS 系统初始化相关头文件
cmsis_os2.h 实时操作系统头文件
hi_timer.h 海思提供的 timer 定时器头文件
*/
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "hi_timer.h"
#include "hi_time.h" // 对应 hi_udelay us 延时函数头文件
#include "hi_errno.h" // 使用 Hi3861 提供的官方错误宏定义
// 海思(OHOS) 提供的标准 IO 头文件,所有对外接口的模式,相关函数
#include "hi_io.h"
// 海思(OHOS) 提供的标准 GPIO 头文件,针对 GPIO 相关控制
#include "hi_gpio.h"
#include "dht11_source.h"
void dht11_thread(void *arg);
static void dht11_testTask(void)
{
    osThreadAttr_t thread_attr;
    memset(&thread_attr, 0, sizeof(osThreadAttr_t));
    thread_attr.name = "DHT11_Thread";
    thread_attr.stack_size = 1024;
    thread_attr.priority = osPriorityNormal;
    osThreadId_t tid = osThreadNew(dht11_thread, HI_NULL, &thread_attr);
    if (HI_NULL == tid)
    {
        perror("[osThreadNew] create DHT11_Thread Failed!");
    }
}
APP_FEATURE_INIT(dht11_testTask);
void dht11_thread(void *arg)
{
    hi_u32 tem = 0;
    hi_u32 tem_dec = 0;
    hi_u32 hum = 0;
    hi_u32 hum_dec = 0;
    // 初始化完成
    dht11_init();
    while (1)
    {
        // MCU 发送起始信号到 DHT11
        dht11_start_signal();
        // 判断 DHT11 响应结果
        if (dht11_response())
        {
            printf("DHT11 Response Failed!\n");
            osDelay(500);
            continue;
        }
        // DHT11 响应正常,读取 DHT11 数据反馈
        if (dht11_read_data(&tem, &tem_dec, &hum, &hum_dec))
        {
            printf("DHT11 Read Data Failed!\n");
        }
        else
        {
printf("Temperature : %d.%d, Humidity : %d.%d\n",
tem, tem_dec, hum, hum_dec);
}
osDelay(500);
}
}

组件内 BUILD.gn

static_library("demo13_DHT11") {
sources = [
"demo_dht11.c",
"dht11_source.c"
]
include_dirs = [
"//utils/native/lite/include", 
"//kernel/liteos_m/kal/cmsis"
]
}

APP 目录下 BUILD.gn

import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
# "demo01_hello",
# "demo02_thread",
# "demo03_mutex",
# "demo04_sem",
# "demo05_sem",
# "demo06_messagequeue",
# "demo07_timer",
# "demo08_led",
# "demo09_beep_rgb",
# "demo10_sr602",
# "demo11_Key",
# "demo12_Key_ISR",
"demo13_DHT11",
]
}

12. PWM 控制

12.1 PWM 控制

PWM 核心参数

  • 频率/周期:单个高电平 + 低电平周期时间,控制整个 PWM 的频率。例如一个周期时间为 10 ms,对应 PWM 的频率是 100 Hz, 周期时间为 20 ms,对应 PWM 的频率是 50 Hz
  • 占空比: 在一个周期内,高电平占用整个周期的时间,例如 PWM 周期为 20 ms,高电平占用 5 ms,低电平占用 15ms,占空比为 25%
  • PWM 周期为 20ms,高电平在周期内容的持续时间为 5 ms,可以认为当前 PWM 对应的工 作频率是 50 Hz ,占空比为 25 %.

PWM 应用场景

  • 电机控制,灯光控制,屏幕控制。

12.2 Hi3861 PWM 相关引脚控制和 API

MCU 中提供了 PWM0 ~ PWM5 6路 PWM 控制输出,在 MCU 内核设计中,分别对应的 IO 引脚 如上图所示,存在一路 PWM 输出对应多个 IO 引脚,为了开发中引脚复用需求。例如 PWM2_OUT 可以使用的 IO 引脚为 IO02, IO05, IO11。

相关 API

hi_pwm_set_clock 时钟源配置函数

  • 因为当前 Hi3861 开发版未焊接外部晶振,唯一可以提供时间参考依据的是内部 MCU 的 160Mhz 时钟
/**
* @ingroup iot_pwm
*
* Enumerates the PWM clock sources.CNcomment:PWM时钟源枚举。CNend
*/
typedef enum {
PWM_CLK_160M, /**< 160M APB clock.CNcomment:160M 工作时钟 CNend */
PWM_CLK_XTAL, /**< 24M/40M crystal clock.CNcomment:24M或40M 晶体时钟 CNend */
PWM_CLK_MAX   /**< Maximum value, which cannot be used.CNcomment:最大值,不可使
用CNend */
} hi_pwm_clk_source;
/**
* @ingroup iot_pwm
* @brief  Sets the clock type of the PWM module.CNcomment:设置PWM模块时钟类型。CNend
*
* @par   描述:
*            
* @par 依赖:
*            
This function is used to set the clock type of the PWM module.
CNcomment:该函数用于设置PWM模块时钟类型。CNend
*
* @attention This setting takes effect for all PWM modules. The 160 MHz clock is 
used by default.
CNcomment:该设置对所有PWM模块均生效。默认为160M时钟。CNend
*
* @param  clk_type [IN] type #hi_pwm_clk_source Clock type. CNcomment:时钟类型。
CNend
*
* @retval #HI_ERR_SUCCESS Success.
* @retval #Other          
Failure. For details, see hi_errno.h.
@li hi_pwm.h: Describes the APIs of the PWM module.CNcomment:文件用于
描述PWM对外接口。CNend
* @see None
*/
hi_u32 hi_pwm_set_clock(hi_pwm_clk_source clk_type);

hi_pwm_init 函数

  • 初始化当前 MCU 提供 PWM 输出的对外端口是哪一个
  • 要求在 GPIO 控制中,需要明确设置对应的 IO 为工作复用模式选择 PWM 模式,例如
  1. IO11 引脚复用配置为 HI_IO_FUNC_GPIO_11_PWM2_OUT
  2. hi_pwm_init 启动 HI_PWM_PORT_PWM2
/**
 * @ingroup iot_pwm
 *
 * Enumerates the PWM ports.CNcomment:PWM端口枚举。CNend
 */
typedef enum {
    HI_PWM_PORT_PWM0 = 0, /**< PWM port0. Register base address: 0x40040000, 
multiplexed with GPIOX.
                             CNcomment:PWM0端口。寄存器基址:0x40040000,复用GPIOX。
CNend */
    HI_PWM_PORT_PWM1 = 1, /**< PWM port1. Register base address: 0x40040100, 
multiplexed with GPIOX.
                             CNcomment:PWM1端口。寄存器基址:0x40040100,复用GPIOX。
CNend */
    HI_PWM_PORT_PWM2 = 2, /**< PWM port2. Register base address: 0x40040200, 
multiplexed with GPIOX.
                             CNcomment:PWM2端口。寄存器基址:0x40040200,复用GPIOX。
CNend */
    HI_PWM_PORT_PWM3 = 3, /**< PWM port3. Register base address: 0x40040300, 
multiplexed with GPIOX.
                             CNcomment:PWM3端口。寄存器基址:0x40040300,复用GPIOX。
CNend */
    HI_PWM_PORT_PWM4 = 4, /**< PWM port4. Register base address: 0x40040400, 
multiplexed with GPIOX.
                             CNcomment:PWM4端口。寄存器基址:0x40040400,复用GPIOX。
CNend */
    HI_PWM_PORT_PWM5 = 5, /**< PWM port5. Register base address: 0x40040500, 
multiplexed with GPIOX.
                             CNcomment:PWM5端口。寄存器基址:0x40040500,复用GPIOX。
CNend */
    HI_PWM_PORT_MAX   /**< Maximum value, which cannot be used.CNcomment:最大值,
不可使用CNend */
} hi_pwm_port;
/**
* @ingroup iot_pwm
* @brief  Initializes the PWM module.CNcomment:PWM初始化。CNend
*
* @par   描述:
*            Initializes the PWM module.CNcomment:PWM初始化。CNend
* @attention
*        @li Before using the PWM function, ensure that the GPIO multiplexing 
relationship has been configured.
CNcomment:使用PWM功能前需要确认已配置GPIO复用关系。CNend
*        @li For details, see Hi3861 V100 API Development Guide.
CNcomment:具体配置方法请参见<Hi3861V100 API开发指南>Demo介绍小节相关说明。CNend
*        @li For details about the multiplexed GPIO, see the Hi3861 V100 Data 
Sheet.
CNcomment:具体复用的GPIO请参见<Hi3861 芯片 硬件用户指南>。CNend
*        @li Before using the PWM function, initialize the PWM.CNcomment:使用PWM功
能前需要先初始化。CNend
*
* @param  port [IN]  type #hi_pwm_port PWM port number.CNcomment:PWM端口号。CNend
*
* @retval #HI_ERR_SUCCESS Success.
* @retval #Other          Failure. For details, see hi_errno.h.
* @par 依赖:
*            
@li hi_pwm.h: Describes the APIs of the PWM module.CNcomment:文件用于
描述PWM对外接口。CNend
* @see hi_pwm_deinit | hi_pwm_start | hi_pwm_stop。
*/
hi_u32 hi_pwm_init(hi_pwm_port port);

hi_pwm_start 开启 PWM 输出

  • 明确对应 PWM 输出通道
  • 需要告知当前占空比数据 ==duty / freq
  • 对应分频倍数
/**
* @ingroup iot_pwm
* @brief  Outputs the PWM signal according to the configured parameter.
*         
PWM signal duty cycle = duty/freq Frequency = Clock source 
frequency/freq.
CNcomment:按配置的参数输出PWM信号。PWM信号占空比=duty/freq。频率=时钟源频率/freq。CNend
*
* @par   描述:
*            
Starts the PWM signal output.CNcomment:启动PWM信号输出。CNend
* @par 依赖:
*            
*
* @attention This API cannot be called in an interrupt.CNcomment:不支持在中断中调
用。CNend
*
* @param  port [IN] type #hi_pwm_port PWM port number.CNcomment:PWM端口号。CNend
* @param  duty [IN] type #hi_u16 PWM duty cycle count. Value range: [1, 65535]. 
The default value is 750.
CNcomment:PWM占空比计数值。取值范围为:[1, 65535]。默认值为750。CNend
* @param  freq [IN] type #hi_u16 Frequency division multiple. Value range: [1, 
65535]. The default value is 1500.
CNcomment:分频倍数。取值范围为:[1, 65535]。默认值为1500。CNend
*
* @retval #HI_ERR_SUCCESS Success.
* @retval #Other          
Failure. For details, see hi_errno.h.
@li hi_pwm.h: Describes the APIs of the PWM module.CNcomment:文件用于
描述PWM对外接口。CNend
* @see  hi_pwm_init | hi_pwm_stop。
*/
hi_u32 hi_pwm_start(hi_pwm_port port, hi_u16 duty, hi_u16 freq);

呼吸灯实现

/*
C 语言标准库三剑客
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
以下是需要导入的 OpenHarmony 相关头文件
ohos_init.h OpenHarmony OS 系统初始化相关头文件
cmsis_os2.h 实时操作系统头文件
hi_timer.h 海思提供的 timer 定时器头文件
*/
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "hi_timer.h"
#include "hi_time.h" // 对应 hi_udelay us 延时函数头文件
#include "hi_pwm.h"  // 对应 Hi3861 提供的 PWM 模块
#include "hi_errno.h" // 使用 Hi3861 提供的官方错误宏定义
// 海思(OHOS) 提供的标准 IO 头文件,所有对外接口的模式,相关函数
#include "hi_io.h"
// 海思(OHOS) 提供的标准 GPIO 头文件,针对 GPIO 相关控制
#include "hi_gpio.h"
#define LED3_PIN HI_IO_NAME_GPIO_2
#define LED3_PIN_FUNC HI_IO_FUNC_GPIO_2_PWM2_OUT
// 预设 PWM 控制的分频倍数
#define FREQ (4000)
void pwm_led_init(void);
/**
* @brief 当前 LED 灯控制占空比 + 分频倍数,时钟源【160MHz】
* 
* @param duty 占空比参考数据,当前 Hi3861 占空比计算规则是 duty / freq
*          
例如 duty = 2000, freq = 4000,此时占空比为 50%,按照 LED 分析
*              
可以认为 LED 亮度为 50 %。
* @param freq 分频倍数,最大值是 65535 ,freq 值越大,对应的 PWM 频率越低
*          
例如 freq = 200 ,当前 PWM 频率 160MHz / 200 ==> 0.8MHz ==> 800KHz
*               
freq = 4000 ,当前 PWM 频率 160MHz / 4000 ==> 0.04MHz ==> 40KHz
*/
void led_pwm_set(hi_u16 duty, hi_u16 freq);
void led_pwm_thread(void *arg);
static void led_pwm_testTask(void)
{
osThreadAttr_t thread_attr;
memset(&thread_attr, 0, sizeof(osThreadAttr_t));
thread_attr.name = "led_pwm_thread";
thread_attr.stack_size = 1024;
thread_attr.priority = osPriorityNormal;
osThreadId_t tid = osThreadNew(led_pwm_thread, HI_NULL, &thread_attr);
if (HI_NULL == tid)
{
perror("[osThreadNew] create led_pwm_thread Failed!");
}
}
APP_FEATURE_INIT(led_pwm_testTask);
void pwm_led_init(void)
{
hi_gpio_init();
    /*
    【重点】
    设置 IO02 引脚复用模式 ==> HI_IO_FUNC_GPIO_2_PWM2_OUT
    */
    hi_io_set_func(LED3_PIN, LED3_PIN_FUNC);
    hi_gpio_set_dir(LED3_PIN, HI_GPIO_DIR_OUT);
    /*
    【重点】
    利用 Hi3861 提供的 PWM 初始化函数,开启 PWM2 输出端口。
    hi_u32 hi_pwm_init(hi_pwm_port port);   
        提供的参数是 Hi3861 内置的 PWM 端口枚举类型,因为操作的 IO 引脚为 IO02
        根据官方手册对应的映射关系,需要开启 PWM2 端口
    */
    hi_pwm_init(HI_PWM_PORT_PWM2);
}
void led_pwm_set(hi_u16 duty, hi_u16 freq)
{
    hi_pwm_start(HI_PWM_PORT_PWM2, duty, freq);
}
void led_pwm_thread(void *arg)
{
    /*
    PWM 占空比控制参数,占空比数据为 duty / freq
    */
    hi_u16 duty = 0;
    /*
    LED 灯亮灭切换参数
    */
    int flag = 0;
    pwm_led_init();
    
    while (1)
    {
        if (0 == flag)
        {
            // 亮度逐步增加
            led_pwm_set(duty, FREQ);
            duty += 10;
            osDelay(1);
            // 如果 duty 等于 4010 ,表示 LED 最高亮度,切换为逐步熄灭模式
            if (4010 == duty)
            {
                flag = 1;
            }
        }
        else
        {
            // 亮度逐步降低
            duty -= 10;
            led_pwm_set(duty, FREQ);
            osDelay(1);
组件内 BUILD.gn
APP 目录下 BUILD.gn
12.3 PWM 实现呼吸灯 
利用 PWM 控制实现 LED 呼吸灯效果
控制 LED3 对应 IO 引脚为 IO02,要求当前 IO02 引脚复用关系修改为 PWM2_OUT
MCU 打开的 PWM 端口对应  PWM2
            if (0 == duty)
            {
                flag = 0;
            }
        }
    }
}

组件内 BUILD.gn

static_library("demo14_Led_PWM") {
    sources = [
        "demo_led_pwm.c"
    ]
    include_dirs = [
        "//utils/native/lite/include", 
        "//kernel/liteos_m/kal/cmsis"
    ]
}

APP 目录下 BUILD.gn

import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
    features = [
      # "demo01_hello",
      # "demo02_thread",
      # "demo03_mutex",
      # "demo04_sem",
      # "demo05_sem",
      # "demo06_messagequeue",
      # "demo07_timer",
      # "demo08_led",
      # "demo09_beep_rgb",
      # "demo10_sr602",
      # "demo11_Key",
      # "demo12_Key_ISR",
      # "demo13_DHT11",
      "demo14_Led_PWM",
    ]
}

12.3 PWM 实现呼吸灯

利用 PWM 控制实现 LED 呼吸灯效果

  • 控制 LED3 对应 IO 引脚为 IO02,要求当前 IO02 引脚复用关系修改为 PWM2_OUT
  • MCU 打开的 PWM 端口对应 PWM2

12.4 PWM 控制 SG90 舵机

核心:控制信号与速度关系

这是360度舵机的关键,请务必理解:

  • 1.5ms 脉冲(占空比7.5%):停止。舵机内部电机停止转动。
  • 1.0ms ~ 1.4ms 脉冲(占空比5% ~ 7%):顺时针旋转。脉冲越接近1.0ms,顺时针转速越 快。
  • 1.6ms ~ 2.0ms 脉冲(占空比8% ~ 10%):逆时针旋转。脉冲越接近2.0ms,逆时针转速越 快。

代码实现

  • 实现函数,函数执行周期利用延时控制,保证单次函数执行时间为 20 ms
  • 利用 GPIO 输出模式高电平和低电平时间控制占空比
  1. 停止状态 高电平 1.5 ms ,低电平 18.5 ms
  2. 顺时针旋转状态 高电平 1 ms ,低电平 19 ms
  3. 逆时针旋转平 2 ms ,低电平 18 ms

SG90 控制

/*
C 语言标准库三剑客
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
以下是需要导入的 OpenHarmony 相关头文件
ohos_init.h OpenHarmony OS 系统初始化相关头文件
cmsis_os2.h 实时操作系统头文件
hi_timer.h 海思提供的 timer 定时器头文件
*/
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "hi_timer.h"
#include "hi_time.h" // 对应 hi_udelay us 延时函数头文件
#include "hi_pwm.h"  // 对应 Hi3861 提供的 PWM 模块
#include "hi_errno.h" // 使用 Hi3861 提供的官方错误宏定义
// 海思(OHOS) 提供的标准 IO 头文件,所有对外接口的模式,相关函数
#include "hi_io.h"
// 海思(OHOS) 提供的标准 GPIO 头文件,针对 GPIO 相关控制
#include "hi_gpio.h"
#define LED3_PIN HI_IO_NAME_GPIO_2
#define LED3_PIN_FUNC HI_IO_FUNC_GPIO_2_PWM2_OUT
#define SG90_PIN HI_IO_NAME_GPIO_14
#define SG90_PIN_FUNC HI_IO_FUNC_GPIO_14_GPIO
// 顺时针旋转,高电平脉冲时间
#define CLOCKWISE_PULSE_WIDTH (1 * 1000)
// 逆时针旋转,高电平脉冲时间
#define ANTICLOCKWISE_PULSE_WIDTH (2 * 1000)
// 停止状态,高电平脉冲时间
#define STOP_PULSE_WIDTH (1.5 * 1000)
// 单一周期总脉冲时间,20 ms
#define TOTAL_PULSE_WIDTH (20 * 1000)
void sg90_init(void);
void sg90_clockwise(void);
void sg90_anticlockwise(void);
void sg90_stop(void);
void sg90_thread(void *arg);
static void led_pwm_testTask(void)
{
osThreadAttr_t thread_attr;
memset(&thread_attr, 0, sizeof(osThreadAttr_t));
thread_attr.name = "sg90_thread";
thread_attr.stack_size = 1024;
thread_attr.priority = osPriorityNormal;
osThreadId_t tid = osThreadNew(sg90_thread, HI_NULL, &thread_attr);
if (HI_NULL == tid)
{
perror("[osThreadNew] create sg90_thread Failed!");
}
}
APP_FEATURE_INIT(led_pwm_testTask);
void sg90_init(void)
{
hi_gpio_init();
// 设置 SG90 对应数据控制引脚 GPIO 为输出模式,且默认低电平
hi_io_set_func(SG90_PIN, SG90_PIN_FUNC);
hi_gpio_set_dir(SG90_PIN, HI_GPIO_DIR_OUT);
hi_io_set_pull(SG90_PIN, HI_IO_PULL_DOWN);
}
void sg90_clockwise(void) 
{
// 顺时针状态,高电平时间 1ms
hi_gpio_set_ouput_val(SG90_PIN, HI_GPIO_VALUE1);
hi_udelay(CLOCKWISE_PULSE_WIDTH);
// 低电平时间是 20ms - 1ms
hi_gpio_set_ouput_val(SG90_PIN, HI_GPIO_VALUE0);
hi_udelay(TOTAL_PULSE_WIDTH - CLOCKWISE_PULSE_WIDTH);
}
void sg90_anticlockwise(void) 
{
// 逆时针状态,高电平时间 2ms
hi_gpio_set_ouput_val(SG90_PIN, HI_GPIO_VALUE1);
hi_udelay(ANTICLOCKWISE_PULSE_WIDTH);
// 低电平时间是 20ms - 2ms
hi_gpio_set_ouput_val(SG90_PIN, HI_GPIO_VALUE0);
hi_udelay(TOTAL_PULSE_WIDTH - ANTICLOCKWISE_PULSE_WIDTH);
}
void sg90_stop(void) 
{
// 停止状态,高电平时间 1.5ms
hi_gpio_set_ouput_val(SG90_PIN, HI_GPIO_VALUE1);
hi_udelay(STOP_PULSE_WIDTH);
// 低电平时间是 20ms - 1.5ms
hi_gpio_set_ouput_val(SG90_PIN, HI_GPIO_VALUE0);
hi_udelay(TOTAL_PULSE_WIDTH - STOP_PULSE_WIDTH);
}
void sg90_thread(void *arg)
组件内 BUILD.gn
APP 目录下 BUILD.gn
{
    sg90_init();
    while (1)
    {
        for (size_t i = 0; i < 150; i++)
        {
            sg90_clockwise();
        }
        for (size_t i = 0; i < 150; i++)
        {
            sg90_stop();
        }
        for (size_t i = 0; i < 150; i++)
        {
            sg90_anticlockwise();
        }
        for (size_t i = 0; i < 150; i++)
        {
            sg90_stop();
        }        
    }
}

组件内 BUILD.gn

static_library("demo15_SG90") {
    sources = [
        "demo_sg90.c"
    ]
    include_dirs = [
        "//utils/native/lite/include", 
        "//kernel/liteos_m/kal/cmsis"
    ]
}

APP 目录下 BUILD.gn

import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
    features = [
      # "demo01_hello",
      # "demo02_thread",
      # "demo03_mutex",
      # "demo04_sem",
      # "demo05_sem",
      # "demo06_messagequeue",
      # "demo07_timer",
      # "demo08_led",
      # "demo09_beep_rgb",
      # "demo10_sr602",
      # "demo11_Key",
      # "demo12_Key_ISR",
      # "demo13_DHT11",
      # "demo14_Led_PWM",
      "demo15_SG90",
     ]
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐