本文分享自华为云社区《基于 STM32 设计的 WiFi 语音播报日程表》,作者: DS 小龙哥 。

  1. 前言

近年来,随着电子产品的发展,数字日程表这项应用在人们工作和生活中起到越来越重要的作用。时间对人们来说总是那么宝贵,工作的忙碌性和繁杂性容易使人忘记当前的时间,忘记了要做的事情,当事情不是很重要的时候,这种遗忘无伤大雅。但是,遇上重要事务,一时的耽误可能酿成大祸。
因此从人们的日常生活到公司办公,从台式电脑到便携式智能手机,都要求标配上日程表的作用。人们要求随时随地都能快速准确的提醒当前事务,并且要求日程表能够更直观、更可靠、更便宜。这种要求催生了新型日程表的产生。除此之外,由于对社会责任的更多承担,人们要求所设计的产品能够产生尽量少的垃圾、能够消耗尽量少的能量。因此人们对日程表的又有了体积小、功耗低的要求。

  2. 功能需求
  2.1 硬件部分
整个项目在正点原子 stm32f103mini 开发板环境下进行。开发板主控是 stm32f103rct6。

项目模块由一些部分组成:

(1)2.8 寸 tft 触摸屏负责显示和进行交互;
(2)pcf8563t 用作时钟计时,并把日期和时间显示在屏幕上。
(3)时间采用 24 小时制显示,上位机要支持设备端 RTC 日期及时间信息更新.
(4)DHT11 温湿度传感器检测环境信息,并把信息显示在液晶屏幕上;
(5)使用 esp8266 作 WiFi 模块与手机 app 进行通信;
(6)单片机将接收到的内容存储在 w25Q64 内,同时可以在屏幕上指定位置将内容显示出来。内容包括具体日程的文字内容以及日程开始、结束的时间;
(7)使用蜂鸣器以及通过 syn6288 语音芯片合成的语音信息,通过喇叭播报实现提醒功能。在日程开始、结束前五分钟提前(这个提前提醒的时间要可以修改)通过蜂鸣器以及喇叭发出语音提示。
(8)显示屏(横屏显示)上应包含:
基础的日期、时间、温湿度信息显示,屏幕主体部分通过列表的方式显示从手机 app 端接收到的日程内容;屏幕上设置一个触屏按钮,按下该按钮是可以跳当前进行中的,或者还未开始的下一项即将开始的日程。
日程显示部分:因为 2.8 寸显示屏的空间有限,所以同屏范围内只显示一到两个日程的具体内容。需要显示出日程的文字内容,开始和结束的时间。当日程时间即将开始以及即将结束时触发语音提示;日程结束之后要从当前显示位置上清除,同时删除 w25Q64 上存储的信息。同时显示下一个待开始的日程。
显示的文本部分要求能够支持显示 16 和 24 大小的,包括中文字符在内的所有字符(无法兼容就做成 24 大小即可)。从手机端发送的中文文本信息在在屏幕上显示的同时要存储在 w25q64 内。并且单片机终端上要支持存储最少十五条日程内容。单片机要能识别具体日程的时间信息,根据时间排序,同时判断日程是否过时,过时的日程直接删除,删除和跳过日程不需要触发提示。
(9)语音提示内容:
1:您有待开始的日程,请注意时间。(如果可以实现将手机端输入的日程内容(主要是汉字)读出来,那么此句改为:下一项日程:XXX 即将开始,请注意时间。(XXX 内即为日程内容))
2:当前日程即将结束
3:连接成功(成功连上 app 时播报)
4:连接失败
5:日程已设置(单片机接收到手机上发送的日程内容。)
(10)每句提示播报前蜂鸣器响一声,响完后停顿一秒再播报。

  2.2 软件部分
软件部分主要就是手机上的控制 app,手机的 app 包括可以选择添加日程的按钮,可以输入信息的文本框,可以将文本框内的内容发送到单片机的按钮。同时要可以在 app 上查看单片机终端的已录入的日程内容,以及加入一个可以删除已录入内容的按钮。文本框分为三部分,一部分输入文本内容(两到八个汉字字符左右的长度即可。)输入开始时间的文本框,输入结束时间的文本框。

  2.3 功能总结
