Category Archives: 软硬兼施

Hello World 软硬兼施

Arduino如何多任务

小搜了一下,Arduino没有简单的多线程/多任务的解决方案
换个方向想想,也对,Arduino本身那点存储空间,就是个微控制器,专心能控制好一个东西就行了,还搞神马多任务
不过,考虑到代码的整齐度,简单的模块划分还是需要的,比如,需要同时分别控制指示灯和舵机时,你不能把所有的代码都放在Loop函数里吧
借鉴以前用js做动画里的实现方式

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
#include <Servo.h>
Servo duoji1servo;
 
const int pinLed1   = 12;   //连接红色led
const int pinLed2   = 13;   //连接蓝色led
const int pinDuoji  = 9;    //连接舵机控制端口
 
void setup() {
  pinMode(pinLed1, OUTPUT);
  pinMode(pinLed2, OUTPUT);
  duoji1servo.attach(pinDuoji);
}
 
long _ti = 0;  //控制参数累加
void loop() {
  _ti++;
  ledTest(_ti);
  duojiTest(_ti);
  delay(1);    //1毫秒
}
 
//led闪动测试
int ledi = 0;
void ledTest(long ti){
  ledi++;
  if(ledi == 512){
      ledi = 0;
  }
  int n = ledi;
  if(ledi > 255){
    n = 512 - ledi;
  }
  analogWrite(pinLed1, n);   //分别控制LED亮度
  analogWrite(pinLed2, (255 - n));
}
 
//舵机测试
int duojii = 0;
void duojiTest(long ti){
  if(ti % 10 == 0){//调用间隔,10毫秒计算一次
    duojii++;
    if(duojii > 360){
      duojii = 1;
    }
    if(duojii <= 180){  //正传
      duoji1servo.write(duojii);
    }else{              //反转
      duoji1servo.write(360 - duojii);
    }
  }
}
Hello World 软硬兼施

Arduino的指令时间研究

习惯了服务器端编程的思路,玩硬件也想把时间搞得清楚一些
简单研究Arduino一个指令所需的时钟周期,板子是某宝上最常见的Arduino UNO R3,先上代码:

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
const int LED1  = 13;               //红色LED
const int LED2  = 12;               //蓝色LED
int loopin      = 0;
 
void setup() {
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
}
 
void loop() {
  loopin++;
  if(loopin % 2 == 1){              //点亮红色LED
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, LOW);
    delay(1000);                    //持续1秒
  }else{                            //点亮红色LED
    digitalWrite(LED2, HIGH);
    digitalWrite(LED1, LOW);
    delay(1000);                    //持续1秒
    //******************************关键看这里,这个for循环的时间,约等于delay(1000),此时双色同时亮起
    for(long i = 0; i < 160000; i++){
      digitalWrite(LED1, i % 2 == 0 ? LOW : HIGH);
      digitalWrite(LED2, i % 2 == 1 ? LOW : HIGH);
    }
  }
}

就这么多~~
整体上,用一个超长的for循环模拟出一个1秒的delay,然后观察同时亮起的时间,是否等于单独亮起的时间
实际测算,到16万时,同时亮起的时间,基本等于另外两个亮起的时间
(这里只是估算,有时间的话,也可以算的更精确一点:对着计数器数秒,看闪动的次数是否和秒数一直,不一致的话,就调整这个i得最大值)
也就是说,这里的一个for循环,Arduino的大约需要 1/160 毫秒,或者说0.16M,简答分析这个for循环做了哪些事情
1. i+1,= i, i与最大值比较,大约3次
2. i%2,== 0 ?, 高电平或低电平输出,大约3次
3. 与上一步基本一致
加起来大约10个指令?
Arduino UNO R3的晶振频率是16M,也就是说,大约,晶振的10个周期,可以执行一个程序指令
另一个方面,UNO的处理器核心是ATmega328,8位处理器,i是long型32位,上面的每次运算,cpu至少要算四次(具体8位处理器怎么捣腾相加两个32位的数,记不清了),按这种方式算的话,其实也差不多
—-over
转载请注明出处: 昆仑的山头

3D打印 日志 软硬兼施

将虚拟照进现实—-3D打印初体验

