打印

[原创]用STM32 199的元开发板来玩俄罗斯方块-用USB摄像头实现

[复制链接]
50156|133
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lxyppc|  楼主 | 2010-3-28 16:27 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 lxyppc 于 2010-3-30 23:16 编辑

新增源代码打包下载 本地下载: TetrisSrc.zip (293.37 KB)
Google Code下载: http://lxyppc-tetrix.googlecode.com/files/TetrisSrc.zip
编译环境IAR 4.42
工程配置说明:
STM3210E-EVAL        红牛开发板使用
STM3210B-EVAL        万利199开发板使用
STM3210B-HEX          生成万利开发板Hex文件
SIM                           程序仿真
RAM_DEBUG              RAM中调试程序

红牛开发板原理图 红牛电路图(黑白).pdf (101.01 KB)
万利开发板原理图http://www.manley.com.cn/web/admin_ml32/pic/down/STM3210B-LK1_UM.pdf
开发板是万利的那个带有ST-Link2的199开发板  STM3210B-LK1
上面有一块STM32F103VBT6,这个片子有128K的Flash,20K的RAM
开发板上面可以用到的资源
·1 个LCD 显示,通过跳线选择连接LCD
·四个LED 发光管
·一个五方向输入摇杆
·两个GPIO 按键
可惜那个LCD是米字的LCD,如果是点阵的话,就可以在它上面画图了
不过不要紧,我之前做了一个OLed显示的小东西http://blog.**/lxyppc/725361/message.aspx
在调试它的UI的时候我把开发板虚拟成了一个USB设备,并将数据以摄像头的格式发送上来,这样我就可以在电脑上直接调试了。
受此思路影响,只需要将游戏图像数据转换成摄像头数据发送上来,这样就解决了没有显示屏的问题,实际上把电脑显示器当成了显示屏。
最后我将这块开发板虚拟成了一个USB摄像头和一个USB鼠标,不玩游戏的时候可做鼠标用。游戏的时候画面通过摄像头传到电脑上来。
未来计划:
拆解一个USB的游戏手柄,将里面的主控芯片换成STM32,在实现手柄所有功能的同时,虚拟出一个摄像头设备让游戏手柄变成“游戏”手柄。
现在已经成功虚拟出了一个摄像头和一个鼠标设备,剩下来要做的只需要考虑在硬件上怎样改造手柄。

Hex文件下载 Tetris.zip (54 KB)
直接下载到万利199元的开发板中,然后重新插拔一次USB线。
会发现一个USB摄像头,和一个USB鼠标。未打开摄像头时可当鼠标使用,打开摄像头后即可开始游戏。关闭摄像头游戏自动暂停。
资源使用情况
  9 568 bytes of CODE  memory
  4 855 bytes of DATA  memory
34 646 bytes of CONST memory
游戏说明:
当USB摄像头没有打开时:五方向输入摇杆控制鼠标的上下左右,KEY2为鼠标左键,KEY3为鼠标右键
当USB摄像头打开时:左右键移动方块,向上键旋转方块,向下键让方块快速下落,KEY2为开始/暂停
工程源代码在Google Code上,编译环境IAR4.42
http://code.google.com/p/lxyppc-tetrix/
SVN地址:
http://lxyppc-tetrix.googlecode.com/svn/trunk
游戏“快照”


TetrisSrc.zip

293.37 KB

评分
参与人数 9威望 +10 收起 理由
nickyamw + 1
jzb8736 + 1 赞一个!
mxh0506 + 1 我很赞同
wangzk + 1 nb
腾腾 + 1

查看全部评分

相关帖子

来自 2楼
lxyppc|  楼主 | 2010-3-30 22:11 | 只看该作者

USB图像数据传输与STM32

本帖最后由 lxyppc 于 2010-3-30 23:11 编辑

USB协议中有一个专门用于图像传输的类,Video Class。视频类的相关文档在这个位置http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip
USB直接支持的视频有好几种传输格式,Uncompress和MJPEG是帧与帧之间无关格式。不需要知道上一帧的状态,只需要把每一帧的数据向上报就行了。考虑到STM32的速度和内存,这里使用帧与帧无关的格式。这两种格式实现起来也比较简单。

