嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 超级精简系列之十八:超级精简的IO模拟驱动黑白电子书屏LS013B4DN04 1.3" 168x144

超级精简系列之十八:超级精简的IO模拟驱动黑白电子书屏LS013B4DN04 1.3" 168x144

.前言

    这一篇我们继续超级精简IO模拟系列。本篇通过IO模拟,实现驱动黑白电子书屏LS013B4DN04 1.3" 168x144. 参考屏的手册

https://cdn-shop.adafruit.com/datasheets/LS013B4DN04-3V_FPC-204284.pdf

https://cdn-learn.adafruit.com/downloads/pdf/adafruit-sharp-memory-display-breakout.pdf


    注:测试环境为RP2040开发板。

二.接线

电源只需要3.3VVIN,GND

信号使用3个输出引脚接CS,DI,CLK

三.显存映射

分辨率144x168,注意这里不要搞反了否则显示会不对。

V垂直方向对应Line地址144x168就是168.

H水平方向对应P,一个字节对应8列,144列需要144/8个字节。

需要显存大小是144*168/8字节。

四.时序

操作参考1文档的6.5章节。

两个重点参数帧率1~60HZ,时钟频率最大1MHz

其他重点时序参数

tsSCS最小6uS SCS建立时间

thSCS最小2uS SCS保持时间

tsSI最小380nS 数据建立时间

thSI最小440nS 数据保持时间

twSCLKL 最低450nS 时钟低时间

twSCLKL 最低450nS 时钟高时间

SCS高有效,SCLK低时准备数据,上升沿设备采样数据。

关注以下操作,显示单行,多行和清除显示,只需要这几个操作即可,前者将存在控制器的显存刷新到液晶显示,后者清除显示为白色。

1显示单行6-5-1 Data update mode (1 line)

 

2显示多行6-5-2 Data Update Mode (Multiple Lines)

 

3清除显示6-5-4 All Clear Mode

 

五.驱动

1 数据结构和接口

定义基本的IO操作接口

typedef void (*ls013_io_cs_set)(uint8_t level);    /**< CS输出接口  */typedef void (*ls013_io_di_set)(uint8_t level);    /**< DI输出接口  */typedef void (*ls013_io_clk_set)(uint8_t level);   /**< SCK输出接口 */typedef void (*ls013_io_delay)(uint32_t ns);       /**< 延时接口    */typedef void (*ls013_io_init)(void);               /**< 初始化      */typedef void (*ls013_io_deinit)(void);             /**< 解除初始化      */
/** \struct ls013_dev_st * 设备接口结构体 */typedef struct{    ls013_io_cs_set cs_set;    /**< CS输出接口  */    ls013_io_di_set di_set;    /**< DI输出接口  */    ls013_io_clk_set clk_set;  /**< SCK输出接口 */    ls013_io_delay delay;      /**< 延时接口    */    ls013_io_init init;        /**< 初始化      */    ls013_io_deinit deinit;    /**< 解除初始化      */} ls013_dev_st;

定义对外接口

/** * \fn ls013_dis * 显示一帧 * \param[in]  dev \ref ls013_dev_st 设备指针 * \param[in]  buffer 帧缓存 * \param[in] h 水平像素点 * \param[in] v 垂直像素点*/void ls013_dis(ls013_dev_st* dev, uint8_t* buffer, uint8_t h, uint8_t v);
/** * \fn ls013_clr * 清除显示 * \param[in]  dev \ref ls013_dev_st 设备指针*/void ls013_clr(ls013_dev_st* dev);
/** * \fn ls013_init * 初始化 * \param[in]  dev \ref ls013_dev_st 设备指针*/void ls013_init(ls013_dev_st* dev);
/** * \fn ls013_deinit * 解除初始化 * \param[in]  dev \ref ls013_dev_st 设备指针*/void ls013_deinit(ls013_dev_st* dev);

定时参数宏

/** * 时序宏定义 * tsSCS最小6uS  SCS建立时间 * thSCS最小2uS    SCS保持时间 * tsSI最小380nS   数据建立时间 * thSI最小440nS   数据保持时间 * twSCLKL 最低450nS 时钟低时间 * twSCLKL 最低450nS 时钟高时间 * twSCSL 最低2uS    SCS低时间 * twSCSH 最低24/136uS    SCS高时间*/#define TSSCS    6000#define THSCS    2000#define TSSI     380#define THSI     440#define TWSCLKL  450#define TWSCLKH  450#define TWSCSL  2000#define TWSCSH  24000
/** * 命令宏定义 * M2-M1-M0的组合*/#define CMD_ALL_CLR 0x04  /* M2 */#define CMD_NO_CLR 0x00#define CMD_VCOM_H 0x02   /* M1 */#define CMD_VCOM_L 0x00#define CMD_MODE_UPDATE 0x01 /* M0 */#define CMD_MODE_DISPLAY 0x00

