Перейти к содержанию
    

Вернусь к вопросу использования DMA.

64 байта пакета - это всего 16 слов, возможность выигрыша в такой конфигурации представляется сомнительной.

Для OUT передач это, возможно, и верно, т.к. для BULK и INT трансферов 64 байта - это максимум. Я проверил на HID и на CDC с штатным Windows драйвером usbser.sys.

Для IN передач все намного радужнее. У IN точек есть персональный хардварный буфер, который может быть по размеру намного больше чем размер точки. Т.е. к примеру, если точка имеет размер 64 байта, я свободного могу сделать буфер размером, к примеру, в 512 байт и загрузить его полностью используя DMA. В данном случае будет явный прирост производительности.

 

Жаль, конечно, что для OUT точек сделали какой то кастрированный общий буфер...

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Продолжаю потихоньку грызть OTG.

 

Построил USB MSD устройство. Столкнулся с вот такой проблемой:

 

Рабочая конфигурация FIFO:

1. OUT FIFO - 128 байт. (Возможны и другие варианты).

2. IN FIFO для EP0 - 64 байта.

3. IN FIFO для EP1 (MSD bulk IN) < 512 байт. Любой значение в пределах 64 * (1..7)

так все работает прекрасно.

 

Если ставлю IN FIFO для EP1 =512 или более - передача в хост затыкается на команде SCSI Read10 чтения 0 сектора. Состояние регистров EP1 IN становится вот такое:

В регистре DIEPCTL1 бит EPENA установлен, что говорит о том, что точка занята (в режиме передачи).

В регистре DIEPSIZ1 поле PKTCNT равно количеству 64 бит пакетов, т.е. 512/64 = 8. Поле XFRSIZ = 0;

В регистре DTXFSTS1 поле INEPTFSAV = 0 - Буфер полный.

 

Хост, при этом, передачу не видит.

 

Сделал обманку в обработчике прерываний от Tx FIFO empty - в прерывании гружу не все, что нужно, а только 64/128/192 байт. Все начинает работать как часы.

Если гружу 256 байт или больше опять зависон на передаче 512 байт сектора.

 

В чем может быть проблема?

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Подниму темку в связи со схожим вопросом...

Построил USB MSD устройство. Столкнулся с вот такой проблемой:

 

Если ставлю IN FIFO для EP1 =512 или более - передача в хост затыкается на команде SCSI Read10 чтения 0 сектора. Состояние регистров EP1 IN становится вот такое:

В регистре DIEPCTL1 бит EPENA установлен, что говорит о том, что точка занята (в режиме передачи).

В регистре DIEPSIZ1 поле PKTCNT равно количеству 64 бит пакетов, т.е. 512/64 = 8. Поле XFRSIZ = 0;

В регистре DTXFSTS1 поле INEPTFSAV = 0 - Буфер полный.

 

Хост, при этом, передачу не видит.

 

CDC устройство (свое), данные ходят через BULK EP1. Работает все, кроме передачи данных от девайса к хосту (при этом через EP0 ответы проходят нормально, код пересылки данных на 99% общий). После записи в TX FIFO EP1 остается в таком виде:

DIEPCTL1 = 0x80498040, т.е. EPENA установлен

DIEPSIZ1 = 0x80000, поле PKTCNT=1, поле XFRSIZ = 0 (изначально отправлялся 1 пакет размером 5 байт, соответственно было 0x80005)

DTXFSTS1 = 14, исходный размер был 16 слов, записал 5байт (=2 слова), т.е. все логично.

Если писать данные дальше, то буфер заполняется и все. :crying:

 

Ситуация почти 1:1 с Вашей, интересно что это может быть, и чем все закончилось у Вас?

P.S. Да, камень STM32F746

Изменено пользователем Шаманъ

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Не совсем как в документации инициализирую GRXFSIZ - если точно по формуле, висим. Попробуйте увеличить, не забывая про размер FIFO (для FS - 1280 байт).

