试用12864液晶播放.docx_第1页
试用12864液晶播放.docx_第2页
试用12864液晶播放.docx_第3页
试用12864液晶播放.docx_第4页
试用12864液晶播放.docx_第5页
已阅读5页,还剩32页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

试用12864液晶播放Bad Apple发表回复Bad Apple是一个很有趣的视频,配上棒棒的音乐,让我这个非二次元的人也对此深感兴趣。这是Bad Apple的观看链接:/video/av706/这是Bad Apple的维基百科链接:/zh/Bad_apple!这个作品是一个影绘作品,画面仅有黑色和白色两色组成,因此被好事者弄到了各种点阵屏上播放。我最近从寝室里面翻出来一块初中的时候玩的12864液晶屏,颇为感慨,于是才萌发出一个把Bad Apple放这屏幕上显示的想法。/2014/08/IMG_5710.mp400:0000:0000:00我现在做到的情况是,原视频帧率30,我能以平均10帧的速度回放。使用STC12C5A32S2单片机,时钟频率24Mhz,单片机串口波特率460800 bps,屏幕驱动芯片为ST7920,并行形式驱动。不带声音。下位机用C写的,上位机用Python写的。(其实就是专门来学Python。)值得一提的是,看到这块屏幕的插针的时候,才觉得那个时候我的焊工真是超级烂啊。介绍一下12864液晶,这是一种点阵型的液晶,分辨率为128(宽)x64(高),颜色只有黑白两种。当然有些人看了下面的图之后会问我,“这不明明是蓝黄两色吗,你色盲吧?“,对于这样的同学我也表示赞同,希望你们把这样作死的精神继续发扬下去。这是今天的主角12864屏幕屏幕一角的微距拍摄图(屏幕上面比较脏)播放Bad Apple时屏幕微距拍摄播放Bad Apple时的屏幕结构整个系统的结构和过程都很简单,下面我来大致描述一下我程序的工作过程。1. 将原始的Bad Apple的视频转换成128x64分辨率的视频bad_apple.mp42. 利用ffmpeg将bad_apple.mp4的6569帧转换为6569张bmp文件存于capture文件夹3. 运行compressor.py1. 将6569帧bmp图像全部读入内存存到列表lFrame中,并在lFrame头添加一全黑帧2. 将每一帧转换为适应12864液晶屏的双字节格式,并按lFrame中的顺序存于列表lDByteString中3. 从第一帧(不要忘了第零帧)开始直到最后遍历lDByteString中的每一帧,将每一帧与上一帧的所有差异找出,并按指定的文件格式存放到文件bad_apple.jh中4. 运行player.py1. 按照指定的文件格式读取存放于文件bad_apple.jh的每一帧2. 打开串口,连接屏幕(我们称下位机为屏幕)3. 遍历每一帧,并把每一帧的所有差异字节发送到屏幕,同时计算每一帧的耗时,将耗时控制在1.0(秒)/30(帧)4. 播放结束将原始的Bad Apple的视频转换成128x64分辨率的视频bad_apple.mp4这一步我们使用Mediacoder对视频进行转码。我随意找了台跑着老掉牙的XP的虚拟机,装了个我最熟悉的Mediacoder转换视频。至于码率之类的参数都无所谓,视频也就那么点大,只要分辨率压缩正确就行了。另外我没有将音频音轨压入bad_apple.mp4,反正我也用不到。; )利用ffmpeg将bad_apple.mp4的6569帧转换为6569张bmp文件存于capture文件夹得到bad_apple.mp4文件之后,利用ffmpeg就可以直接的把视频转换为图片序列,这个功能很方便1ffmpeg -i bad_apple.mp4 capture/%d.bmp转换的过程大致如下:(背景是慈母madoka喔)转换出一大堆文件:运行compressor.py程序compressor.py将会对图片序列进行预处理,生成一个bad_apple.jh的文件。提示 本文并非教您使用12864液晶,下面的说明仅为后文做铺垫。我们知道,12864的屏幕可以用下列表格示意,一个格子对应一个像素。根据文档所述,屏幕可以分为这16块(每一块宽16个像素,高32个像素),对应的是X方向的坐标。而这16块中的每一块,又可以分为32行(每行宽16个像素,高1个像素),这里对应的是Y方向的坐标。每一行代表中的又可以分为16个像素点,也就是8个像素点+8个像素点,也就是两个字节。如果想在屏幕上画个点,或者是删个点,最低的操作单位就是上面的一行(也就是16个像素点)。随后记录帧差异的时候,就会按一个DByte为单位进行比较,也就是按上面的一行为单位进行比较。将6569帧bmp图像全部读入内存存到列表lFrame中,并在lFrame头添加一全黑帧在这一步,我们通过Python Imaging Library(简称”PIL“)这个库来读取图像信息。因为视频Bad Apple本身就是黑白的视频,虽然考虑视频中有灰度的情况(如1分54秒处的普莉兹姆利巴三姐妹有灰色的影子),但我们也把读取到的每一帧转换为灰度模式。1frame = frame.convert(L)我们把文件夹capture里面的每一帧都读取进lFrame后。这时列表lFrame中存储的一系列的Image对象。将每一帧转换为适应12864液晶屏的双字节格式,并按lFrame中的顺序存于列表lDByteString中再使用函数convertToDByteString()将每一帧的Image对象转换为一个DByte(DByte是双字节的意思,即代表16位的变量)的列表,这个列表的结构请参考函数convertToDByteString()。因为通过上面的convert(L)操作后,每一个像素点由RGB三色描述,变为由range(0,256)的灰度值所描述。为了考虑把灰度值和我们12864液晶上的点对应起来(想在12864实现灰度非常困难,所以我们不考虑在屏幕上实现灰度效果,这里的12864的每个点只有两种状态),我们考虑每一个像素点的灰度值,如果大于等于128,则判定为屏幕上这个像素点点亮,否则是灭。转换完之后,我们得到一个存放有(以DByte列表形式存放的每一帧)的列表lDByteString(名字随便取的,别吐槽)。从第一帧(不要忘了第零帧)开始直到最后遍历lDByteString中的每一帧,将每一帧与上一帧的所有差异找出,并按指定的文件格式存放到文件bad_apple.jh中我们从第一帧开始(第零帧是我们之前填的空白帧)遍历lDByteString中的每一帧,将每一帧与其上一帧的差异找出。这里我直接用了个炫酷的lambda函数实现的。12difference = map(lambda x, y: True if x != y else False,lDByteStringframe_no, lDByteStringframe_no-1)然后将帧差异写入到bad_apple.jh文件中。这里的过程,容易使人想到H.264之类视频编码里面的关键帧这一概念,在这里,我的关键帧只有第零帧,后面的帧都是通过计算与前一帧的差异得到的。bad_apple.jh的文件格式其实非常简单。差异数是一个DByte,高字节在左,低字节在右,用于描述随后差异的数量。差异是由四个字节描述,前两个字节分别描述当前差异所在的屏幕坐标,后两个字节描述该坐标新的内容。即刷新到这一帧时,这个屏幕坐标所在位置的DByte会被新的内容所取代。这里引入了两个新的概念,一是“屏幕坐标”,指的是一个宽16个像素高1个像素的像素块在屏幕的位置,具体概念见运行compressor.py部分。另一个概念是“自然坐标”,我们定义视频的左上角的点为(0,0),横轴为x轴,纵轴为y轴,横轴正方向为右,纵轴正方向为下。自然坐标是用来描述点的位置的。当然这两个概念都是我瞎编的.只是为了方便描述才定义的。程序compressor.py中有两个函数_locateDByteFromPixel_()和_locateMSBPixelFromDByte_()。第一个函数的功能是将自然坐标的点映射到屏幕坐标的位置,这样就可以知道这个点属于屏幕上的哪一个DByte了。后面那个函数(这个函数在程序里面并没有被用到,我也不知道我为什么写了个这个函数),功能几乎是相反的,给它一个屏幕坐标,返回一个自然坐标,这个自然坐标描述给定的屏幕坐标最左边的那一个像素点的位置。运行player.py程序player.py是用于将bad_apple.jh文件里面的数据,经过简单处理后,通过串口传输到下位机。这个程序里面import了另外一个device.py程序,这个程序是负责串口通讯的。按照指定的文件格式读取存放于文件bad_apple.jh的每一帧这个没什么好说的。rawStream用于存放直接读取到的文件。列表stream用于存放每一帧,每一帧也是一个列表,是记录差异列表(长度为4,即四字节)的列表。(这段很绕,建议直接读代码)打开串口,连接屏幕(我们称下位机为屏幕)这部分使用的是库pyserial里面的函数,也没啥特别的。我的USB转串口的线采用的是PL2302芯片,很开心在OS X下面有驱动,但是总感觉这个驱动怪怪的。插入串口线之后,会在/dev/目录下出现一个文件,这个文件就是用来访问串口的。device.py中的DEVICE_FILE就是说明这个文件的位置,每次重启电脑的之后,这个文件的后几位都有变化,需要更改。串口没啥好说的,波特率是460800,在单片机上跑得很艰辛的说.因为单片机上对串口做了特殊的处理,有时候可能会丢数据,为了避免丢数据之后画面卡着一直不动,这里将timeout设置为了1秒,即串口的read()函数最多只会堵塞1秒的时间。本来我设计的是,上位机程序启动的时候,发送一个0x00的信号,单片机此时自动测量波特率。但后来要在单片机上跑上460800波特率,只能手动用IO模拟串口了。(幸亏有示波器。)所以这个自适应波特率的功能,也被固定波特率所取代。串口的话,为了保证愚慢的单片机跟得上,单片机收到数据并处理完之后,会返回一个0x00以表示处理完成,这个时候电脑才会继续下一步的反应。遍历每一帧,并把每一帧的所有差异字节发送到屏幕,同时计算每一帧的耗时,将耗时控制在1.0(秒)/30(帧)单片机方面,上电之后首先会进入扩展指令模式,并启用绘图模式,等待来自上位机的数据。它会无限循环式的依次接收之前说的差异的四个字节,接收到前两个字节时,每接收到一个并向屏幕发送指令之后,返回一个0x00,再接收后两个字节,每接收到一个并向屏幕发送数据之后,返回一个0x00。这就是整个程序的描述。关于进一步优化的话,我想到过很多方面。首先可以肯定的是瓶颈在串口上,即便我跑到了460800bps,依然无法承受视频数据量之大。这里我做了个测试,当单片机在使用串口时,我把某个IO口拉低,然后用示波器记录。你可以观察到,绝大部分时间这个IO口都处于低电平的位置,所以串口依然是瓶颈。互联网上绝大部分12864跑视频的例子,都是这么用SD卡等存储介质来存放视频数据。而使用串口实时传输数据的动机,竟然只是我手上只有一张32G的高速卡,担心弄坏。然后,刚买的2G卡还在快递那儿。(似乎逻辑很奇怪吧.)不改变硬件,而进一步优化的方向只有三个:一,降低串口传输的数据量;二,提高串口的速率;三,提高串口的使用率。降低串口的数据量。可以考虑使用LZW无损压缩算法,对bad_apple.jh进行数据压缩。LZW算法我没有仔细学习,粗略浏览之后,觉得在单片机上实现实时解压缩应当不会有性能问题。还可以借助梅森旋转算法,来个很特别的压缩。(这个压缩方法,我目前还没有想太明白,暂时有个思考的方向)提高串口的速率,只能考虑提升波特率了。提升波特率一方面是重新调整串口的时序,一方面是提示单片机的运行频率。我手上的单片机最高频率是35Mhz,但是基频在33Mhz以上的晶振几乎没有,而三泛音的晶振本小白也不会用。我手上基频最高的晶振就是24Mhz了,如果说花6块钱运费在网上再买几个晶振,总觉得有点奢侈。这个方案暂缓。提高串口的使用率。可以考虑在单片机内部划一块空间作为cache使用,这样对瞬间变化的场景卡顿的现象会有极大极大的帮助,但是可惜的是我手上的这块单片机SRAM才区区1280字节暂时也不抱希望了(有个10K内存的单片机估计就可以满帧跑这个视频了)过几天我的便宜的SD卡到了之后,我先试试用过SD卡播放视频吧。这整个系统没任何实用价值,程序代码几乎没有遵循代码规范,阅读起来有点困难,深感抱歉。还有,用IO口模拟的串口,不同于硬件串口会进行三次采样,所以有时候会丢数据。(但是对于视频,小量的丢数据是可以容忍的)单片机方面的程序,我也是在网上找来改改的。做这个东西的目的也可以说是为了学习Python参考资料: 利用ffmpeg将AVI输出BMP序列 Python函数式编程指南(三):迭代器 Python读写文件:/paper/books/dive-into-python3/files.html 梅森旋转算法 宣传吐槽向东方project同人魅力(标清) Python 三目运算符与三元表达式 Python二维数组实际应用方法浅析 Python:使用 lambda 函数 Python中的enumerate()函数:http:/100/196179/216770 STC单片机波特率自适应方法 PL2302芯片在OS X下的驱动:http:/www.flytec.ch/de/support/downloads/usb-serial-driver.html pySerial的文档:/pyserial/pyserial_api.html Python中的字节是什么:/paper/books/dive-into-python3/strings.html#byte-arrays Python对象的深拷贝与浅拷贝 Python模块学习 - logging 日志记录(一)程序源码:device.py123456789101112131415161718192021222324252627282930313233343536373839# coding: utf-8# device.pyimport serialimport sysfrom time import sleepimport timeDEVICE_FILE = /dev/cu.PL2303-0020121Adef connectScreen():global serser = serial.Serial()ser.port = DEVICE_FILEser.baudrate = 460800ser.timeout = 1ser.open()print +_file_+Serial port openedprint +_file_+The screen is readydef writeDataDByte(data):while True:ser.write(data8)if x00 = ser.read():breakwhile True:ser.write(data&0xFF)if x00 = ser.read():breakdef setXY(x, y):x = 0x80 + xy = 0x80 + ywhile True:ser.write(y)if x00 = ser.read():breakwhile True:ser.write(x)if x00 = ser.read():breakplayer.py123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566# player.pyimport deviceimport osimport timeFPS = 30.0rawStream = stream = def readVideo(filename):global streamprint +_file_+ + Reading file + filenamefin = open(filename, r)rawStream = fin.read()rawStreamI = iter(rawStream)frameLength = 0try:while True:frame = frameLength = ord(rawStreamI.next() 1.0/FPS:#print +_file_+ + Skipped out#breakdevice.setXY(dif0, dif1)device.writeDataDByte(dif28) | dif3)while time.time() - curFrameStartTime 1.0/FPS:passframeClock.append(time.time()frameIntervalTime.append(frameClockframeNo+1 - frameClockframeNo)fps = 0 if frameNo =128, frame.tostring()print Converting Frame %d to DByteString%frame_nodByteString = for bit16 in bitStringi:i+16 for i in xrange(0, len(bitString), 16):r = 0for p in bit16:r = r = 32 else x/16, y%32def _locateMSBPixelFromDByte_(X, Y):return X%8*16, Y+32 if X = 8 else Yif _name_ = _main_:print Preparing videofout = open(OUTPUT_FILENAME, wb)lFrame = Image.new(L, (128, 64), 0) + readFrame()lDByteString = convertToDByteString(lFrame)for frame_no in range(1, FRAMES):difference = map(lambda x, y: True if x != y else False,lDByteStringframe_no, lDByteStringframe_no-1)difCount = sum(difference)fout.write(%c%c%(difCount8, difCount&0xFF)for differDBytePos in range(0, 128*64/16):if differencedifferDBytePos:x = differDBytePos%8*16y = differDBytePos/8X, Y = _locateDByteFromPixel_(x, y)fout.write(%c%c%c%c%(X&0xFF, Y&0xFF, lDByteStringframe_nodifferDBytePos8, lDByteStringframe_nodifferDBytePos&0xFF)print frame=%d, difDByte=%d%(frame_no, difCount) fout.close()print This is the end of video preparing单片机上的程序123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144/ main.c#include #include #define DATAPORT P0sbit RS = P25;sbit RW = P26;sbit EN = P27;sbit PSB = P22;sbit RST = P24;void Delay75us() /24MHzunsigned char i, j;_nop_();_nop_();i = 2;j = 188;dowhile (-j); while (-i);unsigned char Busy()RS = 0;RW = 1;EN = 1;while(DATAPORT & 0x80) = 0x80);EN = 0;return 1;void WriteCmd(unsigned char cmd)while( Busy() = 0)Delay75us() ;RS = 0;RW = 0;EN = 0;DATAPORT = cmd;EN = 1;Delay75us() ;EN = 0;void WriteData(unsigned char dat)while( Busy() = 0)Delay75us() ;RS = 1;RW = 0;EN = 0;DATAPORT = dat;EN = 1;Delay75us() ;EN = 0;void writeUART(unsigned char c)unsigned char i;TXD = 0;for (i=11; i0; -i);TXD = 0x01 & c;for (i=6; i0; -i);TXD = 0x01 & (c = _cror_(c, 1);for (i=6; i0; -i);TXD = 0x01 & (c = _cror_(c, 1);for (i=6; i0; -i);TXD = 0x01 & (c = _cror_(c, 1);for (i=6; i0; -i);TXD = 0x01 & (c = _cror_(c, 1);for (i=6; i0; -i);TXD = 0x01 & (c = _cror_(c, 1);for (i=6; i0; -i);TXD = 0x01 & (c = _cror_(c, 1);for (i=6; i0; -i);TXD = 0x01 & (c = _cror_(c, 1);for (i=12; i0; -i);TXD = 1;for (i=11; i0;

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论