
Всем привет!
В данной статье я хочу Вам рассказать про датчик HEDR(от компании avago technologies) - это двухканальный инкрементальный оптический датчик, предназначен для измерения пройденного пути, линейной скорости, угловой скорости и направлении вращения вала.
С помощью данного датчика будет реализован энкодер на базе микроконтроллера STM32, который будет производить вычисление пройденного пути.
В данной статье будет рассмотрено:
Принцип работы датчика HEDR-5420-ES214;
Схема подключения к микроконтроллеру STM32;
Программная реализация (расчет пройденного пути и вывод информации на дисплей).
Технические характеристики датчика HEDR-5420-ES214
Напряжение питания [ 4.5 - 5.5В ];
Тип выхода [ квадратурный ];
Диаметр вала [ 5 мм ];
Разрешение [ 200 отсчетов на оборот ];
Рабочая температура [ от -10°C до +85°C ].
Принцип работы датчика HEDR-5420-ES214
Устройство состоит из трех основных компонентов:
Источник света (светодиод, формирующий поток света);
Оптическая система (линза, обеспечивает фокусировку и отражение света);
Фотодетектор.

Линза фокусирует излучаемый свет на кодовое колесо (диск с чередующимися отражающими и неотражающими участками), при вращении диска, отраженный свет проходит обратно через оптическую систему и попадает на фотодиоды, таким образом на их поверхности формируется чередующийся рисунок света и тени, соответствующий узору кодового диска.
Эти изменения интенсивности света преобразуются в внутренние сигналы А и В, которые проходят через компараторы в составе обработки сигналов, на выходе формируются два цифровых прямоугольных сигнала - канал А и В, находящиеся в квадратурной фазе на 90°, что позволяет микроконтроллеру определять направление вращения вала, к примеру:
если канал А опережает канал B - вращение происходит в одну сторону;
если канал B опережает канал А - вращение происходит в противоположную сторону.
Для своей задачи применяется следующая последовательность, если канал А опережает канал B - движение энкодера считается положительным, если на оборот, то движение будет отрицательным.

Схема подключения к микроконтроллеру STM32


В данной схеме используются преобразователь напряжения DA1 (+12V +5V) и стабилизатор напряжения DA2, дисплей подключается к выводам МК 21_SCL_I2C2 и 22_SDA_I2C2, датчик HEDR подключается к выводам МК 29_CH.A и 30_CH.B, данные сигналы сначала проходят через делители, R17-R18-[CH.A] и R15-R16-[CH.B], так как датчик работает от +5V, сигналы соответственно тоже у него +5V, я всегда стараюсь дополнительно защитить МК, после делителя амплитуда сигналов снизится до +3.3V, копипастить информацию по описанию узлов преобразователя, стабилизатора, узла обвязки напряжения питания и резонатора для МК не особо хочется, поэтому кому интересно можно почитать статью [https://habr.com/ru/articles/950818/].
Прикладываю модуль main.c (конфигурация микроконтроллера)
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "./Project/proj_main.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c2;
TIM_HandleTypeDef htim1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
static void MX_I2C2_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
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();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
MX_I2C2_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
proj_main();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
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.PLLMUL = RCC_PLL_MUL6;
RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief I2C2 Initialization Function
* @param None
* @retval None
*/
static void MX_I2C2_Init(void)
{
/* USER CODE BEGIN I2C2_Init 0 */
/* USER CODE END I2C2_Init 0 */
/* USER CODE BEGIN I2C2_Init 1 */
/* USER CODE END I2C2_Init 1 */
hi2c2.Instance = I2C2;
hi2c2.Init.Timing = 0x2010091A;
hi2c2.Init.OwnAddress1 = 0;
hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c2.Init.OwnAddress2 = 0;
hi2c2.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c2) != HAL_OK)
{
Error_Handler();
}
/** Configure Analogue filter
*/
if (HAL_I2CEx_ConfigAnalogFilter(&hi2c2, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
{
Error_Handler();
}
/** Configure Digital filter
*/
if (HAL_I2CEx_ConfigDigitalFilter(&hi2c2, 0) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN I2C2_Init 2 */
/* USER CODE END I2C2_Init 2 */
}
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 65535;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 3;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 3;
if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#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 /* USE_FULL_ASSERT */
Настройка микроконтроллера STM32F030CCTx в CubeIDE

Настройка RCC и SYS (в RCC выбираю Crystal/Ceramic Resonator, так как у меня внешний кварц на 8 МГц)

Настройка дисплея
Взаимодействие дисплея с МК будет через I2C2

Настройка выводов узла подключения датчика HEDR
TIM1_CH1 (к данному выводу будет подключаться сигнал CH.A);
TIM1_CH2 (к данному выводу будет подключаться сигнал CH.B).


Таймер используется в режиме Encoder mode - это специальный аппаратный режим, который позволяет микроконтроллеру автоматически подсчитывать импульсы от инкрементального датчика и определять направление вращения, данная конфигурация освобождает МК от необходимости программно обрабатывать прерывания по каждому импульсу.
Encoder Mode TI1 and TI2 данный параметр указывает, что используется оба канала датчика (A и B), это дает разрешение X4 - т.е. счетчик будет увеличиваться на 4 шага за один полный оборот.
Описание режимов
TI1 - подсчет ведется по фронту одного канала А, направление определяется по уровню В, разрешение 1.8 градусов;
TI2 - аналогично логике TI1, но базируется на канале В;
TI1 and TI2 - подсчет ведется на каждом фронте обоих каналов (А+, А-, В+, В-), направление определяется автоматически, т.е. количеством импульсов на оборот 200, я получаю 800 шагов на оборот, разрешение будет 0.45 градусов.
Input Filter - включает цифровую фильтрацию входного сигнала, помогает убрать дребезг и шум, значения от 0 до 15, чем выше значение, тем надежнее фильтрация, но будет повышаться задержка.
Polarity (Rising Edge) - счетчик реагирует на восходящие фронты сигнала.
Настройка Clock

Программная реализация ведомого устройства
Ссылка на скачивание исходного кода [ https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_исходный_код — Исходный код для Encoder_HEDR_5420_STM32F030CCTx].
Модуль process_Encoder
Данный модуль реализует считывание сигналов с инкрементального датчика HEDR и вычисляет:
Количество импульсов на оборот;
Пройденную дистанцию;
Отображение данных на дисплее.
#define ENCODER_MODE_X4 4
Данный параметр отражает режим подсчета импульсов, привожу формулу
Этот режим обеспечивает максимальную точность - 0.45 на один шаг.
#define WHEEL_DIAMETR_M 0.230f // 230 мм
#define WHEEL_RADIUS_M (WHEEL_DIAMETR_M / 2.0f)
Здесь я задаю геометрические размеры колеса, на валу которого установлен датчик%
Диаметр колеса 230 мм (0.230м);
Радиус вычисляется так:
#define STEPS_PER_REV (ENCODER_PPR * ENCODER_MODE_X4)
Максимальное количество шагов за один оборот
#define CIRCUMFERENCE_M (2.0f *M_PI * WHEEL_RADIUS_M)
Длина окружности колеса - это путь, который проходит колесо за один оборот
т.е. при моем радиусе 0.115, получится за один полный оборот 0.72 м.

Функция display_init() - инициализация дисплея
Инициализируется драйвер дисплея;
Выполняется заливка экрана черным цветом;
На дисплее на 2 секунды отображается стартовый экран с надписью ''ChipCraft";
После задержки экран очищается для дальнейшей работы.
библиотеку для работы с дисплеем я взял с [https://github.com/afiskon/stm32-ssd1306/tree/master]
Функция display_update()
Отвечает за визуализацию информации на дисплее:
Экран предварительно очищается с помощью ssd1306_Fill(Black);
В верхней части по центру отображается надпись «Encoder»;
-
Ниже последовательно выводятся:
количество импульсов;
дистанция;
Буфер графики передается на дисплей вызовом ssd1306_UpdateScreen()
Функция encoder_Handler()
Логика работы:
считывание текущего значения таймера;
определение разницы (delta) между текущим и предыдущим значениями;
накопление общего счетчика enocoder_position;
вызов функций для вычисления дистанции и обновление дисплея.
Функция get_distance_m() - вычисление пройденной дистанции
Переводит количество импульсов датчика в физическую длину пути в (метрах).
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 ENCODER_PPR 200 // импульсов на оборот
#define ENCODER_MODE_X4 4
#define WHEEL_DIAMETR_M 0.230f // 230 мм
#define WHEEL_RADIUS_M (WHEEL_DIAMETR_M / 2.0f)
#define STEPS_PER_REV (ENCODER_PPR * ENCODER_MODE_X4)
#define CIRCUMFERENCE_M (2.0f *M_PI * WHEEL_RADIUS_M)
uint16_t current_count= 0;
int16_t delta = 0;
float distance = 0.0f;
int32_t encoder_position = 0;
uint8_t ssd1306_buffer[SSD1306_BUFFER_SIZE];
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) {
static uint16_t last_count = 0;
current_count = __HAL_TIM_GET_COUNTER(&htim1);
delta = (int16_t)(current_count - last_count);
encoder_position += delta;
last_count = current_count;
get_distance_m();
display_update(encoder_position, distance);
}
void display_update(int32_t pulses, float distance) {
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);
ssd1306_UpdateScreen();
}
float get_distance_m(void){
distance = ((float) encoder_position / STEPS_PER_REV) * CIRCUMFERENCE_M;
return distance;
}Модуль proj_main() - главный метод
Выполняется инициализация дисплея;
Запуск таймера;
Запуск функции encoder_Handler().
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_Encoder_Start(&htim1, TIM_CHANNEL_ALL);
while (1){
//хэндлеры
encoder_Handler();
}//while (1)
}Если статья показалась Вам интересной, буду рад выпустить для Вас еще множество статей исследований по всевозможным видам устройств, так что, если не хотите их пропустить — буду благодарен за подписку на мой ТГ-канал.
Комментарии (28)

mmMike
01.11.2025 05:35Ссылка на скачивание исходного кода [ https://t.me/ChipCraft
буду благодарен за подписку на мой ТГ-канал.
На что только люди не идут что бы на них подписались...
Целая статья в которой специально нет ключевых подробностей.
Соблазн посмотреть правильный ли режим таймера STM32 (для работы с энкодером) был выставлен, был мной у себя задавлен с мыслью "Да и пофиг, что там автор сделал. Как нужно я и так знаю".Достали статьи с лейтмотивом "ну подпишитесь на мой TG канал, что вам стоит то".

DM_ChipCraft Автор
01.11.2025 05:35Дело каждого, я никого не умоляю подписаться, а по вопросу, настройки таймера и реализации самого кода(с пояснениями), я предоставил всю информацию в данной статье:), надо только внимательно посмотреть статью.

mmMike
01.11.2025 05:35ну ну.
Я вот только по косвенным признакам (Описание режимов..TI1 and TI2) могу догадаться что используется режим работы с энкодером таймера (исходники то инициализации таймера только по ссылке на TG)Поэтому ценность статьи сомнительная. Для тех кто в теме - ничего нового.
Для тех кто не совсем в теме - "раз раз и работает" (без пояснения ключевых мест). Типа скачайте с TG..
Угу. Скачайте..Да еще HAL либа (хотя это дело вкуса).

DM_ChipCraft Автор
01.11.2025 05:35хм, честно говоря у меня не было в планах скрывать информацию о настройке таймера, а уж тем более коварного плана по получению подписчиков:) по факту можно зайти - подписаться (скачать исходник) и отписаться :) я на самом деле думал что достаточно показать настройку таймера в граф.интерфейсе, естественно я не против открыть конфиги, обязательно дополню информацию)

mmMike
01.11.2025 05:35Извините, если что.
но что то триггерят статьи у которых "подпишитесь на мой канал".
А тут еще исходники на TG канале, а не github, например.минусы в таких случая не ставлю. Но побурчать... без проблем (пока релиз кандидат собирается и тесты прогоняются.. хоть отвлечься от работы)

DM_ChipCraft Автор
01.11.2025 05:35Ничего страшного, я прекрасно Вас понимаю, скрывать не буду, я с github особо не работаю, и не умею в нем работать, но обязательно задумаюсь, чтобы исходники заливать и туда:)

