LINUX.ORG.RU

Заюзать изохронный USB дата стрим, используя libopencm3

 , ,


0

2

Здарова, народ.

Решил смастерить на STM32F103x некую простецкую USB аудио карту. Для этого заюзал библиотеку libopencm3.

Но, кажется, что ее создатели решили не реализовывать полную поддержку USB стека, и ограничились только эндпойнтами bulk и interrupt типов.

Оказалось что isochronous ендпойнты не работают вообще там.

Блин, знал бы заранее - то не брал бы эту игрушечную поделку libopencm3, взял бы родной HAL и не парился.

Это называется: начитался много лестных отзывов об libopencm3, но оказалось, что все не так радужно.

Есть на форуме люди, которые ее юзали? Мож у кого есть «патченная версия» с работой этих USB?

Ответ на: комментарий от COKPOWEHEU

Прошил твою прошивку - определяется в винде два устройства. Но при активации микрофона он не передает никаких данных.

kuzulis ★★ ()
Последнее исправление: kuzulis (всего исправлений: 1)
Ответ на: комментарий от kuzulis

О, заработало теперь (после пере-пере дергикания, пере-пере удаления девайса из системы)

Возможно, это из-за того, что у меня используется VID:PID от avr-vusb (16c0:05df). Возможно, вы раньше подключали что-то другое с теми же VID:PID. Я с таким сталкивался на WinXP, Win7 - они запоминали предыдущее устройство и не догадывались перчитать VID:PID.

Кстати, если вы использовали прошивку для F103, в моей версии подтягивающий резистор управляется ногой PA10 (это в hardware.h можно посмотреть). Если у вас по-другому, тоже может быть проблема.

COKPOWEHEU ()
Ответ на: комментарий от Eddy_Em

В опенсм3 вообще нет изохронок, как и булок с дабл буфферингом.

Там еще то ли особенность, то ли баг, но если юзать эндпойнты не по порядку, то получаем треш.

Например, у меня композитное у-во, с хидом на еп 1, и аудио микрофоном на еп 2. В такой конфигурации работает более менее до 22 кГц аудио. Но если аудио ендпойт поставить как 3,4 и т.п то пдц, аудио данные получаются вообще говеные.

kuzulis ★★ ()
Ответ на: комментарий от COKPOWEHEU

Блин, а как твой дескриптор переписать на одно у-во, например на микрофон.

Я удалил из него спикер, подправил нумерацию юнитов, уменьшил колво интерфейсов до двух. Но теперь происходит сбой энумерауиии дескриптора. У тебя там какая то магия в подсчете полей ))). Блин.

kuzulis ★★ ()
Последнее исправление: kuzulis (всего исправлений: 3)
Ответ на: комментарий от Eddy_Em

Вы бы лучше в линуксе свои железяки проверяли. Так ЛОР же!!!

В линуксе не интересно: оно либо просто работает (если слегка кривое), либо просто не работает (если кривое совсем). Без спецэффектов. А вот в винде еще добавляется развлечение угадать как же она отреагирует на поделие - опознает, посчитает бракованным, а то и вовсе упадет в BSOD.

Собственно, поэтому я на ней и не разрабатываю, что дико неудобно. А вот тестировать - самое то. Виртуалку не жалко.

А изохронную точку ты сам написал или расковырял opencm3?

Сам.

COKPOWEHEU ()
Ответ на: комментарий от kuzulis

Блин, а как твой дескриптор переписать на одно у-во, например на микрофон.

У меня архитектура аудиоустройства такая:

  • 1 USB (на динамик)

  • 2 MIC (виртуальный)

  • 3 [->1], заглушка под feature

  • 4 [->2], заглушка под feature

  • 5 [->3], Speaker (виртуальный)

  • 6 [->4], USB (на микрофон)

Соответственно если динамик не нужен, удаляете все нечетные сущности. Плюс можно в начале дескриптора изменить bNumInterfaces на 2, в заголовочном дескрипторе уменьшить количество интерфейсов до 1:

