当前位置: 首页 > news >正文

MSPM0G3507学习笔记(三)软硬I2C任意切换,兼容HAL:oled与mpu6050

电赛备赛中,打算系统过一遍MSPM0G3507的各个部分,同时把过程记录下来。本系列以代码全部能用+复用性、可移植性高为目的。本系列所有的代码会开源至github,如果觉得有用,请点个赞/给我的github仓库点一颗star吧。

github地址:https://github.com/whyovo/MSPM0G3507Learning-Notes

取模软件链接: https://pan.baidu.com/s/1CywED7jgdv5acmSqtsR4Qw?pwd=1234 提取码: 1234

上一篇:https://blog.csdn.net/qq_23220445/article/details/148502065?spm=1001.2014.3001.5501

1.oled使用方法:

仅需在main.c调用这一句话即可,其他无需关心!
oled_init();

调用以下函数显示图像:

// 显示图像oled_show_image(15, 16, &new_custom_image);
oled_display(); // 刷新OLED显示

结果如下:

调用以下函数显示字符串、中文,

    oled_show_string(0, 0, "Test:");oled_show_chinese_char(40, 0, "咪"); oled_show_chinese_char(56, 0, "啪");oled_show_string(0, 16, "123456");oled_show_chinese_string(0, 32, "咪啪");
oled_display(); // 刷新OLED显示

结果如下:

切换软硬件i2c:

在oled.h里面用宏定义切换,我软硬件i2c都调通了,没有任何问题!兼容HAL库。

在oled.c里面修改软件i2c的引脚,现在是pa10和pa11。修改好即可用,底层我都写好了,连sysconfig那个图形化界面都不需要修改(头秃调试了好几天…)。硬件i2c仅需指定用0还是1就可以了。

硬件i2c的引脚是固定的,在hard_i2c.h可以看到

2.中文与图片取模方法

2.1中文

用取模软件取模,我的设置是:阴码,逐行,逆向,十六进制,C51格式。

取模完毕后,置于oledfont.c以下位置,之后出现过的汉字在主函数里面便可以直接调用了,比如oled_show_chinese_string(0, 0, “咪啪”);

2.2图片

水平扫描,单色

然后把数组粘贴到这里,并修改下面的长宽即可

再去.h里面修改一下extern

之后在主函数调用即可。

2.3gif

同图片显示,直接修改数据,然后在主函数调用即可。

3.陀螺仪使用方法

在main函数添加个初始化即可使用。引脚切换和oled一模一样。但有个问题,经过我测试只有硬件i2c速度是100khz才能正常工作,哪怕是改成400khz都会出问题。
mpu6050_init();

main.c示例:

#include "general.h"
#include <stdio.h>int main(void)
{SYSCFG_DL_init();oled_init();//  key_init();//  led_init();mpu6050_init();float angles[3];oled_clear();while (1) {mpu6050_update();mpu6050_get_angles(angles);// 显示Roll角度oled_show_string(0, 0, "pitch:");oled_show_float(60, 0, angles[0], 3, 1);// 显示Pitch角度oled_show_string(0, 16, "roll:");oled_show_float(60, 16, angles[1], 3, 1);// 显示Yaw角度oled_show_string(0, 32, "Yaw:");oled_show_float(60, 32, angles[2], 3, 1);// 显示温度float temp = mpu6050_get_temperature();oled_show_string(0, 48, "T:");oled_show_float(20, 48, temp, 2, 1);oled_display(); // 刷新OLED显示}
}

结果:

还是比较准的,飘动也比较小,帧数还算可以。

4.部分源码

