8 Aralık 2012 Cumartesi

Digital voltmeter – Arduino and PC (Visual C++) comunication via serial port


Today I will show how to make digital bridge between Arduino and PC: control analog – digital converter and send measured data to PC. Windows application will be created using Visual C++ 2008 Express.
Voltmeter demo software is very simple, and here is a lot room for improvement, but I just wanted to show basics how to control com port and execute data exchange between PC and Arduino.
Communication between Arduino and PC:
  • ADC measurement loop starts when PC sends 0xAC and 0x1y  to Arduino. Where y is ADC channel number(0-2);
  • Measurement stops after Arduino receives 0xAC and 0×00;
  • In measurement loop with 50ms delay Arduino sends 0xAB 0xaa 0xbb, where  aa and bb is higher and lower bytes of measurement result.
Arduino code
More about serial communication You could find at arduino.cc. Code is simple, the biggest part of it takes switch structure that takes action if data available at serial port buffer. If it’s so ADC conversion is started on selected channel, or stopped accordingly to received data.
After ADC conversion is finished there is 10 bit voltage value (0×0000-0×0400) in 16 bit variable (int). Serial (RS-232) protocol enables sending data in packets, where raw data can contain maximum 8 bits. It gives a need to split 16 bit variable in 2 parts of 8 bits. To get higher byte variable is shifted to right by 8 bits. To get lower byte variable is divided by 256 and remainder is taken.
    Serial.print(voltage>>8,BYTE);
    Serial.print(voltage%256,BYTE);
Full Arduino code:
//electronicsblog.net 
//Arduino communication with PC via serial port.

int voltage=0;
int channel =0;
unsigned char incomingByte = 0;
boolean measure=false;

void setup() {

  Serial.begin(9600);
}

void loop() { 

  if (measure) {

    voltage=analogRead(channel);
    Serial.print(0xAB,BYTE);
    Serial.print(voltage>>8,BYTE);
    Serial.print(voltage%256,BYTE);
    delay(50);

  }

  if (Serial.available() > 0) {
    delay(10);

    if(Serial.read()==0xAC) {
      incomingByte =Serial.read();

      switch (incomingByte) {

      case 0x10:
        measure=true;
        channel=0;
        break;

      case 0x11:
        measure=true;
        channel=1;
        break;

      case 0x12:
        measure=true;
        channel=2;
        break;

      case 0x00:
        measure=false;
        break;

      }

    }

  }                

};
Visual C++
I assume, that You already have a basic knowledge of Windows forms programing in C++,  if not when just Google, internet is full of examples for beginners.
First thing to do is add serial port resource from toolbar to area below form. Properties allows to change some important settings of  serial port: port name, baud rate, databits.  It’s more useful to add controls in application window to change these settings any time, without need to recompile program again.  “Voltmeter demo” application have only one of these control and it’s dropdown list of available ports.
After retrieval of available serial port names list the first port is selected as default. Code for that trick:

array< String ^>^ serialPorts = nullptr;
serialPorts = serialPort1->GetPortNames();
this->comboBox1->Items->AddRange(serialPorts);
this->comboBox1->SelectedIndex=0;
Serial port in PC could be used only by one application at one time, so port must be opened before use, and closed after no longer needed. Simple commands for that:

serialPort1->Open();
serialPort1->Close();
Reading serial port.  To do is properly, it’s necessary to  use “received data” event (interrupt) .  At serial port properties select events.
When double click “DataReceived” drop-down list.
Event’s function code is automatically generated:
private: System::Void serialPort1_DataReceived(System::Object^ sender, System::IO::Ports::SerialDataReceivedEventArgs^ e) {
}
Code from “Voltmeter demo”:
Where is a check to see if first byte arrived to serial port is 0xAB, if is it means, that other bytes carries voltage data. In other case blank reading is executed to clean serial port buffer.

private: System::Void serialPort1_DataReceived(System::Object^ sender, System::IO::Ports::SerialDataReceivedEventArgs^ e) {
unsigned char data0, data1;
if (serialPort1->ReadByte()==0xAB) {
data0=serialPort1->ReadByte();
data1=serialPort1->ReadByte();
voltage=Math::Round((float(data0*256+data1)/1024*5.00),2);
data_count++;


}
serialPort1->ReadByte();
}
Sending data/writing serial port.
Some problem for me here was to send raw hexadecimal data via serial port. Write(); command was used, but with 3 arguments: array, start byte number, number of bytes to write).

private: System::Void button2_Click_1(System::Object^ sender, System::EventArgs^ e) {
unsigned char channel=0;
channel=this->listBox1->SelectedIndex;
array^start ={0xAC,(0x10+channel)};
array^stop ={0xAC,0x00};
if (!adc_on) {
serialPort1->Write(start,0,2);
this->button2->Text="Stop";
adc_on=true;

} else {
serialPort1->Write(stop,0,2);
this->button2->Text="Start";
adc_on=false;}

}
That’s all what I wanted to share. If You need full Visual C++ code please ask it in comments, and i will upload it.


QUESTIONS
you use 2 pair of byte for read the max value in C++ of "99,99" but if i want to have a higher precision (for example a voltage with 4 numbers after the coma "99,9999") what i need to change?

