Raspberry Pi的GPIO中断编程

背景介绍

树莓派的GPIO引脚不仅可以输出高低电平,也可以当做输入端口(可以想象成键盘输入),当GPIO接入的是高电平,GPIO的值可以认为是1,如果是低电平则是0。如下图所示,可以使用一个Push Button开关按键来控制GPIO 25(BCM Numbering)的高低电平以达到控制的目的。

GPIO 25和VCC(3.3V)之间通过R1(10K欧姆)和R2(1K欧姆)上拉电阻相连,当按键未被按下时,GPIO 25上拉到VCC,程序可以读到1,当按键按下时,GPIO 25被下拉电阻R2拉到GND(0V),程序可以读到0。如果不加R1,而GPIO 25不小心被设置成输出低电平时,将直接和VCC相连而造成短路,这样可能会烧掉这个引脚,所以加上限流电阻R1后,即使发生这样的情况,也不会出现短路情况。

应用

如果我们需要根据GPIO 25的值来控制树莓派,比如按下按钮时希望点亮某个LED或在液晶上显示当前时间,就需要通过程序来获取状态的变化。

一种常见的做法是在循环里不断读取该引脚的状态,当发生对应的变化的时执行控制逻辑,但显而易见,这种做法很消耗CPU,如果在循环增加sleep(1000)这样的调用,又很容易错过按键变化。较好的做法则是通过中断来实现。

最新的树莓派Raspbian和Arch Linux内核都已经包含了GPIO的中断处理支持。但使用前需要将指定GPIO引脚输出,方法如下:

首先可以通过命令echo 25 > /sys/class/gpio/export导出GPIO 25端口,执行成功后在相应的目录下看到以下文件,得益于Linux下一切都是文件的设计理念,GPIO的状态可以通过value文件来获取,这样就可以利用Linux的poll/epoll来获取value文件的变化(这点和Linux高性能网络编程是类似的)。

root@raspberrypi2 ~/projects/interrupt_test # ls -l /sys/class/gpio/gpio25/
total 0
-rw-r--r-- 1 root root 4096 Apr  8 23:56 active_low
-rw-r--r-- 1 root root 4096 Apr  8 22:29 direction
-rw-r--r-- 1 root root 4096 Apr  8 22:29 edge
drwxr-xr-x 2 root root    0 Apr  8 23:56 power
lrwxrwxrwx 1 root root    0 Apr  8 23:56 subsystem -> ../../../../class/gpio
-rw-r--r-- 1 root root 4096 Apr  8 22:08 uevent
-rw-r--r-- 1 root root 4096 Apr  8 22:29 value
root@raspberrypi2 ~/projects/interrupt_test # 

wiringPi

wiringPi库封装了一个简单的接口,传入一个回调函数,当事件发生时传入的函数将被调用。

[Read More]

备份Raspberry Pi

树莓派的操作系统安装在SD卡,使用一段时间后还是很有必要备份一下,以防哪天SD卡就坏了。

备份的目的地最方便的还是使用网络存储,我使用的是西部数据的MyBooklive3T网络硬盘。挺不错的一个产品,功能基本满足我的需求。

准备好备份目标盘,将Nas的备份目录mount到树莓派:

mkdir /mnt/backup
mount -t cifs //mybooklive/Public/Backup /mnt/backup -o guest

完整备份

确定相应的SD卡设备ID

root@raspberrypi2 ~/bin # fdisk -l

Disk /dev/mmcblk0: 1973 MB, 1973420032 bytes, 3854336 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0004f23a

        Device Boot      Start         End      Blocks   Id  System
/dev/mmcblk0p1   *        2048      186367       92160    c  W95 FAT32 (LBA)
/dev/mmcblk0p2          186368     3667967     1740800   83  Linux

Disk /dev/sda: 2107 MB, 2107637760 bytes, 4116480 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

执行备份

[Read More]

在Raspberry Pi上使用硬件SPI

什么是SPI

SPI (Serial Peripheral Interface),是一种高速,全双工,同步的通信总线协议,基于SPI的设备需要4根线:

  1. SDO / MOSI - 主设备数据输出,从设备数据输入
  2. SDI / MISO - 主设备数据输入,从设备数据输出
  3. SCLK / CLK - 时钟信号,由主设备产生
  4. CS / SS - 从设备使能信号,由主设备控制

通过CS,主设备可以控制和哪个从设备通信。

Bit Banging

Bit-banging是一种用软件替代专职硬件的串行通信的技术。软件直接对微处理器的管脚的状态进行设置和采样,其功能涵盖诸如:时钟,电平,同步等所有参数。与此不同的是(传统的串行通信技术中),专职硬件诸如 modem、UART 或者 位移寄存器等一般是用来处理这些参数并且提供一个(缓存)的数据接口,软件在这种情况下同信号处理无关。

bit-banging 具有明显优点诸如:让相同的设备运行不同的协议而只需很小的(甚至不需)硬件的改动。借助很少的额外设备,我们也许可以从数字管脚(数字终端)可以得到视频信号。

bit-banging 也有一些明显的缺点。在软件仿真的过程中消耗的能量比同样功能的专职硬件大。微处理器过忙地从管脚采样和发送采样信号到管脚。在同等微处理器处理能力下,系统常常会有些噪音。

在Rasperry Pi上使用Bit Banging在实际情况下有可能因为操作系统调度造成时钟信号不稳定而使设备收到错误的消息,具体的表现就是Nokia 5110屏在长时间运行过程中出现白屏或花屏现象,如下图:

采用硬件SPI,由Pi的管脚14号Pin(左边倒数第二个)SCLK发出一定频率的时钟信号。经过测试,这种方法产生的时钟信号比Bit Banging软件模拟产生的信号要稳定很多。

软件模拟时钟信号波形 硬件SPI时钟信号波形

测试Pi的硬件SPI

确认内核支持

root@raspberrypi2 ~/projects/spi_test # ls -la /dev/spi*
crw------- 1 root root 153, 0 Jan  1  1970 /dev/spidev0.0
crw------- 1 root root 153, 1 Jan  1  1970 /dev/spidev0.1

测试代码

下载 spidev_test.c 或拷贝下面的代码:

[Read More]

在Raspberry Pi上使用Google Channel服务搭建实时应用

前面提到了有关个人网站的实时在线人数问题,本文要讨论的是如何自己来实现一个这样的统计服务。因为网站也同时部署在Github上,海外用户访问Github镜像网站的访问日志Pi是拿不到的,这怎么办?

Google Channel Service

Google Channel Service允许应用和GAE (Google App Engine) 保持一个长连接,允许应用实时发送消息给JavaScript客户端,而不用让客户端用效率很低的定时轮询获取新消息。这个服务是允许有多个发布者和多个订阅者,也能创建多个主题来关联发布者和订阅者。

使用这个服务分两步:

  1. 客户端请求服务器端(部署在GAE上)获取一个Channel的Token:

  2. 客户端根据Channel Token和服务器建立长连接,并开始接收消息,这时其它的客户端(或服务器端)可以想这个通道发送消息

在线人数统计实现

在页面上部署beacon

通常的网站流量统计是依赖部署在页面上的beacon(Javascript或图片标签)来实现的,这样做的好处是可以直接过滤掉一些机器流量,并且可以将日志集中存储在日志收集服务器上,和网站分离开。

于是可以利用GAE实现一个简单的Beacon服务,这里采用Go语言来实现,用Java或Python也是可以的。

package counter

import (
	"encoding/base64"
	"fmt"
	"time"
	"net/http"

	"appengine"
	"appengine/channel"
)

var GIF []byte

const (
	TOPIC = "counter"
)

func init() {
	GIF, _ = base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
	
	
	http.HandleFunc("/beacon.gif", handler)
	http.HandleFunc("/new_token", handler_new_token)	
}

func handler(w http.ResponseWriter, r *http.Request) {
	context := appengine.NewContext(r)

	now := time.Now()
	expire := now.AddDate(30, 0, 0)
	zcookie, _ := r.Cookie("z")
	if zcookie == nil {
		zcookie = &http.Cookie{}
		zcookie.Name = "z"
		zcookie.Value = make_hash("<your_salt>", r.RemoteAddr, now.UnixNano())
		zcookie.Expires = expire
		zcookie.Path = "/"
		http.SetCookie(w, zcookie)
	}

	w.Header().Set("Content-type", "image/gif")
	w.Header().Set("Cache-control", "no-cache, must-revalidate")
	w.Header().Set("Expires", "Sat, 26 Jul 1997 05:00:00 GMT")

	fmt.Fprintf(w, "%s", GIF)

	channel.Send(context, TOPIC, zcookie.Value+"\n"+r.RemoteAddr+"\n"+r.Referer()+"\n"+r.UserAgent())
}

func handler_new_token(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	tok, err := channel.Create(c, TOPIC)	
	callback := r.FormValue("callback")	
	if err != nil {
		http.Error(w, "Couldn't create Channel", http.StatusInternalServerError)
		c.Errorf("channel.Create: %v", err)
		return
	}
	if callback == "" {
		w.Header().Set("Content-type", "text/javascript")
		fmt.Fprintf(w, "%s", tok)
	} else {
		fmt.Fprintf(w, callback+"('%s')", tok)
	}
}

代码最后一行是将访问日志实时通过Channel发送出去,该通道有一个指定的主题,这样订阅该主题的客户端都可以收到相应的消息。

[Read More]

Arduino初试

今天拿到一块Arduino UNO R3板,迫不及待就开始试用了。相比Raspberry Pi是一个全能的电脑,Arduino则是个硬件开源的单片机,因为开源,资料和配件网上就很很多了,也就容易让初学者上手了。

Arduino特点:

  1. 开源,硬件标准化,配套传感器等模块很多;
  2. 结构简单
  3. 实时系统,稳定,启动只要0.5秒

Arduino IDE

下载Arduino IDE

上电测试

用USB线接在电脑USB口,然后在GND和PIN 13上插一个二极管,注意二极管正极插在PIN 13上, 如下图:

(注:还应该串联一个300欧姆的限流电阻才保险!)

上传代码

在Arduino IDE编辑好下面的代码,然后点Upload后就会运行了,会看到LED一闪一闪。

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.
 
  This example code is in the public domain.
 */
 
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second
}

参考链接

  1. http://arduino.cc/en/Tutorial/Blink

并发编程之内存屏障

原文地址:http://mechanical-sympathy.blogspot.com/2011/07/memory-barriersfences.htmlhttp://ifeve.com/memory-barriersfences/

关键词:Load Barrier, Store Barrier, Full Barrier

本文我将和大家讨论并发编程中最基础的一项技术:内存屏障或内存栅栏,也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。

CPU使用了很多优化技术来达成一个事实:CPU执行单元的速度要远超主存访问速度。在我上一篇文章 “Write Combing - 合并写"中我已经介绍了其中的一项技术。CPU避免内存访问延迟最常见的技术是将指令管道化,然后尽量重排这些管道的执行以最大利用缓存而把因为缓存未命中引起的延迟降到最小。

当一个程序执行时指令是否被重排并不重要,只要最终的结果是一样的。例如,在一个循环里,如果循环体内没用到这个计数器,循环的计数器什么时候更新(在循环开始,中间还是最后)并不重要。编译器和CPU可以自由的重排指令以最佳的利用CPU,只要下一次循环前更新该计数器即可。并且在循环执行中,这个变量可能一直存在寄存器上,并没有被推到缓存或主存,这样这个变量对其他CPU来说一直都是不可见的。

