串口通信简介
参考文章(大佬写的很好可以去看一下) arduino支持的串行通信有UART,I2C和SPI三种通信协议方式 根据串行数据的传输方向,我们可以将通信分为单工,半双工,双工 单工 是指数据传输仅能沿一个方向,不能实现反向传输 半双工 是指数据传输可以沿两个方向,但不能同时进行传输 全双工 是指数据可以同时进行双向传输
硬件串口通信(UART)——HardwareSerial 类库
除了常见的函数外,另外比较常用的 peek() 功能:返回1字节的数据,但不会从接受缓冲区删除数据,与read()函数不同,read()函数读取该函数后,会从接受缓冲区删除该数据。 write() 功能:输出数据到串口。以字节形式输出到串口,它与print()的区别在于:当使用print()发送一个数据时,arduino发送的并不是数据本身,而是将数据转换为字符,再将字符对应的ASCII码发送出去,串口监视器收到ASCII码,则会显示对应的字符,因此使用print()函数是以ASCII码形式输出数据到串口; 而当使用write() 函数时,arduino发送的是数值本身。但串口监视器接收到数据后,会将数值当做ASCII码而显示其对应的字符。 例如,当使用serial.write(INT)输出一个整型数 123 时,显示出的字符为"{",因为ASCII码 123 对应的字符为"{"
软件模拟串口通信——softwareserial 类库使用
除HardwareSerial 类库外,arduino还提供了softwareserial类库,可将其他数字引脚通过程序模拟成串口通信引脚 通常将arduino上自带的串口成为硬件串口,而使用softwareserial类库模拟成的串口称为软件模拟串口 sofawareserial类库成员函数 其中定义的成员函数和硬件串口的类似 available(), begin(), read(), write(), print(), println(), peek(),函数用法相同 此外软串口还有如下成员函数 SofaWareSerial() 功能:这是SoftwareSerial类的构造函数,通过它可以指定软串口的RX和TX引脚 语法:SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin) listen() 功能:开启软串口监听状态 arduino在同一时间仅能监听一个软串口,当需要监听某一串口时,需要对该对象调用此函数开启监听功能 overflow() 功能:检测缓冲区是否已经溢出。软串口缓冲区最多可保存64B的数据
实验
使用UART通信模式,需要两部分RX-TX, TX-RX的连接 两个arduino实现通信,一个uno,一个mega,uno端连接lcd1602,显示通信类容 mega的程序如下:
String device_mega = "";
String device_uno = "";
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
}
void loop() {
if(Serial.available()>0){
if(Serial.peek() != '\n')
device_mega += (char)Serial.read();
else{
Serial.read();
Serial.print("you said: ");
Serial.println(device_mega);
Serial1.println(device_mega);
device_mega = "";
}
}
if(Serial1.available()>0){
if(Serial.available()>0){
if(Serial1.peek() != '\n')
device_uno += (char)Serial1.read();
else{
Serial1.read();
Serial.print("the uno said: ");
Serial.println(device_uno);
device_uno = "";
}
}
}
}
uno的程序如下
#include "LiquidCrystal.h"
#include "SoftwareSerial.h"
SoftwareSerial myserial(10,11);
String device_mega = "";
String device_uno = "";
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
void setup() {
Serial.begin(9600);
myserial.begin(9600);
myserial.listen();
lcd.begin(16,2);
lcd.clear();
}
void loop() {
if(softSerial.available()>0){
delay(100);
lcd.clear();
while(Serial.available()>0)
lcd.write(Serial.read());
device_mega = "";
}
if(Serial.available()>0){
if(softSerial.peek() != '\n')
device_uno += (char)Serial.read();
else
{
Serial.read();
Serial.print("you said:");
Serial.println(device_uno);
device_uno = "";
}
}
}
I2C协议
使用IIC协议可以通过两根双向的总线数据线SDA 和时钟线 SCL 使arduino 连接最多128个 IIC 从机设备。 与串口通信的一对一通信方式不同,总线通信通常有主机和从机之分。通信时,主机负责启动和终止数据传送,同时还要输出时钟信号;从机会被主机寻址,并且响应主机的通信请求;在IIC通信中,通信速率的控制有主机完成,主机会通过SCL引脚输出时钟信号供总线上的所有从机使用;同时,IIC是一种半双工通信方式
注意一定是A4,A5不是标有SDA 和 SCL 的引脚 arduino的强大在于,它有各种已经封装好的库,便于初学者使用
Wire 类库
begin() 功能:初始化II连接,并作为主机或者从机设备加入IIC总线 begin(address) 当没有填写参数时,设备会以主机模式加入IIC总线;当填写了参数时,设备以从机模式加入IIC总线,address 可以设置为0~127 中任意地址 requesFrom() 功能:主机向从机发送数据请求信号 使用requesFrom() 后,从极端可以使用 onReceive () 注册一个事件以响应主机的请求;主机可以通过available() 和 read() 函数读取这些数据 beginTransmission() 功能:设定传输数据到指定的从机设备。 wire.beginTransmission(address) endTransmission() 功能:结束数据传输 onReceive() 该函数可以在从机端注册一个事件,当从机收到主机发送的数据时即被触发 onRequest() 注册一个事件,当主机收到从机发送数据请求时触发
实验
主机发送数据从机接收数据和从机发送数据主机接收数据 主机部分:
#include <Wire.h>
void setup() {
Wire.begin();
}
byte com = 0;
void loop() {
Wire.beginTransmission(4);
Wire.write("com is ");
Wire.write(com);
Wire.endTransmission();
com ++;
delay(500);
}
从机部分
#include <Wire.h>
void setup() {
Wire.begin(4);
Wire.onReceive(receiveEvent);
Serial.begin(9600);
}
void loop() {
delay(500);
}
void receiveEvent(int howMany){
while(Wire.available() > 1){
char c = Wire.read();
Serial.print(c);
}
int com = Wire.read();
Serial.println(com);
}
将上面两个程序分别上传到mega和uno上可以实现两个板子的通信 (2)从机发数据主机收数据
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
}
void loop() {
Wire.requestFrom(8, 6);
while (Wire.available()) {
char c = Wire.read();
Serial.print(c);
}
delay(500);
}
从机
#include <Wire.h>
void setup() {
Wire.begin(8);
Wire.onRequest(requestEvent);
}
void loop() {
delay(100);
}
void requestEvent() {
Wire.write("hello ");
}
实验
IIC总线的好处在于可以只用两条总线同时控制多个从机, iic控制舵机 参考文章:链接 pca9865模块 连线: A4--------SDA A5--------SCL 5V--------VCC GND-------GND 使用PCA9865模块需要用到 adafruit pwm 库 舵机为50HZ的控制频率,脉宽为0.5ms~2.5ms,12位分辨率(4096)。PCA9685采用12位寄存器来控制PWM占比,对于0.5ms, 相当于0.5/204096=102的寄存器值。 0.5ms-------0度 2.5ms---------180度 依次类推 下面是库里面的示例
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
#define SERVOMIN 150
#define SERVOMAX 600
#define USMIN 600
#define USMAX 2400
#define SERVO_FREQ 50
uint8_t servonum = 0;
void setup() {
Serial.begin(9600);
Serial.println("8 channel Servo test!");
pwm.begin();
pwm.setOscillatorFrequency(27000000);
pwm.setPWMFreq(SERVO_FREQ);
delay(10);
}
void setServoPulse(uint8_t n, double pulse) {
double pulselength;
pulselength = 1000000;
pulselength /= SERVO_FREQ;
Serial.print(pulselength); Serial.println(" us per period");
pulselength /= 4096;
Serial.print(pulselength); Serial.println(" us per bit");
pulse *= 1000000;
pulse /= pulselength;
Serial.println(pulse);
pwm.setPWM(n, 0, pulse);
}
void loop() {
Serial.println(servonum);
for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
pwm.setPWM(servonum, 0, pulselen);
}
delay(500);
for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
pwm.setPWM(servonum, 0, pulselen);
}
delay(500);
for (uint16_t microsec = USMIN; microsec < USMAX; microsec++) {
pwm.writeMicroseconds(servonum, microsec);
}
delay(500);
for (uint16_t microsec = USMAX; microsec > USMIN; microsec--) {
pwm.writeMicroseconds(servonum, microsec);
}
delay(500);
servonum++;
if (servonum > 5) servonum = 0;
}
下载这个程序,将对应的舵机角度换算到脉冲宽度后,可以用IIC同时控制多个舵机,这里我用了五个舵机
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
#define SERVO_0 102
#define SERVO_45 187
#define SERVO_90 280
#define SERVO_135 373
#define SERVO_180 510
uint8_t servonum = 0;
char comchar;
void setup() {
Serial.begin(9600);
Serial.println("8 channel Servo test!");
pwm.begin();
pwm.setPWMFreq(50);
delay(10);
}
void loop() {
while(Serial.available()>0){
comchar = Serial.read();
switch(comchar)
{
case '0':
pwm.setPWM(0, 0, SERVO_0);
Serial.write(comchar);
break;
case '1':
pwm.setPWM(0, 0, SERVO_45);
Serial.write(comchar);
break;
case '2':
pwm.setPWM(0, 0, SERVO_90);
Serial.write(comchar);
break;
case '3':
pwm.setPWM(0, 0, SERVO_135);
Serial.write(comchar);
break;
case '4':
pwm.setPWM(0, 0, SERVO_180);
Serial.write(comchar);
break;
default:
Serial.write(comchar);
break;
}
}
}
这个通过简单的换算实现了对一个舵机多个固定角度的控制,若想要精细控制每个舵机,可以构造一个换算函数,然后实现多每个舵机的精细控制
SPI协议
SPI(Serial Peripheral Interface, 串行外设接口), 是Areuino 自带的一种高速通信接口,通过它可以连接使用具有同样接口的外部设备。SPI是双工通信,因此常用于数据传输量大的外部设备
SPI设备的引脚
引脚 | 说明 |
---|
MISO(Master in Slave out) | 主机数据输入,从机数据输出 | MOSI | 主机数据输出从机数据输入 | SCK(Serial Clock) | 用于同步通信的时钟信号,该时钟信号由主机产生 | SS (SLAVE select) | 从机使能信号 |
在SPI 总线中也有住从机之分,主机负责输出时钟信号及选择通信从设备。时钟信号会通过主机的SCK引脚输出,提供给通信从机使用。而对于通信从机的选择,由从机的SS引脚决定,当SS引脚为低电平时,该从机被选中 SPI类库成员函数
-
SPI.begin() 初始化SPI通信,调用该函数后,SCK/MOSI/SS引脚将被设置为输出模式,且SCK/MOSI引脚拉低,SS引脚拉高。 -
SPI.end() 关闭SPI总线通信 -
SPI.setBitOrder(order) 设置传输顺序。order:传输顺序,LSBFIRST,低位在前;MSBFIRST,高位在前 -
SPI.setClockDivider(divider) 设置通信时钟,由主机产生,从机不用配置。divider:SPI通信的系统时钟分频得到,可选配置有SPI_CLOCK_DIV2、SPI_CLOCK_DIV4(默认配置)等,最大可达128分频 -
SPI.setDataMode(mode) 设置数据模式。mode:可配置的模式,可选项有SPI_MODE0、SPI_MODE1、SPI_MODE2、SPI_MODE3 -
SPI.transfer(val) 传输1Byte的数据,SPI是全双工通信,所以发送1B的数据,也会接收到1B的数据。val:要发送的字节数据。 原文链接 arduino的SPI库只提供了主机的通信示例
实验:SPI通信
由于官方当中没有说明如何实现ARDUINO之间SPI通信,苦苦在网上搜寻,终于找到一个讲的清楚的 原文链接
master 主机代码
#include <SPI.h>
void setup (void)
{
digitalWrite(SS, HIGH);
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV8);
}
void loop (void)
{
char c;
digitalWrite(SS, LOW);
for (const char * p = "Hello, world!\n" ; c = *p; p++)
SPI.transfer (c);
digitalWrite(SS, HIGH);
delay (1000);
}
slave 从机代码
#include <SPI.h>
char buf [100];
volatile byte pos;
volatile boolean process_it;
void setup (void)
{
Serial.begin (115200);
SPCR |= bit (SPE);
pinMode(MISO, OUTPUT);
pos = 0;
process_it = false;
SPI.attachInterrupt();
}
ISR (SPI_STC_vect)
{
byte c = SPDR;
if (pos < (sizeof (buf) - 1))
buf [pos++] = c;
if (c == '\n')
process_it = true;
}
void loop (void)
{
if (process_it)
{
buf [pos] = 0;
Serial.println (buf);
pos = 0;
process_it = false;
}
}
串口可以看到程序效果
软件模拟SPI通信
模拟SPI 通信可以指定ARDUINO 上的任意数字引脚为模拟SPI 引脚功能, 并与其他SPI 器件进行通信
实验:使用 74HC595
当ARDUINO 引脚不够时,可以使用74HC595扩展I/O口
#include <SPI.h>
#define STCP 8
#define DS 51
#define HSCP 50
void setup(){
pinMode(STCP,OUTPUT);
pinMode(HSCP,OUTPUT);
pinMode(DS,OUTPUT);
}
void loop(){
for (int i=0;i<256;i++){
digitalWrite(STCP,LOW);
shiftOut(DS,HSCP,LSBFIRST,i);
digitalWrite(STCP,HIGH);
delay(50);
}
}
|