草庐IT

Arduino软串口通信的实现及softwareserial库应用详解

不赦先生 2023-05-20 原文

    我们都知道Arduino UNO板的数字IO端口为D0~D13,其中D0、D1还作为串口通信的接收(Rx)、发送(Tx)端口,当Arduino UNO板外接的串口通信模块超过一个时,怎么办?此外,我们调试程序时经常会用到串口显示语句Serial.print(),如果我们的模块占用了这个串口,就没法用Serial.print()语句来显示我们的测试数据了。Arduino UNO板解决这个问题的办法,就是用其它的IO端口来模拟串口通信,也就是我们通常称之为软串口(或模拟串口)。

    一般采用微处理器芯片的单板机等,数字IO口的电平信号都是采用TTL电平标准的,即5V为1,0V为0Arduino的数字IO口亦是采用TTL电平标准,它上面的硬件串口(D0、D1)采用的也是TTL标准的串行协议因此我们就可以用其它的IO口来模拟硬件串口,即用软件程序将一个字节(BYTE)拆成8个位(bit),根据bit=1或0,依次按指定的频率用相应的高低电平串行发送,接收端读取到这些高低电平后,再将这些高低电平信号还原成1或0,然后还原成一个字节数据,也就是模拟硬件串口进行通信。

    如果按上述的方法编程,那不是太麻烦了啊!幸好Arduino IDE里已经自带了一个softwareserial.h库,它让我们使用软串口编程与硬串口编程几乎一模一样。我们使用这个库可以将D2~D13的任何2个IO端口定义为软串口的接收(Rx)或发送(Tx)端口。可以创建多个SoftwareSerial对象,但是在给定时刻只能有一个活动。

    下面说下如何在程序中使用softwareserial.h库。

    

    (一)softwareserial.h库提供了一个SoftwareSerial类,当然我们首先要引用一下库,即:

    #include<SoftwareSerial.h>

    

    (二)接着自定义一个软串口SoftwareSerial类的实例对象:

    SoftwareSerial mySerial(rxPin, txPin)

    参数:

    mySerial:用户自定义软串口对象名

    rxPin:软串口接收引脚

    txPin:软串口发送引脚

    返回:

    无

    这里自定义软件串口对象的名,可以随便取,例如你可以取名为SS1或其他的,如果用D10作为接收端口,D11作为发送端口,则程序语句为:

    SoftwareSerial  SS1(10,11);

    

    (三)接着我们要用begin来设置串行通信的速度(波特率):

    mySerial.begin(speed)

    参数

    speed:所需波特率。支持的波特率为:300、600、1200、2400、4800、9600、14400、19200、28800、31250、38400、57600。

    返回:

    无

    

    (四)接下来我们就可以用available来检测接收缓冲区里是否有已经接收到的数据:

    mySerial.available()

    参数

    无

    返回:

    可读取的字节数

    

    (五)然后就可以用read来读取接收引脚(rxPin上接收到的字符,并且清除掉接收缓冲区中的第一个字节。请注意,一次只能有一个SoftwareSerial对象接收传入的数据(使用listen()函数选择哪个对象)。

    (1)

    mySerial.read()

    参数

    无

    返回:

    如果没有可用的字符,则为-1。

    

    (2)

    也可以用peek来读取接收引脚(rxPin上接收到的字符,但是与read()不同,它不清除掉接收缓冲区中的第一个字节,因此对该函数的后续调用将返回相同的字符。

    mySerial.peek()

    参数

    无

    返回:

    如果没有可用的字符,则为-1。

    

    (六)发送数据的话,可以用print、println、write。

    (1)print是将数据作为ASCII文本发送到串行端口。这个函数可以有多种形式。每个数字都使用ASCII字符打印数字。浮点数也是打印为ASCII数字,默认为两位小数。字节作为单个字符发送。字符和字符串按原样发送。

    mySerial.print(val)

    mySerial.print(val, format)

    参数

    val:要发送的值。允许的数据类型:任何数据类型。

    返回:

    发送的字节数(读取此数字是可选的)。

    

    mySerial.print(val, format)

    可选的第二个参数指定要使用的格式允许值为BIN(二进制)OCT(八进制)DEC(十进制)HEX(十六进制或十六进制)。对于浮点数,此参数指定要使用的小数位数。例如

    

    mySerial.print(78, BIN)给出“1001110”

    

    mySerial.print(78, OCT)给出“116”

    

    mySerial.print(78, DEC)给出“78”

    

    mySerial.print(78, HEX)给出“4E”

    

    mySerial.print(1.23456, 0)给出“1”

    

    mySerial.print(1.23456, 2)给出“1.23”

    

    mySerial.print(1.23456, 4)给出“1.2346”

    

    (2)println也是将数据作为ASCII文本发送到串行端口只是在发送完参数val后,自行再增加发送了回车和换行符。工作原理和println()函数

    

    (3)还有一个发送数据的函数write,write也是发送数据,但它发送的是数值本身。这里我简单的说下与print的区别,当用print发送一个数据时,Arduino发送的并不是数据本身,而是将数据转换成字符,再将对应的ASCII码发送出去,即print是以ASCII码的形式输出数据到串口。而当用write发送一个数据时,Arduino发送的是数值本身。

    mySerial.write(val)

    参数

    发送的数值

    返回:

    发送的字节数(读取此数字是可选的)。

    

    (七)我们前面说到“可以创建多个SoftwareSerial对象,但是在给定时刻只能有一个活动”,那我们怎么转换到指定的软串口呢?SoftwareSerial库提供了2个相关的函数。

    (1)当我们需要启用所选的某个SoftwareSerial对象来进行软串口通信,可以用listen。因为一次只能监听一个SoftwareSerial对象,因此到达其他端口的数据将被丢弃。在调用listen函数期间,将丢弃已接收到的任何数据(除非给定实例已在侦听)。

    mySerial.listen()

    参数:

    无

    返回:

    如果替换另一个则返回true。

    

    (2)还有一个isListening函数,是用来测试某个指定的软串口是否为当前活动的SoftwareSerial对象。

    mySerial.isListening()

    参数:

    无

    返回:

    布尔值

    

    使用Listen和isListening例:

    

    #include <SoftwareSerial.h>

    

    // Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11

    SoftwareSerial portOne(10, 11);

    

    // Set up a new SoftwareSerial object with RX in digital pin 8 and TX in digital pin 9

    SoftwareSerial portTwo(8, 9);

    

    void setup() {

        // Set the baud rate for the Serial object

        Serial.begin(9600);

    

        // Set the baud rate for the SerialSoftware objects

        portOne.begin(9600);

        portTwo.begin(9600);

    }

    

    void loop() {

        // Enable SoftwareSerial object to listen

        portOne.listen();

        

        if (portOne.isListening()) {

            Serial.println("portOne is listening!");

        } else {

            Serial.println("portOne is not listening!");

        }

    

        if (portTwo.isListening()) {

            Serial.println("portTwo is listening!");

        } else {

            Serial.println("portTwo is not listening!");

        }

    }

    

    (八)还有一个overflow函数,是用来检测是否发生软件串行缓冲区溢出。调用此函数将清除溢出标志,这意味着后续调用将返回false,除非在此期间接收并丢弃另一字节的数据。软件串行缓冲区最多可容纳64个字节。

    mySerial.overflow()

    参数:

    无

    返回:

    布尔值

    

    下面我们就用一个实验来印证一下软串口的使用,就还用我上一篇写的《Arduino实现语音实时播报当前温湿度》实验,这次我们在Arduino端不使用硬件串口,使用IO端口D10、D11作为软串口的接收(Rx)和发送(Tx)端口,程序基本不用改,除了在前面添加了引用softwareserial库和建立一个软串口实例(我取名为softSerial),其他就是在原程序里把Serial对象替换成softSerial对象,当然连接线也要改下,原来接D0、D1的线,改接到D10、D11,是不是非常简单?如果没有看过《Arduino实现语音实时播报当前温湿度》的,可以点击这个文章名,直接进入该文查看。接线图还是再放一下:

    

    用软串口的Arduino端的完整程序如下( LU-ASR01端程序完全不用改,就不再放上来了):

/*

   本程序通过DHT11获得当前温湿度,并通过软串口通信与ASR01连接,然后由ASR01播报

   当前温湿度。

   通信报文长度为3字节,报文格式:

   第1字节:命令编号   第2、3字节:数据

   本程序的报文用了三个命令:

   发送温度报文:编号:0x23(字节1),字节2温度整数部分,字节3温度2位小数部分

   发送湿度报文:编号:0x24(字节1),字节2湿度整数部分,字节3湿度2位小数部分

   接收请求发送温湿度报文:编号:0x25(字节1),字节2未使用,字节3  =1温度,=2湿度

*/

#include "DHT.h"

#include<SoftwareSerial.h>

const int DHTPin =  12; // 定义DHTPin连接的引脚为D12

const int SRxPin =  10; // 定义软串口的接收(Receive Rx)引脚为D10

const int STxPin =  11; // 定义软串口的接收(Transmit Tx)引脚为D11

DHT dht;

SoftwareSerial softSerial(SRxPin,STxPin);

#define MLEN 3   //定义报文长度为3

unsigned char Txbyte[MLEN];  //串口发送的字符数据,长度为MLEN

unsigned char Rxbyte[MLEN];  //串口读取的字符数据,长度为MLEN

float thtmp; //

//初始化

void setup() {

  softSerial.begin(9600); //设置串口波特率9600

  pinMode(DHTPin, INPUT);  //设置DHTPin    

  dht.setup(DHTPin); // 设置DHT11数据传输的引脚

}

//发送报文子程序

//参数txb为准备发送的报文数据,n为报文长度

void txmss(unsigned char txb[],int n){

  int i;

  for(i=0;i<n;++i){

    softSerial.write(txb[i]);

    delay(2);  

  }

}

//接收报文子程序,成功接收返回值为true,否则为false

//参数rxb为返回的报文数据,n为报文长度

bool rxmss(unsigned char rxb[],int n){

int i,dn;  //dn为超时计数器

  for (i=0; i<n; ++i) {

    dn=0;  //超时计数变量复位

    while(1){   //  

      if (dn<=50){  //设等待读取一字节的最大时间为50ms,若没有超时则

        if(softSerial.available() > 0){ //当串口缓冲区有数据

          rxb[i]=softSerial.read(); //读取一个字节

          break;  //跳出while(1)循环

        }

        else{  //若串口缓冲区没有数据,且没有超时,则超时计数器+1

          delay(1);

          dn+=1;    //超时计数器+1

        }

      }

      else return false;  //接收超时则函数rxmss退出,返回false

    }

  }

  return true;  //正常接收到n个字节,函数rxmss返回true

}

//主程序

void loop() {

  delay(dht.getMinimumSamplingPeriod()); //获取DHT11的最小采样周期

  if (softSerial.available() > 0){     //当串口缓冲区有数据

    if (rxmss(Rxbyte,MLEN)){    //若成功接收到一个报文(即3字节数据已接收到数组Rxbyte)

      if(Rxbyte[0]==0x25) {    //若接收的Rxbyte[0]为0x25则为要求发送温湿度命令

        if(Rxbyte[2]==1){ //要求发送的是温度

          //填写报文

          Txbyte[0]=0x23;  //命令码0x23表示发送温度数据

          thtmp=(float) dht.getTemperature(); //从DHT11读取温度

          Txbyte[1]=(int) thtmp; //温度的整数部分放在Txbyte[1]

          Txbyte[2]=(int) (thtmp-Txbyte[1]*100);  //温度的2位小数放在Txbyte[2]

          txmss(Txbyte,MLEN);  //发送报文,将温度数据发送给LU-ASR01

        }

        if(Rxbyte[2]==2){ //要求发送的是湿度

          //填写报文

          Txbyte[0]=0x24;  //命令码0x24表示发送温度数据

          thtmp=(float) dht.getHumidity(); //从DHT11读取湿度

          Txbyte[1]=(int) thtmp; //湿度的整数部分放在Txbyte[1]

          Txbyte[2]=(int) (thtmp-Txbyte[1]*100);  //湿度的2位小数放在Txbyte[2]

          txmss(Txbyte,MLEN);  //发送报文,将湿度数据发送给LU-ASR01

        }

      }

    }

  }

}

    

本人网名为“不赦先生”,发表在CSDN上的文章均为本人原创,且仅在CSDN网站发布,其他网站的转载均未获得过本人的授权。

    

有关Arduino软串口通信的实现及softwareserial库应用详解的更多相关文章

  1. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  2. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  3. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  4. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  5. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  6. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  7. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  10. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

随机推荐