В этом уроке мы рассмотрим протокол связи CAN, связав модуль шины CAN MCP2515 с микроконтроллером ESP32. Наша цель — передавать данные с датчика температуры DS18B20 на указанное расстояние с использованием протокола CAN. Также ранее на нашем сайте мы рассматривали подключение контроллера шины CAN MCP2515 к плате Arduino — сейчас это одна из самых популярных статей в сети на русском языке по данному вопросу и одна из самых популярных статей на нашем сайте, в ней вы можете прочитать обзор протокола CAN, форматы CAN сообщений и сравнение CAN с протоколами SPI и I2C.
Шина Controller Area Network (CAN) широко используется в промышленных условиях и автомобильной диагностике благодаря своей способности покрывать большие расстояния, умеренной скорости связи и высокой надежности. Стандартные протоколы, такие как UART, SPI и I2C, менее надежны на больших расстояниях. Но CAN идеально подходит для автомобильных систем и сред, где связь осуществляется по обширной проводке. Связь CAN обычно поддерживает скорости в диапазоне от 50 Кбит/с до 1 Мбит/с и может поддерживать целостность сигнала на расстоянии от 40 метров при 1 Мбит/с до 1000 метров при 50 Кбит/с.
Мы будем использовать пару плат ESP32 и модулей MCP2515 для демонстрации передачи данных датчика температуры DS18B20. Передаваемые данные будут получены приемником и отображены на OLED-дисплее. Давайте начнем с сегодняшнего руководства по связи по шине CAN с использованием модуля шины CAN Microchip MCP2515.
Необходимые компоненты
- Модуль ESP32 — 2 шт. (купить на AliExpress).
- Датчик температуры DS18B20 (купить на AliExpress).
- Модуль OLED дисплея SSD1306 128×64 с диагональю 0.96 дюйма и интерфейсом I2C (купить на AliExpress).
- MCP2515 CAN Module (контроллер шины CAN MCP2515) – 2 шт. (купить на AliExpress).
- Резистор 4,7 кОм (купить на AliExpress).
- Макетная плата.
- Соединительные провода.
Реклама: ООО «АЛИБАБА.КОМ (РУ)» ИНН: 7703380158
Модуль контроллера шины CAN MCP2515
Контроллер шины CAN MCP2515 — это простой модуль, поддерживающий протокол CAN версии 2.0B и способный обеспечивать связь со скоростью 1 Мбит/с.
Этот конкретный модуль основан на CAN контроллере IC MCP2515 и CAN трансивере IC TJA1050. Микросхема MCP2515 является автономным CAN контроллером и имеет интегрированный интерфейс SPI для связи с микроконтроллерами. Что касается микросхемы (IC) TJA1050, она действует как интерфейс между MCP2515 CAN контроллером IC и физической CAN шиной.
Плата имеет кварцевый генератор 8 МГц. Также доступна версия 16 МГц. Можно прикрепить перемычку, которая даст оконечное сопротивление 120 Ом. CAN_H и CAN_L — это два винта, к которым можно прикрепить провода на расстоянии для связи с другим модулем CAN.
Ниже приведена схема CAN-модуля MCP2515.
Микросхема MCP2515 — это основной контроллер, который внутри состоит из трех основных подкомпонентов: CAN-модуля, управляющей логики и блока SPI. CAN-модуль отвечает за передачу и прием сообщений по CAN-шине. Управляющая логика управляет настройкой и работой MCP2515, связывая все блоки. SPI-блок отвечает за интерфейс связи SPI. Микросхема TJA1050 отвечает за получение данных от контроллера и передачу их на шину.
Особенности и характеристики MCP2515:
- Использует высокоскоростной CAN-трансивер TJA1050
- Размеры: 40×28 мм
- Управление SPI для расширения интерфейса шины Multi CAN
- Кварцевый генератор 8 МГц
- Оконечное сопротивление 120 Ом
- Имеет независимую клавишу, светодиодный индикатор, индикатор питания
- Поддерживает работу CAN со скоростью 1 Мбит/с
- Режим ожидания с низким током
- Возможно подключение до 112 узлов.
Более подробную информацию о данном модуле можно посмотреть в техническом описании MCP2515.
Взаимодействие модуля шины CAN MCP2515 с ESP32
Теперь давайте соединим модуль шины CAN MCP2515 с платой ESP32 и протестируем протокол связи CAN. Мы передадим данные водонепроницаемого датчика температуры DS18B20 по шине CAN с помощью пары модулей ESP32 и модулей CAN MCP2515. Схема для нашего проекта показана на следующем рисунке.
Левая часть схемы, состоящая из платы ESP32, CAN-модуля MCP2515 и датчика DS18B20, является передающей частью.
Аналогично, правая часть схемы, состоящая из платы ESP32, CAN-модуля MCP2515 и OLED-дисплея, является приемной частью.
Передатчик и приемник соединены друг с другом с помощью модуля CAN Bus MCP2515. CAN_H и CAN_L передатчика соединены с CAN_H и CAN_L приемника соответственно.
Соединение между платой ESP32 и CAN-модулем MCP2515 осуществляется следующим образом.
Контакт MCP2515 | Контакт ESP32 |
VCC | 5V |
GND | GND |
CS | IO5 |
SO | IO19 |
SI | IO23 |
SCK | IO13 |
INT | IO4 |
На передающей части вывод датчика DS18B20 подключен к контакту IO21 ESP32. Выход датчика DS18B20 подключен к VCC через резистор 4,7 кОм.
На приемной части дисплей I2C OLED имеет 4 контакта и VCC, GND, SCL и SDA. SCL и SDA — это контакты I2C, подключенные к IO22 и IO21 платы ESP32. VCC и GND OLED подключаются к 3,3 В и GND платы ESP32.
Вы можете собрать схему на макетной плате или использовать для этого проекта собственную печатную плату.
Исходный код программы
Прежде чем перейти к написанию программы для проекта, нам нужно установить библиотеку MCP2515 CAN Bus Library в Arduino IDE. Загрузите библиотеку по следующей ссылке — библиотека интерфейса CAN MCP2515, а затем добавьте ее в папку Arduino Library.
- реализует CAN V2.0B со скоростью до 1 Мбит/с;
- интерфейс SPI до 10 МГц;
- Стандартные (11 бит) и расширенные (29 бит) данные и удаленные кадры;
- два приемных буфера с приоритетным хранением сообщений.
Вам также необходимо добавить библиотеку OneWire, а также библиотеку температуры Dallas для датчика температуры DS18B20.
Код разделен на две части: одна часть — код CAN-передатчика, другая — код CAN-приемника.
Код передатчика ESP32 CAN
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 |
#include <OneWire.h> #include <DallasTemperature.h> #include <SPI.h> #include <mcp2515.h> #define ONE_WIRE_BUS 21 // GPIO where the DS18B20 is connected OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); struct can_frame canMsg; struct MCP2515 mcp2515(5); // CS pin is GPIO 5 #define MAX_RETRIES 3 #define CAN_ACK_ID 0x037 // CAN ID for acknowledgment void setup() { Serial.begin(115200); SPI.begin(); mcp2515.reset(); mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ); mcp2515.setNormalMode(); sensors.begin(); } void loop() { sensors.requestTemperatures(); float temperatureC = sensors.getTempCByIndex(0); int tempInt = (int)(temperatureC * 100); // Convert temperature to integer for transmission // Prepare CAN message canMsg.can_id = 0x036; // CAN ID canMsg.can_dlc = 2; // Data length code (number of bytes) canMsg.data[0] = (tempInt >> 8) & 0xFF; // MSB of temperature canMsg.data[1] = tempInt & 0xFF; // LSB of temperature bool messageSent = false; int retries = 0; while (!messageSent && retries < MAX_RETRIES) { if (mcp2515.sendMessage(&canMsg) == MCP2515::ERROR_OK) { Serial.print("Temperature sent: "); Serial.print(temperatureC); Serial.println(" °C"); // Wait for acknowledgment unsigned long startTime = millis(); bool ackReceived = false; while (millis() - startTime < 500) { // Wait up to 500ms for an ACK if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) { if (canMsg.can_id == CAN_ACK_ID) { ackReceived = true; break; } } } if (ackReceived) { Serial.println("ACK received"); messageSent = true; } else { Serial.println("ACK not received, retrying..."); retries++; } } else { Serial.println("Error sending message, retrying..."); retries++; } } if (!messageSent) { Serial.println("Failed to send message after retries"); } delay(1000); // Send data every second } |
Код CAN-приемника ESP32
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 |
#include <SPI.h> #include <mcp2515.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); struct can_frame canMsg; struct MCP2515 mcp2515(5); // CS pin is GPIO 5 #define CAN_ACK_ID 0x037 // CAN ID for acknowledgment void setup() { Serial.begin(115200); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextColor(WHITE); SPI.begin(); mcp2515.reset(); mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ); mcp2515.setNormalMode(); } void loop() { if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) { if (canMsg.can_id == 0x036) // Check if the message is from the sender { int tempInt = (canMsg.data[0] << 8) | canMsg.data[1]; // Combine MSB and LSB float temperatureC = tempInt / 100.0; // Convert back to float Serial.print("Temperature received: "); Serial.print(temperatureC); Serial.println(" °C"); display.clearDisplay(); display.setTextSize(1); display.setCursor(25, 10); display.print("Temperature:"); display.setTextSize(2); display.setCursor(25, 30); display.print(temperatureC); display.print("C"); display.display(); // Send acknowledgment canMsg.can_id = CAN_ACK_ID; // Use ACK ID canMsg.can_dlc = 0; // No data needed for ACK mcp2515.sendMessage(&canMsg); Serial.println("ACK sent"); } } } |
Объяснение работы кода
Тип данных структуры canMsg для хранения формата сообщений CAN.
1 |
struct can_frame canMsg; |
Устанавливаем номер контакта, к которому подключен SPI CS .
1 |
MCP2515 mcp2515(5); |
MCP2515 сбрасывается с помощью следующей команды.
1 |
mcp2515.reset(); |
MCP2515 настроен на скорость 500 Кбит/с и тактовую частоту 8 МГц .
1 |
mcp2515.setBitrate(CAN_500KBPS,MCP_8MHZ); |
MCP2525 установлен в нормальный режим.
1 |
mcp2515.setNormalMode(); |
CAN ID задан как 0x036 , а DLC как 2, и мы определяем данные температуры для data[0] и data[1] и сбрасываем все данные на 0. DLC представляет собой код длины данных, указывающий количество байтов, передаваемых в сообщении. В этом случае это 2 для данных температуры.
1 2 3 4 |
canMsg.can_id = 0x036; // CAN ID canMsg.can_dlc = 2; // Data length code (number of bytes) canMsg.data[0] = (tempInt >> 8) & 0xFF; // MSB of temperature canMsg.data[1] = tempInt & 0xFF; // LSB of temperature; |
Для отправки сообщения в шину CAN мы используем следующий оператор.
1 2 3 4 5 6 7 8 |
while (millis() - startTime < 500) { // Wait up to 500ms for an ACK if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) { if (canMsg.can_id == CAN_ACK_ID) { ackReceived = true; break; } } } |
Отправитель пытается повторно отправить сообщение до MAX_RETRIES раз, если подтверждение не получено в течение 500 миллисекунд. Это помогает гарантировать надежную доставку сообщения.
1 2 3 4 5 6 7 8 9 10 11 |
if (ackReceived) { Serial.println("ACK received"); messageSent = true; } else { Serial.println("ACK not received, retrying..."); retries++; } } else { Serial.println("Error sending message, retrying..."); retries++; } |
Этот оператор используется для получения сообщения от шины CAN. Если сообщение получено, оно попадает в условие if.
1 |
if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) |
Если отправитель не может отправить сообщение после разрешенного количества повторных попыток, он выводит сообщение об ошибке на монитор последовательной связи. Это помогает в диагностике проблем со связью.
Тестирование связи между модулями ESP32 через шину CAN
Загрузите оба представленных кода программ на соответствующие платы ESP32. В меню Tools выберите ESP32 Dev Module. Затем выберите COM port. Затем загрузите код.
После загрузки кода, когда плата будет включена, вы должны заметить, что значение температуры, считываемое DS18B20, будет отправлено на другой ESP32 через CAN-коммуникацию.
Сообщение будет отображено на OLED-дисплее второго ESP32.
Вы также можете открыть последовательный монитор и проверить те же сообщения передатчика и приемника.
Помните, вам может потребоваться закоротить резистор 120 Ом на плате приемника MCP2515. В противном случае, если он не закорочен, OLED иногда может быть пустым, так как сообщение не получено. Вот как вы можете использовать связь по шине CAN с микроконтроллерами ESP32.
3 ответа к “Использование стандарта CAN в ESP32 – полное руководство”
Приветствую. Давно и с удовольствием читаю ваш сайт. Использую его иногда как справочник. Вот вопрос, который я плохо знаю. Решил поискать ответ у вас. Вы привели схему и код для MCP2515 и ESP32.
Но совсем ничего не сказали о согласовании логических уровней ESP32 и MCP2515. В сети немало вариантов подобных схем. С комментариями в диапазоне от согласование не нужно совсем, в виду толерантности входов. До схем согласования логических уровней на специализированных микросхемах или на транзисторах. В том числе и с применением раздельных источников питания на 5 вольт и на 3,3 вольта.
Внесите пожалуйста ясность в этот вопрос. Как решили его вы? Нужно или не нужно делать согласование? И если нужно, как вы рекомендуете это сделать? Спасибо.
Не стал заморачиваться с этими согласованиями. В сети в буквальном смысле религиозные войны по этому поводу. Включил по вашей схеме. Все работает. Спасибо.
В большинстве случаев все работает без согласования, но я бы для подстраховки использовал бы самый простой вариант согласования с делителями напряжения на резисторах