Sign in to follow this  
tw1911

CubeHAL SPI и LIS3DH

31 posts in this topic

tw1911    1

Добрые день! Простите если вопрос нубский.

Пытаюсь подружиться с CubeMX и акселерометром.

В общем начну с простого, по обычному SPI, без DMA и прерываний пытаюсь получить байт идентификатора по адресу 0x0F, он по идее должен быть 3F, но почему то код возвращает FF, хотя, если тот же кусок кода написать второй раз следом, получается нормально. Смотрю результат через отладчик. Что то я видимо не понимаю...

#include "main.h"
#include "stm32f4xx_hal.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

int main(void)
{

  /* USER CODE BEGIN 1 */
	uint8_t SPI_Out[2] = {0x8F,0x00};
	uint8_t SPI_In[8];
  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();

  /* USER CODE BEGIN 2 */
	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
	HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, sizeof(SPI_In), 100);
	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

    /**Configure the main internal regulator output voltage 
    */
  __HAL_RCC_PWR_CLK_ENABLE();

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV4;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure the Systick interrupt time 
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick 
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* SPI1 init function */
static void MX_SPI1_Init(void)
{

  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }

}

/** Configure pins as 
        * Analog 
        * Input 
        * Output
        * EVENT_OUT
        * EXTI
*/
static void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);

  /*Configure GPIO pin : PE3 */
  GPIO_InitStruct.Pin = GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @param  None
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler */
  /* User can add his own implementation to report the HAL error return state */
  while(1) 
  {
  }
  /* USER CODE END Error_Handler */ 
}

#ifdef USE_FULL_ASSERT

/**
   * @brief Reports the name of the source file and the source line number
   * where the assert_param error has occurred.
   * @param file: pointer to the source file name
   * @param line: assert_param error line source number
   * @retval None
   */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */

}

#endif

/**
  * @}
  */ 

/**
  * @}
*/ 

И еще, я не особо уверен в смысле аргумента timout. Я так понимаю, это время которое он ждет в случае занятости интерфейса?

Share this post


Link to post
Share on other sites
tw1911    1
1 час назад, dm37 сказал:

посмотрите, здесь как раз рассматривается работа с акселерометром, правда на SPL (урок 28, а также предыдущие)

https://www.youtube.com/watch?v=hfAdH1Q_zzU&index=28&list=PL8OgDYWys_b6XtOjCejd37aVv0ic24jqV

Да так то он работает. и делал я так же. Просто хочу перевести это все на HAL, а тут грабли. Самое забавное, что с DMA оно вроде бы работает. Но как то странно, почему то CS наоборот работает. Короче надо сначала с базовым SPI разобраться.

Может кто то подскажет какие особенности работы или скинет рабочий пример?

Share this post


Link to post
Share on other sites

DC/DC-преобразователи MORNSUN R3. Повышенная надёжность - сниженная стоимость!

Особенностью преобразователей R3 являются улучшенные технические характеристики, повышенная надёжность и сниженная стоимость. Электрическая прочность изоляции представленных преобразователей не менее 1500 В, а температурный диапазон применения -40…105ºС.

Подробнее...

dm37    54

в строке

HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, sizeof(SPI_In), 100);

вы создаёте массив SPI_Out[2] на два байта, а отправляете 8 байт (размерность массива SPI_In[8]). Т.е. отправляете 0x8F, 0x00 и что-то ещё (6 байт мусора)

попробуйте так

HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, 2, 100);

 

Share this post


Link to post
Share on other sites
tw1911    1

Да так то же не работает. На самом деле, если написать

	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
	HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, sizeof(SPI_In), 100);
	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
	HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, sizeof(SPI_In), 100);
	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);

И поставить бряк в 3 и 6 строки, то в первом бряке будет значение FF,FF, а во втором FF,3F, то есть по идее то что надо. Но какого оно так работает вообще не понятно.

UPDATE

В общем опытным путем установил, что оно работает, если в отдельном CS ON-OFF просто передать на чип хоть что нибудь.

Но все равно, может кто нибудь объяснить что это ???

