Tag Archives: 树莓派

软硬兼施

让python和nodejs一起玩耍

做树莓派,为什么还要用nodejs?
没啥特别原因,主要是自己对nodejs+webscoket这套玩的比较熟,而对python的web开发没啥研究
所以,定下这样一个技术方案:python负责硬件驱动和底层逻辑,nodejs负责上面的“好玩”的东西,也就是一般说的“业务逻辑”,web/webscoket服务服务由nodejs提供~
那么问题来了,如何让python和nodejs可以方便的互相调用哪?
按照自己的知识储备,大概有三个方案,按照优先级:
1. 双向的webscoket
2. 单向的thrift,python和nodejs各建一个server端和一个client端,实现双向通信
3. 单向的http,和上面的思路类似,但是速度会比thrift慢一个数量级
webscoket最好用,可以和页面通讯实现极速统一,而且一个连接就可以实现双向通讯,~~但是协议相对复杂,本来想使用nodejs的socket.io,让nodejs做server,python做client,但是试了几种方案都连不上,如果手动实现webscoket通讯协议。。。算了吧,还是下一个吧
第2个方案其实也不顺利,由于对python不熟悉,thrift的库文件导了几次总是失败,拖了几天仔细研究了python的import方式才算成功
这样,整个系统的方案也就是这样

(我的初步打算是做一个能自主行走、壁障,又能用手机网页遥控的机器人,所以硬件部分是直流电机控制芯片、超声测距、红外感应)

下面就来分享一下nodejs与python基于thrift做互联的代码,根据这个方案,对于不熟悉python又想在树莓派上实现较复杂业务逻辑的人,完全可以将nodejs换成自己更熟悉的语言,对于thrift不熟悉的朋友,可以自己学习:http://thrift.apache.org
和一般的thrift的思路不同,为了简化代码逻辑,这里其实类似http接口,action类似url,post一坨键值对,然会返回一坨信息,之所以这么设计,是因为一直觉得thrift太啰嗦,每次添加新接口还要替换定义文件
thrift定义文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 统一的返回值
 */
struct Retu{
    1: i32 code, //返回码
    2: string msg, //返回错误时的详细信息
    3: map<string, string> data//map/字典 类型的返回值
}
 
/**
* 异常信息
*/
exception UException{
    1:i32 errorCode,      //错误码      1=参数错误,2=没有结果 99=系统异常
    2:string errorMessage    //错误描述
}
 
service PiMessService{
	Retu c(1:string action, 2:map<string, string> params) throws (1:UException ex),
}

python端:

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
#coding:utf-8
import sys, glob, os
import event  #这里是自己封装的python事件中心,可以订阅、触发事件,以后有机会单独给大家分享
rootPath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(rootPath + '/lib/gen-py')  #thrift库文件位置
 
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
 
import piMess.PiMessService as PiMessService
import piMess.ttypes as ttypes
 
#####server start#####
class PiMessHandler:
    def __init__(self):
        self.log = {}
    #会被client调用的方法
    def c(self, action, params):
        #收到一次调用,其实是触发一次特定事件,程序其他部分可以去订阅这个事件,返回给出返回值
        rets    = event.trigger('tele_' + action, params)  
        msg     = ''
        data    = {}
        if(len(rets) > 0): #以第一个绑定的返回值为准,后续可以根据实际需求在扩展
            if(type(rets[0]) == type('')):#只使用字符串和字典值
                msg     = rets[0]
            elif(type(rets[0]) == type({})):
                data    = rets[0]
        return ttypes.Retu(200, msg, data)
#启动服务
def serviceStart():
    print 'PiMessService start on 9001 ...'
    handler     = PiMessHandler()
    processor   = PiMessService.Processor(handler)
    transport   = TSocket.TServerSocket(port=9001) #python server端的端口
    tfactory    = TTransport.TBufferedTransportFactory()
    pfactory    = TBinaryProtocol.TBinaryProtocolFactory()
    server      = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
    server.serve()
    print 'done~'
#####server end#######
 
#####client start#####
__transport = TSocket.TSocket('localhost', 9002)   #nodejs server端的端口
__transport = TTransport.TBufferedTransport(__transport)
__protocol  = TBinaryProtocol.TBinaryProtocol(__transport)
__client    = PiMessService.Client(__protocol)
def clientOpen():
    __transport.open()
 
