关于SSD1306的OLED的显示的研究

文章目录

本文从内存的显示,缓存的读写来进行研究

python 复制代码
class SSD1306(object):

  def __init__(self, pinout, height=32, external_vcc=True, i2c_devid=DEVID):
    self.external_vcc = external_vcc
    self.height  = 32 if height == 32 else 64
    
    self.pages   = int(self.height / 8) 
    self.columns  = 128
    self.i2c = pyb.I2C(1)
    self.i2c.init(pyb.I2C.MASTER, baudrate=400000) # 400kHz
    self.devid = i2c_devid

    self.offset = 1
    self.cbuffer = bytearray(2)
    self.cbuffer[0] = CTL_CMD

  def clear(self):
    self.buffer = bytearray(self.offset + self.pages * self.columns)
    if self.offset == 1:
      self.buffer[0] = CTL_DAT
  def display(self):
    self.write_command(COLUMNADDR)
    self.write_command(0)
    self.write_command(self.columns - 1)
    self.write_command(PAGEADDR)
    self.write_command(0)
    self.write_command(self.pages - 1)
    if self.offset == 1:
      self.i2c.send(self.buffer, addr=self.devid, timeout=5000)
    else:
      self.dc.high()
      self.spi.send(self.buffer)

  def set_pixel(self, x, y, state):
    index = x + (int(y / 8) * self.columns)
    if state:
      self.buffer[self.offset + index] |= (1 << (y & 7))
    else:
      self.buffer[self.offset + index] &= ~(1 << (y & 7))

对于这段代码,我们可以知道,OLED设置了两个缓冲区,一个是命令缓冲区cbuffer ,一个是buffer数据缓冲区。

在读写数据前,我们要使用clear函数,来创建缓冲区,

self.buffer = bytearray(self.offset + self.pages * self.columns)

我们来粗略的计算一下这个buffer。

8*128+1 =1025

绘制字体的代码,需要

python 复制代码
# 计算真实的x坐标,怎么计算
#初始位置x,第几个字符*放缩尺寸*字符的列数,space * char_number间隔
char_offset = x + char_number * size * font.cols + space * char_number
#像素的偏移,除了char_offset,还有字符内部是第几列(这个是x的偏移乘以放缩尺寸),Point_row是尺寸增加的额外补全点
pixel_offset = char_offset + char_column * size + point_row
#计算出最后的坐标,使用128减之后,代表
    return 128 - pixel_offset
python 复制代码
 def draw_text(self, x, y, string, size=1, space=1):
    def pixel_x(char_number, char_column, point_row):
      char_offset = x + char_number * size * font.cols + space * char_number
      pixel_offset = char_offset + char_column * size + point_row
      return self.columns - pixel_offset

    def pixel_y(char_row, point_column):
      char_offset = y + char_row * size
      return char_offset + point_column

    def pixel_mask(char, char_column, char_row):
      char_index_offset = ord(char) * font.cols
      #取到对应的font.py数组
     
      return font.bytes[char_index_offset + char_column] >> char_row & 0x1
      # 0x1 是二进制的 00000001。& 运算符是位与运算符,它的作用是从字节的最低位提取出那一位的值(0 或 1)。
		# & 0x1 操作确保我们只关注字节的最低位,判断这一位是 1(点亮)还是 0(不点亮)。

    pixels = (
      (pixel_x(char_number, char_column, point_row),#(第几个字母,字母列定位,生成多个(尺寸的点))放大尺寸
       pixel_y(char_row, point_column),#
       pixel_mask(char, char_column, char_row))
      for char_number, char in enumerate(string)
      for char_column in range(font.cols)#cols=5
      for char_row in range(font.rows)#rows=8
      for point_column in range(size)
      for point_row in range(1, size + 1))

    for pixel in pixels:
      self.set_pixel(*pixel)

这个 draw_text 函数的作用是在 OLED 显示屏上显示文本。它通过将文本字符串中的每个字符逐个绘制到显示屏上,模拟了字符的像素显示效果。以下是对该函数的详细分析:

函数作用

draw_text 负责将字符串中的每个字符逐个绘制到 OLED 显示屏上。它根据字符的大小、间距以及具体的像素位置,计算出每个字符应该显示的像素位置,并调用 set_pixel 来设置相应的像素点。

参数解释

  • x:文本的起始 x 坐标,表示文本在屏幕的水平位置。
  • y:文本的起始 y 坐标,表示文本在屏幕的垂直位置。
  • string:要显示的字符串。
  • size:字符的大小缩放比例,默认为 1,表示原始大小。
  • space:字符之间的间隔,默认为 1。

嵌套函数分析

  1. pixel_x

    • 计算当前字符的像素 x 坐标。
    • 参数:
      • char_number: 当前字符在字符串中的位置。
      • char_column: 当前字符的列位置(表示字符的某一列像素)。
      • point_row: 字符点阵的某一行的像素。
    • 公式解释:
      • char_offset: 基于 x 坐标和字符的编号、大小、间距,计算当前字符的偏移量。
      • pixel_offset: 计算字符在列方向上的像素偏移量。self.columns 用来确保不超出显示屏的宽度。
  2. pixel_y

    • 计算当前字符的像素 y 坐标。
    • 参数:
      • char_row: 当前字符在点阵中的行数。
      • point_column: 点阵某一列的像素点。
    • 公式解释:
      • char_offset: 基于 y 坐标和字符的行数、大小,计算字符在垂直方向的偏移量。
      • 最终返回的是这个字符的像素点在显示屏中的 y 坐标。
  3. pixel_mask

    • 获取当前字符在某一行某一列的像素点是亮(1)还是灭(0)。
    • 参数:
      • char: 当前字符。
      • char_column: 当前字符的列位置。
      • char_row: 当前字符的行位置。
    • 通过 ord(char) 获取字符的 ASCII 编码,然后从字体数组中找到该字符的位图信息,具体到每一个像素点。

主代码分析

  • pixels = (...)

    • 这是一个生成器表达式,它遍历字符串中的每个字符,逐行逐列地处理字符的每个像素点。每个字符由 font.cols 列和 font.rows 行组成。
    • 它遍历每个字符的所有像素点,并调用上面定义的 pixel_x, pixel_y, pixel_mask 来确定每个像素点的 x 和 y 坐标,以及它是否点亮(通过 pixel_mask 函数)。
    • 该生成器最终生成一个包含了所有像素信息的三元组 (pixel_x, pixel_y, pixel_mask),并将其保存为 pixels
  • for pixel in pixels:

    • 遍历生成的所有像素点,并调用 self.set_pixel() 来设置 OLED 显示屏上相应位置的像素点。
    • self.set_pixel(*pixel):将 pixel 作为位置参数解包,传入 set_pixel(),从而在 OLED 上设置对应的像素。

逻辑流程

  1. 遍历字符串 :函数首先遍历传入的字符串 string,并对每个字符进行处理。
  2. 逐列处理字符像素 :每个字符由多个列组成,每列包含多个像素点。通过 char_columnchar_row 逐个遍历字符的点阵。
  3. 获取像素信息 :通过 pixel_xpixel_ypixel_mask 获取字符中每个像素点的位置和状态(亮或灭)。
  4. 绘制到显示屏 :通过 self.set_pixel() 将每个像素点绘制到 OLED 显示屏上。

总结

draw_text 函数的主要目的是在 OLED 显示屏上绘制文本字符串。它通过遍历每个字符的像素点阵信息,将字符的像素映射到显示屏的相应位置,并通过设置像素来显示文本内容。

如果需要在显示屏上显示不同大小、不同间隔的文本,或者需要精确控制字符在屏幕上的位置,可以通过修改 sizespace 参数来实现。

难点的解析:

生成器的代码:

这个生成器表达式 pixels = (...) 是一个复杂的多重循环,它遍历了字符串中的每个字符、字符的每个列、每列的每一行、行中的每一个像素点,然后计算出每个像素点的 x 坐标、y 坐标,以及该像素点是否点亮(通过 pixel_mask 函数决定)。

生成器的主要逻辑分解:

这个生成器由多个嵌套的 for 循环构成。它的执行过程可以分为以下几个步骤:

  1. for char_number, char in enumerate(string)
  • 这是最外层的循环,遍历字符串 string 中的每个字符。
  • char_number:字符在字符串中的索引,用于计算字符在屏幕上的横向位置。
  • char:当前字符。
  1. for char_column in range(font.cols)
  • 这是第二层循环,遍历当前字符在字体点阵中的列。font.cols 是字符的列数(即字符的宽度,以像素为单位),通常一个字符在点阵字体中是 5 列。
  • char_column:当前列的索引,用于计算字符点阵中的列像素。
  1. for char_row in range(font.rows)
  • 这是第三层循环,遍历当前列中字符的行。font.rows 是字符的行数(即字符的高度,以像素为单位),通常一个字符在点阵字体中有 8 行。
  • char_row:当前行的索引,用于计算字符点阵中的行像素。
  1. for point_column in range(size)

    • 这是第四层循环,用于处理字符放大(缩放)时的列像素扩展。size 是字符的缩放大小,range(size) 表示在 x 轴上重复 size 次,以模拟字符的放大效果。
    • point_column:缩放列的索引,用于实现字符的宽度放大。
  2. for point_row in range(1, size + 1)

  • 这是第五层循环,类似于 point_column,用于处理字符放大时的行像素扩展。在 y 轴上重复 size 次,以实现字符的高度放大。
  • point_row:缩放行的索引,用于实现字符的高度放大。

每次生成的元组 (pixel_x, pixel_y, pixel_mask)

这三个值是通过每层循环计算出来的,它们表示当前像素点的 x 坐标、y 坐标和该像素点是否点亮的状态。每次生成一个这样的三元组,传递给 set_pixel() 方法,来在屏幕上绘制这个像素点。

  • pixel_x(char_number, char_column, point_row)

    通过字符的索引 char_number 和当前列索引 char_column,结合字符缩放后的列索引 point_row 计算出这个像素点在屏幕上的 x 坐标。

  • pixel_y(char_row, point_column)

    通过字符的行索引 char_row 和缩放后的行索引 point_column 计算出这个像素点在屏幕上的 y 坐标。

  • pixel_mask(char, char_column, char_row)

    通过字符的 ASCII 值 char 以及字符点阵中的行列位置来确定这个像素点是否需要点亮(返回 1)或不点亮(返回 0)。

生成器的整体流程

假设我们有一个字符串 "A",字体点阵为 5 列 8 行,size = 2,那么生成器的执行流程大致如下:

  1. for char_number, char in enumerate("A")

    • 遍历字符串 "A" 中的字符,当前字符是 "A"char_number = 0
  2. for char_column in range(font.cols)

    • 遍历 "A" 的点阵列,假设 "A" 是 5 列字符,这个循环会从 0 遍历到 4
  3. for char_row in range(font.rows)

    • 遍历 "A" 每列的点阵行,假设 "A" 是 8 行字符,这个循环会从 0 遍历到 7
  4. for point_column in range(size)

    • 假设 size = 2,表示字符需要放大 2 倍,这里会重复每列两次。
  5. for point_row in range(1, size + 1)

    • 同样地,字符的每一行也会放大 2 倍,行像素重复 2 次。

举例

假设 "A" 的点阵如下(5x8 字符):

01110
10001
10001
11111
10001
10001
10001
10001

生成器会依次生成 "A" 的每个像素点的坐标和亮度信息。通过缩放,每个原始像素点都会被放大 2 倍,从而使 "A" 的像素图显示更大。

总结

这个生成器的作用是通过多重嵌套循环遍历字符串中的每个字符,并处理字符的点阵像素信息。在考虑字符的大小缩放和位置偏移的基础上,生成对应的屏幕坐标和像素状态,最后通过 set_pixel() 绘制到 OLED 显示屏上。

一个例子:

python 复制代码
cols = 5
rows = 8
bytes = [
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x3E, 0x5B, 0x4F, 0x5B, 0x3E,

0x是一个八位的数,就是一列。每一行,就是一个字符的表示。

0x3E -> 00111110

0x5B -> 01011011

0x4F -> 01001111

0x5B -> 01011011

0x3E -> 00111110

00111110 -> ******

01011011 -> * ** **

01001111 -> * ****

01011011 -> * ** **

00111110 -> ******

如上图表示。所以每个字符都是这样保存和取用的。

反转后的文本绘制

修改过上下翻转的字符绘制:

python 复制代码
#可以修改一下函数名
# draw_text_normal()
 def draw_text(self, x, y, string, size=1, space=1):
    def pixel_x(char_number, char_column, point_row):
      char_offset = x + char_number * size * font.cols + space * char_number
      pixel_offset = char_offset + char_column * size + point_row
      # return self.columns - pixel_offset
      return pixel_offset    *************************************************************
    def pixel_y(char_row, point_column):
      #char_offset = y + char_row * size
      #另一种布局,修改一下坐标
      #原来的布局
      # char_offset=y+char_row*size
      char_offset = y + (font.rows - 1 - char_row) * size***************************
      #y垂直方向不设置间隔
      # return char_offset + point_column
      return  char_offset + point_column

    def pixel_mask(char, char_column, char_row):
      char_index_offset = ord(char) * font.cols
      return font.bytes[char_index_offset + char_column] >> char_row & 0x1

    pixels = ( 
      (pixel_x(char_number, char_column, point_row),
       pixel_y(char_row, point_column),
       pixel_mask(char, char_column, char_row))
      for char_number, char in enumerate(string)
      for char_column in range(font.cols)
      for char_row in range(font.rows)
      for point_column in range(size)
      for point_row in range(1, size + 1))

    for pixel in pixels:
      self.set_pixel(*pixel)

修改了星标这两个地方。

竖直布局

为了实现竖直布局的书写,我们的屏幕从(128,64)->(64,128)

我们不需要修改mask的部分,只需要把,求y和x的逻辑进行交换就可以。

python 复制代码
def draw_ylayout(self, x, y, string, size=1, space=1):
    def pixel_y(char_number,char_column, point_row):
        char_offset = y + char_number * size * font.cols+space * char_number
        pixel_offset =char_offset+char_column * size + point_row
        #y垂直方向不设置间隔
        # return char_offset + point_column
        return  pixel_offset
    def pixel_x( char_row , point_column):
        #行的距离域(128->64)偏移从(5->8)s
          char_offset = x +  char_row * size
          # return self.columns - pixel_offset
          return char_offset + point_column    
    def pixel_mask(char, char_column, char_row):
        char_index_offset = ord(char) * font.cols
        return font.bytes[char_index_offset + char_column] >> char_row & 0x1
    pixels = ((pixel_x(char_row, point_column),
          pixel_y(char_number, char_column, point_row),
          pixel_mask(char, char_column, char_row))
          for char_number, char in enumerate(string)
          for char_column in range(font.cols)
          for char_row in range(font.rows)
          for point_column in range(1, size + 1)
          for point_row in range(size))
          
    for pixel in pixels:
        self.set_pixel(*pixel)

本来本文尝试实现90度旋转,但是因为最终需要的模式不同,非方阵,矩阵的旋转很容易出现溢出。尝试了几个生成的代码,没有比较满意的。

下面这个应该有错,但现在不想探索了。

有问题的旋转

python 复制代码
def rotate_buffer(self):
    # 旋转后的缓冲区
    rotated_buffer = bytearray(1025)  # 保留控制字节
    rotated_buffer[0] = self.buffer[0]  # 保留控制字节
    
    for x in range(128):
        for y in range(64):
            # 计算原缓冲区中的位置
            original_index = 1 + (x * 8) + (y // 8)
            original_bit = (self.buffer[original_index] >> (y % 8)) & 0x01
            
            # 计算旋转后的位置
            new_x = y  # 90度旋转,新列
            new_y = 127 - x  # 反转行
            
            # 计算新缓冲区中的位置
            new_index = 1 + (new_x * 8) + (new_y // 8)
            if original_bit:  # 如果当前像素为1
                rotated_buffer[new_index] |= (1 << (new_y % 8))

    # 用旋转后的缓冲区替换原缓冲区
    self.buffer = rotated_buffer

180度旋转

旋转180度的反转代码是好用的。

python 复制代码
def rotate_180(self):
    # 创建一个新的缓冲区来存储反转后的内容
    new_buffer = bytearray(self.offset + self.pages * self.columns)
    
    # 复制控制字节
    new_buffer[0] = self.buffer[0]
    
    # 反转每个字节和行的顺序
    for page in range(self.pages):  # 这里 pages 是 8,因为屏幕高度为 64 像素
        for column in range(self.columns):  # 这里 columns 是 128
            # 计算反转后的索引
            original_index = 1 + page * self.columns + column  # 原始索引
            new_index = 1 + (self.pages - 1 - page) * self.columns + (self.columns - 1 - column)  # 新索引
            
            # 将字节反转并赋值到新的缓冲区
            new_buffer[new_index] = self.buffer[original_index] ^ 0xFF  # 反转字节

    # 更新原始缓冲区
    self.buffer = new_buffer

代码并不难,但是旋转感觉自己绕来绕去,以后要多写文档,理清楚思路。180旋转直接就可以替代反转后的文本绘制

额外的绘制函数坐标轴。

坐标轴

绘制了正方向,设置了一下角度,按照原来的。

可调参数不多,需要进一步调整。

python 复制代码
def draw_axes(self, origin_x=None, origin_y= None, length_x=None, length_y=None):
          # 绘制 X 轴
      if origin_x is None:
        origin_x = self.columns-38
      if origin_y is None:
        origin_y = self.height-10

      # 默认 X 轴长度为屏幕宽度
      if length_x is None:
          length_x = self.columns

      # 默认 Y 轴长度为屏幕高度
      if length_y is None:
          length_y = self.height

      for x in range(1, origin_x):
          self.set_pixel(x, origin_y, 1)
      # 绘制 Y 轴
      for y in range(origin_y, origin_y - length_y, -1):
          self.set_pixel(origin_x, y, 1)
      #绘制单位
      self.draw_text(110,56,"X/s")
      self.draw_text(1, 1, "Y/d")
      #绘制横的三角
      self.draw_triangle(0,54,5,1)
      self.draw_triangle(90, 0, 5, 0)
      #绘制竖的三角
    #是线,不是填充的。
  def draw_triangle(self, x, y, size=2,direction=1):
#认为是横的三角

    if 1 == direction:
       for i in range(1, size + 1):
          self.set_pixel(x + i, y, 1)
          self.set_pixel(x + i, y - i, 1)
          self.set_pixel(x + i, y + i, 1) 
    
    else:
      #认为是竖的三角
      for i in range(1, size + 1):
          self.set_pixel(x, y + i, 1)
          self.set_pixel(x - i, y + i, 1)
          self.set_pixel(x + i, y + i, 1)

绘制矩形

python 复制代码
  def draw_rectangle(self, x, y, width, height, filled=True):
    """
    绘制一个方块或矩形
    :param x: 方块左上角的 X 坐标
    :param y: 方块左上角的 Y 坐标
    :param width: 方块的宽度
    :param height: 方块的高度
    :param filled: 是否填充方块(True 为填充,False 为仅绘制边框)
    """
    # 绘制填充的方块
    if filled:
        for i in range(x, x + width):
            for j in range(y, y + height):
                self.set_pixel(i, j, 1)
    else:
        # 绘制空心方块(仅边框)
        # 上边框
        for i in range(x, x + width):
            self.set_pixel(i, y, 1)
        # 下边框
        for i in range(x, x + width):
            self.set_pixel(i, y + height - 1, 1)
        # 左边框
        for j in range(y, y + height):
            self.set_pixel(x, j, 1)
        # 右边框
        for j in range(y, y + height):
            self.set_pixel(x + width - 1, j, 1)

    # 显示绘制的方块
    self.display()

可以利用这个来绘制直方图。

绘制曲线(正弦波)

python 复制代码
import math#记得添加这个库
 def draw_curve(self, start_x=98, start_y=32, step=-1):
    #可绘制的区域x[98,0],y[54,0]数越大,绘制的是越小,或者说越早的值
    # """
    # 从 x = 98 处开始动态绘制曲线,x 递减到 0 时重新更新显示器并从新开始绘制。
    # :param start_x: 起始 x 坐标,默认为 98
    # :param start_y: 起始 y 坐标,默认为屏幕中间
    # :param step: 每次 x 变化的步长,默认为 -1(向左绘制)
    # """
    x = start_x
    y = start_y

    while x >= 0:
        # 根据某个逻辑更新 y 值(这里示例使用正弦函数进行动态曲线绘制)
        y = int(32 + 10 * math.sin(x / 10))

        # 绘制当前像素
        self.set_pixel(x, y, 1)

        # 更新 x 值
        x += step

        # 如果 x 达到 0,刷新显示器并清空屏幕
        if x < 0:
            # 刷新显示内容
            self.display()

            # 清空显示屏以重新绘制曲线
            self.clear()
            #绘制坐标轴
            self.draw_axes()

            # 重新设置 x 为 98,重新开始绘制曲线
            x = 98

        # 显示绘制的点
        self.display()

控制命令

python 复制代码
  def poweron(self):
    if self.offset == 1:
      pyb.delay(10)
    else:
      self.res.high()
      pyb.delay(1)
      self.res.low()
      pyb.delay(10)
      self.res.high()
      pyb.delay(10)

  def poweroff(self):
    self.write_command(DISPLAYOFF)

  def contrast(self, contrast):
    self.write_command(SETCONTRAST)
    self.write_command(contrast)

屏幕的滚动

python 复制代码
  def write_command(self, command_byte):
    self.cbuffer[1] = command_byte
    self.i2c.send(self.cbuffer, addr=self.devid, timeout=5000)
python 复制代码
  def setup_scroll(self):
    # Step 1: 停止任何正在进行的滚动
    self.write_command(DEACTIVATE_SCROLL)

    # Step 2: 设置水平滚动,从右向左滚动
    self.write_command(LEFT_HORIZONTAL_SCROLL)  # 左水平滚动
    self.write_command(0x00)  # 虚拟字节
    self.write_command(0x00)  # 起始页地址 (0表示第1页)
    self.write_command(0x00)  # 设置滚动速度,0x00 是较快滚动
    self.write_command(0x07)  # 结束页地址 (7表示第8页)
    self.write_command(0x00)  # 无垂直偏移
    self.write_command(0xFF)  # 无垂直偏移

就先记录到这里。

相关推荐
engchina3 分钟前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
枯无穷肉1 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶1 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
不过四级不改名6771 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普2 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
汪洪墩2 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
云山工作室2 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
程序员shen1616113 小时前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法