功能描述
该程序基于ACZ702/ACZ7015开发板的出厂固件环境,将OV5640摄像头采集的图像通过网口或wifi传输到PC端,并使用上位机查看传输的图像。如何恢复出厂固件环境,请查看【如何烧录ACZ702开发板的出厂固件】
上位机软件、应用程序源码以及测试脚本见文章末尾的附件。
本例使用OV5640摄像头,购买链接【OV5640高清摄像头】
应用程序源码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <dirent.h>
#include <inttypes.h>
#include <sys/time.h>
#include <stdbool.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include "math.h"
#include <string.h>
#include <linux/fb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <sys/stat.h>
#include <errno.h>
#include <ctype.h>
#include <termios.h>
unsigned char STOP = 0;
void signalFunc(int _signal)
{
STOP = 1;
}
#define MAP_SIZE 4096UL //映射的内存区大小
#define MAP_MASK (MAP_SIZE - 1) //MAP_MASK = 0XFFF
/**
* @brief 从实际物理地址读取数据。
* @details 通过 mmap 映射关系,找到对应的实际物理地址对应的虚拟地址,然后读取数据。
* 读取长度,每次最低4字节。
* @param[in] readAddr, unsigned long, 需要操作的物理地址。
* @param[out] buf,unsigned char *, 读取数据的buf地址。
* @param[in] bufLen,unsigned long , buf 参数的容量,4字节为单位,如 unsigned long buf[100],那么最大能接收100个4字节。
* 用于避免因为buf容量不足,导致素组越界之类的软件崩溃问题。
* @return len,unsigned long, 读取的数据长度,字节为单位。如果读取出错,则返回0,如果正确,则返回对应的长度。
*/
static int Devmem_Read(unsigned long readAddr, unsigned long* buf, unsigned long len)
{
int i = 0;
int fd,ret;
int offset_len = 0;
void *map_base, *virt_addr;
off_t addr = readAddr;
unsigned long littleEndianLength = 0;
if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
return 0;
}
//将内核空间映射到用户空间
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
if(map_base == (void *) -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
for (i = 0; i < len; i++)
{
// 翻页处理
if(offset_len >= MAP_MASK)
{
offset_len = 0;
if(munmap(map_base, MAP_SIZE) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
if(map_base == (void *) -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
}
virt_addr = map_base + (addr & MAP_MASK); // 将内核空间映射到用户空间操作
buf[i] = *((unsigned long *) virt_addr); // 读取数据
addr += 4;
offset_len += 4;
}
if(munmap(map_base, MAP_SIZE) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
close(fd);
return i;
}
int main(int argc, char *argv[]) {
unsigned long readData[192000];
char ip_addr[50] = {0};
signal(SIGINT, signalFunc);
signal(SIGTERM, signalFunc);
int nX = 640;
int nY = 480;
Devmem_Read((unsigned long)0x1800000, readData, 192000); // 读取数据
int retval;
char buf[2048];
int socket_descriptor; //套接口描述字
struct sockaddr_in address;//处理网络通信的地址
int port=6000;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
sprintf(ip_addr,"192.168.%s",argv[1]);
address.sin_addr.s_addr=inet_addr(ip_addr);//这里不一样
address.sin_port=htons(port);
//unsigned char *p;
//创建一个 UDP socket
socket_descriptor=socket(AF_INET,SOCK_DGRAM,0);//IPV4 SOCK_DGRAM 数据报套接字(UDP协议)
int rbufsz = 5 * 1024 * 1024; //5M
setsockopt(socket_descriptor, SOL_SOCKET, SO_RCVBUF, &rbufsz, sizeof(rbufsz));
int wbufsz = 5 * 1024 * 1024; //5M
setsockopt(socket_descriptor, SOL_SOCKET, SO_SNDBUF, &wbufsz, sizeof(wbufsz));
int maxfdp;
fd_set fds;
fd_set fdsErr;
struct timeval timeout = {1, 0};//设置select等待3秒,3秒轮询,非阻塞就置0
int nSendFile = 0;
long iter = 0;
connect(socket_descriptor, (struct sockaddr *)&address,sizeof(address));
while (!STOP)
{
FD_ZERO(&fds);//每次循环都要清空集合,否则不能检测描述符变化
FD_SET(socket_descriptor, &fds);//添加描述符
//FD_ZERO(&fdsErr);//每次循环都要清空集合,否则不能检测描述符变化
//FD_SET(socket_descriptor, &fdsErr);//添加描述符
maxfdp = socket_descriptor + 1;//描述符最大值加1
// printf("1\n");
retval = select(maxfdp, NULL, &fds, NULL, &timeout);
if (retval <= 0)
{
//usleep(1000);
continue;
}
//p = (unsigned char *)((nSendFile == 0) ? szBmp1 : szBmp2);
buf[1]=iter >> 8;
buf[0]=iter & 0xff;
//memcpy(&buf[2],p,1280);
memcpy(&buf[2], (unsigned char*)readData + iter * 1600, 1280);
if(FD_ISSET(socket_descriptor,&fds))//测试skt是否可写,即网络上是否有数据
{
retval = write(socket_descriptor, buf, 1282);
if(retval < 0){
perror("sendto error: ");
exit(1);
}
}
iter++;
if (iter > nY - 1)
{
iter = 0;
Devmem_Read((unsigned long)0x1800000, readData, 192000); // 读取数据
//nSendFile = (nSendFile + 1) % 2;
//usleep(1000);
}
}
printf("app exit\n");
}
使用方法
(1)将该程序文件使用交叉编译器编译,得到可执行文件udp_send_video(附件中已提供)
arm-linux-gnueabihf-gcc udp_send_video.c -o udp_send_video
(2)将可执行文件“udp_send_video”和文章末尾的附件“send_video.sh”拷贝到开发板的Linux系统/home/root/路径下。
(3)查看电脑网口的ip,记住IP地址,例如下图就是192.168.6.176
(4)查看开发板的网口ip地址,注意开发板和电脑的IP地址必须在同一网段(此处也可使用板载wifi实现无线视频传输,但限于wifi性能,传输会很慢而且距离受限)
ifconfig
(5)输入以下命令运行脚本,开启视频传输,脚本后的参数为ip地址后两位,由步骤(3)可知我的电脑端ip地址为192.168.6.176,因此脚本后的参数为6.176
./send_video.sh 6.176
开机后的第一次运行会弹出配置信息,再次运行就不会弹出了。
(6)以"管理员权限"运行附件“小梅哥UDP摄像头V3.2.exe”程序,并按下图所示修改配置。
(7)修改完成后,点击连接,开始图像传输
(8)输入以下命令关闭摄像头传输
./send_video.sh 0
上位机源码
需要上位机源码,可查看该帖子【上位机源码】
附件下载