#供外部调用的向nodejs端发送消息的方法
def send(action, params):
    r = __client.c(action, params)
    print('call nodejs:', action, params, r)
    return r
 
def clientClose():
    __transport.close()
#####client end#######
 
if __name__ == "__main__":
    clientOpen()
    send('py-client', {'QQQ': 'ZZZ', 'abc':'PPP'})
    serviceStart()

nodejs端:

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
var thrift          = require('thrift');
var Thrift          = thrift.Thrift;
 
var piMessService   = require('../gen-nodejs/PiMessService'),
  ttypes            = require('../gen-nodejs/piMess_types');
 
 
/***server start***/
var server          = thrift.createServer(piMessService, {
  c: function(action, params, result){  
    //server,可以根据action值,调用对应的外部方法
    console.log('piMessService.c:', action, params);
    retu            = new ttypes.Retu({code:200, msg:'ok', data:null});
    result(null, retu);
  }
}, {});
server.listen(9002);
/***server end*****/
 
/***client start***/
var connection      = thrift.createConnection('localhost', 9001),
  client            = thrift.createClient(piMessService, connection);
 
//对外暴漏一个send方法
exports.send = function(action, params, callback){
  console.log('exports.send:', action, params);
  client.c(action, params, function(err, response){
    if(response){
      console.log('response:', response);
    }
    callback && callback(err, response);
  });
};
 
exports.clientEnd = function(){
  connection.end();
};
/***client end*****/

over
———
转载请注明出处:昆仑的山头

Hello World 软硬兼施

树莓派控制直流电机

使用树莓派控制直流电机,包括控制转速和转向,这里使用的是L293D驱动芯片,简单易用
芯片接线方式如下图所示

L293D可以控制两路直流电机,这里做个简单演示,只控制一路,所以只用到了一侧的管脚
需要树莓派的三个gpio口,分别连L293D的1、2、7
1是使能信号,写死成高电平就行了
2是pwm,“脉冲宽度调制”,也就是宽边宽度的方波,7需要的信号与2相同
2、7的区别是,2或7有pwm信号是,电机转向是相反的
呵呵,偷个懒,继续简化,这里在用到2,将接2的线头改插到7上,电机如果翻转,也就成功了
也就是说,实际演示树莓派只用了2个gpio口~~嘻嘻

然后再看怎么用树莓派产生pwm信号~很简单:

1
2
3
4
5
6
GPIO.setup(gpiox, GPIO.OUT)
p = GPIO.PWM(gpiox, frequency) #创建一个 PWM 实例:
p.start(dc)   # dc 代表占空比(范围:0.0 <= dc >= 100.0)
p.ChangeFrequency(freq)   # freq 为设置的新频率,单位为 Hz
p.ChangeDutyCycle(dc)  # 修改占空比,范围:0.0 <= dc >= 100.0
p.stop() 停止 PWM:

好了,下面是整体的测试代码~~~其实是在一个“呼吸灯”的基础上加了一个使能信号,对应的,看到的是电机加速-减速-加速的过程

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
#coding:utf-8 脉宽调制(PWM)输出
import time
import RPi.GPIO as GPIO
 
def init(pwm, en):
    GPIO.setmode(GPIO.BCM)
    #使能信号
    GPIO.setup(en, GPIO.OUT)
    GPIO.output(en, True)
    GPIO.setup(pwm, GPIO.OUT)
    p = GPIO.PWM(pwm, 50)
    p.start(0)
    try:
        while 1: #周期性修改占空比
            for dc in range(0, 101, 5):
                p.ChangeDutyCycle(dc);
                time.sleep(0.1)
            for dc in range(100, -1, -5):
                p.ChangeDutyCycle(dc);
                time.sleep(0.1)
    except KeyboardInterrupt:
        pass
    p.stop()
    GPIO.cleanup()
 
if __name__ == '__main__':
    init(20, 16)  # gpio20接L293D的2或7管脚,gpio16接1管脚

接线图如下(有点乱,电机的电源是一块12v的电池,没有出现在画面中,另外,呼吸灯还保留着)
另外,电机两极最好并联一个陶瓷电容,转动会更平滑:


