总体平衡小车的学习思路和流程

总体思路,先移植OLED模块以及按键模块,把显示以及发指令的部分先搞定,然后去搞直流有刷电机的驱动和控制。然后再去看蓝牙和WiFi模块以及通讯的设置。

OLED模块

首先先把OLED的屏幕源码调出来,看了一下和正点原子的教程上基本是一样的,除了初始化的一些代码不一样,GPIO口使用基地址移位的操作,我自己写的话准备用HAL库直接写。
正点原子的屏幕以及源码都是default为并线8080,我把小车的OLED拆下来看,包括源码比较,采用的是4线SPI,所以写的时候要改一下。

从图中可以看到,OLED的接线是PB5、PB4、PB3以及PA15,分别对应功能接口SCL、CDA、RES和DC。所以4线SPI的GPIO口定义需要在这四个口之上,并完成功能编写。具体编写可以直接移植正点原子的OLED的例程。同时,再次询问客服资料以及对引脚的观察和源码的解读,轮趣的OLED没有CS片选引脚,也可以不考虑这个代码。
同时,四个引脚在GPIO初始化时,全部设置为NOPULL。

根据正点原子的oled屏幕驱动,有以下的代码:

oled.c

/**
 ****************************************************************************************************
 * @file        oled.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       OLED 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的OLED驱动代码
 *
 * 小车的OLED模块默认是4线SPI,所以把8080的并线全部删去
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */
 


#include "stdlib.h"
#include "./BSP/OLED/oled.h"
#include "./BSP/OLED/oledfont.h"
#include "./SYSTEM/delay/delay.h"


/*
 * OLED的显存
 * 每个字节表示8个像素, 128,表示有128列, 8表示有64行, 高位表示高行数.
 * 比如:g_oled_gram[0][0],包含了第一列,第1~8行的数据. g_oled_gram[0][0].0,即表示坐标(0,0)
 * 类似的: g_oled_gram[1][0].1,表示坐标(1,1), g_oled_gram[10][1].2,表示坐标(10,10),
 *
 * 存放格式如下(高位表示高行数).
 * [0]0 1 2 3 ... 127
 * [1]0 1 2 3 ... 127
 * [2]0 1 2 3 ... 127
 * [3]0 1 2 3 ... 127
 * [4]0 1 2 3 ... 127
 * [5]0 1 2 3 ... 127
 * [6]0 1 2 3 ... 127
 * [7]0 1 2 3 ... 127
 */
static uint8_t g_oled_gram[128][8];

/**
 * @brief       更新显存到OLED
 * @param       无
 * @retval      无
 */
void oled_refresh_gram(void)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++)
    {
        oled_wr_byte (0xb0 + i, OLED_CMD); /* 设置页地址(0~7) */
        oled_wr_byte (0x00, OLED_CMD);     /* 设置显示位置—列低地址 */
        oled_wr_byte (0x10, OLED_CMD);     /* 设置显示位置—列高地址 */

        for (n = 0; n < 128; n++)
        {
            oled_wr_byte(g_oled_gram[n][i], OLED_DATA);
        }
    }
}


/**
 * @brief       向OLED写入一个字节
 * @param       data: 要输出的数据
 * @param       cmd: 数据/命令标志 0,表示命令;1,表示数据;
 * @retval      无
 */
static void oled_wr_byte(uint8_t data, uint8_t cmd)
{
    uint8_t i;
    OLED_RS(cmd);   /* 写命令 */

    for (i = 0; i < 8; i++)
    {
        OLED_SCLK(0);

        if (data & 0x80)
        {
            OLED_SDIN(1);
        }
        else
        {
            OLED_SDIN(0);
        }

        OLED_SCLK(1);
        data <<= 1;
    }

    OLED_RS(1);
}


/**
 * @brief       开启OLED显示
 * @param       无
 * @retval      无
 */
void oled_display_on(void)
{
    oled_wr_byte(0X8D, OLED_CMD);   /* SET DCDC命令 */
    oled_wr_byte(0X14, OLED_CMD);   /* DCDC ON */
    oled_wr_byte(0XAF, OLED_CMD);   /* DISPLAY ON */
}

/**
 * @brief       关闭OLED显示
 * @param       无
 * @retval      无
 */
void oled_display_off(void)
{
    oled_wr_byte(0X8D, OLED_CMD);   /* SET DCDC命令 */
    oled_wr_byte(0X10, OLED_CMD);   /* DCDC OFF */
    oled_wr_byte(0XAE, OLED_CMD);   /* DISPLAY OFF */
}

/**
 * @brief       清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
 * @param       无
 * @retval      无
 */
void oled_clear(void)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++)for (n = 0; n < 128; n++)g_oled_gram[n][i] = 0X00;

    oled_refresh_gram();    /* 更新显示 */
}

/**
 * @brief       OLED画点
 * @param       x  : 0~127
 * @param       y  : 0~63
 * @param       dot: 1 填充 0,清空
 * @retval      无
 */
void oled_draw_point(uint8_t x, uint8_t y, uint8_t dot)
{
    uint8_t pos, bx, temp = 0;

    if (x > 127 || y > 63) return;  /* 超出范围了. */

    pos = 7 - y / 8;            /* 计算GRAM里面的y坐标所在的字节, 每个字节可以存储8个行坐标 */
    bx = y % 8;             /* 取余数,方便计算y在对应字节里面的位置,及行(y)位置 */
    temp = 1 << (7 - bx);         /* 高位表示高行号, 得到y对应的bit位置,将该bit先置1 */

    if (dot)    /* 画实心点 */
    {
        g_oled_gram[x][pos] |= temp;
    }
    else        /* 画空点,即不显示 */
    {
        g_oled_gram[x][pos] &= ~temp;
    }
}

/**
 * @brief       OLED填充区域填充
 *   @note:     注意:需要确保: x1<=x2; y1<=y2  0<=x1<=127  0<=y1<=63
 * @param       x1,y1: 起点坐标
 * @param       x2,y2: 终点坐标
 * @param       dot: 1 填充 0,清空
 * @retval      无
 */
void oled_fill(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t dot)
{
    uint8_t x, y;

    for (x = x1; x <= x2; x++)
    {
        for (y = y1; y <= y2; y++)oled_draw_point(x, y, dot);
    }

    oled_refresh_gram();    /* 更新显示 */
}

/**
 * @brief       在指定位置显示一个字符,包括部分字符
 * @param       x   : 0~127
 * @param       y   : 0~63
 * @param       size: 选择字体 12/16
 * @param       mode: 0,反白显示;1,正常显示
 * @retval      无
 */
void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode)
{
    uint8_t temp, t, t1;
    uint8_t y0 = y;
    uint8_t *pfont = 0;
    uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); /* 得到字体一个字符对应点阵集所占的字节数 */
    chr = chr - ' ';        /* 得到偏移后的值,因为字库是从空格开始存储的,第一个字符是空格 */

    if (size == 12)         /* 调用1206字体 */
    {
        pfont = (uint8_t *)oled_asc2_1206[chr];
    }
    else if (size == 16)     /* 调用1608字体 */
    {
        pfont = (uint8_t *)oled_asc2_1608[chr];
    }
    else                    /* 没有的字库 */
    {
        return;
    }

    for (t = 0; t < csize; t++)
    {
        temp = pfont[t];

        for (t1 = 0; t1 < 8; t1++)
        {
            if (temp & 0x80)oled_draw_point(x, y, mode);
            else oled_draw_point(x, y, !mode);

            temp <<= 1;
            y++;

            if ((y - y0) == size)
            {
                y = y0;
                x++;
                break;
            }
        }
    }
}

/**
 * @brief       平方函数, m^n
 * @param       m: 底数
 * @param       n: 指数
 * @retval      无
 */
static uint32_t oled_pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;

    while (n--)
    {
        result *= m;
    }

    return result;
}

/**
 * @brief       显示len个数字
 * @param       x,y : 起始坐标
 * @param       num : 数值(0 ~ 2^32)
 * @param       len : 显示数字的位数
 * @param       size: 选择字体 12/16
 * @retval      无
 */
void oled_show_num(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size)
{
    uint8_t t, temp;
    uint8_t enshow = 0;

    for (t = 0; t < len; t++)   /* 按总显示位数循环 */
    {
        temp = (num / oled_pow(10, len - t - 1)) % 10;  /* 获取对应位的数字 */

        if (enshow == 0 && t < (len - 1))   /* 没有使能显示,且还有位要显示 */
        {
            if (temp == 0)
            {
                oled_show_char(x + (size / 2) * t, y, ' ', size, 1); /* 显示空格,站位 */
                continue;       /* 继续下个一位 */
            }
            else
            {
                enshow = 1;     /* 使能显示 */
            }
        }

        oled_show_char(x + (size / 2) * t, y, temp + '0', size, 1);    /* 显示字符 */
    }
}

/**
 * @brief       显示字符串
 * @param       x,y : 起始坐标
 * @param       size: 选择字体 12/16
 * @param       *p  : 字符串指针,指向字符串首地址
 * @retval      无
 */
void oled_show_string(uint8_t x, uint8_t y, const char *p, uint8_t size)
{
    while ((*p <= '~') && (*p >= ' '))   /* 判断是不是非法字符! */
    {
        if (x > (128 - (size / 2)))     /* 宽度越界 */
        {
            x = 0;
            y += size;                  /* 换行 */
        }

        if (y > (64 - size / 2))            /* 高度越界 */
        {
            y = x = 0;
            oled_clear();
        }

        oled_show_char(x, y, *p, size, 1);   /* 显示一个字符 */
        x += size / 2 + size / 6;      /* ASCII字符宽度为汉字宽度的一半 */
        p++;
    }
}

/**
 * @brief       初始化OLED(SSD1306)
 * @param       无
 * @retval      无
 */
void oled_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    OLED_SPI_RST_CLK_ENABLE();
    OLED_SPI_RS_CLK_ENABLE();
    OLED_SPI_SCLK_CLK_ENABLE();
    OLED_SPI_SDIN_CLK_ENABLE();
    
    gpio_init_struct.Pin = OLED_SPI_RST_PIN;                /* RST引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(OLED_SPI_RST_PORT, &gpio_init_struct);    /* RST引脚模式设置 */

    gpio_init_struct.Pin = OLED_SPI_RS_PIN;                 /* RS引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(OLED_SPI_RS_PORT, &gpio_init_struct);     /* RS引脚模式设置 */

    gpio_init_struct.Pin = OLED_SPI_SCLK_PIN;               /* SCLK引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(OLED_SPI_SCLK_PORT, &gpio_init_struct);   /* SCLK引脚模式设置 */

    gpio_init_struct.Pin = OLED_SPI_SDIN_PIN;               /* SDIN引脚模式设置 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(OLED_SPI_SDIN_PORT, &gpio_init_struct);   /* SDIN引脚模式设置 */
    
    OLED_RST(0);
    delay_ms(100);
    OLED_RST(1);

    oled_wr_byte(0xAE, OLED_CMD);   /* 关闭显示 */
    oled_wr_byte(0xD5, OLED_CMD);   /* 设置时钟分频因子,震荡频率 */
    oled_wr_byte(80, OLED_CMD);     /* [3:0],分频因子;[7:4],震荡频率 */
    oled_wr_byte(0xA8, OLED_CMD);   /* 设置驱动路数 */
    oled_wr_byte(0X3F, OLED_CMD);   /* 默认0X3F(1/64) */
    oled_wr_byte(0xD3, OLED_CMD);   /* 设置显示偏移 */
    oled_wr_byte(0X00, OLED_CMD);   /* 默认为0 */

    oled_wr_byte(0x40, OLED_CMD);   /* 设置显示开始行 [5:0],行数. */

    oled_wr_byte(0x8D, OLED_CMD);   /* 电荷泵设置 */
    oled_wr_byte(0x14, OLED_CMD);   /* bit2,开启/关闭 */
    oled_wr_byte(0x20, OLED_CMD);   /* 设置内存地址模式 */
    oled_wr_byte(0x02, OLED_CMD);   /* [1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; */
    oled_wr_byte(0xA1, OLED_CMD);   /* 段重定义设置,bit0:0,0->0;1,0->127; */
    oled_wr_byte(0xC0, OLED_CMD);   /* 设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */
                                    /* 0xc0就是上下反置,0xc8就是正常 */
    oled_wr_byte(0xDA, OLED_CMD);   /* 设置COM硬件引脚配置 */
    oled_wr_byte(0x12, OLED_CMD);   /* [5:4]配置 */

    oled_wr_byte(0x81, OLED_CMD);   /* 对比度设置 */
    oled_wr_byte(0xEF, OLED_CMD);   /* 1~255;默认0X7F (亮度设置,越大越亮) */
    oled_wr_byte(0xD9, OLED_CMD);   /* 设置预充电周期 */
    oled_wr_byte(0xf1, OLED_CMD);   /* [3:0],PHASE 1;[7:4],PHASE 2; */
    oled_wr_byte(0xDB, OLED_CMD);   /* 设置VCOMH 电压倍率 */
    oled_wr_byte(0x30, OLED_CMD);   /* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */

    oled_wr_byte(0xA4, OLED_CMD);   /* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
    oled_wr_byte(0xA6, OLED_CMD);   /* 设置显示方式;bit0:1,反相显示;0,正常显示 */
    oled_wr_byte(0xAF, OLED_CMD);   /* 开启显示 */
    oled_clear();
}

oled.h

/**
 ****************************************************************************************************
 * @file        oled.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       OLED 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的OLED驱动代码
 *
 * 小车的OLED模块默认是4线SPI,所以把8080的并线全部删去
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */
 
#ifndef __OLED_H
#define __OLED_H

#include "stdlib.h" 
#include "./SYSTEM/sys/sys.h"


/* OLED模式设置
 * 0: 4线串行模式  (模块的BS1,BS2均接GND)
 * 1: 并行8080模式 (模块的BS1,BS2均接VCC)
 * 轮趣的小车上的OLED没有CS片选信号,且默认为4线SPI
 */

/******************************************************************************************/
/* OLED SPI模式引脚 定义 */

#define OLED_SPI_RST_PORT               GPIOB
#define OLED_SPI_RST_PIN                GPIO_PIN_3
#define OLED_SPI_RST_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

#define OLED_SPI_RS_PORT                GPIOA
#define OLED_SPI_RS_PIN                 GPIO_PIN_15
#define OLED_SPI_RS_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define OLED_SPI_SCLK_PORT              GPIOB
#define OLED_SPI_SCLK_PIN               GPIO_PIN_5
#define OLED_SPI_SCLK_CLK_ENABLE()      do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

#define OLED_SPI_SDIN_PORT              GPIOB
#define OLED_SPI_SDIN_PIN               GPIO_PIN_4
#define OLED_SPI_SDIN_CLK_ENABLE()      do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

/******************************************************************************************/

/* OLED SPI模式相关端口控制函数 定义 
 * 注意:OLED_RST/OLED_CS/OLED_RS,这三个是和80并口模式共用的,即80模式也必须实现这3个函数!
 */
#define OLED_RST(x)     do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RST引脚 */

#define OLED_RS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RS引脚 */
                              
#define OLED_SCLK(x)    do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置SCLK引脚 */
#define OLED_SDIN(x)    do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置SDIN引脚 */


/* 命令/数据 定义 */
#define OLED_CMD        0       /* 写命令 */
#define OLED_DATA       1       /* 写数据 */

/******************************************************************************************/
    
static void oled_wr_byte(uint8_t data, uint8_t cmd);    /* 写一个字节到OLED */
static uint32_t oled_pow(uint8_t m, uint8_t n);         /* OLED求平方函数 */


void oled_init(void);           /* OLED初始化 */
void oled_clear(void);          /* OLED清屏 */
void oled_display_on(void);     /* 开启OLED显示 */
void oled_display_off(void);    /* 关闭OLED显示 */
void oled_refresh_gram(void);   /* 更新显存到OLED */ 
void oled_draw_point(uint8_t x, uint8_t y, uint8_t dot);    /* OLED画点 */
void oled_fill(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t dot);        /* OLED区域填充 */
void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode); /* OLED显示字符 */
void oled_show_num(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size);  /* OLED显示数字 */
void oled_show_string(uint8_t x, uint8_t y, const char *p, uint8_t size);           /* OLED显示字符串 */

#endif

oledfont.h

/**
 ****************************************************************************************************
 * @file        oledfont.h
 * @author      正点原子团队(ALIENTEK)
 * @version     V1.0
 * @date        2020-04-21
 * @brief       包含12*12,16*16,24*24,三种OLED用ASCII字体
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:正点原子 STM32F103开发板
 * 在线视频:www.yuanzige.com
 * 技术论坛:www.openedv.com
 * 公司网址:www.alientek.com
 * 购买地址:openedv.taobao.com
 *
 * 修改说明
 * V1.0 20200421
 * 第一次发布
 *
 ****************************************************************************************************
 */
 
#ifndef __OLEDFONT_H
#define __OLEDFONT_H  

/* 常用ASCII表
 * 偏移量32 
 * ASCII字符集: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
 * PC2LCD2002取模方式设置:阴码+逐列式+顺向+C51格式
 * 总共:3个字符集(12*12、16*16和24*24),用户可以自行新增其他分辨率的字符集。
 * 每个字符所占用的字节数为:(size/8+((size%8)?1:0))*(size/2),其中size:是字库生成时的点阵大小(12/16/24...)
 */
 
