|
首先感谢大家和网站的支持!上个月把一块 LCD-320240 把玩成了一块高度抽象的实体模块,取得了不错效果:https://www.mydigit.cn/thread-474575-1-1.html
然这样搞出来终究只是个单片机显示屏,若无大用必然吃灰。
整天拿笔记本写东西,副屏又恰好不在手头,着实有些不便。
这时候如果能把 320240 模块搞成 Windows 副屏,必然神清气爽。
技术上,本文介绍了一系列 C/C++ 软件代码的设计框架,并最终成功实现:
使用 Arduino+u8g2 类,驱动带串并中继的 LCD-320240;
设计 Win32-App,实现基于串口的 Windows 桌面副屏扩展。
【系统结构】
Windows 原生支持多屏幕显示,快捷键 Win+P 即可召唤多屏设置。
当采用“扩展”方式延伸桌面时,相当于将桌面坐标系扩展到多块显示器上,实现拼接效果:
此次盘屏把玩即以此为目标,预计给笔记本盘一个可扩展的单色副屏。
提要求容易,实际做起来相当费劲。Windows 常见显示器如图所示:
Windows OS 通过内置的 VGA/HDMI 驱动访问相应端口,实现显示。
此次折腾桌面副屏有两个技术路线:
(a) 如图蓝色,设计 VGA/HDMI 采集器获取图案,进而驱动 LCD;
(b) 如图绿色,设计虚拟驱动直接抽取图像,经USB下发给单片机。
方案 (a) 相当于自造了一台标准显示器,兼容性好,但硬件要求高;
方案 (b) 从实用出发,折中了硬件vs软件复杂度,更适合折腾。
大致定下系统结构后,开始从液晶底层一步步搭建LCD副屏系统。
【u8g2 刷屏原理】
之前模块化设计已将 LCD320240、CPLD串并中继封装进 u8g2 驱动类,
简单的 u8g2_GDI 接口调用即可实现绘图显示,例如:
然此次目标是向LCD刷入任意图像,在不放弃 u8g2 的前提下,需要使用其底层 u8x8 接口:
这样一来在保证液晶访问效率的前提下,可最大程度复用之前的类封装工作,避免重复造轮子。
用于刷新 LCD-320240 的 u8x8_API 原型如下:
对于 LCD320240,使用 drawTile 可绘制一个 320px*8px 的条形区域,内存消耗量为 320Bytes:
连续调用 30 次 drawTile 即可完成整屏刷新,内存消耗量不超过 320 B.
在 16MHz Arduino UNO 的驱动环境下,整屏刷新时间大约 1s,
与之前 19264 的单位面积刷新速率基本一致。
由于 Arduino 平台基本是硬件无关的,因而可以通过升级 CPU 获得更快刷新率。
这次折腾以 UNO 为准,尝试在低性能平台上跑到极致效果。
【Win32+AVR 双缓存传输】
至此在硬件+u8g2 层面已实现显示驱动:输入 30*320 字节,即可刷新全屏。
然实际系统中,显示数据位于上位机 Windows OS 中,
刷屏接口位于下位机 Arduino UNO 中,二者需要使用硬件串口实现数据互联。
为确保下位机刷屏效率不减,使用了双缓存机制进行传输:
下位机 Arduino UNO 使用乒乓缓存结构,实现软件刷屏、硬件接收同时进行:
线程#1 负责将 BufA 数据送入 u8x8_drawTile,实现实时刷屏;
线程#2 负责将 UART 数据送入 BufB,实现后台接收。
当刷屏与接收均完成后,切换 BufA 与 BufB 角色,零延时进入下一包数据处理。
对于 AVR 这类简单单片机,双线程乒乓操作由串行中断实现:
为了避免奇怪的 BUG,双线程设计了完备的临界区及接口,实测效果不错;
对于 ARM 或其他支持 DMA 的单片机,使用 DMA 可以更高效实现双线程工作。
上位机这边则需要一套同步机制,当下位机请求新数据包时予以回应(例如刷屏+接收均已完成):
至此已打通任督二脉,Win32 上位机数据可以高速下传给 Arduino 下位机,
并由后者同步刷新至 LCD-320240 单元模块,实现实时显示刷新。
实测加入了接收线程后,全屏刷新时间由 1.0s 上升至约 1.5s,
若采用支持 DMA 的处理器进行接收,相信对刷屏时间的影响会更小。
【Win32 截屏】
写到这里,技术上已实现了下位机刷屏、上下位机串行传输,
距离 Windows 桌面副屏只差一层虚拟的屏幕驱动代码:
让 Windows 认为有这么块屏幕,并向这块屏幕显示窗口内容。
实际做起来可借助现有的 VGA/HDMI 驱动:
在 Win+P 菜单中以扩展方式开启副屏,Windows 会扩展桌面坐标区域,并向相应端口(以VGA为例)输出视频信号。
此时物理 VGA 端口上并不插真实显示器,但另写一段 Win32-App 对副屏坐标区域进行截屏(如图绿色部分),
以此就可以成功把 Windows 显示数据抠出来送下位机,实现虚拟的显示驱动。
说干就干,截屏代码大致流程是 Win32API::GetDC( ) => ::BitBit( ) => ::GetDIBits( )
至此,上位机获得了一张存于内存中的 320x240xRGB888 截屏像素矩阵。
【Win32 二值化】
此次把玩的 LCD-320240 属于单色液晶屏,每个像素的数据仅有 1bit,
像素显示效果仅有纯蓝、纯白两种样式,通常记作 1bpp 位深度。
常见的彩屏液晶使用 16M 真彩色域,每个像素的数据有 24bit,
像素显示效果为红、绿、蓝三个分量的叠加,各分量再细分 256 阶亮度,
通常记作 24bpp 位深度,也是前文截屏获所获得的图像结构。
将上述 24bpp 的像素矩阵转换为 1bpp 像素矩阵的过程,称作“图像二值化”:
二值化过程中图像的色彩信息完全丢失,仅保留部分灰度信息,其余全靠观众脑补。
好的二值算法可以通过抖动(Dithering)模拟中等灰度,效果有灵魂;
渣的二值算法直接给你糊成一坨黑白,结果不忍直视。
下图为常见的全局阈值法二值化所得,即判断 R,G,B 的加权平均是否超过阈值,作为输出黑白的依据:
左图选择阈值太低,计算结果大量被认为是白色,进而亮瞎眼+断线;
右图选择阈值太高,计算结果大量被认为是黑色,进而糊成一坨。
全局阈值算法简单粗暴,但一个合适的阈值显得尤为重要。
当然也可以通过统计来自动计算一个最佳阈值,例如“大津阈值”.
副屏显示这里就偷个懒,直接搞个拖动条来设置全局阈值,省去诸多烦恼:
【实现效果】
搞了这么久,终于把上下位机都搞定了,再就是 CPLD 也烧好了。
果断插上 USB 通上电,亮屏还是冒烟全随缘。
上位机界面很给力,把自己截成图片传给了下位机,320240 液晶努力显示中:
把“我的电脑”窗口拖过来看看,效果挺像回事:
Win10 里调整下各显示器的相对坐标,开个数码坛看看分屏扩展,大致对上了:
受限于 AVR 的性能,刷新率不能太高,副屏适合显示静态参考资料。比如写东西列个大纲啥的:
当然最最重要的用途,监视各路下载/上传进度:
折腾了一大圈,获得一只拥有着朴素色彩的 Windows 副屏显示器。
附上文中涉及的源码,整理不易,仅在本坛分享:
------------------------------------------------------------------------------------------------------------------------
以上,从模块化开始,把一只 LCD-320240 最终盘成了 Windows 桌面副屏。
成功在单片机上亮起了 Win10 特有界面,获得了一只朴素的显示器,实测辅助写东西效果不错。
最后祝大家 DIY 愉快!
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
打赏
-
查看全部打赏
|