基于涂鸦和ITOP4412开发板的宠物喂食器

更新时间Invalid dateyuanjunke

概况

基于涂鸦模组和ITOP4412开发板的宠物喂食器

​ 在不知不觉中,身边已经充斥着各种智能设备,很多人都过上了智能生活,很多事几乎都不用亲力亲为,不仅人们的生活被智能设备占领,宠物市场也是如此,尤其是宠物智能喂食器已经成为家喻户晓的智能设备,如今也正是宠物智能喂食器飞速发展和技术更成熟的时候,宠物智能喂食器未来是我市场前景将不可估量。近来由于新冠疫情的影响,宠物被独自放置在家中,宠物的温饱成了一个大问题,为了不让铲屎官白天工作的时候担心宠物会饿肚子,宠物喂食器应运而生。

1、作品描述

​ 宠物喂食器,基于涂鸦wifi模组和ITOP4412开发板,因为之前没有使用过单片机,本次采用的是在linux环境下进行开发,涂鸦VWXR2模组出厂已经烧录好固件,所以不需要开发,十分方便,该模组主要是进行数据转发,itop4412开发板主要是进行数据处理,和外设控制。

2、作品介绍

2.1 开发环境

2.1.1硬件设备

​ (1) 涂鸦VWXR2 WIFI通信模组

​ (2) 涂鸦12V电源板

​ (3) 涂鸦H桥电机驱动板

​ (4) ITOP4412开发板

​ (5) 385直流电机

2.2 主要实现的功能

2.1.1 通过涂鸦APP直接进行喂食;
2.1.2 定时喂食,当没有网络时,依旧可以根据设置的闹钟来自动喂食 2.1.3 小夜灯模式
2.1.4 语音唤醒

2.3 后续更新

物料清单

硬件 (3)
  • 涂鸦三明治语音 Wi-Fi 通信板(VWXR2)

    数量:1

    产品型号:TYDE5-VWXR2-MCU-1

  • 涂鸦三明治H桥直流电机驱动功能板

    数量:1

    产品型号:TYDE5-H-BRIDGE-1

  • 涂鸦三明治直流供电电源板

    数量:1

    产品型号:TYDE5-POWER-DC_DC-1

步骤

3、开发流程

3.1 使用涂鸦IOT平台创建项目

3.1.1 进入涂鸦IOT平台创建产品

​ 在涂鸦IOT平台找到 创建产品 -> 小家电 -> 宠物喂食器

3.1.2 给宠物喂食器添加功能点

3.1.3 选择设备面板

​ 这里可以选择公版面板,也可以选择自定义面板,当现有的需求公版面板不满足时,就需要使用自定义面板了; 这里我选择公版面板

3.1.4 硬件开发下载SDK

​ 后期根据我们下载的MCU SDK进行开发

3.2 熟悉涂鸦模组的通讯协议

​ 首先下载完SDK以后,分析其源码,花了一些时间理清了大致的框架,用户主要做的是命令下发后,主控板接收到wifi模组转发的数据,经过SDK对数据的解析,然后根据对应的数据帧中的命令字

字段进入相应的功能处理函数,最后用户在功能函数中,对下发的数据进行处理,例如,当接收到客户端下发的快速喂食命令,便执行打开电机的操作。

3.3 将涂鸦模组的SDK移植到linux

​ 这个SDK的移植相对简单,因为不涉及依赖库的问题,所以只需要使用交叉编译器进行编译然后使用nfs服务挂载到开发板即可,这里我使用的交叉编译器是arm-linux-4.4.3。

3.3.1 首先完善串口的读写操作

​ 在linux中对于串口的读写,并不复杂,主要步骤 1、打开串口 2、对串口参数进行配置 3、对串口进行读写;

3.3.1.1对串口的读取操作
int uart_receive(void)
{
	int ret ;
	unsigned char c;                                                                                   	
	do{				
		ret = read(fd_wifi_uart, &c, 1);					
	}while(ret == -1 && errno == EINTR);	
	if (-1 == ret)  
	{
		perror("read");
	//	return -1;
	}
	if (1 == ret)	
	uart_receive_input(c);	
//	printf("recv:0x%x\n",c);
	
	return ret;
}
3.3.1.2 将数据通过串口发送
static void wifi_uart_write_data(unsigned char *in, unsigned short len)
{
    if((NULL == in) || (0 == len)) {
        return;
    }
/* 串口发送数据*/
if (-1 == write(fd_wifi_uart, in, len))
	{
		perror("uart write");
		return ;
	}
}

