Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

HIPPCRON Парктроник + Arduino (AVR LGT8F328P) Задача Захотелось поставить парктроник ...

Дата публикации: 03-09-2026 09:00:12

HIPPCRON Парктроник + Arduino (AVR LGT8F328P)
Задача
Захотелось поставить парктроник на автомобиль с выводом информации на Android-магнитолу через CAN-шину. Требования простые: чтобы недорого и с возможностью задать свою логику работы.
В машине нет штатной CAN-шины, поэтому я строю свою систему связи между автомобилем и Android-магнитолой Teyes CC3 360. На маркетплейсах есть готовые комплекты парктроников, которые умеют выводить данные на Android-магнитолу — на 4 или 8 датчиков. Варианты с 8 датчиками стоят уже недёшево, а главное ограничение в другом: к магнитоле подключается только блок парктроников, и больше ничего. При этом комплект занимает единственный последовательный порт (Serial) магнитолы.
Из интереса поковыряться в чём-то новом, изучить устройство и адаптировать под свои задачи — купил самый дешёвый комплект, который удалось найти: HIPPCRON.
Как устроен парктроник
Прежде чем что-то переделывать, разобрался, как работает штатная система.
Датчик — это пьезоэлемент, который одновременно излучает и принимает ультразвук. Основной блок по очереди опрашивает каждый датчик: генерирует ультразвуковой импульс, ждёт отражённый сигнал и по времени задержки рассчитывает расстояние до препятствия. Затем результат передаётся на дисплей цифровым сигналом по одному проводу.
Анализ сигнала
Чтобы понять формат данных, подключил осциллограф на базе Arduino и снял график обмена между блоком и дисплеем.
Удалось определить логические уровни:
рецессивное состояние — 0 В,
доминантное состояние — 5 В.
Структура одного пакета:
Стартовый сигнал — длится около 1000 мкс.
16 бит данных (два байта) — в начале каждого бита формируется нарастающий фронт, по которому можно ориентироваться, в какой момент считывать бит.
Стоповый сигнал — после 16 бит.
Два байта описывают состояние одного датчика:
первый байт — адрес датчика и его состояние,
второй байт — данные (расстояние).
Формат байта адреса и состояния
Младшие 3 бита кодируют номер датчика:
Датчик Младшие биты
A 0bxxxxx000
B 0bxxxxx001
C 0bxxxxx010
D 0bxxxxx011
Бит 4 указывает, обнаружено ли препятствие в зоне действия датчика:
Значение Состояние
0bxxxx0xxx препятствие в зоне
0bxxxx1xxx препятствия нет
Старшие 4 бита за всё время наблюдений всегда имели значение 0b1000xxxx.
Реализация
Написал класс SensorProtocol, адаптированный для контроллера LGT8F328P.
SensorProtocol.h:
/************************************/
// —- Макрос быстрого чтения пина D2 (бит 2 регистра PIND) —-
#define READ_PIN_D2() (((PIND) » 2) & 0x01)
class SensorProtocol {
public:
/// Настройка пина D2 и запуск прерывания по нарастающему фронту
void begin() {
pinMode(2, INPUT);
int intNum = digitalPinToInterrupt(D2);
attachInterrupt(intNum, risingEdgeISR, RISING);
}
/// Возвращает количество непрочитанных байтов в кольцевом буфере
uint8_t available() {
return ringCount;
}
/// Возвращает значение старейшего непрочитанного байта из буфера.
/// Если буфер пуст — возвращает 0.
uint8_t readByte() {
if (ringCount == 0) return 0;
uint8_t selectByte = 0;
if (ringCount > ringHead) {
selectByte = RING_SIZE - ringCount + ringHead;
} else {
selectByte = ringHead - ringCount;
}
uint8_t read_byte = ringBuffer[selectByte];
ringCount--;
return read_byte;
}
private:
// —- Параметры протокола —-
static const uint32_t MIN_WAKEUP_US = 1050; // Мин. длительность стартового импульса (WakeUp), мкс
static const uint32_t MAX_WAKEUP_US = 1150; // Макс. длительность стартового импульса, мкс
static const uint32_t PULSE_CENTER_BIT_US = 170; // Смещение от фронта до центра битового интервала, мкс
static const uint32_t DEADLINE_READBIT_US = 500; // Макс. пауза между битами: при превышении — сброс приёма
// —- Параметры кольцевого буфера —-
static const uint8_t RING_SIZE = 64; // Размер буфера в байтах
// —- Переменные прерывания (volatile: меняются в ISR) —-
static volatile uint32_t lastEdgeTimeUs; // Время предыдущего фронта, мкс
static volatile uint32_t nowTimeUs; // Текущее время micros() на входе в ISR, мкс
static volatile uint32_t impulseDurationUs; // Длительность текущего импульса (now − prev), мкс
static volatile bool isReceiving; // Флаг: идёт приём битового потока после WakeUp
static volatile uint8_t currentByteValue; // Текущий собираемый байт
static volatile uint8_t bitPosition; // Позиция бита в байте (7 → старший, 0 → младший)
// —- Переменные кольцевого буфера —-
static uint8_t ringBuffer[RING_SIZE]; // Хранилище принятых байт
static uint8_t ringHead; // Индекс записи следующего байта
static uint8_t ringCount; // Количество непрочитанных байт
// —- Вспомогательный флаг —-
static bool skipFirstBit; // Пропуск проверки длительности для первого бита после WakeUp
// —- Внутренние методы —-
/// Записать байт в кольцевой буфер (с перезаписью при переполнении)
static void pushByteToRing(uint8_t b) {
ringBuffer[ringHead] = b;
ringHead = (ringHead + 1) % RING_SIZE;
if (ringCount < RING_SIZE) ringCount++;
}
/// Обработчик прерывания по нарастающему фронту на пине D2
static void risingEdgeISR() {
nowTimeUs = micros();
impulseDurationUs = nowTimeUs - lastEdgeTimeUs;
lastEdgeTimeUs = nowTimeUs;
// —- Поиск стартового импульса (WakeUp) —-
if (!isReceiving) {
if (impulseDurationUs > MIN_WAKEUP_US && impulseDurationUs < MAX_WAKEUP_US) {
isReceiving = true;
currentByteValue = 0;
bitPosition = 7;
skipFirstBit = true;
}
}
// —- Приём битового потока —-
if (isReceiving) {
// Проверка таймаута между битами (кроме первого бита после WakeUp)
if (impulseDurationUs > DEADLINE_READBIT_US && !skipFirstBit) {
isReceiving = false;
return;
}
skipFirstBit = false;
// Ожидание центра битового интервала, затем чтение уровня
while (lastEdgeTimeUs + PULSE_CENTER_BIT_US > micros()) {};
bool level = READ_PIN_D2();
// Запись бита в текущую позицию
if (level) {
currentByteValue |= (1 « bitPosition);
} else {
currentByteValue &= ~(1 « bitPosition);
}
// Байт собран — отправляем в буфер, иначе переходим к следующему биту
if (bitPosition == 0) {
pushByteToRing(currentByteValue);
currentByteValue = 0;
bitPosition = 7;
} else {
bitPosition--;
}
}
}
};
// —- Определения статических членов класса —-
volatile uint32_t SensorProtocol::lastEdgeTimeUs = 0;
volatile uint32_t SensorProtocol::nowTimeUs = 0;
volatile uint32_t SensorProtocol::impulseDurationUs = 0;
volatile bool SensorProtocol::isReceiving = false;
volatile uint8_t SensorProtocol::currentByteValue = 0;
volatile uint8_t SensorProtocol::bitPosition = 7;
uint8_t SensorProtocol::ringBuffer[SensorProtocol::RING_SIZE];
uint8_t SensorProtocol::ringHead = 0;
uint8_t SensorProtocol::ringCount = 0;
bool SensorProtocol::skipFirstBit = false;
/************************************/
вывод расстояния каждого датчика используя написанный выше класс
/************************************/
#include "SensorProtocol.h"
// Экземпляр класса протокола сенсора — инкапсулирует всю логику приёма/парсинга
SensorProtocol sensor;
// Порог времени (в микросекундах), после которого считаем, что это новый пакет данных
const uint32_t CUT_DATA_US = 20000;
void setup() {
// Инициализация последовательного порта на скорости 1 Мбит/с для быстрой отладки
Serial.begin(1000000);
// Запуск сенсора: настройка пинов, прерываний, таймеров и т.п. внутри класса
sensor.begin();
}
// Перечисление адресов сенсоров — делает код читаемым и защищает от опечаток в числах
enum address_sensor {
sensor_a = 128,
sensor_b = 129,
sensor_c = 130,
sensor_d = 131,
};
// Текущий распознанный адрес сенсора (из первого байта пакета)
address_sensor nam_sensor;
// Время (в микросекундах) последнего успешного чтения байта — нужно для таймаута
volatile uint32_t dataOutTime = 0;
// Входной буфер для текущего пакета данных: [0] = адрес, [1] = значение
uint8_t buffSensor[2];
// Индекс текущего записываемого байта в buffSensor (0 или 1)
volatile uint8_t currentSensor = 0;
void loop() {
// Проверяем, есть ли в кольцевом буфере сенсора готовые данные для чтения
if (sensor.available()) {
// Если с момента последнего чтения прошло больше CUT_DATA_US — считаем, что это новый пакет
if ((dataOutTime + CUT_DATA_US) < micros()) {
currentSensor = 0; // Сброс индекса приёма
}
// Читаем следующий байт из сенсора
buffSensor[currentSensor] = sensor.readByte();
// Если прочитали только первый байт (адрес), ждём второй (значение)
if (currentSensor < 1) {
currentSensor++;
} else {
// Прочитали оба байта — обрабатываем пакет
dataSensor();
// После обработки можно сразу сбросить индекс
currentSensor = 0;
}
// Обновляем время последнего успешного чтения
dataOutTime = micros();
}
}
// Обработка принятого пакета: вывод значения в Serial в зависимости от адреса
void dataSensor() {
nam_sensor = static_cast(buffSensor[0]);
switch (nam_sensor) {
case sensor_a:
Serial.print("Sensor A = ");
Serial.println(buffSensor[1]);
break;
case sensor_b:
Serial.print("Sensor B = ");
Serial.println(buffSensor[1]);
break;
case sensor_c:
Serial.print("Sensor C = ");
Serial.println(buffSensor[1]);
break;
case sensor_d:
Serial.print("Sensor D = ");
Serial.println(buffSensor[1]);
break;
default:
// На случай получения неизвестного адреса — полезно для отладки
Serial.print("Unknown sensor addr = ");
Serial.println(buffSensor[0], HEX);
break;
}
}
Архив проекта:

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Honda Element: backup camera06.2226-07-2026
2Как я запустил перцептрон на обычном непрограммируемом калькуляторе Casio2703-07-2026
3Сусанин: как я перестал кормить MikroTik списками доменов и научил искать рабочий маршрут самостоятельно08.7731-08-2026
4ВНИМАНИЕ! ПРОДАЖА НА ВАШ ВЫБОР. _______МОНИТОР_______ ARDOR GAMING 27" GAMING ...19.9902-09-2026
5ВНИМАНИЕ! ПРОДАЖА НА ВАШ ВЫБОР. _______МОНИТОР_______ ARDOR GAMING 27" GAMING ...19.9902-09-2026
6ВНИМАНИЕ! ПРОДАЖА НА ВАШ ВЫБОР. _______МОНИТОР_______ ARDOR GAMING 27" GAMING ...19.9902-09-2026
7Вайбкодю, потихоньку0502-07-2026
8I loaded my Android Auto dashboard with these apps; my car display finally feels useful07.1631-07-2026
9Что-то напоминает? Они точно (тупо) используют ИИ. Тогда, скоро будут ...-5703-07-2026

Классификация: Наука. Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 16.16. Источник: vk.com.