
FPGA流光溢彩控制器
简介
FPGA硬件实现的流光溢彩控制器,微信小程序配置灯珠,集成HDMI分配器,支持HDMI输入,1080p/4K视频,支持WS2812,WS2815等系列灯带。
简介:FPGA硬件实现的流光溢彩控制器,微信小程序配置灯珠,集成HDMI分配器,支持HDMI输入,1080p/4K视频,支持WS2812,WS2815等系列灯带。开源协议
:CERN Open Hardware License
(未经作者授权,禁止转载)描述
项目说明
视频氛围灯控制系统,就是根据视频信号画面边缘的颜色,控制电视边框对应灯带颜色,视觉上起到平滑过渡的效果,营造出了视频内容和外界环境融为一体的氛围。
目前商用的主要是飞利浦的流光溢彩系列电视以及hue系统,而常见的开源项目,多是通过树莓派或者PC,安装复杂繁琐的软件,并且需要众多配置步骤,硬件上经过多个模块转换来实现,其核心原理是通过网络抓取视频流,软件分析计算视频流,从视频流中提取画面对应的色彩信息,最后再通过GPIO口,或者USB转串口来控制可编程的灯珠,本项目特点是设备简单,只需要一个的控制器,即插即用。前者的缺点显而易见,软硬件配置复杂,同时软件系统的顺行处理,必然会导致氛围灯的色彩与视频信号的产生滞后,而本系统使用FPGA纯硬件电路实现,不存在软件系统顺序执行导致的延迟,因而会有更完美的视频氛围场景体验。
开源协议
CERN Open Hardware License
项目相关功能
1)集成HDMI一分二控制器,支持主流的1080p 、4K@60Hz刷新;
2)同时支持8路不同灯带配置,可单独控制或拼接控制;
3)标准DC接口,或者螺柱端子接线,电源输入5-15V,适配WS2812或WS2815系列灯带
4)微信小程序直接控制灯珠配置,适应各种场景应用。
项目属性
本项目为首次公开,为本人原创项目,项目未曾在别的比赛中获奖。
项目进度
2024-2-18 设计构思文档完整中
2024-2-22 硬件工程设计中
2024-2-28 PCB完成,贴片生产
2024-3-15 基本功能调试完成,可以跟随视频控制灯带变化
2024-3-20 3D外壳及贴纸设计完成
设计原理
硬件系统整体设计框图