发送字节实现

/** * \fn ls013_write_byte * 发送一个字节*/static void ls013_write_byte(ls013_dev_st* dev, uint8_t val){    /* 默认SCLK为0 */    dev->clk_set(0);
    for(int i=0; i<8; i++)    {        if((val & 0x01) != 0)        {            dev->di_set(1);        }        else        {            dev->di_set(0);           }        if(dev->delay != 0)        {            dev->delay(TWSCLKL); /* 足够数据建立时间 TWSCLKL>TSSI */        }        dev->clk_set(1);      /* SCK上升沿触发对方采样 */        if(dev->delay != 0)        {            dev->delay(TWSCLKH); /* 足够数据保持时间 TWSCLKH>THSI */        }        /* 恢复SCLK为0 */        dev->clk_set(0);        val >>= 1; /* 准备下一位, 低位在前发送 */    }}

2设置模式实现

/** * \fn ls013_set_mode * 设置m2-m1-m0 * \param[in] dev \ref ls013_dev_st 设备指针 * \param[in] mode m2-m1-m0的组合*/static void ls013_set_mode(ls013_dev_st* dev, uint8_t mode){    if(dev == 0)    {        return;    }    if((dev->clk_set == 0) || (dev->cs_set == 0) || (dev->di_set == 0))    {        return;    }    dev->cs_set(1);  /* 拉高SCS */    if(dev->delay != 0)    {        dev->delay(TSSCS); /* 等待TSSCS */    }    ls013_write_byte(dev,mode);  /* M2-M1-M0 */    ls013_write_byte(dev,0x00);  /* dummy */    if(dev->delay != 0)    {        dev->delay(TSSCS); /* 等待THSCS */    }    dev->cs_set(0);  /* 拉低SCS */    if(dev->delay != 0)    {        dev->delay(TWSCSL); /* 等待TWSCSL */    }}

3单行显示

void ls013_dis_line(ls013_dev_st* dev, uint8_t* buffer, uint8_t h, uint8_t line){    uint8_t index = 0;
    dev->cs_set(1);  /* 拉高SCS */    if(dev->delay != 0)    {        dev->delay(TSSCS); /* 等待TSSCS */    }
    ls013_write_byte(dev,CMD_NO_CLR | CMD_VCOM_L | CMD_MODE_UPDATE);  /* M2-M1-M0 */    ls013_write_byte(dev,line+1);  /* line地址,从1开始 */    for(int j=0; j<h/8; j++)    {        ls013_write_byte(dev,buffer[index++]);  /* 数据 */    }
    ls013_write_byte(dev,0);  /* 最后16个CLK dummy */    ls013_write_byte(dev,0);    if(dev->delay != 0)    {        dev->delay(TSSCS); /* 等待THSCS */    }    dev->cs_set(0);  /* 拉低SCS */    if(dev->delay != 0)    {        dev->delay(TWSCSL); /* 等待TWSCSL */    }}

4多行显示

基于单行更新实现,或者直接使用多行更新。

