单片机图像采集与网络传输
扫描二维码
随时随地手机看文章
1.引言
随着网络技术的发展和网络应用的普及,如何充分利用网络资源来实现低成本、高可靠的远程视频监控,已成为一个技术热点。本文介绍一个用单片机与图像采集模块接口,嵌入TCP/IP协议栈,制作“网络摄像头”的方法。本网络摄像头在一个组播式视频图像监控系统中,只作为组播源向以太网发送视频图像数据;其它监控计算机则作为组播成员接收数据。整个视频图像发送和监控系统在局域网中使用时,监控接收端的PC机只要加入了组播组,不必知道网络摄像头的IP地址和 MAC地址,也不需要两者的IP地址是在同一网段,均可接收到网络摄像头发出的图像数据,使用起来相当方便。
2. 硬件接口电路
网络摄像头的硬件接口电路如图1所示。该电路采用的单片机是89C52芯片,另扩展32K的外部存储器,供网络和图像数据处理用。
图1中的DB200是一个产品摄像模块,它由微型摄像镜头、图像缓存、时序发生、总线接口等电路构成;其外接信号是一个16脚的插座(9 ~ 16脚分别对应数据线D7 ~ D0,其它为地址、电源和读写控制线)。
图1中,U1、U4和DB200的片选信号由89C52的地址线A14、A15和74HC00的3个与非门提供:A15=0时选通U4;A15=1及A14=0时选通U1;A15=1及A14=1时选通DB200。DB200的第8脚接A13用来选择其内部寄存器。
RTL8019AS有3种工作方式:(1)跳线方式。(2)即插即用方式。(3)免跳线方式。RTL8019AS使用哪种工作方式由第65脚(JP)决定。为减少连线,我们采用跳线方式(把65脚接高电平)。这样网卡的传输介质、I/O基地址和中断号就由74、77、78、79、80、81、82、 84、85等引脚状态决定。
RTL8019AS的81、82、84、85(BD0-BD3)脚接低电平,对应32个I/O寄存器地址范围为300H - 31FH;78-80(BD4-BD6)脚接低电平,对应中断号为INT0(IRQ2/9);74(BA14)、77(BD7)脚接低电平,为自动检测传输介质方式。
RTL8019AS有20根地址(SA0-SA19)线,将其5、7、8、9、10(SA0-SA4)脚接89C52的A8-A12,将15、 16(SA8、SA9)脚接高电平来确保基地址为300H,其余地址线则全部接地。由于RTL8019AS的使能(AEN)信号是由89C52的 A15=1及A14=0时提供,因此我们可得出以下地址关系:
89C52: A15 A14 A13 A12 A11 A10 A9 A8 A7………A0
8019: SA4 SA3 SA2 SA1 SA0 …………
2进制数: 1 0 0 0 0 0 0 0 0000 0000 16进制数: 0X8000
2进制数: 1 0 0 1 1 1 1 1 0000 0000 16进制数: 0X9F00
可见,如果89C52输出地址0X8000至0X9F00,均可选中RTL8019AS。由于RTL8019AS的SA9和SA8恒接高电平,当89C52 的地址信号由0X8000至0X9F00变动时,会有:SA9 SA8 SA7 SA6 SA5 SA4 SA3 SA2 SA1 SA0 =11 0000 0000 至 11 0001 1111,即对应选择RTL8019AS的I/O寄存器地址300H至31FH。
RTL8019AS的96脚(IOCS16B)接低电平,使其工作在8位总线模式;64脚接低电平,使用非AUI接口;31、32脚接高电平,屏蔽远程自举加载功能;33脚所需复位信号,由89C52的P1.5提供;29、30脚对应接89C52的读写脚。
3. 软件模块设计
嵌入式系统一般采用简化的TCP/IP协议栈。常用的有IP、ARP、UDP、ICMP、TCP以及HTTP等协议。为了尝试实现一个最简易的嵌入式TCP/IP协议,我们选用UDP通讯方式。
UDP的通讯方式有3种:点对点、广播和组播。考虑到点对点通讯需要ARP协议来取得目标节点的物理地址,我们不用点对点通讯。至于广播通讯和组播通讯,两者都不需要ARP协议。但广播方式有如下缺陷:(1)广播数据报不能跨过路由器传播;(2)广播时本地子网的所有主机都会接收到广播并作出响应,既增加了非接收者的开销,保密性也不好。我们采用的组播方式不存在这些问题,较适合作为网络视频监控的信息传输。
3.1 主程序工作流程
网络摄像头的图像采集、打包、发送的软件流程如图2所示,对应的主程序源码见例程1。为了便于接收端正确判断每帧图像的开始,主程序在发送1帧图像数据前,先用Send_lwm( ) 函数发送特征字为“lwm”的4字节长的数据包(该函数从略),然后再发送图像数据。
void main(void) //(例程1--循环采集和发送图像数据的主程序):
{ init_8019( ); // RTLS8019AS初始化。
while(1) // 循环采集和发送图像:
{ img_capture( ); // 采集1帧图像。
Send_lwm( ); // 发送图像开始的特征字“lwm”
Send_img( ); // 发送1帧图像数据。
}
}
3.2 以太网控制芯片的初始化
RTL8019AS 芯片有32个寄存器地址,映射到4个页面,每页有16个寄存器。本系统只用0页的14个(00-01H,04-0BH,0D-0EH,0F-10H)寄存器。程序先定义reg00-reg10, 然后用初始化函数init_8019( )对RTL8019AS各寄存器进行配置:
#define XBYTE ((unsigned char volatile xdata *) 0)
#define reg00 XBYTE[0x8000] //对应300H A15=1, A14=0,A13=A12=A11=A10=A9=A8=0
………………..
#define reg10 XBYTE[0x9000] //对应310H A15=1, A14=0,A13=0,A12=1,A11=A10=A9=A8=0
sbit RST8019 = P1 ^ 5; // RST8019AS的硬件复位端。
void init_8019(void) // (例程2--RTL8019AS的初始化):
{ UINT C1; for(C1=0;C1<1000;C1++); // 软件延时,确保芯片进入稳定状态
RST8019=1; for(C1=0;C1<1000;C1++); // 硬件复位、延时以确保芯片完全复位
RST8019=0; for(C1=0;C1<1000;C1++); // 硬件复位、延时以确保芯片完全复位
reg00=0x21; // 选择第0页寄存器,并使芯片停止收发和DMA操作reg0e=0xC8; // DCR: 采用普通、8位DMA方式
reg07=0xFF; reg0f=0x00; // 清除和屏蔽所有中断(本系统未采用中断)
reg0d=0xE0; // TCR:采用普通发送模式、允许CRC产生和校验
}
为了节省资源,上述对RTLS8019AS的初始化中,凡是与发送无关的寄存器都没有设置。发送时所要用的组播地址、物理地址和IP地址,则在打包时再封装到各协议层数据包的头部。
3.3 图像数据的采集
负责图像采集的DB200摄像模块内部有数据、状态、采集控制和地址控制4个寄存器,表1是它们的寻址方式。对各寄存器的操作规则如下:
(1)写操作-对采集控制寄存器(CAP_CTRLr)D0位写1,可启动采集过程;对地址控制寄存器(CAP_INCr)D0位写一次1,其图像缓存的地址就加1。
(2)读操作-如果状态寄存器(CAP_STAUSr)的D0=1,表示可以开始图像采集过程;D1=1表示已完成1帧图像采集,可以读取数据寄存器(CAP_DATAr)的图像数据。
DB200工作的地址范围是:1100 0000 0000 0000 ~ 1110 0000 0000 0000 = 0C00H ~ 0E00H。据此,我们可写出如下图像采集函数(例程3):
#define CAP_CONTROLr XBYTE[0X0C000] //A15=1=A14, A13=0
#define CAP_STATUSr XBYTE[0X0C000]
void img_capture( ) // (例程3--图像采集):
{ while(!(CAP_STATUSr & 0x01)); // 检查DB200是否准备就绪?
CAP_CONTROLr=0xff; // 启动采集1帧图像过程。
while(!(CAP_CONTROLr & 0x02)); // 是否采集完1帧图像?是就结束。
}
img_capture( )函数只完成了1帧图像数据的采集,采集好的数据存在DB200的数据缓冲区内,留待Send_img( )函数读取和发送。Send_img( )是以读1行图像数据就发送1行的方式工作。其源码如下:
#define CAP_INCr XBYTE[0X0E000] //A15=1=A14,A13=1
#define CAP_DATAr XBYTE[0X0E000]
extern UCHAR xdata outbuf[1520];
void Send_img( ) // (例程4--图像数据的读取和发送):
{ UINT data Colon, Line ;
for(Line=0;Line<288;Line++) // 288行
{ for(Colon=0;Colon<385;Colon++) // 385 列
{ databuf[Colon]=CAP_DATAr; // 从 db200读1个像点到databuf。
CAP_INCr=0xff; // db200图像数据缓存地址加1。
}
udp_send(databuf, UDP_PORT, 386); // 封装并发送1行图像数据。
}
}
3.4 图像数据的封装
DB200采集完1帧图像后,通过数据总线传送给89C52;89C52则将图像数据按TCP/IP协议封装成以太网帧(图3),然后通过数据总线送给RTL8019AS;RTL8019AS则将以太网帧经RJ45接口送到10M以太网上。
从图3可以看到,每个以太网帧的最大长度为1518字节,最小为64字节。其数据部分最大为1500字节,最小为46字节。每个UDP数据传输前,必须加上 8字节的UDP头来构成UDP数据报;再加上20字节的IP头来构成IP数据报;最后加上14字节的帧头来构成以太网帧。这就是所谓的数据包封装。为了避免分段操作的麻烦,UDP数据报的最大长度应为1500 - 28=1472字节。在实际应用中,我们采用每行图像数据(385字节)封装一个包的方式传输数据。
void Data_send( UINT src_port, UINT datalen) // (例程5--UDP数据报封装):
{ UDP_HEADER xdata * udp;
udp = (UDP_HEADER xdata *)(outbuf + 34); // 34=14(以太网帧头长)+ 20(IP报头长) udp->dest_port = 2001; // 目的端口号
udp->source_port = 20011; // 源端口号
udp->length = 8 + datalen; // UDP包总长= UDP头长度+数据长度
udp->checksum = 0; // 不校验UDP数据报
ip_pack(outbuf, dest_ipaddr, udp->length); // 封装IP包
}
void ip_pack(UCHAR xdata * outbuf, ULONG dest_ipaddr, UINT datalen)//(例程6-封装IP包)
{ IP_HEADER xdata * ip; static UINT ip_ident; // datalen为UDP数据报总长度
ip = (IP_HEADER xdata *)(outbuf + 14); // 14字节为以太网帧头长度。
ip->ver_len = 0x45; ip->type_of_service = 0; // 版本号和服务类型。
ip->total_length = 20 + datalen; // 数据报总长=IP头长度+数据长度
ip->identifier = ip_ident++; // IP数据报序列号。
ip->fragment_info = 0; // IP数据报不分段。
ip->time_to_live = 32; // 生存时间。
ip->protocol_id = UDP_TYPE; // 协议类型为UDP=17。
ip->header_cksum = 0; // 校验和清0。
ip->dest_ipaddr = 0xEA050607L; // 0xEA050607L=234.5.6.7 为目标组播地址
ip->source_ipaddr = 0xD224446FL; // 0xD224446FL=210.36.68.111为本地IP地址
ip->header_cksum = ~cksum(outbuf + 14, 20); // 计算20字节的IP数据报头校验和
eth_send(outbuf, 34 + datalen); // 封装以太网帧并发送。
}
在上述例程5和例程6中,UDP的校验和是可选的,IP头的校验和是必需的。例程5将UDP检验和置为0,例程6则用cksum( )函数来计算IP头校验和。本地IP地址可取任意有效值。
3.5 以太网帧的封装和发送
从图3可知,以太网帧封装就是要将目的MAC地址、源MAC地址和所要传输的数据类型放到待发送的IP数据报前(成为以太网帧头)。因为我们要进行的是组播发送,所以目的MAC地址也必须是组播的MAC地址;而本地的源MAC地址我们就用01:02:03:04:05:06。
上面说到,“IP组播地址”范围是224.0.0.0到239.255.255.255。而以太网帧用到的“MAC组播地址”范围是 01:00:5E:00:00:00-01:00:5E:7F:FF:FF 。MAC组播地址的构成方法是:前3个字节固定用01:00:5E,后3个字节则用IP组播地址的后3个字节。所以我们用234.5.6.7作IP组播地址时,对应的MAC组播地址就是 01:00:5E:05:06:07。
在下面的程序中,RTL8019AS用查询方式进行发送操作。
UCHAR code dest_hwaddr[6] = { 0x01, 0x00, 0x5E, 0x05, 0x06, 0x07}; // 目标MAC组播地址
UCHAR code my_hwaddr[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; // 源MAC地址
void eth_send(UCHAR xdata * outbuf, UINT Data_len) // (例程7--以太网帧封装和发送):
{ UINT i , send_len; ETH_HEADER xdata * eth; eth = (ETH_HEADER xdata *)outbuf;
for(i=0;i<6;i++)
{ eth->dest_hwaddr[i]= dest_hwaddr[i]; // 装入目的MAC地址。
eth->source_hwaddr[i]= my_hwaddr[i]; // 装入源MAC地址。
}
eth->frame_type = IP_PACKET; // 数据类型为IP数据报
// send_len为实际要发送的以太网帧长度,它不能小于60字节:
send_len = (Data_len>=60) ? Data_len : 60 ;
reg00=0x22; // 选择0页寄存器,启动芯片。
while( reg00 & 0x04 ); // 原来的数据发送完没有?完了往下执行
reg08=0x00; reg09=0x40; // 设置发送缓冲区开始地址为4000H。
//设置(RBCR0-1)远端DMA传送数据包长度(高、低字节):
reg0a=(unsigned char)(Data_len); reg0b=(unsigned char)(Data_len>>8);
reg00=0x16; // 设置远端DMA写,启动远端DMA传送数据到发送缓冲区
for(i=0;i<Data_len;i++) reg10=outbuf[i]; // 往RTL8019AS的I/O端口传送图像数据
reg04=0x40; // 设置发送缓冲区开始地址高字节
//设置(TBCR0-1)发送字节计数器的计数长度(高、低字节):
reg05=(unsigned char)(send_len); reg06=(unsigned char)(send_len>>8);
reg00=0x26; // 启动本地DMA操作,向网络发送图像数据。
}
上述程序先设置好远端DMA开始地址(RSAR)和远端DMA数据字节数(RBCR),并设置远端DMA写( reg00=0x16),就可以把图像数据写入RTL8019AS的数据缓冲区。给出发送缓冲区首地址和数据包长度后,启动发送命令 (reg00=0x26), RTL8019AS就会按以太网协议将图像数据发送到网络上。
4. 结束语
TCP/IP协议较复杂,嵌入式系统自身的资源又很有限,动辄几十K的TCP/IP协议栈,使很多入门者望而却步。其实要在嵌入式系统实现TCP/IP协议,只不过就是编制各个符合TCP/IP协议规则的软件模块罢了。根据自己系统的具体情况编制专门的功能模块,也可以实现相当简化的TCP/IP协议栈。本文在此给出了一个可供借鉴的实例。
参考文献:
[1] 吴礼发, 网络程序设计教程[M],北京:希望电子出版社,2002.1
[2] RTL8019AS SPECIFICATION[EB/OL],REALTEKSEMI-CONDUCTOR CO., LTD , 2000.8.20
[3] 单片机与TCP/IP网络[EB/OL], HTTP:// WWW.LAOGU.COM