
Привет, Хабр!
В системах точного позиционирования и измерения угла поворота оптические энкодеры остаются критически важным компонентом, обеспечивающим обратную связь по положению. Среди множества датчиков данной категории, будет рассмотрен HOA0902-11 - это двухканальный фотодатчик, предназначенный для высокоточного измерения углового положения, скорости и направления вращения, его конструкция и схема работы основаны на принципе оптической модуляции света через диск.
Устройство датчика:
Внутри установлен светодиод (ИК-излучатель), который подсвечивает импульсный диск;
С противоположной стороны находятся два фотоприемника (фототранзистора);
Эти два фотоприемника расположены со смещением относительно друг друга (90° фазовый сдвиг).

В данной статье будут рассмотрены
Физические принципы работы HOA092-11;
Схема подключения к микроконтроллеру STM32F030CCTx;
Программная реализация (расчет пройденного пути, скорости и направления движения, а также вывод информации на дисплей).
Технические характеристики HOA092-11
Ссылка на техническую документацию HOA0902-11 [https://static.chipdip.ru/lib/059/DOC000059035.pdf]
Напряжение питания [ 4.5 - 5.5В ];
Тип выхода [ NPN открытый коллектор ];
Слот (зазор) [ 3.2 мм ];
Минимальный механический период (разрешение) [ предназначен для работы с механическим периодом равным 0,036 in (≈ 0.914 мм), что дает разрешение до 0,018 in (≈ 0,457 мм) ];
Температурный диапазон эксплуатации [ от -40°C до +70°C ]|.
Применение
Высокоточные оптические энкодеры;
Системы позиционирования в робототехнике;
Прецизионные системы контроля скорости вращения;
Линейные и ротационные измерительные системы.
Принцип работы HOA0902
Инфракрасный излучатель (IRED) генерирует постоянный поток ИК-излучения;
Кремниевый NPN фототранзистор выполняет функцию приемника излучения;
Оптический зазор между излучателем и приемником обеспечивает зону детектирования.
Режимы работы:
Выход(SPEED(TACH)) - генерирует импульс при каждом пересечении порога освещенности, т.е. как только импульсный диск будет проходить через щель, появляется импульс (ширина 8 микросекунд, частота 125 kHz), как раз этот импульс я и буду обрабатывать в микроконтроллере.

В данном проекте используется импульсный диск он состоит из 16 "окон", за одно прохождение "окна", датчик будет выдавать контроллеру по 2 импульса,
Конструкция датчика и расположение "окон" на диске сделаны так, что когда одно "окно" проходит над датчиком, два приемника оказываются в разных фазах этого "окна".
Один приемник (канал A) находится точно по центру окна, когда другой (канал B) находится уже на краю (или наоборот).
Это приводит к тому, что сигналы с каналов A и B сдвинуты относительно друг друга на 90 электрических градусов.

Зачем это нужно?
Такой сдвиг (квадратура) решает сразу несколько критически важных задач:
1. Определение направления вращения (главная причина)
Это самая важная функция, схема обработки сигнала в датчике смотрит, какой канал опережает.
Если канал A опережает канал B → вращение по часовой стрелке.
Если канал B опережает канал A → вращение против часовой стрелки.
2. Повышение разрешения
Удвоение количества импульсов достигается за счет обработки двух сдвинутых по фазе сигналов, что позволяет извлекать из одной механической конструкции вдвое больше полезной информации, в данной статье датчик HOA0902-11, через который проходит импульсный диск имеющий 16 "окон" при полном обороте, будет выдавать 32 импульса, таким образом получается более точное измерение угла, расстояния и скорости, что позволяет разрешению увеличиваться.
16 импульсов - шаг угла 360° / 16 = 22.5°;
32 импульса - шаг угла 360° / 32 = 11.25°.
3. Повышение помехоустойчивости и надежности Система может проверять соответствие сигналов двух каналов друг другу. Если возникает одиночный ложный импульс на одном канале, которому нет соответствия на другом, его можно отфильтровать как ошибку.
-
Выход направления (DIRECTION) - это логический вывод, который формируется внутри датчика HOA0902-11 из сравнения сигналов А и В, т.е. датчик сам определяет направление вращения и выдает это как отдельный цифровой сигнал:
DIRECTION = 0 (LOW) - вращение вперед (например, по часовой стрелке);
DIRECTION = 1 (HIGHT) - вращение назад (против часовой стрелки).
Таким образом, не нужно в МК сравнивать А и В, чтобы вычислять направление - датчик сам это делает, достаточно просто повесить прерывание.


!!! Очень важная информация, так как датчик HOA0902-11 работает по +5В, подключать напрямую сигналы "SPEED(TACH)" и "DIRECTION(DIR)" к микроконтроллеру STM32 (у которого логика работы по +3В) опасно, есть риск повреждения выводов, в следствии МК может просто выйти из строя.


Для того чтобы напряжение было приемлемым, для МК, необходимо поставить делители напряжения, на сигналы "SPEED" и "DIRECTION".


На данной схеме изображено подключение датчика HOA0902-11 и дисплея, к микроконтроллеру STM32F030CCTx, а также реализация преобразователя со стабилизатором напряжения

Перечень компонентов
Резисторы |
Конденсаторы, чип-дроссели, резонатор |
Микросхемы |
Прочие (АКБ, дисплей, соединители) |
R1, R5, R6, R7, R8- (0805 - 10 кОм ± 5%) |
C1, C5, C6, C8, C10, C13, C14 - 0805 – *(X7R – 50B - 0,1 мкФ ± 10%) |
DD1 - датчик HOA0902-11 |
Блок питания +12V |
R3 - (0805 - 1 кОм ± 5%) |
C2 - (X7R – 50B - 1мкФ ± 10%) |
DD2 - микроконтроллер STM32F030CCT6 |
X1 - Вилка PLD-4 |
R4 - (0805 - 5,6 кОм ± 5%) |
C3, C4 - (X7R – 50B - 12 пФ ± 10%) |
DA1 - преобразователь напряжения MP2315GJ(+12 -- +5V) |
X2 - разъем ОНЦ-БС-1/4 |
R9, - (0805 - 20 Ом ± 5%) |
C7, C15, C17 - (X7R – 50B - 4,7 мкФ ± 10%) |
DA2 - линейный стабилизатор напряжения (+5 -- +3.3V) |
Дисплей - OLED 0.96" 128x64, I2C, 4 pin, монохромный ГОЛУБОЙ |
R10 - (0805 - 100 кОм ± 5%) |
C9, C12 - (Корпус A 10 В - 22 мкФ ± 10% ) |
||
R11 - (0805 - 20 Ом ± 5%) |
C11 - (Корпус C 16 В - 47 мкФ ± 10%) |
||
R12 - (0805 - 39 кОм ± 5%) |
L1 - (10мкФ, CDRH64) |
||
R13 - (0805 - 65 кОм ± 5%) |
BQ1 - кварцевый резонатор - 8 МГц |
||
R14 - (0805 - 7,5 кОм ± 5%) |
Пояснения к схеме
Выводы NRST и BOOT0
Вывод NRST(reset) используется для аппаратного сброса МК, подключается через резистор R7(10кОм) к питанию +3В - подтягивает NRST к логической "1", конденсатор С5(0,1мкФ), формирует RC-цепочку, используется для подавления помех и автосброса при включении питания, данный пример схемы гарантирует корректный старт МК после подачи питания, защищает от ложных срабатываний при скачках напряжения.

Вывод BOOT0 определяет, откуда МК будет загружать программу после сброса:
BOOT0 = 0 - загрузка из Flash-памяти(основной режим работы);
BOOT0 = 1 - загрузка из системной памяти (встроенный загрузчик через UART, I2C, SPI).
В схеме вывод подтянут резистором R8 к земле (логический "0"), это обеспечивает автоматическую загрузку программы из flash-памяти после старта, если потребуется использовать встроенный загрузчик, можно временно подать "1" на BOOT0.
Обвязка питания VCC и VA
МК имеет несколько выводов питания:
VCC - основное цифровое питание (3.3В);
VA - питание аналоговой части (АЦП, компараторы и т.д.).
На выводах VCC и VA уставлен конденсатор С6 (0.1мкФ), он фильтрует высокочастотные помехи, возникающие при переключении логики, конденсатор ставиться как можно ближе к выводам МК, также дополнительно установлен танталовый конденсатор 4.7мкФ, он сглаживает низкочастотные колебания и стабилизирует питание аналоговой части.
Микросхема DA1 - MP2315 представляет собой синхронный понижающий (buck) DC-DC преобразователь с интегрированными силовыми MOSFET-ключами. Высокая частота переключения (до 2.2 МГц), компактный корпус и широкий диапазон входных напряжений (от 4.5 В до 24 В), ссылка на техническую документацию MP2315 [https://www.alldatasheet.com/datasheet-pdf/pdf/1035056/MPS/MP2315.html].
Микросхема DA2 - LP2985 представляет собой малошумящий стабилизатор, предназначен для преобразования входного напряжения +5В в стабильное напряжение +3В, используемое МК и периферийными узлами, ссылка на техническую документацию LP2985 [https://www.alldatasheet.com/datasheet-pdf/pdf/99706/TI/LP2985.html].
Подключение датчика HOA0902-11
Сигнал SPEED(TACH) - подключается через делитель R1 и R3, к выводу МК-12(PA2);
-
Сигнал DIR(DIRECTION) - подключается через делитель R4 и R5, к выводу МК-13(PA3).
Подключение дисплея
Сигнал SDA - подключается к выводу 21(PB10);
Сигнал SCL - подключается к выводу 22(PB11).

Настройка микроконтроллера STM32F030CCTx в CubeIDE

Настройка выводов

Вывод PA2 настроен на внешнее прерывание (EXTI) (выполняет роль триггера для подсчета импульса) к нему подключается сигнал SPEED(TACH) датчика HOA0902-11, каждый раз, когда через окно датчика проходит импульсный диск, датчик формирует импульс, далее он фиксируется аппаратным модулем EXTI, который:
Генерирует прерывание;
Устанавливает флаг encoderDone;
Делегирует дальнейшую обработку в функцию encoder_Handler().
Вывод PA3(DIRECTION) (GPIO_INPUT), используется для определения направления вращения.
Настройка таймеров
TIM3 используется как счетчик временных интервалов, настроен на генерацию прерываний, каждое срабатывание таймера фиксирует количество импульсов, поступивших от энкодера, таймер настраивается на прерывание раз в 100ms.

TIM14 используется в качестве системного диспетчера вывода информации на дисплей, таймер настраивается на генерацию прерываний с периодом 200mS, при каждом срабатывании прерывания происходит вызов функции display_update(), которая обновляет содержимое экрана.

Настройка приоритетов

Настройка Clock

Реализация программного кода
Ссылка на скачивание исходного кода [ https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_исходный_код - Исходный код для Encoder_HOA0902-11_STM32F030CCT6], а так же видео тестирования энкодера [#исскуствомк_тестирование_Encoder].
Библиотеку для работы с дисплеем я взял с [https://github.com/afiskon/stm32-ssd1306/tree/master]
Функция display_init()
Выполняет начальную инициализацию дисплея, подключенного по интерфейсу I2C(на базе контроллера SSD1306)
Инициализируется драйвер дисплея;
Выполняется заливка экрана черным цветом;
На дисплее на 2 секунды отображается стартовый экран с надписью ''ChipCraft";
После задержки экран очищается для дальнейшей работы.
display_init()
void display_init(void) {
ssd1306_Init();
ssd1306_Fill(Black);
ssd1306_SetCursor(20, 25);
ssd1306_WriteString("ChipCraft", Font_11x18, White);
ssd1306_UpdateScreen();
HAL_Delay(2000);
ssd1306_Fill(Black);
ssd1306_UpdateScreen();
}
Функция encoder_Handler() обработчик инкрементального энкодера
Вызывается при установке флага encoderDone, который формируется в прерывании внешнего входа (EXTI);
-
Определяется направление вращения вала датчика по состоянию пина DIRECTION_Pin:
RESET - движение вперед (инкремент счетчика);
SET - движение назад (декремент счетчика).
Вызывается метод get_distance_m() для расчета дистанции.
encoder_Handler()
void encoder_Handler(void) {
if(encoderDone){
encoderDone = 0;
//Читаю DIR состояние (RESET = forward(движение вперед), SET = backward(движение назад))
GPIO_PinState dir_state = HAL_GPIO_ReadPin(DIRECTION_GPIO_Port, DIRECTION_Pin);
if(dir_state == GPIO_PIN_RESET){
pulse_counter++;
pulse_counter_window++;
}
else{
pulse_counter--;
pulse_counter_window--;
}
}
distance_m = get_distance_m();
}
Тест в отладке

Функция display_update()
Отвечает за визуализацию информации на дисплее:
Экран предварительно очищается с помощью ssd1306_Fill(Black);
В верхней части по центру отображается надпись "Encoder";
-
Ниже последовательно выводятся:
количество импульсов;
дистанция;
скорость.
Буфер графики передается на дисплей вызовом ssd1306_UpdateScreen()
display_update()
void display_update(int32_t pulses, float distance, float speed) {
char buf[32];
ssd1306_Fill(Black);
ssd1306_SetCursor(25, 2);
ssd1306_WriteString("Encoder" ,Font_11x18, White);
sprintf(buf, "Pulses: %ld", pulses);
ssd1306_SetCursor(2, 22);
ssd1306_WriteString(buf, Font_7x10, White);
sprintf(buf, "Dist: %.2f m", distance);
ssd1306_SetCursor(2, 36);
ssd1306_WriteString(buf, Font_7x10, White);
sprintf(buf, "Speed: %.2f m/s", speed);
ssd1306_SetCursor(2, 50);
ssd1306_WriteString(buf, Font_7x10, White);
ssd1306_UpdateScreen();
}

Функция float get_distance_m()
Возвращает текущее значение пройденного пути в метрах, расчет выполняется как произведение общего числа импульсов pulse_counter на метрический коэффициент DIST_PER_STEP_M, который определяется из геометрии колеса и количества "окон" импульсного диска.
Формула расчета дистанции
где:
N - Число импульсов;
D - Диаметр колеса;
PPR - количество импульсов на оборот.
HAL_TIM_PeriodElapsedCallback Функция обратного вызова, выполняется при переполнении таймера
Каждые WINDOW_TIME_S секунд фиксируется количество импульсов в pulse_counter_window;
После чтения счетчик сбрасывается;
-
Рассчитывается скорость
Формула расчета скорости
окно - это фиксированный промежуток времени, в течении которого производится расчет количества импульсов от датчика, в данном проекте будет 100 мс (0.1 с)
Скорость определяется как отношение пройденного пути за окно.
где S - путь в окне, t - длительность окна.
также в данной функции происходит вызывается display_update(), для визуализации текущих значений на дисплее.
HAL_TIM_PeriodElapsedCallback()
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM3) {
int32_t pulses = pulse_counter_window;
pulse_counter_window = 0;
float dist = (float)pulses * DIST_PER_STEP_M;
dist_speed = dist / WINDOW_TIME_S;
}
else if(htim->Instance == TIM14) {
display_update(pulse_counter, distance_m, dist_speed);
}
}
Модуль process_Encoder
process_Encoder.c
#include "./Project/process_Encoder.h"
#include "./Project/shared.h"
#include "./Project/ssd1306.h"
#include "./Project/ssd1306_fonts.h"
#include "main.h"
#include <stdlib.h>//abs
#include <string.h>//memset
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#define WINDOW_TIME_S 0.1
#define WHEEL_DIAMETR_M 0.190 //Диаметр колеса 0.190
#define NOTCHES_PER_REV 16 //Диск - 16 насечек
#define PULSES_PER_REV (NOTCHES_PER_REV * 2)//32 импульса за один оборот
#define DIST_PER_STEP_M (M_PI * WHEEL_DIAMETR_M / PULSES_PER_REV)// [0.018643 m]
uint8_t ssd1306_buffer[SSD1306_BUFFER_SIZE];
volatile int32_t pulse_counter = 0;//счетчик импульсов
volatile int32_t pulse_counter_window = 0;//импульсы за последнее окно времени
volatile float distance_m = 0;//дистанция в метрах
volatile float dist_speed = 0;//скорость
volatile uint8_t encoderDone = 0; //сработало прерывание по фронту TACH
void display_init(void) {
ssd1306_Init();
ssd1306_Fill(Black);
ssd1306_SetCursor(20, 25);
ssd1306_WriteString("ChipCraft", Font_11x18, White);
ssd1306_UpdateScreen();
HAL_Delay(2000);
ssd1306_Fill(Black);
ssd1306_UpdateScreen();
}
void encoder_Handler(void) {
if(encoderDone){
encoderDone = 0;
//Читаю DIR состояние (RESET = forward(движение вперед), SET = backward(движение назад))
GPIO_PinState dir_state = HAL_GPIO_ReadPin(DIRECTION_GPIO_Port, DIRECTION_Pin);
if(dir_state == GPIO_PIN_RESET){
pulse_counter++;
pulse_counter_window++;
}
else{
pulse_counter--;
pulse_counter_window--;
}
}
distance_m = get_distance_m();
}
void display_update(int32_t pulses, float distance, float speed) {
char buf[32];
ssd1306_Fill(Black);
ssd1306_SetCursor(25, 2);
ssd1306_WriteString("Encoder" ,Font_11x18, White);
sprintf(buf, "Pulses: %ld", pulses);
ssd1306_SetCursor(2, 22);
ssd1306_WriteString(buf, Font_7x10, White);
sprintf(buf, "Dist: %.2f m", distance);
ssd1306_SetCursor(2, 36);
ssd1306_WriteString(buf, Font_7x10, White);
sprintf(buf, "Speed: %.2f m/s", speed);
ssd1306_SetCursor(2, 50);
ssd1306_WriteString(buf, Font_7x10, White);
ssd1306_UpdateScreen();
}
float get_distance_m(void){
return (float)pulse_counter * DIST_PER_STEP_M;
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_PIN) {//Прерывание по фронту TACH
encoderDone = 1;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM3) {
int32_t pulses = pulse_counter_window;
pulse_counter_window = 0;
float dist = (float)pulses * DIST_PER_STEP_M;
dist_speed = dist / WINDOW_TIME_S;
}
else if(htim->Instance == TIM14) {
display_update(pulse_counter, distance_m, dist_speed);
}
}
Модуль proj_main()
proj_main.c
#include "./Project/shared.h"
#include "./Project/proj_main.h"
#include "./Project/process_Encoder.h"
#include "./Project/process_Encoder.h"
void proj_main()
{
volatile const char *ch = ";V-F-BIN;ver: "VER_PROG(VER_a,VER_b,VER_c);(void)ch;//0x8008b00
display_init();
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_Base_Start_IT(&htim14);
while (1){
//хэндлеры
encoder_Handler();
}//while (1)
}
Выполняется инициализация дисплея;
Запуск таймеров;
Запуск функции encoder_Handler().
Вывод
В результате проделанной работы реализована полноценная система измерения и отображения параметров энкодера, на базе датчика HOA0902-11 и микроконтроллера STM32F030CCTx.

Если статья показалась Вам интересной, буду рад выпустить для Вас еще множество статей исследований по всевозможным видам устройств, так что, если не хотите их пропустить – буду благодарен за подписку на мой ТГ-канал: https://t.me/ChipCraft.
aitras
Вообще-то необязательно. Многие пины у STM32 FT - толерантны к 5В. Там какие-то из порта PA только 3,3V only, ЕМНИП.
DM_ChipCraft Автор
Соглашусь с Вами, но я конечно же стараюсь по возможности ставить небольшую безопасность в схему, чтобы потом не копаться, в случае нестабильной работы, у меня был опыт когда я подключил GPS по 5В напрямую и работало некорректно, подключал к STM32F1 и F0