У меня опытным путем получено addplaces = 3

to Шаманъ: а попробовать мой исходник затащить? На каждом углу же предлагаю взять...

 

static uint32_t usbd_makefifoparam(uint_fast16_t base, uint_fast16_t size)
{
return ((uint32_t) size << 16) | base;
}

// Преобразование размера в байтах размера данных в требования к fifo
// Расчет аргумента функции HAL_PCDEx_SetRxFiFo, HAL_PCDEx_SetTxFiFo
static uint_fast16_t size2buff4(uint_fast16_t size)
{
const uint_fast16_t size4 = (size + 3) / 4;		// размер в 32-бит значениях
return ulmax16(0x10, size4);
}

static void usbd_fifo_initialize(PCD_HandleTypeDef * hpcd, uint_fast16_t fullsize)
{
PCD_TypeDef * const instance = hpcd->Instance;

// DocID028270 Rev 2 (RM0410): 41.11.3 FIFO RAM allocation
// DocID028270 Rev 2 (RM0410): 41.16.6 Device programming model
uint_fast16_t maxoutpacketsize4 = size2buff4(USB_OTG_MAX_EP0_SIZE);
//uint_fast16_t base4;
uint_fast8_t numcontrolendpoints = 1;
uint_fast8_t numoutendpoints = 1;

// при addplaces = 3 появился звук на передаче в трансивер (при 2-х компортах)
// но если CDC и UAC включать поодиночке, обмен не нарушается и при 0.
// todo: найти все-таки документ https://www.synopsys.com/ip_prototyping_kit...tgv2_drd_pc.pdf

uint_fast8_t addplaces = 3;			

#if WITHUSBUAC
numoutendpoints += 1;
#if WITHRTS96
	enum { nuacinpackets = 1, nuacoutpackets = 1 };
#elif WITHRTS192
	enum { nuacinpackets = 1, nuacoutpackets = 1 };
#else /* WITHRTS96 || WITHRTS192 */
	enum { nuacinpackets = 2, nuacoutpackets = 2 };
#endif /* WITHRTS96 || WITHRTS192 */
const uint_fast16_t uacinmaxpacket = uasbd_getuacinmaxpacket();
maxoutpacketsize4 = ulmax16(maxoutpacketsize4, nuacoutpackets * size2buff4(VIRTUAL_AUDIO_PORT_DATA_SIZE_OUT));
#endif /* WITHUSBUAC */

#if WITHUSBCDC
numoutendpoints += 2;
#if WITHUSBUAC
	#if WITHRTS96 || WITHRTS192
		enum { ncdcindatapackets = 3, ncdcoutdatapackets = 4 };
	#else /* WITHRTS96 || WITHRTS192 */
		enum { ncdcindatapackets = 2, ncdcoutdatapackets = 2 };
	#endif /* WITHRTS96 || WITHRTS192 */
#else /* WITHUSBUAC */
	enum { ncdcindatapackets = 4, ncdcoutdatapackets = 4 };
#endif /* WITHUSBUAC */

maxoutpacketsize4 = ulmax16(maxoutpacketsize4, ncdcoutdatapackets * size2buff4(VIRTUAL_COM_PORT_DATA_SIZE));
#endif /* WITHUSBCDC */

#if WITHUSBCDCEEM
numoutendpoints += 1;
#if WITHUSBUAC
	#if WITHRTS96 || WITHRTS192
		enum { ncdceemindatapackets = 3, ncdceemoutdatapackets = 4 };
	#else /* WITHRTS96 || WITHRTS192 */
		enum { ncdceemindatapackets = 2, ncdceemoutdatapackets = 2 };
	#endif /* WITHRTS96 || WITHRTS192 */
#else /* WITHUSBUAC */
	enum { ncdceemindatapackets = 4, ncdceemoutdatapackets = 4 };
#endif /* WITHUSBUAC */

maxoutpacketsize4 = ulmax16(maxoutpacketsize4, ncdceemoutdatapackets * size2buff4(USBD_CDCEEM_BUFSIZE));
#endif /* WITHUSBCDCEEM */

uint_fast16_t base4;
uint_fast16_t size4;

{
	/* Установить размер RX FIFO */
	// (4 * number of control endpoints + 6) + 
	// ((largest USB packet used / 4) + 1 for status information) + 
	// (2 * number of OUT endpoints) + 
	// 1 for Global NAK 
	size4 = 
			(4 * numcontrolendpoints + 6) + 
			(maxoutpacketsize4 + 1) + 
			(2 * numoutendpoints) + 
			1 +
			addplaces;

	instance->GRXFSIZ = size4;
	base4 = size4;

	/* Установить размер TX FIFO EP0 */
	size4 = 2 * (size2buff4(USB_OTG_MAX_EP0_SIZE) + 0);
	instance->DIEPTXF0_HNPTXFSIZ = usbd_makefifoparam(base4, size4);
	base4 += size4;
}

#if WITHUSBUAC
{
	/* endpoint передачи звука в компютер */
	const uint_fast8_t pipe = USBD_EP_ISOC_IN & 0x7F;

	size4 = nuacinpackets * (size2buff4(uacinmaxpacket) + 0);
	instance->DIEPTXF [pipe - 1] = usbd_makefifoparam(base4, size4);
	base4 += size4;
}

#endif /* WITHUSBUAC */

#if WITHUSBCDC

{
	/* полнофункциональное устройство */
	const uint_fast8_t pipe = USBD_EP_CDC_IN & 0x7F;

	size4 = ncdcindatapackets * (size2buff4(VIRTUAL_COM_PORT_DATA_SIZE) + 0);
	instance->DIEPTXF [pipe - 1] = usbd_makefifoparam(base4, size4);
	base4 += size4;
}

/* Эти endpoints не используются для обмена */
size4 = 0x10;
instance->DIEPTXF [(USBD_EP_CDC_INT & 0x7F) - 1] = usbd_makefifoparam(base4, size4);
instance->DIEPTXF [(USBD_EP_CDC_INb & 0x7F) - 1] = usbd_makefifoparam(base4, size4);
instance->DIEPTXF [(USBD_EP_CDC_INTb & 0x7F) - 1] = usbd_makefifoparam(base4, size4);

#endif /* WITHUSBCDC */

#if WITHUSBCDCEEM

{
	/* полнофункциональное устройство */
	const uint_fast8_t pipe = USBD_EP_CDCEEM_IN & 0x7F;

	size4 = ncdceemindatapackets * (size2buff4(USBD_CDCEEM_BUFSIZE) + 0);
	instance->DIEPTXF [pipe - 1] = usbd_makefifoparam(base4, size4);
	base4 += size4;
}

#endif /* WITHUSBCDCEEM */

USB_FlushRxFifo(instance);
USB_FlushTxFifoAll(instance);

if ((base4 * 4) > fullsize)
{
	char b [32];
	debug_printf_P(PSTR("usbd_fifo_initialize error: base4=%u, fullsize=%u\n"), (base4 * 4), fullsize);
	local_snprintf_P(b, sizeof b / sizeof b [0], PSTR("used=%u"), (base4 * 4));
	display_setcolors(COLOR_RED, BGCOLOR);
	display_at(0, 0, B);
	for (;;)
	;
}
else
{
	debug_printf_P(PSTR("usbd_fifo_initialize: base4=%u, fullsize=%u\n"), (base4 * 4), fullsize);
#if 0
	// Диагностическая выдача использованного объёма FIFO RAM
	char b [32];

	local_snprintf_P(b, sizeof b / sizeof b [0], PSTR("used=%u"), (base4 * 4));
	display_setcolors(COLOR_GREEN, BGCOLOR);
	display_at(0, 0, B);
	local_delay_ms(2000);
#endif
}
}

