打印
[活动专区]

【N32G430开发板试用】智能手表功能演示样机

[复制链接]
1948|15
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
@安小芯    @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解析数据时,是动态开辟的内存空间,在解析完成后切记一定要将刚刚开辟的内存空间释放掉,否则会产生内存溢出,导致程序程序运行异常的情况发生!!!
心率监测我们使用官方开放的算法库,这个算法库存在不稳定性,是不能商用的,但作为演示测试是可以的,官方或者第三方提供收费的算法库,可用于商用监测。


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


N32G430C8L7-STB_EBK功能扩展板
原理图

PCB-2D正面图


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

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

    do
    {
        SysTick_DelayMS(10);

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

    } while(Result == 0);

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

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

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

    /* 连接WiFi */
    if(ATK_ESP01_AT_SendCommand("AT+CWJAP=\"********\",\"********\"", "OK", 8000) == 0)
    {
        if(ATK_ESP01_AT_SendCommand(NULL, "OK", 8000) == 0)
        {
            printf("\r\nWi-Fi Connect Error!");
            return;
        }
    }

    ESP01S_WiFiFlag = 1;

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

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


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

    ESP01S_ServerFlag = 1;

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

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

    ESP01S_InitFlag = 1;
}

void ATK_ESP01_Init(void)
{
    ATK_ESP01_InitRESET();

    ATK_ESP01_InitUSART();

    ATK_ESP01_StartProcess();

    TASK_Append(TASK_ID_ATK, ATK_ESP01_Handler, 5000);
}

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


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

    char *cText = "     ";
    char *dText = "你好!";

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

    srand(SysTick_Tick);

    LCD_ShowLOG(StartX, StartY, cText);

    if(DirX == 0)
    {
        StartX -= StepX;

        if(StartX <= 36)
        {
            StartX = 36;
            DirX   = 1;

            StepX = rand() % 10 + 5;
            StepY = rand() % 10 + 5;
        }
    }
    else
    {
        StartX += StepX;

        if((StartX + Width) >= 204)
        {
            StartX = 204 - Width;
            DirX   = 0;

            StepX = rand() % 10 + 5;
            StepY = rand() % 10 + 5;
        }
    }

    if(DirY == 0)
    {
        StartY -= StepY;

        if(StartY <= 36)
        {
            StartY = 36;
            DirY   = 1;

            StepX = rand() % 10 + 5;
            StepY = rand() % 10 + 5;
        }
    }
    else
    {
        StartY += StepY;

        if((StartY + Height) >= 204)
        {
            StartY = 204 - Height;
            DirY   = 0;

            StepX = rand() % 10 + 5;
            StepY = rand() % 10 + 5;
        }
    }

    LCD_ShowLOG(StartX, StartY, dText);
}

void SmartWatch_CreatePageRTC(void)
{
    uint8_t YearNumber[4] = {0x02, 0x00, 0x00, 0x00};
    uint8_t DateNumber[5] = {0x00, 0x00, 0x0E, 0x00, 0x00};
    uint8_t TimeNumber[5] = {0x00, 0x00, 0x0B, 0x00, 0x00};
    uint8_t MiaoNumber[2] = {0x00, 0x00};

    if(SmartWatch_PageState == 0)
    {
        TimeNumber[0] = (RTC_Calendar.hour   / 10) % 10;
        TimeNumber[1] = (RTC_Calendar.hour   % 10);
        TimeNumber[3] = (RTC_Calendar.minute / 10) % 10;
        TimeNumber[4] = (RTC_Calendar.minute % 10);

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

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

        for(uint8_t i = 0; i < sizeof(MiaoNumber); i++)
        {
            LCD_DrawFontLED(88+i*32, 140, MiaoNumber[i]);
        }
    }
    else
    {
        YearNumber[2] = (RTC_Calendar.year / 10) % 10;
        YearNumber[3] = (RTC_Calendar.year % 10);

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

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

        for(uint8_t i = 0; i < sizeof(DateNumber); i++)
        {
            LCD_DrawFontLED(40+i*32, 140, DateNumber[i]);
        }
    }
}

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

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

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

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

            break;
        }
    }

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

    for(uint8_t i = 0; i < sizeof(TempNumber); i++)
    {
        LCD_DrawFontLED(72+i*32, 150, TempNumber[i]);
    }
}

extern int32_t  n_sp02;
extern int32_t  n_heart_rate;