randomsimplenumber
01.11.2025 05:35Круто, конечно, что в stm32 есть таймер, который сразу умеет в енкодер. Буду знать ;) перфекционист говорит, что, наверное, у того таймера и прерывания есть ;)

MikeNer
01.11.2025 05:35Глянул канал в телеге, расхотелось подписываться, в названии 2 орфографических ошибки, не исскуство, а искусство, и не миксросхем, а микросхем, наверное. Исправьте, пожалуйста.

shkal
01.11.2025 05:35Вообще то у квадратурных энкодеров надо декодировать запрещённые переходы для подавления дребезга, или там это аппаратно сделано?

DM_ChipCraft Автор
01.11.2025 05:35Нет, к сожалению в данном датчике нет обработки подавления дребезга, поэтому в настройке таймера я включил параметр Input Filter (3) - для моей небольшой скорости достаточно, проблем не возникало.

firehacker
01.11.2025 05:35И какие переходы у квадратурного энкодера запрещённые?

DM_ChipCraft Автор
01.11.2025 05:35Сигналы каналов А и В всегда должны быть сдвинуты друг относительно друга на 90 градусов, рисунок (1)являются нормальными переходами, на рисунке 2 переходы не нормальные и такого не должно происходить, это и есть запрещенный переход


firehacker
01.11.2025 05:35И в каком случае на оптическом энкодере будет получен сигнал с картинки 2?

DM_ChipCraft Автор
01.11.2025 05:35В моих задачах такого не было напишу честно, но такое может произойти :
слишком быстрое вращение вала , к примеру прошли какой - то участок с одной скорости, а потом резко к примеру начали движение в два раза больше чем было;
Далее плохая реализация кабеля, который связывает к примеру датчик и другое устройство , на которое датчик передает информацию, у меня кабель используется Unitronic lapp Li2YCY 4x0,75 с витыми парами;
Еще проблема может быть из - за ну очень длинного кабеля(если он без экрана, то он просто будет работать как антенна и создавать помехи).

firehacker
01.11.2025 05:35слишком быстрое вращение вала , к примеру прошли какой - то участок с одной скорости, а потом резко к примеру начали движение в два раза больше чем было
Если крутизна нарастания фронтов в выходном каскаде энкодера такая низкая, что при быстром вращении сигналы сливаются — значит вы не по задаче выбрали энкодер.
Если крутизна достаточная, но ваш MCU работает на слишком медленной частоте, или вместо прерываний использует опрос, который делается очень редко, то вы неправильно работаете с датчиком.
Это, грубо говоря, как с теоремой Найквиста-Шеннона aka Котельникова — вы должны заранее определиться с максимальной скоростью вращения вала энкодера, вычислить из неё максимальную частоту следования четверть-периодов (квадратур) и выбрать частоту опроса пинов как минимум вдвое больше, чтобы гарантированно ничего не пропустить. Или, если используете прерывания, убедиться, что прерывания успевают вовремя отработать.
Или использовать железную логику и превратить Ch.A + Ch.B в пару Step/Dir, потому что обрабатывать прерывания с такими сигналами должно быть быстрее.
Но в любом случае, если энкодер способен выдавать импульсы чаще, чем МК может обработать, бороться с этим отфильтровыванием запрещённых переходов — бесполезно. Потому что, допустим, вы откинули «запрещённый» переход и отловили легитимный переход. Откуда вы можете быть уверенными, что в интервале между двумя сэмплированиями от энкодера пришёл именно один переход? Может за это время там 3 или 10 переходных edge-в было? Ниоткуда. Поэтому фундаментально правильный подход это гарантированный оверсэмплинг, а не отбрасывание запрещённых переходов.

shkal
01.11.2025 05:35Любые, при которых 2 бита меняются одновременно, 00-11, 01-10

randomsimplenumber
01.11.2025 05:35Кмк, все равно, как вы будете такое обрабатывать. Енкодер будет либо пропускать шаги, либо добавлять, в любом случае ошибка.

