Funpack第六期--使用美信半导体MAX32660-EVSYS开发板制作的具有通知提醒和体温测量功能的手表原型-by叶开
本设计使用MAX32660作为主控,具有时间显示,温度测量和串口接收通知的功能。
标签
Funpack
MCU
MAX32660
美信
yekai
更新2021-03-17
1176

本代码主要实现了日期星期时间显示,体温测量,通知提醒等手表上常见的功能

硬件连接

I2C总线(P0_3&P0_2):一个OLED屏幕和一个SHT30模块

UART外设(RX:P0_5):蓝牙串口透传模块

GPIO(P0_6):一个线性马达

数据处理

时间数据

使用RTC模块的时间中的时分秒,使用普通变量记录年月日,使用万年历算法计算星期

uint8_t dayRedress_leap[12] = {
        0, 3, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6
};
uint8_t dayRedress_common[12] = {
        0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5
};

void GetNowTime() {
    uint32_t day, hr, min, sec;

    const uint32_t SECS_PER_MIN = 60;
    const uint32_t SECS_PER_HR = 60 * SECS_PER_MIN;
    const uint32_t SECS_PER_DAY = 24 * SECS_PER_HR;

    sec = RTC_GetSecond();

    day = sec / SECS_PER_DAY;
    sec -= day * SECS_PER_DAY;

    hr = sec / SECS_PER_HR;
    sec -= hr * SECS_PER_HR;

    min = sec / SECS_PER_MIN;
    sec -= min * SECS_PER_MIN;

    if (day >= 1) {
        nowTime.day = nowTime.day + 1;
        RTC_Init(MXC_RTC, sec - 24 * 60 * 60, 0, &sys_cfg);
        RTC_EnableRTCE(MXC_RTC);
    }
    if ((nowTime.year % 400 == 0) || ((nowTime.year % 100 != 0) && (nowTime.year % 4 == 0))) {
        nowTime.leap = true;
    } else {
        nowTime.leap = false;
    }
    switch (nowTime.month) {
        case 1: case 3: case 5: case 7: case 8: case 10: case 12:
            if (nowTime.day >= 32) {
                nowTime.month++;
                nowTime.day -= 31;
            }
            break;
        case 4: case 6: case 9: case 11:
            if (nowTime.day >= 31) {
                nowTime.month++;
                nowTime.day -= 30;
            }
            break;
        case 2:
            if (nowTime.leap) {
                if (nowTime.day >= 30) {
                    nowTime.month++;
                    nowTime.day -= 29;
                }
            } else {
                if (nowTime.day >= 29) {
                    nowTime.month++;
                    nowTime.day -= 28;
                }
            }
            break;
        default:
            break;
    }
    /// (年+年/4+年/400-年/100+月日天数-1)/7=XX……余星期几
    nowTime.weekday = (nowTime.year + nowTime.year / 4 + nowTime.year / 400 - nowTime.year / 100
                       + (nowTime.leap ? dayRedress_leap[nowTime.month - 1] :
                          dayRedress_common[nowTime.month - 1]) + nowTime.day - 1) % 7;
    nowTime.hour = hr;
    nowTime.minute = min;
    nowTime.second = sec;
}

温度数据

温度数据可以直接读取SHT30传感器的温湿度,只要温度即可。SHT30的驱动可在我的github工程中找到

void GetTemperature() {
    float humidity = 0.0;
    uint8_t recv_dat[30];
    SHT30_Read_Dat(recv_dat);
    SHT30_Dat_To_Float(recv_dat, &temperature, &humidity);
}

void ShowTemperature(){
    GetTemperature();
    nowAct = Temperature;
}

串口下发数据

串口下发设定时间数据和通知数据。上位机发送数据格式为"标题:内容",特殊地,如果标题为time,即为设置时间指令。串口设为异步接收一个字符,接收回调函数如下

void read_cb(uart_req_t *req, int error) {
    static uint8_t position = 0;
    static bool recording = false;
    uint8_t i = 0, j = 0, len = 0;
    if (uartRcv == '\"') {   //从"开始记录,到"结束记录
        if (!recording) {    //如果还未开始记录,开始记录
            recording = true;
        } else {             //如果已经开始记录,结束记录并显示
            memset(title, 0, sizeof(title));
            memset(message, 0, sizeof(message));
            // 分离标题和内容。上位机中用:分割
            len = strlen(revBuf);
            for (i = 0; (i < len) && (revBuf[i] != ':'); i++) {
                title[i] = revBuf[i];
            }
            for (i++, j = 0; i < len; i++, j++) {
                message[j] = revBuf[i];
            }
            // 检查是否为设置时间的通知
            if (strcmp(title, "time") == 0) {
                SolveTimeString(message);
            } else {
                strcpy(notificationTitle, title);
                strcpy(notificationMessage, message);
                nowAct = Notification;
            }
            // 重置到未读取过字符串状态
            memset(revBuf, 0, sizeof(revBuf));
            recording = false;
            position = 0;
        }
    } else if (recording) {  //如果正在记录,就记录
        revBuf[position] = uartRcv;
        position++;
    }
    UART_ReadAsync(MXC_UART_GET_UART(0), &read_req);
}

设置时间函数如下

void SolveTimeString(char *timeString) {
    // 上位机发送数据为YYYY-MM-DD-HH-MM-SS
    nowTime.year = atoi(timeString);
    nowTime.month = atoi(timeString + 5);
    nowTime.day = atoi(timeString + 8);
    nowTime.hour = atoi(timeString + 11);
    nowTime.minute = atoi(timeString + 14);
    nowTime.second = atoi(timeString + 17);
    RTC_DisableRTCE(MXC_RTC);
    RTC_Init(MXC_RTC, nowTime.hour * 60 * 60 + nowTime.minute * 60 + nowTime.second, 0, &sys_cfg);
    RTC_EnableRTCE(MXC_RTC);
    // 更新星期数和闰年
    GetNowTime();
}

人机交互

OLED显示模块

我使用的是SSD1306的I2C的128x64分辨率的OLED模块,相关驱动也可在我的github工程找到

我简单地把显示部分分成了三个场景

enum ActName {
    Time = 1, Temperature, Notification
};

默认显示Time场景,按下按钮后切换到Temperature场景,收到串口下发非调整时间的数据切换到Notification场景。Temperature和Notification场景持续5秒后切换回Time场景

void Oled_Task(uint8_t tickTime) {
    switch (nowAct) {
        case Temperature:
            if (displayTempOrNotiTime == 0) {                       // 仅首次执行
                OLED_Clear();
                Oled_ShowTemperatureBackground();
                displayTempOrNotiTime++;
            } else if (displayTempOrNotiTime < 5000 / tickTime) {  // 5s内刷新
                Oled_ShowTemperature();
                GetTemperature();
                displayTempOrNotiTime++;
            } else {                                                // 结束后执行
                nowAct = Time;
                OLED_Clear();
                Oled_ShowTemperature();
            }
            break;
        case Notification:
            if (displayTempOrNotiTime == 0) {                       // 仅首次执行
                OLED_Clear();
                Oled_ShowNotification();
                Motor_Click();
                displayTempOrNotiTime++;
            } else if (displayTempOrNotiTime < 5000 / tickTime) {   // 5s内持续执行
                displayTempOrNotiTime++;
            } else {                                                // 结束后执行
                nowAct = Time;
                OLED_Clear();
            }
            break;
        default:
            displayTempOrNotiTime = 0;
            Oled_ShowTime();
            break;
    }
}

Time场景显示部分

void Oled_ShowTime() {
    const char weekStr[7][3] = {
            "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
    };

    char ch[30];
    GetNowTime();
    OLED_ShowBigNum(0, 3, nowTime.hour / 10);
    OLED_ShowBigNum(16, 3, nowTime.hour % 10);
    OLED_ShowBigNum(32, 3, 10);
    OLED_ShowBigNum(48, 3, nowTime.minute / 10);
    OLED_ShowBigNum(64, 3, nowTime.minute % 10);
    OLED_ShowBigNum(80, 3, 10);
    OLED_ShowBigNum(96, 3, nowTime.second / 10);
    OLED_ShowBigNum(112, 3, nowTime.second % 10);
    OLED_ShowChinese(32, 0, 4);
    OLED_ShowChinese(64, 0, 5);
    OLED_ShowChinese(96, 0, 6);
    sprintf(ch, "%04d", nowTime.year);
    OLED_ShowString(0, 0, ch, 16);
    sprintf(ch, "%02d", nowTime.month);
    OLED_ShowString(48, 0, ch, 16);
    sprintf(ch, "%02d", nowTime.day);
    OLED_ShowString(80, 0, ch, 16);
    OLED_ShowString(112, 0, weekStr[nowTime.weekday], 16);
}

Temperature场景显示部分

void Oled_ShowTemperature() {
    char ch[30];
    static float temptemp = 0;
    if (temptemp != temperature){
        sprintf(ch, "%.1f", temperature);
        OLED_ShowString(96, 4, ch, 16);
        temptemp = temperature;
    }
}

void Oled_ShowTemperatureBackground() {
    OLED_ShowChinese(96, 0, 0);         //体
    OLED_ShowChinese(112, 0, 1);        //温
}

Notification场景显示部分

void Oled_ShowNotification() {
    OLED_ShowString(0, 0, notificationTitle, 16);
    OLED_ShowString(0, 2, notificationMessage, 16);
}

线性马达震动模块

由于线性马达网上没找到什么单片机控制的教程,购买的小米8的线性马达震感似乎有点弱,也可能是我调教问题。总之,能在通知来时震一下做一个提醒。代码如下

void Motor_Click(){
    GPIO_OutSet(&motor_pin_cfg);
    mxc_delay(MXC_DELAY_MSEC(8));
    GPIO_OutClr(&motor_pin_cfg);
}

原理图如图

Fl39kiYQSbblGUM0eeTkVYHsgFyX

心得体会

每一次funpack,我都会尝试并且收获不一样的东西。这次美信的单片机的活动,我自己重新从零创建了一遍工程,参考st的文件编写clion所需的cmake文件,对单片机开发有了一个更深的认识。

安排的腾讯会议交流,也让我了解到了开发者们对RT- Thread等开源项目贡献的热情和开源的重要性。只有我们开发者一起努力贡献,才能创建出一个强大的开源项目。同时,开源也让我们在学习时避免重复造轮、重复踩坑,节约我们的时间。

感谢硬禾学堂和得捷电子,让我能在这个活动中能够结识到许多志同道合的好朋友,一起交流真的能收获好多。

附件下载
max32660-i2c.zip
团队介绍
中国计量大学机电工程学院
团队成员
叶开
一个大二的电子小白
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号