void ls013_dis(ls013_dev_st* dev, uint8_t* buffer, uint8_t h, uint8_t v){#if 1    uint8_t index = 0;    if(dev == 0)    {        return;    }    if((dev->clk_set == 0) || (dev->cs_set == 0) || (dev->di_set == 0))    {        return;    }    dev->cs_set(1);  /* 拉高SCS */    if(dev->delay != 0)    {        dev->delay(TSSCS); /* 等待TSSCS */    }
    for(int i=0; i<v; i++)    {        if(i==0)        {            ls013_write_byte(dev,CMD_NO_CLR | CMD_VCOM_L | CMD_MODE_UPDATE);  /* M2-M1-M0 */        }        else        {            ls013_write_byte(dev,0);        }        ls013_write_byte(dev,i+1);  /* line地址,从1开始 */        for(int j=0; j<h/8; j++)        {            ls013_write_byte(dev,buffer[index++]);  /* 数据 */        }    }    ls013_write_byte(dev,0);  /* 最后16个CLK dummy */    ls013_write_byte(dev,0);    if(dev->delay != 0)    {        dev->delay(TSSCS); /* 等待THSCS */    }    dev->cs_set(0);  /* 拉低SCS */    if(dev->delay != 0)    {        dev->delay(TWSCSL); /* 等待TWSCSL */    }
    //ls013_set_mode(dev, CMD_NO_CLR | CMD_VCOM_L | CMD_MODE_DISPLAY);#else    for(int i=0; i<v; i++)    {        ls013_dis_line(dev, buffer+i*(h/8), h, i);    }#endif}

逻辑分析仪测试如下


5清除显示

void ls013_clr(ls013_dev_st* dev){    ls013_set_mode(dev, CMD_ALL_CLR | CMD_VCOM_L | CMD_MODE_DISPLAY);}

逻辑分析测量如下

6初始化解除初始化

void ls013_init(ls013_dev_st* dev){    if(dev == 0)    {        return;    }    if(dev->init == 0)    {        return;    }    dev->init();}
void ls013_deinit(ls013_dev_st* dev){    if(dev == 0)    {        return;    }    if(dev->deinit == 0)    {        return;    }    dev->deinit();
}

7 测试

实现ls013_lcd.h

#ifndef LS013_LCD_H#define LS013_LCD_H
#ifdef __cplusplus    extern "C"{#endif
#define LCD_H (uint8_t)144#define LCD_V (uint8_t)168
void lcd_init(void);void lcd_deinit(void);void lcd_sync(void);void lcd_fill(uint8_t val);void lcd_set_pixel(uint8_t x, uint8_t y, uint8_t val);uint8_t lcd_get_pixel(uint8_t x, uint8_t y);
#ifdef __cplusplus    }#endif
#endif

实现ls013_lcd.c

#include "ls013.h"#include "ls013_lcd.h"#include "pico/stdlib.h"#include <string.h>

uint8_t lcd_buffer[LCD_V*LCD_H/8];
void ls013_port_init(void){    gpio_init(2);    gpio_set_dir(2, GPIO_OUT);    gpio_put(2, 0);
    gpio_init(3);    gpio_set_dir(3, GPIO_OUT);    gpio_put(3, 0);
    gpio_init(4);    gpio_set_dir(4, GPIO_OUT);    gpio_put(4, 0);}
void ls013_port_deinit(void){
}
void ls013_port_cs_set(uint8_t val){    if(val == 0)    {        gpio_put(2, 0);    }    else    {        gpio_put(2, 1);    }}
void ls013_port_di_set(uint8_t val){    if(val == 0)    {        gpio_put(3, 0);    }    else    {        gpio_put(3, 1);    }}
void ls013_port_clk_set(uint8_t val){    if(val == 0)    {        gpio_put(4, 0);    }    else    {        gpio_put(4, 1);    }}
void ls013_port_delay(uint32_t ns){    sleep_us((ns+999)/1000);}
ls013_dev_st ls013_dev ={    .cs_set = ls013_port_cs_set,    .di_set = ls013_port_di_set,    .clk_set = ls013_port_clk_set,    .delay = ls013_port_delay,    .init = ls013_port_init,    .deinit = ls013_port_deinit, };
void lcd_init(void){    memset(lcd_buffer,0,sizeof(lcd_buffer));    ls013_init(&ls013_dev);    ls013_clr(&ls013_dev);}
void lcd_deinit(void){    ls013_deinit(&ls013_dev);}
void lcd_sync(void){    ls013_dis(&ls013_dev, lcd_buffer, LCD_H, LCD_V);}
void lcd_fill(uint8_t val){    memset(lcd_buffer,val,sizeof(lcd_buffer));    ls013_dis(&ls013_dev, lcd_buffer, LCD_H, LCD_V);}
void lcd_set_pixel(uint8_t x, uint8_t y, uint8_t val){    uint8_t byte;    uint8_t offset;    byte = x>>3;    offset = x&0x07;    if(val != 0)    {        lcd_buffer[y*(LCD_H/8)+byte] |= ((uint8_t)1<<offset);    }    else    {        lcd_buffer[y*(LCD_H/8)+byte] &= ~((uint8_t)1<<offset);    }}
uint8_t lcd_get_pixel(uint8_t x, uint8_t y){    uint8_t byte;    uint8_t offset;    byte = x>>3;    offset = x&0x07;
    return ((lcd_buffer[y*(LCD_H/8)+byte] & ((uint8_t)1<<offset)) == 0) ? 0 : 1;
}

测试代码如下,黑白刷屏

  lcd_init();    while (true) {        for(int i=0; i<LCD_H; i++)        {            for(int j=0; j<LCD_V; j++)            {                lcd_set_pixel(i,j,1);            }        }        lcd_sync();        //lcd_fill(0xFF);        sleep_ms(1000);        for(int i=0; i<LCD_H; i++)        {            for(int j=0; j<LCD_V; j++)            {                lcd_set_pixel(i,j,0);            }        }        lcd_sync();        //lcd_fill(0);        sleep_ms(1000);    }

六.移植GUI

参见以下链接不再赘述

http://bbs.eeworld.com.cn/thread-1271866-1-1.html

http://bbs.eeworld.com.cn/thread-1271865-1-1.html

七.总结

 以上通过IO模拟驱动黑白电子书屏LS013B4DN04 1.3" 168x144,代码非常简单具备良好的可移植性,可以快速移植使用。