6_Project/Zigner

통신 알고리즘 - HC12 테스트

Mi:sAng 2023. 12. 3. 00:03

*Notice

일단 delay()값을 어떻게 주느냐에 따라서 통신의 품질이 정해진다.

아래사진처럼 일부 데이터가 누락된다. 

또한 모터와 서보모터 동작 부 또한 추가되면 통신부와 동작부의 멀티태스킹이 필요하다. 

따라서 유닛을 나눌 필요가 있다. Nano2개를 Flight에 사용하는 것을 고려할 필요가 있다. 

이는 추후 고민해야할 부분이고 일단 통신에 성공하였다.

 

*성공한 통신

 

*Flight-20231202

#include <SoftwareSerial.h>
#include <string.h>
SoftwareSerial HC12(2,3);
//https://rasino.tistory.com/326
//https://docs.arduino.cc/learn/built-in-libraries/software-serial
//https://blog.naver.com/darknisia/220808977305
//https://www.youtube.com/watch?v=B6qdNjtGfXE
/*
listen() : 수신 대기 상태
소프트웨어 시리얼 통신의 Rx핀으로 데이터 수신이 올 때 까지 대기한다
여러 소프트웨어 시리얼을 사용하는 경우 앞서 말했듯이 한 번에 하나의 통신만이
가능하므로 반드시 원하는 포트를 수신 대기 생태로 지정해 주어야 한다.
그리고 데이터 수신이 오게 되며 이 함수는 true를 반환하고 대기상태라면 
false 를 반환한다.

isListening()  : 수신 대기 여부 반환
소프트웨어 시리얼이 수신 대기 상태인지 아닌지를 반환하는 함수다.

read()
데이터 수신 버퍼의 첫 번째 문자를 반환하는 함수로 앞의 peek 함수랑 같은 기능을 가진다.
하지만 name.read() 함수는 수신 버퍼에서 반환한 데이터를 제거한다 

name.readBytes(buffer, length)
이 함수는 지정한 개수만큼 읽어와 버퍼에 저장하는 함수다.
길이가 길거나 정해진 데이터를 수신할 때 유용하다
만약 지정한 개수만큼 데이터가 수신되지 않을 경우 수신된 개수만큼만 저장하고
그 개수를 반환한다.

name.write(character)
바이트 단위의 데이터를 전송한다. 
하지만 Serial 클래스와 달리 바이트 배열을 한 번에 전송하는 기능은 없어 한 번에 
하나의 데이터만을 전송할 수 있다. 
그렇기 때문에 반환되는 값이 전송한 바이트 수인데 0과 1만이 반환된다. 

name.avaiable() 
수신 버퍼에 저장되어있는 데이터의 바이트수를 반환한다. 
만약 소프트웨어 시리얼이 수신 대기 상태에 있지 않거나 버퍼가 비어 있는
경우 0을 반환한다. 
일반적으로 데이터를 받기를 기다릴 때 if 문을 이용하여 많이 사용된다. 

Serial.end();
HC12.end(); 있다
*/

struct actingData {
  //QJ,96(모터세기(%)) ,LU(서보LEFT) ,RD(서보 RIGHT ),BU(서보 BACK) ,E(END BYTE)

  int motorSpeed=0; // 0~100(%)
  String servoLeft="";  //LU/LD, RU/RD, BU/BD
  String servoRight="";
  String servoBehind="";
};

struct sensorData{
  //QS,100(고도),100(배터리 (%)),x10(자이로),x10(가속),y20(자이로),y20(가속),z30(자이로),z30(가속),E(End Byte)

  double altitude=10; 
  double battery=20; 
  double acX=30; 
  double acY=40;
  double acZ=50;
  double gyX=60;
  double gyY=70;
  double gyZ=80;
};


struct actingData actData;
struct sensorData senData;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  HC12.begin(9600);
  Serial.println("Flight Site");


}



void loop() {//
  // put your main code here, to run repeatedly:
  int loop1Count=0;
  int loop2Count=0;
  //0.일단 수신
  String input = HC12.readString(); //1번신호 받음
  Serial.print("[ Receiving Section ]");
  Serial.println(input);
  String value = "";

  //1.통신체크 신호
  if(input =="RDYJOY1\r\n"){//통신 체크용
    Serial.print("Receive  RDYJOY1\r\n : ");
    Serial.println(input);
    Serial.println("Sent RDYJOY2");
    HC12.println("RDYJOY2");
    delay(100);
  }
  //2.조이스틱 값을 받고 센서값을 보낸다.
  else if(dataFormatCheck(input)==1){//조이스틱 포멧 맞으면 
    //1)받은 조이스틱 데이터 
    Serial.print("[ Receive Acting Value ] ");
    Serial.println(input); 
    
    //2)조이스틱 데이터 다듬기 
    int start1= input.indexOf("QJ");
    int start2= input.indexOf("E");
    if(start1!=-1 || start2!=-1){
      value= input.substring(start1,start2+1);//value : 다듬은 데이터

    }
    else{
      Serial.println("Wrong Data!");
    }

    //3)데이터 모터 구조체에 넣기
    getDataFromAct(&actData,value);
    Serial.println("[ Put the Joystick Value in struct]");
    Serial.println("[ "+ String(actData.motorSpeed)+" ]");
    Serial.println("[ "+ actData.servoLeft +" ]");
    Serial.println("[ "+ actData.servoRight+" ]");
    Serial.println("[ "+ actData.servoBehind+" ]");
    //4)센서값 전송
      //일단 구조체에서 센서값을 형식에 맞게 만들어야함 
    String str=sensorStrMaker(&senData);
    HC12.println(str); //센서값 전달 
    Serial.print("[ Send Sensor Value ]");
    Serial.println(str);
    Serial.println("/////////////////////////////////////////////////////////");     
    delay(50);
  }
  /*
  모터동작
  */
}
String sensorStrMaker(struct sensorData *senData){
  //QS,100(고도),100(배터리 (%)),x10(자이로),x10(가속),y20(자이로),y20(가속),z30(자이로),z30(가속),E(End Byte)
  //double형을 문자형으로 변환해야함
  /*
    double altitude=0; 
  double battery=0; 
  double acX=0; 
  double acY=0;
  double acZ=0;
  double gyX=0;
  double gyY=0;
  double gyZ=0;
  */
  String str1 = "QS,"+String(senData->altitude)+","+String(senData->battery)+","+String(senData->gyX)+","+String(senData->acX)+",";
  String str2 = String(senData->gyY)+","+String(senData->acY)+","+String(senData->gyZ)+","+String(senData->acZ)+",E";
  String str3 = str1+str2;
  return str3;
}

String joyStrMaker(struct actingData *actData){
  //QJ,96(모터세기(%)) ,LU(서보LEFT) ,RD(서보 RIGHT ),BU(서보 BACK) ,E(END BYTE)
  /*
  int motorSpeed=0; // 0~100(%)
  String servoLeft="";  //LU/LD, RU/RD, BU/BD
  String servoRight="";
  String servoBehind="";
  */
  String str= "QJ,"+String(actData->motorSpeed)+","+actData->servoLeft+","+actData->servoRight+","+actData->servoBehind+",E";
  return str;

}