开源到了[https://github.com/whyovo/MSPM0G3507Learning-Notes](https://github.com/whyovo/MSPM0G3507Learning-Notes),以下给出oled.c.h和mpu6050.c.h的代码:

oled.c

#include "oled.h"
#include "delay.h"
#include <string.h>
#include <stdio.h>#if I2C_MODE == I2C_MODE_SOFT_I2C
// 软件I2C配置
Soft_I2C_Config oled_i2c_config = {.sda_port = GPIOA,.sda_pin  = DL_GPIO_PIN_10,.scl_port = GPIOA,.scl_pin  = DL_GPIO_PIN_11,.delay_us = 1};
static Soft_I2C_Config *oled_i2c = &oled_i2c_config;
#elif I2C_MODE == I2C_MODE_HARD_I2C
// 硬件I2C配置
Hard_I2C_Config oled_hard_i2c = {.instance    = HARD_I2C_1,.speed       = HARD_I2C_SPEED_1M,.timeout_ms  = 100,.initialized = false};
static Hard_I2C_Config *oled_i2c = &oled_hard_i2c;
#elif I2C_MODE == I2C_MODE_HAL_I2C
// HAL库I2C配置
extern I2C_HandleTypeDef hi2c1; // 从main.c中获取HAL库I2C句柄
static I2C_HandleTypeDef *oled_i2c = &hi2c1;
#endifstatic uint8_t oled_gram[OLED_PAGES][OLED_WIDTH];// GIF播放控制变量
static volatile bool gif_playing   = false;
static volatile bool gif_stop_flag = false;// 函数前向声明
static void oled_show_char_no_refresh(uint8_t x, uint8_t y, char chr);
static void oled_show_chinese_char_no_refresh(uint8_t x, uint8_t y, const char *utf8_char);
static uint32_t utf8_decode(const char **str);
static uint16_t utf8_to_unicode(uint32_t utf8_code);// OLED初始化命令序列
static const uint8_t oled_init_cmd[] = {0xAE,       // 关闭显示0x20, 0x02, // 设置内存地址模式 - 页地址模式0xB0,       // 设置页起始地址0xC8,       // 设置COM扫描方向0x00,       // 设置列地址低4位0x10,       // 设置列地址高4位0x40,       // 设置显示开始行0x81, 0xFF, // 设置对比度0xA1,       // 设置段重映射0xA6,       // 正常显示0xA8, 0x3F, // 设置复用比0xA4,       // 输出遵循RAM内容0xD3, 0x00, // 设置显示偏移0xD5, 0x80, // 设置显示时钟分频比/振荡频率0xD9, 0xF1, // 设置预充电周期0xDA, 0x12, // 设置COM硬件配置0xDB, 0x40, // 设置VCOMH电压0x8D, 0x14, // 启用电荷泵0xAF        // 开启显示
};// 发送命令
static bool oled_write_cmd(uint8_t cmd)
{
#if I2C_MODE == I2C_MODE_SOFT_I2Creturn soft_i2c_write_data(oled_i2c, OLED_ADDRESS, OLED_CMD, &cmd, 1);
#elif I2C_MODE == I2C_MODE_HARD_I2Cuint8_t buffer[2] = {OLED_CMD, cmd};return (hard_i2c_write_data(oled_i2c, OLED_ADDRESS, buffer[0], &buffer[1], 1) == HARD_I2C_OK);
#elif I2C_MODE == I2C_MODE_HAL_I2Cuint8_t buffer[2] = {OLED_CMD, cmd};return (HAL_I2C_Master_Transmit(oled_i2c, OLED_ADDRESS << 1, buffer, 2, 100) == HAL_OK);
#endif
}// 发送数据
static bool oled_write_byte(uint8_t data)
{
#if I2C_MODE == I2C_MODE_SOFT_I2Creturn soft_i2c_write_data(oled_i2c, OLED_ADDRESS, OLED_DATA, &data, 1);
#elif I2C_MODE == I2C_MODE_HARD_I2Cuint8_t buffer[2] = {OLED_DATA, data};return (hard_i2c_write_data(oled_i2c, OLED_ADDRESS, buffer[0], &buffer[1], 1) == HARD_I2C_OK);
#elif I2C_MODE == I2C_MODE_HAL_I2Cuint8_t buffer[2] = {OLED_DATA, data};return (HAL_I2C_Master_Transmit(oled_i2c, OLED_ADDRESS << 1, buffer, 2, 100) == HAL_OK);
#endif
}// OLED初始化
bool oled_init(void)
{
#if I2C_MODE == I2C_MODE_SOFT_I2Csoft_i2c_init(oled_i2c);
#elif I2C_MODE == I2C_MODE_HARD_I2Chard_i2c_init(oled_i2c);
#elif I2C_MODE == I2C_MODE_HAL_I2C// 使用HAL库时通常在主程序中初始化I2C,这里可以不做额外操作// 如果需要特定OLED配置,可以在此添加
#endifdelay_ms(100); // 等待OLED上电稳定// 发送初始化命令序列for (uint8_t i = 0; i < sizeof(oled_init_cmd); i++) {if (!oled_write_cmd(oled_init_cmd[i])) {return false;}delay_ms(1);}oled_clear();return true;
}// 设置位置
void oled_set_pos(uint8_t x, uint8_t y)
{oled_write_cmd(0xB0 + y);                 // 设置页地址oled_write_cmd(((x & 0xF0) >> 4) | 0x10); // 设置列地址高4位oled_write_cmd((x & 0x0F) | 0x00);        // 设置列地址低4位
}// 清屏
void oled_clear(void)
{memset(oled_gram, 0, sizeof(oled_gram));
}// 显示缓存内容到OLED
void oled_display(void)
{for (uint8_t page = 0; page < OLED_PAGES; page++) {oled_set_pos(0, page);#if I2C_MODE == I2C_MODE_SOFT_I2C// 软件I2C模式soft_i2c_write_data(oled_i2c, OLED_ADDRESS, OLED_DATA, oled_gram[page], OLED_WIDTH);
#elif I2C_MODE == I2C_MODE_HARD_I2C// 硬件I2C模式hard_i2c_write_data(oled_i2c, OLED_ADDRESS, OLED_DATA, oled_gram[page], OLED_WIDTH);
#elif I2C_MODE == I2C_MODE_HAL_I2C// HAL库I2C模式uint8_t buffer[OLED_WIDTH + 1];buffer[0] = OLED_DATA;memcpy(&buffer[1], oled_gram[page], OLED_WIDTH);HAL_I2C_Master_Transmit(oled_i2c, OLED_ADDRESS << 1, buffer, OLED_WIDTH + 1, 100);
#endif}
}// 内部使用的不自动刷新版本
static void oled_show_char_no_refresh(uint8_t x, uint8_t y, char chr)
{if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return;uint8_t page       = y / 8;     // 转换为页地址uint8_t char_index = chr - ' '; // 字符索引if (char_index >= 95) return;   // 超出ASCII可打印字符范围// 复制字符数据到显存for (uint8_t i = 0; i < 8 && (x + i) < OLED_WIDTH; i++) {if (page < OLED_PAGES) {oled_gram[page][x + i] = oled_font_8x16[char_index][i];}if ((page + 1) < OLED_PAGES) {oled_gram[page + 1][x + i] = oled_font_8x16[char_index][i + 8];}}
}// 显示字符
void oled_show_char(uint8_t x, uint8_t y, char chr)
{if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return;uint8_t page       = y / 8;     // 转换为页地址uint8_t char_index = chr - ' '; // 字符索引if (char_index >= 95) return;   // 超出ASCII可打印字符范围// 复制字符数据到显存for (uint8_t i = 0; i < 8 && (x + i) < OLED_WIDTH; i++) {if (page < OLED_PAGES) {oled_gram[page][x + i] = oled_font_8x16[char_index][i];}if ((page + 1) < OLED_PAGES) {oled_gram[page + 1][x + i] = oled_font_8x16[char_index][i + 8];}}
}// 显示字符串
void oled_show_string(uint8_t x, uint8_t y, const char *str)
{if (!str) return;uint8_t pos_x = x;while (*str && pos_x < OLED_WIDTH) {oled_show_char_no_refresh(pos_x, y, *str);pos_x += 8;str++;}
}
// 显示数字
void oled_show_num(uint8_t x, uint8_t y, uint32_t num, uint8_t len)
{char str[12];snprintf(str, sizeof(str), "%*u", len, num);oled_show_string(x, y, str);
}// 显示浮点数
void oled_show_float(uint8_t x, uint8_t y, float num, uint8_t int_len, uint8_t dec_len)
{// 边界检查if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return;if (int_len > 6 || dec_len > 4) return; // 限制格式长度防止溢出uint8_t pos_x = x;// 处理特殊值if (num != num) { // NaN检查oled_show_string(x, y, "NaN");return;}if (num > 999999.0f || num < -999999.0f) { // 无穷大或过大值oled_show_string(x, y, "Inf");return;}// 处理负数if (num < 0) {if (pos_x < OLED_WIDTH) {oled_show_char_no_refresh(pos_x, y, '-');pos_x += 8;}num = -num;}// 分离整数和小数部分uint32_t integer_part = (uint32_t)num;float decimal_part    = num - integer_part;// 显示整数部分if (integer_part == 0) {if (pos_x < OLED_WIDTH) {oled_show_char_no_refresh(pos_x, y, '0');pos_x += 8;}} else {// 计算整数位数uint32_t temp  = integer_part;uint8_t digits = 0;while (temp > 0) {temp /= 10;digits++;}// 显示整数部分的每一位for (int8_t i = digits - 1; i >= 0; i--) {uint32_t divisor = 1;for (uint8_t j = 0; j < i; j++) {divisor *= 10;}uint8_t digit = (integer_part / divisor) % 10;if (pos_x < OLED_WIDTH) {oled_show_char_no_refresh(pos_x, y, '0' + digit);pos_x += 8;}}}// 显示小数点和小数部分if (dec_len > 0 && pos_x < OLED_WIDTH) {oled_show_char_no_refresh(pos_x, y, '.');pos_x += 8;// 显示小数部分for (uint8_t i = 0; i < dec_len && pos_x < OLED_WIDTH; i++) {decimal_part *= 10;uint8_t digit = (uint8_t)decimal_part % 10;oled_show_char_no_refresh(pos_x, y, '0' + digit);pos_x += 8;}}
}// UTF-8解码
static uint32_t utf8_decode(const char **str)
{const uint8_t *s = (const uint8_t *)*str;uint32_t code    = 0;if ((*s & 0x80) == 0) {// ASCII字符code = *s;*str += 1;} else if ((*s & 0xE0) == 0xC0) {// 2字节UTF-8code = ((*s & 0x1F) << 6) | (*(s + 1) & 0x3F);*str += 2;} else if ((*s & 0xF0) == 0xE0) {// 3字节UTF-8code = ((*s & 0x0F) << 12) | ((*(s + 1) & 0x3F) << 6) | (*(s + 2) & 0x3F);*str += 3;} else if ((*s & 0xF8) == 0xF0) {// 4字节UTF-8code = ((*s & 0x07) << 18) | ((*(s + 1) & 0x3F) << 12) |((*(s + 2) & 0x3F) << 6) | (*(s + 3) & 0x3F);*str += 4;}return code;
}// UTF-8编码转Unicode编码
static uint16_t utf8_to_unicode(uint32_t utf8_code)
{if (utf8_code < 0x80) {// ASCII字符return (uint16_t)utf8_code;} else if (utf8_code < 0x800) {// 2字节UTF-8return (uint16_t)utf8_code;} else if (utf8_code < 0x10000) {// 3字节UTF-8 - 直接返回,因为大多数中文字符在此范围内return (uint16_t)utf8_code;}// 4字节UTF-8暂不支持return 0;
}// 显示中文字符
void oled_show_chinese_char(uint8_t x, uint8_t y, const char *utf8_char)
{oled_show_chinese_char_no_refresh(x, y, utf8_char);
}// 内部使用的不自动刷新的中文字符显示版本
static void oled_show_chinese_char_no_refresh(uint8_t x, uint8_t y, const char *utf8_char)
{if (!utf8_char || x >= OLED_WIDTH || y >= OLED_HEIGHT) return;// 解码UTF-8字符串获取编码值const char *str    = utf8_char;uint32_t utf8_code = utf8_decode(&str);uint16_t unicode   = utf8_to_unicode(utf8_code);// 查找字符点阵数据const uint8_t *bitmap = get_chinese_char_bitmap(unicode);if (!bitmap) return; // 字符未找到// 显示16x16中文字符for (uint8_t row = 0; row < 16; row++) {uint8_t byte1 = bitmap[row * 2];     // 左半部分(前8列)uint8_t byte2 = bitmap[row * 2 + 1]; // 右半部分(后8列)uint8_t target_page = (y + row) / 8; // 目标页uint8_t target_bit  = (y + row) % 8; // 页内位置if (target_page >= OLED_PAGES) break;// 处理左半部分(前8列)for (uint8_t col = 0; col < 8; col++) {if ((x + col) >= OLED_WIDTH) break;if (byte1 & (1 << col)) {oled_gram[target_page][x + col] |= (1 << target_bit);} else {oled_gram[target_page][x + col] &= ~(1 << target_bit);}}// 处理右半部分(后8列)for (uint8_t col = 0; col < 8; col++) {if ((x + col + 8) >= OLED_WIDTH) break;if (byte2 & (1 << col)) {oled_gram[target_page][x + col + 8] |= (1 << target_bit);} else {oled_gram[target_page][x + col + 8] &= ~(1 << target_bit);}}}
}// 显示中文字符串
void oled_show_chinese_string(uint8_t x, uint8_t y, const char *utf8_str)
{if (!utf8_str) return;uint8_t pos_x   = x;const char *str = utf8_str;while (*str && pos_x < OLED_WIDTH) {// 构造单个中文字符char single_char[5] = {0}; // 最大4字节UTF-8 + 结束符int char_len        = 0;// 确定UTF-8字符长度if ((*str & 0x80) == 0) {char_len = 1; // ASCII} else if ((*str & 0xE0) == 0xC0) {char_len = 2; // 2字节UTF-8} else if ((*str & 0xF0) == 0xE0) {char_len = 3; // 3字节UTF-8} else if ((*str & 0xF8) == 0xF0) {char_len = 4; // 4字节UTF-8}// 复制单个字符for (int i = 0; i < char_len && i < 4; i++) {single_char[i] = str[i];}// 调用不自动刷新的单字符显示函数oled_show_chinese_char_no_refresh(pos_x, y, single_char);// 移动到下一个字符位置pos_x += 16;str += char_len;}
}// 显示图片
void oled_show_image(uint8_t x, uint8_t y, const OLED_Image *image)
{if (!image || !image->data) return;if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return;// 水平扫描,高位到低位uint8_t bytes_per_row = (image->width + 7) / 8;for (uint8_t row = 0; row < image->height; row++) {if ((y + row) >= OLED_HEIGHT) break;uint8_t target_page = (y + row) / 8;uint8_t target_bit  = (y + row) % 8;if (target_page >= OLED_PAGES) break;for (uint8_t byte_idx = 0; byte_idx < bytes_per_row; byte_idx++) {uint8_t data_byte = image->data[row * bytes_per_row + byte_idx];for (uint8_t bit = 0; bit < 8; bit++) {uint8_t pixel_x = x + byte_idx * 8 + bit;if (pixel_x >= OLED_WIDTH) break;if (data_byte & (1 << (7 - bit))) { // 高位到低位oled_gram[target_page][pixel_x] |= (1 << target_bit);} else {oled_gram[target_page][pixel_x] &= ~(1 << target_bit);}}}}
}// 显示图片的部分区域
void oled_show_image_part(uint8_t x, uint8_t y, const OLED_Image *image,uint8_t src_x, uint8_t src_y, uint8_t width, uint8_t height)
{if (!image || !image->data) return;if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return;if (src_x >= image->width || src_y >= image->height) return;// 限制复制区域if (src_x + width > image->width) width = image->width - src_x;if (src_y + height > image->height) height = image->height - src_y;if (x + width > OLED_WIDTH) width = OLED_WIDTH - x;if (y + height > OLED_HEIGHT) height = OLED_HEIGHT - y;uint8_t bytes_per_row = (image->width + 7) / 8; // 原图每行字节数for (uint8_t row = 0; row < height; row++) {uint8_t src_row = src_y + row;uint8_t dst_row = y + row;if (dst_row >= OLED_HEIGHT) break;uint8_t target_page = dst_row / 8;uint8_t target_bit  = dst_row % 8;if (target_page >= OLED_PAGES) break;// 处理这一行的像素for (uint8_t col = 0; col < width; col++) {uint8_t src_col = src_x + col;uint8_t dst_col = x + col;if (dst_col >= OLED_WIDTH) break;// 计算源像素在数据中的位置uint8_t byte_idx  = src_col / 8;uint8_t bit_idx   = src_col % 8;uint8_t data_byte = image->data[src_row * bytes_per_row + byte_idx];// 检查该像素是否需要点亮if (data_byte & (1 << bit_idx)) {oled_gram[target_page][dst_col] |= (1 << target_bit);} else {oled_gram[target_page][dst_col] &= ~(1 << target_bit);}}}
}// 显示GIF的指定帧
void oled_show_gif_frame(uint8_t x, uint8_t y, const OLED_GIF *gif, uint8_t frame_index)
{if (!gif || !gif->frames || frame_index >= gif->frame_count) return;OLED_Image frame_image = {.width  = gif->width,.height = gif->height,.data   = gif->frames[frame_index]};oled_show_image(x, y, &frame_image);
}// 播放GIF动画
bool oled_play_gif(uint8_t x, uint8_t y, const OLED_GIF *gif, uint16_t loop_count)
{if (!gif || !gif->frames || gif->frame_count == 0) return false;gif_playing   = true;gif_stop_flag = false;for (uint16_t loop = 0; loop < loop_count && !gif_stop_flag; loop++) {for (uint8_t frame = 0; frame < gif->frame_count && !gif_stop_flag; frame++) {oled_show_gif_frame(x, y, gif, frame);delay_ms(gif->delay_ms);}}gif_playing = false;return !gif_stop_flag;
}// 停止GIF播放
void oled_stop_gif(void)
{gif_stop_flag = true;while (gif_playing); // 等待播放结束
}

oled.h

#ifndef __OLED_H
#define __OLED_H// OLED I2C配置宏定义
#define I2C_MODE_SOFT_I2C 1 // 软件I2C
#define I2C_MODE_HARD_I2C 2 // 硬件I2C (TI驱动库)
#define I2C_MODE_HAL_I2C  3 // STM32 HAL库// 选择使用的I2C模式
#define I2C_MODE I2C_MODE_HARD_I2C// 根据配置包含相应头文件
#if I2C_MODE == I2C_MODE_SOFT_I2C
#include "soft_i2c.h"
#elif I2C_MODE == I2C_MODE_HARD_I2C
#include "hard_i2c.h"
#elif I2C_MODE == I2C_MODE_HAL_I2C
#include "stm32f1xx_hal.h" // 根据实际MCU型号调整
#endif#include "sys_init.h"
#include <stdint.h>
#include <stdbool.h>
#include "oledfont.h"// OLED基本参数
#define OLED_WIDTH  128
#define OLED_HEIGHT 64
#define OLED_PAGES  8// OLED I2C地址 (7位地址)
#define OLED_ADDRESS 0x3C// OLED命令/数据标识
#define OLED_CMD  0x00
#define OLED_DATA 0x40// 函数声明
bool oled_init(void);
void oled_clear(void);
void oled_display(void);
void oled_set_pos(uint8_t x, uint8_t y);
void oled_show_char(uint8_t x, uint8_t y, char chr);
void oled_show_string(uint8_t x, uint8_t y, const char *str);
void oled_show_num(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void oled_show_float(uint8_t x, uint8_t y, float num, uint8_t int_len, uint8_t dec_len);// 中文显示函数
void oled_show_chinese_char(uint8_t x, uint8_t y, const char *utf8_char);
void oled_show_chinese_string(uint8_t x, uint8_t y, const char *utf8_str);// 图片显示函数
void oled_show_image(uint8_t x, uint8_t y, const OLED_Image *image);
void oled_show_image_part(uint8_t x, uint8_t y, const OLED_Image *image,uint8_t src_x, uint8_t src_y, uint8_t width, uint8_t height);// GIF显示函数
void oled_show_gif_frame(uint8_t x, uint8_t y, const OLED_GIF *gif, uint8_t frame_index);
bool oled_play_gif(uint8_t x, uint8_t y, const OLED_GIF *gif, uint16_t loop_count);
void oled_stop_gif(void);#endif

mpu6050.c

//前几秒保持静止!!!!有校准
#include "mpu6050.h"
#include "delay.h"
#include <math.h>// 根据不同I2C模式配置MPU6050
#if MPU_I2C_MODE == MPU_I2C_MODE_SOFT_I2C
// 软件I2C配置
Soft_I2C_Config mpu6050_i2c_config = {.sda_port = GPIOA,.sda_pin  = DL_GPIO_PIN_0, // PA0作为SDA.scl_port = GPIOA,.scl_pin  = DL_GPIO_PIN_1, // PA1作为SCL.delay_us = 1};
static Soft_I2C_Config *mpu6050_i2c = &mpu6050_i2c_config;
#elif MPU_I2C_MODE == MPU_I2C_MODE_HARD_I2C
// 硬件I2C配置
Hard_I2C_Config mpu6050_hard_i2c = {.instance    = HARD_I2C_0,.speed       = HARD_I2C_SPEED_100K,.timeout_ms  = 100,.initialized = false};
static Hard_I2C_Config *mpu6050_i2c = &mpu6050_hard_i2c;
#elif MPU_I2C_MODE == MPU_I2C_MODE_HAL_I2C
// HAL库I2C配置
extern I2C_HandleTypeDef hi2c1; // 从main.c中获取HAL库I2C句柄
static I2C_HandleTypeDef *mpu6050_i2c = &hi2c1;
#endifstatic mpu6050_data_t mpu6050_data;// 向MPU6050写入数据
static bool mpu6050_write_reg(uint8_t reg_addr, uint8_t data)
{
#if MPU_I2C_MODE == MPU_I2C_MODE_SOFT_I2Creturn soft_i2c_write_data(mpu6050_i2c, MPU6050_ADDR, reg_addr, &data, 1);
#elif MPU_I2C_MODE == MPU_I2C_MODE_HARD_I2Creturn (hard_i2c_write_data(mpu6050_i2c, MPU6050_ADDR, reg_addr, &data, 1) == HARD_I2C_OK);
#elif MPU_I2C_MODE == MPU_I2C_MODE_HAL_I2Cuint8_t buffer[2] = {reg_addr, data};return (HAL_I2C_Master_Transmit(mpu6050_i2c, MPU6050_ADDR << 1, buffer, 2, 100) == HAL_OK);
#endif
}// 从MPU6050读取数据
static bool mpu6050_read_reg(uint8_t reg_addr, uint8_t *data, uint16_t len)
{
#if MPU_I2C_MODE == MPU_I2C_MODE_SOFT_I2Creturn soft_i2c_read_data(mpu6050_i2c, MPU6050_ADDR, reg_addr, data, len);
#elif MPU_I2C_MODE == MPU_I2C_MODE_HARD_I2Creturn (hard_i2c_read_data(mpu6050_i2c, MPU6050_ADDR, reg_addr, data, len) == HARD_I2C_OK);
#elif MPU_I2C_MODE == MPU_I2C_MODE_HAL_I2Creturn (HAL_I2C_Mem_Read(mpu6050_i2c, MPU6050_ADDR << 1, reg_addr, I2C_MEMADD_SIZE_8BIT, data, len, 100) == HAL_OK);
#endif
}// 初始化MPU6050
bool mpu6050_init(void)
{
#if MPU_I2C_MODE == MPU_I2C_MODE_SOFT_I2C// 初始化软件I2Csoft_i2c_init(mpu6050_i2c);
#elif MPU_I2C_MODE == MPU_I2C_MODE_HARD_I2C// 初始化硬件I2Chard_i2c_init(mpu6050_i2c);
#elif MPU_I2C_MODE == MPU_I2C_MODE_HAL_I2C// 使用HAL库时通常在主程序中初始化I2C// 如果需要特定MPU6050配置,可以在此添加
#endifdelay_ms(50);// 简单唤醒MPU6050if (!mpu6050_write_reg(MPU6050_PWR_MGMT_1, 0x00)) {return false;}delay_ms(100);// 初始化数据结构mpu6050_data.accel_x       = 0;mpu6050_data.accel_y       = 0;mpu6050_data.accel_z       = 0;mpu6050_data.gyro_x        = 0;mpu6050_data.gyro_y        = 0;mpu6050_data.gyro_z        = 0;mpu6050_data.temp          = 0;mpu6050_data.roll          = 0;mpu6050_data.pitch         = 0;mpu6050_data.yaw           = 0;mpu6050_data.last_time     = get_tick();mpu6050_data.gyro_x_offset = 0;mpu6050_data.gyro_y_offset = 0;mpu6050_data.gyro_z_offset = 0;// 初始化卡尔曼滤波器kalman_init(&mpu6050_data.kalman_roll);kalman_init(&mpu6050_data.kalman_pitch);mpu6050_calibrate();return true;
}// 简化读取MPU6050数据 - 修复数据读取问题
bool mpu6050_read_data(mpu6050_data_t *data)
{
#if MPU_I2C_MODE == MPU_I2C_MODE_SOFT_I2C// 使用更小的数据包读取,每次只读2字节uint8_t buffer[2];// 读取加速度计X轴数据if (!mpu6050_read_reg(MPU6050_ACCEL_XOUT_H, buffer, 2)) {return false;}int16_t accel_x = (int16_t)((buffer[0] << 8) | buffer[1]);// 读取加速度计Y轴数据if (!mpu6050_read_reg(MPU6050_ACCEL_XOUT_H + 2, buffer, 2)) {return false;}int16_t accel_y = (int16_t)((buffer[0] << 8) | buffer[1]);// 读取加速度计Z轴数据if (!mpu6050_read_reg(MPU6050_ACCEL_XOUT_H + 4, buffer, 2)) {return false;}int16_t accel_z = (int16_t)((buffer[0] << 8) | buffer[1]);// 读取温度数据if (!mpu6050_read_reg(MPU6050_TEMP_OUT_H, buffer, 2)) {return false;}int16_t temp_raw = (int16_t)((buffer[0] << 8) | buffer[1]);// 读取陀螺仪X轴数据if (!mpu6050_read_reg(MPU6050_GYRO_XOUT_H, buffer, 2)) {return false;}int16_t gyro_x = (int16_t)((buffer[0] << 8) | buffer[1]);// 读取陀螺仪Y轴数据if (!mpu6050_read_reg(MPU6050_GYRO_XOUT_H + 2, buffer, 2)) {return false;}int16_t gyro_y = (int16_t)((buffer[0] << 8) | buffer[1]);// 读取陀螺仪Z轴数据if (!mpu6050_read_reg(MPU6050_GYRO_XOUT_H + 4, buffer, 2)) {return false;}int16_t gyro_z = (int16_t)((buffer[0] << 8) | buffer[1]);// 转换为实际值data->accel_x = (float)accel_x / 16384.0f;data->accel_y = (float)accel_y / 16384.0f;data->accel_z = (float)accel_z / 16384.0f;data->temp = ((float)temp_raw / 340.0f) + 36.53f;data->gyro_x = ((float)gyro_x / 131.0f) - data->gyro_x_offset;data->gyro_y = ((float)gyro_y / 131.0f) - data->gyro_y_offset;data->gyro_z = ((float)gyro_z / 131.0f) - data->gyro_z_offset;return true;
#elseuint8_t buffer[14]; // 一次性读取所有数据(加速度、温度、陀螺仪)// 从ACCEL_XOUT_H开始连续读取14个字节if (!mpu6050_read_reg(MPU6050_ACCEL_XOUT_H, buffer, 14)) {return false;}// 解析数据int16_t accel_x = (int16_t)((buffer[0] << 8) | buffer[1]);int16_t accel_y = (int16_t)((buffer[2] << 8) | buffer[3]);int16_t accel_z = (int16_t)((buffer[4] << 8) | buffer[5]);int16_t temp_raw = (int16_t)((buffer[6] << 8) | buffer[7]);int16_t gyro_x = (int16_t)((buffer[8] << 8) | buffer[9]);int16_t gyro_y = (int16_t)((buffer[10] << 8) | buffer[11]);int16_t gyro_z = (int16_t)((buffer[12] << 8) | buffer[13]);// 转换为实际值data->accel_x = (float)accel_x / 16384.0f;data->accel_y = (float)accel_y / 16384.0f;data->accel_z = (float)accel_z / 16384.0f;data->temp = ((float)temp_raw / 340.0f) + 36.53f;data->gyro_x = ((float)gyro_x / 131.0f) - data->gyro_x_offset;data->gyro_y = ((float)gyro_y / 131.0f) - data->gyro_y_offset;data->gyro_z = ((float)gyro_z / 131.0f) - data->gyro_z_offset;return true;
#endif
}// 修复角度计算函数
void mpu6050_compute_angles(mpu6050_data_t *data)
{uint32_t current_time = get_tick();float dt              = (float)(current_time - data->last_time) / 1000.0f;// 检查加速度计数据是否有效 - 放宽检查条件float accel_magnitude = sqrtf(data->accel_x * data->accel_x +data->accel_y * data->accel_y +data->accel_z * data->accel_z);if (accel_magnitude < 0.01f) {// 加速度计数据太小,保持之前的角度data->last_time = current_time;return;}// 修复角度计算公式// Roll角度 (绕X轴旋转) - 使用Y和Z轴数据if (fabsf(data->accel_z) > 0.01f) { // 避免除零data->roll = atan2f(data->accel_y, data->accel_z) * 180.0f / M_PI;}// Pitch角度 (绕Y轴旋转) - 使用X轴和Y、Z轴合成float yz_magnitude = sqrtf(data->accel_y * data->accel_y + data->accel_z * data->accel_z);if (yz_magnitude > 0.01f) { // 避免除零data->pitch = atan2f(-data->accel_x, yz_magnitude) * 180.0f / M_PI;}// 积分陀螺仪数据计算Yaw角度data->yaw += data->gyro_z * dt;// 限制Yaw角度在-180到180度之间if (data->yaw > 180.0f) data->yaw -= 360.0f;if (data->yaw < -180.0f) data->yaw += 360.0f;data->last_time = current_time;
}// 校准函数 
void mpu6050_calibrate(void)
{const int sample_count = 200; // 采样次数float gyro_x_sum       = 0;float gyro_y_sum       = 0;float gyro_z_sum       = 0;delay_ms(1000); // 等待设备稳定// 采集多次数据求平均for (int i = 0; i < sample_count; i++) {uint8_t buffer[6];// 读取陀螺仪数据if (mpu6050_read_reg(MPU6050_GYRO_XOUT_H, buffer, 6)) {int16_t gyro_x = (int16_t)((buffer[0] << 8) | buffer[1]);int16_t gyro_y = (int16_t)((buffer[2] << 8) | buffer[3]);int16_t gyro_z = (int16_t)((buffer[4] << 8) | buffer[5]);// 转换为实际值并累加gyro_x_sum += (float)gyro_x / 131.0f;gyro_y_sum += (float)gyro_y / 131.0f;gyro_z_sum += (float)gyro_z / 131.0f;}delay_ms(5); // 短暂延时}// 计算平均值作为偏移量mpu6050_data.gyro_x_offset = gyro_x_sum / sample_count;mpu6050_data.gyro_y_offset = gyro_y_sum / sample_count;mpu6050_data.gyro_z_offset = gyro_z_sum / sample_count;// 重置角度mpu6050_data.roll      = 0.0f;mpu6050_data.pitch     = 0.0f;mpu6050_data.yaw       = 0.0f;mpu6050_data.last_time = get_tick();
}void mpu6050_update(void)
{if (mpu6050_read_data(&mpu6050_data)) {mpu6050_compute_angles(&mpu6050_data);}
}void mpu6050_get_angles(float angles[3])
{angles[0] = mpu6050_data.roll;angles[1] = mpu6050_data.pitch;angles[2] = mpu6050_data.yaw;
}float mpu6050_get_temperature(void)
{return mpu6050_data.temp;
}// 添加调试函数
bool mpu6050_test_communication(void)
{uint8_t who_am_i;if (!mpu6050_read_reg(MPU6050_WHO_AM_I, &who_am_i, 1)) {return false;}return (who_am_i == MPU6050_ADDR);
}// 获取原始数据用于调试
bool mpu6050_get_raw_data(int16_t raw_data[7])
{uint8_t buffer[2];// 逐个读取每个寄存器if (!mpu6050_read_reg(MPU6050_ACCEL_XOUT_H, buffer, 2)) return false;raw_data[0] = (int16_t)((buffer[0] << 8) | buffer[1]); // accel_xif (!mpu6050_read_reg(MPU6050_ACCEL_XOUT_H + 2, buffer, 2)) return false;raw_data[1] = (int16_t)((buffer[0] << 8) | buffer[1]); // accel_yif (!mpu6050_read_reg(MPU6050_ACCEL_XOUT_H + 4, buffer, 2)) return false;raw_data[2] = (int16_t)((buffer[0] << 8) | buffer[1]); // accel_zif (!mpu6050_read_reg(MPU6050_TEMP_OUT_H, buffer, 2)) return false;raw_data[3] = (int16_t)((buffer[0] << 8) | buffer[1]); // tempif (!mpu6050_read_reg(MPU6050_GYRO_XOUT_H, buffer, 2)) return false;raw_data[4] = (int16_t)((buffer[0] << 8) | buffer[1]); // gyro_xif (!mpu6050_read_reg(MPU6050_GYRO_XOUT_H + 2, buffer, 2)) return false;raw_data[5] = (int16_t)((buffer[0] << 8) | buffer[1]); // gyro_yif (!mpu6050_read_reg(MPU6050_GYRO_XOUT_H + 4, buffer, 2)) return false;raw_data[6] = (int16_t)((buffer[0] << 8) | buffer[1]); // gyro_zreturn true;
}// 添加获取转换后加速度计数据的调试函数
bool mpu6050_get_accel_data(float accel[3])
{if (mpu6050_read_data(&mpu6050_data)) {accel[0] = mpu6050_data.accel_x;accel[1] = mpu6050_data.accel_y;accel[2] = mpu6050_data.accel_z;return true;}return false;
}// 卡尔曼滤波器初始化
void kalman_init(kalman_filter_t *kalman)
{// 设置卡尔曼滤波器参数kalman->Q_angle   = 0.001f; // 过程噪声协方差 - 角度噪声kalman->Q_bias    = 0.003f; // 过程噪声协方差 - 角速度偏差噪声kalman->R_measure = 0.03f;  // 测量噪声协方差// 初始化状态kalman->angle = 0.0f; // 初始角度kalman->bias  = 0.0f; // 初始偏差// 初始化误差协方差矩阵kalman->P[0][0] = 0.0f;kalman->P[0][1] = 0.0f;kalman->P[1][0] = 0.0f;kalman->P[1][1] = 0.0f;
}// 卡尔曼滤波器更新函数
float kalman_update(kalman_filter_t *kalman, float new_angle, float new_rate, float dt)
{// 1. 预测状态// 角度预测: 角度 += (角速度 - 偏差) * dtfloat rate = new_rate - kalman->bias;kalman->angle += rate * dt;// 2. 更新误差协方差矩阵// P[0][0] += dt * (dt * P[1][1] - P[0][1] - P[1][0] + Q_angle)kalman->P[0][0] += dt * (dt * kalman->P[1][1] - kalman->P[0][1] - kalman->P[1][0] + kalman->Q_angle);kalman->P[0][1] -= dt * kalman->P[1][1];kalman->P[1][0] -= dt * kalman->P[1][1];kalman->P[1][1] += kalman->Q_bias * dt;// 3. 计算卡尔曼增益float S = kalman->P[0][0] + kalman->R_measure;float K[2];K[0] = kalman->P[0][0] / S;K[1] = kalman->P[1][0] / S;// 4. 计算测量与预测的差值float y = new_angle - kalman->angle;// 5. 更新状态kalman->angle += K[0] * y;kalman->bias += K[1] * y;// 6. 更新误差协方差矩阵float P00_temp = kalman->P[0][0];float P01_temp = kalman->P[0][1];kalman->P[0][0] -= K[0] * P00_temp;kalman->P[0][1] -= K[0] * P01_temp;kalman->P[1][0] -= K[1] * P00_temp;kalman->P[1][1] -= K[1] * P01_temp;// 返回滤波后的角度return kalman->angle;
}

mpu6050.h

#ifndef MPU6050_H
#define MPU6050_H// MPU6050 I2C配置宏定义
#define MPU_I2C_MODE_SOFT_I2C 1 // 软件I2C
#define MPU_I2C_MODE_HARD_I2C 2 // 硬件I2C (TI驱动库)
#define MPU_I2C_MODE_HAL_I2C  3 // STM32 HAL库// 选择使用的I2C模式
#define MPU_I2C_MODE MPU_I2C_MODE_HARD_I2C// 根据配置包含相应头文件
#if MPU_I2C_MODE == MPU_I2C_MODE_SOFT_I2C
#include "soft_i2c.h"
#elif MPU_I2C_MODE == MPU_I2C_MODE_HARD_I2C
#include "hard_i2c.h"
#elif MPU_I2C_MODE == MPU_I2C_MODE_HAL_I2C
#include "stm32f1xx_hal.h" // 根据实际MCU型号调整
#endif#include "sys_init.h"
#include <stdint.h>
#include <stdbool.h>
#include <math.h>// MPU6050 I2C 地址
#define MPU6050_ADDR 0x68// MPU6050 寄存器地址
#define MPU6050_SMPLRT_DIV   0x19 // 采样率分频器
#define MPU6050_CONFIG       0x1A // 配置寄存器
#define MPU6050_GYRO_CONFIG  0x1B // 陀螺仪配置
#define MPU6050_ACCEL_CONFIG 0x1C // 加速度计配置
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_TEMP_OUT_H   0x41
#define MPU6050_GYRO_XOUT_H  0x43
#define MPU6050_PWR_MGMT_1   0x6B
#define MPU6050_WHO_AM_I     0x75#ifndef M_PI
#define M_PI 3.14159265359f
#endif// 卡尔曼滤波器结构体
typedef struct
{float Q_angle;   // 过程噪声协方差float Q_bias;    // 过程噪声协方差float R_measure; // 测量噪声协方差float angle;     // 角度float bias;      // 陀螺仪偏差float P[2][2];   // 误差协方差矩阵
} kalman_filter_t;// MPU6050 数据结构体
typedef struct
{float accel_x;float accel_y;float accel_z;float gyro_x;float gyro_y;float gyro_z;float temp;float roll;float pitch;float yaw;// 添加卡尔曼滤波器kalman_filter_t kalman_roll;kalman_filter_t kalman_pitch;uint32_t last_time; // 上次采样时间(毫秒)// 校准偏移量float gyro_x_offset;float gyro_y_offset;float gyro_z_offset;
} mpu6050_data_t;// MPU6050 函数声明
bool mpu6050_init(void);
bool mpu6050_read_data(mpu6050_data_t *data);
void mpu6050_compute_angles(mpu6050_data_t *data);
void mpu6050_calibrate(void);
void mpu6050_update(void);
void mpu6050_get_angles(float angles[3]);
float mpu6050_get_temperature(void);// 调试函数
bool mpu6050_test_communication(void);
bool mpu6050_get_raw_data(int16_t raw_data[7]);
bool mpu6050_get_accel_data(float accel[3]); // 新增// 卡尔曼滤波器函数
void kalman_init(kalman_filter_t *kalman);
float kalman_update(kalman_filter_t *kalman, float new_angle, float new_rate, float dt);#endif

相关文章:

  • RK 安卓10/11平台 HDMI-IN 调试
  • SSRF4 SSRF-gopher 协议扩展利用-向内网发起 GET/POST 请求
  • Java中间件使用方式与实战应用
  • 基于深度学习的智能文本摘要系统:技术与实践
  • 【音视频】SIP基础、搭建服务器和客户端
  • uniapp 配置devserver代理
  • P6 QT项目----汽车仪表盘(6.4)
  • C++ vector深度剖析与模拟实现:探索模板的泛型应用
  • 腾讯云国际站缩容:策略、考量与实践
  • 智慧园区建设资料合集(Wordppt原件)
  • Spring Boot 中的条件装配:@Conditional 系列注解详解
  • 答辩讲解387基于Spring Boot的心理健康管理系统
  • 【Python系列PyCharm实战】ModuleNotFoundError: No module named ‘sklearn’ 系列Bug解决方案大全
  • Windows Server系统只有命令行不显示桌面的解决方法
  • 【超详细】讯飞智能车PC电脑烧录指南(高级系统部署与恢复)
  • LDPC码校验矩阵和生成矩阵的生成
  • Java在IDEA中终端窗口输出正常,但打包成JAR后中文乱码问题
  • 《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- 实战基于CSI2 Rx 构建高性能摄像头输入系统
  • 51c嵌入式~电路~合集2
  • 【沉浸式解决问题】优化MySQL中多表union速度慢的问题
  • 建设自己的网站步骤/整合网络营销公司
  • 学校门户网站建设工作汇报/seo外包公司兴田德润官方地址
  • 厦门市网站建设app开发/百度网站联系方式
  • 问答网站怎么做营销/彩虹云商城网站搭建
  • 深圳做网站的公司 cheungdom/西安百度百科
  • wordpress需要授权吗/邯郸seo推广