飞凌嵌入式
直播中

jf_15487227

未满1年用户 10经验值
私信 关注
[活动]

【OK3506-S12Mini试用评测】如何最简单驱动0.96寸oled(不修改内核)

首先板载的系统在板子上树莓派40pin接口具有两个iic接口并且以及打开了
image.png

下面这个表格汇总了主要步骤和要点:

步骤 关键行动 说明/命令示例
1. 确认接口与连接 确认OLED使用I2C还是SPI通信,并正确连接硬件 参考理解常见接线逻辑;具体引脚需查阅OK3506原理图
2. 检查与使能接口 确认系统识别到I2C或SPI设备 I2C检查:ls /dev/i2c*;SPI检查:ls /dev/spi*;必要时在系统配置中使能
3. 获取OLED用户层驱动代码 编写或获取基于I2C/SPI的OLED驱动代码 代码需基于I2C(如使用/dev/i2c-1)或SPI(如使用/dev/spidev1.0)设备文件操作
4. 交叉编译 在主机上使用交叉编译工具链编译代码 arm-linux-gnueabihf-gcc -o oled_test oled_test.c -static
5. 部署与测试 将编译好的程序传到板子上运行 使用scp传输;设置可执行权限chmod +x oled_test;运行./oled_test

? 硬件连接

  • 确认OLED接口 :你的0.96寸OLED屏幕很可能使用 I2C (通常是4线:VCC, GND, SCL, SDA)或 SPI (通常是7线)接口。请根据屏幕型号确认。
  • 连接OLED与OK3506 :以常见的I2C接口OLED为例,你需要将OLED的引脚连接到OK3506的对应GPIO上。接线逻辑可参考中ESP8266的接法,但具体引脚需查阅 OK3506的原理图或引脚定义 ,找到其I2C(例如I2C1_SDA, I2C1_SCL)或SPI接口对应的物理引脚。
  • 注意电压 :确保OLED的工作电压(通常是3.3V)与OK3506的GPIO电压匹配。

⚙️ 软件配置与驱动准备

检查与使能I2C/SPI接口

在OK3506的终端中,检查I2C或SPI设备是否已被系统识别:

  • 对于I2C :执行 ls /dev/i2c*,如果能看到类似 /dev/i2c-0/dev/i2c-1 的设备文件,说明I2C驱动已加载。
  • 对于SPI :执行 ls /dev/spi*,检查是否存在类似 /dev/spidev1.0 的设备文件。

如果找不到对应的设备文件,你可能需要在Buildroot的系统配置中使能I2C或SPI的用户空间设备支持,并重新构建根文件系统。

获取OLED驱动代码

你需要一个在用户空间通过I2C或SPI控制OLED的程序。由于你的OLED是I2C接口,下面提供一个基于I2C的简单C代码框架(例如保存为 oled_i2c.c),用于点亮OLED并进行基本显示。此代码框架思路亦可见于的ESP8266驱动。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

#define OLED_I2C_DEV "/dev/i2c-1" // 根据实际i2c设备文件修改
#define OLED_ADDR    0x3C         // OLED的I2C地址,常见为0x3C或0x3D

int i2c_fd;

// 向OLED发送命令
void oled_write_cmd(unsigned char cmd) {
    unsigned char buffer[2] = {0x00, cmd}; // I2C模式下,通常第一个字节为0x00表示命令
    if (write(i2c_fd, buffer, 2) != 2) {
        perror("Failed to write command");
    }
}

// 初始化OLED序列
void oled_init() {
    // 详细的初始化命令序列需参考SSD1306数据手册或厂家示例
    oled_write_cmd(0xAE); // 关闭显示
    // ... 这里需要添加更多的初始化命令,例如设置内存地址模式、扫描方向、对比度等
    // oled_write_cmd(0x20); oled_write_cmd(0x00); // 设置内存地址模式为水平模式
    // ... 更多初始化命令
    oled_write_cmd(0xAF); // 打开显示
}

