本文章允许涂鸦智能转载使用
去年疫情突发,全国很多地区封城,封小区,让原本备受宠爱的主子与铲屎官异地分离。遇到有准备的铲屎官,主子们还能勉强度日,那些没有准备的主子只能靠吃猫砂,吃垃圾度日,甚至有些小可怜被活活饿死。
疫情缓解后,为了避免这样的事件再次发生,也为了解放懒惰的铲屎官,于是自动猫砂盆,自动喂食器,自动喂水器的需求不断提升。有数据显示,疫情后自动猫砂盆增长879%,自动喂水器增长120%,自动喂食器增长也超一倍。毕竟主子是家里的老大,要吃好喝好还要拉好。
此次DIY自动投食器由涂鸦智能开展的【宠物喂食器】实战营策划,并提供了涂鸦三明治开发套件,其中包括:
涂鸦三明治 Wi-Fi MCU 通信板1
涂鸦三明治H桥直流电机驱动功能板1
涂鸦三明治直流供电电源板*1。
除此之外,用户还需要自行准备MCU控制板和电机等组件,该WiFi模组出厂默认为透传模式,只负责数据转发,不负责数据处理,所以我们只需要准备MCU用于数据处理和外设控制。
(https://auth.tuya.com/register_source=7b811ac2e872ccb62376ba4dfe0568eb)注册开发者账号
以上步骤更加具体配置可参考涂鸦IOT平台产品创建流程(https://shimo.im/docs/HvHRgTtjUDYIvLlA/read)产品创建完成后下载开发资料,建议全部下载,其中MCU SDK的内容会根据你所选择的标准功能不同而不同,为了方便可在选择功能时尽可能多的考虑到需要配置的功能,当然,即使你一个不选,SDK也开放了各个功能的函数,只是被屏蔽,可自行放开。
将WiFi的通信板的串口1接到usb-ttl上,接到电脑,一定是串口1,串口0是查看模组本身的logo的,打开涂鸦调试助手。选择MCU模拟,此时调试助手就相当于是MCU,可以与WiFi模组通信,可用此来调试WiFi模组。选择好串口,波特率默认9600.功能点调试文件选择之前下载的json文件。初始化配置保持默认,点击开始调试。如收到以下数据说明模组与助手连接正常,可以开始调试。
此时我们下载涂鸦智能APP,注册后选择添加设备,在小家电里面找到宠物喂食器,选择2.4G的WiFi网络。输入密码。点击下一步
此时在模组调试助手点击smart配网,手机点击下一步
配网成功后会在手机APP和调试助手同时看到相应信息。连接成功后会定时发送心跳包保持连接。
至此WiFi模组配网完成,改配网信息会保存在WiFi模组内部,下次上电会自动连接该网络。如果更换网络环境需要重置后再次配网。此时可在DP CMD里面测试相关DP点的数据上报,观察有无数据的上报下发。
WiFi模组调试完成之后我们需要调试我们的主控,也就是MCU,在我这里就是STM32F103ZET6。在进行MCU调试之前我们需要先进行SDK的移植,将之前下载的SDK移植到我们的STM32项目中。
本文实在串口驱动实验的基础上搭建的,因为后面需要用到串口;
usart.h
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "sys.h"
#define USART_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART_RX_STA; //接收状态标记
//如果想串口中断接收,请不要注释以下宏定义
void uart_init(u32 bound);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
#endif
usart.c
#include "sys.h"
#include "usart.h"
#include "mcu_api.h"
#include "protocol.h"
#include "system.h"
#include "wifi.h"
//
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void uart_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
void USART1_IRQHandler(void)
{
uint8_t ucTemp;
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
{
ucTemp = USART_ReceiveData(USART1);
uart_receive_input(ucTemp);
}
USART_ClearFlag(USART1,USART_FLAG_RXNE);
}
#endif
写一个发送单字节函数,此函数必须,用于向WiFi模组发送数据。
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
基础工程搭建完成后将SDK放入工程,记得在keil中添加文件路径。
这个时候要是编译,会疯狂报错,主要是官方方便我们修改。
在下图的函数中填入上面写的串口单字节发送函数
1.定义 PID。PRODUCT_KEY 为产品 PID 宏定义。PID 即产品 ID, 为每个产品的唯一标识,可在 IoT 平台的产品详情页面获取。
#define PRODUCT_KEY "gktqnpciyofn****" //开发平台创建产品后生成的16位字符产品唯一标识
2.定义 Wi-Fi 模块工作模式。CONFIG_MODE 为配网方式,支持默认模式(AP 和 SmartConfig 互相切换)、安全模式、防误触模式。建议选择防误触模式。
3.定义模块工作方式(必选)
如果配网按键和 LED 接在 MCU 端,即选择模块和 MCU 配合处理工作模式(常用),保持 WIFI_CONTROL_SELF_MODE 宏定义处于被注释状态。
//#define WIFI_CONTROL_SELF_MODE //Wi-Fi 自处理按键及LED指示灯,如为MCU外接按键/LED指示灯请关闭该宏
如果配网指示灯和按键是接在 Wi-Fi 模块上的,即选择模块自处理工作模式,开启 WIFI_CONTROL_SELF_MODE 宏定义,然后根据实际的硬件连接,将指示灯和按键所连接的 GPIO 脚位填入下面两个宏定义。
#ifdef WIFI_CONTROL_SELF_MODE //模块自处理
#define WF_STATE_KEY 14 //Wi-Fi 模块状态指示按键,请根据实际 GPIO 管脚设置
#define WF_RESERT_KEY 0 //Wi-Fi 模块重置按键,请根据实际 GPIO 管脚设置
#endif
以上三点较为重要,其他可自行安排,都有相关介绍。
void USART1_IRQHandler(void)
{
uint8_t ucTemp;
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
{
ucTemp = USART_ReceiveData(USART1);
uart_receive_input(ucTemp);
}
USART_ClearFlag(USART1,USART_FLAG_RXNE);
}
配网有两种模式AP配网和smart配网,这里仅介绍smart类型。
配网指令有两个函数可以实现:mcu_reset_wifi() 和 mcu_set_wifi_mode()。通常在按键触发配网后,在按键处理函数中调用。mcu_reset_wifi()调用后复位 Wi-Fi 模组,复位后之前的配网信息全部清除。mcu_reset_wifi() 每调用一次,Wi-Fi 模块即在 AP 和 Smart 之间切换一次配网模式。
mcu_set_wifi_mode()参数为SMART_CONFIG和AP_CONFIG。调用后清除配网信息,进入 Smart 模式或者 AP 模式。值了在按键KEY0中断函数中调用 mcu_get_wifi_work_state() 函数主动获取 Wi-Fi 状态。根据 Wi-Fi 状态,写入相应闪灯的模式。通过switch()判断进入何种状态,状态可选参数如下:
exti.c
#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
//#include "beep.h"
#include "mcu_api.h"
#include "protocol.h"
#include "system.h"
#include "wifi.h"
#include "lcd.h"
#include "text.h"
#include "motor.h"
//外部中断0服务程序
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
KEY_Init(); // 按键端口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟
//GPIOE.2 中断线以及中断初始化配置 下降沿触发
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
EXTI_InitStructure.EXTI_Line=EXTI_Line2; //KEY2
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOE.3 中断线以及中断初始化配置 下降沿触发 //KEY1
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line=EXTI_Line3;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOE.4 中断线以及中断初始化配置 下降沿触发 //KEY0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOA.0 中断线以及中断初始化配置 上升沿触发 PA0 WK_UP
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line=EXTI_Line0;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键WK_UP所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键KEY2所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键KEY1所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按键KEY0所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
//外部中断0服务程序
void EXTI0_IRQHandler(void)
{
delay_ms(10);//消抖
if(WK_UP==1) //WK_UP按键
{
LED0=!LED0;
mcu_reset_wifi();
mcu_set_wifi_mode(SMART_CONFIG);
// EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}
//外部中断2服务程序
void EXTI2_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY2==0) //按键KEY2
{
int i=1000;
LED0=!LED0;
for(i=1000;i>0;i--)
motor_control_F(2);
delay_ms(1000);
}
EXTI_ClearITPendingBit(EXTI_Line2); //清除LINE2上的中断标志位
}
//外部中断3服务程序
void EXTI3_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY1==0) //按键KEY1
{
int i=1000;
LED0=!LED0;
for(i=1000;i>0;i--)
motor_control_Z(2);
delay_ms(1000);
//LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
}
void EXTI4_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY0==0) //按键KEY0
{
LED0=!LED0;
switch(mcu_get_wifi_work_state())
{
case SMART_CONFIG_STATE:
Led_Blink_Quick();
POINT_COLOR=BLUE;
Show_Str(30,20,200,16,"处于 Smart 配置状态",16,0);
break;
case AP_STATE:
Led_Blink_Slow();
POINT_COLOR=BLUE;
Show_Str(30,40,200,16,"处于 AP 配置状态",16,0);
break;
case WIFI_NOT_CONNECTED:
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
POINT_COLOR=BLUE;
Show_Str(30,60,200,16,"Wi-Fi 配置完成,正在连接路由器",16,0);
break;
case WIFI_CONNECTED:
GPIO_SetBits(GPIOB,GPIO_Pin_5);
POINT_COLOR=BLUE;
Show_Str(30,80,200,16,"路由器连接成功",16,0);
break;
case WIFI_CONN_CLOUD:
GPIO_SetBits(GPIOB,GPIO_Pin_5);
LCD_Clear(WHITE);//清屏
POINT_COLOR=BLUE;
Show_Str(30,100,200,16,"WIFI已经连接上云服务器",16,0);
break;
default:break;
}
}
EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
}
在外部中断函数0里面添加了mcu_reset_wifi(),复位模组,清除全部配网信息,然后调用mcu_set_wifi_mode()函数,参数添加SMART_CONFIG进入smart模式。在这用TFTLCD屏幕显示是否进入smart配网模式,还可显示其他状态。
现在将MCU与模组通过串口1连接,注意TX与RX反接并共地。按下按键,LED快闪,打开手机APP进行配网,当wifi配置完成灯会熄灭,连接上路由器之后灯会重新点亮,并保持常亮。此过程本人已测试无问题,但是过程比较长不适合贴图,会在视频中展示。
配网完成了,那么怎么执行功能呢?在protocol.c中dp_download_handle()函数可以处理下发的数据。在此函数中会对下发的指令进行归类,我们找到小夜灯的处理,这里可以进行小夜灯指令的处理,处理完成之后会上报数据,用于更新APP数据。我们跳转进dp_download_light_handle()函数,此函数中有具体的处理,针对不同的开或关会进入不同的if函数。我们在对LED的端口初始化后便可以将开关灯填入。达到不同下发指令实现开关灯。开关功能类似,不赘述。
这里没有使用涂鸦提供的H桥驱动板,大家要是想要用,直接上一个12V的减速电机,每分钟12转,驱动力大,速度慢,易于控制。将电机接到驱动板的U和V接线柱上,控制口PWM1和PWM2接到单片机PA2和PA3。给PA2和PA3不同的高低电平就可以实现正反转,因为电机本身速度比较慢,就不用软件进行控制速度了。而且因为场景的关系,不用控制正反两个反向,只控制正转和停止。
我在这里用的就不是涂鸦提供的了,步进电机:28BYJ-48,驱动电路:ULN2003芯片的驱动板,
motor.c
#include "motor.h"
#include "delay.h"
#include "led.h"
#define A1 GPIO_SetBits(GPIOC, GPIO_Pin_3);
#define A2 GPIO_ResetBits(GPIOC, GPIO_Pin_3);
#define B1 GPIO_SetBits(GPIOC, GPIO_Pin_4);
#define B2 GPIO_ResetBits(GPIOC, GPIO_Pin_4);
#define C1 GPIO_SetBits(GPIOC, GPIO_Pin_5);
#define C2 GPIO_ResetBits(GPIOC, GPIO_Pin_5);
#define D1 GPIO_SetBits(GPIOC, GPIO_Pin_6);
#define D2 GPIO_ResetBits(GPIOC, GPIO_Pin_6);
void motor_configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6; //PC.012 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOC.5
GPIO_ResetBits(GPIOC,GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6); //PC.012 输出低
}
void motor_control_F(int n)
{
A1;
delay_ms(n);
A2;
B1;
delay_ms(n);
B2;
C1;
delay_ms(n);
C2;
D1;
delay_ms(n);
D2;
}
void motor_control_Z(int n)
{
A1;
delay_ms(n);
A2;
D1;
delay_ms(n);
D2;
C1;
delay_ms(n);
C2;
B1;
delay_ms(n);
B2;
}
void Motor_Ctrl_Off(void){
GPIO_ResetBits(GPIOC,GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6);
}
编写控制函数,参数为投喂量,通过转动时间来控制投喂量,延时时间需要与宠物的饭盒出粮口搭配,不同出粮口修改延时基数。
随后在dp_download_quick_feed_handle()函数中执行该函数,参数由mcu_get_dp_download_value()函数提供,该函数会提取手机下发的命令。
最后我还加了语音控制功能,下面的文件里有这个工程的全部文件,包括语音模块的设计,就是类似于小度小度那样的智能管家,这里就不在一一介绍了,大家自行下载,看我的演示视频吧。
文件地址:https://download.csdn.net/download/kekebb/16207490