[活动专区] 【N32G430开发板试用】智能手表功能演示样机

[复制链接]
2669|15
 楼主| GrandLine 发表于 2022-7-26 08:59 | 显示全部楼层 |阅读模式
@安小芯    @21小跑堂


概述
基于N32G430C8L7-STB开发板结合圆形TFT、Wi-Fi模块、RTC、心率模块、按键模块做一个简单功能的智能手表,具有显示时钟、心率、实时天气的功能。


软件功能
  • 通过板载的按键实现智能手表不同功能界面的切换:待机界面、时间界面、日期界面、天气界面、心率监测界面;
  • 通过SPI接口驱动SPI接口的圆形TFT,显示相应的功能界面
  • 通过SPI接口驱动SPI FLASH;SPI FLASH中存放着字库、图片等信息(天气图片、心率波形图等);
  • 通过USART2与ESP8266 Wi-Fi模块进行通讯,连接远程的知心天气,获取本地数据,更新显示到显示屏上;
  • 通过I2C驱动心率监测模块,读取当前的心率信息,结合官方的算法,在屏幕上进行显示;


系统框图
天气数据获取我们使用ESP8266 Wi-Fi模块与知心天气网进行连接,注册用户可以创建一个免费版的数据通道(访问频次有限制,只能固定获取一个地址的天气数据),我们向知心天气发送一个带有特定参数的GET请求,然后根据知心天气反馈的数据通过cJSON解析得到天气代码和温度信息,然后对应相应的天气图标显示的屏幕上。在使用cJSON解析数据时,是动态开辟的内存空间,在解析完成后切记一定要将刚刚开辟的内存空间释放掉,否则会产生内存溢出,导致程序程序运行异常的情况发生!!!
心率监测我们使用官方开放的算法库,这个算法库存在不稳定性,是不能商用的,但作为演示测试是可以的,官方或者第三方提供收费的算法库,可用于商用监测。
系统框图.jpg


硬件准备
  • N32G430C8L7-STB开发板
  • N32G430C8L7-STB_EBK功能扩展板(ESP8266 Wi-Fi模块、心率监测模块、SPI FLASH模块、TFT显示屏)
  • 相关数据连接线
硬件准备.jpg


N32G430C8L7-STB_EBK功能扩展板
原理图
原理图.png

PCB-2D正面图
扩展板PCB.png


代码实现
代码部分仅摘录了部分,完整的功能代码可以下载文末的软件工程源代码附件。

ESP8266 Wi-Fi模块相关部分
  1. void ATK_ESP01_StartProcess(void)
  2. {
  3.     uint8_t Result = 0;

  4.     do
  5.     {
  6.         SysTick_DelayMS(10);

  7.         Result = ATK_ESP01_AT_SendCommand("AT", "OK", 100);

  8.     } while(Result == 0);

  9.     /* 开回显 */
  10.     ATK_ESP01_AT_SendCommand("ATE1",   "OK", 100);

  11.     /* 查询版本信息 */
  12.     ATK_ESP01_AT_SendCommand("AT+GMR", "OK", 100);

  13.     /* 设置当前Wi-Fi模式: Station模式 */
  14.     ATK_ESP01_AT_SendCommand("AT+CWMODE=1", "OK", 100);

  15.     /* 连接WiFi */
  16.     if(ATK_ESP01_AT_SendCommand("AT+CWJAP="********","********"", "OK", 8000) == 0)
  17.     {
  18.         if(ATK_ESP01_AT_SendCommand(NULL, "OK", 8000) == 0)
  19.         {
  20.             printf("\r\nWi-Fi Connect Error!");
  21.             return;
  22.         }
  23.     }

  24.     ESP01S_WiFiFlag = 1;

  25.     /* 查询IP地址 */
  26.     ATK_ESP01_AT_SendCommand("AT+CIFSR", "OK", 100);

  27.     /* 设置单链接 */
  28.     ATK_ESP01_AT_SendCommand("AT+CIPMUX=0", "OK", 100);


  29.     /* 连接服务器 */
  30.     if(ATK_ESP01_AT_SendCommand("AT+CIPSTART="TCP","api.seniverse.com",80", "OK", 8000) == 0)
  31.     {
  32.         printf("\r\nServer Connect Error!");
  33.         return;
  34.     }

  35.     ESP01S_ServerFlag = 1;

  36.     /* 透明传输 */
  37.     ATK_ESP01_AT_SendCommand("AT+CIPMODE=1", "OK", 100);

  38.     /* 开始传输 */
  39.     if(ATK_ESP01_AT_SendCommand("AT+CIPSEND", ">", 1000) == 0)
  40.     {
  41.         printf("\r\nTransfer Not Ready...");
  42.         return;
  43.     }

  44.     ESP01S_InitFlag = 1;
  45. }

  46. void ATK_ESP01_Init(void)
  47. {
  48.     ATK_ESP01_InitRESET();

  49.     ATK_ESP01_InitUSART();

  50.     ATK_ESP01_StartProcess();

  51.     TASK_Append(TASK_ID_ATK, ATK_ESP01_Handler, 5000);
  52. }

  53. void ATK_ESP01_Handler(void)
  54. {
  55.     if(ESP01S_InitFlag)
  56.     {
  57.         /* 每间隔5秒获取一次天气信息, 服务器限制每分钟最多20次!!! */
  58.         ATK_ESP01_SendString("GET https://api.seniverse.com/v3/weather/now.json?key=*****************&location=shanghai&language=zh-Hans&unit=c\r\n");
  59.     }
  60. }


