杨培文

Yang Peiwen

首先你需要配置好WiFi,能上网,然后:

1
2
apt-get update
apt-get install libcv-dev libopencv-dev

有一点比较特别的是NanoPi2的摄像头插上以后,设备是/dev/video9而不是通常情况下的/dev/video0。

测试代码如下(参考自VideoCapture):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
using namespace cv;
int main()
{
VideoCapture cap(9);
Mat frame;
while(1)
{
cap.read(frame);
imshow("test",frame);
waitKey(30);
}
return 0;
}
1
2
g++ opencv.cpp -o testcv -lopencv_core -lopencv_highgui -lopencv_imgproc
./testcv

这里出现了 HIGHGUI ERROR: V4L: index 9 is not correct! 这样的错误提示,具体原因不明,不过我们想到了一个好的解决方法,那就是直接使用v4l2获取摄像头的数据,然后再转为OpenCV的cv::Mat。

下面的代码就是OpenCV结合V4L2的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
#include<sys/ioctl.h>
#include<sys/types.h>
#include<sys/mman.h>
#include<libv4l1.h>
#include<libv4l2.h>
#include<linux/videodev2.h>

#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/core/core.hpp>
using namespace cv;

char videodevice[] = "/dev/video9";
int imageWidth = 320;
int imageHeight = 240;

#define CLEAR(x) memset(&(x), 0, sizeof(x))

struct buffer {
void *start;
size_t length;
};

