Funpack第六期--基于MAX32660-EVSYS设计的具有计步和指南针功能的手表
本项目使用MAX32660作为主控制作电子手表,具有日常日期时间显示,具有计步,温度,指南针等功能。
标签
Funpack
MAX32660
电子手表
振青666
更新2021-03-31
837

简介

本项目使用MAX32660作为主控制作电子手表,具有日常日期时间显示,具有计步,温度,指南针等功能。

硬件组成

MAX32660-EVK
九轴传感器:MPU9250
屏幕:OLED12864-SSD1306

接线

GY91模块接线

模块定义 MAX32660定义
VIN NC
3V3 VDDIO
GND GND
SCL P0_8
SDA P0_9
SDO/SAO NC
NCS NC
CSB NC
 

OLED接线

模块定义 MAX32660定义
GND GND
VCC VDDIO
D0 P0_7
D1 P0_6
RES P0_5
DC GND
CS GND

FreIeRA21lXtiuWlmsjujCjOQ34m

软件介绍

程序没有使用操作系统,所有程序直接基于裸机控制,分为主函数(包含主循环),TMR中断函数,UART中断函数等

主程序

int main(void)
{
    year = START_YEAR;
    moon = START_MOON;
    day = START_DAY;
    hr = START_HR;
    min = START_MIN;
    sec = START_SEC;
    RTC_init(day, hr, min, sec);
    MPU_init();
    oled_init();
    tmr1_init();
    UART_SETUP();
    while (1)
    {
        if (read_flag == 0)
        {
            year = (time[0] - '0') * 1000 + (time[1] - '0') * 100 + (time[2] - '0') * 10 + (time[3] - '0');
            moon = (time[5] - '0') * 10 + (time[6] - '0');
            day = (time[8] - '0') * 10 + (time[9] - '0');
            hr = (time[11] - '0') * 10 + (time[12] - '0');
            min = (time[14] - '0') * 10 + (time[15] - '0');
            sec = (time[17] - '0') * 10 + (time[18] - '0');
            error = UART_ReadAsync(MXC_UART_GET_UART(1), &read_req);
            RTC_init(day, hr, min, sec);

            read_flag = 1;
            printf("Clock Correct success!\r\n");
        }
        printTime();
        OLED_Refresh();
        delay_ms(200);
    }
}
 

主函数初始化部分

调用了重新封装的各类初始化函数

1.RTC初始化函数

在原初始化函数的基础上,按照工程需要,优化了传递内容,方便后续使用串口接收校时数据后,对RTC进行较准  
void RTC_init(uint8_t day, uint8_t hr, uint8_t min, uint32_t sec)
{
    sys_cfg_rtc_t sys_cfg;
    NVIC_EnableIRQ(RTC_IRQn);
    RTC_DisableRTCE(MXC_RTC);

    sys_cfg.tmr = MXC_TMR2;
    if (RTC_Init(MXC_RTC, day * SECS_PER_DAY + hr * SECS_PER_HR + min * SECS_PER_MIN + sec, 0, &sys_cfg) != E_NO_ERROR)
    {
        printf("Failed RTC_Setup().\n");
        while (1)
            ;
    }
    if (RTC_EnableRTCE(MXC_RTC) != E_NO_ERROR)
    {
        printf("Failed RTC_EnableRTCE().\n");
        while (1)
            ;
    }
}

2.MPU初始化函数

初始化了MPU9250使用的I2C0,随后对MPU9250的寄存器进行了一系列的配置,对9250进行初始化,随后烧入DMP固件,对DMP进行初始化,并在串口汇报初始化状态
void MPU_init()
{
    printf("MPU9250Init\r\n");
    I2C_Init(MXC_I2C0, I2C_FAST_MODE, NULL);
    MPU9250_Init();
    uint8_t err = mpu_dmp_init();
    while (err)
    {
        printf("mpu_init_err:%d\r\n", err);
        while (1)
            ;
    }
    printf("MPU9250Success\r\n");
}

3.OLED初始化函数

对oled进行初始化配置,并通过串口汇报运行状态
void oled_init()
{
    printf("OLED_INIT\r\n");
    OLED_Init();
    OLED_ColorTurn(0);
    OLED_DisplayTurn(0);
    OLED_DrawCircle(13, 51, 12);

    printf("OLED_Success\r\n");
}

4.TMR初始化函数

将TMR1初始化为50Hz中断,并指定中断函数
void tmr1_init()
{
    tmr_cfg_t tmr;
    NVIC_SetVector(TMR1_IRQn, ContinuousTimer_Handler);
    NVIC_EnableIRQ(TMR1_IRQn);
    uint32_t period_ticks = PeripheralClock / 4000 * INTERVAL_TIME_CONT;
    TMR_Disable(MXC_TMR1);
    TMR_Init(MXC_TMR1, TMR_PRES_4, 0);
    tmr.mode = TMR_MODE_CONTINUOUS;
    tmr.cmp_cnt = period_ticks;
    tmr.pol = 0;
    TMR_Config(MXC_TMR1, &tmr);

    TMR_Enable(MXC_TMR1);
}

5.串口初始化函数

将串口1初始化为异步接收模式,波特率115200,8位数据,1位停止码,无校验,在中断时,调用串口回调函数,记录错误标识
void UART_SETUP()
{
    NVIC_SetVector(UART1_IRQn, UART1_IRQHandler);
    NVIC_ClearPendingIRQ(MXC_UART_GET_IRQ(1));
    NVIC_DisableIRQ(MXC_UART_GET_IRQ(1));
    NVIC_SetPriority(MXC_UART_GET_IRQ(1), 1);
    NVIC_EnableIRQ(MXC_UART_GET_IRQ(1));

    uart_cfg_t cfg;
    cfg.parity = UART_PARITY_DISABLE;
    cfg.size = UART_DATA_SIZE_8_BITS;
    cfg.stop = UART_STOP_1;
    cfg.flow = UART_FLOW_CTRL_EN;
    cfg.pol = UART_FLOW_POL_EN;
    cfg.baud = 115200;
    const sys_cfg_uart_t sys_uart_cfg = {
        MAP_A,
        UART_FLOW_DISABLE,
    };
    error = UART_Init(MXC_UART_GET_UART(1), &cfg, &sys_uart_cfg);
    if (error != E_NO_ERROR)
    {
        printf("Error initializing UART %d\n", error);
        while (1)
            ;
    }

    read_req.data = time;
    read_req.len = 19;
    read_req.callback = read_cb;
    read_flag = 1;
    error = UART_ReadAsync(MXC_UART_GET_UART(1), &read_req);
    if (error != E_NO_ERROR)
    {
        printf("Error starting async read %d\n", error);
        while (1)
            ;
    }
}

主循环部分

1.通过判断read_flag状态决定是否进入调表模块,在调表模块中,通过对接收到的字符串数据进行分解、处理,得到由主机发来的年-月-日 时:分:秒,
将获得的信息发送给RTC初始化函数,实现对RTC进行校正,通过串口汇报调整状态
串口调表发送数据的格式:yyyy-mm-dd hh:mm:ss
2.其次频率为5Hz的循环,读取RTC时间并进行屏幕刷新屏幕较为流畅  

MPU_9250Read() 函数

在每次中断对MPU9250进行读取,将获取的数据发到DMP中获取计步信息,将获取的原始磁力计数据进行处理,解算出指南针的角度,判断当前的方向,并将获取的信息写入显存

