mirror of
https://github.com/eddyem/stm32samples.git
synced 2026-01-31 20:35:06 +03:00
try to make PL2303 @ new concept
This commit is contained in:
parent
d8284ea725
commit
f9d9735a5e
@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
#include "hardware.h"
|
||||
#include "usb_lib.h"
|
||||
#include "usb_dev.h"
|
||||
|
||||
volatile uint32_t Tms = 0;
|
||||
@ -30,24 +29,28 @@ void sys_tick_handler(void){
|
||||
#define STRLEN (256)
|
||||
|
||||
int main(void){
|
||||
char inbuff[RBINSZ+1];
|
||||
uint32_t lastT = 0;
|
||||
StartHSE();
|
||||
hw_setup();
|
||||
USBPU_OFF();
|
||||
SysTick_Config(72000);
|
||||
USB_setup();
|
||||
i2c_setup();
|
||||
#ifndef EBUG
|
||||
iwdg_setup();
|
||||
#endif
|
||||
USBPU_ON();
|
||||
|
||||
while (1){
|
||||
IWDG->KR = IWDG_REFRESH; // refresh watchdog
|
||||
if(lastT > Tms || Tms - lastT > 499){
|
||||
LED_blink(LED0);
|
||||
lastT = Tms;
|
||||
}
|
||||
int l = USB_receivestr(inbuff, RBINSZ);
|
||||
if(l < 0) USB_sendstr("ERROR: USB buffer overflow or string was too long\n");
|
||||
else if(l){
|
||||
USB_sendstr("RECEIVED: _"); USB_sendstr(inbuff); USB_sendstr("_\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
167
F1:F103/USB_NEW_concept/ringbuffer.c
Normal file
167
F1:F103/USB_NEW_concept/ringbuffer.c
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ringbuffer.h"
|
||||
|
||||
static int datalen(ringbuffer *b){
|
||||
if(b->tail >= b->head) return (b->tail - b->head);
|
||||
else return (b->length - b->head + b->tail);
|
||||
}
|
||||
|
||||
// stored data length
|
||||
int RB_datalen(ringbuffer *b){
|
||||
if(b->busy) return -1;
|
||||
b->busy = 1;
|
||||
int l = datalen(b);
|
||||
b->busy = 0;
|
||||
return l;
|
||||
}
|
||||
|
||||
static int hasbyte(ringbuffer *b, uint8_t byte){
|
||||
if(b->head == b->tail) return -1; // no data in buffer
|
||||
int startidx = b->head;
|
||||
if(b->head > b->tail){ //
|
||||
for(int found = b->head; found < b->length; ++found)
|
||||
if(b->data[found] == byte) return found;
|
||||
startidx = 0;
|
||||
}
|
||||
for(int found = startidx; found < b->tail; ++found)
|
||||
if(b->data[found] == byte) return found;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RB_hasbyte - check if buffer has given byte stored
|
||||
* @param b - buffer
|
||||
* @param byte - byte to find
|
||||
* @return index if found, -1 if none or busy
|
||||
*/
|
||||
int RB_hasbyte(ringbuffer *b, uint8_t byte){
|
||||
if(b->busy) return -1;
|
||||
b->busy = 1;
|
||||
int ret = hasbyte(b, byte);
|
||||
b->busy = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// poor memcpy
|
||||
static void mcpy(uint8_t *targ, const uint8_t *src, int l){
|
||||
while(l--) *targ++ = *src++;
|
||||
}
|
||||
|
||||
// increment head or tail
|
||||
TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){
|
||||
*what += n;
|
||||
if(*what >= b->length) *what -= b->length;
|
||||
}
|
||||
|
||||
static int read(ringbuffer *b, uint8_t *s, int len){
|
||||
int l = datalen(b);
|
||||
if(!l) return 0;
|
||||
if(l > len) l = len;
|
||||
int _1st = b->length - b->head;
|
||||
if(_1st > l) _1st = l;
|
||||
if(_1st > len) _1st = len;
|
||||
mcpy(s, b->data + b->head, _1st);
|
||||
if(_1st < len && l > _1st){
|
||||
mcpy(s+_1st, b->data, l - _1st);
|
||||
incr(b, &b->head, l);
|
||||
return l;
|
||||
}
|
||||
incr(b, &b->head, _1st);
|
||||
return _1st;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RB_read - read data from ringbuffer
|
||||
* @param b - buffer
|
||||
* @param s - array to write data
|
||||
* @param len - max len of `s`
|
||||
* @return bytes read or -1 if busy
|
||||
*/
|
||||
int RB_read(ringbuffer *b, uint8_t *s, int len){
|
||||
if(b->busy) return -1;
|
||||
b->busy = 1;
|
||||
int r = read(b, s, len);
|
||||
b->busy = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
|
||||
int idx = hasbyte(b, byte);
|
||||
if(idx < 0) return 0;
|
||||
int partlen = idx + 1 - b->head;
|
||||
// now calculate length of new data portion
|
||||
if(idx < b->head) partlen += b->length;
|
||||
if(partlen > len) return -read(b, s, len);
|
||||
return read(b, s, partlen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RB_readto fill array `s` with data until byte `byte` (with it)
|
||||
* @param b - ringbuffer
|
||||
* @param byte - check byte
|
||||
* @param s - buffer to write data
|
||||
* @param len - length of `s`
|
||||
* @return amount of bytes written (negative, if len<data in buffer or buffer is busy)
|
||||
*/
|
||||
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
|
||||
if(b->busy) return -1;
|
||||
b->busy = 1;
|
||||
int n = readto(b, byte, s, len);
|
||||
b->busy = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int write(ringbuffer *b, const uint8_t *str, int l){
|
||||
int r = b->length - 1 - datalen(b); // rest length
|
||||
if(l > r) l = r;
|
||||
if(!l) return 0;
|
||||
int _1st = b->length - b->tail;
|
||||
if(_1st > l) _1st = l;
|
||||
mcpy(b->data + b->tail, str, _1st);
|
||||
if(_1st < l){ // add another piece from start
|
||||
mcpy(b->data, str+_1st, l-_1st);
|
||||
}
|
||||
incr(b, &b->tail, l);
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RB_write - write some data to ringbuffer
|
||||
* @param b - buffer
|
||||
* @param str - data
|
||||
* @param l - length
|
||||
* @return amount of bytes written or -1 if busy
|
||||
*/
|
||||
int RB_write(ringbuffer *b, const uint8_t *str, int l){
|
||||
if(b->busy) return -1;
|
||||
b->busy = 1;
|
||||
int w = write(b, str, l);
|
||||
b->busy = 0;
|
||||
return w;
|
||||
}
|
||||
|
||||
// just delete all information in buffer `b`
|
||||
int RB_clearbuf(ringbuffer *b){
|
||||
if(b->busy) return -1;
|
||||
b->busy = 1;
|
||||
b->head = 0;
|
||||
b->tail = 0;
|
||||
b->busy = 0;
|
||||
return 1;
|
||||
}
|
||||
41
F1:F103/USB_NEW_concept/ringbuffer.h
Normal file
41
F1:F103/USB_NEW_concept/ringbuffer.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined STM32F0
|
||||
#include <stm32f0.h>
|
||||
#elif defined STM32F1
|
||||
#include <stm32f1.h>
|
||||
#elif defined STM32F3
|
||||
#include <stm32f3.h>
|
||||
#endif
|
||||
|
||||
typedef struct{
|
||||
uint8_t *data; // data buffer
|
||||
const int length; // its length
|
||||
int head; // head index
|
||||
int tail; // tail index
|
||||
volatile int busy; // == TRUE if buffer is busy now
|
||||
} ringbuffer;
|
||||
|
||||
int RB_read(ringbuffer *b, uint8_t *s, int len);
|
||||
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len);
|
||||
int RB_hasbyte(ringbuffer *b, uint8_t byte);
|
||||
int RB_write(ringbuffer *b, const uint8_t *str, int l);
|
||||
int RB_datalen(ringbuffer *b);
|
||||
int RB_clearbuf(ringbuffer *b);
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 14.0.2, 2024-11-30T23:28:05. -->
|
||||
<!-- Written by QtCreator 14.0.2, 2024-12-12T22:54:34. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
hardware.c
|
||||
hardware.h
|
||||
main.c
|
||||
ringbuffer.c
|
||||
ringbuffer.h
|
||||
usb_descr.c
|
||||
usb_descr.h
|
||||
usb_lib.c
|
||||
|
||||
@ -16,23 +16,10 @@
|
||||
*/
|
||||
|
||||
#include "usb_descr.h"
|
||||
#include "usb_lib.h"
|
||||
|
||||
// string descriptors
|
||||
enum{
|
||||
iLANGUAGE_DESCR,
|
||||
iMANUFACTURER_DESCR,
|
||||
iPRODUCT_DESCR,
|
||||
iSERIAL_DESCR,
|
||||
iINTERFACE_DESCR1,
|
||||
iINTERFACE_DESCR2,
|
||||
iINTERFACE_DESCR3,
|
||||
iINTERFACE_DESCR4,
|
||||
iINTERFACE_DESCR5,
|
||||
iINTERFACE_DESCR6,
|
||||
iINTERFACE_DESCR7,
|
||||
iDESCR_AMOUNT
|
||||
};
|
||||
// low/high for uint16_t
|
||||
#define L16(x) (x & 0xff)
|
||||
#define H16(x) (x >> 8)
|
||||
|
||||
const uint8_t USB_DeviceDescriptor[] = {
|
||||
USB_DT_DEVICE_SIZE, // bLength
|
||||
@ -43,12 +30,12 @@ const uint8_t USB_DeviceDescriptor[] = {
|
||||
bDeviceSubClass, // bDeviceSubClass
|
||||
bDeviceProtocol, // bDeviceProtocol
|
||||
USB_EP0BUFSZ, // bMaxPacketSize
|
||||
L(idVendor), // idVendor_L
|
||||
H(idVendor), // idVendor_H
|
||||
L(idProduct), // idProduct_L
|
||||
H(idProduct), // idProduct_H
|
||||
L(bcdDevice_Ver), // bcdDevice_Ver_L
|
||||
H(bcdDevice_Ver), // bcdDevice_Ver_H
|
||||
L16(idVendor), // idVendor_L
|
||||
H16(idVendor), // idVendor_H
|
||||
L16(idProduct), // idProduct_L
|
||||
H16(idProduct), // idProduct_H
|
||||
L16(bcdDevice_Ver), // bcdDevice_Ver_L
|
||||
H16(bcdDevice_Ver), // bcdDevice_Ver_H
|
||||
iMANUFACTURER_DESCR, // iManufacturer - indexes of string descriptors in array
|
||||
iPRODUCT_DESCR, // iProduct
|
||||
iSERIAL_DESCR, // iSerial
|
||||
@ -87,20 +74,47 @@ const uint8_t USB_ConfigDescriptor[] = {
|
||||
USB_DT_INTERFACE, /* bDescriptorType: Interface */
|
||||
0, /* bInterfaceNumber: Number of Interface */
|
||||
0, /* bAlternateSetting: Alternate setting */
|
||||
1, /* bNumEndpoints: only 1 used */
|
||||
0, /* bInterfaceClass */
|
||||
3, /* bNumEndpoints */
|
||||
USB_CLASS_VENDOR_SPEC, /* bInterfaceClass */
|
||||
0, /* bInterfaceSubClass */
|
||||
0, /* bInterfaceProtocol */
|
||||
iINTERFACE_DESCR1, /* iInterface: */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Endpoint 1 Descriptor */
|
||||
USB_DT_ENDPOINT_SIZE, /* bLength: Endpoint Descriptor size */
|
||||
USB_DT_ENDPOINT, /* bDescriptorType: Endpoint */
|
||||
0x81, /* bEndpointAddress IN1 */
|
||||
EP_TYPE_INTERRUPT, /* bmAttributes: Interrupt */
|
||||
L16(USB_EP1BUFSZ), /* wMaxPacketSize LO */
|
||||
H16(USB_EP1BUFSZ), /* wMaxPacketSize HI */
|
||||
0x01, /* bInterval: 1ms */
|
||||
/* Endpoint OUT2 Descriptor */
|
||||
USB_DT_ENDPOINT_SIZE, /* bLength: Endpoint Descriptor size */
|
||||
USB_DT_ENDPOINT, /* bDescriptorType: Endpoint */
|
||||
0x02, /* bEndpointAddress: OUT2 */
|
||||
EP_TYPE_BULK, /* bmAttributes: Bulk */
|
||||
L16(USB_RXBUFSZ), /* wMaxPacketSize LO */
|
||||
H16(USB_RXBUFSZ), /* wMaxPacketSize HI */
|
||||
0, /* bInterval: ignore for Bulk transfer */
|
||||
/*Endpoint IN3 Descriptor*/
|
||||
USB_DT_ENDPOINT_SIZE, /* bLength: Endpoint Descriptor size */
|
||||
USB_DT_ENDPOINT, /* bDescriptorType: Endpoint */
|
||||
0x83, /* bEndpointAddress: IN3 */
|
||||
EP_TYPE_BULK, /* bmAttributes: Bulk */
|
||||
L16(USB_TXBUFSZ), /* wMaxPacketSize LO */
|
||||
H16(USB_TXBUFSZ), /* wMaxPacketSize HI */
|
||||
0, /* bInterval: ignore for Bulk transfer */
|
||||
};
|
||||
|
||||
//const uint8_t HID_ReportDescriptor[];
|
||||
|
||||
_USB_LANG_ID_(LD, LANG_US);
|
||||
_USB_STRING_(SD, u"0.0.1");
|
||||
_USB_STRING_(MD, u"eddy@sao");
|
||||
_USB_STRING_(PD, u"Some USB device");
|
||||
_USB_STRING_(ID, u"first interface");
|
||||
_USB_STRING_(MD, u"eddy@sao.ru");
|
||||
_USB_STRING_(PD, u"USB-Serial Controller");
|
||||
_USB_STRING_(ID, u"USB-STM32");
|
||||
|
||||
const uint8_t* const StringDescriptor[iDESCR_AMOUNT] = {
|
||||
const void* const StringDescriptor[iDESCR_AMOUNT] = {
|
||||
[iLANGUAGE_DESCR] = &LD,
|
||||
[iMANUFACTURER_DESCR] = &MD,
|
||||
[iPRODUCT_DESCR] = &PD,
|
||||
@ -108,3 +122,55 @@ const uint8_t* const StringDescriptor[iDESCR_AMOUNT] = {
|
||||
[iINTERFACE_DESCR1] = &ID
|
||||
};
|
||||
|
||||
void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){
|
||||
if(askedsize < size) size = askedsize; // shortened request
|
||||
if(size < USB_EP0BUFSZ){
|
||||
EP_WriteIRQ(0, buf, size);
|
||||
return;
|
||||
}
|
||||
while(size){
|
||||
uint16_t l = size;
|
||||
if(l > USB_EP0BUFSZ) l = USB_EP0BUFSZ;
|
||||
EP_WriteIRQ(0, buf, l);
|
||||
buf += l;
|
||||
size -= l;
|
||||
uint8_t needzlp = (l == USB_EP0BUFSZ) ? 1 : 0;
|
||||
if(size || needzlp){ // send last data buffer
|
||||
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]);
|
||||
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
|
||||
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
|
||||
^ USB_EPnR_STAT_TX;
|
||||
uint32_t ctr = 1000000;
|
||||
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
|
||||
if((USB->ISTR & USB_ISTR_CTR) == 0){
|
||||
return;
|
||||
}
|
||||
if(needzlp) EP_WriteIRQ(0, NULL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void get_descriptor(config_pack_t *pack){
|
||||
uint8_t descrtype = pack->wValue >> 8,
|
||||
descridx = pack->wValue & 0xff;
|
||||
switch(descrtype){
|
||||
case DEVICE_DESCRIPTOR:
|
||||
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor), pack->wLength);
|
||||
break;
|
||||
case CONFIGURATION_DESCRIPTOR:
|
||||
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor), pack->wLength);
|
||||
break;
|
||||
case STRING_DESCRIPTOR:
|
||||
if(descridx < iDESCR_AMOUNT) wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]), pack->wLength);
|
||||
else EP_WriteIRQ(0, NULL, 0);
|
||||
break;
|
||||
case DEVICE_QUALIFIER_DESCRIPTOR:
|
||||
wr0(USB_DeviceQualifierDescriptor, sizeof(USB_DeviceQualifierDescriptor), pack->wLength);
|
||||
break;
|
||||
/* case HID_REPORT_DESCRIPTOR:
|
||||
wr0(HID_ReportDescriptor, sizeof(HID_ReportDescriptor), pack->wLength);
|
||||
break;*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "usb_lib.h"
|
||||
|
||||
// definition of parts common for USB_DeviceDescriptor & USB_DeviceQualifierDescriptor
|
||||
// bcdUSB: 1.10
|
||||
#define bcdUSB 0x0110
|
||||
@ -28,10 +30,6 @@
|
||||
#define bcdDevice_Ver 0x0205
|
||||
#define bNumConfigurations 1
|
||||
|
||||
// low/high for uint16_t
|
||||
#define L16(x) (x & 0xff)
|
||||
#define H16(x) (x >> 8)
|
||||
|
||||
// amount of interfaces and endpoints (except 0) used
|
||||
#define bNumInterfaces 1
|
||||
#define bTotNumEndpoints 1
|
||||
@ -41,6 +39,27 @@
|
||||
#define SelfPowered (1<<6)
|
||||
#define RemoteWakeup (1<<5)
|
||||
|
||||
extern const uint8_t USB_DeviceDescriptor[];
|
||||
extern const uint8_t USB_DeviceQualifierDescriptor[];
|
||||
extern const uint8_t USB_ConfigDescriptor[];
|
||||
// buffer sizes
|
||||
// for USB FS EP0 buffers are from 8 to 64 bytes long
|
||||
#define USB_EP0BUFSZ 64
|
||||
#define USB_EP1BUFSZ 10
|
||||
#define USB_RXBUFSZ 64
|
||||
#define USB_TXBUFSZ 64
|
||||
|
||||
// string descriptors
|
||||
enum{
|
||||
iLANGUAGE_DESCR,
|
||||
iMANUFACTURER_DESCR,
|
||||
iPRODUCT_DESCR,
|
||||
iSERIAL_DESCR,
|
||||
iINTERFACE_DESCR1,
|
||||
/* iINTERFACE_DESCR2,
|
||||
iINTERFACE_DESCR3,
|
||||
iINTERFACE_DESCR4,
|
||||
iINTERFACE_DESCR5,
|
||||
iINTERFACE_DESCR6,
|
||||
iINTERFACE_DESCR7,*/
|
||||
iDESCR_AMOUNT
|
||||
};
|
||||
|
||||
void get_descriptor(config_pack_t *pack);
|
||||
|
||||
@ -18,65 +18,261 @@
|
||||
#include <stm32f1.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ringbuffer.h"
|
||||
#include "usb_descr.h"
|
||||
#include "usb_dev.h"
|
||||
|
||||
// Class-Specific Control Requests
|
||||
#define SEND_ENCAPSULATED_COMMAND 0x00 // unused
|
||||
#define GET_ENCAPSULATED_RESPONSE 0x01 // unused
|
||||
#define SET_COMM_FEATURE 0x02 // unused
|
||||
#define GET_COMM_FEATURE 0x03 // unused
|
||||
#define CLEAR_COMM_FEATURE 0x04 // unused
|
||||
#define SET_LINE_CODING 0x20
|
||||
#define GET_LINE_CODING 0x21
|
||||
#define SET_CONTROL_LINE_STATE 0x22
|
||||
#define SEND_BREAK 0x23
|
||||
|
||||
// control line states
|
||||
#define CONTROL_DTR 0x01
|
||||
#define CONTROL_RTS 0x02
|
||||
|
||||
// inbuf overflow when receiving
|
||||
static volatile uint8_t bufovrfl = 0;
|
||||
|
||||
// receive buffer: hold data until chkin() call
|
||||
static uint8_t volatile rcvbuf[USB_RXBUFSZ];
|
||||
static uint8_t volatile rcvbuflen = 0;
|
||||
// line coding
|
||||
usb_LineCoding WEAK lineCoding = {115200, 0, 0, 8};
|
||||
// CDC configured and ready to use
|
||||
static volatile uint8_t CDCready = 0;
|
||||
|
||||
// ring buffers for incoming and outgoing data
|
||||
static uint8_t obuf[RBOUTSZ], ibuf[RBINSZ];
|
||||
static volatile ringbuffer rbout = {.data = obuf, .length = RBOUTSZ, .head = 0, .tail = 0};
|
||||
static volatile ringbuffer rbin = {.data = ibuf, .length = RBINSZ, .head = 0, .tail = 0};
|
||||
// last send data size
|
||||
static volatile int lastdsz = 0;
|
||||
|
||||
static void EP1_Handler(){
|
||||
uint16_t epstatus = KEEP_DTOG(USB->EPnR[1]);
|
||||
if(RX_FLAG(epstatus)) epstatus = (epstatus & ~USB_EPnR_STAT_TX) ^ USB_EPnR_STAT_RX; // set valid RX
|
||||
else epstatus = epstatus & ~(USB_EPnR_STAT_TX|USB_EPnR_STAT_RX);
|
||||
// clear CTR
|
||||
epstatus = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX));
|
||||
USB->EPnR[1] = epstatus;
|
||||
}
|
||||
|
||||
void set_configuration(uint16_t _U_ configuration){
|
||||
static void chkin(){
|
||||
if(bufovrfl) return; // allow user to know that previous buffer was overflowed and cleared
|
||||
if(!rcvbuflen) return;
|
||||
int w = RB_write((ringbuffer*)&rbin, (uint8_t*)rcvbuf, rcvbuflen);
|
||||
if(w < 0) return;
|
||||
if(w != rcvbuflen) bufovrfl = 1;
|
||||
rcvbuflen = 0;
|
||||
uint16_t status = KEEP_DTOG(USB->EPnR[2]); // don't change DTOG
|
||||
USB->EPnR[2] = status ^ USB_EPnR_STAT_RX;
|
||||
}
|
||||
|
||||
|
||||
void usb_class_request(config_pack_t *req, uint8_t *data, unsigned int datalen){
|
||||
uint8_t buf[USB_EP0BUFSZ];
|
||||
size_t len = 0;
|
||||
//uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType);
|
||||
if((req->bmRequestType & 0x80) == 0){ // OUT - setters
|
||||
switch(req->bRequest){
|
||||
case CMD_I2C_IO:
|
||||
case CMD_I2C_IO | CMD_I2C_BEGIN:
|
||||
case CMD_I2C_IO | CMD_I2C_END:
|
||||
case CMD_I2C_IO | CMD_I2C_BEGIN | CMD_I2C_END: // write
|
||||
if(req->wValue & I2C_M_RD) break; // OUT - only write
|
||||
if(!data) break; // wait for data
|
||||
len = datalen;
|
||||
usb_i2c_io(req, data, &len);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
EP_WriteIRQ(0, 0, 0);
|
||||
// called from transmit EP to send next data portion or by user - when new transmission starts
|
||||
static void send_next(){
|
||||
uint8_t usbbuff[USB_TXBUFSZ];
|
||||
int buflen = RB_read((ringbuffer*)&rbout, (uint8_t*)usbbuff, USB_TXBUFSZ);
|
||||
if(buflen == 0){
|
||||
if(lastdsz == 64) EP_Write(3, NULL, 0); // send ZLP after 64 bits packet when nothing more to send
|
||||
lastdsz = 0;
|
||||
return;
|
||||
}else if(buflen < 0){
|
||||
lastdsz = 0;
|
||||
// Uncomment next line if you want 4Mbit/s instead of 6Mbit/s
|
||||
//EP_Write(3, NULL, 0); // send ZLP if buffer is in writting state now
|
||||
return;
|
||||
}
|
||||
switch(req->bRequest){
|
||||
case CMD_ECHO:
|
||||
memcpy(buf, &req->wValue, sizeof(req->wValue));
|
||||
len = sizeof(req->wValue);
|
||||
break;
|
||||
case CMD_GET_FUNC:
|
||||
/* Report our capabilities */
|
||||
bzero(buf, req->wLength);
|
||||
memcpy(buf, &func, sizeof(func));
|
||||
len = req->wLength;
|
||||
break;
|
||||
case CMD_I2C_IO:
|
||||
case CMD_I2C_IO | CMD_I2C_BEGIN:
|
||||
case CMD_I2C_IO | CMD_I2C_END:
|
||||
case CMD_I2C_IO | CMD_I2C_BEGIN | CMD_I2C_END: // read
|
||||
if(req->wValue & I2C_M_RD){ // IN - only read
|
||||
len = req->wLength;
|
||||
usb_i2c_io(req, buf, &len);
|
||||
EP_Write(3, (uint8_t*)usbbuff, buflen);
|
||||
lastdsz = buflen;
|
||||
}
|
||||
|
||||
// data IN/OUT handlers
|
||||
static void transmit_Handler(){ // EP3IN
|
||||
uint16_t epstatus = KEEP_DTOG_STAT(USB->EPnR[3]);
|
||||
// clear CTR keep DTOGs & STATs
|
||||
USB->EPnR[3] = (epstatus & ~(USB_EPnR_CTR_TX)); // clear TX ctr
|
||||
send_next();
|
||||
}
|
||||
|
||||
// receiver reads data from local buffer and only then ACK'ed
|
||||
static void receive_Handler(){ // EP2OUT
|
||||
uint16_t status = KEEP_DTOG_STAT(USB->EPnR[2]); // don't change DTOG and NACK
|
||||
if(rcvbuflen){
|
||||
bufovrfl = 1; // lost last data
|
||||
rcvbuflen = 0;
|
||||
}
|
||||
rcvbuflen = EP_Read(2, (uint8_t*)rcvbuf);
|
||||
USB->EPnR[2] = status & ~USB_EPnR_CTR_RX;
|
||||
}
|
||||
|
||||
// weak handlers: change them somewhere else if you want to setup USART
|
||||
// SET_LINE_CODING
|
||||
void WEAK linecoding_handler(usb_LineCoding *lc){
|
||||
lineCoding = *lc;
|
||||
}
|
||||
|
||||
// SET_CONTROL_LINE_STATE
|
||||
void WEAK clstate_handler(uint16_t _U_ val){
|
||||
}
|
||||
|
||||
// SEND_BREAK
|
||||
void WEAK break_handler(){
|
||||
}
|
||||
|
||||
|
||||
// USB is configured: setup endpoints
|
||||
void set_configuration(){
|
||||
EP_Init(1, EP_TYPE_INTERRUPT, USB_EP1BUFSZ, 0, EP1_Handler); // IN1 - transmit
|
||||
EP_Init(2, EP_TYPE_BULK, 0, USB_RXBUFSZ, receive_Handler); // OUT2 - receive data
|
||||
EP_Init(3, EP_TYPE_BULK, USB_TXBUFSZ, 0, transmit_Handler); // IN3 - transmit data
|
||||
}
|
||||
|
||||
// PL2303 CLASS request
|
||||
void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){
|
||||
uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType);
|
||||
uint8_t dev2host = (req->bmRequestType & 0x80) ? 1 : 0;
|
||||
switch(recipient){
|
||||
case REQ_RECIPIENT_INTERFACE:
|
||||
switch(req->bRequest){
|
||||
case SET_LINE_CODING:
|
||||
if(!data || !datalen) break; // wait for data
|
||||
if(datalen == sizeof(usb_LineCoding))
|
||||
linecoding_handler((usb_LineCoding*)data);
|
||||
break;
|
||||
case CMD_GET_STATUS:
|
||||
memcpy(buf, &status, sizeof(status));
|
||||
len = sizeof(status);
|
||||
case GET_LINE_CODING:
|
||||
EP_WriteIRQ(0, (uint8_t*)&lineCoding, sizeof(lineCoding));
|
||||
break;
|
||||
case SET_CONTROL_LINE_STATE:
|
||||
CDCready = 1;
|
||||
clstate_handler(req->wValue);
|
||||
break;
|
||||
case SEND_BREAK:
|
||||
CDCready = 0;
|
||||
break_handler();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
EP_WriteIRQ(0, buf, len); // write ZLP if nothing received
|
||||
break;
|
||||
default:
|
||||
if(dev2host) EP_WriteIRQ(0, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_vendor_request(config_pack_t *req, uint8_t *data, unsigned int datalen) __attribute__ ((alias ("usb_class_request")));
|
||||
// Vendor request for PL2303
|
||||
void usb_vendor_request(config_pack_t *req, uint8_t _U_ *data, uint16_t _U_ datalen){
|
||||
uint8_t c;
|
||||
if(req->bmRequestType & 0x80){ // read
|
||||
switch(req->wValue){
|
||||
case 0x8484:
|
||||
c = 2;
|
||||
break;
|
||||
case 0x0080:
|
||||
c = 1;
|
||||
break;
|
||||
case 0x8686:
|
||||
c = 0xaa;
|
||||
break;
|
||||
default:
|
||||
c = 0;
|
||||
}
|
||||
EP_WriteIRQ(0, &c, 1);
|
||||
}else{ // write ZLP
|
||||
EP_WriteIRQ(0, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// blocking send full content of ring buffer
|
||||
int USB_sendall(){
|
||||
while(lastdsz > 0){
|
||||
if(!CDCready) return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// put `buf` into queue to send
|
||||
int USB_send(const uint8_t *buf, int len){
|
||||
if(!buf || !CDCready || !len) return FALSE;
|
||||
while(len){
|
||||
int a = RB_write((ringbuffer*)&rbout, buf, len);
|
||||
if(a > 0){
|
||||
len -= a;
|
||||
buf += a;
|
||||
} else if (a < 0) continue; // do nothing if buffer is in reading state
|
||||
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int USB_putbyte(uint8_t byte){
|
||||
if(!CDCready) return FALSE;
|
||||
int l = 0;
|
||||
while((l = RB_write((ringbuffer*)&rbout, &byte, 1)) != 1){
|
||||
if(l < 0) continue;
|
||||
}
|
||||
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int USB_sendstr(const char *string){
|
||||
if(!string || !CDCready) return FALSE;
|
||||
int len = 0;
|
||||
const char *b = string;
|
||||
while(*b++) ++len;
|
||||
if(!len) return FALSE;
|
||||
return USB_send((const uint8_t*)string, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief USB_receive - get binary data from receiving ring-buffer
|
||||
* @param buf (i) - buffer for received data
|
||||
* @param len - length of `buf`
|
||||
* @return amount of received bytes (negative, if overfull happened)
|
||||
*/
|
||||
int USB_receive(uint8_t *buf, int len){
|
||||
chkin();
|
||||
if(bufovrfl){
|
||||
while(1 != RB_clearbuf((ringbuffer*)&rbin));
|
||||
bufovrfl = 0;
|
||||
return -1;
|
||||
}
|
||||
int sz = RB_read((ringbuffer*)&rbin, buf, len);
|
||||
if(sz < 0) return 0; // buffer in writting state
|
||||
return sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief USB_receivestr - get string up to '\n' and replace '\n' with 0
|
||||
* @param buf - receiving buffer
|
||||
* @param len - its length
|
||||
* @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared)
|
||||
*/
|
||||
int USB_receivestr(char *buf, int len){
|
||||
chkin();
|
||||
if(bufovrfl){
|
||||
while(1 != RB_clearbuf((ringbuffer*)&rbin));
|
||||
bufovrfl = 0;
|
||||
return -1;
|
||||
}
|
||||
int l = RB_readto((ringbuffer*)&rbin, '\n', (uint8_t*)buf, len);
|
||||
if(l < 1){
|
||||
if(rbin.length == RB_datalen((ringbuffer*)&rbin)){ // buffer is full but no '\n' found
|
||||
while(1 != RB_clearbuf((ringbuffer*)&rbin));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if(l == 0) return 0;
|
||||
buf[l-1] = 0; // replace '\n' with strend
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
@ -18,3 +18,47 @@
|
||||
#include <stm32f1.h>
|
||||
#include "usb_lib.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t dwDTERate;
|
||||
uint8_t bCharFormat;
|
||||
#define USB_CDC_1_STOP_BITS 0
|
||||
#define USB_CDC_1_5_STOP_BITS 1
|
||||
#define USB_CDC_2_STOP_BITS 2
|
||||
uint8_t bParityType;
|
||||
#define USB_CDC_NO_PARITY 0
|
||||
#define USB_CDC_ODD_PARITY 1
|
||||
#define USB_CDC_EVEN_PARITY 2
|
||||
#define USB_CDC_MARK_PARITY 3
|
||||
#define USB_CDC_SPACE_PARITY 4
|
||||
uint8_t bDataBits;
|
||||
} __attribute__ ((packed)) usb_LineCoding;
|
||||
|
||||
extern usb_LineCoding lineCoding;
|
||||
|
||||
void break_handler();
|
||||
void clstate_handler(uint16_t val);
|
||||
void linecoding_handler(usb_LineCoding *lc);
|
||||
|
||||
|
||||
// sizes of ringbuffers for outgoing and incoming data
|
||||
#define RBOUTSZ (256)
|
||||
#define RBINSZ (256)
|
||||
|
||||
#define newline() USB_putbyte('\n')
|
||||
#define USND(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0)
|
||||
|
||||
#define STR_HELPER(s) #s
|
||||
#define STR(s) STR_HELPER(s)
|
||||
|
||||
#ifdef EBUG
|
||||
#define DBG(str) do{USB_sendstr(__FILE__ " (L" STR(__LINE__) "): " str); newline();}while(0)
|
||||
#else
|
||||
#define DBG(str)
|
||||
#endif
|
||||
|
||||
int USB_sendall();
|
||||
int USB_send(const uint8_t *buf, int len);
|
||||
int USB_putbyte(uint8_t byte);
|
||||
int USB_sendstr(const char *string);
|
||||
int USB_receive(uint8_t *buf, int len);
|
||||
int USB_receivestr(char *buf, int len);
|
||||
|
||||
@ -28,62 +28,14 @@ static uint8_t ep0dbuflen = 0;
|
||||
static config_pack_t *setup_packet = (config_pack_t*) setupdatabuf;
|
||||
volatile uint8_t usbON = 0; // device is configured and active
|
||||
|
||||
static void wr0(const uint8_t *buf, uint16_t size){
|
||||
if(setup_packet->wLength < size) size = setup_packet->wLength; // shortened request
|
||||
if(size < endpoints[0].txbufsz){
|
||||
EP_WriteIRQ(0, buf, size);
|
||||
return;
|
||||
}
|
||||
while(size){
|
||||
uint16_t l = size;
|
||||
if(l > endpoints[0].txbufsz) l = endpoints[0].txbufsz;
|
||||
EP_WriteIRQ(0, buf, l);
|
||||
buf += l;
|
||||
size -= l;
|
||||
uint8_t needzlp = (l == endpoints[0].txbufsz) ? 1 : 0;
|
||||
if(size || needzlp){ // send last data buffer
|
||||
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]);
|
||||
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
|
||||
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
|
||||
^ USB_EPnR_STAT_TX;
|
||||
uint32_t ctr = 1000000;
|
||||
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
|
||||
if((USB->ISTR & USB_ISTR_CTR) == 0){
|
||||
return;
|
||||
}
|
||||
if(needzlp) EP_WriteIRQ(0, (uint8_t*)0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void get_descriptor(){
|
||||
uint8_t descrtype = setup_packet->wValue >> 8,
|
||||
descridx = setup_packet->wValue & 0xff;
|
||||
switch(descrtype){
|
||||
case DEVICE_DESCRIPTOR:
|
||||
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor));
|
||||
break;
|
||||
case CONFIGURATION_DESCRIPTOR:
|
||||
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor));
|
||||
break;
|
||||
case STRING_DESCRIPTOR:
|
||||
if(descridx < iDESCR_AMOUNT) wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]));
|
||||
else EP_WriteIRQ(0, (uint8_t*)0, 0);
|
||||
break;
|
||||
case DEVICE_QUALIFIER_DESCRIPTOR:
|
||||
wr0(USB_DeviceQualifierDescriptor, USB_DeviceQualifierDescriptor[0]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const uint8_t HID_ReportDescriptor[] WEAK = {0};
|
||||
|
||||
static uint16_t configuration = 0; // reply for GET_CONFIGURATION (==1 if configured)
|
||||
static inline void std_d2h_req(){
|
||||
uint16_t st = 0;
|
||||
switch(setup_packet->bRequest){
|
||||
case GET_DESCRIPTOR:
|
||||
get_descriptor();
|
||||
get_descriptor(setup_packet);
|
||||
break;
|
||||
case GET_STATUS:
|
||||
EP_WriteIRQ(0, (uint8_t *)&st, 2); // send status: Bus Powered
|
||||
@ -105,7 +57,7 @@ static inline void std_h2d_req(){
|
||||
case SET_CONFIGURATION:
|
||||
// Now device configured
|
||||
configuration = setup_packet->wValue;
|
||||
set_configuration(configuration);
|
||||
set_configuration();
|
||||
usbON = 1;
|
||||
break;
|
||||
default:
|
||||
@ -116,6 +68,7 @@ static inline void std_h2d_req(){
|
||||
void WEAK usb_standard_request(){
|
||||
uint8_t recipient = REQUEST_RECIPIENT(setup_packet->bmRequestType);
|
||||
uint8_t dev2host = (setup_packet->bmRequestType & 0x80) ? 1 : 0;
|
||||
uint8_t reqtype = REQUEST_TYPE(setup_packet->bmRequestType);
|
||||
switch(recipient){
|
||||
case REQ_RECIPIENT_DEVICE:
|
||||
if(dev2host){
|
||||
@ -123,15 +76,31 @@ void WEAK usb_standard_request(){
|
||||
return;
|
||||
}else{
|
||||
std_h2d_req();
|
||||
EP_WriteIRQ(0, (uint8_t *)0, 0);
|
||||
EP_WriteIRQ(0, NULL, 0);
|
||||
}
|
||||
break;
|
||||
case REQ_RECIPIENT_INTERFACE:
|
||||
//EP_WriteIRQ(0, (const uint8_t*)"epi", 4);
|
||||
switch(reqtype){
|
||||
case REQ_TYPE_STANDARD:
|
||||
if(dev2host && setup_packet->bRequest == GET_DESCRIPTOR){
|
||||
get_descriptor(setup_packet);
|
||||
}else EP_WriteIRQ(0, NULL, 0);
|
||||
break;
|
||||
case REQ_TYPE_CLASS:
|
||||
if(setup_packet->bRequest == GET_INTERFACE){
|
||||
EP_WriteIRQ(0, NULL, 0);
|
||||
}/*else if (setup_packet->bRequest == SET_FEAUTRE){
|
||||
//set_featuring = 1;
|
||||
}*/
|
||||
break;
|
||||
default:
|
||||
if(dev2host) EP_WriteIRQ(0, NULL, 0);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case REQ_RECIPIENT_ENDPOINT:
|
||||
if(setup_packet->bRequest == CLEAR_FEATURE){
|
||||
EP_WriteIRQ(0, (uint8_t *)0, 0);
|
||||
EP_WriteIRQ(0, NULL, 0);
|
||||
}else{
|
||||
//EP_WriteIRQ(0, (const uint8_t*)"epr", 4);
|
||||
}
|
||||
@ -142,12 +111,12 @@ void WEAK usb_standard_request(){
|
||||
}
|
||||
}
|
||||
|
||||
void WEAK usb_class_request(config_pack_t _U_ *req, uint8_t _U_ *data, unsigned int _U_ datalen){
|
||||
EP_WriteIRQ(0, (const uint8_t*)"cls", 4);
|
||||
void WEAK usb_class_request(config_pack_t _U_ *req, uint8_t _U_ *data, uint16_t _U_ datalen){
|
||||
EP_WriteIRQ(0, NULL, 0);
|
||||
}
|
||||
|
||||
void WEAK usb_vendor_request(config_pack_t _U_ *packet, uint8_t _U_ *data, unsigned int _U_ datalen){
|
||||
EP_WriteIRQ(0, (const uint8_t*)"vnd", 4);
|
||||
void WEAK usb_vendor_request(config_pack_t _U_ *packet, uint8_t _U_ *data, uint16_t datalen){
|
||||
EP_WriteIRQ(0, NULL, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -76,10 +76,6 @@
|
||||
#endif // NOCAN
|
||||
|
||||
// first 64 bytes of USB_BTABLE are registers!
|
||||
// for USB FS EP0 buffers are from 8 to 64 bytes long
|
||||
#define USB_EP0BUFSZ 64
|
||||
// EP1 - interrupt - buffer size
|
||||
#define USB_EP1BUFSZ 8
|
||||
|
||||
#define USB_BTABLE_BASE 0x40006000
|
||||
#define USB ((USB_TypeDef *) USB_BASE)
|
||||
@ -218,6 +214,7 @@ typedef struct{
|
||||
#define REQ_TYPE_VENDOR 2
|
||||
#define REQ_TYPE_RESERVED 3
|
||||
|
||||
/*
|
||||
// deprecated defines:
|
||||
#define STANDARD_DEVICE_REQUEST_TYPE 0
|
||||
#define STANDARD_INTERFACE_REQUEST_TYPE 1
|
||||
@ -225,6 +222,7 @@ typedef struct{
|
||||
#define STANDARD_OTHER_REQUEST_TYPE 3
|
||||
#define VENDOR_REQUEST_TYPE 0x40
|
||||
#define CONTROL_REQUEST_TYPE 0x21
|
||||
*/
|
||||
|
||||
// bRequest, standard; for bmRequestType == 0x80
|
||||
#define GET_STATUS 0x00
|
||||
@ -247,6 +245,7 @@ typedef struct{
|
||||
#define CONFIGURATION_DESCRIPTOR 0x02
|
||||
#define STRING_DESCRIPTOR 0x03
|
||||
#define DEVICE_QUALIFIER_DESCRIPTOR 0x06
|
||||
#define HID_REPORT_DESCRIPTOR 0x2200
|
||||
|
||||
// EP types
|
||||
#define EP_TYPE_BULK 0x00
|
||||
@ -315,6 +314,6 @@ void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size);
|
||||
int EP_Read(uint8_t number, uint8_t *buf);
|
||||
|
||||
// could be [re]defined in usb_dev.c
|
||||
extern void usb_class_request(config_pack_t *packet, uint8_t *data, unsigned int datalen);
|
||||
extern void usb_vendor_request(config_pack_t *packet, uint8_t *data, unsigned int datalen);
|
||||
extern void set_configuration(uint16_t configuration);
|
||||
extern void usb_class_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
|
||||
extern void usb_vendor_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
|
||||
extern void set_configuration();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user