Изменено пользователем Genadi Zawidowski

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Геннадий приветствую!

Спасибо за ответ. Я вчера обнаружил похожую тему https://community.st.com/thread/15083

 

Вот оттуда про магическое число 3 :):

I have found this comment in the HAL_PCDEx_SetTxFiFo function code (Cube lib 1.6.0):

 

"When DMA is used 3n * FIFO locations should be reserved for internal DMA registers"

 

Но тут речь про TXFIFO и про DMA. У Вас же RXFIFO, а у меня DMA нет...

 

Не совсем как в документации инициализирую GRXFSIZ - если точно по формуле, висим. Попробуйте увеличить, не забывая про размер FIFO (для FS - 1280 байт).

У меня опытным путем получено addplaces = 3

Причем, что интересно EP0 работает нормально, а у нее FIFO как раз за RXFIFO - получается фигня какая-то...

 

to Шаманъ: а попробовать мой исходник затащить? На каждом углу же предлагаю взять...

Заглянул - у меня совсем другая концепция построения модуля (я не пытаюсь сваять еще одного сколь-либо универсального монстра - минимум кода, минимум памяти, максимум быстродействия). Библиотеки от STM я не использую - все свое. Но всеравно спасибо!

 

Кстати про FIFO и доки. В доках написано несколько туманно, но по факту OTG_DIEPTXF0 и OTG_DIEPTXF1 адресуют один и тот же регистр.

 

Кстати, а какие размеры fifo и их адреса вызывали проблемы, а какие были рабочими? Может удастся установить связь и найти корень зла :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Рабочие в проекте. Проблемы если по формуле (там где кодичество IN уendpoints итак далее учитываетмся). GRXFSIZ увеличивайте пока не заработает. DMA я не использовал (хотя и с ним работает).

Или - попробуйте для TX FIFO выделять память от конца - а в GRXFSIZ все что останется...

 

Или - срисуйте с моего проекта для Вашего конкретного случая распределение памяти FIFO под CDC - и проверьте, работает или нет, что виновато - остальной код или эта часть.

Да хоть пополам разделите 1280.

 

ps: в аттачменте - тестовый бинарник под плату STM32F746G-DISCO, на FS порту создает два компорта и аудио. Один из компортов - кенвудовский CAT.

tc1_stm32f746zg_rom_DISCO.zip

Storch_HF_TRX_CAT.zip

___________________________.pdf

Изменено пользователем Genadi Zawidowski

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Проблемы если по формуле (там где кодичество IN уendpoints итак далее учитываетмся). GRXFSIZ увеличивайте пока не заработает.

У меня изначально под RXFIFO выделялось памяти намного больше, чем в формуле. Одну проблему нашел, исправил, проблема была очень глупой (описка).

 

Получил первый пакет на компе (пока в сниффере). Потом затык с теми же симптомами. Вызывает подозрение этот фрагмент (стрелочками отметил, что это такое непонятно):

post-39839-1490004086_thumb.png

 

/* Эти endpoints не используются для обмена */

size4 = 0x10;

instance->DIEPTXF [(USBD_EP_CDC_INT & 0x7F) - 1] = usbd_makefifoparam(base4, size4);

instance->DIEPTXF [(USBD_EP_CDC_INb & 0x7F) - 1] = usbd_makefifoparam(base4, size4);

instance->DIEPTXF [(USBD_EP_CDC_INTb & 0x7F) - 1] = usbd_makefifoparam(base4, size4);

 

И у Вас получается туда что-либо записать? У меня регистры у отключенных эндпоинтов совершенно мертвые (возвращают 0, запись игнорируют).

 

ps: в аттачменте - тестовый бинарник под плату STM32F746G-DISCO, на FS порту создает два компорта и аудио. Один из компортов - кенвудовский CAT.

Спасибо, но дискавери у меня нет :rolleyes:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1) эти неиспользуемые ендпоинты инициализируются обычном способом, просто не выделяется память под буферы (а так как написано).

2) Ты помнишь, что в OTG_FS DMA отсутствует?

