用16M的SD卡启动树莓派,在U盘上安装和运行Linux

树莓派官方的操作系统Raspbian最少需要4G的SDCard,如果你恰好有比较小的SD卡怎么办呢?设计上树莓派只能从SD卡引导启动,但我们可以在SD卡上装一个最小的引导系统,然后把树莓派引导到U盘上启动。SD卡连续运行,读写比较频繁也容易损坏(我已经坏掉3张了。。。),相比之下U盘价格便宜些,读写速度可以比SD卡还高。

制作引导SD卡

这个引导系统只需要16M容量的SD卡,先把SD卡格式化成FAT(windows),然后拷贝下面的文件到根目录。

git clone https://github.com/hugozhu/mini_raspbian_boot

修改 cmdline.txt 中的 root=/dev/mmcblk0p2root=/dev/sda2 以指定用U盘启动

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait cgroup_enable=memory

安装操作系统Raspbian到U盘

方法和安装到SD卡一样,在Mac上使用 dd 命令安装,在Windows上可以用win32diskimager:

sudo dd bs=1m if=2015-01-31-raspbian.img of=/dev/disk4 #/dev/disk4

扩展分区

如果你的U盘容量大于4G,那么上一步安装完后,你的U盘只能看到4G空间,大容量的SD卡的这个问题可以通过自带的raspi-config命令来解决。 Linux下可以用fdisk来解决,我的方法是把该U盘插到已经正常启动的树莓派的USB口上,正常识别后,执行sudo fdisk /dev/sda

  1. p后打印出分区表:
/dev/sda1            8192      122879       57344    c  W95 FAT32 (LBA)
/dev/sda2          122880     6399999     3138560   83  Linux
  1. 记住/dev/sda2的起始点122880',然后按d,删除掉二个分区,接着按n重新创建一个分区,起始点就设置为122880`,结束点可以设置为最大

  2. 最后按w保存新的分区表

插上引导SD卡和系统U盘后树莓派就可以愉快的运行起来了,如果U盘的速度足够快,你会发现系统还变快了。

参考文章

  1. http://www.raspberrypi.org/boot-from-a-16mb-sd-card/

用Telegram和树莓派交互

如果你的树莓派不能通过路由器端口转发直接从家庭网络外访问,你还可以用类似聊天App一样的方式来和防火墙后的树莓派轻松交互,通过命令来控制树莓派。

Telegram

image

Telegram是一个跨平台的即时通讯软件,客户端和通讯及加密协议完全公开且开源。官方有正式发布Android,iOS,Mac OS X, Web等客户端版本;Telegram允许多端同时登录。我们在手机上安装Telegram客户端,同时在树莓派上用同一个帐号或另一个帐号也登录Telegram,那么就可以用手机和树莓派聊天了,更高级的一些用法可以是把树莓派帐号加到群聊中,实现类似微软小冰的功能;也可以由树莓派主动向你的手机Push消息实现提醒功能。

安装telegram-cli

Linux的Telegram客户端telegram-cli源代码在 https://github.com/vysheng/tg,按照Readme在树莓派上编译好。telegram-cli支持消息事件对lua脚本中的函数进行回调,支持的函数列表在:https://github.com/vysheng/tg/blob/master/README-LUA,也支持外部程序通过telegram-cli侦听的端口发送交互命令(一次连接只能一个命令)

Lua交互脚本

脚本命名为:tg_raspberrypi.lua

now = os.time()

chat = "树莓派通知" -- telegram的会话名称

safe_commands = {}
safe_commands["uptime"]  = "uptime"
safe_commands["w"]       = "w"
safe_commands["ps"]      = "ps ax"
safe_commands["netstat"] = "netstat -na"
safe_commands["df"]      = "df"
safe_commands["ss"]      = "ss"
safe_commands["free"]    = "free"

function on_msg_receive (msg)
	if msg.out then
	    return
	end
	if msg.text then
		-- mark_read(msg.from.print_name)	
		-- vardump(msg)
		cmd = string.lower(trim(msg.text))
		if cmd == "ping" then
			send_msg (chat, 'pong', ok_cb, false)
		elseif safe_commands[cmd] ~= nil then
			send_msg (chat, exec(safe_commands[cmd]), ok_cb, false)
		end
	end
end

function on_our_id (id)
end
 
function on_secret_chat_created (peer)
end
 
function on_user_update (user)
end
 
function on_chat_update (user)
end
 
function on_get_difference_end ()
end
 
function on_binlog_replay_end ()
end

function exec(cmd)
   local output = ""
   f = assert (io.popen (cmd))
   for line in f:lines() do
     output = output .. "\n" .. line
   end -- for loop
   f:close()
   return output
end	

function trim(s)
  return (s:gsub("^%s*(.-)%s*$", "%1"))
end

function vardump(value, depth, key)
  local linePrefix = ""
  local spaces = ""
  
  if key ~= nil then
    linePrefix = "["..key.."] = "
  end
  
  if depth == nil then
    depth = 0
  else
    depth = depth + 1
    for i=1, depth do spaces = spaces .. "  " end
  end
  
  if type(value) == 'table' then
    mTable = getmetatable(value)
    if mTable == nil then
      print(spaces ..linePrefix.."(table) ")
    else
      print(spaces .."(metatable) ")
        value = mTable
    end		
    for tableKey, tableValue in pairs(value) do
      vardump(tableValue, depth, tableKey)
    end
  elseif type(value)	== 'function' or 
      type(value)	== 'thread' or 
      type(value)	== 'userdata' or
      value		== nil
  then
    print(spaces..tostring(value))
  else
    print(spaces..linePrefix.."("..type(value)..") "..tostring(value))
  end
end

print(exec("uptime"))

启动telegram-cli

第一次启动

第一次启动telegram-cli会需要你输入手机号码,输入短信验证码后登录,成功登录后会在~/.telegram-cli下保存登录信息,后面就不需要再登录了。

[Read More]

用树莓派2代打造智能家庭路由

家里的宽带上下行都有10Mbps了吧?除了可以BT下载外还能让你的移动设备在外的时候也能科学上网。

区别于在海外架设VPN服务:

  • 国内和大部分国外网站都可以直连而不降低速度;不像海外VPN所有流量(国内和国外网站)都要经过海外服务器,速度有一定的延迟
  • 这个方案代理可以使用免费的Goagent服务;或低成本的ssh帐号;而租用海外VPS服务器自建服务或购买VPN帐号费用较高些;
  • 利用的是家里的宽带,只有树莓派的硬件成本,没有主机托管成本;

假设家里的路由器IP地址为:192.168.1.1,树莓派2的IP地址为:192.168.1.3,以下是需要安装和设置步骤。

PPTP和L2TP VPN Server

首先在树莓派上安装和设置VPN服务器,移动设备就可以通过运营商网络连接回家里的树莓派(iPhone和Android都内置了PPTP和L2TP客户端),这样移动设备将以树莓派为路由访问网站,通过一些设置我们可以让树莓派提供科学上网服务。

关于PPTP和L2TP VPN设置和安装可以参考: http://hugozhu.myalert.info/2013/03/01/setup-l2tp-pptp-openvpn-on-ubuntu.html

但在树莓派上安装L2TP时不能直接apt-get install openswan,需要手动下载来安装,原因是因为最新的版本在协议上有些不兼容:

wget http://snapshot.raspbian.org/201403301125/raspbian/pool/main/o/openswan/openswan_2.6.37-3_armhf.deb
sudo dpkg -i openswan_2.6.37-3_armhf.deb

假设VPN服务端的local ip我们设置为192.168.3.1,PPTP客户端IP分配区间为:192.168.3.200~192.168.3.210,L2TP 客户端IP分配区间为:192.168.3.100~192.168.3.110,我们可以通过iptables对IP来源为192.168.3.0/24网段的流量做特殊的处理以达到科学上网的目的。