void getDataFromSen(struct sensorData *senData,String strData){
  //QS,100(고도),100(배터리 (%)),x10(자이로),x10(가속),y20(자이로),y20(가속),z30(자이로),z30(가속),E(End Byte)

  int count1 = strData.indexOf(",");// 첫 번째 콤마 위치
  int count2 = strData.indexOf(",",count1+1); // 두 번째 콤마 위치
  int count3 = strData.indexOf(",",count2+1);
  int count4= strData.indexOf(",",count3+1);
  int count5= strData.indexOf(",",count4+1);
  int count6 =strData.indexOf(",",count5+1);
  int count7=strData.indexOf(",",count6+1);
  int count8 =strData.indexOf(",",count7+1);
  int count9 =strData.indexOf(",",count8+1);


  String startByte=strData.substring(0,count1);
  String altitude1=strData.substring(count1+1,count2); //pc수신의 경우 N
  String battery1=strData.substring(count2+1,count3);  //pc수신의 경우 N
  String acXX=strData.substring(count3+1,count4); //무인기가 수신의 경우 N
  String gyXX=strData.substring(count4+1,count5); //무인기가 수신의 경우 N
  String acYY=strData.substring(count5+1,count6); //무
  String gyYY=strData.substring(count6+1,count7); //무
  String acZZ=strData.substring(count7+1,count8); //무
  String gyZZ=strData.substring(count8+1,count9); //무
  String endByte=strData.substring(count9+1,strData.length()); //무
  
  senData->altitude = altitude1.toDouble();
  senData->battery = battery1.toDouble();
  senData->acX=acXX.toDouble();
  senData->gyX=gyXX.toDouble();
  senData->acY=acYY.toDouble();
  senData->gyY=gyYY.toDouble();
  senData->acZ=acZZ.toDouble();
  senData->gyZ=gyZZ.toDouble();
}
void getDataFromAct(struct actingData *actData,String strData){
  // QJ,96(모터세기(%)) ,LU(서보LEFT) ,RD(서보 RIGHT ),BU(서보 BACK) ,E(END BYTE)
  int first = strData.indexOf(",");// 첫 번째 콤마 위치
  int second = strData.indexOf(",",first+1); // 두 번째 콤마 위치
  int third = strData.indexOf(",",second +1);
  int fourth = strData.indexOf(",", third+1);
  int fifth = strData.indexOf(",",fourth+1);
  //int sixth =strData.indexOf(",",fifth+1);
  String startByte=strData.substring(0,first);
  String motorSpeed=strData.substring(first+1,second); //pc수신의 경우 N
  String servoLeft1=strData.substring(second+1,third);  //pc수신의 경우 N
  String servoRight1=strData.substring(third+1,fourth); //무인기가 수신의 경우 N
  String servoBehind1=strData.substring(fourth+1,fifth); //무인기가 수신의 경우 N
  String endByte=strData.substring(fifth+1,strData.length()); //무


  actData->motorSpeed = motorSpeed.toInt();
  actData->servoLeft=servoLeft1;
  actData->servoRight=servoRight1;
  actData->servoBehind=servoBehind1;
  

}
int dataFormatCheck(String data ){
  //return 1  : 앞 두 문자가 QJ이면 조이스틱값
  //return 2  : 앞 두 문자가 QS이면 센서값
  //return 3  : 앞 세 문자가 RDY이면 통신체크 신호
  //return -1 : 잘못된 문자열
  //indexOf는 찾는 문자열이 없으면 -1
  int joy1= data.indexOf("QJ");
  int sen1= data.indexOf("QS");
  int rdy1= data.indexOf("QRDY");

  if(joy1 != -1){
    return 1;
  }
  else if(sen1 !=-1){
    return 2;
  }
  else if(rdy1 !=-1){
    return 3;
  }
  else{
    return -1;
  }
}

 

 

*PC_20231202 

#include <SoftwareSerial.h>
#include <String.h>
SoftwareSerial HC12(2,3);
//https://rasino.tistory.com/326
//https://docs.arduino.cc/learn/built-in-libraries/software-serial
//https://blog.naver.com/darknisia/220808977305
//https://www.youtube.com/watch?v=B6qdNjtGfXE
/*
listen() : 수신 대기 상태
소프트웨어 시리얼 통신의 Rx핀으로 데이터 수신이 올 때 까지 대기한다
여러 소프트웨어 시리얼을 사용하는 경우 앞서 말했듯이 한 번에 하나의 통신만이
가능하므로 반드시 원하는 포트를 수신 대기 생태로 지정해 주어야 한다.
그리고 데이터 수신이 오게 되며 이 함수는 true를 반환하고 대기상태라면 
false 를 반환한다.

isListening()  : 수신 대기 여부 반환
소프트웨어 시리얼이 수신 대기 상태인지 아닌지를 반환하는 함수다.

read()
데이터 수신 버퍼의 첫 번째 문자를 반환하는 함수로 앞의 peek 함수랑 같은 기능을 가진다.
하지만 name.read() 함수는 수신 버퍼에서 반환한 데이터를 제거한다 

name.readBytes(buffer, length)
이 함수는 지정한 개수만큼 읽어와 버퍼에 저장하는 함수다.
길이가 길거나 정해진 데이터를 수신할 때 유용하다
만약 지정한 개수만큼 데이터가 수신되지 않을 경우 수신된 개수만큼만 저장하고
그 개수를 반환한다.

name.write(character)
바이트 단위의 데이터를 전송한다. 
하지만 Serial 클래스와 달리 바이트 배열을 한 번에 전송하는 기능은 없어 한 번에 
하나의 데이터만을 전송할 수 있다. 
그렇기 때문에 반환되는 값이 전송한 바이트 수인데 0과 1만이 반환된다. 

name.avaiable() 
수신 버퍼에 저장되어있는 데이터의 바이트수를 반환한다. 
만약 소프트웨어 시리얼이 수신 대기 상태에 있지 않거나 버퍼가 비어 있는
경우 0을 반환한다. 
일반적으로 데이터를 받기를 기다릴 때 if 문을 이용하여 많이 사용된다. 

Serial.end();
HC12.end(); 있다
*/
struct actingData {
  //QJ,96(모터세기(%)) ,LU(서보LEFT) ,RD(서보 RIGHT ),BU(서보 BACK) ,E(END BYTE)

