Some mounth ago some Malakian light up my brains with ultraviolet. In conclusion, everyone of us remembers this ROM ICs with glass window for clearing by ultraviolet bulb? I am not. That is why I start my simple USB device example not from the example code from chip supplier company. I started to write my own driver using datasheets and standards.
Structure
This main structure is shown on the picture.
The main part is USB core. It contains USB event loop, implemented in main usb interrupt. Also it based on registers and uses requests. In my case this is gamepad, so the buttons poll function with all relateds is in separate file. Then, both, the USB core and port poll are combined in one file. Totaly, user may only include main file and call main init in order to use my usb hid driver.
Usb Core.
Firstly you should init USB. In order to do it, you must confiure appropriate clocking, standard 48MHz. Port settings does not affect peripheral, because usb is internally connected to pins. As for USB settings firstly disable both sleep modes and enable usb power, also null and check reset.
RCC_APB1ENR |= USBEN;
// usb on
USB_CNTR &= ~((uint32_t)PDWN);
rough_delay_us(2);
USB_ISTR = 0;
// reset usb
USB_CNTR &= ~((uint32_t)FRES);
uint32_t timeout = 1e6;
while( ((USB_ISTR & RESET) == 0) && (--timeout < 2) );
USB_ISTR = 0;
USB_CNTR &= ~((uint32_t)(LP_MODE | FSUSP));
USB_ISTR = 0;
That is all. Because data transmittion performed only after reset request from the host. In reset function, who runs after reset request from the host, interrupts can be activated and null address should be setted along with yet another enable bit. And endpoints should be enabled. Here is fragment of reset function:
USB_DADDR = EF | 0x00;
usbHidEndpInit();
usbReportEndpInit();
usbItInit();
Also driver must support wakeup and suspend events. In suspend mode the whole peripherial can be turned off. But futher wakeup should be carried out throw the special interrupt line. Anyway the whole power consumption of the MCU is small, and always fits into the requirements of the standard. In my case the system clock is slowing down when suspend then it inits again when wakeup.
Endpoints
As it sayed in standard, endpoint is a separate portion of usb device. For every endpoint there is allocated memory and separate control register. Endpoints are initialized firstly, then data can be transmitted through they. There are four types of endpoints: bulk, isochronous, interrupt and control. They also can be transmit only, receive only or bidirectional. I.e. before endpoint using you must set endpoint type, address, and allocate it's memory buffers. Here is example of endpoint1 initializaion, which uses for transmitting reports with buttons data to host PC:
// endpoint 1 tx buffer
USB_ADDR1_TX = EP1_TX_START;
// endpoint 1 address 1, type interrupt endpoint
USB_EP1R = EP_TYPE_INTERRUPT | (1 & EA_MASK);
defaultDtogInit(1);
epRxStatusSet(1, STAT_RX_DISABLED);
epTxStatusSet(1, STAT_TX_NAK);
After receiving of data package throught the any endpoint, CTR bit of main interrupt is activated and endpoint state bits changes. Also for transmitting data after filling buffer you must change endpoint state fron TX_NAK to TX_VALID.
USB RAM
In F103 there are 512 bytes of SRAM for USB. Memory access is not so obvious. Firstly, user can allocate memory for USB manually. Secondly, this memory have complicated access rule. Memory is accessed by 16bit words who contained in every 32 bits as lowest bits. And the memory allocation table is kept in the same memory. So, that complicated case can be shown only in code example.
Allocation table addresses:
/* USB RAM table */
/* Transmission buffer address 0 */
#define USB_ADDR0_TX MMIO32(USB_CAN_SRAM_BASE + ((uint16_t)USB_BTABLE + 0x00)*2)
/* Transmission byte count 0 */
#define USB_COUNT0_TX MMIO32(USB_CAN_SRAM_BASE + ((uint16_t)USB_BTABLE + 0x02)*2)
/* Reception buffer address 0 */
/* Reception byte count 0 */
#define USB_COUNT0_RX MMIO32(USB_CAN_SRAM_BASE + ((uint16_t)USB_BTABLE + 0x06)*2)
// addresses of endpoint 1
#define USB_ADDR1_TX MMIO32(USB_CAN_SRAM_BASE + ((uint16_t)USB_BTABLE + 0x08)*2)
#define USB_COUNT1_TX MMIO32(USB_CAN_SRAM_BASE + ((uint16_t)USB_BTABLE + 0x0a)*2)
#define USB_ADDR1_RX MMIO32(USB_CAN_SRAM_BASE + ((uint16_t)USB_BTABLE + 0x0c)*2)
#define USB_COUNT1_RX MMIO32(USB_CAN_SRAM_BASE + ((uint16_t)USB_BTABLE + 0x0e)*2)
Example of memory access to transmit packet buffer of endpoint 0:
void controlTxDataN(uint8_t *data, int size)
{
uint16_t *input = (uint16_t*)data;
uint16_t *bufferPtr = (uint16_t*)(USB_ADDR0_TX*2 + USB_CAN_SRAM_BASE);
for(int i=0 ; i<(size/2) ; ++i) {
*bufferPtr = *input;
input++;
bufferPtr += 2;
}
}
Port poll and report send.
The report data in USB HID gamepad is result of MCU GPIO polling. In order to support polling and transmitting permanently I have a timer running poll every millisecond. The same interrupt sends report with report duration periodicity. Every seven fixed pushes of button means the putton push. Then information about buttons saved in report byte then this report may be transmitted to the host PC through the endpoint1.
Here is listing of the interrupt:
static int cnt=0;
portPoll();
reportUpdate();
++cnt;
sendReport(gamepadPar.report, &cnt);
And part of port poll function:
gamepadPar.report = 0;
// angles of d-pad including two buttons pressed simultaneously
if( gamepadPar.leftCnt >= cntPressed ) {
gamepadPar.report |= XM1;
}
if( gamepadPar.rightCnt >= cntPressed ) {
gamepadPar.report |= XP1;
}
if( gamepadPar.upCnt >= cntPressed ) {
gamepadPar.report |= YM1;
}
if( gamepadPar.dnCnt >= cntPressed ) {
gamepadPar.report |= YP1;
}
Standard USB requests.
There are 9 pcs of mandatory requests. But only two are userfull. First sets address to the device and second returns descriptors to the host PC. Also the HID related requests are exists, but linux driwer knows nothing about they. Here is request handler function listing:
int stReqHandler(requestTyp *request)
{
switch ( request->bRequest ) {
case GET_STATUS:
return getStatusReqHandler(request);
case CLEAR_FEATURE:
return clearFeatureReqHandler(request);
case SET_FEATURE:
return setFeatureReqHandler(request);
case SET_ADDRESS:
return setAddressReqHandler(request);
case GET_DESCRIPTOR:
return getDescriptorReqHandler(request);
case GET_CONFIGURATION:
return getConfigurationReqHandler(request);
case SET_CONFIGURATION:
return setConfigurationReqHandler(request);
case GET_INTERFACE:
return getInterfaceReqHandler(request);
case SET_INTERFACE:
return setInterfaceReqHandler(request);
default:
return NOT_ST_REQ;
}
return REQ_ERROR;
}
Descriptors.
In my case descriptors are typical and contained in separate file. Also report descriptor was described in previous post.
Compiling.
The makefile is typical, the details are in that post. The whole project is located on github. So, the tools can be installed in ubuntu by that command:
apt install gcc-arm-none-eabi libnewlib-arm-none-eabi
apt install gdb-multiarch git stlink-tools jstest-gtk
After cloning project you can compile and upload the firmware to any STM32F103 based board by commands:
make all
st-flash write result/button.bin 0x8000000
Комментариев нет :
Отправить комментарий