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

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.