完成这一步后,需要在路由器上设置端口转发,使得使用运营商网络如移动4G的手机可以通过PPTP或L2TP连到树莓派上。

PPTP需要设置的端口转发 - tcp: 1723

L2TP需要设置的端口转发 - tcp: 50, udp: 500,4500,1701

两种VPN服务相比较:PPTP拨号速度比较快,但是不安全;L2TP有加密,相对安全。

Redsocks2

redsocks2是一个透明TCP代理,其实现使用了libevent库,性能较好,其最大的特点是如果目标IP可以直连则不会转发流量给加密代理,如果IP不能直连(通过连接超时判断)则会将流量转发给加密代理。这样可以将最少的流量转发到代理上,访问一般的国外网站如yahoo.com也不会经过代理而减速,在配置方面则做到了零配置,不需要手工维护网站名单。代理也能支持很多中类型,如socks5, shadowsocks, goagent, http-proxy等,redsocks2安装和配置可以见链接: http://github.com/hugozhu/redsocks

这里我们假设redsocks2的端口使用12345

iptables

使用iptables我们可以将VPN客户端192.168.3.0/24的流量转发到redsocks5的端口12345

sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
sudo iptables -t nat -X
sudo iptables -t nat -A PREROUTING -s 192.168.3.0/24 -p tcp --dport 80 -j REDIRECT --to-ports 12345 #转发VPN客户端的HTTP流量到端口12345
sudo iptables -t nat -A PREROUTING -s 192.168.3.0/24 -p tcp --dport 443 -j REDIRECT --to-ports 12345 #转发VPN客户端的HTTPS流量到端口12345
sudo iptables -t nat -A POSTROUTING -s 192.168.3.0/24 -o eth0 -j MASQUERADE #转发VPN客户端的TCP流量到网络出口,并进行IP伪装;如果树莓派使用无线网卡则将eth0改成wlan0

DNS加固

上面的设置我们解决了VPN拨号到树莓派的客户端通过redsocks2透明代理分流为直连或通过加密代理连接和访问目标网站,我们还需要解决一下DNS查询被纂改为不存在的IP地址的问题。

[Read More]

树莓派的GPIO接口输出电流限制

树莓派提供了一个连接头让我们访问CPU的17个GPIO接口,如下图

这些接口可配置成输入或输出。本文主要讨论GPIO引脚作为输出时电流的限制。

阻抗 (impendance)

阻抗和和电阻的区别(resistance)在于电阻的阻值是固定的,不会随着电流变化,阻抗则不然,可能随着外部变化,如电流或频率变化。从另一个角度来说,电阻是线性的,但阻抗不是。比如放大器的阻抗会随着输出的信号频率变化。

树莓派的的每个GPIO引脚都有一个寄存器可以设置引脚的驱动强度,也就是在保持输出电压为逻辑0和1的情况下,可以改变阻抗的大小从而改变GPIO引脚的输出电流大小。

通过如下电路测量相同电流下不同阻抗对应的GPIO电压输出(其中用到了一个电位器调节电流保持恒定):

通过计算后,下表是当输出电流为2,4 … 16mA时,对应的阻抗大小以及如果发生短路时的短路电流大小。

可以看出短路电流都是超过16mA的。

一个发光二极管压降约为1.52.0v,工作电流为310v

GPIO引脚的电流是通过板上的3.3V电压调整器输出的,树莓派是按平均每个引脚3mA来设计的,所以总的电流不能超过17 * 3 = 51mA。

结论

树莓派引脚电流大小的限制是:每个引脚最大输出电流为16毫安(mA),且同一时刻所有引脚的总输出电流不超过51毫安

参考链接

  1. http://www.thebox.myzen.co.uk/Raspberry/Understanding_Outputs.html

使用8位移位寄存器74HC595扩展树莓派的IO端口

树莓派的GPIO接口数目有限,驱动一个步进电机需要占用4个, 一个Nokia 5110液晶也要占4个, 传感器输入至少需要一个,多玩几个外设后接口就不够用了。如果接口可以复用就可以让树莓派驱动更多的外设了,本文讨论如何使用74HC595集成电路芯片来扩展树莓派的I/O接口。

