鸣涧 发表于 2022-8-5 17:30:42

基于 OpenHarmony 操作系统开发的智能风扇模块

基于 OpenHarmony 操作系统开发的智能风扇模块



本示例将演示如何利用启航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

/*gpio5按键控制电机速度*/
    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, //优先级范围20~30之间
    .stack_size = 4096, //任务大小
    .task_name = "BuggyNetworkMonitorTask",//任务名称,可自行修改
};
void *MonitorOledTask(void * para) /* OLEDtask处理函数 */
{
    while(1){
      test_led_screen();
      printf("OLED task \r\n");
    }
    return NULL;
}
// 电机task处理函数
void *MonitorMotorTask(void * para)
{
    while(1){
      gpio_getval();//主要任务,对电机的控制在此函数中实现
      infrared_ctrl();
      //printf("fan task \r\n");
    }
    return NULL;
}


步骤3 在gpio_getval()实现对电机控制;

//按键控制电机程序
hi_void gpio_getval(hi_void)
{
    hi_u32 ret;
    int temp;
    static int key = 0;
    //将gpio_val_1置为1,置为1是因为从原理图中可知按键默认是高电平,按下为低电平
    hi_gpio_value gpio_val_1 = HI_GPIO_VALUE1;
    temp = infrared_ctrl();
    //获取gpio5引脚电平,并赋值给gpio_val_1
    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;
    }
    //printf("----- gpio input val is:%d. -----\r\n", gpio_val_1);
    //当gpio_val_1为低电平是说明按下了按键,按1次按键增加一个档位
    if(gpio_val_1 == 0){
         //休眠1s是为了消除按键的抖动
      sleep(1);
      if(gpio_val_1 == 0){
            key++;
      }
      //key表示是几档,根据不同的档位再输出不同占空比的pwm波去控制电机
      switch(key){
      case 0:
            break;
      case 1:
            //电机1档,控制端口2输出1占空比的pwm波
            motor_pwm_start(1);
            break;
      case 2:
            //电机2档,控制端口2输出2占空比的pwm波
            motor_pwm_start(2);
            break;
      case 3:
            //电机3档,控制端口2输出3占空比的pwm波
            motor_pwm_start(3);
            break;
      default:
            printf("invalid mode \r\n");
      }
      //当key大于4或等于0时停止电机
      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");
            }
      }
    }
    //printf("key : %d \r\n",key);
}


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); //初始化端口2
    if(ret != 0){
      printf("hi_pwm_deinit failed :%#x \r\n",ret);
    }
    ret = hi_pwm_init(HI_PWM_PORT_PWM2); //初始化端口2
    if(ret != 0){
      printf("hi_pwm_init failed :%#x \r\n",ret);
    }
    ret = hi_pwm_set_clock(PWM_CLK_160M); //设置端口2的时钟源频率
    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_u16freq);参数port表示端口号,duty占空比值,freq频率。

在我们使用的模块中,时钟频率默认是160000000hz,所以可以定义一个宏去表示时钟频率,这里我们是用PWM_CLK_FREQ表示时钟频率,分频倍数165535,频率范围就是2441160000000(频率=时钟源频率/分频倍数),我们用最低频率就可以了,所以将频率用一个宏freq去表示值为2441。

步骤5motor_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);//停止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); //输出duty占空比的pwm波
    if(ret != 0){
      printf("hi_pwm_start failed ret : %#x \r\n",ret);
    }
}

步骤6motor_pwm_start(unsigned int duty)中duty就表示占空比,想让pwm2端口输出多少占空比的波形,直接在调用该函数时传入占空比值就可以了。

hi_void motor_demo(hi_void)
{   
    int ret;
    motor_gpio_io_init();//完成对按键所接io的复用
    motor_pwm_init();   //对pwm进行初始化
   //创建一个任务去专门处理电机控制任务
ret = hi_task_create(&g_MonitorTask, // task标识//
      &MonitorTaskAttr,
      MonitorMotorTask, // task处理函数 //
      NULL); // task处理函数参数 //
    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的复用。

//设置io8的方向,因为led灯是输出信号,所以设置成out
    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;
    }

    /*gpio7 电机模块红外传感*/
    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");

    //设置io7的方向,因为红外传感器是输入信号,所以设置成输入
    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;//设置gpio_val_7默认值
    hi_gpio_value gpio_val_8 = HI_GPIO_VALUE0;//设置gpio_val_8默认值
    ret = hi_gpio_get_input_val(HI_GPIO_IDX_7, &gpio_val_7);//监控gpio_val_7的电平
    if (ret != HI_ERR_SUCCESS) {
      printf("===== ERROR ===== gpio -> hi_gpio_get_input_val ret:%d\r\n", ret);
      return;
    }
    //printf("----- gpio input val is:%d. -----\r\n", gpio_val_7);
    if(gpio_val_7 == 1){
      hi_gpio_set_ouput_val(HI_GPIO_IDX_8,HI_GPIO_VALUE1); //gpio_val_7为高电平时,将HI_GPIO_IDX_8置为高电平输出
    }else{
      hi_gpio_set_ouput_val(HI_GPIO_IDX_8,HI_GPIO_VALUE0);//gpio_val_7为低电平时,将HI_GPIO_IDX_8置为低电平输出
    }
}

步骤3功能完成后就需要在适合的时机去调用,因为红外线监控也是需要实时监控gpio引脚,和监控按键一样所以可以在电机任务中去调用。

void *MonitorMotorTask(void * para) /* 电机task处理函数 */
{
    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%。具体规格和原理参考说明手册。

步骤1初始化sth3x

void SHT3X_init(void)
{
    int ret = 0;
    unsigned short data = {0};
    SHT3X_SoftReset(); //软件复位SHT3X
    SHT3x_WriteCMD(CMD_READ_SERIALNBR); //向i2c发送读命令   
    SHT3x_WriteCMD(CMD_MEAS_PERI_2_M); //设置读取周期为2hz
}

步骤2读取测量数据

}
//将读取的数据转换成浮点型的湿度
static float SHT3X_CalcHumidity(unsigned short rawValue)
{
    return 100.0f * (float)rawValue / 65535.0f;//转换公式
}
//读取4字节数据的具体实现
int SHT3x_Read4BytesDataAndCrc(unsigned short *data)
{
    int ret = -1;
    unsigned char sendbuf = {0};
    unsigned char rcvbuf = {0};
    hi_i2c_data sht3x_i2c_data = { 0 };//i2c在该模块的数据收发数据都是存储在结构体中的,该结构体可以再include/hi_i2c.h中查看
    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); //sht3x地址为0x44,读温湿度传感器中数据,数据存储在sht3x_i2c_data中
    if(ret != 0){
      DBG("hi_i2c_read failed ret :%#x \r\n",ret);
      return ret;
    }
    ret = SHT3X_CheckCrc(rcvbuf,2,rcvbuf); //将读取的数据进行校验
    if(ret != NO_ERROR){
      DBG("read serial number crc check failed \r\n");
      return ret;
    }
    ret = SHT3X_CheckCrc(&rcvbuf,2,rcvbuf); //将读取的数据进行校验
    if(ret != NO_ERROR){
      DBG("read serial number crc check failed \r\n");
      return ret;
    }
    data = rcvbuf << 8 | rcvbuf;//将数据存到data数组中
    data = rcvbuf << 8 | rcvbuf;
    return 0;      
}


步骤3任务调用

hi_void motor_demo(hi_void)
{   
    int ret;
    SHT3X_init(); //初始化SHT3X   
    ret = hi_task_create(&g_MonitorTask, // task标识 //
      &MonitorTaskAttr,
      MonitorSthTask, // task处理函数 //
      NULL); // task处理函数参数 //
    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文件中




步骤1给spi基础属性赋值

spi_para.spi_id = spi_id; //spi端口号,我们是用的是0
    spi_para.irq = HI_FALSE; //是否启用中断,选择否
    spi_para.cfg_info.data_width = HI_SPI_CFG_DATA_WIDTH_E_8BIT; //传输数据位为8位
    spi_para.cfg_info.cpha = HI_SPI_CFG_CLOCK_CPHA_0;//时钟相位0,采集第一个跳变沿数据
    spi_para.cfg_info.cpol = HI_SPI_CFG_CLOCK_CPOL_0; //时钟极性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; //不采用dma
    spi_para.cfg_info.freq = 2000000;//通讯频率2Mhz
    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); //spi的系统初始化
    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); /* 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); //设置dma模式
    hi_sleep(1000); /* 1000 */
}




步骤2初始化完成之后,需要创建一个oled任务去完成显示功能

ret = hi_task_create(&g_MonitorTask, // task标识 //
      &MonitorTaskAttr,
      MonitorOledTask, // task处理函数 //
      NULL); // task处理函数参数 //
    if (ret < 0) {
      printf("Create monitor oled task failed [%d]\r\n", ret);
      return;
    }
void *MonitorOledTask(void * para) /* OLEDtask处理函数 */
{
      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:%fHumidity:%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:%dc:%dd:%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);
}




步骤1在motor_demo()中去调用

hi_void motor_demo(hi_void)
{   
    int ret;
    motor_gpio_io_init(); //智能风扇模块gpio的初始化
    SHT3X_init(); //温湿度传感器的初始化
    motor_pwm_init(); //pwm的初始化
    hi_spi_deinit(HI_SPI_ID_0);
    screen_spi_master_init(0); //spi的初始化
    ret = hi_task_create(&g_MonitorTask, // task标识 //
      &MonitorTaskAttr,
      MonitorOledTask, // oled task处理函数 //
      NULL); // task处理函数参数 //
    if (ret < 0) {
      printf("Create monitor oled task failed [%d]\r\n", ret);
      return;
    }
    ret = hi_task_create(&g_MonitorTask, // task标识 //
      &MonitorTaskAttr,
      MonitorMotorTask, // motor task处理函数 //
      NULL); // task处理函数参数 //
    if (ret < 0) {
      printf("Create monitor motor task failed [%d]\r\n", ret);
      return;
    }   
    ret = hi_task_create(&g_MonitorTask, // task标识 //
      &MonitorTaskAttr,
      MonitorShtTask, // shttask处理函数 //
      NULL); // task处理函数参数 //
    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模块安装在开发板上,将上面编译好的程序下载到模组上验证温湿度的显示。

页: [1]
查看完整版本: 基于 OpenHarmony 操作系统开发的智能风扇模块