Arduino Joy Stick program that is supposed to draw lines on a Windows GUI app screen is not working

Joseph RW 105 Reputation points
2025-12-04T02:55:53.32+00:00

Hi Microsoft team I have a Arduino connected Joy Stick that Writes X and Y cordinate data to the Serial Monitor on Joy Stick Move , but when I read that data from a Windows GUI app in order to get it to draw on the windows GUI APP nothing happens

Windows APP:

#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
#define COM_PORT "\\\\.\\COM7"
#define BUF_SIZE 256
// Global variables
HANDLE hComm;
HWND g_hWnd = NULL;
int g_currentX = 100;
int g_currentY = 100;
// Data structures for IOCP
typedef struct _PER_IO_DATA {
    OVERLAPPED Overlapped;
    WSABUF DataBuf;
    CHAR Buffer[BUF_SIZE];
    DWORD BytesRECV;
    DWORD BytesSEND;
} PER_IO_DATA, * LPPER_IO_DATA;
typedef struct _PER_HANDLE_DATA {
    HANDLE FileHandle;
    char lineBuffer[BUF_SIZE];
    int linePos;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
// --------------------------------------------------------------
void process_line(const char* s)
{
    int dx, dy;
    if (sscanf(s, "%d,%d", &dx, &dy) == 2) {
        // Update drawing position
        g_currentX += dx;
        g_currentY += dy;
        // Keep within reasonable bounds
        if (g_currentX < 0) g_currentX = 0;
        if (g_currentY < 0) g_currentY = 0;
        if (g_currentX > 800) g_currentX = 800;
        if (g_currentY > 600) g_currentY = 600;
        // Trigger window redraw
        if (g_hWnd) {
            InvalidateRect(g_hWnd, NULL, TRUE);
        }
    }
}
// --------------------------------------------------------------
DWORD WINAPI WorkerThread(LPVOID lpParam) {
    HANDLE hCompletionPort = (HANDLE)lpParam;
    DWORD BytesTransferred = 0;
    LPPER_IO_DATA lpPerIoData = NULL;
    LPPER_HANDLE_DATA lpPerHandleData = NULL;
    DWORD dwError = 0;
    while (TRUE) {
        // Wait for I/O completion
        BOOL bRet = GetQueuedCompletionStatus(
            hCompletionPort,
            &BytesTransferred,
            (PULONG_PTR)&lpPerHandleData,
            (LPOVERLAPPED*)&lpPerIoData,
            INFINITE
        );
        if (!bRet) {
            dwError = GetLastError();
            if (lpPerIoData == NULL) {
                // Error occurred without a valid OVERLAPPED structure
                break;
            }
        }
        // Check for shutdown signal
        if (BytesTransferred == 0 && lpPerHandleData == NULL) {
            break;
        }
        // Process received data
        if (BytesTransferred > 0 && lpPerHandleData != NULL) {
            // Process each character received
            for (DWORD i = 0; i < BytesTransferred; i++) {
                char ch = lpPerIoData->Buffer[i];
                if (ch == '\r' || ch == '\n') {
                    if (lpPerHandleData->linePos > 0) {
                        lpPerHandleData->lineBuffer[lpPerHandleData->linePos] = '\0';
                        process_line(lpPerHandleData->lineBuffer);
                        lpPerHandleData->linePos = 0;
                    }
                }
                else if (lpPerHandleData->linePos < BUF_SIZE - 1) {
                    lpPerHandleData->lineBuffer[lpPerHandleData->linePos++] = ch;
                }
            }
            // Issue another read
            ZeroMemory(&(lpPerIoData->Overlapped), sizeof(OVERLAPPED));
            lpPerIoData->DataBuf.len = BUF_SIZE;
            lpPerIoData->DataBuf.buf = lpPerIoData->Buffer;
            DWORD Flags = 0;
            DWORD RecvBytes = 0;
            if (!ReadFile(
                lpPerHandleData->FileHandle,
                lpPerIoData->Buffer,
                BUF_SIZE,
                &RecvBytes,
                &(lpPerIoData->Overlapped)
            )) {
                dwError = GetLastError();
                if (dwError != ERROR_IO_PENDING) {
                    printf("ReadFile failed: %d\n", dwError);
                    free(lpPerIoData);
                }
            }
        }
    }
    return 0;
}
// --------------------------------------------------------------
int InitializeSerialPort(HANDLE hCompletionPort)
{
    // Open port
    hComm = CreateFileA(COM_PORT, GENERIC_READ | GENERIC_WRITE,
        0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hComm == INVALID_HANDLE_VALUE) {
        printf("Cannot open %s (Error: %d)\n", COM_PORT, GetLastError());
        return 1;
    }
    // Allocate per-handle data
    LPPER_HANDLE_DATA lpPerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
    if (lpPerHandleData == NULL) {
        CloseHandle(hComm);
        return 1;
    }
    lpPerHandleData->FileHandle = hComm;
    lpPerHandleData->linePos = 0;
    // Associate with completion port
    if (CreateIoCompletionPort(hComm, hCompletionPort, (ULONG_PTR)lpPerHandleData, 0) == NULL) {
        printf("CreateIoCompletionPort failed: %d\n", GetLastError());
        free(lpPerHandleData);
        CloseHandle(hComm);
        return 1;
    }
    // Configure serial port: 115200, 8N1
    DCB dcb = { 0 };
    dcb.DCBlength = sizeof(dcb);
    GetCommState(hComm, &dcb);
    dcb.BaudRate = 115200;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    SetCommState(hComm, &dcb);
    // Set timeouts for overlapped I/O
    COMMTIMEOUTS to = { 0 };
    to.ReadIntervalTimeout = MAXDWORD;
    to.ReadTotalTimeoutConstant = 0;
    SetCommTimeouts(hComm, &to);
    PurgeComm(hComm, PURGE_RXCLEAR | PURGE_TXCLEAR);
    // Allocate per-I/O data and start first read
    LPPER_IO_DATA lpPerIoData = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
    if (lpPerIoData == NULL) {
        free(lpPerHandleData);
        CloseHandle(hComm);
        return 1;
    }
    ZeroMemory(&(lpPerIoData->Overlapped), sizeof(OVERLAPPED));
    lpPerIoData->DataBuf.len = BUF_SIZE;
    lpPerIoData->DataBuf.buf = lpPerIoData->Buffer;
    DWORD Flags = 0;
    DWORD RecvBytes = 0;
    if (!ReadFile(hComm, lpPerIoData->Buffer, BUF_SIZE, &RecvBytes, &(lpPerIoData->Overlapped))) {
        DWORD dwError = GetLastError();
        if (dwError != ERROR_IO_PENDING) {
            printf("Initial ReadFile failed: %d\n", dwError);
            free(lpPerIoData);
            free(lpPerHandleData);
            CloseHandle(hComm);
            return 1;
        }
    }
    printf("Joystick -> Drawing ready (COM7 @ 115200)\n");
    return 0;
}
// --------------------------------------------------------------
VOID OnPaint(HDC hdc)
{
    Graphics graphics(hdc);
    // Draw crosshair at current position
    Pen penRed(Color(255, 255, 0, 0), 2);
    Pen penBlue(Color(255, 0, 0, 255), 1);
    // Draw lines showing joystick position
    graphics.DrawLine(&penRed, g_currentX - 20, g_currentY, g_currentX + 20, g_currentY);
    graphics.DrawLine(&penRed, g_currentX, g_currentY - 20, g_currentX, g_currentY + 20);
    // Draw circle
    graphics.DrawEllipse(&penBlue, g_currentX - 10, g_currentY - 10, 20, 20);
    // Draw some reference lines
    Pen penGray(Color(255, 200, 200, 200));
    graphics.DrawLine(&penGray, 0, 100, 800, 100);
    graphics.DrawLine(&penGray, 0, 200, 800, 200);
    graphics.DrawLine(&penGray, 0, 300, 800, 300);
    graphics.DrawLine(&penGray, 100, 0, 100, 600);
    graphics.DrawLine(&penGray, 200, 0, 200, 600);
    graphics.DrawLine(&penGray, 300, 0, 300, 600);
}
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow)
{
    HWND hWnd;
    MSG msg;
    WNDCLASS wndClass;
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    // Initialize GDI+
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;
    wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = TEXT("JoystickDrawing");
    RegisterClass(&wndClass);
    hWnd = CreateWindow(
        TEXT("JoystickDrawing"),
        TEXT("Joystick Drawing Application"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        800,
        600,
        NULL,
        NULL,
        hInstance,
        NULL);
    g_hWnd = hWnd;
    // Create I/O Completion Port
    HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
    if (hCompletionPort == NULL) {
        printf("CreateIoCompletionPort failed: %d\n", GetLastError());
        return 1;
    }
    // Initialize serial port
    if (InitializeSerialPort(hCompletionPort) != 0) {
        CloseHandle(hCompletionPort);
        return 1;
    }
    // Create worker thread
    HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hCompletionPort, 0, NULL);
    if (hThread == NULL) {
        printf("CreateThread failed: %d\n", GetLastError());
        CloseHandle(hComm);
        CloseHandle(hCompletionPort);
        return 1;
    }
    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);
    // Message loop
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    // Cleanup
    CloseHandle(hComm);
    PostQueuedCompletionStatus(hCompletionPort, 0, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(hCompletionPort);
    GdiplusShutdown(gdiplusToken);
    return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    switch (message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        OnPaint(hdc);
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
}

Arduino JoyStick Code:

// Arduino Joystick to Serial Communication
// Connect joystick VRx to A0, VRy to A1
// This sends dx,dy movements compatible with the Windows drawing app
const int VRX_PIN = A0;  // Analog pin for X axis
const int VRY_PIN = A1;  // Analog pin for Y axis
const int SW_PIN = 2;    // Digital pin for joystick button (optional)
// Center calibration values (adjust these based on your joystick)
int centerX = 512;
int centerY = 512;
// Deadzone to prevent drift when joystick is centered
const int DEADZONE = 50;
// Sensitivity multiplier (adjust for faster/slower movement)
const float SENSITIVITY = 0.3;
// Update rate in milliseconds
const int UPDATE_INTERVAL = 20;  // 50Hz update rate
unsigned long lastUpdate = 0;
void setup() {
  Serial.begin(115200);
  
  pinMode(SW_PIN, INPUT_PULLUP);  // Button pin with pullup
  
  // Calibrate center position on startup
  delay(500);  // Wait for stable readings
  
  long sumX = 0, sumY = 0;
  const int samples = 100;
  
  for (int i = 0; i < samples; i++) {
    sumX += analogRead(VRX_PIN);
    sumY += analogRead(VRY_PIN);
    delay(5);
  }
  
  centerX = sumX / samples;
  centerY = sumY / samples;
  
  Serial.println("0,0");  // Initial position
  Serial.print("# Calibrated - Center X: ");
  Serial.print(centerX);
  Serial.print(", Center Y: ");
  Serial.println(centerY);
}
void loop() {
  unsigned long currentTime = millis();
  
  // Only send updates at the specified interval
  if (currentTime - lastUpdate >= UPDATE_INTERVAL) {
    lastUpdate = currentTime;
    
    // Read joystick positions
    int rawX = analogRead(VRX_PIN);
    int rawY = analogRead(VRY_PIN);
    
    // Calculate offset from center
    int offsetX = rawX - centerX;
    int offsetY = rawY - centerY;
    
    // Apply deadzone
    if (abs(offsetX) < DEADZONE) {
      offsetX = 0;
    }
    if (abs(offsetY) < DEADZONE) {
      offsetY = 0;
    }
    
    // Calculate movement deltas with sensitivity
    int dx = (int)(offsetX * SENSITIVITY);
    int dy = (int)(offsetY * SENSITIVITY);
    
    // Only send if there's movement
    if (dx != 0 || dy != 0) {
      Serial.print(dx);
      Serial.print(",");
      Serial.println(dy);
    }
    
    // Optional: Handle button press (e.g., reset position or change color)
    if (digitalRead(SW_PIN) == LOW) {
      Serial.println("# Button pressed");
      delay(200);  // Simple debounce
    }
  }
}
/*
 * WIRING INSTRUCTIONS:
 * ====================
 * Joystick Module -> Arduino
 * ---------------------
 * VCC  -> 5V
 * GND  -> GND
 * VRx  -> A0
 * VRy  -> A1
 * SW   -> D2 (optional, for button)
 * 
 * CALIBRATION:
 * ============
 * The code automatically calibrates the center position on startup.
 * Keep the joystick centered (not touched) when powering on.
 * 
 * ADJUSTMENTS:
 * ============
 * - Increase SENSITIVITY for faster cursor movement
 * - Decrease SENSITIVITY for slower, more precise movement
 * - Adjust DEADZONE if cursor drifts when joystick is centered
 * - Change UPDATE_INTERVAL for faster/slower response
 * 
 * TROUBLESHOOTING:
 * ================
 * 1. If movement is inverted, swap the formulas for dx/dy
 * 2. If movement is too sensitive, decrease SENSITIVITY
 * 3. If joystick drifts, increase DEADZONE
 * 4. Check serial monitor at 115200 baud to see output
 */
Developer technologies | C++
Developer technologies | C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Surya Amrutha Vaishnavi Lanka (INFOSYS LIMITED) 1,120 Reputation points Microsoft External Staff
    2025-12-05T12:24:41.9866667+00:00

    Thanks for sharing the details!

    Here are some breakdown steps which can be helpful

    1. Serial Communication: Make sure that the Arduino is properly sending the coordinates over serial communication and that your Windows app is correctly reading them. You can verify this by checking the Serial Monitor to ensure that the output format is dx,dy (like 5,10) as expected.
    2. COM Port Configuration: Double-check that the correct COM port is specified in your Windows application (currently set to COM7). Sometimes the COM port might change, so verify that it matches the one being used by the Arduino.
    3. Error Checking: The code has multiple printf statements for debugging. Make sure you check the output in the console to catch any errors during execution.
    4. Thread Handling: Ensure the worker thread is correctly processing the incoming serial data. If data is not being captured properly in WorkerThread, it might be due to the reading function failing.
    5. Bounds Checking: In process_line, ensure that the validity of g_currentX and g_currentY stays within the window size. Adjust if necessary to see if any drawing occurs.
    6. Drawing Logic: If the application is receiving the coordinates but not drawing anything, verify that wc.lpfnWndProc = WndProc; is properly set up to handle both the paint calls and any window message events.
    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.