Arduino Playground is read-only starting December 31st, 2018. For more info please look at this Forum Post

(Cet article contient quelques erreurs avec les caractères spéciaux à corriger)

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ées 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ées analogiques à surveiller. Si dans l'intervalle spécifiée, la valeur de l'entrée analogique (AI) a changé, il envoie sur le port série un message contenant la valeur de l'AI qui change 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預C, ce projet est fait en C# et a pour but de cr饲 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褥r aux ressources de la carte : Ex, une trackbar permet de contr? l'鴡t de sortie d'une sortie PWM.

Enfin en ce qui concerne le protocole d'飨ange 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 ࠬa pin '12' d'effectuer un pulse 'P' de longueur '1250'ms.

Le protocole

Actuellement, le protocole suivant est impl魥nt頺

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

'D' : Met la DO (digital output) ࠬa valeur pass饠en param贲e. Dans le cas d'une DO simple, les valeurs support饠sont 0 (鴡t LOW) et 255 (鴡t 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饠en entr饬 et n'est pas utilis饠en tant que sortie.
  • 1 correspond on mode 'input' : le DI sera scrut饠afin de detecter ses changement d'鴡ts.
  • 2 correspond au mode 'output', la DO sera utilis饠en tant que sortie.

'M' (Mesure) : Indique si l'AI concern饠doit 괲e scrut饠ou non.

  • 0 : La AI sera ignor饮
  • 1 : La AI sera scrut饮

'R' (Rate) : Indique un temps minimum (en ms) en de硠duquel un changement de valeur de l'AI sera ignor鮊 'P' (Pulse) : Indique ࠬa carte de g鮩rer un pulse (passage en 鴡t HIGH puis en LOW), d'une longueur pass饠en param贲e. Cette commande est tr鳠utile pour le controle de servos-moteurs.

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

'D' : indique un changement d'鴡t de la DI concern饮

'A' : indique un changement de valeur de l'AI concern饮

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 à dire sur ce code, et des optimisations à faire, mais j'ai voulu privilegier la facilité de lecture.

Pascal BUIREY.