芯片介绍

SN74HC595N是德州仪器公司生产的集成电路芯片,是一个8位串行输入变串行输出或并行输出移位寄存器,具有高阻关断,高电平和低电平三态输出。在IO扩充上,可以最多串联15片,也就是高达120个IO扩充。

(注意到芯片上的小凹槽了吗,拿芯片的时候以这个为参考物就不会搞反了)

接口的常用命名方式有以下两种:

接口代号(编号)说明接口代号(编号)说明
Q7’(9)serial data outputQH’ (9)serial data output
MR (10)Master Reset (Active Low)SRCLR (10)Shift register CLeaR
SH_CP (11)shift register clock inputSRCLK (11)Shift Register CLocK input
ST_CP (12)storage register clock inputRCLK (12)storage Register CLocK input
OE (13)output enable input (Active Low)OE (13)Output Enable
DS (14)serial data inputSER (14)SERial data input
Qx (15,1-7)data outputQx (15,1-7)data output

控制流程

如果要在8个引脚输出01010101

[Read More]

使用Go语言在树莓派上编程

WiringPi是树莓派上比较好的一个开发库,是用C语言写的。使用cgo,我们可以在Go语言里方便的调用WiringPI的函数,于是我包装了一个WiringPi-Go,目前支持wiringPi的基本功能,硬件SPI协议驱动Nokia 5110屏幕,以及中断,未来还会增加PWM和I2C协议的支持。

下面是一个完整的使用例子,结合了之前的两个电路:链接1链接2

通过push button可以切换液晶屏显示不同脚本的输出内容。

lcd_switch.go

package main

import (
	. "github.com/hugozhu/rpi"
	"github.com/hugozhu/rpi/pcd8544"
	"log"
	"os/exec"
	"time"
)

const (
	DIN        = PIN_MOSI
	SCLK       = PIN_SCLK
	DC         = PIN_GPIO_2
	RST        = PIN_GPIO_0
	CS         = PIN_CE0
	PUSHBUTTON = PIN_GPIO_6
	CONTRAST   = 40 //may need tweak for each Nokia 5110 screen
)

var screen_chan chan int
var TOTAL_MODES = 3

func init() {
	WiringPiSetup()
	pcd8544.LCDInit(SCLK, DIN, DC, CS, RST, CONTRAST)
	screen_chan = make(chan int, 1)
}

func main() {
	//a goroutine to check button push event
	go func() {
		last_time := time.Now().UnixNano() / 1000000
		btn_pushed := 0
		for pin := range WiringPiISR(PUSHBUTTON, INT_EDGE_FALLING) {
			if pin > -1 {
				n := time.Now().UnixNano() / 1000000
				delta := n - last_time
				if delta > 300 { //software debouncing
					log.Println("btn pushed")
					last_time = n
					btn_pushed++
					screen_chan <- btn_pushed % TOTAL_MODES //switch the screen display
				}
			}
		}
	}()

	//a groutine to update display every 5 seconds
	go loop_update_display()

	//set screen 0 to be default display
	screen_chan <- 0

	ticker := time.NewTicker(5 * time.Second)

	for {
		<-ticker.C
		screen_chan <- -1 //refresh current screen every 5 seconds
	}
}

func loop_update_display() {
	current_screen := 0
	for screen := range screen_chan {
		if screen >= 0 {
			if screen != current_screen {
				//btn pushed
				current_screen = screen
				display_loading()
			}
		}
		switch current_screen {
		case 0:
			display_screen0()
		case 1:
			display_screen1()
		case 2:
			display_screen2()
		}
	}
}

func display_loading() {
	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 20, "Loading ...")
	pcd8544.LCDdisplay()
}

func display_screen0() {
	out, err := exec.Command("/bin/screen_0.sh").CombinedOutput()
	if err != nil {
		out = []byte(err.Error())
	}

	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 0, string(out))
	pcd8544.LCDdisplay()
}

func display_screen1() {
	out, err := exec.Command("/bin/screen_1.sh").CombinedOutput()
	if err != nil {
		out = []byte(err.Error())
	}

	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 0, string(out))
	pcd8544.LCDdisplay()
}

func display_screen2() {
	out, err := exec.Command("/bin/screen_2.sh").CombinedOutput()
	if err != nil {
		out = []byte(err.Error())
	}

	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 0, string(out))
	pcd8544.LCDdisplay()
}

/bin/screen_2.sh

[Read More]

使用tsar记录和监控树莓派CPU温度

夏天到了,树莓派的CPU温度也开始节节攀升,虽然我们也可以用云服务cosm来监控,但每5分钟采样一次精度不够高,每分钟采样一次则上传次数又太多了点。最好的方法还是使用tsar这样的工具本地高频(如每1分钟)采样,然后再定时将5分钟的均值上传到cosm绘图。

Tsar是淘宝的一个用来收集服务器系统和应用信息的采集报告工具,如收集服务器的系统信息(cpu,mem等),以及应用数据(nginx、swift等),收集到的数据存储在服务器磁盘上,可以随时查询历史信息,也可以将数据发送到nagios报警。Tsar能够比较方便的增加模块,只需要按照tsar的要求编写数据的采集函数和展现函数,就可以把自定义的模块加入到tsar中。

更新

[2013-04-14] mod_rpi已经被合并到了主干代码:https://github.com/alibaba/tsar/blob/master/modules/mod_rpi.c 只需要增加文件:/etc/tsar/conf.d/rpi.conf,内容为以下即可开始使用mod_rpi模块:

mod_rpi on

####add it to tsar default output
output_stdio_mod mod_rpi

mod_rpi模块开发方法

首先按照安装说明,见https://github.com/alibaba/tsar将tsar和tsardevel安装好。

首先运行下面的命令生成mod_rpi模块:

hugo@raspberrypi2 ~/projects/tsardevel $ tsardevel rpi 
build:make
install:make install
uninstall:make uninstall
hugo@raspberrypi2 ~/projects/tsardevel $ ls rpi
Makefile  mod_rpi.c  mod_rpi.conf

然后修改mod_rpi.c,增加读取CPU温度的逻辑:

/*
 * (C) 2010-2011 Alibaba Group Holding Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "tsar.h"

/*
 * Structure for rpi infomation.
 */
struct stats_rpi {
	unsigned int cpu_temp;
};

#define STATS_TEST_SIZE (sizeof(struct stats_rpi))

static char *rpi_usage = "    --rpi               Rapsberry Pi information (CPU temprature ...)";


static void read_rpi_stats(struct module *mod, char *parameter)
{
	FILE *fp;
	char buf[64];
	memset(buf, 0, sizeof(buf));
	struct stats_rpi st_rpi;
	memset(&st_rpi, 0, sizeof(struct stats_rpi));

	if ((fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r")) == NULL) {
		return;
	}

	int cpu_temp;

	fscanf(fp, "%d", &cpu_temp);

	st_rpi.cpu_temp = cpu_temp;

	int pos = sprintf(buf, "%u",
			/* the store order is not same as read procedure */
			st_rpi.cpu_temp);
	buf[pos] = '\0';
	set_mod_record(mod, buf);
	fclose(fp);
	return;
}

static struct mod_info rpi_info[] = {
	{"  temp", SUMMARY_BIT,  0,  STATS_NULL}
};

static void set_rpi_record(struct module *mod, double st_array[],
		U_64 pre_array[], U_64 cur_array[], int inter)
{
	st_array[0] = cur_array[0]/1000.0;
}

void mod_register(struct module *mod)
{	
	register_mod_fileds(mod, "--rpi", rpi_usage, rpi_info, 1, read_rpi_stats, set_rpi_record);
}

最后make && sudo make install将mod_rpi自定义tsar模块安装好。

[Read More]