Luckfox开发板移植0.96寸oled屏幕—应用层篇

1 前言

  应用层的程序比较简单,不像底层驱动包含的知识太多,会C语言就能看懂。官方的程序为了兼容设备分了很多层,有很多用不上的代码,所以我删除了一些代码,又重写了一部分代码的逻辑。
  简化后的代码只需要搞清楚spi传递参数的流程、oled屏幕点亮的逻辑、显示线段和圆的逻辑、显示中文的逻辑就行了。

2 应用层dc和rst引脚的重写

  这里重写了rst和dc引脚的的驱动,所以应用层的控制逻辑也对于修改了。如下是新的控制代码。

2.1 根据引脚编号配置宏

//gpio引脚
#define OLED_RST_PIN 51  //复位引脚
#define OLED_DC_PIN 34  //数据和命令切换引脚

2.2 控制引脚初始化硬件。

static void oled_reset(void)
{
    dev_digital_write(OLED_RST_PIN, '1'); // 启动复位
    delay_ms(100);
    dev_digital_write(OLED_RST_PIN, '0'); // 关闭复位
    delay_ms(100);
    dev_digital_write(OLED_RST_PIN, '1'); // 启动复位
}

2.3 打开驱动传递参数

  追踪dev_digital_write函数,跳转至操控驱动传输数据的函数。这里使用系统调用的方式打开驱动文件,向内核传递参数,由内核驱动控制引脚的电平变化。

int dev_digital_write(uint16_t pin, uint8_t value)
{
    int fd, ret = -1;
    do
    {
        if (pin != OLED_RST_PIN && pin != OLED_DC_PIN)
        {
            break;
        }
        if ((fd = open(pin == OLED_RST_PIN ? "/dev/gpiodevrst" : "/dev/gpiodevdc", O_RDWR)) < 0)
        {
            printf("打开文件失败\r\n");
            break;
        }
        if (write(fd, &value, sizeof(value)) < 0)
        {
            printf("dc写数据失败\r\n");
            break;
        }
        if (fd > 0)
        {
            close(fd);
        }
        ret = 0;
    } while (0);
    return ret;
}

  上面就是简化后的控制dc和rst的流程了,对比sysfs操作代码量和操作逻辑都简化很多。

3 spi传输数据

  应用层的spi传输数据的整个流程逻辑很清晰合理,没必要更改,我只做了注释和逻辑梳理。

3.1 定义spi的变量

  使用spi传输数据前要配置spi的传输速率、模式、发送时间间隔等参数。如下是根据需求配置的各类参数。

struct spi_ioc_transfer tr; // 官方配的spi传输数据结构体

// /***************************
//  *
//  * struct spi_ioc_transfer 
//  *__u64 tx_buf; 写数据缓冲  
//  *__u64 rx_buf;  读数据缓冲  
//  *__u32 len;       缓冲的长度 
//  *__u32 speed_hz; 通信的时钟频率 
//  *__u16 delay_usecs;  两个spi_ioc_transfer之间的延时 
//  *__u8 bits_per_word;  字长(比特数)  
//  *__u8 cs_change;      是否改变片选 
//  *__u32 pad;
// *********************************/

#define SPI_CPHA 0x01
#define SPI_CPOL 0x02

#define mySPI_MODE_0 (0 | 0)
#define mySPI_MODE_1 (0 | SPI_CPHA)
#define mySPI_MODE_2 (SPI_CPOL | 0)
#define mySPI_MODE_3 (SPI_CPOL | SPI_CPHA)

typedef enum{
  SPI_MODE0 = mySPI_MODE_0, // 00
  SPI_MODE1 = mySPI_MODE_1, // 01
  SPI_MODE2 = mySPI_MODE_2, // 10
  SPI_MODE3 = mySPI_MODE_3  // 11
} SPIMode;

    // 定义spi属性
typedef struct SPIstruct{
  uint16_t SCLK_PIN;
  uint16_t MOSI_PIN;
  uint16_t MISO_PIN;

  uint16_t CS0_PIN;
  uint16_t CS1_PIN;

  uint32_t speed;
  uint16_t mode;
  uint16_t delay;
  int fd;
} HARDWARE_SPI;

typedef enum
{
  SPI_CS_MODE_LOW = 0,  // 片选0
  SPI_CS_MODE_HIGH = 1, // 片选1
  SPI_CS_MODE_NONE = 3  // 没有片选,自己控制

} SPIChipSelect;

HARDWARE_SPI hardware_spi;

static uint8_t bits = 8;

