Commit d6c4247c authored by Gallacchi Mattia's avatar Gallacchi Mattia
Browse files

Add serial class for windows

parent c1088c5f
Loading
Loading
Loading
Loading
+39 −4
Original line number Diff line number Diff line
@@ -5,7 +5,14 @@
#include <cstdint>
#include <string>

#ifdef _WIN32
#include <Windows.h>
#endif

#define MCP2221_NAME            "MCP2221 USB-I2C/UART Combo"
#define I2C_SPEED_100KHZ        100000
#define I2C_SPEED_400KHZ        400000
#define I2C_TIMEOUT_1MS         1

namespace MCP2221A {

@@ -44,6 +51,8 @@ namespace MCP2221A {
        std::string mcp2221a_path_;
        uint8_t current_slave_addr_;
        std::vector<int> slaves_addr_;
        unsigned int i2c_speed_;
        unsigned char i2c_timeout_ms_;

        /**
         * @brief Scan the I2C bus looking for available devices
@@ -79,7 +88,7 @@ namespace MCP2221A {
         * @throw I2CMasterException if something fails.
         */
#ifdef _WIN32
        I2CMaster(std::string port = "COM3");
        I2CMaster(unsigned int speed = I2C_SPEED_100KHZ, unsigned char timeout_ms = I2C_TIMEOUT_1MS);
#elif UNIX
        I2CMaster(std::string port = "ttyACM0");
#endif
@@ -129,6 +138,15 @@ namespace MCP2221A {
         * @return std::vector<int> Slave addresses. List can be empty if no slave are found
         */
        std::vector<int> get_slaves_addresses(bool force_scan = false);

        /**
         * @brief Set the I2C bus speed
         * 
         * @param speed Speed in Hz. Supported speeds are 100000 (standard mode) and 400000 (fast mode)
         * 
         * @throw I2CMasterException if an error occurs
         */
        void set_speed(uint32_t speed);
    };

    /**
@@ -154,10 +172,16 @@ namespace MCP2221A {
        };
        
        private:
#ifdef UNIX
        int fd_;
        std::string port_;
        BAUD baudrate_;
        uint8_t read_timeout_;
#elif _WIN32
        HANDLE handle_;
        std::wstring port_;
        DWORD read_timeout_;
#endif
        BAUD baudrate_;

        public:
        /**
@@ -169,7 +193,7 @@ namespace MCP2221A {
         * @throw SerialException if something fails.
         */
#ifdef _WIN32
        Serial(std::string port = "COM3", BAUD baudrate = B_115200);
        Serial(std::wstring port = std::wstring(L"COM3"), BAUD baudrate = B_115200);
#elif UNIX
        Serial(std::string port = "ttyACM0", BAUD baudrate = B_115200);
#endif
@@ -209,6 +233,7 @@ namespace MCP2221A {
         */
        void set_baudrate(BAUD baudrate);

#ifdef UNIX
        /**
         * @brief Set the timeout 
         * 
@@ -217,5 +242,15 @@ namespace MCP2221A {
         * @throw SerialException if an error occurs
         */
        void set_timeout(uint8_t timeout);
#elif _WIN32
        /**
         * @brief Set the timeout 
         * 
         * @param timeout Timeout value in milliseconds.
         * 
         * @throw SerialException if an error occurs
         */
        void set_timeout(DWORD timeout_ms);
#endif
    };
}
 No newline at end of file
+30 −9
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@
#define I2C_MIN_ADDR        0x08
#define I2C_10BIT_ADDRESS   0
#define I2C_7BIT_ADDRESS    1
#define I2C_SPEED_100KHZ    100000
#define I2C_MAX_RETRIES     5

using namespace std;

@@ -43,7 +43,7 @@ namespace MCP2221A {
        }
    }

    I2CMaster::I2CMaster(std::string port) : handle_(nullptr), mcp2221a_path_(port), current_slave_addr_(0)
    I2CMaster::I2CMaster(unsigned int speed, unsigned char timeout_ms) : handle_(nullptr), current_slave_addr_(0), i2c_speed_(speed), i2c_timeout_ms_(timeout_ms)
    {
        unsigned int device_count = 0;
        int ret = 0;
@@ -63,8 +63,11 @@ namespace MCP2221A {
            throw I2CMasterException("Failed to open MCP2221A device: " + get_error_string(ret));
        }

        if ((ret = Mcp2221_SetSpeed(handle_, I2C_SPEED_100KHZ)) < 0) {
            throw I2CMasterException("Failed to set I2C speed: " + get_error_string(ret));
        set_speed(speed);

        if ((ret = Mcp2221_SetAdvancedCommParams(handle_, i2c_timeout_ms_, I2C_MAX_RETRIES)) < 0) {
            handle_ = nullptr;
            throw I2CMasterException("Failed to set I2C advanced communication parameters: " + get_error_string(ret));
        }
    }   

@@ -83,15 +86,24 @@ namespace MCP2221A {
        int ret = 0;
        unsigned char dummy = 0;

        slaves_addr_.clear();

        if ((ret = Mcp2221_SetAdvancedCommParams(handle_, I2C_TIMEOUT_1MS, 2)) < 0) {
            throw I2CMasterException("Failed to set I2C advanced communication parameters: " + get_error_string(ret));
        }

        for (unsigned char addr = I2C_MIN_ADDR; addr < I2C_MAX_ADDR; addr++) {
            // if ((ret = Mcp2221_I2cWrite(handle_, sizeof(dummy), addr, I2C_7BIT_ADDRESS, &dummy)) < 0) {
            //     throw I2CMasterException(get_error_string(ret));
            // }
            if ((ret = Mcp2221_I2cRead(handle_, sizeof(dummy), addr, I2C_7BIT_ADDRESS, &dummy)) < 0) {
            try {
                receive(addr, &dummy, 1);
            } catch (const I2CMasterException &e) {
                continue;
            }
            slaves_addr_.push_back(addr);
        }

        if ((ret = Mcp2221_SetAdvancedCommParams(handle_, i2c_timeout_ms_, I2C_MAX_RETRIES)) < 0) {
            throw I2CMasterException("Failed to set I2C advanced communication parameters: " + get_error_string(ret));
        }
    }

    void I2CMaster::set_slave_address(uint8_t addr)
@@ -144,4 +156,13 @@ namespace MCP2221A {
        return slaves_addr_;
    }

    void I2CMaster::set_speed(uint32_t speed)
    {
        int ret = 0;

        if ((ret = Mcp2221_SetSpeed(handle_, speed)) < 0) {
            throw I2CMasterException("Failed to set I2C speed: " + get_error_string(ret));
        }
    }

} // namespace MCP2221A
 No newline at end of file
+241 −9
Original line number Diff line number Diff line
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif

#include <string>
#include <iostream>
#include <map>

extern "C" {
    #include <Windows.h>
#include "mcp2221a.h"
    #include <setupapi.h>
    #include <devguid.h>
    #include <regstr.h>
    #include <tchar.h>
}

#include "mcp2221a.h"
#include "mcp2221_dll_um.h"

#pragma comment(lib, "setupapi.lib")

#define MCP2221A_VID       "04D8"
#define MCP2221A_PID       "00DD"
#define READ_TIMEOUT        1000 // in milliseconds

namespace MCP2221A {
    static std::map<Serial::BAUD, int> baud_map = {
        {Serial::B_300, CBR_300},
        {Serial::B_1200, CBR_1200},
        {Serial::B_2400, CBR_2400},
        {Serial::B_4800, CBR_4800},
        {Serial::B_9600, CBR_9600},
        {Serial::B_19200, CBR_19200},
        {Serial::B_38400, CBR_38400},
        {Serial::B_57600, CBR_57600},
        {Serial::B_115200, CBR_115200}
    };

    static void PrintCommState(DCB dcb)
    {
        //  Print some of the DCB structure values
        _tprintf( TEXT("\nBaudRate = %d, ByteSize = %d, Parity = %d, StopBits = %d\n"), 
              dcb.BaudRate, 
              dcb.ByteSize, 
              dcb.Parity,
              dcb.StopBits );
    }

    static bool check_com_port_vid_pid(const std::wstring& targetPort, const std::string& targetVid = MCP2221A_VID, const std::string& targetPid = MCP2221A_PID)
    {
        bool ret = false;

        HDEVINFO deviceInfoSet = SetupDiGetClassDevs(
            (const GUID*)&GUID_DEVCLASS_PORTS,
            NULL,
            NULL,
            DIGCF_PRESENT
        );

        if (deviceInfoSet == INVALID_HANDLE_VALUE) {
            std::cerr << "Failed to get device info set." << std::endl;
            return false;
        }

        SP_DEVINFO_DATA deviceInfoData;
        deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

        for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfoData); ++i) {
            TCHAR deviceInstanceId[512];
            if (SetupDiGetDeviceInstanceId(deviceInfoSet, &deviceInfoData, deviceInstanceId, sizeof(deviceInstanceId) / sizeof(TCHAR), NULL)) {
                // Example: "USB\\VID_2341&PID_0043\\55639313337351D011E1"
                std::basic_string<TCHAR> instance(deviceInstanceId);

                // Retrieve COM port name
                HKEY hDeviceRegistryKey = SetupDiOpenDevRegKey(
                    deviceInfoSet,
                    &deviceInfoData,
                    DICS_FLAG_GLOBAL,
                    0,
                    DIREG_DEV,
                    KEY_READ
                );

    Serial::Serial(std::string port, BAUD baudrate)
                if (hDeviceRegistryKey == INVALID_HANDLE_VALUE)
                    continue;

                TCHAR portName[256];
                DWORD size = sizeof(portName);
                DWORD type = 0;

                if (RegQueryValueEx(hDeviceRegistryKey, _T("PortName"), NULL, &type, (LPBYTE)portName, &size) == ERROR_SUCCESS) {
                    std::basic_string<TCHAR> tsPortName(portName);
                    std::wstring comPort(tsPortName.begin(), tsPortName.end());

                    if (comPort == targetPort) {
                        // std::wcout << L"Found port: " << tsPortName.c_str() << std::endl;
                        // std::wcout << L"Instance ID: " << instance << std::endl;

                        // Extract VID and PID
                        size_t vidPos = instance.find(_T("VID_"));
                        size_t pidPos = instance.find(_T("PID_"));

                        if (vidPos != std::basic_string<TCHAR>::npos && pidPos != std::basic_string<TCHAR>::npos) {
                            std::basic_string<TCHAR> vid = instance.substr(vidPos + 4, 4);
                            std::basic_string<TCHAR> pid = instance.substr(pidPos + 4, 4);
                            std::string strVid(vid.begin(), vid.end());
                            std::string strPid(pid.begin(), pid.end());

                            if (strVid == targetVid && strPid == targetPid) {
                                ret = true;
                                RegCloseKey(hDeviceRegistryKey);
                                break;
                            }
                        }
                    }
                }

                RegCloseKey(hDeviceRegistryKey);
            }
        }

        SetupDiDestroyDeviceInfoList(deviceInfoSet);

        return ret;
    }

    Serial::Serial(std::wstring port, BAUD baudrate): handle_(NULL), port_(port), baudrate_(baudrate), read_timeout_(READ_TIMEOUT)
    {
        DCB dcb;
        bool ret;

        TCHAR *port_name = new TCHAR[port.length() + 8]; // +8 for "\\\\.\\"
        if (!port_name) {
            throw SerialException("Failed to allocate memory for port name");
        }

        if (_stprintf(port_name, TEXT("\\\\.\\%s"), port.c_str()) < 0) {
            delete[] port_name;
            throw SerialException("Failed to format port name");
        }

        if (!check_com_port_vid_pid(port_)) {
            delete[] port_name;
            std::string narrow_port(port_.begin(), port_.end());
            throw SerialException(MCP2221_NAME " not found or not connected on port " + narrow_port);
        }

        handle_ = CreateFile(port_name,
                             GENERIC_READ | GENERIC_WRITE,
                             0,
                             NULL,
                             OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL,
                             NULL);

        if (handle_ == INVALID_HANDLE_VALUE) {
            delete[] port_name;
            std::string narrow_port(port_.begin(), port_.end());
            throw SerialException("Failed to open serial port " + narrow_port);
        }

        SecureZeroMemory(&dcb, sizeof(DCB));
        dcb.DCBlength = sizeof(DCB);

        ret = GetCommState(handle_, &dcb);
        if (!ret) {
            CloseHandle(handle_);
            delete[] port_name;
            throw SerialException("Failed to get serial port state");
        }
        
        dcb.BaudRate = baud_map[baudrate_];
        dcb.ByteSize = 8;
        dcb.Parity   = NOPARITY;
        dcb.StopBits = ONESTOPBIT;
        ret = SetCommState(handle_, &dcb);
        if (!ret) {
            CloseHandle(handle_);
            delete[] port_name;
            throw SerialException("Failed to set serial port state");
        }

        set_timeout(read_timeout_);
    }   

    // Implementations of pure virtual destructor
    Serial::~Serial() 
    {

        delete[] port_.c_str();
        if (handle_ != INVALID_HANDLE_VALUE) {
            CloseHandle(handle_);
        }
    }

    void Serial::send(uint8_t *data, size_t length) 
    {
        // Windows implementation to send data
        DWORD bytes_written = 0;
        bool ret;

        ret = WriteFile(handle_, data, length, &bytes_written, NULL);
        if (!ret) {
            throw SerialException("Failed to send data");
        }
    }

    void Serial::receive(uint8_t *data, size_t length) 
    {
        // Windows implementation to receive data
        DWORD bytes_read = 0;
        bool ret;

        ret = ReadFile(handle_, data, length, &bytes_read, NULL);
        if (!ret) {
            throw SerialException("Failed to receive data");
        }

        if (bytes_read < length) {
            throw TimeoutException("Timeout expired and " + std::to_string(bytes_read) + "/" + std::to_string(length) + " bytes were received");
        }
    }

    void Serial::set_baudrate(BAUD baudrate)
    {
        // Windows implementation to set baudrate
        DCB dcb;
        bool ret;

        if (baudrate_ == baudrate)
            return;
        
        SecureZeroMemory(&dcb, sizeof(DCB));
        dcb.DCBlength = sizeof(DCB);

        ret = GetCommState(handle_, &dcb);
        if (!ret) {
            throw SerialException("Failed to get serial port state");
        }

        dcb.BaudRate = baud_map[baudrate];

        ret = SetCommState(handle_, &dcb);
        if (!ret) {
            throw SerialException("Failed to set serial port state");
        }

    void Serial::set_timeout(uint8_t timeout)
        baudrate_ = baudrate;
    }

    void Serial::set_timeout(DWORD timeout_ms)
    {
        // Windows implementation to set timeout
        if (read_timeout_ == timeout_ms)
            return;

        COMMTIMEOUTS timeouts;
        SecureZeroMemory(&timeouts, sizeof(COMMTIMEOUTS));
        
        if (!GetCommTimeouts(handle_, &timeouts)) {
            throw SerialException("Failed to get serial port timeouts");
        }

        timeouts.ReadTotalTimeoutConstant = timeout_ms;

        if (!SetCommTimeouts(handle_, &timeouts)) {
            throw SerialException("Failed to set serial port timeouts");
        }

        read_timeout_ = timeout_ms;
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -5,9 +5,9 @@ project(test LANGUAGES C CXX)
# add_executable(${PROJECT_NAME} test.cpp)
# target_link_libraries(${PROJECT_NAME} PRIVATE mcp2221a)

# add_executable(main main.cpp)
# target_link_libraries(main PRIVATE mcp2221a)
add_executable(serial_test serial_test.cpp)
target_link_libraries(serial_test PRIVATE mcp2221a)

add_executable(i2c_scan scan.cpp)
add_executable(i2c_scan i2c_scan.cpp)
target_link_libraries(i2c_scan PRIVATE mcp2221a)
copy_runtime_dll(i2c_scan)
 No newline at end of file
+0 −0

File moved.

Loading