Обучение STM32. Подключаем к микроконтроллеру STM32 бесконтактный индуктивный датчик.

В этом уроке по микроконтроллеру STM32 вы научитесь:

  • подключать индуктивный датчик
  • конфигурировать порты на вход в CubeMX
  • включить прерывание по сигналу с порта

 

Что такое бесконтактный индуктивный датчик 

Рис.1. Бесконтактный индуктивный датчик  Бесконтактные индуктивные датчики используются в основном в автоматизации технологических процессов для обнаружения металлических предметов на определенной дистанции. Возьмем для нашего случая цилиндрический датчик на напряжение 24В постоянного тока PNP-типа c нормально разомкнутой цепью коммутации нагрузки. PNP-тип означает тип  выходного транзистора и определяет способ подключения нагрузки.

 

Подключение индуктивного датчика

Рис.2. Типовая схема работы PNP датчика Все типовые индуктивные датчики имеют три-четыре провода: коричневый +24В, синий - 0, черный провод - нормально открытый контакт и белый - нормально закрытый контакт. У нас датчик трехпроводный с НО-контактом. При приближении металла к активной части датчика на черном проводе должен появиться потенциал +24В у датчика PNP-типа. 

 

Рис.3. Не инвертирующая схема включения датчика через оптрон На входе STM32 логическая единица соответствует напряжению 3.3В. Для преобразования уровня сигнала будем использовать стандартную оптронную развязку с элементами защиты и помехоподавлением на входе. Первая схема является неинвертирующей: на STM32 по умолчанию выдается логический нуль, при срабатывании датчика выдается логическая единица. Но помехозащищенность этой схемы невелика, т.к. по земле могут пойти помехи в порт микроконтроллера. И тем не менее эта схема вполне рабочая и использована автором в промышленной установке.

 

Рис.4. Инвертирующая схема включения датчика через оптрон Вторая схема является инвертирующей: на STM32 по умолчанию выдается логическая единица, при срабатывании датчика выдается логический нуль. В принципе, это не является проблемой в обработке состояния входа микроконтроллера. Тем более, что при включении прерывания в STM32 имеется возможность отслеживать нарастающий или спадающий фронт импульса. Но если не использовать прерывания по входам, то надо об этом помнить.

 

Настройка внешнего прерывания по входному сигналу в CubeMX

Рис.5. Настройка входов и выходов микроконтроллера STM32 в CubeMX Наконец перейдем теперь в CubeMX, где выбираем тип нашего микроконтроллера STM32F100RBT6B. Перейдем на вкладку Pinout назначим порту C0 режим работы GPIO_EXTI0 - отработка внешнего прерывания. Порт PC8 (подключен к синему светодиоду) настроим на выход (GPIO_Output) для сигнализации о срабатывании индуктивного датчика.

 

Рис.6. Конфигурирование выводов микроконтроллера  Перейдем на вкладку Configuration и проверим конфигурацию модуля GPIO. Порт PC0 автоматически настроен на внешнее прерывание по нарастающему фронту сигнала и не притянут ни к земле, ни к питанию. Оставим это как есть. С портом PC8 тоже все понятно, он выходной. CubeMX постарался за нас и сконфигурировал все как нужно.

 

Рис.7. Включение прерывания по входном сигналу микроконтроллера  На вкладке Configuration теперь проверим конфигурацию модуля NVIC. Порт PC0 сидит на линии прерывания 0 - общей для всех нулевых выводов групп портов A, B, C... Так уж устроен STM32. Включим прерывание для жтой линии галочкой напротив EXTI line0 interrupt. Здесь же можно установить приоритеты прерываний относительно других событий. Очень удобно.

 

Остальные настройки оставим без изменений и сгенерируем код, например для KEIL. В папке Src появился основной файл main.c, вспомогательный файл конфигурирования периферии stm32f1xx_hal_msp.c и файл прерываний stm32f1xx_it.c.

 

Включение-выключение светодиода по внешнему сигналу от индуктивного датчика

Зайдем в файл прерываний stm32f1xx_it.c и в функции обработки прерываний линии 0 запишем наш код между строчками /* USER CODE BEGIN EXTI0_IRQn 0 */ и /* USER CODE END EXTI0_IRQn 0 */ для сохранения пользовательского кода при перегенерации проекта из CubeMX .

/* stm32f1xx_it.c */
/**
* @brief This function handles EXTI line0 interrupt.
*/
void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */
 
	//PC0 - signal
	EXTI->PR|=0x00; //сбрасываем флаг прерывания на линии 0
 
	//задержка считается по формуле: 1 тик = (1/8000000 MHz)=0,000000125 сек;
	//10 ms = 80000 * 0,000000125 = 0x13880h * 0,000000125;
 
	__IO uint32_t nCount = 0x13880;
	while (nCount--) //включаем задержку на срабатывание датчика 10 ms
	{
	}
 
	if (GPIOC->IDR & GPIO_IDR_IDR0) //проверяем состояние порта PC0 после задержки
		{
			//если в порте PC0 есть сигнал от датчика после задержки, значит это не помеха
			GPIOC->ODR^=GPIO_Pin_8; //переключаем светодиод на порту PC8
			//это аналогично HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8);
		}
 
  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  /* USER CODE BEGIN EXTI0_IRQn 1 */
 
  /* USER CODE END EXTI0_IRQn 1 */
}

 

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

При обработке прерываний здесь использованы функции непосредственной работы с регистрами, что явно быстрее будут отрабатываться. HAL-овские функции использовать здесь вполне уместно, но FreeRTOS-овские функции (например, osDelay) здесь не работают без подключения библиотек FreeRTOS  - да это и не нужно, будем экономить память. Задержка 10 миллисекунд устанавливается в качестве защиты от помех, ведь любая помеха может вызвать прерывание. И если через 10 мс сигнал от датчика будет поступать, то значит это была не помеха. Это конечно примитивная, но все-таки проверка на помеху. Задержка в обработчике прерывания крайне нежелательна и приводится здесь в качестве примера. Но для простых применений такое решение можно использовать.

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

/* main.c */

	uint8_t buffer[3];
	uint8_t count = 0;
  /* Infinite loop */
 
  for(;;)
  {
		//Проверка порта PC0
		buffer[count] = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_0);
		//если все элементы массива buffer[0...2] будут равны 1, то на PC0 пришел сигнал от датчика
		if (buffer[0] == 0x01 && buffer[1] == 0x01 && buffer[2] == 0x01)
		{
			HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET); //включаем светодиод
		}
		else
		{
			HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET); //отключаем светодиод
		}
 
		count++;
		if (count == 3) // при переполнении счетчика обнуляем его и заполняем снова - кольцевой алгоритм
		{
			count = 0;
		}
    osDelay(5); //задержка 5 мс для защиты от помех - можно изменять это значение для более лучшей настройки от помех
  }

 

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

Здесь реализована бесконечная проверка на заполнение массива единицами. Если он весь заполняется единицами, это значит датчик сработал и металл находится вблизи него некоторое время. Массив можно сделать любого размера, но чем больше размер, тем больше должен находится металл около датчика. Это все проверяется экспериметальным путем. При массиве уже из 5 элементов этот алгоритм обеспечивает более-менее хорошую помехозащищенность даже при постоянном дребезге контактов при подключении проводников от датчика.

Код проекта доступен по кнопке Скачать пример для всех пользователей.

Поделиться ссылкой на статью

CADLife - лучший инженерный опыт, бесплатные уроки и обучение Solidworks, Creo, Pro/Engineer, STM32

Комментарии к статье

8 месяцев назад
Существует ли на HAL функция замещающая EXTI->PR|=0x00; //сбрасываем флаг прерывания на линии 0
p.s.
(шутка) - "наHAL"
- сам нахал.
а не сброс флага прерывания выполняется записью 1 в соответсвующий розряд региста PR?

Администратор

8 месяцев назад
На HALе я нашел вот такие абсолютно одинаковые функции (на проверку нет времени, к сожалению):
__HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_X);
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_X);

Их функционал одинаковый: EXTI->PR =(GPIO_PIN_X)

Рядом с этими функциями лежит также интересная функция программной генерации внешнего прерывания: __HAL_GPIO_EXTI_GENERATE_SWIT(GPIO_PIN_X);

Надеюсь ответил на вопрос...

Написать ответ...