/* 12*12 ASCII字符集点阵 */
const unsigned char oled_asc2_1206[95][12]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
{0x00,0x00,0x00,0x00,0x3F,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
{0x00,0x00,0x30,0x00,0x40,0x00,0x30,0x00,0x40,0x00,0x00,0x00},/*""",2*/
{0x09,0x00,0x0B,0xC0,0x3D,0x00,0x0B,0xC0,0x3D,0x00,0x09,0x00},/*"#",3*/
{0x18,0xC0,0x24,0x40,0x7F,0xE0,0x22,0x40,0x31,0x80,0x00,0x00},/*"$",4*/
{0x18,0x00,0x24,0xC0,0x1B,0x00,0x0D,0x80,0x32,0x40,0x01,0x80},/*"%",5*/
{0x03,0x80,0x1C,0x40,0x27,0x40,0x1C,0x80,0x07,0x40,0x00,0x40},/*"&",6*/
{0x10,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x80,0x20,0x40,0x40,0x20},/*"(",8*/
{0x00,0x00,0x40,0x20,0x20,0x40,0x1F,0x80,0x00,0x00,0x00,0x00},/*")",9*/
{0x09,0x00,0x06,0x00,0x1F,0x80,0x06,0x00,0x09,0x00,0x00,0x00},/*"*",10*/
{0x04,0x00,0x04,0x00,0x3F,0x80,0x04,0x00,0x04,0x00,0x00,0x00},/*"+",11*/
{0x00,0x10,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/
{0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x00,0x00},/*"-",13*/
{0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/
{0x00,0x20,0x01,0xC0,0x06,0x00,0x38,0x00,0x40,0x00,0x00,0x00},/*"/",15*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"0",16*/
{0x00,0x00,0x10,0x40,0x3F,0xC0,0x00,0x40,0x00,0x00,0x00,0x00},/*"1",17*/
{0x18,0xC0,0x21,0x40,0x22,0x40,0x24,0x40,0x18,0x40,0x00,0x00},/*"2",18*/
{0x10,0x80,0x20,0x40,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"3",19*/
{0x02,0x00,0x0D,0x00,0x11,0x00,0x3F,0xC0,0x01,0x40,0x00,0x00},/*"4",20*/
{0x3C,0x80,0x24,0x40,0x24,0x40,0x24,0x40,0x23,0x80,0x00,0x00},/*"5",21*/
{0x1F,0x80,0x24,0x40,0x24,0x40,0x34,0x40,0x03,0x80,0x00,0x00},/*"6",22*/
{0x30,0x00,0x20,0x00,0x27,0xC0,0x38,0x00,0x20,0x00,0x00,0x00},/*"7",23*/
{0x1B,0x80,0x24,0x40,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"8",24*/
{0x1C,0x00,0x22,0xC0,0x22,0x40,0x22,0x40,0x1F,0x80,0x00,0x00},/*"9",25*/
{0x00,0x00,0x00,0x00,0x08,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/
{0x00,0x00,0x00,0x00,0x04,0x60,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/
{0x00,0x00,0x04,0x00,0x0A,0x00,0x11,0x00,0x20,0x80,0x40,0x40},/*"<",28*/
{0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x00,0x00},/*"=",29*/
{0x00,0x00,0x40,0x40,0x20,0x80,0x11,0x00,0x0A,0x00,0x04,0x00},/*">",30*/
{0x18,0x00,0x20,0x00,0x23,0x40,0x24,0x00,0x18,0x00,0x00,0x00},/*"?",31*/
{0x1F,0x80,0x20,0x40,0x27,0x40,0x29,0x40,0x1F,0x40,0x00,0x00},/*"@",32*/
{0x00,0x40,0x07,0xC0,0x39,0x00,0x0F,0x00,0x01,0xC0,0x00,0x40},/*"A",33*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"B",34*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x30,0x80,0x00,0x00},/*"C",35*/
{0x20,0x40,0x3F,0xC0,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"D",36*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x2E,0x40,0x30,0xC0,0x00,0x00},/*"E",37*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x2E,0x00,0x30,0x00,0x00,0x00},/*"F",38*/
{0x0F,0x00,0x10,0x80,0x20,0x40,0x22,0x40,0x33,0x80,0x02,0x00},/*"G",39*/
{0x20,0x40,0x3F,0xC0,0x04,0x00,0x04,0x00,0x3F,0xC0,0x20,0x40},/*"H",40*/
{0x20,0x40,0x20,0x40,0x3F,0xC0,0x20,0x40,0x20,0x40,0x00,0x00},/*"I",41*/
{0x00,0x60,0x20,0x20,0x20,0x20,0x3F,0xC0,0x20,0x00,0x20,0x00},/*"J",42*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x0B,0x00,0x30,0xC0,0x20,0x40},/*"K",43*/
{0x20,0x40,0x3F,0xC0,0x20,0x40,0x00,0x40,0x00,0x40,0x00,0xC0},/*"L",44*/
{0x3F,0xC0,0x3C,0x00,0x03,0xC0,0x3C,0x00,0x3F,0xC0,0x00,0x00},/*"M",45*/
{0x20,0x40,0x3F,0xC0,0x0C,0x40,0x23,0x00,0x3F,0xC0,0x20,0x00},/*"N",46*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"O",47*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x24,0x00,0x18,0x00,0x00,0x00},/*"P",48*/
{0x1F,0x80,0x21,0x40,0x21,0x40,0x20,0xE0,0x1F,0xA0,0x00,0x00},/*"Q",49*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x26,0x00,0x19,0xC0,0x00,0x40},/*"R",50*/
{0x18,0xC0,0x24,0x40,0x24,0x40,0x22,0x40,0x31,0x80,0x00,0x00},/*"S",51*/
{0x30,0x00,0x20,0x40,0x3F,0xC0,0x20,0x40,0x30,0x00,0x00,0x00},/*"T",52*/
{0x20,0x00,0x3F,0x80,0x00,0x40,0x00,0x40,0x3F,0x80,0x20,0x00},/*"U",53*/
{0x20,0x00,0x3E,0x00,0x01,0xC0,0x07,0x00,0x38,0x00,0x20,0x00},/*"V",54*/
{0x38,0x00,0x07,0xC0,0x3C,0x00,0x07,0xC0,0x38,0x00,0x00,0x00},/*"W",55*/
{0x20,0x40,0x39,0xC0,0x06,0x00,0x39,0xC0,0x20,0x40,0x00,0x00},/*"X",56*/
{0x20,0x00,0x38,0x40,0x07,0xC0,0x38,0x40,0x20,0x00,0x00,0x00},/*"Y",57*/
{0x30,0x40,0x21,0xC0,0x26,0x40,0x38,0x40,0x20,0xC0,0x00,0x00},/*"Z",58*/
{0x00,0x00,0x00,0x00,0x7F,0xE0,0x40,0x20,0x40,0x20,0x00,0x00},/*"[",59*/
{0x00,0x00,0x70,0x00,0x0C,0x00,0x03,0x80,0x00,0x40,0x00,0x00},/*"\",60*/
{0x00,0x00,0x40,0x20,0x40,0x20,0x7F,0xE0,0x00,0x00,0x00,0x00},/*"]",61*/
{0x00,0x00,0x20,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00},/*"^",62*/
{0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10},/*"_",63*/
{0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
{0x00,0x00,0x02,0x80,0x05,0x40,0x05,0x40,0x03,0xC0,0x00,0x40},/*"a",65*/
{0x20,0x00,0x3F,0xC0,0x04,0x40,0x04,0x40,0x03,0x80,0x00,0x00},/*"b",66*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x06,0x40,0x00,0x00},/*"c",67*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x24,0x40,0x3F,0xC0,0x00,0x40},/*"d",68*/
{0x00,0x00,0x03,0x80,0x05,0x40,0x05,0x40,0x03,0x40,0x00,0x00},/*"e",69*/
{0x00,0x00,0x04,0x40,0x1F,0xC0,0x24,0x40,0x24,0x40,0x20,0x00},/*"f",70*/
{0x00,0x00,0x02,0xE0,0x05,0x50,0x05,0x50,0x06,0x50,0x04,0x20},/*"g",71*/
{0x20,0x40,0x3F,0xC0,0x04,0x40,0x04,0x00,0x03,0xC0,0x00,0x40},/*"h",72*/
{0x00,0x00,0x04,0x40,0x27,0xC0,0x00,0x40,0x00,0x00,0x00,0x00},/*"i",73*/
{0x00,0x10,0x00,0x10,0x04,0x10,0x27,0xE0,0x00,0x00,0x00,0x00},/*"j",74*/
{0x20,0x40,0x3F,0xC0,0x01,0x40,0x07,0x00,0x04,0xC0,0x04,0x40},/*"k",75*/
{0x20,0x40,0x20,0x40,0x3F,0xC0,0x00,0x40,0x00,0x40,0x00,0x00},/*"l",76*/
{0x07,0xC0,0x04,0x00,0x07,0xC0,0x04,0x00,0x03,0xC0,0x00,0x00},/*"m",77*/
{0x04,0x40,0x07,0xC0,0x04,0x40,0x04,0x00,0x03,0xC0,0x00,0x40},/*"n",78*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x03,0x80,0x00,0x00},/*"o",79*/
{0x04,0x10,0x07,0xF0,0x04,0x50,0x04,0x40,0x03,0x80,0x00,0x00},/*"p",80*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x50,0x07,0xF0,0x00,0x10},/*"q",81*/
{0x04,0x40,0x07,0xC0,0x02,0x40,0x04,0x00,0x04,0x00,0x00,0x00},/*"r",82*/
{0x00,0x00,0x06,0x40,0x05,0x40,0x05,0x40,0x04,0xC0,0x00,0x00},/*"s",83*/
{0x00,0x00,0x04,0x00,0x1F,0x80,0x04,0x40,0x00,0x40,0x00,0x00},/*"t",84*/
{0x04,0x00,0x07,0x80,0x00,0x40,0x04,0x40,0x07,0xC0,0x00,0x40},/*"u",85*/
{0x04,0x00,0x07,0x00,0x04,0xC0,0x01,0x80,0x06,0x00,0x04,0x00},/*"v",86*/
{0x06,0x00,0x01,0xC0,0x07,0x00,0x01,0xC0,0x06,0x00,0x00,0x00},/*"w",87*/
{0x04,0x40,0x06,0xC0,0x01,0x00,0x06,0xC0,0x04,0x40,0x00,0x00},/*"x",88*/
{0x04,0x10,0x07,0x10,0x04,0xE0,0x01,0x80,0x06,0x00,0x04,0x00},/*"y",89*/
{0x00,0x00,0x04,0x40,0x05,0xC0,0x06,0x40,0x04,0x40,0x00,0x00},/*"z",90*/
{0x00,0x00,0x00,0x00,0x04,0x00,0x7B,0xE0,0x40,0x20,0x00,0x00},/*"{",91*/
{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xF0,0x00,0x00,0x00,0x00},/*"|",92*/
{0x00,0x00,0x40,0x20,0x7B,0xE0,0x04,0x00,0x00,0x00,0x00,0x00},/*"}",93*/
{0x40,0x00,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x40,0x00},/*"~",94*/
}; 

/* 16*16 ASCII字符集点阵 */
const unsigned char oled_asc2_1608[95][16]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
{0x00,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x00,0x00},/*""",2*/
{0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x00,0x00},/*"#",3*/
{0x00,0x00,0x0E,0x18,0x11,0x04,0x3F,0xFF,0x10,0x84,0x0C,0x78,0x00,0x00,0x00,0x00},/*"$",4*/
{0x0F,0x00,0x10,0x84,0x0F,0x38,0x00,0xC0,0x07,0x78,0x18,0x84,0x00,0x78,0x00,0x00},/*"%",5*/
{0x00,0x78,0x0F,0x84,0x10,0xC4,0x11,0x24,0x0E,0x98,0x00,0xE4,0x00,0x84,0x00,0x08},/*"&",6*/
{0x08,0x00,0x68,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x18,0x18,0x20,0x04,0x40,0x02,0x00,0x00},/*"(",8*/
{0x00,0x00,0x40,0x02,0x20,0x04,0x18,0x18,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,0x00},/*")",9*/
{0x02,0x40,0x02,0x40,0x01,0x80,0x0F,0xF0,0x01,0x80,0x02,0x40,0x02,0x40,0x00,0x00},/*"*",10*/
{0x00,0x80,0x00,0x80,0x00,0x80,0x0F,0xF8,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00},/*"+",11*/
{0x00,0x01,0x00,0x0D,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/
{0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80},/*"-",13*/
{0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/
{0x00,0x00,0x00,0x06,0x00,0x18,0x00,0x60,0x01,0x80,0x06,0x00,0x18,0x00,0x20,0x00},/*"/",15*/
{0x00,0x00,0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"0",16*/
{0x00,0x00,0x08,0x04,0x08,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"1",17*/
{0x00,0x00,0x0E,0x0C,0x10,0x14,0x10,0x24,0x10,0x44,0x11,0x84,0x0E,0x0C,0x00,0x00},/*"2",18*/
{0x00,0x00,0x0C,0x18,0x10,0x04,0x11,0x04,0x11,0x04,0x12,0x88,0x0C,0x70,0x00,0x00},/*"3",19*/
{0x00,0x00,0x00,0xE0,0x03,0x20,0x04,0x24,0x08,0x24,0x1F,0xFC,0x00,0x24,0x00,0x00},/*"4",20*/
{0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",21*/
{0x00,0x00,0x07,0xF0,0x08,0x88,0x11,0x04,0x11,0x04,0x18,0x88,0x00,0x70,0x00,0x00},/*"6",22*/
{0x00,0x00,0x1C,0x00,0x10,0x00,0x10,0xFC,0x13,0x00,0x1C,0x00,0x10,0x00,0x00,0x00},/*"7",23*/
{0x00,0x00,0x0E,0x38,0x11,0x44,0x10,0x84,0x10,0x84,0x11,0x44,0x0E,0x38,0x00,0x00},/*"8",24*/
{0x00,0x00,0x07,0x00,0x08,0x8C,0x10,0x44,0x10,0x44,0x08,0x88,0x07,0xF0,0x00,0x00},/*"9",25*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0C,0x03,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/
{0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/
{0x00,0x00,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10,0x04,0x00,0x00},/*"<",28*/
{0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x00,0x00},/*"=",29*/
{0x00,0x00,0x10,0x04,0x08,0x08,0x04,0x10,0x02,0x20,0x01,0x40,0x00,0x80,0x00,0x00},/*">",30*/
{0x00,0x00,0x0E,0x00,0x12,0x00,0x10,0x0C,0x10,0x6C,0x10,0x80,0x0F,0x00,0x00,0x00},/*"?",31*/
{0x03,0xE0,0x0C,0x18,0x13,0xE4,0x14,0x24,0x17,0xC4,0x08,0x28,0x07,0xD0,0x00,0x00},/*"@",32*/
{0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04},/*"A",33*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x11,0x04,0x0E,0x88,0x00,0x70,0x00,0x00},/*"B",34*/
{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x08,0x1C,0x10,0x00,0x00},/*"C",35*/
{0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"D",36*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x17,0xC4,0x10,0x04,0x08,0x18,0x00,0x00},/*"E",37*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x17,0xC0,0x10,0x00,0x08,0x00,0x00,0x00},/*"F",38*/
{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x44,0x1C,0x78,0x00,0x40,0x00,0x00},/*"G",39*/
{0x10,0x04,0x1F,0xFC,0x10,0x84,0x00,0x80,0x00,0x80,0x10,0x84,0x1F,0xFC,0x10,0x04},/*"H",40*/
{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x00,0x00,0x00,0x00},/*"I",41*/
{0x00,0x03,0x00,0x01,0x10,0x01,0x10,0x01,0x1F,0xFE,0x10,0x00,0x10,0x00,0x00,0x00},/*"J",42*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x03,0x80,0x14,0x64,0x18,0x1C,0x10,0x04,0x00,0x00},/*"K",43*/
{0x10,0x04,0x1F,0xFC,0x10,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x0C,0x00,0x00},/*"L",44*/
{0x10,0x04,0x1F,0xFC,0x1F,0x00,0x00,0xFC,0x1F,0x00,0x1F,0xFC,0x10,0x04,0x00,0x00},/*"M",45*/
{0x10,0x04,0x1F,0xFC,0x0C,0x04,0x03,0x00,0x00,0xE0,0x10,0x18,0x1F,0xFC,0x10,0x00},/*"N",46*/
{0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"O",47*/
{0x10,0x04,0x1F,0xFC,0x10,0x84,0x10,0x80,0x10,0x80,0x10,0x80,0x0F,0x00,0x00,0x00},/*"P",48*/
{0x07,0xF0,0x08,0x18,0x10,0x24,0x10,0x24,0x10,0x1C,0x08,0x0A,0x07,0xF2,0x00,0x00},/*"Q",49*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x11,0xC0,0x11,0x30,0x0E,0x0C,0x00,0x04},/*"R",50*/
{0x00,0x00,0x0E,0x1C,0x11,0x04,0x10,0x84,0x10,0x84,0x10,0x44,0x1C,0x38,0x00,0x00},/*"S",51*/
{0x18,0x00,0x10,0x00,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x00,0x18,0x00,0x00,0x00},/*"T",52*/
{0x10,0x00,0x1F,0xF8,0x10,0x04,0x00,0x04,0x00,0x04,0x10,0x04,0x1F,0xF8,0x10,0x00},/*"U",53*/
{0x10,0x00,0x1E,0x00,0x11,0xE0,0x00,0x1C,0x00,0x70,0x13,0x80,0x1C,0x00,0x10,0x00},/*"V",54*/
{0x1F,0xC0,0x10,0x3C,0x00,0xE0,0x1F,0x00,0x00,0xE0,0x10,0x3C,0x1F,0xC0,0x00,0x00},/*"W",55*/
{0x10,0x04,0x18,0x0C,0x16,0x34,0x01,0xC0,0x01,0xC0,0x16,0x34,0x18,0x0C,0x10,0x04},/*"X",56*/
{0x10,0x00,0x1C,0x00,0x13,0x04,0x00,0xFC,0x13,0x04,0x1C,0x00,0x10,0x00,0x00,0x00},/*"Y",57*/
{0x08,0x04,0x10,0x1C,0x10,0x64,0x10,0x84,0x13,0x04,0x1C,0x04,0x10,0x18,0x00,0x00},/*"Z",58*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x40,0x02,0x40,0x02,0x40,0x02,0x00,0x00},/*"[",59*/
{0x00,0x00,0x30,0x00,0x0C,0x00,0x03,0x80,0x00,0x60,0x00,0x1C,0x00,0x03,0x00,0x00},/*"\",60*/
{0x00,0x00,0x40,0x02,0x40,0x02,0x40,0x02,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00},/*"]",61*/
{0x00,0x00,0x00,0x00,0x20,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00},/*"^",62*/
{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01},/*"_",63*/
{0x00,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
{0x00,0x00,0x00,0x98,0x01,0x24,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xFC,0x00,0x04},/*"a",65*/
{0x10,0x00,0x1F,0xFC,0x00,0x88,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"b",66*/
{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x00},/*"c",67*/
{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x11,0x08,0x1F,0xFC,0x00,0x04},/*"d",68*/
{0x00,0x00,0x00,0xF8,0x01,0x44,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xC8,0x00,0x00},/*"e",69*/
{0x00,0x00,0x01,0x04,0x01,0x04,0x0F,0xFC,0x11,0x04,0x11,0x04,0x11,0x00,0x18,0x00},/*"f",70*/
{0x00,0x00,0x00,0xD6,0x01,0x29,0x01,0x29,0x01,0x29,0x01,0xC9,0x01,0x06,0x00,0x00},/*"g",71*/
{0x10,0x04,0x1F,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"h",72*/
{0x00,0x00,0x01,0x04,0x19,0x04,0x19,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"i",73*/
{0x00,0x00,0x00,0x03,0x00,0x01,0x01,0x01,0x19,0x01,0x19,0xFE,0x00,0x00,0x00,0x00},/*"j",74*/
{0x10,0x04,0x1F,0xFC,0x00,0x24,0x00,0x40,0x01,0xB4,0x01,0x0C,0x01,0x04,0x00,0x00},/*"k",75*/
{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"l",76*/
{0x01,0x04,0x01,0xFC,0x01,0x04,0x01,0x00,0x01,0xFC,0x01,0x04,0x01,0x00,0x00,0xFC},/*"m",77*/
{0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"n",78*/
{0x00,0x00,0x00,0xF8,0x01,0x04,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0xF8,0x00,0x00},/*"o",79*/
{0x01,0x01,0x01,0xFF,0x00,0x85,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"p",80*/
{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x05,0x01,0xFF,0x00,0x01},/*"q",81*/
{0x01,0x04,0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x04,0x01,0x00,0x01,0x80,0x00,0x00},/*"r",82*/
{0x00,0x00,0x00,0xCC,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x98,0x00,0x00},/*"s",83*/
{0x00,0x00,0x01,0x00,0x01,0x00,0x07,0xF8,0x01,0x04,0x01,0x04,0x00,0x00,0x00,0x00},/*"t",84*/
{0x01,0x00,0x01,0xF8,0x00,0x04,0x00,0x04,0x00,0x04,0x01,0x08,0x01,0xFC,0x00,0x04},/*"u",85*/
{0x01,0x00,0x01,0x80,0x01,0x70,0x00,0x0C,0x00,0x10,0x01,0x60,0x01,0x80,0x01,0x00},/*"v",86*/
{0x01,0xF0,0x01,0x0C,0x00,0x30,0x01,0xC0,0x00,0x30,0x01,0x0C,0x01,0xF0,0x01,0x00},/*"w",87*/
{0x00,0x00,0x01,0x04,0x01,0x8C,0x00,0x74,0x01,0x70,0x01,0x8C,0x01,0x04,0x00,0x00},/*"x",88*/
{0x01,0x01,0x01,0x81,0x01,0x71,0x00,0x0E,0x00,0x18,0x01,0x60,0x01,0x80,0x01,0x00},/*"y",89*/
{0x00,0x00,0x01,0x84,0x01,0x0C,0x01,0x34,0x01,0x44,0x01,0x84,0x01,0x0C,0x00,0x00},/*"z",90*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x3E,0xFC,0x40,0x02,0x40,0x02},/*"{",91*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},/*"|",92*/
{0x00,0x00,0x40,0x02,0x40,0x02,0x3E,0xFC,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"}",93*/
{0x00,0x00,0x60,0x00,0x80,0x00,0x80,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x20,0x00},/*"~",94*/
};      


#endif

按键

阅读源码,看到按键KEY连接的是PA5,改一下就可以了。

key.c

/**
 ****************************************************************************************************
 * @file        key.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       按键输入 驱动代码
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的按键驱动
 * 只有一个按键,在PA5
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */



#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"


/**
 * @brief       按键初始化函数
 * @param       无
 * @retval      无
 */
void key_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    KEY_GPIO_CLK_ENABLE();                                     /* KEY时钟使能 */

    gpio_init_struct.Pin = KEY_GPIO_PIN;                       /* KEY引脚 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                   /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                       /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;             /* 高速 */
    HAL_GPIO_Init(KEY_GPIO_PORT, &gpio_init_struct);           /* KEY引脚模式设置,上拉输入 */

}

/**
 * @brief       按键扫描函数
 * @note        该函数有响应优先级(同时按下多个按键): WK_UP > KEY1 > KEY0!!
 * @param       mode:0 / 1, 具体含义如下:
 *   @arg       0,  不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
 *                  必须松开以后, 再次按下才会返回其他键值)
 *   @arg       1,  支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
 * @retval      键值, 定义如下:
 *              KEY_PRES, 1, KEY按下
 */
uint8_t key_scan(uint8_t mode)
{
    static uint8_t key_up = 1;  /* 按键按松开标志 */
    uint8_t keyval = 0;

    if (mode) key_up = 1;       /* 支持连按 */

    if (key_up && KEY == 0)  /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        delay_ms(10);           /* 去抖动 */
        key_up = 0;

        if (KEY == 0)  keyval = KEY_PRES;

    }
    else if (KEY == 1) /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
    }

    return keyval;              /* 返回键值 */
}

key.h

/**
 ****************************************************************************************************
 * @file        key.h
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       按键输入 驱动代码
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的按键驱动
 * 只有一个按键,在PA5
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */
 
 
#ifndef __KEY_H
#define __KEY_H

#include "./SYSTEM/sys/sys.h"

/******************************************************************************************/
/* 引脚 定义 */

#define KEY_GPIO_PORT                  GPIOA
#define KEY_GPIO_PIN                   GPIO_PIN_5
#define KEY_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

/******************************************************************************************/

#define KEY        HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN)     /* 读取KEY引脚 */


#define KEY_PRES    1              /* KEY0按下 */

void key_init(void);                /* 按键初始化函数 */
uint8_t key_scan(uint8_t mode);     /* 按键扫描函数 */

#endif

LED

LED灯接在PA4,把这个初始化了然后通过翻转当前IO状态就可以控制灯的亮灭了。

led.c

/**
 ****************************************************************************************************
 * @file        led.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       LED 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的LED驱动,LED接在PA4
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */


#include "./BSP/LED/led.h"


/**
 * @brief       初始化LED相关IO口, 并使能时钟
 * @param       无
 * @retval      无
 */
void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED_GPIO_CLK_ENABLE();                                 /* LED时钟使能 */

    gpio_init_struct.Pin = LED_GPIO_PIN;                   /* LED引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(LED_GPIO_PORT, &gpio_init_struct);       /* 初始化LED引脚 */

    LED(0);                                                /* 关闭 LED */
}

/**
 * @brief       LED闪烁
 * @param       无
 * @retval      无
 */
void led_flash(uint16_t time)
{
    static int temp;
    if(!time) LED(0);
    else if(++temp == time)
    {
        LED_TOGGLE();
        temp = 0;
    }
}

led.h

/**
 ****************************************************************************************************
 * @file        led.h
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       LED 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的LED驱动,LED接在PA4
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */


#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define LED_GPIO_PORT                  GPIOA
#define LED_GPIO_PIN                   GPIO_PIN_4
#define LED_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)           /* PA口时钟使能 */

/******************************************************************************************/
/* LED端口定义 */
#define LED(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)      /* LED翻转 */


/* LED取反定义 */
#define LED_TOGGLE()   do{ HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN); }while(0)        /* 翻转LED */

/******************************************************************************************/
/* 外部接口函数*/
void led_init(void);                                                                         /* 初始化 */
void led_flash(uint16_t time);                                                               /* LED闪烁 */

#endif

高级定时器初始化

c8t6只有4个定时器,查阅f1的参考手册,应该是TIM1为高级定时器,而TIM2~4都是通用定时器

根据查询小车的开发手册可以看到,控制电机的PWM波有PA8和PA11产生,通过查询手册,PA8对应TIM1CH1,PA11对应TIM1CH4
采用的直流有刷驱动器是TB6612,可以同时控制两个电机:
TB6612的接线示意图

接入一个电机为例,PWMA就是单片机的一个PWM输出,AIN1/2用来控制旋转方向,AO1/2就是输出的电平,接到电机的正负极;B的同理,以此来控制两个电机。要注意的是STBY需要在高电平才能够工作。电机的旋转方向如下表所示:
TB6612控制真值表
直流有刷电机驱动的代码,我会在最后的整体控制中写,定时器1的初始化如下面所示:

atim.c

/**
 ****************************************************************************************************
 * @file        atim.c
 * @author      Xia
 * @version     V1.1
 * @date        2023-08-18
 * @brief       高级定时器 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的高级定时器驱动
 * 用TIM1的CH1和CH4发出两路PWM
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 * V1.1 20230818
 * 1.删除死区刹车部分,对于本项目没有用处,且之前导致PWM没有产生的原因就是使能刹车
 *
 *
 ****************************************************************************************************
 */


#include "./BSP/TIMER/atim.h"
#include "./BSP/TIMER/gtim.h"
#include "./BSP/CONTROL/control.h"
 
TIM_HandleTypeDef g_tim1_handle;

/* c8t6的唯一一个高级定时器 */
/**
 * @brief       高级定时器TIM1 通道1、通道4 PWM输出模式 初始化函数
 * @note
 *              高级定时器的时钟来自APB2, 而PCLK2 = 72Mhz, 我们设置PPRE2不分频, 因此
 *              高级定时器时钟 = 72Mhz高级定时器时钟 = 72Mhz 
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us. 
 *              Ft=定时器工作频率,单位:Mhz 
 * @param       arr: 自动重装值 
 * @param       psc: 时钟预分频数 
 * @retval 无
 */
void atim_tim1_pwm_init(uint16_t psc, uint16_t arr)
{
    TIM_OC_InitTypeDef sConfigOC = {0};
//    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
    
    /* 1.通过TIM_HandleTypeDef初始TIM1的参数 */
    g_tim1_handle.Instance = ATIM_TIM1_PWM;
    g_tim1_handle.Init.Prescaler = psc;                                         /* 预分频系数 */
    g_tim1_handle.Init.CounterMode = TIM_COUNTERMODE_UP;                        /* 递增 */
    g_tim1_handle.Init.Period = arr;                                            /* 自动重装载值 */
    g_tim1_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;                  /* 时钟不分频 */
    g_tim1_handle.Init.RepetitionCounter = 0;                                   /* 不开启重复计数器 */
    g_tim1_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;       /* 开启影子寄存器,有缓冲 */
    HAL_TIM_PWM_Init(&g_tim1_handle);
    
    /* 2.设置PWM输出 */
    /* 控制两个电机,所以要放入两个channel中 */
    sConfigOC.OCMode = TIM_OCMODE_PWM1;                                         /* PWM模式1 */
    sConfigOC.Pulse = 0;                                                        /* 用来设置占空比,初始化为0 */
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;                                 /* 输出极性为高电平 */
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;                               /* 互补输出通道也是高电平 */
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;                                  /* 不使用快速模式 */
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;                              /* 开启刹车才会生效,空闲状态下低电平 */
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;                            /* 开启刹车才会生效,空闲状态下低电平 */
    HAL_TIM_PWM_ConfigChannel(&g_tim1_handle, &sConfigOC, ATIM_TIM1_PWM_CH1);
    HAL_TIM_PWM_ConfigChannel(&g_tim1_handle, &sConfigOC, ATIM_TIM1_PWM_CH4);
    
//    /* 3.设置死区参数 */
//    /* 运行状态关闭死区 */
//    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
//    /* 空闲模式的关闭输出状态 */
//    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
//    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;                         /* 关闭寄存器锁功能 */
//    sBreakDeadTimeConfig.DeadTime = 0;                                          /* 初始化死去时间设置为0 */
//    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;                        /* 失能刹车输入 */
//    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;                /* 刹车输入有效信号极性为高 */
//    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;         /* 未使能AOE,刹车后不会自动恢复 */
//    HAL_TIMEx_ConfigBreakDeadTime(&g_tim1_handle, &sBreakDeadTimeConfig);
    
    /* 4.开启PWM输出 */
    HAL_TIM_PWM_Start(&g_tim1_handle, ATIM_TIM1_PWM_CH1);
    HAL_TIM_PWM_Start(&g_tim1_handle, ATIM_TIM1_PWM_CH4);
    
//    __HAL_TIM_ENABLE_IT(&g_tim1_handle,TIM_IT_UPDATE);               /* 使能更新中断 */
//    __HAL_TIM_CLEAR_FLAG(&g_tim1_handle,TIM_IT_UPDATE);              /* 清除更新中断标志位 */
}

/* 在MSP函数中定义NVIC、GPIO等 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == ATIM_TIM1_PWM)
    {
        GPIO_InitTypeDef gpio_init_struct;                      /* GPIO句柄 */
        ATIM_TIM1_PWM_CH1_CLK_ENABLE();                         /* 开启TIM1时钟 */
        ATIM_TIM1_PWM_CH4_CLK_ENABLE();
        ATIM_TIM1_PWM_CH1_GPIO_CLK_ENABLE();                    /* 开启GPIOA时钟 */
        ATIM_TIM1_PWM_CH4_GPIO_CLK_ENABLE();
        
        /* 初始化GPIO */
        gpio_init_struct.Pin = ATIM_TIM1_PWM_CH1_GPIO_PIN;      /* 选定定时器1通道1引脚 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                /* 复用推挽输出 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;           /* 低速 */
        HAL_GPIO_Init(ATIM_TIM1_PWM_CH1_GPIO_PORT, &gpio_init_struct);
        
        gpio_init_struct.Pin = ATIM_TIM1_PWM_CH4_GPIO_PIN;      /* 选定定时器1通道4引脚 */
        HAL_GPIO_Init(ATIM_TIM1_PWM_CH4_GPIO_PORT, &gpio_init_struct);
    }
}

atim.h

/**
 ****************************************************************************************************
 * @file        atim.h
 * @author      Xia
 * @version     V1.1
 * @date        2023-08-18
 * @brief       高级定时器 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的高级定时器驱动
 * 用TIM1的CH1和CH4发出两路PWM
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 * V1.1 20230818
 * 1.删除死区刹车部分,对于本项目没有用处,且之前导致PWM没有产生的原因就是使能刹车
 *
 *
 ****************************************************************************************************
 */

#ifndef __ATIM_H
#define __ATIM_H

#include "./SYSTEM/sys/sys.h"

/******************************************************************************************/
/* 高级定时器 定义 */


#define ATIM_TIM1_PWM_CH1_GPIO_PORT            GPIOA
#define ATIM_TIM1_PWM_CH1_GPIO_PIN             GPIO_PIN_8
#define ATIM_TIM1_PWM_CH1_GPIO_CLK_ENABLE()    do{  __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */
#define ATIM_TIM1_PWM_CH4_GPIO_PORT            GPIOA
#define ATIM_TIM1_PWM_CH4_GPIO_PIN             GPIO_PIN_11
#define ATIM_TIM1_PWM_CH4_GPIO_CLK_ENABLE()    do{  __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define ATIM_TIM1_PWM                          TIM1
#define ATIM_TIM1_PWM_IRQn                     TIM1_UP_IRQn
#define ATIM_TIM1_PWM_IRQHandler               TIM1_UP_IRQHandler
#define ATIM_TIM1_PWM_CH1                      TIM_CHANNEL_1                                  /* 通道Y,  1<= Y <=4 */
#define ATIM_TIM1_PWM_CH1_CCRX                 TIM1->CCR1                                     /* 通道Y的输出比较寄存器 */
#define ATIM_TIM1_PWM_CH1_CLK_ENABLE()         do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0)     /* TIM1 时钟使能 */
#define ATIM_TIM1_PWM_CH4                      TIM_CHANNEL_4                                  /* 通道Y,  1<= Y <=4 */
#define ATIM_TIM1_PWM_CH4_CCRX                 TIM1->CCR4                                     /* 通道Y的输出比较寄存器 */
#define ATIM_TIM1_PWM_CH4_CLK_ENABLE()         do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0)     /* TIM1 时钟使能 */


/******************************************************************************************/
void atim_tim1_pwm_init(uint16_t psc, uint16_t arr);

#endif

通用定时器初始化

这里的TIM2和TIM4是用作编码器模式,而TIM3则是超声波传感器的相关代码,这里我先介绍编码器。

编码器,在正点原子的章节中也有所介绍,如果感兴趣可以去看我的知乎上的学习笔记(基础课程,没什么代码所以我没有发表在csdn上),我最后贴出来以供参考,可以进我的专栏里面翻翻看。主要的定时器操作就是要完成输入捕获,通过编码器输出的脉冲信号计数来判断电机运动的角度,这些操作就要配置好编码器之后,在更新中断中完成(也可以跟轮趣小车的代码一样在外部中断中完成,只要是读取定时器的cnt值就可以,然后根据中断的触发频率来计算速度就可以了)。
在轮趣小车的硬件设计中,对应的编码器引脚如下图所示:
对应引脚
根据以上的引脚,查询出对应的TIM Channel是分别为TIM2CH1、TIM2CH2以及TIM4CH1、TIM4CH2(如果我没有记错,编码器模式只能是CH1和CH2)。

而对于超声波传感器来说,我这边就不多做传感器的原理解释,就是直接应用以及配置:使用的是定时器3通道3进行输入捕获,对应硬件设计的IO口是PB0,然后还有一个GPIO的引脚PB1作为触发的信号来发射超声波。超声波传感器的时序图如下图所示:
超声波传感器的信号触发时序图
从图中可以看到,我们需要做到的就是控制PB1产生一个10us的高电平信号,然后等待输入捕获就可以了。输入捕获我在正点原子的学习笔记中也有所写到,具体的代码逻辑大概可以概括为:

首先,设置TIM3_CH3捕获上升沿,然后
等待上升沿中断到来,当捕获到上升沿中断,此时如果g_timxchy_cap_sta的第 6位为 0,则表
示还没有捕获到新的上升沿,就先把 g_timxchy_cap_sta、g_timxchy_cap_val和 TIM3_CNT寄存器等清零,然后再设置 g_timxchy_cap_sta的第 6位为 1,标记捕获到高电平,最后设置为下降
沿捕获,等待下降沿到来;如果等待下降沿到来期间,定时器发生了溢出,就用 g_timxchy_cap_sta变量对溢出次数进行计数,当最大溢出次数来到的时候,就强制标记捕获完成,并 配置 定时器通道上升沿捕获 。当下降沿到来的时候,先设置 g_timxchy_cap_sta的第 7位为 1,标记成功捕获一次高电平,然后读取此时的定时器值到 g_timxchy_cap_val里面,最后设置为上升沿捕获,回到初始状态。

以上是直接从正点原子的开发手册上摘录下来的,如果不想看也可以直接把代码拉下来解读或者直接用就可以了,通用的输入捕获都可以用这段代码。最后得到了计数值之后进行距离的换算就可以了:
距离=高电平时间*声速(340M/S)

gtim.c

/**
 ****************************************************************************************************
 * @file        gtim.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       通用定时器 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的通用定时器驱动代码
 * TIM2与TIM4连接两个编码器
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#include "./BSP/TIMER/gtim.h"
#include "./BSP/TIMER/atim.h"
#include "./BSP/CONTROL/control.h"
#include "./SYSTEM/delay/delay.h"


TIM_HandleTypeDef g_tim2_encode_handle;         /* 定时器2句柄 */
TIM_Encoder_InitTypeDef g_tim2_encoder_handle;  /* 定时2编码器句柄 */
TIM_HandleTypeDef g_tim4_encode_handle;         /* 定时器4句柄 */
TIM_Encoder_InitTypeDef g_tim4_encoder_handle;  /* 定时4编码器句柄 */

TIM_HandleTypeDef g_tim3_ultrasonic_handle;     /* 定时器3句柄 */


/**
 * @brief       通用定时器TIM2、4的 通道1、通道2 编码器模式 初始化函数
 * @note
 *              通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              通用定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 时钟预分频数
 * @retval      无
 */
void gtim_tim2_encoder_init(uint16_t psc, uint16_t arr)
{
    /* 定时器x配置 */
    g_tim2_encode_handle.Instance = GTIM_TIM2_ENCODER;                      /* 定时器x */
    g_tim2_encode_handle.Init.Prescaler = psc;                              /* 定时器分频 */
    g_tim2_encode_handle.Init.Period = arr;                                 /* 自动重装载值 */
    g_tim2_encode_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;       /* 时钟分频因子 */
    
    /* 定时器x编码器配置 */
    g_tim2_encoder_handle.EncoderMode = TIM_ENCODERMODE_TI12;               /* TI1、TI2都检测,4倍频 */
    g_tim2_encoder_handle.IC1Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */
    g_tim2_encoder_handle.IC1Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */
    g_tim2_encoder_handle.IC1Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */
    g_tim2_encoder_handle.IC1Filter = 10;                                   /* 滤波器设置 */
    g_tim2_encoder_handle.IC2Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */
    g_tim2_encoder_handle.IC2Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */
    g_tim2_encoder_handle.IC2Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */
    g_tim2_encoder_handle.IC2Filter = 10;                                   /* 滤波器设置 */
    HAL_TIM_Encoder_Init(&g_tim2_encode_handle, &g_tim2_encoder_handle);    /* 初始化定时器x编码器 */
     
    HAL_TIM_Encoder_Start(&g_tim2_encode_handle,GTIM_TIM2_ENCODER_CH1);     /* 使能编码器通道1 */
    HAL_TIM_Encoder_Start(&g_tim2_encode_handle,GTIM_TIM2_ENCODER_CH2);     /* 使能编码器通道2 */
//    __HAL_TIM_ENABLE_IT(&g_tim2_encode_handle,TIM_IT_UPDATE);               /* 使能更新中断 */
//    __HAL_TIM_CLEAR_FLAG(&g_tim2_encode_handle,TIM_IT_UPDATE);              /* 清除更新中断标志位 */
}


void gtim_tim4_encoder_init(uint16_t psc, uint16_t arr)
{
    /* 定时器x配置 */
    g_tim4_encode_handle.Instance = GTIM_TIM4_ENCODER;                      /* 定时器x */
    g_tim4_encode_handle.Init.Prescaler = psc;                              /* 定时器分频 */
    g_tim4_encode_handle.Init.Period = arr;                                 /* 自动重装载值 */
    g_tim4_encode_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;       /* 时钟分频因子 */
    
    /* 定时器x编码器配置 */
    g_tim4_encoder_handle.EncoderMode = TIM_ENCODERMODE_TI12;               /* TI1、TI2都检测,4倍频 */
    g_tim4_encoder_handle.IC1Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */
    g_tim4_encoder_handle.IC1Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */
    g_tim4_encoder_handle.IC1Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */
    g_tim4_encoder_handle.IC1Filter = 10;                                   /* 滤波器设置 */
    g_tim4_encoder_handle.IC2Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */
    g_tim4_encoder_handle.IC2Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */
    g_tim4_encoder_handle.IC2Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */
    g_tim4_encoder_handle.IC2Filter = 10;                                   /* 滤波器设置 */
    HAL_TIM_Encoder_Init(&g_tim4_encode_handle, &g_tim4_encoder_handle);/* 初始化定时器x编码器 */
     
    HAL_TIM_Encoder_Start(&g_tim4_encode_handle,GTIM_TIM4_ENCODER_CH1);     /* 使能编码器通道1 */
    HAL_TIM_Encoder_Start(&g_tim4_encode_handle,GTIM_TIM4_ENCODER_CH2);     /* 使能编码器通道2 */
//    __HAL_TIM_ENABLE_IT(&g_tim4_encode_handle,TIM_IT_UPDATE);               /* 使能更新中断 */
//    __HAL_TIM_CLEAR_FLAG(&g_tim4_encode_handle,TIM_IT_UPDATE);              /* 清除更新中断标志位 */
}


void gtim_tim3_ultrasonic_init(uint16_t psc, uint16_t arr)
{
    TIM_IC_InitTypeDef timx_ic_cap_chy = {0};                                   /* 输入捕获结构体 */
    
    /* 定时器x配置 */
    g_tim3_ultrasonic_handle.Instance = GTIM_TIM4_ENCODER;                      /* 定时器x */
    g_tim3_ultrasonic_handle.Init.Prescaler = psc;                              /* 定时器分频 */
    g_tim3_ultrasonic_handle.Init.Period = arr;                                 /* 自动重装载值 */
    g_tim3_ultrasonic_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;       /* 时钟分频因子 */
    
    timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING;                 /* 上升沿捕获 */
    timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;             /* 映射到TI1上 */
    timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1;                       /* 配置输入分频,不分频 */
    timx_ic_cap_chy.ICFilter = 0;                                       /* 配置输入滤波器,不滤波 */
    HAL_TIM_IC_ConfigChannel(&g_tim3_ultrasonic_handle, &timx_ic_cap_chy, GTIM_TIM3_CAP_CH3);  /* 配置TIM3通道3 */

    __HAL_TIM_ENABLE_IT(&g_tim3_ultrasonic_handle, TIM_IT_UPDATE);         /* 使能更新中断 */
    HAL_TIM_IC_Start_IT(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3);     /* 开始捕获TIM3的通道1 */
}

/**
 * @brief       定时器底层驱动,时钟使能,引脚配置
                此函数会被HAL_TIM_Encoder_Init()调用
 * @param       htim:定时器句柄
 * @retval      无
 */
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIM2_ENCODER)
    {
        GPIO_InitTypeDef gpio_init_struct;
        GTIM_TIM2_ENCODER_CH1_CLK_ENABLE();                                      /* 开启定时器时钟 */
        GTIM_TIM2_ENCODER_CH2_CLK_ENABLE();
        GTIM_TIM2_ENCODER_CH1_GPIO_CLK_ENABLE();                                 /* 开启通道的GPIO时钟 */
        GTIM_TIM2_ENCODER_CH2_GPIO_CLK_ENABLE();


        gpio_init_struct.Pin = GTIM_TIM2_ENCODER_CH1_GPIO_PIN;                   /* 通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_INPUT;                                 /* 输入 */
        gpio_init_struct.Pull = GPIO_NOPULL;                                     /* 无上下拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                           /* 高速 */
        HAL_GPIO_Init(GTIM_TIM2_ENCODER_CH1_GPIO_PORT, &gpio_init_struct);  
        
        gpio_init_struct.Pin = GTIM_TIM2_ENCODER_CH2_GPIO_PIN;                   /* 通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_INPUT;                                 /* 输入 */
        gpio_init_struct.Pull = GPIO_NOPULL;                                     /* 无上下拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                           /* 高速 */
        HAL_GPIO_Init(GTIM_TIM2_ENCODER_CH2_GPIO_PORT, &gpio_init_struct);         
       
//        HAL_NVIC_SetPriority(GTIM_TIM2_ENCODER_INT_IRQn, 3, 0);                  /* 中断优先级设置 */
//        HAL_NVIC_EnableIRQ(GTIM_TIM2_ENCODER_INT_IRQn);                          /* 开启中断 */
    }
    if (htim->Instance == GTIM_TIM4_ENCODER)
    {
        GPIO_InitTypeDef gpio_init_struct;
        GTIM_TIM4_ENCODER_CH1_CLK_ENABLE();                                      /* 开启定时器时钟 */
        GTIM_TIM4_ENCODER_CH2_CLK_ENABLE();
        GTIM_TIM4_ENCODER_CH1_GPIO_CLK_ENABLE();                                 /* 开启通道的GPIO时钟 */
        GTIM_TIM4_ENCODER_CH2_GPIO_CLK_ENABLE();


        gpio_init_struct.Pin = GTIM_TIM4_ENCODER_CH1_GPIO_PIN;                   /* 通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_INPUT;                                 /* 输入 */
        gpio_init_struct.Pull = GPIO_NOPULL;                                     /* 无上下拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                           /* 高速 */
        HAL_GPIO_Init(GTIM_TIM4_ENCODER_CH1_GPIO_PORT, &gpio_init_struct);  
        
        gpio_init_struct.Pin = GTIM_TIM4_ENCODER_CH2_GPIO_PIN;                   /* 通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_INPUT;                                 /* 输入 */
        gpio_init_struct.Pull = GPIO_NOPULL;                                     /* 无上下拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                           /* 高速 */
        HAL_GPIO_Init(GTIM_TIM4_ENCODER_CH2_GPIO_PORT, &gpio_init_struct);         
       
//        HAL_NVIC_SetPriority(GTIM_TIM4_ENCODER_INT_IRQn, 3, 1);                  /* 中断优先级设置 */
//        HAL_NVIC_EnableIRQ(GTIM_TIM4_ENCODER_INT_IRQn);                          /* 开启中断 */
    }
}

/**
 * @brief       通用定时器输入捕获初始化接口
                HAL库调用的接口,用于配置不同的输入捕获
 * @param       htim:定时器句柄
 * @note        此函数会被HAL_TIM_IC_Init()调用
 * @retval      无
 */
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIM3_CAP)                    /*输入通道捕获*/
    {
        GPIO_InitTypeDef gpio_init_struct;
        GTIM_TIM3_CAP_CH3_CLK_ENABLE();                     /* 使能TIMx时钟 */
        GTIM_TIM3_CAP_CH3_GPIO_CLK_ENABLE();                /* 开启捕获IO的时钟 */

        gpio_init_struct.Pin = GTIM_TIM3_CAP_CH3_GPIO_PIN;  /* 输入捕获的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_INPUT;            /* 输入 */
        gpio_init_struct.Pull = GPIO_NOPULL;                /* 浮空 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
        HAL_GPIO_Init(GTIM_TIM3_CAP_CH3_GPIO_PORT, &gpio_init_struct);

        HAL_NVIC_SetPriority(GTIM_TIM3_CAP_IRQn, 1, 3);     /* 抢占1,子优先级3 */
        HAL_NVIC_EnableIRQ(GTIM_TIM3_CAP_IRQn);             /* 开启ITMx中断 */
    }
}

///**
// * @brief       定时器中断服务函数
// * @param       无
// * @retval      无
// */
//void GTIM_TIM2_ENCODER_INT_IRQHandler(void)
//{
//    HAL_TIM_IRQHandler(&g_tim2_encode_handle);
//}

//void GTIM_TIM4_ENCODER_INT_IRQHandler(void)
//{
//    HAL_TIM_IRQHandler(&g_tim4_encode_handle);
//}


///* 两个通用定时器统计溢出次数 */
//volatile int g_tim2_encode_count = 0;                                   /* 溢出次数 */
//volatile int g_tim4_encode_count = 0;                                   /* 溢出次数 */

///**
// * @brief       定时器更新中断回调函数
// * @param        htim:定时器句柄指针
// * @note        此函数会被定时器中断函数共同调用的
// * @retval      无
// */
//void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
//{
//    if (htim->Instance == GTIM_TIM2_ENCODER)
//    {
//        if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&g_tim2_encode_handle))   /* 判断CR1的DIR位 */
//        {
//            g_tim2_encode_count--;                                      /* DIR位为1,也就是递减计数 */
//        }
//        else
//        {
//            g_tim2_encode_count++;                                      /* DIR位为0,也就是递增计数 */
//        }
//    }
//    if (htim->Instance == GTIM_TIM4_ENCODER)
//    {
//        if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&g_tim4_encode_handle))   /* 判断CR1的DIR位 */
//        {
//            g_tim4_encode_count--;                                      /* DIR位为1,也就是递减计数 */
//        }
//        else
//        {
//            g_tim4_encode_count++;                                      /* DIR位为0,也就是递增计数 */
//        }
//    }

//}

/* 输入捕获状态(g_tim3ch3_cap_sta)
 * [7]  :0,没有成功的捕获;1,成功捕获到一次.
 * [6]  :0,还没捕获到高电平;1,已经捕获到高电平了.
 * [5:0]:捕获高电平后溢出的次数,最多溢出63次,所以最长捕获值 = 63*65536 + 65535 = 4194303
 *       注意:为了通用,我们默认ARR和CCRy都是16位寄存器,对于32位的定时器(如:TIM5),也只按16位使用
 *       按1us的计数频率,最长溢出时间为:4194303 us, 约4.19秒
 *
 *      (说明一下:正常32位定时器来说,1us计数器加1,溢出时间:4294秒)
 */
uint8_t g_tim3ch3_cap_sta = 0;    /* 输入捕获状态 */
uint16_t g_tim3ch3_cap_val = 0;   /* 输入捕获值 */


/**
 * @brief       定时器中断服务函数
 * @param       无
 * @retval      无
 */
void GTIM_TIM3_CAP_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim3_ultrasonic_handle);  /* 定时器HAL库共用处理函数 */
}


/**
 * @brief       定时器输入捕获中断处理回调函数
 * @param       htim:定时器句柄指针
 * @note        该函数在HAL_TIM_IRQHandler中会被调用
 * @retval      无
 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIM3_CAP)
    {
        if ((g_tim3ch3_cap_sta & 0X80) == 0)                /* 还未成功捕获 */
        {
            if (g_tim3ch3_cap_sta & 0X40)                   /* 捕获到一个下降沿 */
            {
                g_tim3ch3_cap_sta |= 0X80;                  /* 标记成功捕获到一次高电平脉宽 */
                g_tim3ch3_cap_val = HAL_TIM_ReadCapturedValue(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3);  /* 获取当前的捕获值 */
                TIM_RESET_CAPTUREPOLARITY(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3);                      /* 一定要先清除原来的设置 */
                TIM_SET_CAPTUREPOLARITY(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3, TIM_ICPOLARITY_RISING); /* 配置TIM3通道3上升沿捕获 */
            }
            else /* 还未开始,第一次捕获上升沿 */
            {
                g_tim3ch3_cap_sta = 0;                              /* 清空 */
                g_tim3ch3_cap_val = 0;
                g_tim3ch3_cap_sta |= 0X40;                          /* 标记捕获到了上升沿 */
                __HAL_TIM_DISABLE(&g_tim3_ultrasonic_handle);          /* 关闭定时器3 */
                __HAL_TIM_SET_COUNTER(&g_tim3_ultrasonic_handle, 0);   /* 定时器3计数器清零 */
                TIM_RESET_CAPTUREPOLARITY(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3);   /* 一定要先清除原来的设置!! */
                TIM_SET_CAPTUREPOLARITY(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3, TIM_ICPOLARITY_FALLING); /* 定时器3通道3设置为下降沿捕获 */
                __HAL_TIM_ENABLE(&g_tim3_ultrasonic_handle);           /* 使能定时器3 */
            }
        }
    }
}

/**
 * @brief       定时器更新中断回调函数
 * @param        htim:定时器句柄指针
 * @note        此函数会被定时器中断函数共同调用的
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIM3_CAP)
    {
        if ((g_tim3ch3_cap_sta & 0X80) == 0)            /* 还未成功捕获 */
        {
            if (g_tim3ch3_cap_sta & 0X40)               /* 已经捕获到高电平了 */
            {
                if ((g_tim3ch3_cap_sta & 0X3F) == 0X3F) /* 高电平太长了 */
                {
                    TIM_RESET_CAPTUREPOLARITY(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3);                     /* 一定要先清除原来的设置 */
                    TIM_SET_CAPTUREPOLARITY(&g_tim3_ultrasonic_handle, GTIM_TIM3_CAP_CH3, TIM_ICPOLARITY_RISING);/* 配置TIM3通道3上升沿捕获 */
                    g_tim3ch3_cap_sta |= 0X80;          /* 标记成功捕获了一次 */
                    g_tim3ch3_cap_val = 0XFFFF;
                }
                else      /* 累计定时器溢出次数 */
                {
                    g_tim3ch3_cap_sta++;
                }
            }
        }
    }
}


/**
 * @brief       获取编码器的值
 * @param       无
 * @retval      编码器值
 */
int gtim2_get_encode(void)
{
//    int count = __HAL_TIM_GET_COUNTER(&g_tim2_encode_handle);
//    int sum = count + g_tim2_encode_count * 65536;
//    g_tim2_encode_count = 0;
//    __HAL_TIM_SET_COUNTER(&g_tim2_encode_handle, 0);
//    return ( int32_t ) sum;       /* 当前计数值+之前累计编码器的值=总的编码器值 */
    int encode = __HAL_TIM_GET_COUNTER(&g_tim2_encode_handle);
    __HAL_TIM_SET_COUNTER(&g_tim2_encode_handle, 0);
    return ( int32_t ) encode;              /* 当前计数值 */
}

int gtim4_get_encode(void)
{
//    int count = __HAL_TIM_GET_COUNTER(&g_tim4_encode_handle);
//    int sum = count + g_tim4_encode_count * 65536;
//    g_tim4_encode_count = 0;
//    __HAL_TIM_SET_COUNTER(&g_tim4_encode_handle, 0);
//    return ( int32_t ) sum;       /* 当前计数值+之前累计编码器的值=总的编码器值 */
    int encode = __HAL_TIM_GET_COUNTER(&g_tim4_encode_handle);
    __HAL_TIM_SET_COUNTER(&g_tim4_encode_handle, 0);
    return ( int32_t ) encode;              /* 当前计数值 */
}

uint32_t Distance = 0;
/**
 * @brief       超声波传感器接收回波函数
 * @param       无
 * @retval      无
 */
void ultrasonic_distance(void)
{
    ULTRASONIC(1);
    delay_us(15);
    ULTRASONIC(0);
    
    if (g_tim3ch3_cap_sta & 0X80)               /* 成功捕获到1次高电平 */
    {
        Distance = g_tim3ch3_cap_sta & 0X3F;    /* 取出当前的溢出次数 */
        Distance *= 65536;                      /* 溢出时间总和 */
        Distance += g_tim3ch3_cap_val;          /* 得到总的高电平时间 */
        Distance = Distance * SOUND / 2 / 1000;       /* 该模块的距离计算公式,初始化时把定时器3设为1MHz,故/1000换算成mm */
        g_tim3ch3_cap_sta = 0;                  /* 开启下一次输入捕获 */
    }
}

/**
 * @brief       超声波传感器TRIG引脚GPIO初始化
 * @param       无
 * @retval      无
 */
void ultrasonic_trig_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    ULTRASONIC_TRIG_GPIO_CLK_ENABLE();                  /* 开启GPIOB时钟 */

    gpio_init_struct.Pin = ULTRASONIC_TRIG_GPIO_PIN;  /* 输入捕获的GPIO口 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;                /* 浮空 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
    HAL_GPIO_Init(ULTRASONIC_TRIG_GPIO_PORT, &gpio_init_struct);
    
    ULTRASONIC(0);
}

gtim.h

/**
 ****************************************************************************************************
 * @file        gtim.h
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       通用定时器 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车的通用定时器驱动代码
 * TIM2与TIM4连接两个编码器
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#ifndef __GTIM_H
#define __GTIM_H

#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* 通用定时器 定义 */

 
#define GTIM_TIM2_ENCODER_CH1_GPIO_PORT         GPIOA
#define GTIM_TIM2_ENCODER_CH1_GPIO_PIN          GPIO_PIN_0
#define GTIM_TIM2_ENCODER_CH1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)  /* PA口时钟使能 */

#define GTIM_TIM2_ENCODER_CH2_GPIO_PORT         GPIOA
#define GTIM_TIM2_ENCODER_CH2_GPIO_PIN          GPIO_PIN_1
#define GTIM_TIM2_ENCODER_CH2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)  /* PA口时钟使能 */

#define GTIM_TIM2_ENCODER                       TIM2                                         /* TIM2 */
#define GTIM_TIM2_ENCODER_INT_IRQn              TIM2_IRQn
#define GTIM_TIM2_ENCODER_INT_IRQHandler        TIM2_IRQHandler

#define GTIM_TIM2_ENCODER_CH1                   TIM_CHANNEL_1                                /* 通道1 */
#define GTIM_TIM2_ENCODER_CH1_CLK_ENABLE()      do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0)   /* TIM2 时钟使能 */

#define GTIM_TIM2_ENCODER_CH2                   TIM_CHANNEL_2                                /* 通道2 */
#define GTIM_TIM2_ENCODER_CH2_CLK_ENABLE()      do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0)   /* TIM2 时钟使能 */


#define GTIM_TIM4_ENCODER_CH1_GPIO_PORT         GPIOB
#define GTIM_TIM4_ENCODER_CH1_GPIO_PIN          GPIO_PIN_6
#define GTIM_TIM4_ENCODER_CH1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)  /* PB口时钟使能 */

#define GTIM_TIM4_ENCODER_CH2_GPIO_PORT         GPIOB
#define GTIM_TIM4_ENCODER_CH2_GPIO_PIN          GPIO_PIN_7
#define GTIM_TIM4_ENCODER_CH2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)  /* PB口时钟使能 */

#define GTIM_TIM4_ENCODER                       TIM4                                         /* TIM4 */
#define GTIM_TIM4_ENCODER_INT_IRQn              TIM4_IRQn
#define GTIM_TIM4_ENCODER_INT_IRQHandler        TIM4_IRQHandler

#define GTIM_TIM4_ENCODER_CH1                   TIM_CHANNEL_1                                /* 通道1 */
#define GTIM_TIM4_ENCODER_CH1_CLK_ENABLE()      do{ __HAL_RCC_TIM4_CLK_ENABLE(); }while(0)   /* TIM4 时钟使能 */

#define GTIM_TIM4_ENCODER_CH2                   TIM_CHANNEL_2                                /* 通道2 */
#define GTIM_TIM4_ENCODER_CH2_CLK_ENABLE()      do{ __HAL_RCC_TIM4_CLK_ENABLE(); }while(0)   /* TIM4 时钟使能 */

#define GTIM_TIM3_CAP_CH3_GPIO_PORT             GPIOB
#define GTIM_TIM3_CAP_CH3_GPIO_PIN              GPIO_PIN_0
#define GTIM_TIM3_CAP_CH3_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)  /* PB口时钟使能 */

#define GTIM_TIM3_CAP                           TIM3                       
#define GTIM_TIM3_CAP_IRQn                      TIM3_IRQn
#define GTIM_TIM3_CAP_IRQHandler                TIM3_IRQHandler
#define GTIM_TIM3_CAP_CH3                       TIM_CHANNEL_3                                /* 通道Y,  1<= Y <=4 */
#define GTIM_TIM3_CAP_CH3_CCRX                  TIM3->CCR3                                   /* 通道Y的输出比较寄存器 */
#define GTIM_TIM3_CAP_CH3_CLK_ENABLE()          do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0)   /* TIM3 时钟使能 */

#define ULTRASONIC_TRIG_GPIO_PORT               GPIOB
#define ULTRASONIC_TRIG_GPIO_PIN                GPIO_PIN_1
#define ULTRASONIC_TRIG_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)  /* PB口时钟使能 */

#define SOUND       340         /* 当地声速 */

/******************************************************************************************/
/* 超声波传感器端口定义 */
#define ULTRASONIC(x)   do{ x ? \
                           HAL_GPIO_WritePin(ULTRASONIC_TRIG_GPIO_PORT, ULTRASONIC_TRIG_GPIO_PIN, GPIO_PIN_SET) : \
                           HAL_GPIO_WritePin(ULTRASONIC_TRIG_GPIO_PORT, ULTRASONIC_TRIG_GPIO_PIN, GPIO_PIN_RESET); \
                        }while(0)      /* ULTRASONIC翻转 */


/******************************************************************************************/
void gtim_tim2_encoder_init(uint16_t psc, uint16_t arr);
void gtim_tim4_encoder_init(uint16_t psc, uint16_t arr);
int gtim2_get_encode(void);                                          /* 获取编码器总计数值 */
int gtim4_get_encode(void);                                          /* 获取编码器总计数值 */

void gtim_tim3_ultrasonic_init(uint16_t psc, uint16_t arr);
extern uint32_t Distance;
void ultrasonic_distance(void);                                      /* 获取超声波回波 */
void ultrasonic_trig_init(void);

#endif

ADC检测

通过ADC的通道6,也就是PA6引脚检测电池(12V)的输入电压,检测的原理图如下:
电池电压检测原理图
其中,这个OUT引脚是直接连到PA6进行测量的,那么根据分压原理可以得到如下公式:
计算公式

adc.c

/**
 ****************************************************************************************************
 * @file        adc.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       ADC 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车ADC驱动
 * 用以检测电池的电压
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#include "./BSP/ADC/adc.h"
#include "./SYSTEM/delay/delay.h"


ADC_HandleTypeDef g_adc_handle;   /* ADC句柄 */

/**
 * @brief       ADC初始化函数
 *   @note      本函数支持ADC1/ADC2任意通道, 但是不支持ADC3
 *              我们使用12位精度, ADC采样时钟=12M, 转换时间为: 采样周期 + 12.5个ADC周期
 *              设置最大采样周期: 239.5, 则转换时间 = 252 个ADC周期 = 21us
 * @param       无
 * @retval      无
 */
void adc_init(void)
{
    g_adc_handle.Instance = ADC_ADCX;                        /* 选择哪个ADC */
    g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;       /* 数据对齐方式:右对齐 */
    g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;       /* 非扫描模式,仅用到一个通道 */
    g_adc_handle.Init.ContinuousConvMode = DISABLE;          /* 关闭连续转换模式 */
    g_adc_handle.Init.NbrOfConversion = 1;                   /* 赋值范围是1~16,本实验用到1个规则通道序列 */
    g_adc_handle.Init.DiscontinuousConvMode = DISABLE;       /* 禁止规则通道组间断模式 */
    g_adc_handle.Init.NbrOfDiscConversion = 0;               /* 配置间断模式的规则通道个数,禁止规则通道组间断模式后,此参数忽略 */
    g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 触发转换方式:软件触发 */
    HAL_ADC_Init(&g_adc_handle);                             /* 初始化 */

    HAL_ADCEx_Calibration_Start(&g_adc_handle);              /* 校准ADC */
}

/**
 * @brief       ADC底层驱动,引脚配置,时钟使能
                此函数会被HAL_ADC_Init()调用
 * @param       hadc:ADC句柄
 * @retval      无
 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    if(hadc->Instance == ADC_ADCX)
    {
        GPIO_InitTypeDef gpio_init_struct;
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
        
        ADC_ADCX_CHY_CLK_ENABLE();                                /* 使能ADCx时钟 */
        ADC_ADCX_CHY_GPIO_CLK_ENABLE();                           /* 开启GPIO时钟 */

        /* 设置ADC时钟 */
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;    /* ADC外设时钟 */
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;       /* 分频因子6时钟为72M/6=12MHz */
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);                 /* 设置ADC时钟 */

        /* 设置AD采集通道对应IO引脚工作模式 */
        gpio_init_struct.Pin = ADC_ADCX_CHY_GPIO_PIN;             /* ADC通道IO引脚 */
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;                 /* 模拟 */
        HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);
    }
}