3.3.2 处理APP下发的喂食指令

​ 因为喂食操作需要对电机进行操作,需要将电机的驱动编译到内核中去,但是电机驱动占用了我的摄像头驱动,所以只能先将摄像头驱动给砍掉,导致我的远程监控功能暂时搁浅,后期想方法解决。

对于喂食操作也相对简单,例如快速喂食,当APP下发指令后,开发板收到指令进入相应的功能函数,只需要打开电机或者关闭电机。

3.3.2.1 快速喂食
static unsigned char dp_download_quick_feed_handle(const unsigned char value[], unsigned short length)
{
    //示例:当前DP类型为BOOL
    unsigned char ret;
    //0:关/1:开
    unsigned char quick_feed;
    
    quick_feed = mcu_get_dp_download_bool(value,length);
    if(quick_feed == 0) {
        //开关关
    }else {
    	// 打开电机开关
        //开关开
		printf("quick feed\n");
		motor_on('R', 10, 1);  //打开电机 
		feed_status.quick_feed = 1;
		slow_feed = 0;
	} 
    //处理完DP数据后应有反馈        
    // 数据处理完上报
    ret = mcu_dp_bool_update(DPID_QUICK_FEED,quick_feed);
    if(ret == SUCCESS)
        return SUCCESS;
    else
        return ERROR;
}
3.3.2.2 手动喂食

​ 根据下发的value值,对电机转动的时长进行控制;

static unsigned char dp_download_manual_feed_handle(const unsigned char value[], unsigned short length)
{
    //示例:当前DP类型为VALUE
    unsigned char ret;
    unsigned long manual_feed;
    
    manual_feed = mcu_get_dp_download_value(value,length);
    /*
    //VALUE类型数据处理
    
    */
	//喂食value份
		
    motor_on('R', manual_feed * 10, 2);  //打开电机   
	printf("manual feed value : %ld\n", manual_feed);
	feed_status.manual_feed = manual_feed;
    //处理完DP数据后应有反馈
    ret = mcu_dp_value_update(DPID_MANUAL_FEED,manual_feed);
    if(ret == SUCCESS)
        return SUCCESS;
    else
        return ERROR;
}

3.3.3 处理喂食计划

3.3.3.1 同步本地时钟

​ 要执行定时操作,需要对本地时钟进行同步,同步时钟需要先将protocol.h中的 SUPPORT_MCU_RTC_CHECK 宏打开

#define         SUPPORT_MCU_RTC_CHECK                //开启校时功能

1 然后进行时钟同步,因为时钟同步需要在wifi模组联网之后才能进行,这里 在get_wifi_status()获取wifi状态函数中,当 wifi状态为已连接上路由器且连接到云端时,开发板便向wifi模组发送时钟同步命令

        case 4:
            //wifi工作状态5
            // 当wifi模组连接上路由器时,开发板向wifi模组获取格林威治时间
            //向wifi模组发送 获取格林威治时间的指令           55 AA 03 0C 00 00 0E
            //void wifi_uart_write_frame(unsigned char fr_type, unsigned char fr_ver, unsigned short len)
            //wifi_uart_write_frame(0x03, 0x0c, 0x00);
			printf("wifi连接上云端\n");
			mcu_get_system_time(); //获取本地时间 

获取本地时钟,然后进行时钟同步

void mcu_write_rtctime(unsigned char time_[])
{
 //   #error "请自行完成RTC时钟写入代码,并删除该行"
 	/*
    Time[0] 为是否获取时间成功标志,为 0 表示失败,为 1表示成功
    Time[1] 为年份,0x00 表示 2000 年
    Time[2] 为月份,从 1 开始到12 结束
    Time[3] 为日期,从 1 开始到31 结束
    Time[4] 为时钟,从 0 开始到23 结束
    Time[5] 为分钟,从 0 开始到59 结束
    Time[6] 为秒钟,从 0 开始到59 结束
    Time[7] 为星期,从 1 开始到 7 结束,1代表星期一
   */
	if(time_[0] == 1) {
        //正确接收到wifi模块返回的本地时钟数据
		struct tm stpr;
	 	stpr.tm_year = time_[1] + 100;   //0x00 代表2000年  自1900年经过了多少年
	 	stpr.tm_mon = time_[2] - 1;		// 从0 - 11
	 	
		stpr.tm_hour = time_[4];   // 格林威治时区比北京时间多了8小时
		stpr.tm_mday = time_[3];	
		stpr.tm_min = time_[5];
	 	stpr.tm_sec = time_[6];
		printf("time[1] :0x%x\n",time_[1]);	
		printf("time[1] :0x%x\n",time_[2]);	
		printf("time[1] :0x%x\n",time_[3]);	
		printf("time[1] :0x%x\n",time_[4]);	
		printf("time[1] :0x%x\n",time_[5]);	
		printf("time[1] :0x%x\n",time_[6]);	
		struct timeval tv;
	 	tv.tv_sec = mktime(&stpr); 	 //将st结构体数据 解析为自1970年1月1日00:00:00以来的秒数							
		tv.tv_usec = 0;
	 	settimeofday(&tv, NULL);  	//更新系统时间
		
		printf("time set done\n");
				
		time_t result = time(NULL);
        printf("%s secs since the Epoch\n",
                asctime(localtime(&result)));
	}else {
        //获取本地时钟数据出错,有可能是当前wifi模块未联网
        fprintf(stderr, "get rtctime is error!\n");
    }
}
3.3.3.2 对下发的喂食计划进行解析

​ 当时钟同步完成后,接收到下发的喂食计划,对喂食计划raw数据进行解析,并且将定时的闹钟写入文件

static unsigned char dp_download_meal_plan_handle(const unsigned char value[], unsigned short length)
{
    //示例:当前DP类型为RAW
    unsigned char ret;
    /*
    //RAW类型数据处理		
    */
	bzero(feed_plan_buf.buf, 128);
    bzero(&feed_plan, sizeof(feed_plan));
	feed_num = length / 5;
	lseek(feedplan_fd, 0, SEEK_SET);
	unsigned int i = 0;
	if (10 >= feed_num)
	for (i = 0; i < feed_num; i++)   //获取所有的喂食计划
	{
		get_week(value[0 + i*5], i);   // 获取计划中的week
		feed_plan[i].hour = value[1 + i*5];
		feed_plan[i].minute = value[2 + i*5];
		feed_plan[i].weight = value[3 + i*5];
		feed_plan[i].swch = value[4 + i*5];	 //开关
		int ret = write(feedplan_fd, &feed_plan[i], sizeof(Feed_Plan));
		printf("write ret %d\n", ret);
	}
	ftruncate(feedplan_fd, sizeof(Feed_Plan) * feed_num);
	kill(cpid, SIGUSR1); // 更新完毕,向子进程发送新信号,使其获取新的喂食计划
	my_memcpy(feed_plan_buf.buf, value, length);  // 更新喂食计划
	feed_plan_buf.length = length;
	//喂食计划        
    //处理完DP数据后应有反馈
	ret = mcu_dp_raw_update(DPID_MEAL_PLAN,value,length);
    if(ret == SUCCESS)
        return SUCCESS;
    else
        return ERROR;
}

​ 这里的闹钟操作是由子进程,子进程主要是对定时计划进行操作,对于喂食计划的同步,是通过父子进程对文件的读写来进行的,当APP更新喂食计划后,父进程通过向子进程发送SIGUSR1信号,来唤醒子进程,然后子进程更新喂食计划,获取新的闹钟;

if (0 == cpid)  //子进程
	{	
		// 获取当前文件偏移量	如果非0,代表feedplan更新;
		int file_seek; //获取当前文件偏移量
		int i = 0;
		int ret = 1;
		get_feedplan();
		printf("child get_feedplan success!\n");
		if (signal(SIGALRM, sig_alrm) == SIG_ERR)  //捕捉SIGALRM信号
		{
			perror("signal");
			exit(EXIT_FAILURE);
		}		
		if (signal(SIGUSR1, sig_usr) == SIG_ERR)// 捕捉父进程发送给子进程的SIGUSR1信号
		{
			perror("signal");
			exit(EXIT_FAILURE);
		}
		while (1)
		{
			Alarm_clock();						
		}
		// 子进程获取喂食计划;		
	}

3.3.4 快速喂食与慢速喂食

​ 因为H桥电路主要是负责电机的正反转,控制其转速的是PWM,在这里是使用GPIO的拉高与拉低来模拟PWM波

int motor_on(const unsigned char direction, unsigned short feed_num, unsigned short speed)
{
	feed_status.feed_report = feed_num / 10;
	if(mcu_dp_value_update(DPID_FEED_REPORT,feed_status.feed_report) == SUCCESS)  //喂食结果上报
		printf ("feed_report:%d\n", feed_status.feed_report);
	
	if(direction == 'R')
	{
		printf ("slow_feed :%d\n", feed_status.slow_feed);
		mcu_dp_enum_update(DPID_FEED_STATE,feeding);
		while (feed_num)
		{
			ioctl(fd_moter, CMD_STEPMOTOR_C, HIGHT);
			ioctl(fd_moter, CMD_STEPMOTOR_D, LOW);
			usleep(3000);
			ioctl(fd_moter, CMD_STEPMOTOR_C, LOW);
			ioctl(fd_moter, CMD_STEPMOTOR_D, LOW);
			if (feed_status.slow_feed == TRUE)
				usleep(6000);    //慢速喂食
			else 
				usleep(3000);	//快速喂食
			feed_num--;	
		}
		mcu_dp_enum_update(DPID_FEED_STATE,done);
		
	}
	set_motor_off();
}

3.3.5 喂食记录上报

​ 这里我是将喂食记录上报,放在了motor控制中,当motor接收到下发的feed_num, 根据feed_num 来确定喂食的分量,然后对喂食分量进行上报

int motor_on(const unsigned char direction, unsigned short feed_num, unsigned short speed)
{
	feed_status.feed_report = feed_num / 10;
	if(mcu_dp_value_update(DPID_FEED_REPORT,feed_status.feed_report) == SUCCESS)  //喂食结果上报
		printf ("feed_report:%d\n", feed_status.feed_report);
	
	if(direction == 'R')
	{
		printf ("slow_feed :%d\n", feed_status.slow_feed);
		mcu_dp_enum_update(DPID_FEED_STATE,feeding);
		while (feed_num)
		{
			ioctl(fd_moter, CMD_STEPMOTOR_C, HIGHT);
			ioctl(fd_moter, CMD_STEPMOTOR_D, LOW);
			usleep(3000);
			ioctl(fd_moter, CMD_STEPMOTOR_C, LOW);
			ioctl(fd_moter, CMD_STEPMOTOR_D, LOW);
			if (feed_status.slow_feed == TRUE)
				usleep(6000);    //慢速喂食
			else 
				usleep(3000);	//快速喂食
			feed_num--;	
		}
		mcu_dp_enum_update(DPID_FEED_STATE,done);
		
	}
	set_motor_off();
}

3.3.6 小夜灯

​ 由于手上的资源比较少,就用开发板上的LED来代替小夜灯,控制起来相对简单,这里不过多介绍

3.4 手机APP端

​ 这里采用的是涂鸦宠物喂食器的公版模板

3.5外壳以及结构设计

您正在浏览的内容为涂鸦开发者平台注册用户自主发布,版权归原作者所有,涂鸦开发者平台不拥有其著作权,亦不承担相应法律责任,涂鸦开发者平台不对该等内容作出不违反、不侵权的陈述与保证。您应知晓并了解您对于该等内容的复制、改编、转发传播等任何其他使用行为应当符合法律法规并应取得相关权利人的许可,您的该等行为所造成的全部相应后果(包括但不限于侵权、违约、受损、与第三方的纠纷等)均应由您个人承担。内容知识产权相关条款可查看涂鸦开发者平台用户协议。如果您发现有涉嫌侵权的内容,请立即通过平台上的联系方式联系平台进行举报并发送有关证据,一经查实,平台将立刻删除涉嫌侵权内容。

喜欢举报