Arduino通过简单报文实现串口通信的尝试——及语音调节灯光亮度实验

    所谓串口通信,就是在一条数据线上,将数据依次分割为一个一个的二进制位(bit),然后进行传输。从电平角度来看,通常我们微控制器的串口通信都是采用TTL串行标准。这些其实都是硬件实现方面的范畴,我们从程序角度来看,就是一个字节一个字节的顺序收发数据,也就是说我们还是以字节(Byte)为单位来处理数据。那么从更高的角度来看,我们为了规范正确的收发数据,一般都是将数据按一定的要求组成一组数据(数据帧)进行收发,这一组数据,更规范的说法就是一帧数据,就是我们所说的报文。为保证不同厂家不同设备之间能够相互进行数据交换,于是就制定了一些报文格式的规范协议,目前在工业控制领域使用的最普遍的是Modbus协议,Modbus协议具有标准、开放,可以支持多种电气接口,数据帧格式简单紧凑,数据传输量大、实时性好等特点。不过正因为其的高通用性和可靠性,所以其格式比较繁复,从而造成程序的编写也比较繁复。当我们使用Arduino与其他设备进行一些简单的命令传输和通信数据交换实验,若是采用Modbus协议,则大大增加了程序编写的复杂度。本文正是从方便简洁地进行这类实验,同时又能满足一般的数据传输要求角度出发,尝试用一种简单的报文格式来满足Arduino与其他设备进行一些简单的命令传输和通信数据交换实验要求。

    根据上述的需求,我们首先确定报文的长度是固定的并具有一个简单的规范,本次实验采用的是3字节的报文长度,不带CRC等校验码,当然在可靠性方面就欠缺了些。报文格式如下:

        第1字节:功能码(建议取值范围为1—254)

        第2字节:数据字节1(取值范围为0—255)

        第3字节:数据字节2(取值范围为0—255)

    第1字节的功能码(或可称做命令编号),可以根据各自的要求自己定义,表示此报文代表某个指定功能,建议取值范围为1—254。第2、3字节为此功能所需的数据,若此功能不需要数据,第2、3字节数值可以不赋值,不过传输时仍然会将其发送出去,同样接收端也会接收到此第2、3字节。

    当某功能数据超过2个字节的情况,可以简单地用不同的功能码来表示更多的数据,例如某功能(这里假定功能码为48)的数据是一个长整数,我们可以用功能码48先发前2个字节,然后再用功能码49发后2个字节。

    本次实验仍然使用一块Arduino UNO和一块LU-ASR01进行(请原谅我,这是我手头唯一的两块带有TTL串口的板子)。实验通过LU-ASR01接收我们的语音命令:打开灯光、关闭灯光、调亮灯光、调暗灯光,然后发送相应的功能码给Arduino,Arduino接收到此功能码后,根据报文后面的数据来调节连接在Arduino上的LED亮度,可以重复命令调亮灯光、调暗灯光语音,每发出一次,调节全亮度的10%,直至全亮或全黑。此外,当LED在打开状态下(即非全黑状态)超过1分钟没有进行灯的亮度改变,则Arduino关闭LED,并发送报文给LU-ASR01,而LU-ASR01接收到此报文后,则播报“灯亮超过一分钟,阿杜已经关闭灯了”。

