STM32 - организация виртуального COM-порта


Все в железе пробовалось на камне STM32F103ZE, на аналогах тоже должно взлететь.
Забиваем.
Как я уже говорил, пока я сторонник библиотечных решений. Памяти хватает, байтовыцарапыванием заниматься рано. Так что пойдем по легкому пути.
STM предоставила нам библиотеку STM32F10x and STM32L1xx USB full-speed device library, версия 3.3.0
Описание к ней: документ CD00158241 — UM0424 User manual
Точные названия я привожу на случай если на сайте производителя будет какая-нибудь реорганизация и ссылки станут невалидными.
Не забываем дрова виртуального ком-порта. У меня вообще-то оно само все нашлось, но я много чего ставил от STM, может там в комплекте и пришлись.
Также нам пригодятся следующие стандарты
USB 2.0 Specification
USB CDC
В составе библиотеки есть пример как раз организации виртуального COM-порта. Правда там он сделан как мост USART-USB, но мы же не боимся трудностей?
Цель этой статьи — разобрать приложение, делающее следующее:

Поджигаем
Собираем проект из нужных нам библиотек: CMSIS, SPD, USB. Дерево проекта прилагаю.

Как обычно, самое интересное в папочке /src. Вот её-то и будем разбирать.
Неторопливо курим
Начнем с раскуривания библиотеки от STM

Эта библиотека предназначена и для connectivity-line устройств (это STM32F105/107), у них не просто USB FS, а USB OTG. Поскольку камней с OTG у меня пока нет, сконцентрируемся на простом USB FS.
В примере, который мы взяли за основу, есть куча дефайнов, как раз на случай отличить connectivity-line от других демоплат. Я эти дефайны повырезал, чтобы глаза не мозолили.
Cо всей картинки нас интересует High Layer — синенькие квадратики, которые и составляют, собственно, пользовательскую часть библиотеки. Их мы меняем под свои нужды, все остальное остается неизменным. Если заглянуть в папочку /src, то видно, что все эти файлики там и собраны.
Первая затяжка — usb_conf.h
Самые общие настройки библиотеки.
Указано сколько у нас будет endpoints (а нам их надо 4 штуки — нулевой для начального конфигурирования устройства, один для команд, один для приема и один для передачи).
Также расписаны, какие нам будут нужны коллбэки. Все взаимодействие библиотеки и пользовательской программы построено на обратных вызовах: в случае возникновения какого-либо события вызывается функция с заданным названием. А уже содержание этой функции — проблема разработчика.
Нам будет нужен SOF_CALLBACK – сигнал начала фрейма (Start Of Frame). По нему будем выплевывать то, что накопилось в буфере передачи.
Также резервируем еще два коллбэка — на прием и передачу. По приему символа будем мигать светодиодами, чтобы уж как-нибудь задействовать канал приема.
Упс, кончился файл. Короткая получилась затяжка.
Файл берем из примера, ничего не меняем.
Вторая затяжка — usb_desc.h / usb_desc.c
В этих файлах находятся дескрипторы устройства и эндпоинтов.
Информация по стандартным дескрипторам есть в стандарте USB, в разделе 9.6 Standard USB Descriptor Definitions
Специфические дескрипторы описаны в USB CDC, раздел 5 (так и называется Descriptors),
Эти все тонны текста в стандартах для того, чтобы USB стала действительно Universal. Поэтому тщательно выведена классификация устройств и интерфейсов — чтобы глянув на дескриптор хост сразу понял, что с этим делать.
Особо подробно разбирать смысла не вижу — это не характерно для STM32, это общая боль разработчиков USB устройств.
Файл берем из примеров, ничего не меняем.
Продолжение дескрипторов — usb_prop.h / usb_prop.c
В этих файлах описана таблица реакции на запросы. Все запросы к устройству пронумерованы и стандартизованы. В этих файлах определяется массивы функций Device_Property, User_Standard_Requests и просто массивы String_Descriptor, например. Это все используется ядром. Когда в устройство приходит определенный запрос, например «Выдай мне дескриптор устройства», то ядро посмотрит в таблице, какая функция обрабатывает этот запрос, вызовет эту функцию, а оно уже отработает:
uint8_t *Virtual_Com_Port_GetDeviceDescriptor(uint16_t Length)
{
return Standard_GetDescriptorData(Length, &Device_Descriptor);
}
Опять же, берем файл из примеров.
Прерываемся — usb_istr.h / usb_istr.c
Тоже два коротеньких файла. В них находится то, что надо вызывать при приходе прерывания от USB. Поскольку прерывание одно, то по значениям флагов определяется событие и вызывается соответствующий коллбэк.
Прерывание будет настраиваться в файле hw_config.c, обработчик выглядит очень просто:
void USB_LP_CAN1_RX0_IRQHandler(void)
{
USB_Istr();
}
его можно разместить в файлах stm32f10x_it.*, а в данном проекте, чтобы не растекаться мысью по древу, я его включил в файл main.c
И тут особо ничего не меняем, все как в примере.
Питание — usb_pwr.h / usb_pwr.c
Здесь все, что касается питания. По стандарту, устройство может быть переведено в режим спячки, тогда, если оно питается от порта, нужно позаботиться об отключении жрущей периферии. Поскольку у нас плата питается отдельно, то особого смысла в управлении питанием нет. Просто устройство формально переводится в состояние SUSPENDED.
Ничего не меняем, файл из примеров.
Коллбэки — usb_endp.c
Этот файл я переписал, поскольку у нас не будет работы с USART, как это было в примере. Приведу код с комментариями:
/* Сколько фреймов будет между отсылкой наших пакетов. */
#define VCOMPORT_IN_FRAME_INTERVAL 5
// буфер приема данных из USB, исходя из этих данных будем мигать светодиодами
uint8_t USB_Rx_Buffer[VIRTUAL_COM_PORT_DATA_SIZE];
// для нас отправка, для хоста IN. Путает немного название, но справимся
// выполняем отправку данных, накопленных в буфере USART_Rx_Buffer
// название буфера не менял, так и осталось историческое
void EP1_IN_Callback (void)
{
Handle_USBAsynchXfer();
}
// коллбэк при приеме данных от хоста. Анализируем посимвольно и мигаем светодиодами
void EP3_OUT_Callback(void)
{
uint16_t USB_Rx_Cnt;
uint16_t i;
/* Get the received data buffer and update the counter */
USB_Rx_Cnt = USB_SIL_Read(EP3_OUT, USB_Rx_Buffer);
for (i=0; i<USB_Rx_Cnt; i++) {
USB_SetLeds(USB_Rx_Buffer[i]);
}
/* Enable the receive of data on EP3 */
SetEPRxValid(ENDP3);
}
// коллбэк по началу фрейма.
// Через определенное количество фреймов сбрасываем накопившийся буфер.
void SOF_Callback(void)
{
static uint32_t FrameCount = 0;
if(bDeviceState == CONFIGURED)
{
if (FrameCount++ == VCOMPORT_IN_FRAME_INTERVAL)
{
/* Reset the frame counter */
FrameCount = 0;
/* Check the data to be sent through IN pipe */
Handle_USBAsynchXfer();
}
}
}
Работа с железом — hw_config.h / hw_config.c
В этом файле собраны функции инициализации периферии, прерываний, функции управления светодиодами и работы с буфером отправки.
USB-порт и подтяжки на моей плате сделаны по такой схеме:

И USB_EN заведен на пин PF11. Значит надо не забыть его проинициализировать и дернуть вовремя.
void Set_System(void);
void Set_USBClock(void);
void Enter_LowPowerMode(void);
void Leave_LowPowerMode(void);
void USB_Interrupts_Config(void);
void USB_Cable_Config (FunctionalState NewState);
void Get_SerialNum(void);
void Handle_USBAsynchXfer (void);
void USB_Send_Data(uint8_t data);
void USB_SetLeds(uint8_t LED_Command);
По сравнению с примером, выкинуты функции инициализации и работы с USART, добавлена функция мигания светодиодом.
void USB_SetLeds(uint8_t LED_Command) {
switch (LED_Command) {
case 'A': {
GPIO_SetBits(GPIOF, GPIO_Pin_6);
break;
}
case 'B': {
GPIO_SetBits(GPIOF, GPIO_Pin_7);
break;
}
case 'C': {
GPIO_SetBits(GPIOF, GPIO_Pin_8);
break;
}
case 'D': {
GPIO_SetBits(GPIOF, GPIO_Pin_9);
break;
}
case 'a': {
GPIO_ResetBits(GPIOF, GPIO_Pin_6);
break;
}
case 'b': {
GPIO_ResetBits(GPIOF, GPIO_Pin_7);
break;
}
case 'c': {
GPIO_ResetBits(GPIOF, GPIO_Pin_8);
break;
}
case 'd': {
GPIO_ResetBits(GPIOF, GPIO_Pin_9);
break;
}
case '1': {
GPIO_SetBits(GPIOF, GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9);
break;
}
case '0': {
GPIO_ResetBits(GPIOF, GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9);
break;
}
}
}
По приему символов 'A', 'B', 'C', 'D' – зажигаются соответствующие светодиоды, 'a', 'b', 'c', 'd' – гасятся.
'1' и '0' — соответственно зажигают и гасят все светодиоды.
Ну и все, что будет передано функции USB_Send_Data() попадает в буфер, а затем и через USB – в комп.
Уфф. С USB вроде закончили.
Теперь в головной программе можно просто вызывать USB_Send_Data() и посимвольно передавать данные в комп.
Если мы на этом остановимся, то размер кода будет порядка 11 кБ:
arm-none-eabi-size --format=berkeley test03.elf
text data bss dec hex filename
11660 236 2744 14640 3930 test03.elf
Более двух килобайт bss – это буферы приема и передачи. Понятное дело, их можно уменьшить.
Выдыхаем — printf()
Но мы же хотим, чтобы вывод функции printf() перенаправлялся в наш свежесозданный COM-порт. Подключаем <stdio.h> и офигеваем от количества ошибок линковки:
sbrkr.c:(.text+0x12): undefined reference to `_sbrk'
writer.c:(.text+0x16): undefined reference to `_write'
closer.c:(.text+0x12): undefined reference to `_close'
lseekr.c:(.text+0x16): undefined reference to `_lseek'
readr.c:(.text+0x16): undefined reference to `_read'
fstatr.c:(.text+0x14): undefined reference to `_fstat'
isattyr.c:(.text+0x12): undefined reference to `_isatty'
collect2: ld returned 1 exit status
cs-make: *** [test03.elf] Error 1
А тут все просто — стандартная библиотека ввода-вывода подразумевает работу с файлами, но в больших системах есть ОС, которая собственно и занимается организацией файлов для программ пользователя. А поскольку у нас нет никакой ОС, то библиотека вполне справедливо недоумевает «А что же мне делать-то?»
Вот, чтобы это обойти, в проект включается файл newlib_stubs.c
В нем находятся функции-заглушки ко всем вышеперечисленным ошибкам. Но и кроме этого, есть одна функция, которую нам и надо переписать, чтобы весь вывод шел в USB:
int _write(int file, char *ptr, int len)
{
int n;
switch (file)
{
case STDOUT_FILENO: /*stdout*/
for (n = 0; n < len; n++)
{
USB_Send_Data((uint8_t) * ptr++);
}
break;
case STDERR_FILENO: /* stderr */
for (n = 0; n < len; n++)
{
USB_Send_Data((uint8_t) * ptr++);
}
break;
default:
errno = EBADF;
return -1;
}
return len;
}
Видим, что как раз и вызывается наша функция USB_Send_Data()
Все, компилим, собираем, запускаем.
arm-none-eabi-size --format=berkeley test03.elf
text data bss dec hex filename
19744 1520 2808 24072 5e08 test03.elf
Finished building: test03.siz
Архив с проектом прилагаю — это на случай, если кто не заметит маленькие буковки внизу :)

- +6
- 01 февраля 2012, 15:41
- steel_ne
- 1
Файлы в топике:
test03.zip
А какой IDE ты пользуешься? Вроде редактор кода выглядит симпатично, на кейл или иар не похоже. Больше похоже на разновидность эклипса.
Добрый день скачал пример настроил эклипс пытаюсь комиплить
компиляция доходит до
Config\startup_stm32f10x_hd.o
и начинает сыпать типа такого
src\usb_pwr.o: In function `_init':
usb_pwr.c:(.text+0x30): multiple definition of `_init'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.2/thumb2/crti.o:(.init+0x0): first defined here
src\usb_pwr.o: In function `_fini':
usb_pwr.c:(.text+0x3c): multiple definition of `_fini'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.2/thumb2/crti.o:(.fini+0x0): first defined here
src\usb_prop.o: In function `__exidx_start':
usb_prop.c:(.text+0x48): multiple definition of `__exidx_end'
src\usb_pwr.o:usb_pwr.c:(.text+0x48): first defined here
src\usb_prop.o: In function `__exidx_start':
usb_prop.c:(.text+0x48): multiple definition of `_etext'
src\usb_pwr.o:usb_pwr.c:(.text+0x48): first defined here
src\usb_prop.o: In function `__bss_start__':
уважаемый автор не подскажешь в чем может быть дело?
компиляция доходит до
Config\startup_stm32f10x_hd.o
и начинает сыпать типа такого
src\usb_pwr.o: In function `_init':
usb_pwr.c:(.text+0x30): multiple definition of `_init'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.2/thumb2/crti.o:(.init+0x0): first defined here
src\usb_pwr.o: In function `_fini':
usb_pwr.c:(.text+0x3c): multiple definition of `_fini'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.2/thumb2/crti.o:(.fini+0x0): first defined here
src\usb_prop.o: In function `__exidx_start':
usb_prop.c:(.text+0x48): multiple definition of `__exidx_end'
src\usb_pwr.o:usb_pwr.c:(.text+0x48): first defined here
src\usb_prop.o: In function `__exidx_start':
usb_prop.c:(.text+0x48): multiple definition of `_etext'
src\usb_pwr.o:usb_pwr.c:(.text+0x48): first defined here
src\usb_prop.o: In function `__bss_start__':
уважаемый автор не подскажешь в чем может быть дело?
в файл hw_config.h
были добавлены строки
#include «stm32f10x_usart.h»
#include «misc.h»
#include «stm32f10x_gpio.h»
#include «stm32f10x_rcc.h»
так как лезли ошибки
..\src\hw_config.c:33:19: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'USART_InitStructure'
..\src\hw_config.c: In function 'Set_System':
..\src\hw_config.c:54:3: error: 'GPIO_InitTypeDef' undeclared (first use in this function)
..\src\hw_config.c:54:3: note: each undeclared identifier is reported only once for each function it appears in
..\src\hw_config.c:54:21: error: expected ';' before 'GPIO_InitStructure'
..\src\hw_config.c:65:3: warning: implicit declaration of function 'RCC_APB2PeriphClockCmd'
и тп
были добавлены строки
#include «stm32f10x_usart.h»
#include «misc.h»
#include «stm32f10x_gpio.h»
#include «stm32f10x_rcc.h»
так как лезли ошибки
..\src\hw_config.c:33:19: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'USART_InitStructure'
..\src\hw_config.c: In function 'Set_System':
..\src\hw_config.c:54:3: error: 'GPIO_InitTypeDef' undeclared (first use in this function)
..\src\hw_config.c:54:3: note: each undeclared identifier is reported only once for each function it appears in
..\src\hw_config.c:54:21: error: expected ';' before 'GPIO_InitStructure'
..\src\hw_config.c:65:3: warning: implicit declaration of function 'RCC_APB2PeriphClockCmd'
и тп
А есть пример такого же, но с перламутровыми пуговицами? (Композитное устройство, состоящее из двух и более виртуальных COM портов). Хотя бы в общих чертах, что в проекте изменить надо?
изменить дескрипторы, дописать обработчики ендпоинтов. Но тут уже как драйвера воспримут это композитное устройство. Информации к дровам подробной нет, только методом тыка.
Десрипторов то там больше одного ))). Склоняюсь к тому, что необходимо добавлять количество интерфейсов и изменять дескриптор конфигурации под количество интерфейсов. Драйвера должны воспринять, CDC вроде как стандартное устройство. Из VCP_V1.3.1_Setup.exe достаточно выудить .inf файл. Хотя, возможно, я ошибаюсь.
Так чуть короче и нету дублирования кода:
Ещё лучше сравнивать объекты одного типа ('a' — имеет тип char, а не uint8_t).
void USB_SetLeds(char LED_Command) {
switch (LED_Command) {
case 'A':
case 'a':
GPIO_SetBits(GPIOF, GPIO_Pin_6);
break;
case 'B':
case 'b':
GPIO_SetBits(GPIOF, GPIO_Pin_7);
break;
case 'C':
case 'c':
GPIO_SetBits(GPIOF, GPIO_Pin_8);
break;
case 'D':
case 'd':
GPIO_SetBits(GPIOF, GPIO_Pin_9);
break;
case '1':
GPIO_SetBits(GPIOF, GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9);
break;
case '0':
GPIO_ResetBits(GPIOF, GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9);
break;
}
}
Ещё лучше сравнивать объекты одного типа ('a' — имеет тип char, а не uint8_t).
Возможно тогда уже лучше что-то типа такого:
Тут дублирования кода нет совсем, независимо от того, сколько вариантов надо обработать.
typedef struct {
char key;
uint16_t bits;
char reset;
} action_t;
action_t actions[] =
{
{'A', GPIO_Pin_6},
{'a', GPIO_Pin_6},
{'B', GPIO_Pin_7},
{'b', GPIO_Pin_7},
{'C', GPIO_Pin_8},
{'c', GPIO_Pin_8},
{'D', GPIO_Pin_9},
{'d', GPIO_Pin_9},
{'1', GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9},
{'0', GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9, RESET_BITS},
};
void USB_SetLeds(char LED_Command) {
for(int i = 0; i < sizeof(actions)/sizeof(action_t); i++) {
if (LED_Command == actions[i].key) {
actions[i].reset ? GPIO_ResetBits(GPIOF, actions[i].bits) : GPIO_SetBits(GPIOF, actions[i].bits);
}
}
}
Тут дублирования кода нет совсем, независимо от того, сколько вариантов надо обработать.
Опс, извиняюсь, не уследил различия между 'A' и 'a'
#define RESET_BITS 1
typedef struct {
char key;
uint16_t bits;
char reset;
} action_t;
const action_t actions[] =
{
{'A', GPIO_Pin_6},
{'a', GPIO_Pin_6, RESET_BITS},
{'B', GPIO_Pin_7},
{'b', GPIO_Pin_7, RESET_BITS},
{'C', GPIO_Pin_8},
{'c', GPIO_Pin_8, RESET_BITS},
{'D', GPIO_Pin_9},
{'d', GPIO_Pin_9, RESET_BITS},
{'1', GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9},
{'0', GPIO_Pin_6 | GPIO_Pin_7 |GPIO_Pin_8 |GPIO_Pin_9, RESET_BITS},
{'\0', 0}
};
void USB_SetLeds(char LED_Command) {
int i = 0;
while (actions[i].key != LED_Command && actions[i].key != '\0' )
{
i++;
}
if (LED_Command == actions[i].key) {
(actions[i].reset ? GPIO_ResetBits : GPIO_SetBits)(GPIOF, actions[i].bits);
}
}
Да, проглядел.
P.S. а вот вешать в конце маркер в виде 0 я принципиально не хотел. да и врядли модифицированный вами поиск проще или лучше читается.
P.S. а вот вешать в конце маркер в виде 0 я принципиально не хотел. да и врядли модифицированный вами поиск проще или лучше читается.
Можно прикинуть на пальцах: свич на один кейс это одна проверка+переход+положить два параметра+вызов+переход в конец свичаю. Если очень грубо оценивать каждый шаг как 2 байта, то это 10байт. Одна запись в actions занимает (если паковать структуру) 4 байта ну и общий код (~равен двум кейсам, один на сравнение ключа и вызов и один на внешний цикл). Получается, что уже с тремя командами может быть выиграш по размеру. Понятно, что оценка очень грубая, но врядли реальное число кейсов, которые выгоднее таблички будет больше 4-5.
Ну не по размеру, так по скорости. В реальном проекте впрочем и правда лучше таблица. Кроме того, есть еще оптимизатор. Мож заметит, что кейсы отличаются тока параметрами и переместит вызов функции на выход из свитча. Да и поиск в свитче афайк бинарный. В реальном проекте впрочем и правда лучше таблица.
В реальном проекте команды вряд-ли будут однобитные. Либо жесткая бинарная структура, либо (если требуется человекочитаемость) какая-либо грамматика, которую без анализатора с наскока и не разберешь.
А что ето за адреса указаны в функции Get_SerialNum файла hw_config.c?
Device_Serial0 = *(__IO uint32_t*)(0x1FFFF7E8);
Device_Serial1 = *(__IO uint32_t*)(0x1FFFF7EC);
Device_Serial2 = *(__IO uint32_t*)(0x1FFFF7F0);
у меня в кейле при дебаге на них пишет *** error 65: access violation at 0x1FFFF7E8: no 'read' permission
Device_Serial0 = *(__IO uint32_t*)(0x1FFFF7E8);
Device_Serial1 = *(__IO uint32_t*)(0x1FFFF7EC);
Device_Serial2 = *(__IO uint32_t*)(0x1FFFF7F0);
у меня в кейле при дебаге на них пишет *** error 65: access violation at 0x1FFFF7E8: no 'read' permission
- bigdigital
- 02 марта 2012, 17:35
- ↓
Читаем в Reference manual (RM0008):
28.2 Unique device ID register (96 bits)
The unique device identifier is ideally suited:
● for use as serial numbers (for example USB string serial numbers or other end
applications)
● for use as security keys in order to increase the security of code in Flash memory while
using and combining this unique ID with software cryptographic primitives and
protocols before programming the internal Flash memory
● to activate secure boot processes, etc.
The 96-bit unique device identifier provides a reference number which is unique for any
device and in any context. These bits can never be altered by the user.
The 96-bit unique device identifier can also be read in single bytes/half-words/words in
different ways and then be concatenated using a custom algorithm.
Base address: 0x1FFF F7E8
28.2 Unique device ID register (96 bits)
The unique device identifier is ideally suited:
● for use as serial numbers (for example USB string serial numbers or other end
applications)
● for use as security keys in order to increase the security of code in Flash memory while
using and combining this unique ID with software cryptographic primitives and
protocols before programming the internal Flash memory
● to activate secure boot processes, etc.
The 96-bit unique device identifier provides a reference number which is unique for any
device and in any context. These bits can never be altered by the user.
The 96-bit unique device identifier can also be read in single bytes/half-words/words in
different ways and then be concatenated using a custom algorithm.
Base address: 0x1FFF F7E8
У кого-нибудь были проблемы с драйвером? У меня 64х битная 7ка и нехочет распознавать девайс.
Отладочная плата — HY-Mini STM32V с STM32А103VCT6 на борту, второстепенный USB подключен как и в примере только USB_EN на другом пине. Проэкт собрался после пары добавленных '#include' так как импользовал КуКокс и смены LED пинов на собственные.
Отладочная плата — HY-Mini STM32V с STM32А103VCT6 на борту, второстепенный USB подключен как и в примере только USB_EN на другом пине. Проэкт собрался после пары добавленных '#include' так как импользовал КуКокс и смены LED пинов на собственные.
Спасибо огромное ресурсу. Очень помог!!!
Внимание!!!
Использовал этот пример. После долгих ночных побайтных ковыряний выяснилась неприятность.
При транзакциях передачи.
а Именно.
Передачу надо делать в функции SOF_Calback. а не в EP3_OUT_Callback.
связано скорей всего Handle_USBAsynchXfer.
Ошибка вылитала только когда много данных с ПК кидал.
Теперь как часы.
Реально без потерь на 72 мгц с кодированием в эскейп последовательности и crc32.
150-200 килобайт в сек больше не пробовал.
И сам правленый код.
Внимание!!!
Использовал этот пример. После долгих ночных побайтных ковыряний выяснилась неприятность.
При транзакциях передачи.
а Именно.
Передачу надо делать в функции SOF_Calback. а не в EP3_OUT_Callback.
связано скорей всего Handle_USBAsynchXfer.
Ошибка вылитала только когда много данных с ПК кидал.
Теперь как часы.
Реально без потерь на 72 мгц с кодированием в эскейп последовательности и crc32.
150-200 килобайт в сек больше не пробовал.
И сам правленый код.
void EP3_OUT_Callback(void)
{
unsigned int USB_Rx_Cnt;
unsigned int i,j;
// Get the received data buffer and update the counter
USB_Rx_Cnt = USB_SIL_Read(EP3_OUT, USB_Rx_Buffer);
for (i=0; i<USB_Rx_Cnt; i++)
{
if(dec(USB_Rx_Buffer[i])) /// ДО КОНЦА ДОДЕКОДИРОВАЛИ
{
if(dec_check()) /// ПРОВЕРКА CRC32
{
/// PARSED OK
if(cmd_parse()==1) /// КОМАНДА ОТРАБОТАНА
{ /// В НЕЙ И ЗАПОЛНЯЕТСЯ ENC БУФЕР СМ НИЖЕ
dec_reset(); // СБРОСИТЬ ЕНКОДЕР
}
}
}
}
SetEPRxValid(ENDP3);
// Enable the receive of data on EP3
}
//
void SOF_Callback(void) /// 200 hz
{
int i;
static uint32_t FrameCount = 0;
if(bDeviceState == CONFIGURED)
{
if (FrameCount++ == VCOMPORT_IN_FRAME_INTERVAL)
{
/// ПРОВЕРЯЕМ НОВЫЕ ДАННЫЕ
for(i=0;i<enc_size();i++)
{
USB_Send_Data(enc_buffer()[i]);
}
enc_reset();
esc_timeout();
// Reset the frame counter
FrameCount = 0;
// Check the data to be sent through IN pipe
Handle_USBAsynchXfer();
}
}
}
А кто-нибудь знает, как вообще с этим устройством общаться?
У меня получается его открыть, считать дескриптор устройства и конфигурации. При попытке считать дескриптор ендпоинта ошибка «ERROR_PIPE», при попытке установить конфигурацию — «ERROR_BUSY». Любые записи в любые BULK ендпоинты мгновенно сообщают «ERROR_IO».
Пытался с оригинальной программой от STM для работы с COM портом работать, выкидывая аппаратное общение с собственно COM портом и вставляя загушки. Результат тот же.
Может тут сначала через control ендпоинт надо что-то передать?
У меня получается его открыть, считать дескриптор устройства и конфигурации. При попытке считать дескриптор ендпоинта ошибка «ERROR_PIPE», при попытке установить конфигурацию — «ERROR_BUSY». Любые записи в любые BULK ендпоинты мгновенно сообщают «ERROR_IO».
Пытался с оригинальной программой от STM для работы с COM портом работать, выкидывая аппаратное общение с собственно COM портом и вставляя загушки. Результат тот же.
Может тут сначала через control ендпоинт надо что-то передать?
Проблему решил.
Она в том, что это устройство типа CDC, которое по-умолчанию поддерживается системой. Для полноценной работы с ним надо отсоединить драйвер (модуль ядра), который по-умолчанию его захватывает.
Для меня было странно, что захваченное устройство таки открывалось.
Блин, целый день пропал!
Она в том, что это устройство типа CDC, которое по-умолчанию поддерживается системой. Для полноценной работы с ним надо отсоединить драйвер (модуль ядра), который по-умолчанию его захватывает.
Для меня было странно, что захваченное устройство таки открывалось.
Блин, целый день пропал!
Господа, помогите осознать, что происходит.
Микроконтроллер STM32F102C8T6.
В прошивке виртуального COM порта внутрь калбэка void EP1_IN_Callback(void) вставил мигание светодиодом, если были отправлены данные. Судя по миганию, вызывается постоянно, не зависимо от того, читает ли кто-нибудь данные или нет, сразу после вставки устройства в компьютер. Помогите осознать, с чем это связано.
То есть, получается, что всё, что было отправлено до того как подключили терминал, куда-то считывается и пропадает бесследно.
При подключении терминала всё, что микроконтроллер отправляет, появляется на терминале, никуда ничего не пропадает. После отключения терминала опять всё, как было — постоянные чтения и данные опять уходят в никуда.
Я подозревал, что система так работает с виртуальным COM портом. Пробовал в дескрипторе тип устройства с CDC (0x02) заменить на VendorSpecific (0xFF). Результат тот же — постоянные чтения, но устройство /dev/ttyACM0 не появляется. Может ещё что-то надо было поменять?
Если работать с устройством через функции системы fctrl, open, close, read, write, то всё норм. А вот если пытаться через libusb напрямую обращаться к ендпоинтам, то запись работает без проблем, а вот чтение обычно заканчивается прочтением сообщения нулевой длины (но без ошибок), но иногда удаётся прочитать, то что было отправлено. Такое впечатление, что моя программа соревнуется с системой, кто успеет первым считать, тот и получит данные.
Раньше один раз делал своё устройство, но не на основе виртуального COM порта, а на базе MassStorage примера для микроконтроллера LPC2148. Так там такого не было: всё отправленное приходило и только тогда, когда программа попросит, а не постоянно.
Микроконтроллер STM32F102C8T6.
В прошивке виртуального COM порта внутрь калбэка void EP1_IN_Callback(void) вставил мигание светодиодом, если были отправлены данные. Судя по миганию, вызывается постоянно, не зависимо от того, читает ли кто-нибудь данные или нет, сразу после вставки устройства в компьютер. Помогите осознать, с чем это связано.
То есть, получается, что всё, что было отправлено до того как подключили терминал, куда-то считывается и пропадает бесследно.
При подключении терминала всё, что микроконтроллер отправляет, появляется на терминале, никуда ничего не пропадает. После отключения терминала опять всё, как было — постоянные чтения и данные опять уходят в никуда.
Я подозревал, что система так работает с виртуальным COM портом. Пробовал в дескрипторе тип устройства с CDC (0x02) заменить на VendorSpecific (0xFF). Результат тот же — постоянные чтения, но устройство /dev/ttyACM0 не появляется. Может ещё что-то надо было поменять?
Если работать с устройством через функции системы fctrl, open, close, read, write, то всё норм. А вот если пытаться через libusb напрямую обращаться к ендпоинтам, то запись работает без проблем, а вот чтение обычно заканчивается прочтением сообщения нулевой длины (но без ошибок), но иногда удаётся прочитать, то что было отправлено. Такое впечатление, что моя программа соревнуется с системой, кто успеет первым считать, тот и получит данные.
Раньше один раз делал своё устройство, но не на основе виртуального COM порта, а на базе MassStorage примера для микроконтроллера LPC2148. Так там такого не было: всё отправленное приходило и только тогда, когда программа попросит, а не постоянно.
www.st.com/stonline/stappl/resourceSelector/app?page=resourceSelector&doctype=FIRMWARE&SubClassID=1520
С библиотекой для USB там примеры идут
С библиотекой для USB там примеры идут
Доброго времени суток всем. Пытаюсь запустить USB с помощью этой библиотеки на STM32F107RCT6. Получаю разное поведение на разных компах (рабочем и домашнем). На рабочем вообще почти глухо. При втыкании кабеля получаю прерывание RXFLVL: RxFIFO non-empty. Соответственно библиотечный обработчик вычитывает из FIFO пакет, судя по документации, после этого в регистре DOEPINT0 бит STUP должен стать единичным. Этого не происходит. Ну собственно на этом вся «инициализация» заканчивается.
Дома немного по-другому. Библиотека вызывает callback CustomHID_GetDeviceDescriptor (я за основу брал пример HID устройства). callback отрабатывает и все. дальше опять глухо. Сделал элементарный лог событий — бросал в массив код события (по-сути код вызванного callback-а). Получается такая картина. Вызывается CustomHID_GetDeviceDescriptor. После этого дважды CustomHID_Reset. Так повторяется раза 3. Потом все замолкает. Windows соответственно сообщает что устройство — неизвестное. В свойствах в диспетчере устройств VID/PID=0. Все дескрипторы содраны с примера HID без изменений.
Может у кого-нибудь есть мысли в чем дело?
Дома немного по-другому. Библиотека вызывает callback CustomHID_GetDeviceDescriptor (я за основу брал пример HID устройства). callback отрабатывает и все. дальше опять глухо. Сделал элементарный лог событий — бросал в массив код события (по-сути код вызванного callback-а). Получается такая картина. Вызывается CustomHID_GetDeviceDescriptor. После этого дважды CustomHID_Reset. Так повторяется раза 3. Потом все замолкает. Windows соответственно сообщает что устройство — неизвестное. В свойствах в диспетчере устройств VID/PID=0. Все дескрипторы содраны с примера HID без изменений.
Может у кого-нибудь есть мысли в чем дело?
Разобрался. Сам же и отвечаю для тех, кто с таким столкнется. Проблема классическая — неверная частота контроллера. Но причина её возникновения следующая.
В файле system_stm32f10x.c из STM32 StdPeriphLib
в функции static void SetSysClockTo72(void)
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
Первая строка сбрасывает бит PLLXTPRE регистра RCC_CFGR. Что не есть хорошо, так как это младший бит коэффициента делителя частоты PREDIR1.
В результате коэффициент деления оказывался на 1 меньше. Частота проца была около 82МГц
Решение проблемы — переделать первую строку в следующее:
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
В файле system_stm32f10x.c из STM32 StdPeriphLib
в функции static void SetSysClockTo72(void)
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
Первая строка сбрасывает бит PLLXTPRE регистра RCC_CFGR. Что не есть хорошо, так как это младший бит коэффициента делителя частоты PREDIR1.
В результате коэффициент деления оказывался на 1 меньше. Частота проца была около 82МГц
Решение проблемы — переделать первую строку в следующее:
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
Пара вопросов:
1. Много ли надо переделывать, чтобы запустить проект на STM32F4?
2. Не собирается проект (открывал в Eclipse Helios)
Из консоли:
cs-make all
Building file: ../src/hw_config.c
Invoking: ARM Sourcery Windows GCC C Compiler
arm-none-eabi-gcc -DSTM32F10X_HD -DMALLOC_PROVIDED -DUSE_STDPERIPH_DRIVER -O0 -ffunction-sections -Wall -std=gnu99 -Wa,-adhlns=«src/hw_config.o.lst» -c -fmessage-length=0 -MMD -MP -MF«src/hw_config.d» -MT«src/hw_config.d» -mcpu=cortex-m3 -mthumb -g3 -gdwarf-2 -o«src/hw_config.o» "../src/hw_config.c"
../src/hw_config.c:17: fatal error: usb_lib.h: No such file or directory
compilation terminated.
cs-make: *** [src/hw_config.o] Error 1
1. Много ли надо переделывать, чтобы запустить проект на STM32F4?
2. Не собирается проект (открывал в Eclipse Helios)
Из консоли:
cs-make all
Building file: ../src/hw_config.c
Invoking: ARM Sourcery Windows GCC C Compiler
arm-none-eabi-gcc -DSTM32F10X_HD -DMALLOC_PROVIDED -DUSE_STDPERIPH_DRIVER -O0 -ffunction-sections -Wall -std=gnu99 -Wa,-adhlns=«src/hw_config.o.lst» -c -fmessage-length=0 -MMD -MP -MF«src/hw_config.d» -MT«src/hw_config.d» -mcpu=cortex-m3 -mthumb -g3 -gdwarf-2 -o«src/hw_config.o» "../src/hw_config.c"
../src/hw_config.c:17: fatal error: usb_lib.h: No such file or directory
compilation terminated.
cs-make: *** [src/hw_config.o] Error 1
Люди, а кто-нибудь в кокосе собирал сей проект. У меня вот после некоторых доработок (инклюды добавлял) он собрался, но корректно работать отказался, камень STM32F103RET6. Под виндой определился, винда сказала, что устройство установлено, но потом тут же сказала, что устройство работает неправильно…
P.S.: я в стм-ках начинающий…
P.S.: я в стм-ках начинающий…
Есть простой проект(CooCox) под STM32F4Discovery — в свое время он мне очень помог(сам проверял на F4 — все работает).
github.com/xenovacivus/STM32DiscoveryVCP
github.com/xenovacivus/STM32DiscoveryVCP
Приветствую! У меня такой вопрос. В проекте инициализирована 18 линия EXTI. Как я понял, по этой линии генерируется прерывание, когда USB зашуршит, т.е. что бы проснуться по приходу данных с USB. А если мк у меня не спит, молотит постоянно, могу я выкинуть работу с EXTI и соответствующее прерывание? Или я не прав?
Товарищи, а вот такая штука… Может, есть у кого предположения.
Я работу с USART не вырезал, т.к., хотел попробовать заюзать IRDA, и вот с этим самым USART`ом есть проблемка — при передаче любых данных в Windows — (отправкой данных в USART) зацикливается на проверке флага TXE (TC, ессно, тоже) навечно. А вот с Linux такой проблемы нету — принимает и передает вполне себе нормально :)
Я работу с USART не вырезал, т.к., хотел попробовать заюзать IRDA, и вот с этим самым USART`ом есть проблемка — при передаче любых данных в Windows — (отправкой данных в USART) зацикливается на проверке флага TXE (TC, ессно, тоже) навечно. А вот с Linux такой проблемы нету — принимает и передает вполне себе нормально :)
Полезная публикация. Спасибо автору! Мне надо было поднять Virtual COM на STM32L152. Пришлось внести ряд изменений в проекте (в основном связаны с отличием функций стандартной библиотеки для L серии. Свой проект под STM32L152 я выложил в отдельной публикации USB Virtual COM на STM32Lxx — Keil Project
Если не ошибаюсь, скорость отправки (именно отправки, из буфера) ограничена возможностями USB.
На примере Linux в качестве хост-системы (про Win мало чего подскажу в этом плане):
при подключении создается в /dev устройство ttyACM0 (частный случай), которое суть файл, читать/ из/в него можно с «неограниченной» скоростью. В случае реального COM, скорость обмена строго зависит от настроек физической части. В случае с USB нет такого ограничения (в смысле baudrate не устанавливается). Легко проверить с помощью терминала — для COM необходимо точно установить скорость обмена, с нашим virtual COM — терминал можно запустить с любым значением скорости и все будет работать.
На примере Linux в качестве хост-системы (про Win мало чего подскажу в этом плане):
при подключении создается в /dev устройство ttyACM0 (частный случай), которое суть файл, читать/ из/в него можно с «неограниченной» скоростью. В случае реального COM, скорость обмена строго зависит от настроек физической части. В случае с USB нет такого ограничения (в смысле baudrate не устанавливается). Легко проверить с помощью терминала — для COM необходимо точно установить скорость обмена, с нашим virtual COM — терминал можно запустить с любым значением скорости и все будет работать.
ааааааа, собрал проект под stm32f107VC, целый день промудохался.
В итоге оказалось, что st.com ествественным образом игнорирует Linux, ибо драйвера только под $MS
В итоге оказалось, что st.com ествественным образом игнорирует Linux, ибо драйвера только под $MS
А что за дистрибутив? libusb?
У меня просто без установки драйверов все работает (kubuntu). Да и всегда подобного рода устройства запускаются сразу (та же FT).
Что пишут
1. udevadm monitor (при подключении/отключении девайса)
2. lsusb (при подключеном)
3. ls /dev/ttyA*
У меня просто без установки драйверов все работает (kubuntu). Да и всегда подобного рода устройства запускаются сразу (та же FT).
Что пишут
1. udevadm monitor (при подключении/отключении девайса)
2. lsusb (при подключеном)
3. ls /dev/ttyA*
вроде видится как ttyAHC, и терминал позволяет обращаться к нему ...
Но я совершенно не понимаю, где можно выцепить сами данные?
Если в примерах на 103-ю серию все было очень понятно, то примеры usb на 105-107 просто кошмар.
Мои попытки подрихтовать пример HID-а под себя, так ничего и принесли, добавил Endpoint, исправил дескриптор — репорты, теперь контроллер падает при приеме репорта, вот решил VCP договнокодить )
Но я совершенно не понимаю, где можно выцепить сами данные?
Если в примерах на 103-ю серию все было очень понятно, то примеры usb на 105-107 просто кошмар.
Мои попытки подрихтовать пример HID-а под себя, так ничего и принесли, добавил Endpoint, исправил дескриптор — репорты, теперь контроллер падает при приеме репорта, вот решил VCP договнокодить )
Здравствуйте, уважаемые. Будьте так добры, подскажите. У меня микруха stm32f103c8. Собрал под нее проект по примеру, прошил, а usb на компе вообще никак не отображается, нет его. Отладочная плата показала, что в прерывание не происходит. Кварц стоит на 8Mhz. Может чего с частотой не так. В общем, подскажите люди добрые и знающие)
если схема как на оригинальных бордах, проблем вроде быть не должно.
А так… проверьте линию VBUS если используется и подтяжку на транзисторах сигнальной линии.
А так… проверьте линию VBUS если используется и подтяжку на транзисторах сигнальной линии.
Я транзистор не вешал, просто напрямую D+ и D- подсоединил, а также минус выровнял. А что вы имеете ввиду под сигнальной линией (для особо одаренных)? Прерывание вообще тут происходит от сигналов порта, или внутренним таймером?
А зачем она, я на стремном PIC напрямую вешал, и то все работала, подтяжка, как я понимаю не обязательна, или я не прав
Затем, что это требование шины USB и без нее хост даже не обратит внимание, что в него какую-то хрень воткнули.
Обрати внимание, какую линию подтягивать — зависит от желаемого скоростного режима (LS или FS/HS). Я не помню какую линию для какого режима подтягивать надо.
Обрати внимание, какую линию подтягивать — зависит от желаемого скоростного режима (LS или FS/HS). Я не помню какую линию для какого режима подтягивать надо.
Сам с такой же фигней боролся, когда сперва собирал пример на stm32f4, потом на 103-м. В stm32f4, где есть otg, есть автоматическая подтяжка к 3-м вольтам на нужный порт, в pic'е вероятно тоже есть внутренняя подтяжка. В stm32f103, совершенно точно нужна внешняя подтяжка, согласно схеме выше или проще сразу к питанию 3.3В через 1.5k резистор.
Комментарии (104)
RSS свернуть / развернуть