这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » Let'sdo2025第二期活动[智能手环DIY活动](过程贴)任务二

共2条 1/1 1 跳转至

Let'sdo2025第二期活动[智能手环DIY活动](过程贴)任务二

工程师
2025-09-09 18:39:54     打赏

简介

在本章节中我们将使用MAX78000的I2C来驱动SSD1306,根据原理图得知I2C1已经连接了一个传感器,好在它还有额外的引出PIN,现在使用杜邦线将SSD1306连接到对应的PIN上。

image.png


然后打开Example下的I2C_Scan的代码,可以根据代码中发现(或者原理图)其中定义的I2CPin 为16和17,如果烧录上述Demo的话,其扫描到的I2C地址为0x3C

image.png

然后我们可以从网上查找现成的SSD1306驱动并且将其I2C的实现修改为MAX78000实现即可。如下所示。

#ifndef SSD1306_H
#define SSD1306_H

#include <stdint.h>

#define SSD1306_ADDR 0x3C
#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 64
#define SSD1306_PAGE_NUM (SSD1306_HEIGHT / 8)

#define SSD1306_COMMAND 0x00
#define SSD1306_DATA 0x40

#ifdef __cplusplus
extern "C"
{
#endif

    // 初始化 SSD1306
    void SSD1306_Init(void);

    // 清屏
    void SSD1306_Clear(void);

    // 显示测试图案
    void SSD1306_DisplayDemo(void);

    // 显示缓冲区数据
    void SSD1306_DisplayFrame(uint8_t *buffer);

    // 打印字符串(支持 6x8 ASCII)
    void SSD1306_Print(uint8_t x, uint8_t page, const char *str);

    // 全屏缓冲区
    extern uint8_t SSD1306_Buffer[SSD1306_PAGE_NUM * SSD1306_WIDTH];

    // 打印整数
    void SSD1306_PrintInt(uint8_t x, uint8_t page, int32_t num);

    // 打印浮点数
    void SSD1306_PrintFloat(uint8_t x, uint8_t page, float num, uint8_t decimals);

#ifdef __cplusplus
}
#endif

#endif // SSD1306_H


然后便是对应的C文件

#include "ssd1306.h"
#include "i2c.h"
#include "mxc_device.h"
#include "mxc_delay.h"
#include <string.h>
#include <stdio.h>

// 6x8 ASCII 字体表
static const uint8_t Font6x8[95][6] = {
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ' '
    {0x00, 0x00, 0x5F, 0x00, 0x00, 0x00}, // '!'
    {0x00, 0x07, 0x00, 0x07, 0x00, 0x00}, // '"'
    {0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00}, // '#'
    {0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00}, // '$'
    {0x23, 0x13, 0x08, 0x64, 0x62, 0x00}, // '%'
    {0x36, 0x49, 0x55, 0x22, 0x50, 0x00}, // '&'
    {0x00, 0x05, 0x03, 0x00, 0x00, 0x00}, // '''
    {0x00, 0x1C, 0x22, 0x41, 0x00, 0x00}, // '('
    {0x00, 0x41, 0x22, 0x1C, 0x00, 0x00}, // ')'
    {0x14, 0x08, 0x3E, 0x08, 0x14, 0x00}, // '*'
    {0x08, 0x08, 0x3E, 0x08, 0x08, 0x00}, // '+'
    {0x00, 0x50, 0x30, 0x00, 0x00, 0x00}, // ','
    {0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, // '-'
    {0x00, 0x60, 0x60, 0x00, 0x00, 0x00}, // '.'
    {0x20, 0x10, 0x08, 0x04, 0x02, 0x00}, // '/'
    {0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00}, // '0'
    {0x00, 0x42, 0x7F, 0x40, 0x00, 0x00}, // '1'
    {0x42, 0x61, 0x51, 0x49, 0x46, 0x00}, // '2'
    {0x21, 0x41, 0x45, 0x4B, 0x31, 0x00}, // '3'
    {0x18, 0x14, 0x12, 0x7F, 0x10, 0x00}, // '4'
    {0x27, 0x45, 0x45, 0x45, 0x39, 0x00}, // '5'
    {0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00}, // '6'
    {0x01, 0x71, 0x09, 0x05, 0x03, 0x00}, // '7'
    {0x36, 0x49, 0x49, 0x49, 0x36, 0x00}, // '8'
    {0x06, 0x49, 0x49, 0x29, 0x1E, 0x00}, // '9'
    {0x00, 0x36, 0x36, 0x00, 0x00, 0x00}, // ':'
    {0x00, 0x56, 0x36, 0x00, 0x00, 0x00}, // ';'
    {0x08, 0x14, 0x22, 0x41, 0x00, 0x00}, // '<'
    {0x14, 0x14, 0x14, 0x14, 0x14, 0x00}, // '='
    {0x00, 0x41, 0x22, 0x14, 0x08, 0x00}, // '>'
    {0x02, 0x01, 0x51, 0x09, 0x06, 0x00}, // '?'
    {0x32, 0x49, 0x79, 0x41, 0x3E, 0x00}, // '@'
    {0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00}, // 'A'
    {0x7F, 0x49, 0x49, 0x49, 0x36, 0x00}, // 'B'
    {0x3E, 0x41, 0x41, 0x41, 0x22, 0x00}, // 'C'
    {0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00}, // 'D'
    {0x7F, 0x49, 0x49, 0x49, 0x41, 0x00}, // 'E'
    {0x7F, 0x09, 0x09, 0x09, 0x01, 0x00}, // 'F'
    {0x3E, 0x41, 0x49, 0x49, 0x7A, 0x00}, // 'G'
    {0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00}, // 'H'
    {0x00, 0x41, 0x7F, 0x41, 0x00, 0x00}, // 'I'
    {0x20, 0x40, 0x41, 0x3F, 0x01, 0x00}, // 'J'
    {0x7F, 0x08, 0x14, 0x22, 0x41, 0x00}, // 'K'
    {0x7F, 0x40, 0x40, 0x40, 0x40, 0x00}, // 'L'
    {0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00}, // 'M'
    {0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00}, // 'N'
    {0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00}, // 'O'
    {0x7F, 0x09, 0x09, 0x09, 0x06, 0x00}, // 'P'
    {0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00}, // 'Q'
    {0x7F, 0x09, 0x19, 0x29, 0x46, 0x00}, // 'R'
    {0x46, 0x49, 0x49, 0x49, 0x31, 0x00}, // 'S'
    {0x01, 0x01, 0x7F, 0x01, 0x01, 0x00}, // 'T'
    {0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00}, // 'U'
    {0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00}, // 'V'
    {0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00}, // 'W'
    {0x63, 0x14, 0x08, 0x14, 0x63, 0x00}, // 'X'
    {0x07, 0x08, 0x70, 0x08, 0x07, 0x00}, // 'Y'
    {0x61, 0x51, 0x49, 0x45, 0x43, 0x00}, // 'Z'
    {0x00, 0x7F, 0x41, 0x41, 0x00, 0x00}, // '['
    {0x02, 0x04, 0x08, 0x10, 0x20, 0x00}, // '\'
    {0x00, 0x41, 0x41, 0x7F, 0x00, 0x00}, // ']'
    {0x04, 0x02, 0x01, 0x02, 0x04, 0x00}, // '^'
    {0x40, 0x40, 0x40, 0x40, 0x40, 0x00}, // '_'
    {0x00, 0x01, 0x02, 0x04, 0x00, 0x00}, // '`'
    {0x20, 0x54, 0x54, 0x54, 0x78, 0x00}, // 'a'
    {0x7F, 0x48, 0x44, 0x44, 0x38, 0x00}, // 'b'
    {0x38, 0x44, 0x44, 0x44, 0x20, 0x00}, // 'c'
    {0x38, 0x44, 0x44, 0x48, 0x7F, 0x00}, // 'd'
    {0x38, 0x54, 0x54, 0x54, 0x18, 0x00}, // 'e'
    {0x08, 0x7E, 0x09, 0x01, 0x02, 0x00}, // 'f'
    {0x0C, 0x52, 0x52, 0x52, 0x3E, 0x00}, // 'g'
    {0x7F, 0x08, 0x04, 0x04, 0x78, 0x00}, // 'h'
    {0x00, 0x44, 0x7D, 0x40, 0x00, 0x00}, // 'i'
    {0x20, 0x40, 0x44, 0x3D, 0x00, 0x00}, // 'j'
    {0x7F, 0x10, 0x28, 0x44, 0x00, 0x00}, // 'k'
    {0x00, 0x41, 0x7F, 0x40, 0x00, 0x00}, // 'l'
    {0x7C, 0x04, 0x18, 0x04, 0x78, 0x00}, // 'm'
    {0x7C, 0x08, 0x04, 0x04, 0x78, 0x00}, // 'n'
    {0x38, 0x44, 0x44, 0x44, 0x38, 0x00}, // 'o'
    {0x7C, 0x14, 0x14, 0x14, 0x08, 0x00}, // 'p'
    {0x08, 0x14, 0x14, 0x18, 0x7C, 0x00}, // 'q'
    {0x7C, 0x08, 0x04, 0x04, 0x08, 0x00}, // 'r'
    {0x48, 0x54, 0x54, 0x54, 0x20, 0x00}, // 's'
    {0x04, 0x3F, 0x44, 0x40, 0x20, 0x00}, // 't'
    {0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00}, // 'u'
    {0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00}, // 'v'
    {0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00}, // 'w'
    {0x44, 0x28, 0x10, 0x28, 0x44, 0x00}, // 'x'
    {0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00}, // 'y'
    {0x44, 0x64, 0x54, 0x4C, 0x44, 0x00}, // 'z'
    {0x00, 0x08, 0x36, 0x41, 0x00, 0x00}, // '{'
    {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00}, // '|'
    {0x00, 0x41, 0x36, 0x08, 0x00, 0x00}, // '}'
    {0x10, 0x08, 0x08, 0x10, 0x08, 0x00}, // '~'
};

// 是否开启调试日志
#define SSD1306_DEBUG 0 // 1: 开启日志,0: 关闭日志

#if SSD1306_DEBUG
#define SSD1306_LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define SSD1306_LOG(fmt, ...) // 什么都不做
#endif

// SSD1306 全屏缓冲区
uint8_t SSD1306_Buffer[SSD1306_PAGE_NUM * SSD1306_WIDTH];

// I2C 写入函数,增加返回值日志
static int SSD1306_Write(uint8_t control, uint8_t *data, uint16_t len)
{
    mxc_i2c_req_t req;
    req.i2c = MXC_I2C1; // 根据你的硬件选择
    req.addr = SSD1306_ADDR;

    uint8_t buffer[len + 1];
    buffer[0] = control;
    memcpy(buffer + 1, data, len);

    req.tx_buf = buffer;
    req.tx_len = len + 1;
    req.rx_buf = NULL;
    req.rx_len = 0;
    req.restart = 0;
    req.callback = NULL;

    int ret = MXC_I2C_MasterTransaction(&req);
    if (ret != 0)
    {
        SSD1306_LOG("[SSD1306] I2C Write failed, addr=0x%02X, control=0x%02X, ret=%d\n", SSD1306_ADDR, control, ret);
    }
    else
    {
        SSD1306_LOG("[SSD1306] I2C Write success, addr=0x%02X, control=0x%02X, len=%d\n", SSD1306_ADDR, control, len);
    }
    MXC_Delay(MXC_DELAY_MSEC(5)); // 给总线一些时间
    return ret;
}

// 初始化 SSD1306,增加日志
void SSD1306_Init(void)
{
    SSD1306_LOG("[SSD1306] Init start\n");
    uint8_t init_cmds[] = {
        0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
        0x81, 0xFF, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3, 0x00,
        0xD5, 0xF0, 0xD9, 0x22, 0xDA, 0x12, 0xDB, 0x20, 0x8D, 0x14,
        0xAF};
    int ret = SSD1306_Write(SSD1306_COMMAND, init_cmds, sizeof(init_cmds));
    if (ret != 0)
    {
        SSD1306_LOG("[SSD1306] Init failed\n");
        return;
    }
    SSD1306_Clear();
    SSD1306_LOG("[SSD1306] Init done\n");
}

// 清屏
void SSD1306_Clear(void)
{
    memset(SSD1306_Buffer, 0x00, sizeof(SSD1306_Buffer));
    SSD1306_DisplayFrame(SSD1306_Buffer);
    SSD1306_LOG("[SSD1306] Clear screen\n");
}

// 显示缓冲区
void SSD1306_DisplayFrame(uint8_t *buffer)
{
    for (uint8_t page = 0; page < SSD1306_PAGE_NUM; page++)
    {
        uint8_t set_page_cmds[] = {0xB0 | page, 0x00, 0x10};
        if (SSD1306_Write(SSD1306_COMMAND, set_page_cmds, sizeof(set_page_cmds)) != 0)
        {
            SSD1306_LOG("[SSD1306] Set page %d failed\n", page);
        }
        if (SSD1306_Write(SSD1306_DATA, buffer + page * SSD1306_WIDTH, SSD1306_WIDTH) != 0)
        {
            SSD1306_LOG("[SSD1306] Write page %d data failed\n", page);
        }
    }
}

// 打印字符串
void SSD1306_Print(uint8_t x, uint8_t page, const char *str)
{
    while (*str)
    {
        if (x > SSD1306_WIDTH - 6)
        {
            x = 0;
            page++;
            if (page >= SSD1306_PAGE_NUM)
                break;
        }

        uint8_t c = *str;
        if (c < ' ' || c > '~')
            c = ' '; // 非法字符用空格
        memcpy(&SSD1306_Buffer[page * SSD1306_WIDTH + x], Font6x8[c - ' '], 6);

        x += 6;
        str++;
    }
    SSD1306_DisplayFrame(SSD1306_Buffer);
    SSD1306_LOG("[SSD1306] Print string done\n");
}

// 打印整数
void SSD1306_PrintInt(uint8_t x, uint8_t page, int32_t num)
{
    char buf[12]; // 足够容纳 -2147483648\0
    sprintf(buf, "%ld", num);
    SSD1306_Print(x, page, buf);
}

// 打印浮点数
void SSD1306_PrintFloat(uint8_t x, uint8_t page, float num, uint8_t decimals)
{
    char buf[20]; // 足够容纳浮点数
    char format[8];
    sprintf(format, "%%.%df", decimals); // 生成格式字符串,例如 "%.2f"
    sprintf(buf, format, num);
    SSD1306_Print(x, page, buf);
}


我上述只包括了一个字库,并且显示的话不是按照Xy进行显示的,而是根据Line进行显示,这样也省去了每次计算坐标的麻烦。 缺点是不够灵活。

#include <stdio.h>
#include "board.h"
#include "mxc_device.h"
#include "mxc_delay.h"
#include "i2c.h"
#include "ssd1306.h"

int main(void)
{
    printf("SSD1306 0.96 inch Example\n");

    // 初始化 I2C
    int res = MXC_I2C_Init(MXC_I2C1, 1, 0);
    printf("I2C Init: %d\n", res);
    MXC_I2C_SetFrequency(MXC_I2C1, 400000);
    printf("I2C Frequency: %d\n", MXC_I2C_GetFrequency(MXC_I2C1));
    // 初始化 OLED
    SSD1306_Init();

    // 清屏
    SSD1306_Clear();

    // 打印文字
    SSD1306_Print(0, 0, "Hello MAX78000!");
    SSD1306_Print(0, 1, "SSD1306 OLED Demo");
    SSD1306_Print(0, 2, "Hello EEPW!");
    SSD1306_Print(0, 3, "Hello Let’s do !");
    SSD1306_PrintInt(0, 4, 33);
    SSD1306_PrintFloat(0, 5, 77.7, 2);
    while (1)
    {
        MXC_Delay(MXC_DELAY_MSEC(1000));
    }

    return 0;
}

在主函数中进行调用。 然后便可以观察到屏幕已经正常的点亮。 黄蓝屏,真的很好看!

ac3662d17430b9bbba435f8e868e26f2.jpg


插曲

是时候换一个电烙铁了,焊接这个排针真的给我折磨疯了,这个黄花电烙铁头氧化太严重了。焊接一个排针要好久好久。焊接好了之后发现不小心把开发板上的绝缘图层刮露铜了。然后开发板经常启动不了。不知道怎么回事。后来尝试使用吹风机一加热就可以正常启动了。 不加热启动不了。后来发现是因为晶振不起振的问题。我还拆下了一个起振的电容。我自己是根本装不上去,太小了。现在那个电容已经不知道飞到哪里去了。 所以起振电容目前只有一个,我的开发板变成了需要钥匙启动的。 每次要是想能正常启动,必须找一个杜邦线插到晶振上五秒时间,然后晶振就起振了。




关键词: 手环     SSD1306     MAX78000    

院士
2025-09-10 10:18:50     打赏
2楼

这焊接的曲折,听上去真是心疼工程师好几秒~


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]