В уроке представлю библиотеку поддержки протокола ModBus для ведущего контроллера. С помощью нее реализую обмен данными между двумя платами Ардуино.
Предыдущий урок Список уроков Следующий урок
В предыдущем уроке мы разработали локальный контроллер для сети с протоколом ModBus. В качестве ведущего ModBus устройства использовали компьютер. В этом уроке подключим локальный контроллер к другой плате Ардуино. Т.е. реализуем систему из урока 49, только с использованием протокола ModBus.
В предыдущем уроке я представил библиотеку Tiny_ModBusRTU_Slave. Надеюсь, вы оценили, как просто с помощью нее создавать программное обеспечение ведомого ModBus контроллера. В этом уроке я представлю аналог этой библиотеки для ведущего устройства.
Библиотека Tiny_ModBusRTU_Master.
Загрузить библиотеку можно по этой ссылке:
Tiny_ModBusRTU_Master позволяет простыми средствами реализовать программное обеспечение ведущих ModBus контроллеров. Может быть использована совместно с интерфейсами RS-232, RS-422, RS-485, UART и т.п. Поддерживает управление состоянием передатчика в шинных интерфейсах, например в RS-485.
Библиотека является своеобразной ”ответной частью” библиотеки Tiny_ModBusRTU_Slave. Но может быть использована и с другими ведомыми контроллерами в ModBus сетях.
Библиотека работает в фоновом режиме, параллельным процессом. Все операции происходят в обработчике прерывания по таймеру. В основном цикле программа только инициирует обмен данными.
Библиотека поддерживает минимальный набор функций для работы с регистрами хранения (всего 3 функции). Это основные операции, которых, как правило, достаточно для управления любыми контроллерами.
Код функции | Название | Описание |
03 | READ HOLDING REGISTERS | Чтение значений одного или нескольких регистров хранения |
06 | FORCE SINGLE REGISTER | Запись в один регистр хранения |
16 | FORCE MULTIPLE REGISTERS | Последовательная запись нескольких регистров хранения |
Описание класса Tiny_ModBusRTU_Master выглядит так:
class Tiny_ModBusRTU_Master {
public:
Tiny_ModBusRTU_Master(byte timeOutTransmit, byte timeOutRecieve); // конструктор
Tiny_ModBusRTU_Master(byte timeOutTransmit, byte timeOutRecieve, byte directPin); // конструктор
void update(); // загрузка данных
void read(byte adress, unsigned int* reg, unsigned int holdingRegBegin, unsigned int holdingRegNumber); // чтение регистров хранения
void writeSingle(byte adress, unsigned int data, unsigned int holdingRegBegin); // запись одного регистра хранения
void writeMultiple(byte adress, unsigned int* reg, unsigned int holdingRegBegin, unsigned int holdingRegNumber); // запись нескольких регистров хранения
byte state; // состояние обмена
};
Tiny_ModBusRTU_Master(byte timeOutTransmit, byte timeOutRecieve, byte directPin) - конструктор.
Создает объект Tiny_ModBusRTU_Master со следующими параметрами:
- timeOutTransmit – время паузы (тишины) между фреймами. Зависит от скорости обмена. Должно быть не менее времени, необходимого для передачи 3,5 байта. Рассчитывается, как timeOutTransmit, умноженное на время периода вызова функции update().
- timeOutRecieve – время тайм-аута ответа ведомого устройства. Время ожидания приема первого байта от ведомого устройства. Рассчитывается, как timeOutRecieve, умноженное на время периода вызова функции update(). При отсутствии ответа за это время формируется ошибка тайм-аута (код 2).
- directPin – номер вывода разрешения передатчика ведущего устройства. Вывод используется в шинных интерфейсах, у которых передатчик имеет три состояния. Параметр необязательный. При отсутствии параметра вывод не используется.
Tiny_ModBusRTU_Master master(8, 30, 13); // создаем объект, времена тайм-аутов 4 и 15 мс, управляющий вывод 13
Методы.
void update() – управление обменом. Метод должен регулярно вызываться в параллельном процессе, например, в прерывании по таймеру.
//--------------------------- обработчик прерывания 500 мкс
void timerInterrupt() {
master.update(); // управление обменом
}
Метод полностью управляет обменом данными. Основная программа только вызывает функцию, инициирующую обмен с нужными параметрами. Сам обмен происходит параллельным процессом. О завершении операции основной программе сообщает свойство state - состояние обмена. Во время обмена программа не останавливается, не зависает.
void read(byte adress, unsigned int* reg, unsigned int holdingRegBegin, unsigned int holdingRegNumber) – чтение регистров хранения. Функция инициирует чтение одного или нескольких регистров хранения. Имеет следующие аргументы:
- adress – адрес ведомого устройства. Может иметь значения от 1 до 247.
- reg – указатель на массив для прочитанных данных.
- holdingRegBegin – начальный адрес регистров хранения.
- holdingRegNumber – количество регистров хранения.
master.read(1, regTable, 0, 5); // инициация чтения 5 регистров хранения начиная с адреса 0, у контроллера с адресом 1, в массив regTable
void writeSingle(byte adress, unsigned int data, unsigned int holdingRegBegin) - запись одного регистра хранения. Функция инициирует запись одного регистра хранения. Имеет следующие параметры:
- adress – адрес ведомого устройства. Может иметь значения от 0 до 247. В случае, если адрес равен 0, инициируется широковещательный режим. При этом данные передаются всем ведомым контроллерам одновременно и ответ не ожидается.
- data – данное для записи в регистр хранения.
- holdingRegBegin – адрес регистра хранения.
master.writeSingle(1, (unsigned int)button1.flagPress, 5); // запись регистра хранения с адресом 5, контроллера с адресом 1
void writeMultiple(byte adress, unsigned int* reg, unsigned int holdingRegBegin, unsigned int holdingRegNumber) - запись нескольких регистров хранения. Инициирует запись нескольких регистров хранения. Имеет параметры:
- adress – адрес ведомого устройства. Может иметь значения от 0 до 247. В случае, если адрес равен 0, инициируется широковещательный режим. При этом данные передаются всем ведомым контроллерам одновременно и ответ не ожидается.
- reg – указатель на массив данных записи.
- holdingRegBegin – начальный адрес регистров хранения.
- holdingRegNumber – количество регистров хранения.
master.writeMultiple(1, regTable, 5, 2); // запись 2 регистров хранения начиная с адреса 5, контроллер с адресом 1
byte state – public свойство класса - состояние обмена. Сообщает о результате операции. Может иметь следующие значения:
- 0 - операция завершена успешно;
- 1 - идет операция;
- 2 - ошибка тайм-аута;
- 4 - ошибка данных;
- 8 - недопустимый адрес данных;
- 16 - код функции не поддерживается;
- 32 – другая ошибка.
Применение библиотеки Tiny_ModBusRTU_Master.
Для практической реализации программы ведущего контроллера необходимо:
Подключить библиотеку Tiny_ModBusRTU_Master;
создать объект Tiny_ModBusRTU_Master;
задать параметры UART (через класс Serial);
реализовать прерывание по таймеру и в его обработчике разместить функцию update().
Теперь в основной программе можно инициировать операции обмена функциями: read(), writeSingle() или writeMultiple.
Для проверки завершения операции в основной программе надо контролировать состояние свойства state. Как только его значение перестает быть равным 1, операция завершена. При state=0 операция завершена успешно.
Реализация ведущего контроллера с протоколом ModBus RTU.
Я использовал центральный контроллер из урока 49 без каких-либо изменений схемы.
У меня контроллер выглядит так.
Программу для центрального будем разрабатывать новую. Для локального - программа из предыдущего урока.
Напомню, что центральный контроллер должен считывать из локального:
- температуру;
- напряжение;
- состояние кнопки.
Кроме того центральный контроллер должен управлять светодиодом локального контроллера и выводить статистику обмена: количество циклов обмена и ошибок.
Напомню формат регистров хранения локального контроллера.
Номер регистра | Формат числа | Параметр |
0 | float | Температура |
1 | ||
2 | float | Напряжение |
3 | ||
4 | бит | Состояние кнопки (мл. бит) |
5 | бит | Состояние светодиода (мл. бит) |
С регистров 0-4 мы будем считывать данные, а в регистр 5 – записывать.
Первый вариант программы центрального контроллера я разработал в демонстрационных целях. У него понятная логика работы, но программа зависает в ожидании окончания операции обмена. Т.е. не используется главное преимущество библиотеки Tiny_ModBusRTU_Master – работа в фоновом режиме.
После инициации операции обмена программа ждет момента, когда переменная state перестанет быть равной 1. Т.е. когда операция закончится.
master.read(1, regTable, 0, 5); // чтение регистров хранения
while(master.state == 1) {} // ожидание данных
Обратите внимание, что в этом случае при объявлении объекта Tiny_ModBusRTU_Master надо использовать квалификатор volatile. Об этом написано в уроке 10.
Полностью скетч выглядит так.
// центральный контроллер с протоколом ModBus
#include <TimerOne.h>
#include <LiquidCrystal.h>
#include <Button.h>
#include <Tiny_ModBusRTU_Master.h>
volatile Tiny_ModBusRTU_Master master(8, 30, 13);
LiquidCrystal disp(6, 7, 2, 3, 4, 5); // объект дисплей
volatile Button button1(10, 30); // кнопка 1 подключена к выводу 10
unsigned int regTable[6]; // таблица регистров
unsigned int cyclCount= 0; // счетчик циклов
unsigned int errorCount= 0; // счетчик ошибок
void setup() {
Timer1.initialize(500); // инициализация таймера 1, период 500 мкс
Timer1.attachInterrupt(timerInterrupt, 500); // задаем обработчик прерываний
Serial.begin(9600);
disp.begin(20, 4); // инициализируем дисплей 4 x 20 символов
}
void loop() {
master.read(1, regTable, 0, 5); // чтение регистров хранения
while(master.state == 1) {} // ожидание данных
if(master.state == 0) {
// данные получены
cyclCount++; // счетчик циклов
disp.clear(); // очистка экрана
disp.print("C=");
disp.print(cyclCount);
disp.print(" E=");
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print("T=");
disp.print(* ((float *)regTable),1);
disp.print(" C U=");
disp.print(* ( ((float *)regTable) +1 ),1);
if ( (regTable[4] & 1) == 0) disp.print(" V B=F");
else disp.print(" V B=P");
}
else {
// ошибка обмена
errorCount++; // счетчик ошибок
disp.clear(); // очистка экрана
disp.print("C=");
disp.print(cyclCount);
disp.print(" E=");
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print("ERROR= ");
disp.print(master.state);
}
master.writeSingle(1, (unsigned int)button1.flagPress, 5); // запись регистра хранения (светодиод)
while(master.state == 1) {} // ожидание данных
if(master.state != 0) errorCount++;
delay(500);
}
//--------------------------- обработчик прерывания 500 мкс
void timerInterrupt() {
master.update(); // проверка данных обмена
button1.scanState(); // обработка сигнала кнопки 1
}
При загрузке скетча из Arduino IDE необходимо удерживать кнопку сброса локального контроллера нажатой. В этом состоянии микроконтроллер отключается и не мешает загрузке программы в центральный контроллер.
Проверка работы программы.
Запустил систему, ошибок обмена нет, данные правильные. Убедился, что по нажатию кнопки центрального контроллера зажигается светодиод локального контроллера.
Нажал кнопку сброса локального контроллера. Посыпались ошибки.
Отпустил кнопку – обмен возобновился, ошибки прекратились.
Другой вариант программы.
Во втором варианте программа не зависает, ожидая окончания операции обмена. Каждый проход цикла loop() происходит без задержек. В квалификаторе volatile при объявлении объекта Tiny_ModBusRTU_Master нет необходимости. Обмен происходит в фоновом режиме, программу можно дополнить другими задачами в цикле loop().
// центральный контроллер с протоколом ModBus
#include <TimerOne.h>
#include <LiquidCrystal.h>
#include <Button.h>
#include <Tiny_ModBusRTU_Master.h>
Tiny_ModBusRTU_Master master(8, 30, 13);
LiquidCrystal disp(6, 7, 2, 3, 4, 5); // объект дисплей
Button button1(10, 30); // кнопка 1 подключена к выводу 10
unsigned int regTable[6]; // таблица регистров
unsigned int cyclCount= 0; // счетчик циклов
unsigned int errorCount= 0; // счетчик ошибок
byte mode=0; // режим: 0 - чтение, 1 - запись
void setup() {
Timer1.initialize(500); // инициализация таймера 1, период 500 мкс
Timer1.attachInterrupt(timerInterrupt, 500); // задаем обработчик прерываний
Serial.begin(9600);
disp.begin(20, 4); // инициализируем дисплей 4 x 20 символов
master.read(1, regTable, 0, 5); // чтение регистров хранения
}
void loop() {
if( (mode & 1) == 0 ) {
// чтение регистров хранения
if(master.state != 1) {
// операция завершена
if(master.state == 0) {
// данные получены
disp.clear(); // очистка экрана
disp.print("C=");
disp.print(cyclCount);
disp.print(" E=");
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print("T=");
disp.print(* ((float *)regTable),1);
disp.print(" C U=");
disp.print(* ( ((float *)regTable) +1 ),1);
if ( (regTable[4] & 1) == 0) disp.print(" V B=F");
else disp.print(" V B=P");
}
else {
// ошибка обмена
errorCount++; // счетчик ошибок
disp.clear(); // очистка экрана
disp.print("C=");
disp.print(cyclCount);
disp.print(" E=");
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print("ERROR= ");
disp.print(master.state);
}
master.writeSingle(1, (unsigned int)button1.flagPress, 5); // запись регистра хранения (светодиод)
mode++;
}
}
else {
// запись регистра хранения
if(master.state != 1) {
// операция завершена
if(master.state != 0) errorCount++;
master.read(1, regTable, 0, 5); // чтение регистров хранения
mode++;
cyclCount++; // счетчик циклов
}
}
delay(500);
}
//--------------------------- обработчик прерывания 500 мкс
void timerInterrupt() {
master.update(); // проверка данных обмена
button1.scanState(); // обработка сигнала кнопки 1
}
Функционально программа полностью аналогична предыдущему варианту.
В предыдущих уроках мы использовали радиальные интерфейсы, а значит, могли объединить в сеть только 2 устройства. В следующем уроке простыми средствами преобразуем радиальный интерфейс UART в магистральный. С помощью него по двух проводной линии связи соединим в локальную сеть 3 платы Ардуино.
disp.print(* ((float *)regTable),1);
disp.print(* ( ((float *)regTable) +1 ),1);
Расшифруйте данные строчки. Ума не хватает зачем *
Здравствуйте!
Почитайте урок про указатели.
regTable это имя массива, а значит указатель на его первый элемент.
В этот массив мы считали данные из локального контроллера. Данные были типа float, но протокол ModBus передает только int. Поэтому в локальном контроллере данное float мы преобразовали и передали как 2 данных int (4 байта). В центральном контроллере надо выполнить обратное преобразование, т.е. из 2 int получить float.
(float *)regTable преобразование указателя в указатель на float.
* ((float *)regTable) — чтение данного по адресу (float *)regTable.
* ( ((float *)regTable) +1 ) чтение данного типа float по следующему адресу
спасибо
Здравствуйте, может найдете время подсказать, а то сам не могу разобраться. Пример с библиотекой (Tiny_ModBusRTU_Slave) компилируется без ошибок, а с библиотекой(Tiny_ModBusRTU_Master) выдает ошибку при компиляции (In file included from Tiny_ModBusRTU_Master.ino:5:0:
D:\КИПиА\ARDUINO\Среда разработки\Arduino 1.6.5\libraries\Tiny_ModBusRTU_Master/Tiny_ModBusRTU_Master.h:47:10: error: extra qualification ‘Tiny_ModBusRTU_Master::’ on member ‘errorFrame’ [-fpermissive]
void Tiny_ModBusRTU_Master::errorFrame(byte st); // ошибка кадра
^
Ошибка компиляции.)
В чем может быть причина ошибки?
Здравствуйте!
Попробуйте разместить файлы библиотеки и сам скетч в папке, путь к которой содержит только латинские символы.
Такая же ошибка.Переместил библиотеку в папку проекта, функцию errorFrame объявил public.Ошибку перестала выдавать. Работоспособность ещё не проверял.
Здравствуйте!
Сигнал DE в любом случае должен меняться. В программе QModBus я не увидел выбора управляющего сигнала для DE. Может программа не работает с драйверами с тремя состояниями.
Куда подключен DE в конвертере USB-RS485? В Главе 2 раздела Умный дом я разработал свой конвертер USB-RS485 и управляю им контроллерами из урока 57. Но это конвертер с платой Ардуино и моим специализированным протоколом обмена данными с компьютером.
Здравствуйте.Это Вы перескочили с комментарием с другого урока. В упоминаемом модуле стоит MAX485 и пины DE подключены к коллектору транзистора(предположительно) в корпусе SOT23.
Здравствуйте!
Тогда вопрос к управляющей программе. Сигнал DE должен меняться.
Проблема не решена, пробовал все варианты, не помогает. Стоит windows7, arduina 1.6.5, но думаю вряд ли в этом проблема.
Здравствуйте, подскажите пожалуйста, я правильно понял что — данные которые поступают от локального контроллера попадают в — unsigned int regTable[6]; // таблица регистров МАСТЕРА и потом уже выводятся на дисплей и прочее regTable[ ]
Здравствуйте!
Да, конечно. Задачи распараллелены. Библиотека работы с ModBus занимается получением данных от локального контроллера. Основная программа работает с этими данными. А промежуточное звено это массив, в котором данные хранятся.
Во первых спасибо за урок. Но столкнулся с проблемой.
void loop() {
display.setCursor(0,0); // установка позиции курсора
display.clearDisplay();
display.println(«MODE» +String(mode,DEC) );
display.println(«Error: «+String(errorCount,DEC));
display.println(«Error code: «+String(Error_code,DEC));
display.println(«SEND: «+String(cyclCount,DEC));
display.println(«Good SEND: «+String(linkCount,DEC));
if ((unsigned int)button1.flagPress ) display.println(«Button: ON»);
else display.println(«Button: OF»);
display.display();
if (mode==0) {
master.writeSingle(ADR_CONTR_1, (unsigned int)button1.flagPress, 5); // запись регистра хранения (светодиод)
cyclCount++;
mode=3;
}
if(mode == 3) {
// ожидание записи в контроллер 1
Error_code = master.state;
// проверка состояния записи
if(master.state != 1) {
// обработка результата связи
if(master.state == 0)
{linkCount++;
errorCount=0; }
else
errorCount++;
mode=0;
}
}
Вроде бы связь идет. Светодиод на ведущем контроллере отвечает, но почему то переменная linkCount не инкриментируется. То-есть постоянно идет ошибка, при этом связь вроде как проходит.
Постоянно лезет ошибка тайм-аута. Попробовал подключить к эмутятору ПК как ведущее так и ведомое. По отдельности все работает, но мастер только если увеличить тайм-аут до 200. Вместе никак.
При этом с мастера данные уходят. Пишутся в слэйв, но подтверждения что операция выполнена нет. И со слэйва регистры уже не читаются.
Выкинул MAX485/ Соединил на прямую. При тайм-ауте в 200 все работает. Начал копать в схеме интерфейса. Нашел неверное соединение. Исправил. Теперь работает и через MAX485, но ведущий периодически виснет. При этом при прямом соединении работает стабильно. Капаю дальше 🙂
Запитал контроллеры от разных БП — и все завелось как надо. Жаль только что аппаратный порт занят.
Здравствуйте, подскажите пожалуйста, если использовать Mega, то там несколько Serial, как с этой библиотекой, указать на каком шина с ModBus а на каком USB ковертер?
К примеру
Serial.begin(9600); // 0 (RX) и 1 (TX); (USB)
Serial1.begin(9600); // 1: 19 (RX) и 18 (TX); (ModBus Slave подключен)
Serial2.begin(9600); // 2: 17 (RX) и 16 (TX); свободен
Serial3.begin(9600); // 3: 15 (RX) и 14 (TX) свободен
Здравствуйте!
Эта библиотека не работает с Mega. Собираюсь сделать такой вариант, но сейчас нет времени.
Ок. Попробую распространенные библиотеки. Может для уроков лучше использовать, что то более стандартизированное, в смысле библиотек?
Здравствуйте, а вообще на какой-нибудь плате, где есть несколько Serial, можно подключить данную библиотеку? Хочется выводить показания на экран, а не на дисплей.
Здравствуйте!
Библиотека предназначена для работы с одним Serial ATmega328/168. Что-то пытались переделывать для Mega2560. Я уже не помню, что получилось. Посмотрите на форуме сайта http://mypractic-forum.ru/viewtopic.php?t=110.
Эдуард, добрый день.
При компиляции первого скетча ошибок много.
На втором примере ошибка осталась одна:
In file included from C:\Data\Arduino\sketch_200422b_Tiny_ModBus_Master\sketch_200422b_Tiny_ModBus_Master.ino:4:0:
C:\Data\Arduino\libraries\Tiny_ModBusRTU_Master/Tiny_ModBusRTU_Master.h:47:10: warning: extra qualification ‘Tiny_ModBusRTU_Master::’ on member ‘errorFrame’ [-fpermissive]
void Tiny_ModBusRTU_Master::errorFrame(byte st); // ошибка кадра
^~~~~~~~~~~~~~~~~~~~~
————————————————————-
Ничего не поменялось в библиотеке с 15.01.2018
http://mypractic.ru/urok-58-obmen-dannymi-mezhdu-platami-arduino-cherez-uart-po-protokolu-modbus-biblioteka-tiny_modbusrtu_master.html#comment-10715
Здравствуйте!
В конце марта я подправил библиотеку Tiny_ModBusRTU_Slave. Добавил метод setAdress(), позволяющий оперативно устанавливать адрес сетевого устройства.
У вас предупреждение, а не ошибка.
Спасибо за оперативный ответ,
только это никак не помогло 🙂
На дисплее мастера ERROR=2
Для инфо, вдруг связано, показания с LM35 дефектные. Переменная с температурой на уровне 435,5 С.
Вы загрузили мой скетч?
Ведомое устройство отвечает? Мигает светодиод прием на ведущем устройстве?
Ведомое (slave) работает без ошибок, если не считать значение температуры, но это не важно.
Оба скетча (master и slave)загрузил.
В мастере поменял вывод на двустрочный дисплей:
#include
#include «LiquidCrystal_I2C.h»
LiquidCrystal_I2C disp(0x27, 16, 2);
На ведомом расчет температуры для LM35.
Оба изменения не принципиальны для передачи данных.
На ведомом мигает TX и L.
На ведущем мигает RX и L.
На дисплее со старта:
C=0 E=1
ERROR=2
Далее:
C=1 E=3
C=2 E=5
C=3 E=7
и т.д.
Совсем не работает с DUE
1. В файле Tiny_ModBusRTU_Master.h опечатка в строке 47, нужно заменить void Tiny_ModBusRTU_Master::errorFrame(byte st); // ошибка кадра
на void errorFrame(byte st); // ошибка кадра
это позволит исправить ошибку типа «extra qualification».
2. В примере, в месте вывода на дисплей,
disp.print(«T=»);
disp.print(* ((float *)regTable),1);
я заменил на:
disp.print(«T=»);
memcpy(&sens1, ®Table, 4);
disp.print(sens1),1);
предварительно создав переменную:
float sens1;
Это избавило от предупреждения:
dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
Теперь работает даже на ProMini (моя любимая плата).
PS. Эдуард, ОГРОМНОЕ спасибо тебе за твои уроки. Очень нравятся. Жаль что последние уроки не такие понятные как в начале, приходится много искать в других источниках, чтоб понять.
Сейчас пока плотно работаю с этой библиотекой, хотел бы ещё добавить, может кому и пригодится. Эдуард, рад буду если меня подкорректируешь.
1. В файле Tiny_ModBusRTU_Master.cpp
while (true) { if (Serial.read() == 0xffff) break;} // сброс порта
заменить на:
while (true) { if (Serial.read() == int(0xffff)) break;} // сброс порта
for(int i=0; i<_holdingRegNumber; i++)
заменить на:
for(int i=0; i<int(_holdingRegNumber); i++)
всего 6 замен.
2. Тоже самое надо сделать в файле Tiny_ModBusRTU_Slave.cpp в процедуре:
void Tiny_ModBusRTU_Slave::errorFrame
кажется в этом файле всего одна замена, компилятор вам подскажет.
3. Если вы при создании объекта данного класса (Tiny_ModBusRTU) оставите временные настройки интервалов и прерываний как в примере то, ни в коем случае не передавайте регистр длинною 9 байт типа int, на скорости 9600, так как при таком сочетании LCD дисплей (16х2) начинает вести себя не адекватно (весь экран заполняется постоянно меняющимися иероглифами).
Любопытно, это так мне повезло или ещё кто увидит такое явление. Надеюсь ему это сообщение поможет.
жалко что с мегой не работает придется сочинять свой простенький протокол если не найду подходящий .
Эдуард, спасибо за библиотеку, столкнулся с проблемой при опросе регистров промышленного счетчика импульсов по Modbus, мой мастер выдает ошибку 2 , (тайм-аут). Увеличение параметров в строке (volatile Tiny_ModBusRTU_Master master(8, 30, 13);) не помогли. Может что посоветуете?
Здравствуйте!
А ответ от счетчика приходит? Светодиод на RxD Ардуино мигает?
нет, не мигает. Мастером на компе читаю регистры с счетчика нормально, а вот ардуиной почему то не получается. Пример скетча.
void loop() {
lcd.setCursor(0, 1);
lcd.print(int (master.state));
if (mode == 0) {
master.read(18, regTable, 0, 4);
mode = 1;
}
//—————————————————
if (mode == 1) {
if (master.state == 0) {
lcd.setCursor(0, 0);
lcd.print(* (((long *)regTable)));
mode = 0;
}
}
}
Еще из за стоп бита не может быть? Счетчику нужно число 2, на 1 дает ошибку, когда опрашиваю через компьютер.
Здравствуйте!
Возможно. Попробуйте задать 2 стоп бита.
UCSR0C |= 8;
Здуард, здравствуйте! а где это прописать?
После Serial.begin.
наверно прямо в Setup?
но теперь хоть rx моргает, наверно задержки попробую подобрать.
Спасибо, задержки поправил, пошли данные
буду должен, обещаю. Все получилось!!!
Рад, что помог.
Здравствуйте! Тут многие пишут что библиотека не работает с Мега256, в чем это выражается? Я подключил Мегу и счетчик, и Меага все прочитала, без ошибок. Подключал к Serial0, типа как на Uno.
Здравствуйте Эдуард.
урок 57
Подскажите пожалуйста,
// перегрузка состояния светодиода из таблицы регистров
if ( (regTable[5] & 1) == 0) digitalWrite(5, LOW);
Если надо увеличить число подключаемых СИДов, к примеру до трех штук,
увеличиваем таблицу регистров на три до семи «if ( (regTable[7] & 1) == 0)»?
урок 58
Второй вопрос.
Что означает «1» в строке «if((mode & 1)==0){«?
Вопрос третий.
Что означает «1» в строке «disp.print(* ((float *)regTable),1);»?
Вопрос четвертый
Что мы записываем в регистр хранения «(unsigned int)data», «1» или «true»?
master.writeSingle(1, (unsigned int)button1.flagPress, 5); // запись регистра хранения (светодиод)
ВЫ в первых уроках пишете, что функцию delay() не желательно использовать, потому как программа
останавливается. От этой функции можно отказаться?
Спасибо.
Добрый вечер!
1. Не обязательно. В слове regTable[5] 16 бит. Для управления светодиодом достаточно одного бита. Мы один и используем. Если задействовать остальные биты можно управлять 16тью светодиодами.
if ( (regTable[5] & 1) == 0) digitalWrite(5, LOW);
if ( (regTable[5] & 2) == 0) digitalWrite(6, LOW);
if ( (regTable[5] & 4) == 0) digitalWrite(7, LOW);
if ( (regTable[5] & 8) == 0) digitalWrite(8, LOW);
. . . . . . . .
2. Формально выполните действия и поймете. С переменной mode производится логическое умножение на 1, т.е. оставляется только младший бит. Состояние остальных никак не влияет на результат. В итоге проверяется младший бит переменной mode равен 0?
3. Смотрите формат функции print. Последний аргумент задает число разрядов после запятой. В примере число выводятся с точностью 1 десятая.
4. Для булевых выражений 0 это false, а не ноль это true. Можно вместо true записать, например, 5.
5. Надо смотреть по ситуации. Иногда зависание программы на пару миллисекунд не критично, а иногда это фатально. В общем случае нехорошо использовать ее для длительных задержек, особенно если выполняется несколько задач.
Здравствуйте Эдуард.
Функцию «master.writeSingle(1, (unsigned int)button1.flagPress, 0);» мы используем если надо управлять одним
единственым светодиодом на ЛК?
А если несколькими надо управлять светодиодами на ЛК то используем функцию «master.writeMultiple(1, regTable, 5, 2);»?
master.writeMultiple(1, button1.flagPress, 5, 2); //светодиод 1
master.writeMultiple(1, button2.flagPress, 6, 2); //светодиод 2
master.writeMultiple(1, button3.flagPress, 7, 2); //светодиод 3
…
master.writeMultiple(1, buttonN.flagPress, N, 2); //светодиод N
Спасибо.
Здравствуйте!
Функции записи в регистры используются для передачи данных от ведущего устройства к ведомым. Сколько данных надо передать для управления светодиодами — это вопрос кодирования данных. Можно одним 16ти разрядным словом управлять 16тью светодиодами.
Если для управления использовать отдельные регистры для каждого светодиода, то надо сделать запись в несколько регистров ведомого устройства и использовать функцию master.writeMultiple. Но не так, как вы написали.
Функция master.writeMultiple осуществляет запись в несколько подряд расположенных регистров. Т.е. вызов master.writeMultiple выполняет то же действие, что и несколько вызовов master.writeSingle. Посмотрите внимательно форматы функций
void writeSingle(byte adress, unsigned int data, unsigned int holdingRegBegin)
void writeMultiple(byte adress, unsigned int* reg, unsigned int holdingRegBegin, unsigned int holdingRegNumber)
В первом случае указывается адрес регистра и данное для него. Во втором — начальный адрес блока данных и их количество.
Здравствуйте Эдуард.
Прошу Вас подсказать , что у меня не правильно.
Для эксперbмента и закрепления материала, добавил две кнопки к ЦК и два СИДа к ЛК.
Чтобы оба Ваших скетча не повторять, я изменил некоторые Ваши строчки и добавил свои.
Центральный контроллер (UNO).
Изменил строчку:
unsigned int regTable[8]; // таблица регистров
добавил строчки:
master.writeSingle(1, (unsigned int)button2.flagPress, 6);
master.writeSingle(1, (unsigned int)button3.flagPress, 7);
button2.scanState();
button3.scanState();
——————————————
Локальный контроллер (NANO) из урока 57
Изменл строчки:
unsigned int regTable[8]; //таблица регистров
Tiny_ModBusRTU_Slave slave(1, 8, regTable, 8); // создаем объект ModBus, адрес 1,
//таимаут 4 мс, массив , размер 6
добавил строчки:
pinMode(6, OUTPUT); //СИД 2
pinMode(7, OUTPUT); //СИД 3
if ((regTable[6] & 1) ==0) digitalWrite(6, LOW); //светодиод 2
else digitalWrite(6, HIGH);
if((regTable[7] & 1) ==0) digitalWrite(7, LOW); //светодиод 3
else digitalWrite(7, HIGH);
Проверил в работе (обаварианта урока 58-1 и 58-2) и обнаружил, что светодиоды 1 и 2 подключеные
к выводам 5 и 6 ЛК не отвечают на нажатие кнопок button1 и button2 на ЦК. При нажатии
кнопки button3 на ЦК светодиод 3 подключеный к выводу 7 ЛК отвечает.
по шагово за коментировал строчки
// master.writeSingle(1, (unsigned int)button3.flagPress, 7);. Светодиод 2 начал отвечать на
кнопку button2, а светодиод 1 также не отвечает на кнопку button1. Таким образом перебрал
все строчки и убедился, что только один светодиод отвечает на свою кнопку.
ЦК получает показания температуры, напряжени и кнопки от ЛК без ошибок.
Здравствуйте!
Команда записи или чтения регистра запускает соответствующую операцию. Она происходит параллельным процессом в обработчике прерывания и требует определенного времени. Вы должны дождаться завершения чтения или записи и только потом вызывать следующую операцию. Например, так
while(master.state == 1) {} // ожидание данных
Только после этого можно вызывать следующую операцию MODBUS. А вы запускаете сразу несколько команд
master.writeSingle(1, (unsigned int)button2.flagPress, 6);
master.writeSingle(1, (unsigned int)button3.flagPress, 7);
Здравствуйте Эдуард.
Получилось, но только скетч 58_1:
if(master.state==0)
master.writeSingle(1, (unsigned int)button1.flagPress, 5); // запись регистра хранения (светодиод)
while(master.state == 1) {} // ожидание данных
if(master.state==0)
master.writeSingle(1, (unsigned int)button2.flagPress, 6);
while(master.state == 1) {} // ожидание данных
if(master.state==0)
master.writeSingle(1, (unsigned int)button3.flagPress, 7);
while(master.state == 1) {} // ожидание данных
if(master.state != 0) errorCount++;
delay(500);
}
А вот скетч 58_2 так и не получилось. Подскажите, что в нем добавить, изменить?
Спасибо.
Здравствуйте!
Вы понимаете разницу между этими программами. В первом случае при ожидании выполнения операции ModBus программа зависает в цикле while(master.state == 1) {} // ожидание данных. Во втором случае даже при ожидании завершения операции обмена программа ходит по циклу loop.
В моем примере только одна команда обмена. В цикле loop есть две ветки, которые определяются условием if( (mode & 1) == 0 ) .
В вашем случае надо выделить несколько веток.
if( mode == 0 ) {
// ожидание завершения первой команды
}
else if( mode == 1 ) {
// ожидание завершения второй команды
}
else if( mode == 2 ) {
// ожидание завершения третей команды
}
В этих блоках проверять условие if( master.state == 0 ) и по нему запускать следующую команду и переходить на следующий шаг mode=1;
Это принцип построения таких программ в двух словах.
Переменная mode определяет по какой ветки проходит программа, а в самой ветке производятся соответствующие действия.
Здравствуйте Эдуард.
Заработало! Но! Обратите внимание на блок button3 управление светодиодом 3 ЛК, если встроке «if( mode == 3 ) {»
заменить «3» на «2» то программа не работает. Светодиоды ЛК на кнопки ЦК отвечают, а ЦК не принимает данные
(температура, напряжение, нажатие кнопки) от ЛК. Светодиоды Rx и Tx на ЛК мигают.
void loop() {
if( mode == 0 ) {
// чтение регистров хранения
if(master.state != 1) {
// операция завершена
if(master.state == 0) {
// данные получены
disp.clear(); // очистка экрана
disp.print(«C=»);
disp.print(cyclCount);
disp.print(» E=»);
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print(«T=»);
disp.print(* ((float *)regTable),1);
disp.print(» C U=»);
disp.print(* ( ((float *)regTable) +1 ),1);
if ( (regTable[4] & 1) == 0) disp.print(» V B=F»);
else disp.print(» V B=P»);
}
else {
// ошибка обмена
errorCount++; // счетчик ошибок
disp.clear(); // очистка экрана
disp.print(«C=»);
disp.print(cyclCount);
disp.print(» E=»);
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print(«ERROR= «);
disp.print(master.state);
}
master.writeSingle(1, (unsigned int)button1.flagPress, 5); // запись регистра хранения (светодиод 1)
mode=1;
}
}
if( mode == 1 ) {
// чтение регистров хранения
if(master.state != 1) {
// операция завершена
if(master.state == 0) {}
master.writeSingle(1, (unsigned int)button2.flagPress, 6); //светодиод 2
mode=2;
}
}
if( mode == 3 ) {
// чтение регистров хранения
if(master.state != 1) {
// операция завершена
if(master.state == 0) {}
master.writeSingle(1, (unsigned int)button3.flagPress, 7); //светодиод 3
mode=0;
}
}
else{
// запись регистра хранения
if(master.state != 1) {
// операция завершена
if(master.state != 0) errorCount++;
master.read(1, regTable, 0, 5); // чтение регистров хранения
mode++;
cyclCount++; // счетчик циклов
}
}
delay(500);
Спасибо.
Здравствуйте Эдуард.
Скажите пожалуйста, что делать?
К ЦК подсоединил подстроечный резистор, при установке некоего напряжения
вырабатывается признак LED_ON, по сети на ЛК отправляю этот сигнал для управления СИДом.
СИД отвечает, но мигает с некой (timerInterrupt()?) частотой. Функция delay() за коментирована.
Я думал, что признак LED_ON установлен СИД светится, признак LED_OFF его погасит?!
Кнопку держу нажатой СИД светится постояно, по логике тогда и СИД должен мигать, но не мигает.
Спасибо.
Здравствуйте!
Что я могу сказать. Может, у вас логика управления светодиодом неправильная. Зачем 2 признака LED_ON и LED_OFF. Достаточно одного. Светодиод либо светится, либо не светится.
Добрый день.
«Убил» весь день но так и не смог добиться «правды».
Пытаюсь читать сторонне ModBus устройство и выводить значение считанного регистра на 7-серм.индикатор.
#include
#include
#include
#include
#define ADR_CONTR 5 // адрес контроллера 5
volatile Tiny_ModBusRTU_Master master(28, 120, 5);
// тип индикатора 1; выводы разрядов 5,4,3,2; выводы сегментов 6,7,8,9,10,11,12,13
Led4Digits disp(1, 2,3,4,5, 6,7,8,9,10,11,12,13);
unsigned int regTable[2]; //таблица регистров
unsigned int cyclCount= 0; // счетчик циклов
unsigned int errorCount= 0; // счетчик ошибок
byte mode=0; // режим
int timeCount = 0;
int timeRead = 0;
void setup() {
MsTimer2::set(2, timerInterrupt); // прерывание по таймеру 2 мс
MsTimer2::start(); // разрешение прерывания
Serial.begin(9600);
// UCSR0C |= 8;
}
void loop() {
if (timeRead > 10){
master.read(ADR_CONTR, *(regTable), 264, 2); // чтение регистров контроллера 1
timeRead = 0;
}
if(master.state != 1) {
if(master.state == 0) { // данные получены
disp.print((*(regTable)), 3, 1);
}
if(master.state == 2) { // таймаут
disp.print(202, 3, 1);
}
if(master.state == 4) { // ошибка данных
disp.print(404, 3, 1);
}
if(master.state == 8) { // неправ. адрес
disp.print(808, 3, 1);
}
if(master.state == 16) { // код функции
disp.print(116, 3, 1);
}
if(master.state == 32) { // другая ошибка
disp.print(132, 3, 1);
}
}
}
// обработчик прерывания 2 мс
void timerInterrupt() {
disp.regen(); // регенерация индикатора
timeCount++;
if (timeCount >= 250) {
timeCount = 0;
timeRead++;
master.update();
}
}
На сетевой карте светодиоды TX/RX мигают. Если разорвать витую пару либо если сменить адрес Slave контроллера TX мигает, RX -= нет.
Т.о. предполагаю, что Slave отвечает. Но в regTable категорически не меняются данные.
Ни как не соображу как правильно подбирать timeOutTransmit и timeOutRecieve. С какой периодичностью необходимо вызывать master.update() ? Где настраивать формат передачи данных?
Здравствуйте!
У вас функция обработки данных UART master.update() вызывается с периодичностью 2 мс. А при выбранной вами скорости UART 9600, данные поступают с периодичностью 1 мс. Т.е. данные UART теряются. Необходимо, чтобы период вызова master.update() был по крайней мере в 2 раза чаще, чем скорость поступления данных.
Посмотрите примеры из уроков. Начните отталкиваться от них. Затем будете изменять параметры на свои.
Ну почему же, вызов update у меня с периодичностью 500 МС. А чтение с периодичностью 5000 МС (5 сек.).
При скорости 9600 данные в буферный регистр UART поступают каждую 1 мс. До прихода нового байта предыдущий должен быть считан из UART. Чтение данных производится в функции update(). Поэтому она должна вызываться чаще, чем приходят данные UART.
А вызов update() у вас каждые 2 мс.
MsTimer2::set(2, timerInterrupt); // прерывание по таймеру 2 мс
Нет, у вас каждые 500 мс! Это совершенно недопустимо.
Повторите мои примеры. Посмотрите еще в статьях об умном доме. Там тоже эта библиотека используется.
Мне необходимо опрашивать ведомое с периодичностью 1 раз в 5(10) секунд.
Как я понимаю запрос ведомому происходит по команде master.read, что я и реализовал.
По факту я вижу мигание светодиода в с этой периодичностью.
А вот по update я не понимаю ее назначения.
Здравствуйте!
Функция update() собственно и осуществляет обмен по протоколу Modbus между ведущим устройством и ведомыми. На ведомом устройстве в прерывании по таймеру она должна вызываться с определенным периодом. Таким образом, массив регистров Modbus ведомого устройства становится доступным ведущему устройству. И это производится параллельным процессом в фоновом режиме.
Посмотрите разработку программы для умного дома.
Спасибо.
Победил!
Эдуард добрый день.
Подскажите, каким образом можно считывать с ведомого несколько гегистров размещенных не последовательно.
Сейчас читаю master.read(ADR_CONTR, regTable, 264, 2); — все нормально.
Но необходимо еще читать master.read(ADR_CONTR, regAlarm, 537, 1);
Здравствуйте!
Все правильно. Последняя строка тоже должна работать.
Два запроса на чтение последовательно, а потом проверка ..master.state ?
Почему 2 запроса. Запрос, потом проверка и ожидание выполнения команды.
Эдуард добрый день.
В продолжении переписки по поводу чтения ведомого.
У меня получается, что «master.read(ADR_CONTR, regTable, 264, 2);» читается нормально. Регистр с адресом 264 — это регистр хранения.
А вот «master.read(ADR_CONTR, regAlarm, 537, 1);» не читается. По моему проблема в том, что регистр с адресом 537 — это регистр флагов.
Странно!
Последний мой вопрос «повис в воздухе». Тем не менее задам еще один.
На UNO программа работает безупречно. Попытался перенести проект на NANO и «начались танцы с бубнами».
Вроде ничего мудреного. Возвращается State = 4 (ошибка данных). Причем иногда (очень редко) все таки проскакивает чтение правильно.
Здравствуйте!
Это какая-то аппаратная ошибка. Для программы нет никакой разницы между UNO и NANO.
Видимо так.
На эту мысль меня натолкнул тот факт, что на одной плате NANO ошибка 4, а на другой ошибка 2 !
Есть подозрение, что это подгаживает мост UART-USB. Попробую отцепить его.
Нашел две заморочки.
У одной NANO выпаял резисторы между UART и USB мостом. Ошибка 2 исчезла. Получается, что именно мост не переведен в режим Z.
Вторая заморочка в том, что при реализации динамической индикации на 4 разряда 7 сегментных индикаторов, для управления ключами давал задержку delay(1). Сейчас сделал delayMicroseconds(500) — ошибка ушла. Но что то не нравится такой вариант. Как бы реализовать останов прерывания для чтения ModBus на время «обслуживания» индикаторов.
Здравствуйте!
Эти резисторы мешать обмену не должны. Выход конвертера USB находится в состоянии логической 1. Т.е. вход Rx микроконтроллера подтягивается к + 5 В.
Что касается динамической индикации LED-индикаторов, попробуйте мою библиотеку. Регенерация дисплея происходит в том же прерывании, что обмен по Modbus.
Я индикатор подключил через сдвиговый регистр. Ваша библиотека не подойдёт.
Подскажите пожалуйста как настраивать фреймы на контроль четности, количество бит и пр.
Здравствуйте!
Установкой режима непосредственно в регистрах UART ATmega328. Форматы регистров можно посмотреть в документации на ATmega328.
Например, для режима с 2мя стоповыми битами надо в setup() после инициализации Serial добавить UCSR0C |= 8;
Для проверки на четность UCSR0C |= 0x20; UCSR0C &= 0xef;
Для проверки на не четность UCSR0C |= 0x30;
У меня пару лет назад задавали такой вопрос по поводу проверки на четность. Все работало.
Конечно, лучше собрать параметры и передать одним пакетом. Получится намного быстрее.
Я так понял UCSR0C — регистр конфигурации UART. А что означает |= ?
Таким образом маску накладываем?
UCSR0C |= 8; Сокращенная запись UCSR0C = UCSR0C | 8;
Т.е. логическое ИЛИ UCSR0C с числом 8, или установка бита 3 в 1.
Эдуард добрый день.
Есть потребность в библиотеке реализующей функцию modbus spy, его еще называют sniffer. Вы не рассматривали вопрос написать такое?
Здравствуйте!
Нет, не думал, не встречал такой необходимости.
Вот у меня встретилась такая необходимость.