cubieboard – 折腾 https://blog.sorz.org 没治了 Sun, 14 Jan 2018 11:08:20 +0000 zh-CN hourly 1 https://wordpress.org/?v=4.9.4 Cubieboard 更新至主线内核 /p/cubieboard-on-mainline-kernel/ /p/cubieboard-on-mainline-kernel/#comments Sun, 28 Jun 2015 14:02:06 +0000 /?p=348052 Allwinner 此前一直在使用它自己 fork 的 kernelU-Boot
现在,他们正在努力将这些代码并入主线。

我在用的 Arch Linux,众所周知,是一个比较激进的发新版本。它已经开始送主线内核linux-armv7啦。

毫无悬念地,更新后就无法启动了。原因似乎是它没有更新 U-Boot。
参照这篇 Mainline U-Boot 编译最新的主线版 U-Boot 然后写入 SD 卡就好了。

需要注意的是,boot.cmd内容需要根据 Arch Linux 的安排有所更改:

setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait panic=10
load mmc 0:1 0x43000000 dtbs/${fdtfile}
load mmc 0:1 0x42000000 zImage
bootz 0x42000000 - 0x43000000

(不同之处也就是使用bootz来载入zImagefdtfile位于dtbs/下)

另外注意似乎还有一些驱动不太完善,比如无法使用 NAND,具体请参考 Linux mainlining effort

]]>
/p/cubieboard-on-mainline-kernel/feed/ 2
室内空气监测 /p/air-mointor/ /p/air-mointor/#respond Sat, 28 Feb 2015 14:25:28 +0000 /?p=348022 GP2Y1010AU0F
左图[1] 是夏普的紧凑型光学灰尘密度传感器 GP2Y1010AU0F (PDF),淘宝三十元不到。

传感器内部有一个 LED,通过检测灰尘对光的反射的强度来估计空气中单位体积内灰尘的质量。
虽然因为无法区分灰尘颗粒的体积,无法直接推算出 PM2.5 数值,但我想它和 PM2.5 之间应该还是比较强的相关性的 来源请求

(嘛 30 块一个东西还是不要想太多比较好。

另一个传感器是 DHT22,淘宝大概 20 元一个。用于测量空气温度和湿度。

去年暑假我把这两个东西用了起来,监测室内空气状况并绘制图表然后显示在网页上。干嘛用?就是玩具,好玩嘛… 如果你也觉得有点意思,想搞个玩玩… 这篇文章也许可以用作参考。

事隔较久,一些细节记不太清了,可能会有些疏漏。相关代码在使用前记得改改文件名路径什么的。

读取传感器

这两传感器都不是面向一般用户的,没有 USB 这样方便的东西呢…
DHT22 是用一根线串行输入输出
GP2Y1010AU0F 输出的是模拟信号,要测量输出口的电压,根据电压来计算灰尘密度(如下图[2])。
电压——灰尘密度图示

DHT22 也许[3]能用 Cubieboard (山寨树莓派) 的 GPIO 来读取。
但 GP2Y1010AU0F 则需要测量电压(A/D),Cubieboard 不带此功能。

于是就用了 Arduino,先用它定时从传感器读取数据,然后通过串口(UART)发送给 Cubieboard
读取传感器的模块:DHT22.cpp DHT22.h, GP2Y1010AU0F.cpp GP2Y1010AU0F.h
每隔三秒读取一次两传感器,从串口输出 的 Arduino 代码:Sensors.ino

储存数据

数据通过 UART 传给了 Cubiebaord,接下来就是想怎么存怎么存啦。
这里按照传统来源请求用 RRDtool 来储存数据。简单方便[4]自带数据库顺带还能把画图给做了。

先用rrdtool create创建数据库:rrd-create.sh
别问我这些参数是啥意思,半年多过去了我早忘了(
RRDtool 也是挺坑的,参数多不说,写出来一点都不直观,不对手册根本不知道这都是些什么鬼(

arduino-uart.py 这个 Python 脚本将持续地从串口读取 Arduino 发来的数据,然后执行rrdtool update往数据库里喂数据。

呈现数据

使用rrdtool graph绘制折线图:graph.sh
./graph.sh 1d 绘制近一天的图像,./graph.sh 1w 绘制近一周的图像。
(输出和数据文件路径请修改开头的DIRDATA变量)

可以使用crontab -e或者 systemd-timer[5] 定时执行脚本更新图像。

再加个网页做索引:index.html
网页上方可选择查看近一天、一周、一个月的数据[6]。这样就基本完成啦。

完成

其实装好后我一直挺担心灰尘传感器的读数是否正确的,没法方法确认,读出来的 µg/cm³ 我也没啥概念。不过这幅大年三十的图看起来倒是挺像回事的:
灰尘折线图(图中数据单位有误,应为 µg/m³)

即使家里门窗紧闭,依然能看到中午一点和午夜的两条高峰。想必是受周边鞭炮燃放的影响吧。

附注

[1] 来源 iMall
[2] 来源 Datasheet PDF
[3] 我不太确定,因为 Cubieboard 的每一个 GPIO 口似乎只能在开机时设置为读或者写二选一,而 DHT22 的读写都在一根线上。也许能用两个 GPIO 口并加少许电路来实现?
[4] 好吧其实一点都不简单方便。
[5] systemd 用户请参考 Gist 中的.timer .service等文件。
[6] 需分别生成日、周、月的图片,参见 [4] 中的三个 .timer 文件。

]]>
/p/air-mointor/feed/ 0
Watchdog Timer on Cubieboard /p/cubieboard-watchdog/ /p/cubieboard-watchdog/#comments Sat, 04 Jan 2014 16:56:49 +0000 https://sorz.org/?p=347795 准备将 Cubieboard 留在家里,放在路由器边长期开着收收短信什么的。
有了 Watchdog Timer(看门狗计时器)一旦 Cubieboard 死机,系统将自动重启。重启由硬件实现,确保可靠性。

内核支持

需在编译 Linux 内核时开启 Watchdog Timer 支持、编译 sunxi watchdog 驱动

选项位于Device Drivers >> Watchdog Timer Support,勾选WatchDog Timer Driver CoreAllwinner A10/A13 Watchdog。可以考虑勾选Disable watchdog shutdown on close,选择后若 watchdog 进程意外结束也将触发重启,否则 watchdog 将随 watchdog 进程的关闭而关闭。[1]

CONFIG_WATCHDOG=y
CONFIG_WATCHDOG_CORE=y
CONFIG_WATCHDOG_NOWAYOUT=y
CONFIG_SUNXI_WDT=m

若驱动编译为内核模块,需要手动加载:

sudo modprobe sunxi_wdt

可在/etc/modules加入一行,系统启动时将自动加载:

sunxi_wdt

加载成功后可以看见/dev/watchdog。任意程序打开该文件将启动 watchdog,打开后必须定期“戳一下” (poking) 它,即往里写数据。[1]

安装用户空间程序

戳狗程序当然有现成的:

sudo apt-get install watchdog
sudo service watchdog start

可以留意下配置/etc/watchdog.conf,有检测负载、内存、网络和自定义检测命令等可选功能。

U-Boot 支持

上述方案中,watchdog 只会在系统正常启动,且正常加载 sunxi_wdt,且 watchdog daemon 正常运行后才会生效。若开启 U-Boot 内置的 watchdog 支持,可在系统引导失败时重启系统

这是可选项,我还没试过,只是看文档中有写。(嘛毕竟有山寨货了(见下文((

首先编译 u-boot-sunxi 时需启用CONFIG_WATCHDOG
其次修改boot.scr,在bootm这行之上加入watchdog 16。该文件在引导分区,若无需自行建立。
我还没试过,就不乱说了,请参考 u-boot-sunxi/wiki#bootscr-support 配置。

需要注意的是,A10 只支持最长 16 秒的间隔时间[2]。也就是说,从加载内核、挂载分区、一直到启动 watchdog daemon 必须在 16 秒内完成,否则将触发重启。

在此之前…

由于不知道 SoC 内置了 watchdog,我翻了些零件自己山寨了一个

山寨 Watchdog (左) 和 Cubieboard (右)

山寨 Watchdog (左) 和 Cubieboard (右)

洞洞板是以前焊的,其上是一块 ATmega16U,都闲置好久了;
左边方块是一个继电器和三极管二极管电阻之类的,通过 USB 口连接着 Cubieboard 的电源;
中间蓝线连着单片机与一 GPIO 口,用一个脚本周期性输出 0,1 来戳狗。

单片机检测到超时没戳时,给继电器通电几秒,就成了..

(说来 ATmega 也内置一个 Watchdog Timer…

[1] The Linux Watchdog driver API;
[2] Allwinner A10 User Manual v1.20 (PDF), P99 10.3.25. Watch-Dog Mode Register.

]]>
/p/cubieboard-watchdog/feed/ 4
利用上网卡转发短信至邮箱,来电提醒 /p/sms2email/ /p/sms2email/#comments Tue, 18 Jun 2013 14:11:04 +0000 http://sorz.org/?p=347689 想法
SMS to Email 效果

SMS to Email 效果

唔,最早的想法是:学校离家较远所以有两张SIM卡,而且由于资费原因只能分别在两地使用,想通过互联网代理,使得可以在一地同时使用两张卡..

最早想到的是使用闲置低端安卓机,后来发现手头闲置的一张华为数据卡(EC1261)可以利用,遂试着捣鼓出一个这玩意..

本文实现:

  • 将所收短信转发至指定 E-mail
  • 挂断来电并回复短信,同时发送 E-mail 提醒

虽然还不能接打电话,但至少接收各种验证短信不再麻烦了呢 🙂

设备

闲置 华为数据卡 EC1261 一张
装 ubuntu 的 Cubieboard 一块

这张上网卡除了上网,收发短信也是可以的:流量提醒就是用短信实现的(所以总是一条条地接收);带了个 2.5mm 耳机插口,应该是用来打电话的,虽然电信给的软件不带此功能。

其他型号的华为上网卡应该类似

安装

Windows: 插入装好驱动就成,华为上网卡用串口和 PC 通讯,插入后设备管理器中可见串口号(COMXX)。

Linux: 以前在 ubuntu 10.10 试过插上就能用。在刷了官网上下的 ubuntu 镜像的 Cubieboard 上,貌似装了 usb_modeswitch 才正常。若自行编译内核,需确保勾选usb_wwanoption模块。会看见类似/dev/ttyUSB0的设备出现。

脚本设计运行于 Linux,依赖 Python 2 和 pySerial,发邮件需装 sendmail 。

apt-get install python-serial sendmail usb-modeswitch

通讯

关于如何“操作”上网卡,这个是关键呢,感谢这篇文章给了我线索,搜索关键词 HUAWEI CDMA Datacard Modem AT Command Interface Specification 即可,PDF 里有详细说明。

根据说明写了段程序,完成标题所述功能:hw_smsd.py
(注意下读写串口可能需要 root 权限)

#!/usr/bin/env python
#encoding: utf-8

# Copyright (C) 2013 @XiErCh
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import time
import serial
import smtplib
from datetime import datetime


LOGFILE_PATH  = 'mail.log'
TTY_DEV_PATH  = '/dev/ttyUSB0'

SENDER_EMAIL  = 'hw-smsd@somehost'
NOTICE_EMAIL  = 'your@email'
CALL_SMSREPLY = u'抱歉,本号码暂时停用。请拨打 13800138000 联系我,谢谢。'

class ATSerial(serial.Serial):
    '''Send a AT command and return the response or raise a error.
    '''
    # Please see HUAWEI CDMA Datacard Modem AT Command Interface Specification
    # for detial.
    def at(self, cmd):
        self.write('AT%s\r\n' % cmd)
        self.flush()
        self.readline()
        rsp = ''
        while True:
            l = self.readline().strip()
            if not l:
                continue
            elif l == 'OK':
                return rsp
            elif l == 'ERROR':
                raise IOError('AT: ERROR')
            elif l == 'COMMAND NOT SUPPORT':
                raise IOError('AT: COMMAND NOT SUPPORT')
            elif l == 'TOO MANY PARAMETERS':
                raise IOError('AT: TOO MANY PARAMETERS')
            rsp += '%s\n' % l


def get_serial():
    return ATSerial(TTY_DEV_PATH)


def sendmail(msg, subject='',fromaddr=SENDER_EMAIL, toaddr=NOTICE_EMAIL):
    s = smtplib.SMTP('localhost')
    msg = u"From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" \
            % (fromaddr, toaddr, subject, msg)
    s.sendmail(fromaddr, toaddr, msg.encode('utf-8'))
    logfile.write(msg.encode('utf-8') + '\n\n')
    logfile.flush()
    s.quit()


def sendsms(s, number, text, number_type=0):
    logfile.write('SMS to %s:\n%s\n\n' % (number, text))
    logfile.flush()
    s.write('AT^HCMGS="%s",%s\r' % (number, number_type))
    s.flush()
    time.sleep(0.1)
    s.write(text.encode('utf-16-be') + '\x00\x1a')
    while True:
        l = s.readline().strip()
        if l == 'OK':
            return
        elif l.startswith('+CMS ERROR:'):
            raise IOError(l)


def e_new_sms(s, opt):
    # opt[0]     callerID
    #    [1:7]   yyyy,mm,dd,hh,mm,ss
    #    [7]     lang
    #    [8]     format
    #    [9]     length
    #    [10-13] ...
    text = s.read(int(opt[9])).decode('utf-16-be')
    s.read(3) # Ignore ^Z\r\n
    send_time = "%s-%s-%s %s:%s:%s" % tuple(opt[1:7])
    recv_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    mailsubject = u'来自 %s 的短信' % opt[0]
    mailtext = u'%s\r\n\r\n----\r\n发送号码:%s\r\n发送日期:%s\r\n接收日期:%s' \
            % (text, opt[0], send_time, recv_time)
    sendmail(mailtext, mailsubject)
    print("SMS => E-mail")
    s.at('+CNMA') # ack


def e_income_call(s, opt):
    # opt[0] number
    #    [1] type
    #    [5] CLI validity
    if not opt[0]:
        if opt[5] == '1':
            opt[0] = u'隐藏号码'
        else:
            opt[0] = u'未知号码'
    mailsubject = u'来自 %s 的来电' % opt[0]
    mailtext = u'来电者:%s\r\n来电时间:%s' \
            % (opt[0], datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
    sendmail(mailtext, mailsubject)
    print("Call => E-mail")
    s.at('+CHV') # reject
    if opt[0].startswith('1'):
        sendsms(s, opt[0], CALL_SMSREPLY, opt[1])
        print("Call <= SMS")


def loop(s):
    line = s.readline().strip()
    if not line:
        return
    elif line == 'RING':
        return
    try:
        cmd, rsp = line.split(':', 1)
        rsp = rsp.strip()
        opt = rsp.split(',')
    except ValueError:
        print('unknow [%s]' % line)
        return

    if cmd == '^HCMT':
        e_new_sms(s, opt)
    elif cmd == '+CLIP':
        e_income_call(s, opt)

    else:
        print('unknow - %s:' % cmd)
        print(opt)


def main():
    s = get_serial()

    # SMS Parameter Selection:
    # ^HSMSSS=<ack>,<prt>,<fmt>,<prv>
    #   <fm>  6: Unicode
    s.at("^HSMSSS=0,0,6,0")

    # New SMS Notification Setting:
    # +CNMI=[<mode>[,<mt>[,<bm>[,<ds>[,<bfr>]]]]]
    s.at("+CNMI=1,2")

    #sendmail('test', 'sendmail test')
    while True:
        loop(s)

if __name__ == '__main__':
    logfile = open(LOGFILE_PATH, 'a')
    main()

还比较简陋,但运行了几天没问题,就发上来~怎么说也算留个思路吧。

改进

记得刚才说的,这张上网卡有带一个 2.5mm 插口么,
和 Cubieboard 的 Line-In, 耳机口 相连,再装个 Asterisk 配置一下,一个自己的 SIP 网络电话就架成了~

不过目前来说,我对这功能需求并不迫切呢..

]]>
/p/sms2email/feed/ 14
MMA7455L + Python + Cubieboard /p/accel-py/ /p/accel-py/#respond Sat, 11 May 2013 17:11:22 +0000 http://sorz.org/?p=347656 MMA7455L (PDF) 三轴加速度传感器,可以使用 I2C 或者 SPI 总线连接。两者 Cubieboard 均有提供,虽然不懂有多大区别但是 SPI 要四根线好麻烦所以就用 I2C 了..(

与 Cubieboard 的连接参照 WikiPB18 (TWI1-SCK)PB19 (TWI1-SDA) 两根接在模块的 SCLSDI 口上,接好电源(3.3V)和地线,模块的 CS 也接在电源上(以使能 I2C)。官网 ubuntu 镜像默认启用 I2C-1,若未启用则需修改 Fex 文件:twi1_used = 1

可以使用 i2c-tools 确认连接正确。装好后输入i2cdetect -y 1,如果出现1d(MMA7455L 的地址),说明连接无误可以通讯。

在 Cubieboard 上使用 Python 读取加速度数值,代码在这:MMA7455L.py,目前实现功能有:

  • 读取 8 或 10 位的 X, Y, Z 轴加速度数值(-127 ~ 128 / -511 ~ 512);
  • 切换 ±2g, ±4g 和 ±8g(set_range());
  • 自动校准(set_offset()clear_offset())。

#!/usr/bin/env python
import smbus
import time


class Accel():
    def __init__(self, dev=1, g=2):
        self.bus = smbus.SMBus(dev)
        self.set_range(g)


    def write(self, register, data):
        self.bus.write_byte_data(0x1D, register, data)


    def read(self, data):
        return self.bus.read_byte_data(0x1D, data)


    def set_range(self, g):
        if g == 8:
            d = 0b01000001
        elif g == 4:
            d = 0b01001001
        elif g == 2:
            d = 0b01000101
        else:
            raise ValueError("Only 2, 4 or 8.")
        self.write(0x16, d)
        self.g = g


    def _get(self, addr):
        l = self.read(addr)
        h = self.read(addr + 1)
        d = h << 8 | l
        if d > 511:
            d -= 1024
        return d


    def get_x(self):
        return self._get(0x00)


    def get_y(self):
        return self._get(0x02)


    def get_z(self):
        return self._get(0x04)


    def clear_offset(self):
        self.write(0x10, 0)
        self.write(0x11, 0)
        self.write(0x12, 0)
        self.write(0x13, 0)
        self.write(0x14, 0)
        self.write(0x15, 0)
        time.sleep(0.1)


    def _set_offset(self, addr, zero):
        d = self._get(addr - 0x10)
        d = zero - d
        d = int(d * 2.5)
        d += 65536
        self.write(addr, d & 0xff) # Low
        self.write(addr + 1, d >> 8 & 0xff) # High


    def set_offset(self):
        self.clear_offset()
        self._set_offset(0x10, 0)
        self._set_offset(0x12, 0)
        if self.g == 4:
            zero = 32
        else:
            zero = 64
        self._set_offset(0x14, zero)
        time.sleep(0.1)


def main():
    mma = Accel(1)
    mma.set_offset()
    while True:
        x = mma.get_x()
        y = mma.get_y()
        z = mma.get_z()
        print("x =%4d ,y =%4d, z =%4d" % (x, y, z))
        time.sleep(0.1)


if __name__ == '__main__':
    main()

这块IC功能挺多的,先把好玩的给写了,以后有需要再改脚本。

P.s. 写校准时被坑了两次,MMA7455L 的 PDF 里不给计算方法,得下 AN3745 看。里面有两句话:

(…) values calculated in Step 2 must be multiplied by two.
Note that there will still be a bit of offset shift, and you may need to multiply by a bit more than two to exactly subtract the offset.

果断只看到前一句就乘 2 了,实际上反复测试要乘 2.5 才完美…
另外虽然 PDF 里写数据只有 10 位,但负数取补码时得按 16 位取,发 16 位才行…

]]>
/p/accel-py/feed/ 0