我们电子日历的产品,选用的屏幕尺寸为5.83寸,分辨率为648*480。屏幕本身支持黑白红三色,我们使用黑白两色,单色位图表示的话,每个位都能表示一个像素点。所以对于这个屏幕而言,要显示一整幅图,需要的字节数为 38880。然后,由于屏幕需要的只是像素点,所以不能直接将一个位图数据写进去,需要预先转换一下。然后屏幕本身对于像素点的处理方式的不同,也会导致图片的预处理过程不一样。

对于这个墨水屏的屏幕而言,非专业人员读他们这个datasheet难度较大,各种术语和参数意义不是很容易看懂。我开发主要是先按照官方给的示例程序和图像跑一遍,然后基本跑通了大致的操作也就清楚了,然后开始看datasheet这么个流程。

我们前后接触了三个厂商,一个是大连奇云电子,这个是纯从淘宝上找的一个,他们这个有四灰度的屏幕,我是用他们这个demo刷了一副太祖的画像进去想要膜拜一下,由于像素密度不太够,看起来效果不是很好,这个后边没有继续接触了。后边使用了龙宁科技和威锋科技的屏幕,我们之前很多产品也使用了他们俩的段码墨水屏,算是比较熟悉的了。电子日历这个产品,这两家的屏幕都调好了,现在可以通用。

不论是哪个厂商,其上游技术都是元太科技垄断,厂家再进行二次开发。因此不论是硬件上还是软件上,基本都差不太多。屏幕本身带有一个简单的驱动IC,我们应用开发的话,主要是和这个IC打交道。IC提供了二十多个各种接口供选择,除了电源和GND之外,我们还使用了SPI(由于不需要读取屏幕的数据,少了一根MISO),RST复位、BUSY状态、BS选择SPI类型(这个一般只会采用一个,要么三线要么四线,如果硬件设计上直接拉低应该就不用这个了),CD命令/数据输入。

比较曲折的是,一开始技术方案没有考虑好,选择了最难的那种,想要实现根据文本样式和内容生成图像数据这样的复杂方案,研究FreeType怎么渲染文本,折腾了三个多星期,倒是把这玩意儿差不多给搞出来了。不过对于一个裸奔的MCU程序而言,自己渲染的实现过于复杂了。最后采用的简化方案是:直接下载一整副图像或者将小图标的数据进行组合显示出来。

最开始厂家提供了一个软件,用于帮助从图像生成数据文件。    就是下面这货:

按照厂家的说明可以使用这个软件快速提取一副图像的数据,刷入到demo中即可以运行。

需要注意的是,用于生成数据的图像必须是单色位图,且分辨率必须和屏幕的分辨率严格对应,480*648或者648*480也可以。

然后开始研究我们自己的应用场景。因为如果每出一张图都要手工使用这个软件生成数据再转换成bin文件放到服务器上,太麻烦了!所以得有一个可以自动根据图片生成h文件或者二进制文件的程序,因为都是我在研究屏幕相关的技术,所以由我自己来写预处理的程序。

IC的分辨率为648*480,带FPC的那一边为下方。根据datasheet,扫描数据的时候,每个字节8位共可以表示8个像素,从左到右(或反过来,UD参数)逐个扫描直至填满本行所有的像素,扫完一行之后,根据SHL参数可以向上或者向下扫描第二行。

我们这个日历,是竖着的,和屏厂默认的横着有90度的差。所以这里如果不想要美工MM每次都给出横着的日历,我就得在程序中处理这种转换。

对于符合屏厂默认方向的数据而言,不论是从左到右从上到下还是反过来,写入一整个屏幕的数据为 每行81个字节*480行,所以如果应用的UI是648*480的分辨率的,提取图像的时候,可以直接按照逐行扫描每8个像素合并一个字节即可。但是如果是像我们这种竖着的,则不太一样。要符合屏幕的扫描逻辑,则应该改为每一列共8行合并一个字节,然后是从左向右从上向下还是反过来根据参数决定。这里我前期研究的时候,选定的方向是更适合理解的从左到右从上到下,不过实际上这不符合Bitmap的扫描方向,Bitmap是从左到右从下往上扫描的,导致处理图像的时候我需要额外翻转一下数据。