小时候一直梦想有一支神秘马良的笔,后来才懂得,神笔只存在于童话中,想把大脑构思千百遍的东西做出来,只能靠自己的双手和合适的工具
于是开始发掘各种各样的工具:从剪刀、各种改锥,到烙铁、各种钳子扳手,再到电锯、电钻
相信每一个男孩子都或多或少的接触过这些工具,并且用它们来编制自己的梦想

用捡来的小马达、铁皮罐子剪出来的铁皮,做成小风扇
用撑温室大棚用的竹条、废旧的木条,做成弩弓
用拆开的自行车链条、车条、剪成条的内胎皮条,做成的火柴枪
自己在纸箱上画一个表盘,然后配上捡来的机芯、表针,重新拼成挂钟~~初中时做的,一直到我上大学,老妈都拿这个钟看时间

后来,开始接触电脑里的各种建模工具:flash、cad、3dmax,建模、然后几行代码的脚本生成动画
再后来,工作了,偶尔需要做些小游戏,和设计师一起,用更复杂的代码,完成更炫的动画

然而,电脑里建出模型和上面动手做的东西,却有一个不可逾越的鸿沟:虚拟与现实
即便时下流行的AR、VR,也只是把虚拟做的更具现实感
你不可能拿pc软件画出来的杯子喝水,更不可能用画出的大饼来填饱你的肚皮

但是,随着一件事物的出现,这一切似乎有了转机,这就是3D打印
简单来说,3D打印就是可以把虚拟建模出来的东西转变成现实的工具
—-对,它可以部分的完成神笔的功能~~虽然这种能力还很弱

以前的3D打印机价格都挺高,最少的也要三五千,所以限于囊中羞涩,一直没有出手购买
直到偶然在淘宝发现micromake“三角洲”3D打印机,相对于普通的方盒子机,这种打印机结构更简单,成本也更低,淘宝售价低配版¥999,虽然精度较低,但是做入门用也够了
到手的就是上图的这堆散件,把这些东西拼装起来,大约用了我一个下午的时间
现成的各类小玩意的3D模型并不难取得,搜一下一大堆
使用工具软件“cura”来连接打印机,导入“.stl”3d模型文件,直接打印,或者生成“.gcode”文件,放在sd卡再打印
使用3dmax建模,可以导出成stl文件,即可再用curl来完成后续的打印动作
整个过程很简单,但其中也有不少的坑:

1. 提前准备“704硅橡胶”(淘宝买,或者让打印机卖家送,五金店什么的很难找的,更不要用牙膏什么的替代…),新手使用不熟练,喷头、喉管的连接位置都很容易堵,这时就需要拆开清理,然后再组装时就需要特别注意:照教程吐足够的硅橡胶、两个螺口都要拧好晾干以后再做下一步的组装动作,否则打印时会漏的一塌糊涂。。。别问我怎么知道的,这个过程我已经做过五次了,最近一次才算弄利索
2. 3dmax和cura配合,长度单位协调有问题,都是“毫米”单位,3dmax到处的stl模型,导入到cura里,需要放大“25.4”倍~~~对,1英寸=25.4毫米,应该是3dmax到处时单位搞混了
3. 昨天刚刚发现的问题,建好的模型,或者下载的模型到了cura里,打印出可能会有问题,空心的东西莫名其妙会被封口,所以打印前一定要检查Layers分层
4. 打印机校准一次、校准对就行了,不用重复校准
5. 要有耐心,打印速度不要太快,30足够了,否则打印出来会特别粗糙~~这种速度,打印一个杯子预计需要五个小时以上
6. 打印材料方面,同样价钱、同样卖家的PLA材料,白色的最好用,红色的其次,黑色的最差
7. 建模方面,有个45度原则,就是倾斜的部分,不要超过45度,否则就需要加支架
8. 最后一点。。。不要抱太大希望,真的只是入门而已:打印出的东西很粗糙,表面是水波纹的,精度也就0.5mm

ok,基本就这么多,这个打印机做“神笔”确实有些吃力,也就是入门,打印一些精度不需要太高的东西,或者,搁家里做摆设也不错



↓↓–2016年05月28日 新加–↓↓
之前一直在试着用3dmax做建模工具,除了上面的单位问题,发现3dmax建模最大的问题是,稍微复杂一点的模型,打出来就会乱,变形、或分层错误
无奈,只能使用更专业的工具,solidWorks
最近一周在学solidWorks,很棒的工具

软硬兼施

让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年没怎么搞过硬件的人,能搞得这个程度,自己还算满意 :)