понеділок, 28 листопада 2016 р.

Далекомір HC-SR04 -- зовнішні переривання EXTI/HAL

Подивимося, що в порівнянні із використанням CMSIS, надає нам STM32CubeMX разом із HAL для організації обробки переривань від зовнішніх джерел, традиційно -- реалізувавши приклад із попереднього CMSIS-поста.

Периферія сконфігурована як і раніше:
  • Trig -- пін PA10,  вивід. Ми керуватимемо ним третім каналом таймера TIM1.
  • Echo -- PA6, генеруємо переривання.
"Малюємо" відповідну конфігурацію:


Пам'ятаємо, що треба сконфігурувати таймер -- тактування внутрішнє, ШІМ на третьому каналі, автоматична зупинка (OPM).
Налаштування таймера ті ж, що і в попередньому пості:

Клікабельно!

Конфігурація пінів теж очевидна та (крім імен) встановлюється автоматично:

Клікабельно!
Клікабельно!
Залишається дозволити відповідні переривання і можна генерувати код:

Шукаємо "галочки" в стовпчику Enabled. :-)
Клікабельно!

Логіка програми залишилася тією ж, хіба що:
  • Керування пінами та іншою периферією здійснюється, в основному, звертанням до функцій HAL а не маніпуляцію регістрами.
  • Обробники переривань реалізовані в стилі HAL -- заміщено відповідні Callback-функції.
Виглядають ці обробники так:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
 if (GPIO_Pin == GPIO_PIN_6)
 {
  switch (state) {
  case WAITING_FOR_ECHO_START_S: {
   echo_start_ticks = TIM1->CNT;
   state = WAITING_FOR_ECHO_STOP_S;
   disable_capture_rising();
   enable_capture_falling();
   break;
  }
  case WAITING_FOR_ECHO_STOP_S: {
   echo_finish_ticks = TIM1->CNT;
   measured_time = echo_finish_ticks - echo_start_ticks;
   state = READING_DATA_S;
   disable_capture_falling();
   break;
  }
  default:
   puts("Unexpected signal on EXTI"); // Задовга операція, щоб тут залишати, якщо часто виконуватиметься
  }
 }
}

void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
 if( htim->Instance == TIM1 )
 {
  TIM_CCxChannelCmd(htim->Instance,TIM_CHANNEL_3, TIM_CCx_DISABLE);
 }

}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
 if( htim->Instance == TIM1 )
 {
  switch (state) {
  case WAITING_FOR_ECHO_START_S:
   state = ECHO_TIMEOUT_S;
   break;
  case WAITING_FOR_ECHO_STOP_S:
   state = ECHO_NOT_WENT_LOW_S;
   break;
  case IDLE_S:
  default:
   ;
   //puts("Unexpected status");
  }
 }
}

Як видно, логіка їх роботи та ж, що і в попередньому пості, лише немає явної маніпуляції бітом Pending Interrupt.

Перерахування, що описує стан програми, відповідні змінні та функції enable_capture_falling() і т.д. -- такі ж, як і раніше, тому тут їх не повторюватиму. Наведу int main():


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
int main(void) {

 /* USER CODE BEGIN 1 */

 /* 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_TIM1_Init();

 /* USER CODE BEGIN 2 */
 // Перевіримо, чи лінія Echo в нулі поки ми ще не подали імпульс
 if (HAL_GPIO_ReadPin(ECHO_EXTI_PA6_GPIO_Port, ECHO_EXTI_PA6_Pin)
   == GPIO_PIN_SET) {
  on_error("Error -- Echo line is high, though no impulse was given.",
    true);
 }
 puts("Starting");
 /* USER CODE END 2 */

 /* Infinite loop */
 /* USER CODE BEGIN WHILE */
 HAL_NVIC_ClearPendingIRQ(EXTI9_5_IRQn);
 HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
 while (1) {
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_3); // Біт OPM встановлено, але запускаємо як PWM
  HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);
  state = WAITING_FOR_ECHO_START_S;
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

  enable_capture_rising();
  while ( TIM1->CCER & TIM_CCER_CC3E ) // Не придумав, як тут HAL використати, не ускладнюючи коду додатковими прапорцями...
   // Поки вивід дозволено -- чекаємо
   ;

  if( HAL_GPIO_ReadPin(TRIG_PA10_TIM1_CH3_GPIO_Port, TRIG_PA10_TIM1_CH3_Pin) == GPIO_PIN_SET  )
  {
   state = TRIG_NOT_WENT_LOW_S;
   puts("Trigger does not went low!");
   HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);
   state = IDLE_S;
   HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
   continue;
  } else {
   while (state != READING_DATA_S && state != ECHO_TIMEOUT_S
     && state != ECHO_NOT_WENT_LOW_S);
   HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);
   state_t state_copy = state;
   state = IDLE_S;
   HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
   if ( HAL_GPIO_ReadPin(ECHO_EXTI_PA6_GPIO_Port, ECHO_EXTI_PA6_Pin) == GPIO_PIN_SET
     || state == ECHO_NOT_WENT_LOW_S) {
    puts("Echo line does not went low!");
    if (state_copy == ECHO_NOT_WENT_LOW_S)
     puts("\tConfirmed from interrupt");
   }

   if (state_copy == ECHO_TIMEOUT_S) {
    printf("Echo timeout!\n");
   } else {
    if (measured_time > TOO_FAR_TIMEOUT) {
     printf("Tooo far -- no echo received, wating for %"PRIu32" mks\n", measured_time);
    } else
    {
     uint32_t distance_mm = (measured_time*10)/58;
     printf("Distance: %"PRIu32" mm, measured time: %"PRIu32" us\n",distance_mm, measured_time);
    }
   }
  }
  HAL_Delay(200);


  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

 }
 /* USER CODE END 3 */

}

В принципі, все очевидно. В одному місці, там де очікуємо на кінець імпульсу Trig, (рядок 40), залишено низькорівневий код -- HAL не дає простого способу це зробити, у решті -- менш-більш звичні функції HAL. 

Всі використані функції вже було розглянуто в попередніх постах, про таймери та далекоміри. Певний інтерес може представляти хіба згенерована Cube MX_GPIO_Init():


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __GPIOD_CLK_ENABLE();
  __GPIOA_CLK_ENABLE();
  __GPIOC_CLK_ENABLE();

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = ECHO_EXTI_PA6_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(ECHO_EXTI_PA6_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : PCPin PCPin */
  GPIO_InitStruct.Pin = BLUE_LED_PC8_Pin|GREEN_LED_PC9_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

}

В рядку 13 вказується, що функцією піна PA6 буде генерувати переривання у відповідь на фронт сигналу (це стан по замовчуванню -- далі ми явно ним маніпулюватимемо, як описано в попередньому пості).

Власне, все. Доволі просто. Єдине, якби не розуміння деталей на рівні CMSIS, досягнути того ж міркуючи виключно на рівні абстракції HAL, було б багато складніше! Привід задуматися, звичайно. Але не дуже вагомий. :-)

Повний main.c сенсу наводити тут немає -- важливі частини наведені вище, а кому цікаво -- див. проект. Скачати його, проект, можна тут.

На разі, із ультразвуковим далекоміром, нарешті -- все, 

Дякую за увагу!

Немає коментарів:

Дописати коментар