软件说明
软件部分主要分为两个部分
1. FPGA处理部分的代码,这部分代码参考开源项目 https://github.com/esar/hdmilight-v2
2. 灯珠场景配置部分代码,分为控制器配置代码,以及微信小程序用户交互设置代码
FPGA模块是通过串口指令和外界交互,几个比较重要的串口接口命令引用如下
Set Area-------- SA index xmin xmax ymin ymax divshift* index (0-255): The index or range of indices of the area definition(s) that should be set* xmin (0-63): The left most column of the area's rectangle* xmax (0-63): The right most column of the area's rectangle* ymin (0-63): The upper row of the area's rectangle* ymax (0-63): The lower row of the area's rectangle* divshift (0-): The number of places to right-shift the accumulated R, G and B value for the area, or in other words the divisor used to calculate the average * 1 = divide by 2 * 2 = divide by 4 * 3 = divide by 8 * etc.
Get Output Map-------------- GO output light* output (0-7): The index or indices of the output channels whose mapping should be retrieved* light (0-511): The index or indices of the LED(s) whose mapping should be retrieved
Set Output Map-------------- SO output light area colour gamma enable* output (0-7): The index or indices of the output channels whose mapping should be set* light (0-511): The index or indices of the LED(s) whose mapping should be set* area (0-255): The index of the area definition that the LED(s) should use* colour (0-15): The index of the colour matrix that the LED(s) shoudl use* gamma (0-7): The index of the gamma table that the LED(s) should use* enable (0-1): * 1 = enabled * 0 = disabled
控制器板上MCU与FPGA交互关键代码
void SetLightToFrame(int LightNO, uint16_t yLEFT, uint16_t xUP, uint16_t yRIGHT, uint16_t xDOWN){
for (int i = 0; i < yLEFT; i++) { setOutput(LightNO, i, map(i, 0, yLEFT - 1, 32, 0)); }
for (int i = yLEFT; i < yLEFT + xUP; i++) { setOutput(LightNO, i, map(i, yLEFT, yLEFT + xUP - 1, 33, 92)); }
for (int i = yLEFT + xUP; i < yLEFT + xUP + yRIGHT; i++) { setOutput(LightNO, i, map(i, yLEFT + xUP, yLEFT + xUP + yRIGHT - 1, 93, 125)); }
for (int i = yLEFT + xUP + yRIGHT; i < yLEFT + xUP + yRIGHT + xDOWN ; i++) { setOutput(LightNO, i, map(i, yLEFT + xUP + yRIGHT, yLEFT + xUP + yRIGHT + xDOWN - 1, 185, 126)); }}
控制器板上MCU与微信小程序交互关键代码
// Create the BLE Device BLEDevice::init("流光溢彩控制器");
// Create the BLE Server pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic pTxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY );
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE );
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service pService->start();
// Start advertising pServer->getAdvertising()->start();
微信小程序与板上MCU交互关键代码
//index.js//获取应用实例const app = getApp()function inArray(arr, key, val) { for (let i = 0; i < arr.length; i++) { if (arr[i][key] === val) { return i; } } return -1;}// ArrayBuffer转16进度字符串示例function ab2hex(buffer) { var hexArr = Array.prototype.map.call( new Uint8Array(buffer), function (bit) { return ('00' + bit.toString(16)).slice(-2) } ) return hexArr.join('');}//ASCII码转16进制function strToHexCharCode(str) { if (str === "") { return ""; } else { var hexCharCode = []; hexCharCode.push("0x"); for (var i = 0; i < str.length; i++) { hexCharCode.push((str.charCodeAt(i)).toString(16)); } return hexCharCode.join(""); }}// 字符串转bytefunction stringToBytes(str) { var array = new Uint8Array(str.length); for (var i = 0, l = str.length; i < l; i++) { array[i] = str.charCodeAt(i); } console.log(array); return array.buffer;} Page({ data: { devices:[], connected: false, chs: [], sendData:"", logs: [], dataType: true,//false: HEX type, true: ASCII type }, printLog:function(log) { var logs = this.data.logs; logs.push(log); this.setData({log_list: logs.join('\n')}) }, printInfo:function(info) { wx.showToast({ title: info, icon: 'none', duration: 1200, mask: true }) }, // 开始发现设备处理函数 startBluetoothDevicesDiscovery() { if(this._discoveryStarted) { this.printLog("已经正在发现设备...") return } this._discoveryStarted = true wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: true, success: (res) => { this.printLog("开始发现设备...") this.onBluetoothDeviceFound() }, }) }, // 停止发现设备处理函数 stopBluetoothDevicesDiscovery() { this.printLog('停止发现设备') this._discoveryStarted = false wx.stopBluetoothDevicesDiscovery() }, // 正在查找设备 onBluetoothDeviceFound() { this.printLog('正在发现设备...') wx.onBluetoothDeviceFound((res) => { res.devices.forEach(device => { if (!device.name && !device.localName) { return } const foundDevices = this.data.devices const idx = inArray(foundDevices, 'deviceId', device.deviceId) const data = {} if (idx === -1) { data[`devices[${foundDevices.length}]`] = device } else { data[`devices[${idx}]`] = device } this.setData(data) }) }) }, // 创建连接 bindcreateBLEConnection(e) { const ds = e.currentTarget.dataset const deviceId = ds.deviceId const name = ds.name this.printLog("开始连接设备 [" + name + "]") wx.createBLEConnection({ deviceId, success: (res) => { this.setData({ connected: true, name, deviceId, }) this.getBLEDeviceServices(deviceId) } }) // this.stopBluetoothDevicesDiscovery() }, // 断开连接 closeBLEConnection() { this.printLog("断开连接") this.printInfo("成功断开设备") wx.closeBLEConnection({ deviceId: this.data.deviceId }) this.setData({ devices: [], connected: false, chs: [], canWrite: false, }) }, // 获取要连接设备的服务 getBLEDeviceServices(deviceId) { this.printLog("获取设备服务: " + deviceId) wx.getBLEDeviceServices({ deviceId, success: (res) => { for (let i = 0; i < res.services.length; i++) { if (res.services[i].isPrimary) { this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid) return } } } }) }, // 获取要连接设备的属性 getBLEDeviceCharacteristics(deviceId, serviceId) { this.printLog('开始获取设备属性: ' + deviceId + serviceId) wx.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (res) => { this.printLog('成功获取设备属性') for (let i = 0; i < res.characteristics.length; i++) { let item = res.characteristics[i] if (item.properties.read) { wx.readBLECharacteristicValue({ deviceId, serviceId, characteristicId: item.uuid, }) } if (item.properties.write) { this.setData({ canWrite: true }) this._deviceId = deviceId this._serviceId = serviceId this._characteristicId = item.uuid // this.writeBLECharacteristicValue() } if (item.properties.notify || item.properties.indicate) { wx.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId: item.uuid, state: true, }) } } }, fail(res) { this.printLog('设备属性获取失败') } }) // 操作之前先监听,保证第一时间获取数据 wx.onBLECharacteristicValueChange((characteristic) => { const idx = inArray(this.data.chs, 'uuid', characteristic.characteristicId) const data = {} if (idx === -1) { data[`chs[${this.data.chs.length}]`] = { uuid: characteristic.characteristicId, value: ab2hex(characteristic.value) } } else { data[`chs[${idx}]`] = { uuid: characteristic.characteristicId, value: ab2hex(characteristic.value) } } data[`chs[${this.data.chs.length}]`] = { uuid: characteristic.characteristicId, value: ab2hex(characteristic.value) } this.setData(data) }) }, writeBLECharacteristicValue() { // 向蓝牙设备发送一个0x00的16进制数据 // let buffer = new ArrayBuffer(1) // let dataView = new DataView(buffer) // dataView.setUint8(0, Math.random() * 255 | 0) var that = this; if(this.data.dataType) { var buffer = stringToBytes(this.data.sendData) that.printLog("发送数据:" + this.data.sendData) console.log("ascii") } else { // var buffer = strToHexCharCode("rice") // this.printLog("16: " + buffer) console.log("hex") } wx.writeBLECharacteristicValue({ deviceId: this._deviceId, serviceId: this._serviceId, characteristicId: this._characteristicId, value: buffer, success (res) { // that.printLog("发送数据:" + that.data.sendData) // that.printLog("发送数据成功"); }, fail (res) { that.printLog("发送数据失败") } }) }, // 获取写数据控件的数据 bindWriteData (e) { this.setData({ sendData: e.detail.value }) }, // 发送数据类型选择 dataTypeSelect(e) { var that = this if(e.detail.value == "hex") { that.data.dataType = false that.printLog("dataType: HEX") } else { that.data.dataType = true that.printLog("dataType: ASCII") } }, // 开始扫描按键 openBluetoothAdapter() { this.printLog("启动蓝牙适配器..."); this.setData({ devices: [], connected: false, chs: [], canWrite: false, }) wx.openBluetoothAdapter({ success: (res) => { this.printLog("蓝牙启动成功,开始进入发现设备"); this.startBluetoothDevicesDiscovery() }, fail: (res) => { this.printInfo("请先打开蓝牙") if (res.errCode === 10001) { wx.onBluetoothAdapterStateChange(function (res) { if (res.available) { this.printLog("蓝牙启动成功,开始进入发现设备"); this.startBluetoothDevicesDiscovery() } }) } } }) }, // 停止扫描按键 closeBluetoothAdapter() { this.printLog("停止扫描"); wx.closeBluetoothAdapter() this.stopBluetoothDevicesDiscovery() this._discoveryStarted = false }, onLoad: function () { this.printInfo("欢迎使用流光溢彩控制器配置小程序"); },})
2.提示:软件可以使用代码块来进行嵌套放置,无需全部软件部分说明,只需说明重要部分即可
实物展示

HDMI一分二功能验证

完整的产品必须要有外壳
3D外壳设计,比较简单,直接使用立创EDA设计

有了外壳,当然还要有面板丝印

外壳,及面板丝印


设计注意事项
提示:这里说明作品在制作中需要注意的一些注意事项(没有可以不写)
其他
演示视频:演示视频上传附件即可,附件最大只能长传50M的文件,大于50M的文件可放置在其他网盘或视频网站上,只需把地址链接放入这里即可
工程附件:参加活动的作品必须把工程相关的程序附件上传至开源平台或个人的代码存储云端,附件最大支持50M上传(请勿在立创工作区上传,有限制)
设计图
未生成预览图,请在编辑器重新保存一次BOM
暂无BOM
克隆工程

评论