firehacker
01.11.2025 05:35В оптическом энкодере да ещё и с интегрированным фронтендом (в котором наверняка есть триггеры Шмитта) такого быть не может.
Если у вас polling вместо event-based обработки сигналов, то при слишком медленном опросе и слишком быстром вращении вы можете пропустить оба фронта в интервале между двумя опросами. Но это не запрещённый переход, это, извините, выбранная вами частота опроса порта не соответствует максимально допустимой скорости вращения, определяемой техзаданием.

shkal
01.11.2025 05:35В оптическом хорошего качества скорее всего нет, а вот в недорогих механических сплошь и рядом

Sergey_12345
01.11.2025 05:35Ну в энкодеры может быть и не может, а вот в кабеле все может быть, особенно в промышленных условиях. Поэтому полезно иметь критерии по котором сигнал энкодеры признается не валидным и вся система на это как то реагирует.
Промышленность имеет такие особенности, что если Вы даже все экраны и разводки сделаете правильно, завтра все это может измениться после воздействия механиков ( это такие вредители электроники).
А точные показания энкодеры бывают критически важны для безопасности.

firehacker
01.11.2025 05:35Знаете, бороться с помехами на уровне кода — это последнее дело. В прямом смысле последнее — это значит вы на предыдущих рубежах ничего не смогли сделать с помехами. А должны были.
Когда говорят «разводку сделали правильно, но помехи всё равно одерживают верх», чаще всего это означает, что разводку сделали неправильно, но к своему несчастью неправильные решения считают за правильные.

Sergey_12345
01.11.2025 05:35Я вообще не писал о борьбе с помехами.
Хотя, бороться с ними нужно на всех уровнях.
Я писал, что если помехи почему то возникли - контроллер должен это понять и принять решении (скорее всего остановка механизма до устранения проблем)
Рессурсы STM32 вполне позволяют отличить правильные сигналы от помех или сбоев, почему бы этим не пользоваться.
К примеру, на одном проекте, у меня основной функционал занимал только 1/6 часть кода, а 5/6 - это контроль целостности самой системы (что все датчики и проводки действительно работают, ничего не оборвано, не закорочено, и т.д.) Все зависит от цены ошибки, а в промышленности она обычно очень высокая.

DungeonLords
01.11.2025 05:35Оффтоп. А почему в энкодерах не принято передавать свет по оптическому волноводу и уже на месте ставить компараторы? У меня двигатель шумел и энкрдер сбоил, решилось экранированием, но осталось впечатление, что это это ненадёжно...

DM_ChipCraft Автор
01.11.2025 05:35Могу рассказать свой личный опыт, я лет 10 назад использовал метод передачи по кабелю ШОС, в качестве передатчика был HFBR-1412-TMZ, безусловно если использовать для датчика который находится в помещении, это хорошо, но если его использовать на улице при -10 к примеру , кабель ШОС дублет просто и ломается .. передатчик постоянно загрязняется и тоже выходит из строя… но единственный + что помех конечно вообще не создает такое соединение!!Интересно кстати проанализировать не будут ли теряться данные на высоких скоростях, высоких но приемлемых для датчика имею в виду, и сравнить, откопаю как нибудь посмотрю


diakin
Не очень понятно в даташите, какой там выход? Открытый коллектор? Такое впечатление, что ТТЛ, судя по токам.
А на осциллограмме выше - это сигнал с выхода датчика или после делителя?
Некоторые характеристики TTL-выхода:
Напряжение логического нуля: не выше 0,8 В при рабочем выходном токе 8 мА.
Напряжение логической единицы: не ниже 2 В при рабочем выходном токе -0,4 мА.
Не теряются импульсы при высокой скорости вращения? Можно поставить метку на валу, быстро покрутить туда-сюда и снова выставить на метку. Должен вернуться в +-0. Или 360 )
DM_ChipCraft Автор
Это сигналы после делителя, тестировал конечно же как Вы и написали, ставил метку на оси и быстро крутил, в 0 возвращается, этот датчик я применяю при пешеходной съемке, максимальная скорость у меня была 4-5 км/ч, можно конечно сделать тест , посадить датчик на шаговый двигатель какой нибудь, кстати попробую:)
Выход у датчика TTL, все верно
Спасибо за комментарий