专栏名称: 面包板社区
面包板社区——中国第一电子人社交平台 面包板社区是Aspencore旗下媒体,整合了电子工程专辑、电子技术设计、国际电子商情丰富资源。社区包括论坛、博客、问答,拥有超过250万注册用户,加入面包板社区,从菜鸟变大神,打造您的电子人脉社交圈!
目录
相关文章推荐
人人都是产品经理  ·  做AI产品带来的思考:拥抱变革,理性前行 ·  19 小时前  
91产品  ·  电商直播带货话术模版 ·  昨天  
人人都是产品经理  ·  微信读书拆解:如何“防止用户出错”? ·  昨天  
刘言飞语  ·  9 个 Manus 实测案例:眼前一亮,也问题很多 ·  2 天前  
51好读  ›  专栏  ›  面包板社区

开源项目:智能门锁爆改游戏机!从设计打板到代码实现

面包板社区  · 公众号  ·  · 2024-05-16 20:00

正文

@工程师们,无人机、平板、耳机、京东卡,板哥喊你拿大奖! 大奖叠加单篇奖!👆扫码去社区发帖参加吧!
之前分享了智能门锁的拆解 ,今天接着看博主将 智能门锁 改造成游戏机。
改造-玩转NES游戏


4.1 方案设计


我们从前面的方案分析,精简出如下的数据流,原来视频流有两条,一条是摄像头到猫眼模块通过WIFI到云端到手机,即可视对讲的路径,一条是摄像头到猫眼模块通过USB到后屏,TFT液晶屏显示,即本地猫眼可视的路径。


我们只需要替代摄像头,输入视频流就可以实现上述视频流,这样就可以在本地TFT屏幕和远程手机显示游戏界面。


当然要用门锁玩游戏,最直接的方式是基于猫眼模块的主控这里是君正的T21开发NES游戏模拟器,但是这是不现实的因为没有SDK,没有原来的业务源码,我们退而求其次,”劫持”视频流,输入我们的视频流,这样基于我们自己手里的控制板开发NES游戏模拟器。
而现成的USB充电接口是对外的,我们就可以用此USB接口跳线到后屏USB接口实现游戏视频流接入,并增加切换开关,可以切换使用原数据流还是USB口引入的数据流,这样可以切换,不影响原来的功能,如下所示(注:图中条线应该是跳线)

所以我们需要做的就是模拟猫眼的USB数据,即模拟猫眼同样的USB设备,将我们的NES游戏模拟器的视频帧数据转为同样的格式,后面我们抓包可以看到是MJPEG格式的UVC视频流,还有UAC的音频流。
4.2 分析猫眼和后屏UVC+UAC传输协议
前面我们拆解分析,知道了猫眼和后屏是通过4P USB线进行传输,将猫眼采集的图像在后屏显示的,可以猜测是走的UVC协议,我们就测试分析下。
先找到线束,制作转接线监控传输过程。
1.先将猫眼转接到电脑,抓取枚举和传输数据等信息。
2.然后制作转接线,使用USB分析仪,监控猫眼和后屏的通讯过程。
3.最后将线引出到临时USB充电口,按照前面的设计,设置切换开关要么切换到猫眼要么切换到USB口,用自己的设备接到USB口,替代猫眼摄像头,显示任意内容在后屏,玩转NES游戏。

4.2.1 抓包猫眼的枚举和通讯信息


猫眼是如下4P usb接口


制作USB转接线接电脑,抓取到描述符和通讯过程,分析其通讯。
过程略,这里画出整个描述符拓扑结构。可以看出使用了UVC+UAC的复合设备,UVC使用MJPEG格式,所以我们也需要按照猫眼同样的方式实现UVC+UAC的设备。
具体实现参见本人分享的文章《 USB系列之-UVC+UAC扬声器+麦克风实例分享 》,这里不再赘述。

4.3 手柄设计


为了方便玩转NES游戏,我们先参考常见的NES游戏机手柄,自行设计一个手柄。一般手柄有上下左右,select,start,a,b共8个按键。直接使用8个IO或者2x4矩阵按键也可以但是浪费IO,所以这里使用IIC接口的PCF8574T扩展8路IO,并且IIC地址可配,可以接多片支持双手柄。


4.3.1 硬件设计


硬件使用嘉立创EDA在线版设计,项目已经开源

https://oshwhub.com/qinyunti/key
4.3.1.1原理图

原理图如下比较简单,
4.3.1.2 PCB

PCB如下,设计为10cm以内,这样打样比较便宜。
4.3.1.3 BOM

BOM成本3块钱左右

4.3.1.4 PCB打样
20块钱搞定包邮,速度也很快几天就回来了
4.3.1.5 焊接

很快样板回来了,做的紫色的,手工焊接下。

焊接好后如下

4.3.2 驱动


4.3.2.1设备地址

我这里是PCF8574T不带A的型号,设备地址如下,我这A2-A1-A0都接地,所以读地址是0x41,写地址是0x40.
4.3.2.2输出模式
发送STOP-> 注意这里根据测试上电后第一次总是start无ack,所以这里加个stop回到默认状态。
发送START->
发送0x40写地址->
设备回ACK->
写8位数据->
设备回ACK->
写8位数据->
设备回ACK->
...
发送STOP

4.3.2.3 输入模式

输入模式必须保证对应的端口先输出的是1(复位默认状态).
发送STOP-> 注意这里根据测试上电后第一次总是start无ack,所以这里加个stop回到默认状态。
发送START->
发送0x41读地址->
设备回ACK->
读8位数据->
主机回ACK->
读8位数据->
主机回ACK->
...
主机回NACK
发送STOP


4.3.2.4 驱动代码
这里使用之前实现的IO模拟IIC的实现

超级精简系列之三:超级精简的IO模拟IIC的C实现

io_iic.c
#include "io_iic.h"
/**
* _______________________ * SCL ____________| |__ * ———————————————————————— * SDA |______________ * (1) (2) (4) (6) * (3) (5) * 其中(3) SDA低建立时间 (5) SDA高保持时间 * (1) 拉高SDA (4)拉高SDA产生上升沿 * (2) SCL拉高 SCL高时SDA上升沿即停止信号 */void io_iic_start(io_iic_dev_st* dev){ /* SCL高时,SDA下降沿 */ dev->sda_write(1); /* (1) SDA拉高以便后面产生下降沿 */ dev->scl_write(1); /* (2) 拉高SCL */ if(dev->delay_pf != 0) /* (3) SCL高保持*/ { dev->delay_pf(dev->delayus); } dev->sda_write(0); /* (4)SCL高时SDA下降沿 启动 */ if(dev->delay_pf != 0) /* (5)SCL高保持 */ { dev->delay_pf(dev->delayus); } dev->scl_write(0); /* (6)SCL恢复 */}
/**
* _______________________ * SCL ____________| |____ * ———————————————— * SDA ————————————————————————| * (1) (2) (4) (6) * (3) (5) * 其中(3) SDA低建立时间 (5) SDA高保持时间 * (1) 拉低SDA (4)拉高SDA产生上升沿 * (2) SCL拉高 SCL高时SDA上升沿即停止信号 */void io_iic_stop(io_iic_dev_st* dev){ /* SCL高时,SDA上升沿 */ dev->sda_write(0); /* (1)SDA先输出低以便产生上升沿 */ dev->scl_write(1); /* (2)SCL高 */ if(dev->delay_pf != 0) /* (3)SCL高保持 */ { dev->delay_pf(dev->delayus); } dev->sda_write(1); /* (4)SCL高时SDA上升沿 停止 */ if(dev->delay_pf != 0) /* (5)SCL高保持 */ { dev->delay_pf(dev->delayus); } dev->scl_write(0); /* (6)SCL恢复 */}
/** * | B0 | B1~B6| B7 | NACK/ACK | * ___________ _ __________ ____________ * ____________| | x |__________| |____________| | * (1)[2] (4) (6)[7] (9)[10] (12) * (3) (5) (8) (11) * 其中(1)(6)(12)拉低SCL;(4)(9)拉高SCL; * [2]输出 [7]转为读 [10]读ACK; * (3)(8)低保持时间,(5)(11)高保持时间。 */int io_iic_write(io_iic_dev_st* dev, uint8_t val){ uint8_t tmp = val; uint8_t ack = 0; if(dev == 0) { return -1; } if((dev->scl_write == 0) || (dev->sda_write == 0) || (dev->sda_read == 0) || (dev->sda_2read == 0)) { return -1; } /* SCL下降沿后准备数据,对方上升沿采集数据,高位在前 */ for(uint8_t i=0; i<8; i++) { dev->scl_write(0); /* (1) SCL拉低以便修改数据 */ if((tmp & 0x80) != 0) /* [2] 准备SDA数据 */ { dev->sda_write(1); } else { dev->sda_write(0); } if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus); /* (3) SCL拉低时间即数据建立时间 */ } dev->scl_write(1); /*(4) SCL上升沿对方采样 */ if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus); /* (5) SCL高保持时间,数据保持时间 */ } tmp <<= 1; /* 处理下一位 */ } dev->scl_write(0); /* (6)SCL归0 完成8个CLK */ dev->sda_2read(); /* [7]SDA转为读 */ if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus); /* (8)第九个时钟拉低时间 */ } dev->scl_write(1); /* (9)SCL上升沿 */ ack = dev->sda_read(); /* [10]上升沿后读 */ if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus); /* (11)第九个时钟高保持 */ } dev->scl_write(0); /* (12)恢复SCL到低 */ return (ack==0) ? 0 : -1;}
/** * | B0 | B1~B6| B7 | NACK/ACK | * ___________ _ __________ ____________ * ____________| | x |__________| |____________| | * (1)[2] (4)[5] (7)[8] (10) (12) * (3) (6) (9) (11) * 其中(1)(7)(12)拉低SCL;(4)(10)拉高SCL; * [2]转为读 [5]读 [8]输出ACK; * (3)(9)低保持时间,(6)(11)高保持时间。 */int io_iic_read(io_iic_dev_st* dev, uint8_t* val, uint8_t ack){ uint8_t tmp = 0; if((dev == 0) || (val == 0)) { return -1; } if((dev->scl_write == 0) || (dev->sda_write == 0) || (dev->sda_read == 0) || (dev->sda_2read == 0)) { return -1; } /* SCL下降沿后对方准备数据,上升沿读数据,高位在前 */ for(uint8_t i=0; i<8; i++) { tmp <<= 1; /* 处理下一位,先移动后读取 */ dev->scl_write(0); /* (1) */ dev->sda_2read(); /* [2]转为读输入高阻,以便对方能输出 */ if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus);/* (3)SCL低保持时间 */ } dev->scl_write(1); /* (4)SCL上升沿 */ if(dev->sda_read()) /* (5)读数据(SCL低时对方已经准备好数据) */ { tmp |= 0x01; /* 高位在前,最后左移到高位 */ } if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus);/* (6)SCL高保持时间 */ } } dev->scl_write(0); /* (7)恢复SCL时钟为低 */ dev->sda_write(ack); /* [8]准备ACK信号(SCL低才能更行SDL) */ if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus); /* (9)第九个SCL拉低时间 */ } dev->scl_write(1); /* (10)SCL上升沿发数据触发对方读 */ if(dev->delay_pf != 0) { dev->delay_pf(dev->delayus); /* (11)第九个SCL拉高保持时间 */ } dev->scl_write(0); /* (12)第九个SCL完成恢复低 */ //dev->sda_write(1); /* 这里无需驱动SDA,后面可能还是读),需要发送时再驱动 */ *val = tmp; return 0;}
void io_iic_init(io_iic_dev_st* dev){ if((dev != 0) && (dev->init != 0)) { dev->init(); }}
void io_iic_deinit(io_iic_dev_st* dev){ if((dev != 0) && (dev->deinit != 0)) { dev->deinit(); }}

io_iic.h
#ifndef IO_IIC_H#define IO_IIC_H
#ifdef __cplusplus extern "C"{#endif
#include
typedef void (*io_iic_scl_write_pf)(uint8_t val); /**< SCL写接口 */typedef void (*io_iic_sda_write_pf)(uint8_t val); /**< SDA写接口 */typedef void (*io_iic_sda_2read_pf)(void); /**< SDA转为读接口 */typedef uint8_t (*io_iic_sda_read_pf)(void); /**< SDA读接口 */typedef void (*io_iic_delay_us_pf)(uint32_t delay); /**< 延时接口 */typedef void (*io_iic_init_pf)(void); /**< 初始化接口 */typedef void (*io_iic_deinit_pf)(void); /**< 解除初始化接口 */
/** * \struct io_iic_dev_st * 接口结构体*/typedef struct{ io_iic_scl_write_pf scl_write; /**< scl写接口 */ io_iic_sda_write_pf sda_write; /**< sda写接口 */ io_iic_sda_2read_pf sda_2read; /**< sda转为读接口 */ io_iic_sda_read_pf sda_read; /**< sda读接口 */ io_iic_delay_us_pf delay_pf; /**< 延时接口 */ io_iic_init_pf init; /**< 初始化接口 */ io_iic_deinit_pf deinit; /**< 解除初始化接口 */ uint32_t delayus; /**< 延迟时间 */} io_iic_dev_st;
/** * \fn io_iic_start * 发送启动信号 * \param[in] dev \ref io_iic_dev_st*/void io_iic_start(io_iic_dev_st* dev);
/** * \fn io_iic_stop * 发送停止信号 * \param[in] dev \ref io_iic_dev_st*/void io_iic_stop(io_iic_dev_st* dev);
/** * \fn io_iic_write * 写一个字节 * \param[in] dev \ref io_iic_dev_st * \param[in] val 待写入的值 * \retval 0 写成功(收到了ACK) * \retval -2 写失败(未收到ACK) * \retval -1 参数错误*/int io_iic_write(io_iic_dev_st* dev, uint8_t val);
/** * \fn io_iic_read * 读一个字节 * \param[in] dev \ref io_iic_dev_st * \param[out] val 存储读到的值 * \param[in] ack 1发送NACK 0发送ACK * \retval 0 读成功 * \retval -1 参数错误*/int io_iic_read(io_iic_dev_st* dev, uint8_t* val, uint8_t ack);
/** * \fn io_iic_init * 初始化 * \param[in] dev \ref io_iic_dev_st*/void io_iic_init(io_iic_dev_st* dev);
/** * \fn io_iic_deinit * 解除初始化 * \param[in] dev \ref io_iic_dev_st*/void io_iic_deinit(io_iic_dev_st* dev);
#ifdef __cplusplus }#endif
#endif
pcf8574.c
#include "pcf8574.h"
#define PCF8574_WR_ADDR 0x40 /**< 写地址 */#define PCF8574_RD_ADDR 0x41 /**< 读地址 */
/** * \fn pcf8574_read * 读数据 * \param[in] dev \ref pcf8574_dev_st * \param[in] val 存读出的值 * \retval 0 成功 * \retval <0 失败*/int pcf8574_read(pcf8574_dev_st* dev, uint8_t* val){ /* 启动 */ dev->stop(); /* 如果直接start 上电后第一次总是无ACK,所以先stop进入确定状态 */ dev->start();
/* 发送读地址 */ if(0 != dev->write(PCF8574_RD_ADDR | ((dev->addr & 0x07)<<1))) { dev->stop(); return -1; }
/* 读数据 */ if(0 != dev->read(val,0)) { dev->stop(); return -2; }
/* 结束 */ dev->stop(); return 0;}
/** * \fn pcf8574_write * 写数据 * \param[in] dev \ref pcf8574_dev_st * \param[in] val 待写入的值 * \retval 0 成功 * \retval <0 失败*/int pcf8574_write(pcf8574_dev_st* dev, uint8_t val){ /* 启动 */ dev->stop(); /* 如果直接start 上电后第一次总是无ACK,所以先stop进入确定状态 */ dev->start();
/* 发送写地址 */ if(0 != dev->write(PCF8574_WR_ADDR | ((dev->addr & 0x07)<<1))) { dev->stop(); return -1; }
/* 写数据 */ if(0 != dev->write(val)) { dev->stop(); return -2; } /* 结束 */ dev->stop(); return 0;}
/** * \fn pcf8574_init * 初始化 * \param[in] dev \ref pcf8574_dev_st*/void pcf8574_init(pcf8574_dev_st* dev){ dev->init();}
/** * \fn pcf8574_deinit * 解除初始化 * \param[in] dev \ref pcf8574_dev_st*/void pcf8574_deinit(pcf8574_dev_st* dev){ dev->deinit();}


pcf8574.h
#ifndef PCF8574_H#define PCF8574_H
#ifdef __cplusplus extern "C"{#endif
#include
typedef void (*pcf8574_iic_start_pf)(void); /**< IIC启动接口 */typedef void (*pcf8574_iic_stop_pf)(void); /**< IIC停止接口 */typedef int (*pcf8574_iic_read_pf)(uint8_t* val, uint8_t ack); /**< IIC读接口 */typedef int (*pcf8574_iic_write_pf)(uint8_t val); /**< IIC写接口 */typedef void (*pcf8574_iic_init_pf)(void); /**< 初始化接口 */typedef void (*pcf8574_iic_deinit_pf)(void); /**< 解除初始化接口 */typedef void (*pcf8574_iic_delay_us_pf)(uint32_t delay); /**< 延时接口 */
/** * \struct pcf8574_dev_st * 接口结构体*/typedef struct{ pcf8574_iic_start_pf start; /**< IIC启动接口 */ pcf8574_iic_stop_pf stop; /**< IIC停止接口 */ pcf8574_iic_read_pf read; /**< IIC读接口 */ pcf8574_iic_write_pf write; /**< IIC写接口 */ pcf8574_iic_init_pf init; /**< 初始化接口 */ pcf8574_iic_deinit_pf deinit; /**< 解除初始化接口 */ uint8_t addr; /**< 3位硬件地址 */} pcf8574_dev_st;
/** * \fn pcf8574_read * 读数据 * \param[in] dev \ref pcf8574_dev_st * \param[in] val 存读出的值 * \retval 0 成功 * \retval <0 失败*/int pcf8574_read(pcf8574_dev_st* dev, uint8_t* val);
/** * \fn pcf8574_write * 写数据 * \param[in] dev \ref pcf8574_dev_st * \param[in] val 待写入的值 * \retval 0 成功 * \retval <0 失败*/int pcf8574_write(pcf8574_dev_st* dev, uint8_t val);/** * \fn pcf8574_init * 初始化 * \param[in] dev \ref pcf8574_dev_st*/void pcf8574_init(pcf8574_dev_st* dev);/** * \fn pcf8574_deinit * 解除初始化 * \param[in] dev \ref pcf8574_dev_st*/void pcf8574_deinit(pcf8574_dev_st* dev);
#ifdef __cplusplus }#endif
#endif


4.3.3 测试


根据平台移植IO操作

Key.c
#include "io_iic.h"#include "pcf8574.h"#include "key.h"
#include "gpio.h"
/* IIC IO操作的移植 */static void io_iic_scl_write_port(uint8_t val){ if(val) { gpio_write(GPIO_09, 1); } else { gpio_write(GPIO_09, 0); }}
static void io_iic_sda_write_port(uint8_t val){ gpio_close(GPIO_07); gpio_open(GPIO_07, GPIO_DIRECTION_OUTPUT); if(val) { gpio_write(GPIO_07, 1); } else { gpio_write(GPIO_07, 0); }}
static void io_iic_sda_2read_port(void){ gpio_write(GPIO_07, 1); gpio_close(GPIO_07); gpio_open(GPIO_07, GPIO_DIRECTION_INPUT);}
static uint8_t io_iic_sda_read_port(void){ if(0 == gpio_read(GPIO_07)) { return 0; } else { return 1; }}
static void io_iic_delay_us_port(uint32_t delay){ uint32_t volatile t=delay; while(t--);}
static void io_iic_init_port(void){ gpio_open(GPIO_07, GPIO_DIRECTION_OUTPUT); gpio_open(GPIO_09, GPIO_DIRECTION_OUTPUT); gpio_write(GPIO_07, 0); gpio_write(GPIO_09, 0);}
static void io_iic_deinit_port(void){ gpio_close(GPIO_07); gpio_close(GPIO_09);}
static io_iic_dev_st iic_dev={ .scl_write = io_iic_scl_write_port, .sda_write = io_iic_sda_write_port, .sda_2read = io_iic_sda_2read_port, .sda_read = io_iic_sda_read_port, .delay_pf = io_iic_delay_us_port, .init = io_iic_init_port, .deinit = io_iic_deinit_port, .delayus = 200,};
/* pcf8574接口移植 */static void pcf8574_iic_start_port(void){ io_iic_start(&iic_dev);}
static void pcf8574_iic_stop_port(void){ io_iic_stop(&iic_dev);}
static int pcf8574_iic_read_port(uint8_t* val, uint8_t ack){ return io_iic_read(&iic_dev,val,ack);}
static int pcf8574_iic_write_port(uint8_t val){ return io_iic_write(&iic_dev,val);}
static void pcf8574_iic_init_port(void){ io_iic_init(&iic_dev);}
static void pcf8574_iic_deinit_port(void){ io_iic_deinit(&iic_dev);}
pcf8574_dev_st pcf8574_dev={ .start = pcf8574_iic_start_port, .stop = pcf8574_iic_stop_port, .read = pcf8574_iic_read_port, .write = pcf8574_iic_write_port, .init = pcf8574_iic_init_port, .deinit = pcf8574_iic_deinit_port, .addr = 0,};
/* 对外接口 */void key_init(void){ pcf8574_init(&pcf8574_dev);}
void key_deinit(void){ pcf8574_deinit(&pcf8574_dev);}
int key_write(uint8_t val){ return pcf8574_write(&pcf8574_dev,val);}
int key_read(uint8_t* val){ return pcf8574_read(&pcf8574_dev,val);}

Key.h
#ifndef KEY_H#define KEY_H
#ifdef __cplusplus extern "C"{#endif
#include
void key_init(void);void key_deinit(void);int key_write(uint8_t val);int key_read(uint8_t* val);
#ifdef __cplusplus }#endif
#endif

测试代码
key_init(); os_delay(1000); while (1) { int res; uint8_t key_pre = 0; uint8_t key_now = 0;
/* 初始状态 */ if(0 == (res = key_read(&key_now))) { if(key_now != key_pre) { key_pre = key_now; } } else { printf("key%d,read err,res:%d\n",res); }
for(int n=0; n<10; n++) { for(int i=0; i<8; i++) { if(0 != (res = key_write(1< { printf("key%d,write err,res:%d\n",i,res); } os_delay(10); } } /* 读之前先转为全输出1 */ key_write(0xFF);
/* 初始状态 */ if(0 == (res = key_read(&key_now))) { if(key_now != key_pre) { key_pre = key_now; } } else { printf("key%d,read err,res:%d\n",res); }
/* 读状态,有改变则打印 */ while(1) { if(0 == key_read(&key_now)) { if(key_now != key_pre) { key_pre = key_now; printf("key change to %02x\n",key_now); } } else { printf("key%d,read err,res:%d\n",res); } os_delay(1); } /* code */ }
示波器查看输出波形,然后按键查看按键识别测试获取状态正常。

4.4 移植NES游戏模拟器


NES相关移植好的代码参考本人开源的仓库https://gitee.com/qinyunti/my-info-nes.git,基于InfoNES进行修改,如果使用需要实现自己的显示,音频输出,按键获取等接口。文件系统基于littlefs。通过串口shell导入rom文件进行运行(shell实现可以参见本人公众号文章《 一个超级精简高可移植的shell命令行C实现 》)。


4.4.1系统框图


整个系统框图如下,主要有和PC通过串口shell实现文件传输,用于导入rom游戏文件,IIC接口获取我们自行设计的手柄按键,PDM/PWM驱动的本地喇叭播放声音。


UVC显示,UAC音频,通过USB导入到后屏显示或者手机APP上远程显示,声音播放。


4.4.2 UVC显示设计


视频部分的框架如下,前面拆解知道,智能门锁后屏支持USB接口的UVC,可以实时视频, 即上行


摄像头->猫眼模块->USB(UVC)->后屏
->WIFI->云端->手机
而我们游戏只需要播放视频即可,我们可以直接对接USB接口,将游戏视频通过UVC发送到后屏,通过云端到手机端播放,也可以直接本地通过后屏的TFT液晶播放。

相关的实现文章可以参考本文公众号系列文章。
由于后屏只支持MJPEG格式,所以需要将NES模拟器的RGB565格式转为MJPEG,而我们的MJEPG编码器只支持输入NV12格式,所以需要先将RGB565转为NV12然后编码为MJPEG通过UVC传输到后屏。由于NES模拟器显示大小是256x240像素,而后屏是1280x720,所以RGB565转NV12时同时进行放大处理,实现代码如下
void InfoNES_LoadFrame(void){ framebuffer_sync((uint16_t*)WorkFrame, NES_DISP_WIDTH,NES_DISP_HEIGHT);}
void framebuffer_sync(uint16_t * buffer, uint16_t x, uint16_t y){ //memset(DDR_IN_BUFFER_ADDR+H_SIZE*V_SIZE,0x80,H_SIZE*V_SIZE/2); //uint8_t sx=H_SIZE/x; //uint8_t sy=V_SIZE/y; uint8_t sx = 5; uint8_t sy = 3; uint8_t* py = (uint8_t*)DDR_IN_BUFFER_ADDR; uint8_t* puv = (uint8_t*)(DDR_IN_BUFFER_ADDR+(uint32_t)H_SIZE*V_SIZE); uint8_t* p; uint32_t offset; uint8_t y00; uint8_t y01; uint8_t y10; uint8_t y11; uint8_t u00; uint8_t u01; uint8_t u10; uint8_t u11; uint8_t v00; uint8_t v01; uint8_t v10; uint8_t v11;
for(int j=0;j2) { /* 一次处理2行 */ for(int i=0;i2) { /* 一次处理两列 */ /* 输入2x2 4个点 */ offset = j*x+i; rgb565_2_yuv(buffer[offset], &y00, &u00, &v00); rgb565_2_yuv(buffer[offset+1], &y01, &u01, &v01); rgb565_2_yuv(buffer[offset+x], &y10, &u10, &v10); rgb565_2_yuv(buffer[offset+x+1], &y11, &u11, &v11);
/* Y方向j放大sy倍 X方向i放大sx倍 */
/* 第一行的2个Y */ for(int sy_j=0; sy_j/* 放大sy倍 */ { p = py+(j*sy)*H_SIZE+i*sx + sy_j*H_SIZE; /* 第1行j */ for(int sx_i=0; sx_i/* 第1个点放大sx列 */ { *p++ = y00; } for(int sx_i=0; sx_i/* 第2个点放大sx列 */ { *p++ = y01; } } /* 第二行的2个Y */ for(int sy_j=0; sy_j/* 放大sy倍 */ { p = py+((j+1)*sy)*H_SIZE+i*sx + sy_j*H_SIZE; /* 第2行j+1*/ //for(int sx_i=0; sx_i //{ // *p++ = y10; //} *p++ = y10; *p++ = y10; *p++ = y10; *p++ = y10; *p++ = y10; //for(int sx_i=0; sx_i //{ // *p++ = y11; //} *p++ = y11; *p++ = y11; *p++ = y11; *p++ = y11; *p++ = y11; }
/* 2x2个点得到一个uv */ uint8_t u = ((uint16_t)u00+(uint16_t)u01+(uint16_t)u10+(uint16_t)u11)/4; uint8_t v = ((uint16_t)v00+(uint16_t)v01+(uint16_t)v10+(uint16_t)v11)/4; for(int sy_j=0; sy_j/* 放大sy倍行 */ { p = puv+((j/2)*sy)*H_SIZE + i*sx + sy_j*H_SIZE; //for(int sx_i=0; sx_i //{ // *p++ = u; // *p++ = v; //} *p++ = u; *p++ = v; *p++ = u; *p++ = v; *p++ = u; *p++ = v; *p++ = u; *p++ = v; *p++ = u; *p++ = v; } } } wq_cache_flush(WQ_DCACHE_ID_ACORE,DDR_IN_BUFFER_ADDR,H_SIZE*V_SIZE*3/2); s_sync_flag_u8 = 1;}


4.3.3 UAC音频设计


音频部分的框架如下,前面拆解知道,智能门锁后屏支持USB接口的UAC,可以实现音频对讲,即上行


ADC采集到音频->UAC->后屏->云端->手机
反方向
手机->云端->后屏->UAC->PWM或者PDM->喇叭播放
而我们游戏只需要播放音频即可,我们可以直接对接USB接口,将游戏音频通过UAC发送到后屏,通过云端到手机端播放,也可以直接本地通过PWM或者PDM播放。
相关的实现文章可以参考本文公众号系列文章
https://mp.weixin.qq.com/s/QbngRy9ph2NIuULHxvQwqg
mp.weixin.qq.com/s/F9Q6ynmclC-SPmpOCQWysQ






请到「今天看啥」查看全文