界面显示相关部分
  1. void SmartWatch_CreatePageIDLE(void)
  2. {
  3.     static uint8_t StartX = 120, StartY = 120;
  4.     static uint8_t StepX  = 10,  StepY  = 10;
  5.     static uint8_t DirX   = 0,   DirY   = 0;

  6.     char *cText = "     ";
  7.     char *dText = "你好!";

  8.     uint8_t Height = 16;
  9.     uint8_t Width  = strlen(dText) * 8;

  10.     srand(SysTick_Tick);

  11.     LCD_ShowLOG(StartX, StartY, cText);

  12.     if(DirX == 0)
  13.     {
  14.         StartX -= StepX;

  15.         if(StartX <= 36)
  16.         {
  17.             StartX = 36;
  18.             DirX   = 1;

  19.             StepX = rand() % 10 + 5;
  20.             StepY = rand() % 10 + 5;
  21.         }
  22.     }
  23.     else
  24.     {
  25.         StartX += StepX;

  26.         if((StartX + Width) >= 204)
  27.         {
  28.             StartX = 204 - Width;
  29.             DirX   = 0;

  30.             StepX = rand() % 10 + 5;
  31.             StepY = rand() % 10 + 5;
  32.         }
  33.     }

  34.     if(DirY == 0)
  35.     {
  36.         StartY -= StepY;

  37.         if(StartY <= 36)
  38.         {
  39.             StartY = 36;
  40.             DirY   = 1;

  41.             StepX = rand() % 10 + 5;
  42.             StepY = rand() % 10 + 5;
  43.         }
  44.     }
  45.     else
  46.     {
  47.         StartY += StepY;

  48.         if((StartY + Height) >= 204)
  49.         {
  50.             StartY = 204 - Height;
  51.             DirY   = 0;

  52.             StepX = rand() % 10 + 5;
  53.             StepY = rand() % 10 + 5;
  54.         }
  55.     }

  56.     LCD_ShowLOG(StartX, StartY, dText);
  57. }

  58. void SmartWatch_CreatePageRTC(void)
  59. {
  60.     uint8_t YearNumber[4] = {0x02, 0x00, 0x00, 0x00};
  61.     uint8_t DateNumber[5] = {0x00, 0x00, 0x0E, 0x00, 0x00};
  62.     uint8_t TimeNumber[5] = {0x00, 0x00, 0x0B, 0x00, 0x00};
  63.     uint8_t MiaoNumber[2] = {0x00, 0x00};

  64.     if(SmartWatch_PageState == 0)
  65.     {
  66.         TimeNumber[0] = (RTC_Calendar.hour   / 10) % 10;
  67.         TimeNumber[1] = (RTC_Calendar.hour   % 10);
  68.         TimeNumber[3] = (RTC_Calendar.minute / 10) % 10;
  69.         TimeNumber[4] = (RTC_Calendar.minute % 10);

  70.         for(uint8_t i = 0; i < sizeof(TimeNumber); i++)
  71.         {
  72.             LCD_DrawFontLED(40+i*32,  80, TimeNumber[i]);
  73.         }

  74.         MiaoNumber[0] = (RTC_Calendar.second / 10) % 10;
  75.         MiaoNumber[1] = (RTC_Calendar.second % 10);

  76.         for(uint8_t i = 0; i < sizeof(MiaoNumber); i++)
  77.         {
  78.             LCD_DrawFontLED(88+i*32, 140, MiaoNumber[i]);
  79.         }
  80.     }
  81.     else
  82.     {
  83.         YearNumber[2] = (RTC_Calendar.year / 10) % 10;
  84.         YearNumber[3] = (RTC_Calendar.year % 10);

  85.         for(uint8_t i = 0; i < sizeof(YearNumber); i++)
  86.         {
  87.             LCD_DrawFontLED(56+i*32,  80, YearNumber[i]);
  88.         }

  89.         DateNumber[0] = (RTC_Calendar.month / 10) % 10;
  90.         DateNumber[1] = (RTC_Calendar.month % 10);
  91.         DateNumber[3] = (RTC_Calendar.day   / 10) % 10;
  92.         DateNumber[4] = (RTC_Calendar.day   % 10);

  93.         for(uint8_t i = 0; i < sizeof(DateNumber); i++)
  94.         {
  95.             LCD_DrawFontLED(40+i*32, 140, DateNumber[i]);
  96.         }
  97.     }
  98. }

  99. void SmartWatch_CreatePageWeather(void)
  100. {
  101.     uint8_t TempNumber[3] = {0x00, 0x00, 0x0D};

  102.     for(uint8_t i = 0; i < 40; i++)
  103.     {
  104.         if(Weather[i].Value == atoi(Weather_Code))
  105.         {
  106.             uint8_t StartX = (240 - strlen(Weather[i].Chinese) * 8) / 2;

  107.             LCD_ShowLOG(StartX, 115, Weather[i].Chinese);

  108.             LCD_DrawImage(0x100000, Weather[i].Offset, Weather[i].Length, 0);

  109.             break;
  110.         }
  111.     }

  112.     TempNumber[0] = (atoi(Weather_Temp) / 10) % 10;
  113.     TempNumber[1] = (atoi(Weather_Temp) % 10);

  114.     for(uint8_t i = 0; i < sizeof(TempNumber); i++)
  115.     {
  116.         LCD_DrawFontLED(72+i*32, 150, TempNumber[i]);
  117.     }
  118. }

  119. extern int32_t  n_sp02;
  120. extern int32_t  n_heart_rate;

  121. void SmartWatch_CreatePageHeartRate(void)
  122. {
  123.     static uint8_t Index = 0, Heartrate = 0;
  124.     static uint8_t OldWeiShu = 0;

  125.     uint8_t HR_Number[3] = {0x00, 0x00, 0x00};
  126.     uint8_t HR_WeiShu    = 0, StartX = 0;

  127.     LCD_DrawImage(0x200000, HR_IMAGE[Index][0], HR_IMAGE[Index][1], 1);

  128.     Index = (Index + 1) % 46;

  129.     Heartrate = n_heart_rate;

  130.     if(Heartrate < 10)
  131.     {
  132.         HR_WeiShu = 1;
  133.         HR_Number[0] = Heartrate % 10;

  134.         StartX = (240 - 32 * 1) / 2;
  135.     }
  136.     else if(Heartrate < 99)
  137.     {
  138.         HR_WeiShu = 2;
  139.         HR_Number[0] = (Heartrate / 10) % 10;
  140.         HR_Number[1] = (Heartrate % 10);

  141.         StartX = (240 - 32 * 2) / 2;
  142.     }
  143.     else
  144.     {
  145.         HR_WeiShu = 3;
  146.         HR_Number[0] = (Heartrate / 100);
  147.         HR_Number[1] = (Heartrate / 10) % 10;
  148.         HR_Number[2] = (Heartrate % 10);

  149.         StartX = (240 - 32 * 3) / 2;
  150.     }

  151.     if(OldWeiShu > HR_WeiShu)
  152.     {
  153.         LCD_ShowLOG(72, 150, "            ");
  154.         LCD_ShowLOG(72, 166, "            ");
  155.     }

  156.     OldWeiShu = HR_WeiShu;

  157.     for(uint8_t i = 0; i < HR_WeiShu; i++)
  158.     {
  159.         LCD_DrawFontLED(StartX+i*32, 150, HR_Number[i]);
  160.     }

  161.     Heartrate++;

  162.     LCD_ShowLOG(108, 190, "BPM");
  163. }



心率监测相关部分
  1. void maxim_max30102_startup(void)
  2. {
  3.     uint8_t dummy = 0;

  4.     /* resets the MAX30102 */
  5.     maxim_max30102_reset();

  6.     /* read and clear status register */
  7.     maxim_max30102_read_reg(0, &dummy);

  8.     /* initializes the MAX30102 */
  9.     maxim_max30102_init();

  10.     /* read the first 500 samples, and determine the signal range */
  11.     for(uint16_t i = 0; i < 500; i++)
  12.     {
  13.         /* wait until the interrupt pin asserts */
  14.         while(GPIO_Input_Pin_Data_Get(GPIOB, GPIO_PIN_2) == PIN_SET);

  15.         /* read from MAX30102 FIFO */
  16.         maxim_max30102_read_fifo((aun_red_buffer + i),  (aun_ir_buffer + i));

  17.         printf("\r\nred = %i, ir = %i", aun_red_buffer[i], aun_ir_buffer[i]);
  18.     }

  19.     /* calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples) */
  20.     maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, 500, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
  21. }

  22. void MAX30102_Handler(void)
  23. {
  24.     static uint8_t  maxim_max30102_staus = 0;
  25.     static uint16_t maxim_max30102_index = 0;

  26.     switch(maxim_max30102_staus)
  27.     {
  28.         case 0:
  29.             for(uint16_t i = 100; i < 500; i++)
  30.             {
  31.                 aun_red_buffer[i - 100] = aun_red_buffer[i];
  32.                 aun_ir_buffer [i - 100] = aun_ir_buffer [i];
  33.             }

  34.             maxim_max30102_staus = 1;
  35.             maxim_max30102_index = 400;
  36.             break;

  37.         case 1:
  38.             if(GPIO_Input_Pin_Data_Get(GPIOB, GPIO_PIN_2) != PIN_SET)
  39.             {
  40.                 maxim_max30102_read_fifo((aun_red_buffer + maxim_max30102_index),
  41.                                          (aun_ir_buffer  + maxim_max30102_index));

  42.                 if(maxim_max30102_index++ >= 500)
  43.                 {
  44.                     maxim_max30102_staus = 2;
  45.                 }
  46.             }
  47.             break;

  48.         case 2:
  49.             maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, 500, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
  50.             maxim_max30102_staus = 0;

  51.             if(ch_hr_valid   == 1 && n_heart_rate < 120)
  52.             {
  53.                 printf("\r\nvalid heart rate : %d", n_heart_rate);
  54.             }

  55.             if(ch_spo2_valid == 1 && n_sp02      <= 100)
  56.             {
  57.                 printf("\r\nvalid heart rate : %d", n_heart_rate);                    
  58.                 printf("\r\nvalid spO2       : %d", n_sp02);
  59.             }
  60.             break;

  61.         default:
  62.             break;
  63.     }
  64. }