————–over
———————-后记
如何用树莓派控制电机,进而控制小车的前进、后退、转向,这个问题困扰了我半个多月
大学四年,唯一值得炫耀的,是大三暑假时参加的电子设计大赛集训,后来还拿了一等奖,也是在那次的比赛作品中,接触到了可变频宽方波控制功率输出的思路,貌似现在满街跑的电动车也是这么控制车速的
这样,树莓派产生这种方波就行了,当时还不知道树莓派有专门的pmw输出,想的还是程序写循环、通过不同的sleep长度、输出方波。。。
然后下一个问题,方波信号是3.3v,怎么控制12v的电机?
开始的想法是继电器,但是继电器这东西,连变频方波,肯定不连续,估计效果会很差
两一个思路是三极管、场效应管放大,拿手头的三极管搭了一个两级的放大电路,不成功,而且第二级管子发热特别厉害~~这个思路肯定是通的,L293D内部也就是这玩意,只是咱水平不够
于是继续搜索解决方案,然后在“极客范”找到了类似功能的文章,不过它讲的貌似是老版的树莓派,产生pmw信号的方法很麻烦;然后最后找到一篇树莓派pmw信号实现呼吸灯的介绍,于是二者结合,有了上面的方案
~~呵呵,作为已有近10年没怎么搞过硬件的人,能搞得这个程度,自己还算满意 :)

Hello World 软硬兼施

使用树莓派驱动超声测距模块

这里用的模块,是SRF04超声测距模块,淘宝上买的,10块钱,加八块运费

测距的时序图如下:

想给触发信号trig一个约10us的高电平,模块会发送测距信号,回响信号echo会输出一个高电平,高电平的次序时间,与检测距离成正比,这就是该模块超声定位的基本原理
先面是相关的pytho代码

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
#srf04.py coding:utf-8 srf04超声测距模块驱动
import RPi.GPIO as GPIO
import time
from time import ctime, sleep
 
__gproTrig = 16    trig
__gproEcho = 12    echo
#初始化输入、输出gpio口
def init():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(__gproTrig, GPIO.OUT)
    GPIO.setup(__gproEcho, GPIO.IN)
 
def measure():
    #确保启动前为低电平
    GPIO.output(__gproTrig, False)
    time.sleep(0.0001)
    #启动测距
    GPIO.output(__gproTrig, True)
    time.sleep(0.00001) #10us
    GPIO.output(__gproTrig, False)
    #开始记时
    start   = 0
    stop    = 0
   #         print("measure--1")
    while start == 0 or stop == 0:
        val     = GPIO.input(__gproEcho)
        if stop == 0 and start == 0 and val == 1:
   #         print("measure--2")
            start = time.time()
        if start > 0 and stop == 0 and val == 0:
      #      print("measure--3")
            stop = time.time()
    #根据驱动公式计算测距结果
    elapsed = stop - start
    distance = (elapsed * 34300)/2
    return distance
 
if __name__ == '__main__':
    init()
    for i in range(300)://五分钟测试
     #   print("srf05-start:", i)
        dis = measure()
        print('--------------dis:', dis)
        sleep(1)

over~
~~~~~~后记
其实这个超声模块已经到手很久了,但是看到驱动要使用10us信号后,就一直纠结怎么来做10us的时钟,测试用python的sleep实现时钟,又特别不精确,那么,就需要100MHz的晶振,可晶振怎么接到树莓派上。。。于是就卡到这里了。
后来偶然搜到了类似模块的驱动方式,才发现其实是我想多了,要什么晶振,sleep足够了~~~嘿嘿,照着这种思路做下去,居然真的成功了
很多事情就是这样:先有一个大概的思路,没有更好地办法的情况下,就先照着这个思路走,大部分时候都能走的过去

——本文为原创,转载请注明出处 昆仑的山头

Hello World 软硬兼施

树莓派GPIO使用练习–点亮LED点阵

这里只是为了练习GPIO的使用,没有加任何的译码设备,也没加任何的放大设备,直接用树莓派的16个gpio引脚,连接一个8*8LED点阵的16个引脚,考虑到树莓派所能提供的电流不能太大,所以统一在点阵的8个阳极各加了一个1kΩ的电阻

8*8电阻的16个引脚,8个阳极,8个阴极,我们可以第一为R1~R8、C1~C8(考虑到点阵是方形的,所以不去区分行列,只区分阴阳,上面的R/C和“阴/阳”的对应关系,请根据实际共阴还是共阳区分清楚)

当我们给R1/C1两个管脚上电时(即在树莓派中,将R1对应的GPIO口拉到高电平,C1拉到低电平),第一行、第一列的这个点就会亮起来,同理,给R3/C5上电,[3, 5]的这个点会连起来

于是,我们可以通过这种方式依次点亮所有的点
。。。

于是,问题来了

如果我想同时点亮[3, 5]/[3, 6]/[4, 6]三个点,就需要同时拉高C3/C4,拉低R5/R6,可是,如果我做了这个操作,[4, 5]这个点也会亮起。。。这怎么办哪?

答案时,依次先点亮[3, 5]/[3, 6],熄灭这两个点,再点亮[4, 6],。。。熄灭[4, 6],点亮[3, 5]/[3, 6],如此往复,当这种切换的频率足够高时,由于认得视觉暂留,看到这三个点就是同时亮起的,也就是说,同一时刻,自会有单独一行、或者一列有点在发光

呵呵,很熟悉吧,电影、大屁股显示器什么的,和这个原理类似

好的,明白了原理,就可以动手写程序吧

这里使用树莓派最主流的编程语言:python,显示的东西,是“Hello word”,左右移动显示

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
import RPi.GPIO as GPIO  #导入必要的python库
import time
 
#Hello World 字模
vhws = [
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,1,1,1,0,0,0,0,0],
]
 
#左移还是右移
isRig = 1
vhwi = 0
#此函数返回当前时间点当前帧需要显示的8*8数组
def hwv():
  global isRig
  global vhwi
  l = len(vhws[0])
  if isRig &gt; 0:
    vhwi += 1
  else:
    vhwi -= 1
  if vhwi &gt;= (l - 8):
    isRig = 0
  if vhwi  0):
    if timeLine % 10 == 0:
      nowV = hwv()
    #双层循环实现的扫描显示  
    for i in range(0, len(nowV)):
      allOver()
      #拉低所有阴极
      GPIO.output(rs[i], GPIO.LOW)
      for j in range(0, len(nowV[i])):
        if nowV[i][j] &gt; 0:
          GPIO.output(cs[j], GPIO.HIGH) #点亮这个点
        else:
          GPIO.output(cs[j], GPIO.LOW)  #不点亮
        time.sleep(sleepTime)
    timeLine = timeLine - 1
  return
#ledtest 17 27 22 5 6 13 19 26 18 23 24 25 12 16 20 21
#这里是管脚对应关系
rs = [21, 20, 16, 12, 25, 24, 23, 18] #R1:13 R2:3 R3:4 R4:10 R5:6 R6:11 R7:15 R8:16
cs = [26, 19, 13, 6, 5, 22, 27, 17] #C1:5 C2:2 C3:7 C4:1 C5:12 C6:8 C7:14 C8:9
#开始
def startVs():
  GPIO.setmode(GPIO.BCM)
  for i in xrange(8):
    GPIO.setup(cs[i], GPIO.OUT)
    GPIO.setup(rs[i], GPIO.OUT)
  allOver()
  return
#关闭
def stopVs():
  GPIO.cleanup()
  return
#将所有管脚置为反响,即熄灭所有
def allOver():
  for i in xrange(8):
    GPIO.output(cs[i], GPIO.LOW)
    GPIO.output(rs[i], GPIO.HIGH)
  return
 
#timeLine = 1000
frames = 60
#显示一个8*8数组
def viewArr():
  timeLine = 1000
  sleepTime = 0.0001 #1 / (frames * 8)
  while(timeLine > 0):
    if timeLine % 10 == 0:
      nowV = hwv()
    for i in range(0, len(nowV)):
      allOver()#每次点亮前,先熄灭所有
      GPIO.output(rs[i], GPIO.LOW)
      for j in range(0, len(nowV[i])):
        if nowV[i][j] &gt; 0:
          GPIO.output(cs[j], GPIO.HIGH)  #点亮
        else:
          GPIO.output(cs[j], GPIO.LOW)   #不点亮
        time.sleep(sleepTime)  #扫屏的时延
  timeLine = timeLine - 1   #显示时常控制
return
 
if __name__ == '__main__':
  startVs()
  viewArr()
  stopVs()

下面是实际效果的几个截图:

下面两个是线路连接:

转载请注明出处:昆仑的山头