asdus 0 20 января, 2012 Опубликовано 20 января, 2012 (изменено) · Жалоба Есть проектик, STM32F10*, FreeRTOS 7.1.0 USART реализован на прерываниях и очередях, по примеру из FreeRTOS С приемом все хорошо, а вот при передаче через некоторое время работы "портятся" данные. Свиду - сдвигаются указатели на голову и хвост в очереди. В данный момент пройтись отладчиком возможности нет. Выписал код в минипрогу, ошибка повторяется. Через некоторое, рандомное время, вместо "Hello World!\n" становится, например "rlWorld!\no Wo", потом еще что-нибудь и т.д. #include "stm32f10x.h" #include "FreeRTOS.h" #include "task.h" #include "queue.h" static xQueueHandle USART1_TxQueue; uint8_t USART1_PutChar( uint8_t cOutChar ) { uint8_t xReturn; if( xQueueSend( USART1_TxQueue, &cOutChar, 10 ) == pdPASS ) { xReturn = pdPASS; USART_ITConfig( USART1, USART_IT_TXE, ENABLE ); } else { xReturn = pdFAIL; } return xReturn; } void USART1_PutString( const uint8_t * pcString ) { uint8_t *pxNext; pxNext = ( uint8_t * ) pcString; while( *pxNext ) { USART1_PutChar( *pxNext ); pxNext++; } } void HelloWorldTask(void *pvParameters) { for(;;) { USART1_PutString("Hello World!\n"); vTaskDelay( 1000 ); } } void USART1_IRQHandler( void ) { portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; uint8_t cChar; if( USART_GetITStatus( USART1, USART_IT_TXE ) == SET ) { if( xQueueReceiveFromISR( USART1_TxQueue, &cChar, &xHigherPriorityTaskWoken ) == pdTRUE ) { USART_SendData( USART1, cChar ); } else { USART_ITConfig( USART1, USART_IT_TXE, DISABLE ); } } portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); } int main(void) { NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; /* Enable USART1 GPIO clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); /* Enable UART1 clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); /* Enable the USART1 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = configLIBRARY_LOWEST_INTERRUPT_PRIORITY; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* Configure USART1 Tx as alternate function push-pull */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure USART1 Rx as input floating */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure USART1 parameters 115200 8n1 noHW Tx+Rx*/ USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); /* Create TxQueue for USART1 */ USART1_TxQueue = xQueueCreate( 256, ( unsigned portBASE_TYPE ) sizeof( uint8_t ) ); USART1_PutString("Program started!\n"); USART1_PutString("================\n"); xTaskCreate(HelloWorldTask, "HelWor", configMINIMAL_STACK_SIZE, NULL, 2, NULL); vTaskStartScheduler(); for(;;) { } } Изменено 20 января, 2012 пользователем asdus Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
kan35 7 21 января, 2012 Опубликовано 21 января, 2012 · Жалоба Во первых, вы начинаете использовать queue до старта шедулера, это грубая ошибка. И на сколько мне помнится надо сбрасывать соответствующий pending bit при обработке ISR Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
asdus 0 22 января, 2012 Опубликовано 22 января, 2012 (изменено) · Жалоба Во первых, вы начинаете использовать queue до старта шедулера, это грубая ошибка. Спасибо за подсказку. В оригинальной программе такого небыло, прибил в тестовой. И на сколько мне помнится надо сбрасывать соответствующий pending bit при обработке ISRGetITStatus уже давно сбрасывает соответствующий ITPendingBit. На всякий случай добавил в тестовую прогу. Запустил тесты, ждем-с... Долго ждать не пришлось, ошибка сохранилась. К слову, с передачей, реализованной на тех-же прерываниях и очередях проблем нет, как и с передачей без использования очередей... Изменено 22 января, 2012 пользователем asdus Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
kan35 7 23 января, 2012 Опубликовано 23 января, 2012 · Жалоба Смущает вот что: бит TXE выставляется когда TXE: Transmit data register empty This bit is set by hardware when the content of the TDR register has been transferred into the shift register. Таким образом при первой записи в очередь прерывание возникнет ДВАЖДЫ, но при втором входе очередь будет пустой. Может быть не это вызывает ваш глюк, однако обратите внимание на это. Возможно проблемы с настройкой прерываний. Неплохо было бы посмотреть как freertos сконфигурирована. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
asdus 0 24 января, 2012 Опубликовано 24 января, 2012 · Жалоба Смущает вот что: бит TXE выставляется когда TXE: Transmit data register empty This bit is set by hardware when the content of the TDR register has been transferred into the shift register. Таким образом при первой записи в очередь прерывание возникнет ДВАЖДЫ, но при втором входе очередь будет пустой. Может быть не это вызывает ваш глюк, однако обратите внимание на это. Это нормально, обрабатывается ;) Возможно проблемы с настройкой прерываний. Неплохо было бы посмотреть как freertos сконфигурирована. Проблема была решена. Действительно, она была в настройке прерываний. Конкретно: порт FreeRTOS под STM32 требует следующей настройки: NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); Почему он не делает это сам - непонятно. После выполнения этой команды все стало работать как должно. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Bass 0 25 января, 2012 Опубликовано 25 января, 2012 (изменено) · Жалоба С позволения ТС, задам вопрос здесь, чтобы темы не плодить. Начал осваивать FreeRTOS, и споткнулся на таком месте: создаю задачу "отложенного прерывания", которая должна отрабатывать после получения байта во USART'у. Использую для этого семафор xHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR( USART_RxSemaphore, &xHigherPriorityTaskWoken ); portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); Код самой задачи: void USART_RX_TaskHandler( void *params ) { for(;; ) { xSemaphoreTake( USART_RxSemaphore, portMAX_DELAY ); // ждем семафора USART_puts("byte recieved\r\n"); } } и код создания задачи: #define USART_RX_TASKHDL_PRIORITY (configMAX_PRIORITIES-1) // семафор для задачи получения данных по USART vSemaphoreCreateBinary( USART_RxSemaphore ); if ( USART_RxSemaphore != NULL ) { xTaskCreate( USART_RX_TaskHandler, (signed char *) "RX handler task", USART_RX_TASKHDL_STACK, NULL, USART_RX_TASKHDL_PRIORITY, NULL ); } Проблема в том, что при запуске шедулера первый раз выполняется задача USART_RX_TaskHandler, т.е. не останавливается на xSemaphoreTake (хотя по логике вроде как должна), а потом уже выполняется после "пинка" из прерывания. Подскажите это нормально или я где-то накосячил? Изменено 25 января, 2012 пользователем Bass Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Gunner 0 25 января, 2012 Опубликовано 25 января, 2012 · Жалоба Проблема в том, что при запуске шедулера первый раз выполняется задача USART_RX_TaskHandler, т.е. не останавливается на xSemaphoreTake (хотя по логике вроде как должна), а потом уже выполняется после "пинка" из прерывания. Подскажите это нормально или я где-то накосячил? Я тоже только еще изучаю FreeRTOS. Насколько я понял, семафор, как только создан, сразу становится доступен. Поэтому задача USART_RX_TaskHandler сразу захватывает его. Так что это нормально. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Гость MALLOY2 25 января, 2012 Опубликовано 25 января, 2012 · Жалоба Есть такая беда у FreeRTOS нельзя при создании семафора задать его состояние. Я выкрутился созданием такого макроса: #define osSemNew(Sem,State,Name)\ do\ {\ Sem = xQueueCreate( ( unsigned portBASE_TYPE ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH );\ if (Sem!=NULL)\ {\ vQueueAddToRegistry(Sem,(signed char*)Name);\ if((State))\ {\ xSemaphoreGive( Sem );\ }\ }\ }while(0)\ Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Bass 0 25 января, 2012 Опубликовано 25 января, 2012 · Жалоба Ага, нашел сам макрос vSemaphoreCreateBinary, в котором видно, что после создания он сразу отдается: xSemaphoreGive( ( xSemaphore ) ); Тогда я думаю проще при инициализации сразу после создания взять семафор, тогда задача не выполнится до повторной выдачи уже в прерывании. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
diwil 0 27 января, 2012 Опубликовано 27 января, 2012 · Жалоба ээээ... как показывает практика - использование очередей в уарте не есть самый лучший способ передачи данных по нескольким причинам - необходимо распределеить память под очередь - передача одного байта вызывает большой overhead - еще какие-то-там-я-и-не-помню. Более того, несмотря на то, что прерывания при низких скоростях приходят довольно-таки редко, для их обработки, тем не менее, требуется выполнить несколько действий. В stm32 можно использовать DMA на уарте и это позволяет решить проблему оверхеда. Однако без прерываний никак по-любому, однако они будут случаться только тогда, когда приемный буфер заполнен, или когда передача прекращена. При это в оси будет использоваться только один семафор. Код для stm32l152 ниже. Может кому пригодится еще. у меня работает в оч критической задаче. #include "stm32l1xx.h" #include "stm32l1xx_rcc.h" #include "stm32l1xx_tim.h" #include "stm32l1xx_gpio.h" #include "stm32l1xx_spi.h" #include "stm32l1xx_dma.h" #include "stm32l1xx_usart.h" #include "misc.h" #include "FreeRTOS.h" #include "semphr.h" #include "task.h" #include "queue.h" #define USART2_RX_BUF_SIZE 128 static char usart2_rx_buffer[USART2_RX_BUF_SIZE]; xSemaphoreHandle xSemaphoreUSART2; void usart2_init(void) { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); USART_InitStructure.USART_BaudRate = 57600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); USART_DMACmd(USART2, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE); /* Enable USART1 IDLE line interrupts because all others are handeled via DMA */ USART_ITConfig(USART2, USART_IT_IDLE , ENABLE); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2 ); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2 ); /* Configure USART1 Tx, RX (PA.02 PA.03) as alternate function push-pull */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Enable the USART1 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 12; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); DMA_DeInit(DMA1_Channel6); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart2_rx_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = USART2_RX_BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel6, &DMA_InitStructure); USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 11; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* Enable interrupts on channel 6 */ DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE); DMA_ITConfig(DMA1_Channel6, DMA_IT_HT, ENABLE); vSemaphoreCreateBinary( xSemaphoreUSART2 ); xSemaphoreTake( xSemaphoreUSART2, ( portTickType ) portMAX_DELAY ); USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); USART2->SR = 0; DMA_Cmd(DMA1_Channel6, ENABLE); USART_Cmd(USART2, ENABLE); } int usart2_send(void *buffer, int size) { DMA_InitTypeDef DMA_InitStructure; if(size > 65535) return -2; // if (DMA1_Channel7->CNDTR) // return -1; while(DMA1_Channel7->CNDTR); // now configure transmit line: // do not enable interrupts DMA_DeInit(DMA1_Channel7); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = size; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel7, &DMA_InitStructure); DMA_Cmd(DMA1_Channel7, ENABLE); return 0; } void USART2_IRQHandler(void) { portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; int statusreg = USART2->SR; char uart_messages; uart_messages = 0; if(statusreg & (1<<4) ) // IDLE. ONLY this interrupt is enabled!!! { uart_messages = 3; USART2->DR; // dummy read to clear idle flag } if(uart_messages) { xSemaphoreGiveFromISR( xSemaphoreUSART2, &xHigherPriorityTaskWoken ); } portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } void DMA1_Channel6_IRQHandler(void) { portBASE_TYPE xHigherPriorityTaskWoken; DMA1->IFCR = 7 << 20; // clear DMA interrupt flags and continue xSemaphoreGiveFromISR( xSemaphoreUSART2, &xHigherPriorityTaskWoken ); portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } static int rx_read; int uart2_get_char(portTickType block_time) { int res; while(1) { if (USART2_RX_BUF_SIZE - DMA1_Channel6->CNDTR != rx_read) { res = usart2_rx_buffer[rx_read]; rx_read++; if(rx_read == USART2_RX_BUF_SIZE) rx_read = 0; return res & 0xff; } if (pdFALSE == xSemaphoreTake( xSemaphoreUSART2, ( portTickType ) block_time )) return -1; } return 0; } Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
asdus 0 30 января, 2012 Опубликовано 30 января, 2012 · Жалоба Обязательно перепишу на DMA, как только, так сразу :) Для истории пишу свой код целиком, на неэффективных прерываниях и очередях :) Как-нибудь при случае замеряю загрузку ЦП. Работает на STM32F100RB (STM32VLDISCOVERY) И не забываем запустить шулдер до использования) хотя у меня работало и до включения, но не протестировано на стабильность. #include <stm32f10x.h> #include <FreeRTOS.h> #include <queue.h> static xQueueHandle MODBUS_RxQueue; static xQueueHandle MODBUS_TxQueue; void MODBUS_Init( void ) { NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; /* Create Rx/Tx Queues */ MODBUS_RxQueue = xQueueCreate( 256, sizeof( uint32_t ) ); MODBUS_TxQueue = xQueueCreate( 256, sizeof( uint32_t ) ); /* Enable GPIOA clock (for USART1 TX=PA9, RX=PA10) */ RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); /* Enable AFIO clock (for USART1 TX) */ RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO, ENABLE ); /* Enable UART1 clock */ RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1, ENABLE ); /* Select NVIC Preemption Priority Group 4 (for FreeRTOS) */ NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); /* Enable the USART1 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = configLIBRARY_LOWEST_INTERRUPT_PRIORITY; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init( &NVIC_InitStructure ); /* Configure USART1 GPIO PINs (TX=PA9, RX=PA10) */ /* Configure USART1 Tx as alternate function push-pull */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); /* Configure USART1 Rx as input floating */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); /* Configure USART1 parameters 115200 8n1 noHW Tx+Rx*/ USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init( USART1, &USART_InitStructure ); USART_ITConfig( USART1, USART_IT_RXNE, ENABLE ); USART_Cmd( USART1, ENABLE ); } uint8_t MODBUS_GetChar( uint8_t *pcRxedChar ) { if( xQueueReceive( MODBUS_RxQueue, pcRxedChar, 0 ) ) { return pdTRUE; } else { return pdFALSE; } } void MODBUS_PutString( const uint8_t * pcString ) { uint8_t *pxNext; pxNext = ( uint8_t * ) pcString; while( *pxNext ) { MODBUS_PutChar( *pxNext ); pxNext++; } } uint8_t MODBUS_PutChar( uint8_t cOutChar ) { uint8_t xReturn; if( xQueueSend( MODBUS_TxQueue, &cOutChar, 10 ) == pdPASS ) { xReturn = pdPASS; USART_ITConfig( USART1, USART_IT_TXE, ENABLE ); } else { xReturn = pdFAIL; } return xReturn; } void USART1_IRQHandler( void ) { portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; uint8_t cChar; if( USART_GetITStatus( USART1, USART_IT_TXE ) == SET ) { if( xQueueReceiveFromISR( MODBUS_TxQueue, &cChar, &xHigherPriorityTaskWoken ) == pdTRUE ) { USART_SendData( USART1, cChar ); } else { USART_ITConfig( USART1, USART_IT_TXE, DISABLE ); } } if( USART_GetITStatus( USART1, USART_IT_RXNE ) == SET ) { cChar = USART_ReceiveData( USART1 ); xQueueSendFromISR( MODBUS_RxQueue, &cChar, &xHigherPriorityTaskWoken ); } portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); } Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться