├── LICENSE ├── README.md ├── examples ├── USBPrinter │ └── USBPrinter.ino └── chipTest │ └── chipTest.ino ├── library.properties └── src ├── CH375.cpp ├── CH375.h ├── CH375USBPrinter.cpp └── CH375USBPrinter.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gianluca Nitti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CH375-Arduino 2 | 3 | * This is an open source library for the Arduino development environment which simplifies the interaction with the CH375 USB controller chips. 4 | * Currently in very early development stage: 5 | * The CH375 supports communication with a microcontroller both via UART serial and via an 8-bit parallel bust, but at the moment onle the serial mode is supported. 6 | * Only supports USB host mode (CH375 can also work as a USB device but this is not supported yet and not a priority). 7 | * The goal is to provide a low-level API for USB requests (control transfers, bulk transfers, etc) as well as high-level APIs for the most common [USB device classes](https://en.wikipedia.org/wiki/USB#Device_classes) (printer, HID, mass storage, etc.). 8 | * The low-level API is provided by the CH375 class (see CH375.h and CH375.cpp) 9 | * At the moment the only available high-level API is for the [Printer device class](https://cscott.net/usb_dev/data/devclass/usbprn10.pdf) (see CH375USBPrinter.h and CH375USBPrinter.cpp) 10 | * Currently the main target microcontroller is the ESP8266; tests are done using the [EspSoftwareSerial](https://github.com/plerup/espsoftwareserial) library by Peter Lerup for ESP8266-CH375 communication. The example compiles for the AVR architecture as well but no testing has been done on AVR boards yet. 11 | 12 | ## Useful links for development 13 | 14 | ### General information about USB 15 | * [Official docs](http://www.usb.org/developers/docs/usb20_docs/) 16 | * [usbmadesimple.co.uk](http://www.usbmadesimple.co.uk/) 17 | * [beyondlogic.org](http://www.beyondlogic.org/usbnutshell/usb1.shtml) 18 | 19 | ### CH375 documentation 20 | Unfortunately there isn't a lot of English documentation about this chip. A lot of material that can be found on the manufacturer's website is only available in Chinese (which I don't understand), however I found that Google Translate can produce a quite readable English from it. 21 | * [English datasheet](http://www.electrodragon.com/w/images/1/18/CH375DS1.PDF) 22 | * [Official webpage](http://www.wch.cn/product/CH375.html) - I used Google Translate to get to the Downloads section at the bottom of the page. There are some zips with C code examples for other microcontrollers (Intel MCS-51). Said source files have function and variable names in English but comments in Chinese. Copying and pasting these comments in Google Translate can give a decent understanding of what the code does. 23 | -------------------------------------------------------------------------------- /examples/USBPrinter/USBPrinter.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CH375.h" 3 | #include "CH375USBPrinter.h" 4 | 5 | // ESP8266 example pins 6 | //SoftwareSerial swSer(D6, D3, false, 32); 7 | //CH375 ch375(swSer, D4); 8 | // Arduino AVR example pins 9 | SoftwareSerial swSer(2, 3, false); 10 | CH375 ch375(swSer, 4); 11 | CH375USBPrinter printer(ch375); 12 | 13 | void setup() { 14 | Serial.begin(115200); 15 | Serial.println("preparing..."); 16 | delay(5000); 17 | Serial.println("ready"); 18 | swSer.begin(9600); 19 | if (ch375.init()) { 20 | Serial.println("CH375 test OK"); 21 | } else { 22 | Serial.println("CH375 is not working properly"); 23 | while (true) delay(1000); 24 | } 25 | } 26 | 27 | void printHex(String msg, int data) { 28 | Serial.print(msg + ": 0x"); 29 | Serial.println(data, HEX); 30 | } 31 | 32 | void waitSecs(int secs) { 33 | for (int i = secs; i > 0; i--) { 34 | Serial.print(i); 35 | Serial.print("..."); 36 | delay(1000); 37 | } 38 | } 39 | 40 | void loop() { 41 | delay(1000); 42 | Serial.print("Chip version: 0x"); 43 | Serial.println(ch375.getChipVersion(), HEX); 44 | 45 | Serial.println("Waiting for device..."); 46 | USBDeviceDescriptor deviceDescriptor; 47 | if (ch375.resetAndGetDeviceDescriptor(&deviceDescriptor)) { 48 | Serial.println("USB device descriptor: "); 49 | printHex("bLength", deviceDescriptor.bLength); 50 | printHex("bDescriptorType", deviceDescriptor.bDescriptorType); 51 | printHex("bcdUSB", deviceDescriptor.bcdUSB); 52 | printHex("bDeviceClass", deviceDescriptor.bDeviceClass); 53 | printHex("bDeviceSubClass", deviceDescriptor.bDeviceSubClass); 54 | printHex("bDeviceProtocol", deviceDescriptor.bDeviceProtocol); 55 | printHex("bMaxPacketSize", deviceDescriptor.bMaxPacketSize); 56 | printHex("idVendor", deviceDescriptor.idVendor); 57 | printHex("idProduct", deviceDescriptor.idProduct); 58 | printHex("bdcDevice", deviceDescriptor.bcdDevice); 59 | printHex("iManufacturer", deviceDescriptor.iManufacturer); 60 | printHex("iProduct", deviceDescriptor.iProduct); 61 | printHex("iSerialNumber", deviceDescriptor.iSerialNumber); 62 | printHex("bNumConfigurations", deviceDescriptor.bNumConfigurations); 63 | 64 | Serial.println("Initializing as printer..."); 65 | if (printer.init()) { 66 | Serial.println("Printer intialization OK"); 67 | Serial.println("Port status: "); 68 | Serial.println(printer.getPortStatus(), BIN); 69 | Serial.println("Starting to print in "); 70 | waitSecs(5); 71 | // simple document in HP page description language 72 | uint8_t dataToPrint[] = {0x1B, 'E', '\n', '\n', '\n', 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\n', 0x1B, 'E'}; 73 | uint8_t len = sizeof(dataToPrint) / sizeof(dataToPrint[0]); 74 | bool success = true; 75 | for (int i = 0; i < len; i++) { 76 | if(!printer.write(dataToPrint[i])) { 77 | Serial.println("Failed to send data to the printer"); 78 | success = false; 79 | break; 80 | } 81 | } 82 | if(success) { 83 | Serial.println("Succesfully printed!"); 84 | } else { 85 | Serial.println("Failed"); 86 | } 87 | } 88 | 89 | /*Serial.println("Setting device address to 3..."); 90 | if (ch375.setAddress(3)) { 91 | Serial.println("Address set"); 92 | Serial.println("Reading configuration descriptor..."); 93 | USBConfigurationDescriptorFull configurationDescriptor; 94 | if (ch375.getFullConfigurationDescriptor(&configurationDescriptor)) { 95 | Serial.println("USB configuration descriptor read OK."); 96 | printHex("bInterfaceClass", configurationDescriptor.interface.bInterfaceClass); 97 | printHex("bInterfaceSubClass", configurationDescriptor.interface.bInterfaceSubClass); 98 | Serial.println("Setting configuration..."); 99 | if (ch375.setConfiguration(configurationDescriptor.configuration.bConfigurationValue)) { 100 | Serial.println("Configuration set"); 101 | } else { 102 | Serial.println("Failed to set configuration"); 103 | } 104 | } else { 105 | Serial.println("Failed to read configuration descriptor"); 106 | } 107 | } else { 108 | Serial.println("Failed to set address"); 109 | }*/ 110 | } else { 111 | Serial.println("Failed"); 112 | } 113 | waitSecs(10); 114 | } 115 | -------------------------------------------------------------------------------- /examples/chipTest/chipTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CH375.h" 3 | 4 | SoftwareSerial swSer(D6, D3, false, 32); 5 | CH375 ch375(swSer, D4); 6 | 7 | void setup() { 8 | Serial.begin(115200); 9 | Serial.println("preparing..."); 10 | delay(5000); 11 | Serial.println("ready"); 12 | swSer.begin(9600); 13 | if (ch375.init()) { 14 | Serial.println("CH375 test OK"); 15 | } else { 16 | Serial.println("CH375 is not working properly"); 17 | while (true) delay(1000); 18 | } 19 | ch375.setBaudRate(115200, [](){ 20 | swSer.begin(115200); 21 | }); 22 | } 23 | 24 | void loop() { 25 | Serial.println("Testing CH375..."); 26 | bool test = ch375.test(); 27 | Serial.println(test ? "OK" : "ERROR"); 28 | if (!test) { 29 | while (true) delay(1000); 30 | } 31 | //delay(1000); 32 | } 33 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CH375-Arduino 2 | version=0.1.0 3 | author=Gianluca Nitti 4 | maintainer=Gianluca Nitti 5 | sentence=Communicate with USB devices using the CH375 chip 6 | paragraph=This library provides you with an API to talk to USB devices from a microcontroller through the CH375 USB controller. At the moment only the USB Host mode (not the device mode) and the serial communication mode (not the parallel interface) of the CH375 are supported. 7 | category=Communication 8 | url=https://github.com/gianluca-nitti/CH375-Arduino 9 | architectures=esp8266 10 | -------------------------------------------------------------------------------- /src/CH375.cpp: -------------------------------------------------------------------------------- 1 | #include "CH375.h" 2 | //#define CH375_DEBUG 3 | 4 | CH375::CH375(Stream& _stream, int _interruptPin): stream(_stream), interruptPin(_interruptPin) { 5 | } 6 | 7 | void CH375::sendCommand(uint8_t b) { 8 | delayMicroseconds(2); 9 | #ifdef CH375_DEBUG 10 | Serial.print("Sending: 0x"); 11 | Serial.println(b, HEX); 12 | #endif 13 | stream.write(b); 14 | delayMicroseconds(100); 15 | } 16 | 17 | void CH375::sendData(uint8_t b) { 18 | #ifdef CH375_DEBUG 19 | Serial.print("Sending: 0x"); 20 | Serial.println(b, HEX); 21 | #endif 22 | stream.write(b); 23 | delayMicroseconds(100); 24 | } 25 | 26 | uint8_t CH375::receive() { 27 | while (stream.available() <= 0) yield(); 28 | uint8_t b = stream.read(); 29 | #ifdef CH375_DEBUG 30 | Serial.print("Received: 0x"); 31 | Serial.println(b, HEX); 32 | #endif 33 | return b; 34 | } 35 | 36 | bool CH375::init() { 37 | pinMode(interruptPin, INPUT); 38 | delay(200); 39 | sendCommand(CH375_CMD_CHECK_EXIST); 40 | sendData(0xCC); 41 | return receive() == 0x33; 42 | } 43 | 44 | bool CH375::test() { 45 | uint8_t b = random(0, 255); 46 | sendCommand(CH375_CMD_CHECK_EXIST); 47 | sendData(b); 48 | return ((uint8_t) receive()) == ((uint8_t) ~b); 49 | } 50 | 51 | bool CH375::setBaudRate(uint32_t baudRate, std::function setLocalBaudRate) { 52 | sendCommand(CH375_CMD_SET_BAUDRATE); 53 | sendData(0x03); 54 | sendData((uint8_t) (256 - 6000000/baudRate)); //TODO: may be architecture-dependend, check on AVR 55 | setLocalBaudRate(); 56 | return receive() == CH375_CMD_RET_SUCCESS; 57 | } 58 | 59 | uint8_t CH375::getChipVersion() { 60 | sendCommand(CH375_CMD_GET_IC_VER); 61 | return receive(); 62 | } 63 | 64 | bool CH375::execCommand(uint8_t cmd, uint8_t arg) { 65 | sendCommand(cmd); 66 | sendData(arg); 67 | return receive() == CH375_CMD_RET_SUCCESS; 68 | } 69 | 70 | uint8_t CH375::waitInterrupt() { 71 | while (digitalRead(interruptPin) != LOW) yield(); 72 | sendCommand(CH375_CMD_GET_STATUS); 73 | return receive(); 74 | } 75 | 76 | void CH375::rd_usb_data(uint8_t* buf, uint8_t maxLen) { 77 | sendCommand(CH375_CMD_RD_USB_DATA); 78 | uint8_t len = receive(); 79 | for (uint8_t i = 0; i < len; i++) { 80 | uint8_t data = receive(); 81 | if (i < maxLen) { 82 | buf[i] = data; 83 | } 84 | } 85 | } 86 | 87 | void CH375::wr_usb_data(uint8_t* buf, uint8_t len) { 88 | sendCommand(CH375_CMD_WR_USB_DATA7); 89 | sendData(len); 90 | for (int i = 0; i < len; i++) { 91 | sendData(buf[i]); 92 | } 93 | } 94 | 95 | bool CH375::getDescriptor(uint8_t descriptorType) { 96 | sendCommand(CH375_CMD_GET_DESCR); 97 | sendData(descriptorType); 98 | return waitInterrupt() == CH375_USB_INT_SUCCESS; 99 | } 100 | 101 | bool CH375::resetAndGetDeviceDescriptor(USBDeviceDescriptor* result) { 102 | if (!execCommand(CH375_CMD_SET_USB_MODE, CH375_USB_MODE_HOST)) return false; 103 | while (waitInterrupt() != CH375_USB_INT_CONNECT) yield(); //wait for device to connect 104 | if (!execCommand(CH375_CMD_SET_USB_MODE, CH375_USB_MODE_HOST_RESET)) return false; //perform reset 105 | delay(10); 106 | if (!execCommand(CH375_CMD_SET_USB_MODE, CH375_USB_MODE_HOST)) return false; 107 | delay(100); 108 | while (waitInterrupt() != CH375_USB_INT_CONNECT) yield(); //wait for device to come back online after reset 109 | delay(200); 110 | if (!getDescriptor(CH375_USB_DEVICE_DESCRIPTOR)) return false; 111 | rd_usb_data((uint8_t*)result, sizeof(USBDeviceDescriptor)); 112 | return result->bLength >= 18 //device descriptor length should be 18 113 | && result->bDescriptorType == CH375_USB_DEVICE_DESCRIPTOR //ensure it's actually a device descriptor 114 | && result->bNumConfigurations == 1; //devices with more than 1 configuration are not supported (for now at least) 115 | } 116 | 117 | bool CH375::setAddress(uint8_t address) { 118 | if (address & 0b10000000) return false; //first bit must be zero (USB addresses are 7 bit long) 119 | sendCommand(CH375_CMD_SET_ADDRESS); //assign this address to the device 120 | sendData(address); 121 | if (waitInterrupt() != CH375_USB_INT_SUCCESS) return false; 122 | sendCommand(CH375_CMD_SET_USB_ADDR); //instruct the CH375 (USB host) to talk to the device with this address 123 | sendData(address); 124 | delay(5); 125 | return true; 126 | } 127 | 128 | bool CH375::getFullConfigurationDescriptor(USBConfigurationDescriptorFull* result) { 129 | if(!getDescriptor(CH375_USB_CONFIGURATION_DESCRIPTOR)) return false; 130 | rd_usb_data((uint8_t*)result, sizeof(USBConfigurationDescriptorFull)); 131 | return result->configuration.bDescriptorType == CH375_USB_CONFIGURATION_DESCRIPTOR //ensure configuration descriptor is actually a configuration descriptor 132 | && result->configuration.bNumInterfaces == 1 //only one interface is supported (for now at least) 133 | && result->interface.bDescriptorType == CH375_USB_INTERFACE_DESCRIPTOR //ensure interface descriptor is actually an interface descriptor 134 | && result->interface.bNumEndpoints <= 4; //a maximum of 4 endpoints are supported 135 | } 136 | 137 | bool CH375::setConfiguration(uint8_t configuration) { 138 | sendCommand(CH375_CMD_SET_CONFIG); 139 | sendData(configuration); 140 | return waitInterrupt() == CH375_USB_INT_SUCCESS; 141 | } 142 | 143 | bool CH375::issueToken(uint8_t targetEndpoint, uint8_t pid) { 144 | if ((targetEndpoint & 0xF0) || (pid & 0xF0)) return false; //both arguments must be 4 bits long 145 | sendCommand(CH375_CMD_ISSUE_TOKEN); 146 | sendData((targetEndpoint << 4) | pid); 147 | return waitInterrupt() == CH375_USB_INT_SUCCESS; 148 | } 149 | 150 | void CH375::toggleHostEndpoint(uint8_t setEndpointCommand, bool tog) { 151 | sendCommand(setEndpointCommand); 152 | sendData(tog ? 0xC0 : 0x80); 153 | delay(2); 154 | } 155 | 156 | void CH375::toggleHostEndpoint6(bool tog) { 157 | toggleHostEndpoint(CH375_CMD_SET_ENDP6, tog); 158 | } 159 | 160 | void CH375::toggleHostEndpoint7(bool tog) { 161 | toggleHostEndpoint(CH375_CMD_SET_ENDP7, tog); 162 | } 163 | 164 | bool CH375::doBulkOutTransfer(uint8_t targetEndpoint, uint8_t* buf, uint8_t len) { 165 | if (len > 64) return false; //must not exceed CH375's send buffer size 166 | toggleHostEndpoint7(toggleSend); 167 | wr_usb_data(buf, len); 168 | if (issueToken(targetEndpoint, USB_PID_OUT)) { 169 | toggleSend = !toggleSend; 170 | return true; 171 | } else { 172 | return false; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/CH375.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Arduino.h" 4 | 5 | #define CH375_CMD_GET_IC_VER 0x01 6 | #define CH375_CMD_SET_BAUDRATE 0x02 7 | #define CH375_CMD_CHECK_EXIST 0x06 8 | #define CH375_CMD_SET_USB_ADDR 0x13 9 | #define CH375_CMD_SET_USB_MODE 0x15 10 | #define CH375_CMD_SET_ENDP6 0x1C 11 | #define CH375_CMD_SET_ENDP7 0x1D 12 | #define CH375_CMD_GET_STATUS 0x22 13 | #define CH375_CMD_RD_USB_DATA 0x28 14 | #define CH375_CMD_WR_USB_DATA7 0x2B 15 | #define CH375_CMD_SET_ADDRESS 0x45 16 | #define CH375_CMD_GET_DESCR 0x46 17 | #define CH375_CMD_SET_CONFIG 0x49 18 | #define CH375_CMD_ISSUE_TOKEN 0x4F 19 | 20 | #define CH375_CMD_RET_SUCCESS 0x51 21 | 22 | #define CH375_USB_MODE_HOST 0x06 23 | #define CH375_USB_MODE_HOST_RESET 0x07 24 | 25 | #define CH375_USB_INT_SUCCESS 0x14 26 | #define CH375_USB_INT_CONNECT 0x15 27 | 28 | #define CH375_USB_DEVICE_DESCRIPTOR 0x01 29 | #define CH375_USB_CONFIGURATION_DESCRIPTOR 0x02 30 | #define CH375_USB_INTERFACE_DESCRIPTOR 0x04 31 | #define CH375_USB_ENDPOINT_DESCRIPTOR 0x05 32 | 33 | #define USB_PID_OUT 0x01 34 | #define USB_PID_IN 0x09 35 | #define USB_PID_SETUP 0x0D 36 | 37 | typedef struct __attribute__((__packed__)) { 38 | uint8_t bLength; 39 | uint8_t bDescriptorType; 40 | uint16_t bcdUSB; 41 | uint8_t bDeviceClass; 42 | uint8_t bDeviceSubClass; 43 | uint8_t bDeviceProtocol; 44 | uint8_t bMaxPacketSize; 45 | uint16_t idVendor; 46 | uint16_t idProduct; 47 | uint16_t bcdDevice; 48 | uint8_t iManufacturer; 49 | uint8_t iProduct; 50 | uint8_t iSerialNumber; 51 | uint8_t bNumConfigurations; 52 | } USBDeviceDescriptor; 53 | 54 | typedef struct __attribute__((__packed__)) { 55 | uint8_t bLength; 56 | uint8_t bDescriptorType; 57 | uint16_t wTotalLength; 58 | uint8_t bNumInterfaces; 59 | uint8_t bConfigurationValue; 60 | uint8_t iConfiguration; 61 | uint8_t bmAttributes; 62 | uint8_t bMaxPower; 63 | } USBConfigurationDescriptor; 64 | 65 | typedef struct __attribute__((__packed__)) { 66 | uint8_t bLength; 67 | uint8_t bDescriptorType; 68 | uint8_t bInterfaceNumber; 69 | uint8_t bAlternateSetting; 70 | uint8_t bNumEndpoints; 71 | uint8_t bInterfaceClass; 72 | uint8_t bInterfaceSubClass; 73 | uint8_t bInterfaceProtocol; 74 | uint8_t iInterface; 75 | } USBInterfaceDescriptor; 76 | 77 | typedef struct __attribute__((__packed__)) { 78 | uint8_t bLength; 79 | uint8_t bDescriptorType; 80 | uint8_t bEndpointAddress; 81 | uint8_t bmAttributes; 82 | uint16_t wMaxPacketSize; 83 | uint8_t bInterval; 84 | } USBEndpointDescriptor; 85 | 86 | typedef struct __attribute__((__packed__)) { 87 | USBConfigurationDescriptor configuration; 88 | USBInterfaceDescriptor interface; 89 | USBEndpointDescriptor endpoints[4]; 90 | } USBConfigurationDescriptorFull; 91 | 92 | class CH375 { 93 | private: 94 | Stream& stream; 95 | int interruptPin; 96 | bool toggleSend = false; //toggle DATA0/DATA1 for sending 97 | 98 | void sendCommand(uint8_t b); 99 | void sendData(uint8_t b); 100 | uint8_t receive(); 101 | bool execCommand(uint8_t cmd, uint8_t arg); 102 | uint8_t waitInterrupt(); 103 | bool getDescriptor(uint8_t descriptorType); 104 | void toggleHostEndpoint(uint8_t setEndpointCommand, bool tog); 105 | public: 106 | CH375(Stream& _stream, int _interruptPin); 107 | bool init(); 108 | bool test(); 109 | bool setBaudRate(uint32_t baudRate, std::function setLocalBaudRate); 110 | uint8_t getChipVersion(); 111 | void rd_usb_data(uint8_t* buf, uint8_t maxLen); 112 | void wr_usb_data(uint8_t* buf, uint8_t len); 113 | bool resetAndGetDeviceDescriptor(USBDeviceDescriptor* result); 114 | bool setAddress(uint8_t address); 115 | bool getFullConfigurationDescriptor(USBConfigurationDescriptorFull* result); 116 | bool setConfiguration(uint8_t configuration); 117 | bool issueToken(uint8_t targetEndpoint, uint8_t pid); 118 | void toggleHostEndpoint6(bool tog); 119 | void toggleHostEndpoint7(bool tog); 120 | bool doBulkOutTransfer(uint8_t targetEndpoint, uint8_t* buf, uint8_t len); 121 | }; 122 | -------------------------------------------------------------------------------- /src/CH375USBPrinter.cpp: -------------------------------------------------------------------------------- 1 | #include "CH375USBPrinter.h" 2 | 3 | CH375USBPrinter::CH375USBPrinter(CH375& _ch375): ch375(_ch375) { 4 | } 5 | 6 | bool CH375USBPrinter::init() { 7 | USBDeviceDescriptor deviceDescriptor; 8 | if (!ch375.resetAndGetDeviceDescriptor(&deviceDescriptor)) return false; 9 | if (deviceDescriptor.bDeviceClass != 0 || deviceDescriptor.bDeviceSubClass != 0 || deviceDescriptor.bDeviceProtocol != 0) return false; 10 | if (!ch375.setAddress(3)) return false; 11 | USBConfigurationDescriptorFull configurationDescriptor; 12 | if (!ch375.getFullConfigurationDescriptor(&configurationDescriptor)) return false; 13 | if (configurationDescriptor.interface.bInterfaceClass != USB_PRINTER_INTERFACE_CLASS 14 | || configurationDescriptor.interface.bInterfaceSubClass != USB_PRINTER_INTERFACE_SUBCLASS) return false; 15 | if (!ch375.setConfiguration(configurationDescriptor.configuration.bConfigurationValue)) return false; 16 | // find the OUT endpoint 17 | for (uint8_t i = 0; i < configurationDescriptor.interface.bNumEndpoints; i++) { 18 | // according to USB specification, the OUT endpoint's address most significant bit is 0; 19 | // if there is an IN endpoint, it's address has the most significant bit set to 1 20 | if ((configurationDescriptor.endpoints[i].bEndpointAddress & 0b10000000) == 0) { 21 | // less significant 4 bits of the address are the endpoint number 22 | outEndpointNumber = configurationDescriptor.endpoints[i].bEndpointAddress & 0x0F; 23 | // the wMaxPacketSize field is 2 bytes large, but the CH375 send buffer size is 64 bytes; 24 | // thus, if the printer supports larger packets, the bottleneck is the CH375 25 | uint16_t maxPacketSize = configurationDescriptor.endpoints[i].wMaxPacketSize; 26 | if (maxPacketSize > 64) { 27 | packetSize = 64; 28 | } else { 29 | packetSize = (uint8_t) (maxPacketSize & 0x00FF); 30 | } 31 | 32 | if (buffer != NULL) { 33 | delete[] buffer; 34 | } 35 | buffer = new uint8_t[packetSize]; 36 | bufIndex = 0; 37 | 38 | return true; 39 | } 40 | } 41 | Serial.println("no OUT endpoint!"); 42 | return false; //no OUT endpoint found 43 | } 44 | 45 | uint8_t CH375USBPrinter::getPortStatus() { 46 | ch375.toggleHostEndpoint7(false); 47 | //TODO: refactor and document 48 | uint8_t buf[8] = {0}; 49 | buf[0] = 0b10100001; //bmRequestType 50 | buf[1] = 1; //bRequest 51 | buf[6] = 1; //wLength 52 | ch375.wr_usb_data(buf, 8); 53 | if (!ch375.issueToken(0, USB_PID_SETUP)) return 0xFF; //SETUP phase 54 | ch375.toggleHostEndpoint6(true); 55 | if (!ch375.issueToken(0, USB_PID_IN)) return 0xFF; //DATA phase 56 | uint8_t result; 57 | ch375.rd_usb_data(&result, 1); 58 | ch375.toggleHostEndpoint7(true); 59 | ch375.wr_usb_data(NULL, 0); //send 0-length data to report that control transfer was successful 60 | if (!ch375.issueToken(0, USB_PID_OUT)) return 0xFF; 61 | return result; 62 | } 63 | 64 | bool CH375USBPrinter::write(uint8_t b) { 65 | if (bufIndex == packetSize) { 66 | if (!flush()) return false; 67 | } 68 | buffer[bufIndex++] = b; 69 | return true; 70 | } 71 | 72 | bool CH375USBPrinter::flush() { 73 | if(!ch375.doBulkOutTransfer(outEndpointNumber, buffer, bufIndex)) return false; 74 | bufIndex = 0; 75 | return true; 76 | } 77 | 78 | CH375USBPrinter::~CH375USBPrinter() { 79 | if (buffer != NULL) { 80 | delete[] buffer; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/CH375USBPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CH375.h" 3 | 4 | #define USB_PRINTER_INTERFACE_CLASS 0x07 5 | #define USB_PRINTER_INTERFACE_SUBCLASS 0x01 6 | 7 | class CH375USBPrinter { 8 | private: 9 | CH375& ch375; 10 | uint8_t outEndpointNumber; 11 | uint8_t packetSize; 12 | uint8_t* buffer = NULL; 13 | uint8_t bufIndex = 0; 14 | public: 15 | CH375USBPrinter(CH375& _ch375); 16 | bool init(); 17 | uint8_t getPortStatus(); 18 | bool write(uint8_t b); 19 | bool flush(); 20 | ~CH375USBPrinter(); 21 | }; 22 | --------------------------------------------------------------------------------