Search

2012年5月26日 星期六

wxWidgets Connect Arduino(Linux)


    其實前陣子都在幫人做PC端控制PLC與接收一些電壓電流參數的軟體,只是想到還沒測試過Arduino的Serial Library,所以前幾天有空就先做Linux上的,其實Windows取得COM Port也不難API Call一下就好了,主要想幫Arduino與PIC寫類似一些賣Hexapod套件附的動作編輯軟體,除了動作編輯與除存我還要用OpenGL寫個簡單的動畫模擬在螢幕中央以及支援Webcam(前述部份很簡單),還要有正逆向運動學的計算以及一些校準(這部份不熟)。

    要讓PC端連接一些設備可以用UART或是USB的方式傳輸,只要搞懂Spec文件就可以了,Linux下通過Serial Port可以跟Arduino、PIC等等的單晶片溝通,Linux下可以用linudev取得可用的Serial Port設備的Node Path,然後就可以直接當作檔案Open與Close,只是跟讀寫一般純文字文件不同的是Open之後必須要設定Baudrate與Parity,然後用Write與Read 就可以接收資料了,而Arduino的Serial Port接收資料必須要Delay 1ms左右的時間,否則速度太快會接收錯誤資料。






    說明:PC端的MAX與Arduino的MAX都是表示Servo的數量,比如設定為12時就會有12條Slider(建議在自己Layout)可以控制12個Servo,然後PC端與Arduino的MAX數量要一樣。

wxarduino.h


#ifndef __WX_ARDUINO__
#define __WX_ARDUINO__

#include <wx/wx.h>
#include <wx/spinctrl.h>

#include <libudev.h>

#include "serialport.h"
#include "connectargsdlg.h"

class App:public wxApp
{
   public:
      bool OnInit();
};

class Frame:public wxFrame
{
   public:
      Frame(const wxString&);

      void OnSendData(wxTimerEvent&);
      void OnConnectDevice(wxCommandEvent&);
      void OnReleaseDevice(wxCommandEvent&);
      void OnExit(wxCommandEvent&);

   private:

      enum{
ID_SEND_DATA = 100,
ID_CONNECT_DEVICE,
ID_RELEASE_DEVICE
      };

      static const unsigned int MAX = 2;

      wxMenu *device_path;

      wxTimer timer;

      SerialPort serial;

      wxSlider *servos[MAX];
      wxBoxSizer *box[MAX];
      wxStaticText *servos_text[MAX];
      unsigned char data[MAX];

      DECLARE_EVENT_TABLE()
};

#endif


wxarduino.cpp


#include "wxarduino.h"

IMPLEMENT_APP(App)
DECLARE_APP(App)

bool App::OnInit()
{
   wxFrame *frame = new Frame(wxT("wxArduino"));

   frame->Show(true);

   return true;
}

BEGIN_EVENT_TABLE(Frame,wxFrame)
   EVT_MENU(wxID_EXIT,Frame::OnExit)
   EVT_MENU(ID_CONNECT_DEVICE,Frame::OnConnectDevice)
   EVT_MENU(ID_RELEASE_DEVICE,Frame::OnReleaseDevice)
   EVT_TIMER(ID_SEND_DATA,Frame::OnSendData)
END_EVENT_TABLE()