3) производительность, простота... Я начал тоже с написания с чистого листа (только заглядывая в кубокод). Это было после того как за месяц аналогичным способом поднял USB DEVICE на Renesas. Уткнувшись в стену через пару недель, решил сперва поднять как работает (и получил железно работающий интерфейс CDC+AUDIO). И за 33 дня (trial period USBLyzer-а) все закончил. Сейчас я пожалуй под структуру от ST склонен перетащить версию под Renesas, чем сильно менять то что работает. Но полигон в любом случае есть.

4) по логу: смотреть кто за кем... без этого могу предположить что в 13 строке инициировано чтение и в 14 удачно завершилось - десяток байт передан. А что в 15 строке не понял... Или не получилось начать передачу. Или это неудачно завершилась ранее инициированная операция. НЕ знаю как с этим логгером работать (варишарк?)

 

Изменено пользователем Genadi Zawidowski

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

У меня был похожий случай недавно (на USB CDC), что данные от Хоста к устройству шли нормально, а от устройства к Хосту ни в какую.

Дескрипторы устройства были классические (EP1 IN/OUT - данные BULK, EP2 IN - Interrupt), всё вроде должно работать.

 

А проблема была в том, что конечную точку EP2 я забыл проинициализировать в контроллере. Я через неё отправлять ничего не собирался и забыл про неё, но Хост, зная из дескрипторов что эта точка есть, периодически (но крайне редко) - делал к ней запрос. Я этот запрос в логах даже найти не мог, т.к. он был 1 раз на 255 кадров SOF.

А поскольку EP2 не была инициализирована в железе, то она никак не отвечала на этот запрос: ни ACK, ни NACK ни как-то ещё. И Хост вообще прекращал делать запросы типа IN не только к EP2 IN но и к EP1 IN. В результате данные от устройства к Хосту не читались. Но передавались от Хоста к устройству исправно.

 

Когда я вспомнил-таки про EP2 и инициализировал её - данные от EP1 пошли нормально. У меня эта точка ничего не делает, просто отвечает NACK на запрос от Хоста, и всё.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Изменено пользователем Genadi Zawidowski

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1) эти неиспользуемые ендпоинты инициализируются обычном способом, просто не выделяется память под буферы (а так как написано).

Что значит обычным способом? Попробуте как-нибудь ради прикола что-то записать регистр конфигурации ФИФО неиспользуемой точки.

2) Ты помнишь, что в OTG_FS DMA отсутствует?

Да, мне ДМА не нужен, соответственно не использую.

3) производительность, простота... Я начал тоже с написания с чистого листа (только заглядывая в кубокод). Это было после того как за месяц аналогичным способом поднял USB DEVICE на Renesas. Уткнувшись в стену через пару недель, решил сперва поднять как работает (и получил железно работающий интерфейс CDC+AUDIO). И за 33 дня (trial period USBLyzer-а) все закончил.

Ну у меня пока с чистого листа это дело заняло три дня, из которых один потрачен на идиотскую ошибку с распределением ФИФО и эту последнюю нерешенную проблему - врод все не так уж и плохо.

по логу: смотреть кто за кем... без этого могу предположить что в 13 строке инициировано чтение и в 14 удачно завершилось - десяток байт передан. А что в 15 строке не понял... Или не получилось начать передачу. Или это неудачно завершилась ранее инициированная операция. НЕ знаю как с этим логгером работать (варишарк?)

Я тоже в этом логгере не силен, есть подозрение, что это вообще глюк логгера. Там вдруг посылается 10 байт через нулевую точку как будто это BULK точка. Перелопатил уже все, что можно, ну не посылаю я ничего такого, и само оно не может уйти - или глюк железки/ее инициализации, или логгер.

 

Сейчас вообще весело - первый пакет уходит и я его вижу в логгере. Но из очереди (в STMме в смысле) он не убирается и за ним уже ничего в сторону хоста не ходит.

 