(注:关于LU-ASR01的双向串口通信采用软串口这点,可以参看本人之前写的《Arduino与LU-ASR01语音识别模块的双向串口通信实现》

    硬件连接方式:Arduino仍然采用硬件串口通信,使用TX和RX端口,LU-ASR01则使用IO6端口作为发送数据的软TX,IO7端口作为接收数据的软RX。由Arduino提供5V电源给LU-ASR01(下图中的红黑2线),Arduino的TX连接到LU-ASR01的IO7端口(软RX),Arduino的RX连接到LU-ASR01的IO6端口(软TX),LED的正极连接到Arduino的D11,连接图如下:

    下面简单介绍一下Arduino端的程序。程序中有2个报文收发的子程序,一个是发送报文的子程序void txmss(unsigned char txb[],int n),参数txb[]是存放准备发送的报文数据(包括功能码),n为报文长度(报文长度可通过程序开始部分的常数MLEN来定义,调用时直接填MLEN即可)。另一个是接收报文的子程序bool rxmss(unsigned char rxb[],int n),当成功收到完整的报文后,该子程序返回true,否则返回false,参数rxb[]是存放接收到的报文数据(包括功能码),n为报文长度。

    在主循环程序loop中,我们只需判断串口有没有数据,有则调用rxmss接收数据报文,然后根据接收的功能码Rxbyte[0]进行相应的操作。本实验中关于灯亮度调节的功能码定义为33(即16进制0x21),第2个字节未用,第3个字节为亮度值,当接收到功能码等于33的报文后,就用第3字节为亮度值,调用函数analogWrite设置LED亮度。

    如需要LU-ASR01进行某项操作,可以填写Txbyte[0]、Txbyte[1]、Txbyte[2]后调用txmss,就将该报文发给LU-ASR01了。loop中if(brightness>0){ 到结束这段就是判别有没有超过1分钟,超过了就关闭LED,然后发送一个报文给LU-ASR01,告诉其开亮后已超过1分钟关闭了LED,报文的功能码仍定义为33(可以自定义为其他值),而字节3则设为0。

    下面是完整程序:

/*

   这是一个简易通信报文程序,报文长度为3字节,报文格式:

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

   通过Arduino与ASR01语音识别模块的串口通讯实验

   程序的报文只用一个命令编号:0x21(字节1),字节2没有用,字节3为灯的亮度(值域0—255)

   ASR01会发送这个报文到本机,本机收到该报文后设置相应的LED亮度

   当LED灯点亮后,等待1分钟后,就会自动关闭LED,且发送关闭灯报文到ASR01

*/

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

const int LedPin =  11; // 定义led连接的引脚为D11(具有PWM的IO口均可),作为LED灯的正极

int brightness = 0; // LED 亮度,值为0—255,0为全黑,255为全亮

long previousMillis = 0; // 存储最近一次的 LED 开灯状态时的机器运行时间

long interval = 60000; // 等待关灯的时间长度为1分钟

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

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

//初始化

void setup() {

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

  pinMode(LedPin, OUTPUT);  //设置LedPin    

}

//发送报文子程序

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

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

  int i;

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

    Serial.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(Serial.available() > 0){ //当串口缓冲区有数据

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

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

        }

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

          delay(1);

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

        }

      }

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

    }

  }

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

}

//主程序

void loop() {

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

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

      if(Rxbyte[0]==0x21) {    //若接收的Rxbyte[0]为0x21则为调节灯亮度命令

        brightness = Rxbyte[2];   //brightness的值为0—255,0为全黑,255为全亮

        analogWrite(LedPin, brightness); //用PWM方式,brightness设置灯亮度

        if(brightness>0){ //灯是亮的情况,开始记时

          previousMillis = millis(); //millis函数获取机器运行的时间长度,单位ms

        }

      }

    }

  }

  //LED点亮计时超过interval指定的毫秒数,则关闭LED,并发送报文到ASR01

  if(brightness>0){ //如果灯是亮的

    unsigned long currentMillis = millis(); //millis函数获取机器运行的时间长度,单位ms

    if(currentMillis - previousMillis > interval) {    // 点亮LED的时间超过interval指定的毫秒数

      //填写报文

      Txbyte[0]=0x21;

      Txbyte[1]=0x00;

      Txbyte[2]=0x00;

      txmss(Txbyte,MLEN);  //发送报文

      brightness=0;  //记录当前亮度值为0

      analogWrite(LedPin, brightness); //用PWM方式,brightness设置灯亮度为0,即关灯

    }

  }

}

    LU-ASR01端仍然采用“天问block”的图形化编程平台,为方便大家分析程序,采用了与Arduino完全相同的程序结构,子程序名、变量名和数组名也完全相同,就不再做分析介绍了。下面是在“天问block”上的完整程序:

 

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

猜你喜欢

转载自blog.csdn.net/m0_61543203/article/details/126331582
今日推荐