本示例将演示如何利用启航KP_IOT主控板和智能风扇模块进行案例开发。
模块介绍
智能风扇模块主要的部件有STH30温湿度传感器,一个红外传感器,一个led灯,一个按键和电机,该模块能够实现按键控制电机的启停,电机启动一共分三档,按一次按键增加一个档位,控制电机这一部分用到了pwm,和gpio的知识点。该模块上的STH30传感器可以监控环境温湿度,STH30是通过i2c进行数据交互的,采集的数据还可以显示在oled屏上,oled屏是通过spi进行数据交互。模块上的红外传感器能够实现对物体的检测,当红外传感器检测到物体时led灯会被点亮。
智能风扇模块
主要试验步骤
我们将调用motor_demo()函数,我们就可以在motor_module.c文件中motor_demo()函数声明中完成电机的控制功能,电机控制需要用到pwm,按键需要用到一个gpio,第一步先对io口进行复用,像i2c,pwm,spi,uart等这样功能性引脚复用是在wifiiot/init/app_io_init.c文件中完成的。
查看电路原理图和芯片手册中可以知道电机是使用的pwm2,知道电机使用的是pwm2之后,就要知道pwm2是哪个gpio引脚输出的,在include/hi_io.h中可以查看,每个gpio引脚可以复用的功能。
gpio2管能够复用成gpio,uart1_rts,spi,pwm2_out等功能,我们需要用到的是pwm2_out,所以在wifiiot/init/app_io_init.c中将gpio_2复用成pwm2_out功能,设置引脚功能的函数hi_u32
hi_io_set_func(hi_io_name id, hi_u8 val)的具体功能介绍可以在include/hi_io.h中可以查看。
pwm设置完成之后在motor_module.c文件中motor_gpio_io_init()函数中对按键接入的引脚进行复用,从原理图可看到按键KEY-1是接GPIO_05。
- 步骤1 按键对应io5之后在motor_gpio_io_init()中将io5复用成gpio
ret = hi_io_set_func(HI_IO_NAME_GPIO_5, HI_IO_FUNC_GPIO_5_GPIO);
if (ret != HI_ERR_SUCCESS) {
printf("===== ERROR ===== gpio -> hi_io_set_func ret:%d\r\n", ret);
return;
}
printf("----- gpio5 fan set func success-----\r\n");
ret = hi_gpio_set_dir(HI_GPIO_IDX_5, HI_GPIO_DIR_IN);
if (ret != HI_ERR_SUCCESS) {
printf("===== ERROR ===== gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret);
return;
}
printf("----- gpio set dir success! -----\r\n");
io设置完成之后,就可以进行功能的编写了,我们想要实现的功能是通过按键控制电机的启停,电机有三个档位,按键按一次增加一个档位,电机处于三档时再按一次按键电机将停止。首先我们要创建一个任务去实时监听按键接入io引脚的状态,OpenHarmony系统中任务的创建调用hi_u32
hi_task_create(hi_u32 *taskid, const hi_task_attr attr,hi_void (*task_route)(hi_void *), hi_void *arg);
- 步骤2 创建的电机任务的属性,包括任务优先级,任务栈的大小,任务名,在任务处理函数中去实现我们想要的功能
static unsigned int g_MonitorTask;
const hi_task_attr MonitorTaskAttr = {
.task_prio = 20,
.stack_size = 4096,
.task_name = "BuggyNetworkMonitorTask",
};
void *MonitorOledTask(void * para)
{
while(1){
test_led_screen();
printf("OLED task \r\n");
}
return NULL;
}
void *MonitorMotorTask(void * para)
{
while(1){
gpio_getval();
infrared_ctrl();
}
return NULL;
}
- 步骤3 在gpio_getval()实现对电机控制;
hi_void gpio_getval(hi_void)
{
hi_u32 ret;
int temp;
static int key = 0;
hi_gpio_value gpio_val_1 = HI_GPIO_VALUE1;
temp = infrared_ctrl();
ret = hi_gpio_get_input_val(HI_GPIO_IDX_5, &gpio_val_1);
if (ret != HI_ERR_SUCCESS) {
printf("===== ERROR ===== gpio -> hi_gpio_get_input_val ret:%d\r\n", ret);
return;
}
if(gpio_val_1 == 0){
sleep(1);
if(gpio_val_1 == 0){
key++;
}
switch(key){
case 0:
break;
case 1:
motor_pwm_start(1);
break;
case 2:
motor_pwm_start(2);
break;
case 3:
motor_pwm_start(3);
break;
default:
printf("invalid mode \r\n");
}
if(key >= 4 || key == 0 || temp == 1){
key = 0;
ret = hi_pwm_stop(HI_PWM_PORT_PWM2);
if(ret != 0){
printf("hi_pwm_stop failed \r\n");
}
}
}
}
pwm波控制电机输出部分程序:
在使用pwm之前需要先对pwm进行初始化,初始化pwm只需要调用hi_pwm_init(parm),函数中的参数(parm)是需要初始化的端口,使用的pwm2需要填宏定义HI_PWM_PORT_PWM2,具体宏定义含义在include/hi_pwm.h中有说明。
- 步骤4 对端口的初始化只需要完成一次,所以在创建电机任务之前调用一次motor_pwm_init()即可
hi_void motor_pwm_init(hi_void)
{
int ret = -1;
ret = hi_pwm_deinit(HI_PWM_PORT_PWM2);
if(ret != 0){
printf("hi_pwm_deinit failed :%#x \r\n",ret);
}
ret = hi_pwm_init(HI_PWM_PORT_PWM2);
if(ret != 0){
printf("hi_pwm_init failed :%#x \r\n",ret);
}
ret = hi_pwm_set_clock(PWM_CLK_160M);
if(ret != 0){
printf("hi_pwm_set_clock failed ret : %#x \r\n",ret);
}
}
对电机速度的控制实际上就是控制pwm的占空比和频率,想要输出不同占空比的pwm波调用函数hi_u32
hi_pwm_start(hi_pwm_port port, hi_u16 duty, hi_u16
freq);参数port表示端口号,duty占空比值,freq频率。
在我们使用的模块中,时钟频率默认是160000000hz,所以可以定义一个宏去表示时钟频率,这里我们是用PWM_CLK_FREQ表示时钟频率,分频倍数165535,频率范围就是2441160000000(频率=时钟源频率/分频倍数),我们用最低频率就可以了,所以将频率用一个宏freq去表示值为2441。
- 步骤5 motor_pwm_start(unsigned int duty)中duty就表示占空比,想让pwm2端口输出多少占空比的波形,直接在调用该函数时传入占空比值就可以了。
hi_void motor_pwm_start(unsigned int duty)
{
int ret = 0;
DBG("motor start \r\n");
if(duty == 0){
ret = hi_pwm_stop(HI_PWM_PORT_PWM2);
if(ret != 0){
printf("hi_pwm_start failed ret : %#x \r\n",ret);
}
}
ret = hi_pwm_start(HI_PWM_PORT_PWM2, duty*(PWM_CLK_FREQ/freq)/100, PWM_CLK_FREQ/freq);
if(ret != 0){
printf("hi_pwm_start failed ret : %#x \r\n",ret);
}
}
- 步骤6 motor_pwm_start(unsigned int duty)中duty就表示占空比,想让pwm2端口输出多少占空比的波形,直接在调用该函数时传入占空比值就可以了。
hi_void motor_demo(hi_void)
{
int ret;
motor_gpio_io_init();
motor_pwm_init();
ret = hi_task_create(&g_MonitorTask,
&MonitorTaskAttr,
MonitorMotorTask,
NULL);
if (ret < 0) {
printf("Create monitor motor task failed [%d]\r\n", ret);
return;
}
return;
}
红外传感器模块
红外传感器有一个发射端和一个接收端,当发射端发出的电磁波被挡住返回,接收端接收到后红外传感器接到模块上的引脚就会从低电平变成高电平,所以我们可以通过监控红外传感器的引脚电平高低来判断是否检测到物体,目前实现的现象是,当检测到物体时led就会被点亮,没检测到物体led就灭。
写程序之前我们要先确定红外传感器接入的引脚和led接入的引脚,从原理图中可以看到红外传感器LED_infrared接入的引脚是GPIO_07,LED_SW1灯接入的引脚是GPIO_06。
- 步骤1 知道红外传感器和led接入的引脚后,就需要对相应的IO进行复用,因为是同一个模块所以可以在motor_gpio_io_init()中实现io7,io8的复用。
ret = hi_gpio_set_dir(HI_GPIO_IDX_8, HI_GPIO_DIR_OUT);
if (ret != HI_ERR_SUCCESS) {
printf("===== ERROR ===== gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret);
return;
}
ret = hi_io_set_func(HI_IO_NAME_GPIO_7, HI_IO_FUNC_GPIO_7_GPIO);
if (ret != HI_ERR_SUCCESS) {
printf("===== ERROR ===== gpio -> hi_io_set_func ret:%d\r\n", ret);
return;
}
printf("----- io set func success-----\r\n");
ret = hi_gpio_set_dir(HI_GPIO_IDX_7, HI_GPIO_DIR_IN);
if (ret != HI_ERR_SUCCESS) {
printf("===== ERROR ===== gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret);
return;
}
printf("----- gpio set dir success! -----\r\n");
- 步骤2
对io功能复用完成后,就可以完成对led控制这一部分的功能了,首先这两引脚的默认值都是低电平,所以先将这两个引脚赋值HI_GPIO_VALUE0,然后根据gpio_val_7引脚的电平高低来判断红外传感器是否检测到物体,检测到物体是将gpio_val_8置为高电平点亮led。
hi_void infrared_ctrl(hi_void)
{
hi_u32 ret;
hi_gpio_value gpio_val_7 = HI_GPIO_VALUE0;
hi_gpio_value gpio_val_8 = HI_GPIO_VALUE0;
ret = hi_gpio_get_input_val(HI_GPIO_IDX_7, &gpio_val_7);
if (ret != HI_ERR_SUCCESS) {
printf("===== ERROR ===== gpio -> hi_gpio_get_input_val ret:%d\r\n", ret);
return;
}
if(gpio_val_7 == 1){
hi_gpio_set_ouput_val(HI_GPIO_IDX_8,HI_GPIO_VALUE1);
}else{
hi_gpio_set_ouput_val(HI_GPIO_IDX_8,HI_GPIO_VALUE0);
}
}
- 步骤3 功能完成后就需要在适合的时机去调用,因为红外线监控也是需要实时监控gpio引脚,和监控按键一样所以可以在电机任务中去调用。
void *MonitorMotorTask(void * para)
{
while(1){
gpio_getval();
infrared_ctrl();
}
return NULL;
}
sht3x温湿度传感器模块
SHT3x-DIS是Sensirion新一代的温湿度传感器,精度为±2%RH和±0.3℃,输入电压范围从2.4V到5.5V,采用IIC总线接口,速率可达1MHz。测量温湿度范围分别为是-40℃ ~ 125℃和0 ~ 100%。具体规格和原理参考说明手册。
void SHT3X_init(void)
{
int ret = 0;
unsigned short data[2] = {0};
SHT3X_SoftReset();
SHT3x_WriteCMD(CMD_READ_SERIALNBR);
SHT3x_WriteCMD(CMD_MEAS_PERI_2_M);
}
void SHT3X_ReadMeasurementVal(unsigned int para)
{
(void) para;
static int cunt = 0;
static float humidity = 0.0;
static float temperature = 0.0;
SHT3X_ReadMeasurementBuffer(&temperature,&humidity);
}
void SHT3X_ReadMeasurementBuffer(float* temperature, float* humidity)
{
unsigned int rawValueTemp = 0;
SHT3x_WriteCMD(CMD_FETCH_DATA);
SHT3x_Read4BytesDataAndCrc((unsigned short *)&rawValueTemp);
dump_buf((unsigned char *)&rawValueTemp,sizeof(rawValueTemp));
*temperature = SHT3X_CalcTemperature(rawValueTemp);
*humidity = SHT3X_CalcHumidity(*((unsigned short *)(&rawValueTemp)+1));
Temperature = *temperature;
Humidity = *humidity;
DBG("temp :%f,hum :%f \r\n",Temperature,Humidity);
}
static float SHT3X_CalcTemperature(unsigned short rawValue)
{
return 175.0f * (float)rawValue / 65535.0f - 45.0f;
}
static float SHT3X_CalcHumidity(unsigned short rawValue)
{
return 100.0f * (float)rawValue / 65535.0f;
}
int SHT3x_Read4BytesDataAndCrc(unsigned short *data)
{
int ret = -1;
unsigned char sendbuf[2] = {0};
unsigned char rcvbuf[6] = {0};
hi_i2c_data sht3x_i2c_data = { 0 };
sht3x_i2c_data.send_buf = sendbuf;
sht3x_i2c_data.send_len = sizeof(sendbuf);
sht3x_i2c_data.receive_buf = rcvbuf;
sht3x_i2c_data.receive_len = sizeof(rcvbuf);
if(data == NULL){
DBG("invalid para \r\n");
return ret;
}
ret = hi_i2c_read(0, ((unsigned char)0x44) << 1 | 0x01, &sht3x_i2c_data);
if(ret != 0){
DBG("hi_i2c_read failed ret :%#x \r\n",ret);
return ret;
}
ret = SHT3X_CheckCrc(rcvbuf,2,rcvbuf[2]);
if(ret != NO_ERROR){
DBG("read serial number crc check failed \r\n");
return ret;
}
ret = SHT3X_CheckCrc(&rcvbuf[3],2,rcvbuf[5]);
if(ret != NO_ERROR){
DBG("read serial number crc check failed \r\n");
return ret;
}
data[0] = rcvbuf[0] << 8 | rcvbuf[1];
data[1] = rcvbuf[3] << 8 | rcvbuf[4];
return 0;
}
hi_void motor_demo(hi_void)
{
int ret;
SHT3X_init();
ret = hi_task_create(&g_MonitorTask,
&MonitorTaskAttr,
MonitorSthTask,
NULL);
if (ret < 0) {
printf("Create monitor motor task failed [%d]\r\n", ret);
return;
}
return;
}
到这一步温湿度传感器的程序已经编写完成了,可以将程序进行编译然后下载到模组中验证一下是否可以读取温湿度,如果能读取到温湿度,在日志中会打印出读取的数据。
OLED显示模块
采集完成数据之后可以在oled模块上显示,oled模块的具体开发详见oled开发流程知道,这里是直接使用oled去显示SHT3X读取的温湿度,显示温湿度首先我们要对oled进行初始化,我们使用的spi0所以初始化时spi_id=0。
hi_void screen_spi_master_init(hi_spi_idx spi_id){
int ret = -1;//screen_ERR;
test_spi_para spi_para; //test_spi_para结构体是spi的基础属性,定义在oled_module/spi_screen.h文件中
spi_para.spi_id = spi_id;
spi_para.irq = HI_FALSE;
spi_para.cfg_info.data_width = HI_SPI_CFG_DATA_WIDTH_E_8BIT;
spi_para.cfg_info.cpha = HI_SPI_CFG_CLOCK_CPHA_0;
spi_para.cfg_info.cpol = HI_SPI_CFG_CLOCK_CPOL_0;
spi_para.cfg_info.fram_mode = HI_SPI_CFG_FRAM_MODE_MOTOROLA;
spi_para.cfg_info.endian = HI_SPI_CFG_ENDIAN_LITTLE;
spi_para.slave = HI_FALSE;
spi_para.lb = HI_FALSE;
spi_para.dma_en = HI_FALSE;
spi_para.cfg_info.freq = 2000000;
test_spi_printf("app_demo_spi_test_cmd_mw_sr Start");
ret = screen_spi_init(spi_para.spi_id, &(spi_para.cfg_info), spi_para.slave);
if (ret == HI_ERR_SUCCESS) {
test_spi_printf("SPI init succ!");
} else {
test_spi_printf("SPI init fail! %x ", ret);
return;
}
hi_spi_set_loop_back_mode(spi_para.spi_id, spi_para.lb);
hi_sleep(1000);
hi_spi_set_irq_mode(spi_para.spi_id, spi_para.irq);
hi_spi_set_dma_mode(spi_para.spi_id, spi_para.dma_en);
hi_sleep(1000);
}
- 步骤2 初始化完成之后,需要创建一个oled任务去完成显示功能
ret = hi_task_create(&g_MonitorTask,
&MonitorTaskAttr,
MonitorOledTask,
NULL);
if (ret < 0) {
printf("Create monitor oled task failed [%d]\r\n", ret);
return;
}
void *MonitorOledTask(void * para)
{
while(1){
test_led_screen();
printf("OLED task \r\n");
}
return NULL;
}
温湿度的显示主要是由TEST_Menu2()显示的,这个oled模块显示数字有一个特点,它显示20这样的两位数时,是将这两位数分开来显示的,先显示2再显示0,我们读取的温度是一个浮点型数据,所以在显示时需要将各个位上的数字分离出来。
void TEST_Menu2(void)
{
extern float Temperature; //Temperature的读取是在不同的文件中,所以想在这个文件中使用就需要将Temperature定义成全局变量,这边引用时加extern
extern float Humidity;
printf("Temperature:%f Humidity:%f \r\n",Temperature,Humidity);
int a = 0;
int b = 0;
int c = 0;
int d = 0;
int e = 0;
int f = 0;
int g = 0;
a = Temperature;
b = a / 10; //整除获取十位上的数据
c = a % 10; //取余获取个位上的数据
d = (Temperature - a) * 10; //Temperature为浮点型数据,a为整形,相减之后就是小数,再乘十,获取到的就是小数后的第一位
e = Humidity;
f = e / 10; //整除获取十位上的数据
g = e % 10; //取余获取各位上的数据
printf("b:%d c:%d d:%d f:%d g:%d\r\n",b,c,d,f,g);
u8 i;
//图形界面的绘制
GUI_DrawLine(0, 10, WIDTH-1, 10,1);
GUI_DrawLine(WIDTH/2-1,11,WIDTH/2-1,HEIGHT-1,1);
GUI_DrawLine(WIDTH/2-1,10+(HEIGHT-10)/2-1,WIDTH-1,10+(HEIGHT-10)/2-1,1);
GUI_ShowString(0,1,"2021-08-1",8,18);
GUI_ShowString(14,HEIGHT-1-10,"Cloudy",8,1);
GUI_ShowString(WIDTH/2-1+2,13,"TEMP",8,1);
GUI_DrawCircle(WIDTH-1-19, 25, 1,2);
GUI_ShowString(WIDTH-1-14,20,"C",16,1);
GUI_ShowString(WIDTH/2-1+2,39,"HUMI",8,1);
GUI_DrawBMP(6,16,51,32, BMP5, 1);
//温湿度的显示
GUI_ShowNum(WIDTH/2-1+9,20,b,1,16,1); //温度的十位数字显示
GUI_ShowNum(WIDTH/2-1+9+8,20,c,1,16,1); //温度的十位数字显示
GUI_ShowString(WIDTH/2-1+9+8+8,20,".",16,1); //小数点显示
GUI_ShowNum(WIDTH/2-1+9+8+16,20,d,1,16,1); //温度的小数显示
GUI_ShowNum(WIDTH/2-1+5,46,f,1,16,1); //湿度的十位显示
GUI_ShowNum(WIDTH/2-1+5+8,46,g,1,16,1); //湿度的个位显示
GUI_ShowString(WIDTH/2-1+5+8+8,46,"/rh",16,1); //湿度的单位显示
sleep(2);
}
hi_void motor_demo(hi_void)
{
int ret;
motor_gpio_io_init();
SHT3X_init();
motor_pwm_init();
hi_spi_deinit(HI_SPI_ID_0);
screen_spi_master_init(0);
ret = hi_task_create(&g_MonitorTask,
&MonitorTaskAttr,
MonitorOledTask,
NULL);
if (ret < 0) {
printf("Create monitor oled task failed [%d]\r\n", ret);
return;
}
ret = hi_task_create(&g_MonitorTask,
&MonitorTaskAttr,
MonitorMotorTask,
NULL);
if (ret < 0) {
printf("Create monitor motor task failed [%d]\r\n", ret);
return;
}
ret = hi_task_create(&g_MonitorTask,
&MonitorTaskAttr,
MonitorShtTask,
NULL);
if (ret < 0) {
printf("Create monitor motor task failed [%d]\r\n", ret);
return;
}
return;
}
- 修改 applications / sample / wifi-iot / app / 路径下 BUILD.gn 文件,指定 motor_module 参与编译。
"22_KP_SHT30_example:motor_module",
运行结果
将智能风扇模块和oled模块安装在开发板上,将上面编译好的程序下载到模组上验证温湿度的显示。