#define SPI_CS_HIGH 0x04 // 芯片选高
// #define SPI_LSB_FIRST
// #define SPI_3WIRE
//#define SPI_LOOP
#define SPI_NO_CS 0x40 // 单个设备占用一条SPI总线,没有芯片选择
#define SPI_READY 0x80 // 从机拉低以停止数据传输

3.2 spi传输数据的方式

  使用ioctl往内核传递参数时,内核需要知道这个参数的作用是什么才能往下配置,所以标志位的需求诞生了。如下所示ioctl的第二个参数是官方定义的标志位,有如下几种:

SPI_IOC_WR_MODE:设置 SPI 设备的模式,包括时钟极性(CPOL)和相位(CPHA)等。
SPI_IOC_RD_MODE:读取 SPI 设备的当前模式。
SPI_IOC_WR_BITS_PER_WORD:设置 SPI 设备的每个数据传输的位数。
SPI_IOC_RD_BITS_PER_WORD:读取 SPI 设备的每个数据传输的位数。
SPI_IOC_WR_MAX_SPEED_HZ:设置 SPI 设备的最大时钟频率。
SPI_IOC_RD_MAX_SPEED_HZ:读取 SPI 设备的最大时钟频率。

3.3 spi配置函数

3.3.1 设置spi的模式

/****************************************
 *
 * 设置spi模式
 *  SPI_MODE0 = SPI_MODE_0, //00
 *  SPI_MODE1 = SPI_MODE_1, //01
 *  SPI_MODE2 = SPI_MODE_2, //10
 *  SPI_MODE3 = SPI_MODE_3, //11
 *
 * return:
 *  return 1 success
 *  return -1  failed
 *
 *  SPI_IOC_WR_MODE 设置总线的极性和相位
 * **************************************/
int dev_spi_mode(SPIMode mode)
{
    hardware_spi.mode &= 0xfc; // 清除低2位数
    hardware_spi.mode |= mode; // 设置模式,看上面宏值,只有2位有效
    // 写到驱动中
    if (ioctl(hardware_spi.fd, SPI_IOC_WR_MODE, &hardware_spi.mode) == -1)
    {
        DEV_SPI_DEBUG("设置spi模式失败\r\n");
        return -1;
    }
    return 1;
}

3.3.2 spi片选

  设置spi的片选,这里就一个设备,应该走SPI_NO_CS分支。

int dev_spi_chipselect(SPIChipSelect cs_mode)
{
    if (cs_mode == SPI_CS_MODE_HIGH)
    {
        hardware_spi.mode |= SPI_CS_HIGH;
        hardware_spi.mode &= ~SPI_NO_CS;
        DEV_SPI_DEBUG("片选拉高\r\n");
    }
    else if (cs_mode == SPI_CS_MODE_LOW)
    {
        hardware_spi.mode &= ~SPI_CS_HIGH;
        hardware_spi.mode &= ~SPI_NO_CS;
    }
    else if (cs_mode == SPI_CS_MODE_NONE)
    {
        hardware_spi.mode |= SPI_NO_CS;
    }

    if (ioctl(hardware_spi.fd, SPI_IOC_WR_MODE, &hardware_spi.mode) == -1)
    {
        DEV_SPI_DEBUG("不能设置spi的模式\r\n");
        return -1;
    }
    return 1;
}

3.3.3 spi传输频率

  设置spi的传输频率。

