Introduction

Ce projet a pour but de faciliter la création d'applications permettant de contrôler une carte arduino depuis un PC, via le cable usb. Pour cela, ce projet comporte trois parties :

  • Une partie embarquée, qui va tourner sur la carte.
  • Une partie PC, qui permet de créer des applications qui vont communiquer avec la carte.
  • Un protocole commun aux deux autres parties qui permet d'envoyer des ordres à la carte et de remonter des mesures issues des entrés analogiques (AI: Analog Input) et digitales (Digital input).

Principe général

Un programme tourne en boucle sur la carte Arduino. Ce programme fait en permanence 3 choses :

  • Il scrute les entrées digitales (DI) à surveiller. Si une de ces entrées a changé, il envoie sur le port série un message contenant la nouvelle valeur de cette DI, ainsi que son identité (numéro de pin).
  • Il scrute les entrés analogiques à surveiller. Si dans l'interval spécifié, la valeur de l'entrée analogique (AI) a changée, il envoie sur le port série un message contenant la valeur de l'AI qui changé ainsi que son identité (numéro de pin).
  • Il scrute le port série. Si un message arrive, il le décode, et effectue l'action correspondante.

Coté PC, ce projet est fait en C# et a pour but de créer des composants .net permettant de faire facilement des applications utilisant l'arduino. Le composant fondamental du projet est ArduinoCtrl. Ce composant permet de faire le lien entre l'application et la carte. Plusieurs controles permettent d'accèder aux ressources de la carte : Ex, une trackbar permet de contrôler l'état de sortie d'une sortie PWM.

Enfin en ce qui concerne le protocole d'échange entre les deux parties, il s'agit d'un protocole simple sur le principe suivant : un message "O/P/V" indique un ordre 'O', concerne la pin 'P', avec la valeur 'V'. Un exemple : P/12/1250 indique à la pin '12' d'effectuer un pulse 'P' de longueur '1250'ms.

Le protocole

Actuellement, le protocole suivant est implémenté :

Du PC vers l'Arduino, on dispose des ordres suivants :

'D' : Met la DO (digital output) à la valeur passée en paramètre. Dans le cas d'une DO simple, les valeurs supportée sont 0 (état LOW) et 255 (état HIGH). Dans le cas d'un DO PWM, toutes les valeurs entre 0 et 255 sont valables.

'S' (Set) : Indique le changer le mode d'une DIO.

  • 0 correspond au mode 'ignore' : La DI n'est pas scrutée en entrée, et n'est pas utilisée en tant que sortie.
  • 1 correspond on mode 'input' : le DI sera scrutée afin de detecter ses changement d'états.
  • 2 correspond au mode 'output', la DO sera utilisée en tant que sortie.

'M' (Mesure) : Indique si l'AI concernée doit être scrutée ou non.

  • 0 : La AI sera ignorée.
  • 1 : La AI sera scrutée.

'R' (Rate) : Indique un temps minimum (en ms) en deça duquel un changement de valeur de l'AI sera ignoré.

'P' (Pulse) : Indique à la carte de générer un pulse (passage en état HIGH puis en LOW), d'une longueur passée en paramètre. Cette commande est trés utile pour le controle de servos-moteurs.

De l'arduino, vers le PC, on a simplement :

'D' : indique un changement d'état de la DI concernée.

'A' : indique un changement de valeur de l'AI concernée.

Voici le code embarqué :

/*
 * UsbControl
 * by Pascal BUIREY
 *
 * embedded software to communicate with monitoring software
 * messages are in the following format :
 * arduino => PC
 * 1/N/V A/N/V Send the Analog value V read from pin N
 * 2/N/V D/N/V Send the Digital value V read from pin N
 * 64/N/V Send return value V  for initialisation  on device N
 * 65/N/V Send the byte V read on SPI Device #N
 *
 * PC => arduino
 * 0/N/V S/N/V Set the pinmode for the specified pin 0=ignore, 1=INPUT, 2=OUTPUT
 * 1/N/V M/N/V set the Measure pin for INPUT (1) or ignore(0);
 * 2/N/V D/N/V write the value V on digital pin N 0=LOW, 255=HIGH. 
 * 99/N/V T/N/V activate/deactivate traces
 * intermediate values are possible for PWM digital IOs.
 *
 * SPI commands :
 * 6/N/V : Declare SPI device N is ignored V=SPI Id (0-3);
 * 60/N/V :setup SPI Device #N Chip Select pin = V;
 * 61/N/V :setup SPI Device #N Clock pin = V;
 * 62/N/V :setup SPI Device #N MISO pin = V;
 * 63/N/V :setup SPI Device #N MOSI pin = V;
 * 64/N/V :initialize SPI Device (pins must be setup)
 * 65/N/V :on SPI Device #N, write the byte V
 *
 */