CPU核内部包含了多个执行单元。例如,现代Intel CPU包含了6个执行单元,可以组合进行算术运算,逻辑条件判断及内存操作。每个执行单元可以执行上述任务的某种组合。这些执行单元是并行执行的,这样指令也就是在并行执行。但如果站在另一个CPU角度看,这也就产生了程序顺序的另一种不确定性。

最后,当一个缓存失效发生时,现代CPU可以先假设一个内存载入的值并根据这个假设值继续执行,直到内存载入返回确切的值。

CPU核
  |
  V
寄存器
  |
  V
执行单元 -> Load/Store缓冲区->L1 Cache --->L3 Cache-->内存控制器-->主存
       |                                   |
       +-> Write Combine缓冲区->L2 Cache ---+

代码顺序并不是真正的执行顺序,CPU和编译器可以各种优化只要有空间提高性能。缓存和主存的读取会利用load, store和write-combining缓冲区来缓冲和重排。这些缓冲区是查找速度很快的关联队列,当一个后来发生的load需要读取上一个store的值,而该值还没有到达缓存,查找是必需的,上图描绘的是一个简化的现代多核CPU,从上图可以看出执行单元可以利用本地寄存器和缓冲区来管理和缓存子系统的交互。

在多线程环境里需要使用技术来使得程序结果尽快可见。这篇文章里我不会涉及到 Cache Conherence 的概念。请先假定一个事实:一旦内存数据被推送到缓存,就会有消息协议来确保所有的缓存会对所有的共享数据同步并保持一致。这个使内存数据对CPU核可见的技术被称为内存屏障或内存栅栏。

内存屏障提供了两个功能。首先,它们通过确保从另一个CPU来看屏障的两边的所有指令都是正确的程序顺序,而保持程序顺序的外部可见性;其次它们可以实现内存数据可见性,确保内存数据会同步到CPU缓存子系统。

大多数的内存屏障都是复杂的话题。在不同的CPU架构上内存屏障的实现非常不一样。相对来说Intel CPU的强内存模型比DEC Alpha的弱复杂内存模型(缓存不仅分层了,还分区了)更简单。因为x86处理器是在多线程编程中最常见的,下面我尽量用x86的架构来阐述。

Store Barrier

Store屏障,是x86的”sfence“指令,强制所有在store屏障指令之前的store指令,都在该store屏障指令执行之前被执行,并把store缓冲区的数据都刷到CPU缓存。这会使得程序状态对其它CPU可见,这样其它CPU可以根据需要介入。一个实际的好例子是Disruptor中的BatchEventProcessor。当序列Sequence被一个消费者更新时,其它消费者(Consumers)和生产者(Producers)知道该消费者的进度,因此可以采取合适的动作。所以屏障之前发生的内存更新都可见了。

private volatile long sequence = RingBuffer.INITIAL_CURSOR_VALUE;
 
// from inside the run() method
 
T event = null;
long nextSequence = sequence.get() + 1L;
while (running)
{
    try
    {
        final long availableSequence = barrier.waitFor(nextSequence);
 
        while (nextSequence <= availableSequence)
        {
            event = ringBuffer.get(nextSequence);
            boolean endOfBatch = nextSequence == availableSequence;
            eventHandler.onEvent(event, nextSequence, endOfBatch);
            nextSequence++;
        }
 
        sequence.set(nextSequence - 1L); 
        // store barrier inserted here !!!
    }
    catch (final Exception ex)
    {
        exceptionHandler.handle(ex, nextSequence, event);
        sequence.set(nextSequence);
        // store barrier inserted here !!!
        nextSequence++;
    }
}

Load Barrier

Load屏障,是x86上的”ifence“指令,强制所有在load屏障指令之后的load指令,都在该load屏障指令执行之后被执行,并且一直等到load缓冲区被该CPU读完才能执行之后的load指令。这使得从其它CPU暴露出来的程序状态对该CPU可见,这之后CPU可以进行后续处理。一个好例子是上面的BatchEventProcessor的sequence对象是放在屏障后被生产者或消费者使用。

[Read More]

个人网站实时在线人数接口

感觉把个人网站正在访问的在线人数显示在Nokia 5110液晶屏挺好玩,就稍微研究了一下如何提取实时在线人数。

实现方法

Google Analytics

Google Analytics具有很强大的实时流量分析功能,不过网站主必须登陆到后台才能看,但并没有提供Open API,所以就不能用这个服务了。

日志分析

不修改网站通过web服务器的日志分析,用一个脚本统计15分钟内日志的Unique IP可以粗略的获得一个在线人数。 但多个用户可能通过一个IP过来,这种做法肯定不精确。一般我们可以通过在页面上部署Javascript脚本,由Javascript为每一个浏览器产生一个独特的持久化Cookie,用这个Cookie代替IP来统计。但用Raspberry Pi来做这件事情会拖慢网站,于是一种方案是采用免费的Google App Engine来实现,打算有空来实现一个。

CNZZ

CNZZ也提供了15分钟内的在线人数统计功能。分析CNZZ的计数器代码后发现如下方法可以提取到在线人数:

curl -s "http://online.cnzz.com/online/online.php?id=[your_cnzz_id]&h=[your_cnzz_server_id].cnzz.com&on=1&s=line" | sed -e 's/.*当前在线\[\([0-9]\).*/\1/g'

于是先通过上面的脚本提取在线人数并上传到Cosm,Cosm有个触发器功能可以当在线人数超过某个值后发Twitter或HTTP Post到指定URL,并通过程序显示在液晶屏上。

升级版电子钟 - 如何使用Raspberry Pi驱动Nokia 5110液晶屏

Nokia 5110屏比前面介绍过的1602液晶屏功能好很多,淘宝上买价格相差不大(二手5110 12块左右, 全新1602 8块左右),Nokia 5110最少只需要占用4个GPIO引脚:

  1. 带蓝色背光
  2. 使用Philips PCD8544 LCD控制器(通过SPI接口)
  3. 84x48点阵,可显示100多个字符

硬件准备

  1. 树莓派
  2. Nokia 5110 拆机屏焊好的? 注意不要买裸屏,需要带电路板的
  3. 杜邦线 母对母8条
  4. 8P排针 用来焊接5110屏幕PCB板
  5. 电烙铁

电路

5110电路板有8个引脚,使用排针(如下图)将其焊上,方便后面用杜邦线连接,如果不会焊也可以买焊接好的。

  1. RST —— 复位 接GPIO 0
  2. CE —— 片选 接GPIO 1 或 不接
  3. DC —— 数据/指令选择 接GPIO 2
  4. DIN —— 串行数据线 接GPIO 3
  5. CLK —— 串行时钟线 接GPIO 5 (因为我的GPIO 4已经接了一个DHT11传感器)
  6. VCC —— 电源输入 接3.3v
  7. BL —— 背光控制端 接3.3v
  8. GND —— 地线 接地

PS. 编号规范看这里 VCC, BK, GND可以接在面包板电源上

[Read More]

如何使用Raspberry Pi在1602液晶屏上显示当前时间--电子钟

硬件准备

需要以下硬件:

  1. 树莓派
  2. 面包板
  3. 1602液晶屏一块
  4. 10K电位器
  5. 杜邦线
  6. 排针
  7. 面包板电源

1602 LCD液晶屏

