Topik tulisan ini sangatlah common, sangat biasa saja, yakni bagaimana membuat program komunikasi serial antara PC dan Arduino. Yang jadi agak istimewa adalah karena tulisan ini akan memberikan contoh program komunikasi serial menggunakan bahasa C dengan menggunakan kompiler ANSI C Pelles C. Jadi, pada tulisan ini kita akan membuat program komunikasi serial menggunakan fungsi-fungsi WINAPI (Win32).

Nah, langsung saja ya.

Pertama-tama kita buat dulu program di sisi Arduino. Program ini bertugas untuk menerima perintah dari PC melalui komunikasi serial dan merespon perintah tersebut dengan benar.

Namun demikian, sebelum membuat program Arduino, ada baiknya kita simak terlebih dahulu gambar skenario sistem yang akan kita buat.

Untuk menyalakan LED-13 pada board Arduino Nano, PC (Pelles C) harus mengirimkan karakter “1“. Dan ketika Arduino menerima karakter “1”, maka Arduino akan menyalakan LED-13 dengan memberikan logika HIGH pada Pin-13 dan mengirimkan string status “LED is ON” ke PC.

Untuk memadamkan LED-13, karakter perintahnya adalah “0“. Ketika Arduino menerima perintah ini, maka Arduino akan memadamkan LED-13 dengan memberikan logika LOW pada Pin-13 dan mengirimkan string status “LED is OFF” ke PC.

Untuk membaca Analog-Input A0, PC harus mengirimkan karakter perintah “2“.Ketika menerima karakter perintah “2”, maka Arduino akan membaca Analog-Input A0 dan mengirimkan datanya ke PC.

Untuk kode program Arduinonya, silakan simak kode program berikut ini.

Kode Program Arduino

void setup()
{
  pinMode(13, OUTPUT);
  Serial.begin(19200);
}

void loop()
{
  if (Serial.available())
  {
    char ch = Serial.read();

    if (ch=='1')  //perintah untuk LED ON
    {
      digitalWrite(13, HIGH);
      Serial.println("LED is ON");
    }
    else if (ch=='0')  //perintah untuk LED OFF
    {
      digitalWrite(13, LOW);
      Serial.println("LED is OFF");
    }
    else if (ch=='2')  //perintah untuk baca ADC A0
    {
      Serial.print("A0 = ");
      Serial.println(analogRead(A0));
    }
  }
}

Program PC (Pelles C)

Program komunikasi serial pada sisi PC kita buat sedikit “mewah” dengan tampilan GUI. Tampilan GUI pada program ini biasa saja alias sederhana seperti yang ditunjukkan oleh gambar berikut ini.

Pelles C adalah program kompiler ANSI C untuk platform Windows yang memiliki fitur dan fasilitas yang lengkap, termasuk diantaranya adalah GUI Editor atau Form Editor. Dengan Form Editor ini, kita bisa merancang tampilan program dengan mudah menggunakan komponen-komponen standar Windows.

Text Editor Pelles C juga cukup nyaman digunakan. Meskipun pewarnaan sintaksisnya kurang menarik, tapi lama-lama akan terbiasa juga, hehehe… Kalau beneran bosan, warna bisa di-custom kok.

Dan berikut ini adalah kode program C selengkapnya pada sisi PC.

/****************************************************************************
 *                                                                          *
 * File    : main.c                                                         *
 * Purpose : Komunikasi Serial PC dengan Arduinoion.                        *
 * History : Date      Reason                                               *
 *           22/10/21  First Created                                        *
 ****************************************************************************/

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <tchar.h>
#include <stdio.h>
#include "main.h"

#define NELEMS(a)  (sizeof(a) / sizeof((a)[0]))

static INT_PTR CALLBACK MainDlgProc(HWND, UINT, WPARAM, LPARAM);

static HANDLE ghInstance;
HANDLE hComm;