(1)STM32 采用正点原子 mini 板。正点原子的 2.8 寸 tft 触摸屏
(2)pcf8563t 用作时钟计时,并把日期和时间显示在屏幕上
(3)DHT11 温湿度传感器检测环境信息,并把信息显示在液晶屏幕上
(4)使用 esp8266 作 WiFi 模块与手机 app 进行通信;
(5)w25Q64 烧录字库,存放字库,存放日程提醒信息
实现思路:将 W25Q64 安装 FATFS 文件系统,方便数据存放读取,读写日程信息,字库信息。
(6)syn6288 语音芯片合成的语音信息,通过喇叭播报实现提醒功能
(7)开发手机 APP 输入提醒日程,单片机接收到手机上发送的日程内容。
单片机将接收到的内容存储在 w25Q64 内,同时可以在屏幕上指定位置将内容显示出来。内容包括具体日程的文字内容以及日程开始、结束的时间;日程信息采用文件形式存储,修改、读写都针对文件进行操作。

  3. 软件运行效果
软件打开之后先输入设备端的 IP 地址和端口,连接成功之后就可以进行功能操作。
软件上有日程表查看页面(也就是主页面)、日志页面、新增日程提醒页面。
软件分为 windows 桌面版本和 Android 手机版本,下面演示的截图以 windows 桌面版本为例。
软件采用 QT 设计,Qt Creator 是跨平台的 Qt IDE, Qt Creator 是 Qt 被 Nokia 收购后推出的一款新的轻量级集成开发环境(IDE)。此 IDE 能够跨平台运行,支持的系统包括 Linux(32 位及 64 位)、Mac OS X 以及 Windows。
Qt Creator 官网下载地址:Download Qt | Embedded System | Real Time Embedded Systems | Qt
QT 所有版本下载地址:Index of /archive/qt
  1)日程表查看页面(也就是主页面),查看日程提醒事件,点击更新日程按钮,可以从设备端获取最新的数据过来。 v2-042de55f99fcfcba0dfd49645eeaecea_720w.jpg
  (2)日志页面用来查看软件与设备间交互的过程,可以调试了解发送的数据是否正常。 v2-eab8ebe8ff9caec93bde3ab3c2df1b8e_720w.jpg
v2-cf63a30ac4b2b9f515cc9a389399133c_720w.jpg
v2-f4935d0f839db8aa9036af5bcb772c3b_720w.jpg
  (3)新增日程提醒页面
在这个页面上可以填入提醒的事件内容,输入提醒的起始时间、结束时间,提前提醒的时,输入完毕后,点击新增提醒事件按钮,就可以将数据发送给设备端,并且在主页面添加数据显示。
v2-0e45ca3349beaaf3f96800a781eae1b7_720w.jpg
  (4)Android 手机运行效果 v2-2eede7e23a6b64520786483490231d51_720w.jpg
v2-9b9d7fef362825f234e969fa06f1238a_720w.jpg
v2-48fe59abd54dc8bc536e44f0a2360b22_720w.jpg

  4. 通信协议
设备端与软件上位机之间数据交互的的协议:
  