int main() {
    // 打开I2C设备
    i2c_fd = open(OLED_I2C_DEV, O_RDWR);
    if (i2c_fd < 0) {
        perror("Failed to open I2C device");
        return -1;
    }

    // 设置I2C从设备地址
    if (ioctl(i2c_fd, I2C_SLAVE, OLED_ADDR) < 0) {
        perror("Failed to set I2C slave address");
        close(i2c_fd);
        return -1;
    }

    // 初始化并点亮OLED
    oled_init();
    printf("OLED initialized and turned on.\\n");

    // ... 这里可以添加具体的显示函数,例如显示字符串、图形等

    sleep(5); // 保持显示5秒
    close(i2c_fd);
    return 0;
}

下面是我自己的oled驱动代码是iic-2的接口

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>

#define OLED_I2C_ADDR 0x3C    // 常见 I2C 地址 (有些是 0x3D)
#define OLED_CMD      0x00    // 控制字节: 命令
#define OLED_DATA     0x40    // 控制字节: 数据

#define OLED_WIDTH    128
#define OLED_HEIGHT   64
#define OLED_PAGES    (OLED_HEIGHT / 8)

// 如果你的屏是 SH1106 控制器,常见需要列偏移 2;SSD1306 一般为 0
#ifndef OLED_COL_OFFSET
#define OLED_COL_OFFSET 0
#endif

static int i2c_fd = -1;

// 发送一条命令
static int oled_write_cmd(uint8_t cmd) {
    uint8_t buf[2] = { OLED_CMD, cmd };
    ssize_t n = write(i2c_fd, buf, 2);
    if (n != 2) return -1;
    return 0;
}

// 发送一段数据(内部自动加控制字节0x40,分片写入)
static int oled_write_data(const uint8_t* data, size_t len) {
    // 一些适配器对 I2C 块写长度有限制,分片到 16 字节以内更稳妥
    const size_t chunk = 16;
    uint8_t buf[1 + chunk];
    buf[0] = OLED_DATA;

    size_t sent = 0;
    while (sent < len) {
        size_t n = (len - sent) > chunk ? chunk : (len - sent);
        memcpy(buf + 1, data + sent, n);
        ssize_t w = write(i2c_fd, buf, 1 + n);
        if (w != (ssize_t)(1 + n)) return -1;
        sent += n;
    }
    return 0;
}

static void oled_set_pos(uint8_t page, uint8_t col) {
    col += OLED_COL_OFFSET;
    oled_write_cmd(0xB0 | (page & 0x07));                      // 页地址
    oled_write_cmd(0x00 | (col & 0x0F));                       // 列低4位
    oled_write_cmd(0x10 | ((col >> 4) & 0x0F));                // 列高4位
}

static int oled_init(void) {
    // 参考 SSD1306 128x64 初始化,改为“页寻址模式”
    if (oled_write_cmd(0xAE)) return -1;        // Display OFF
    if (oled_write_cmd(0x20)) return -1;        // Set Memory Addressing Mode
    if (oled_write_cmd(0x02)) return -1;        // Page Addressing Mode
    if (oled_write_cmd(0xB0)) return -1;        // Page Start Address for Page Addressing Mode
    if (oled_write_cmd(0xC8)) return -1;        // COM Output Scan Direction: remapped mode
    if (oled_write_cmd(0x00)) return -1;        // set low column address
    if (oled_write_cmd(0x10)) return -1;        // set high column address
    if (oled_write_cmd(0x40)) return -1;        // set start line address

    if (oled_write_cmd(0x81)) return -1;        // Contrast
    if (oled_write_cmd(0x7F)) return -1;

    if (oled_write_cmd(0xA1)) return -1;        // Segment re-map (A0/A1)
    if (oled_write_cmd(0xA6)) return -1;        // Normal display (A7 inverse)

    if (oled_write_cmd(0xA8)) return -1;        // Multiplex ratio
    if (oled_write_cmd(0x3F)) return -1;        // 1/64 duty

    if (oled_write_cmd(0xA4)) return -1;        // Display follows RAM
    if (oled_write_cmd(0xD3)) return -1;        // Display offset
    if (oled_write_cmd(0x00)) return -1;

    if (oled_write_cmd(0xD5)) return -1;        // Display clock divide
    if (oled_write_cmd(0x80)) return -1;

    if (oled_write_cmd(0xD9)) return -1;        // Pre-charge
    if (oled_write_cmd(0xF1)) return -1;

    if (oled_write_cmd(0xDA)) return -1;        // COM pins
    if (oled_write_cmd(0x12)) return -1;        // For 128x64

    if (oled_write_cmd(0xDB)) return -1;        // VCOMH
    if (oled_write_cmd(0x40)) return -1;

    if (oled_write_cmd(0x8D)) return -1;        // Charge pump
    if (oled_write_cmd(0x14)) return -1;        // Enable

    if (oled_write_cmd(0xAF)) return -1;        // Display ON
    usleep(1000 * 100);
    return 0;
}

// 清屏
static void oled_clear(void) {
    uint8_t zeros[OLED_WIDTH];
    memset(zeros, 0x00, sizeof(zeros));
    for (uint8_t p = 0; p < OLED_PAGES; ++p) {
        oled_set_pos(p, 0);
        oled_write_data(zeros, OLED_WIDTH);
    }
}

// 简易 5x7 字库(仅覆盖本例中用到的字符;其余显示为空白)
// 每个字符 5 列(第6列留作1像素空列)
static void glyph5x7(char c, uint8_t out5[5]) {
    // 预设为空白
    memset(out5, 0x00, 5);

    switch (c) {
        case ' ': out5[0]=0x00; out5[1]=0x00; out5[2]=0x00; out5[3]=0x00; out5[4]=0x00; break;

        // Digits '0'-'9'
        case '0': out5[0]=0x3E; out5[1]=0x51; out5[2]=0x49; out5[3]=0x45; out5[4]=0x3E; break;
        case '1': out5[0]=0x00; out5[1]=0x42; out5[2]=0x7F; out5[3]=0x40; out5[4]=0x00; break;
        case '2': out5[0]=0x42; out5[1]=0x61; out5[2]=0x51; out5[3]=0x49; out5[4]=0x46; break;
        case '3': out5[0]=0x21; out5[1]=0x41; out5[2]=0x45; out5[3]=0x4B; out5[4]=0x31; break;
        case '4': out5[0]=0x18; out5[1]=0x14; out5[2]=0x12; out5[3]=0x7F; out5[4]=0x10; break;
        case '5': out5[0]=0x27; out5[1]=0x45; out5[2]=0x45; out5[3]=0x45; out5[4]=0x39; break;
        case '6': out5[0]=0x3C; out5[1]=0x4A; out5[2]=0x49; out5[3]=0x49; out5[4]=0x30; break;
        case '7': out5[0]=0x01; out5[1]=0x71; out5[2]=0x09; out5[3]=0x05; out5[4]=0x03; break;
        case '8': out5[0]=0x36; out5[1]=0x49; out5[2]=0x49; out5[3]=0x49; out5[4]=0x36; break;
        case '9': out5[0]=0x06; out5[1]=0x49; out5[2]=0x49; out5[3]=0x29; out5[4]=0x1E; break;

        // Uppercase 'A'-'Z'
        case 'A': out5[0]=0x7E; out5[1]=0x11; out5[2]=0x11; out5[3]=0x11; out5[4]=0x7E; break;
        case 'B': out5[0]=0x7F; out5[1]=0x49; out5[2]=0x49; out5[3]=0x49; out5[4]=0x36; break;
        case 'C': out5[0]=0x3E; out5[1]=0x41; out5[2]=0x41; out5[3]=0x41; out5[4]=0x22; break;
        case 'D': out5[0]=0x7F; out5[1]=0x41; out5[2]=0x41; out5[3]=0x22; out5[4]=0x1C; break;
        case 'E': out5[0]=0x7F; out5[1]=0x49; out5[2]=0x49; out5[3]=0x49; out5[4]=0x41; break;
        case 'F': out5[0]=0x7F; out5[1]=0x09; out5[2]=0x09; out5[3]=0x09; out5[4]=0x01; break;
        case 'G': out5[0]=0x3E; out5[1]=0x41; out5[2]=0x49; out5[3]=0x49; out5[4]=0x3A; break;
        case 'H': out5[0]=0x7F; out5[1]=0x08; out5[2]=0x08; out5[3]=0x08; out5[4]=0x7F; break;
        case 'I': out5[0]=0x00; out5[1]=0x41; out5[2]=0x7F; out5[3]=0x41; out5[4]=0x00; break;
        case 'J': out5[0]=0x20; out5[1]=0x40; out5[2]=0x41; out5[3]=0x3F; out5[4]=0x01; break;
        case 'K': out5[0]=0x7F; out5[1]=0x08; out5[2]=0x14; out5[3]=0x22; out5[4]=0x41; break;
        case 'L': out5[0]=0x7F; out5[1]=0x40; out5[2]=0x40; out5[3]=0x40; out5[4]=0x40; break;
        case 'M': out5[0]=0x7F; out5[1]=0x02; out5[2]=0x0C; out5[3]=0x02; out5[4]=0x7F; break;
        case 'N': out5[0]=0x7F; out5[1]=0x04; out5[2]=0x08; out5[3]=0x10; out5[4]=0x7F; break;
        case 'O': out5[0]=0x3E; out5[1]=0x41; out5[2]=0x41; out5[3]=0x41; out5[4]=0x3E; break;
        case 'P': out5[0]=0x7F; out5[1]=0x09; out5[2]=0x09; out5[3]=0x09; out5[4]=0x06; break;
        case 'Q': out5[0]=0x3E; out5[1]=0x41; out5[2]=0x51; out5[3]=0x21; out5[4]=0x5E; break;
        case 'R': out5[0]=0x7F; out5[1]=0x09; out5[2]=0x19; out5[3]=0x29; out5[4]=0x46; break;
        case 'S': out5[0]=0x46; out5[1]=0x49; out5[2]=0x49; out5[3]=0x49; out5[4]=0x31; break;
        case 'T': out5[0]=0x01; out5[1]=0x01; out5[2]=0x7F; out5[3]=0x01; out5[4]=0x01; break;
        case 'U': out5[0]=0x3F; out5[1]=0x40; out5[2]=0x40; out5[3]=0x40; out5[4]=0x3F; break;
        case 'V': out5[0]=0x1F; out5[1]=0x20; out5[2]=0x40; out5[3]=0x20; out5[4]=0x1F; break;
        case 'W': out5[0]=0x7F; out5[1]=0x20; out5[2]=0x18; out5[3]=0x20; out5[4]=0x7F; break;
        case 'X': out5[0]=0x63; out5[1]=0x14; out5[2]=0x08; out5[3]=0x14; out5[4]=0x63; break;
        case 'Y': out5[0]=0x03; out5[1]=0x04; out5[2]=0x78; out5[3]=0x04; out5[4]=0x03; break;
        case 'Z': out5[0]=0x61; out5[1]=0x51; out5[2]=0x49; out5[3]=0x45; out5[4]=0x43; break;

        // Lowercase (覆盖本例需要)
        case 'a': out5[0]=0x20; out5[1]=0x54; out5[2]=0x54; out5[3]=0x54; out5[4]=0x78; break;
        case 'd': out5[0]=0x38; out5[1]=0x44; out5[2]=0x44; out5[3]=0x48; out5[4]=0x7F; break;
        case 'e': out5[0]=0x38; out5[1]=0x54; out5[2]=0x54; out5[3]=0x54; out5[4]=0x18; break;
        case 'l': out5[0]=0x00; out5[1]=0x41; out5[2]=0x7F; out5[3]=0x40; out5[4]=0x00; break;
        case 'o': out5[0]=0x38; out5[1]=0x44; out5[2]=0x44; out5[3]=0x44; out5[4]=0x38; break;
        case 's': out5[0]=0x48; out5[1]=0x54; out5[2]=0x54; out5[3]=0x54; out5[4]=0x20; break;
        case 't': out5[0]=0x04; out5[1]=0x3F; out5[2]=0x44; out5[3]=0x40; out5[4]=0x20; break;
        default: break; // 其它字符暂不显示
    }
}

// 显示一个字符(6列:5列字模 + 1列空白)
static void oled_draw_char(uint8_t page, uint8_t col, char c) {
    if (page >= OLED_PAGES || col >= OLED_WIDTH) return;
    uint8_t g[5]; glyph5x7(c, g);

    // 如超出右边界则截断
    int remain = OLED_WIDTH - col;
    uint8_t buf[6] = { g[0], g[1], g[2], g[3], g[4], 0x00 };
    int to_write = remain >= 6 ? 6 : remain;

    oled_set_pos(page, col);
    oled_write_data(buf, to_write);
}

// 在指定页与列显示字符串(超宽会自动换到下一页)
static void oled_show_text(uint8_t page, uint8_t col, const char* text) {
    uint8_t p = page;
    uint8_t x = col;
    for (const char* s = text; *s; ++s) {
        if (x + 6 > OLED_WIDTH) { // 换到下一页
            x = 0;
            p++;
            if (p >= OLED_PAGES) break;
        }
        oled_draw_char(p, x, *s);
        x += 6; // 每字 6 列(含1列间隔)
    }
}

int main(int argc, char** argv) {
    const char* dev = (argc >= 2) ? argv[1] : "/dev/i2c-2"; // 可传 /dev/i2c-2
    int addr = (argc >= 3) ? strtol(argv[2], NULL, 0) : OLED_I2C_ADDR;

    i2c_fd = open(dev, O_RDWR);
    if (i2c_fd < 0) {
        fprintf(stderr, "无法打开I2C设备 %s: %s\\n", dev, strerror(errno));
        return 1;
    }
    if (ioctl(i2c_fd, I2C_SLAVE, addr) < 0) {
        fprintf(stderr, "无法设置I2C地址 0x%02X: %s\\n", addr, strerror(errno));
        close(i2c_fd);
        return 1;
    }

    if (oled_init() < 0) {
        fprintf(stderr, "OLED 初始化失败\\n");
        close(i2c_fd);
        return 1;
    }

    oled_clear();
    usleep(1000 * 50);

    // 显示测试内容(隔一页便于行距:0,2,4)
    oled_show_text(0, 0, "Hello");
    oled_show_text(2, 0, "OK3506");
    oled_show_text(4, 0, "OLED Test");
    oled_show_text(6, 0, "xiaozhou");
    

    close(i2c_fd);
    return 0;
}

注意 :以上代码中的 oled_init 函数内的初始化命令序列并不完整。你需要根据OLED驱动芯片(通常是SSD1306)的数据手册或厂家提供的初始化代码,补充完整的初始化命令序列,屏幕才能正常显示内容。可以参考中关于SSD1306初始化命令的说明。

? 交叉编译与部署

交叉编译

由于OK3506板载Buildroot系统没有GCC,你需要在你的主机(例如x86电脑)上使用交叉编译工具链来编译这个程序。

  1. 安装交叉编译工具链 :在你的主机上安装针对ARM架构的交叉编译器,例如 gcc-arm-linux-gnueabihf
    • 在Ubuntu主机上:sudo apt-get install gcc-arm-linux-gnueabihf
  2. 编译程序 :使用交叉编译器编译你的OLED驱动代码。 强烈建议使用 -static 选项进行静态链接 ,这样可以避免板子上缺少动态库的问题。
arm-linux-gnueabihf-gcc -o oled_i2c oled_i2c.c -static

这将生成一个名为 oled_i2c 的可执行文件。

部署与测试

  1. 传输可执行文件 :使用U盘或者sd卡进行传输

`

  1. 在板子上运行 :通过串口或SSH登录OK3506,为程序添加执行权限并运行。
cd /root
chmod +x oled_i2c
./oled_i2c

最后的运行效果:
7173d5c1fa20b3a68ddd512d8cfd4fb.jpg

? 调试与优化

  • 排查I2C/SPI设备 :如果程序无法打开I2C或SPI设备,确认设备树中相应的控制器和用户设备(如 spidev)已正确启用,且用户有访问权限(如 /dev/i2c-1/dev/spidev1.0)。
  • 调整I2C地址 :如果I2C通信失败,尝试扫描I2C总线确认OLED地址。在板子上执行 i2cdetect -y 1(假设总线是 i2c-1),查看OLED设备的地址。
  • 优化性能 :如需提高刷新速度,可尝试提高I2C或SPI时钟频率(需在代码中配置),或优化显存更新机制。

更多回帖

发帖
×
20
完善资料,
赚取积分