ARRLEN1(//AC interface header
        bLENGTH, //bLength
        USB_DESCR_CS_INTERFACE, //bDescriptorType
        1, //bDescriptorSubType
        USB_U16(0x0100), //bcdADC //AudioDeviceClass серийный номер
        wTOTALLENGTH, //wTotalLength
/**/   1, //bInCollection //количество интерфейсов в коллекции
        1, //bInterfaceNr(1), //массив (список) номеров интерфейсов в коллекции
/**/    //bInterfaceNr(2), ...
      )

Ну и лишний интерфейс вместе с эндпоинтом удалить. Само собой, в оставшемся эндпоинте поменять bTerminalLink.

У тебя там какая то магия в подсчете полей ))). Блин.

Ну магия, ну и что. С кем не бывает… А если серьезно, я как представил считать все это вручную, сразу стало лень, пришлось колдовать. Статьи-то мои почитайте, там ядро более-менее описано

COKPOWEHEU ()
Последнее исправление: COKPOWEHEU (всего исправлений: 1)
Ответ на: комментарий от kuzulis

Я удалил из него спикер, подправил нумерацию юнитов, уменьшил колво интерфейсов до двух. Но теперь происходит сбой энумерауиии дескриптора.

Кстати довольно много времени потратил пытаясь понять почему второе подустройство не видит. То есть если 1-м дескриптором объявить микрофон, получался микрофон, если динамик - то динамик. Вместе никак не хотели.

К слову о винде - winXP и win7 от такого надругательства вообще в обморок падали. Десятка на удивление выжила, правда даже микрофон не определяла. А линуксу как обычно пофиг: насколько сумел разобрать кривой дескриптор, так и работает.

Оказалось, в заголовочном интерфейсе нужно было указать что интерфейсов не один, а два. Вот тут

ARRLEN1(//AC interface header
        bLENGTH,
        USB_DESCR_CS_INTERFACE,
        1,
        USB_U16(0x0100),
        wTOTALLENGTH,
        2, //количество интерфейсов
      //массив номеров интерфейсов
        1, //первый
        2, //второй
        //можно добавлять еще если надо
      )
COKPOWEHEU ()
Ответ на: комментарий от kuzulis

Еще в даташите как то мутно написано про использование дабл буфферинга.

В обычном режиме

DTOG ( Data Transmission Syncronization бит ) - индикатор того, какой пакет (DATA0/DATA1) должен быть принят/передан. В double-buffer режиме используется USB peripheral ещё и для того, чтобы понять из какого буфера данные передавать / в какой принимать.

SW_BUF - индикатор того, какой буфер сейчас используется software. В качестве этого бита используется DTOG противоположной направленности. Если endpoint RX - то SW_BUF это DTOG_TX, если TX - то SW_BUF это DTOG_RX. Если софт не успел обработать буфер, то не переключит этот SW_BUF, и USB ответит NAK - типа не готов.

А в изохронном режиме NAK не пошлёшь, так ему пофиг успел софт или нет. И double buffer принудительный. Поэтому оно само дёргает DTS бит c каждым пакетом чтобы писать/читать то один буфер то другой и всё.

Насчёт HAL - не в курсе, не использую его, Может там противоположный DTOG зачем-то переключается, например.

Stanson ★★★★★ ()
Последнее исправление: Stanson (всего исправлений: 5)
Ответ на: комментарий от COKPOWEHEU

Хаха, я все-таки добился косяков даже с твоим кодом на регистрах. :)

Я чуть подправил дескриптор, теперь там двух-канальный микрофон. Макс. размер эндпойнта увеличен то 256 байт, как в дескрипторе, так и при настройке эндпойнта.

При установке частоты дискретизации 48000 Гц - начинаются косяки (мы передаем в эндпойнт буфер размером 192 байта), но при 32 кГц - все ровно..