int MPU_9250Read() //读取9250数据并处理显示
{
    uint8_t i = 0;
    unsigned long time;
    u8 x2, y2;
    temp = MPU_Get_Temperature();
    mpu_get_compass_reg(compass, &time); //获得磁力计原始数据
    dmp_get_pedometer_step_count(&step); //读取步数
    char wendu[9], bushu[10];

    sprintf(wendu, "%.1fC", temp / 100.0);
    sprintf(bushu, "%04ld", step);
    //为磁力计值滤波
    for (i = 0; i < 19; i++)
    {
        compass0[i] = compass0[i + 1];
        compass1[i] = compass1[i + 1];
    }
    compass0[19] = compass[0];
    compass1[19] = compass[1];
    compass0_avg = 0;
    compass1_avg = 0;
    for (i = 0; i < 19; i++)
    {
        compass0_avg += (double)compass0[i] / 20.0;
        compass1_avg += (double)compass1[i] / 20.0;
    }

    //滤波结束

    //处理磁力计值
    if (abs((int)compass1_avg - COMPASS1) > 20)
    {
        compassAngle = atan((compass0_avg - COMPASS0) / (compass1_avg - COMPASS1));
        //printf("channels:%.4f\n",(doubel)(compass[0]-80)/(float)(compass[1]-960));
    }
    else if(abs((int)compass0_avg - COMPASS0) <100)
    {
        //printf("channels:0.0000\n");
        compassAngle = 0;
    }
		else 
    {
        //printf("channels:0.0000\n");
        compassAngle = PI/2;
    }
    compassAngle = compassAngle * 360 / PI;

    if (compassAngle < 0)
    {
        compassAngle = compassAngle + 360;
    }
		//printf("channels:%f,%f,%f\n",compassAngle,compass0_avg,compass1_avg);
    //磁力计处理完成
    //画线
    x2 = 13 + (int)(11 * cos(compassAngle_old / 57.29578)); //清除上次的线
    y2 = 51 - (int)(11 * sin(compassAngle_old / 57.29578));
    if (y2 > 51)
    {
        OLED_DrawLine(13, 51, x2, y2, 0);
    }
    else
    {
        OLED_DrawLine(x2, y2, 13, 51, 0);
    }

    x2 = 13 + (int)(11 * cos(compassAngle / 57.29578)); //画新线
    y2 = 51 - (int)(11 * sin(compassAngle / 57.29578));
    if (y2 > 51)
    {
        OLED_DrawLine(13, 51, x2, y2, 1);
    }
    else
    {
        OLED_DrawLine(x2, y2, 13, 51, 1);
    }
    if (((int)compassAngle - 180) > -45 && ((int)compassAngle - 180) < 45)
    {
        OLED_ShowChar(10, 48, 'E', 8, 1);
    }
    else if (((int)compassAngle - 90) > -45 && ((int)compassAngle - 90) < 45)
    {
        OLED_ShowChar(10, 48, 'N', 8, 1);
    }
    if (((int)compassAngle - 270) > -45 && ((int)compassAngle - 270) < 45)
    {
        OLED_ShowChar(10, 48, 'S', 8, 1);
    }
    if (((int)compassAngle - 0) > -45 && ((int)compassAngle - 0) < 45)
    {
        OLED_ShowChar(10, 48, 'W', 8, 1);
    }
    compassAngle_old = compassAngle; //保存本次角度,用来下次清除
    //画线完成

    //printf("channels:%.4f,%.4f,%.4f\n", compassAngle, compass0_avg, compass1_avg);
    OLED_ShowString(30, 43, wendu, 16, 1);
    OLED_ShowString(80, 43, bushu, 16, 1);
    return 0;
}

1.读取MPU9250的原始数据

获取MPU9250的温度传感器,加速度传感器信息发送给DMP,用于获取计步数据
获取陀螺仪数据,并在随后进行滑动滤波,提供给随后的部分,解算地磁角度,获得当前朝向

2.解算地磁角度并根据角度解算朝向方向

对传感器原始数据进行滑动滤波,消除传感器抖动的影响
指南针的解算使用了传感器的X,Y轴数据,通过对两个数据减去程序中存储的北方较准数据,求反正切函数,即可获得当前屏幕所指向的方向与北方的夹角,
通过一系列的判断得到当前屏幕指向的方向,和北方的方向,在屏幕上指示出来

心得体会

感谢硬禾学堂和 Digi-Key,这是我第一次接触到美信的单片机,这款单片机麻雀虽小五脏俱全,有丰富的外设接口,方便我们连接各种外设,在有限的时间内,挑战自己,完成指定项目,思考得到了指南针的写法,并学会了在keil中移植代码,收获颇丰

附件下载
max32660-watch.rar
程序源码
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号