stm32平衡小车——正点原子风格初始化

编程入门 行业动态 更新时间:2024-10-14 18:17:58

stm32平衡小车——正点原子风格<a href=https://www.elefans.com/category/jswz/34/1770206.html style=初始化"/>

stm32平衡小车——正点原子风格初始化

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

总体思路,先移植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* 技术论坛:www.openedv* 公司网址:www.alientek* 购买地址:openedv.taobao** 修改说明* 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,可以同时控制两个电机:

接入一个电机为例,PWMA就是单片机的一个PWM输出,AIN1/2用来控制旋转方向,AO1/2就是输出的电平,接到电机的正负极;B的同理,以此来控制两个电机。要注意的是STBY需要在高电平才能够工作。电机的旋转方向如下表所示:

直流有刷电机驱动的代码,我会在最后的整体控制中写,定时器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;elseb = 7;      // errorreturn 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基础就是通用定时器,其他都很基础,应该不需要过多关注,直接用就行。

更多推荐

stm32平衡小车——正点原子风格初始化

本文发布于:2024-02-27 13:54:33,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1706808.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:初始化   小车   原子   风格

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!