т.е. твоя реализация переигрывает opencm3 только на одну градацию. ))

Вот картинка: https://paste.pics/3bafdb347aa0a06daad2c44c1b6c6c56

Вот мой форк: https://github.com/denis-shienkov/usb/tree/main/5.Audio_F1

Я там добавил новую папочку: 5.Audio_F1. Менять частоту дискретизации просто в константе USB_MIC_SAMPLE_RATE (сейчас там 48000 стоит).

Если поставить 32000 - то будет все норм.

PS: Я там чуток изменил еще генерацию синуса, я юзаю ф-ю из match, так что тебе надо будет подрубить либу -lm, если будешь пробовать.. т.к. я юзаю Qbs для сборки, а не make.

kuzulis ★★ ()
Последнее исправление: kuzulis (всего исправлений: 5)
Ответ на: комментарий от Stanson

DTOG ( Data Transmission Syncronization бит ) - индикатор того, какой пакет (DATA0/DATA1) должен быть принят/передан. В double-buffer режиме используется USB peripheral ещё и для того, чтобы понять из какого буфера данные передавать / в какой принимать.

SW_BUF - индикатор того, какой буфер сейчас используется software. В качестве этого бита используется DTOG противоположной направленности. Если endpoint RX - то SW_BUF это DTOG_TX, если TX - то SW_BUF это DTOG_RX. Если софт не успел обработать буфер, то не переключит этот SW_BUF, и USB ответит NAK - типа не готов.

Спасибо. Если дойдут руки до двойной буферизации, воспользуюсь этой информацией.

Получается, для OUT точки в изохронном режиме надо проверять DTOG_RX, а в bulk - писать в DTOG_TX. А можно ли сначала прочитать DTOG_RX а потом записать DTOG_TX независимо от режима?

COKPOWEHEU ()
Ответ на: комментарий от kuzulis

Я чуть подправил дескриптор, теперь там двух-канальный микрофон. Макс. размер эндпойнта увеличен то 256 байт, как в дескрипторе, так и при настройке эндпойнта.

Это не сработает. Размер PMA у них всего 512 байт. Минус 8-64 байта на ep0. Вы же пытаетесь выделить два буфера по 256 байт. Ограничьтесь 200 байтами.

И если тема аудиоустройств вам действительно интересна (мне-то нет, я всего лишь различные типы endpoint’ов тестировал), попробуйте все же с синхронизацией поиграться.

Я там добавил новую папочку: 5.Audio_F1.

А чем 4.Audio_F1 не устроила?

Менять частоту дискретизации просто в константе USB_MIC_SAMPLE_RATE (сейчас там 48000 стоит).

У меня было F_SAMPLE

Но радует что разобрались что где меняется.

COKPOWEHEU ()
Последнее исправление: COKPOWEHEU (всего исправлений: 1)
Ответ на: комментарий от COKPOWEHEU

Это не сработает. Размер PMA у них всего 512 байт. Минус 8-64 байта на ep0. Вы же пытаетесь выделить два буфера по 256 байт. Ограничьтесь 200 байтами.

Хмм.. чЁрт. так может все ограничения из-за размера PMA, а мы (Я) тут мучаемся?

У меня перед инициализацией изохронных эндпойнтов, инициализируется еп0 (это 64+64), плюс еще HID еп, итого набегает 320 байт.. остается 192 байта… но я ендпойнту изохронному даю 256… ГЫЫЫ..

Во делааа… елки… )))

kuzulis ★★ ()
Последнее исправление: kuzulis (всего исправлений: 1)
Ответ на: комментарий от COKPOWEHEU

Получается, для OUT точки в изохронном режиме надо проверять DTOG_RX, а в bulk - писать в DTOG_TX

Типа того - в изохронном DTOG_RX - указатель какой буфер сейчас заполняется, а какой можно читать-писать, а в bulk надо сообщить USB peripheral что данные в неактивном буфере готовы и их уже можно отправлять переключив DTOG_TX AKA SW_BUF

Stanson ★★★★★ ()
Ответ на: комментарий от kuzulis

Хмм.. чЁрт. так может все ограничения из-за размера PMA, а мы (Я) тут мучаемся?

Да, дело было именно в этом… Урезал размеры еп. для HID и оно на 32000 заработало.

Блин, всем спасибки! :)

kuzulis ★★ ()
Последнее исправление: kuzulis (всего исправлений: 1)
Ответ на: комментарий от kuzulis

Хмм.. чЁрт. так может все ограничения из-за размера PMA, а мы (Я) тут мучаемся?

Я-то по этим граблям потоптался еще при написании второй статьи, про MSD. Поганцы из ST запихнули указание размера памяти куда-то в недра рефмана, а вовсе не туда, где про эту память рассказывается. Плюс в заголовочник вынести поленились.

С другой стороны, мне там уже рекомендовали статическое распределение памяти прикрутить, перенесет эти вычисления в компил-тайм, да и проверку переполнения можно будет прикрутить.

У меня перед инициализацией изохронных эндпойнтов, инициализируется еп0 (это 64+64), плюс еще HID еп, итого набегает 320 байт..

А оно надо так много? То есть если ep0 у вас в основном для передачи дескрипторов в начале, может ей по 8 байт выдать и пускай на десяток пакетов разбивает. Один раз при подключении можно и потерпеть. Да и у HID’ов точки вроде небольшие: у меня для клавиатуры 9 байт посылка всего, у «мыши» и того меньше.

но я ендпойнту изохронному даю 256… ГЫЫЫ..

2х256 прошу заметить, двойная буферизация же.

Типа того - в изохронном DTOG_RX - указатель какой буфер сейчас заполняется, а какой можно читать-писать, а в bulk надо сообщить USB peripheral что данные в неактивном буфере готовы и их уже можно отправлять переключив DTOG_TX AKA SW_BUF

Я больше с практической точки зрения спрашивал. Можно ли одно проверить и тут же второе дернуть? Не поломается ли в контроллере чего.

COKPOWEHEU ()
Ответ на: комментарий от Stanson

Типа того - в изохронном DTOG_RX - указатель какой буфер сейчас заполняется, а какой можно читать-писать, а в bulk надо сообщить USB peripheral что данные в неактивном буфере готовы и их уже можно отправлять переключив DTOG_TX AKA SW_BUF

Попытался это воспроизвести на USB-CDC, безуспешно. Точнее, на прием работает, на передачу нет. В лучшем случае если дергать функцию отправки постоянно, может повезти и пакет отправится.

Может быть, есть где-то готовый код для двойной буферизации bulk точек?

COKPOWEHEU ()
Ответ на: комментарий от anonymous

Эм-м-м, а полный проект аудиоустройства микрофон + динамик, ссылку на который я кидал, не устраивает? Могу повторить, мне не жалко

https://github.com/COKPOWEHEU/usb

Или вам только lsusb -v нужно?

COKPOWEHEU ()
Ответ на: комментарий от kuzulis

Да, про булки. KIND, естественно, выставил. Иначе бы работал хотя бы обычный, не буферизованный обмен.

usb_lib.c


void usb_ep_init_double(uint8_t epnum, uint8_t ep_type, uint16_t size, epfunc_t func){
  uint8_t dir_in = (epnum & 0x80);
  epnum &= 0x0F;
  
  ENDP_STAT_TX(epnum, USB_EP_TX_DIS);
  ENDP_STAT_RX(epnum, USB_EP_RX_DIS);
  
  uint16_t buf = USB_EPx(epnum);
  buf = (buf & ~(USB_EP_DTOG_RX | USB_EP_DTOG_TX | USB_EPTX_STAT | USB_EPRX_STAT)) | USB_EP_CTR_RX | USB_EP_CTR_TX;
  buf = (buf & ~USB_EPADDR_FIELD) | epnum;
  
  buf &=~ USB_EP_T_FIELD;
  switch(ep_type){
    case USB_ENDP_CTRL: buf |= USB_EP_CONTROL; break;
    case USB_ENDP_BULK: buf |= USB_EP_BULK; break;
    case USB_ENDP_INTR: buf |= USB_EP_INTERRUPT; break;
    default: buf |= USB_EP_ISOCHRONOUS; //в дескрипторах изохронные точки могут иметь расширенные настройки
  }
  buf |= USB_EP_KIND;
  USB_EPx(epnum) = buf;
  
  
  if( dir_in ){
    usb_epdata[epnum].usb_tx_addr = lastaddr;
    usb_epdata[epnum].usb_rx_addr = lastaddr + size;
    ENDP_DTOG_RX( epnum, 0*USB_EP_DTOG_RX );
    ENDP_DTOG_TX( epnum, 1*USB_EP_DTOG_TX );
    //ENDP_STAT_TX(epnum, USB_EP_TX_NAK);
    //ENDP_STAT_RX(epnum, USB_EP_RX_NAK);
  }else{
    usb_epdata[epnum].usb_rx_addr = lastaddr;
    usb_epdata[epnum].usb_tx_addr = lastaddr + size;
    if(size < 64){
      usb_epdata[epnum].rx_blocksize = usb_epdata[epnum].tx_blocksize = 0;
      usb_epdata[epnum].rx_num_blocks = usb_epdata[epnum].tx_num_blocks = size / 2;
    }else{
      usb_epdata[epnum].rx_blocksize = usb_epdata[epnum].tx_blocksize = 1;
      if(size < 32)size = 32;
      usb_epdata[epnum].rx_num_blocks = usb_epdata[epnum].tx_num_blocks = size / 32 - 1;
    }
    ENDP_STAT_RX(epnum, USB_EP_RX_VALID);
    ENDP_STAT_TX(epnum, USB_EP_TX_VALID);
  }
  epfunc_in[epnum] = func;
  epfunc_out[epnum]= func;
    
  lastaddr += 2*size;
}

typedef struct{
  volatile uint32_t addr;
  volatile union{
    uint32_t count; //SINGLE mode, TX count
    struct{         //DOUBLE mode, RX struct
      uint32_t rx_count:10;
      uint32_t rx_num_blocks:5;
      uint32_t rx_blocksize:1;
    };
  };
}pma_descr_t;

void _usb_ep_write(uint8_t idx, const uint8_t *buf, uint16_t size){
  pma_descr_t *descr = &((pma_descr_t*)usb_epdata)[idx];
  uint16_t N2 = (size + 1) >> 1;
  // the buffer is 16-bit, so we should copy data as it would be uint16_t
  uint16_t *buf16 = (uint16_t *)buf;
  uint32_t *out = (uint32_t*)((uint16_t *)(USB_PMAADDR + descr->addr*2));
  for(uint16_t i = 0; i < N2; ++i, ++out){
    *out = buf16[i];
  }
  descr->count = size;
  
  ENDP_STAT_TX((idx/2), USB_EP_TX_VALID);
}

usb_lib.h

#define ENDP_TOG(num, tog) do{USB_EPx(num) = ((USB_EPx(num) & ~(USB_EP_DTOG_RX | USB_EP_DTOG_TX | USB_EPRX_STAT | USB_EPTX_STAT)) | USB_EP_CTR_RX | USB_EP_CTR_TX) | tog; }while(0)

static inline void usb_ep_write(uint8_t epnum, const uint8_t *buf, uint16_t size){
  _usb_ep_write((epnum & 0x0F)*2, buf, size);
}
static inline void usb_ep_write_double(uint8_t epnum, const uint8_t *buf, uint16_t size){
  uint8_t idx = !(USB_EPx(epnum) & USB_EP_DTOG_TX);
  _usb_ep_write((epnum & 0x0F)*2 + idx, buf, size);
  ENDP_TOG( (epnum & 0x0F), USB_EP_DTOG_RX );
}

Это та версия, которая пока что кажется наиболее близкой к даташиту.

Вайршарк показывает что два пакета передаются, после чего никакой активности.

COKPOWEHEU ()
Ответ на: комментарий от kuzulis

И кстати, а что калокуб про это говорит?

А черт его знает. Я им пользоваться не умею, тем более настраивать экзотические режимы. Даже голые шаблоны почти никогда не запускаются без бубна.

Хотя тамошний код читал, вроде бы не сильно от моего отличается по сути.

COKPOWEHEU ()
Ответ на: комментарий от COKPOWEHEU

Глянь еще на сорцы вот этой либы, я тож на нее ориентировался.

Если вдруг поможет, то отпишись плз (я тогда тоже буду знать откуда брать ништяки). :)

kuzulis ★★ ()
Последнее исправление: kuzulis (всего исправлений: 1)
Ответ на: комментарий от kuzulis

Кажется, прогресс есть. Хотя пока и не понимаю в чем именно разница между моим кодом и тем, что по ссылке. Но по крайней мере пакеты успешно ходят, что не может не радовать!

Прикручу еще проверку отправленности буфера и надо будет оформлять код.

…Эх, вот только что было веселье с кодом и железом и уже наступает бумагомарательство как бы это веселье описать и не получить по рукам за говнокод…

COKPOWEHEU ()
Ответ на: комментарий от kuzulis

Будем считать, что разобрался. Вот результат: https://habr.com/ru/post/558822/ (на вас с вашей ссылкой тоже сослался)

Почему для двойной буферизации на ПЕРЕДАЧУ надо и проверять, и дергать DTOG_RX я так и не понял. Но это же ST…

COKPOWEHEU ()
Ответ на: комментарий от COKPOWEHEU

А вот с тестированием возникла небольшая проблема: я не нашел простого способа перенаправить сигнал с микрофона на stdin какой-нибудь программы.

parec, arecord. Для визуализации полно lv2 плагинов, которые отображают звук в каком угодно виде. Соединить плагины с микрофоном можно через Jack, alsa_in и jalv2. Некоторые плагины, навроде LSP или x42 идут с исполняемыми файлами и подключаются к Джеку без хоста.

https://hsto.org/webt/9j/ep/n3/9jepn3qwp2xxm3auzi84oxs73cq.jpeg

А нельзя ли соединить 2 сразу с 6, без промежуточной «фичи»?

anonymous ()
Ответ на: комментарий от kuzulis

192 кГц

Лучше сделай микрофон с охренительным семпл-рейтом под 192 кГц

Парни, я сделал. На Blue Pill (STM32F103). 1 канал, честных 15 бит (12 бит АЦП + ещё 3 бита — 8 оверсемплов), 192 килогерца. Вся прошивка занимает меньше 2 килобайт. Без библиотек, только CMSIS.

USB у STM оказался довольно простым. Всё заработало почти с первого раза, без осциллографов, отладчиков и вайршарков. С настройкой АЦП немного только попарился, чтобы из двух АЦП, 12 МГц и 14 тактов сделать 192k*8 оцифровок.

Если делать два канала, то будет только 96 ksps и 14 бит. Это реально. Максимальный семплрейт для 2-канального варианта — 124 ksps. Для одноканального, соответственно, в 2 раза больше. Естественно, с ещё большей потерей точности.

Исходники пока что не выкладываю, так как неизвестно надо ли оно вообще кому-то.

Запись тестового сигнала — меандр 96 кГц. Запись средней точки питания. Она же, но усиленная. На третьей картинке можно заметить просадку питания сразу после отправки пакета хосту — это единственный момент, когда процессор занят делом — он суммирует семплы для следующей отправки и копирует результат в PMA-буфер. (Записывал с бредборды с длиннющими неэкранированными проводами, включая сигнальные линии USB, которые гуляют по всей макетке.)