//SPI opcodes
#define WREN  6
#define WRDI  4
#define RDSR  5
#define WRSR  1
#define READ  3
#define WRITE 2 

class SPIDevice {
  private :
  byte clr;

  public:
  short MOSIPin; //Master Out Slave In
  short MISOPin; //Master In Slave Out
  short ClockPin; //Clock
  short ChipSelectPin; //CS Chip Select

  SPIDevice() {
    MOSIPin = -1;
    MISOPin = -1;
    ClockPin = -1;
    ChipSelectPin = -1;    
    }

    byte SPI_Init() {
      short ret_val=0;
    //verify that pins have been setup      
      if ((MOSIPin==-1)||
        (MISOPin == -1)||
        (ClockPin == -1)||
        (ChipSelectPin == -1)) {
          return 0;
      }

      //Setup SPI Pins
      pinMode(MOSIPin, OUTPUT);
      pinMode(MISOPin, INPUT);
      pinMode(ClockPin,OUTPUT);
      pinMode(ChipSelectPin,OUTPUT);
      digitalWrite(ChipSelectPin,HIGH); //disable device 

      //Setup SPI Protocol
      // SPCR = 01010000
      //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
      //sample on leading edge of clk,system clock/4 rate (fastest)
      SPCR = (1<<SPE)|(1<<MSTR);
      clr=SPSR;
      clr=SPDR;
      delay(10);
     return(1); 
    }

    // transfer Data to/from SPI Device
    char spi_transfert(volatile char data)
    {
      SPDR = data;                    // Start the transmission
      while (!(SPSR & (1<<SPIF)))     // Wait for the end of the transmission
      {
      };
      return SPDR;                    // return the received byte
    }

    char spi_write(char c) {
      spi_transfert(c);
    }

    char spi_read() {
      return spi_transfert(0XFF);
    }

    ~SPIDevice() {
      }
  };


/*pin modes stored as an array of bytes 0=ignore 1=input, 2=output*/
byte diPinMode[14];
byte aiPinMode[6];
int dioPinValues[14];
int aiPinValues[6];
int aiRefreshRate[6];
unsigned long aiLastRefresh[6];

//support up to 4 SPI devices
SPIDevice *SPIDevices[4];

/*incoming message from monitoring*/
/*maximum message size*/
char sIncomingMsg[255];
/*current index of received chars*/
int iCurrentMsgIdx;
boolean bMsgComplete;
boolean bTraceOn;

void Trace(char* text) {
  if (bTraceOn) {
    Serial.print("TRACE : ");
    Serial.println(text);
    }
  }

/*sendToMonitoring*/
/*send data for monitoring software*/
/*pinType = 'D' for digitals or 'A' for analogs*/
void sendToMonitoring(char pinType,byte pinNumber,unsigned int value) {
  Serial.print(pinType);
  Serial.print("/");
  Serial.print(pinNumber,DEC);
  Serial.print("/");
  Serial.println(value,DEC);
}

void check4DIOChanges() {
  /*pins 0 and 1 are used for serial communication*/
  for (int i=2;i<14;i++) {
    /*if this is not an output pin*/
    if (diPinMode[i]==1) {
      int newval = digitalRead(i);
      /*if the value for that pin as changed*/
      if (newval!=dioPinValues[i]) {
          /*send the new value to monitoring software*/
          sendToMonitoring('D',i,newval);
          /*store the new value*/
          dioPinValues[i]=newval;        
        }
      }
    }
  }

void check4AIChanges() {
  for (int i=0;i<6;i++) {
    if (aiPinMode[i]==1) {
      unsigned long curTime = millis();
      if ((aiLastRefresh[i]+aiRefreshRate[i])<curTime) {
        aiLastRefresh[i]=curTime;
      int newval = analogRead(i);
      /*if the value for that pin as changed*/
      if (newval!=aiPinValues[i]) {
        /*send the new value to monitoring software*/
          sendToMonitoring('A',i,newval);
          /*store the new value*/
          aiPinValues[i]=newval;          
        }
        }
      }
      }
    }