/**
 * @brief       设置ADC通道采样时间
 * @param       adcx : adc句柄指针,ADC_HandleTypeDef
 * @param       ch   : 通道号, ADC_CHANNEL_0~ADC_CHANNEL_17
 * @param       stime: 采样时间  0~7, 对应关系为:
 *   @arg       ADC_SAMPLETIME_1CYCLE_5, 1.5个ADC时钟周期        ADC_SAMPLETIME_7CYCLES_5, 7.5个ADC时钟周期
 *   @arg       ADC_SAMPLETIME_13CYCLES_5, 13.5个ADC时钟周期     ADC_SAMPLETIME_28CYCLES_5, 28.5个ADC时钟周期
 *   @arg       ADC_SAMPLETIME_41CYCLES_5, 41.5个ADC时钟周期     ADC_SAMPLETIME_55CYCLES_5, 55.5个ADC时钟周期
 *   @arg       ADC_SAMPLETIME_71CYCLES_5, 71.5个ADC时钟周期     ADC_SAMPLETIME_239CYCLES_5, 239.5个ADC时钟周期
 * @param       rank: 多通道采集时需要设置的采集编号,
                假设你定义channle1的rank=1,channle2 的rank=2,
                那么对应你在DMA缓存空间的变量数组AdcDMA[0] 就i是channle1的转换结果,AdcDMA[1]就是通道2的转换结果。 
                单通道DMA设置为 ADC_REGULAR_RANK_1
 *   @arg       编号1~16:ADC_REGULAR_RANK_1~ADC_REGULAR_RANK_16
 * @retval      无
 */
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime)
{
    ADC_ChannelConfTypeDef adc_ch_conf;
    
    adc_ch_conf.Channel = ch;                            /* 通道 */
    adc_ch_conf.Rank = rank;                             /* 序列 */
    adc_ch_conf.SamplingTime = stime;                    /* 采样时间 */
    HAL_ADC_ConfigChannel(adc_handle, &adc_ch_conf);     /* 通道配置 */
}

