stm32平衡小车——正点原子风格初始化
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.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
* V1.0 20200421
* 第一次发布
*
****************************************************************************************************
*/
#ifndef __OLEDFONT_H
#define __OLEDFONT_H
/* 常用ASCII表
* 偏移量32
* ASCII字符集: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
* PC2LCD2002取模方式设置:阴码+逐列式+顺向+C51格式
* 总共:3个字符集(12*12、16*16和24*24),用户可以自行新增其他分辨率的字符集。
* 每个字符所占用的字节数为:(size/8+((size%8)?1:0))*(size/2),其中size:是字库生成时的点阵大小(12/16/24...)
*/
/* 12*12 ASCII字符集点阵 */
const unsigned char oled_asc2_1206[95][12]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
{0x00,0x00,0x00,0x00,0x3F,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
{0x00,0x00,0x30,0x00,0x40,0x00,0x30,0x00,0x40,0x00,0x00,0x00},/*""",2*/
{0x09,0x00,0x0B,0xC0,0x3D,0x00,0x0B,0xC0,0x3D,0x00,0x09,0x00},/*"#",3*/
{0x18,0xC0,0x24,0x40,0x7F,0xE0,0x22,0x40,0x31,0x80,0x00,0x00},/*"$",4*/
{0x18,0x00,0x24,0xC0,0x1B,0x00,0x0D,0x80,0x32,0x40,0x01,0x80},/*"%",5*/
{0x03,0x80,0x1C,0x40,0x27,0x40,0x1C,0x80,0x07,0x40,0x00,0x40},/*"&",6*/
{0x10,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x80,0x20,0x40,0x40,0x20},/*"(",8*/
{0x00,0x00,0x40,0x20,0x20,0x40,0x1F,0x80,0x00,0x00,0x00,0x00},/*")",9*/
{0x09,0x00,0x06,0x00,0x1F,0x80,0x06,0x00,0x09,0x00,0x00,0x00},/*"*",10*/
{0x04,0x00,0x04,0x00,0x3F,0x80,0x04,0x00,0x04,0x00,0x00,0x00},/*"+",11*/
{0x00,0x10,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/
{0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x00,0x00},/*"-",13*/
{0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/
{0x00,0x20,0x01,0xC0,0x06,0x00,0x38,0x00,0x40,0x00,0x00,0x00},/*"/",15*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"0",16*/
{0x00,0x00,0x10,0x40,0x3F,0xC0,0x00,0x40,0x00,0x00,0x00,0x00},/*"1",17*/
{0x18,0xC0,0x21,0x40,0x22,0x40,0x24,0x40,0x18,0x40,0x00,0x00},/*"2",18*/
{0x10,0x80,0x20,0x40,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"3",19*/
{0x02,0x00,0x0D,0x00,0x11,0x00,0x3F,0xC0,0x01,0x40,0x00,0x00},/*"4",20*/
{0x3C,0x80,0x24,0x40,0x24,0x40,0x24,0x40,0x23,0x80,0x00,0x00},/*"5",21*/
{0x1F,0x80,0x24,0x40,0x24,0x40,0x34,0x40,0x03,0x80,0x00,0x00},/*"6",22*/
{0x30,0x00,0x20,0x00,0x27,0xC0,0x38,0x00,0x20,0x00,0x00,0x00},/*"7",23*/
{0x1B,0x80,0x24,0x40,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"8",24*/
{0x1C,0x00,0x22,0xC0,0x22,0x40,0x22,0x40,0x1F,0x80,0x00,0x00},/*"9",25*/
{0x00,0x00,0x00,0x00,0x08,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/
{0x00,0x00,0x00,0x00,0x04,0x60,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/
{0x00,0x00,0x04,0x00,0x0A,0x00,0x11,0x00,0x20,0x80,0x40,0x40},/*"<",28*/
{0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x00,0x00},/*"=",29*/
{0x00,0x00,0x40,0x40,0x20,0x80,0x11,0x00,0x0A,0x00,0x04,0x00},/*">",30*/
{0x18,0x00,0x20,0x00,0x23,0x40,0x24,0x00,0x18,0x00,0x00,0x00},/*"?",31*/
{0x1F,0x80,0x20,0x40,0x27,0x40,0x29,0x40,0x1F,0x40,0x00,0x00},/*"@",32*/
{0x00,0x40,0x07,0xC0,0x39,0x00,0x0F,0x00,0x01,0xC0,0x00,0x40},/*"A",33*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"B",34*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x30,0x80,0x00,0x00},/*"C",35*/
{0x20,0x40,0x3F,0xC0,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"D",36*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x2E,0x40,0x30,0xC0,0x00,0x00},/*"E",37*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x2E,0x00,0x30,0x00,0x00,0x00},/*"F",38*/
{0x0F,0x00,0x10,0x80,0x20,0x40,0x22,0x40,0x33,0x80,0x02,0x00},/*"G",39*/
{0x20,0x40,0x3F,0xC0,0x04,0x00,0x04,0x00,0x3F,0xC0,0x20,0x40},/*"H",40*/
{0x20,0x40,0x20,0x40,0x3F,0xC0,0x20,0x40,0x20,0x40,0x00,0x00},/*"I",41*/
{0x00,0x60,0x20,0x20,0x20,0x20,0x3F,0xC0,0x20,0x00,0x20,0x00},/*"J",42*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x0B,0x00,0x30,0xC0,0x20,0x40},/*"K",43*/
{0x20,0x40,0x3F,0xC0,0x20,0x40,0x00,0x40,0x00,0x40,0x00,0xC0},/*"L",44*/
{0x3F,0xC0,0x3C,0x00,0x03,0xC0,0x3C,0x00,0x3F,0xC0,0x00,0x00},/*"M",45*/
{0x20,0x40,0x3F,0xC0,0x0C,0x40,0x23,0x00,0x3F,0xC0,0x20,0x00},/*"N",46*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"O",47*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x24,0x00,0x18,0x00,0x00,0x00},/*"P",48*/
{0x1F,0x80,0x21,0x40,0x21,0x40,0x20,0xE0,0x1F,0xA0,0x00,0x00},/*"Q",49*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x26,0x00,0x19,0xC0,0x00,0x40},/*"R",50*/
{0x18,0xC0,0x24,0x40,0x24,0x40,0x22,0x40,0x31,0x80,0x00,0x00},/*"S",51*/
{0x30,0x00,0x20,0x40,0x3F,0xC0,0x20,0x40,0x30,0x00,0x00,0x00},/*"T",52*/
{0x20,0x00,0x3F,0x80,0x00,0x40,0x00,0x40,0x3F,0x80,0x20,0x00},/*"U",53*/
{0x20,0x00,0x3E,0x00,0x01,0xC0,0x07,0x00,0x38,0x00,0x20,0x00},/*"V",54*/
{0x38,0x00,0x07,0xC0,0x3C,0x00,0x07,0xC0,0x38,0x00,0x00,0x00},/*"W",55*/
{0x20,0x40,0x39,0xC0,0x06,0x00,0x39,0xC0,0x20,0x40,0x00,0x00},/*"X",56*/
{0x20,0x00,0x38,0x40,0x07,0xC0,0x38,0x40,0x20,0x00,0x00,0x00},/*"Y",57*/
{0x30,0x40,0x21,0xC0,0x26,0x40,0x38,0x40,0x20,0xC0,0x00,0x00},/*"Z",58*/
{0x00,0x00,0x00,0x00,0x7F,0xE0,0x40,0x20,0x40,0x20,0x00,0x00},/*"[",59*/
{0x00,0x00,0x70,0x00,0x0C,0x00,0x03,0x80,0x00,0x40,0x00,0x00},/*"\",60*/
{0x00,0x00,0x40,0x20,0x40,0x20,0x7F,0xE0,0x00,0x00,0x00,0x00},/*"]",61*/
{0x00,0x00,0x20,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00},/*"^",62*/
{0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10},/*"_",63*/
{0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
{0x00,0x00,0x02,0x80,0x05,0x40,0x05,0x40,0x03,0xC0,0x00,0x40},/*"a",65*/
{0x20,0x00,0x3F,0xC0,0x04,0x40,0x04,0x40,0x03,0x80,0x00,0x00},/*"b",66*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x06,0x40,0x00,0x00},/*"c",67*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x24,0x40,0x3F,0xC0,0x00,0x40},/*"d",68*/
{0x00,0x00,0x03,0x80,0x05,0x40,0x05,0x40,0x03,0x40,0x00,0x00},/*"e",69*/
{0x00,0x00,0x04,0x40,0x1F,0xC0,0x24,0x40,0x24,0x40,0x20,0x00},/*"f",70*/
{0x00,0x00,0x02,0xE0,0x05,0x50,0x05,0x50,0x06,0x50,0x04,0x20},/*"g",71*/
{0x20,0x40,0x3F,0xC0,0x04,0x40,0x04,0x00,0x03,0xC0,0x00,0x40},/*"h",72*/
{0x00,0x00,0x04,0x40,0x27,0xC0,0x00,0x40,0x00,0x00,0x00,0x00},/*"i",73*/
{0x00,0x10,0x00,0x10,0x04,0x10,0x27,0xE0,0x00,0x00,0x00,0x00},/*"j",74*/
{0x20,0x40,0x3F,0xC0,0x01,0x40,0x07,0x00,0x04,0xC0,0x04,0x40},/*"k",75*/
{0x20,0x40,0x20,0x40,0x3F,0xC0,0x00,0x40,0x00,0x40,0x00,0x00},/*"l",76*/
{0x07,0xC0,0x04,0x00,0x07,0xC0,0x04,0x00,0x03,0xC0,0x00,0x00},/*"m",77*/
{0x04,0x40,0x07,0xC0,0x04,0x40,0x04,0x00,0x03,0xC0,0x00,0x40},/*"n",78*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x03,0x80,0x00,0x00},/*"o",79*/
{0x04,0x10,0x07,0xF0,0x04,0x50,0x04,0x40,0x03,0x80,0x00,0x00},/*"p",80*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x50,0x07,0xF0,0x00,0x10},/*"q",81*/
{0x04,0x40,0x07,0xC0,0x02,0x40,0x04,0x00,0x04,0x00,0x00,0x00},/*"r",82*/
{0x00,0x00,0x06,0x40,0x05,0x40,0x05,0x40,0x04,0xC0,0x00,0x00},/*"s",83*/
{0x00,0x00,0x04,0x00,0x1F,0x80,0x04,0x40,0x00,0x40,0x00,0x00},/*"t",84*/
{0x04,0x00,0x07,0x80,0x00,0x40,0x04,0x40,0x07,0xC0,0x00,0x40},/*"u",85*/
{0x04,0x00,0x07,0x00,0x04,0xC0,0x01,0x80,0x06,0x00,0x04,0x00},/*"v",86*/
{0x06,0x00,0x01,0xC0,0x07,0x00,0x01,0xC0,0x06,0x00,0x00,0x00},/*"w",87*/
{0x04,0x40,0x06,0xC0,0x01,0x00,0x06,0xC0,0x04,0x40,0x00,0x00},/*"x",88*/
{0x04,0x10,0x07,0x10,0x04,0xE0,0x01,0x80,0x06,0x00,0x04,0x00},/*"y",89*/
{0x00,0x00,0x04,0x40,0x05,0xC0,0x06,0x40,0x04,0x40,0x00,0x00},/*"z",90*/
{0x00,0x00,0x00,0x00,0x04,0x00,0x7B,0xE0,0x40,0x20,0x00,0x00},/*"{",91*/
{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xF0,0x00,0x00,0x00,0x00},/*"|",92*/
{0x00,0x00,0x40,0x20,0x7B,0xE0,0x04,0x00,0x00,0x00,0x00,0x00},/*"}",93*/
{0x40,0x00,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x40,0x00},/*"~",94*/
};
/* 16*16 ASCII字符集点阵 */
const unsigned char oled_asc2_1608[95][16]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
{0x00,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x00,0x00},/*""",2*/
{0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x00,0x00},/*"#",3*/
{0x00,0x00,0x0E,0x18,0x11,0x04,0x3F,0xFF,0x10,0x84,0x0C,0x78,0x00,0x00,0x00,0x00},/*"$",4*/
{0x0F,0x00,0x10,0x84,0x0F,0x38,0x00,0xC0,0x07,0x78,0x18,0x84,0x00,0x78,0x00,0x00},/*"%",5*/
{0x00,0x78,0x0F,0x84,0x10,0xC4,0x11,0x24,0x0E,0x98,0x00,0xE4,0x00,0x84,0x00,0x08},/*"&",6*/
{0x08,0x00,0x68,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x18,0x18,0x20,0x04,0x40,0x02,0x00,0x00},/*"(",8*/
{0x00,0x00,0x40,0x02,0x20,0x04,0x18,0x18,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,0x00},/*")",9*/
{0x02,0x40,0x02,0x40,0x01,0x80,0x0F,0xF0,0x01,0x80,0x02,0x40,0x02,0x40,0x00,0x00},/*"*",10*/
{0x00,0x80,0x00,0x80,0x00,0x80,0x0F,0xF8,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00},/*"+",11*/
{0x00,0x01,0x00,0x0D,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/
{0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80},/*"-",13*/
{0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/
{0x00,0x00,0x00,0x06,0x00,0x18,0x00,0x60,0x01,0x80,0x06,0x00,0x18,0x00,0x20,0x00},/*"/",15*/
{0x00,0x00,0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"0",16*/
{0x00,0x00,0x08,0x04,0x08,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"1",17*/
{0x00,0x00,0x0E,0x0C,0x10,0x14,0x10,0x24,0x10,0x44,0x11,0x84,0x0E,0x0C,0x00,0x00},/*"2",18*/
{0x00,0x00,0x0C,0x18,0x10,0x04,0x11,0x04,0x11,0x04,0x12,0x88,0x0C,0x70,0x00,0x00},/*"3",19*/
{0x00,0x00,0x00,0xE0,0x03,0x20,0x04,0x24,0x08,0x24,0x1F,0xFC,0x00,0x24,0x00,0x00},/*"4",20*/
{0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",21*/
{0x00,0x00,0x07,0xF0,0x08,0x88,0x11,0x04,0x11,0x04,0x18,0x88,0x00,0x70,0x00,0x00},/*"6",22*/
{0x00,0x00,0x1C,0x00,0x10,0x00,0x10,0xFC,0x13,0x00,0x1C,0x00,0x10,0x00,0x00,0x00},/*"7",23*/
{0x00,0x00,0x0E,0x38,0x11,0x44,0x10,0x84,0x10,0x84,0x11,0x44,0x0E,0x38,0x00,0x00},/*"8",24*/
{0x00,0x00,0x07,0x00,0x08,0x8C,0x10,0x44,0x10,0x44,0x08,0x88,0x07,0xF0,0x00,0x00},/*"9",25*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0C,0x03,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/
{0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/
{0x00,0x00,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10,0x04,0x00,0x00},/*"<",28*/
{0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x00,0x00},/*"=",29*/
{0x00,0x00,0x10,0x04,0x08,0x08,0x04,0x10,0x02,0x20,0x01,0x40,0x00,0x80,0x00,0x00},/*">",30*/
{0x00,0x00,0x0E,0x00,0x12,0x00,0x10,0x0C,0x10,0x6C,0x10,0x80,0x0F,0x00,0x00,0x00},/*"?",31*/
{0x03,0xE0,0x0C,0x18,0x13,0xE4,0x14,0x24,0x17,0xC4,0x08,0x28,0x07,0xD0,0x00,0x00},/*"@",32*/
{0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04},/*"A",33*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x11,0x04,0x0E,0x88,0x00,0x70,0x00,0x00},/*"B",34*/
{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x08,0x1C,0x10,0x00,0x00},/*"C",35*/
{0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"D",36*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x17,0xC4,0x10,0x04,0x08,0x18,0x00,0x00},/*"E",37*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x17,0xC0,0x10,0x00,0x08,0x00,0x00,0x00},/*"F",38*/
{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x44,0x1C,0x78,0x00,0x40,0x00,0x00},/*"G",39*/
{0x10,0x04,0x1F,0xFC,0x10,0x84,0x00,0x80,0x00,0x80,0x10,0x84,0x1F,0xFC,0x10,0x04},/*"H",40*/
{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x00,0x00,0x00,0x00},/*"I",41*/
{0x00,0x03,0x00,0x01,0x10,0x01,0x10,0x01,0x1F,0xFE,0x10,0x00,0x10,0x00,0x00,0x00},/*"J",42*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x03,0x80,0x14,0x64,0x18,0x1C,0x10,0x04,0x00,0x00},/*"K",43*/
{0x10,0x04,0x1F,0xFC,0x10,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x0C,0x00,0x00},/*"L",44*/
{0x10,0x04,0x1F,0xFC,0x1F,0x00,0x00,0xFC,0x1F,0x00,0x1F,0xFC,0x10,0x04,0x00,0x00},/*"M",45*/
{0x10,0x04,0x1F,0xFC,0x0C,0x04,0x03,0x00,0x00,0xE0,0x10,0x18,0x1F,0xFC,0x10,0x00},/*"N",46*/
{0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"O",47*/
{0x10,0x04,0x1F,0xFC,0x10,0x84,0x10,0x80,0x10,0x80,0x10,0x80,0x0F,0x00,0x00,0x00},/*"P",48*/
{0x07,0xF0,0x08,0x18,0x10,0x24,0x10,0x24,0x10,0x1C,0x08,0x0A,0x07,0xF2,0x00,0x00},/*"Q",49*/
{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x11,0xC0,0x11,0x30,0x0E,0x0C,0x00,0x04},/*"R",50*/
{0x00,0x00,0x0E,0x1C,0x11,0x04,0x10,0x84,0x10,0x84,0x10,0x44,0x1C,0x38,0x00,0x00},/*"S",51*/
{0x18,0x00,0x10,0x00,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x00,0x18,0x00,0x00,0x00},/*"T",52*/
{0x10,0x00,0x1F,0xF8,0x10,0x04,0x00,0x04,0x00,0x04,0x10,0x04,0x1F,0xF8,0x10,0x00},/*"U",53*/
{0x10,0x00,0x1E,0x00,0x11,0xE0,0x00,0x1C,0x00,0x70,0x13,0x80,0x1C,0x00,0x10,0x00},/*"V",54*/
{0x1F,0xC0,0x10,0x3C,0x00,0xE0,0x1F,0x00,0x00,0xE0,0x10,0x3C,0x1F,0xC0,0x00,0x00},/*"W",55*/
{0x10,0x04,0x18,0x0C,0x16,0x34,0x01,0xC0,0x01,0xC0,0x16,0x34,0x18,0x0C,0x10,0x04},/*"X",56*/
{0x10,0x00,0x1C,0x00,0x13,0x04,0x00,0xFC,0x13,0x04,0x1C,0x00,0x10,0x00,0x00,0x00},/*"Y",57*/
{0x08,0x04,0x10,0x1C,0x10,0x64,0x10,0x84,0x13,0x04,0x1C,0x04,0x10,0x18,0x00,0x00},/*"Z",58*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x40,0x02,0x40,0x02,0x40,0x02,0x00,0x00},/*"[",59*/
{0x00,0x00,0x30,0x00,0x0C,0x00,0x03,0x80,0x00,0x60,0x00,0x1C,0x00,0x03,0x00,0x00},/*"\",60*/
{0x00,0x00,0x40,0x02,0x40,0x02,0x40,0x02,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00},/*"]",61*/
{0x00,0x00,0x00,0x00,0x20,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00},/*"^",62*/
{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01},/*"_",63*/
{0x00,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
{0x00,0x00,0x00,0x98,0x01,0x24,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xFC,0x00,0x04},/*"a",65*/
{0x10,0x00,0x1F,0xFC,0x00,0x88,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"b",66*/
{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x00},/*"c",67*/
{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x11,0x08,0x1F,0xFC,0x00,0x04},/*"d",68*/
{0x00,0x00,0x00,0xF8,0x01,0x44,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xC8,0x00,0x00},/*"e",69*/
{0x00,0x00,0x01,0x04,0x01,0x04,0x0F,0xFC,0x11,0x04,0x11,0x04,0x11,0x00,0x18,0x00},/*"f",70*/
{0x00,0x00,0x00,0xD6,0x01,0x29,0x01,0x29,0x01,0x29,0x01,0xC9,0x01,0x06,0x00,0x00},/*"g",71*/
{0x10,0x04,0x1F,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"h",72*/
{0x00,0x00,0x01,0x04,0x19,0x04,0x19,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"i",73*/
{0x00,0x00,0x00,0x03,0x00,0x01,0x01,0x01,0x19,0x01,0x19,0xFE,0x00,0x00,0x00,0x00},/*"j",74*/
{0x10,0x04,0x1F,0xFC,0x00,0x24,0x00,0x40,0x01,0xB4,0x01,0x0C,0x01,0x04,0x00,0x00},/*"k",75*/
{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"l",76*/
{0x01,0x04,0x01,0xFC,0x01,0x04,0x01,0x00,0x01,0xFC,0x01,0x04,0x01,0x00,0x00,0xFC},/*"m",77*/
{0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"n",78*/
{0x00,0x00,0x00,0xF8,0x01,0x04,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0xF8,0x00,0x00},/*"o",79*/
{0x01,0x01,0x01,0xFF,0x00,0x85,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"p",80*/
{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x05,0x01,0xFF,0x00,0x01},/*"q",81*/
{0x01,0x04,0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x04,0x01,0x00,0x01,0x80,0x00,0x00},/*"r",82*/
{0x00,0x00,0x00,0xCC,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x98,0x00,0x00},/*"s",83*/
{0x00,0x00,0x01,0x00,0x01,0x00,0x07,0xF8,0x01,0x04,0x01,0x04,0x00,0x00,0x00,0x00},/*"t",84*/
{0x01,0x00,0x01,0xF8,0x00,0x04,0x00,0x04,0x00,0x04,0x01,0x08,0x01,0xFC,0x00,0x04},/*"u",85*/
{0x01,0x00,0x01,0x80,0x01,0x70,0x00,0x0C,0x00,0x10,0x01,0x60,0x01,0x80,0x01,0x00},/*"v",86*/
{0x01,0xF0,0x01,0x0C,0x00,0x30,0x01,0xC0,0x00,0x30,0x01,0x0C,0x01,0xF0,0x01,0x00},/*"w",87*/
{0x00,0x00,0x01,0x04,0x01,0x8C,0x00,0x74,0x01,0x70,0x01,0x8C,0x01,0x04,0x00,0x00},/*"x",88*/
{0x01,0x01,0x01,0x81,0x01,0x71,0x00,0x0E,0x00,0x18,0x01,0x60,0x01,0x80,0x01,0x00},/*"y",89*/
{0x00,0x00,0x01,0x84,0x01,0x0C,0x01,0x34,0x01,0x44,0x01,0x84,0x01,0x0C,0x00,0x00},/*"z",90*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x3E,0xFC,0x40,0x02,0x40,0x02},/*"{",91*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},/*"|",92*/
{0x00,0x00,0x40,0x02,0x40,0x02,0x3E,0xFC,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"}",93*/
{0x00,0x00,0x60,0x00,0x80,0x00,0x80,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x20,0x00},/*"~",94*/
};
#endif
按键
阅读源码,看到按键KEY连接的是PA5,改一下就可以了。
key.c
/**
****************************************************************************************************
* @file key.c
* @author Xia
* @version V1.0
* @date 2023-08-13
* @brief 按键输入 驱动代码
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 轮趣小车的按键驱动
* 只有一个按键,在PA5
*
* 修改说明
* V1.0 20230813
* 第一次发布
*
*
****************************************************************************************************
*/
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
KEY_GPIO_CLK_ENABLE(); /* KEY时钟使能 */
gpio_init_struct.Pin = KEY_GPIO_PIN; /* KEY引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY_GPIO_PORT, &gpio_init_struct); /* KEY引脚模式设置,上拉输入 */
}
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键): WK_UP > KEY1 > KEY0!!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* KEY_PRES, 1, KEY按下
*/
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && KEY == 0) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
delay_ms(10); /* 去抖动 */
key_up = 0;
if (KEY == 0) keyval = KEY_PRES;
}
else if (KEY == 1) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
key.h
/**
****************************************************************************************************
* @file key.h
* @author Xia
* @version V1.0
* @date 2023-08-13
* @brief 按键输入 驱动代码
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 轮趣小车的按键驱动
* 只有一个按键,在PA5
*
* 修改说明
* V1.0 20230813
* 第一次发布
*
*
****************************************************************************************************
*/
#ifndef __KEY_H
#define __KEY_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define KEY_GPIO_PORT GPIOA
#define KEY_GPIO_PIN GPIO_PIN_5
#define KEY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
/******************************************************************************************/
#define KEY HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN) /* 读取KEY引脚 */
#define KEY_PRES 1 /* KEY0按下 */
void key_init(void); /* 按键初始化函数 */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
#endif
LED
LED灯接在PA4,把这个初始化了然后通过翻转当前IO状态就可以控制灯的亮灭了。
led.c
/**
****************************************************************************************************
* @file led.c
* @author Xia
* @version V1.0
* @date 2023-08-13
* @brief LED 驱动代码
****************************************************************************************************
* @attention
*
* 轮趣小车的LED驱动,LED接在PA4
*
*
* 修改说明
* V1.0 20230813
* 第一次发布
*
*
****************************************************************************************************
*/
#include "./BSP/LED/led.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LED_GPIO_CLK_ENABLE(); /* LED时钟使能 */
gpio_init_struct.Pin = LED_GPIO_PIN; /* LED引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LED_GPIO_PORT, &gpio_init_struct); /* 初始化LED引脚 */
LED(0); /* 关闭 LED */
}
/**
* @brief LED闪烁
* @param 无
* @retval 无
*/
void led_flash(uint16_t time)
{
static int temp;
if(!time) LED(0);
else if(++temp == time)
{
LED_TOGGLE();
temp = 0;
}
}
led.h
/**
****************************************************************************************************
* @file led.h
* @author Xia
* @version V1.0
* @date 2023-08-13
* @brief LED 驱动代码
****************************************************************************************************
* @attention
*
* 轮趣小车的LED驱动,LED接在PA4
*
*
* 修改说明
* V1.0 20230813
* 第一次发布
*
*
****************************************************************************************************
*/
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define LED_GPIO_PORT GPIOA
#define LED_GPIO_PIN GPIO_PIN_4
#define LED_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
/******************************************************************************************/
/* LED端口定义 */
#define LED(x) do{ x ? \
HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED翻转 */
/* LED取反定义 */
#define LED_TOGGLE() do{ HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN); }while(0) /* 翻转LED */
/******************************************************************************************/
/* 外部接口函数*/
void led_init(void); /* 初始化 */
void led_flash(uint16_t time); /* LED闪烁 */
#endif
高级定时器初始化
c8t6只有4个定时器,查阅f1的参考手册,应该是TIM1为高级定时器,而TIM2~4都是通用定时器。
根据查询小车的开发手册可以看到,控制电机的PWM波有PA8和PA11产生,通过查询手册,PA8对应TIM1CH1,PA11对应TIM1CH4。
采用的直流有刷驱动器是TB6612,可以同时控制两个电机:
接入一个电机为例,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;
else
b = 7; // error
return b;
}
/**
* @brief 将方向矩阵转换为标量表示,以供DMP使用
* @param mtx: 方向矩阵
* @retval 标量表示的方向参数
*/
static inline unsigned short inv_orientation_matrix_to_scalar(const signed char *mtx)
{
unsigned short scalar;
scalar = inv_row_2_scale(mtx);
scalar |= inv_row_2_scale(mtx + 3) << 3;
scalar |= inv_row_2_scale(mtx + 6) << 6;
return scalar;
}
/**
* @brief MPU6050传感器自测试函数
* @param 无
* @retval 0: 函数执行成功
* 1: 函数执行失败
* 轮趣小车直接 static void函数,正点原子是uint8_t
*/
static void mpu6050_run_self_test(void)
{
int result;
long gyro[3], accel[3];;
result = mpu_run_self_test(gyro, accel);
if (result == 0x7)
{
/* Test passed. We can trust the gyro data here, so let's push it down
* to the DMP.
*/
float sens;
unsigned short accel_sens;
mpu_get_gyro_sens(&sens);
gyro[0] = (long)(gyro[0] * sens);
gyro[1] = (long)(gyro[1] * sens);
gyro[2] = (long)(gyro[2] * sens);
dmp_set_gyro_bias(gyro);
mpu_get_accel_sens(&accel_sens);
accel[0] *= accel_sens;
accel[1] *= accel_sens;
accel[2] *= accel_sens;
dmp_set_accel_bias(accel);
// return 0;
}
// else
// {
// return 1;
// }
}
/**
* @brief MPU6050初始化
* @param 无
* @retval MPU6050_EOK: 函数执行成功
* MPU6050_EID: 获取ID错误,函数执行失败
*/
uint8_t mpu6050_init(void)
{
uint8_t id;
mpu6050_hw_init(); /* MPU6050硬件初始化 */
mpu6050_iic_init(); /* 初始化IIC接口 */
mpu6050_sw_reset(); /* MPU050软件复位 */
mpu6050_set_gyro_fsr(3); /* 陀螺仪传感器,±2000dps */
mpu6050_set_accel_fsr(0); /* 加速度传感器,±2g */
//mpu6050_set_rate(50); /* 采样率,50Hz */
/* 轮趣小车设置失能睡眠模式,应该在设置时钟源的时候也设置了 */
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT1_REG, 0X00); /* 进入工作状态 */
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_INT_EN_REG, 0X00); /* 关闭所有中断 */
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_USER_CTRL_REG, 0X00); /* 关闭IIC主模式 */
/* 轮趣小车失能旁路IIC,应该是在后面INT低电平有效设置了 */
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_INTBP_CFG_REG, 0X00); /* 关闭IIC旁路直通 */
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_FIFO_EN_REG, 0X00); /* 关闭FIFO */
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_INTBP_CFG_REG, 0X80); /* INT引脚低电平有效 */
mpu6050_read_byte(MPU6050_IIC_ADDR, MPU_DEVICE_ID_REG, &id); /* 读取设备ID */
if (id != MPU6050_IIC_ADDR)
{
return MPU6050_EID;
}
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT1_REG, 0x02); /* 设置CLKSEL,PLL Y轴为参考,正点原子是X轴 */
mpu6050_write_byte(MPU6050_IIC_ADDR, MPU_PWR_MGMT2_REG, 0x00); /* 加速度与陀螺仪都工作 */
//mpu6050_set_rate(50); /* 采样率,50Hz */
return MPU6050_EOK;
}
/**
* @brief MPU6050 DMP初始化
* @param 无
* @retval 0: 函数执行成功
* 1: 函数执行失败
* 轮趣小车直接 void,正点原子是uint8_t
*/
void mpu6050_dmp_init(void)
{
uint8_t ret;
/* 轮趣小车的读取缓冲 */
uint8_t temp[1] = {0};
mpu6050_read(MPU_SIGPATH_RST_REG, MPU_DEVICE_ID_REG, 1, temp);;
// ret = mpu_init(NULL);
ret = mpu_init(); /* 硬件初始化,正点原子因为没有把含中断的入口参数修改掉,所以传参NULL */
ret += mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL); /* 开启指定传感器 */
ret += mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL); /* 设置FIFO */
ret += mpu_set_sample_rate(DEFAULT_MPU_HZ); /* 设置采样率 */
ret += dmp_load_motion_driver_firmware(); /* 加载DMP镜像 */
ret += dmp_set_orientation( /* 设置陀螺仪方向 */
inv_orientation_matrix_to_scalar(gyro_orientation));
ret += dmp_enable_feature( DMP_FEATURE_6X_LP_QUAT | /* 设置DMP功能 */
DMP_FEATURE_TAP |
DMP_FEATURE_ANDROID_ORIENT |
DMP_FEATURE_SEND_RAW_ACCEL |
DMP_FEATURE_SEND_CAL_GYRO |
DMP_FEATURE_GYRO_CAL);
ret += dmp_set_fifo_rate(DEFAULT_MPU_HZ); /* 设置DMP输出速率 */
ret += mpu_set_dmp_state(1); /* 使能DMP */
// ret += mpu6050_run_self_test(); /* 传感器自测试 */
mpu6050_run_self_test(); /* 传感器自测试 */
// return ((ret == 0) ? 0 : 1);
}
/**
* @brief 获取MPU6050 DMP处理后的数据
* @note 获取数据的频率需与宏DEFAULT_MPU_HZ定义的频率一致,
* 获取太快,可能因MPU6050还未进行数据采样,导致FIFO中无数据,从而获取失败,
* 获取太慢,可能无法及时读出MPU6050 FIFO中的数据,导致FIFO溢出,从而获取失败
* @param pitch: 俯仰角(精度: 0.1° 范围: -90.0° <---> +90.0°)
* roll : 横滚角(精度: 0.1° 范围: -180.0° <---> +180.0°)
* yaw : 航向角(精度: 0.1° 范围: -180.0° <---> +180.0°)
* @retval 0: 函数执行成功
* 1: 函数执行失败
* 轮趣小车直接是 void,正点原子是uint8_t,且有参数三个欧拉角
*/
//uint8_t mpu6050_dmp_get_data(float *pitch, float *roll, float *yaw)
void mpu6050_dmp_get_data(void)
{
// float q0 = 0.0f;
// float q1 = 0.0f;
// float q2 = 0.0f;
// float q3 = 0.0f;
// short gyro[3], accel[3], sensors;
unsigned long sensor_timestamp;
unsigned char more;
long quat[4];
/* 读取MPU6050 FIFO中数据的频率需与宏DEFAULT_MPU_HZ定义的频率一直
* 读取得太快或太慢都可能导致读取失败
* 读取太快:MPU6050还未采样,FIFO中无数据,读取失败
* 读取太慢:MPU6050的FIFO溢出,读取失败
*/
// if (dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more) != 0)
// {
// return 1;
// }
dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more);
if (sensors & INV_WXYZ_QUAT)
{
/* MPU6050的DMP输出的是姿态解算后的四元数,
* 采用q30格式,即结果被放大了2的30次方倍,
* 因为四元数并不是角度信号,因此为了得到欧拉角,
* 就需要对MPU6050的DMP输出结果进行转换
*/
q0 = quat[0] / q30;
q1 = quat[1] / q30;
q2 = quat[2] / q30;
q3 = quat[3] / q30;
/* 计算俯仰角、横滚角、航向角
* 57.3为弧度转角度的转换系数,即180/PI
*/
// *pitch = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3;
// *roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1) * 57.3;
// *yaw = atan2(2 * (q1 * q2 + q0 * q3), q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.3;
pitch = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3;
roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1) * 57.3;
yaw = atan2(2 * (q1 * q2 + q0 * q3), q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.3;
}
// else
// {
// return 1;
// }
//
// return 0;
}
/**
* @brief MPU6050获取温度值
* @param temperature: 获取到的温度值(扩大了10倍)
* @retval MPU6050_EOK : 函数执行成功
* MPU6050_EACK: IIC通讯ACK错误,函数执行失败
* 轮趣小车则是直接返回温度值
*/
//uint8_t mpu6050_get_temperature(int16_t *temp)
int mpu6050_get_temperature(void)
{
// uint8_t dat[2];
// uint8_t ret;
// int16_t raw = 0;
//
// ret = mpu6050_read(MPU6050_IIC_ADDR, MPU_TEMP_OUTH_REG, 2, dat);
// if (ret == MPU6050_EOK)
// {
// raw = ((uint16_t)dat[0] << 8) | dat[1];
// *temp = (int16_t)((36.53f + ((float)raw / 340)) * 10);
// }
//
// return ret;
float temp;
uint8_t dat[2];
int16_t raw = 0;
mpu6050_read(MPU6050_IIC_ADDR, MPU_TEMP_OUTH_REG, 2, dat);
raw = ((uint16_t)dat[0] << 8) | dat[1];
temp = (int16_t)((36.53f + ((float)raw / 340)) * 10);
return (int)temp;
}
/**
* @brief MPU6050获取陀螺仪值
* @param gx,gy,gz: 陀螺仪x、y、z轴的原始度数(带符号)
* @retval MPU6050_EOK : 函数执行成功
* MPU6050_EACK: IIC通讯ACK错误,函数执行失败
*/
uint8_t mpu6050_get_gyroscope(int16_t *gx, int16_t *gy, int16_t *gz)
{
uint8_t dat[6];
uint8_t ret;
ret = mpu6050_read(MPU6050_IIC_ADDR, MPU_GYRO_XOUTH_REG, 6, dat);
if (ret == MPU6050_EOK)
{
*gx = ((uint16_t)dat[0] << 8) | dat[1];
*gy = ((uint16_t)dat[2] << 8) | dat[3];
*gz = ((uint16_t)dat[4] << 8) | dat[5];
}
return ret;
}
/**
* @brief MPU6050获取加速度值
* @param ax,ay,az: 加速度x、y、z轴的原始度数(带符号)
* @retval MPU6050_EOK : 函数执行成功
* MPU6050_EACK: IIC通讯ACK错误,函数执行失败
*/
uint8_t mpu6050_get_accelerometer(int16_t *ax, int16_t *ay, int16_t *az)
{
uint8_t dat[6];
uint8_t ret;
ret = mpu6050_read(MPU6050_IIC_ADDR, MPU_ACCEL_XOUTH_REG, 6, dat);
if (ret == MPU6050_EOK)
{
*ax = ((uint16_t)dat[0] << 8) | dat[1];
*ay = ((uint16_t)dat[2] << 8) | dat[3];
*az = ((uint16_t)dat[4] << 8) | dat[5];
}
return ret;
}
mpu6050.h
/**
****************************************************************************************************
* @file mpu6050.h
* @author Xia
* @version V1.0
* @date 2023-08-13
* @brief MPU6050模块驱动代码
****************************************************************************************************
* @attention
*
* 轮趣小车完成MPU6050的初始化与读数据
*
*
* 修改说明
* V1.0 20230813
* 第一次发布
*
*
****************************************************************************************************
*/
#ifndef __MPU6050_H
#define __MPU6050_H
#include "./SYSTEM/sys/sys.h"
/* 引脚定义 */
#define MPU6050_AD0_GPIO_PORT GPIOA
#define MPU6050_AD0_GPIO_PIN GPIO_PIN_12
#define MPU6050_AD0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE();}while(0)
/* IO操作 */
#define MPU6050_AD0(x) do{ x ? \
HAL_GPIO_WritePin(MPU6050_AD0_GPIO_PORT, MPU6050_AD0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(MPU6050_AD0_GPIO_PORT, MPU6050_AD0_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
/* MPU6050的IIC通讯从机地址
* 如果MPU6050的AD0引脚被拉低,则其IIC通讯的地址为0x68
* 如果MPU6050的AD0引脚被拉高,则其IIC通讯的地址为0x69
*/
#define MPU6050_IIC_ADDR 0x68
/* MPU6050寄存器地址定义 */
#define MPU_ACCEL_OFFS_REG 0X06 // accel_offs寄存器,可读取版本号,寄存器手册未提到
#define MPU_PROD_ID_REG 0X0C // prod id寄存器,在寄存器手册未提到
#define MPU_SELF_TESTX_REG 0X0D // 自检寄存器X
#define MPU_SELF_TESTY_REG 0X0E // 自检寄存器Y
#define MPU_SELF_TESTZ_REG 0X0F // 自检寄存器Z
#define MPU_SELF_TESTA_REG 0X10 // 自检寄存器A
#define MPU_SAMPLE_RATE_REG 0X19 // 采样频率分频器
#define MPU_CFG_REG 0X1A // 配置寄存器
#define MPU_GYRO_CFG_REG 0X1B // 陀螺仪配置寄存器
#define MPU_ACCEL_CFG_REG 0X1C // 加速度计配置寄存器
#define MPU_MOTION_DET_REG 0X1F // 运动检测阀值设置寄存器
#define MPU_FIFO_EN_REG 0X23 // FIFO使能寄存器
#define MPU_I2CMST_CTRL_REG 0X24 // IIC主机控制寄存器
#define MPU_I2CSLV0_ADDR_REG 0X25 // IIC从机0器件地址寄存器
#define MPU_I2CSLV0_REG 0X26 // IIC从机0数据地址寄存器
#define MPU_I2CSLV0_CTRL_REG 0X27 // IIC从机0控制寄存器
#define MPU_I2CSLV1_ADDR_REG 0X28 // IIC从机1器件地址寄存器
#define MPU_I2CSLV1_REG 0X29 // IIC从机1数据地址寄存器
#define MPU_I2CSLV1_CTRL_REG 0X2A // IIC从机1控制寄存器
#define MPU_I2CSLV2_ADDR_REG 0X2B // IIC从机2器件地址寄存器
#define MPU_I2CSLV2_REG 0X2C // IIC从机2数据地址寄存器
#define MPU_I2CSLV2_CTRL_REG 0X2D // IIC从机2控制寄存器
#define MPU_I2CSLV3_ADDR_REG 0X2E // IIC从机3器件地址寄存器
#define MPU_I2CSLV3_REG 0X2F // IIC从机3数据地址寄存器
#define MPU_I2CSLV3_CTRL_REG 0X30 // IIC从机3控制寄存器
#define MPU_I2CSLV4_ADDR_REG 0X31 // IIC从机4器件地址寄存器
#define MPU_I2CSLV4_REG 0X32 // IIC从机4数据地址寄存器
#define MPU_I2CSLV4_DO_REG 0X33 // IIC从机4写数据寄存器
#define MPU_I2CSLV4_CTRL_REG 0X34 // IIC从机4控制寄存器
#define MPU_I2CSLV4_DI_REG 0X35 // IIC从机4读数据寄存器
#define MPU_I2CMST_STA_REG 0X36 // IIC主机状态寄存器
#define MPU_INTBP_CFG_REG 0X37 // 中断/旁路设置寄存器
#define MPU_INT_EN_REG 0X38 // 中断使能寄存器
#define MPU_INT_STA_REG 0X3A // 中断状态寄存器
#define MPU_ACCEL_XOUTH_REG 0X3B // 加速度值,X轴高8位寄存器
#define MPU_ACCEL_XOUTL_REG 0X3C // 加速度值,X轴低8位寄存器
#define MPU_ACCEL_YOUTH_REG 0X3D // 加速度值,Y轴高8位寄存器
#define MPU_ACCEL_YOUTL_REG 0X3E // 加速度值,Y轴低8位寄存器
#define MPU_ACCEL_ZOUTH_REG 0X3F // 加速度值,Z轴高8位寄存器
#define MPU_ACCEL_ZOUTL_REG 0X40 // 加速度值,Z轴低8位寄存器
#define MPU_TEMP_OUTH_REG 0X41 // 温度值高八位寄存器
#define MPU_TEMP_OUTL_REG 0X42 // 温度值低8位寄存器
#define MPU_GYRO_XOUTH_REG 0X43 // 陀螺仪值,X轴高8位寄存器
#define MPU_GYRO_XOUTL_REG 0X44 // 陀螺仪值,X轴低8位寄存器
#define MPU_GYRO_YOUTH_REG 0X45 // 陀螺仪值,Y轴高8位寄存器
#define MPU_GYRO_YOUTL_REG 0X46 // 陀螺仪值,Y轴低8位寄存器
#define MPU_GYRO_ZOUTH_REG 0X47 // 陀螺仪值,Z轴高8位寄存器
#define MPU_GYRO_ZOUTL_REG 0X48 // 陀螺仪值,Z轴低8位寄存器
#define MPU_I2CSLV0_DO_REG 0X63 // IIC从机0数据寄存器
#define MPU_I2CSLV1_DO_REG 0X64 // IIC从机1数据寄存器
#define MPU_I2CSLV2_DO_REG 0X65 // IIC从机2数据寄存器
#define MPU_I2CSLV3_DO_REG 0X66 // IIC从机3数据寄存器
#define MPU_I2CMST_DELAY_REG 0X67 // IIC主机延时管理寄存器
#define MPU_SIGPATH_RST_REG 0X68 // 信号通道复位寄存器
#define MPU_MDETECT_CTRL_REG 0X69 // 运动检测控制寄存器
#define MPU_USER_CTRL_REG 0X6A // 用户控制寄存器
#define MPU_PWR_MGMT1_REG 0X6B // 电源管理寄存器1
#define MPU_PWR_MGMT2_REG 0X6C // 电源管理寄存器2
#define MPU_FIFO_CNTH_REG 0X72 // FIFO计数寄存器高八位
#define MPU_FIFO_CNTL_REG 0X73 // FIFO计数寄存器低八位
#define MPU_FIFO_RW_REG 0X74 // FIFO读写寄存器
#define MPU_DEVICE_ID_REG 0X75 // 器件ID寄存器
/* 函数错误代码 */
#define MPU6050_EOK 0 /* 没有错误 */
#define MPU6050_EID 1 /* ID错误 */
#define MPU6050_EACK 2 /* IIC通讯ACK错误 */
/* 参数 */
extern short gyro[3], accel[3];
extern float pitch, roll, yaw;
/* 操作函数 */
uint8_t mpu6050_write(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *dat); /* 往MPU6050的指定寄存器连续写入指定数据 */
uint8_t mpu6050_write_byte(uint8_t addr, uint8_t reg, uint8_t dat); /* 往MPU050的指定寄存器写入一字节数据 */
uint8_t mpu6050_read(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *dat); /* 连续读取MPU6050指定寄存器的值 */
uint8_t mpu6050_read_byte(uint8_t addr, uint8_t reg, uint8_t *dat); /* 读取MPU6050指定寄存器的值 */
void mpu6050_sw_reset(void); /* MPU6050软件复位 */
uint8_t mpu6050_set_gyro_fsr(uint8_t fsr); /* MPU6050设置陀螺仪传感器量程范围 */
uint8_t mpu6050_set_accel_fsr(uint8_t fsr); /* MPU6050设置加速度传感器量程范围 */
uint8_t mpu6050_set_lpf(uint16_t lpf); /* MPU6050设置数字低通滤波器频率 */
uint8_t mpu6050_set_rate(uint16_t rate); /* MPU6050设置采样率 */
uint8_t mpu6050_init(void); /* MPU6050初始化 */
static void mpu6050_run_self_test(void);
void mpu6050_dmp_init(void);
void mpu6050_dmp_get_data(void);
//uint8_t mpu6050_get_temperature(int16_t *temp); /* MPU6050获取温度值 */
int mpu6050_get_temperature(void);
uint8_t mpu6050_get_gyroscope(int16_t *gx, int16_t *gy, int16_t *gz); /* MPU6050获取陀螺仪值 */
uint8_t mpu6050_get_accelerometer(int16_t *ax, int16_t *ay, int16_t *az); /* MPU6050获取加速度值 */
#endif
官方库文件,直接复制就可以,有更改的地方比如头文件什的么自行修改,其余照抄,根据我的经验就可以了
- inv_mpu.c
- inv_mpu.h
- inv_mpu_dmp_motion_driver.c
- inv_mpu_dmp_motion_driver.h
这四个文件,除了头文件其他基本没啥改动啥也不用改,篇幅问题我就不放进来了。
外部中断
整个程序最重要的定时,也就是处理的频率,就依靠外部中断来完成!
通过MPU6050的INT引脚,接到PA12上,可以固定触发一个中断,设置为低电平触发;我们设置的DMP采样是200Hz(由于DMP的限制,最少需要5ms计算时间,不能再短了),然后在中断中设置触发十次才进入控制计算。
exti.c
/**
****************************************************************************************************
* @file exti.c
* @author Xia
* @version V1.0
* @date 2023-08-13
* @brief 外部中断 驱动代码
****************************************************************************************************
* @attention
*
* 轮趣小车,用外部中断完成50ms的精准计时
*
*
* 修改说明
* V1.0 20230813
* 第一次发布
*
*
****************************************************************************************************
*/
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/EXTI/exti.h"
volatile uint8_t delay_flag, delay_50; /* 提供延时的变量 */
/**
* @brief MPU6050_INT 外部中断服务程序
* @param 无
* @retval 无
*/
void MPU6050_INT_IRQHandler(void)
{
if (MPU6050_read == 0)
{
if(delay_flag == 1)
{
delay_50++;
if(delay_50 == 10)
{
delay_50 = 0;
delay_flag = 0;/* 给主函数提供50ms的精准延时,示波器需要50ms高精度延时 */
}
}
}
/* 调用中断处理公用函数 清除MPU6050_INT所在中断线 的中断标志位 */
HAL_GPIO_EXTI_IRQHandler(MPU6050_INT_GPIO_PIN);
/* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
__HAL_GPIO_EXTI_CLEAR_IT(MPU6050_INT_GPIO_PIN);
}
/**
* @brief 外部中断初始化程序
* @param 无
* @retval 无
*/
void extix_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
MPU6050_INT_GPIO_CLK_ENABLE(); /* MPU6050_INT时钟使能 */
gpio_init_struct.Pin = MPU6050_INT_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下降沿触发 */
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(MPU6050_INT_GPIO_PORT, &gpio_init_struct); /* MPU6050_INT配置为下降沿触发中断 */
HAL_NVIC_SetPriority(MPU6050_INT_IRQn, 2, 1); /* 抢占2,子优先级1 */
HAL_NVIC_EnableIRQ(MPU6050_INT_IRQn); /* 使能中断线15_10 */
}
exti.h
/**
****************************************************************************************************
* @file exti.h
* @author Xia
* @version V1.0
* @date 2023-08-13
* @brief 外部中断 驱动代码
****************************************************************************************************
* @attention
*
* 轮趣小车,用外部中断完成50ms的精准计时
*
*
* 修改说明
* V1.0 20230813
* 第一次发布
*
*
****************************************************************************************************
*/
#ifndef __EXTI_H
#define __EXTI_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 和 中断编号 & 中断服务函数 定义 */
#define MPU6050_INT_GPIO_PORT GPIOA
#define MPU6050_INT_GPIO_PIN GPIO_PIN_12
#define MPU6050_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define MPU6050_INT_IRQn EXTI15_10_IRQn
#define MPU6050_INT_IRQHandler EXTI15_10_IRQHandler
#define MPU6050_read HAL_GPIO_ReadPin(MPU6050_INT_GPIO_PORT, MPU6050_INT_GPIO_PIN) /* 读取MPU6050_INT引脚 */
/******************************************************************************************/
void extix_init(void); /* 外部中断初始化 */
#endif
总结
到这里,按照正点原子的HAL库学到的代码管理风格的初始化移植基本就完成了,最后在main函数里面完成调用初始化就OK了(注意要失能JTAG失使能SWD)。
问题
一些控制代码我没放进来,但是按照这个方法全写完之后,烧进去oled的更新就很慢,而且小车控制不住。我猜测的原因,可能是因为c8t6的flash只有64kb,但我写完代码烧进去的大小有56kb,可能有点带不动;或者是这样子代码管理,宏定义太多了影响了速度。
总之现在就是按照这个代码是跑步起来的,**尽管我已经另外写了CUBEMX版本证明代码逻辑都是对的!**所以这一篇就是来学习怎么初始化这些引脚、定时器、中断和传感器的,看一下逻辑和一些写法就可以了!
后续更新计划
我会把控制、oled显示内容以及滤波算法写一下,然后把源代码打包发上来。
可供参考的学习资料
我把我的知乎发在这里,我的专栏里面有电机的内容以及stm32基础内容的学习笔记,可以帮你快速上手基础知识。
知乎搜索昵称:我就像一个哑巴;看更多基础学习笔记
文章:电机就是有刷电机部分,stm32基础就是通用定时器,其他都很基础,应该不需要过多关注,直接用就行。
更多推荐
所有评论(0)