Pages: [1]   Go Down
Author Topic: Sample Project: Data from Arduino to C++ Program for Windows  (Read 910 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello there community, long time lurker first time poster.  I have been working off-and-on for a LONG TIME trying to crack this problem, and now I've finally reached a point where I'm satisfied with the results.  I never quite seemed to be able to get a reliable way to transmit numbers from the Arduino so that I could actually use them in a C++ program.  Now I htink I finally figured out a way that works for me, and I though I'd share!

I will present an Arduino sketch and a small C++ program I developed in Code::Blocks and compiled with gcc.  The idea is to use the Arduino as a sensor and transmit the data reasonably quickly.  The PC-side program waits for a one-line text string, and once the entire string has made it, the PC processes the text and extracts numerical data.

CAVIAT:  When dealing with Windows API, you should always check your filehandles are valid before using them, otherwise your program might crash.  None of this checking is implemented here because it renders already verbose winapi code almost meaningless.  if(WindowsFunction(sz_SomeNastyPointerName) != ERROR_INVALID_FILE_WHATEVER) throw{GetLastError(){] meh you get the idea... it's ugly smiley but the code is free  smiley-mr-green

The text string is a fixed size and must be known by both PC and Arduino programs.  For the purpose of this example, imagine we have some sort of sensor hooked up to an ADC on the Arduino and it measures heading.  The sketch transmits every 16ms the following string: "HDGxxxOK" where "xxx" is the heading from 001 up to 360.  The key is that I know that string is 8 characters long.  (Nothing special about 8, just that's what it is.)

I haven't actually got such a sensor for my Arduino, so I fudge it.  I just use the math library to get an arbitrary sinusoid whose range crosses 100 to demonstrate how to take care of leading '0's.

The Arduino sketch (in my case a Duemilenova 328)
(No hardware required)
Code:
int tsamp_ms = 16;
double heading = 0;
int headingOnes = 0;
double headingPeriod = 2.0f;
double pi = 3.14159265359f;
double t = 0;
int t_ms = 0;
int t_ms_prev = 0;

void do16ms()
{
    // fudge some value for transmission
    heading = 15 * sin( 2*pi*t / headingPeriod )+90;

    headingOnes = (int)heading;

    Serial.print("HDG");

    if(headingOnes < 100)
        Serial.print("0");

    if(headingOnes < 10)
        Serial.print("0");

    Serial.print(headingOnes);

    Serial.println("OK");
}

void setup()
{
    Serial.begin (57600);
}

void loop()
{
    // A more efficient way to wait 16 ms
    t_ms = millis();
    t = (double)t_ms / 1000.0;

    if ( t_ms - t_ms_prev > (tsamp_ms-1) )
    {
        do16ms();
        t_ms_prev = t_ms;
    }
}

Ok, now since I'm using C++, I thought it wise to whip up a serial port class.  It doesn't do any PC-to-Arduino transmission, but you could shove some in there easily enough.  I never got good results using non-overlapped reads with the Windows API, so just take everything relating to it with a grain of salt.  My Arduino shows up as COM11 which apparently requires some crazy syntax, which is why I open "\\\\.\\COM11" so don't ask... smiley

Also, I am lazy and I just hard-coded my baud rate and port names into the class implementation.  I know that's terrible OO, but you're getting this code for free smiley-wink

Serial.h
Code:
#pragma once

#include <string>
#include <windows.h>

class Serial
{
public:
    Serial();
    ~Serial();
    void open();
    void read(char *buffer, int buffLen);
    void close();
private:
    HANDLE m_pComPortHandle;
    BOOL m_bOpened;
    DCB m_dcb;
    OVERLAPPED m_OverlappedRead;
};

Serial.cpp
Code:
#include "Serial.h"
#include <iostream>
using namespace std;

Serial::Serial()
{
    memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) );
}

Serial::~Serial()
{
    close();
}

void Serial::open()
{
    // reset DCB
    memset(&m_dcb,0,sizeof(m_dcb));
   
    // open the serial port
    m_pComPortHandle = CreateFile("\\\\.\\COM11", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
   
    // set up the overlapped event
    m_OverlappedRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
   
    // set the DCB
    GetCommState(m_pComPortHandle, &m_dcb);
    m_dcb.DCBlength = sizeof(m_dcb);
    m_dcb.BaudRate = 57600;
    m_dcb.Parity = NOPARITY;
    m_dcb.StopBits = ONESTOPBIT;
    m_dcb.ByteSize = 8;
    SetCommState(m_pComPortHandle, &m_dcb);
}

void Serial::read(char *buffer, int dwBytesToRead)
{
    BOOL bReadStatus;
    DWORD dwBytesRead, dwErrorFlags;
    COMSTAT comStat;

    // Empty the input buffer
    PurgeComm(m_pComPortHandle, PURGE_RXCLEAR);

    // Set comStat.cbInQue to zero (number of bytes in the inut buffer)
    ClearCommError( m_pComPortHandle, &dwErrorFlags, &comStat );

    // Get the number of bytes in the input buffer
    dwBytesRead = (DWORD) comStat.cbInQue;

    // Wait until the unput buffer is full
    while(dwBytesRead <= dwBytesToRead)
    {
        ClearCommError( m_pComPortHandle, &dwErrorFlags, &comStat );
        dwBytesRead = (DWORD) comStat.cbInQue;
    }

    // Read the input buffer
    bReadStatus = ReadFile( m_pComPortHandle, buffer, dwBytesToRead+2, &dwBytesRead, &m_OverlappedRead );

    // Make sure the unput buffer is enpty
    PurgeComm(m_pComPortHandle, PURGE_RXCLEAR);

    buffer[dwBytesToRead] = 0;
}

void Serial::close()
{
    if( m_OverlappedRead.hEvent != NULL )
        CloseHandle( m_OverlappedRead.hEvent );
   
    if( m_pComPortHandle != NULL )
        CloseHandle(m_pComPortHandle);
}

Ok so now how do you use this?  Well the idea is that the calling thread will hang until read() fill up the input buffer with the prescribed number of bytes.  Remember I want to receive "HDGxxxOK" and if anything gets garbled up I'm not interested in it.  No it's not the safest or best way, and could use with a good heal of exception/error handling.  I left most of that out because it tends to obfuscate the business logic really fast.  Again, free code.

So now I wrote a little int main() to get you started, but I've got this slated for its own thread in a larger application.

main.cpp
Code:
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include "Serial.h"
using namespace std;

void parseInput(char * buffer)
{
    string input;
    int heading;
    input = string(buffer);

    string firstThree = input.substr(0,3);
    string nextThree = input.substr(3,3);
    string lastTwo = input.substr(6,2);
    string hdgStr = string("HDG");
    string okStr = string("OK");
    if(!strcmp(firstThree.c_str(), hdgStr.c_str()) && !strcmp(lastTwo.c_str(),okStr.c_str()))
    {
        heading = atoi(nextThree.c_str());
        cout << "Heading = " << heading << endl;
    }
    else
    {
        // heading invalid
    }
}

int main()
{
    Serial serial;
    serial.open();
    Sleep(2000);
    const int buffLen = 8; // "HDGxxxOK" is 8 characters
    char buffer[buffLen];

    for( int iterCount = 0; iterCount < 150; iterCount++ ) // loop for a little while and quit
    {
        serial.read(buffer, buffLen); // Doesn't return until buffLen bytes are read
        parseInput(buffer);
    }
    return 0;
}

Questions, comments, suggestions for alternatives, all welcome.
Logged

Pages: [1]   Go Up
Jump to: