揭开面纱
机缘巧合的情况下,博主有幸得到了一次高通Dragonboard410C开发板的试用机会,Dragonboard410C也熟称龙板,下面简单介绍一下这个板子吧,资料摘录于网上。
硬件方面,DragonBoard 410c是一款搭载Qualcomm® Snapdragon™ 410(64位的四核处理器)的开发板,内置 8GB eMMC (支持标准microSD卡槽),内置wifi、蓝牙、和GPS模块,具有HDMI 输出及USB 接口 (3个),兼容96Boards消费版(CE)规范。
软件方面,DragonBoard 410c目前已经可以运行Android5.1、Ubuntu、Windows 10 IoT Core、Debian系统。
经过一段时间的等待,开发板送到博主手里,博主便面临了第一个问题,以往树莓派或者国产Pi这类开发板都是USB供电,但是龙板需要12V 2A的电源进行供电,而且电源插头还比较小,经过淘宝筛选,最终选定如下电源组合方案:网件路由器原装电源 + 万能DC转接头。东西到手后发现非常好用。
装系统
龙板装系统还是比较简单的,博主选用Debian系统,装新系统前使用了一会默认自带的Android,正好接到电视上,运行十分的流程,毫无卡顿的感觉。
1.TF卡安装
Mac终端登录到root用户
diskutil unmount /dev/disk1s1
dd bs=409600 if=debian.img | pv | dd bs=409600 of=/dev/rdisk1
这里加pv命令是为了能看见写入进度
当TF卡烧录完成之后,龙板断电,把TF卡插入龙板,将开发板背面的启动选择开关拨动为0-1-0-0,即将SD Boot拨动到ON位置,其他关闭,之后连接显示器、鼠标、键盘等外设,龙板上电开机,按照图形提示安装即可,步骤很少比较简单。
2.Fastboot线刷
博主是iOS程序猿,一些Android程序猿电脑估计Fastboot是必备的,博主这里现安装的
下载Google官方的Fastboot独立包:
platform-tools-latest-darwin.zip
platform-tools-latest-linux.zip
platform-tools-latest-windows.zip
之后解压缩到工作目录,并在终端里导入环境变量
export PATH=$PATH:工作目录/platform-tools
龙板Fastboot连接
首先需要确保开发板背面的启动选择开关处于0-0-0-0状态,然后需要通过usb连接到电脑。先启动时按住S4(音量 -)键再插电源,20左右fastboot devices看见设备。之后烧写bootloader、boot、root
顺便利用nBench来横向测评一下龙板、树莓派3、树莓派2的性能横向对比
龙板:
树莓派3:
树莓派2:
试用选题
起初博主是打算把之前做的机器人核心板用这个龙板来实现的,但是后来考虑时间很短,虽然三周,但是帝都的程序员加班大家懂的,难免保证不了开发时间,所以再三考虑,决定把选题修改为环境监测器,模拟要解决的情景就是上班族在下班回家的路上就可以通过手机查看家里的温湿度情况,然后控制空调,来预先调节好家里舒适的温度环境。
功能设计
针对上述情景,其实博主想做的就是一个最简单的闭环操作,检测到传感器检测到温湿度->反馈给开发板->开发板通过手机反馈给人->人操作空调调整温湿度->形成闭环。那么就设计功能如下
1.外网服务器,跑内网穿透服务端用;
2.开发板需实现温湿度采集,和控制空调,以及搭建Web服务器和运行内网穿透客户端;
3.后端服务接口,提供历史温湿度、实时温湿度;
4.iOS客户端,用来查看温湿度和控制空调;
查找资料
1.内网穿透之前就一直在用,这个就不过多说明了,Ngork和FRP都可以,博主选用的是FRP,服务于家里的Gen8塔式服务器和几个树莓派。
2.温湿度采集需要用到DHT11模块,之前这类模块可以很简单方便的在树莓派上使用因为有现成的驱动和程序,在龙板上需要自己写驱动,DHT11使用GPIO通信,故需要查找龙板的GPIO操作方法,以及Linux系统下与驱动相关的知识(设备树、内核编译等)。
3.遥控空调使用的是Linux下开源的LIRC软件,这是一个红外万能遥控器程序,需要与红外发射模块和红外接收模块配合使用,但是有一个困难跟上一条一样,就是树莓派内置了LIRC的设备驱动lirc_rpi,龙板上没有,也得自己想办法编写驱动。
到这里博主其实遇见了很多困难和问题,最主要的一点客观原因是,博主本质职iOS程序猿,接触硬件是兴趣因素比如我喜欢鼓捣玩或者做做机器人玩玩模型啥的,另一点接触硬件的原因就是第一份全职工作是开发智能家居,所以综上,博主属于那种软件程序猿,但是硬件懂一些懂的不特别深入的那种,这时候查阅高通文档的时候就遇见了一些困难,比如操作GPIO需要了解设备手册和原理图一类的,有时候搜索查阅文档就显得有些力不从心。
开发环境的搭建
这里博主在Gen8上虚拟化了一个Ubuntu来做编译内核和驱动用,之后再传到龙板上,因为毕竟服务器的编译速度和磁盘空间大很多。
下载内核源码,内核源码仓库地址:
kernel.git
使用Tag:debian-qcom-dragonboard410c-17.09
可以用git克隆下来自己检出代码,也可以直接下载源码压缩包
kernel-debian-qcom-dragonboard410c-17.09.tar.gz
下载编译工具:
gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu.tar.xz(这里博主机器上用的是老版本的)
下载DTC(设备树编译工具):
sudo apt-get install device-tree-compiler
git clone git://codeaurora.org/quic/kernel/skales
下载initrd镜像
打开http://builds.96boards.org/releases/dragonboard410c/linaro/debian/latest/
下载:initrd.img-4.9.56-linaro-lt-qcom
至此下载准备工作完成,把文件按照分类放到工作目录的根目录下,分别是gcc-linaro-4.8-2015.06-x86_64_aarch64-linux-gnu(编译工具)、image(镜像)、kernel-debian-qcom-dragonboard410c-17.09(内核源码)、skales(设备树编译工具)
工作目录如上
配置编译源码时编译工具的环境变量,有2种方法:
1.直接修改内核源码的Makefile,修改ARCH和CROSS_COMPILE
ARCH ?= arm64
CROSS_COMPILE ?= /root/db410c/gcc-linaro-4.8-2015.06-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
2.Makefile里写默认是环境变量,所以可以每次ssh登录到终端后,export环境变量,当然,想写死在环境变量的配置文件里也是可以的
export ARCH=arm64
export CROSS_COMPILE=/root/db410c/gcc-linaro-4.8-2015.06-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
至此,开发环境准备完成
开发
看了博主说那么多应该也能感觉到,博主是好折腾的那种人,软件、硬件、服务器、服务端、运维全都接触,所以上来就先在自己的Git上创建了该试用项目的组织,并邀请好友一起帮忙开发(PS.一个人时间太紧了,总加班,ಥ_ಥ)~
花了两天晚上的业余时间,参考了一款天气预报的APP,画出了原型图
内网穿透
使用FRP,穿透的方案是:公网Nginx(域名解析)->公网FRP服务端->龙板FRP客户端->龙板Nginx(域名解析)。
创建工作路径mkdir -p /etc/frp
分别把frpc frpc.ini放到工作路径下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[common] server_addr = zasper.net server_port = xxxx log_file = ./frpc.log log_level = info log_max_days = 3 privilege_token = xxxx pool_count = 5 tcp_mux = true user = Dragonboard410C login_fail_exit = true protocol = tcp [ssh] type = tcp local_ip = 127.0.0.1 local_port = 22 use_encryption = true use_compression = true remote_port = xxxx [web] type = http local_ip = 127.0.0.1 local_port = 80 custom_domains = xxxx.zasper.net |
安装supervisor做FRP的守护进程
sudo apt-get install supervisor
编写服务的conf
sudo vim /etc/supervisor/conf.d/frpc.conf
启动supervisor
supervisord -c /etc/supervisor/supervisord.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[program:frp] user=root command=/etc/frp/frpc -c /etc/frp/frpc.ini startsecs=1 startretries=100 autorstart=true autorestart=true stderr_logfile=/tmp/err-frps.log stderr_logfile_maxbytes=50MB stderr_logfile_backups=10 stdout_logfile=/tmp/out-frps.log stdout_logfile_maxbytes=50MB stdout_logfile_backups=10 |
接下来是恼人的驱动开发了,网上Google搜索DragonBoard410C GPIO的关键词,搜出来的全是如下
1. 导出
/sys/class/gpio# echo 44 > export
2. 设置方向
/sys/class/gpio/gpio44# echo out > direction
3. 查看方向
/sys/class/gpio/gpio44# cat direction
4. 设置输出
/sys/class/gpio/gpio44# echo 1 > value
5. 查看输出值
/sys/class/gpio/gpio44# cat value
6. 取消导出
/sys/class/gpio# echo 44 > unexport
但是后来发现驱动里一般不用这个方法,于是静下心来开始查阅学习了Linux ARM板的驱动开发流程。
DHT11
先改写设备树增加设备描述,之后编译内核、设备树,并与initrd一起生成boot.img引导镜像。
添加设备树在跟节点上
之后配置编译
make defconfig distro.config
make -j4 Image.gz dtbs KERNELRELEASE=4.9.56-linaro-lt-qcom
把编译好的设备树生成镜像
./skales/dtbTool -o image/dt.img -s 2048 kernel-debian-qcom-dragonboard410c-17.09/arch/arm64/boot/dts/qcom/
把内核镜像和设备树镜像和初始化磁盘镜像,打包生成boot镜像
./skales/mkbootimg –kernel kernel-debian-qcom-dragonboard410c-17.09/arch/arm64/boot/Image –ramdisk image/initrd.img-4.9.56-linaro-lt-qcom –output image/boot-db410c.img –dt image/dt.img –pagesize 2048 –base 0x80000000 –cmdline “root=/dev/disk/by-partlabel/rootfs rw rootwait console=ttyMSM0,115200n8”
之后龙板开启到fastboot模式,刷入boot镜像,系统镜像不需要重刷。
刷好后重启进入系统,这里的感受就是,龙板每次重启速度真的很快。重启后进入/sys/devices/platform/路径下,如果有dht11路径,则说明设备树添加成功,进入DHT11路径后发现没有iio设备,这是因为我们还没有挂载驱动的原因。
这里使用github搜到的dht11.c驱动为蓝本,更改驱动的匹配标识跟设备树的一致,调整gpio获取方式,调整数据原始数据的读取。
中间多次尝试多次失败的过程就不在累述,直接贴驱动源码
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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
/* * DHT11/DHT22 bit banging GPIO driver * * Copyright (c) Harald Geyer <harald@ccbib.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/err.h> #include <linux/interrupt.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/printk.h> #include <linux/slab.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/sysfs.h> #include <linux/io.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/wait.h> #include <linux/bitops.h> #include <linux/completion.h> #include <linux/mutex.h> #include <linux/delay.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/timekeeping.h> #include <linux/iio/iio.h> #define DRIVER_NAME "dht11" #define DHT11_DATA_VALID_TIME 2000000000 /* 2s in ns */ #define DHT11_EDGES_PREAMBLE 2 #define DHT11_BITS_PER_READ 40 /* * Note that when reading the sensor actually 84 edges are detected, but * since the last edge is not significant, we only store 83: */ #define DHT11_EDGES_PER_READ (2 * DHT11_BITS_PER_READ + \ DHT11_EDGES_PREAMBLE + 1) /* * Data transmission timing: * Data bits are encoded as pulse length (high time) on the data line. * 0-bit: 22-30uS -- typically 26uS (AM2302) * 1-bit: 68-75uS -- typically 70uS (AM2302) * The acutal timings also depend on the properties of the cable, with * longer cables typically making pulses shorter. * * Our decoding depends on the time resolution of the system: * timeres > 34uS ... don't know what a 1-tick pulse is * 34uS > timeres > 30uS ... no problem (30kHz and 32kHz clocks) * 30uS > timeres > 23uS ... don't know what a 2-tick pulse is * timeres < 23uS ... no problem * * Luckily clocks in the 33-44kHz range are quite uncommon, so we can * support most systems if the threshold for decoding a pulse as 1-bit * is chosen carefully. If somebody really wants to support clocks around * 40kHz, where this driver is most unreliable, there are two options. * a) select an implementation using busy loop polling on those systems * b) use the checksum to do some probabilistic decoding */ #define DHT11_START_TRANSMISSION_MIN 18000 /* us */ #define DHT11_START_TRANSMISSION_MAX 20000 /* us */ #define DHT11_MIN_TIMERES 34000 /* ns */ #define DHT11_THRESHOLD 49000 /* ns */ #define DHT11_AMBIG_LOW 23000 /* ns */ #define DHT11_AMBIG_HIGH 30000 /* ns */ struct dht11 { struct device *dev; int gpio; int irq; struct completion completion; /* The iio sysfs interface doesn't prevent concurrent reads: */ struct mutex lock; s64 timestamp; int temperature; int humidity; /* num_edges: -1 means "no transmission in progress" */ int num_edges; struct {s64 ts; int value; } edges[DHT11_EDGES_PER_READ]; }; #ifdef CONFIG_DYNAMIC_DEBUG /* * dht11_edges_print: show the data as actually received by the * driver. */ static void dht11_edges_print(struct dht11 *dht11) { int i; dev_dbg(dht11->dev, "%d edges detected:\n", dht11->num_edges); for (i = 1; i < dht11->num_edges; ++i) { dev_dbg(dht11->dev, "%d: %lld ns %s\n", i, dht11->edges[i].ts - dht11->edges[i - 1].ts, dht11->edges[i - 1].value ? "high" : "low"); } } #endif /* CONFIG_DYNAMIC_DEBUG */ static unsigned char dht11_decode_byte(char *bits) { unsigned char ret = 0; int i; for (i = 0; i < 8; ++i) { ret <<= 1; if (bits[i]) ++ret; } return ret; } static int dht11_decode(struct dht11 *dht11, int offset) { int i, t; char bits[DHT11_BITS_PER_READ]; unsigned char temp_int, temp_dec, hum_int, hum_dec, checksum; for (i = 0; i < DHT11_BITS_PER_READ; ++i) { t = dht11->edges[offset + 2 * i + 2].ts - dht11->edges[offset + 2 * i + 1].ts; if (!dht11->edges[offset + 2 * i + |