int dev_spi_set_speed(uint32_t speed)
{
    uint32_t speed1 = hardware_spi.speed;
    hardware_spi.speed = speed;

    // 写频率
    if (ioctl(hardware_spi.fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1)
    {
        DEV_SPI_DEBUG("不能设置最大频率\r\n");
        hardware_spi.speed = speed1; // 设置故障频率不变
        return -1;
    }

    // 读频率
    if (ioctl(hardware_spi.fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) == -1)
    {
        DEV_SPI_DEBUG("不能获取最大频率\r\n");
        hardware_spi.speed = speed1; // 设置故障频率不变
        return -1;
    }
    hardware_spi.speed = speed;
    tr.speed_hz = hardware_spi.speed; //(目前看只是赋值,没看到应用)
    return 1;
}

3.3.4 spi传输数据时间间隔

  设置spi发送数据的时间间隔。

void dev_spi_set_data_interval(uint16_t us)
{
    hardware_spi.delay = us;
    tr.delay_usecs = hardware_spi.delay;//(这里未直接发送数据,判断在其他位置发送,验证)
}

3.3.5 配置spi的读写位

  在此函数中调用上面的配置函数,同时配置spi传输数据时每次传输的位数。

/****************************************
 *
 * 设置spi参数,spi设备名称,spi模式,传输频率
 *
 * SPI_IOC_WR_BITS_PER_WORD //写 每字多少位
 * SPI_IOC_RD_BITS_PER_WORD //读每字多少位
 * SPI_IOC_RD_MAX_SPEED_HZ //读 最大速率
 * SPI_IOC_WR_MAX_SPEED_HZ //写 最大速率
 * **************************************/
void dev_hardware_spi_beginset(char *spi_device, SPIMode mode, uint32_t speed) // 设置spi驱动参数
{
    int ret = 0;
    hardware_spi.mode = 0;
    if ((hardware_spi.fd = open(spi_device, O_RDWR)) < 0)
    {
        perror("打开spi驱动失败\n");
        exit(1);
    }
    else
    {
        DEV_SPI_DEBUG("打开驱动:%s\r\n", spi_device);
    }

    ret = ioctl(hardware_spi.fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    // 设置spi总线写每字多少位,宏命令,+数值bits
    if (ret == -1)
    {
        DEV_SPI_DEBUG("不能设置spi写每字的位数\r\n");
    }
    ret = ioctl(hardware_spi.fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
    // 写 每字多少位
    if (ret == -1)
    {
        DEV_SPI_DEBUG("不能设置spi读每字的位数\r\n");
    }
    dev_spi_mode(mode);
    dev_spi_chipselect(SPI_CS_MODE_LOW);
    dev_spi_set_speed(speed);
    dev_spi_set_data_interval(0);
}

  下面是我实际使用时的spi配置参数。

dev_hardware_spi_beginset("/dev/spidev0.0",mySPI_MODE_3,10000000);

3.4 spi点亮屏幕

  至此spi参数的配置基本完成。可以调用spi初始化oled屏幕了。

3.4.1 硬件初始化屏幕

  调用写函数通过驱动往oled屏幕传递初始化命令。

//节选其中一个命令展示
oled_write_reg(0xae); // 关闭显示

3.4.2 传递命令及引脚编号

  跳转至写函数。根据屏幕驱动设定,发送命令或数据前需要配置dc引脚的电平,然后屏幕才能知道接收的数据是命令还是点亮屏幕的坐标。dc引脚的函数上文已有不再赘述。可以看到命令被dev_spi_transfer_byte函数进一步传递。

static void oled_write_reg(uint8_t reg)
{
    dev_digital_write(OLED_DC_PIN, '0'); // 给dc引脚置零,切换为输入命令
    dev_spi_transfer_byte(reg);        // 使用ioctl传输数据
}

3.4.3 向驱动传递数据

  追踪dev_spi_transfer_byte函数,这个就是应用层往内核传递数据的最终函数了。很简单调用上面官方定义的tr结构体填充要往内核传递的数据,使用ioctl往内核传递。

uint8_t dev_spi_transfer_byte(uint8_t buf)
{
    uint8_t rbuf[1];
    tr.len = 1;
    tr.tx_buf = (unsigned long)&buf; // 发送缓冲区
    tr.rx_buf = (unsigned long)rbuf; // 接受缓冲区

    // ioctl 操作,传输数据
    if (ioctl(hardware_spi.fd, SPI_IOC_MESSAGE(1), &tr) < 1)
    //上文的时间间隔通过这个tr结构体传输到内核
    // cmd SPI_IOC_MESSAGE(N) 其中N是spi_ioc_transfer结构数组元素个数
    // 这里只定义了一个结构体tr,所以是1
    // 这个SPI_IOC_MESSAGE(1) 命令,表示要传输一个 SPI 消息。
    // 全双工工作, SPI 驱动执行一次数据传输,
    // 并将发送缓冲区(tr.tx_buf)中的数据发送到 SPI 设备。
    // 同时,SPI 驱动也会将接收到的数据存储在接收缓冲区(tr.rx_buf)中。
    {
        DEV_SPI_DEBUG("can't send spi message\r\n");
    }
 //    printf("硬件写数据成功\r\n");
    return rbuf[0];
}

4 清屏

  上述流程完成后就可以点亮屏幕了,点亮前软件清屏,若不清屏可能会出现雪花屏,影响后面的参数实现。同时也可以把传输的数据设置成0xff,这样可以看屏幕和点亮函数是否有问题。
遇到这个问题的时候没保存图片,网上随便找的一张凑合看吧。



void oled_clear()
{
    uint16_t width, height, column, i, j;
    width = oled_width; // 值是128列
    height = (oled_height % 8 == 0) ? (oled_height / 8) : (oled_height / 8 + 1);
    oled_write_reg(0xb0); // 设置行的起始地址,使用命令设置显示位置的目标起始位置是b0-b7
    for (j = 0; j < height; j++)
    {
       oled_write_reg(0xb0 + j);
        for (i = 0; i < width; i++)
        {
          column = 0 + i;
          oled_write_reg(0x00 + (column & 0x0f)); // 设置列的低起始地址
          oled_write_reg(0x10 + (column >> 4));   // 设置列的高起始地址
          oled_write_data(0x01); // 将对应的列置成0
        }
    }
}

5 画图

5.1 画点

  若无问题,进入下一步实现精确点亮某一个oled屏幕像素点。因为整体代码太长,且都用不上,所以只放置了核心代码。
  画点函数。核心思想是点亮一个像素是1X1的矩阵,点亮4个像素是2X2的矩阵,那么就可以减少绘制其他图像宽度等方面的代码复杂度。其实现的原理很简单。双层for循环遍历整个128X64的整个屏幕的像素点。color表示点亮与否,黑色是灭白色是亮;然后将每个像素点的坐标和状态传递给paint_set_pixel函数,由此函数做进一步处理。

    int16_t xdir_num, ydir_num;
    if (dot_style == DOT_FILL_AROUND)
    {
       for (xdir_num = 0; xdir_num <  dot_pixel ; xdir_num++)
        {
           // printf("xdir_num[%d],ydir_num[%d]\r\n",xdir_num,ydir_num);
            for (ydir_num = 0; ydir_num < dot_pixel; ydir_num++)
            {
                if (xpoint + xdir_num - dot_pixel < 0 || ypoint + ydir_num - dot_pixel < 0)
                {
                    break;
                }
                // 这里循环是,因为控制的像素若是矩阵,一次只能操控一个像素点,
                // 若想操控矩阵,需要多次循环,如2*2矩阵
                // a= xpoint + xdir_num - dot_pixel;
                // b=ypoint + ydir_num - dot_pixel;
                // printf("a[%d],b[%d]\r\n",a,b);
                paint_set_pixel(xpoint + xdir_num , ypoint + ydir_num, color);//2
                //paint_set_pixel(xpoint + xdir_num - dot_pixel, ypoint + ydir_num - dot_pixel, color);//2
            }
        }

    }

  paint_set_pixel函数的作用是将每个函数的状态写到一个具体的具体的数组中,它的长度是1024个且是char类型的,正好表示整个屏幕的每一个像素点。向数组中写入数据。定义地址addr和显示数据rdata,地址计算方式如上文所述,rdata是8位的数据,代表一列;这时black是0、white是1,根据具体的上面函数的遍历可以知道每一点是0/1,这时即可对rdata精确的赋值。

void paint_set_pixel(uint16_t xpoint, uint16_t ypoint, uint16_t color)
{
    uint32_t addr = 0;
    uint8_t rdata = 0;
       addr = x + ((y/8)*128);
        rdata = paint.image[addr];
        if (color == BLACK)
        {
            paint.image[addr] = rdata & ~(0x80 >> (y % 8));
        }
        else
        {
            paint.image[addr] = rdata | (0x80 >> (y % 8));
            printf("22222rdata[%d],x[%d],y[%d],addr[%d]\r\n",paint.image[addr],x,y,addr);
        }
}

  每画完一组图像,即可调用oled_display函数向驱动传输数据。

void oled_display(uint8_t *image)
{
    uint16_t width, height, column, temp, i, j;
    width = oled_width; // 值是128列
    height = (oled_height % 8 == 0) ? (oled_height / 8) : (oled_height / 8 + 1);
    oled_write_reg(0xb0); // 设置行的起始地址,使用命令设置显示位置的目标起始位置是b0-b7
    for (j = 0; j < height; j++)
    {
        oled_write_reg(0xb0 + j);
        for (i = 0; i < width; i++)
        {
        column = 0 + i;
        oled_write_reg(0x00 + (column & 0x0f)); // 设置列的低起始地址
        oled_write_reg(0x10 + (column >> 4));   // 设置列的高起始地址
        temp = image[i + j* width ]; // 在其他位置定义并分配空间,越界最后处理
        temp = reverse(temp);//翻转函数,作用解决画圆出现两个半圆翻转的问题
        oled_write_data(temp);
        }
    }
}

  如下是点亮以下5个坐标的情况:
  (0,0)、(127,0)、(63,0)、(63,127)、(6,19)



5.2 画线

  我看官方给出的算法画线段只有直线,没有斜线。这里给出了Bresenham算法(任意斜率)。核心思想就是看下一点的实际坐标距离屏幕上两点坐标哪个近,选择近的那一点;循环往复直到尽头。具体算法我开了单章讲解,有兴趣可以看看。

void paint_draw_slope_line(uint16_t xstart, uint16_t ystart, uint16_t xend, uint16_t yend,
                     uint16_t color, DOT_PIXEL line_width, LINE_STYLE line_style){
    uint16_t temp = 0;
    uint16_t xpoint = xstart;
    uint16_t ypoint = ystart;
    //算出2点间距离,确保值为非负值(abs函数是获取绝对值)
    int dx = (int)xend - (int)xstart >= 0 ? xend - xstart : xstart - xend; // 算出两点距离
    int dy = (int)yend - (int)ystart >= 0 ? yend - ystart : ystart - yend;
    if(dy>dx)//为真证明斜率绝对值大于1,主要以y轴方向递增
    {
       temp = xstart; //x,y值互换
        xstart = ystart;
        ystart = temp;
        temp = 0;
    }
    if(xstart >xend)//为真,说明起点大于终点,交换方向
    {
       temp = xstart;
       xstart = xend;
       xend = temp;
       temp = 0;
       temp = ystart;
       ystart = yend;
       yend = temp;
       temp = 0; 
    }
    uint16_t delta_x = xend -xstart;//上面换算判断,这步xend一定大于xstart
    uint16_t delta_y =  (int)yend - (int)ystart >= 0 ? yend - ystart : ystart - yend;
    uint16_t error = 0; //误差量
    uint16_t delta_error = delta_x/delta_y;//斜率
    uint16_t yk = ystart;
    uint16_t y_step = 0;
    if(ystart < yend)
    {
        y_step = 1;
    }else{
        y_step = -1;
    }
    for(uint16_t xk = xstart; xk <= xend; xk++)
    {
        if(dy>dx)
        {
           paint_draw_point(yk, xk, color, line_width, DOT_FILL_AROUND); 
        }else{
           paint_draw_point(xk, yk, color, line_width, DOT_FILL_AROUND);
        }
        error = error +delta_error;
        if(error >= 0.5)
        {
            yk=yk+y_step;
            error = error -1;
        }
    }
}

如下是使用新算法绘制的直线和斜线:



5.3 画圆

  画圆一样是使用Bresenham画圆算法,核心思想是绘制八分之一圆,剩余部分通过对称获取点的坐标信息。具体算法思想我开了单章讲解,可以看看。

void paint_draw_circle(uint16_t x_center, uint16_t y_center, uint16_t radius,
uint16_t color, DOT_PIXEL line_width, DRAW_FILL draw_fill)
{
if (x_center > 128 || y_center > 64)
{
Debug("超出正常显示范围\r\n");
return;
}
// 画一个圆从(0,r)处作为一个起点
int16_t xcurrent, ycurrent;
xcurrent = 0;
ycurrent = radius;
    // 累计误差,判断下一个点的位置
    int16_t esp = 3 - (radius << 1);
// 画一个空心圆
while (xcurrent <= ycurrent)
{
paint_draw_point(x_center + xcurrent, y_center + ycurrent, color, line_width, DOT_FILL_AROUND); // 1
paint_draw_point(x_center - xcurrent, y_center + ycurrent, color, line_width, DOT_FILL_AROUND); // 2
paint_draw_point(x_center - ycurrent, y_center + xcurrent, color, line_width, DOT_FILL_AROUND); // 3
paint_draw_point(x_center - ycurrent, y_center - xcurrent, color, line_width, DOT_FILL_AROUND); // 4
paint_draw_point(x_center - xcurrent, y_center - ycurrent, color, line_width, DOT_FILL_AROUND); // 5
paint_draw_point(x_center + xcurrent, y_center - ycurrent, color, line_width, DOT_FILL_AROUND); // 6
paint_draw_point(x_center + ycurrent, y_center - xcurrent, color, line_width, DOT_FILL_AROUND); // 7
paint_draw_point(x_center + ycurrent, y_center + xcurrent, color, line_width, DOT_FILL_AROUND); // 0

            if (esp <0)
            {
                esp += 4 * xcurrent + 6;
            }
            else
            {
                esp += 10 + 4 * (xcurrent - ycurrent);
                ycurrent--;
            }
            xcurrent++;
        }
}

  如下是绘制的圆:



5.4 显示字符

  显示中文、英文、数字等,这里觉得官方的方式就很好用,他将常用字符取模后制作成字库文件,检测字符串及字库编号,显示不同尺寸的中英文。显示原理就是对比字符,如果解析的ascii码小于126证明是数字、字母、符合,大于等于126则是汉字,因为汉字是双字节表示,所以对比是两个变量。

void paint_draw_string_cn(uint16_t xstart, uint16_t ystart, const char *pstring, cFONT *font,
                          uint16_t color_foreground, uint16_t color_background)
{
    const char *p_text = pstring;
    int x = xstart, y = ystart;
    int i, j, num;
    // 在EPD上逐个字符发送字符串
    while (*p_text != 0)
    {
        if (*p_text <= 0x7F) // ascii <126
        {
            for (num = 0; num < font->size; num++)
            {
                if (*p_text == font->table[num].index[0])
                {
                    const char *ptr = &font->table[num].matrix[0];
                    for (j = 0; j < font->Height; j++)
                    {
                        for (i = 0; i < font->Width; i++)
                        {
                            if (WHITE == FONT_BACKGROUND) // 加快扫描速度
                            {
                                if (*ptr & (0x80 >> (i % 8)))
                                {
                                    paint_set_pixel(x + i, y + j, color_foreground);
                                }
                                else
                                {
                                    paint_set_pixel(x + i, y + j, color_background);
                                }
                            }
                            else
                            {
                                if (*ptr & (0x80 >> (i % 8)))
                                {
                                    paint_set_pixel(x + i, y + j, color_background);
                                }
                                else
                                {
                                    paint_set_pixel(x + i, y + j, color_foreground);
                                }
                            }
                            if (i % 8 == 7) // (等于7这里存疑,后续测试)
                            {
                                ptr++;
                            }
                        }
                        if (font->Width % 8 != 0)
                        {
                            ptr++;
                        }
                    }
                    break;
                }
            }
            // 指向下一个字符
            p_text += 1;
            // 将列的位置减16
            x += font->ASCII_Width;
        }
        else
        {
            for (num = 0; num < font->size; num++)
            {
                if ((*p_text == font->table[num].index[0]) && (*(p_text + 1) == font->table[num].index[1]))
                {
                    const char *ptr = &font->table[num].matrix[0];
                    for (j = 0; j < font->Height; j++)
                    {
                        for (i = 0; i < font->Width; i++)
                        {
                            if (WHITE == FONT_BACKGROUND)
                            {
                                if (*ptr & (0x80 >> (i % 8)))
                                {
                                    paint_set_pixel(x + i, y + j, color_foreground);
                                }
                                else
                                {
                                    paint_set_pixel(x + i, y + j, color_background);
                                }
                            }
                            else
                            {
                                if (*ptr & (0x80 >> (i % 8)))
                                {
                                    paint_set_pixel(x + i, y + j, color_background);
                                }
                                else
                                {
                                    paint_set_pixel(x + i, y + j, color_foreground);
                                }
                            }
                            if (i % 8 == 7)
                            {
                                ptr++;
                            }
                        }
                        if (font->Width % 8 != 0)
                        {
                            ptr++;
                        }
                    }
                    break;
                }
            }
            // 指向下一个字符
            p_text += 3;
            // 将列位置减16
            x += font->Width;
        }
    }
}

  如下是显示的中英文字符:



5.5 最终调用

  最后所有操作完成后,还有分配空间的问题;其实就是创建了一个1024字节大小的空间用来存储图片数据使用。最后分析发现对于这个空间的使用是基于char类型数组使用的。所以我觉得直接定义成一个char类型的数组更合理。

    // 1.创建一个新的图片缓存
    uint8_t *blackimage;
    uint16_t imagesize = ((oled_height % 8 == 0) ? (oled_height / 8) : (oled_height / 8 + 1)) * (oled_width);
                         // 分配空间,1个字节表示8个像素点,128*8= 1024
                         // 空间是char类型的,步长为1步
    if ((blackimage = (uint8_t *)malloc(imagesize)) == NULL)
    {
        printf("申请内存失败\r\n");
        return -1;
    }

6 总结

  至此整个移植过程就算完成了。中间肯定有些瑕疵,但所有功能都实现了,也算是这个目标完成了。修改完的代码我都放到我gitee上的仓库上了,有需要的可以自取。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