/*parse the incoming message and perform corresponding action*/
void parseIncomingMsg(char *msg) {
  int pinNumber = 0;
  int value=0;
  int idx=0;
  /*get the pin number*/
  /* for debug*/
  Trace("Parsing ");
  Trace(msg);

  //read the Command... 
  while (msg[idx]!='/') {
    idx++;
    }
  idx++;

  while (msg[idx]!='/') {
    pinNumber=pinNumber*10+msg[idx]-'0';
    idx++;    
    }
  idx++;
    //for debug only 

    /*get the value*/
  while (msg[idx]!='\0') {
    if ((msg[idx]>='0')&&(msg[idx]<='9')) {
      value=value*10+msg[idx]-'0';
    }
    idx++;

    }

  switch(msg[0]) {
    /*incoming order to output on a DIO */
    case '?' : {
        Serial.println("Arduino");
      }
    case 'D' : {
      if((value == 255)||(value==0)) {
        if (value==255) {
          digitalWrite(pinNumber,HIGH);
        }
        if (value==0) {
          digitalWrite(pinNumber,LOW);
        }
      }else{
         analogWrite(pinNumber,value);
      }               
      };break;
      /*incoming ordre for changing pinmode*/
    case 'S' : {
      /*ignored pin*/
      if (value==0) {
        diPinMode[pinNumber]=0;
        }
        /*set pin for reading*/
      if (value==1) {
        pinMode(pinNumber,INPUT);
        diPinMode[pinNumber]=1;
        }
        /*set pin for writing*/
        if (value==2) {
        pinMode(pinNumber,OUTPUT);
        diPinMode[pinNumber]=2;
        Trace("Pin set");
          }
      };break;
      case 'M' : {
        if (value ==0) {
          aiPinMode[pinNumber]=0;
          }
        if (value == 1) {
          aiPinMode[pinNumber]=1;
          }
        };break;
      case 'R' : {
        aiRefreshRate[pinNumber]=value;
      }  
      case 'P' : { /*P = Pulse value = pulse length (milliseconds)*/
        if (value !=0) {
          if (diPinMode[pinNumber]==2) {
            digitalWrite(pinNumber,HIGH);
            delayMicroseconds(value);
            digitalWrite(pinNumber,LOW);            
	    }
          }
        };break;
      case 'T' : { /*T = activate(1)/deactivate(0) traces*/
        bTraceOn=(value==1);
        Trace("Trace mode set");
        };break;
      /*default : wrong message*/
    default : {
      //for debug only
      Trace("Error parsing message : ");
      Trace(msg);
      };break;
    }
    Trace("Processed ok");
  }

void check4IncomingMsg() {
  /*if com is Ok ?*/
    int nbBytes =Serial.available(); 

    if (nbBytes>0) {
        char msg[nbBytes+1];
        /*read incoming bytes*/
        char c='\0';
        int i=0;
        for (i=0;(i<nbBytes)&&(c!=10);i++) {
          c = Serial.read();
          msg[i]=c;          
          }     
        /*10 indicates the end of a message*/
        if (c==10) {
          //msg[i]='\0';
          bMsgComplete=true;
        }
        /*add read bytes to current message*/
        for (int j=0;j<i;j++) {
          sIncomingMsg[iCurrentMsgIdx] = msg[j];
          iCurrentMsgIdx++;
          }
        if (bMsgComplete) {
          sIncomingMsg[iCurrentMsgIdx]='\0';
        // for debug only
        Trace("Received");
        Trace(sIncomingMsg);
        parseIncomingMsg(sIncomingMsg);
        /*message treated, reset the buffer*/
        bMsgComplete=false;
        iCurrentMsgIdx=0;
        }
    }
  }

void setup()
{
  // begin the serial communication
  Serial.begin(19200);

  bMsgComplete=false;
  iCurrentMsgIdx=0;

  /*Init all pins to INPUT, and their values to 0*/
  /*pins 0 and 1 are used in serial communication*/
  for (int i=2;i<14;i++) {
    diPinMode[i]=0;
    dioPinValues[i]=0;
    pinMode(i,INPUT);
    }

  /*init ai pins values*/
  for (int i=0;i<6;i++) {
    aiPinMode[i]=0;
    aiPinValues[i]=0;
    /*refresh every second by default*/
    aiRefreshRate[i]=1000;
    aiLastRefresh[i]=millis();
    }
}

void loop()
{
  /*check for changes on AI and DIO. If changes happen, send them to monitoring*/
  check4DIOChanges();  
  check4AIChanges();
  /*check for message coming from monitoring*/
  //delay(100);
  check4IncomingMsg();

}

Il y a surement pas mal à redire sur ce code, et des optimisations à faire, mais j'ai voulu privilegier la facilité de lecture.

Pascal BUIREY.

Share