static void xioctl(int fh, int request, void *arg)
{
int r;
do {
r = v4l2_ioctl(fh, request, arg);
} while (r == -1 && ((errno == EINTR) || (errno == EAGAIN)));

if (r == -1) {
fprintf(stderr, "error %d, %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
}

int main()
{
//打开摄像头设备
int fd = -1;
//fd = open (videodevice, O_RDWR | O_NONBLOCK, 0);
fd = open (videodevice, O_RDWR, 0);
if(fd < 0){
printf("failed to open\n");
exit(EXIT_FAILURE);
}
printf("打开摄像头设备成功,%s\n", videodevice);

//查询摄像头信息,VIDIOC_QUERYCAP
printf("------------\n");
struct v4l2_capability cap;
xioctl (fd, VIDIOC_QUERYCAP, &cap);
printf("Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\n\n",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF);

//查询摄像头视频格式,VIDIOC_ENUM_FMT
printf("------------\n");
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index=0;
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Supportformat:\n");
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
{
printf("\t%d.%c%c%c%c\t%s\n",fmtdesc.index+1,fmtdesc.pixelformat & 0xFF, (fmtdesc.pixelformat >> 8) & 0xFF,(fmtdesc.pixelformat >> 16) & 0xFF, (fmtdesc.pixelformat >> 24) & 0xFF,fmtdesc.description);
fmtdesc.index++;
}

struct v4l2_format fmt;
struct v4l2_buffer buf;
struct v4l2_requestbuffers req;

CLEAR(fmt);
CLEAR(buf);
CLEAR(req);

printf("------------\n");
printf("设置摄像头视频数据格式\n");
//设置摄像头视频数据格式,VIDIOC_S_FMT
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = imageWidth;
fmt.fmt.pix.height = imageHeight;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
//fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
//fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YVU420;
//fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
xioctl(fd, VIDIOC_S_FMT, &fmt);

if ((fmt.fmt.pix.width != imageWidth) || (fmt.fmt.pix.height != imageHeight))
printf("Warning: driver is sending image at %dx%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
else {
printf("fmt.type is %d\n",fmt.type);
printf("Width = %d\n",fmt.fmt.pix.width);
printf("Height= %d\n",fmt.fmt.pix.height);
printf("Sizeimage = %d\n",fmt.fmt.pix.sizeimage);
}

printf("------------\n");
printf("请求分配视频缓冲区\n");
//请求分配视频缓冲区,VIDIOC_REQBUFS
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
xioctl(fd, VIDIOC_REQBUFS, &req);

printf("------------\n");
printf("查询缓冲信息,将内核空间映射到用户空间\n");
//查询缓冲信息,将内核空间映射到用户空间,VIDIOC_QUERYBUF
struct buffer *buffers;
unsigned int i, n_buffers;

buffers = (struct buffer*)calloc(req.count, sizeof(*buffers));
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
CLEAR(buf);

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
//把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
xioctl(fd, VIDIOC_QUERYBUF, &buf);

buffers[n_buffers].length = buf.length;
buffers[n_buffers].start = v4l2_mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);

if (MAP_FAILED == buffers[n_buffers].start) {
perror("mmap");
exit(EXIT_FAILURE);
}
}

//投放一个空的视频缓冲区到视频缓冲区输入队列中,VIDIOC_QBUF
printf("------------\n");
printf("投放一个空的视频缓冲区到视频缓冲区输入队列中\n");
for (i = 0; i < n_buffers; ++i) {
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
xioctl(fd, VIDIOC_QBUF, &buf);
}

//启动视频采集命令,VIDIOC_STREAMON
printf("------------\n");
printf("启动视频采集\n");
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
xioctl(fd, VIDIOC_STREAMON, &type);

fd_set fds;
int r;
struct timeval tv;

do {
FD_ZERO(&fds);
FD_SET(fd, &fds);

/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;

r = select(fd + 1, &fds, NULL, NULL, &tv);
} while ((r == -1 && (errno = EINTR)));
if (r == -1) {
perror("select");
return errno;
}
Mat frame;
while(1)
{
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
//从视频缓冲区的输出队列中取得一个已经保存有一帧视频数据的视频缓冲区,VIDIOC_DQBUF
xioctl(fd, VIDIOC_DQBUF, &buf);
char * p = (char *)(buffers[buf.index].start);
printf("------------\n");
printf("取得一帧,%fkb\n", buf.bytesused/1024.0);

vector<char> data(p, p + buf.bytesused);
frame = imdecode(Mat(data), CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_COLOR);
imshow("test", frame);
int c = waitKey(10)&0xFF;
if(c == 27){
break;
}

xioctl(fd, VIDIOC_QBUF, &buf);
}

//停止视频采集命令,VIDIOC_STREAMOFF
printf("------------\n");
printf("停止视频采集\n");
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
xioctl(fd, VIDIOC_STREAMOFF, &type);
for (i = 0; i < n_buffers; ++i) {
v4l2_munmap(buffers[i].start, buffers[i].length);
}
v4l2_close(fd);
exit (EXIT_SUCCESS);
}
1
g++ test.cpp -o test -lopencv_core -lopencv_highgui -lopencv_imgproc -lv4l2

注意,凡是带imshow的程序都必须到触摸屏上操作,不然就会出现错误,比如 ssh中的:(test:1500): Gtk-WARNING **: cannot open display: ,或者vnc中的:(test:1521): GdkGLExt-WARNING **: Window system doesn't support OpenGL.

最后的视频(591KB):

1
2
3
apt-get update
apt-get install locales
dpkg-reconfigure locales

选择:

  • en_US.UTF-8 UTF-8
  • zh_CN.UTF-8 UTF-8

然后接下来的默认语言可以选中文也可以选英文。

最后reboot重启即可。

本文基于2015-12-18固件。

首先编辑网络设置:

1
nano /etc/network/interfaces.d/wlan0

我们将之前的ap模式的static改为client模式的dhcp,然后设置好ssid和密码。

1
2
3
4
5
6
7
8
9
10
11
allow-hotplug wlan0
auto wlan0

# iface wlan0 inet static
# address 192.168.8.1
# netmask 255.255.255.0

iface wlan0 inet dhcp
wpa-ssid YPW-Macbook-Pro
wpa-ap-scan 1
wpa-psk 88888888888

然后输入:

1
turn-wifi-into-apmode no

等待大约十秒钟,不出意外WiFi已经连上了。

通过下面的命令可以查看WiFi是否已经连上,当然也可以ping百度或者apt-get。

1
ifconfig

固件的下载地址:http://wiki.friendlyarm.com/nanopi2/download/

下载好固件以后,将microSD卡通过读卡器连接到电脑上,然后使用win32diskimager软件进行烧写。过程极其简单,因此不再详细说明。

开机只用了12秒,真是太棒了

烧写镜像文件到SD卡中以后,SD卡变成了4GB,可是我的卡是32GB的,因此我们可以在linux中用gparted命令来调整分区。

1
2
sudo apt-get install gparted 
sudo gparted
进入gparted界面以后,首先在右上角选择你的SD卡,一般情况下是sdb。

然后卸载你要扩容的那个分区。

右击需要扩容的分区,选择更改大小/移动。

直接拖到最右,并点击调整大小/移动。

点击图中的钩,并且应用。

这个过程比较久,我的32GB卡扩容需要用5分钟,理论上越大越久。

我们可以看到,将近六分钟以后成功完成所有操作。

分区顺利扩容。

参考自:http://forum.lemaker.org/cn/forum.php?mod=viewthread&tid=8869

本文基于Ubuntu 15.10操作系统。

首先需要安装一些软件包:

1
2
3
sudo apt-get update
sudo apt-get -y upgrade
sudo apt-get -y install python scons gcc-arm-none-eabi git

然后下载源码:

1
2
3
4
5
cd Desktop
mkdir wrtnode2r-stm32
cd wrtnode2r-stm32
git clone https://github.com/WRTnode/wrtnode2r_stm32 wrtnode2r_stm32
git clone https://git.oschina.net/SchumyHao/rt-thread.git rtt

配置gcc路径:

1
2
cd wrtnode2r_stm32
nano rtconfig.py

其中的gcc改为/usr/share/lintian/overrides/ ,这是gcc-arm-none-eabi所在的目录。

然后就可以执行scons命令了!

1
scons

编译好的固件被放置在

1
wrtnode2r-stm32/wrtnode2r_stm32/rtthread.bin

之后就可以使用ST-Link烧程序啦!

烧程序教程参考:WRTnode 2R STM32固件的烧写和spi-bridge的使用

参考资料:WRTnode2R的STM32开发指南

WRTnode2r_STM32_bootloader.hex

rtthread.bin

首先放上来的这两个文件,是WRTnode群共享放出的bootloader和我自己编译的rtthread程序。大家可以先拿编译好的固件使用。

STM32固件的烧写

接线

我这里使用的烧写工具是ST-Link,接线方式参考pinMap:

只需要接GND,SWCLK和SWDIO即可调试。

烧写

接好之后,我们打开STM32 ST-LINK Utility软件开始烧写固件。

首先点击Connect to the target连接STM32,连接成功之后应该可以在右上角看到STM32F10xx Medium-density,64KBytes等信息,这是STM32F103T8U6芯片的信息。

Snip20151123_33

然后我们点击Program verify,选WRTnode2r_STM32_bootloader.hex开始烧写。

Snip20151123_33

然后烧写rtthread.bin,这里一定要记得地址必须填0x08001000,不然bootloader就没了。

 烧好以后,就可以在终端中使用spi-bridge与STM32进行交互了。

spi-bridge

更新&编译

在内测版WRTnode 2R的SDK中是没有WRTndoe2r-stm32这个package的,所以我们首先得更新feeds,进入wrtnode文件夹,然后输入:

1
2
3
./scripts/feeds update wrtnode
./scripts/feeds install -a
make menuconfig

在WRTnode中选择WRTndoe2r-stm32,你将得到spi-bridge命令和flash-stm32命令。

1
spi-bridge

下图就是spi-bridge的效果:

输入tab,回车,就可以看到所有的命令和命令的解释说明。个人认为最大的亮点在于,它加入了Arduino的语法,可以使用pinMode,digitalWrite,digitalRead等命令,由于是测试版,还未加入所有的功能,以后肯定会加入诸如analogWrite(PWM),analogRead(ADC)等功能,大大降低单片机开发难度,让更多人能够跨入这个门槛中。

1
2
3
4
spi-bridge             # 进入交互模式
spi-bridge write ps # 执行命令
spi-bridge read # 读取命令结果
spi-bridge status # 获取当前状态,通常返回OK

通过输入下面的命令,我们可以控制PB5输出高电平:

1
2
spi-bridge write "pinMode 5 0"         # 0表示输出
spi-bridge write "digitalWrite 5 1" # 1表示高电平

至于各个引脚的定义,在官方的PIN_MAP中可以找到:https://github.com/WRTnode/wrtnode2r_stm32/blob/master/maple/wirish/boards/wrtnode2/board.cpp#L67

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
extern const stm32_pin_info PIN_MAP[BOARD_NR_GPIO_PINS] = {
PMAP_ROW(GPIOB, 0, TIMER3, 3, ADC1, 8), /* D0/PB0 */
PMAP_ROW(GPIOB, 1, TIMER3, 4, ADC1, 9), /* D1/PB1 */
PMAP_ROW(GPIOB, 2, NULL, 0, NULL, ADCx), /* D2/PB2 */
PMAP_ROW(GPIOB, 3, NULL, 0, NULL, ADCx), /* D3/PB3 */
PMAP_ROW(GPIOB, 4, NULL, 0, NULL, ADCx), /* D4/PB4 */
PMAP_ROW(GPIOB, 5, NULL, 0, NULL, ADCx), /* D5/PB5 */
PMAP_ROW(GPIOB, 6, TIMER4, 1, NULL, ADCx), /* D6/PB6 */
PMAP_ROW(GPIOB, 7, TIMER4, 2, NULL, ADCx), /* D7/PB7 */
PMAP_ROW(GPIOA, 0, TIMER2, 1, ADC1, 0), /* D8/PA0 */
PMAP_ROW(GPIOA, 1, TIMER2, 2, ADC1, 1), /* D9/PA1 */
PMAP_ROW(GPIOA, 2, TIMER2, 3, ADC1, 2), /* D10/PA2 */
PMAP_ROW(GPIOA, 3, TIMER2, 4, ADC1, 3), /* D11/PA3 */
PMAP_ROW(GPIOA, 8, TIMER1, 1, NULL, ADCx), /* D12/PA8 */
PMAP_ROW(GPIOA, 9, TIMER1, 2, NULL, ADCx), /* D13/PA9 */
PMAP_ROW(GPIOA, 10, TIMER1, 3, NULL, ADCx), /* D14/PA10 */
PMAP_ROW(GPIOA, 11, TIMER1, 4, NULL, ADCx), /* D15/PA11 */
PMAP_ROW(GPIOA, 12, NULL, 0, NULL, ADCx), /* D16/PA12 */
PMAP_ROW(GPIOA, 13, NULL, 0, NULL, ADCx), /* D17/PA13 */
PMAP_ROW(GPIOA, 14, NULL, 0, NULL, ADCx), /* D18/PA14 */
PMAP_ROW(GPIOA, 15, NULL, 0, NULL, ADCx), /* D19/PA15 */
PMAP_ROW(GPIOA, 4, NULL, 0, NULL, ADCx), /* D20/PA4 */
PMAP_ROW(GPIOA, 5, NULL, 0, NULL, ADCx), /* D21/PA5 */
PMAP_ROW(GPIOA, 6, NULL, 0, NULL, ADCx), /* D22/PA6 */
PMAP_ROW(GPIOA, 7, NULL, 0, NULL, ADCx), /* D23/PA7 */
};

下面的链接是spi-bridge的源码,有兴趣的同学可以自行研究:

https://github.com/WRTnode/openwrt-packages/blob/master/wrtnode/spi-bridge/src/main.c

创建一个 docker:

1
docker pull ubuntu:14.04
启动一个docker:
1
docker run -t -i --cpuset-cpus=0-7 wrtnode-2r-sdk /bin/bash
恢复运行中的docker:
1
docker attach wrtnode-2r-sdk
保存一个docker:
1
2
docker ps
docker commit 7d mycaffe
导出一个docker:
1
2
docker ps
docker export 4bf6cd922cfd > wrtnode-2r-sdk.tar
导入一个docker:
1
cat wrtnode-2r-sdk.tar | docker import - wrtnode-2r-sdk:latest
删除一个docker:
1
docker rm -f wrtnode-2r-sdk
修改DNS:
1
echo "nameserver 8.8.8.8" > /etc/resolv.conf

当你修改了网络设置,或者刷了其他型号的固件,或者各种复杂的原因,导致了你无法连接上WRTnode,那么我们就可以认为WRTnode已经变成砖头了。这时候就需要救砖。

可能需要的软件:tftp.zip 包括tftpd32,putty,sscom42,openwrt-ramips-mt7628-wrtnode2r-squashfs-sysupgrade.bin(自己编译的WRTnode 2R固件,带中文,有点偏大是因为我之前编译过opencv之类的东西可能有依赖库没有去掉)

首先我们需要一个USB转TTL,通过它我们可以让电脑连上WRTnode的UART接口。这是单片机常用的通讯方式,这里不作详细介绍。接线方式:

WRTndoe ---- USB to TTL TX0 ---- RXD RX0 ---- TXD GND ---- GND

如图所示:

然后我们还需要连接上网线口,如图所示:

我们连接网线以后,需要设置静态IP,设置方法如下:

然后我们打开串口助手,上电,可以看到类似的信息输出:

很显然内核挂了,我们打开Tftpd32软件,配置如图所示:

配置好之后将WRTnode的电断掉再插上,再次重启,然后在出现下面这段话的时候按2,然后按y来配置IP等信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
Please choose the operation: 

1: Load system code to SDRAM via TFTP.

2: Load system code then write to Flash via TFTP.

3: Boot system code via Flash (default).

4: Entr boot command line interface.

7: Load Boot Loader code then write to Flash via Serial.

9: Load Boot Loader code then write to Flash via TFTP.
1
2
3
4
5
6
You choosed 2

2: System Load Linux Kernel then write to Flash via TFTP.

Warning!! Erase Linux in Flash then burn new one. Are you sure?(Y/N)

1
2
3
4
5
6
7
Please Input new ones /or Ctrl-C to discard

Input device IP (192.168.1.1) ==:192.168.1.1

Input server IP (192.168.1.100) ==:192.168.1.100

Input Linux Kernel filename () ==:test.bin

当你按下回车的那一瞬间,Tftpd32就会开始传输你选择的固件。

传输完成之后,WRTnode就会开始进行刷机,这时候注意千万不能断电。

最后我们可以看到,WRTnode已经可以正常启动!

0%