KEY相关部分
  1. void KEY_Handler(char *Name, uint8_t State)
  2. {
  3.     if(strcmp(Name, "WAKEUP") == 0)
  4.     {
  5.         if(State == 0)
  6.         {
  7.             LCD_ClearScreen(BACKCOLOR);
  8.             SmartWatch_PageIndex  =  0;
  9.         }
  10.     }

  11.     if(strcmp(Name, "KEY1") == 0)
  12.     {
  13.         if(State == 0)
  14.         {
  15.             if(SmartWatch_PageIndex != 1)
  16.             {
  17.                 SmartWatch_PageState = 0;
  18.             }
  19.             else
  20.             {
  21.                 if(SmartWatch_PageState == 0)
  22.                 {
  23.                     SmartWatch_PageState = 1;
  24.                 }
  25.                 else
  26.                 {
  27.                     SmartWatch_PageState = 0;
  28.                 }
  29.             }

  30.             LCD_ClearScreen(BACKCOLOR);
  31.             SmartWatch_PageIndex  =  1;
  32.         }
  33.     }

  34.     if(strcmp(Name, "KEY2") == 0)
  35.     {
  36.         if(State == 0)
  37.         {
  38.             LCD_ClearScreen(BACKCOLOR);
  39.             SmartWatch_PageIndex  =  2;
  40.         }
  41.     }

  42.     if(strcmp(Name, "KEY3") == 0)
  43.     {
  44.         if(State == 0)
  45.         {
  46.             LCD_ClearScreen(BACKCOLOR);
  47.             SmartWatch_PageIndex  =  3;
  48.         }
  49.     }
  50. }