LCD1602液晶屏提供了16列x2行的ASCII字符显示能力,工作电压5V,提供4位数据与8位数据两种工作模式,因为Raspberry Pi的GPIO口数量很有限,所以使用4位数据模式。LCD1602液晶屏模块提供了16个引脚,我们只需接其中的12个即可–请参考GPIO命名规则

  1. VSS,接地,RPi PIN 6
  2. VDD,接5V电源,PRi PIN 2
  3. VO,液晶对比度调节,接电位器中间的引脚
  4. RS,寄存器选择,接GPIO 14,RPi PIN 8
  5. RW,读写选择,接地,表示写模式,PRi PIN 6
  6. EN,使能信号,接GPIO 15,RPi PIN 10
  7. D0,数据位0,4位工作模式下不用,不接
  8. D1,数据位1,4位工作模式下不用,不接
  9. D2,数据位2,4位工作模式下不用,不接
  10. D3,数据位3,4位工作模式下不用,不接
  11. D4,数据位4,接GPIO 17,RPi PIN 11
  12. D5,数据位5,接GPIO 18,RPi PIN 12
  13. D6,数据位6,接GPIO 27,RPi PIN 13
  14. D7,数据位7,接GPIO 22,RPi PIN 15
  15. A,液晶屏背光+,接5V,RPi PIN 2
  16. K,液晶屏背光-,接地,RPi PIN 6

注意事项

  1. 电源VDD最后接上
  2. 排针焊接在液晶屏时注意不要虚焊,也可以用万用表测量一下
  3. RW脚注意一定要接地
  4. 调节电位器可以调节液晶对比度

电路图

代码

#!/usr/bin/python

#
# based on code from lrvick and LiquidCrystal
# lrvic - https://github.com/lrvick/raspi-hd44780/blob/master/hd44780.py
# LiquidCrystal - https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp
#

from time import sleep
from datetime import datetime
from time import sleep

class Adafruit_CharLCD:

    # commands
    LCD_CLEARDISPLAY 		= 0x01
    LCD_RETURNHOME 		    = 0x02
    LCD_ENTRYMODESET 		= 0x04
    LCD_DISPLAYCONTROL 		= 0x08
    LCD_CURSORSHIFT 		= 0x10
    LCD_FUNCTIONSET 		= 0x20
    LCD_SETCGRAMADDR 		= 0x40
    LCD_SETDDRAMADDR 		= 0x80

    # flags for display entry mode
    LCD_ENTRYRIGHT 		= 0x00
    LCD_ENTRYLEFT 		= 0x02
    LCD_ENTRYSHIFTINCREMENT 	= 0x01
    LCD_ENTRYSHIFTDECREMENT 	= 0x00

    # flags for display on/off control
    LCD_DISPLAYON 		= 0x04
    LCD_DISPLAYOFF 		= 0x00
    LCD_CURSORON 		= 0x02
    LCD_CURSOROFF 		= 0x00
    LCD_BLINKON 		= 0x01
    LCD_BLINKOFF 		= 0x00

    # flags for display/cursor shift
    LCD_DISPLAYMOVE 		= 0x08
    LCD_CURSORMOVE 		= 0x00

    # flags for display/cursor shift
    LCD_DISPLAYMOVE 		= 0x08
    LCD_CURSORMOVE 		= 0x00
    LCD_MOVERIGHT 		= 0x04
    LCD_MOVELEFT 		= 0x00

    # flags for function set
    LCD_8BITMODE 		= 0x10
    LCD_4BITMODE 		= 0x00
    LCD_2LINE 			= 0x08
    LCD_1LINE 			= 0x00
    LCD_5x10DOTS 		= 0x04
    LCD_5x8DOTS 		= 0x00



    def __init__(self, pin_rs=8, pin_e=10, pins_db=[11,12,13,15], GPIO = None):
	# Emulate the old behavior of using RPi.GPIO if we haven't been given
	# an explicit GPIO interface to use
	if not GPIO:
	    import RPi.GPIO as GPIO
        GPIO.setwarnings(False)

   	self.GPIO = GPIO
        self.pin_rs = pin_rs
        self.pin_e = pin_e
        self.pins_db = pins_db

        self.GPIO.setmode(GPIO.BOARD)
        self.GPIO.setup(self.pin_e, GPIO.OUT)
        self.GPIO.setup(self.pin_rs, GPIO.OUT)

        for pin in self.pins_db:
            self.GPIO.setup(pin, GPIO.OUT)

	self.write4bits(0x33) # initialization
	self.write4bits(0x32) # initialization
	self.write4bits(0x28) # 2 line 5x7 matrix
	self.write4bits(0x0C) # turn cursor off 0x0E to enable cursor
	self.write4bits(0x06) # shift cursor right

	self.displaycontrol = self.LCD_DISPLAYON | self.LCD_CURSOROFF | self.LCD_BLINKOFF

	self.displayfunction = self.LCD_4BITMODE | self.LCD_1LINE | self.LCD_5x8DOTS
	self.displayfunction |= self.LCD_2LINE

	""" Initialize to default text direction (for romance languages) """
	self.displaymode =  self.LCD_ENTRYLEFT | self.LCD_ENTRYSHIFTDECREMENT
	self.write4bits(self.LCD_ENTRYMODESET | self.displaymode) #  set the entry mode

        self.clear()


    def begin(self, cols, lines):

	if (lines > 1):
		self.numlines = lines
    		self.displayfunction |= self.LCD_2LINE
		self.currline = 0


    def home(self):

	self.write4bits(self.LCD_RETURNHOME) # set cursor position to zero
	self.delayMicroseconds(3000) # this command takes a long time!
	

    def clear(self):

	self.write4bits(self.LCD_CLEARDISPLAY) # command to clear display
	self.delayMicroseconds(3000)	# 3000 microsecond sleep, clearing the display takes a long time


    def setCursor(self, col, row):

	self.row_offsets = [ 0x00, 0x40, 0x14, 0x54 ]

	if ( row > self.numlines ): 
		row = self.numlines - 1 # we count rows starting w/0

	self.write4bits(self.LCD_SETDDRAMADDR | (col + self.row_offsets[row]))


    def noDisplay(self): 
	""" Turn the display off (quickly) """

	self.displaycontrol &= ~self.LCD_DISPLAYON
	self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)


    def display(self):
	""" Turn the display on (quickly) """

	self.displaycontrol |= self.LCD_DISPLAYON
	self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)


    def noCursor(self):
	""" Turns the underline cursor on/off """

	self.displaycontrol &= ~self.LCD_CURSORON
	self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)


    def cursor(self):
	""" Cursor On """

	self.displaycontrol |= self.LCD_CURSORON
	self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)


    def noBlink(self):
	""" Turn on and off the blinking cursor """

	self.displaycontrol &= ~self.LCD_BLINKON
	self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)


    def noBlink(self):
	""" Turn on and off the blinking cursor """

	self.displaycontrol &= ~self.LCD_BLINKON
	self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)


    def DisplayLeft(self):
	""" These commands scroll the display without changing the RAM """

	self.write4bits(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVELEFT)


    def scrollDisplayRight(self):
	""" These commands scroll the display without changing the RAM """

	self.write4bits(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVERIGHT);


    def leftToRight(self):
	""" This is for text that flows Left to Right """

	self.displaymode |= self.LCD_ENTRYLEFT
	self.write4bits(self.LCD_ENTRYMODESET | self.displaymode);


    def rightToLeft(self):
	""" This is for text that flows Right to Left """
	self.displaymode &= ~self.LCD_ENTRYLEFT
	self.write4bits(self.LCD_ENTRYMODESET | self.displaymode)


    def autoscroll(self):
	""" This will 'right justify' text from the cursor """

	self.displaymode |= self.LCD_ENTRYSHIFTINCREMENT
	self.write4bits(self.LCD_ENTRYMODESET | self.displaymode)


    def noAutoscroll(self): 
	""" This will 'left justify' text from the cursor """

	self.displaymode &= ~self.LCD_ENTRYSHIFTINCREMENT
	self.write4bits(self.LCD_ENTRYMODESET | self.displaymode)


    def write4bits(self, bits, char_mode=False):
        """ Send command to LCD """

	self.delayMicroseconds(1000) # 1000 microsecond sleep

        bits=bin(bits)[2:].zfill(8)

        self.GPIO.output(self.pin_rs, char_mode)

        for pin in self.pins_db:
            self.GPIO.output(pin, False)

        for i in range(4):
            if bits[i] == "1":
                self.GPIO.output(self.pins_db[::-1][i], True)

	self.pulseEnable()

        for pin in self.pins_db:
            self.GPIO.output(pin, False)

        for i in range(4,8):
            if bits[i] == "1":
                self.GPIO.output(self.pins_db[::-1][i-4], True)

	self.pulseEnable()


    def delayMicroseconds(self, microseconds):
	seconds = microseconds / float(1000000)	# divide microseconds by 1 million for seconds
	sleep(seconds)


    def pulseEnable(self):
	self.GPIO.output(self.pin_e, False)
	self.delayMicroseconds(1)		# 1 microsecond pause - enable pulse must be > 450ns 
	self.GPIO.output(self.pin_e, True)
	self.delayMicroseconds(1)		# 1 microsecond pause - enable pulse must be > 450ns 
	self.GPIO.output(self.pin_e, False)
	self.delayMicroseconds(1)		# commands need > 37us to settle


    def message(self, text):
        """ Send string to LCD. Newline wraps to second line"""

        for char in text:
            if char == '\n':
                self.write4bits(0xC0) # next line
            else:
                self.write4bits(ord(char),True)


