通过USB摄像头拍摄JPG照片

一、前言

  本文中的程序适用于Ubuntu或者ARM linux平台上外接USB摄像头,将摄像头插入USB口后在/dev目录下会出现名为video*的设备。需要注意的是,电脑自带的USB设备也可以接入Ubuntu系统中,并且在/dev目录下也会出现名为video*的设备,但是本文的例程不适用于电脑自带的摄像头。

二、代码

  1 /**
  2  * filename: camera.c
  3  * author: Suzkfly
  4  * date: 2021-08-15
  5  * platform: S3C2416或Ubuntu
  6  * 程序运行成功后会在当前目录下生成pic.jpg文件,如果在Ubuntu上运行,需要超级用权限。
  7  */
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <string.h>
 11 #include <getopt.h>           
 12 #include <fcntl.h>             
 13 #include <unistd.h>
 14 #include <errno.h>
 15 #include <malloc.h>
 16 #include <sys/stat.h>
 17 #include <sys/types.h>
 18 #include <sys/time.h>
 19 #include <sys/mman.h>
 20 #include <sys/ioctl.h>
 21 #include <asm/types.h>         
 22 #include <linux/videodev2.h>
 23 #include <time.h>
 24 
 25 /** <\brief 定义摄像头设备路径 */
 26 #define CAMERA_DEV  "/dev/video0"
 27 
 28 /** <\brief 定义清零数据的宏 */
 29 #define CLEAR(x) memset (&(x), 0, sizeof (x))
 30 
 31 /** <\brief 定义保存摄像头数据的结构体 */
 32 struct buffer {
 33     void * start;   /* 起始地址 */
 34     size_t length;  /* 数据长度 */
 35 };
 36 
 37 #define BUFFER_CNT 4    /* 缓冲帧的个数 */
 38 
 39 /**
 40  * \brief 初始化v4l2
 41  *
 42  * \param[in] p_dev_name:摄像头所处路径
 43  * \param[out] p_fd:得到的摄像头设备文件描述符
 44  * \param[out] pp_buffers:得到的缓冲帧的地址,注意这是一个二级指针,该变量保存的数据是一个地址
 45  *
 46  * \retval 成功返回0,失败返回-1
 47  */
 48 int v4l2_init(const char *p_dev_name, int *p_fd, struct buffer **pp_buffers)
 49 {
 50     int fd = -1;
 51     struct v4l2_capability cap;
 52     struct v4l2_format fmt;
 53     enum v4l2_buf_type type;
 54     struct v4l2_requestbuffers req;
 55     time_t t;
 56     struct tm *ptm;
 57     unsigned int i;
 58        struct v4l2_buffer buf;   //驱动中的一帧
 59     
 60     /* 打开摄像头设备 */
 61     if ((fd = open (p_dev_name, O_RDWR | O_NONBLOCK, 0)) < 0) {
 62         perror("fail to open\n");
 63         return -1;
 64     }
 65 
 66     /* ioctl是一个强大的函数,它的功能取决于传入的第2个参数,这里第2个参数传入
 67        VIDIOC_QUERYCAP表示获取设备属性,获取到的属性会保存在第3个参数中,第3个
 68        参数是一个struct v4l2_capability类型的结构体指针,struct v4l2_capability
 69        结构定义如下:
 70        struct v4l2_capability
 71        {
 72            u8 driver[16];         // 驱动名字
 73            u8 card[32];         // 设备名字
 74            u8 bus_info[32];     // 设备在系统中的位置
 75            u32 version;         // 驱动版本号
 76            u32 capabilities;     // 设备支持的操作
 77            u32 reserved[4];     // 保留字段
 78        };
 79        
 80        capabilities是能力的意思,其中支持的能力如下所示:
 81        #V4L2_CAP_VIDEO_CAPTURE            0x00000001  //是视频采集设备
 82        #V4L2_CAP_VIDEO_OUTPUT            0x00000002  //是视频输出设备
 83        #V4L2_CAP_VIDEO_OVERLAY            0x00000004  //可以做视频叠加
 84        #V4L2_CAP_VBI_CAPTURE            0x00000010  //是一个原始的 VBI 捕获设备
 85        #V4L2_CAP_VBI_OUTPUT                0x00000020  //是一个原始的 VBI 输出设备
 86        #V4L2_CAP_SLICED_VBI_CAPTURE        0x00000040  //是一个切片(sliced)的 VBI 捕获设备
 87        #V4L2_CAP_SLICED_VBI_OUTPUT        0x00000080  //是一个切片(sliced)的 VBI 输出设备
 88        #V4L2_CAP_RDS_CAPTURE            0x00000100  //RDS数据采集
 89        #V4L2_CAP_VIDEO_OUTPUT_OVERLAY    0x00000200  //可以做视频输出叠加
 90        #V4L2_CAP_HW_FREQ_SEEK            0x00000400  //可以做硬件寻频
 91        
 92        #V4L2_CAP_TUNER                    0x00010000  //有一个协调器
 93        #V4L2_CAP_AUDIO                    0x00020000  //支持音频
 94        #V4L2_CAP_RADIO                    0x00040000  //是无线电设备
 95        
 96        #V4L2_CAP_READWRITE              0x01000000  //读/写系统调用
 97        #V4L2_CAP_ASYNCIO                0x02000000  // 异步 I/O
 98        #V4L2_CAP_STREAMING              0x04000000  // streaming I/O ioctls
 99     */
100     
101     /* 获取设备参数 */
102     if (0 != ioctl(fd, VIDIOC_QUERYCAP, &cap)) {
103         perror("fail to ioctl\n");
104         return -1;
105     }
106 #if 0
107     printf("cap.driver = %s\n", cap.driver);
108     printf("cap.card = %s\n", cap.card);
109     printf("cap.bus_info = %s\n", cap.bus_info);
110     printf("cap.version = %u\n", cap.version);
111     printf("cap.capabilities = %#08x\n", cap.capabilities);
112 #endif
113     /* struct v4l2_format结构定义如下:
114     struct v4l2_format {
115         enum v4l2_buf_type type;
116         union {
117             struct v4l2_pix_format            pix;     // V4L2_BUF_TYPE_VIDEO_CAPTURE 
118             struct v4l2_window                win;     // V4L2_BUF_TYPE_VIDEO_OVERLAY
119             struct v4l2_vbi_format            vbi;     // V4L2_BUF_TYPE_VBI_CAPTURE
120             struct v4l2_sliced_vbi_format    sliced;  // V4L2_BUF_TYPE_SLICED_VBI_CAPTURE
121             __u8    raw_data[200];                   // user-defined
122         } fmt;
123     };
124     可以看出struct v4l2_format结构中有1个枚举类型的type和一个共用体fmt,数据保存
125     在fmt中,fmt共用体中具体使用哪个结构由type的值决定,本程序中需要设置图像格式,
126     因此type的值设为V4L2_BUF_TYPE_VIDEO_CAPTURE,使用fmt中的pix成员。
127     struct v4l2_pix_format结构定义如下:
128     struct v4l2_pix_format {
129         __u32                     width;          //图像宽度
130         __u32                    height;         //图像高度
131         __u32                    pixelformat;    //像素格式
132         enum v4l2_field          field;          //场格式,见下文
133         __u32                    bytesperline;    //表明缓冲区中有多少字节用于表示图像中一行像素的所有像素值。
134         //由于一个像素可能有多个字节表示,所以 bytesperline 可能是字段 width 值的若干倍
135         __u32                      sizeimage;      //图像大小
136         enum v4l2_colorspace    colorspace;     //色彩空间,其中V4L2_COLORSPACE_JPEG = 7
137         __u32            priv;                    //私有数据,取决于像素格式
138     };
139     pixelformat表示像素格式,可以设置的格式有很多,比如
140     #V4L2_PIX_FMT_RGB565
141     #V4L2_PIX_FMT_RGB24
142     #V4L2_PIX_FMT_YUV565
143     #V4L2_PIX_FMT_JPEG
144     #V4L2_PIX_FMT_MPEG
145     等。
146     
147     enum v4l2_field {
148         V4L2_FIELD_ANY           = 0, //驱动程序可以选择无、顶部、底部、隔行扫描...
149         V4L2_FIELD_NONE          = 1, //此设备没有场
150         V4L2_FIELD_TOP           = 2, //只有顶场
151         V4L2_FIELD_BOTTOM        = 3, //只有底场
152         V4L2_FIELD_INTERLACED    = 4, //两个场交错
153         V4L2_FIELD_SEQ_TB        = 5, //两个场顺序合并到一个缓冲区中,从上到下顺序
154         V4L2_FIELD_SEQ_BT        = 6, //同上,加自上而下的顺序
155         V4L2_FIELD_ALTERNATE     = 7, //两个场交替进入单独的缓冲区
156         V4L2_FIELD_INTERLACED_TB = 8, //两个场交错,顶场在前,顶场先传输
157         V4L2_FIELD_INTERLACED_BT = 9, //两个场交错,前场先传输,后场先传输
158     };  */
159     
160     /* 设置图像格式 */
161     CLEAR(fmt);
162     fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
163     fmt.fmt.pix.width       = 800;
164     fmt.fmt.pix.height      = 600;
165     fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
166     fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
167     if (0 != ioctl(fd, VIDIOC_S_FMT, &fmt)) {
168         perror("fail to ioctl\n");
169         return -1;
170     }
171     
172     /* 设置结果可能与写入的数值不一样,可以打印出来看一下 */
173 #if 0
174     printf("fmt.type = %d\n", fmt.type);
175     printf("fmt.fmt.pix.width = %d\n", fmt.fmt.pix.width);
176     printf("fmt.fmt.pix.height = %d\n", fmt.fmt.pix.height);
177     printf("fmt.fmt.pix.pixelformat = %c%c%c%c\n", 
178             (fmt.fmt.pix.pixelformat >> 0) & 0xFF, 
179             (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
180             (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
181             (fmt.fmt.pix.pixelformat >> 24) & 0xFF
182             );
183     printf("fmt.fmt.pix.field = %d\n", fmt.fmt.pix.field);            
184     printf("fmt.fmt.pix.bytesperline = %d\n", fmt.fmt.pix.bytesperline);
185     printf("fmt.fmt.pix.sizeimage = %d\n", fmt.fmt.pix.sizeimage);
186     printf("fmt.fmt.pix.colorspace = %d\n", fmt.fmt.pix.colorspace);
187     printf("fmt.fmt.pix.priv = %d\n", fmt.fmt.pix.priv);
188     printf("V4L2_BUF_TYPE_VIDEO_CAPTURE = %d\n", V4L2_BUF_TYPE_VIDEO_CAPTURE);
189 #endif
190 
191     //file_length = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; //计算图片大小
192 
193     /* struct v4l2_requestbuffers结构定义如下:
194     struct v4l2_requestbuffers {
195         __u32                count;      // 缓冲区内缓冲帧的数目
196         enum v4l2_buf_type  type;       // 缓冲帧数据格式
197         enum v4l2_memory    memory;     // 区别是内存映射还是用户指针方式,
198             V4L2_MEMORY_MMAP 为内存映射, V4L2_MEMORY_USERPTR 为用户指针
199         __u32                reserved[2];
200     }; */
201     
202     /* 向设备申请缓冲区 */
203     CLEAR (req);
204     req.count  = BUFFER_CNT;     /* \todo 为什么要申请4个缓冲帧 */
205     req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
206     req.memory = V4L2_MEMORY_MMAP;
207     ioctl(fd, VIDIOC_REQBUFS, &req);
208 
209     if (req.count < 2) {
210            printf("Insufficient buffer memory\n"); /* 缓冲帧数量不够 */
211     }
212     
213     /* 内存中建立对应空间 */
214     *pp_buffers = (struct buffer *)calloc(req.count, sizeof(struct buffer));
215     if (*pp_buffers == NULL) {
216         perror("calloc failed\n");
217         return -1;
218     }
219 
220     /* 得到缓冲帧的起始地址和长度 */
221     for (i = 0; i < req.count; i++) {
222         /* struct v4l2_buffer {
223                 __u32                    index;          //缓存编号
224                 enum v4l2_buf_type      type;           //视频捕获模式
225                 __u32                    bytesused;      //缓存已使用空间大小
226                 __u32                    flags;          //缓存当前状态,可取下列值
227                     #V4L2_BUF_FLAG_MAPPED    0x0001  //当前缓存已经映射
228                     #V4L2_BUF_FLAG_QUEUED    0x0002    //缓存可以采集数据
229                     #V4L2_BUF_FLAG_DONE        0x0004    //缓存可以提取数据
230                     #V4L2_BUF_FLAG_KEYFRAME    0x0008    //Image is a keyframe (I-frame) 
231                     #V4L2_BUF_FLAG_PFRAME    0x0010    //Image is a P-frame 
232                     #V4L2_BUF_FLAG_BFRAME    0x0020    //Image is a B-frame 
233                     #V4L2_BUF_FLAG_TIMECODE    0x0100    //timecode field is valid 
234                     #V4L2_BUF_FLAG_INPUT    0x0200  //input field is valid 
235                 enum v4l2_field            field;
236                 struct timeval            timestamp;  //获取第一个字节时的系统时间
237                 struct v4l2_timecode    timecode;
238                 __u32                    sequence;   //队列中的序号
239 
240                 // memory location 
241                 enum v4l2_memory        memory;     //IO 方式,被应用程序设置
242                 union {
243                     __u32           offset;         //缓冲帧地址偏移量,只对MMAP 有效
244                     unsigned long   userptr;
245                 } m;
246                 __u32            length;             //缓冲帧长度
247                 __u32            input;
248                 __u32            reserved;
249             };
250         */
251         
252         /* 获取缓冲帧的地址,长度 */
253            CLEAR (buf);
254            buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
255            buf.memory      = V4L2_MEMORY_MMAP;
256            buf.index       = i;
257            if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) { 
258             printf ("VIDIOC_QUERYBUF error\n");
259         }
260 
261            (*pp_buffers)[i].length = buf.length;
262         /* 通过mmap建立映射关系 */
263            (*pp_buffers)[i].start  = mmap (NULL,
264                                     buf.length,
265                                     PROT_READ | PROT_WRITE,
266                                     MAP_SHARED,
267                                     fd, 
268                                     buf.m.offset);    /* 被映射内容的偏移量 */
269            if (MAP_FAILED == (*pp_buffers)[i].start) {   /* MAP_FAILED其实是((void *) -1),mmap失败时返回 */
270                 printf ("mmap failed\n");
271         }
272     }
273 
274     for (i = 0; i < req.count; i++) {
275             CLEAR (buf);
276 
277             buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
278          buf.memory      = V4L2_MEMORY_MMAP;
279          buf.index       = i;
280 
281          /* 把帧放入队列 */
282          if (0 != ioctl (fd, VIDIOC_QBUF, &buf)) {
283            printf ("VIDIOC_QBUF failed\n");
284          }
285     }
286 
287     /* 开始捕捉图像数据 */
288     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
289     if (-1 == ioctl (fd, VIDIOC_STREAMON, &type)) {
290        printf ("VIDIOC_STREAMON failed\n");
291     }
292     
293     *p_fd = fd;
294     
295     return 0;
296 }
297 
298 /**
299  * \brief v4l2去除初始化
300  *
301  * \param[in] camera_fd:打开的摄像头设备文件描述符
302  * \param[in] p_buffers:缓冲帧地址
303  *
304  * \retval 成功返回0,失败返回-1
305  */
306 void v4l2_deinit(int camera_fd, struct buffer *p_buffers)
307 {
308     int i;
309 
310     /* 解除映射关系 */
311     for (i = 0; i < BUFFER_CNT; i++) {
312        if (-1 == munmap (p_buffers[i].start, p_buffers[i].length)) {
313             printf ("munmap error\n");
314        }
315     }
316     
317     /* 释放申请的内存 */
318     free(p_buffers);
319     
320     /* 关闭文件 */
321     close(camera_fd);
322 }
323 
324 /**
325  * \brief 读取一帧数据
326  *
327  * \param[in] camera_fd:打开摄像头设备得到的文件描述符
328  * \param[in] p_buffers:缓冲帧地址
329  * \param[in] filename:保存的jpg图片的路径
330  *
331  * \retval 成功返回0,失败返回-1
332  */
333 static int __read_frame (int camera_fd, struct buffer *p_buffers, char *filename)
334 {
335     FILE *file_fd = NULL;
336     struct v4l2_buffer buf;
337     unsigned int i;
338 
339     /* 以"w+"方式打开文件,如果文件不存在则会创建文件,但前提是文件所在目录对于
340        其他用户有写权限,可以用umask命令查看和修改掩码 */
341     if((file_fd = fopen(filename, "w+")) == NULL) {
342         perror("fail to fopen\n");
343         return -1;
344     }
345 
346     /* 从队列中取出帧 */
347     CLEAR(buf);
348     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
349     buf.memory = V4L2_MEMORY_MMAP;
350     ioctl(camera_fd, VIDIOC_DQBUF, &buf);
351     
352     fwrite(p_buffers[buf.index].start, p_buffers[buf.index].length, 1, file_fd); //将其写入文件中
353     
354     ioctl(camera_fd, VIDIOC_QBUF, &buf); /* 把帧放入队列 */
355 
356     fclose(file_fd);
357     
358     return 0;
359 }
360 
361 /**
362  * \brief 得到一张jpg图片 
363  *
364  * \param[in] camera_fd:打开摄像头设备得到的文件描述符
365  * \param[in] p_buffers:缓冲帧地址
366  * \param[in] filename:保存的jpg图片的路径
367  *
368  * \retval 成功返回0,失败返回-1
369  */
370 int camera_jpg (int camera_fd, struct buffer *p_buffers, char *filename)
371 {
372     unsigned int i;
373     fd_set fds;
374     struct timeval tv;
375     int r;
376     
377     /* 设定超时时间 */
378     tv.tv_sec = 2;
379     tv.tv_usec = 0;
380 
381     while (1) { //这一段涉及到异步IO
382         FD_ZERO (&fds);     //将指定的文件描述符集清空
383         FD_SET (camera_fd, &fds);  //在文件描述符集合中增加一个新的文件描述符
384         
385         /* select函数执行成功返回集合内包含的文件描述符数量,失败返回-1,超时返回0 */
386         r = select (camera_fd + 1, &fds, NULL, NULL, &tv);//判断是否可读(即摄像头是否准备好),tv是定时
387 
388         if (-1 == r) {
389             if (EINTR == errno) {   /* 系统调用被中断 */
390                 continue;
391             }
392             printf ("select err\n");
393             return -1;
394         } else if (0 == r) {        /* 超时 */
395             fprintf (stderr, "select timeout\n");
396             return -1;
397         }
398         
399         /* 如果可读,__read_frame()函数,并跳出循环 */
400         return __read_frame(camera_fd, p_buffers, filename);
401     }
402     
403     return 0;
404 }
405 
406 #if 1
407 /* 将摄像头拍到的照片保存成pic.jpg文件 */
408 int main(int argc, const char *argv[])
409 {
410     int camera_fd = 0;                  /* 摄像头设备的文件描述符 */
411     struct buffer *  p_buffers = NULL;    /* 用于保存缓冲帧地址和长度 */
412     
413     if (0 != v4l2_init(CAMERA_DEV, &camera_fd, &p_buffers)) {
414         perror("v4l2_init failed\n");
415         return 0;
416     }
417     camera_jpg(camera_fd, p_buffers, "pic.jpg");
418 
419     v4l2_deinit(camera_fd, p_buffers);
420     
421     return 0;
422 }
423 
424 #else
425 /* 将摄像头拍摄的照片实时显示出来,需要将jpg.c一同编译,要加-ljpeg选项 */
426 extern int framebuffer_init (void);
427 extern int show_jpg(unsigned int x, unsigned int y, const char *name);
428 
429 int main(int argc, const char *argv[])
430 {
431     int camera_fd = 0;                  /* 摄像头设备的文件描述符 */
432     struct buffer *  p_buffers = NULL;    /* 用于保存缓冲帧地址和长度 */
433     
434     framebuffer_init();
435     if (0 != v4l2_init(CAMERA_DEV, &camera_fd, &p_buffers)) {
436         perror("v4l2_init failed\n");
437         return 0;
438     }
439     
440     while (1) {
441         camera_jpg(camera_fd, p_buffers, "pic.jpg");
442         show_jpg(0, 0, "pic.jpg");
443     }
444     
445     v4l2_deinit(camera_fd, p_buffers);
446     
447     return 0;
448 }
449 #endif

  程序运行后,在当前路径下会出现名为pic.jpg的文件,这就是用摄像头拍摄到的照片。如果使用代码最后面的main函数,可以将拍摄到的照片直接在屏幕上显示出来,但是要支持jpg图片的显示。通过framebuffer显示jpg图片可以参考我这篇博客:framebuffer显示jpg图片。但是用这种方式显示出来的图片是一卡一卡的,应该是处理器性能不够强大,再者,摄像头本来就支持视频显示的,本文中的例程是取出摄像头拍摄到的画面,保存成jpg文件,再将图片显示出来,这个过程做了很多多余的事情,因此显示出来是一卡一卡的。

三、问题解答

  如果使用nfs挂载运行该程序,那么很容易出现下面两个问题。

  1. 运行程序时,如果pic.jpg不存在,那么在调用fopen打开pic.jpg时会打开失败,并且报错“Permission denied”。

  既然报“Permission denied”,那显然就是权限问题。(有些人可能会觉得是文件不存在导致的,但是本程序使用fopen打开文件,传入的标志是“w+”,这个标志在文件不存在时会自己创建文件,而且如果是文件不存在,那么会直接报“No such file or directory”,而不是“Permission denied”。)

   要解决这个问题,有2种方法

  1)在Ubuntu上用touch命令手动创建pic.jpg文件。

  (因为nfs目录下的文件是Ubuntu的,因此只能在Ubutnu上创建,在开发板终端没有权限创建文件)。创建完成之后可以用ls -l命令查看文件权限,此时pic.jpg的权限为“rw-rw-r--”,为什么是这个权限呢,首先,文件权限和umask有关,可以在终端上直接输入umask命令,可以看到终端打印出来“0002”,这表示其他用户是没有写权限的。另外使用touch命令创建的文件都是没有执行权限的,因此创建出来的文件权限为“rw-rw-r--”。所以创建出来的文件还需要使用chmod 0666 pic.jpg命令,让其他用户有写权限。这样再在开发板端运行程序就没问题了。但是这种方法缺点很明显,就是如果改变了文件名,那么又要进行一遍这样的操作,或者如果需要一次性拍很多张照片,这种方法就不适用了。

  2)通过程序自动创建文件。

  前面已经说了,如果pic.jpg文件是不存在的,那么程序运行时报没有权限的错误,既然是创建文件时需要权限,就应该看看希望被创建的文件所处的文件夹有没有写权限,如果没有的话,使用chmod加入写权限。之后运行程序,发现文件可以自动创建。

  但是这又带来一个新的问题,就是文件的所有者变成了nobody,组变成了nogroup,如下图。

  通过USB摄像头拍摄JPG照片

   暂时不知道这种情况会带来什么后果,但是这个是可以解决的,解决办法如下:

  在Ubuntu终端中,输入sudu vi /etc/exports

  文件的最后一行写的是nfs共享目录的路径也权限等信息,在其中加入一项:no_root_squash,修改过后如下图:

  通过USB摄像头拍摄JPG照片

   注意各项中间用逗号分隔,且没有空格。保存文件,使用命令:sudo /etc/init.d/nfs-kernel-server restart重启nfs服务。

  之后再在开发板上运行程序,这时创建出来的文件的用户和组都变成了root。

 

  2. 程序运行一段时间就死了,经过统计,问题会有下面几种:

  1)程序运行时报错:

  通过USB摄像头拍摄JPG照片

   然后程序自动退出,但系统没有死掉,并且可以再次用a.out运行程序,但程序运行一段时间后还是会死掉。

  2)程序运行时报错,如下图:

  通过USB摄像头拍摄JPG照片

  并且系统死掉,需要重启,但问题又来了,重启也不一定成功,可能需要多次断电上电才能成功重启。

  最气的是,重烧系统之后这个问题就没再出现了。

  虽然问题没再出现了,但是没有找到出现问题的根本原因和100%能解决问题的方法。在这里先作几点猜测:

  1. 网络连接问题。既然在开发板上运行系统不会死机,而使用nfs挂载就会死机,那有理由怀疑和网络连接有关,但是这肯定不是问题发生的根本原因,只是由某个问题导致了两种不同的现象;

  2. 存储器问题。可能是Nand Flash出问题,导致内核下载进去时出现了某些错误内容。这样推测的理由是出现问题之后系统变得难以启动,而且重烧系统之后这个问题就不再出现了。

上一篇:Flutter Widgets 之 ShaderMask


下一篇:Flutter Widgets 之 ListWheelScrollView