diff --git a/minichlink/Makefile b/minichlink/Makefile
index 6d42c72d20536afc93317717bf2fe15f954f0529..964341c11b51b66accf732be82639d887df7c0cd 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 pgm-b003fun.c
+C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c ardulink.c serial_dev.c pgm-b003fun.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..82284c66d8e0be8a342b492b33419e4f82cb0280 100644
--- a/minichlink/ardulink.c
+++ b/minichlink/ardulink.c
@@ -1,17 +1,10 @@
 #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);
+void * TryInit_Ardulink(const init_hints_t*);
 
 static int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command);
 static int ArdulinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp);
@@ -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 != '+')
@@ -99,67 +91,55 @@ int ArdulinkTargetReset(void * dev, int reset) {
     return 0;
 }
 
-void * TryInit_Ardulink(void)
+void * TryInit_Ardulink(const init_hints_t* hints)
 {
     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");
-        return NULL;
-    }
-
-    cfmakeraw(&attr);
-    cfsetspeed(&attr, 115200);
-
-    if (tcsetattr(ctx->fd, TCSANOW, &attr) == -1) {
-        perror("tcsetattr");
-        return NULL;
+    const char* serial_to_open = NULL;
+    // Get the serial port that shall be opened.
+    // First, if we have a directly set serial port hint, use that.
+    // Otherwise, use the environment variable MINICHLINK_SERIAL.
+    // If that also doesn't exist, fall back to the default serial.
+    if (hints && hints->serial_port != NULL) {
+        serial_to_open = hints->serial_port;
     }
-
-    // Arduino DTR reset.
-    if (ioctl(ctx->fd, TIOCMBIC, &argp) == -1) {
-        perror("ioctl");
-        return NULL;
+    else if ((serial_to_open = getenv("MINICHLINK_SERIAL")) == NULL) {
+        // fallback
+        serial_to_open = DEFAULT_SERIAL_NAME;
     }
 
-    if (tcdrain(ctx->fd) == -1) {
-        perror("tcdrain");
+    if (serial_dev_create(&ctx->serial, serial_to_open, 115200) == -1) {
+        perror("create");
         return NULL;
     }
 
-    if (ioctl(ctx->fd, TIOCMBIS, &argp) == -1) {
-        perror("ioctl");
+    if (serial_dev_open(&ctx->serial) == -1) {
+        perror("open");
         return NULL;
     }
 
-    if (tcdrain(ctx->fd) == -1) {
-        perror("tcdrain");
+    // Arduino DTR reset.
+    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 7e2e154aa0ad7cb0f62d2ddf8a55ba73a7cf90ea..2bb7a04429fea0d0546aeabe20ee4df20ccfc03d 100644
--- a/minichlink/minichlink.c
+++ b/minichlink/minichlink.c
@@ -8,17 +8,16 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <getopt.h>
 #include "minichlink.h"
 #include "../ch32v003fun/ch32v003fun.h"
 
 #if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
-#define DISABLE_ARDULINK
 void Sleep(uint32_t dwMilliseconds);
 #else
 #include <unistd.h>
 #endif
 
-
 static int64_t StringToMemoryAddress( const char * number ) __attribute__((used));
 static void StaticUpdatePROGBUFRegs( void * dev ) __attribute__((used));
 static int InternalUnlockBootloader( void * dev ) __attribute__((used));
@@ -27,7 +26,7 @@ int DefaultReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t r
 void TestFunction(void * v );
 struct MiniChlinkFunctions MCF;
 
-void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO )
+void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO, const init_hints_t* init_hints )
 {
 	void * dev = 0;
 	if( (dev = TryInit_WCHLinkE()) )
@@ -46,12 +45,10 @@ void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO )
 	{
 		fprintf( stderr, "Found B003Fun Bootloader\n" );
 	}
-#ifndef DISABLE_ARDULINK
-	else if ((dev = TryInit_Ardulink()))
+	else if ((dev = TryInit_Ardulink(init_hints)))
 	{
 		fprintf( stderr, "Found Ardulink Programmer\n" );
 	}
-#endif
 	else
 	{
 		fprintf( stderr, "Error: Could not initialize any supported programmers\n" );
@@ -66,6 +63,27 @@ void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO )
 	return dev;
 }
 
+void parse_possible_init_hints(int argc, char **argv, init_hints_t *hints)
+{
+	if (!hints)
+		return;
+	int c;
+	opterr = 0;
+	/* we're only interested in the value for the COM port, given in a -c parameter */
+	/* the '-' is really important so that getopt does not permutate the argv array and messes up parsing later */
+	while ((c = getopt(argc, argv, "-c:")) != -1)
+	{
+		switch (c)
+		{
+		case 'c':
+			// we can use the pointer as-is because it points in our 
+			// argv array and that is stable.
+			hints->serial_port = optarg;
+			break;
+		}
+	}
+}
+
 #if !defined( MINICHLINK_AS_LIBRARY ) && !defined( MINICHLINK_IMPORT )
 int main( int argc, char ** argv )
 {
@@ -73,7 +91,10 @@ int main( int argc, char ** argv )
 	{
 		goto help;
 	}
-	void * dev = MiniCHLinkInitAsDLL( 0 );
+	init_hints_t hints;
+	memset(&hints, 0, sizeof(hints));
+	parse_possible_init_hints(argc, argv, &hints);
+	void * dev = MiniCHLinkInitAsDLL( 0, &hints );
 	if( !dev )
 	{
 		fprintf( stderr, "Error: Could not initialize any supported programmers\n" );
@@ -151,6 +172,16 @@ keep_going:
 				else
 					goto unimplemented;
 				break;
+			case 'c':
+				// COM port argument already parsed previously
+				// we still need to skip the next argument
+				iarg+=1;
+				if( iarg >= argc )
+				{
+					fprintf( stderr, "-c argument (COM port) required 2 arguments\n" );
+					goto unimplemented;
+				}
+				break;
 			case 'u':
 				if( MCF.Unbrick )
 					MCF.Unbrick( dev );
@@ -560,6 +591,7 @@ help:
 	fprintf( stderr, " -5 Enable 5V\n" );
 	fprintf( stderr, " -t Disable 3.3V\n" );
 	fprintf( stderr, " -f Disable 5V\n" );
+	fprintf( stderr, " -c [serial port for Ardulink, try /dev/ttyACM0 or COM11 etc]\n" );
 	fprintf( stderr, " -u Clear all code flash - by power off (also can unbrick)\n" );
 	fprintf( stderr, " -b Reboot out of Halt\n" );
 	fprintf( stderr, " -e Resume from halt\n" );
@@ -1543,6 +1575,11 @@ static int DefaultHaltMode( void * dev, int mode )
 	}
 #endif
 
+	// pull reset line back to 0 again (NO RESET)
+	if (MCF.TargetReset) {
+		MCF.TargetReset(dev, 0);
+	}
+
 	iss->processor_in_mode = mode;
 	return 0;
 }
diff --git a/minichlink/minichlink.exe b/minichlink/minichlink.exe
index be95741c4b2eb1f14e5805055bedb7a41cab47d1..a52fdbc65872c0fc70a1facfb7fa9cbe22a35552 100644
Binary files a/minichlink/minichlink.exe and b/minichlink/minichlink.exe differ
diff --git a/minichlink/minichlink.h b/minichlink/minichlink.h
index 37960eacafe4751fdb2424adb80129134697a714..77310efb917dcf421acde7fc7436cec613c5fd32 100644
--- a/minichlink/minichlink.h
+++ b/minichlink/minichlink.h
@@ -145,16 +145,22 @@ struct InternalState
 	#define DLLDECORATE
 #endif
 
-void * MiniCHLinkInitAsDLL(struct MiniChlinkFunctions ** MCFO) DLLDECORATE;
+/* initialization hints for init functions */
+/* could be expanded with more in the future (e.g., PID/VID hints, priorities, ...)*/
+/* not all init functions currently need these hints. */
+typedef struct {
+	const char* serial_port;
+} init_hints_t;
+
+void * MiniCHLinkInitAsDLL(struct MiniChlinkFunctions ** MCFO, const init_hints_t* init_hints) DLLDECORATE;
 extern struct MiniChlinkFunctions MCF;
 
-
 // Returns 'dev' on success, else 0.
 void * TryInit_WCHLinkE(void);
 void * TryInit_ESP32S2CHFUN(void);
 void * TryInit_NHCLink042(void);
 void * TryInit_B003Fun(void);
-void * TryInit_Ardulink();
+void * TryInit_Ardulink(const init_hints_t*);
 
 // Returns 0 if ok, populated, 1 if not populated.
 int SetupAutomaticHighLevelFunctions( void * dev );
diff --git a/minichlink/serial_dev.c b/minichlink/serial_dev.c
new file mode 100644
index 0000000000000000000000000000000000000000..f39780fa580f833a47a6ecb8ee756957937637fc
--- /dev/null
+++ b/minichlink/serial_dev.c
@@ -0,0 +1,173 @@
+#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
+    // Windows quirk: port = "COM10" is invalid, has to be encoded as "\\.\COM10".
+    // This also works for COM below 9. So, let's give the user the ability to use
+    // any "COMx" string and just prepend the "\\.\".
+    char winPortName[64];
+    if(dev->port[0] != '\\') {
+        snprintf(winPortName, sizeof(winPortName), "\\\\.\\%s", dev->port);
+    } else {
+        // copy verbatim if string already starts with a '\'
+        snprintf(winPortName, sizeof(winPortName), "%s", dev->port);
+    }
+    dev->handle = CreateFileA(winPortName, 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);
+            // weird: without this, errno = 0 (no error).
+            _set_errno(ERROR_FILE_NOT_FOUND);
+            return -1; // Device not found
+        }
+        // Error while opening the device
+        return -1;
+    }
+    DCB dcbSerialParams;
+    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
+    if (!GetCommState(dev->handle, &dcbSerialParams)) {
+        return -1;
+    }
+    // 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 -1;
+    }
+    // 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 -1;
+    }
+#else
+    struct termios attr;
+    if ((dev->fd = open(dev->port, O_RDWR | O_NOCTTY)) == -1) {
+        perror("open");
+        return -1;
+    }
+
+    if (tcgetattr(dev->fd, &attr) == -1) {
+        perror("tcgetattr");
+        return -1;
+    }
+
+    cfmakeraw(&attr);
+    cfsetspeed(&attr, dev->baud);
+
+    if (tcsetattr(dev->fd, TCSANOW, &attr) == -1) {
+        perror("tcsetattr");
+        return -1;
+    }
+#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 -1;
+    }
+
+    if (ioctl(dev->fd, TIOCMBIS, &argp) == -1) {
+        perror("ioctl");
+        return -1;
+    }
+#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