Jump to content

    

STM32, USART на прерываниях и очередях, проблемы с передачей

Есть проектик, 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(;;)
{
}
}

Edited by asdus

Share this post


Link to post
Share on other sites

Во первых, вы начинаете использовать queue до старта шедулера, это грубая ошибка.

И на сколько мне помнится надо сбрасывать соответствующий pending bit при обработке ISR

Share this post


Link to post
Share on other sites
Во первых, вы начинаете использовать queue до старта шедулера, это грубая ошибка.
Спасибо за подсказку. В оригинальной программе такого небыло, прибил в тестовой.

 

И на сколько мне помнится надо сбрасывать соответствующий pending bit при обработке ISR
GetITStatus уже давно сбрасывает соответствующий ITPendingBit. На всякий случай добавил в тестовую прогу.

 

Запустил тесты, ждем-с...

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

Edited by asdus

Share this post


Link to post
Share on other sites

Смущает вот что:

бит 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 сконфигурирована.

Share this post


Link to post
Share on other sites
Смущает вот что:

бит 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);

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

Share this post


Link to post
Share on other sites

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

Начал осваивать 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 (хотя по логике вроде как должна), а потом уже выполняется после "пинка" из прерывания.

Подскажите это нормально или я где-то накосячил?

Edited by Bass

Share this post


Link to post
Share on other sites
Проблема в том, что при запуске шедулера первый раз выполняется задача USART_RX_TaskHandler, т.е. не останавливается на xSemaphoreTake (хотя по логике вроде как должна), а потом уже выполняется после "пинка" из прерывания.

Подскажите это нормально или я где-то накосячил?

Я тоже только еще изучаю FreeRTOS. Насколько я понял, семафор, как только создан, сразу становится доступен. Поэтому задача USART_RX_TaskHandler сразу захватывает его. Так что это нормально.

Share this post


Link to post
Share on other sites

Есть такая беда у 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)\

Share this post


Link to post
Share on other sites

Ага, нашел сам макрос vSemaphoreCreateBinary, в котором видно, что после создания он сразу отдается:

xSemaphoreGive( ( xSemaphore ) );

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

Share this post


Link to post
Share on other sites

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

- необходимо распределеить память под очередь

- передача одного байта вызывает большой 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;
}

Share this post


Link to post
Share on other sites

Обязательно перепишу на 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 );
}

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this