本帖最后由 呐咯密密 于 2021-4-1 15:21 编辑
完善功能
一、添加配网功能及指示灯函数
前面的准备工作完成后我们需要添加功能,从最重要的配网开始吧,总不能每次使用都插上电脑用助手配网呀,那多不方便,一键上云才是王道。其实语音也可以,嘻嘻嘻,而且音色也不错,是个萌妹子的声音。跑题了,语音功能容后考虑,因为我觉得加个语音功能我家的主子会炸毛,可能会有吃饭恐惧症。
配网有两种模式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_UP
- GPIO_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; //子优先级3
- NVIC_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)中添加配网状态判断: 现在将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不同的高低电平就可以实现正反转,因为电机本身速度比较慢,就不用软件进行控制速度了。而且因为场景的关系,不用控制正反两个反向,只控制正转和停止。 那么老规矩,首先配置GPIO为输出: - void GPIO_CONFIG(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- // 打开串口GPIO的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- }
复制代码编写控制函数,参数为投喂量,通过转动时间来控制投喂量,延时时间需要与宠物的饭盒出粮口搭配,不同出粮口修改延时基数。 - 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和触摸板界面初始化就不贴了,主要说我改动的地方。 首先放一张丑图,没有时间美化,仅实现功能。
左边的“加”“减”用于设定手动喂食量,设定好单次喂食量按喂食就会控制电机进行出粮,开灯和关灯就是打开和关闭小夜灯,主界面会实时显示服务器在线状态和单次喂食量,总喂食量以及小夜灯的状态。 代码首先要修改按钮的初始化 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);
- switch(mcu_get_wifi_work_state())
- {
- case SMART_CONFIG_STATE:
- //处于 Smart 配置状态,即 LED 快闪
- Led_Blink_Quick();
- break;
- case AP_STATE:
- //处于 AP 配置状态,即 LED 慢闪
- Led_Blink_Slow();
- break;
- case WIFI_NOT_CONNECTED:
- //Wi-Fi 配置完成,正在连接路由器,即 LED 常暗
- LED2_OFF;
- break;
- case WIFI_CONNECTED:
- //路由器连接成功,即 LED 常亮
- LED2_ON;
- LCD_SetFont(&Font8x16);
- LCD_SetColors(RED,BLACK);
- ILI9341_DisplayStringEx(2*48,0*48,16,16,(uint8_t *)"服务器在线!!!",0);
- break;
- default:break;
- }
-
-
- }
-
- }
复制代码主函数就是初始化相关外设,显示开机需要打印的文字,在while(1)中添加XPT2046_TouchEvenHandler();用于触摸检测,并循环检测总喂食量用于实时显示,如果路由器连接成功会打印“服务器在线”的提示语。 XPT2046_TouchEvenHandler()函数原型: - /**
- * [url=home.php?mod=space&uid=247401]@brief[/url] 检测到触摸中断时调用的处理函数,通过它调用tp_down 和tp_up汇报触摸点
- * [url=home.php?mod=space&uid=536309]@NOTE[/url] 本函数需要在while循环里被调用,也可使用定时器定时调用
- * 例如,可以每隔5ms调用一次,消抖阈值宏DURIATION_TIME可设置为2,这样每秒最多可以检测100个点。
- * 可在XPT2046_TouchDown及XPT2046_TouchUp函数中编写自己的触摸应用
- * @param none
- * @retval none
- */
- void XPT2046_TouchEvenHandler(void )
- {
- static strType_XPT2046_Coordinate cinfo={-1,-1,-1,-1};
-
- if(XPT2046_TouchDetect() == TOUCH_PRESSED)
- {
- //获取触摸坐标
- XPT2046_Get_TouchedPoint(&cinfo,strXPT2046_TouchPara);
-
- //输出调试信息到串口
- XPT2046_DEBUG("x=%d,y=%d",cinfo.x,cinfo.y);
-
- //调用触摸被按下时的处理函数,可在该函数编写自己的触摸按下处理过程
- XPT2046_TouchDown(&cinfo);
-
- /*更新触摸信息到pre xy*/
- cinfo.pre_x = cinfo.x; cinfo.pre_y = cinfo.y;
- }
- else
- {
- //调用触摸被释放时的处理函数,可在该函数编写自己的触摸释放处理过程
- XPT2046_TouchUp(&cinfo);
-
- /*触笔释放,把 xy 重置为负*/
- cinfo.x = -1;
- cinfo.y = -1;
- cinfo.pre_x = -1;
- cinfo.pre_y = -1;
- }
- }
复制代码按键按下会触发XPT2046_TouchDown()函数,该函数原型为: - /**
- * @brief 触摸屏被按下的时候会调用本函数
- * @param touch包含触摸坐标的结构体
- * [url=home.php?mod=space&uid=536309]@NOTE[/url] 请在本函数中编写自己的触摸按下处理应用
- * @retval 无
- */
- void XPT2046_TouchDown(strType_XPT2046_Coordinate * touch)
- {
- //若为负值表示之前已处理过
- if(touch->pre_x == -1 && touch->pre_x == -1)
- return;
-
- /***在此处编写自己的触摸按下处理应用***/
-
- /*处理触摸画板的选择按钮*/
- Touch_Button_Down(touch->x,touch->y);
-
- /*处理描绘轨迹*/
- Draw_Trail(touch->pre_x,touch->pre_y,touch->x,touch->y,&brush);
-
- /***在上面编写自己的触摸按下处理应用***/
- }
复制代码此函数触发Touch_Button_Down(); - void Touch_Button_Down(uint16_t x,uint16_t y)
- {
- uint8_t i;
- for(i=0;i<BUTTON_NUM;i++)
- {
- /* 触摸到了按钮 */
- if(x<=button[i].end_x && y<=button[i].end_y && y>=button[i].start_y && x>=button[i].start_x )
- {
- if(button[i].touch_flag == 0) /*原本的状态为没有按下,则更新状态*/
- {
- button[i].touch_flag = 1; /* 记录按下标志 */
-
- button[i].draw_btn(&button[i]); /*重绘按钮*/
- }
-
- }
- else if(button[i].touch_flag == 1) /* 触摸移出了按键的范围且之前有按下按钮 */
- {
- button[i].touch_flag = 0; /* 清除按下标志,判断为误操作*/
-
- button[i].draw_btn(&button[i]); /*重绘按钮*/
- }
- }
- }
复制代码此函数会改变相应按键的按下标志位,触发按键按下的函数处理。之后会重绘按键,函数为Draw_Num_Button(),这里不贴了,量太大。这里举例说明,比如按下了开灯键,会触发Command_Select_Light()
- /**
- * @brief Command_Select_Light 夜灯控制
- * @param btn Touch_Button 类型的按键参数
- * @retval 无
- */
- static void Command_Select_Light(void *btn)
- {
- Touch_Button *ptr = (Touch_Button *)btn;
- if(ptr->para==2)
- {
- /* 开启led灯 */
- LED1_ON;
- LCD_SetFont(&Font8x16);
- LCD_SetColors(RED,BLACK);
- ILI9341_DispString_EN_CH(2*48,3*48,"Light On");
- }
- if(ptr->para==10)
- {
- /* 关闭led灯 */
- LED1_OFF;
- LCD_SetFont(&Font8x16);
- LCD_SetColors(RED,BLACK);
- ILI9341_DispString_EN_CH(2*48,3*48,"Light Off");
- }
- }
复制代码此处代码量大且杂,这里就不一一介绍,若想看具体的代码可git或直接下载。 在写这篇文章的时候H桥驱动烧了,视频又要延迟更新了,真的抱歉。先上代码吧。
|