基于STM32F103+涂鸦三明治的宠物自动喂食器
涂鸦三明治的宠物喂食器
- 方案概述
- 方案应用场景
- 技术要点
- 硬件概述
- 涂鸦IOT平台搭建项目
- 通信功能调试
- wifi模组调试
- MCU调试
- 完善功能
- 添加配网功能及指示灯函数
- 添加小夜灯执行功能
- 添加手动喂食执行功能
- 触摸屏手动喂食
- 温湿度采集显示
本文章允许涂鸦智能转载使用
方案概述
方案应用场景
该方案应用于铲屎官们家中的宠物喂食,比如猫咪和狗狗。
喂养宠物的铲屎官都明白,宠物平时的最大消耗就是他们的宠粮,每天要加几次宠粮才能满足宠物对食物的需求。但是每天需要上班赚取生活费的铲屎官,在家里的时间并不多,宠物白天的饮食就是一个大问题,如果早上过多的添加宠粮,会造成宠物不停地进食,造成肥胖等疾病,但是如果添加的过少,在白天家中无人的情况下宠物有得不到及时的食物补充,对宠物的生长也不利。这时候一块自动的定时宠粮喂食器便显得尤为重要。特别是在铲屎官因故长期不在家,造成宠物得不到进食的问题。
该方案设计以下几点功能:
1.自动定时投食
2.自定义每次投食量
3.手机远程投食
4.可查看投食记录
5.小夜灯功能,为凌晨起床的宠物提供照明
6.实时检测温湿度
7.LCD触摸屏显示,无需打开手机便可获得相关信息
8.ws2812环形彩灯,增加美观
技术要点
1.STM32MCU作为主控,通过WiFi模组获取服务器的指令,完成相应处理和判断,如手动喂食。并上传相应数据,如上报余量,电池电量,充电状态。
2.涂鸦WiFi模组作为通信模块用来连接stm32和涂鸦IOT云平台,用来作为数据的透传,将下位机的数据通过MQTT上传至云平台,并将云平台下发的指令转发给下位机,作为上位机和下位机的良好握手。
3.涂鸦IOT云平台提供云端数据储存
4.手机APP的开发,因为涂鸦的IOT平台已经拥有快速开发APP的功能,用户所要做的事情非常少,但是使用模板会显得千篇一律,如果自己开发,还是有一定的难度的。
4.减速电机的控制,此次设计采用的是直流减速电机,控制比较简单,不过此设计太好的电机只会增加难度和成本。(因H桥控制板损坏,已替换为继电器控制。)
5.外壳的搭建,试验版本采用纸壳搭建,后期改用亚克力板或者PLA打印,纸壳肯定不适合宠物,特别是猫咪,对纸壳拥有无与伦比的破坏力。 (此步骤因材料限制等原因,舍弃了)
硬件概述
主控:
主控采用了STM32F103,选用了野火的开发板,因为我想加一个屏幕直接使用开发板会比较方便,虽然成本提高太多,但是造就完了。
其余的硬件大多由涂鸦提供:
涂鸦三明治 Wi-Fi MCU 通信板1
涂鸦三明治H桥直流电机驱动功能板1
涂鸦三明治直流供电电源板*1
对于这三块板想要吐槽一下,第一块的wifi通信板没话说,非常好,第二块的H桥控制经不住折腾,烧了,第三块的电源板电池槽太高,没法与其他两块板一起形成“三明治”。
电机,传感器等外设由我们自己选择。
涂鸦IOT平台搭建项目
一款无线的产品开发就必须依托好用的云平台,此次涂鸦提供了免费的云平台,我们只需要注册好开发者账号便可以依托其强大的IOT平台进行开发。
点此前往 涂鸦智能开发平台进行注册。
注册后可参考官方的涂鸦IOT平台产品创建流程。
产品创建完成后下载开发资料,建议全部下载,其中MCU SDK的内容会根据你所选择的标准功能不同而不同,为了方便可在选择功能时尽可能多的考虑到需要配置的功能,当然,即使你一个不选,SDK也开放了各个功能的函数,只是被屏蔽,可自行放开。
通信功能调试
在进行功能调试之前先看MCU对接方案的原理图:
mcu与涂鸦的wifi模组通过串口进行通信,此处的wifi模组的作用是透传,相当于在MCU和云平台之间进行牵手,只进行消息的转发,不进行处理。手机APP和云平台通过wifi网络与4G网络进行通信。手机与模组之间通过蓝牙进行通信,只不过该应用并未涉及。
wifi模组调试
第一步打开我们之前下载的文件,打开涂鸦调试助手。
将WiFi的通信板的串口1接到usb-ttl上,接到电脑,一定是串口1,串口0是查看模组本身的log的,打开涂鸦调试助手。选择MCU模拟,此时调试助手就相当于是MCU,可以与WiFi模组通信,可用此来调试WiFi模组。选择好串口,波特率默认9600.功能点调试文件选择之前下载的json文件。初始化配置保持默认,点击开始调试。如收到以下数据说明模组与助手连接正常,可以开始调试。
此时我们下载涂鸦智能APP,注册后选择添加设备,在小家电里面找到宠物喂食器,选择2.4G的WiFi网络。输入密码。点击下一步
此时在模组调试助手点击smart配网,手机点击下一步
配网成功后会在手机APP和调试助手同时看到相应信息。连接成功后会定时发送心跳包保持连接。
至此WiFi模组配网完成,改配网信息会保存在WiFi模组内部,下次上电会自动连接该网络。如果更换网络环境需要重置后再次配网。
此时可在DP CMD里面测试相关DP点的数据上报,观察有无数据的上报下发。
MCU调试
WiFi模组调试完成之后我们需要调试我们的主控,也就是MCU,在我这里就是STM32F103。
在进行MCU调试之前我们需要先进行SDK的移植,将之前下载的SDK移植到我们的STM32项目中。
(1),基础工程创建
移植前,我们需要准备一个空的工程,工程中只需要添加一个串口驱动便可。
#ifndef __USART_H
#define __USART_H#include "stm32f10x.h"
#include <stdio.h>// 串口1-USART1
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 9600// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandlervoid USART_Config(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
#endif /* __USART_H */
先在串口的.h文件中进行宏定义。然后开始USART1的初始化,串口接收中断的初始化。
/*** [url=home.php?mod=space&uid=247401]@brief[/url] 配置嵌套向量中断控制器NVIC* @param 无* @retval 无*/
static void NVIC_Configuration(void)
{NVIC_InitTypeDef NVIC_InitStructure;/* 嵌套向量中断控制器组选择 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);/* 配置USART为中断源 */NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;/* 抢断优先级*/NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;/* 子优先级 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;/* 使能中断 */NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;/* 初始化配置NVIC */NVIC_Init(&NVIC_InitStructure);
}/*** [url=home.php?mod=space&uid=247401]@brief[/url] USART GPIO 配置,工作参数配置* @param 无* @retval 无*/
void USART_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 打开串口GPIO的时钟DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);// 打开串口外设的时钟DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);// 将USART Tx的GPIO配置为推挽复用模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);// 将USART Rx的GPIO配置为浮空输入模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);// 配置串口的工作参数// 配置波特率USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;// 配置 针数据字长USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 配置停止位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(DEBUG_USARTx, &USART_InitStructure);// 串口中断优先级配置NVIC_Configuration();// 使能串口接收中断USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); // 使能串口USART_Cmd(DEBUG_USARTx, ENABLE);
}
写一个发送单字节函数,此函数必须,用于向WiFi模组发送数据。
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{/* 发送一个字节数据到USART */USART_SendData(pUSARTx,ch);/* 等待发送数据寄存器为空 */while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
(2)移植SDK
基础工程搭建完成后将SDK放入工程,下图是SDK的结构目录:
添加进SDK后编译会疯狂报错,因为下载的SDK中有很多未屏蔽的注释,都是很重要的注释,可能怕我们找不到关键点吧。
确认 protocol.h 宏定义
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
这三项是很重要的配置,因为篇幅原因,其他配置不再此赘述,代码中均有注释。可自行开放。
移植 protocol.c 文件及函数调用
1.将wifi.h 文件保存至存放 Wi-Fi 相关文件的文件夹中,例如 main.c文件夹。而我是创建了一个WiFi文件夹,WiFi.c只是引用了wifi.h这个头文件,其他为空。
2.在 MCU 串口及其他外设初始化后调用 mcu_api.c 文件中的 wifi_protocol_init() 函数。
3.将 MCU 串口单字节发送函数填入 protocol.c 文件中 uart_transmit_output 函数内,并删除 #error。此步骤在上文已介绍。不赘述。
4.在串口接收中断服务函数里面调用 mcu_api.c 文件内的 uart_receive_input 函数,并将接收到的字符作为参数传入。示例如下
// 串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{uint8_t ucTemp;if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET){ ucTemp = USART_ReceiveData(DEBUG_USARTx);uart_receive_input(ucTemp); } USART_ClearFlag(USART1,USART_FLAG_RXNE);
}
5.在主函数的while(1) 循环后调用 mcu_api.c 文件内的 wifi_uart_service() 函数。该函数用于wifi串口数据处理服务,同时维持心跳。此函数无需任何判断条件,直接调用。在使用该函数时最好不要关闭总中断或串口中断,防止数据丢失,如必要,尽可能短的时间关闭。
至此,SDK移植便已完成,此时可将单片机的串口接到电脑进行调试。此时调试助手选择模组模拟。
为了便于观察,我们在protocol.c文件中将all_data_update() 函数中的所有DP点上传函数打开,默认为0。
此时MCU接到调试助手,打开串口,添加DP点文件,启动调试,便会看到所有DP点的上报。
进入DP CMD添加一个开启小夜灯的指令并下发,便可看到模组成功接收,说明MCU的SDK移植成功。
到此为止,一个完整的自动喂食器的MCU工程便已搭建完成,后续我们只需要解析wifi模组下发的消息,并进行相应的外设控制便可。当然,每次MCU完成动作后也要上传数据给模组。完成服务器和手机APP端的数据刷新。
完善功能
添加配网功能及指示灯函数
前面的准备工作完成后我们需要添加功能,从最重要的配网开始吧,总不能每次使用都插上电脑用助手配网呀,那多不方便,一键上云才是王道。其实语音也可以,嘻嘻嘻,而且音色也不错,是个萌妹子的声音。跑题了,语音功能容后考虑,因为我觉得加个语音功能我家的主子会炸毛,可能会有吃饭恐惧症。
配网有两种模式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 模式。
通常在 while(1) 调用 mcu_get_wifi_work_state() 函数获取 Wi-Fi 状态。根据 Wi-Fi 状态,写入相应闪灯的模式。
通过switch()判断进入何种状态,状态可选参数如下:
所以,我们先开始写按键的驱动以及外部中断:
//按键初始化函数
void KEY_Init(void) //IO初始化
{ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA,PORTE时钟//初始化 WK_UP-->GPIOA.0 下拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //PA0设置成输入,默认下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0}
//外部中断0服务程序
void EXTIX_Init(void)
{EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;KEY_Init(); // 按键端口初始化RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟//GPIOA.0 中断线以及中断初始化配置 上升沿触发 PA0 WK_UPGPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); EXTI_InitStructure.EXTI_Line=EXTI_Line0;/* EXTI 为中断模式 */EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键WK_UP所在的外部中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00; //抢占优先级2, NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure);
}//外部中断0服务程序
void EXTI0_IRQHandler(void)
{mcu_reset_wifi();mcu_set_wifi_mode(SMART_CONFIG);EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}
在外部中断函数0里面添加了mcu_reset_wifi(),复位模组,清除全部配网信息,然后调用mcu_set_wifi_mode()函数,参数添加SMART_CONFIG进入smart模式。然后写两个闪灯函数,用于指示配网状态,Led_Blink_Quick()快闪,用于指示进入smart配网模式,Led_Blink_Slow()慢闪,用于指示进入AP配网模式。
void Led_Blink_Quick(void)
{LED2_ON;Delay(500000);LED2_OFF;Delay(500000);}void Led_Blink_Slow(void)
{LED2_ON;Delay(5000000);LED2_OFF;Delay(5000000);}
最后在主函数的while(1)中添加配网状态判断函数:
get_wifi_status(mcu_get_wifi_work_state());
void get_wifi_status(unsigned char result)
{// #error "请自行完成获取 WIFI 状态结果代码,并删除该行"switch(result) {case 0://处于 Smart 配置状态,即 LED 快闪Led_Blink_Quick();break;case 1://处于 AP 配置状态,即 LED 慢闪Led_Blink_Slow();break;case 2://Wi-Fi 配置完成,正在连接路由器,即 LED 常暗LED2_OFF;break;case 3://WIFI 已配置且连上路由器LED1_ON;break;case 4:LED2_ON;LCD_SetFont(&Font8x16); LCD_SetColors(RED,BLACK);ILI9341_DisplayStringEx(2*48,0*48,16,16,(uint8_t *)"Wifi connected",0);break;case 5://wifi工作状态6break;case 6://wifi工作状态7break;default:break;}
}
现在将MCU与模组通过串口1连接,注意TX与RX反接并共地。按下按键,LED快闪,打开手机APP进行配网,当wifi配置完成灯会熄灭,连接上路由器之后灯会重新点亮,并保持常亮。此过程本人已测试无问题,但是过程比较长不适合贴图,会在视频中展示。
添加小夜灯执行功能
配网完成了,那么怎么执行功能呢?在protocol.c中dp_download_handle()函数可以处理下发的数据。在此函数中会对下发的指令进行归类,我们找到小夜灯的处理
static unsigned char dp_download_light_handle(const unsigned char value[], unsigned short length)
{//示例:当前DP类型为BOOLunsigned char ret;//0:关/1:开unsigned char light;light = mcu_get_dp_download_bool(value,length);if(light == 0) {//开关关LED1_OFF;mcu_dp_bool_update(DPID_LIGHT,0);}else {//开关开LED1_ON;mcu_dp_bool_update(DPID_LIGHT,1);}//处理完DP数据后应有反馈ret = mcu_dp_bool_update(DPID_LIGHT,light);if(ret == SUCCESS)return SUCCESS;elsereturn ERROR;
}
这里可以进行小夜灯指令的处理,处理完成之后会上报数据,用于更新APP数据。我们跳转进dp_download_light_handle()函数,此函数中有具体的处理,针对不同的开或关会进入不同的if函数。我们在对LED的端口初始化后便可以将开关灯填入。达到不同下发指令实现开关灯。开关功能类似,不赘述
添加手动喂食执行功能
此次涂鸦提供了H桥驱动板,那么我们直接上一个12V的减速电机,每分钟12转,驱动力大,速度慢,易于控制。将电机接到驱动板的U和V接线柱上,控制口PWM1和PWM2接到单片机PA2和PA3。给PA2和PA3不同的高低电平就可以实现正反转,因为电机本身速度比较慢,就不用软件进行控制速度了。而且因为场景的关系,不用控制正反两个反向,只控制正转和停止。
编写控制函数,参数为投喂量,通过转动时间来控制投喂量,延时时间需要与宠物的饭盒出粮口搭配,不同出粮口修改延时基数。
void motor_concrol_right(uint8_t tim)//电机正转延时后停止
{GPIO_SetBits(PWM_PORT,PWM2_GPIO);GPIO_ResetBits(PWM_PORT,PWM1_GPIO);Delay(10000000*tim);GPIO_ResetBits(PWM_PORT,PWM1_GPIO);GPIO_ResetBits(PWM_PORT,PWM2_GPIO);
}
随后在dp_download_handle()函数中的case DPID_MANUAL_FEED:条件下执行该函数,参数由mcu_get_dp_download_value()函数提供,该函数会提取手机下发的出粮量。
触摸屏手动喂食
我在无线的基础上添加屏幕,还是触摸屏,毕竟我们在家的时候还用手机投喂,这不是舍近求远吗?直接手动不是很喵吗?
屏幕太贵?增加成本?占用资源?增加功耗?体积太大?墨水屏就行?
这是我们工程师该考虑的问题吗?帅就完了,成本是资本家考虑的事,反正我又不量产,自己家主子用的,就是要贵,买不起我还做不起吗。
那么开始吧,添加屏幕驱动,这里我直接用的野火的电阻触摸屏例程,额,因为屏幕和开发板都是他们家的,开发板太大了,以后考虑做个板子,现在就将就用了,毕竟我还不一定会做成实物,毕竟我外壳还没有。。。。。
那些LCD和触摸板界面初始化就不贴了,主要说我改动的地方。
首先放一张丑图,没有时间美化,仅实现功能。
左边的“加”“减”用于设定手动喂食量,设定好单次喂食量按喂食就会控制电机进行出粮,开灯和关灯就是打开和关闭小夜灯,主界面会实时显示服务器在线状态和单次喂食量,总喂食量以及小夜灯的状态。
代码首先要修改按钮的初始化
/**
* [url=home.php?mod=space&uid=247401]@brief[/url] Touch_Button_Init 初始化按钮参数
* @param 无
* @retval 无
*/
void Touch_Button_Init(void)
{/*第一列,主要为颜色按钮*/button[0].start_x = BUTTON_START_X;button[0].start_y = 0;button[0].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[0].end_y = COLOR_BLOCK_HEIGHT;button[0].para = 1;button[0].touch_flag = 0; button[0].draw_btn = Draw_Num_Button ;button[0].btn_command = Command_Select_Meannum ;button[1].start_x = BUTTON_START_X;button[1].start_y = COLOR_BLOCK_HEIGHT;button[1].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[1].end_y = COLOR_BLOCK_HEIGHT*2;button[1].para = 2;button[1].touch_flag = 0; button[1].draw_btn = Draw_Num_Button ;button[1].btn_command = Command_Select_Light ;button[2].start_x = BUTTON_START_X;button[2].start_y = COLOR_BLOCK_HEIGHT*2;button[2].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[2].end_y = COLOR_BLOCK_HEIGHT*3;button[2].para = 3;button[2].touch_flag = 0; button[2].draw_btn = Draw_Num_Button ;button[2].btn_command = Command_Select_Color ;button[3].start_x = BUTTON_START_X;button[3].start_y = COLOR_BLOCK_HEIGHT*3;button[3].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[3].end_y = COLOR_BLOCK_HEIGHT*4;button[3].para = 4;button[3].touch_flag = 0; button[3].draw_btn = Draw_Num_Button ;button[3].btn_command = Command_Select_Color ;button[4].start_x = BUTTON_START_X;button[4].start_y = COLOR_BLOCK_HEIGHT*4;button[4].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[4].end_y = COLOR_BLOCK_HEIGHT*5;button[4].para = 5;button[4].touch_flag = 0; button[4].draw_btn = Draw_Num_Button ;button[4].btn_command = Command_Select_Color ;button[5].start_x = BUTTON_START_X;button[5].start_y = COLOR_BLOCK_HEIGHT*5;button[5].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[5].end_y = COLOR_BLOCK_HEIGHT*6;button[5].para = 6;button[5].touch_flag = 0; button[5].draw_btn = Draw_Num_Button ;button[5].btn_command = Command_Select_Color ;button[6].start_x = BUTTON_START_X;button[6].start_y = COLOR_BLOCK_HEIGHT*6;button[6].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[6].end_y = COLOR_BLOCK_HEIGHT*7;button[6].para = 7;button[6].touch_flag = 0; button[6].draw_btn = Draw_Num_Button ;button[6].btn_command = Command_Select_Color ; button[7].start_x = BUTTON_START_X;button[7].start_y = COLOR_BLOCK_HEIGHT*7;button[7].end_x = BUTTON_START_X+COLOR_BLOCK_WIDTH ;button[7].end_y = LCD_Y_LENGTH;button[7].para = CL_BUTTON_GREY;button[7].touch_flag = 0; button[7].draw_btn = Draw_Clear_Button ;button[7].btn_command = Command_Clear_Palette ;/*第二列,主要为画刷按钮*/button[8].start_x = BUTTON_START_X + COLOR_BLOCK_WIDTH;button[8].start_y = 0;button[8].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[8].end_y = COLOR_BLOCK_HEIGHT;button[8].para = 9;button[8].touch_flag = 0; button[8].draw_btn = Draw_Num_Button ;button[8].btn_command = Command_Select_Meannum ;button[9].start_x = BUTTON_START_X + COLOR_BLOCK_WIDTH;button[9].start_y = COLOR_BLOCK_HEIGHT;button[9].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[9].end_y = COLOR_BLOCK_HEIGHT*2;button[9].para = 10;button[9].touch_flag = 0; button[9].draw_btn = Draw_Num_Button ;button[9].btn_command = Command_Select_Light ;button[10].start_x =BUTTON_START_X + COLOR_BLOCK_WIDTH;button[10].start_y = COLOR_BLOCK_HEIGHT*2;button[10].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[10].end_y = COLOR_BLOCK_HEIGHT*3;button[10].para = 11;button[10].touch_flag = 0; button[10].draw_btn = Draw_Shape_Button ;button[10].btn_command = Command_Select_Brush ;button[11].start_x = BUTTON_START_X + COLOR_BLOCK_WIDTH;button[11].start_y = COLOR_BLOCK_HEIGHT*3;button[11].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[11].end_y = COLOR_BLOCK_HEIGHT*4;button[11].para = 12;button[11].touch_flag = 0; button[11].draw_btn = Draw_Shape_Button ;button[11].btn_command = Command_Select_Brush ;button[12].start_x = BUTTON_START_X + COLOR_BLOCK_WIDTH;button[12].start_y = COLOR_BLOCK_HEIGHT*4;button[12].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[12].end_y = COLOR_BLOCK_HEIGHT*5;button[12].para = 13;button[12].touch_flag = 0; button[12].draw_btn = Draw_Shape_Button ;button[12].btn_command = Command_Select_Brush ;button[13].start_x = BUTTON_START_X + COLOR_BLOCK_WIDTH;button[13].start_y = COLOR_BLOCK_HEIGHT*5;button[13].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[13].end_y = COLOR_BLOCK_HEIGHT*6;button[13].para = 14;button[13].touch_flag = 0; button[13].draw_btn = Draw_Shape_Button ;button[13].btn_command = Command_Select_Brush ;button[14].start_x = BUTTON_START_X + COLOR_BLOCK_WIDTH;button[14].start_y = COLOR_BLOCK_HEIGHT*6;button[14].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[14].end_y = COLOR_BLOCK_HEIGHT*7;button[14].para = 15;button[14].touch_flag = 0; button[14].draw_btn = Draw_Shape_Button ;button[14].btn_command = Command_Select_Brush ; button[15].start_x = BUTTON_START_X + COLOR_BLOCK_WIDTH;button[15].start_y = COLOR_BLOCK_HEIGHT*7;button[15].end_x = BUTTON_START_X + COLOR_BLOCK_WIDTH*2 ;button[15].end_y = LCD_Y_LENGTH;button[15].para = 16;button[15].touch_flag = 0; button[15].draw_btn = Draw_Shape_Button ;button[15].btn_command = Command_Select_Brush ;
}
button[0].para 参数用于定位按键,主函数做如下改变,初始化相关外设。
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./lcd/bsp_ili9341_lcd.h"
#include "./lcd/bsp_xpt2046_lcd.h"
#include "./flash/bsp_spi_flash.h"
#include "./led/bsp_led.h"
#include "palette.h"
#include <string.h>
#include "mcu_api.h"
#include "protocol.h"
#include "system.h"
#include "wifi.h"
#include "concrol.h"extern int MANUAL_FEED_NUM;
char dispBuff[100];
extern int MANUAL_FEED_SUM;
int main(void)
{ //LCD 初始化ILI9341_Init(); //触摸屏初始化XPT2046_Init();//从FLASH里获取校正参数,若FLASH无参数,则使用模式3进行校正Calibrate_or_Get_TouchParaWithFlash(3,0);/* USART config */USART_Config(); LED_GPIO_Config();EXTIX_Init();//其中0、3、5、6 模式适合从左至右显示文字,//不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果 //其中 6 模式为大部分液晶例程的默认显示方向 ILI9341_GramScan ( 3 ); //绘制触摸画板界面Palette_Init(LCD_SCAN_MODE);wifi_protocol_init();GPIO_CONFIG(); LCD_SetFont(&Font8x16); LCD_SetColors(RED,BLACK);sprintf(dispBuff,"手动投喂量: %d ",MANUAL_FEED_NUM);ILI9341_DispString_EN_CH(2*48,2*48,dispBuff);ILI9341_DisplayStringEx(2*48,0*48,16,16,(uint8_t *)"服务器在线!!!",0); while ( 1 ){ wifi_uart_service();//心跳检测//触摸检测函数,本函数至少10ms调用一次XPT2046_TouchEvenHandler();LCD_SetFont(&Font8x16); LCD_SetColors(RED,BLACK);sprintf(dispBuff,"总喂食量: %d ",MANUAL_FEED_SUM);ILI9341_DispString_EN_CH(2*48,1*48,dispBuff); get_wifi_status(mcu_get_wifi_work_state()); }}
温湿度采集显示
因为H桥驱动板烧了,在自己的垃圾箱里面翻找替代品的时候发现了以前做毕设遗留下来的DHT11温湿度模块和WS2812灯环,物联网设计嘛,玩啥不是玩呢,都安排上。
页面也更新了:
代码也要添加DHT11的驱动:
DTH11.C
#include "dht11.h"
#include "delay.h"//复位DHT11
void DHT11_Rst(void)
{ DHT11_IO_OUT(); //SET OUTPUTDHT11_DQ_OUT=0; //拉低DQdelay_ms(20); //拉低至少18msDHT11_DQ_OUT=1; //DQ=1 delay_us(30); //主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{ u8 retry=0;DHT11_IO_IN();//SET INPUT while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us{retry++;delay_us(1);}; if(retry>=100)return 1;else retry=0;while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us{retry++;delay_us(1);};if(retry>=100)return 1; return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{u8 retry=0;while(DHT11_DQ_IN&&retry<100)//等待变为低电平{retry++;delay_us(1);}retry=0;while(!DHT11_DQ_IN&&retry<100)//等待变高电平{retry++;delay_us(1);}delay_us(40);//等待40usif(DHT11_DQ_IN)return 1;else return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{ u8 i,dat;dat=0;for (i=0;i<8;i++) {dat<<=1; dat|=DHT11_Read_Bit();} return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{ u8 buf[5];u8 i;DHT11_Rst();if(DHT11_Check()==0){for(i=0;i<5;i++)//读取40位数据{buf[i]=DHT11_Read_Byte();}if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]){*humi=buf[0];*temp=buf[2];}}else return 1;return 0;
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能PG端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PG11端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化IO口GPIO_SetBits(GPIOC,GPIO_Pin_11); //PG11 输出高DHT11_Rst(); //复位DHT11return DHT11_Check();//等待DHT11的回应
}
DTH11.H
#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h" //IO方向设置
#define DHT11_IO_IN() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=8<<12;}
#define DHT11_IO_OUT() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=3<<12;}
IO操作函数
#define DHT11_DQ_OUT PCout(11) //数据端口 PA0
#define DHT11_DQ_IN PCin(11) //数据端口 PA0 u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif
然后再while(1)中循环采集数据,并显示在屏幕上:
DHT11_Read_Data(&temperature,&humidity); //读取温湿度值sprintf(humbuf,"湿度: %d ",humidity);ILI9341_DispString_EN_CH(4*48,4*48,humbuf);sprintf(tempbuff,"温度: %d ",temperature);ILI9341_DispString_EN_CH(2*48,4*48,tempbuff);
本地获取温湿度和LCD显示都没问题,那我们就必须将数据上传到手机了。
在IOT平台自定义温湿度的DP。
然后在主函数的while(1)中,采集完温湿度后,将数据上传便可:
视频链接: BILIBILI.
CSDN源代码下载链接:传送门.
基于STM32F103+涂鸦三明治的宠物自动喂食器相关推荐
- 机智云代码移植_IoT开发者 | 基于STM32F103的机智云宠物屋外加4路继电器开源教程...
[ 写在前面 ] 自智云社区开辟IoT开源项目专区以来,一直有IoT开发者在贡献案例.玛莉甄选了一些具有代表性的案例分享给IoT爱好者们,本文亦如此. 若你有好的案例,想和IoT爱好者们分享,欢迎投稿 ...
- 宠物喂食器的设计-基于涂鸦三明治三件套
宠物喂食器的设计-基于涂鸦三明治三件套 本文章允许涂鸦智能转载使用 去年疫情突发,全国很多地区封城,封小区,让原本备受宠爱的主子与铲屎官异地分离.遇到有准备的铲屎官,主子们还能勉强度日,那些没有准备的 ...
- 开源:纯手工基于小熊派涂鸦三明治模组快速打造一款智能宠物喂食器连载贴(一)
涂鸦智能最近出了很多产品方案,有相当多的目前已经在业界量产了.本人有幸参与此次涂鸦的DIY宠物喂食器的活动,拿到了涂鸦智能赠送给我的三明治开发板. 如下图所示,包装还是相当精美的. 关于这些开发板的介 ...
- 基于涂鸦智能的宠物喂食器
基于涂鸦智能的宠物喂食器 基于涂鸦智能的宠物喂食器 项目地址 作品视频 硬件介绍 系统框架 程序编写 1. 产品创建 2. MCU SDK 移植 3.按键选择程序 4.WiFi状态指示灯 5.云端数据 ...
- 《STM32库开发实战指南:基于STM32F103(第2版)》——2.1节仿真器简介
本节书摘来自华章社区<STM32库开发实战指南:基于STM32F103(第2版)>一书中的第2章,第2.1节仿真器简介,作者刘火良 杨森,更多章节内容可以访问云栖社区"华章社区& ...
- 基于STM32F103的步进电机S型曲线加减速算法与实现
步进电机是将电脉冲信号转变为角位移或线位移的开环控制电机,是现代数字程序控制系统中的主要执行元件,应用极为广泛.在非超载的情况下,电机的转速.停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的 ...
- 基于Stm32f103硬件iic驱动LM75A温度传感器
这是LM75A温度传感器的概述,本文主要介绍基于Stm32f103的硬件iic驱动LM75A温度传感器. 这是我所使用的硬件电路,很简单. 对于该传感器的使用,主要是读取温度值,查看数据手册我们知道需 ...
- 基于STM32F103芯片实现LED灯闪烁
基于STM32F103芯片实现LED灯闪烁 前言 一,寄存器配置 1,时钟控制 2,GPIO端口设置: 二.实际操作 1.具体代码 2.keil5项目运行 3.硬件的连接 4.链接到 mcuisp 串 ...
- java毕业设计——基于java+Spring+JSP的宠物网站设计与实现(毕业论文+程序源码)——宠物网站
基于java+Spring+JSP的宠物网站设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+Spring+JSP的宠物网站设计与实现,文章末尾附有本毕业设计的论文和源码下载地址哦 ...
- 基于STM32F103平台的ADS79xx系列ADC(TI公司)应用方案
目录 第一章 ADS79xx系列芯片特性简介 第二章 芯片内部结构简介 第三章 封装介绍 第四章 硬件布线设计 第五章 基于STM32F103平台的Keil编程 参考文献 第一章 ADS79xx系列芯 ...
最新文章
- Facebook的加密货币即将到来会对整个加密货币领域意味着什么
- vivo是安卓手机吗_vivo手机更新安卓9.0!4款产品尝鲜:有你的吗?
- python几岁开始学_python编程少儿几岁可以学?有哪些优势?
- 在图形化界面中为Ubuntu18.04更新源
- mysql 建索引_mysql数据库正确建立索引及使用
- mysql表空间大小_浅谈mysql中各种表空间(tablespaces)的概念
- 工程安全cso千人千面计算机,千人一面变为千人千面 自适应教育助力因材施教...
- 实战 | 后端日志的前世今生
- SQL注入-布尔盲注
- 常见物理性能测试仪器设备档案
- 4.11 UIDatePicker日期时间选择器 [原创iOS开发-Xcode教程]
- border边框属性的介绍
- win10解除usb禁用_Windows10系统禁用usb存储设备的方法
- 微信小程序:用户头像的更改与保存
- 怎样提高电脑开机速度
- mysql系列之十一许可更新及对象搜索
- 关于JDK8发送邮件失败的问题
- 年后跳槽全过程总结(上)——从面试准备到拿到offer
- 微信小程序 实现 树形菜单其实很简单
- 常用增强学习实验环境 II (ViZDoom, Roboschool, TensorFlow Agents, ELF, Coach等)
热门文章
- 外贸独立站卖家如何借势营销?
- Interpreting visually-grounded navigation instructions in real environments论文翻译
- Java 视频转码(转为MPEG-4格式)
- oracle12c性能测试,Oracle12c IMO 测试
- Android 在后台无法启动Activity
- 全栈工程师就无敌吗?真的能做到个人即团队吗?
- win10 matlab打开,win10系统启动matlab出现闪退的处理步骤
- python词频统计西游记_基于Python第三方插件实现西游记章节标注汉语拼音
- 网页另存word分页
- 【Word】无分页符却出现异常分页情况