0x01 前言
红外遥控是一种无线、非接触控制技术,在工业控制、航空航天、家电等领域都得到了广泛应用。大部分的电视的遥控器,空调遥控器就都是红外遥控。在baidu上检索了树莓派小车的各种控制方案,没有找到红外遥控的控制方案。所以本文尝试使用红外控制方案对树莓派小车进行控制。
0x02 所需材料
1. 树莓派小车。(树莓派小车的安装不是本文重点,如果读者不熟悉小车的安装,请自行搜索。)
2. 红外接收头
型号:VS1838B(价格很便宜,几毛钱一个。)
3. 家里废旧的红外遥控器
0x03 红外通讯协议学习
遥控器的基带通信协议很多,大概有几十种,用的最多的就是 NEC 协议了,下面开始了解下NEC协议。
图 1 NEC 协议数据格式
NEC 协议的数据格式包括了引导码、用户码、用户码(或者用户码反码)、按键键码和键码反码,最后一个停止位。停止位主要起隔离作用,一般不进行判断,编程时我们也不予理会。其中数据编码总共是 4 个字节 32 位。第一个字节是用户码,第二个字节可能也是用户码,或者是用户码的反码,具体由生产商决定,第三个字节就是当前按键的键数据码,而第四个字节是键数据码的反码,可用于对数据的纠错。
注意: NEC 协议中的每一位数据本身也需要进行编码,编码后再进行载波调制。
引导码:9ms 的载波 4.5ms 的空闲。 比特值“0”:560us 的载波 560us 的空闲。 比特值“1”:560us 的载波 1.68ms 的空闲。
红外接收头,当收到有载波的信号的时候,会输出一个低电平,空闲的时候会输出高电平,我们用逻辑分析仪抓出来一个红外按键通过解码后的图形来了解一下,如图 2 所示。
图 2 红外遥控器按键编码
从图上可以看出,先是 9ms 载波加 4.5ms 空闲的起始码,数据码是低位在前,高位在后,数据码第一个字节是 8 组 560us 的载波加 560us 的空闲,也就是 0x00,第二个字节是 8 组 560us的载波加 1.68ms 的空闲,可以看出来是 0xFF,这两个字节就是用户码和用户码的反码。按键的键码二进制是 0x0C,反码就是 0xF3,最后跟了一个 560us 载波停止位。对于我们的遥控器来说,不同的按键,就是键码和键码反码的区分,用户码是一样的。这样我们就可以通过单片机的程序,把当前的按键的键码给解析出来。
额外提一句:空调的遥控器比较特殊,用户码和键盘均存储数据。
0x04 监听红外遥控
VS1838B的3个管脚分别为OUT、GND、VCC。当收到有载波的信号的时候,OUT管脚会输出一个低电平,空闲的时候会输出高电平。
本次将树莓派的18引脚作为红外接收引脚。
监听红外的机能代码如下:
代码语言:javascript复制#define IR_INPUT_PIN (18)
#define ERROR (0xFE)
// 请事先破获遥控器的各个键的按键码,然后与小车的前、后、左、右、停的行为相对应。// 以下5个值需要与遥控器的按键码匹配。#define IR_CONTROL_KEY_UP (0x01)#define IR_CONTROL_KEY_DOWN (0x02)#define IR_CONTROL_KEY_LEFT (0x03)#define IR_CONTROL_KEY_RIGHT (0x04)#define IR_CONTROL_KEY_STOP (0x05)
// 信号量sem_t g_edge_falling, g_edge_rising;
void setup();void edgeFalling();void edgeRising();void GPIO_wait_for_edge(int event);
bool IRStart();unsigned char getByte();unsigned char getKey();
void setup(){ if(-1==wiringPiSetup()) { std::cerr<<"wiringPi setup error"<<std::endl; exit(-1); }
pinMode(IR_INPUT_PIN,INPUT); //配置引脚为输入 pullUpDnControl(IR_INPUT_PIN,PUD_UP); //引脚上拉到3.3v
//注册中断处理函数 if(0>wiringPiISR(IR_INPUT_PIN,INT_EDGE_FALLING,edgeFalling)) { std::cerr<<"interrupt function[INT_EDGE_FALLING] register failure"<<std::endl; exit(-1); }
if(0>wiringPiISR(IR_INPUT_PIN,INT_EDGE_RISING,edgeRising)) { std::cerr<<"interrupt function[INT_EDGE_RISING] register failure"<<std::endl; exit(-1); }}
void edgeFalling() { sem_post(&g_edge_falling);}
void edgeRising() { sem_post(&g_edge_rising);}
void GPIO_wait_for_edge(int event){
if(INT_EDGE_FALLING==event){ sem_init(&g_edge_falling, 0, 0); sem_wait(&g_edge_falling); } else if(INT_EDGE_RISING==event) { sem_init(&g_edge_rising, 0, 0); sem_wait(&g_edge_rising); }}
bool IRStart() { time_t timeFallingEdge[] = {0,0}; time_t timeRisingEdge = 0; time_t timeSpan[] = {0, 0};
//GPIO.wait_for_edge(PIN, GPIO.FALLING); GPIO_wait_for_edge(INT_EDGE_FALLING); timeFallingEdge[0] = time(NULL);
//GPIO.wait_for_edge(PIN, GPIO.RISING); GPIO_wait_for_edge(INT_EDGE_RISING); timeRisingEdge = time(NULL);
//GPIO.wait_for_edge(PIN, GPIO.FALLING); GPIO_wait_for_edge(INT_EDGE_FALLING);
timeFallingEdge[1] = time(NULL); timeSpan[0] = timeRisingEdge - timeFallingEdge[0]; timeSpan[1] = timeFallingEdge[1] - timeRisingEdge; if (timeSpan[0] > 0.0085 && timeSpan[0] < 0.0095 && timeSpan[1] > 0.004 && timeSpan[1] < 0.005){ return true; } else { return false; }}
unsigned char getByte() { unsigned char byte = 0; time_t timeRisingEdge = 0; time_t timeFallingEdge = 0; time_t timeSpan = 0; for(int i =0; i<8; i) { //GPIO.wait_for_edge(PIN, GPIO.RISING); GPIO_wait_for_edge(INT_EDGE_RISING); timeRisingEdge = time(NULL);
//GPIO.wait_for_edge(PIN, GPIO.FALLING); GPIO_wait_for_edge(INT_EDGE_FALLING); timeFallingEdge = time(NULL); timeSpan = timeFallingEdge - timeRisingEdge; if(timeSpan > 0.0016 && timeSpan < 0.0018) { byte |= 1 << i; } }
return byte;}
unsigned char getKey() { unsigned char byte[] = {0x00,0x00,0x00,0x00};
if(false==IRStart()) { sleep(0.11); // One message frame lasts 108 ms. return ERROR; } else { for( int i=0;i<4; i) { byte[i] = getByte(); }
if((byte[0] byte[1] == 0xff)&&(byte[2] byte[3] == 0xff)) { return byte[2]; } else { return ERROR; } }}
void *listenIRThread(void *arg) {
printf("IRM Test Start ...n");
setup();
while(true) { unsigned char key = getKey(); if(key != ERROR) { printf("Get the key: 0xxn",key);
switch(key) { case IR_CONTROL_KEY_UP: { // 前进 std::cout << "command: CARRUN FORWARD"<< std::endl;
DirectionReq *req = new DirectionReq(); req->setValue(DIRECTION_FORWARD); ControlManager::instance()->postActionReq(req);
break; }
case IR_CONTROL_KEY_DOWN: { // 后退 std::cout << "command: CARRUN BACK"<< std::endl;
DirectionReq *req = new DirectionReq(); req->setValue(DIRECTION_BACK); ControlManager::instance()->postActionReq(req);
break; }
case IR_CONTROL_KEY_LEFT: { // 左转 std::cout << "command: CARRUN LEFT"<< std::endl;
DirectionReq *req = new DirectionReq(); req->setValue(DIRECTION_LEFT); ControlManager::instance()->postActionReq(req);
break; }
case IR_CONTROL_KEY_RIGHT: { // 右转 std::cout << "command: CARRUN RIGHT"<< std::endl;
DirectionReq *req = new DirectionReq(); req->setValue(DIRECTION_RIGHT); ControlManager::instance()->postActionReq(req);
break; }
case IR_CONTROL_KEY_STOP: { // 停车 std::cout << "command: CARRUN STOP"<< std::endl;
StatusReq *req = new StatusReq(); ControlManager::instance()->postStatusReq(req);
break; }
default: break; } }
}}
0x05 控制小车动作
本次使用python语言提供的接口来控制小车的动作:前进、后退、左转、右转、停止。
用python控制小车动作的代码如下:
代码语言:javascript复制 #!/usr/bin/Python# -*- coding: UTF-8 -*-
#引入gpio的模块import RPi.GPIO as GPIOimport time
#设置in1到in4接口IN1 = 12IN2 = 16IN3 = 18IN4 = 22
#初始化接口def car_init(): #设置GPIO模式 GPIO.setmode(GPIO.BOARD)
GPIO.setup(IN1,GPIO.OUT) GPIO.setup(IN2,GPIO.OUT) GPIO.setup(IN3,GPIO.OUT) GPIO.setup(IN4,GPIO.OUT)
#前进的代码def car_forward(): GPIO.output(IN1,GPIO.HIGH) GPIO.output(IN2,GPIO.LOW) GPIO.output(IN3,GPIO.HIGH) GPIO.output(IN4,GPIO.LOW) time.sleep(0.15) GPIO.cleanup()
#后退def car_back(): GPIO.output(IN1,GPIO.LOW) GPIO.output(IN2,GPIO.HIGH) GPIO.output(IN3,GPIO.LOW) GPIO.output(IN4,GPIO.HIGH) time.sleep(0.15) GPIO.cleanup()
#左转def car_left(): GPIO.output(IN1,False) GPIO.output(IN2,False) GPIO.output(IN3,GPIO.HIGH) GPIO.output(IN4,GPIO.LOW) time.sleep(0.15) GPIO.cleanup()
#右转def car_right(): GPIO.output(IN1,GPIO.HIGH) GPIO.output(IN2,GPIO.LOW) GPIO.output(IN3,False) GPIO.output(IN4,False) time.sleep(0.15) GPIO.cleanup()
#停止def car_stop(): GPIO.output(IN1,GPIO.LOW) GPIO.output(IN2,GPIO.LOW) GPIO.output(IN3,GPIO.LOW) GPIO.output(IN4,GPIO.LOW) GPIO.cleanup()
0x06 结束
到此红外遥控器控制树莓派小车小车的控制系统就介绍完了。
本文重点讲解的是红外协议的解析部分,而小车的控制策略部分没有详细讲解,读者可以参考我之前写的几篇小车相关的文章,进行了解。
最后,整套代码已经发到了百度网盘上。
链接: https://pan.baidu.com/s/1vHUFd8Uui17EIlkehNClrw 提取码: q49