Edited by tw1911

Share this post


Link to post
Share on other sites

Новые методы уменьшения дрейфа нуля в малошумящих АЦП Texas Instruments

Стабилизация с помощью двойного прерывания с двойным контролем в новых АЦП ADS1235 производства Texas Instruments позволяет снизить до минимума напряжение смещения и достичь высокой точности в измерительных цепях современных прецизионных цифровых приборов.

Подробнее...

snn_krs    54

Все правильно. Почитайте внимательно описание акселерометра. Правильный ответ он отсылает после того, как
 примет команду. Во время отправки команды мастер получает то, что было в сдвиговом регистре акселерометра.
 Это обычно FF. Если этот байт не считать, будет ошибка Буфер полный.
 В некоторых описаниях это называется Dummy read.
 Происходит это потому что в SPI сдвиговые регистры мастера и слейва соединяются в кольцо, и передача и прием

и передача идут одновременно.

Share this post


Link to post
Share on other sites
mail_robot    1485

Для того чтобы не получать ерунду по SPI надо почитать мануал и выяснить, что ответ поступает после команды, а не во время. Накой ляд вы тогда используете одновременную передачу с приемом?

Достаточно написать друг за другом два простых оператора HAL_SPI_Transmitt и HAL_SPI_Recieve и все нормально заработает.

HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit((&hspi1, SPI_Out, 2, 100);
HAL_SPI_Receive(&hspi1, SPI_In, 8, 100);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);

И нафига перед каждой посылкой вычислять размер массивов, если их размер и так известен и не изменяется? (на работу не влияет)

Share this post


Link to post
Share on other sites
tw1911    1

Ну это, я передаю сначала адрес, там 8F(0F|1000000) соответственно в ответе будет FF, а когда я толкаю второй байт(00), в ответе должна быть по идее содержимое регистра. Разве не так? Вроде бы рисунок 7 на странице 23 к мануалу акселерометра.

12 часа назад, mail_robot сказал:

И нафига перед каждой посылкой вычислять размер массивов, если их размер и так известен и не изменяется? (на работу не влияет)

Это для того, что бы не менять размер все время и не ошибаться все время.

Share this post


Link to post
Share on other sites
mail_robot    1485

а если предположить что между командой и ответом должна быть пауза? Тогда ваши умозаключения как минимум не верны. Толкать второй байт для приема не обязательно. SPI может прекрасно тактироваться и на приеме мастером.

По поводу размера. Гораздо экономичнее с точки зрения ресурсов было бы обозначить размер массива константой и использовать именно ее и для определения массива и для определения его размера в байтах. Таким образом все ошибки свелись бы к нулю, а все вычисления взял бы на себя только компилятор. Просто совет на будущее

Share this post


Link to post
Share on other sites
tw1911    1
55 минут назад, mail_robot сказал:

а если предположить что между командой и ответом должна быть пауза? Тогда ваши умозаключения как минимум не верны. Толкать второй байт для приема не обязательно. SPI может прекрасно тактироваться и на приеме мастером.

В даташите нет ничего про паузу. Рисунок же есть. К том же через SPL все и без паузы работает.

Share this post


Link to post
Share on other sites
Yurkin2015    341

Не надо никакой паузы. Надо отослать всего 2 байта: первый байт команда 0x8F, второй - абы какой, без разницы. При передаче первого байта выход LIS3DH в третьем состоянии, при передаче второго байта - датчик выталкивает в процессор нужный байт 0х33.

Вот если сделать так, что будет в приёмном буфере?

HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, 2, 100);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);

 

Share this post


Link to post
Share on other sites
mail_robot    1485

пара нолей. Передастся команда, но одновременно будут приняты 2 пустых байта синхронно с передачей. Эта функция передает и принимает одновременно по одним и тем же тактам. Используется крайне редко

Share this post


Link to post
Share on other sites
Yurkin2015    341

Как происходит обмен байтами с датчиком по SPI я уже написал в своём посте.

55 минут назад, mail_robot сказал:

будут приняты 2 пустых байта

Мэйл-робот, Вы, видимо, не в теме, но, извините, у меня совершенно нет времени что-либо объяснять Вам.

Share this post


Link to post
Share on other sites
tw1911    1
3 часа назад, mail_robot сказал:

пара нолей. Передастся команда, но одновременно будут приняты 2 пустых байта синхронно с передачей. Эта функция передает и принимает одновременно по одним и тем же тактам. Используется крайне редко

Так то оно так, но в данном конкретном случае как раз первый байт содержит команду, и мне абсолютно без разницы что там содержится в ответе, а вот второй передают что угодно, но принимать в это время должен данные из регистра WHO_AM_I. Но почему то оно не работает так первый раз. А второй раз работает. Причем паузу в разных местах я пробовал вставлять вплоть до секунды, и это не дает никакого результата.

Edited by tw1911

Share this post


Link to post
Share on other sites
tw1911    1
5 часов назад, Yurkin2015 сказал:

Не надо никакой паузы. Надо отослать всего 2 байта: первый байт команда 0x8F, второй - абы какой, без разницы. При передаче первого байта выход LIS3DH в третьем состоянии, при передаче второго байта - датчик выталкивает в процессор нужный байт 0х33.

Вот если сделать так, что будет в приёмном буфере?


HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, 2, 100);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);

 

Вот так в приемном буфере будет FF,FF. А вот если написать

HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, 2, 100);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET); //в этом местее FF,FF
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, 2, 100);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);//а вот тут FF,3F

Что то совсем непонятное(

Share this post


Link to post
Share on other sites
tw1911    1
15 минут назад, dm37 сказал:

нашёл подобный вопрос, посмотрите, автор вроде разобрался

http://forum.cxem.net/index.php?/topic/153734-работа-с-hal_spi/

погуглил немного, ваша проблема достаточно распространённая

Я не совсем понимаю о какой предделителе идет речь в ссылке. Если об BaudRatePrescaler, то нет, это не мой случай.

В общем пока получается так.

Первый раз отправляем хоть что то. Потом отправляем нормальный адрес и считываем.

Share this post


Link to post
Share on other sites
mail_robot    1485
14 часа назад, Yurkin2015 сказал:

Мэйл-робот, Вы, видимо, не в теме

дорогой мой, человек который в теме. Со стороны мастера передача абы какого байта и прием ничем не отличаются. Но можете тупить свою тупилку дальше

Share this post


Link to post
Share on other sites
tw1911    1
8 часов назад, mail_robot сказал:

дорогой мой, человек который в теме. Со стороны мастера передача абы какого байта и прием ничем не отличаются. Но можете тупить свою тупилку дальше

Слушайте, как Вы написали один хрен не работает.

Share this post


Link to post
Share on other sites
dm37    54

добрался до отладочной платы stm32f429i-disco (правда на ней установлен L3GD20)
всё работает.

Сначала проверил при тактировании от встроенного генератора (установки HAL по умолчанию 16 МГц) - работает.
Потом от внешнего кварца 8МГц (разогнал до 180 Мгц) - работает.

проблемы начинаются при неправильном тактировании SPI (hspi1.Init.BaudRatePrescaler)

to tw1911
а какая у вас отладочная плата или какой контроллер?

попробуйте HAL с тактированием по умолчанию (внутренний генератор)

p.s.
у меня cube обновлён до последней версии.
библиотеки для STM32F4 обновлены до версии 1.13.1

Edited by dm37

Share this post


Link to post
Share on other sites
tw1911    1

У меня stm32f407i-disco. Могу файлик куба выложить. Но тактирование у меня от внешнего 8Мгц кварца.

Мне правда кажется, что вы не совсем понимаете мою проблему. У меня как бы не совсем передача не работает. У меня она работает просто только после того, как я хоть что нибудь передам на аксель и дерну CS.

	uint8_t SPI_Out[2] = {0x8F,0x00};
	uint8_t SPI_In[2];
	uint8_t dummyAdr = 0xFF;
	HAL_Delay(400);
	
	if(HAL_SPI_GetState(&hspi1)==HAL_SPI_STATE_READY){
		HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
		HAL_SPI_Transmit(&hspi1, &dummyAdr, 2, 100);
		HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
		HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
		HAL_SPI_TransmitReceive(&hspi1, SPI_Out, SPI_In, 2, 100);
		HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
	}

К примеру вот такой код отлично работает при любых настройках. Но мне нужна правда. Или место в доках, где написано что оно должно работать именно так, ну что то такое, что объясняет происходящее. А то мне как то боязно двигаться дальше без четкого понимания происходящего.

SPI_HAL.ioc

UPD Проверил и с дефолтной настройой тактирования от встроенного RC - та же песня.

UPD2 Проверил то же самое с DMA. Все так же.

Edited by tw1911

Share this post


Link to post
Share on other sites
dm37    54

Если говорите, что с использованием SPL всё работает, остаётся осциллографом посмотреть, что происходит на выводах при использовании SPL и HAL. Попытаться локализовать проблему. Может компилятор выделывается, может акселерометр капризный (нужна задержка после подачи питания, задержка между CS и SPC, ...)

Немного не понятно как будет реально работать подряд идущие команды

HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);

после

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

а будет ли вообще меняться CS? И как этот сигнал воспримет акселерометр.
 

p.s.

в datasheet на LIS3DH (стр.11) указано, что Ton (Turn-on time) = 1 ms. Может он включиться не успевает?

Edited by dm37

Share this post


Link to post
Share on other sites
tw1911    1
3 часа назад, dm37 сказал:

в datasheet на LIS3DH (стр.11) указано, что Ton (Turn-on time) = 1 ms. Может он включиться не успевает?

А задержки в 400мс ему типа не хватает?

Share this post


Link to post
Share on other sites
ukr823f    4

Вот из проекта на HAL, который работает

Функции разнесены по файлам, но можете их и в main.c все поставить

1)

void setReg(uint8_t address, uint8_t value) 
		{
 			CS_RESET;
 			HAL_SPI_Transmit(&hspi1,&address ,1,5);
 			HAL_SPI_Transmit(&hspi1,&value ,1,5); 			
			CS_SET;
 		}
		
		uint8_t getReg(uint8_t address)
		{
 			uint8_t data1=0;
 			address|=(1<<7);
 			CS_RESET;
 			HAL_SPI_Transmit(&hspi1,&address ,1,5);
 			HAL_SPI_Receive(&hspi1,&data1 ,1,5);
 		  CS_SET;
 			return data1;
 		}

2)Функции для выдёргивания значений

    //функция включения акселерометра
    void enable_302dlh(void)
 	{
 		setReg(LIS302DL_CTRL_REG1,LIS302DL_Normal_mode50Hz_xyz);  // LIS302DL_Lowpower_mode10hz_xyz
 	}

	//функция получения данных для оси X	
	signed int x_axis_out (void)
 	   {
 	   for(char i=0;i<100;i++)
 	      {
 	       x_h=getReg(0x29);
 	       x_l=getReg(0x28);
 	       x=(x_h*256)+x_l;
 	   	   x_summ=x_summ+x;
 		  }
         	x_filtered=(1-K)*x_filtered + K*(x_summ/100);
 	       	x_summ=0;
        	return x_filtered;
 	   }

Ну и собственно главная функция которой пользуемся

x_filtered=x_axis_out();

 

То есть вызываем функцию - -> x_filtered=x_axis_out();  и получаем в переменную готовые данные.

Edited by ukr823f

Share this post


Link to post
Share on other sites
mail_robot    1485
9 часов назад, ukr823f сказал:

HAL_SPI_Transmit(&hspi1,&address ,1,5);

HAL_SPI_Receive(&hspi1,&data1 ,1,5);

собсна я им так и предлагал сделать с самого начала, но чет аплодисментов не поступило. Наверное ребята знают более мощные рецепты

Share this post


Link to post
Share on other sites

Your content will need to be approved by a moderator

Guest
You are commenting as a guest. If you have an account, please sign in.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoticons maximum are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this