Frame::Frame(const wxString &title):wxFrame(NULL,wxID_ANY,title,wxDefaultPosition,wxDefaultSize,wxMINIMIZE_BOX | wxCLOSE_BOX | wxCAPTION | wxSYSTEM_MENU),timer(this,ID_SEND_DATA)
{
   timer.Stop();

   wxMenu *file = new wxMenu;
   file->Append(wxID_EXIT,wxT("E&xit\tAlt-e"),wxT("Exit"));
   wxMenu *tools = new wxMenu;
   tools->Append(ID_CONNECT_DEVICE,wxT("C&onnect Device\tAlt-c"),wxT("Connect Device"));
   tools->Append(ID_RELEASE_DEVICE,wxT("R&elease Device\tAlt-r"),wxT("Release Device"));

   wxMenuBar *bar = new wxMenuBar;
   bar->Append(file,wxT("File"));
   bar->Append(tools,wxT("Tools"));
   SetMenuBar(bar);

   wxBoxSizer *top = new wxBoxSizer(wxVERTICAL);
   this->SetSizer(top);

   for(int i = 0;i < MAX;++i){
      box[i] = new wxBoxSizer(wxHORIZONTAL);
      top->Add(box[i],0,wxALIGN_CENTER_HORIZONTAL | wxALL,5);

      wxString text;
      text.Printf("Servo %d:",i + 1);
      servos_text[i] = new wxStaticText(this,wxID_STATIC,text,wxDefaultPosition,wxDefaultSize,0);
      box[i]->Add(servos_text[i],0,wxALIGN_CENTER_HORIZONTAL |  wxALIGN_CENTER_VERTICAL,5);

      servos[i] = new wxSlider(this,wxID_ANY,90,0,180,wxDefaultPosition,wxSize(200,-1),wxSL_HORIZONTAL | wxSL_AUTOTICKS | wxSL_LABELS);
      box[i]->Add(servos[i],0,wxALIGN_CENTER_HORIZONTAL | wxALL,5);
   }

   CreateStatusBar(2);
   SetStatusText(wxT("wxArduino"));

   top->Fit(this);
   top->SetSizeHints(this);
}

void Frame::OnSendData(wxTimerEvent &event)
{
   for(int i = 0;i < MAX;++i){
      data[i] = servos[i]->GetValue();
   }
   serial.Write(data,MAX);
}

void Frame::OnConnectDevice(wxCommandEvent &event)
{
    ConnectArgsDialog dlg(this,wxID_ANY,wxT("Connect"),wxDefaultPosition,wxDefaultSize,wxDEFAULT_DIALOG_STYLE);

    if(dlg.ShowModal() == wxID_OK){
       serial.Open(dlg.GetDevicePath().mb_str());
       serial.SetBaudRate(wxAtoi(dlg.GetBaudRate()));
       serial.SetParity(8,1,'N');

       timer.Start(30);
    }
}

void Frame::OnReleaseDevice(wxCommandEvent &event)
{
   serial.Close();
   timer.Stop();
}

void Frame::OnExit(wxCommandEvent &event)
{
   timer.Stop();
   Close();
}


connectargsalg.h


#ifndef __CONNECT_ARGS_DIALOG__
#define __CONNECT_ARGS_DIALOG__
#include <wx/wx.h>
#include <libudev.h>

class ConnectArgsDialog:public wxDialog
{
   public:
      ConnectArgsDialog();

      ConnectArgsDialog(wxWindow *parent,
   wxWindowID id = wxID_ANY,
   const wxString &caption = wxT("Connect Args"),
   const wxPoint &pos = wxDefaultPosition,
   const wxSize &size = wxDefaultSize,
   long style = wxCAPTION | wxRESIZE_BORDER | wxSYSTEM_MENU);

      bool Create(wxWindow *parent,
   wxWindowID id = wxID_ANY,
   const wxString &caption = wxT("Connect Args"),
   const wxPoint &pos = wxDefaultPosition,
   const wxSize &size = wxDefaultSize,
   long style = wxCAPTION | wxRESIZE_BORDER | wxSYSTEM_MENU);

      void Init();
      void CreateControls();

      bool TransferDataToWindow();
      bool TransferDataFromWindow();

      wxString GetDevicePath(){return device_path->GetString(device_path->GetCurrentSelection());}
      wxString GetBaudRate(){return baud_rate->GetString(baud_rate->GetCurrentSelection());}

   private:
      wxChoice *device_path;
      wxChoice *baud_rate;

      DECLARE_CLASS(ConnectArgsDialog)
};

#endif


connectargsdlg.cpp


#include "connectargsdlg.h"

IMPLEMENT_CLASS(ConnectArgsDialog,wxDialog)

ConnectArgsDialog::ConnectArgsDialog()
{
   Init();
}

ConnectArgsDialog::ConnectArgsDialog(wxWindow *parent,wxWindowID id,const wxString &caption,const wxPoint &pos,const wxSize &size,long style)
{
   Create(parent,id,caption,pos,size,style);
   Init();
}

