Hi3861 OpenHarmony 中断服务注册和ISR DTH11 温湿度传感器和PWM 控制
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 硬件电路中信息的传递方式【重点了解】
- 规定时间周期内容高低电平变化。
- 举例:一秒钟时间,每 125 毫秒完成一次高低电平反馈
- 高电平对应 1 低电平对应 0。当前设备 1 秒钟传递一个字节。
- 图例传递的数据为 1011 0001

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

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

11.2 DHT11 概述


11.3 DHT11 工作方式
DHT11 和 MCU 进行通信,对应有三个阶段
- MCU 告知 DHT11 进行数据采样传递,唤醒 DHT11,对应 GPIO 引脚工作模式为输出模式
- DHT11 告知 MCU 设备已准备就绪,准备开始传递有效数据,对应 GPIO 引脚工作模式为输入模式。
- DHT11 发送 40 位数据内容【小重点】
- 需要完成单一数据位 0 和 1 接收
- 每接收 8 个数据位对应一个字节
- 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 模式,例如
- IO11 引脚复用配置为 HI_IO_FUNC_GPIO_11_PWM2_OUT
- 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.5 ms ,低电平 18.5 ms
- 顺时针旋转状态 高电平 1 ms ,低电平 19 ms
- 逆时针旋转平 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",
]
}
更多推荐
所有评论(0)