运行界面 & 演示视频
待机界面.jpg 显示时间.jpg 显示日期.jpg 显示天气.jpg 监测心率.jpg

https://www.bilibili.com/video/BV1Ka411T7iG/


附件
软件工程源代码: SmartWatch.zip (3.64 MB, 下载次数: 80)
功能扩展板原理图: Schematic_N32G430C8L7-STB_EBK_SmartWatch_2022-07-25.pdf (94.14 KB, 下载次数: 36)


其它
当前智能手表的随着显示内容越来越丰富,功能越来越强大,显示分辨率也越来越高;之前240*240的分辨率使用SPI就可以满足刷屏需求;现在使用在智能手表上的高分辨率的320*320、480*480等等液晶显示屏,如果还使用SPI已经有明显的卡顿/迟钝的现象,所以现在很多这样高分辨率的显示屏使用了MIPI或者QSPI的接口来进行通讯控制,国民技术在N32G45X系列MCU上配备了QSPI接口,后面有机会可以申请一块板子来验证一下QSPI接口驱动显示屏的效果方案(模块已到位就差开发板来验证了)。
微信图片_20220726085056.jpg

www5911839 发表于 2022-7-26 12:28 | 显示全部楼层
感谢分享好文章,大佬强无敌
xld0932 发表于 2022-7-26 12:56 | 显示全部楼层
www5911839 发表于 2022-7-26 12:28
感谢分享好文章,大佬强无敌

这都被发现了……
WoodData 发表于 2022-7-26 15:12 | 显示全部楼层
真不错
www5911839 发表于 2022-7-26 17:45 | 显示全部楼层
xld0932 发表于 2022-7-26 12:56
这都被发现了……

每天用爬虫抓得21ic的精华帖,大佬文章篇篇精华,优秀的藏也藏不住啊
gyh974 发表于 2022-7-27 18:59 | 显示全部楼层
www5911839 发表于 2022-7-26 17:45
每天用爬虫抓得21ic的精华帖,大佬文章篇篇精华,优秀的藏也藏不住啊 ...

怎么爬取?
 楼主| GrandLine 发表于 2022-8-24 15:15 | 显示全部楼层

niceguy 发表于 2022-11-3 17:39 | 显示全部楼层
这个QSPI屏的资料可以分享下吗
zhouchen605768 发表于 2023-2-1 19:05 | 显示全部楼层
学习了
abotomson 发表于 2023-3-9 12:03 | 显示全部楼层
网上有资料可以参考的吗?              
ulystronglll 发表于 2023-3-9 12:48 | 显示全部楼层
心率监测使用的是什么模块?              
everyrobin 发表于 2023-3-9 13:14 | 显示全部楼层
这个esp8266是at指令操作的吗
backlugin 发表于 2023-3-10 11:42 | 显示全部楼层
flash是用来保存数据数据的?
macpherson 发表于 2023-3-10 12:55 | 显示全部楼层
图片是怎么保存进去的 ?              
loutin 发表于 2023-3-10 13:29 | 显示全部楼层
这个界面做的非常好看呢。              
burgessmaggie 发表于 2023-3-10 13:40 | 显示全部楼层
GET请求是必须要参数的吗?              
您需要登录后才可以回帖 登录 | 注册

本版积分规则

6

主题

35

帖子

7

粉丝
快速回复 在线客服 返回列表 返回顶部