然后Bitmap格式的图像本身也不全是像素。其文件格式为 文件头+信息头+调色板 三部分组成。其中文件头固定为14个字节,信息头为40个字节,然后颜色表的长度根据图片的颜色模式决定:24位或36位真彩色模式无颜色表,黑白单色图的颜色表大小是8字节,16色图像的颜色表大小是64字节,256色图像的颜色表大小是1024字节。每4字节表示一种颜色,并以B(蓝色)、G(绿色)、R(红色)、alpha(像素的透明度值,一般不需要)。即首先4字节表示颜色号0的颜色,接下来表示颜色号1的颜色,依此类推。

以下定义为使用Visualstudio研究FreeType渲染Bitmap的时候梳理的Bitmap文件格式:

//位图文件头定义:
typedef struct  tagBITMAPFILEHEADER {WORD bfType;//位图类别,根据不同的操作系统而不同,在Windows中,此字段的值总为‘BM’DWORD bfSize; // 位图文件的大小,以字节为单位(3-6字节)WORD bfReserved1; // 位图文件保留字,必须为0(7-8字节)WORD bfReserved2; // 位图文件保留字,必须为0(9-10字节)DWORD bfOffBits; // 位图数据的起始位置,以相对于位图(11-14字节)// 文件头的偏移量表示,以字节为单位
}BITMAPFILEHEADER;  //14字节
//BMP位图信息头数据用于说明位图的尺寸等信息:
typedef struct tagBITMAPINFOHEADER {DWORD biSize; // 本结构所占用字节数(15-18字节)LONG biWidth; // 位图的宽度,以像素为单位(19-22字节)LONG biHeight; // 位图的高度,以像素为单位(23-26字节)WORD biPlanes; // 目标设备的级别,必须为1(27-28字节)WORD biBitCount;// BMP图像的色深,即一个像素用多少位表示,常见有1、4、8、16、24和32,分别对应单色、16色、256色、16位高彩色、24位真彩色和32位增强型真彩色// 4(16色),8(256色)或24(真彩色)之一DWORD biCompression; // 压缩方式,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定// 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一DWORD biSizeImage; // BMP图像数据大小,必须是4的倍数,图像数据大小不是4的倍数时用0填充补足LONG biXPelsPerMeter; // 位图水平分辨率,每米像素数(39-42字节)LONG biYPelsPerMeter; // 位图垂直分辨率,每米像素数(43-46字节)DWORD biClrUsed;//   BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为100h = 256DWORD biClrImportant;// 重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
}BITMAPINFOHEADER; //位图信息头定义,40字节
#if (BMP_BIT_COUNT<24)
typedef struct tagRGBQUAD {BYTE colors[(2 << (BMP_BIT_COUNT - 1)) * 4];//BYTE rgbBlue;// 蓝色的亮度(值范围为0-255)//BYTE rgbGreen; // 绿色的亮度(值范围为0-255)//BYTE rgbRed; // 红色的亮度(值范围为0-255)//BYTE rgbReserved;// 保留,必须为0} RGBQUAD;
#endif
//BMP整体信息:
typedef struct tagBMP_BUFFER
{BITMAPFILEHEADER    hand;BITMAPINFOHEADER    info;#if (BMP_BIT_COUNT<24)RGBQUAD rgbQuad;#endifBYTE* BUFFER;
}BMP_BUFFER;

然后根据以上信息来处理一个480*648分辨率的单色图,里边需要注意的一个问题是,对于非4字节对齐的尺寸,Bitmap会对行进行填充。比如15*18分辨率的图像,在数据上看,是有4字节*18行的,每行多余的字节为填充位。屏幕设计上没有这种对齐填充的说法,处理的时候,需要将填充的数据去掉。

对于单色图的颜色表,只有黑和白两种情况。颜色表也有两种:1表是白色0表示黑色,或者反过来0表示白色1表示白色。这两种情况都存在,我用的Windows10 和设计师的Mac就刚好反过来了。由于屏幕没有颜色表,0和1代表黑还是白依靠一个参数来确定,所以这里需要将不同的颜色表统一生成同样的像素数据。

对于屏幕的C语言处理程序,我定义了一个文件头,规定每一幅要显示的图像的数据必须包含这么一个文件头,类似于Bitmap的文件头。这里有个Keil的强制编译对齐的操作,是由于早期设计的文件头长度不足以自动对齐。图像的日期yDays包含年份(从2000年开始)和当年的第几天,由于不能单独使用1个字节表示,这里合并使用两个字节。早期的格式中,没有定义crc,所以后边的python代码中也没有生成crc的相关内容。

#pragma pack(1)
typedef struct{uint32_t magic_number;//for quickly check data valid.uint16_t data_length;uint8_t res_ver;      //version,the last ver is 2 with crc section.uint8_t compress_flag;uint16_t yDays;uint16_t width;uint16_t height;uint16_t crc;
}res_header;
#pragma pack()#define GET_RES_YEAR(yDays)    ((((uint16_t)yDays)>>10)&0x3F)
#define GET_RES_DAYS(yDays)    (((uint16_t)yDays)&0x3FF)
#define RES_YEAR_FROM          2000

然后因为我们还有一些小图标,这些小图标尺寸不固定,所以需要根据大小记录存放到NandFlash中的位置,会生成一个小图标专用的索引。对于占用一整副图的日历而言,地址都是固定的,不需要索引。对于一整副的日历,由于不能自动识别bitmap中这张日历代表哪一天,最好的处理方式是图片的文件名字中包含有那一天的信息,程序读取后自动写入到头文件中,我这里早期由于日历图片不多没有这么处理。

索引的结构为:

//4 bytes pack to one word.
typedef struct{uint16_t nand_page_num;//page num.uint16_t pack;//invalid.
}icon;

索引,mcu程序根据这个索引在flash中定位小图标的地址:

//save index file in MCU
const icon res_init_icons[116]={{0x0,0},/** res: beautiful ,data size:38896, pages:19 **/
{0x13,0},/** res: a_mmc_footer_56x16 ,data size:128, pages:1 **/
{0x14,0},/** res: icon_batt ,data size:112, pages:1 **/
{0x15,0},/** res: icon_drunk ,data size:88, pages:1 **/
{0x16,0},/** res: icon_medical ,data size:88, pages:1 **/
{0x17,0},/** res: icon_time_separate ,data size:40, pages:1 **/
{0x18,0},/** res: icon_tomato ,data size:88, pages:1 **/
{0x19,0},/** res: icon_unit_C ,data size:112, pages:1 **/
{0x1a,0},/** res: icon_wifi_1_32x24 ,data size:112, pages:1 **/
{0x1b,0},/** res: icon_wifi_2_32x24 ,data size:112, pages:1 **/
{0x1c,0},/** res: icon_wifi_3_32x24 ,data size:112, pages:1 **/
//...
}

下边是图像预处理程序,如果是用于生成小图标索引,START_BLOCK的值应该修改为NandFlash存储小图标的起始block。 然后这里我使用的NandFlash一共1024个block,每个block为64个page,每个page为2kB。

由于对python不是特别的熟悉,图片预处理代码里边对于全局变量的处理不是很规范,后边我把预处理的代码给服务器的同事参考使用的时候,遭到了他们的一致鄙视。回头我找着机会了也会鄙视回去的。

这里边主要的关键步骤是读取bitmap文件信息,然后删除填充的字节,翻转数据顺序,将横向8字节一扫描改为每一列8行一扫描。

#!/usr/bin/pythonimport sys
import os
from shutil import copyfile
import glob
import json
from datetime import datetimeBMP_CONFIG = "release_config.json"  #配置图像的保存位置,生成的数据文件的保存位置等FILE_SIZE_POS = 2  #文件大小
FILE_SIZE_BYTES = 4 # 4bytes
DATA_OFFSET_POS = 0x0a  # 像素数据的开始地址
DATA_OFFSET_BYTES = 4PX_WIDTH_POS = 0x12  #度的偏移量
PX_HEIGHT_POS = 0x16 #高度的偏移量
BMP_SIZE_BYTES = 4   #宽和高各自4个字节PX_RGBQUAD_FIRST = 0x36  #颜色表第一个颜色索引的位置
PX_GRBQUAD_SECOND = 0x3a #颜色表第二个颜色索引的位置
PX_RGB_BYTES = 3  # 4 bytes for r.g.b.alpha.PX_RGBQUAND_THRESHOLD = 0x808080 #用于区分颜色表中1和0哪个在前边#生成的h文件的格式和注释,前期研究的时候我生成的是h文件直接烧录,后边放服务器上供下载使用的时候要改为生成bin文件
H_FILE_PREFIX_COMMENT1 = "/**--------- File:%s.bmp ------------**/\r\n"
H_FILE_PREFIX_COMMENT2 = "/**--------width X height: %s X %s **/\r\n"
H_FILE_PREFIX_1 = "const unsigned char "
H_FILE_PREFIX_2 = "[]={\r\n"
H_FILE_UINT8_PRE = "(unsigned char) "
H_FILE_SUFFIX = "};\r\n"
H_FILE_FORMAT = ".h"H_FILE_VALID_FLAG = 1
H_FILE_COMPRESS_FLAG = 0H_FILE_RECORD_VALID_FLAG = 0xabcd
HEADER_BYTES = 14START_DATE = datetime(1970, 1, 1)# index file
BLOCK_SIZE = 64
PAGE_SIZE = 2048
START_BLOCK = 5  # block index
START_PAGE = 0  # page index in a block
INDEX_FILE = "___res_data_index.h"
NAME_ADDR_REPLACE = "const icon name_replace[]={\r\n"
INDEX_COMMON_RES_FILE = "/****for img res: %s *****/"
INDEX_ADDR_GROUP = "{%s,0},/** res: %s ,data size:%s, pages:%s **/\r\n"
block_acc = START_BLOCK
page_acc = START_PAGE      # current page in a block
##################destPath = None          #数据保存的位置
resPath = None           #图像保存的位置
rbgQuandReverse = False  #是否翻转颜色
px_bytes = None
patch_bytes = None       #用于对齐的填充字节数#移除填充的字节
def remove_filling_invalid_data(datas, px_width, px_height):if(px_bytes == patch_bytes):return datasbuffer = []for i in range(0, px_height):# item = list((datas[i*px_width//8])[0:px_bytes])item = datas[i*patch_bytes:i*patch_bytes+px_bytes]buffer.extend(item)return buffer#翻转数据的顺序,Bitmap从下往上扫描,改为从上往下扫描
def data_sort_to_epd(datas, px_width, px_height):for i in range(px_height-1):datas.extend(datas[((px_height-2-i)*px_width // 8)                           :((px_height-1-i)*px_width//8)])del datas[0:((px_height-1)*px_width//8)]return#判断var从左往右的第bit位的值是0还是1
# var and (1 left shift bit) ,return 0 or 1.
def bit_x_to_bit01(var, bit):return 0 if(var & (1 << (7-bit)) == 0) else 1#将0或1的颜色值填充到var从左到右的第bit位
# var( 1 or 0) left shift bit.
# 1-var:reverse 0/1.
def bit01_to_bit_x(var, bit):global rbgQuandReversereturn ((1-var) << (7-bit)) if(rbgQuandReverse) else (var << (7-bit))#每张图像都会生成一个h文件,并且向索引文件添加一行索引
def create_h_file(destPath, fileName, datas, px_width, px_height, file_index):print("h file path:"+destPath)print("h file name:"+fileName)global block_accglobal page_accdays = datetime.now().__sub__(START_DATE).days# data lengthdata_len = HEADER_BYTES+px_width*px_height//8# page numspage_nums = data_len//PAGE_SIZE+(0 if(data_len % PAGE_SIZE == 0) else 1)if(BLOCK_SIZE-page_acc % (BLOCK_SIZE+1) < page_nums):block_acc += 1page_acc = 0file_index.write(bytes(INDEX_ADDR_GROUP % (hex(block_acc*BLOCK_SIZE+page_acc), fileName, str(data_len), str(page_nums)), encoding='utf-8'))page_acc += page_numswith open(os.path.join(destPath, (fileName+H_FILE_FORMAT)), 'wb') as file_res:# header prefixfile_res.write(bytes(H_FILE_PREFIX_COMMENT1 %fileName, encoding='utf-8'))file_res.write(bytes(H_FILE_PREFIX_COMMENT2 %(px_width, px_height), encoding='utf-8'))file_res.write(bytes(H_FILE_PREFIX_1, encoding='utf-8'))file_res.write(bytes(fileName, encoding='utf-8'))file_res.write(bytes(H_FILE_PREFIX_2, encoding='utf-8'))#后期的应用中,这个字节改为了数据格式的版本,早期的数据文件的文件头中没有crc# record_valid_flagfile_res.write(bytes(hex(H_FILE_RECORD_VALID_FLAG & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(bytes(hex(H_FILE_RECORD_VALID_FLAG >> 8 & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(bytes(hex(H_FILE_RECORD_VALID_FLAG >> 16 & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(bytes(hex(H_FILE_RECORD_VALID_FLAG >> 24 & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(b"/*magic number.*/")file_res.write(b"\r\n")# H_FILE_RECORD_VALID_FLAG# data_lenfile_res.write(bytes(hex(data_len & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(bytes(hex(data_len >> 8 & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(b"/*data length.*/")file_res.write(b"\r\n")# valid.file_res.write(bytes(hex(H_FILE_VALID_FLAG), encoding='utf-8'))file_res.write(b",")file_res.write(b"/*res valid flag.*/")file_res.write(b"\r\n")# compressfile_res.write(bytes(hex(H_FILE_COMPRESS_FLAG), encoding='utf-8'))file_res.write(b",")file_res.write(b"/*res compress flag.*/")file_res.write(b"\r\n")# days from 1970file_res.write(bytes(hex(days & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(bytes(hex(days >> 8 & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(b"/*res days from 1970.*/")file_res.write(b"\r\n")# width.file_res.write(bytes(hex(px_width & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(bytes(hex(px_width >> 8 & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(b"/*res width.*/")file_res.write(b"\r\n")# heightfile_res.write(bytes(hex(px_height & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(bytes(hex(px_height >> 8 & 0xFF), encoding='utf-8'))file_res.write(b",")file_res.write(b"/*res height.*/")file_res.write(b"\r\n")file_res.write(b"/*******now is res px data.**********/\r\n")col = 0row = 0lines = 0temp = 0writed_cnt = 0cvt_index = 0print("h_px_height:"+str(px_height))for i in range(px_height//8):for j in range(px_width):temp = 0for k in range(8):temp |= bit01_to_bit_x(bit_x_to_bit01(datas[(i*8+k)*(px_width//8)+j//8], j % 8), k)file_res.write(bytes(hex(temp), encoding='utf-8'))file_res.write(b",")writed_cnt += 1if writed_cnt % 16 == 0:file_res.write(b"\r\n")file_res.write(bytes(H_FILE_SUFFIX, encoding='utf-8'))returndef auto_release_check():global destPathglobal resPathif(os.path.exists(BMP_CONFIG)):with open(BMP_CONFIG, 'r') as file_read:json_str = json.load(file_read)destPath = json_str["destPath"]resPath = json_str["resPath"]print("destPath:"+destPath)print("resPath:"+resPath)if(os.path.exists(destPath) and os.path.exists(resPath)):return Truereturn Falsedef run_convert(resPath, destPath):global rbgQuandReverseglobal px_bytesglobal patch_bytesfs = os.listdir(resPath)with open(os.path.join(destPath, (INDEX_FILE)), 'wb') as file_index:for f in fs:# header include.file_index.write(bytes("#include \"%s.h\"\r\n" %(f[:-4]), encoding='utf-8'))file_index.write(bytes(NAME_ADDR_REPLACE, encoding='utf-8'))for f in fs:with open(os.path.join(resPath, f), 'rb') as file_read:file_read.seek(FILE_SIZE_POS, 0)file_size = int.from_bytes(file_read.read(FILE_SIZE_BYTES), byteorder='little', signed=False)file_read.seek(DATA_OFFSET_POS, 0)data_offset = int.from_bytes(file_read.read(DATA_OFFSET_BYTES), byteorder='little', signed=False)file_read.seek(PX_WIDTH_POS, 0)px_width = int.from_bytes(file_read.read(BMP_SIZE_BYTES), byteorder='little', signed=False)file_read.seek(PX_HEIGHT_POS, 0)px_height = int.from_bytes(file_read.read(BMP_SIZE_BYTES), byteorder='little', signed=False)file_read.seek(PX_RGBQUAD_FIRST, 0)rgbQuadFirst = int.from_bytes(file_read.read(PX_RGB_BYTES), byteorder='little', signed=False)file_read.seek(PX_GRBQUAD_SECOND, 0)rgbQuadSecond = int.from_bytes(file_read.read(PX_RGB_BYTES), byteorder='little', signed=False)px_bytes = px_width//8patch_bytes = ((px_bytes+3)//4)*4file_read.seek(data_offset)resDatas = file_read.read(patch_bytes*px_height)print("rgbQuadFirst:"+str(rgbQuadFirst))print("rgbQuadSecond:"+str(rgbQuadSecond))if(rgbQuadFirst < PX_RGBQUAND_THRESHOLD and rgbQuadSecond > PX_RGBQUAND_THRESHOLD):rbgQuandReverse = Trueelse:rbgQuandReverse = FalsemidDatas = list(resDatas)midDatas = remove_filling_invalid_data(midDatas, px_width, px_height)data_sort_to_epd(midDatas, px_width, px_height)create_h_file(destPath, f[:-4], midDatas,px_width, px_height, file_index)file_index.write(bytes(H_FILE_SUFFIX, encoding='utf-8'))return# start application
if __name__ == "__main__":os.system('cls')  # clear screenif(auto_release_check()):run_convert(resPath, destPath)else:print("path error!")

点阵墨水屏的使用以及图像预处理相关推荐

  1. 树莓派 java 驱动 微雪 墨水屏 epaper

    树莓派 java 驱动 微雪 墨水屏 epaper B站视频指导 驱动结果显示 官方驱动测试 接线 初步测试 java驱动(仅二值图片展示,非灰阶部分) 引脚初始化 初始化墨水屏 点阵排列 显示方法 ...

  2. 这块DIY墨水屏手表火了!外观可盐可甜,无线蓝牙计步闹钟一应俱全 | 开源

    月石一 发自 凹非寺 量子位 报道 | 公众号 QbitAI 还记得Pebble和索尼的墨水屏手表吗? △图源:Wikipedia/SONY 现在,开源的墨水屏智能手表Watchy来了! 先来一睹为快 ...

  3. 技术宅硬核跨年,DIY墨水屏日历:自动刷新位置、天气,随机播放2000多条「毒鸡汤」| 开源...

    贾浩楠 鱼羊 发自 凹非寺 量子位 报道 | 公众号 QbitAI 自制一个电子墨水屏日历,需要几步? 是能自动刷新位置.天气情况,还能随机播放2000多条毒鸡汤的那种. ↑↑↑就像这样:如果我的人生 ...

  4. MSP430F5529 DriverLib 库函数学习笔记(十)SPI驱动墨水屏

    目录 上机实战 SPI 驱动 墨水屏 墨水屏介绍 电子纸的分类 电泳型电子纸技术详解 原理 结构 优势与不足 实验电路介绍 程序分析 引脚初始化 SPI模块初始化 发送和接收 全部源代码 main.c ...

  5. 基于涂鸦智能开发的墨水屏座位管理器——2.嵌入式功能实现篇

    随着互联网连接技术的日益普及,以及大众环保意识增强,电子纸显示市场不断发展,墨水屏的应用场景也越来越多.墨水屏座位管理器方案具体功耗低,多节点管控,信息实时同步等特点,可应用于智慧办公,智慧零售,智慧 ...

  6. Android 墨水屏黑白红色阶算法和抖动算法,拿过去直接用

    Android 墨水屏黑白红色阶算法和抖动算法, 拿过去直接用, 有问题直接下面问 色阶法 一张图像,我们可以把它划分为几个大的颜色域,图像上的每个像素点根据颜色跟这几个色域的趋近程度,被划分到这些颜 ...

  7. 外设驱动(一)E-Paper墨水屏扫描显示原理

    嵌入式应用笔记 此系列作为产品过程中记录开源库移植和一些遇到的问题,包括有效资源.要点.函数.方法和一些使用心得. 移植 开源库移植(一)轻量级环形缓冲区LwRB 驱动 驱动应用(一)低功耗墨水屏硬件 ...

  8. 【嵌入式项目开源】基于ESP32的墨水屏桌面小屏幕

    首先简要介绍一下这个项目: 这个项目的开源主要包括硬件,固件和软件两部分,外壳后面如果做了也会开源出来.该项目主要是参考了以下教学视频. 链接:桌面小屏幕实战教学 其中硬件部分改动不大,主要是把USB ...

  9. GT30L32S4W中文字库芯片+墨水屏显示调好程序分享

    这公司叫高通,但新版规格书不带地址运算,带地址运算规格书下载GT30L32S4W规格书,带地址运算 芯片管脚配置: 基础驱动及读取代码: /****************************** ...

最新文章

  1. 【运营】策划朋友圈营销必知的微信八大“封杀”规则
  2. c语言块级作用域_块级作用域问题ES5 ES6
  3. mysql 字符串类型 char varchar
  4. 转义字符的使用和功能python_Python中转义符和格式符的混合使用,python,转义字符,与,格式化...
  5. FZOJβ #31.字符串
  6. Github标星35K+超火的Spring Boot实战项目(附项目地址+教程文档)
  7. java JDK8 学习笔记——第13章 时间与日期
  8. 栈在表达式求值中的应用
  9. Nginx+Tomcat+Memecached实现session共享配置
  10. mysql修改默认的存储引擎
  11. Collectors.counting()
  12. timeshift 安装使用说明
  13. [k8s]容器化node-expolore(9100)+cadvisor(8080)+prometheus(9090) metric搜集,grafana展示
  14. 易宝支付 -- 微信小程序对接
  15. Java写的答题助手项目分析与总结
  16. 基于Python的Climate Indices库计算SPEI(标准化降水蒸散发指数)03—单站点不同时间尺度的SPEI计算
  17. Python——百度识图-相似图片爬虫下载解决方案
  18. 【openGL2021版】链接FBX模型库
  19. Qt 之 QSS(黑色炫酷)
  20. C语言基础:求解一元二次方程

热门文章

  1. 微信小程序展开收起通讯录列表
  2. 基于C++MFC的学生成绩管理系统
  3. 一张图看懂手机CPU性能——手机CPU性能天梯图
  4. 深度学习框架之Keras入门教程
  5. windows10安装keras教程
  6. 惠普服务器dl388g7光盘引导,hp dl388g7 无光驱怎么安装系统
  7. windows cmd 添加路由命令
  8. 摄像头、视频采集和摄像设备图像质量判断的几种简单有效目测方法
  9. 第六章 彩色图像处理
  10. NPDP产品经理小知识:端到端的流程建设与跨职能团队管理