if __name__ == '__main__':

    lcd = Adafruit_CharLCD()
    lcd.noBlink()

    while True:
        sleep(1)
        lcd.clear()        
        lcd.message(datetime.now().strftime('  %I : %M : %S \n%a %b %d %Y'))
        

完成后的效果

参考链接

  1. http://learn.adafruit.com/drive-a-16x2-lcd-directly-with-a-raspberry-pi/wiring
  2. http://www.freemindworld.com/blog/tag/树莓派

Raspberry Pi GPIO的编号规范

树莓派和普通电脑不一样的地方在于它还带了17个可编程的GPIO(General Purpose Input/Output),可以用来驱动各种外设(如传感器,步进电机等)。但GPIO的编号方法有些混乱,不同的API(如wiringPi,RPi.GPIO等)对GPIO的端口号编号并不一样,下面则用图表标明了对应的叫法,这样在看程序例子的时候可以确定物理是哪个接口。

GPIO库

  1. wiringPi C,有Perl, PHP, Ruby, Node.JS和**Golang**的扩展,支持wiringPi Pin和BCM GPIO两种编号
  2. RPi.GPIO Python,支持Board Pin和BCM GPIO两种编号
  3. Webiopi,Python, 使用BCM GPIO编号
  4. WiringPi-Go, Go语言,支持以上三种编号

编号规范

  1. 第一列是wiringPi API中的缺省编号,wiringPiSetup()采用这列编号
  2. 第二列(Name)往往是转接板的编号
  3. 第三列是树莓派板子上的自然编号(左边引脚为1-15,右边引脚为2-26),RPi.GPIO.setmode(GPIO.BOARD)采用这列编号
  4. 树莓派主芯片提供商Broadcom的编号方法,相当于调用了WiringPiSetupGpio()RPi.GPIO.setmode(GPIO.BCM)采用这列编号
wiringPi PinNameBoard PinBCM GPIO
0GPIO 01117
1GPIO 11218
2GPIO 21321
3GPIO 31522
4GPIO 41623
5GPIO 51824
6GPIO 62225
7GPIO 774
8SDA30
9SCL51
10CE0248
11CE1267
12MOSI1910
13MISO219
14SCLK2311
15TXD814
16RXD1015

Rev.2 新增的引脚:

[Read More]