Аудио 1 секунды записи средней точки, .wav.

:)

anonymous ()
Ответ на: комментарий от COKPOWEHEU

Похоже, что на передачу хост иногда посылает несколько пакетов подряд, а вот на прием - нет. Может, если добавить точку синхронизации, лучше будет?

Хост посылает изохронные запросы не чаще одного раза за фрейм. Синхронизация, насколько мне известно, нужна только для того, чтобы узнать сколько данных нужно передать хосту на следующий запрос.

anonymous ()
Ответ на: 192 кГц от anonymous

USB у STM оказался довольно простым.

Это ты про наркоманские регистры, где часть битов - только set, часть - только reset, часть - вообще только toggle? Или про 16-битный буфер, выдающий себя за 32-битный? И т.д., и т.п.

Дал бы, что ли, ссылочку на свой код. А то не верится, что ты с USB в 2кБ уместился.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

не верится, что ты с USB в 2кБ уместился.

:) Спасибо. Приятно слышать от тебя такие слова. Я подумаю над предложением. Оформлю посимпатишнее.

наркоманские регистры

Я ту их часть, которая rw всегда пишу, а читаю регистры только когда надо состояние точки переключить. Мне норм.


    #define __CTR   (CTR_RX | CTR_TX)

    #define EPnR_SET_STAT_RX_RW(ep, x, rw)  EPnR[ep] = ((rw) | __CTR) | ((EPnR[ep] & STAT_RX) ^ ((x)<<12))
    #define EPnR_SET_STAT_TX_RW(ep, x, rw)  EPnR[ep] = ((rw) | __CTR) | ((EPnR[ep] & STAT_TX) ^ ((x)<<4))
    #define EPnR_TOGGLE_RW(ep, x, rw)       EPnR[ep] = ((rw) | __CTR) | (x)
    #define EPnR_CLEAR_CTR_RW(ep, x, rw)    EPnR[ep] = ((rw) | __CTR) ^ (x)

    ...

    const uint32_t
        ep_typ_adr[N_EPs] = { (EP_TYPE_CONTROL | EP0), (EP_TYPE_ISOCHRONOUS | EP1) };

    #define EPnR_SET_STAT_TX(ep, x)     EPnR_SET_STAT_TX_RW(ep, x, ep_typ_adr[ep])

    ...

    void ep0_tx_stall() {
        EPnR_SET_STAT_TX(EP0, STALL);
    }

anonymous ()
Ответ на: комментарий от Eddy_Em

Дал бы, что ли, ссылочку на свой код. А то не верится, что ты с USB в 2кБ уместился.

Отчего же. У меня меньше 3 кБ занимает если без таблицы синуса. При том, что дескриптор довольно развесистый и есть не оптимизированные места.

COKPOWEHEU ()
Ответ на: комментарий от Eddy_Em

Дал бы, что ли, ссылочку на свой код

Эдик, прости, но могу только в личку. Мой код - часть коммерческой тайны. Ты есть в одноклассниках или инсте?

Владимир

anonymous ()
Ответ на: комментарий от Eddy_Em

Дал бы, что ли, ссылочку на свой код.

https://github.com/anonymou8/stm32_usb_audio

Буду безмерно благодарен за лайк. Распечатаю и оформлю в рамочку :)

Может кому-то будет полезно: на моём гитхабе есть перевод раздела про USB из RM на русский: https://anonymou8.github.io/usb_ru/USB_ru.html

anonymous ()
Ответ на: комментарий от anonymous
wait_μs(6, F_CPU_MHz);

И что, это работает??? Я про букву μ

Ну и я не понял, зачем там было делать ассемблерные вставки, да и дескрипторы на асме расписывать...

Eddy_Em ☆☆☆☆☆ ()
Последнее исправление: Eddy_Em (всего исправлений: 1)