/****************************************************************************
 * Function: WinMain                                                        *
 * Purpose : Initialize the application.  Register a window class,          *
 *           create and display the main window and enter the               *
 *           message loop.                                                  *
 ****************************************************************************/

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
	INITCOMMONCONTROLSEX icc;
	WNDCLASSEX wcx;

	ghInstance = hInstance;

	/* Initialize common controls. Also needed for MANIFEST's */
	/*
	 * TODO: set the ICC_???_CLASSES that you need.
	 */
	icc.dwSize = sizeof(icc);
	icc.dwICC = ICC_WIN95_CLASSES /*|ICC_COOL_CLASSES|ICC_DATE_CLASSES|ICC_PAGESCROLLER_CLASS|ICC_USEREX_CLASSES|... */ ;
	InitCommonControlsEx(&icc);

	/* Load Rich Edit control support */
	/*
	 * TODO: uncomment one of the lines below, if you are using a Rich Edit control.
	 */
	// LoadLibrary(_T("riched32.dll"));  // Rich Edit v1.0
	// LoadLibrary(_T("riched20.dll"));  // Rich Edit v2.0, v3.0

	/*
	 * TODO: uncomment line below, if you are using the Network Address control (Windows Vista+).
	 */
	// InitNetworkAddressControl();

	/* Get system dialog information */
	wcx.cbSize = sizeof(wcx);
	if (!GetClassInfoEx(NULL, MAKEINTRESOURCE(32770), &wcx))
		return 0;

	/* Add our own stuff */
	wcx.hInstance = hInstance;
	wcx.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_ICO_MAIN));
	wcx.lpszClassName = _T("hello_arClass");
	if (!RegisterClassEx(&wcx))
		return 0;

	/* The user interface is a modal dialog box */
	return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)MainDlgProc);
}

//mencuplik dari program CSerialPort: https://github.com/ABasharEter/CSerialPort
int SerialWrite(HANDLE com_port, const char * data)
{
	DWORD  dNoOFBytestoWrite = strlen(data);
	DWORD  dNoOfBytesWritten;
	BOOL Status = WriteFile(com_port,
				data,
				dNoOFBytestoWrite,
				&dNoOfBytesWritten,
				NULL);
	if (Status == FALSE)
		return -1;
	return dNoOfBytesWritten;
}

//mencuplik dari program CSerialPort: https://github.com/ABasharEter/CSerialPort
int SerialRead(HANDLE com_port, char * data,int len)
{
	DWORD dwEventMask;
	DWORD NoBytesRead;
	BOOL Status = WaitCommEvent(com_port, &dwEventMask, NULL);
	if (Status == FALSE) {
		return FALSE;
	}
	Status = ReadFile(com_port, data, len, &NoBytesRead, NULL);
	data[NoBytesRead] = '\0';
	if (Status == FALSE) {
		return FALSE;
	}
	return TRUE;
}


/****************************************************************************
 * Function: MainDlgProc                                                    *
 * Purpose : Process messages for the Main dialog.                          *
 ****************************************************************************/

static INT_PTR CALLBACK MainDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	char portnum_str[10];
	int idx;
	char comname[10];
	char serbuf[20];

	switch (uMsg)
	{
		case WM_INITDIALOG:

			//mengisi combobox cbSerialPort dengan string COM1 - COM30
			for (int n = 1; n <= 30; n++)
			{
				sprintf(portnum_str, "COM%d", n);
				SendDlgItemMessage(hwndDlg, cbSerialPort, CB_ADDSTRING, 0, (LPARAM)portnum_str);
				SendDlgItemMessage(hwndDlg, cbSerialPort, CB_SETCURSEL, 0, 1);
			}

			return TRUE;

		case WM_COMMAND:
			switch (GET_WM_COMMAND_ID(wParam, lParam))
			{
				case btnOpenSerial:
				  idx = 1 + SendMessage(GetDlgItem(hwndDlg, cbSerialPort), CB_GETCURSEL, 0, 0);
					sprintf(comname, "\\\\.\\COM%d", idx);
					
					hComm = CreateFile(comname,	//port name 
						GENERIC_READ | GENERIC_WRITE,	//Read/Write                   
						0,	// No Sharing                               
						NULL,	// No Security                              
						OPEN_EXISTING,	// Open existing port only                     
						0,	// Non Overlapped I/O                           
						NULL);	// Null for Comm Devices

					if (hComm == INVALID_HANDLE_VALUE)
					{
					   MessageBox(hwndDlg, "Gagal membuka port serial.", comname, MB_OK);
					   return FALSE;
					}
					
					COMMTIMEOUTS timeouts = { 0 };
					timeouts.ReadIntervalTimeout = 100;
					timeouts.ReadTotalTimeoutConstant = 100;
					timeouts.ReadTotalTimeoutMultiplier = 10;
					timeouts.WriteTotalTimeoutConstant = 100;
					timeouts.WriteTotalTimeoutMultiplier = 10;

					SetCommTimeouts(hComm, &timeouts);

					SetCommMask(hComm, EV_RXCHAR);

					DCB dcbSerialParams = { 0 };
					dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
					GetCommState(hComm, &dcbSerialParams);
					dcbSerialParams.BaudRate = CBR_19200;
					dcbSerialParams.ByteSize = 8;
					dcbSerialParams.StopBits = ONESTOPBIT;
					dcbSerialParams.Parity   = NOPARITY;

					SetCommState(hComm, &dcbSerialParams);

					return TRUE;

				case btnON:
					SerialWrite(hComm, "1");
					if (SerialRead(hComm, serbuf, 20))
						SetDlgItemText(hwndDlg, editLEDStatus, serbuf);
					return TRUE;

				case btnOFF:
					SerialWrite(hComm, "0");
					if (SerialRead(hComm, serbuf, 20))
						SetDlgItemText(hwndDlg, editLEDStatus, serbuf);
					return TRUE;
				
				case btnReadADC:
					SerialWrite(hComm, "2");
					if (SerialRead(hComm, serbuf, 20))
					   SetDlgItemText(hwndDlg, editADC, serbuf);
					else
					   MessageBox(hwndDlg, "Gagal membaca nilai ADC A0", "Error", MB_OK);
					return TRUE;

				case btnClose:
					EndDialog(hwndDlg, TRUE);
					return TRUE;
			}
			break;

		case WM_CLOSE:
                        if (hComm)
                             CloseHandle(hComm);
			EndDialog(hwndDlg, 0);
			return TRUE;
	}

	return FALSE;
}

Sekelumit Tentang Kode Program

Terdapat beberapa bagian penting dalam kode program Pelles C di atas.

Yang pertama adalah bagian inisialisasi.

Pada bagian inisialisasi ini, program mengisi Combobox cbSerialPort dengan data-data string “COM1” hingga “COM30”. Adapun caranya adalah sebagai berikut:

 	case WM_INITDIALOG:

		//mengisi combobox cbSerialPort dengan string COM1 - COM30
		for (int n = 1; n <= 30; n++)
		{
			sprintf(portnum_str, "COM%d", n);
			SendDlgItemMessage(hwndDlg, cbSerialPort, CB_ADDSTRING, 0, (LPARAM)portnum_str);
			SendDlgItemMessage(hwndDlg, cbSerialPort, CB_SETCURSEL, 0, 1);
		}
		return TRUE;

Bagian penting berikutnya adalah bagian event-handler untuk komponen BUTTON, yakni bagian yang bertugas merespon ketika User melakukan click pada button. Terdapat 5 buah button yang harus dibuatkan event-handler, yakni:

  • Open Serial Port
  • ON
  • OFF
  • Read ADC
  • EXIT

Sebelum membuka port serial, seyogyanya User melakukan pemilihan port serial yang akan digunakan, yakni port serial yang terhubung ke Arduino. Pada sistem saya, Arduino Nano yang saya gunakan terhubung ke port serial COM2.

Dan ketika button Open Serial Port di-click, maka program akan membuka mengkonfigurasi port serial dengan kode program sebagai berikut.

        case btnOpenSerial:
		idx = 1 + SendMessage(GetDlgItem(hwndDlg, cbSerialPort), CB_GETCURSEL, 0, 0);
		sprintf(comname, "\\\\.\\COM%d", idx);
			
		hComm = CreateFile(comname,	//port name 
			GENERIC_READ | GENERIC_WRITE,	//Read/Write                   
			0,	// No Sharing                               
			NULL,	// No Security                              
			OPEN_EXISTING,	// Open existing port only                     
			0,	// Non Overlapped I/O                           
			NULL);	// Null for Comm Devices

		if (hComm == INVALID_HANDLE_VALUE)
		{
			MessageBox(hwndDlg, "Gagal membuka port serial.", comname, MB_OK);
			return FALSE;
		}
					
		COMMTIMEOUTS timeouts = { 0 };
		timeouts.ReadIntervalTimeout = 100;
		timeouts.ReadTotalTimeoutConstant = 100;
		timeouts.ReadTotalTimeoutMultiplier = 10;
		timeouts.WriteTotalTimeoutConstant = 100;
		timeouts.WriteTotalTimeoutMultiplier = 10;

		SetCommTimeouts(hComm, &timeouts);
		SetCommMask(hComm, EV_RXCHAR);

		DCB dcbSerialParams = { 0 };
		dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
		GetCommState(hComm, &dcbSerialParams);
		dcbSerialParams.BaudRate = CBR_19200;
		dcbSerialParams.ByteSize = 8;
		dcbSerialParams.StopBits = ONESTOPBIT;
		dcbSerialParams.Parity   = NOPARITY;

		SetCommState(hComm, &dcbSerialParams);

		return TRUE;

Untuk button ON, kode program event-handler-nya adalah sebagai berikut.

	case btnON:
		SerialWrite(hComm, "1");
		if (SerialRead(hComm, serbuf, 20))
			SetDlgItemText(hwndDlg, editLEDStatus, serbuf);
		return TRUE;

SerialWrite() dan SerialRead() adalah fungsi yang saya ambil dari cuplikkan dari github.com/ABasharEter/CSerialPort dengan hanya memodifikasi nama fungsi saja. Fungsi aslinya bernama SendData dan ReciveData. Beneran ReciveData lho, bukan ReceiveData. Mungkin Pak Bashar ada salah ketik, hehehe…

//mencuplik dari program CSerialPort: https://github.com/ABasharEter/CSerialPort
int SerialWrite(HANDLE com_port, const char * data)
{
	DWORD  dNoOFBytestoWrite = strlen(data);
	DWORD  dNoOfBytesWritten;
	BOOL Status = WriteFile(com_port,
				data,
				dNoOFBytestoWrite,
				&dNoOfBytesWritten,
				NULL);
	if (Status == FALSE)
		return -1;
	return dNoOfBytesWritten;
}

//mencuplik dari program CSerialPort: https://github.com/ABasharEter/CSerialPort
int SerialRead(HANDLE com_port, char * data,int len)
{
	DWORD dwEventMask;
	DWORD NoBytesRead;
	BOOL Status = WaitCommEvent(com_port, &dwEventMask, NULL);
	if (Status == FALSE) {
		return FALSE;
	}
	Status = ReadFile(com_port, data, len, &NoBytesRead, NULL);
	data[NoBytesRead] = '\0';
	if (Status == FALSE) {
		return FALSE;
	}
	return TRUE;
}

Untuk button OFF dan Read ADC pada intinya sama saja dengan button ON, yakni mengirimkan perintah dan menunggu respon dari Arduino.

	case btnOFF:
		SerialWrite(hComm, "0");
		if (SerialRead(hComm, serbuf, 20))
			SetDlgItemText(hwndDlg, editLEDStatus, serbuf);
		return TRUE;
				
	case btnReadADC:
		SerialWrite(hComm, "2");
		if (SerialRead(hComm, serbuf, 20))
			SetDlgItemText(hwndDlg, editADC, serbuf);
		else
			MessageBox(hwndDlg, "Gagal membaca nilai ADC A0", "Error", MB_OK);
		return TRUE;

Dan yang terakhir, button EXIT, akan menutup port serial jika terbuka dan menutup aplikasi ketika di-click.

	case WM_CLOSE:
		if (hComm)
                     CloseHandle(hComm);
                EndDialog(hwndDlg, 0);
		return TRUE;

Hasil Eksekusi Program

Screenshot berikut ini menunjukkan hasil eksekusi dari program pada sisi PC.

Sekedar informasi bahwa ukuran file aplikasi ini hanyalah 63kb.

Pelatihan PRIVAT Online

Kami melayani Pelatihan dan Bimbingan PRIVAT Online untuk materi pemrograman dan perancangan sistem berbasis mikrokontroler seperti Arduino, 8051 dan PIC.

Jika berminat, silakan menghubungi kami melalui Whatsapp 0882-3560-7047.

Nikmati Artikel Menarik Lainnya

Semoga bermanfaat.