Uncompress格式:
使用YUY2格式,平均一个像素需要两个BYTE来描述。USB Full Speed的理论传输速度为12MBits/s(暂时不考虑协议消耗的带宽)。即1.5MBytes/s,如果要求传输画面为每秒10帧,则一帧只能为150K Bytes,150K可以描述75K个像素,差不多是一个320X240图像。
Uncompress还可以使用NV12格式,带宽占用情况与YUY2格式差不多。
但是考虑到实际上USB达不到12MBit/s的传输速度,并且芯片还需要时间去做其它的事情,不宜使用Uncompress的数据格式。如果图像比较小,而且变化复杂,可以考虑用Uncompress格式,这个便是一个用Uncompress格式传送数据的例子。
http://www.pic16.com/bbs/dispbbs.asp?boardid=11&Id=50399
利用电脑的摄像头来调试简单的UI。

MJPEG格式:刚开始的时候一直不知道MJPEG到底是怎么样一个协议,网上搜了很久也没有找到相关的信息。后来用UltraEditor打开了一个MJPEG文件,恍然大悟,其实说得简单点MJPEG就是把JPEG图像连续播放出来。
一幅320*240的画面进行中等质量Jepg压缩后大小为25K左右。这个和图像内容有关。数据量下降约为uncompress格式的1/5,带宽要求降低,传输环节处理的数据量也减少了。
USB Full Speed将一秒分成1000帧,每一帧的时间是1ms。USB的传输是基于帧的,即主控器在开始前便分配好了这帧各Endpoint要传多少的数据,对一个Full Speed设备而言,一帧中只能进行一次同步(ISO)传输。25K的数据用同步(ISO)方式传输,并且Interval设置为1ms,帧率设定为10帧,则USB传输时每一帧只需要输26K*10FPS / 1000 = 250Byte。
问题又来了,STM32在进行ISO传输时,会自动将Buffer设置成为双缓冲的模式,如果我们开两个缓冲,那至少需要500个Byte,而STM32的Buffer Description Table也是放在Packet Buffer之中,还有控制端点EndPoint0也需要占用一定的buffer,如果按照双缓冲的模式,数据发送不能满足320*240每秒10帧的要求。而Endpoint在同步传输(ISO)下只能使用双缓冲。这里我“欺骗”了一下芯片将双缓冲的两个buffer的地址都定义成同一个的位置。现在就有足够的空间来存放每帧需要发送的数据了。如下:
/*File: usb_conf.h*/
/*-------------------------------------------------------------*/
/* --------------   Buffer Description Table  -----------------*/
/*-------------------------------------------------------------*/
/* buffer table base address */
#define BTABLE_ADDRESS      (0x00)
/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x10)
#define ENDP0_TXADDR        (0x50)
/* EP1  */
/* buffer base address */
#define ENDP1_BUF0Addr      (0x90)
#define ENDP1_BUF1Addr      (0x90)//(0xC0+0x40)

但是这样一来就必需在USB当前数据传输完成后,下一帧数据开始传输前把需要的数据准备好。在EP1_OUT_Callback之后开始准备数据,准备数据使用了另外的一个线程,在EP1_OUT_Callback中实际上只是设置好了堆栈地址,并且设置了PendSV位。这样在USB中断退出后就会执行任务的切换。在数据准备线程中,如果数据填满了Packet Buffer则反转Toggle位,这样在下一帧数据开始传送时USB模块就会将数据发送出去。在反转了Toggle位之后,切换回主线程。直到下一次发送完成中断到来,将任务切换回来后再继续填充数据到Packet Buffer。
/*File: usb_endp.c*/
void  EP1_IN_Callback(void)
{
  SwitchToProc();
}
/*File: Task.h */
#define   SwitchToProc()    \
  currentTaskStack = &mainStackPosition;  \
  nextTaskStack = &procStackPosition; \
   /*Turn on the led */\
  GPIOF->BRR = GPIO_Pin_7;\
  SCB->ICSR |= ((u32)0x01<<(SystemHandler_PSV & 0x1F));  /*设置PendSV标志*/

我将开发板虚拟成为了一个USB摄像头,描述符是根据USB Video Class 1.1文档中USB_Video_Example 1.1.pdf的例子修改而来,去掉了不需要的部分。数据使用MJPEG的格式,分辨率:宽304像素,高368像素,帧率约10帧每秒。详细描述符如下:
Descriptor.zip (1.88 KB)

顺便推荐几个很好用的USB相关工具:
视频设备的查看工具,UVCView.exe。上面的描述符信息便是这个工具生成的。这个工具是微软的DDK附带的,位置是<DDK安装目录>\tools\avstream,这里将之上传 UVCView.zip (102.75 KB) 。这个工具专门针对USB视频设备编写,可以分析出描述符之中的错误,包括格式错误和逻辑错误。通过这个工具可以很方便的检查出描述符相关的错误。
USB Command Verifier。下载位置在此http://www.usb.org/developers/tools/
这个工具是USB IF提供的检测工具,检测结果可以作为USB IF认证的依据。当然DIY不用去通过USB IF认证的,但是当你发现问题,却又不知道从何下手的时候,这个工具可以为你指明方向。这个工具不能直接测试USB Full Speep和Low Speed的设备,如果要测试,需要将设备插在USB High Speed的Hub上。

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
john_light + 1 工欲善其事,必先利其器。
来自 3楼
lxyppc|  楼主 | 2010-3-30 22:37 | 只看该作者

JPEG压缩与俄罗斯方块

本帖最后由 lxyppc 于 2010-3-30 23:46 编辑

维基百科上关于JPEG压缩算法的介绍http://en.wikipedia.org/wiki/JPEG
这里以一个64X64的图片为例子来说明转换过程。如图:

JEPG算法在压缩前会将图像由RGB颜色空间转换成为YUV颜色空间,转换公式如下:
/**
    RGB to YUV Conversion
    Y  =      (0.257 * R) + (0.504 * G) + (0.098 * B) + 16
    Cr = V =  (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
    Cb = U = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128
*/

转换后图片分为YUV三部分,Y为亮度信息(即黑白电视机的信号),UV为色差信息。

再将整幅图像分为若干个8X8的小格子,不能被8整除的会在后面补齐。这样图片被分成了64个8X8的小格子。

由于人眼对亮度(Y)的变化要比对色差(UV)的变化敏感,因此在Jpeg算法中一般将四个8X8的U(V)格子合成一个16X16的格子,再将相邻四个BYTE像素信息合为一个BYTE,U(V)分量的信息就被压缩成了原来的1/4,用一个8X8的信息块就能够描述16X16的U(V)数据。
此时把原始图像分成16个16X16的格子,可以看出每个格子由4个8X8的亮度数据,1个8X8的U色差数据和1个8X8的V色差数据组成。这样的一个16X16的格子称为一个MCU(Minimum Coded Unit)而一幅压缩后的Jpeg图像就是由这样的一个个MCU组合而成的。这幅图片是由16个这样的MCU组合而成的,并且MCU与MCU之间没有太大的关联,这也是能用Jpeg格式来玩俄罗斯方块的基础。

一个MCU做为压缩算法的一个基本单元,其中包括了4个8X8的Y信息,1个8X8的U信息,1个8X8的V信息,一共六个8X8的信息块,前四个是亮度(Y)信息,后两个是色差(UV)信息。Jpeg算法对每个这样8X8的信息单元进行离散余弦变换,并将变换后的结果除上一个数,这个数也表示了压缩的质量。以此来减少信息所需要的位宽。比如有150,43,72三个数,总共需要24位才能全部表示,但是如果将它们都除以16后,最大的数只需要4位,一共12位就行了。虽然数据在还原的时候会有所失真,但是人眼不会对这种差别很敏感。
结果输出是使用之字型(ZigZag)的顺序输出,这样可以让连续零出现的次数尽可能的多,尽量减小编码的长度。
离散余弦变换(DCT)后结果的第一个值为这个单元的直流分量,后面63个值为此单元的交流分量。直流分量输出时不是直接输出,而是与上一个单元的直流分量做减法后输出差值。YUV分别使用三个不同的值来记录上一个单元的直流分量。由于直流分量输出是以差值的形式,因此就输出结果而言,整个图片输出的时候直流值是相关的。这也是在输出图片的代码中需要一定的计算的原因。
接下来是输出每个8X8单元的63个交流值,交流部分直接输出,Huffman编码使用查表的方式得到,其中连续的零和结尾的零用特殊的编码方式表示,用以减少编码长度。
再完成一个MCU的编码之后继续对下一个MCU进行编码,这幅图片一共分为16个MCU。
俄罗斯方块的基本元素就是一个一个的方块,这正好对应了Jpeg压缩算法中的MCU。一般俄罗斯方块的游戏区域为一个10X20的矩阵,我用一个MCU来表示矩阵中的一个点,这样游戏区域就含有10X20个MCU。在输出图像数据的时候,没有方块的地方,使用当前背景的MCU数据,而有方块的地方,使用代表方块图案的MCU数据,这样就能在背景上画出方块的图案了。
像素不变时,使用Baseline DCT的压缩算法数据头不会因内容的改变而改变,因此每帧图像都使用相同的数据头。只需要根据当前方块位置在指定位置输出不同的MCU即可。
MCU的数据由直流和交流部分组成,交流部分不需要重新计算,只要预先算好,需要时取出即可。而直流部分的输出与上一个单元单的直流部分有关。
生成数据的时候提取出MCU的直流部分的原始值,为一个有符号数,范围与图片有关,在图像输出的时候先作差再编码输出。
由于当前MCU的交流值不与其它MCU相关联,生成数据的时候可以直接输出,在重建图像的时候直接输出即可。
注:MCU是以位的方式输出的。
实际代码如下:
// 将一个8X8的信息块进行输出,每一个MCU由6个这样的信息块组成,4个Y,1个U,1个V
int    OutputHuffBlock(const unsigned int* codeTbl, const unsigned char* sizeTbl, int lastDC)
{
  int temp1,temp2;
  dc_t curDC = (dc_t)(*block++); //得到当前直流分量的值
  u32 bitCnt;
  temp1 = temp2 = curDC - lastDC;  //与上一个直流分量的值作差
  if(temp1<0){                     //如果差小于零,取其补码作为输出
    temp1 = -temp1;
    temp2--;
  }
  bitCnt = 32 - __CLZ(temp1);  //计算当前直流差值需要的位数
  BitStreamOut(codeTbl[bitCnt],sizeTbl[bitCnt]); //将位数经过Huffman编码后输出
  if(bitCnt){
    BitStreamOut(temp2,bitCnt);  //如果位不为零,输出当前直流分量的差值
  }
  bitCnt = *block++;                  //得到当前交流分量的位数
  while(bitCnt>=8){
    BitStreamOut(*block++,8); //直接输出交流分量的值
    bitCnt-=8;
  }
  if(bitCnt){
    u32 tmp = *block++;
    tmp >>= 8-bitCnt;
    BitStreamOut(tmp,bitCnt);  //输出最后不足8位的交流分量值
  }
  return curDC;
}


一个MCU数据块的内容:
DC0~DC3表示4个亮度(Y)信息的直流部分,为有符号数。由于直流部分是以差值方式输出,此处记录了MCU直流部分的原始值
DC4表示色差U的直流部分
DC5表示色差V的直流部分
ACx Size表示当前AC位流的长度,单位为bit,由于AC部分直接输出,此处保存的是经过编码的交流部分的内容
ACx Stream表示当前AC位流的内容,结果左对齐
static const unsigned char Block_MCU1[] = {
/* DC0: */ 0x1D,
/* AC0 Size: */ 0x89,
/* AC0 Stream */
0xBA, 0xBA, 0x99, 0x2E, 0x19, 0x46, 0xE0, 0x15, 0x86, 0x0E, 0x3E, 0x56, 0x18, 0xE4, 0x67, 0xB1,
0xCD, 0x00,
/* DC1: */ 0x23,
/* AC1 Size: */ 0x68,
/* AC1 Stream */
0x8E, 0x76, 0x79, 0x97, 0x63, 0x6E, 0x8C, 0xA8, 0x6C, 0xE3, 0x04, 0x1F, 0x4A,
/* DC2: */ 0x1C,
/* AC2 Size: */ 0x62,
/* AC2 Stream */
0x6B, 0x7B, 0x88, 0x89, 0x0A, 0xE6, 0x54, 0xDF, 0xBB, 0x69, 0xE3, 0x22, 0x80,
/* DC3: */ 0x20,
/* AC3 Size: */ 0x69,
/* AC3 Stream */
0x95, 0xA5, 0x69, 0xA2, 0x31, 0xC3, 0xC8, 0x60, 0x58, 0x85, 0xC7, 0x1D, 0xC5, 0x00,
/* DC4: */ 0x00,
/* AC4 Size: */ 0x1F,
/* AC4 Stream */
0xAA, 0xD5, 0xD2, 0x88,
/* DC5: */ 0xFA,
/* AC5 Size: */ 0x19,
/* AC5 Stream */
0x4E, 0x49, 0xEA, 0x00,
};

16X16.jpg (102.3 KB )

16X16.jpg

使用特权

评论回复
评分
参与人数 2威望 +3 收起 理由
john_light + 1 知其然,知其所以然。
古道热肠 + 2
地板
xueqinglin| | 2010-3-28 17:03 | 只看该作者
牛人!

使用特权

评论回复
5
john_light| | 2010-3-28 18:14 | 只看该作者
头一回见到,有创意!:victory:

使用特权

评论回复
6
mcuisp| | 2010-3-28 23:57 | 只看该作者
有创意

使用特权

评论回复
7
itelectron| | 2010-3-29 02:48 | 只看该作者
有创意

使用特权

评论回复
8
www_at91_cn| | 2010-3-29 11:40 | 只看该作者
太有创意了

使用特权

评论回复
9
aozima| | 2010-3-29 12:56 | 只看该作者
NB,顶一下.

使用特权

评论回复
10
jzlin0| | 2010-3-30 16:59 | 只看该作者
:) 太牛了!

使用特权

评论回复
11
xwj| | 2010-3-30 18:19 | 只看该作者
呵呵,挺有创意的~:D

把源码和电路图发这里来啊~

使用特权

评论回复
12
huangqi412| | 2010-3-30 18:55 | 只看该作者
Y的真有创意,顶.

使用特权

评论回复
13
xwj| | 2010-3-30 22:56 | 只看该作者
太好了,
还有详细的原理解说,这个一定要顶!

使用特权

评论回复
14
icecut| | 2010-3-31 15:02 | 只看该作者
这个要顶

使用特权

评论回复
15
bigarmer| | 2010-3-31 15:27 | 只看该作者
这个真的服了。

使用特权

评论回复
16
www_at91_cn| | 2010-3-31 15:38 | 只看该作者
不顶对不起楼主的辛苦劳动

使用特权

评论回复
17
程序匠人| | 2010-3-31 22:48 | 只看该作者
费了我好几条裤子!

使用特权

评论回复
18
xwj| | 2010-3-31 23:04 | 只看该作者
匠人,那你自己呢?
一下就脱了几条裤子岂不是要裸奔?:P

使用特权

评论回复
19
lxyppc|  楼主 | 2010-3-31 23:35 | 只看该作者
现在这个时节一下子穿了三条裤子感觉有些热哈:lol
匠人不给自己留些?

使用特权

评论回复
20
hebeijiang| | 2010-4-1 15:52 | 只看该作者
太牛了,好好学习一下。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:代码发BBS不好看?你需要它 代码着色https://bbs.21ic.com/icview-135254-1-1.html

27

主题

2249

帖子

19

粉丝