[PIC®/AVR®/dsPIC®产品] 驱动SSD1306 0.96寸OLED的程序的编写步骤

[复制链接]
134|3
zhuotuzi 发表于 2025-10-16 14:51 | 显示全部楼层 |阅读模式
第一部分:使用 MCC 进行硬件配置
创建新项目

打开 MPLAB X IDE,创建一个新项目,选择 PIC18F16Q41 作为你的设备。

打开 MCC (MPLAB Code Configurator)

在项目上右键,选择 "Tools" -> "MPLAB Code Configurator v5..." 或者点击工具栏上的 MCC 图标。

配置系统时钟 (System Module)

在 "System Module" 中,选择一个合适的时钟源(例如,使用内部高频振荡器 HFINTOSC,如 64 MHz)。

确保系统时钟配置正确,外设需要稳定的时钟。

配置 I2C 外设

在 "Device Resources" 标签页中找到 "I2C" 驱动(可能是 I2C2 或 I2C1,取决于你的引脚选择)。

将其拖拽到你的项目图中。

在 "Pin Module" 中,确保 I2C 的 SDA 和 SCL 引脚被正确分配。例如:

SDA: RB2

SCL: RB1

在 I2C 的配置窗口中:

I2C Mode: Master mode

Speed: 100 kHz (标准模式,SSD1306 完全支持,如果需要可以提升到 400 kHz)

其他设置可以保持默认。

配置引脚 (Pin Module)

检查并确认所有引脚分配正确。对于 SSD1306,你只需要 I2C 的两个引脚。不需要复位引脚,我们可以用软件命令复位。

生成代码

点击 MCC 界面上的 "Generate" 按钮。MCC 将会根据你的配置生成初始化代码和相应的驱动程序。


 楼主| zhuotuzi 发表于 2025-10-16 14:53 | 显示全部楼层
第二部分:编写 SSD1306 驱动代码
MCC 生成了硬件底层的代码,现在我们还需要编写 SSD1306 本身的驱动逻辑。

在你的项目中,创建一个新的头文件和一个新的源文件,例如 ssd1306.h 和 ssd1306.c。

ssd1306.h
  1. #ifndef SSD1306_H
  2. #define        SSD1306_H

  3. #include <xc.h>
  4. #include "mcc_generated_files/i2c2.h" // 根据你实际使用的 I2C 模块修改

  5. // OLED 屏幕尺寸定义
  6. #define SSD1306_WIDTH            128
  7. #define SSD1306_HEIGHT           64

  8. // I2C 地址 (通常为 0x3C 或 0x3D,根据你的模块)
  9. #define SSD1306_I2C_ADDRESS      0x3C

  10. // 控制字节
  11. #define SSD1306_CONTROL_BYTE_CMD_SINGLE    0x80
  12. #define SSD1306_CONTROL_BYTE_CMD_STREAM    0x00
  13. #define SSD1306_CONTROL_BYTE_DATA_STREAM   0x40

  14. // 常用命令 (来自数据手册)
  15. #define SSD1306_CMD_DISPLAY_OFF            0xAE
  16. #define SSD1306_CMD_DISPLAY_ON             0xAF
  17. #define SSD1306_CMD_SET_DISPLAY_CLOCK_DIV  0xD5
  18. #define SSD1306_CMD_SET_MULTIPLEX          0xA8
  19. #define SSD1306_CMD_SET_DISPLAY_OFFSET     0xD3
  20. #define SSD1306_CMD_SET_START_LINE         0x40
  21. #define SSD1306_CMD_CHARGE_PUMP            0x8D
  22. #define SSD1306_CMD_MEMORY_MODE            0x20
  23. #define SSD1306_CMD_SEGREMAP               0xA1
  24. #define SSD1306_CMD_COMSCAN_DEC            0xC8
  25. #define SSD1306_CMD_COMSCAN_INC            0xC0
  26. #define SSD1306_CMD_SET_COMPINS            0xDA
  27. #define SSD1306_CMD_SET_CONTRAST           0x81
  28. #define SSD1306_CMD_SET_PRECHARGE          0xD9
  29. #define SSD1306_CMD_SET_VCOMDETECT         0xDB
  30. #define SSD1306_CMD_DISPLAYALLON_RESUME    0xA4
  31. #define SSD1306_CMD_DISPLAYALLON           0xA5
  32. #define SSD1306_CMD_NORMAL_DISPLAY         0xA6
  33. #define SSD1306_CMD_INVERT_DISPLAY         0xA7
  34. #define SSD1306_CMD_DEACTIVATE_SCROLL      0x2E
  35. #define SSD1306_CMD_ACTIVATE_SCROLL        0x2F
  36. #define SSD1306_CMD_COLUMN_ADDR            0x21
  37. #define SSD1306_CMD_PAGE_ADDR              0x22

  38. // 函数原型
  39. void SSD1306_WriteCommand(uint8_t command);
  40. void SSD1306_WriteData(uint8_t data);
  41. void SSD1306_Init(void);
  42. void SSD1306_SetCursor(uint8_t x, uint8_t y);
  43. void SSD1306_Clear(void);
  44. void SSD1306_Update(void); // 用于在图形模式下更新整个屏幕
  45. void SSD1306_DrawPixel(uint8_t x, uint8_t y, uint8_t color);
  46. void SSD1306_WriteChar(char ch);
  47. void SSD1306_WriteString(char* str);

  48. #endif        /* SSD1306_H */


ssd1306.c
  1. #include "ssd1306.h"

  2. // 显存,用于存储屏幕上每个像素的状态 (128x64 pixels / 8 = 1024 bytes)
  3. static uint8_t SSD1306_Buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8];

  4. // 向 SSD1306 发送一个命令
  5. void SSD1306_WriteCommand(uint8_t command) {
  6.     uint8_t data[2];
  7.     data[0] = SSD1306_CONTROL_BYTE_CMD_SINGLE;
  8.     data[1] = command;
  9.     I2C2_Write(SSD1306_I2C_ADDRESS, data, 2);
  10. }

  11. // 向 SSD1306 发送一个数据字节
  12. void SSD1306_WriteData(uint8_t data) {
  13.     uint8_t packet[2];
  14.     packet[0] = SSD1306_CONTROL_BYTE_DATA_STREAM;
  15.     packet[1] = data;
  16.     I2C2_Write(SSD1306_I2C_ADDRESS, packet, 2);
  17. }

  18. // 初始化 SSD1306
  19. void SSD1306_Init(void) {
  20.     // 给OLED足够的启动时间
  21.     __delay_ms(100);

  22.     // 一系列初始化命令序列
  23.     SSD1306_WriteCommand(SSD1306_CMD_DISPLAY_OFF);          // 关闭显示

  24.     SSD1306_WriteCommand(SSD1306_CMD_SET_DISPLAY_CLOCK_DIV); // 设置时钟分频
  25.     SSD1306_WriteCommand(0x80);                              // 建议值

  26.     SSD1306_WriteCommand(SSD1306_CMD_SET_MULTIPLEX);        // 设置多路复用比例
  27.     SSD1306_WriteCommand(SSD1306_HEIGHT - 1);               // 对于 64 像素高的屏幕是 63

  28.     SSD1306_WriteCommand(SSD1306_CMD_SET_DISPLAY_OFFSET);   // 设置显示偏移
  29.     SSD1306_WriteCommand(0x00);                             // 无偏移

  30.     SSD1306_WriteCommand(SSD1306_CMD_SET_START_LINE | 0x00);// 设置起始行号为 0

  31.     SSD1306_WriteCommand(SSD1306_CMD_CHARGE_PUMP);          // 启用电荷泵
  32.     SSD1306_WriteCommand(0x14);                             // 0x14 启用,0x10 禁用

  33.     SSD1306_WriteCommand(SSD1306_CMD_MEMORY_MODE);          // 设置内存模式
  34.     SSD1306_WriteCommand(0x00);                             // 水平寻址模式

  35.     SSD1306_WriteCommand(SSD1306_CMD_SEGREMAP | 0x01);      // 段重映射,0xA1 翻转
  36.     SSD1306_WriteCommand(SSD1306_CMD_COMSCAN_DEC);          // 扫描方向重映射,0xC8 翻转

  37.     SSD1306_WriteCommand(SSD1306_CMD_SET_COMPINS);          // 设置硬件引脚配置
  38.     SSD1306_WriteCommand(0x12);                             // 对于 64 像素高的屏幕是 0x12

  39.     SSD1306_WriteCommand(SSD1306_CMD_SET_CONTRAST);         // 设置对比度
  40.     SSD1306_WriteCommand(0xCF);                             // 对比度值

  41.     SSD1306_WriteCommand(SSD1306_CMD_SET_PRECHARGE);        // 设置预充电周期
  42.     SSD1306_WriteCommand(0xF1);                             // 建议值

  43.     SSD1306_WriteCommand(SSD1306_CMD_SET_VCOMDETECT);       // 设置 VCOMH 反压等级
  44.     SSD1306_WriteCommand(0x40);                             // 建议值

  45.     SSD1306_WriteCommand(SSD1306_CMD_DISPLAYALLON_RESUME);  // 不使用整个显示点亮
  46.     SSD1306_WriteCommand(SSD1306_CMD_NORMAL_DISPLAY);       // 非反色显示

  47.     SSD1306_WriteCommand(SSD1306_CMD_DEACTIVATE_SCROLL);    // 关闭滚动

  48.     SSD1306_WriteCommand(SSD1306_CMD_DISPLAY_ON);           // 开启显示!

  49.     // 清空显存并更新显示
  50.     SSD1306_Clear();
  51.     SSD1306_Update();
  52. }

  53. // 清空显存 (清屏)
  54. void SSD1306_Clear(void) {
  55.     for (uint16_t i = 0; i < sizeof(SSD1306_Buffer); i++) {
  56.         SSD1306_Buffer[i] = 0x00;
  57.     }
  58. }

  59. // 设置光标位置 (用于文本模式,简单实现)
  60. void SSD1306_SetCursor(uint8_t x, uint8_t y) {
  61.     // 这个函数在基础图形驱动中不是必须的,但可以用于定位文本
  62.     // 更完善的实现需要管理光标坐标
  63. }

  64. // 将整个显存发送到 SSD1306
  65. void SSD1306_Update(void) {
  66.     // 设置列地址范围 (0 到 127)
  67.     SSD1306_WriteCommand(SSD1306_CMD_COLUMN_ADDR);
  68.     SSD1306_WriteCommand(0);
  69.     SSD1306_WriteCommand(SSD1306_WIDTH - 1);

  70.     // 设置页地址范围 (0 到 7,因为 64/8=8)
  71.     SSD1306_WriteCommand(SSD1306_CMD_PAGE_ADDR);
  72.     SSD1306_WriteCommand(0);
  73.     SSD1306_WriteCommand(7);

  74.     // 准备发送数据流
  75.     uint8_t control_byte = SSD1306_CONTROL_BYTE_DATA_STREAM;
  76.     I2C2_Write(SSD1306_I2C_ADDRESS, &control_byte, 1); // 发送控制字节

  77.     // 发送整个显存
  78.     // 注意:I2C2_Write 可能需要分块发送,如果一次发送太多,请循环发送
  79.     I2C2_Write(SSD1306_I2C_ADDRESS, SSD1306_Buffer, sizeof(SSD1306_Buffer));
  80. }

  81. // 画一个像素点 (这是图形驱动的基础)
  82. void SSD1306_DrawPixel(uint8_t x, uint8_t y, uint8_t color) {
  83.     if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) {
  84.         return; // 超出范围
  85.     }

  86.     uint16_t index = x + (y / 8) * SSD1306_WIDTH;
  87.     if (color) {
  88.         SSD1306_Buffer[index] |= (1 << (y % 8));  // 点亮像素
  89.     } else {
  90.         SSD1306_Buffer[index] &= ~(1 << (y % 8)); // 熄灭像素
  91.     }
  92. }

  93. // 注意:WriteChar 和 WriteString 需要一个字体库,这里只是一个框架。
  94. // 你需要自己实现一个字体数组 (例如 5x7 字体)。
  95. /*
  96. typedef struct {
  97.     uint8_t width;
  98.     uint8_t height;
  99.     const uint8_t *data;
  100. } FontDef;

  101. extern FontDef Font_5x7;
  102. */

  103. void SSD1306_WriteChar(char ch) {
  104.     // 实现需要字体库
  105.     // 1. 根据字符查找字体数据
  106.     // 2. 将字体数据的每一位绘制到显存中
  107.     // 3. 更新光标位置
  108. }

  109. void SSD1306_WriteString(char* str) {
  110.     // 循环调用 SSD1306_WriteChar
  111.     while (*str) {
  112.         SSD1306_WriteChar(*str++);
  113.     }
  114. }
 楼主| zhuotuzi 发表于 2025-10-16 14:53 | 显示全部楼层
第三部分:主程序 (main.c)
  1. #include "mcc_generated_files/system/system.h"
  2. #include "ssd1306.h" // 包含我们的驱动头文件

  3. /*
  4.     Main application
  5. */

  6. int main(void) {
  7.     SYSTEM_Initialize();

  8.     // 初始化 OLED 显示屏
  9.     SSD1306_Init();

  10.     // 简单的演示:画几个像素点
  11.     SSD1306_DrawPixel(0, 0, 1);       // 左上角
  12.     SSD1306_DrawPixel(127, 0, 1);     // 右上角
  13.     SSD1306_DrawPixel(0, 63, 1);      // 左下角
  14.     SSD1306_DrawPixel(127, 63, 1);    // 右下角

  15.     // 画一条对角线
  16.     for (uint8_t i = 0; i < 64; i++) {
  17.         SSD1306_DrawPixel(i*2, i, 1);
  18.     }

  19.     // 将显存更新到屏幕上
  20.     SSD1306_Update();

  21.     while(1) {
  22.         // 主循环,可以在这里添加其他逻辑
  23.         // 例如,动态更新显示内容
  24.     }   
  25. }
 楼主| zhuotuzi 发表于 2025-10-16 14:53 | 显示全部楼层
引脚连接: 确保你的 PIC18F16Q41 的 I2C 引脚(如 RB1, RB2)正确连接到 SSD1306 的 SCL 和 SDA。同时连接 VCC 和 GND。

I2C 地址: 确认你的 SSD1306 模块的 I2C 地址是 0x3C,有些可能是 0x3D。

上拉电阻: I2C 总线上(SDA 和 SCL)需要接上拉电阻(通常 4.7kΩ)。

字体库: 上面的代码只实现了基本的图形功能(画点)。要显示文字,你需要额外添加一个字体库并实现 WriteChar 函数。

I2C 写入函数: 确保 I2C2_Write 函数能够处理单次发送 1024 字节的显存数据。如果不行,你需要在 SSD1306_Update 函数中将其分块发送(例如,每次发送 32 或 64 字节)。

调试: 如果屏幕不亮,首先用逻辑分析仪或示波器检查 I2C 线上是否有信号,或者通过点亮 LED 等方式确认程序是否正常运行。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

217

主题

3393

帖子

7

粉丝
快速回复 在线客服 返回列表 返回顶部