inline void ConnectArgsDialog::Init()
{
}

bool ConnectArgsDialog::Create(wxWindow *parent,wxWindowID id,const wxString &caption,const wxPoint &pos,const wxSize &size,long style)
{
   SetExtraStyle(wxWS_EX_BLOCK_EVENTS | wxDIALOG_EX_CONTEXTHELP);

   if(!wxDialog::Create(parent,id,caption,pos,size,style)){
      return false;
   }

   CreateControls();

   GetSizer()->Fit(this);
   GetSizer()->SetSizeHints(this);

   Center();

   return true;
}

void ConnectArgsDialog::CreateControls()
{
   wxBoxSizer *top = new wxBoxSizer(wxVERTICAL);
   this->SetSizer(top);

   wxBoxSizer *box = new wxBoxSizer(wxHORIZONTAL);
   top->Add(box,0,wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,5);

   wxArrayString paths;

   struct udev *udev;
   struct udev_enumerate *enumerate;
   struct udev_list_entry *devices,*dev_list_entry;
   struct udev_device *dev;

   udev = udev_new();
   enumerate = udev_enumerate_new(udev);
   udev_enumerate_add_match_subsystem(enumerate,"tty");
   udev_enumerate_scan_devices(enumerate);
   devices = udev_enumerate_get_list_entry(enumerate);

   udev_list_entry_foreach(dev_list_entry,devices){
      const char *path;

      path = udev_list_entry_get_name(dev_list_entry);
      dev = udev_device_new_from_syspath(udev,path);

      paths.Add(wxT(udev_device_get_devnode(dev)));

      dev = udev_device_get_parent_with_subsystem_devtype(dev,"usb","usb_device");

      if(!dev){
paths.RemoveAt(paths.GetCount() - 1);
break;
      }

      udev_device_unref(dev);
   }
   udev_enumerate_unref(enumerate);
   udev_unref(udev);

   device_path = new wxChoice(this,wxID_ANY,wxDefaultPosition,wxDefaultSize,paths);
   box->Add(device_path,0,wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,5);

   wxArrayString speed;
   speed.Add(wxT("300"));
   speed.Add(wxT("1200"));
   speed.Add(wxT("2400"));
   speed.Add(wxT("4800"));
   speed.Add(wxT("9600"));
   speed.Add(wxT("19200"));
   speed.Add(wxT("38400"));

   baud_rate = new wxChoice(this,wxID_ANY,wxDefaultPosition,wxDefaultSize,speed);
   box->Add(baud_rate,0,wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,5);

   wxBoxSizer *ResetOkCancelBox = new wxBoxSizer(wxHORIZONTAL);
   top->Add(ResetOkCancelBox,0,wxALIGN_CENTER_HORIZONTAL | wxALL,5);

   wxButton *ok = new wxButton(this,wxID_OK,wxT("&Ok"),wxDefaultPosition,wxDefaultSize,0);
   ResetOkCancelBox->Add(ok,0,wxALIGN_CENTER_VERTICAL | wxALL,5);

   wxButton *cancel = new wxButton(this,wxID_CANCEL,wxT("&Cancel"),wxDefaultPosition,wxDefaultSize,0);
   ResetOkCancelBox->Add(cancel,0,wxALIGN_CENTER_VERTICAL | wxALL,5);
}

bool ConnectArgsDialog::TransferDataToWindow()
{
   return true;
}

bool ConnectArgsDialog::TransferDataFromWindow()
{
   return true;
}


serialport.h


#ifndef __SERIAL_PORT__
#define __SERIAL_PORT__

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <error.h>

class SerialPort
{
   public:

      SerialPort();
      SerialPort(const char*,int,int,int,int);
      ~SerialPort();
      bool Open(const char*);
      bool SetBaudRate(int);
      bool SetParity(int,int,int);
      void Close();
      void Write(unsigned char*,int);
      void Read(unsigned char*,int);

   private:

      const char *path;
      int device;
      int baud_rate;
      int databits;
      int stopbits;
      int parity;
};

