[Python] [Windows] Serial communication in Python using DLL

When it comes to embedded systems, it is often debugged by serial communication. If you make your own IoT device, there is no shell, so a simple serial monitor comes into play.

In that case, will the standard be TeraTerm? Macros are also substantial.

However, when I try to process the serial result or convert the result to csv, I want to use Python or something (in my case, it's just a matter of familiarity, not which one is better). I think it's probably the best way to use pyserial.

But,

It feels strange to be caught in Java (what?). In another case, I was playing with python and C language, so I tried to realize serial communication this time by applying it. Create the serial communication part with DLL and access it from ctypes. This is enough for slow message exchange such as built-in.

I hope it will be helpful as to how to use Windows-specific controls from Python.

Environment, restrictions, etc.

Check below. Since it is a prototype, I would like to forgive you.

--Confirmed on Windows10 (64bit) and Windows7 (64bit). --Python is 2.7 series 32bit, and DLL is also built and used on x86. --I made a DLL in VisualStudio2015 / 2017 Community. --It is assumed that serial only handles character strings. Binaries cannot be used as they are (especially on the Python side) --The maximum communication size at one time is 1024 bytes. So with the image of doing things differently. --This time, only the COM port and BPS can be set, and the rest are the usual fixed values. Because it's basically a 3-line cross, isn't it? (If you need to change it, I think it's easy, so please modify it appropriately.)

Very easy about ctypes

The tutorial is below http://starship.python.net/crew/theller/ctypes/tutorial.html

I think that DLLs using Visual C ++ can be coded in a normal way. On the other hand, the python side needs some ingenuity, and it is necessary to define functions using ctypes. However, I think it's faster to look at the tutorial above and the actual code, so I'll expose the code immediately.

DLL side

Below is the code on the DLL side.

#include "stdafx.h"
#include "stdio.h"
#include "windows.h"

#define DLL_API __declspec(dllexport)
#define WCHAR_PORT_NUMBER_BUF_LENGTH (16 * 2)
#define SERIAL_BUFFER_SIZE (1024)

extern "C" DLL_API INT32 __stdcall SERIAL_open(char *port, INT32 baud);
extern "C" DLL_API char * __stdcall SERIAL_read(void *len);
extern "C" DLL_API INT32 __stdcall SERIAL_write(char *text, INT32 len);
extern "C" DLL_API void __stdcall SERIAL_close(void);

static HANDLE SERIAL_handle = NULL;
static char SERIAL_recv[SERIAL_BUFFER_SIZE + 1];

DLL_API INT32 __stdcall SERIAL_open(char *port, INT32 baud)
{
	TCHAR tcPort[WCHAR_PORT_NUMBER_BUF_LENGTH];
	DCB comDcb;
	BOOL success;
	INT32 ret = 0;
	COMMTIMEOUTS comTimeouts;

	memset(tcPort, 0, 32);
	MultiByteToWideChar(CP_OEMCP,
		MB_PRECOMPOSED,
		port,
		strlen(port),
		tcPort,
		WCHAR_PORT_NUMBER_BUF_LENGTH / 2);

	SERIAL_handle = CreateFile(tcPort, GENERIC_READ | GENERIC_WRITE, 0, NULL,
		OPEN_EXISTING, 0, NULL);
	if (SERIAL_handle == INVALID_HANDLE_VALUE) {
		return -1;
	}

	success = SetupComm(SERIAL_handle, SERIAL_BUFFER_SIZE, SERIAL_BUFFER_SIZE);
	if (success == FALSE)
	{
		ret = -2;
		goto error;
	}

	memset(&comDcb, 0, sizeof(DCB));
	comDcb.DCBlength = sizeof(DCB);
	comDcb.BaudRate = baud;
	comDcb.fParity = FALSE;
	comDcb.Parity = NOPARITY;
	comDcb.ByteSize = 8;
	comDcb.StopBits = ONESTOPBIT;
	success = SetCommState(SERIAL_handle, &comDcb);
	if (success == FALSE) {
		ret = -3;
		goto error;
	}

	memset(&comTimeouts, 0, sizeof(COMMTIMEOUTS));
	comTimeouts.ReadIntervalTimeout = 500;
	comTimeouts.ReadTotalTimeoutMultiplier = 0;
	comTimeouts.ReadTotalTimeoutConstant = 500;
	comTimeouts.WriteTotalTimeoutMultiplier = 0;
	comTimeouts.WriteTotalTimeoutConstant = 500;
	success = SetCommTimeouts(SERIAL_handle, &comTimeouts);
	if (success == FALSE)
	{
		ret = -4;
		goto error;
	}

	success = PurgeComm(SERIAL_handle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
	if (success == FALSE)
	{
		ret = -5;
		goto error;
	}

	return 0;

error:
	CloseHandle(SERIAL_handle);
	return ret;
}

DLL_API char * __stdcall SERIAL_read(void *len)
{
	BOOL  success;
	DWORD recvLen = 0;
	INT32 *lenToPython = (INT32 *)len;

	success = ReadFile(SERIAL_handle, SERIAL_recv, SERIAL_BUFFER_SIZE, &recvLen, NULL);
	if (success == FALSE || recvLen == 0) {
		*lenToPython = 0;
		return "";
	}

	*lenToPython = (INT32)recvLen;
	SERIAL_recv[recvLen] = '\0';
	return SERIAL_recv;
}

DLL_API INT32 __stdcall SERIAL_write(char *text, INT32 len)
{
	BOOL  success;
	DWORD dummy = 0;

	success = WriteFile(SERIAL_handle, text, strlen(text), &dummy, NULL);
	if (success == TRUE)
	{
		return 0;
	}
	else {
		return -1;
	}
}

DLL_API void __stdcall SERIAL_close(void)
{
	if (SERIAL_handle == NULL) {
		return;
	}
	CloseHandle(SERIAL_handle);
}

The following four functions are prepared. It's extremely simple.

//Port open, port="COMxx", Baud is bps. The return value is 0 for success and negative for failure
INT32 SERIAL_open(char *port, INT32 baud)

//Read data. When called, len contains the number of bytes read and char contains the received data.
//With a timeout of 500msec,*len=It can be 0.
char * SERIAL_read(void *len)

//Data writing. The text string is len bytes (as you can see from the code, the maximum is 1024 bytes)
INT32 SERIAL_write(char *text, INT32 len)

//Port close
void SERIAL_close(void)

Build this code as a DLL in Visual Studio. The points to note in that case are as follows.

--Win32 project in Visual C ++. Create a project with the DLL. --And then copy the above code into the empty cpp generated by that project. --Use dllmain.cpp as it is. --By the way, please match the number of bits with the Python environment (I confirmed it with 32 bits) --As you can see in the code, I defined each function with stdcall. --I don't want to think about def, so I use __declspec (dllexport).

The actual code is supposed to have written serial communication according to the textbook using Win32 API (rather, I'm sorry it's a degraded version). .. If you build with, you can create a dll normally. I will write the Python side as if using only this dll.

The following sites were very helpful when creating the program. http://www.geocities.co.jp/SiliconValley-SanJose/5309/serial.html http://www.ys-labo.com/BCB/2007/070512%20RS232C%20zenpan.html

Python side (serial communication using ctypes)

It's the Python side. The code of the class that performs serial communication using the above DLL is shown below. By the way, it is the code when the file name of the DLL is serial_if.dll.

#!/usr/bin/env python

from ctypes import *

'''
Describe serial DLL's functions.
'''

dll = windll.serial_if
dll.SERIAL_open.argtype = (c_char_p, c_int)
dll.SERIAL_open.restype = c_int
dll.SERIAL_read.argtype = c_void_p
dll.SERIAL_read.restype = c_char_p
dll.SERIAL_write.argtype = (c_char_p, c_int)
dll.SERIAL_write.restype = c_int

'''
Serial Device Driver
'''

class SerialDriver:
    def open(self, port, baud):
        int_ret = dll.SERIAL_open(port, baud);
        if int_ret == 0:
            return True
        else:
            return False

    def read(self):
         read_len = c_int(0)
         text = dll.SERIAL_read(byref(read_len))
         return text, read_len.value

    def write(self, text):
        write_ret = dll.SERIAL_write(text, len(text))
        if write_ret == 0:
            return True
        else:
            return False

    def close(self):
        dll.SERIAL_close();

Interface with DLL

Describe serial DLL's functions. Written in the comment is the description for handling the DLL.

This time, we have adopted the description that uses DLL using windll. At that time, first set the name of the DLL to be used as follows.

dll = windll.serial_if
# windll.(DLL file name)Specify the corresponding DLL with. The above example is serial_if.Means dll

Next, define the arguments and return value of each function exported by DLL using the description of ctypes. I will write it in comparison with the C function below.


# INT32 SERIAL_open(char *port, INT32 baud);
dll.SERIAL_open.argtype = (c_char_p, c_int)
dll.SERIAL_open.restype = c_int

# char * SERIAL_read(void *len);
dll.SERIAL_read.argtype = c_void_p
dll.SERIAL_read.restype = c_char_p

# INT32 SERIAL_write(char *text, INT32 len);
dll.SERIAL_write.argtype = (c_char_p, c_int)
dll.SERIAL_write.restype = c_int

# void SERIAL_close(void);
#No definition required if neither argument nor return value

If you have done both C and Python, you can understand the basic writing method by comparing the above. Tutorial will be helpful for dealing with fine types.

After that, you can call the defined function normally. Please note the following. Be especially careful when getting a value from C with a pointer.

The above SerialDriver class calls a DLL function to perform serial communication. Those who use this class are implemented so that they do not need to be aware of ctypes.

For the matter of value, the following site of stackoverflow was helpful. http://stackoverflow.com/questions/2330587/how-to-convert-ctypes-c-long-to-pythons-int

Example of Python side communication app

The following is a program that uses the SerialDriver class to perform actual serial communication and logs the data received serially. By the way, this is based on the assumption that the SerialDriver class is written with the file name serial_lib.py (as you can see by importing the code below).

#!/usr/bin/env python

from serial_lib import SerialDriver
from datetime import datetime
import sys
import time

def serial_read(serial, keyword):
    text = ""
    text_len = 0
    while text.find(keyword) < 0:
        read_text, read_len = serial.read()
        if read_len > 0:
            text_len += read_len
            text += read_text
    return text

def serial_test():

    filename = datetime.now().strftime('%Y%m%d%H%M%S') + ".txt"
    f = open(filename, "w")

    serial = SerialDriver()
    ret = serial.open("COM3", 115200)
    print "python:SERIAL_open=" , ret
    if ret == False:
        sys.exit()

    text = serial_read(serial,"teraterm command1")
    print text
    f.write(text)

    text = "python response1\r\n"
    serial.write(text)

    text = serial_read(serial,"teraterm command2")
    print text
    f.write(text)

    text = "python response2\r\n"
    serial.write(text)

    f.close()
    serial.close()

if __name__ == "__main__":

    serial_test()
    print "python: complete"

#EOF

Also, I wrote this file as serial_test.py. That is the premise of the following explanations.

As you can see from the code, it consists of the following functions.

--serial_test main processing --serial_read Serial reception processing

As you can see by looking at the DLL code, the reception result may be 0 bytes because it times out in 500 msec. Therefore, the process of continuing to read until a specific keyword is received is performed here (so, the mechanism is such that it will not come out unless that keyword comes. I have not taken any measures for this w)

The main process (serial_test function) is processed according to the following flow.

  1. Open the log file. The file name is "Time.txt" so that it will be a different file even if it is executed multiple times.
  2. Open the serial port.
  3. Wait until you receive the string "teraterm command1".
  4. When received, write its contents to the log file.
  5. Send "python response 1".
  6. Wait until you receive the string "teraterm command2".
  7. When received, write its contents to the log file.
  8. Send "python response 2".
  9. Close the log file and serial port.

As an aside, if it is COM10 or higher, it is useless in the above and must be described as \\. \ COM10 "(MS-like specifications as shown below). https://support.microsoft.com/ja-jp/help/115831/howto-specify-serial-ports-larger-than-com9

Behavior of communication partner (TeraTerm macro)

The other party of the above serial_test function was experimentally performed with the following TeraTerm macro.

timeout=30
sendln 'teraterm command1'
wait 'python response1'
sendln 'teraterm command2'

The contents are as stated below.

It feels like communicating with each other.

Execution method etc.

The test was run as follows.

--The DLL, the SerialDeriver class, and the test application that uses it (serial_test.py in the author's environment example) should be placed in the same folder. --Connect to the other side with a serial cable (cross). --Start TeraTerm on the other side, connect with 115200BPS, 8bit, no-parity, stop1bit and execute the above macro. ――If you start the test app within 30 seconds, they will communicate with each other. --In addition, the log file of the received data is also left.

By applying this, it is possible to perform serial communication and data analysis with Python. You will be able to check the function using serial and convert it to CSV. You should be able to do machine learning with this by taking sensor data serial.

license

I used it below. Thank you for providing the wonderful software.

that's all.

Recommended Posts

[Python] [Windows] Serial communication in Python using DLL
Mouse operation using Windows API in Python
Serial communication with Python
dll injection in python
WiringPi-SPI communication using Python
Introduction to serial communication [Python]
Python install in 2 lines @Windows
Translate using googletrans in Python
Using Python mode in Processing
TWE-Lite serial communication app Byte mode setting (send in Python)
Serial communication control with python and I2C communication (using USBGPIO8 device)
Serial communication control with python and SPI communication (using USBGPIO8 device)
[Python] Show multiple windows in Tkinter
GUI programming in Python using Appjar
Precautions when using pit in Python
Put MeCab in "Windows 10; Python3.5 (64bit)"
Try using LevelDB in Python (plyvel)
Windows10: Install MeCab library in python
Using global variables in python functions
Get, post communication memo in Python
Let's see using input in python
Infinite product in Python (using functools)
Edit videos in Python using MoviePy
Handwriting recognition using KNN in Python
Try using Leap Motion in Python
Depth-first search using stack in Python
When using regular expressions in Python
GUI creation in python using tkinter 2
Notes for using OpenCV on Windows10 Python 3.8.3.
Notes using cChardet and python3-chardet in Python 3.3.1.
Try using the Wunderlist API in Python
GUI creation in python using tkinter part 1
Get Suica balance in Python (using libpafe)
(Bad) practice of using this in Python
Slowly hash passwords using bcrypt in Python
Try using the Kraken API in Python
[50 counts] Key transmission using Python for Windows
Check and receive Serial port in Python (Port check)
Socket communication using socketserver with python now
[FX] Hit oanda-API in Python using Docker
Tweet using the Twitter API in Python
I tried using Bayesian Optimization in Python
[Python] [Windows] Take a screen capture in Python
Log in to Slack using requests in Python
Get Youtube data in Python using Youtube Data API
Building scikit-learn in Windows 10 environment using Pycharm
Using physical constants in Python scipy.constants ~ constants e ~
Scraping a website using JavaScript in Python
Develop slack bot in python using chat.postMessage
Write python modules in fortran using f2py
Draw a tree in Python 3 using graphviz
Notes for using python (pydev) in eclipse
Disease classification in Random Forest using Python
Download files in any format using Python
Parallel task execution using concurrent.futures in Python
Python garbled in Windows + Git Bash environment
Notes on using code formatter in Python
Meaning of using DI framework in Python
Python in optimization
CURL in python
Metaprogramming in Python