void SmartWatch_CreatePageHeartRate(void)
{
    static uint8_t Index = 0, Heartrate = 0;
    static uint8_t OldWeiShu = 0;

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

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

    Index = (Index + 1) % 46;

    Heartrate = n_heart_rate;

    if(Heartrate < 10)
    {
        HR_WeiShu = 1;
        HR_Number[0] = Heartrate % 10;

        StartX = (240 - 32 * 1) / 2;
    }
    else if(Heartrate < 99)
    {
        HR_WeiShu = 2;
        HR_Number[0] = (Heartrate / 10) % 10;
        HR_Number[1] = (Heartrate % 10);

        StartX = (240 - 32 * 2) / 2;
    }
    else
    {
        HR_WeiShu = 3;
        HR_Number[0] = (Heartrate / 100);
        HR_Number[1] = (Heartrate / 10) % 10;
        HR_Number[2] = (Heartrate % 10);

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

    if(OldWeiShu > HR_WeiShu)
    {
        LCD_ShowLOG(72, 150, "            ");
        LCD_ShowLOG(72, 166, "            ");
    }

    OldWeiShu = HR_WeiShu;

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

    Heartrate++;

    LCD_ShowLOG(108, 190, "BPM");
}



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

    /* resets the MAX30102 */
    maxim_max30102_reset();

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

    /* initializes the MAX30102 */
    maxim_max30102_init();

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

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

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

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

void MAX30102_Handler(void)
{
    static uint8_t  maxim_max30102_staus = 0;
    static uint16_t maxim_max30102_index = 0;

    switch(maxim_max30102_staus)
    {
        case 0:
            for(uint16_t i = 100; i < 500; i++)
            {
                aun_red_buffer[i - 100] = aun_red_buffer[i];
                aun_ir_buffer [i - 100] = aun_ir_buffer [i];
            }

            maxim_max30102_staus = 1;
            maxim_max30102_index = 400;
            break;

        case 1:
            if(GPIO_Input_Pin_Data_Get(GPIOB, GPIO_PIN_2) != PIN_SET)
            {
                maxim_max30102_read_fifo((aun_red_buffer + maxim_max30102_index),
                                         (aun_ir_buffer  + maxim_max30102_index));

                if(maxim_max30102_index++ >= 500)
                {
                    maxim_max30102_staus = 2;
                }
            }
            break;

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

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

            if(ch_spo2_valid == 1 && n_sp02      <= 100)
            {
                printf("\r\nvalid heart rate : %d", n_heart_rate);                    
                printf("\r\nvalid spO2       : %d", n_sp02);
            }
            break;

        default:
            break;
    }
}

KEY相关部分
void KEY_Handler(char *Name, uint8_t State)
{
    if(strcmp(Name, "WAKEUP") == 0)
    {
        if(State == 0)
        {
            LCD_ClearScreen(BACKCOLOR);
            SmartWatch_PageIndex  =  0;
        }
    }

    if(strcmp(Name, "KEY1") == 0)
    {
        if(State == 0)
        {
            if(SmartWatch_PageIndex != 1)
            {
                SmartWatch_PageState = 0;
            }
            else
            {
                if(SmartWatch_PageState == 0)
                {
                    SmartWatch_PageState = 1;
                }
                else
                {
                    SmartWatch_PageState = 0;
                }
            }

            LCD_ClearScreen(BACKCOLOR);
            SmartWatch_PageIndex  =  1;
        }
    }

    if(strcmp(Name, "KEY2") == 0)
    {
        if(State == 0)
        {
            LCD_ClearScreen(BACKCOLOR);
            SmartWatch_PageIndex  =  2;
        }
    }

    if(strcmp(Name, "KEY3") == 0)
    {
        if(State == 0)
        {
            LCD_ClearScreen(BACKCOLOR);
            SmartWatch_PageIndex  =  3;
        }
    }
}



运行界面 & 演示视频

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


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


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

使用特权

评论回复
沙发
www5911839| | 2022-7-26 12:28 | 只看该作者
感谢分享好文章,大佬强无敌

使用特权

评论回复
板凳
xld0932| | 2022-7-26 12:56 | 只看该作者
www5911839 发表于 2022-7-26 12:28
感谢分享好文章,大佬强无敌

这都被发现了……

使用特权

评论回复
地板
WoodData| | 2022-7-26 15:12 | 只看该作者
真不错

使用特权

评论回复
5
www5911839| | 2022-7-26 17:45 | 只看该作者
xld0932 发表于 2022-7-26 12:56
这都被发现了……

每天用爬虫抓得21ic的精华帖,大佬文章篇篇精华,优秀的藏也藏不住啊

使用特权

评论回复
6
gyh974| | 2022-7-27 18:59 | 只看该作者
www5911839 发表于 2022-7-26 17:45
每天用爬虫抓得21ic的精华帖,大佬文章篇篇精华,优秀的藏也藏不住啊 ...

怎么爬取?

使用特权

评论回复
7
GrandLine|  楼主 | 2022-8-24 15:15 | 只看该作者

使用特权

评论回复
8
niceguy| | 2022-11-3 17:39 | 只看该作者
这个QSPI屏的资料可以分享下吗

使用特权

评论回复
9
zhouchen605768| | 2023-2-1 19:05 | 只看该作者
学习了

使用特权

评论回复
10
abotomson| | 2023-3-9 12:03 | 只看该作者
网上有资料可以参考的吗?              

使用特权

评论回复
11
ulystronglll| | 2023-3-9 12:48 | 只看该作者
心率监测使用的是什么模块?              

使用特权

评论回复
12
everyrobin| | 2023-3-9 13:14 | 只看该作者
这个esp8266是at指令操作的吗

使用特权

评论回复
13
backlugin| | 2023-3-10 11:42 | 只看该作者
flash是用来保存数据数据的?

使用特权

评论回复
14
macpherson| | 2023-3-10 12:55 | 只看该作者
图片是怎么保存进去的 ?              

使用特权

评论回复
15
loutin| | 2023-3-10 13:29 | 只看该作者
这个界面做的非常好看呢。              

使用特权

评论回复
16
burgessmaggie| | 2023-3-10 13:40 | 只看该作者
GET请求是必须要参数的吗?              

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

6

主题

35

帖子

4

粉丝