本帖最后由 0x4C 于 2024-2-25 18:57 编辑
本篇实现USB HOST 下使用USB摄像头和4G模块
1、配置USB摄像头
涉及修改的东西有点多,也记录一下SDK的编译流程,这里重新拉SDK编译,参考链接SDK 环境部署 | LUCKFOX WIKI
git clone https://gitee.com/LuckfoxTECH/luckfox-pico.git
安装交叉编译工具链:
cd {SDK_PATH}/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/
source env_install_toolchain.sh
选择编译目标平台:
./build.sh lunch
我是用的板载的flash
开始编译:
./build.sh
编译后打包固件:
./build.sh firmware
得到基础的SDK包
进入设备树文件路径,修改 <SDK目录>/sysdrv/source/kernel/arch/arm/boot/dts/rv1106g-luckfox-pico-pro-max.dts 将USB配置为USB HOST模式
&usbdrd_dwc3 {
status = "okay";
dr_mode = "host";
};
接下来修改内核配置
cd ~/SDK目录/sysdrv/source/kernel
cp ./arch/arm/configs/luckfox_rv1106_linux_defconfig .config
make ARCH=arm menuconfig
按下 ‘/’进行搜索。
进入搜索栏,输入搜索内容回车
显示搜索相关内容,按数字1-N进行选择。
依次搜索并使能以下选项
SCSI
SCSI_UFSHCD
BLK_DEV_SD
USB_STORAGE
USB_MASS_STORAGE
USB_EHCI_HCD
SCSI
SCSI_UFSHCD
BLK_DEV_SD
USB_STORAGE
USB_MASS_STORAGE
USB_EHCI_HCD
退出界面前先save一下
保存config,回到SDK目录下,并重新编译内核。
make ARCH=arm savedefconfig
cp defconfig arch/arm/configs/luckfox_rv1106_linux_defconfig
cd ~/SDK目录
./build.sh kernel
打包固件
./build.sh firmware
下载固件到板子上(先按住板子上的boot键再按住reset键)
这里要注意一点,电脑的USB供电功率一般比较低,我这里使用开关电源额外提供5v电源输入,特别是后面做EC20联网的时候供电不足会直接导致板子重启,卡了我很长的时间
USB使用一个USB扩展坞扩展
之前的固件烧录后插入USB扩展坞:
接下来继续修改内核配置来支持USB摄像头:
在内核中启用UVC驱动,可通过搜索关键字"Webcam"
在搜索结果中找到并勾选"USB Video Class (UVC)",最后保存并退出。
编译内核 打包固件 下载固件(尝试用串口下载了一下固件,最大波特率下载,奇慢无比,同时有概率写入失败) 插入USB摄像头:
可以看见已经生成了对应的设备节点:
写个程序用V4L2抓个图吧:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <assert.h>
#include <linux/videodev2.h>
int main(int argc, char** argv)
{
printf("********************************start***************************************\n");
int cameraFd;/* 定义一个设备描述符 */
cameraFd = open("/dev/video21", O_RDWR);
if(cameraFd < 0){
perror("video设备打开失败\n");
return -1;
}
else{
printf("video设备打开成功\n");
}
printf("********************************1***************************************\n");
struct v4l2_capability vcap;
ioctl(cameraFd, VIDIOC_QUERYCAP, &vcap);
if (!(V4L2_CAP_VIDEO_CAPTURE & vcap.capabilities)) {
perror("Error: No capture video device!\n");
return -1;
}
printf("********************************2***************************************\n");
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("摄像头支持所有格式如下:\n");
while(ioctl(cameraFd,VIDIOC_ENUM_FMT,&fmtdesc) == 0){
printf("v4l2_format%d:%s\n",fmtdesc.index,fmtdesc.description);
fmtdesc.index++;
}
printf("********************************3***************************************\n");
struct v4l2_frmsizeenum frmsize;
frmsize.index = 0;
frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// printf("MJPEG格式支持所有分辨率如下:\n");
// frmsize.pixel_format = V4L2_PIX_FMT_MJPEG;
// while(ioctl(cameraFd,VIDIOC_ENUM_FRAMESIZES,&frmsize) == 0){
// printf("frame_size<%d*%d>\n",frmsize.discrete.width,frmsize.discrete.height);
// frmsize.index++;
// }
printf("YUYV422格式支持所有分辨率如下:\n");
frmsize.pixel_format = V4L2_PIX_FMT_MJPEG;
while(ioctl(cameraFd,VIDIOC_ENUM_FRAMESIZES,&frmsize) == 0){
printf("frame_size<%d*%d>\n",frmsize.discrete.width,frmsize.discrete.height);
frmsize.index++;
}
printf("********************************4***************************************\n");
// struct v4l2_frmivalenum frmival;
// frmival.index = 0;
// frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// frmival.pixel_format = V4L2_PIX_FMT_MJPEG;
// frmival.width = 640;
// frmival.height = 480;
// while(ioctl(cameraFd,VIDIOC_ENUM_FRAMEINTERVALS,&frmival) == 0){
// printf("frame_interval under frame_size <%d*%d> support %dfps\n",frmival.width,frmival.height,frmival.discrete.denominator / frmival.discrete.numerator);
// frmival.index++;
// }
struct v4l2_frmivalenum frmival;
frmival.index = 0;
frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
frmival.pixel_format = V4L2_PIX_FMT_MJPEG;
frmival.width = 2592;
frmival.height = 1944;
while(ioctl(cameraFd,VIDIOC_ENUM_FRAMEINTERVALS,&frmival) == 0){
printf("frame_interval under frame_size <%d*%d> support %dfps\n",frmival.width,frmival.height,frmival.discrete.denominator / frmival.discrete.numerator);
frmival.index++;
}
printf("********************************5***************************************\n");
// struct v4l2_format vfmt;
// vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// vfmt.fmt.pix.width = 640;
// vfmt.fmt.pix.height = 480;
// vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
// if(ioctl(cameraFd,VIDIOC_S_FMT,&vfmt) < 0){
// perror("设置格式失败\n");
// return -1;
// }
// // 检查设置参数是否生效
// if(ioctl(cameraFd,VIDIOC_G_FMT,&vfmt) < 0){
// perror("获取设置格式失败\n");
// return -1;
// }
// else if(vfmt.fmt.pix.width == 640 && vfmt.fmt.pix.height == 480 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG){
// printf("设置格式生效,实际分辨率大小<%d * %d>,图像格式:Motion-JPEG\n",vfmt.fmt.pix.width,vfmt.fmt.pix.height);
// }
// else{
// printf("设置格式未生效\n");
// }
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.width = 2592;
vfmt.fmt.pix.height = 1944;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
if(ioctl(cameraFd,VIDIOC_S_FMT,&vfmt) < 0){
perror("设置格式失败\n");
return -1;
}
// 检查设置参数是否生效
if(ioctl(cameraFd,VIDIOC_G_FMT,&vfmt) < 0){
perror("获取设置格式失败\n");
return -1;
}
else if(vfmt.fmt.pix.width == 2592 && vfmt.fmt.pix.height == 1944 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG){
printf("设置格式生效,实际分辨率大小<%d * %d>,图像格式:Motion-JPEG\n",vfmt.fmt.pix.width,vfmt.fmt.pix.height);
}
else{
printf("设置格式未生效\n");
}
printf("********************************6***************************************\n");
struct v4l2_requestbuffers reqbuf;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.count = 3; //3个帧缓冲
reqbuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(cameraFd,VIDIOC_REQBUFS,&reqbuf) < 0){
perror("申请缓冲区失败\n");
return -1;
}
printf("********************************7***************************************\n");
// 将帧缓冲映射到进程地址空间
void *frm_base[3]; //映射后的用户空间的首地址
unsigned int frm_size[3];
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
// 将每一帧对应的缓冲区的起始地址保存在frm_base数组中,读取采集数据时,只需直接读取映射区即可
for(buf.index=0;buf.index<3;buf.index++){
ioctl(cameraFd, VIDIOC_QUERYBUF, &buf);
frm_base[buf.index] = mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,cameraFd,buf.m.offset);
frm_size[buf.index] = buf.length;
if(frm_base[buf.index] == MAP_FAILED){
perror("mmap failed\n");
return -1;
}
// 入队操作
if(ioctl(cameraFd,VIDIOC_QBUF,&buf) < 0){
perror("入队失败\n");
return -1;
}
}
printf("********************************8***************************************\n");
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(cameraFd, VIDIOC_STREAMON, &type) < 0){
perror("开始采集失败\n");
return -1;
}
printf("********************************9***************************************\n");
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
readbuffer.memory = V4L2_MEMORY_MMAP;
if(ioctl(cameraFd, VIDIOC_DQBUF, &readbuffer) < 0){
perror("读取帧失败\n");
}
// 保存这一帧,格式为jpg
FILE *file = fopen("v4l2_cap.jpg","w+");
fwrite(frm_base[readbuffer.index],buf.length,1,file);
fclose(file);
printf("********************************10***************************************\n");
// 停止采集
if (ioctl(cameraFd, VIDIOC_STREAMOFF, &type) < 0){
perror("停止采集失败\n");
return -1;
}
// 释放映射
for(int i=0;i<3;i++){
munmap(frm_base[i],frm_size[i]);
}
close(cameraFd);
return 0;
}
编译方法:
vi ~/.bashrc
export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
source ~/.bashrc
arm-rockchip830-linux-uclibcgnueabihf-gcc gpio.c -o gpio
放到开发板,执行后可以看到效果:
2、配置EC20
这里的配置仅仅提供一个参考,参考的博客:Quectel EC20在嵌入式平台的适配 - 大窟窿 - 博客园 (cnblogs.com)
进入文件夹下,路径:luckfox-pico/sysdrv/source/kernel/drivers/usb/serial
打开添加如下内容,保存
{ USB_DEVICE(0x05C6, 0x9215) }, /* Quectel EC20 */
注释掉冲突的vid以及pid设备,路径:luckfox-pico/sysdrv/source/kernel/drivers/usb/serial
修改路径:luckfox-pico/sysdrv/source/kernel/drivers/net/usb
添加零包处理(这个和usb 协议中的批量传输有关)For Linux Kernel Version newer than 2.6.34: 路径:luckfox-pico/sysdrv/source/kernel/drivers/usb/serial
.reset_resume = usb_wwan_resume,
修改内核配置:
编译内核 打包固件 下载固件 可以看见其设备节点ttyUSB0~3
下载ppp2.4.7源码 https://github.com/ppp-project/ppp.git
git clone -b ppp-2.4.7 https://github.com/ppp-project/ppp.git
交叉编译
./configure
make CC=/home/czm/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
编译完成后会在当前目录下生成chat/chat、 pppd/pppd、 pppdump/pppdump 和pppstats/pppstats 这四个文件,把这四个文件拷贝到文件系统中的/usr/bin目录下
最后移植ppp拨号脚本(附件提供),放到/etc/目录下即可
使用 pppd -v :
移植完成后 使用 pppd call quectel-ppp & 拨号
使用iperf测速