[Системное программирование, FPGA, Программирование микроконтроллеров, Компьютерное железо] Добавляем поддержку Vendor-команд к USB3.0 устройству на базе FX3
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В предыдущих статьях мы сделали достаточно интересную железку, состоящую из контроллера FX3 и ПЛИС Cyclone IV. Мы научились гонять через шину USB 3.0 потоки данных с достаточно высокой скоростью (я доказал, что поток 120 МБ/с из ULPI будет проходить через эту систему без искажений и потерь). Всё хорошо, но система, которая просто гонит данные, не имеет смысла. Любую систему надо настраивать. То есть, хочешь — не хочешь, а кроме скоростных данных надо слать не очень спешные команды.
У шины USB для передачи команд предназначена конечная точка EP0. Сегодня мы потренируемся дорабатывать «прошивку» FX3 так, чтобы она обрабатывала команды от PC, а также транслировала их через GPIO в сторону ПЛИС. Кстати, именно здесь проявляется преимущество контроллера над готовым мостом. Что меня в текущей реализации Redd сильно удручает – я не могу посылать никаких команд. Их можно только упаковать в основной поток. В случае же с контроллером – что хочу, то и делаю. Начинаем творить, что хотим…
Предыдущие статьи цикла:
- Начинаем опыты с интерфейсом USB 3.0 через контроллер семейства FX3 фирмы Cypress
- Дорабатываем прошивку USB 3.0, используя анализатор SignalTap, встроенный в среду разработки Quartus
- Учимся работать с USB-устройством и испытываем систему, сделанную на базе контроллера FX3
- Боремся с таймаутами при использовании USB 3.0 через контроллер FX3, возникающими при определенных условиях
Введение
Осматривая исходники типовой «прошивки», я нашёл знакомое имя функции в файле cyfxgpiftousb.c. Функцию зовут:
/* Callback to handle the USB setup requests. */
CyBool_t
CyFxApplnUSBSetupCB (
uint32_t setupdat0, /* SETUP Data 0 */
uint32_t setupdat1 /* SETUP Data 1 */
)
Имея за плечами опыт работы с кучкой USB-контроллеров, начиная от прямого предка нашего (это был FX2LP), через STM32 и далее со всеми остановками, я уже нутром чую, что нужная нам функциональность начинается здесь. Собственно, код этой функции как раз разбирает команды группы STANDARD Request. Осталось добавить туда свою группу VENDOR COMMANDS. Жаль только, что все команды, которые уже имеются в готовой функции, не передают данных. Они ограничиваются работой с полями wData и wIndex, Мне этого недостаточно. Я хочу передавать в ПЛИС байт и два 32-битных слова (команда, адрес, данные), либо передавать байт и DWORD, после чего – принимать DWORD (передали команду и адрес, приняли данные). То есть, без фазы данных точно не обойтись. Начинаем разбираться, где черпать вдохновение и добавлять желаемую функциональность.
Участок в зоне ответственности шины USB
Итак. Добавить фазу данных. Гуглю по слову:
CyU3PUsbAckSetup
И первая же ссылка ответила на все мои вопросы. На всякий случай вот она.
В том коде данные гоняют и туда, и обратно. Хорошо. Начнём с малого. Сначала вставляем только прогон данных через USB, без их передачи в ПЛИС. Будем для самоконтроля отправлять данные в UART, а при приёме, чтобы не тратить время на сложный вспомогательный код, просто будем заполнять память константами 00, 01 02 03…
Добавляем в конец функции CyFxApplnUSBSetupCB() такой блок:
if (bType == CY_U3P_USB_VENDOR_RQT)
{
// Cut size if need
if (wLength > sizeof(ep0_buffer))
{
wLength = sizeof (ep0_buffer);
}
// Need send data to PC
if (bReqType & 0x80)
{
int i;
for (i=0;i<wLength;i++)
{
ep0_buffer [i] = (uint8_t) i;
}
CyU3PUsbSendEP0Data (wLength, ep0_buffer);
isHandled = CyTrue;
} else
{
CyU3PUsbGetEP0Data (wLength, ep0_buffer, NULL);
ep0_buffer [wLength] = 0; // Null terminated String
CyU3PDebugPrint (4, (char*)ep0_buffer);
CyU3PUsbAckSetup();
isHandled = CyTrue;
}
}
«Волшебная константа» 0x80 – согласен, что некрасивая, но не нашлось ничего подходящего в заголовках в районе изучаемого участка, а дальше искать не хотелось. Но, наверное, все помнят, что именно старший бит задаёт направление. Мало того, я в терминологии USB вечно путаюсь, что значит IN, что значит OUT. Я просто запомнил, что, когда есть 0x80 – данные бегут в PC. Остальное, вроде, всё красиво и понятно получилось, даже не требует комментариев.
Чтобы не писать своей тестовой программы, проверять я сегодня буду в сниффере BusHound. Если в нём дважды щёлкнуть по устройству, то появляется очень полезный диалог. Вот тут щёлкаем:
И вот такую красоту получаем:
Я заполнил тип команды 0xC0 (Vendor Specific, данные из устройства в PC). Код команды я сделал равным 23 просто так, чисто во время экспериментов. Сейчас туда можно вписать всё, что угодно, в функции это поле не проверяется. Не проверяются и поля Value и Index. А вот когда я вбил поле Length, у меня внизу появился дамп. Всё готово к посылке команды. Нажимаем Run, получаем:
Всё верно. Функция CyFxApplnUSBSetupCB() посылает из FX3 в USB инкрементирующиеся байты, мы их видим. Теперь пробуем передавать. Подключаем UART (как это сделать – я рассказывал в одной из предыдущих статей), запускаем терминал. Меняем тип запроса на 0x40 (Vendos Specific Command, данные из PC в устройство). Заполняем поля данных ASCII символами:
Жмём Run – получаем:
Прекрасно! Эта часть готова! Переходим к работе с аппаратурой.
Работа с GPIO
Грустная теория
В том же примере, который я нашёл на github, идёт и работа с GPIO. Вот как красиво выглядит это в пользовательской части:
CyU3PGpioSetValue (FPGA_SOFT_RESET, !((ep0_buffer[0] & GPIO_FPGA_SOFT_RESET) > 0));
CyU3PGpioSetValue (FMC_POWER_GOOD_OUT, ((ep0_buffer[0] & GPIO_FMC_POWER_GOOD_OUT) > 0));
Красиво? Ну, конечно же, красиво! Но впору вспомнить, что я писал в одной из статей про нашу ОСРВ МАКС.
Я там рассказывал, что операторы new и delete по факту раскрываются в огромный кусок кода с непредсказуемым временем исполнения. Примерно так и тут. Функция CyU3PGpioSetValue() раскрывается в такую громаду, что я спрячу её под кат.
Смотреть текст функции CyU3PGpioSetValue().
SPL
CyU3PReturnStatus_t
CyU3PGpioSetValue (
uint8_t gpioId,
CyBool_t value)
{
uint32_t regVal;
uvint32_t *regPtr;
if (!glIsGpioActive)
{
return CY_U3P_ERROR_NOT_STARTED;
}
/* Check for parameter validity. */
if (!CyU3PIsGpioValid(gpioId))
{
return CY_U3P_ERROR_BAD_ARGUMENT;
}
if (CyU3PIsGpioSimpleIOConfigured(gpioId))
{
regPtr = &GPIO->lpp_gpio_simple[gpioId];
}
else if (CyU3PIsGpioComplexIOConfigured(gpioId))
{
regPtr = &GPIO->lpp_gpio_pin[gpioId % 8].status;
}
else
{
return CY_U3P_ERROR_NOT_CONFIGURED;
}
regVal = (*regPtr & ~CY_U3P_LPP_GPIO_INTR);
if (!(regVal & CY_U3P_LPP_GPIO_ENABLE))
{
return CY_U3P_ERROR_NOT_CONFIGURED;
}
if (value)
{
regVal |= CY_U3P_LPP_GPIO_OUT_VALUE;
}
else
{
regVal &= ~CY_U3P_LPP_GPIO_OUT_VALUE;
}
*regPtr = regVal;
regVal = *regPtr;
return CY_U3P_SUCCESS;
}
Какое будет максимальное быстродействие у кода, вызывающего эту функцию в цикле, мне страшно подумать. У неё есть более компактный аналог, но и его я предпочту спрятать под кат.
Более компактный аналог.
SPL
CyU3PReturnStatus_t
CyU3PGpioSimpleSetValue (
uint8_t gpioId,
CyBool_t value)
{
uint32_t regVal;
if (!glIsGpioActive)
{
return CY_U3P_ERROR_NOT_STARTED;
}
/* Check for parameter validity. */
if (!CyU3PIsGpioValid(gpioId))
{
return CY_U3P_ERROR_BAD_ARGUMENT;
}
regVal = (GPIO->lpp_gpio_simple[gpioId] &
~(CY_U3P_LPP_GPIO_INTR | CY_U3P_LPP_GPIO_OUT_VALUE));
if (value)
{
regVal |= CY_U3P_LPP_GPIO_OUT_VALUE;
}
GPIO->lpp_gpio_simple[gpioId] = regVal;
return CY_U3P_SUCCESS;
}
Так что придётся написать что-то своё на скорую руку, выкинув лишние проверки. Эта функция обслуживает вызовы не от безвестных пользователей, которые в теории могут учудить всё, что угодно, а от меня. Про некоторых пользователей я наслышан от коллеги, разбирающего запросы поддержки одной библиотеки. Но я уж точно настроил порты при старте, зачем при каждом обращении к порту это проверять, тратя такты процессора?
Чуть более оптимистичная теория
Чтобы не хранить маску записанных в порт данных, а также обеспечить себе максимальную потокобезопасность, мы можем воспользоваться аппаратурой, дающей независимый доступ к каждому биту порта. Вдохновение мы будем искать в разделе 9.2 GPIO Register Interface документа FX3_Programmers_Manual.pdf.
Вот так выглядит блок GPIO:
Мы видим, что кроме классического двоичного представления, есть такое, где каждой линии (а их в контроллере 61 штука) соответствует собственное 32-разрядное слово. Формат его такой:
Собственно, всё ясно. Так как я собираюсь работать с конкретными линиями GPIO, я вполне могу обращаться к битам IN_VALUE и OUT_VALUE в этих регистрах. Больше мне ничего и не надо. Ну, и настройку направления можно произвести здесь же.
С какими линиями мы работаем
Хорошо. Как нам достукиваться до линий, понятно. А как они адресуются? Что за 61 линия GPIO, о которых говорится в документации? С чем предстоит работать мне? Плату для меня разводил знакомый, которому я поставил очень простую задачу: несколько свободных линий от FX3 завести на ПЛИС. Так как конкретные номера не были мною обозначены, он взял те, которые захотел. Вот участок ПЛИС, к которому подходят линии GPIO, именованные в той нотации, какая задана на шелкографии около разъёма макетки:
Я собираюсь программно реализовать шину SPI, значит, мне надо 4 линии (выбор кристалла, тактовый сигнал и данные туда-обратно). Возьмём линии от DQ24 до DQ27 по принципу «А почему бы и нет?». В одной из прошлых статей, я уже показывал таблицу, при помощи которой мы можем быстро сопоставить эти имена с реальными линиями GPIO. Смотрим в неё:
Значит, нас интересуют линии GPIO 41, 42, 43 и 44. Вот с ними я и буду работать.
Инициализация GPIO
Все, кто хорошо знаком с архитектурой ARM, знают, что любые порты надо инициализировать. Как это сделать в нашем случае? Мы работаем с демонстрационным приложением, так что часть работы уже сделана за нас. Доработаем кое-что из готового кода. В функции main(), есть такой участок:
io_cfg.isDQ32Bit = CyTrue;
io_cfg.useUart = CyTrue;
io_cfg.useI2C = CyFalse;
io_cfg.useI2S = CyFalse;
io_cfg.useSpi = CyFalse;
io_cfg.lppMode = CY_U3P_IO_MATRIX_LPP_DEFAULT;
/* No GPIOs are enabled. */
io_cfg.gpioSimpleEn[0] = 0;
io_cfg.gpioSimpleEn[1] = 0;
io_cfg.gpioComplexEn[0] = 0;
io_cfg.gpioComplexEn[1] = 0;
status = CyU3PDeviceConfigureIOMatrix (&io_cfg);
Поправим его так:
То же самое текстом.
SPL
io_cfg.isDQ32Bit = CyFalse;
io_cfg.useUart = CyTrue;
io_cfg.useI2C = CyFalse;
io_cfg.useI2S = CyFalse;
io_cfg.useSpi = CyFalse;
io_cfg.lppMode = CY_U3P_IO_MATRIX_LPP_UART_ONLY;
/* No GPIOs are enabled. */
io_cfg.gpioSimpleEn[0] = 0;
io_cfg.gpioSimpleEn[1] = (1<<9)|(1<<10)|(1<<11)|(1<<12);
io_cfg.gpioComplexEn[0] = 0;
io_cfg.gpioComplexEn[1] = 0;
status = CyU3PDeviceConfigureIOMatrix (&io_cfg);
Биты 9, 10, 11 и 12 в коде – это биты старшего слова. Поэтому физически они соответствуют битам GPIO 9+32=41, 10+32=42, 11+32=43 и 12+32=44. Тем самым, с которыми я собираюсь работать.
Зададим ещё им направления. Скажем, я раскидаю их так:
Бит
Цепь
Направление
41
SS
OUT
42
CLK
OUT
43
MOSI
OUT
44
MOSI
IN
Объявим для этого следующие макросы:
#define MY_BIT_SS 41
#define MY_BIT_CLK 42
#define MY_BIT_MOSI 43
#define MY_BIT_MISO 44
А в функцию CyFxApplnInit() добавим такой код:
CyU3PGpioClock_t gpioClock;
gpioClock.fastClkDiv = 2;
gpioClock.slowClkDiv = 16;
gpioClock.simpleDiv = CY_U3P_GPIO_SIMPLE_DIV_BY_2;
gpioClock.clkSrc = CY_U3P_SYS_CLK;
gpioClock.halfDiv = 0;
apiRetStatus = CyU3PGpioInit (&gpioClock, NULL);
if (apiRetStatus != CY_U3P_SUCCESS)
{
CyU3PDebugPrint (4, "GPIO Init failed, error code = %d\r\n", apiRetStatus);
CyFxAppErrorHandler (apiRetStatus);
}
GPIO->lpp_gpio_simple[MY_BIT_SS] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;
GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;
GPIO->lpp_gpio_simple[MY_BIT_MOSI] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;
GPIO->lpp_gpio_simple[MY_BIT_MISO] = CY_U3P_LPP_GPIO_INPUT_EN | CY_U3P_LPP_GPIO_ENABLE;
Всё, блок GPIO инициализирован, направления заданы. А линия SS ещё и взведена в единицу. Можно начинать пользоваться GPIO для реализации функциональности.
Участок в зоне ответственности аппаратуры
Запись в SPI я сделаю в виде макросов «взвести в 1» и «Сбросить в 0» (увы, именно макросов, перед нами же код на чистых Сях, в плюсах я бы сделал на шаблонных функциях) и одной функции, которая обращается к ним. Получилось так:
#define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] |= CY_U3P_LPP_GPIO_OUT_VALUE
#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] &= ~CY_U3P_LPP_GPIO_OUT_VALUE
void SPI_Write (unsigned int data, int nBits)
{
while (nBits)
{
if (data&1)
{
SET_IO_BIT (MY_BIT_MOSI);
} else
{
CLR_IO_BIT (MY_BIT_MOSI);
}
SET_IO_BIT (MY_BIT_CLK);
data >>= 1;
nBits -= 1;
CLR_IO_BIT (MY_BIT_CLK);
}
}
Соответственно, вместо вывода в UART в ранее написанном обработчике USB-команд, я сделаю вывод в SPI, но по очень хитрому алгоритму. Сначала – байт USB-команды. Затем – слова wData и wIndex, и потом – DWORD, пришедший в фазе данных. При такой солянке сборной, удобнее всё передавать младшим битом вперёд (именно так работает функция SPI_Write()).
Чтение я пока делать не буду. Сейчас проверяется сама идея. Чтобы проверить чтение, надо делать «прошивку» и для ПЛИС, а запись я могу проконтролировать и при помощи осциллографа.
В результате, код обработчика Vendor-команды трансформируется следующим образом:
// Need send data to PC
if (bReqType & 0x80)
{
int i;
for (i=0;i<wLength;i++)
{
ep0_buffer [i] = (uint8_t) i;
}
CyU3PUsbSendEP0Data (wLength, (uint8_t*)ep0_buffer);
isHandled = CyTrue;
} else
{
CyU3PUsbGetEP0Data (wLength, (uint8_t*)ep0_buffer, NULL);
ep0_buffer [wLength] = 0; // Null terminated String
CyU3PDebugPrint (4, (char*)ep0_buffer);
CLR_IO_BIT(MY_BIT_SS);
SPI_Write(bRequest,8);
SPI_Write(wValue,16);
SPI_Write(wIndex,16);
SPI_Write(ep0_buffer[0],32);
SET_IO_BIT(MY_BIT_SS);
CyU3PUsbAckSetup();
isHandled = CyTrue;
}
Итого
Итого, даём такой запрос:
И получаем такой результат:
Немного оптимизации
Видно, что данные передаются младшим битом вперёд, хорошо видны байт 0x23 и начало байта 0x55. Всё верно. Правда, частота, конечно, не ахти (её можно разглядеть, если кликнуть по рисунку и посмотреть его в увеличенном виде). Примерно 1.2 мегагерца. В целом, меня сейчас это сильно не беспокоит, но здесь скорее важен сам принцип. Не люблю, когда всё совсем медленно, и всё тут! Смотрим, во что превратилась функция записи, в этом нам поможет файл GpifToUsb.lst:
40003404 <SPI_Write>:
40003404: ea00000d b 40003440 <SPI_Write+0x3c>
40003408: e59f303c ldr r3, [pc, #60] ; 4000344c <SPI_Write+0x48>
4000340c: e3100001 tst r0, #1
40003410: e59321ac ldr r2, [r3, #428] ; 0x1ac
40003414: e1a000a0 lsr r0, r0, #1
40003418: 13822001 orrne r2, r2, #1
4000341c: 03c22001 biceq r2, r2, #1
40003420: e58321ac str r2, [r3, #428] ; 0x1ac
40003424: e59321a8 ldr r2, [r3, #424] ; 0x1a8
40003428: e2411001 sub r1, r1, #1
4000342c: e3822001 orr r2, r2, #1
40003430: e58321a8 str r2, [r3, #424] ; 0x1a8
40003434: e59321a8 ldr r2, [r3, #424] ; 0x1a8
40003438: e3c22001 bic r2, r2, #1
4000343c: e58321a8 str r2, [r3, #424] ; 0x1a8
40003440: e3510000 cmp r1, #0
40003444: 1affffef bne 40003408 <SPI_Write+0x4>
40003448: e12fff1e bx lr
4000344c: e0001000 .word 0xe0001000
16 строк. Вполне компактно… Я уже много раз писал, что не собираюсь становиться гуру FX3. Поэтому решил не вчитываться в километры документов, а поиграть с кодом на практике. Само собой, несколько часов опытов я опущу, и приведу только итоговый результат. Так что немножко младшим учеником старшего помощника второго заместителя гуру побыть пришлось… Но так или иначе. Я изучил вопрос настройки тактирования GPIO и пришёл к выводу, что оно вполне оптимальное.
Но напишем такой тестовый блок кода (первый макрос роняет значение в порту, второй – взводит, а дальше идёт чреда взлётов и падений):
#define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE
#define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
Ему соответствует участок ассемблерного кода, оптимизировать который в целом, невозможно. Он идеален:
400036c4: e58421a8 str r2, [r4, #424] ; 0x1a8
400036c8: e58431a8 str r3, [r4, #424] ; 0x1a8
400036cc: e58421a8 str r2, [r4, #424] ; 0x1a8
400036d0: e58431a8 str r3, [r4, #424] ; 0x1a8
400036d4: e58421a8 str r2, [r4, #424] ; 0x1a8
400036d8: e58431a8 str r3, [r4, #424] ; 0x1a8
Результат прогона (получаем меандр с частотой 12.5 МГц):
А теперь заменим запись констант с прямой записи на чтение — модификацию — запись, как это реализовано в моих макросах для SPI:
#define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] |= CY_U3P_LPP_GPIO_OUT_VALUE
#define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] &= ~CY_U3P_LPP_GPIO_OUT_VALUE
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
UP;
DOWN;
В ассемблерном коде покажу только одну итерацию вверх-вниз
400036e4: e59431a8 ldr r3, [r4, #424]; 0x1a8
400036e8: e3c33001 bic r3, r3, #1
400036ec: e58431a8 str r3, [r4, #424]; 0x1a8
400036f0: e59431a8 ldr r3, [r4, #424]; 0x1a8
400036f4: e3833001 orr r3, r3, #1
400036f8: e58431a8 str r3, [r4, #424]; 0x1a8
Вместо пары строк получаем шесть. Частота упадёт втрое? Делаем прогон…
12.5/1.9=6.6
Более, чем в шесть раз частота упала! Получается, что чтение из порта – довольно медленная операция. Значит, чуть переписываем мои макросы записи в порт, убирая из них операции чтения:
#define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE
#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE
Делаем прогон записи в SPI…
4 мегагерца. Ну вот. Не особо напрягаясь, разогнали систему почти вчетверо. Меня не покидает ощущение, что всё можно разогнать ещё сильнее, но оставим это на потом. Сейчас особо это не требуется.
Заключение
Мы освоили механизм добавления VENDOR команд в USB-устройство на базе FX3. При этом мы испытали работу с командами, передающими данные через конечную точку EP0 в обоих направлениях. Также мы освоили работу с GPIO у этого контроллера. Теперь, кроме скоростной передачи через конечные точки типа BULK и GPIF, мы можем передавать команды в свою «прошивку» ПЛИС.
А для чего я хочу это применять, будет рассказано в следующей статье.
===========
Источник:
habr.com
===========
Похожие новости:
- [Информационная безопасность, Open source, Системное администрирование, Системное программирование] Защита ядра Linux из ARM Trustzone: как усилить Linux Kernel Runtime Guard и предотвращать последствия zero-day
- [Компьютерное железо, Игры и игровые приставки, IT-компании] Microsoft прогнозирует дефицит Xbox Series X до июня
- [Open source, Гаджеты, Компьютерное железо, Видеокарты] Свобода для видео: представлен открытый GPU на базе RISC-V
- [Программирование, Алгоритмы, Программирование микроконтроллеров, Бизнес-модели, Визуальное программирование] Умеет ли человечество писать алгоритмы? Безошибочные алгоритмы и язык ДРАКОН. Сенатор: они находились в летающих гробах
- [Системное администрирование, Системное программирование, DevOps] Упаковка любого python пакета в rpm пакет с возможностью offline установки
- [C++, Программирование микроконтроллеров] Попытка использовать современный C++ и паттерны проектирования для программирования микроконтроллеров
- [Финансы в IT] Reddit-ралли спасло от долгов кинотеатры AMC
- [Компьютерное железо] Доступные «мониторы» для работы со звуком и мультимедиа — делимся обзорами настоящих «студийников»
- [Серверная оптимизация, Компьютерное железо, Процессоры, Суперкомпьютеры] Самый большой процессор в мире — Cerebras CS-1. Разбор
- [Социальные сети и сообщества, Финансы в IT] Как «Ревущий Котёнок» с Reddit заработал 28.500% на акциях GameStop: объясняю простым языком
Теги для поиска: #_sistemnoe_programmirovanie (Системное программирование), #_fpga, #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_kompjuternoe_zhelezo (Компьютерное железо), #_kontroller_fx3 (контроллер FX3), #_fpga, #_plis (ПЛИС), #_plis_cyclone_iv (ПЛИС Cyclone IV), #_redd, #_usbanalizator (usb-анализатор), #_gpio, #_sistemnoe_programmirovanie (
Системное программирование
), #_fpga, #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
), #_kompjuternoe_zhelezo (
Компьютерное железо
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:14
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В предыдущих статьях мы сделали достаточно интересную железку, состоящую из контроллера FX3 и ПЛИС Cyclone IV. Мы научились гонять через шину USB 3.0 потоки данных с достаточно высокой скоростью (я доказал, что поток 120 МБ/с из ULPI будет проходить через эту систему без искажений и потерь). Всё хорошо, но система, которая просто гонит данные, не имеет смысла. Любую систему надо настраивать. То есть, хочешь — не хочешь, а кроме скоростных данных надо слать не очень спешные команды. У шины USB для передачи команд предназначена конечная точка EP0. Сегодня мы потренируемся дорабатывать «прошивку» FX3 так, чтобы она обрабатывала команды от PC, а также транслировала их через GPIO в сторону ПЛИС. Кстати, именно здесь проявляется преимущество контроллера над готовым мостом. Что меня в текущей реализации Redd сильно удручает – я не могу посылать никаких команд. Их можно только упаковать в основной поток. В случае же с контроллером – что хочу, то и делаю. Начинаем творить, что хотим… Предыдущие статьи цикла:
Введение Осматривая исходники типовой «прошивки», я нашёл знакомое имя функции в файле cyfxgpiftousb.c. Функцию зовут: /* Callback to handle the USB setup requests. */
CyBool_t CyFxApplnUSBSetupCB ( uint32_t setupdat0, /* SETUP Data 0 */ uint32_t setupdat1 /* SETUP Data 1 */ ) Имея за плечами опыт работы с кучкой USB-контроллеров, начиная от прямого предка нашего (это был FX2LP), через STM32 и далее со всеми остановками, я уже нутром чую, что нужная нам функциональность начинается здесь. Собственно, код этой функции как раз разбирает команды группы STANDARD Request. Осталось добавить туда свою группу VENDOR COMMANDS. Жаль только, что все команды, которые уже имеются в готовой функции, не передают данных. Они ограничиваются работой с полями wData и wIndex, Мне этого недостаточно. Я хочу передавать в ПЛИС байт и два 32-битных слова (команда, адрес, данные), либо передавать байт и DWORD, после чего – принимать DWORD (передали команду и адрес, приняли данные). То есть, без фазы данных точно не обойтись. Начинаем разбираться, где черпать вдохновение и добавлять желаемую функциональность. Участок в зоне ответственности шины USB Итак. Добавить фазу данных. Гуглю по слову: CyU3PUsbAckSetup И первая же ссылка ответила на все мои вопросы. На всякий случай вот она. В том коде данные гоняют и туда, и обратно. Хорошо. Начнём с малого. Сначала вставляем только прогон данных через USB, без их передачи в ПЛИС. Будем для самоконтроля отправлять данные в UART, а при приёме, чтобы не тратить время на сложный вспомогательный код, просто будем заполнять память константами 00, 01 02 03… Добавляем в конец функции CyFxApplnUSBSetupCB() такой блок: if (bType == CY_U3P_USB_VENDOR_RQT)
{ // Cut size if need if (wLength > sizeof(ep0_buffer)) { wLength = sizeof (ep0_buffer); } // Need send data to PC if (bReqType & 0x80) { int i; for (i=0;i<wLength;i++) { ep0_buffer [i] = (uint8_t) i; } CyU3PUsbSendEP0Data (wLength, ep0_buffer); isHandled = CyTrue; } else { CyU3PUsbGetEP0Data (wLength, ep0_buffer, NULL); ep0_buffer [wLength] = 0; // Null terminated String CyU3PDebugPrint (4, (char*)ep0_buffer); CyU3PUsbAckSetup(); isHandled = CyTrue; } } «Волшебная константа» 0x80 – согласен, что некрасивая, но не нашлось ничего подходящего в заголовках в районе изучаемого участка, а дальше искать не хотелось. Но, наверное, все помнят, что именно старший бит задаёт направление. Мало того, я в терминологии USB вечно путаюсь, что значит IN, что значит OUT. Я просто запомнил, что, когда есть 0x80 – данные бегут в PC. Остальное, вроде, всё красиво и понятно получилось, даже не требует комментариев. Чтобы не писать своей тестовой программы, проверять я сегодня буду в сниффере BusHound. Если в нём дважды щёлкнуть по устройству, то появляется очень полезный диалог. Вот тут щёлкаем: И вот такую красоту получаем: Я заполнил тип команды 0xC0 (Vendor Specific, данные из устройства в PC). Код команды я сделал равным 23 просто так, чисто во время экспериментов. Сейчас туда можно вписать всё, что угодно, в функции это поле не проверяется. Не проверяются и поля Value и Index. А вот когда я вбил поле Length, у меня внизу появился дамп. Всё готово к посылке команды. Нажимаем Run, получаем: Всё верно. Функция CyFxApplnUSBSetupCB() посылает из FX3 в USB инкрементирующиеся байты, мы их видим. Теперь пробуем передавать. Подключаем UART (как это сделать – я рассказывал в одной из предыдущих статей), запускаем терминал. Меняем тип запроса на 0x40 (Vendos Specific Command, данные из PC в устройство). Заполняем поля данных ASCII символами: Жмём Run – получаем: Прекрасно! Эта часть готова! Переходим к работе с аппаратурой. Работа с GPIO Грустная теория В том же примере, который я нашёл на github, идёт и работа с GPIO. Вот как красиво выглядит это в пользовательской части: CyU3PGpioSetValue (FPGA_SOFT_RESET, !((ep0_buffer[0] & GPIO_FPGA_SOFT_RESET) > 0));
CyU3PGpioSetValue (FMC_POWER_GOOD_OUT, ((ep0_buffer[0] & GPIO_FMC_POWER_GOOD_OUT) > 0)); Красиво? Ну, конечно же, красиво! Но впору вспомнить, что я писал в одной из статей про нашу ОСРВ МАКС. Я там рассказывал, что операторы new и delete по факту раскрываются в огромный кусок кода с непредсказуемым временем исполнения. Примерно так и тут. Функция CyU3PGpioSetValue() раскрывается в такую громаду, что я спрячу её под кат. Смотреть текст функции CyU3PGpioSetValue().SPLCyU3PReturnStatus_t
CyU3PGpioSetValue ( uint8_t gpioId, CyBool_t value) { uint32_t regVal; uvint32_t *regPtr; if (!glIsGpioActive) { return CY_U3P_ERROR_NOT_STARTED; } /* Check for parameter validity. */ if (!CyU3PIsGpioValid(gpioId)) { return CY_U3P_ERROR_BAD_ARGUMENT; } if (CyU3PIsGpioSimpleIOConfigured(gpioId)) { regPtr = &GPIO->lpp_gpio_simple[gpioId]; } else if (CyU3PIsGpioComplexIOConfigured(gpioId)) { regPtr = &GPIO->lpp_gpio_pin[gpioId % 8].status; } else { return CY_U3P_ERROR_NOT_CONFIGURED; } regVal = (*regPtr & ~CY_U3P_LPP_GPIO_INTR); if (!(regVal & CY_U3P_LPP_GPIO_ENABLE)) { return CY_U3P_ERROR_NOT_CONFIGURED; } if (value) { regVal |= CY_U3P_LPP_GPIO_OUT_VALUE; } else { regVal &= ~CY_U3P_LPP_GPIO_OUT_VALUE; } *regPtr = regVal; regVal = *regPtr; return CY_U3P_SUCCESS; } Какое будет максимальное быстродействие у кода, вызывающего эту функцию в цикле, мне страшно подумать. У неё есть более компактный аналог, но и его я предпочту спрятать под кат. Более компактный аналог.SPLCyU3PReturnStatus_t
CyU3PGpioSimpleSetValue ( uint8_t gpioId, CyBool_t value) { uint32_t regVal; if (!glIsGpioActive) { return CY_U3P_ERROR_NOT_STARTED; } /* Check for parameter validity. */ if (!CyU3PIsGpioValid(gpioId)) { return CY_U3P_ERROR_BAD_ARGUMENT; } regVal = (GPIO->lpp_gpio_simple[gpioId] & ~(CY_U3P_LPP_GPIO_INTR | CY_U3P_LPP_GPIO_OUT_VALUE)); if (value) { regVal |= CY_U3P_LPP_GPIO_OUT_VALUE; } GPIO->lpp_gpio_simple[gpioId] = regVal; return CY_U3P_SUCCESS; } Так что придётся написать что-то своё на скорую руку, выкинув лишние проверки. Эта функция обслуживает вызовы не от безвестных пользователей, которые в теории могут учудить всё, что угодно, а от меня. Про некоторых пользователей я наслышан от коллеги, разбирающего запросы поддержки одной библиотеки. Но я уж точно настроил порты при старте, зачем при каждом обращении к порту это проверять, тратя такты процессора? Чуть более оптимистичная теория Чтобы не хранить маску записанных в порт данных, а также обеспечить себе максимальную потокобезопасность, мы можем воспользоваться аппаратурой, дающей независимый доступ к каждому биту порта. Вдохновение мы будем искать в разделе 9.2 GPIO Register Interface документа FX3_Programmers_Manual.pdf. Вот так выглядит блок GPIO: Мы видим, что кроме классического двоичного представления, есть такое, где каждой линии (а их в контроллере 61 штука) соответствует собственное 32-разрядное слово. Формат его такой: Собственно, всё ясно. Так как я собираюсь работать с конкретными линиями GPIO, я вполне могу обращаться к битам IN_VALUE и OUT_VALUE в этих регистрах. Больше мне ничего и не надо. Ну, и настройку направления можно произвести здесь же. С какими линиями мы работаем Хорошо. Как нам достукиваться до линий, понятно. А как они адресуются? Что за 61 линия GPIO, о которых говорится в документации? С чем предстоит работать мне? Плату для меня разводил знакомый, которому я поставил очень простую задачу: несколько свободных линий от FX3 завести на ПЛИС. Так как конкретные номера не были мною обозначены, он взял те, которые захотел. Вот участок ПЛИС, к которому подходят линии GPIO, именованные в той нотации, какая задана на шелкографии около разъёма макетки: Я собираюсь программно реализовать шину SPI, значит, мне надо 4 линии (выбор кристалла, тактовый сигнал и данные туда-обратно). Возьмём линии от DQ24 до DQ27 по принципу «А почему бы и нет?». В одной из прошлых статей, я уже показывал таблицу, при помощи которой мы можем быстро сопоставить эти имена с реальными линиями GPIO. Смотрим в неё: Значит, нас интересуют линии GPIO 41, 42, 43 и 44. Вот с ними я и буду работать. Инициализация GPIO Все, кто хорошо знаком с архитектурой ARM, знают, что любые порты надо инициализировать. Как это сделать в нашем случае? Мы работаем с демонстрационным приложением, так что часть работы уже сделана за нас. Доработаем кое-что из готового кода. В функции main(), есть такой участок: io_cfg.isDQ32Bit = CyTrue; io_cfg.useUart = CyTrue; io_cfg.useI2C = CyFalse; io_cfg.useI2S = CyFalse; io_cfg.useSpi = CyFalse; io_cfg.lppMode = CY_U3P_IO_MATRIX_LPP_DEFAULT; /* No GPIOs are enabled. */ io_cfg.gpioSimpleEn[0] = 0; io_cfg.gpioSimpleEn[1] = 0; io_cfg.gpioComplexEn[0] = 0; io_cfg.gpioComplexEn[1] = 0; status = CyU3PDeviceConfigureIOMatrix (&io_cfg); Поправим его так: То же самое текстом.SPLio_cfg.isDQ32Bit = CyFalse;
io_cfg.useUart = CyTrue; io_cfg.useI2C = CyFalse; io_cfg.useI2S = CyFalse; io_cfg.useSpi = CyFalse; io_cfg.lppMode = CY_U3P_IO_MATRIX_LPP_UART_ONLY; /* No GPIOs are enabled. */ io_cfg.gpioSimpleEn[0] = 0; io_cfg.gpioSimpleEn[1] = (1<<9)|(1<<10)|(1<<11)|(1<<12); io_cfg.gpioComplexEn[0] = 0; io_cfg.gpioComplexEn[1] = 0; status = CyU3PDeviceConfigureIOMatrix (&io_cfg); Биты 9, 10, 11 и 12 в коде – это биты старшего слова. Поэтому физически они соответствуют битам GPIO 9+32=41, 10+32=42, 11+32=43 и 12+32=44. Тем самым, с которыми я собираюсь работать. Зададим ещё им направления. Скажем, я раскидаю их так: Бит Цепь Направление 41 SS OUT 42 CLK OUT 43 MOSI OUT 44 MOSI IN Объявим для этого следующие макросы: #define MY_BIT_SS 41
#define MY_BIT_CLK 42 #define MY_BIT_MOSI 43 #define MY_BIT_MISO 44 А в функцию CyFxApplnInit() добавим такой код: CyU3PGpioClock_t gpioClock;
gpioClock.fastClkDiv = 2; gpioClock.slowClkDiv = 16; gpioClock.simpleDiv = CY_U3P_GPIO_SIMPLE_DIV_BY_2; gpioClock.clkSrc = CY_U3P_SYS_CLK; gpioClock.halfDiv = 0; apiRetStatus = CyU3PGpioInit (&gpioClock, NULL); if (apiRetStatus != CY_U3P_SUCCESS) { CyU3PDebugPrint (4, "GPIO Init failed, error code = %d\r\n", apiRetStatus); CyFxAppErrorHandler (apiRetStatus); } GPIO->lpp_gpio_simple[MY_BIT_SS] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE; GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE; GPIO->lpp_gpio_simple[MY_BIT_MOSI] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE; GPIO->lpp_gpio_simple[MY_BIT_MISO] = CY_U3P_LPP_GPIO_INPUT_EN | CY_U3P_LPP_GPIO_ENABLE; Всё, блок GPIO инициализирован, направления заданы. А линия SS ещё и взведена в единицу. Можно начинать пользоваться GPIO для реализации функциональности. Участок в зоне ответственности аппаратуры Запись в SPI я сделаю в виде макросов «взвести в 1» и «Сбросить в 0» (увы, именно макросов, перед нами же код на чистых Сях, в плюсах я бы сделал на шаблонных функциях) и одной функции, которая обращается к ним. Получилось так: #define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] |= CY_U3P_LPP_GPIO_OUT_VALUE
#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] &= ~CY_U3P_LPP_GPIO_OUT_VALUE void SPI_Write (unsigned int data, int nBits) { while (nBits) { if (data&1) { SET_IO_BIT (MY_BIT_MOSI); } else { CLR_IO_BIT (MY_BIT_MOSI); } SET_IO_BIT (MY_BIT_CLK); data >>= 1; nBits -= 1; CLR_IO_BIT (MY_BIT_CLK); } } Соответственно, вместо вывода в UART в ранее написанном обработчике USB-команд, я сделаю вывод в SPI, но по очень хитрому алгоритму. Сначала – байт USB-команды. Затем – слова wData и wIndex, и потом – DWORD, пришедший в фазе данных. При такой солянке сборной, удобнее всё передавать младшим битом вперёд (именно так работает функция SPI_Write()). Чтение я пока делать не буду. Сейчас проверяется сама идея. Чтобы проверить чтение, надо делать «прошивку» и для ПЛИС, а запись я могу проконтролировать и при помощи осциллографа. В результате, код обработчика Vendor-команды трансформируется следующим образом: // Need send data to PC
if (bReqType & 0x80) { int i; for (i=0;i<wLength;i++) { ep0_buffer [i] = (uint8_t) i; } CyU3PUsbSendEP0Data (wLength, (uint8_t*)ep0_buffer); isHandled = CyTrue; } else { CyU3PUsbGetEP0Data (wLength, (uint8_t*)ep0_buffer, NULL); ep0_buffer [wLength] = 0; // Null terminated String CyU3PDebugPrint (4, (char*)ep0_buffer); CLR_IO_BIT(MY_BIT_SS); SPI_Write(bRequest,8); SPI_Write(wValue,16); SPI_Write(wIndex,16); SPI_Write(ep0_buffer[0],32); SET_IO_BIT(MY_BIT_SS); CyU3PUsbAckSetup(); isHandled = CyTrue; } Итого Итого, даём такой запрос: И получаем такой результат: Немного оптимизации Видно, что данные передаются младшим битом вперёд, хорошо видны байт 0x23 и начало байта 0x55. Всё верно. Правда, частота, конечно, не ахти (её можно разглядеть, если кликнуть по рисунку и посмотреть его в увеличенном виде). Примерно 1.2 мегагерца. В целом, меня сейчас это сильно не беспокоит, но здесь скорее важен сам принцип. Не люблю, когда всё совсем медленно, и всё тут! Смотрим, во что превратилась функция записи, в этом нам поможет файл GpifToUsb.lst: 40003404 <SPI_Write>:
40003404: ea00000d b 40003440 <SPI_Write+0x3c> 40003408: e59f303c ldr r3, [pc, #60] ; 4000344c <SPI_Write+0x48> 4000340c: e3100001 tst r0, #1 40003410: e59321ac ldr r2, [r3, #428] ; 0x1ac 40003414: e1a000a0 lsr r0, r0, #1 40003418: 13822001 orrne r2, r2, #1 4000341c: 03c22001 biceq r2, r2, #1 40003420: e58321ac str r2, [r3, #428] ; 0x1ac 40003424: e59321a8 ldr r2, [r3, #424] ; 0x1a8 40003428: e2411001 sub r1, r1, #1 4000342c: e3822001 orr r2, r2, #1 40003430: e58321a8 str r2, [r3, #424] ; 0x1a8 40003434: e59321a8 ldr r2, [r3, #424] ; 0x1a8 40003438: e3c22001 bic r2, r2, #1 4000343c: e58321a8 str r2, [r3, #424] ; 0x1a8 40003440: e3510000 cmp r1, #0 40003444: 1affffef bne 40003408 <SPI_Write+0x4> 40003448: e12fff1e bx lr 4000344c: e0001000 .word 0xe0001000 16 строк. Вполне компактно… Я уже много раз писал, что не собираюсь становиться гуру FX3. Поэтому решил не вчитываться в километры документов, а поиграть с кодом на практике. Само собой, несколько часов опытов я опущу, и приведу только итоговый результат. Так что немножко младшим учеником старшего помощника второго заместителя гуру побыть пришлось… Но так или иначе. Я изучил вопрос настройки тактирования GPIO и пришёл к выводу, что оно вполне оптимальное. Но напишем такой тестовый блок кода (первый макрос роняет значение в порту, второй – взводит, а дальше идёт чреда взлётов и падений): #define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE
#define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; Ему соответствует участок ассемблерного кода, оптимизировать который в целом, невозможно. Он идеален: 400036c4: e58421a8 str r2, [r4, #424] ; 0x1a8
400036c8: e58431a8 str r3, [r4, #424] ; 0x1a8 400036cc: e58421a8 str r2, [r4, #424] ; 0x1a8 400036d0: e58431a8 str r3, [r4, #424] ; 0x1a8 400036d4: e58421a8 str r2, [r4, #424] ; 0x1a8 400036d8: e58431a8 str r3, [r4, #424] ; 0x1a8 Результат прогона (получаем меандр с частотой 12.5 МГц): А теперь заменим запись констант с прямой записи на чтение — модификацию — запись, как это реализовано в моих макросах для SPI: #define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] |= CY_U3P_LPP_GPIO_OUT_VALUE
#define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] &= ~CY_U3P_LPP_GPIO_OUT_VALUE UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; UP; DOWN; В ассемблерном коде покажу только одну итерацию вверх-вниз 400036e4: e59431a8 ldr r3, [r4, #424]; 0x1a8 400036e8: e3c33001 bic r3, r3, #1 400036ec: e58431a8 str r3, [r4, #424]; 0x1a8 400036f0: e59431a8 ldr r3, [r4, #424]; 0x1a8 400036f4: e3833001 orr r3, r3, #1 400036f8: e58431a8 str r3, [r4, #424]; 0x1a8 Вместо пары строк получаем шесть. Частота упадёт втрое? Делаем прогон… 12.5/1.9=6.6 Более, чем в шесть раз частота упала! Получается, что чтение из порта – довольно медленная операция. Значит, чуть переписываем мои макросы записи в порт, убирая из них операции чтения: #define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE
#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE Делаем прогон записи в SPI… 4 мегагерца. Ну вот. Не особо напрягаясь, разогнали систему почти вчетверо. Меня не покидает ощущение, что всё можно разогнать ещё сильнее, но оставим это на потом. Сейчас особо это не требуется. Заключение Мы освоили механизм добавления VENDOR команд в USB-устройство на базе FX3. При этом мы испытали работу с командами, передающими данные через конечную точку EP0 в обоих направлениях. Также мы освоили работу с GPIO у этого контроллера. Теперь, кроме скоростной передачи через конечные точки типа BULK и GPIF, мы можем передавать команды в свою «прошивку» ПЛИС. А для чего я хочу это применять, будет рассказано в следующей статье. =========== Источник: habr.com =========== Похожие новости:
Системное программирование ), #_fpga, #_programmirovanie_mikrokontrollerov ( Программирование микроконтроллеров ), #_kompjuternoe_zhelezo ( Компьютерное железо ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:14
Часовой пояс: UTC + 5