/**
 * @brief       获得ADC转换后的结果
 * @param       ch: 通道值 0~17,取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_17
 * @retval      无
 */
uint32_t adc_get_result(uint32_t ch)
{
    adc_channel_set(&g_adc_handle, ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);    /* 设置通道,序列和采样时间 */

    HAL_ADC_Start(&g_adc_handle);                            /* 开启ADC */
    HAL_ADC_PollForConversion(&g_adc_handle, 10);            /* 轮询转换 */
    return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);        /* 返回最近一次ADC1规则组的转换结果 */
}

/**
 * @brief       获取通道ch的转换值,取times次,然后平均
 * @param       ch      : 通道号, 0~17
 * @param       times   : 获取次数
 * @retval      通道ch的times次转换结果平均值
 */
uint32_t adc_get_result_average(uint32_t ch, uint8_t times)
{
    uint32_t temp_val = 0;
    uint8_t t;

    for (t = 0; t < times; t++)     /* 获取times次数据 */
    {
        temp_val += adc_get_result(ch);
        delay_ms(5);
    }

    return temp_val / times;        /* 返回平均值 */
}


/**
 * @brief       将获取的ADC平均值转化为对应的电池电压
 * @param       ch      : 通道号, 0~17
 * @param       times   : 获取次数,为了时间考虑,不取平均值
 * @retval      通道ch的times次转换结果平均值对应的电池电压值,mV
 */