  int motorSpeed=10; // 0~100(%)
  String servoLeft="LU";  //LU/LD, RU/RD, BU/BD
  String servoRight="RU";
  String servoBehind="BU";
};

struct sensorData{
  //QS,100(고도),100(배터리 (%)),x10(자이로),x10(가속),y20(자이로),y20(가속),z30(자이로),z30(가속),E(End Byte)

  double altitude=0; 
  double battery=0; 
  double acX=0; 
  double acY=0;
  double acZ=0;
  double gyX=0;
  double gyY=0;
  double gyZ=0;
};


struct actingData actData;
struct sensorData senData;

int loop1;
int loop2;
int loop3;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  HC12.begin(9600);
  Serial.println("PC Site");
  loop1=0;
  loop2=1;
  loop3=0;
}


int loopCount=0;

void loop() {
  // put your main code here, to run repeatedly:
  int loop1Count=0;
  int loop2Count=0;

  //1.통신체크용 시그널
  while(loop1==1){//통신체크용
    HC12.println("RDYJOY1");//신호 보냄
    Serial.println("Sent RDYJOY1");
    String getto = HC12.readString();//신호 받음
    
    int start1= getto.indexOf("RDY");
    int start2= getto.indexOf("E");
    String input ="";
    if(start1!=-1 || start2!=-1){
      input= input.substring(start1,start2+1);// value :다듬은 데이터
      Serial.println(input);
    }
    else{
      Serial.println("Wrong Data! : Joystick");
    }
    delay(100);
 
    if(input == "RDYJOY2\r\n"){//신호 옳게 받음 
      Serial.println("Received RDYJOY2\r\n");
    }
    else{
      Serial.println("NOT Receive : RDYJOY2\r\n");
 
    }
    delay(100);
    Serial.print("loop1Count : ");
    Serial.println(loop1Count);
    loop1Count++;

  }

  //2.조이스틱 값보내고 센서 값 받음 
  while(loop2==1){
    String output = joyStrMaker(&actData);//조이스틱 구조체 가져옴
    HC12.println(output);//조이스틱 정보 보냄
    Serial.print("Sent :");
    Serial.println(output);

    String getto = HC12.readString();//센서 값 받음
    Serial.println(getto);
    delay(60);
    String input="";
    //센서값의 형식을 체크한다
    if(dataFormatCheck(getto)==2){//센서값 포멧 맞으면 
     
      int start1= getto.indexOf("QS");
      int start2= getto.indexOf("E");
      
      if(start1!=-1 || start2!=-1){
        input= input.substring(start1,start2+1);// value :다듬은 데이터
        Serial.println("Sensor Data Format [Correct]");//잘 전달됨
        Serial.println(input);
        getDataFromSen(&senData,input);
      } 
      else{
        Serial.println("Sensor Data Format [Not Correct]");//잘 전달 안됨
      }
    }
    else {
      Serial.println("Sensor Data Format [Not Correct]");//전달받은게 없거나 잘못 전달받음.
    }
    delay(40);
    Serial.print("loop2Count : ");
    Serial.println(loop2Count);
    loop2Count++;
  }


  Serial.print("loopCount : ");
  Serial.println(loopCount);
  loopCount++;
}

String sensorStrMaker(struct sensorData *senData){
  //QS,100(고도),100(배터리 (%)),x10(자이로),x10(가속),y20(자이로),y20(가속),z30(자이로),z30(가속),E(End Byte)
  //double형을 문자형으로 변환해야함
  /*
    double altitude=0; 
  double battery=0; 
  double acX=0; 
  double acY=0;
  double acZ=0;
  double gyX=0;
  double gyY=0;
  double gyZ=0;
  */
  String str1 = "QS,"+String(senData->altitude)+","+String(senData->battery)+","+String(senData->gyX)+","+String(senData->acX)+",";
  String str2 = String(senData->gyY)+","+String(senData->acY)+","+String(senData->gyZ)+","+String(senData->acZ)+",E";
  String str3 = str1+str2;
  return str3;
}

String joyStrMaker(struct actingData *actData){
  //QJ,96(모터세기(%)) ,LU(서보LEFT) ,RD(서보 RIGHT ),BU(서보 BACK) ,E(END BYTE)
  /*
  int motorSpeed=0; // 0~100(%)
  String servoLeft="";  //LU/LD, RU/RD, BU/BD
  String servoRight="";
  String servoBehind="";
  */
  String str= "QJ,"+String(actData->motorSpeed)+","+actData->servoLeft+","+actData->servoRight+","+actData->servoBehind+",E";
  return str;

}

void getDataFromSen(struct sensorData *senData,String strData){
  //QS,100(고도),100(배터리 (%)),x10(자이로),x10(가속),y20(자이로),y20(가속),z30(자이로),z30(가속),E(End Byte)

  int count1 = strData.indexOf(",");// 첫 번째 콤마 위치
  int count2 = strData.indexOf(",",count1+1); // 두 번째 콤마 위치
  int count3 = strData.indexOf(",",count2+1);
  int count4= strData.indexOf(",",count3+1);
  int count5= strData.indexOf(",",count4+1);
  int count6 =strData.indexOf(",",count5+1);
  int count7=strData.indexOf(",",count6+1);
  int count8 =strData.indexOf(",",count7+1);
  int count9 =strData.indexOf(",",count8+1);


  String startByte=strData.substring(0,count1);
  String altitude1=strData.substring(count1+1,count2); //pc수신의 경우 N
  String battery1=strData.substring(count2+1,count3);  //pc수신의 경우 N
  String acXX=strData.substring(count3+1,count4); //무인기가 수신의 경우 N
  String gyXX=strData.substring(count4+1,count5); //무인기가 수신의 경우 N
  String acYY=strData.substring(count5+1,count6); //무
  String gyYY=strData.substring(count6+1,count7); //무
  String acZZ=strData.substring(count7+1,count8); //무
  String gyZZ=strData.substring(count8+1,count9); //무
  String endByte=strData.substring(count9+1,strData.length()); //무
  
  senData->altitude = altitude1.toDouble();
  senData->battery = battery1.toDouble();
  senData->acX=acXX.toDouble();
  senData->gyX=gyXX.toDouble();
  senData->acY=acYY.toDouble();
  senData->gyY=gyYY.toDouble();
  senData->acZ=acZZ.toDouble();
  senData->gyZ=gyZZ.toDouble();
}

void getDataFromAct(struct actingData *actData,String strData){
  // QJ,96(모터세기(%)) ,LU(서보LEFT) ,RD(서보 RIGHT ),BU(서보 BACK) ,E(END BYTE)
  int first = strData.indexOf(",");// 첫 번째 콤마 위치
  int second = strData.indexOf(",",first+1); // 두 번째 콤마 위치
  int third = strData.indexOf(",",second +1);
  int fourth = strData.indexOf(",", third+1);
  int fifth = strData.indexOf(",",fourth+1);
  //int sixth =strData.indexOf(",",fifth+1);
  String startByte=strData.substring(0,first);
  String motorSpeed=strData.substring(first+1,second); //pc수신의 경우 N
  String servoLeft1=strData.substring(second+1,third);  //pc수신의 경우 N
  String servoRight1=strData.substring(third+1,fourth); //무인기가 수신의 경우 N
  String servoBehind1=strData.substring(fourth+1,fifth); //무인기가 수신의 경우 N
  String endByte=strData.substring(fifth+1,strData.length()); //무


  actData->motorSpeed = motorSpeed.toInt();
  actData->servoLeft=servoLeft1;
  actData->servoRight=servoRight1;
  actData->servoBehind=servoBehind1;
  

}
int dataFormatCheck(String data ){
  //return 1  : 앞 두 문자가 QJ이면 조이스틱값
  //return 2  : 앞 두 문자가 QS이면 센서값
  //return 3  : 앞 세 문자가 RDY이면 통신체크 신호
  //return -1 : 잘못된 문자열
  //indexOf는 찾는 문자열이 없으면 -1
  int joy1= data.indexOf("QJ");
  int sen1= data.indexOf("QS");
  int rdy1= data.indexOf("QRDY");

  if(joy1 != -1){
    return 1;
  }
  else if(sen1 !=-1){
    return 2;
  }
  else if(rdy1 !=-1){
    return 3;
  }
  else{
    return -1;
  }
}