(1)
  • 软件上位机对STM32发送:
  • #update          让STM32发送当前存储的所有日程数据过来
  • STM32向上位机返回的数据格式:
  • $update,起始时间,结束时间,事件内容,提前提醒时间(0~59分钟)
  • $update,2022/02/22 13:15,2022/02/23 12:17,吃饭,5
  • (2)
  • 给STM32发送一条日程数据过去
  • 格式:$add,起始时间,结束时间,事件内容,提前提醒时间(0~59分钟)
  • $add,2022/02/22 13:15,2022/02/23 12:17,吃饭,5
  • (3)
  • 给STM32发送校准时间  *20220222131338
  • (4)
  • 删除STM32上存储的日程数据
  • $del,起始时间,结束时间,事件内容,提前提醒时间(0~59分钟)
  • $del,2022/02/22 13:15,2022/02/23 12:17,吃饭,5
  • 复制代码

      5. 测试流程总结
    设备端采用 ESP8266 与上位机进行通信,ESP8266 上电初始化为 AP+TCP 服务器模式,设置固定端口号。
    采用电脑或者手机运行 APP 测试之前,先搜索 ESP8266 创建的 WIFI 热点连接上,然后打开软件,在软件里输入 ESP8266 服务器的 IP 地址和端口号点击连接,连接成功之后就可以与设备端进行交互。
    如果没有设备端,也可以采用网络调试助手与上位机之前交互,测试功能。
    v2-f76d20828a4275ad42b5cf4f27d61b17_720w.jpg
    v2-c90a10ef079963e4ee2a1e6e73813b0d_720w.jpg
    v2-6fae831a441027417f2ab54b8984167c_720w.jpg
      6. 硬件部分  6.1 硬件实物 v2-112f566e92a883b986d4bd55416ae470_720w.jpg
    v2-29697b2e022573e6bc9240bd63ee613e_720w.jpg
    v2-f0951f6f4f9371c6a0323fe169d4d416_720w.jpg
    板子的串口正常提示:
    (1)上电提示
    v2-cad62dbad3d4f29d44569ae412f4923f_720w.jpg
    (2) 更新事件提示
    v2-e94f818a1889634df781d2eb6433c3ad_720w.jpg
    (3)SD 卡上生成的文件
    v2-49ee7571d76af56ca4d21e166bb28d45_720w.jpg
      6.2 外设硬件连线  
    (1) ESP8266 WIFI
  • PB10--->ESP8266-RX
  • PB11--->ESP8266-TX
  • 3.3v--->VCC
  • GND---->GND
  • (2) SYN6628
  • PA2(TX)---SYN6628-RX
  • PA3(RX)---SYN6628-TX
  • 3.3v---->VCC
  • GND----->GND
  • (3) DHT11 温湿度传感器
  • PA5 ---->DHT11-OUT
  • 3.3v---->VCC
  • GND----->GND
  • 复制代码
    剩下的用的硬件是开发板本身自带--正点原子STM32F1战舰V3开发板,硬件连接详情看原理图接口。


      6.3 字库创建 v2-581f6e6b5e406a8610acd7ea09041a5f_720w.jpg
    v2-f5d1cd203e71e9ca8a7cf27f9a84c6d6_720w.jpg
      6.4 SD 卡上存放的字库文件 v2-38fe1f1ac91f578141a7f8fc80cdff5c_720w.jpg

      7. 设备端核心代码及实现思路
    如果需要整个工程直接使用,可以去这里获取: https://download.csdn.net/download/xiaolong1126626497/85892788
    v2-91e274618c066bbf4a7af70556c38f1d_720w.jpg
      7.1 字库读取
    目前设备端 LCD 屏字库存放在 SD 卡上,通过 fatfs 文件系统读取字模进行显示,这样做的优点:更换字库方便,直接把 SD 卡拔出来放在电脑上拷贝字库即可。
    核心代码如下:
      
    void NT35310_DisplayGBKData(u32 x,u32 y,u32 size,u8 *p,u16 c1,u16 c2)
  • {
  •       FIL fp;
  •           UINT br;
  •           u8 L,H;
  •           u32 Addr;
  •           u16 font_size=size/8*size; //字体占用的点阵码字节大小
  •           u8 *buff=NULL;
  •                 H=*p;
  •                 L=*(p+1);
  •                 if(L<0x7f)L=L-0x40;
  •                 else L=L-0x41;
  •                 H=H-0x81;
  •                 Addr=(190*H+L)*font_size; //中文在字库里的偏移量
  •                 buff=malloc(font_size);   //使用的堆空间
  •                 if(buff==NULL)return;
  •                 switch(size)
  •                 {
  •                         case 16:
  •                                 if(f_open(&fp,"0:SYSTEM/FONT/GBK16-H.DZK",FA_READ)!=FR_OK)
  •                 {
  •                       printf("f_open error.\r\n");
  •                 }
  •                                 f_lseek(&fp,Addr);
  •                                 f_read(&fp,buff,font_size,&br);
  •                                 f_close(&fp);
  •                                 break;
  •                         case 24:
  •                 f_open(&fp,"0:SYSTEM/FONT/GBK24-H.DZK",FA_READ);
  •                                 f_lseek(&fp,Addr);
  •                                 f_read(&fp,buff,font_size,&br);
  •                                 f_close(&fp);
  •                                 break;
  •                         case 32:
  •                                 break;
  •                 }
  •                 //显示中文
  •                 NT35310_DisplayData(x,y,size,size,buff,c1,c2);
  •                 //释放空间
  •                 free(buff);
  • }
  • void NT35310_DisplayData(u32 x,u32 y,u32 w,u32 h,u8 *p,u16 c1,u16 c2)
  • {
  •    u16 i,j,x0=x;
  •    u8 data;
  •     u16 colortemp=POINT_COLOR;   
  •    for(i=0;i<w/8*h;i++) //取出的模型总字节数
  •    {
  •        data=p[i]; //取出数组里一个字节的数据
  •        for(j=0;j<8;j++)
  •        {
  •           if(data&0x80)Draw_Point(x0,y,c1); //字体颜色   
  •           else
  •           {
  •               Draw_Point(x0,y,c2); //背景颜色
  •           }
  •           data<<=1; //继续判断下一位
  •           x0++; //继续画下一个点
  •        }
  •        if(x0-x==w) //判断是否需要换行
  •        {
  •           x0=x;//横坐标归位
  •           y++; //纵坐标自增
  •        }
  •    }
  •    POINT_COLOR=colortemp;         
  • }
  • 复制代码

      7.2 解析 ESP8266 数据
    主函数里通过轮询方式检测,ESP8266 是否收到上位机的命令,收到之后进行解析处理
    核心代码如下:
      
    //ESP8266 WIFI 返回的数据
  • if(USART3_RX_FLAG)
  • {
  •         USART3_RX_BUFFER[USART3_RX_CNT]='\0';
  •         printf("%s",USART3_RX_BUFFER);
  •         //解析WIFI返回的数据
  •         //如果是校准RTC时间 +IPD,0,15:*20220304220552
  •         if(strstr((char*)USART3_RX_BUFFER,":*"))
  •         {
  •                  printf("校准时间.\r\n");
  •                  rtc_time_update((char*)USART3_RX_BUFFER);
  •         }
  •         //如果是请求更新提醒
  •         else if(strstr((char*)USART3_RX_BUFFER,"#update"))
  •         {
  •                 printf("请求更新事件.\r\n");
  •                 update_enev();
  •         }
  •         //如果是新增提醒
  •         //+IPD,0,49:$add,2022/03/04 21:56,2022/03/04 21:56,
  •         else if(strstr((char*)USART3_RX_BUFFER,"$add"))
  •         {
  •                  printf("新增提醒事件.\r\n");
  •                  add_enev((char*)USART3_RX_BUFFER);
  •         }
  •         //如果是删除某个提醒
  •         //+IPD,0,49:$del,2022/03/04 21:56,2022/03/04 21:56,水水水水,0
  •         else if(strstr((char*)USART3_RX_BUFFER,"$del"))
  •         {
  •                  printf("删除某个提醒.\r\n");
  •                 del_enev((char*)USART3_RX_BUFFER);
  •         }
  •         USART3_RX_CNT=0;
  •         USART3_RX_FLAG=0;
  • }
  • 复制代码

      7.3 向 SD 卡存放事件信息
    事件提醒都是存放在 SD 卡上,以文件的形式存放,上面封装的几个函数里,主要是就是读写文件。

    核心代码:
      
    /*
  • 函数功能:从buf里面得到第cnt个逗号所在的位置
  • 返 回 值:0~254,代表逗号所在位置的偏移.
  • 255,代表不存在第cnt个逗号
  • */
  • u8 GetCommaOffset(char *buf,u8 cnt)
  • {
  •         char *p=buf;
  •         while(cnt)
  •         {
  •                 if(*buf==',')cnt--;
  •                 buf++;
  •         }
  •         return buf-p; //计算偏移量
  • }
  • /*
  • 写文件
  • */
  • void FATFS_Write(const TCHAR *FileName,const char *WriteBuff)
  • {
  •     FIL fp;
  •     FRESULT res;
  •     UINT cnt; //存放写入成功的数量
  •     /*1. 创建文件*/  
  •     res=f_open(&fp,FileName, FA_WRITE | FA_CREATE_ALWAYS);
  •     if(res!=0)
  •     {
  •         printf("%s文件创建失败!\n",FileName);
  •         return;
  •     }
  •     /*2. 写入数据*/
  •     res=f_write(&fp,WriteBuff,strlen(WriteBuff),&cnt);
  •     if(res!=0)
  •     {
  •         printf("%s文件写入失败!\n",FileName);
  •         return;
  •     }
  •     /*3. 关闭文件*/
  •     f_close(&fp);
  •     printf("%s文件创建成功,成功写入:%d\n",FileName,cnt);
  • }
  • //提取指定逗号位置的数据
  • void GetCommaOffsetBuff(char *buf_in,char *buf_out,u8 cnt)
  • {
  •         while (cnt)
  •         {
  •                 if (*buf_in == ',')cnt--;
  •                 if (*buf_in == '\0')break;
  •                 buf_in++;
  •         }
  •         while (*buf_in != ',' && *buf_in != '\0')
  •         {
  •                 *buf_out=*buf_in;
  •                 buf_in++;
  •                 buf_out++;
  •         }
  •         *buf_out = '\0';
  • }
  • //字符串替换
  • //sub1替换前 sub2替换后字符
  • void StringSubstitution(char *p,char sub1,char sub2)
  • {
  •         while (*p!='\0')
  •         {
  •                 if (*p == sub1)
  •                 {
  •                         *p = sub2;
  •                 }
  •                 p++;
  •         }
  • }
  • //新增提醒
  • //+IPD,0,49:$add,2022/03/04 21:56,2022/03/04 21:56,
  • void add_enev(char *p)
  • {
  •    char buf_out[20];
  •         u8 offset = 0;
  •         char *p2;
  •         offset = GetCommaOffset(p, 3);
  •         p2 = p + offset;
  •         printf("%d,%s\r\n", offset, p2);
  •         //提取提醒的起始时间 例如:2022/03/04 21:56
  •         GetCommaOffsetBuff(p,buf_out,3);
  •         printf("buf_out1=%s\r\n",buf_out);
  •         StringSubstitution(buf_out,'/','-');
  •         StringSubstitution(buf_out, ' ','-');
  •         StringSubstitution(buf_out, ':','-');
  •         printf("buf_out2=%s\r\n", buf_out);
  •         strcat(buf_out,".ev");
  •         printf("buf_out3=%s\r\n", buf_out);
  •         FATFS_Write(buf_out, p2);
  •     //更新事件列表
  •     update_event_list();
  • }
  • //删除某个提醒
  • //+IPD,0,49:$del,2022/03/04 21:56,2022/03/04 21:56,水水水水,0
  • void del_enev(char *p)
  • {
  •     char buf_out[20];
  •     char dir_file_path[50];
  •         //提取提醒的起始时间 例如:2022/03/04 21:56
  •         GetCommaOffsetBuff(p, buf_out, 3);
  •         printf("buf_out1=%s\n", buf_out);
  •         StringSubstitution(buf_out, '/', '-');
  •         StringSubstitution(buf_out, ' ', '-');
  •         StringSubstitution(buf_out, ':', '-');
  •         printf("buf_out2=%s\n", buf_out);
  •         strcat(buf_out, ".ev");
  •         printf("buf_out3=%s\n", buf_out);
  •     //拼接目录名称
  •     sprintf(dir_file_path,"0:/%s",buf_out);
  •     //删除文件
  •     if(f_unlink(dir_file_path)==FR_OK)
  •     {
  •         printf("%s\r\n删除成功.",dir_file_path);
  •     }
  •     else
  •     {
  •         printf("%s\r\n删除失败.",dir_file_path);
  •     }   
  •     //更新事件列表
  •     update_event_list();
  • }
  • //向APP终端更新提醒
  • //+IPD,0,7:#update
  • /*
  • STM32向上位机返回的数据格式:
  • $update,起始时间,结束时间,事件内容,提前提醒时间(0~59分钟)
  • $update,2022/02/22 13:15,2022/02/23 12:17,吃饭,5
  • */
  • u8 update_enev(void)
  • {
  • //    u8 buff[]="$update,2022/02/22 13:15,2022/02/23 12:17,吃饭,5";
  • //    ESP8266_ServerSendData(0,buff,strlen((char*)buff));
  •     char *path="0:/"; //目录位置
  •     DIR dir;
  •     FIL file; //文件指针
  •     FRESULT res;
  •     FILINFO fno; //存放读取的文件信息
  •     char *abs_path=NULL;  
  •     char cmd[]="$update,"; //请求的命令头
  •     char out_buff[100];
  •     UINT cnt;
  •     strcpy(out_buff,cmd);
  •     /*1. 打开目录*/   
  •     res=f_opendir(&dir,path);
  •     if(res!=FR_OK)return res;
  •     /*2. 循环读取目录*/
  •      while(1)
  •      {
  •         res=f_readdir(&dir,&fno);
  •         if(fno.fname[0] == 0 || res!=0)break;
  •         //printf("文件名称: %s,文件大小: %ld 字节\r\n",fno.fname,fno.fsize);
  •         /*过滤目录*/
  •         if(strstr(fno.fname,".ev"))
  •         {
  •             //申请存放文件名称的长度
  •             abs_path=malloc(strlen(path)+strlen(fno.fname)+1);
  •             if(abs_path==NULL)break;
  •             strcpy(abs_path,path);
  •             strcat(abs_path,"/");
  •             strcat(abs_path,fno.fname);
  •             //读取文件数据
  •             f_open(&file,abs_path,FA_READ);
  •             f_read(&file,out_buff+strlen(cmd),100,&cnt);
  •             free(abs_path);      
  •             printf("abs_path=%s,读取:%d\r\n",abs_path,cnt);
  •             out_buff[cnt+strlen(cmd)]='\0';
  •             //发送给上位机
  •             ESP8266_ServerSendData(0,(u8*)out_buff,strlen((char*)out_buff));
  •             printf("发送:%s\r\n",out_buff);
  •             delay_ms(100);
  •         }
  •     }
  •     /*3. 关闭目录*/
  •     f_closedir(&dir);
  •     return 0;
  • }
  • //提取数据,存放到全局事件结构体里
  • //参数: i 索引值  buf_out 源数据内容
  • void ExtractData(int i,char *buf_out)
  • {
  •         //std_to_sec(u16 syear, u8 smon, u8 sday, u8 hour, u8 min)
  •         char buf_num[50];
  •     //格式化处理
  •         StringSubstitution(buf_out, '/', ',');
  •         StringSubstitution(buf_out, ' ', ',');
  •         StringSubstitution(buf_out, ':', ',');
  •         printf("buf_out=%s\r\n",buf_out);
  •         //2022-03-05-10-11-2022-03-05-10-11-打酱油-5
  •         //1. 提取起始时间
  •         GetCommaOffsetBuff(buf_out,buf_num,0);
  •         event_buff[i].s_year = atoi(buf_num);
  •         printf("起始-年: %d,%s\r\n", event_buff[i].s_year, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 1);
  •         event_buff[i].s_month = atoi(buf_num);
  •         printf("起始-月: %d,%s\r\n", event_buff[i].s_month, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 2);
  •         event_buff[i].s_date = atoi(buf_num);
  •         printf("起始-日: %d,%s\r\n", event_buff[i].s_date, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 3);
  •         event_buff[i].s_hour = atoi(buf_num);
  •         printf("起始-时: %d,%s\r\n", event_buff[i].s_hour, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 4);
  •         event_buff[i].s_min = atoi(buf_num);
  •         printf("起始-分: %d,%s\r\n", event_buff[i].s_min, buf_num);
  •         //2. 提取结束时间
  •         GetCommaOffsetBuff(buf_out, buf_num, 5);
  •         event_buff[i].e_year = atoi(buf_num);
  •         printf("结束-年: %d,%s\r\n", event_buff[i].e_year, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 6);
  •         event_buff[i].e_month = atoi(buf_num);
  •         printf("结束-月: %d,%s\r\n", event_buff[i].e_month, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 7);
  •         event_buff[i].e_date = atoi(buf_num);
  •         printf("结束-日: %d,%s\r\n", event_buff[i].e_date, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 8);
  •         event_buff[i].e_hour = atoi(buf_num);
  •         printf("结束-时: %d,%s\r\n", event_buff[i].e_hour, buf_num);
  •         GetCommaOffsetBuff(buf_out, buf_num, 9);
  •         event_buff[i].e_min = atoi(buf_num);
  •         printf("结束-分: %d,%s\r\n", event_buff[i].e_min, buf_num);
  •         //3. 提取事件内容
  •         GetCommaOffsetBuff(buf_out, buf_num,10);
  •         strcpy((char*)event_buff[i].event_buff, buf_num);
  •         printf("事件内容:%s\r\n", event_buff[i].event_buff);
  •         //4. 提前提醒时间
  •         GetCommaOffsetBuff(buf_out, buf_num, 11);
  •         event_buff[i].time_min_advance = atoi(buf_num);
  •         printf("提前提醒时间:%d分钟\r\n", event_buff[i].time_min_advance);
  •         //5. 设置标志位
  •         event_buff[i].flag = 1;
  • }
  • //最多15个事件提醒
  • struct Event event_buff[15];
  • //更新事件列表
  • u8 update_event_list(void)
  • {
  •     char *path="0:/"; //目录位置
  •     DIR dir;
  •     FIL file; //文件指针
  •     FRESULT res;
  •     FILINFO fno; //存放读取的文件信息
  •     char *abs_path=NULL;  
  •     char out_buff[100];
  •     UINT cnt;
  •     int index=0; //数组索引
  •     //先将数组全部清0
  •     memset(event_buff,0,sizeof(event_buff));
  •     /*1. 打开目录*/   
  •     res=f_opendir(&dir,path);
  •     if(res!=FR_OK)return res;
  •     /*2. 循环读取目录*/
  •      while(1)
  •      {
  •         res=f_readdir(&dir,&fno);
  •         if(fno.fname[0] == 0 || res!=0)break;
  •         //printf("文件名称: %s,文件大小: %ld 字节\r\n",fno.fname,fno.fsize);
  •         /*过滤目录*/
  •         if(strstr(fno.fname,".ev"))
  •         {
  •             //申请存放文件名称的长度
  •             abs_path=malloc(strlen(path)+strlen(fno.fname)+1);
  •             if(abs_path==NULL)break;
  •             strcpy(abs_path,path);
  •             strcat(abs_path,"/");
  •             strcat(abs_path,fno.fname);
  •             //读取文件数据
  •             f_open(&file,abs_path,FA_READ);
  •             f_read(&file,out_buff,100,&cnt);
  •             free(abs_path);
  •             out_buff[cnt]='\0'; //添加结束符            
  •             printf("abs_path=%s,读取:%d,%s\r\n",abs_path,cnt,out_buff);
  •             //解析数据存放到数组里
  •             //2022/03/05 10:11,2022/03/05 10:11,打酱油,5
  •             ExtractData(index++,out_buff);
  •             if(index>=15)break; //最大存放15个事件
  •         }
  •     }
  •     /*3. 关闭目录*/
  •     f_closedir(&dir);
  •     return 0;
  • }
  • 复制代码

      7.4 时间更新
    RTC 时间更新,通过上位机发送时间进行更新。
    v2-2c488313fb47875d49a523621c6a69b1_720w.jpg
    核心代码:
      
    void rtc_time_update(char *buff)
  • {
  •     char *time;
  •     /*判断是否收到客户端发来的数据  */
  •     char *p=strstr((char*)buff,"+IPD");
  •     if(p!=NULL) //正常数据格式: +IPD,0,7:LED1_ON    +IPD,0表示第0个客户端   7:LED1_ON表示数据长度与数据
  •     {
  •             /*解析上位机发来的数据*/
  •             p=strstr((char*)buff,":");
  •             if(p!=NULL)
  •             {
  •                     p+=1; //向后偏移1个字节
  •                     if(*p=='*')  //设置RTC时间
  •                     {
  •                             p+=1; //向后偏移,指向正确的时间
  •                             time=p;
  •                             calendar.w_year=(time[0]-48)*1000+(time[1]-48)*100+(time[2]-48)*10+(time[3]-48)*1;
  •                             calendar.w_month=(time[4]-48)*10+(time[5]-48)*1;
  •                             calendar.w_date=(time[6]-48)*10+(time[7]-48)*1;
  •                             calendar.hour=(time[8]-48)*10+(time[9]-48)*1;
  •                             calendar.min=(time[10]-48)*10+(time[11]-48)*1;
  •                             calendar.sec=(time[12]-48)*10+(time[13]-48)*1;
  •                         RTC_Set(calendar.w_year,
  •                                     calendar.w_month,
  •                         calendar.w_date,
  •                         calendar.hour,
  •                         calendar.min,
  •                         calendar.sec);
  •                         printf("时间设置成功:%s\r\n",buff);
  •                     }
  •             }
  •     }
  • }
  • 复制代码

      7.5 LCD 屏几个主要页面
    主循环里通过轮询按键,检测是否需要切换显示页面。
    目前设计有 3 个主页面 + N 个事件显示页面。
    (1)页面 1: 模拟电子时钟页面
    (2)页面 2:日历显示页面
    (3)页面 3-N : 待办事件显示页面

      7.6 RTC 时钟
    RTC 开启了秒中断,在秒中断里绘制模拟时钟页面,更新当前的系统时间。
    v2-aec842a7377e46f17c0a08f3180c3fe1_720w.jpg

      7.7 事件时间判断页面
    主函数里使用定时器 1,以 10 秒的频率,判断待办事件时间是否到达,是否需要语音播报。
    v2-7c40fce31923d8fdd407aa67916cfb22_720w.jpg
    v2-43beea9605a1e38166c00cb19bb050b4_720w.jpg
  • /*
  • 函数功能: 定时器1的更新中断服务函数
  • */
  • #include "app.h"
  • #include "rtc.h"
  • #include "syn6628.h"
  • u32 time_ev_cnt=0;
  • void TIM1_UP_IRQHandler(void)
  • {
  •     int i=0;
  •     u32 t1=0;
  •     u32 t2=0;
  •     u32 t3=0;
  •     char dir_file_path[50];
  •     if(TIM1->SR&1<<0)
  •     {
  •       TIM1->SR&=~(1<<0);
  •     }
  •     time_ev_cnt++;
  •     //10秒时间
  •     if(time_ev_cnt>=2)
  •     {
  •         time_ev_cnt=0;
  •             printf("10秒时间到达.\r\n");
  •             for(i=0;i<15;i++)
  •             {
  •                 if(event_buff[i].flag==0)break;
  •                 //当前时间
  •                 t1=std_to_sec(calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min);
  •                 //提醒开始时间
  •                 t2=std_to_sec(event_buff[i].s_year,event_buff[i].s_month,event_buff[i].s_date,event_buff[i].s_hour,event_buff[i].s_min);
  •                 //提醒结束时间
  •                 t3=std_to_sec(event_buff[i].e_year,event_buff[i].e_month,event_buff[i].e_date,event_buff[i].s_hour,event_buff[i].e_min);
  •                 //加上提前提醒的分钟
  •                 t2+=event_buff[i].time_min_advance*60;
  •                 //判断有没有时间满足需求,满足就语音提示
  •                 if(t1>=t2 && t1<=t3)
  •                 {
  •                     //时间达到,语音播报
  •                     SYN6288_Speech("您有待开始的日程,请注意");
  •                     //如果需要播放具体的内容,直接播放结构体里的成员即可
  •                 }
  •                 //如果时间超过,就可以删除事件
  •                 if(t1>=t3)
  •                 {
  •                     //删除SD卡上的这个文件
  •                     //拼接路径
  •                     sprintf(dir_file_path,"0:/%d-%d-%d-%d-%d.ev",event_buff[i].s_year,event_buff[i].s_month,event_buff[i].s_date,event_buff[i].s_hour,event_buff[i].s_min);
  •                     if(f_unlink(dir_file_path)==FR_OK)
  •                     {
  •                         printf("%s\r\n删除成功.",dir_file_path);
  •                          //更新事件列表
  •                         update_event_list();
  •                     }
  •                     else
  •                     {
  •                         printf("%s\r\n删除失败.",dir_file_path);
  •                     }  
  •                 }
  •             }
  •     }
  • }
  • 复制代码