diff --git a/minichlink/Makefile b/minichlink/Makefile
index a60c9f9b4f4ff6706cbab48dfec14641a1495561..15d3c1a5a3f680519403ae26d9bdb8883b348351 100644
--- a/minichlink/Makefile
+++ b/minichlink/Makefile
@@ -1,7 +1,7 @@
 TOOLS:=minichlink minichlink.so
 
 CFLAGS:=-O0 -g3 -Wall
-C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c minichgdb.c
+C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c ardulink.c serial_dev.c minichgdb.c
 
 # General Note: To use with GDB, gdb-multiarch
 # gdb-multilib {file}
@@ -23,9 +23,6 @@ else
 		LIBINCLUDES:=$(shell pkg-config --libs-only-L libusb-1.0)
 		INCS:=$(INCLUDES) $(LIBINCLUDES)
 	endif
-
-    # Until it's supported on Windows, add the Ardulink file here.
-    C_S:=$(C_S) ardulink.c
 endif
 
 all : $(TOOLS)
diff --git a/minichlink/ardulink.c b/minichlink/ardulink.c
index 5da4d10640b31c44aaa2c8651182aa14aedb8a08..44fa1446f831715b03b56fde47cad800cbaa04c7 100644
--- a/minichlink/ardulink.c
+++ b/minichlink/ardulink.c
@@ -1,14 +1,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
-
-#include <termios.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-
-#include <errno.h>
-
+#include "serial_dev.h"
 #include "minichlink.h"
 
 void * TryInit_Ardulink(void);
@@ -19,10 +12,9 @@ static int ArdulinkFlushLLCommands(void * dev);
 static int ArdulinkExit(void * dev);
 static int ArdulinkTargetReset(void * dev, int reset);
 
-
 typedef struct {
     struct ProgrammerStructBase psb;
-    int fd;
+    serial_dev_t serial;
 } ardulink_ctx_t;
 
 int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command)
@@ -38,10 +30,10 @@ int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command)
     buf[4] = (command >> 16) & 0xff;
     buf[5] = (command >> 24) & 0xff;
 
-    if (write(((ardulink_ctx_t*)dev)->fd, buf, 6) == -1)
+    if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, buf, 6) == -1)
         return -errno;
 
-    if (read(((ardulink_ctx_t*)dev)->fd, buf, 1) == -1)
+    if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, buf, 1) == -1)
         return -errno;
 
     return buf[0] == '+' ? 0 : -EPROTO;
@@ -53,10 +45,10 @@ int ArdulinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp)
     buf[0] = 'r';
     buf[1] = reg_7_bit;
 
-    if (write(((ardulink_ctx_t*)dev)->fd, buf, 2) == -1)
+    if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, buf, 2) == -1)
         return -errno;
 
-    if (read(((ardulink_ctx_t*)dev)->fd, buf, 4) == -1)
+    if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, buf, 4) == -1)
         return -errno;
 
     *commandresp = (uint32_t)buf[0] | (uint32_t)buf[1] << 8 | \
@@ -74,7 +66,7 @@ int ArdulinkFlushLLCommands(void * dev)
 
 int ArdulinkExit(void * dev)
 {
-    close(((ardulink_ctx_t*)dev)->fd);
+    serial_dev_close(&((ardulink_ctx_t*)dev)->serial);
     free(dev);
     return 0;
 }
@@ -86,10 +78,10 @@ int ArdulinkTargetReset(void * dev, int reset) {
 
     // Assert reset.
     c = reset ? 'a' : 'A';
-    if (write(((ardulink_ctx_t*)dev)->fd, &c, 1) == -1)
+    if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, &c, 1) == -1)
         return -errno;
 
-    if (read(((ardulink_ctx_t*)dev)->fd, &c, 1) == -1)
+    if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, &c, 1) == -1)
         return -errno;
 
     if (c != '+')
@@ -102,64 +94,39 @@ int ArdulinkTargetReset(void * dev, int reset) {
 void * TryInit_Ardulink(void)
 {
     ardulink_ctx_t *ctx;
-    struct termios attr;
     char first;
-    int argp = TIOCM_DTR;
 
     if (!(ctx = calloc(sizeof(ardulink_ctx_t), 1))) {
         perror("calloc");
         return NULL;
     }
 
-    if ((ctx->fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY)) == -1) {
-        perror("open");
-        return NULL;
-    }
-
-    if (tcgetattr(ctx->fd, &attr) == -1) {
-        perror("tcgetattr");
+    if (serial_dev_create(&ctx->serial, DEFAULT_SERIAL_NAME, 115200) == -1) {
+        perror("create");
         return NULL;
     }
 
-    cfmakeraw(&attr);
-    cfsetspeed(&attr, 115200);
-
-    if (tcsetattr(ctx->fd, TCSANOW, &attr) == -1) {
-        perror("tcsetattr");
+    if (serial_dev_open(&ctx->serial) == -1) {
+        perror("open");
         return NULL;
     }
 
     // Arduino DTR reset.
-    if (ioctl(ctx->fd, TIOCMBIC, &argp) == -1) {
-        perror("ioctl");
-        return NULL;
-    }
-
-    if (tcdrain(ctx->fd) == -1) {
-        perror("tcdrain");
-        return NULL;
-    }
-
-    if (ioctl(ctx->fd, TIOCMBIS, &argp) == -1) {
-        perror("ioctl");
-        return NULL;
-    }
-
-    if (tcdrain(ctx->fd) == -1) {
-        perror("tcdrain");
+    if (serial_dev_do_dtr_reset(&ctx->serial) == -1) {
+        perror("dtr reset");
         return NULL;
     }
 
     // Flush anything that might be in the RX buffer, we need the sync char.
-    if (tcflush(ctx->fd, TCIFLUSH) == -1) {
-        perror("tcflush");
+    if (serial_dev_flush_rx(&ctx->serial) == -1) {
+        perror("flush rx");
         return NULL;
     }
 
     // Let the bootloader do its thing.
-    sleep(3);
+    usleep(3UL*1000UL*1000UL);
 
-    if (read(ctx->fd, &first, 1) == -1) {
+    if (serial_dev_read(&ctx->serial, &first, 1) == -1) {
         perror("read");
         return NULL;
     }
diff --git a/minichlink/minichlink.c b/minichlink/minichlink.c
index 905fd6c035462e549705a43b71b19c62e3ea6a9b..0513750e3eabf1ca302d370c1941fe092d597341 100644
--- a/minichlink/minichlink.c
+++ b/minichlink/minichlink.c
@@ -12,7 +12,6 @@
 #include "../ch32v003fun/ch32v003fun.h"
 
 #if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
-#define DISABLE_ARDULINK
 void Sleep(uint32_t dwMilliseconds);
 #else
 #include <unistd.h>
diff --git a/minichlink/minichlink.exe b/minichlink/minichlink.exe
index be95741c4b2eb1f14e5805055bedb7a41cab47d1..f366cfcf2e1fb0fd2451a2c4217a62650e72829a 100644
Binary files a/minichlink/minichlink.exe and b/minichlink/minichlink.exe differ
diff --git a/minichlink/serial_dev.c b/minichlink/serial_dev.c
new file mode 100644
index 0000000000000000000000000000000000000000..7afe36d394ddbfea36e5f763e523da1a823b17ac
--- /dev/null
+++ b/minichlink/serial_dev.c
@@ -0,0 +1,161 @@
+#include "serial_dev.h"
+
+int serial_dev_create(serial_dev_t *dev, const char* port, unsigned baud) {
+    if (!dev) 
+        return -1;
+    dev->port = port;
+    dev->baud = baud;
+    #ifdef IS_WINDOWS
+    dev->handle = INVALID_HANDLE_VALUE;
+    #else
+    dev->fd = -1;
+    #endif
+    return 0;
+}
+
+int serial_dev_open(serial_dev_t *dev) {
+    fprintf(stderr, "Opening serial port %s at %u baud.\n", dev->port, dev->baud);
+#ifdef IS_WINDOWS
+    dev->handle = CreateFileA(dev->port, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0,0);
+    if (dev->handle == INVALID_HANDLE_VALUE) {
+        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+            fprintf(stderr, "Serial port %s not found.\n", dev->port);
+            return -1; // Device not found
+        }
+        // Error while opening the device
+        return -2;
+    }
+    DCB dcbSerialParams;
+    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
+    if (!GetCommState(dev->handle, &dcbSerialParams)) {
+        return -3;
+    }
+    // set baud and 8N1 serial formatting
+    dcbSerialParams.BaudRate = dev->baud;
+    dcbSerialParams.ByteSize = 8;
+    dcbSerialParams.StopBits = ONESTOPBIT;
+    dcbSerialParams.Parity = NOPARITY;
+    // write back
+    if (!SetCommState(dev->handle, &dcbSerialParams)){ 
+        return -5;
+    }
+    // Set the timeout parameters to "no timeout" (blocking).
+    // see https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts
+    COMMTIMEOUTS timeouts;
+    timeouts.ReadIntervalTimeout = 0;
+    timeouts.ReadTotalTimeoutConstant = MAXDWORD;
+    timeouts.ReadTotalTimeoutMultiplier = 0;
+    timeouts.WriteTotalTimeoutConstant = MAXDWORD;
+    timeouts.WriteTotalTimeoutMultiplier = 0;
+    // Write the parameters
+    if (!SetCommTimeouts(dev->handle, &timeouts)) {
+        return -6;
+    }
+#else
+    struct termios attr;
+    if ((dev->fd = open(dev->port, O_RDWR | O_NOCTTY)) == -1) {
+        perror("open");
+        return -1;
+    }
+
+    if (tcgetattr(ctx->fd, &attr) == -1) {
+        perror("tcgetattr");
+        return -2;
+    }
+
+    cfmakeraw(&attr);
+    cfsetspeed(&attr, dev->baud);
+
+    if (tcsetattr(ctx->fd, TCSANOW, &attr) == -1) {
+        perror("tcsetattr");
+        return -3;
+    }
+#endif
+    // all okay if we get here
+    return 0;
+}
+
+int serial_dev_write(serial_dev_t *dev, const void* data, size_t len) {
+#ifdef IS_WINDOWS
+    DWORD dwBytesWritten;
+    if (!WriteFile(dev->handle, data, len, &dwBytesWritten,NULL)) {
+        return -1;
+    }
+    return (int) dwBytesWritten;
+#else
+    return write(dev->fd, data, len);
+#endif
+}
+
+int serial_dev_read(serial_dev_t *dev, void* data, size_t len) {
+#ifdef IS_WINDOWS
+    DWORD dwBytesRead = 0;
+    if (!ReadFile(dev->handle, data, len, &dwBytesRead, NULL)) {
+        return -1;
+    }
+    return (int) dwBytesRead;
+#else
+    return read(dev->fd, data, len);
+#endif
+}
+
+int serial_dev_do_dtr_reset(serial_dev_t *dev) {
+#ifdef IS_WINDOWS
+    // EscapeCommFunction returns 0 on fail
+    if(EscapeCommFunction(dev->handle, SETDTR) == 0) {
+        return -1;
+    }
+    if(EscapeCommFunction(dev->handle, CLRDTR) == 0) {
+        return -1;
+    }
+#else
+    int argp = TIOCM_DTR;
+    // Arduino DTR reset.
+    if (ioctl(dev->fd, TIOCMBIC, &argp) == -1) {
+        perror("ioctl");
+        return -1;
+    }
+
+    if (tcdrain(dev->fd) == -1) {
+        perror("tcdrain");
+        return -2;
+    }
+
+    if (ioctl(dev->fd, TIOCMBIS, &argp) == -1) {
+        perror("ioctl");
+        return -3;
+    }
+#endif
+    return 0;
+}
+
+int serial_dev_flush_rx(serial_dev_t *dev) {
+#ifdef IS_WINDOWS
+    // PurgeComm returns 0 on fail
+    if (PurgeComm(dev->handle, PURGE_RXCLEAR) == 0) {
+        return -1;
+    }
+#else
+    if (tcflush(dev->fd, TCIFLUSH) == -1) {
+        perror("tcflush");
+        return -1;
+    }
+#endif
+    return 0;
+}
+
+int serial_dev_close(serial_dev_t *dev) {
+#ifdef IS_WINDOWS
+    if(!CloseHandle(dev->handle)) {
+        return -1;
+    }
+    dev->handle = INVALID_HANDLE_VALUE;
+#else
+    int ret = 0;
+    if((ret = close(dev->fd)) != 0) {
+        return ret;
+    }
+    dev->fd = -1;
+#endif
+    return 0;
+}
\ No newline at end of file
diff --git a/minichlink/serial_dev.h b/minichlink/serial_dev.h
new file mode 100644
index 0000000000000000000000000000000000000000..a75733f41870308408ce0b2f5b82d20ef7ccfe54
--- /dev/null
+++ b/minichlink/serial_dev.h
@@ -0,0 +1,48 @@
+#ifndef _SERIAL_DEV_H
+#define _SERIAL_DEV_H
+
+#include <stddef.h>
+
+#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#define IS_WINDOWS
+#define DEFAULT_SERIAL_NAME "\\\\.\\COM3"
+#else
+#include <termios.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#define IS_POSIX
+#define DEFAULT_SERIAL_NAME "/dev/ttyACM0"
+#endif
+/* these are available on all platforms */
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+
+typedef struct {
+    const char* port;
+    unsigned baud;
+#ifdef IS_WINDOWS
+    HANDLE handle;
+#else
+    int fd;
+#endif
+} serial_dev_t;
+
+/* returns 0 if OK */
+int serial_dev_create(serial_dev_t *dev, const char* port, unsigned baud);
+/* returns 0 if OK */
+int serial_dev_open(serial_dev_t *dev);
+/* returns -1 on write error */
+int serial_dev_write(serial_dev_t *dev, const void* data, size_t len);
+/* returns -1 on read error */
+int serial_dev_read(serial_dev_t *dev, void* data, size_t len);
+/* returns -1 on reset error */
+int serial_dev_do_dtr_reset(serial_dev_t *dev);
+/* returns -1 on flush error */
+int serial_dev_flush_rx(serial_dev_t *dev);
+/* returns -1 on close error */
+int serial_dev_close(serial_dev_t *dev);
+
+#endif