Linkit 系列博文:
联发科Linkit 7688 (一) 上手及在Mac下搭建OpenWrt交叉编译环境,C语言编译Hello,World
联发科Linkit 7688 (二)GPIO基本操作与C语言编程
联发科Linkit 7688 DUO(三): 通过 Arduino 控制外设和传感器
Linkit 7688 DUO(四): 接上各种Arduino传感器和模块——基础篇
Linkit 7688 DUO(五) 接上各种Arduino传感器和模块—扩展篇
Linkit 7688 DUO(六) 加入MQTT物联网协议
前一篇讲了 Linkit 7688DUO操作Arduino的原理和基本方法。现在,我们要为开发板接上各类Arduino的传感器和模块了,这些模块提供了各类输入输出。
一、首先要充分了解 Linkit 7688 DUO开发板的引出管脚
Linkit 7688 DUO开发板上有两个处理器芯片。
一片是 Linkit 7688, 主处理器
一片是 ATmega32U4, 这是Arduino的处理芯片,提供Arduino编程接口,用于控制传感器外设等
两个处理器通过内部串口相连。
在开发中, 要写两个程序:
1, 写一个Arduino程序, 写入ATmega32U4中。Arduino程序通过串口接收主处理器Linkit 7688送来的命令, 执行相应动作。
2, 写一个主程序,写入Linkit7688中。 主程序通过串口向 ATmega32U4 发送命令
开发板有两排管脚,查了管脚说明书。我做了一个简图如下:
图中标识的 从ATmega32U4管脚接出的众多管脚,其中:D0-D13 为数字IO口, A0-A5为模拟IO口, S0-S3为串口设备SPI管脚
还有几个电源口: GND(地), 3.3V, 5V
二、获得Arduino的传感器模块
淘了一套 科易(Keyes)的Arduino传感器套装,37种传感器,70元。便便宜宜,够玩一下了。
为了把传感器模块接到开发板上,还要买一把杜邦线。
本篇选主要的几种传感器,连接Linkit 7688 DUO开发板, 练习如何连线,如何控制输入输出
三、双色LED灯 ( TWO-COLOR), 见下图,这种LED能显示切换两种颜色
模块有三个管脚,其中 (图中左侧)标注‘-’的管脚接地(GND),(图中右侧)标注"S"的管脚接信号(Signal), 即接在数字IO口上。 中间的管脚接3.3V
用杜邦线把模块三个脚分别接到开发板上,其中把信号线接到 D8 口. 这时LED灯已经亮起,为橙色。
编一个Arduino程序,用来控制D8脚的LED灯, 编译上传到ATmegaU32
int pin = 8; //pin connected void setup() { Serial1.begin(57600); //internal serial to MT7688 pinMode(pin, OUTPUT); //set pin output mode } void loop() { int c = Serial1.read(); // read from MT7688 if (c != -1) { switch(c) { case '0': // turn off when receiving "0" digitalWrite(pin, 0); break; case '1': // turn on when receiving "1" digitalWrite(pin, 1); break; } } }
#include <unistd.h> #include "serial.h" int main() { int fd; char c; fd = serial_open(0, 57600);//open serial port 0 : /etc/ttyS0 if ( fd > 0 ) { while (1) { c = '0'; serial_send(fd, &c, 1); //send '0' sleep(1); c = '1'; serial_send(fd, &c, 1); //send '1' sleep(1); } serial_close(fd); } }
SSH进入开发板,执行 serial_test, 则可以看到,双色LED灯每隔一秒换一种颜色(橙-->绿-->橙...)
这个程序是死循环,按 CTRL+C 可中断执行
四、三色LED灯 ( RGB LED ), 见下图,通过设置R, G, B三种颜色值,可以使这种LED显示任意颜色
模块有四个管脚,其中 (图中右侧)标注‘-’的管脚接地(GND), 标注"R", "G", "B"的三个管脚要分别接到三个IO管脚上
用四根杜邦线把模块接到开发板上,其中“-”脚接GND, RGB三个管脚分别接 D3, D5, D6口
注: ATmega32U4芯片的 3、5、6、9、10、11、13管脚 能使用 Arduino 中的 analogWrite()函数支持8位的PWM输出, PWM管脚可用于输出0-255的不同亮度值
在双色LED例子中,Linkit 7688一次送一个字节的命令给ATmegaU32就可以了,串口通信比较简单。
在本例中,为了显示RGB颜色, Linkit 7688要一次送三个字节的命令给ATmegaU32,以后的例子中,可能要发送不同长度的各种命令给ATmegaU32.
因此, 需要设计一个ATmegaU32 与 Linkit 7688间的通用的串口通信协议, 用于传送比较复杂的命令.
这里提供一个工业用的串口通信 消息协议, 可以实现双向通信, 协议定义如下:
1, 进行串口通信的两个设备间可以互发消息数据包,简称消息。
2,每个消息是一串连续的字节流,包含:开始字节、命令字节,长度字节、内容、检验字节等
3, 消息数据的格式如下:
开始字节(1 byte) + 命令字节(1 byte) + 内容长度( 1 byte ) + 内容(长度可变) + 校验字节(1字节)
其中:
开始字节,固定取值为 0x03
命令字节,由用户自定义各种命令,共256种。 其中:COMMAND_ACK (正确响应) , COMMAND_NAK (不正确响应) 是本协议预定义的两种响应命令
内容长度字节,指其后面跟随的内容的长度, 可以为0
内容, 长度可变的一串字节
校验字节,用于校验前面传的数据是否正确,采用BCC算法,取值为从开始字符到命令内容的异或和。
4,消息响应
接收消息的一方收到消息后,如执行正确应回复一个ACK消息。否则应回复一个NAK消息。以便另一方了解消息是否正确。
ACK消息以 COMMAND_ACK 为命令字节(COMMAND_ACK=1),内容长度为1, 内容为上条收到消息的命令字节。
NAK消息以 COMMAND_NAK 为命令字节(COMMAND_NAK=0),内容长度为1, 内容为上条收到消息的命令字节。
然后,在Arduino 和 Linkit 7688主控程序中分别实现这个通信协议。
第1步,编一个Arduino程序,实现消息协议
/************************************************** * Message protocol over serial communication * between Arduino and Linkit 7688 * * message format: * START_BYTE(1 byte) + COMMAND(1 byte) + LENGTH(1 byte) + DATA + CHECKSUM(1 byte) **************************************************/ const unsigned char COMMAND_ACK = 1; //ACK: command received and processed const unsigned char COMMAND_NAK = 0; //NAK: command not processed unsigned char message[258]; //message of serial communication static const unsigned char START_BYTE = 0x03; //start byte of message static int delay_between_bytes = 1; //delay between bytes /* message initialization */ void MessageInit(long baudRate) { Serial1.begin(baudRate); //init Serial1 which connect to Linkit 7688 delay_between_bytes = 1500 / ( baudRate / 8 ); if (delay_between_bytes <= 0) delay_between_bytes = 1; } /* calc checksum using BCC algorithm */ static unsigned char bcc_checksum(unsigned char *buf, int len, unsigned char initial) { int i; unsigned char checksum = initial; for(i = 0; i < len; i++) checksum ^= *buf++; return checksum; } //read byte: return 1 if read success, return 0 if fail static int MessageReadByte(int *byte) { int c, times; times = 3; c = Serial1.read(); // trying: read one byte from MT7688 while ( c == -1 && times-- > 0 ) { delay(delay_between_bytes); } if ( c == -1 ) { return 0; } else { *byte = c; delay(delay_between_bytes); return 1; } } /* receive message: return 1 if message received OK, else return 0 */ int MessageReceive() { int c, n, len; if ( ! MessageReadByte( &c ) ) return 0; if ( c == START_BYTE ) { n = 0; message[ n++ ] = c; //start byte if ( ! MessageReadByte( &c ) ) return 0; // command byte message[ n++ ] = c; if ( ! MessageReadByte( &c ) ) return 0; // length byte message[ n++ ] = c; len = c; while ( len > 0 ) { // read data bytes if ( ! MessageReadByte( &c ) ) return 0; message[ n++ ] = c; len--; } if ( ! MessageReadByte( &c ) ) return 0; // checksum byte message[ n ] = c; //verify checksum if ( message[n] == bcc_checksum(message, n, 0) ) return 1; else return 0; } return 0; } /* send message: return 1 if success, return 0 if fail */ int MessageSend(unsigned char command, unsigned char *data, unsigned char len) { unsigned char buf[3]; unsigned char checksum; int ret; buf[0] = START_BYTE; buf[1] = command; buf[2] = len; checksum = bcc_checksum(buf, 3, 0); if ( len > 0 ) checksum = bcc_checksum(data, len, checksum); if ( 3 == Serial1.write(buf, 3) ) { if ( len > 0 ) { if ( len != Serial1.write( data, len ) ) return 0; } ret = Serial1.write(checksum); return ret == 1; } return 0; } /* send COMMAND_ACK of specified command */ int MessageACK(unsigned char command) { return MessageSend( COMMAND_ACK, &command, 1 ); } /* send COMMAND_NAK of specified command */ int MessageNAK(unsigned char command) { return MessageSend( COMMAND_NAK, &command, 1 ); } /* return command of message */ unsigned char MessageCommand() { return message[1]; } /* return length of message data */ unsigned char MessageDataLength() { return message[2]; } /* return value of specified index of message data */ unsigned char MessageData(int index) { return message[ 3 + index ]; } /******** End of Message Protocol **************/
unsigned char message[258]; 是一个字符数组,用于接收消息的。
void MessageInit(long baudRate);初始化函数,必须且只需调用一次
int MessageReceive() ; 接收消息函数,如果接收成功返回1, 此时接收到的数据在message[]中。 如果接收不成功,返回0
这个函数会先尝试接收一个字节,如果接收到 开始字节,则立即接收后续内容。如果字节间超时,则中断接收。
int MessageSend(unsigned char command, unsigned char *data, unsigned char len) ;发送消息函数,command是命令,data是内容
int MessageACK(unsigned char command); 发送ACK消息的函数
unsigned char MessageCommand(); 返回刚才接收到的消息中的命令字节
unsigned char MessageData(int index); 返回刚才接收到的消息内容 的第index字节
我把上述程序存入一个文件 message_protocol.ino 中(以便重用)。
把 message_protocol.ino 这个文件放在Arduino的任何一个项目文件夹中,然后重新打开项目, 则该项目将自动包含message_protocol.ino模块及上述函数。
第2步,编写一个Arduino主程序,接收消息,控制RGB 三色 LED灯。 把以下Arduino程序编译上传到ATmegaU32
/************************************************** * main program **************************************************/ const char COMMAND_RGB = 'r'; int pinRed = 3; //PIN 3 connect to Red int pinGreen = 5; //PIN 5 connect to Red int pinBlue = 6; //PIN 6 connect to Red void setup() { MessageInit(57600); //init message protocol, set baud rate //set pins mode to OUPTPU pinMode( pinRed, OUTPUT ); pinMode( pinGreen, OUTPUT ); pinMode( pinBlue, OUTPUT ); } void setLedColor(int red, int green, int blue) { analogWrite( pinRed, red ); analogWrite( pinGreen, green ); analogWrite( pinBlue, blue ); } void loop() { if ( MessageReceive() ) { //if message received switch( MessageCommand() ) { //get command of message case COMMAND_RGB: //if command is COMMAND_RGB setLedColor( MessageData(0), MessageData(1), MessageData(2) ); //set LED color MessageACK( COMMAND_RGB ); //send ACK message back break; } } delay(1); //wait 1 milli-second }
第3步 编一个C语言模块 message_protocol.c ,实现消息协议。具体我就不详细说了,可以下载代码看一下。
模块共两个文件: message_protocol.c ,message_protocol.h, 其中包含的函数与Arduino的函数基本相同,但调用方式和函数命名方式略有不同。(见以下例程)
第4步 编一个C语言主程序 rgb_led.c, 向ATmegaU32发送消息, 将C程序交叉编译后生成rgb_led,用scp命令上传到开发板
项目中要使用到 串口函数模块serial.c 和 消息协议模块 message_protocol.c
#include <stdio.h> #include <unistd.h> #include <memory.h> #include "serial.h" #include "message_protocol.h" #define COMMAND_RGB 'r' //send COMMAND_RGB message to Arduino int send_rgb_message(message_t * msg, int r, int g, int b) { unsigned char buf[3]; buf[0] = r; buf[1] = g; buf[2] = b; return message_send(msg, COMMAND_RGB, buf, 3); } //wait for ACK message from Arduino int wait_ack_message(message_t * msg) { int times, ret; times = 0; while ( (ret = message_receive(msg)) != 1 && times++ < 30) { usleep(1*1000); } if ( ret == 1 ) { if ( message_command(msg) == COMMAND_ACK ) { printf("receive ACK\n"); fflush(stdout); } } } int main(int argc, char **argv) { int fd; //file descriptor of serial port message_t msg; //message object fd = serial_open(0, 57600);//open serial port 0, /etc/ttyS0 if ( fd > 0 ) { message_init( &msg, fd, 57600 ); //init message with fd, set baud rate send_rgb_message( &msg, 255, 0, 0) ; //set LED color RED wait_ack_message( &msg ); //wait for ACK message sleep(1); send_rgb_message( &msg, 0, 255, 0) ; //set LED color GREEN wait_ack_message( &msg ); //wait for ACK message sleep(1); send_rgb_message( &msg, 0, 0, 255) ; //set LED color BLUE wait_ack_message( &msg ); //wait for ACK message sleep(1); send_rgb_message( &msg, 255, 255, 0) ; //set LED color YELLOW wait_ack_message( &msg ); //wait for ACK message sleep(1); send_rgb_message( &msg, 255, 0, 255) ; //set LED color PURPLE wait_ack_message( &msg ); //wait for ACK message sleep(1); send_rgb_message( &msg, 0xFF, 0xC0, 0xCB) ; //set LED color PINK wait_ack_message( &msg ); //wait for ACK message sleep(1); send_rgb_message( &msg, 0, 0, 0) ; //set LED turn off wait_ack_message( &msg ); //wait for ACK message serial_close(fd); } return 0; }
SSH进入开发板,执行 rgb_led, 则可以看到,三色LED灯每隔一秒换一种颜色(红-->绿-->蓝->黄->紫色->粉红..熄灯)
五、接键开关(Switch), 见下图,
模块有三个管脚,其中 (图中右侧)标注‘-’的管脚接地(GND), 右侧标注"S”的管脚接信号(数字I/O) , 中间的管脚接 5V
用三根杜邦线把模块接到开发板上,其中“-”脚接GND, S脚接 D3, 中间脚接5V
编一个Arduino程序,监控开关状态,如有状态变化,通过串口发送消息到到主控板。 编译Arduino程序上传到ATmegaU32
注:程序将使用message_protocol.ino模块,要把 message_protocol.ino 这个文件放在本Arduino项目文件夹中, 然后重新打开项目即可包含此模块
#define COMMAND_SWITCH 's' int pinSwitch = 3; // digital pin 3 has a pushbutton attached to it int lastValue = 1; // last value of pin void setup() { MessageInit( 57600 ); // init message < need message_protocol.ino > pinMode(pinSwitch, INPUT); //set pin as INPUT lastValue = digitalRead(pinSwitch); //get last value } void loop() { int value = digitalRead(pinSwitch); // read the input pin: if ( value != lastValue ) { //if value changed unsigned char c = value & 0xFF; //change to unsigned char MessageSend( COMMAND_SWITCH, &c, 1); //send message lastValue = value; //store last value } delay(10); // delay in between reads for stability }
然后,编一个C语言主程序 switch_test.c,监听串口有否消息送到,打印出按键值。 将C程序交叉编译后生成switch_test,用scp命令上传到开发板
#include <stdio.h> #include <unistd.h> #include <memory.h> #include "serial.h" #include "message_protocol.h" #define COMMAND_SWITCH 's' int main(int argc, char **argv) { int fd; //file descriptor of serial port message_t msg; //message object fd = serial_open(0, 57600);//open serial port 0, /etc/ttyS0 if ( fd > 0 ) { message_init( &msg, fd, 57600 ); //init message with fd, set baud rate //loop while ( 1 ) { if ( 1 == message_receive(&msg) ) { //if received message if ( message_command(&msg) == COMMAND_SWITCH ) { printf("switch value = %d\n", message_data(&msg, 0 ) ); //print value fflush(stdout); } } usleep(10*1000); //wait 10 milli-seconds } serial_close(fd); } return 0; }
switch value 0
switch value 1
switch value 0
switch value 1
switch value 0
主程序是死循环,在SSH中按下CTRL+C可中断程序
六、继电器模块(RELAY)
继电器是一种电子开关元件。数字I/O管脚的输出电压常为直流3.3V,不能直接驱动交流220V的开关,因此需要继电器模块来驱动外部开关。当数字I/O输出0或1时,继电器使外部开关打开或关闭。 继电器常用于控制其它电器、电动机等。
继电器模块见下图:
编一个Arduino程序,采用消息协议,根据接收的消息内容,设置 继电器信号脚D3为0或1。 编译Arduino程序上传到ATmegaU32
注:程序将使用message_protocol.ino模块,要把 message_protocol.ino 这个文件放在本Arduino项目文件夹中, 然后重新打开项目即可包含此模块
#define COMMAND_RELAY 'l' int pinRelay = 3; // digital pin 3 has a relay module attached to it int lastValue = 1; // last value of pin void setup() { MessageInit( 57600 ); // init message < need message_protocol.ino > pinMode(pinRelay, OUTPUT); //set pin as OUTPUT } void loop() { if ( MessageReceive() ) { //if message is read if ( MessageCommand() == COMMAND_RELAY ) { //if is COMMAND_RELAY int value = MessageData(0); //get value from message data digitalWrite(pinRelay, value); //output to pin of relay MessageACK( COMMAND_RELAY ); //send ACK back } } delay(1); // delay in between reads for stability }
编一个C语言主程序 relay_test.c,采用串口消息协议,发消息控制继电器开关。 交叉编译后生成relay_test,用scp命令上传到开发板
注:项目中要使用到 串口函数模块serial.c 和 消息协议模块 message_protocol.c
#include <stdio.h> #include <unistd.h> #include <memory.h> #include "serial.h" #include "message_protocol.h" #define COMMAND_RELAY 'l' int main(int argc, char **argv) { int fd; //file descriptor of serial port message_t msg; //message object unsigned char value; fd = serial_open(0, 57600);//open serial port 0, /etc/ttyS0 if ( fd > 0 ) { message_init( &msg, fd, 57600 ); //init message with fd, set baud rate value = 1; message_send( &msg, COMMAND_RELAY, &value, 1); //send COMMAND_RELAY message,value=1 sleep(10); //wait 10 seconds value = 0; message_send( &msg, COMMAND_RELAY, &value, 1); //send COMMAND_RELAY message,value=0 serial_close(fd); } return 0; }
把继电器串联在灯泡电路上,则可以实现用开发板控制电灯开和关了。
要注意继电器上标明的最大电压和电流,不要超,否则继电器会烧掉。我的继电器是: 250V AC, 10A电流。除了大功率空调,一般的家电都可以带。
七、蜂鸣器模块(BUZZER), 见下图
蜂鸣器可以用来发出“嘟嘟”声,也可以发出简单的音乐声音。
编一个Arduino程序,采用消息协议,当接到 COMMAND_BUZZER消息,用 tone()函数通过D3口写蜂鸣器,发出指定频率的声音。
注:程序将使用message_protocol.ino模块,要把 message_protocol.ino 这个文件放在本Arduino项目文件夹中, 然后重新打开项目即可包含此模块
这里先定义一下 COMMAND_BUZZER消息, 该消息的内容数据共4字节,前两个字节是 声音频率(高位在前),后两个字节是声音时长(毫秒,高位在前)
Arduino程序如下,将程序编译上传到ATmegaU32
#define COMMAND_BUZZER 'b' int pinBuzzer = 3; //pin 3 connects to Buzzer module void setup() { MessageInit(57600); //init message pinMode(pinBuzzer, OUTPUT); //set pinBuzzer OUTPUT } void loop() { if ( MessageReceive() ) { //if message received if ( MessageCommand() == COMMAND_BUZZER ) { //if is COMMAND_BUZZER //get frequecy from first two bytes in data long frequency = (MessageData(0) << 8) + MessageData(1); //get duration, the duration of the tone in milliseconds long duration = (MessageData(2) << 8) + MessageData(3); if ( frequency == 0 ) noTone(pinBuzzer); else tone(pinBuzzer, frequency, duration ); } } }
注:项目中要使用到 串口函数模块serial.c 和 消息协议模块 message_protocol.c
#include <stdio.h> #include <unistd.h> #include "serial.h" #include "message_protocol.h" #define COMMAND_BUZZER 'b' /** * send message to Arduino to play tone of specified frequency in specified duration * * @param msg message handle * @param frequency tone frequency * @param duration the duration of the tone in milliseconds * @param imediately_return if this value is 1, this function will return immediately * * @return none */ void play_tone(message_t *msg, int frequency, int duration, int imediately_return) { unsigned char data[4]; //byte 0 and 1 is frequecy data[0] = (frequency & 0xFF00) >> 8; data[1] = (frequency & 0xFF); //byte 2 and 3 is duration data[2] = (duration & 0xFF00) >> 8; data[3] = (duration & 0xFF); //send message to Arduino message_send(msg, COMMAND_BUZZER, data, 4); if ( imediately_return != 1 ) usleep( duration * 1000); } int main(int argc, char **argv) { int fd; //file descriptor of serial port message_t msg; //message object int duration = 600; fd = serial_open(0, 57600);//open serial port 0, /etc/ttyS0 if ( fd > 0 ) { message_init( &msg, fd, 57600 ); //init message with fd, set baud rate play_tone(&msg, 523, duration, 0); // music tone "1" play_tone(&msg, 587, duration, 0); // music tone "2" play_tone(&msg, 659, duration, 0); // music tone "3" play_tone(&msg, 698, duration, 0); // music tone "4" play_tone(&msg, 784, duration, 0); // music tone "5" play_tone(&msg, 880, duration, 0); // music tone "6" play_tone(&msg, 988, duration, 0); // music tone "7" play_tone(&msg, 1046, duration * 2, 0);// music tone high "1" serial_close(fd); } return 0; }
SSH进入开发板,执行 music_test.
呵呵, 蜜蜂鸣叫器发出一段音乐声: 1, 2, 3, 4, 5, 6, 7 ...
主程序中 采用的频率值 523, 587, 659 ...等频率值,是音符对应的频率。其它音符可以百度一下 “音符与频率的对照表”。
有兴趣的童鞋,可以据此写一个播放音乐的小程序了。
相关代码可以在我的资源中下载:Linkit7688DUO开发板连接多种Ardunio模块的范例程序