int adc_get_battery_voltage(uint32_t ch)
//int adc_get_battery_voltage(uint32_t ch, uint8_t times)
{
    //uint16_t get_val = adc_get_result_average(ch, times);
    uint16_t get_val = adc_get_result(ch);

    int voltage = get_val * ADC_MAX * Voltage_Divider * 100 / ADC_MAX_NUM;
    return voltage;
}

adc.h

/**
 ****************************************************************************************************
 * @file        adc.h
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       ADC 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车ADC驱动
 * 用以检测电池的电压
 * 接口在PA6,所以是ADC_Channel6
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *

 ****************************************************************************************************
 */

#ifndef __ADC_H
#define __ADC_H


#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* ADC及引脚 定义 */

#define ADC_ADCX_CHY_GPIO_PORT              GPIOA
#define ADC_ADCX_CHY_GPIO_PIN               GPIO_PIN_6
#define ADC_ADCX_CHY_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)  /* PA口时钟使能 */

#define ADC_ADCX                            ADC1 
#define ADC_ADCX_CHY                        ADC_CHANNEL_6                                /* 通道Y,  0 <= Y <= 17 */ 
#define ADC_ADCX_CHY_CLK_ENABLE()           do{ __HAL_RCC_ADC1_CLK_ENABLE(); }while(0)   /* ADC1 时钟使能 */

#define ADC_MAX                             3.3         /* ADC可测量的最大值 */
#define ADC_MAX_NUM                         4095        /* 对应最大量程值的最大测量值 */
#define Voltage_Divider                     11          /* 原理图所对应的分压系数 */

/******************************************************************************************/

void adc_init(void);                                                /* ADC初始化 */
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch,uint32_t rank, uint32_t stime); /* ADC通道设置 */
uint32_t adc_get_result(uint32_t ch);                               /* 获得某个通道值  */
uint32_t adc_get_result_average(uint32_t ch, uint8_t times);        /* 得到某个通道给定次数采样的平均值 */
int adc_get_battery_voltage(uint32_t ch);                           /* 将ADC读取值转化为电池电压,单位为mV */
//int adc_get_battery_voltage(uint32_t ch, uint8_t times);                           /* 将ADC读取值转化为电池电压,单位为mV */


#endif 

蓝牙BT04

这一块我们不需要掌握蓝牙通讯硬件方面的知识,只需要学会使用就可以了。蓝牙的通讯其实就是串口,然后配合轮趣小车官方已经做好的app,进行信号的传输就可以了。

在这里,蓝牙传输使用的是串口3,波特率是9600,其余的都是类似的配置,然后发送信息的指令表如下表所示:
命令指令表

通过以上表格,我们设置几个信号量(相当于是二值的状态量,表征小车状态的状态机),包括前进后退左右转以及速度信号(在控制代码中会把计算信号再行处理,比如实际输出是给定的target/flag,flag就是速度信号)。

bt04.c

/**
 ****************************************************************************************************
 * @file        bt04.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-21
 * @brief       BT04模块驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 蓝牙模块的驱动代码
 *
 *
 * 修改说明
 * V1.0 20230821
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#include "./BSP/BT04/bt04.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/CONTROL/control.h"
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

static UART_HandleTypeDef g_uart_handle;                    /* BT04 UART */
static uint8_t g_uart_rx_buf[BT04_UART_RX_BUF_SIZE];        /* BT04 UART接收缓冲 */
static uint8_t g_uart_tx_buf[BT04_UART_TX_BUF_SIZE];        /* BT04 UART发送缓冲 */
uint8_t g_usart_receive;                                    /* BT04接收到的数据 */

/* 蓝牙遥控相关的变量 */
uint8_t Flag_Front = 0, Flag_Back = 0;
uint8_t Flag_Left = 0, Flag_Right = 0;
uint8_t Flag_Velocity = 2;
uint8_t PID_Set;

/**
 * @brief       BT04 UART printf
 * @param       fmt: 待打印的数据
 * @retval      无
 */
void bt04_uart_printf(char *fmt, ...)
{
    va_list ap;
    uint16_t len;
    
    va_start(ap, fmt);
    vsprintf((char *)g_uart_tx_buf, fmt, ap);
    va_end(ap);
    
    len = strlen((const char *)g_uart_tx_buf);
    HAL_UART_Transmit(&g_uart_handle, g_uart_tx_buf, len, HAL_MAX_DELAY);
}

/**
 * @brief       BT04 UART初始化
 * @param       baudrate: UART通讯波特率
 * @retval      无
 */
void bt04_uart_init(uint32_t baudrate)
{
    g_uart_handle.Instance          = BT04_UART_INTERFACE;     /* BT04 UART */
    g_uart_handle.Init.BaudRate     = baudrate;                /* 波特率 */
    g_uart_handle.Init.WordLength   = UART_WORDLENGTH_8B;      /* 数据位 */
    g_uart_handle.Init.StopBits     = UART_STOPBITS_1;         /* 停止位 */
    g_uart_handle.Init.Parity       = UART_PARITY_NONE;        /* 校验位 */
    g_uart_handle.Init.Mode         = UART_MODE_TX_RX;         /* 收发模式 */
    g_uart_handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;     /* 无硬件流控 */
    g_uart_handle.Init.OverSampling = UART_OVERSAMPLING_16;    /* 过采样 */
    HAL_UART_Init(&g_uart_handle);                             /* 使能 BT04 UART
                                                                * HAL_UART_Init()会调用函数HAL_UART_MspInit()
                                                                * 该函数定义在文件usart.c中
                                                                */
}



/**
 * @brief       BT04 UART中断回调函数
 * @param       无
 * @retval      无
 */
void BT04_UART_IRQHandler(void)
{
    uint8_t tmp;
    
    if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_ORE) != RESET)        /* UART接收过载错误中断 */
    {
        __HAL_UART_CLEAR_OREFLAG(&g_uart_handle);                           /* 清除接收过载错误中断标志 */
        (void)g_uart_handle.Instance->SR;                                   /* 先读SR寄存器,再读DR寄存器 */
        (void)g_uart_handle.Instance->DR;
    }
    
    if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_RXNE) != RESET)       /* UART接收中断 */
    {
        HAL_UART_Receive(&g_uart_handle, &tmp, 1, HAL_MAX_DELAY);           /* UART接收数据 */
        
        static int uart_receive = 0;
        static uint8_t Flag_PID, i, j, Receive[50];
        static float Data;
        g_uart_rx_buf[0] = tmp;
        uart_receive = g_uart_rx_buf[0];
        g_usart_receive = uart_receive;
        
        /* 先判断小车接收到的蓝牙指令是加速还是减速 */
        if (uart_receive == 0x59) {Flag_Velocity = 2;}      /* 低速档 */
        if (uart_receive == 0x58) {Flag_Velocity = 1;}      /* 高速档 */
        
        if (uart_receive > 10)      /* 默认使用 */
        {
            /* 按照给定的指令集if判断即可 */
            /* 刹车 */
            if (uart_receive == 0x5A)
            {
                Flag_Front = 0, Flag_Back = 0;
                Flag_Left = 0, Flag_Right = 0;
            }
            /* 前进 */
            else if (uart_receive == 0x41)
            {
                Flag_Front = 1, Flag_Back = 0;
                Flag_Left = 0, Flag_Right = 0;
            }
            /* 后退 */
            else if (uart_receive == 0x45)
            {
                Flag_Front = 0, Flag_Back = 1;
                Flag_Left = 0, Flag_Right = 0;
            }
            /* 右上、右、右后 */
            else if (uart_receive == 0x42 || uart_receive == 0x43 || uart_receive == 0x44)
            {
                Flag_Front = 0, Flag_Back = 0;
                Flag_Left = 0, Flag_Right = 1;
            }
            /* 左后、左、左前 */
            else if (uart_receive == 0x46 || uart_receive == 0x47 || uart_receive == 0x48)
            {
                Flag_Front = 0, Flag_Back = 0;
                Flag_Left = 1, Flag_Right = 0;
            }
            /* 其余指令都给刹车 */
            else
            {
                Flag_Front = 0, Flag_Back = 0;
                Flag_Left = 0, Flag_Right = 0;
            }
        }
        
        /* 以下是PID参数调试 */
        if (g_usart_receive == 0x7B) {Flag_PID = 1;}      /* APP的调试指令起始位 */
        if (g_usart_receive == 0x7D) {Flag_PID = 2;}      /* APP的调试指令终止位 */
        
        if (Flag_PID == 1)  /* 接收数据,采集参数 */
        {
            Receive[i] = g_usart_receive;
            i++;
        }
        if (Flag_PID == 2)  /* 数据传输完毕,进行数据解读 */
        {
            if (Receive[3] == 0x50) {PID_Set = 1;}
            else if (Receive[1] != 0x23)
            {
                for (j = i; j >= 4; j--)
                {
                    Data += (Receive[j - 1] - 48) * pow(10, i - j);
                }
                switch (Receive[1])
                {
                    case 0x30: P_stand = Data;break;
                    case 0x31: D_stand = Data;break;
                    case 0x32: P_velocity = Data;break;
                    case 0x33: I_velocity = Data;break;
                    case 0x34: P_turn = Data;break;
                    case 0x35: D_turn = Data;break;
                    case 0x36: break;
                    case 0x37: break;
                    case 0x38: break;
                }
            }
            Flag_PID = 0;
            i = j = 0;
            Data = 0;
            memset(Receive, 0, sizeof(uint8_t) * 50);
        }
    }
}

bt04.h

/**
 ****************************************************************************************************
 * @file        bt04.h
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-21
 * @brief       BT04模块驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 蓝牙模块的驱动代码
 *
 *
 * 修改说明
 * V1.0 20230821
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */
 
 #ifndef __BT04_H
 #define __BT04_H
 
 #include "./SYSTEM/sys/sys.h"
 


/******************************************************************************************/
 /* 引脚定义 */
#define BT04_UART_TX_GPIO_PORT         GPIOB
#define BT04_UART_TX_GPIO_PIN          GPIO_PIN_10
#define BT04_UART_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)

#define BT04_UART_RX_GPIO_PORT         GPIOB
#define BT04_UART_RX_GPIO_PIN          GPIO_PIN_11
#define BT04_UART_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)

#define BT04_UART_INTERFACE            USART3
#define BT04_UART_IRQn                 USART3_IRQn
#define BT04_UART_IRQHandler           USART3_IRQHandler
#define BT04_UART_CLK_ENABLE()         do{ __HAL_RCC_USART3_CLK_ENABLE(); }while(0)

/* UART收发缓冲大小 */
#define BT04_UART_RX_BUF_SIZE          2
#define BT04_UART_TX_BUF_SIZE          2


/******************************************************************************************/

/* 操作函数 */
void bt04_uart_printf(char *fmt, ...);     /* BT04 UART printf */
void bt04_uart_init(uint32_t baudrate);    /* BT04 UART 初始化 */

/* 蓝牙遥控相关的变量 */
extern uint8_t Flag_Front, Flag_Back;
extern uint8_t Flag_Left, Flag_Right;
extern uint8_t Flag_Velocity;
extern uint8_t PID_Set;
 
 #endif

MPU6050

这是stm32平衡小车的精髓所在,是控制小车能否直立的关键传感器!

初始化非常复杂,基本就是把官方提供的库拉入我们的工程项目之中,略微做一些小改动(官方的库是给MSP430用的,我们是stm32);同时还需要软件方式实现IIC通讯(这个是因为IIC是飞利浦有专利的,stm32为了避开,硬件上就比较复杂,通过硬件直接使用不方便,不如按照时序软件实现IIC,顺带一提SPI是可以stm32硬件直接用的)。

IIC的通讯协议我这边就不多做描述了,直接拉正点原子的代码也可以,自己跟着时序图实现也可以,就是定义好几个引脚的GPIO然后按照顺序拉高拉低电平就可以了IIC的SCL对应PB8,SDA对应PB9

mpu6050_iic.c

/**
 ****************************************************************************************************
 * @file        mpu6050_iic.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       MPU6050模块IIC接口驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车软件实现IIC,用以驱动MPU6050
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#include "./BSP/MPU6050/mpu6050_iic.h"
#include "./SYSTEM/delay/delay.h"

/**
 * @brief       IIC接口延时函数,用于控制IIC读写速度
 * @param       无
 * @retval      无
 */
static inline void mpu6050_iic_delay(void)
{
    /* 轮趣小车中只等1us,正点原子等2us,但应该影响不大 */
    delay_us(2);
}

/**
 * @brief       产生IIC起始信号
 * @param       无
 * @retval      无
 */
void mpu6050_iic_start(void)
{
    MPU6050_IIC_SDA(1);
    MPU6050_IIC_SCL(1);
    mpu6050_iic_delay();
    MPU6050_IIC_SDA(0);
    mpu6050_iic_delay();
    MPU6050_IIC_SCL(0);
    mpu6050_iic_delay();
}

/**
 * @brief       产生IIC停止信号
 * @param       无
 * @retval      无
 */
void mpu6050_iic_stop(void)
{
    MPU6050_IIC_SDA(0);
    mpu6050_iic_delay();
    MPU6050_IIC_SCL(1);
    mpu6050_iic_delay();
    MPU6050_IIC_SDA(1);
    mpu6050_iic_delay();
}

/**
 * @brief       等待IIC应答信号
 * @param       无
 * @retval      0: 应答信号接收成功
 *              1: 应答信号接收失败
 */
uint8_t mpu6050_iic_wait_ack(void)
{
    uint8_t waittime = 0;
    uint8_t rack = 0;
    
    MPU6050_IIC_SDA(1);
    mpu6050_iic_delay();
    MPU6050_IIC_SCL(1);
    mpu6050_iic_delay();
    
    while (MPU6050_IIC_READ_SDA())
    {
        waittime++;
        /* 在轮趣小车的代码中这边只等待50,正点原子等250 */
        if (waittime > 50)
        {
            mpu6050_iic_stop();
            rack = 1;
            break;
        }
    }
    
    MPU6050_IIC_SCL(0);
    mpu6050_iic_delay();
    
    return rack;
}

/**
 * @brief       产生ACK应答信号
 * @param       无
 * @retval      无
 */
void mpu6050_iic_ack(void)
{
    /* 轮趣小车中会先拉低SCL */
    MPU6050_IIC_SCL(0);
    mpu6050_iic_delay();
    
    MPU6050_IIC_SDA(0);
    mpu6050_iic_delay();
    MPU6050_IIC_SCL(1);
    mpu6050_iic_delay();
    MPU6050_IIC_SCL(0);
    mpu6050_iic_delay();
    /* 轮趣小车不会拉高SDA电平 */
    MPU6050_IIC_SDA(1);
    mpu6050_iic_delay();
}

/**
 * @brief       不产生ACK应答信号
 * @param       无
 * @retval      无
 */
void mpu6050_iic_nack(void)
{
    /* 轮趣小车中会先拉低SCL */
    MPU6050_IIC_SCL(0);
    mpu6050_iic_delay();
    
    MPU6050_IIC_SDA(1);
    mpu6050_iic_delay();
    MPU6050_IIC_SCL(1);
    mpu6050_iic_delay();
    MPU6050_IIC_SCL(0);
    mpu6050_iic_delay();
}

/**
 * @brief       IIC发送一个字节
 * @param       dat: 要发送的数据
 * @retval      无
 */
void mpu6050_iic_send_byte(uint8_t dat)
{
    uint8_t t;
    
    for (t=0; t<8; t++)
    {
        MPU6050_IIC_SDA((dat & 0x80) >> 7);
        mpu6050_iic_delay();
        MPU6050_IIC_SCL(1);
        mpu6050_iic_delay();
        MPU6050_IIC_SCL(0);
        dat <<= 1;
    }
    /* 轮趣小车不会拉高SDA */
    MPU6050_IIC_SDA(1);
}

/**
 * @brief       IIC接收一个字节
 * @param       ack: ack=1时,发送ack; ack=0时,发送nack
 * @retval      接收到的数据
 */
uint8_t mpu6050_iic_read_byte(uint8_t ack)
{
    uint8_t i;
    uint8_t dat = 0;
    
    for (i = 0; i < 8; i++ )
    {
        dat <<= 1;
        MPU6050_IIC_SCL(1);
        mpu6050_iic_delay();
        
        if (MPU6050_IIC_READ_SDA())
        {
            dat++;
        }
        
        MPU6050_IIC_SCL(0);
        mpu6050_iic_delay();
    }
    
    if (!ack)
    {
        mpu6050_iic_nack();
    }
    else
    {
        mpu6050_iic_ack();
    }

    return dat;
}

/**
 * @brief       初始化IIC接口
 * @param       无
 * @retval      无
 */
void mpu6050_iic_init(void)
{
    GPIO_InitTypeDef gpio_init_struct = {0};
    
    /* 使能SCL、SDA引脚GPIO的时钟 */
    MPU6050_IIC_SCL_GPIO_CLK_ENABLE();
    MPU6050_IIC_SDA_GPIO_CLK_ENABLE();
    
    /* 初始化SCL引脚 */
    gpio_init_struct.Pin    = MPU6050_IIC_SCL_GPIO_PIN;      /* SCL引脚 */
    gpio_init_struct.Mode   = GPIO_MODE_OUTPUT_PP;           /* 推挽输出 */
    gpio_init_struct.Pull   = GPIO_PULLUP;                   /* 上拉 */
    gpio_init_struct.Speed  = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(MPU6050_IIC_SCL_GPIO_PORT, &gpio_init_struct);
    
    /* 初始化SDA引脚 */
    gpio_init_struct.Pin    = MPU6050_IIC_SDA_GPIO_PIN;      /* SDA引脚 */
    gpio_init_struct.Mode   = GPIO_MODE_OUTPUT_OD;           /* 开漏输出 */
    HAL_GPIO_Init(MPU6050_IIC_SDA_GPIO_PORT, &gpio_init_struct);
    
    mpu6050_iic_stop();
}

mpu6050_iic.h

/**
 ****************************************************************************************************
 * @file        mpu6050_iic.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       MPU6050模块IIC接口驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车软件实现IIC,用以驱动MPU6050
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#ifndef __MPU6050_IIC_H
#define __MPU6050_IIC_H

#include "./SYSTEM/sys/sys.h"

/* 引脚定义 */
#define MPU6050_IIC_SCL_GPIO_PORT            GPIOB
#define MPU6050_IIC_SCL_GPIO_PIN             GPIO_PIN_8
#define MPU6050_IIC_SCL_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
#define MPU6050_IIC_SDA_GPIO_PORT            GPIOB
#define MPU6050_IIC_SDA_GPIO_PIN             GPIO_PIN_9
#define MPU6050_IIC_SDA_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)

/* IO操作 */
#define MPU6050_IIC_SCL(x)                   do{ x ?                                                                                             \
                                                HAL_GPIO_WritePin(MPU6050_IIC_SCL_GPIO_PORT, MPU6050_IIC_SCL_GPIO_PIN, GPIO_PIN_SET) :    \
                                                HAL_GPIO_WritePin(MPU6050_IIC_SCL_GPIO_PORT, MPU6050_IIC_SCL_GPIO_PIN, GPIO_PIN_RESET);   \
                                                }while(0)

#define MPU6050_IIC_SDA(x)                   do{ x ?                                                                                             \
                                                HAL_GPIO_WritePin(MPU6050_IIC_SDA_GPIO_PORT, MPU6050_IIC_SDA_GPIO_PIN, GPIO_PIN_SET) :    \
                                                HAL_GPIO_WritePin(MPU6050_IIC_SDA_GPIO_PORT, MPU6050_IIC_SDA_GPIO_PIN, GPIO_PIN_RESET);   \
                                                }while(0)

#define MPU6050_IIC_READ_SDA()               HAL_GPIO_ReadPin(MPU6050_IIC_SDA_GPIO_PORT, MPU6050_IIC_SDA_GPIO_PIN)

/* 操作函数 */
void mpu6050_iic_start(void);                /* 产生IIC起始信号 */
void mpu6050_iic_stop(void);                 /* 产生IIC停止信号 */
uint8_t mpu6050_iic_wait_ack(void);          /* 等待IIC应答信号 */
void mpu6050_iic_ack(void);                  /* 产生ACK应答信号 */
void mpu6050_iic_nack(void);                 /* 不产生ACK应答信号 */
void mpu6050_iic_send_byte(uint8_t dat);     /* IIC发送一个字节 */
uint8_t mpu6050_iic_read_byte(uint8_t ack);  /* IIC接收一个字节 */
void mpu6050_iic_init(void);                 /* 初始化IIC接口 */

#endif

实现了IIC的通讯协议之后,就可以直接去读MPU6050的传感器得到的熟知了,这里由于只考虑移植,所以直接用官方自带的DMP算法来得到角度和角速度角加速度值,没有考虑滤波算法。正点原子关于这个传感器是有文档源代码的,我把几个传输的东西根据小车的实际情况改了改(比如方向矩阵),然后正点原子的逻辑代码是写在了官方库的下面,我把他全部写到mpu6050我自己的代码里了,同时原先是形参为地址直接操作,我是直接通过全局变量来得到数据,略有不同但没有本质区别。

下面把所有的相关代码都贴在下面,比较多,基本都是复制就可以了。

mpu6050.c

/**
 ****************************************************************************************************
 * @file        mpu6050.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       MPU6050模块驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车完成MPU6050的初始化与读数据
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 ****************************************************************************************************
 */

#include "./BSP/MPU6050/mpu6050.h"
#include "./BSP/MPU6050/mpu6050_iic.h"
#include "./BSP/MPU6050/eMPL/inv_mpu.h"
#include "./BSP/MPU6050/eMPL/inv_mpu_dmp_motion_driver.h"
#include "./BSP/MPU6050/eMPL/dmpKey.h"
#include "./BSP/MPU6050/eMPL/dmpmap.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include <math.h>

/* 陀螺仪、加速度计、传感器参数 */
short gyro[3], accel[3], sensors;
/* 欧拉角 */
float pitch, roll, yaw;
/* 四元数计算 */
float q0 = 0.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f;
/* 轮趣小车 MPU6050的陀螺仪方向设置参数 */
static signed char gyro_orientation[9] = { -1, 0, 0,
                                           0, -1, 0,
                                           0, 0, 1};
/* 缓冲数组读取数据 */
uint8_t buffer[14];

/**
 * @brief       MPU6050硬件初始化
 * @param       无
 * @retval      无
 */
static void mpu6050_hw_init(void)
{
    GPIO_InitTypeDef gpio_init_struct = {0};
    
    /* 使能AD0引脚GPIO的时钟 */
    MPU6050_AD0_GPIO_CLK_ENABLE();
    
    /* 初始化AD0引脚 */
    gpio_init_struct.Pin    = MPU6050_AD0_GPIO_PIN;     /* AD0引脚 */
    gpio_init_struct.Mode   = GPIO_MODE_IT_FALLING;     /* 下降沿触发检测的外部中断模式 */
    gpio_init_struct.Pull   = GPIO_PULLUP;              /* 上拉 */
    gpio_init_struct.Speed  = GPIO_SPEED_FREQ_HIGH;     /* 高速 */
    HAL_GPIO_Init(MPU6050_AD0_GPIO_PORT, &gpio_init_struct);
    
    /* 控制MPU6050的AD0引脚为低电平
     * 设置其IIC的从机地址为0x68
     */
    MPU6050_AD0(0);
}

/**
 * @brief       往MPU6050的指定寄存器连续写入指定数据
 * @param       addr: MPU6050的IIC通讯地址
 *              reg : MPU6050寄存器地址
 *              len : 写入的长度
 *              dat : 写入的数据
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_write(uint8_t addr,uint8_t reg, uint8_t len, uint8_t *dat)
{
    uint8_t i;
    
    mpu6050_iic_start();
    mpu6050_iic_send_byte((addr << 1) | 0);
    if (mpu6050_iic_wait_ack())
    {
        mpu6050_iic_stop();
        return MPU6050_EACK;
    }
    /* 这一步轮趣小车就直接等待应答信号,不会判断;正点原子会判断一下是否有应答信号 */
    mpu6050_iic_send_byte(reg);
    if (mpu6050_iic_wait_ack())
    {
        mpu6050_iic_stop();
        return MPU6050_EACK;
    }
    for (i = 0; i < len; i++)
    {
        mpu6050_iic_send_byte(dat[i]);
        if (mpu6050_iic_wait_ack())
        {
            mpu6050_iic_stop();
            return MPU6050_EACK;
        }
    }
    mpu6050_iic_stop();
    return MPU6050_EOK;
}

/**
 * @brief       往MPU6050的指定寄存器写入一字节数据
 * @param       addr: MPU6050的IIC通讯地址
 *              reg : MPU6050寄存器地址
 *              dat : 写入的数据
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_write_byte(uint8_t addr, uint8_t reg, uint8_t dat)
{
    return mpu6050_write(addr, reg, 1, &dat);
}

/**
 * @brief       连续读取MPU6050指定寄存器的值
 * @param       addr: MPU6050的IIC通讯地址
 *              reg : MPU6050寄存器地址
 *              len: 读取的长度
 *              dat: 存放读取到的数据的地址
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_read(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *dat)
{
    mpu6050_iic_start();
    mpu6050_iic_send_byte((addr << 1) | 0);
    if (mpu6050_iic_wait_ack())
    {
        mpu6050_iic_stop();
        return MPU6050_EACK;
    }
    /* 这一步轮趣小车就直接等待应答信号,不会判断;正点原子会判断一下是否有应答信号 */
    mpu6050_iic_send_byte(reg);
    if (mpu6050_iic_wait_ack())
    {
        mpu6050_iic_stop();
        return MPU6050_EACK;
    }
    mpu6050_iic_start();
    /* 正点原子是|1 */
    //mpu6050_iic_send_byte((addr << 1) | 1);
    /* 轮趣小车是+1 */
    mpu6050_iic_send_byte((addr << 1) + 1);
    if (mpu6050_iic_wait_ack())
    {
        mpu6050_iic_stop();
        return MPU6050_EACK;
    }
    while (len)
    {
        *dat = mpu6050_iic_read_byte((len > 1) ? 1 : 0);
        len--;
        dat++;
    }
    mpu6050_iic_stop();
    return MPU6050_EOK;
}

/**
 * @brief       读取MPU6050指定寄存器的值
 * @param       addr: MPU6050的IIC通讯地址
 *              reg : MPU6050寄存器地址
 *              dat: 读取到的寄存器的值
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_read_byte(uint8_t addr, uint8_t reg, uint8_t *dat)
{
    return mpu6050_read(addr, reg, 1, dat);
}

/**
 * @brief       MPU6050软件复位
 * @param       无
 * @retval      无
 */
void mpu6050_sw_reset(void)
{
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT1_REG, 0x80);
    delay_ms(100);
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT1_REG, 0x00);
}

/**
 * @brief       MPU6050设置陀螺仪传感器量程范围
 * @param       frs: 0 --> ±250dps
 *                   1 --> ±500dps
 *                   2 --> ±1000dps
 *                   3 --> ±2000dps
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_set_gyro_fsr(uint8_t fsr)
{
    return mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_GYRO_CFG_REG, fsr << 3);
}

/**
 * @brief       MPU6050设置加速度传感器量程范围
 * @param       frs: 0 --> ±2g
 *                   1 --> ±4g
 *                   2 --> ±8g
 *                   3 --> ±16g
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_set_accel_fsr(uint8_t fsr)
{
    return mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_ACCEL_CFG_REG, fsr << 3);
}

/**
 * @brief       MPU6050设置数字低通滤波器频率
 * @param       lpf: 数字低通滤波器的频率(Hz)
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_set_lpf(uint16_t lpf)
{
    uint8_t dat;
    
    if (lpf >= 188)
    {
        dat = 1;
    }
    else if (lpf >= 98)
    {
        dat = 2;
    }
    else if (lpf >= 42)
    {
        dat = 3;
    }
    else if (lpf >= 20)
    {
        dat = 4;
    }
    else if (lpf >= 10)
    {
        dat = 5;
    }
    else
    {
        dat = 6;
    }
    
    return mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_CFG_REG, dat);
}

/**
 * @brief       MPU6050设置采样率
 * @param       rate: 采样率(4~1000Hz)
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_set_rate(uint16_t rate)
{
    uint8_t ret;
    uint8_t dat;
    
    if (rate > 1000)
    {
        rate = 1000;
    }
    
    if (rate < 4)
    {
        rate = 4;
    }
    
    dat = 1000 / rate - 1;
    ret = mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_SAMPLE_RATE_REG, dat);
    if (ret != MPU6050_EOK)
    {
        return ret;
    }
    
    ret = mpu6050_set_lpf(rate >> 1);
    if (ret != MPU6050_EOK)
    {
        return ret;
    }
    
    return MPU6050_EOK;
}

/**
 * @brief       函数inv_orientation_matrix_to_scalar()的辅助函数
 * @param       row: 输入
 * @retval      输出
 */
static inline unsigned short inv_row_2_scale(const signed char *row)
{
    unsigned short b;
    
    if (row[0] > 0)
        b = 0;
    else if (row[0] < 0)
        b = 4;
    else if (row[1] > 0)
        b = 1;
    else if (row[1] < 0)
        b = 5;
    else if (row[2] > 0)
        b = 2;
    else if (row[2] < 0)
        b = 6;
    else
        b = 7;      // error
    
    return b;
}

/**
 * @brief       将方向矩阵转换为标量表示,以供DMP使用
 * @param       mtx: 方向矩阵
 * @retval      标量表示的方向参数
 */
static inline unsigned short inv_orientation_matrix_to_scalar(const signed char *mtx)
{
    unsigned short scalar;
    
    scalar = inv_row_2_scale(mtx);
    scalar |= inv_row_2_scale(mtx + 3) << 3;
    scalar |= inv_row_2_scale(mtx + 6) << 6;
    
    return scalar;
}

/**
 * @brief       MPU6050传感器自测试函数
 * @param       无
 * @retval      0: 函数执行成功
 *              1: 函数执行失败
 *              轮趣小车直接 static void函数,正点原子是uint8_t
 */
static void mpu6050_run_self_test(void)
{
    int result;
    long gyro[3], accel[3];;
    
    result = mpu_run_self_test(gyro, accel);
    if (result == 0x7)
    {
        /* Test passed. We can trust the gyro data here, so let's push it down
         * to the DMP.
         */
        float sens;
        unsigned short accel_sens;
        
        mpu_get_gyro_sens(&sens);
        gyro[0] = (long)(gyro[0] * sens);
        gyro[1] = (long)(gyro[1] * sens);
        gyro[2] = (long)(gyro[2] * sens);
        dmp_set_gyro_bias(gyro);
        mpu_get_accel_sens(&accel_sens);
        accel[0] *= accel_sens;
        accel[1] *= accel_sens;
        accel[2] *= accel_sens;
        dmp_set_accel_bias(accel);
        
//        return 0;
    }
//    else
//    {
//        return 1;
//    }
}

/**
 * @brief       MPU6050初始化
 * @param       无
 * @retval      MPU6050_EOK: 函数执行成功
 *              MPU6050_EID: 获取ID错误,函数执行失败
 */
uint8_t mpu6050_init(void)
{
    uint8_t id;
    
    mpu6050_hw_init();                                                   /* MPU6050硬件初始化 */
    mpu6050_iic_init();                                                  /* 初始化IIC接口 */
    mpu6050_sw_reset();                                                  /* MPU050软件复位 */
    mpu6050_set_gyro_fsr(3);                                             /* 陀螺仪传感器,±2000dps */
    mpu6050_set_accel_fsr(0);                                            /* 加速度传感器,±2g */
    //mpu6050_set_rate(50);                                                /* 采样率,50Hz */
    
    /* 轮趣小车设置失能睡眠模式,应该在设置时钟源的时候也设置了 */
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT1_REG, 0X00);          /* 进入工作状态 */
    
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_INT_EN_REG, 0X00);          /* 关闭所有中断 */
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_USER_CTRL_REG, 0X00);       /* 关闭IIC主模式 */
    
    /* 轮趣小车失能旁路IIC,应该是在后面INT低电平有效设置了 */
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_INTBP_CFG_REG, 0X00);       /* 关闭IIC旁路直通 */
    
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_FIFO_EN_REG, 0X00);         /* 关闭FIFO */
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_INTBP_CFG_REG, 0X80);       /* INT引脚低电平有效 */
    mpu6050_read_byte(MPU6050_IIC_ADDR, MPU_DEVICE_ID_REG, &id);         /* 读取设备ID */
    if (id != MPU6050_IIC_ADDR)
    {
        return MPU6050_EID;
    }
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT1_REG, 0x02);       /* 设置CLKSEL,PLL Y轴为参考,正点原子是X轴 */
    mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT2_REG, 0x00);       /* 加速度与陀螺仪都工作 */
    //mpu6050_set_rate(50);                                                /* 采样率,50Hz */
    
    return MPU6050_EOK;
}

/**
 * @brief       MPU6050 DMP初始化
 * @param       无
 * @retval      0: 函数执行成功
 *              1: 函数执行失败
 *              轮趣小车直接 void,正点原子是uint8_t
 */
void mpu6050_dmp_init(void)
{
    uint8_t ret;
    /* 轮趣小车的读取缓冲 */
    uint8_t temp[1] = {0};
    mpu6050_read(MPU_SIGPATH_RST_REG, MPU_DEVICE_ID_REG, 1, temp);;
    
//    ret  = mpu_init(NULL);
    ret  = mpu_init();                                          /* 硬件初始化,正点原子因为没有把含中断的入口参数修改掉,所以传参NULL */
    ret += mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL);       /* 开启指定传感器 */
    ret += mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL);    /* 设置FIFO */
    ret += mpu_set_sample_rate(DEFAULT_MPU_HZ);                 /* 设置采样率 */
    ret += dmp_load_motion_driver_firmware();                   /* 加载DMP镜像 */
    ret += dmp_set_orientation(                                 /* 设置陀螺仪方向 */
                                inv_orientation_matrix_to_scalar(gyro_orientation));
    ret += dmp_enable_feature(  DMP_FEATURE_6X_LP_QUAT      |   /* 设置DMP功能 */
                                DMP_FEATURE_TAP             |
                                DMP_FEATURE_ANDROID_ORIENT  |
                                DMP_FEATURE_SEND_RAW_ACCEL  |
                                DMP_FEATURE_SEND_CAL_GYRO   |
                                DMP_FEATURE_GYRO_CAL);
    ret += dmp_set_fifo_rate(DEFAULT_MPU_HZ);                   /* 设置DMP输出速率 */
    ret += mpu_set_dmp_state(1);                                /* 使能DMP */
//    ret += mpu6050_run_self_test();                             /* 传感器自测试 */
    mpu6050_run_self_test();                                    /* 传感器自测试 */
    
//    return ((ret == 0) ? 0 : 1);
}


/**
 * @brief       获取MPU6050 DMP处理后的数据
 * @note        获取数据的频率需与宏DEFAULT_MPU_HZ定义的频率一致,
 *              获取太快,可能因MPU6050还未进行数据采样,导致FIFO中无数据,从而获取失败,
 *              获取太慢,可能无法及时读出MPU6050 FIFO中的数据,导致FIFO溢出,从而获取失败
 * @param       pitch: 俯仰角(精度: 0.1° 范围:  -90.0° <--->  +90.0°)
 *              roll : 横滚角(精度: 0.1° 范围: -180.0° <---> +180.0°)
 *              yaw  : 航向角(精度: 0.1° 范围: -180.0° <---> +180.0°)
 * @retval      0: 函数执行成功
 *              1: 函数执行失败
 *              轮趣小车直接是 void,正点原子是uint8_t,且有参数三个欧拉角
 */
//uint8_t mpu6050_dmp_get_data(float *pitch, float *roll, float *yaw)
void mpu6050_dmp_get_data(void)
{
//    float q0 = 0.0f;
//    float q1 = 0.0f;
//    float q2 = 0.0f;
//    float q3 = 0.0f;
//    short gyro[3], accel[3], sensors;
    unsigned long sensor_timestamp;
    unsigned char more;
    long quat[4];
    
    /* 读取MPU6050 FIFO中数据的频率需与宏DEFAULT_MPU_HZ定义的频率一直
     * 读取得太快或太慢都可能导致读取失败
     * 读取太快:MPU6050还未采样,FIFO中无数据,读取失败
     * 读取太慢:MPU6050的FIFO溢出,读取失败
     */
//    if (dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more) != 0)
//    {
//        return 1;
//    }
    dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more);
    
    if (sensors & INV_WXYZ_QUAT)
    {
        /* MPU6050的DMP输出的是姿态解算后的四元数,
         * 采用q30格式,即结果被放大了2的30次方倍,
         * 因为四元数并不是角度信号,因此为了得到欧拉角,
         * 就需要对MPU6050的DMP输出结果进行转换
         */
        q0 = quat[0] / q30;
        q1 = quat[1] / q30;
        q2 = quat[2] / q30;
        q3 = quat[3] / q30;
        
        /* 计算俯仰角、横滚角、航向角
         * 57.3为弧度转角度的转换系数,即180/PI
         */
//        *pitch  = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3;
//        *roll   = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1) * 57.3;
//        *yaw    = atan2(2 * (q1 * q2 + q0 * q3), q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.3;
        pitch  = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3;
        roll   = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1) * 57.3;
        yaw    = atan2(2 * (q1 * q2 + q0 * q3), q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.3;
    }
