网站首页 > 编程文章 正文
前言
大概10年前,我在咱们EEWORLD论坛看到有人分享了用单片机在液晶屏上播放bad apple视频,当时觉得很有趣,但是奈何当时刚入门单片机,很多都不太懂,自己想做却不会做,只能作罢。如今,工作也好几年了,也不做嵌入式这一行。前段时间,趁着周末,工作也不太忙,突然想起这个事情了,于是从网上买了元器件,自己动手再实现这个功能,以弥补自己当年的遗憾,哈哈。
介绍
本文介绍在51单片机上,使用OLED12864(SSD1306)播放视频,并且使用蜂鸣器播放音乐。(因为是gif的原因,看着会比较卡,实际上是不会有卡顿的,实际效果可以点击阅读原文看视频)
最终的效果如下:
播放bad apple视频:
播放数码宝贝的视频:
使用到的主要元器件如下:
- 国产51单片机:STC15F2K60S2
- OLED显示屏:SSD1306,分辨率为128*64
- 无源蜂鸣器,8550三极管等
原理图如下:
具体方案
由于视频文件比较大(MB级别),而51单片机的flash一般都比较小(KB级别),因此把视频文件直接存储在单片机内部显然是不行的。可以把视频文件存储在SD卡里面,然后单片机读取SD卡里面的内容;或者视频文件直接存储在电脑上,然后电脑通过串口实时发送视频数据给单片机,单片机实时显示视频画面。文本采用后者的方案。
OLED12864绘图
我们购买OLED12864(SSD1306)显示屏时,一般卖家都会提供51单片机的示例代码,或者网上也能找到很多相关的代码。
使用这些代码在整个屏幕上绘图时,发现刷新率比较低,在11.0592M时钟频率的情况下,实测大概只有8.6fps。测试方法如下:
void main(){ for(;;) { p27 = ~p27; oled_drawbmp(pic); }}
oled_drawbmp为卖家提供的绘图的函数,每次屏幕刷新一次,p27 IO口翻转一次。使用逻辑分析仪测试p27的电平变化如下,可以看到频率约为4.3Hz,那么屏幕的刷新率大概为8.6Hz(fps)。
因为我们的目的是使用单片机在这款显示屏上播放视频,而一般视频的帧率需要大于25fps,帧率过低就会有卡顿的感觉。显然,上面提到屏幕8.6Hz的刷新率是比较低的,因此我们需要做一些优化。
比较直观且容易的优化方式之一,就是提高时钟频率,把11.0592M提高到24M或者27M的时钟频率。
第二个优化方法就是优化绘图函数。先来看看iic的开始信号和结束信号的代码:
void iic_start() //开始iic{ scl = 1; sda = 1; delay_5us(); sda = 0; delay_5us(); scl = 0;}void iic_stop() //停止iic{ scl = 0; sda = 0; scl = 1; delay_5us(); sda = 1; delay_5us();}
可以看到,里面有一些延时5微妙(delay_5us),其实这个不是必须的,去掉这个延时,iic同样可以正常通信。因此,去掉这个延时,可以加快显示屏的刷新速率。
其次,我们再看看卖家提供绘图部分的函数:
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y)*****************/void oled_drawbmp(unsigned char bmp[]) //画图{ unsigned int j = 0; unsigned char x, y; for (y = 0; y < 8; y++) { oled_set_pos(0, y); for (x = 0; x < 128; x++) { iic_writedata(bmp[j++]); } }}
而iic_writedata的实现如下:
void iic_writedata(unsigned char iic_data) //写数据{iic_start(); write_byte(0x78); write_byte(0x40); write_byte(iic_data); iic_stop();}
可以看到,每写一次图像数据bmp[j],都会有一次iic开始与结束动作,也都会先发送两个控制指令(0x78, 0x40),这其实没有必要,优化后的函数如下:
// 快速绘制图像void oled_drawbmp_fast(unsigned char BMP[]){ unsigned int j = 0; unsigned char x, y; for (y = 0; y < 8; y++) { oled_set_pos(0, y); iic_start(); write_byte(0x78); write_byte(0x40); for (x = 0; x < 128; x++) { write_byte(BMP[j++]); } iic_stop(); }}
可以看到,上面的函数减少了启动iic、结束iic,减少了写控制命令(0x78, 0x40)。使用跟之前同样的测试方法,经过上述的优化,最终屏幕的刷新率如下:
上述的结果为,使用27M时钟频率,加上上面提到的几点优化,可以看出最终屏幕的刷新率约为34.5*2=69Hz(fps),这已经满足我们播放视频所需的屏幕刷新率了。
另外,再提一点,其实还可以进一步优化,使得屏幕刷新率达到100fps以上,测试结果如下。
视频转码成十六进制格式
单片机播放视频,我们需要将视频转码为单片机可以读取的十六进制数据。
首先我们需要将视频分解为一帧一帧的图像,然后可以用如下的取模软件获得图像的十六进制字模。
但是,由于视频的帧数比较多,我们一帧一帧手动的使用取模软件获取字模,显然是一个比较累的活。因此,我们可以写个python代码,批量生成每帧画面的十六进制数据。python代码如下:
import cv2
def bit2num(pixcels): output_val = 0 for i, pix in enumerate(pixcels): if pix > 128: # 白色 output_val += pow(2, i) return output_val
def main(): video_path = 'BadApple.flv' cap = cv2.VideoCapture(video_path) # 打开视频 cnt = 0 fout = open('bad_apple_data.txt', 'w') # while True: ret, frame = cap.read() # 一帧一帧的读取 if not ret: break cnt += 1 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 转换为灰度的画面 frame = cv2.resize(frame, (128, 64)) # 图像尺寸调整到128*64大小 convert_val = [] for row in range(0, 8): # page0 ~ page7 for col in range(0, 128): # seg0 ~ seg127 cur_data = frame[row*8: row*8+8, col] # 取出对应的8个像素点 convert_val.append(str(bit2num(cur_data))) # 转换成8位的数据 fout.write("%s\n"%(','.join(convert_val)))
# cv2.imshow("capture", frame) #显示画面 # if cv2.waitKey(30) & 0xff == ord('q'): #按q退出 # break
main()
代码比较简单,其中会调用OpenCV的库,用来读取视频以及视频画面对应的像素值。
串口发送
视频数据准备好之后,我们需要把视频数据通过串口发送给单片机,单片机接收到完整的一帧数据(一帧画面)之后,就可以开始显示画面。我们同样可以写个python代码来将视频数据发送给单片机,代码如下:
import serial # 导入串口相关的库from time import sleep
def get_bmp_data(): filepath = 'bad_apple_data.txt' f = open(filepath) bmp_data = [] for line in f: val = line.strip().split(',') if len(val) == 0: continue bmp_data.append([int(x) for x in val]) f.close() return bmp_data
def main(): com = serial.Serial('com10', 345600, timeout=10) # 设置端口号,波特率,超时时间 if not com.isOpen(): # 判断端口是否打开成功 raise "端口打开失败"
bmp_data = get_bmp_data() # 读取刚刚生成的TXT文件 for frame in bmp_data: # 一帧一帧的发送数据 ret = com.write(bytes(frame)) # 将数据转换成二进制后发送 sleep(0.03) # 延时适当时间
main()
代码比较简单,其中会调用串口相关的serial库,然后每次循环发送一帧数据,直至全部发送完成。
小结
最后,总结一下 在51单片机播放视频的大致流程:
- 视频解码成一帧帧的图像,然后再转码成显示屏可以显示的十六进制格式(这一步可以提前完成)
- 电脑通过串口把十六进制格式的视频数据发送给单片机
- 单片机接收到完整的一帧数据(一幅图像)后,显示屏开始显示画面
- 同时,蜂鸣器播放音乐(可选)。
- 上一篇: STM32学习笔记——GUI之emWin的使用
- 下一篇: 超赞!史上最全的43款字体设计工具大集合
猜你喜欢
- 2024-10-24 全瓷牙贴面为什么要取牙模?(全瓷牙 贴面)
- 2024-10-24 《楚简老子通读》高清图版文字横排电子书简繁体区别
- 2024-10-24 正点原子开拓者FPGA开发板资料连载第二十章 VGA字符显示实验
- 2024-10-24 Arduino入门 第五节 摇摇棒(arduinops2摇杆)
- 2024-10-24 用STM32做了个电子秤,成本仅两位数,精度高!解析一下原理
- 2024-10-24 STM32+OLED屏显示字符串、汉字、图片(二)
- 2024-10-24 OLED(0.96寸)如何取模(oled模块使用手册)
- 2024-10-24 12864液晶屏是怎么显示图片的?
- 2024-10-24 「专升本信息技术」计算机基础知识单选题集 (12)
- 2024-10-24 基于前人经验,为MM32L073单键胸牌DS3231时钟配上3D打印外壳
你 发表评论:
欢迎- 06-24一个老爸画了超级有爱的365幅画 | 父亲节献礼
- 06-24产品小白看魏则西事件——用产品思维审视百度推广
- 06-24某教程学习笔记(一):13、脚本木马原理
- 06-24十大常见web漏洞——命令执行漏洞
- 06-24初涉内网,提权那些事(内网渗透提权)
- 06-24黑客命令第16集:47种最常见的**网站方法2/2
- 06-24铭说 | 一句话木马的多种变形方式
- 06-24Java隐藏的10倍效率技巧!90%程序员不知道的魔法方法(附代码)
- 最近发表
- 标签列表
-
- spire.doc (70)
- instanceclient (62)
- solidworks (78)
- system.data.oracleclient (61)
- 按键小精灵源码提取 (66)
- pyqt5designer教程 (65)
- 联想刷bios工具 (66)
- c#源码 (64)
- graphics.h头文件 (62)
- mysqldump下载 (66)
- libmp3lame (60)
- maven3.3.9 (63)
- 二调符号库 (57)
- git.exe下载 (68)
- diskgenius_winpe (72)
- pythoncrc16 (57)
- solidworks宏文件下载 (59)
- qt帮助文档中文版 (73)
- satacontroller (66)
- hgcad (64)
- bootimg.exe (69)
- android-gif-drawable (62)
- axure9元件库免费下载 (57)
- libmysqlclient.so.18 (58)
- springbootdemo (64)
本文暂时没有评论,来添加一个吧(●'◡'●)