Cube HAL zawiera odpowiedniki tych funkcji, ja postanowiłem napisać je samemu, żeby lepiej zrozumieć działanie układu - ale zachęcam do używania HAL-a, żeby nie było na mnie jak coś będzie źle.
Po pierwsze postanowiłem nieco uprościć konfigurację. Endpointy mogą mieć właściwie dowolne wielkości w obrębie dostępnej pamięci oczywiści. Ja ustaliłem wielkość wszystkich na 64 bajty. Ustawiam również numer endpointa (pole EA) na równe indeksowi rejestru.
Moja biblioteka ma następujący interfejs (plik endpoint.h):
Kod: Zaznacz cały
#ifndef ENDPOINT_H_
#define ENDPOINT_H_
#include <stdint.h>
#include <stdbool.h>
#define ENDPOINT_SIZE 64
void endpoint_init(uint8_t ep, uint16_t rx_offset, uint16_t tx_offset);
void endpoint_set_rx_status(uint8_t ep, uint16_t status);
void endpoint_set_tx_status(uint8_t ep, uint16_t status);
void endpoint_use_rx_data0(uint8_t ep);
void endpoint_use_tx_data0(uint8_t ep);
void endpoint_set_type(uint8_t ep, uint16_t type);
void endpoint_clear_rx(uint8_t ep);
void endpoint_clear_tx(uint8_t ep);
bool endpoint_is_setup_req(uint8_t ep);
void endpoint_copy_from(uint8_t ep, void *data, uint16_t length);
void endpoint_copy_to(uint8_t ep, const void *data, uint16_t length);
uint16_t endpoint_get_rx_length(uint8_t ep);
#endif /* ENDPOINT_H_ */
- endpoint_init - wypełnia wpis w "spisie treści" buforów, przypisuje bitom EA wartość równą numerowi endpointu
- endpoint_set_rx_status - ustawia status endpointa odbiorczego (DIABLED, STALL, NAK, VALID)
- endpoint_set_tx_status - ustawia status endpointa nadawczego
- endpoint_use_rx_data0 - wymusza użycie pakietu DATA0 podczas następnego odbioru danych
- endpoint_use_tx_data0 - wymusza użycie pakietu DATA0 podczas następnego wysyłania danych
- endpoint_set_type - ustawia typ endpointu (CONTROL, INTERRUPT, BULK lub ISOCHRONOUS)
- endpoint_clear_rx - kasuje flagę przerwania po odebraniu danych
- endpoint_clear_tx - kasuje flagę przerwania po wysłaniu danych
- endpoint_is_setup_req - zwraca true jeśli otrzymano żądanie SETUP
- endpoint_copy_from - kopiuje dane z endpointa
- endpoint_copy_to - kopiuje dane do endpointa
- endpoint_get_rx_length - zwraca liczbę odebranych bajtów
Kod: Zaznacz cały
#include "stm32l053xx.h"
#include "endpoint.h"
#define EP_REG(n) (*(volatile uint16_t *)((uint32_t)&USB->EP0R + (n) * 4))
struct endpoint_mem_desc {
__IO uint16_t ADDR_TX;
__IO uint16_t COUNT_TX;
__IO uint16_t ADDR_RX;
__IO uint16_t COUNT_RX;
};
static struct endpoint_mem_desc *USB_EP = (struct endpoint_mem_desc*)USB_PMAADDR;
void endpoint_init(uint8_t ep, uint16_t rx_offset, uint16_t tx_offset)
{
uint16_t epr = EP_REG(ep);
EP_REG(ep) = (epr & USB_EPREG_MASK) | ep | USB_EP_CTR_RX | USB_EP_CTR_TX;
USB_EP[ep].ADDR_RX = rx_offset;
USB_EP[ep].COUNT_RX = 0x8800;
USB_EP[ep].ADDR_TX = tx_offset;
USB_EP[ep].COUNT_TX = 0;
}
void endpoint_set_rx_status(uint8_t ep, uint16_t status)
{
uint16_t epr = EP_REG(ep) & USB_EPRX_DTOGMASK;
if (status == USB_EP_RX_VALID)
USB_EP[ep].COUNT_RX = 0x8800;
if (status & USB_EPRX_DTOG1)
epr ^= USB_EPRX_DTOG1;
if (status & USB_EPRX_DTOG2)
epr ^= USB_EPRX_DTOG2;
EP_REG(ep) = epr | USB_EP_CTR_RX | USB_EP_CTR_TX;
}
void endpoint_set_tx_status(uint8_t ep, uint16_t status)
{
uint16_t epr = EP_REG(ep) & USB_EPTX_DTOGMASK;
if (status & USB_EPTX_DTOG1)
epr ^= USB_EPTX_DTOG1;
if (status & USB_EPTX_DTOG2)
epr ^= USB_EPTX_DTOG2;
EP_REG(ep) = epr | USB_EP_CTR_RX | USB_EP_CTR_TX;
}
void endpoint_use_rx_data0(uint8_t ep)
{
uint16_t epr = EP_REG(ep);
if (epr & USB_EP_DTOG_RX)
EP_REG(ep) = (epr & USB_EPREG_MASK) | USB_EP_DTOG_RX | USB_EP_CTR_RX | USB_EP_CTR_TX;
}
void endpoint_use_tx_data0(uint8_t ep)
{
uint16_t epr = EP_REG(ep);
if (epr & USB_EP_DTOG_TX)
EP_REG(ep) = (epr & USB_EPREG_MASK) | USB_EP_DTOG_TX | USB_EP_CTR_RX | USB_EP_CTR_TX;
}
void endpoint_set_type(uint8_t ep, uint16_t type)
{
uint16_t epr = EP_REG(ep);
EP_REG(ep) = (epr & USB_EP_T_MASK) | type | USB_EP_CTR_RX | USB_EP_CTR_TX;
}
void endpoint_clear_rx(uint8_t ep)
{
uint16_t epr = EP_REG(ep) & USB_EPREG_MASK;
EP_REG(ep) = (epr | USB_EP_CTR_TX) & ~USB_EP_CTR_RX;
}
void endpoint_clear_tx(uint8_t ep)
{
uint16_t epr = EP_REG(ep) & USB_EPREG_MASK;
EP_REG(ep) = (epr | USB_EP_CTR_RX) & ~USB_EP_CTR_TX;
}
bool endpoint_is_setup_req(uint8_t ep)
{
uint16_t epr = EP_REG(ep) & USB_EPREG_MASK;
if (epr & USB_EP_SETUP)
return true;
else
return false;
}
void endpoint_copy_from(uint8_t ep, void *data, uint16_t length)
{
volatile uint16_t *p = (volatile uint16_t*)(USB_PMAADDR + USB_EP[ep].ADDR_RX);
uint8_t *pdata = data;
for (int i = 0; i < length / 2; i++) {
uint16_t tmp = *p++;
*pdata++ = tmp;
*pdata++ = tmp >> 8;
}
if (length & 1) {
uint16_t tmp = *p++;
*pdata++ = tmp;
}
}
void endpoint_copy_to(uint8_t ep, const void *data, uint16_t length)
{
volatile uint16_t *p = (volatile uint16_t*)(USB_PMAADDR + USB_EP[ep].ADDR_TX);
const uint8_t *pdata = data;
for (int i = 0; i < length / 2; i++) {
uint16_t tmp = pdata[i * 2] + ((pdata[i * 2 + 1]) << 8);
*p++ = tmp;
}
if (length & 1) {
uint16_t tmp = pdata[length - 1];
*p++ = tmp;
}
USB_EP[ep].COUNT_TX = length;
}
uint16_t endpoint_get_rx_length(uint8_t ep)
{
volatile uint16_t rx = USB_EP[ep].COUNT_RX;
return rx & 0x3ff;
}
Kod: Zaznacz cały
#include <stdio.h>
#include <string.h>
#include "stm32l053xx.h"
#include "endpoint.h"
#include "usb.h"
#include "usb_desc.h"
struct setup_request {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
};
static void usb_reset(void)
{
endpoint_init(0, 0x040, 0x080);
endpoint_init(1, 0x0c0, 0x100);
USB->DADDR = USB_DADDR_EF;
endpoint_use_rx_data0(0);
endpoint_set_type(0, USB_EP_CONTROL);
endpoint_set_rx_status(0, USB_EP_RX_VALID);
endpoint_set_rx_status(1, USB_EP_RX_DIS);
endpoint_set_tx_status(1, USB_EP_TX_DIS);
}
void USB_IRQHandler(void)
{
volatile uint32_t status = USB->ISTR;
struct setup_request req;
if (status & USB_ISTR_RESET) {
USB->ISTR = 0;
usb_reset();
} else if (status & USB_ISTR_CTR) {
uint8_t ep = status & 0xf;
if (status & USB_ISTR_DIR) {
endpoint_clear_rx(ep);
if (endpoint_is_setup_req(ep)) {
// transaction SETUP (RX)
endpoint_copy_from(0, &req, sizeof(req));
// TODO: handle SETUP request
asm volatile ("bkpt #1");
} else {
// transaction OUT (RX)
// TODO:
}
} else {
// transaction IN (TX)
// TODO:
}
}
}
static void clocks_config(void)
{
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
PWR->CR = (PWR->CR & ~PWR_CR_VOS_Msk) | PWR_CR_VOS_0;
RCC->CR |= RCC_CR_HSEBYP | RCC_CR_HSEON;
while ((RCC->CR & RCC_CR_HSERDY) == 0) {}
RCC->CFGR = (RCC->CFGR & ~(RCC_CFGR_PLLSRC_Msk | RCC_CFGR_PLLMUL_Msk | RCC_CFGR_PLLDIV_Msk))
| RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMUL12 | RCC_CFGR_PLLDIV3;
RCC->CR |= RCC_CR_PLLON;
while ((RCC->CR & RCC_CR_PLLRDY) == 0) {}
RCC->CFGR = (RCC->CFGR & ~(RCC_CFGR_HPRE_Msk | RCC_CFGR_PPRE1_Msk | RCC_CFGR_PPRE2_Msk))
| RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV1 | RCC_CFGR_PPRE2_DIV1 | RCC_CFGR_SW_PLL;
RCC->CRRCR |= RCC_CRRCR_HSI48ON;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
SYSCFG->CFGR3 |= SYSCFG_CFGR3_ENREF_HSI48;
while ((RCC->CRRCR & RCC_CRRCR_HSI48RDY) == 0) {}
RCC->CCIPR |= RCC_CCIPR_HSI48MSEL;
RCC->IOPENR |= RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN | RCC_IOPENR_GPIOCEN | RCC_IOPENR_GPIODEN | RCC_IOPENR_GPIOHEN;
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
}
static void usb_init(void)
{
USB->CNTR = 0;
USB->CNTR = USB_CNTR_FRES;
USB->ISTR = 0;
USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM;
USB->BTABLE = 0;
USB->BCDR |= USB_BCDR_DPPU;
usb_reset();
NVIC_SetPriority(USB_IRQn, 0);
NVIC_EnableIRQ(USB_IRQn);
}
int main(void)
{
clocks_config();
usb_init();
while (1) {
}
}
Kod: Zaznacz cały
void USB_IRQHandler(void)
{
volatile uint32_t status = USB->ISTR;
struct setup_request req;
if (status & USB_ISTR_RESET) {
USB->ISTR = 0;
usb_reset();
} else if (status & USB_ISTR_CTR) {
uint8_t ep = status & 0xf;
if (status & USB_ISTR_DIR) {
endpoint_clear_rx(ep);
if (endpoint_is_setup_req(ep)) {
// transaction SETUP (RX)
endpoint_copy_from(0, &req, sizeof(req));
// TODO: handle SETUP request
asm volatile ("bkpt #1");
} else {
// transaction OUT (RX)
// TODO:
}
} else {
// transaction IN (TX)
// TODO:
}
}
}
W zmiennej req znajdziemy odebrane dane, o których napiszę za chwilę. Dla upewnienia się że wszystko działa jeszcze zrzut ekranu analizatora:

Jak widać to co pokazuje debuger odpowiada danym z analizatora - więc pierwsze dane udało się odebrać. Czas przeanalizować otrzymane dane i odpowiedzieć hostowi.
Dodano po 19 minutach 48 sekundach:
Format żądań hosta zdefiniowany jest przez standard USB, więcej szczegółów znajdziemy na stronie https://usb.org. Pakiet SETUP ma rozmiar 8 bajtów do których dekodowania zadeklarowałem strukturę setup_request:
Kod: Zaznacz cały
struct setup_request {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
};
Otrzymana wartość to 0x80, czyli zapytanie (Device-to-host), adresowane do urządzenia (Device), a samo zapytanie to standardowe zapytanie USB. Możliwe są jeszcze zapytania zdefinowane dla klasy (u nas HID, inne ma np. CDC). Możliwe są też zapytania specyficzne dla producenta (Vendor).
Standardowe zapytania znajdziemy w dokumentacji protokołu USB:

Otrzymane przez nas zapytanie znajdziemy w polu bRequest. Jego wartość to 0x06, czyli GET_DESCRIPTOR. W pierwszej części widzieliśmy jak przebiega komunikacja z działającym stosem - na początku host pyta o deskryptor podłączonego urządzenia. Właśnie takie zapytanie teraz otrzymaliśmy.
Powinniśmy w odpowiedzi odesłać odpowiedni deskryptor. Musimy go jednak najpierw przygotować oraz nieco udoskonalić program.
Za chwilę pokażę przykładowy program, który pozwoli odesłać żądanie do hosta.