//    else
//    {
//        return 1;
//    }
//    
//    return 0;
}

/**
 * @brief       MPU6050获取温度值
 * @param       temperature: 获取到的温度值(扩大了10倍)
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 *              轮趣小车则是直接返回温度值
 */
//uint8_t mpu6050_get_temperature(int16_t *temp)
int mpu6050_get_temperature(void)
{
//    uint8_t dat[2];
//    uint8_t ret;
//    int16_t raw = 0;
//    
//    ret = mpu6050_read(MPU6050_IIC_ADDR, MPU_TEMP_OUTH_REG, 2, dat);
//    if (ret == MPU6050_EOK)
//    {
//        raw = ((uint16_t)dat[0] << 8) | dat[1];
//        *temp = (int16_t)((36.53f + ((float)raw / 340)) * 10);
//    }
//    
//    return ret;
    float temp;
    uint8_t dat[2];
    int16_t raw = 0;
    mpu6050_read(MPU6050_IIC_ADDR, MPU_TEMP_OUTH_REG, 2, dat);
    raw = ((uint16_t)dat[0] << 8) | dat[1];
    temp = (int16_t)((36.53f + ((float)raw / 340)) * 10);
    return (int)temp;
}


/**
 * @brief       MPU6050获取陀螺仪值
 * @param       gx,gy,gz: 陀螺仪x、y、z轴的原始度数(带符号)
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_get_gyroscope(int16_t *gx, int16_t *gy, int16_t *gz)
{
    uint8_t dat[6];
    uint8_t ret;
    
    ret =  mpu6050_read(MPU6050_IIC_ADDR, MPU_GYRO_XOUTH_REG, 6, dat);
    if (ret == MPU6050_EOK)
    {
        *gx = ((uint16_t)dat[0] << 8) | dat[1];
        *gy = ((uint16_t)dat[2] << 8) | dat[3];
        *gz = ((uint16_t)dat[4] << 8) | dat[5];
    }
    
    return ret;
}

/**
 * @brief       MPU6050获取加速度值
 * @param       ax,ay,az: 加速度x、y、z轴的原始度数(带符号)
 * @retval      MPU6050_EOK : 函数执行成功
 *              MPU6050_EACK: IIC通讯ACK错误,函数执行失败
 */
uint8_t mpu6050_get_accelerometer(int16_t *ax, int16_t *ay, int16_t *az)
{
    uint8_t dat[6];
    uint8_t ret;
    
    ret =  mpu6050_read(MPU6050_IIC_ADDR, MPU_ACCEL_XOUTH_REG, 6, dat);
    if (ret == MPU6050_EOK)
    {
        *ax = ((uint16_t)dat[0] << 8) | dat[1];
        *ay = ((uint16_t)dat[2] << 8) | dat[3];
        *az = ((uint16_t)dat[4] << 8) | dat[5];
    }
    
    return ret;
}

mpu6050.h

/**
 ****************************************************************************************************
 * @file        mpu6050.h
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       MPU6050模块驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车完成MPU6050的初始化与读数据
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#ifndef __MPU6050_H
#define __MPU6050_H

#include "./SYSTEM/sys/sys.h"

/* 引脚定义 */
#define MPU6050_AD0_GPIO_PORT            GPIOA
#define MPU6050_AD0_GPIO_PIN             GPIO_PIN_12
#define MPU6050_AD0_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOA_CLK_ENABLE();}while(0)

/* IO操作 */
#define MPU6050_AD0(x)                   do{ x ?                                                                                     \
                                            HAL_GPIO_WritePin(MPU6050_AD0_GPIO_PORT, MPU6050_AD0_GPIO_PIN, GPIO_PIN_SET) :    \
                                            HAL_GPIO_WritePin(MPU6050_AD0_GPIO_PORT, MPU6050_AD0_GPIO_PIN, GPIO_PIN_RESET);   \
                                            }while(0)

/* MPU6050的IIC通讯从机地址
 * 如果MPU6050的AD0引脚被拉低,则其IIC通讯的地址为0x68
 * 如果MPU6050的AD0引脚被拉高,则其IIC通讯的地址为0x69
 */
#define MPU6050_IIC_ADDR     0x68

/* MPU6050寄存器地址定义 */
#define MPU_ACCEL_OFFS_REG      0X06    // accel_offs寄存器,可读取版本号,寄存器手册未提到
#define MPU_PROD_ID_REG         0X0C    // prod id寄存器,在寄存器手册未提到
#define MPU_SELF_TESTX_REG      0X0D    // 自检寄存器X
#define MPU_SELF_TESTY_REG      0X0E    // 自检寄存器Y
#define MPU_SELF_TESTZ_REG      0X0F    // 自检寄存器Z
#define MPU_SELF_TESTA_REG      0X10    // 自检寄存器A
#define MPU_SAMPLE_RATE_REG     0X19    // 采样频率分频器
#define MPU_CFG_REG             0X1A    // 配置寄存器
#define MPU_GYRO_CFG_REG        0X1B    // 陀螺仪配置寄存器
#define MPU_ACCEL_CFG_REG       0X1C    // 加速度计配置寄存器
#define MPU_MOTION_DET_REG      0X1F    // 运动检测阀值设置寄存器
#define MPU_FIFO_EN_REG         0X23    // FIFO使能寄存器
#define MPU_I2CMST_CTRL_REG     0X24    // IIC主机控制寄存器
#define MPU_I2CSLV0_ADDR_REG    0X25    // IIC从机0器件地址寄存器
#define MPU_I2CSLV0_REG         0X26    // IIC从机0数据地址寄存器
#define MPU_I2CSLV0_CTRL_REG    0X27    // IIC从机0控制寄存器
#define MPU_I2CSLV1_ADDR_REG    0X28    // IIC从机1器件地址寄存器
#define MPU_I2CSLV1_REG         0X29    // IIC从机1数据地址寄存器
#define MPU_I2CSLV1_CTRL_REG    0X2A    // IIC从机1控制寄存器
#define MPU_I2CSLV2_ADDR_REG    0X2B    // IIC从机2器件地址寄存器
#define MPU_I2CSLV2_REG         0X2C    // IIC从机2数据地址寄存器
#define MPU_I2CSLV2_CTRL_REG    0X2D    // IIC从机2控制寄存器
#define MPU_I2CSLV3_ADDR_REG    0X2E    // IIC从机3器件地址寄存器
#define MPU_I2CSLV3_REG         0X2F    // IIC从机3数据地址寄存器
#define MPU_I2CSLV3_CTRL_REG    0X30    // IIC从机3控制寄存器
#define MPU_I2CSLV4_ADDR_REG    0X31    // IIC从机4器件地址寄存器
#define MPU_I2CSLV4_REG         0X32    // IIC从机4数据地址寄存器
#define MPU_I2CSLV4_DO_REG      0X33    // IIC从机4写数据寄存器
#define MPU_I2CSLV4_CTRL_REG    0X34    // IIC从机4控制寄存器
#define MPU_I2CSLV4_DI_REG      0X35    // IIC从机4读数据寄存器
#define MPU_I2CMST_STA_REG      0X36    // IIC主机状态寄存器
#define MPU_INTBP_CFG_REG       0X37    // 中断/旁路设置寄存器
#define MPU_INT_EN_REG          0X38    // 中断使能寄存器
#define MPU_INT_STA_REG         0X3A    // 中断状态寄存器
#define MPU_ACCEL_XOUTH_REG     0X3B    // 加速度值,X轴高8位寄存器
#define MPU_ACCEL_XOUTL_REG     0X3C    // 加速度值,X轴低8位寄存器
#define MPU_ACCEL_YOUTH_REG     0X3D    // 加速度值,Y轴高8位寄存器
#define MPU_ACCEL_YOUTL_REG     0X3E    // 加速度值,Y轴低8位寄存器
#define MPU_ACCEL_ZOUTH_REG     0X3F    // 加速度值,Z轴高8位寄存器
#define MPU_ACCEL_ZOUTL_REG     0X40    // 加速度值,Z轴低8位寄存器
#define MPU_TEMP_OUTH_REG       0X41    // 温度值高八位寄存器
#define MPU_TEMP_OUTL_REG       0X42    // 温度值低8位寄存器
#define MPU_GYRO_XOUTH_REG      0X43    // 陀螺仪值,X轴高8位寄存器
#define MPU_GYRO_XOUTL_REG      0X44    // 陀螺仪值,X轴低8位寄存器
#define MPU_GYRO_YOUTH_REG      0X45    // 陀螺仪值,Y轴高8位寄存器
#define MPU_GYRO_YOUTL_REG      0X46    // 陀螺仪值,Y轴低8位寄存器
#define MPU_GYRO_ZOUTH_REG      0X47    // 陀螺仪值,Z轴高8位寄存器
#define MPU_GYRO_ZOUTL_REG      0X48    // 陀螺仪值,Z轴低8位寄存器
#define MPU_I2CSLV0_DO_REG      0X63    // IIC从机0数据寄存器
#define MPU_I2CSLV1_DO_REG      0X64    // IIC从机1数据寄存器
#define MPU_I2CSLV2_DO_REG      0X65    // IIC从机2数据寄存器
#define MPU_I2CSLV3_DO_REG      0X66    // IIC从机3数据寄存器
#define MPU_I2CMST_DELAY_REG    0X67    // IIC主机延时管理寄存器
#define MPU_SIGPATH_RST_REG     0X68    // 信号通道复位寄存器
#define MPU_MDETECT_CTRL_REG    0X69    // 运动检测控制寄存器
#define MPU_USER_CTRL_REG       0X6A    // 用户控制寄存器
#define MPU_PWR_MGMT1_REG       0X6B    // 电源管理寄存器1
#define MPU_PWR_MGMT2_REG       0X6C    // 电源管理寄存器2 
#define MPU_FIFO_CNTH_REG       0X72    // FIFO计数寄存器高八位
#define MPU_FIFO_CNTL_REG       0X73    // FIFO计数寄存器低八位
#define MPU_FIFO_RW_REG         0X74    // FIFO读写寄存器
#define MPU_DEVICE_ID_REG       0X75    // 器件ID寄存器

/* 函数错误代码 */
#define MPU6050_EOK      0   /* 没有错误 */
#define MPU6050_EID      1   /* ID错误 */
#define MPU6050_EACK     2   /* IIC通讯ACK错误 */


/* 参数 */
extern short gyro[3], accel[3];
extern float pitch, roll, yaw;

/* 操作函数 */
uint8_t mpu6050_write(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *dat); /* 往MPU6050的指定寄存器连续写入指定数据 */
uint8_t mpu6050_write_byte(uint8_t addr, uint8_t reg, uint8_t dat);          /* 往MPU050的指定寄存器写入一字节数据 */
uint8_t mpu6050_read(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *dat);  /* 连续读取MPU6050指定寄存器的值 */
uint8_t mpu6050_read_byte(uint8_t addr, uint8_t reg, uint8_t *dat);          /* 读取MPU6050指定寄存器的值 */
void mpu6050_sw_reset(void);                                                 /* MPU6050软件复位 */
uint8_t mpu6050_set_gyro_fsr(uint8_t fsr);                                   /* MPU6050设置陀螺仪传感器量程范围 */
uint8_t mpu6050_set_accel_fsr(uint8_t fsr);                                  /* MPU6050设置加速度传感器量程范围 */
uint8_t mpu6050_set_lpf(uint16_t lpf);                                       /* MPU6050设置数字低通滤波器频率 */
uint8_t mpu6050_set_rate(uint16_t rate);                                     /* MPU6050设置采样率 */

uint8_t mpu6050_init(void);                                                  /* MPU6050初始化 */

static void mpu6050_run_self_test(void);
void mpu6050_dmp_init(void);
void mpu6050_dmp_get_data(void);
//uint8_t mpu6050_get_temperature(int16_t *temp);                              /* MPU6050获取温度值 */
int mpu6050_get_temperature(void);
uint8_t mpu6050_get_gyroscope(int16_t *gx, int16_t *gy, int16_t *gz);          /* MPU6050获取陀螺仪值 */
uint8_t mpu6050_get_accelerometer(int16_t *ax, int16_t *ay, int16_t *az);      /* MPU6050获取加速度值 */

#endif

官方库文件,直接复制就可以,有更改的地方比如头文件什的么自行修改,其余照抄,根据我的经验就可以了

  • inv_mpu.c
  • inv_mpu.h
  • inv_mpu_dmp_motion_driver.c
  • inv_mpu_dmp_motion_driver.h

这四个文件,除了头文件其他基本没啥改动啥也不用改,篇幅问题我就不放进来了。

外部中断

整个程序最重要的定时,也就是处理的频率,就依靠外部中断来完成!

通过MPU6050的INT引脚,接到PA12上,可以固定触发一个中断,设置为低电平触发;我们设置的DMP采样是200Hz(由于DMP的限制,最少需要5ms计算时间,不能再短了),然后在中断中设置触发十次才进入控制计算。

exti.c

/**
 ****************************************************************************************************
 * @file        exti.c
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       外部中断 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车,用外部中断完成50ms的精准计时
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/EXTI/exti.h"

volatile uint8_t delay_flag, delay_50;     /* 提供延时的变量 */


/**
 * @brief       MPU6050_INT 外部中断服务程序
 * @param       无
 * @retval      无
 */
void MPU6050_INT_IRQHandler(void)
{
    if (MPU6050_read == 0)
    {
        if(delay_flag == 1)
        {
            delay_50++;
            if(delay_50 == 10)
            {
                delay_50 = 0;
                delay_flag = 0;/* 给主函数提供50ms的精准延时,示波器需要50ms高精度延时 */
            }
        }
    }
    /* 调用中断处理公用函数 清除MPU6050_INT所在中断线 的中断标志位 */
    HAL_GPIO_EXTI_IRQHandler(MPU6050_INT_GPIO_PIN);
    /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
    __HAL_GPIO_EXTI_CLEAR_IT(MPU6050_INT_GPIO_PIN);
}


/**
 * @brief       外部中断初始化程序
 * @param       无
 * @retval      无
 */
void extix_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    MPU6050_INT_GPIO_CLK_ENABLE();                              /* MPU6050_INT时钟使能 */

    gpio_init_struct.Pin = MPU6050_INT_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;               /* 下降沿触发 */
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(MPU6050_INT_GPIO_PORT, &gpio_init_struct);    /* MPU6050_INT配置为下降沿触发中断 */

    HAL_NVIC_SetPriority(MPU6050_INT_IRQn, 2, 1);               /* 抢占2,子优先级1 */
    HAL_NVIC_EnableIRQ(MPU6050_INT_IRQn);                       /* 使能中断线15_10 */
}

exti.h

/**
 ****************************************************************************************************
 * @file        exti.h
 * @author      Xia
 * @version     V1.0
 * @date        2023-08-13
 * @brief       外部中断 驱动代码
 ****************************************************************************************************
 * @attention
 *
 * 轮趣小车,用外部中断完成50ms的精准计时
 *
 *
 * 修改说明
 * V1.0 20230813
 * 第一次发布
 *
 *
 ****************************************************************************************************
 */

#ifndef __EXTI_H
#define __EXTI_H

#include "./SYSTEM/sys/sys.h"

/******************************************************************************************/
/* 引脚 和 中断编号 & 中断服务函数 定义 */ 

#define MPU6050_INT_GPIO_PORT              GPIOA
#define MPU6050_INT_GPIO_PIN               GPIO_PIN_12
#define MPU6050_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */
#define MPU6050_INT_IRQn                   EXTI15_10_IRQn
#define MPU6050_INT_IRQHandler             EXTI15_10_IRQHandler

#define MPU6050_read        HAL_GPIO_ReadPin(MPU6050_INT_GPIO_PORT, MPU6050_INT_GPIO_PIN)     /* 读取MPU6050_INT引脚 */

/******************************************************************************************/


void extix_init(void);  /* 外部中断初始化 */

#endif

总结

到这里,按照正点原子的HAL库学到的代码管理风格的初始化移植基本就完成了,最后在main函数里面完成调用初始化就OK了(注意要失能JTAG失使能SWD)。

问题

一些控制代码我没放进来,但是按照这个方法全写完之后,烧进去oled的更新就很慢,而且小车控制不住。我猜测的原因,可能是因为c8t6的flash只有64kb,但我写完代码烧进去的大小有56kb,可能有点带不动;或者是这样子代码管理,宏定义太多了影响了速度。

总之现在就是按照这个代码是跑步起来的,**尽管我已经另外写了CUBEMX版本证明代码逻辑都是对的!**所以这一篇就是来学习怎么初始化这些引脚、定时器、中断和传感器的,看一下逻辑和一些写法就可以了!

后续更新计划

我会把控制、oled显示内容以及滤波算法写一下,然后把源代码打包发上来。

可供参考的学习资料

我把我的知乎发在这里,我的专栏里面有电机的内容以及stm32基础内容的学习笔记,可以帮你快速上手基础知识。

知乎搜索昵称:我就像一个哑巴​;看更多基础学习笔记

文章:电机就是有刷电机部分,stm32基础就是通用定时器,其他都很基础,应该不需要过多关注,直接用就行。

Logo

欢迎加入我们的广州开发者社区,与优秀的开发者共同成长!

更多推荐