#endif


serialport.cpp


#include "serialport.h"

SerialPort::SerialPort()
{
}

SerialPort::SerialPort(const char *path = "",int baud_rate = 9600,int databits = 8,int stopbits = 1,int parity = 'N')
{
   this->path = path;
   this->baud_rate = baud_rate;
   this->databits = databits;
   this->stopbits = stopbits;
   this->parity = parity;
}

SerialPort::~SerialPort()
{
   Close();
}

bool SerialPort::Open(const char *path = "")
{
   if(device){
      close(device);
   }

   device = open(path,O_RDWR);

   if(device == -1){
      return false;
   }

   return true;
}

bool SerialPort::SetBaudRate(int baud_rate = 9600)
{
   const int speed_arr[] = {B38400,B19200,B9600,B4800,B2400,B1200,B300};
   const int name_arr[] = {38400,19200,9600,4800,2400,1200,300};

   struct termios options;

   tcgetattr(device,&options);

   for(int i = 0; i < sizeof(speed_arr) / sizeof(int);++i){
      if(baud_rate == name_arr[i]){
tcflush(device,TCIOFLUSH);
cfsetispeed(&options,speed_arr[i]);
cfsetospeed(&options,speed_arr[i]);
tcsetattr(device,TCSANOW,&options);
      }
      if(tcflush(device,TCIOFLUSH) != 0){
return false;
      }
   }

   return true;
}

bool SerialPort::SetParity(int databits = 8,int stopbits = 1,int parity = 'N')
{
   struct termios options;

   if(tcgetattr(device,&options) != 0){
      return false;
   }

   options.c_cflag &= ~CSIZE;

   switch(databits)
   {
      case 7:
{
   options.c_cflag |= CS7;
   break;
}
      case 8:
{
   options.c_cflag |= CS8;
   break;
}
      default:
{
   return false;
}
   }

   switch(parity)
   {
      case 'n':case 'N':
{
   options.c_cflag &= ~PARENB;
   options.c_iflag &= ~INPCK;
   break;
}
      case 'o':case 'O':
{
   options.c_cflag |= (PARODD | PARENB);
   options.c_iflag |= INPCK;
   break;
}
      case 'e':case 'E':
{
   options.c_cflag |= PARENB;
   options.c_cflag &= ~PARODD;
   options.c_iflag |= INPCK;
   break;
}
      case 's':case 'S':
{
   options.c_cflag &= ~PARENB;
   options.c_cflag &= ~CSTOPB;
   break;
}
      default:
{
   return false;
}
   }

   switch(stopbits)
   {
      case 1:
{
   options.c_cflag &= ~CSTOPB;
   break;
}
      case 2:
{
   options.c_cflag |= CSTOPB;
   break;
}
      default:
{
   return false;
}
   }

   if(parity != 'n' || parity != 'N'){
      options.c_iflag |= INPCK;
   }

   options.c_cc[VTIME] = 150;
   options.c_cc[VMIN] = 0;

   tcflush(device,TCIFLUSH);

   if(tcsetattr(device,TCSANOW,&options) != 0){
      return false;
   }

   return true;
}

void SerialPort::Close()
{
   if(device){
      close(device);
   }
}

void SerialPort::Write(unsigned char *data,int length)
{
   write(device,data,length);
}

void SerialPort::Read(unsigned char *data,int length)
{
   read(device,data,length);
}


g++ -o2 -o wxArduino wxarduino.cpp connectargsdlg.cpp serialport.cpp -ludev `wx-config --cxxflags --libs`

servo.pde


#include <Servo.h>

const unsigned int MAX = 2;

Servo servos[MAX];

void setup() {              
  Serial.begin(9600);

  for(int i = 0;i < MAX;++i){
    servos[i].attach(i + 3);
    servos[i].write(90);
  }
}

void loop() {

  for(int i = 0;Serial.available() > 0;++i){
    delay(1);    //速度小於1ms接收資料會錯誤
    int angle = Serial.read();
    servos[i].write(angle);
  }
}


沒有留言:

張貼留言