А проблема была в том, что конечную точку EP2 я забыл проинициализировать в контроллере. Я через неё отправлять ничего не собирался и забыл про неё, но Хост, зная из дескрипторов что эта точка есть, периодически (но крайне редко) - делал к ней запрос. Я этот запрос в логах даже найти не мог, т.к. он был 1 раз на 255 кадров SOF.

А вот это теплее. Дело в том, что я EP2 не использую, и в дескрипторе она не объявлена у меня. Она ведь исходя из стандарта опциональная, или нет? Наверное от безисходности щас переделаю дескрипторы 1:1 как у STM, и добавлю еще одну точку.

Изменено пользователем Шаманъ

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Дополнил инициализацию отладочной печатью:

v1=00000000
v2=00000000
v3=00000000
v4=0010013D
v5=0010013D
v6=0010013D
usbd_fifo_initialize: base4=1268, fullsize=1280

    /* Эти endpoints не используются для обмена */
    size4 = 0x10;
    debug_printf_P(PSTR("v1=%08lX\n"), instance->DIEPTXF [(USBD_EP_CDC_INT & 0x7F) - 1]);
    debug_printf_P(PSTR("v2=%08lX\n"), instance->DIEPTXF [(USBD_EP_CDC_INb & 0x7F) - 1]);
    debug_printf_P(PSTR("v3=%08lX\n"), instance->DIEPTXF [(USBD_EP_CDC_INTb & 0x7F) - 1]);
    instance->DIEPTXF [(USBD_EP_CDC_INT & 0x7F) - 1] = usbd_makefifoparam(base4, size4);
    instance->DIEPTXF [(USBD_EP_CDC_INb & 0x7F) - 1] = usbd_makefifoparam(base4, size4);
    instance->DIEPTXF [(USBD_EP_CDC_INTb & 0x7F) - 1] = usbd_makefifoparam(base4, size4);
    debug_printf_P(PSTR("v4=%08lX\n"), instance->DIEPTXF [(USBD_EP_CDC_INT & 0x7F) - 1]);
    debug_printf_P(PSTR("v5=%08lX\n"), instance->DIEPTXF [(USBD_EP_CDC_INb & 0x7F) - 1]);
    debug_printf_P(PSTR("v6=%08lX\n"), instance->DIEPTXF [(USBD_EP_CDC_INTb & 0x7F) - 1]);

Они не используются, но вполне себе инициализированные.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Дополнил инициализацию отладочной печатью:

Забавно, а у меня что-либо поменять получается только в EP0/EP1...Блин это даже не смешно, но в заголовочном файле была ошибка... :1111493779:

 

Переделал на три точки (как у СТМ), поставил USBlyser - правильный инструмент. Теперь вижу откуда были 10байтные передачи данных на control pipe - это драйвер использовал ее вместо дополнительной Interrupt точки. Также вижу где начинаются проблемы, результата правда пока нет...

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

но в заголовочном файле была ошибка

В студию...

 

зы: с каким кварцем макет? - попробую по приколу тест сделать для твоей платы.

Изменено пользователем Genadi Zawidowski

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В студию...

 

У меня он оказался очень древним, все собирался скачать посвежее, но оно ж работает. В структуре забыли __IO в одном месте. Версия файла 0.9.5 (сейчас скачал свежий, он почти в два раза больше по объему и моложе на 1.5года :1111493779: и версия значится 1.2.0). Регистры теперь меняются, как надо, но результат +- такой же:

post-39839-1490039330_thumb.png

 

Там где зеленым выделено это я отдал хосту 70байт (все прошло нормально). А вот с красного начались проблемы...видать что-то профтыкал я :laughing:, наверное надо еще раз сделать RTFM по стандарту и скупым докам от STM... Да, терминалом нормально порт открывается и данные от хоста к девайсу ходят.

 

зы: с каким кварцем макет? - попробую по приколу тест сделать для твоей платы.

8MГц это все вокруг платки от WaveShare Core746I построено.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...