Real higher precision can be achieved only is ADC have higher resolution. With 10 bit ADC there is 1024(2^10) steps, so if ADC reference(max value that can be measured) is 5 V, then measurement resolution is ~ 0.005 V. 
But for show you could have 4 numbers after coma.
Find this line in Form1.h 
voltage=Math::Round((float(data0*256+data1)/1024*5.00),2);
replace to voltage=Math::Round((float(data0*256+data1)/1024*5.00),4);
voltage now would be rounded to 4 numbers after coma instead of 2.





I want to use an ADC (LTC2400) of 24 bits. How can modify your application for this type of ADC. Thank you very much!

You need to connect ADC to Arduino via SPI interface. 
Arduino code should be modified to request data from external ADC and to send not 2, but 4 data bytes to PC.
PC software should be modified to process not 2, but 4 bytes of data.


Dear Darius, I adapted the following code for LTC2400 SPI. There are beginner, please help me PS software can bring value to the LTC2400 ADC. Thank you very much!
// ** ARDUINO CODE *********
#include <stdio.h>
#include <math.h>
#include <newsoftserial.h>
NewSoftSerial mySerial(6, 7);
#ifndef cbi
  #define cbi(sfr, bit)     (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
  #define sbi(sfr, bit)     (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#define ADC_CS   2   // ADC Chip Select Pin  on Portb 2 (10)
#define ADC_MISO 4   // ADC SDO  Select Pin  on Portb 4 (12)
#define ADC_SCK  5   // ADC SCK  Select Pin  on Portb 5 (13)
void setup() {
 mySerial.begin(9600);
 mySerial.flush();
 cbi(PORTB,ADC_SCK); // ADC SCK Low
 sbi (DDRB,ADC_CS);  // ADC CS High
 cbi (DDRB,ADC_MISO);
 sbi (DDRB,ADC_SCK);
 sbi(SPCR,MSTR) ; // SPI master mode
 sbi(SPCR,SPR0) ; // SPI speed
 sbi(SPCR,SPR1);  // SPI speed
 sbi(SPCR,SPE);   // SPI enable
}
//float v_ref=5.0;        
long ltw = 0;             // ADC Data ling int
//long valoare;
byte b0;                  
byte sig;                 // sign bit flag
unsigned char incomingByte = 0;
boolean measure=false;
int channel =0;
void loop() {
 if (measure) {
    while (mySerial.available() == 1); // do nothing if nothing sent
    cbi(PORTB,ADC_CS);                 // ADC CS Low
 delayMicroseconds(1);
 if (!(PINB & (1 << PORTB4))) {    // ADC Converter ready ?
   // cli();
   ltw=0;
   sig=0;
   b0 = SPI_read();             // read 4 bytes adc raw data with SPI
   if ((b0 & 0x20) ==0) sig=1;  // is input negative ?
   b0 &=0x1F;                   // discard bit 25..31
   ltw |= b0;
   ltw <<= 8;
   b0 = SPI_read();
   ltw |= b0;
   ltw <<= 8;
   b0 = SPI_read();
   ltw |= b0;
   ltw <<= 8;
   b0 = SPI_read();
   ltw |= b0;
   delayMicroseconds(1);
   sbi(PORTB,ADC_CS);             // LTC2400 CS Low
   delay(200);
   if (sig) ltw |= 0xf0000000;    // if input negative insert sign bit
   ltw= ltw / 16 ;
   //mySerial.print(ltw);
   mySerial.print(0xAB, BYTE);
   mySerial.print(ltw>>8, BYTE);
   mySerial.print(ltw%256, BYTE);
   delay(50);
 }
}
  if (mySerial.available() > 0) {
    delay(10);
    if(mySerial.read()==0xAC) {
      incomingByte = mySerial.read();
      switch (incomingByte) {
      case 0x10:
        measure=true;
        channel=0;
        break;
      case 0x11:
        measure=true;
        channel=1;
        break;
      case 0x12:
        measure=true;
        channel=2;
        break;
      case 0x00:
        measure=false;
        break;
      }
    }
  }           
 sbi(PORTB,ADC_CS); // LTC2400 CS hi
 delay(20);
}
/********************************************************************/
byte SPI_read()
{
 SPDR = 0;
   while (!(SPSR & (1 << SPIF))) ; 
 return SPDR;
}
</newsoftserial.h></math.h></stdio.h>
  • Avatar
    Darius Mod  basco  a year ago
    Sorry for my stupid mistake, 24 bit can be divided to 3 Bytes.
    Arduino code you wrote must be altered a little:
    ///////////////////////////////
      //mySerial.print(ltw);
       mySerial.print(0xAB, BYTE); // header byte
       mySerial.print(ltw>>16, BYTE); //1st byte
       mySerial.print(ltw>>8, BYTE); //2st byte
       mySerial.print(ltw%256, BYTE); //3st byte
       delay(50);
    ///////////////////////////////////////////
    Visual C++ code
    //////////////////////////////////////////////
    if (serialPort1->ReadByte()==0xAB) {
    data0=serialPort1->ReadByte();
    data1=serialPort1->ReadByte();
    data2=serialPort1->ReadByte();
    voltage=Math::Round((float(data0*65536+data1*256+data3)/16777216*5.00),5);
    //////////////////////////////////////////////
    16777216 is 2^32, ADC steps count
    5.00 V is reference voltage for ADC, it maybe different than this.

Hiç yorum yok: