Modbus + RS485 + Готовый шилд

gregoryl
Offline
Зарегистрирован: 09.09.2013

Добрый вечер.

Нашел на ебай http://www.ebay.com/itm/181214698630?ssPageName=STRK:MEWAX:IT&_trksid=p3984.m1438.l2649

Все просто и понятно.... Но вот засада, все библиотеки которые я видел, не требуют пина на котором будет сидеть переключение режимов RX/TX для 485 они просто пишут в Serial то что надо для ModBus. Схемотехника и AVR дело новое, и слова "чудо" и "религия" всплывают чуть ли не в каждой схеме :-)

Вот тут я никак чуда насчупать не могу.

Например в freemodbus есть такой код, в другой библиотеке очень похожий...

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
#ifdef RTS_ENABLE
    UCSRB |= _BV( TXEN ) | _BV(TXCIE);
#else
    UCSRB |= _BV( TXEN );
#endif

    if( xRxEnable )
    {
        UCSRB |= _BV( RXEN ) | _BV( RXCIE );
    }
    else
    {
        UCSRB &= ~( _BV( RXEN ) | _BV( RXCIE ) );
    }

    if( xTxEnable )
    {
        UCSRB |= _BV( TXEN ) | _BV( UDRE );
#ifdef RTS_ENABLE
        RTS_HIGH;
#endif
    }
    else
    {
        UCSRB &= ~( _BV( UDRE ) );
    }
}

Так вот вопрос, надо ли включать RTS_ENABLE и есть ли пин которые отвечает за RX/TX для этого кода. 

З.Ы. Я паралельно копаю мануол на регистры но пока понятнее не становится....

vlkam
Offline
Зарегистрирован: 17.02.2013

 Начнем с того, что указанный модуль на ебее по сути есть голая микросхема MAX485 и если взять на неё доку там очень хорошо все расписано.

http://www.tlsoundandlight.com/ISC_Material/MAX485.pdf

У этой микрухи есть 2 выхода Receiver Output Enable и Driver Output Enable. Их можно смело объединить и завести на цифровой пин Ардуины. Это и будет переключение режима РХ/ТХ. И можешь с ней смело работать через Serial. Хочешь софт, хочешь хард (хотя с софтом там засада есть, если нужно могу посмотреть)

Т.е. эта микросхема умеет либо только передавать, либо только принимать.

И кстати обрати внимание, если послать в неё данные и отключить режим передачи до того, как они будут переданы, то данные могут передаться не все. Вот такая засада

gregoryl
Offline
Зарегистрирован: 09.09.2013

Что это мне и так понятно.... 

Вопрос был именно в том, как присабачить этот шилд к готовым библиотекам протокола.

Написать, я тоже могу в принципе все что угодно, даже поправить могу если очень надо, просто не хочется трогать чужой код или писать свой протокол.

 

xstramento
Offline
Зарегистрирован: 19.10.2013

vlkam пишет:

 Начнем с того, что указанный модуль на ебее по сути есть голая микросхема MAX485 и если взять на неё доку там очень хорошо все расписано.

http://www.tlsoundandlight.com/ISC_Material/MAX485.pdf

У этой микрухи есть 2 выхода Receiver Output Enable и Driver Output Enable. Их можно смело объединить и завести на цифровой пин Ардуины. Это и будет переключение режима РХ/ТХ. И можешь с ней смело работать через Serial. Хочешь софт, хочешь хард (хотя с софтом там засада есть, если нужно могу посмотреть)

Т.е. эта микросхема умеет либо только передавать, либо только принимать.

И кстати обрати внимание, если послать в неё данные и отключить режим передачи до того, как они будут переданы, то данные могут передаться не все. Вот такая засада

  есть пример схемы подключения модуля 485 к ардуино?

vlkam
Offline
Зарегистрирован: 17.02.2013

Схема громко сказано.

Обычно у таких модулей 4 контакта + питание.

2 контакта это обычные Serial RX и TX

2 контакта переключение между приемом и передачей.  Их объединяешь и кидаешь на цифровой порт ардуины. Его состояние соответственно будет переключением между приемом и передачей.

 

xstramento
Offline
Зарегистрирован: 19.10.2013

vlkam пишет:

Схема громко сказано. Обычно у таких модулей 4 контакта + питание. 2 контакта это обычные Serial RX и TX

2 контакта переключение между приемом и передачей.  Их объединяешь и кидаешь на цифровой порт ардуины. Его состояние соответственно будет переключением между приемом и передачей.

у этого   http://www.lctech-inc.com/Hardware/Detail.aspx?id=608101e2-7167-4926-bd6...

{DI, DE, RE, RO}  и  { VCC, GND,A и B} - по второй группе выводов вопросов нет, а по первой непонятно.

 

 

vlkam
Offline
Зарегистрирован: 17.02.2013

DI Это TX Serial

RO это RX Serial

DE и RE соединяются и служат для переключения между режимом приема и передачи.

Подробнее см

http://www.tlsoundandlight.com/ISC_Material/MAX485.pdf

 

 

xstramento
Offline
Зарегистрирован: 19.10.2013

vlkam пишет:

DI Это TX Serial, RO это RX Serial, DE и RE соединяются и служат для переключения между режимом приема и передачи. Подробнее см

http://www.tlsoundandlight.com/ISC_Material/MAX485.pdf

спасибо, а библиотека для 485 встроенная в Ардуино или нужно искать внешнюю (модбас мне не нужен)

класс Serial наверное не подойдет, тк здесь  нужно управлять третьей ногой 485-го интерфейса?

vlkam
Offline
Зарегистрирован: 17.02.2013

Почему же не подойдет. Как раз Serial очень даже подходит как Hardware, так и  Software.

Но перед передачей или приемом нужно разумеется требуемое состояние выставить (прием или передача)

И с   Software Serial осторожнее, помнится он у меня не сразу заработал, что то там подправлял в стандартной либе

vlkam
Offline
Зарегистрирован: 17.02.2013

 Я вот такой класс использую для работы с RS485

Пугаться не нужно, тут накручено много для универсальности, передачи с подтверждением приема и тд

Все можно гораздо проще сделать

 


class HeaderClass {
	public:
	byte Size; // размер посылки
	byte Command; // команда 
	byte IdDevice; // номер устройства
};

class RS485 {
	
	private:
	bool KindOfSerial; // 0 - hardware 1 -software
	void *AnSerial;
	//HardwareSerial *HWSerial;
	//SoftwareSerial *SWSerial;

	public:


	byte TransmitModePin;			// пин, переключающий режим приема передачи RS485 низкий уровень - чтение, высокий 
	byte ResievedByteOfHeader;		// количество полученных байт заголовка
	byte ResievedByteOfData;		// количество полученный байт данных
	uint8_t buffer[BUFFER_SIZE];
	HeaderClass Header;
	byte Status;
	
	
	//RS485(byte TransmitModePin_);
	RS485(HardwareSerial *HWSerial_,byte TransmitModePin_);
	RS485(SoftwareSerial *SWSerial_,byte TransmitModePin_);
	void Constr(byte TransmitModePin_);


	void Send(void *msg,byte size_struct,byte command,byte IdDevice);
	boolean SendWithConfirmation(void *msg,byte size_struct,byte command,byte IdDevice);
	
	void Reset();
	byte Read();
	byte WaitRead(unsigned int pause = 1000); 
	
	void SetReadMode();
	void SetWriteMode();
	void SendConfirmation(byte IdDevice);

	void ChangeModeDelay();
};


RS485::RS485(HardwareSerial *HWSerial_,byte TransmitModePin_){
	
	KindOfSerial = 0;
	AnSerial = HWSerial_;
	HWSerial_->begin(SERIAL485SPEED);

	Constr(TransmitModePin_);
}

RS485::RS485(SoftwareSerial *SWSerial_,byte TransmitModePin_){
	
	KindOfSerial = 1;
	AnSerial = SWSerial_;
	SWSerial_->begin(SERIAL485SPEED);
	Constr(TransmitModePin_);
}

void RS485::Constr(byte TransmitModePin_){
	TransmitModePin = TransmitModePin_;
	pinMode(TransmitModePin, OUTPUT);
	SetReadMode();
	Reset();
}

void RS485::Send(void *msg,byte size_struct,byte command,byte IdDevice){
	
	SetWriteMode();
	Reset();
	
	byte header_size = sizeof(Header);
	Header.Command = command;
	Header.IdDevice = IdDevice; 
	Header.Size = size_struct;

	buffer[0] = HEADPREF1;
	buffer[1] = HEADPREF2;

	memcpy(&buffer[HEADPREF_SIZE],&Header,header_size);

	if(size_struct > 0) {
		memcpy(&buffer[header_size + HEADPREF_SIZE],msg,size_struct);
	}

	// показать отправляемые данные
	//Serial.println(String(" Qnt ")+String());
	//for(int i=0;i<(header_size + size_struct + HEADPREF_SIZE);i++){Serial.println(buffer[i]);}

	//RS485Serial.write(buffer,header_size + size_struct  + HEADPREF_SIZE);
	if(KindOfSerial == 0){
		((HardwareSerial *)AnSerial)->write(buffer,header_size + size_struct  + HEADPREF_SIZE);
	} else {
		((SoftwareSerial *)AnSerial)->write(buffer,header_size + size_struct  + HEADPREF_SIZE);
	}
	
	ChangeModeDelay();
	SetReadMode();
}

void RS485::Reset(){
	Status = NOSTATUS;
	ResievedByteOfHeader = 0;
	ResievedByteOfData = 0;
}

byte RS485::Read(){
	
	SetReadMode();
	byte dta;

	//while(RS485Serial.available() > 0){	dta = RS485Serial.read();Serial.println(dta);} return 0;
	
	//while(RS485Serial.available() > 0){
	while(1){	
		int avaibl = 0;
		if(KindOfSerial == 0){
			avaibl = ((HardwareSerial *)AnSerial)->available();
		} else {
			avaibl = ((SoftwareSerial *)AnSerial)->available();
		}
		if(avaibl == 0){
			break;
		}


		//dta = RS485Serial.read();
		if(KindOfSerial == 0){
			dta = ((HardwareSerial *)AnSerial)->read();
		} else {
			dta = ((SoftwareSerial *)AnSerial)->read();
		};
		
		//Serial.println(dta);

		switch(Status){
		
			case NOSTATUS :
				if(dta == HEADPREF1){
					Status = CATCHPREF1;
				};
				break;

			case CATCHPREF1 :
				if(dta == HEADPREF2){
					Status = CATCHPREF2;
				} else {
					Reset();
				};
				break;
				
			case CATCHPREF2 :
				if(ResievedByteOfHeader < sizeof(Header)){
					((uint8_t *)&Header)[ResievedByteOfHeader] = dta;
					ResievedByteOfHeader ++;
				}
				if(ResievedByteOfHeader == sizeof(Header)){
					if(Header.Size == 0 ){ // окончили чтение, данных нет, только заголовок
						Reset();
						return Header.Command;
					}
					if(Header.Size > BUFFER_SIZE){// какая то ерунда лезет, игнорируем
						Reset();
					}
					// иначе ожидаем данные
					Status = WAITDATE;
				};
				break;

			case WAITDATE :
				if(ResievedByteOfData < Header.Size){
					buffer[ResievedByteOfData] = dta;
					//Serial.println(String(ResievedByteOfData)+String(" -")+String(dta));
					//Serial.println(dta);
					ResievedByteOfData ++;
					
				}
				if(ResievedByteOfData == Header.Size){
					Reset();
					return Header.Command;
				}
				break;

		}
		
	};
	return 0;
}

byte RS485::WaitRead(unsigned int pause){ 
	unsigned long timeend = millis() + pause;
	while(millis() < timeend){
		byte res = Read();
		if(res > 0 ){
			return res;
		};
		delay(10);
	}
	return 0;
}


//	void Send(void *msg,byte size_struct,byte command,byte IdDevice);
//	boolean SendWithConfirmation(void *msg,byte size_struct,byte command,byte IdDevice,byte pause = 1000);

boolean RS485::SendWithConfirmation(void *msg,byte size_struct,byte command,byte IdDevice){
	Send(msg,size_struct,command,IdDevice);
	byte Command = WaitRead();
	return Command == MESSAGE_DELIVERED ? true : false; 
}

// Включить режим чтения 
void RS485::SetReadMode(){
	digitalWrite(TransmitModePin, LOW);
	if(KindOfSerial == 1){
		((SoftwareSerial *)AnSerial)->isBlocking = false;
	}
	//Serial.println("Set read mode RS485");
}

void RS485::SetWriteMode(){
	if(KindOfSerial == 1){
		((SoftwareSerial *)AnSerial)->isBlocking = true;
	}

	digitalWrite(TransmitModePin, HIGH);
}

void RS485::SendConfirmation(byte IdDevice){
	ChangeModeDelay();
	Send(this,0,MESSAGE_DELIVERED,IdDevice);
	SetReadMode();
}

void RS485::ChangeModeDelay(){
	delay(RS485_CHANGE_MODE_DELAY);
}

 

xstramento
Offline
Зарегистрирован: 19.10.2013

vlkam пишет:
Почему же не подойдет. Как раз Serial очень даже подходит как Hardware, так и  Software.

Но перед передачей или приемом нужно разумеется требуемое состояние выставить (прием или передача)

И с   Software Serial осторожнее, помнится он у меня не сразу заработал, что то там подправлял в стандартной либе

Software Serial мне противопоказан как начинающему.

Какой уровень (HIGH, LOW) нужно выставить перед приемом и передачей, к какому выводу подключить управление состоянием 485 для вашего скетча?  Если его нужно выставить и перед приемой и передачей, то может быть установить в инициализации и не изменять  дальше или тут дело сложнее  ?

Стандартных способов и библиотек для работы с 485 нет ?

 

 

vlkam
Offline
Зарегистрирован: 17.02.2013

 

 Раз Вы начинающий, в мой скетч лезть не нужно, т.к. это и не скетч вовсе, а класс. сам по себе он работать не будет

собственно вся работа с 485 состоит примерно вот в чем

// TransmitModePin - пин переключающий прием/передачу

// Отправка данных
digitalWrite(TransmitModePin, HIGH);
Serial.write(buffer,size);

// чтение данных
digitalWrite(TransmitModePin, LOW);
dta = Serial.read();

Относительно стандартных либ для 485 насколько я понял нет

 

xstramento
Offline
Зарегистрирован: 19.10.2013

vlkam пишет:

 

 Раз Вы начинающий, в мой скетч лезть не нужно, т.к. это и не скетч вовсе, а класс. сам по себе он работать не будет

собственно вся работа с 485 состоит примерно вот в чем ...

Относительно стандартных либ для 485 насколько я понял нет

а в режиме ожидания приема, т.е.  проверки буфера Serial.available() держать пин в низком уровне?

digitalWrite(TransmitModePin, LOW);

 

vlkam
Offline
Зарегистрирован: 17.02.2013

xstramento пишет:

а в режиме ожидания приема, т.е.  проверки буфера Serial.available() держать пин в низком уровне?
 

digitalWrite(TransmitModePin, LOW);

Да, именно.

Единственное, что нужно учесть это то, что после передачи Serial.write() нужно дать паузу. Например delay(10) перед переключением в режим ожидания приема. Это делается для того, чтобы MAX485 успел отправить данные

xstramento
Offline
Зарегистрирован: 19.10.2013

Интервала в  10 мсек может быть избыточно или недостаточно в зависимости от скорости и объема передачи . Читал про функцию Serial.flash()  ожидает реального завершения отправки данных.

vlkam
Offline
Зарегистрирован: 17.02.2013

xstramento пишет:
Интервала в  10 мсек может быть избыточно или недостаточно в зависимости от скорости и объема передачи . Читал про функцию Serial.flash()  ожидает реального завершения отправки данных.

Я эту тему очень хорошо ковырял. И экспериментально и теоретически.

Serial не может знать, отправились данные или нет по той простой причине, что отправляет их MAX485, через свой буфер и своими средствами

Так что единственный вариант гарантировать отправку данных - это подбирать паузу после Serial.write()

xstramento
Offline
Зарегистрирован: 19.10.2013

vlkam пишет:
Я эту тему очень хорошо ковырял. И экспериментально и теоретически.

Serial не может знать, отправились данные или нет по той простой причине, что отправляет их MAX485, через свой буфер и своими средствами .Так что единственный вариант гарантировать отправку данных - это подбирать паузу после Serial.write()

 

 

Прочитал описание MAX485 и делаю вывод -  это аналоговый преобразователь уровней. Ни про буфизацию и размер буфера, ни про скорость передачи (MAX про нее не знает и синхронизация под эту скорость ему не нужна) в описании не упоминается. Сверхмалые задержки

Цитата:
30ns Propagation Delays, 5ns Skew
косвенно свидетельствуют об аналоговой передаче информации.

Если ошибаюсь пожалуйста  не стесняйтесь и поправьте меня.

gregoryl
Offline
Зарегистрирован: 09.09.2013

xstramento пишет:

Если ошибаюсь пожалуйста  не стесняйтесь и поправьте меня.

RS485 это цифровой способ передачи http://vsolike.by/ra/RS485.html

xstramento
Offline
Зарегистрирован: 19.10.2013

gregoryl пишет:
RS485 это цифровой способ передачи http://vsolike.by/ra/RS485.html

Я так и понял, что MAX485 это аналоговый транслятор уровней (драйвер шины) и у него нет никакой буферизации, следовательно предположение о том, что момент завершения передачи неизвестен принципиально неверно.

А способ обработки информации в интерфейсе 485 конечно цифровой.

vlkam
Offline
Зарегистрирован: 17.02.2013

Да, с буфером я погорячился.
Однако все мои эксперименты с Serial и попытками заставить его дожидаться полной отправки провалились.
Если у Вас получится, отпишитесь

xstramento
Offline
Зарегистрирован: 19.10.2013

vlkam пишет:
Да, с буфером я погорячился.
Однако все мои эксперименты с Serial и попытками заставить его дожидаться полной отправки провалились.
Если у Вас получится, отпишитесь

Глюк в функции Serial.flush () ?  Давайте спросим у форумчан. Мне сейчас видится такая возможность  ошибки - при низкой скорости передачи и высоком быстродействии МК, не дожидаясь завершения передачи (если понадобился Serial.flush в том виде, как он есть, значит функция передачи работает в фоновом режиме по прерыванию) не применив Serial.flash можно ошибочно изменить уровень управляющего сигнала на прием.

Тогда последовательный порт честно отработает передачу, но стрелочник-то  уже переключил MAX485 в обратное направление передача на этом была оборвана.

sav13
sav13 аватар
Offline
Зарегистрирован: 17.06.2013

А нормальная реализация ModBus для Arduino есть? С Master/Slave и 255 параллельно подключенными устройствами?

gregoryl
Offline
Зарегистрирован: 09.09.2013

sav13 пишет:

А нормальная реализация ModBus для Arduino есть? С Master/Slave и 255 параллельно подключенными устройствами?

Помоему там только 32 на каждую линию :-)

Смотря что вы называете нормальной ? если полную в соответсвии со стандартом, то помоему нет, да их и под другие платформы халявных нет :-) А если базовый функционал, то я встречал 2 или 3.

sav13
sav13 аватар
Offline
Зарегистрирован: 17.06.2013

gregoryl пишет:

Помоему там только 32 на каждую линию :-)

Смотря что вы называете нормальной ? если полную в соответсвии со стандартом, то помоему нет, да их и под другие платформы халявных нет :-) А если базовый функционал, то я встречал 2 или 3.

Да попутал. Не 255 а 247

http://ru.wikipedia.org/wiki/Modbus

Нормальной я считаю, что любой Modbus ANSI/RTU драйвер SCADA системы будет втдеть ведомые ардуинки.

Идея в том, что 485 достаточно дешевая сеть, работающая на большом расстоянии всего по 2-м проводам, а Modbus протокол позволит интегрировать контроллеры с платной/бесплатной SCADA.

Можете предложить другой способ увязать все Arduino в ожну сеть с ПК кроме RF 2.4?

gregoryl
Offline
Зарегистрирован: 09.09.2013

sav13 пишет:

Идея в том, что 485 достаточно дешевая сеть, работающая на большом расстоянии всего по 2-м проводам, а Modbus протокол позволит интегрировать контроллеры с платной/бесплатной SCADA.

Можете предложить другой способ увязать все Arduino в ожну сеть с ПК кроме RF 2.4?

Хм.... Ну вариантов много, вопрос только в цене... например простой Ethernet сильно удобнее ну и дороже конечно.

Я тоже выбрал эту связку 485 + Modbus. но есть некоторые моменты где надо обратную связь... и тут уж либо думать пулинг умный или другие способы связи RF 2.4 - очень проблемно... особенно коллизии.... Ethernet - очень дорого

zeon13
Offline
Зарегистрирован: 15.06.2016

Можете прислать исходник? Пытаюсь Ваш класс прикрутить к своему проекту, ничего не выходит. Я не профессионал, буду благодарен если пришлете кусок кода где можно увидеть инициализацию порта и чтение / запись.

Спасибо!

Veoramid
Offline
Зарегистрирован: 20.07.2016

Доброго времени суток!

Нашлось програмное решение МodBus RS485?

 

Сколько единиц на линии 485 зависит от микросхемы-драйвера 485: 16/32/64/128/255 нужно читать даташит и смотреть схемотехнику устройства.

Микрохема-драйвер RS485 преобразует отдельные физические уровни TTL (0..5v)  приема и передачи в диференциальный сигнал линии связи RS485.

Драйвер занимается ТОЛЬКО физической стороной приема-передачи. Всю логическую сторону формирования байта берет на себя UART/USART. Дополнительно программа пользователя занимается устаноовкой направления работы драйвера "прием" (всегда) "передача" только на время передачи данных.

Важно вовремя перевести драйвер на прием. Если это сделать рано, то мы не передадим (последний) байт. Если опоздаем, то можем пропустить прием байта. Если говорить об AVR контроллере, то, например, тут: http://avr.ru/ready/inter/usart/uart

USART Control and Status Register A – UCSRA.

Bit 6 – TXC: USART Transmit Complete – этот флаг устанавливается по завершению передачи байта. Он также может быть источником прерывания (если разрешено прерывание по завершению передачи и разрешены прерывания, будет сформировании запрос).

Bit 5 – UDRE: USART Data Register Empty – флаг опустошения регистра данных(указывает, готовность к приему/передачи следующего байта), если бит установлен, передатчик/приемник готов.

Думаю нам нужно смотреть "Bit 6 – TXC: USART Transmit Complete". Дело в том, что есть буфер, в который наша программа забрасывает байт на передачу (свободно - бросаем байт - USAR передает), а есть сверхопативный буфер из которого биты передаются в линию, затем формируется паритет, стоповый бит и только тогда передача завершена - установить 485й на прием! Таким образом у программы фактически есть аппаратный буфер почти на 2 байта.

Поскольку на 485й линии все устройства стоят параллельно, то драйвер RS485 нужно максимально быстро ставить на прием после перезагрузки и не допускать ложного устанавливания его на передачу - в таком случае линия будет заблокирована и общение всех остальных устройств будет невозможным.

harbor
Offline
Зарегистрирован: 31.05.2016

Veoramid пишет:

Поскольку на 485й линии все устройства стоят параллельно, то драйвер RS485 нужно максимально быстро ставить на прием после перезагрузки и не допускать ложного устанавливания его на передачу - в таком случае линия будет заблокирована и общение всех остальных устройств будет невозможным.

Помоему ведомое устройство прежде чем передать мастеру по его запросу данные, ждет окончания передачи. То есть оно не начнет передавать пока линия физически не освободиться, то есть пока мастер не "замолчит", и только после этого спустя какой-то таймаут, он начнет ответ мастеру. 

 тут надо понять, что делает драйвер если его перевести в передачу, но ничего не передавать. Будет ли он занимать линию или нет. Но я даташит на мах485 не курил на эту тему пока. Если драйвер стоит на передаче(пин на прием мы не переключили), то ведомое устройство по идее должно как раз ожидать его переключения и только потом передавать

James
Offline
Зарегистрирован: 26.02.2016

я делал slave ардуинку 


#include "ModbusRtu.h"

#define ID   1      // адрес ведомого
#define stlPin  13  // номер выхода индикатора работы
                    // расположен на плате Arduino


//Задаём ведомому адрес, последовательный порт, выход управления TX
Modbus slave(ID, 0, 10); 
int8_t state = 0;
unsigned long tempus;

// массив данных modbus
uint16_t au16data[11];

void setup() {
  // настраиваем входы и выходы
  io_setup();
  // настраиваем последовательный порт ведомого
  slave.begin( 9600 ); 
  // зажигаем светодиод на 100 мс
  tempus = millis() + 100; 
  digitalWrite(stlPin, HIGH );
}

void io_setup() {
  
  digitalWrite(stlPin, HIGH ); 
  pinMode(stlPin, OUTPUT);   
}

void loop() {
  // обработка сообщений
  state = slave.poll( au16data, 11);  
  // если получили пакет без ошибок - зажигаем светодиод на 50 мс 
  if (state > 4) {
    tempus = millis() + 50;
    digitalWrite(stlPin, HIGH);
  }
  if (millis() > tempus) digitalWrite(stlPin, LOW );
  //обновляем данные в регистрах Modbus и в пользовательской программе
  io_poll();
} 

void io_poll() {
  //Копируем Coil[1] в Discrete[0]
  //Сохраняем состояние аналоговых входов
  au16data[1,1] = analogRead( A0 );
  au16data[2,2] = analogRead( A1 );
  au16data[3,3] = analogRead( A2 );
  au16data[4,4] = analogRead( A3 );
  
  //Сохраняем в регистры отладочную информацию
  au16data[8] = slave.getInCnt();
  au16data[9] = slave.getOutCnt();
  au16data[10] = slave.getErrCnt();
}

если нужна библиотека, могу скинуть

harbor
Offline
Зарегистрирован: 31.05.2016

мне нужен мастер, но я уже смотрю тему с SimpleModbusMaster тут http://arduino.ru/forum/proekty/modbusrtu-modbustcp-arduino-i-owen-plc#comment-210568

правда там непонято с обработкой ошибок..

Kvantos
Offline
Зарегистрирован: 29.08.2016

2James

Добрый день, как раз интересуюсь слейвом на ардуине. Скиньте плс. библиотеку.

James
Offline
Зарегистрирован: 26.02.2016
/**
 * @file 		ModbusRtu.h
 * @version     1.20
 * @date        2014.09.09
 * @author 		Samuel Marco i Armengol
 * @contact     sammarcoarmengol@gmail.com
 * @contribution 
 *
 * @description
 *  Arduino library for communicating with Modbus devices 
 *  over RS232/USB/485 via RTU protocol.
 *
 *  Further information: 
 *  http://modbus.org/
 *  http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
 *
 * @license
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version
 *  2.1 of the License.
 * 
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * @defgroup setup Modbus Object Instantiation/Initialization
 * @defgroup loop Modbus Object Management
 * @defgroup buffer Modbus Buffer Management
 * @defgroup discrete Modbus Function Codes for Discrete Coils/Inputs
 * @defgroup register Modbus Function Codes for Holding/Input Registers
 *
 */

#include <inttypes.h>
#include "Arduino.h"	
#include "Print.h"

/**
 * @struct modbus_t 
 * @brief 
 * Master query structure:
 * This includes all the necessary fields to make the Master generate a Modbus query.
 * A Master may keep several of these structures and send them cyclically or
 * use them according to program needs.
 */
typedef struct {
  uint8_t u8id;          /*!< Slave address between 1 and 247. 0 means broadcast */
  uint8_t u8fct;         /*!< Function code: 1, 2, 3, 4, 5, 6, 15 or 16 */
  uint16_t u16RegAdd;    /*!< Address of the first register to access at slave/s */
  uint16_t u16CoilsNo;   /*!< Number of coils or registers to access */
  uint16_t *au16reg;     /*!< Pointer to memory image in master */
} 
modbus_t;

enum { 
  RESPONSE_SIZE = 6, 
  EXCEPTION_SIZE = 3, 
  CHECKSUM_SIZE = 2
};

/**
 * @enum MESSAGE
 * @brief
 * Indexes to telegram frame positions
 */
enum MESSAGE {
  ID                             = 0, //!< ID field
  FUNC, //!< Function code position
  ADD_HI, //!< Address high byte
  ADD_LO, //!< Address low byte
  NB_HI, //!< Number of coils or registers high byte
  NB_LO, //!< Number of coils or registers low byte
  BYTE_CNT  //!< byte counter
};

/**
 * @enum MB_FC
 * @brief
 * Modbus function codes summary. 
 * These are the implement function codes either for Master or for Slave.
 *
 * @see also fctsupported
 * @see also modbus_t
 */
enum MB_FC {
  MB_FC_NONE                     = 0,   /*!< null operator */
  MB_FC_READ_COILS               = 1,	/*!< FCT=1 -> read coils or digital outputs */
  MB_FC_READ_DISCRETE_INPUT      = 2,	/*!< FCT=2 -> read digital inputs */
  MB_FC_READ_REGISTERS           = 3,	/*!< FCT=3 -> read registers or analog outputs */
  MB_FC_READ_INPUT_REGISTER      = 4,	/*!< FCT=4 -> read analog inputs */
  MB_FC_WRITE_COIL               = 5,	/*!< FCT=5 -> write single coil or output */
  MB_FC_WRITE_REGISTER           = 6,	/*!< FCT=6 -> write single register */
  MB_FC_WRITE_MULTIPLE_COILS     = 15,	/*!< FCT=15 -> write multiple coils or outputs */
  MB_FC_WRITE_MULTIPLE_REGISTERS = 16	/*!< FCT=16 -> write multiple registers */
};

enum COM_STATES {
  COM_IDLE                     = 0,
  COM_WAITING                  = 1

};

enum ERR_LIST {
  ERR_NOT_MASTER                = -1,
  ERR_POLLING                   = -2,
  ERR_BUFF_OVERFLOW             = -3,
  ERR_BAD_CRC                   = -4,
  ERR_EXCEPTION                 = -5
};

enum { 
  NO_REPLY = 255, 		
  EXC_FUNC_CODE = 1,
  EXC_ADDR_RANGE = 2, 		
  EXC_REGS_QUANT = 3,  
  EXC_EXECUTE = 4 
};

const unsigned char fctsupported[] = { 
  MB_FC_READ_COILS,
  MB_FC_READ_DISCRETE_INPUT,
  MB_FC_READ_REGISTERS, 
  MB_FC_READ_INPUT_REGISTER,
  MB_FC_WRITE_COIL,
  MB_FC_WRITE_REGISTER, 
  MB_FC_WRITE_MULTIPLE_COILS,
  MB_FC_WRITE_MULTIPLE_REGISTERS
};

#define T35  5
#define  MAX_BUFFER  64	//!< maximum size for the communication buffer in bytes

/**
 * @class Modbus 
 * @brief
 * Arduino class library for communicating with Modbus devices over
 * USB/RS232/485 (via RTU protocol).
 */
class Modbus {
private:
  HardwareSerial *port; //!< Pointer to Serial class object
  uint8_t u8id; //!< 0=master, 1..247=slave number
  uint8_t u8serno; //!< serial port: 0-Serial, 1..3-Serial1..Serial3
  uint8_t u8txenpin; //!< flow control pin: 0=USB or RS-232 mode, >0=RS-485 mode
  uint8_t u8state;
  uint8_t u8lastError;
  uint8_t au8Buffer[MAX_BUFFER];
  uint8_t u8BufferSize;
  uint8_t u8lastRec;
  uint16_t *au16regs;
  uint16_t u16InCnt, u16OutCnt, u16errCnt;
  uint16_t u16timeOut;
  uint32_t u32time, u32timeOut;
  uint8_t u8regsize;

  void init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
  void sendTxBuffer(); 
  int8_t getRxBuffer(); 
  uint16_t calcCRC(uint8_t u8length);
  uint8_t validateAnswer();
  uint8_t validateRequest(); 
  void get_FC1(); 
  void get_FC3(); 
  int8_t process_FC1( uint16_t *regs, uint8_t u8size ); 
  int8_t process_FC3( uint16_t *regs, uint8_t u8size ); 
  int8_t process_FC5( uint16_t *regs, uint8_t u8size ); 
  int8_t process_FC6( uint16_t *regs, uint8_t u8size ); 
  int8_t process_FC15( uint16_t *regs, uint8_t u8size ); 
  int8_t process_FC16( uint16_t *regs, uint8_t u8size ); 
  void buildException( uint8_t u8exception ); // build exception message

public:
  Modbus(); 
  Modbus(uint8_t u8id, uint8_t u8serno); 
  Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin);
  void begin(long u32speed);
  void begin();
  void setTimeOut( uint16_t u16timeout); //!<write communication watch-dog timer
  uint16_t getTimeOut(); //!<get communication watch-dog timer value
  boolean getTimeOutState(); //!<get communication watch-dog timer state
  int8_t query( modbus_t telegram ); //!<only for master
  int8_t poll(); //!<cyclic poll for master
  int8_t poll( uint16_t *regs, uint8_t u8size ); //!<cyclic poll for slave
  uint16_t getInCnt(); //!<number of incoming messages
  uint16_t getOutCnt(); //!<number of outcoming messages
  uint16_t getErrCnt(); //!<error counter
  uint8_t getID(); //!<get slave ID between 1 and 247
  uint8_t getState();
  uint8_t getLastError(); //!<get last error message
  void setID( uint8_t u8id ); //!<write new ID for the slave
  void end(); //!<finish any communication and release serial communication port
};

/* _____PUBLIC FUNCTIONS_____________________________________________________ */

/**
 * @brief
 * Default Constructor for Master through Serial
 * 
 * @ingroup setup
 */
Modbus::Modbus() {
  init(0, 0, 0);
}

/**
 * @brief
 * Full constructor for a Master/Slave through USB/RS232C
 * 
 * @param u8id   node address 0=master, 1..247=slave
 * @param u8serno  serial port used 0..3
 * @ingroup setup
 * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno)
 * @overload Modbus::Modbus()
 */
Modbus::Modbus(uint8_t u8id, uint8_t u8serno) {
  init(u8id, u8serno, 0);
}

/**
 * @brief
 * Full constructor for a Master/Slave through USB/RS232C/RS485
 * It needs a pin for flow control only for RS485 mode
 * 
 * @param u8id   node address 0=master, 1..247=slave
 * @param u8serno  serial port used 0..3
 * @param u8txenpin pin for txen RS-485 (=0 means USB/RS232C mode)
 * @ingroup setup
 * @overload Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin)
 * @overload Modbus::Modbus()
 */
Modbus::Modbus(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) {
  init(u8id, u8serno, u8txenpin);
}

/**
 * @brief
 * Initialize class object.
 * 
 * Sets up the serial port using specified baud rate.
 * Call once class has been instantiated, typically within setup().
 * 
 * @see http://arduino.cc/en/Serial/Begin#.Uy4CJ6aKlHY
 * @param speed   baud rate, in standard increments (300..115200)
 * @param config  data frame settings (data length, parity and stop bits)
 * @ingroup setup
 */
void Modbus::begin(long u32speed) {

  switch( u8serno ) {
#if defined(UBRR1H)
  case 1:
    port = &Serial1;
    break;
#endif

#if defined(UBRR2H)
  case 2:
    port = &Serial2;
    break;
#endif

#if defined(UBRR3H)
  case 3:
    port = &Serial3;
    break;
#endif
  case 0:
  default:
    port = &Serial;
    break;
  }

  // port->begin(u32speed, u8config);
  port->begin(u32speed);
  if (u8txenpin > 1) { // pin 0 & pin 1 are reserved for RX/TX
    // return RS485 transceiver to transmit mode
    pinMode(u8txenpin, OUTPUT);
    digitalWrite(u8txenpin, LOW);
  }

  port->flush();
  u8lastRec = u8BufferSize = 0;
  u16InCnt = u16OutCnt = u16errCnt = 0;
}

/**
 * @brief
 * Initialize default class object.
 * 
 * Sets up the serial port using 19200 baud.
 * Call once class has been instantiated, typically within setup().
 * 
 * @overload Modbus::begin(uint16_t u16BaudRate)
 * @ingroup setup
 */
void Modbus::begin() {
  begin(19200);
}

/**
 * @brief
 * Method to write a new slave ID address
 *
 * @param 	u8id	new slave address between 1 and 247
 * @ingroup setup
 */
void Modbus::setID( uint8_t u8id) {
  if (( u8id != 0) && (u8id <= 247)) {
    this->u8id = u8id;
  }
}

/**
 * @brief
 * Method to read current slave ID address
 *
 * @return u8id	current slave address between 1 and 247
 * @ingroup setup
 */
uint8_t Modbus::getID() {
  return this->u8id;
}

/**
 * @brief
 * Initialize time-out parameter
 * 
 * Call once class has been instantiated, typically within setup().
 * The time-out timer is reset each time that there is a successful communication
 * between Master and Slave. It works for both.
 * 
 * @param time-out value (ms)
 * @ingroup setup
 */
void Modbus::setTimeOut( uint16_t u16timeOut) {
  this->u16timeOut = u16timeOut;
}

/**
 * @brief
 * Return communication Watchdog state.
 * It could be usefull to reset outputs if the watchdog is fired.
 *
 * @return TRUE if millis() > u32timeOut
 * @ingroup loop
 */
boolean Modbus::getTimeOutState() {
  return (millis() > u32timeOut);
}

/**
 * @brief
 * Get input messages counter value
 * This can be useful to diagnose communication
 * 
 * @return input messages counter
 * @ingroup buffer
 */
uint16_t Modbus::getInCnt() { 
  return u16InCnt; 
}

/**
 * @brief
 * Get transmitted messages counter value
 * This can be useful to diagnose communication
 * 
 * @return transmitted messages counter
 * @ingroup buffer
 */
uint16_t Modbus::getOutCnt() { 
  return u16OutCnt; 
}

/**
 * @brief
 * Get errors counter value
 * This can be useful to diagnose communication
 * 
 * @return errors counter
 * @ingroup buffer
 */
uint16_t Modbus::getErrCnt() { 
  return u16errCnt; 
}

/**
 * Get modbus master state
 * 
 * @return = 0 IDLE, = 1 WAITING FOR ANSWER
 * @ingroup buffer
 */
uint8_t Modbus::getState() {
  return u8state;
}

/**
 * Get the last error in the protocol processor
 * 
 * @returnreturn   NO_REPLY = 255      Time-out
 * @return   EXC_FUNC_CODE = 1   Function code not available
 * @return   EXC_ADDR_RANGE = 2  Address beyond available space for Modbus registers 
 * @return   EXC_REGS_QUANT = 3  Coils or registers number beyond the available space
 * @ingroup buffer
 */
uint8_t Modbus::getLastError() {
  return u8lastError;
}

/**
 * @brief
 * *** Only Modbus Master ***
 * Generate a query to an slave with a modbus_t telegram structure
 * The Master must be in COM_IDLE mode. After it, its state would be COM_WAITING.
 * This method has to be called only in loop() section.
 * 
 * @see modbus_t 
 * @param modbus_t  modbus telegram structure (id, fct, ...)
 * @ingroup loop
 * @todo finish function 15
 */
int8_t Modbus::query( modbus_t telegram ) {
  uint8_t u8regsno, u8bytesno;
  if (u8id!=0) return -2;
  if (u8state != COM_IDLE) return -1;

  if ((telegram.u8id==0) || (telegram.u8id>247)) return -3;

  au16regs = telegram.au16reg;

  // telegram header
  au8Buffer[ ID ]         = telegram.u8id;
  au8Buffer[ FUNC ]       = telegram.u8fct;
  au8Buffer[ ADD_HI ]     = highByte(telegram.u16RegAdd );
  au8Buffer[ ADD_LO ]     = lowByte( telegram.u16RegAdd );

  switch( telegram.u8fct ) {
  case MB_FC_READ_COILS:
  case MB_FC_READ_DISCRETE_INPUT:
  case MB_FC_READ_REGISTERS:
  case MB_FC_READ_INPUT_REGISTER:
    au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
    au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
    u8BufferSize = 6;
    break;
  case MB_FC_WRITE_COIL:
    au8Buffer[ NB_HI ]      = ((au16regs[0] > 0) ? 0xff : 0);
    au8Buffer[ NB_LO ]      = 0;
    u8BufferSize = 6;    
    break;
  case MB_FC_WRITE_REGISTER:
    au8Buffer[ NB_HI ]      = highByte(au16regs[0]);
    au8Buffer[ NB_LO ]      = lowByte(au16regs[0]);
    u8BufferSize = 6;    
    break;
  case MB_FC_WRITE_MULTIPLE_COILS: // TODO: implement "sending coils"
    u8regsno = telegram.u16CoilsNo / 16;
    u8bytesno = u8regsno * 2;
    if ((telegram.u16CoilsNo % 16) != 0) {
      u8bytesno++;
      u8regsno++;
    }

    au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
    au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
    au8Buffer[ NB_LO+1 ]    = u8bytesno;
    u8BufferSize = 7;

    u8regsno = u8bytesno = 0; // now auxiliary registers
    for (uint16_t i = 0; i < telegram.u16CoilsNo; i++) {


    }
    break;

  case MB_FC_WRITE_MULTIPLE_REGISTERS:
    au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
    au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
    au8Buffer[ NB_LO+1 ]    = (uint8_t) ( telegram.u16CoilsNo * 2 );
    u8BufferSize = 7;    

    for (uint16_t i=0; i< telegram.u16CoilsNo; i++) {
      au8Buffer[ u8BufferSize ] = highByte( au16regs[ i ] );
      u8BufferSize++;
      au8Buffer[ u8BufferSize ] = lowByte( au16regs[ i ] );
      u8BufferSize++;
    }
    break;
  }

  sendTxBuffer();
  u8state = COM_WAITING;
  return 0;
}

/**
 * @brief *** Only for Modbus Master ***
 * This method checks if there is any incoming answer if pending.
 * If there is no answer, it would change Master state to COM_IDLE.
 * This method must be called only at loop section.
 * Avoid any delay() function.
 *
 * Any incoming data would be redirected to au16regs pointer,
 * as defined in its modbus_t query telegram.
 * 
 * @params	nothing
 * @return errors counter
 * @ingroup loop
 */
int8_t Modbus::poll() {
  // check if there is any incoming frame
  uint8_t u8current = port->available();  

  if (millis() > u32timeOut) {
    u8state = COM_IDLE;
    u8lastError = NO_REPLY;
    u16errCnt++;
    return 0;
  }

  if (u8current == 0) return 0;

  // check T35 after frame end or still no frame end
  if (u8current != u8lastRec) {
    u8lastRec = u8current;
    u32time = millis() + T35;
    return 0;
  }
  if (millis() < u32time) return 0;

  // transfer Serial buffer frame to auBuffer
  u8lastRec = 0;
  int8_t i8state = getRxBuffer();
  if (i8state < 7) {
    u8state = COM_IDLE;
    u16errCnt++;
    return i8state;
  }

  // validate message: id, CRC, FCT, exception
  uint8_t u8exception = validateAnswer(); 
  if (u8exception != 0) {
    u8state = COM_IDLE;
    return u8exception;
  }

  // process answer
  switch( au8Buffer[ FUNC ] ) {
  case MB_FC_READ_COILS:
  case MB_FC_READ_DISCRETE_INPUT:
    // call get_FC1 to transfer the incoming message to au16regs buffer
    get_FC1( );
    break;
  case MB_FC_READ_INPUT_REGISTER:
  case MB_FC_READ_REGISTERS :
    // call get_FC3 to transfer the incoming message to au16regs buffer
    get_FC3( );
    break;
  case MB_FC_WRITE_COIL:
  case MB_FC_WRITE_REGISTER :
  case MB_FC_WRITE_MULTIPLE_COILS:
  case MB_FC_WRITE_MULTIPLE_REGISTERS :
    // nothing to do
    break;
  default:
    break;
  }  
  u8state = COM_IDLE;
  return u8BufferSize;
}

/**
 * @brief
 * *** Only for Modbus Slave ***
 * This method checks if there is any incoming query
 * Afterwards, it would shoot a validation routine plus a register query
 * Avoid any delay() function !!!!
 * After a successful frame between the Master and the Slave, the time-out timer is reset.
 * 
 * @param *regs  register table for communication exchange
 * @param u8size  size of the register table
 * @return 0 if no query, 1..4 if communication error, >4 if correct query processed
 * @ingroup loop
 */
int8_t Modbus::poll( uint16_t *regs, uint8_t u8size ) {

  au16regs = regs;
  u8regsize = u8size;

  // check if there is any incoming frame
  uint8_t u8current = port->available();  
  if (u8current == 0) return 0;

  // check T35 after frame end or still no frame end
  if (u8current != u8lastRec) {
    u8lastRec = u8current;
    u32time = millis() + T35;
    return 0;
  }
  if (millis() < u32time) return 0;

  u8lastRec = 0;
  int8_t i8state = getRxBuffer();
  u8lastError = i8state;
  if (i8state < 7) return i8state;  

  // check slave id
  if (au8Buffer[ ID ] != u8id) return 0;

  // validate message: CRC, FCT, address and size
  uint8_t u8exception = validateRequest();
  if (u8exception > 0) {
    if (u8exception != NO_REPLY) {
      buildException( u8exception );
      sendTxBuffer(); 
    }
    u8lastError = u8exception;
    return u8exception;
  }

  u32timeOut = millis() + long(u16timeOut);
  u8lastError = 0;
  
  // process message
  switch( au8Buffer[ FUNC ] ) {
  case MB_FC_READ_COILS:
  case MB_FC_READ_DISCRETE_INPUT:
    return process_FC1( regs, u8size );
    break;
  case MB_FC_READ_INPUT_REGISTER:
  case MB_FC_READ_REGISTERS :
    return process_FC3( regs, u8size );
    break;
  case MB_FC_WRITE_COIL:
    return process_FC5( regs, u8size );
    break;
  case MB_FC_WRITE_REGISTER :
    return process_FC6( regs, u8size );
    break;
  case MB_FC_WRITE_MULTIPLE_COILS:
    return process_FC15( regs, u8size );
    break;
  case MB_FC_WRITE_MULTIPLE_REGISTERS :
    return process_FC16( regs, u8size );
    break;
  default:
    break;
  }
  return i8state;
}

/* _____PRIVATE FUNCTIONS_____________________________________________________ */

void Modbus::init(uint8_t u8id, uint8_t u8serno, uint8_t u8txenpin) {
  this->u8id = u8id;
  this->u8serno = (u8serno > 3) ? 0 : u8serno;
  this->u8txenpin = u8txenpin;
  this->u16timeOut = 1000;
}

/**
 * @brief
 * This method moves Serial buffer data to the Modbus au8Buffer.
 *
 * @return buffer size if OK, ERR_BUFF_OVERFLOW if u8BufferSize >= MAX_BUFFER
 * @ingroup buffer
 */
int8_t Modbus::getRxBuffer() {
  boolean bBuffOverflow = false;

  if (u8txenpin > 1) digitalWrite( u8txenpin, LOW );

  u8BufferSize = 0;
  while ( port->available() ) {
    au8Buffer[ u8BufferSize ] = port->read();
    u8BufferSize ++;

    if (u8BufferSize >= MAX_BUFFER) bBuffOverflow = true;
  }
  u16InCnt++;

  if (bBuffOverflow) {
    u16errCnt++;
    return ERR_BUFF_OVERFLOW;
  }
  return u8BufferSize;
}

/**
 * @brief
 * This method transmits au8Buffer to Serial line.
 * Only if u8txenpin != 0, there is a flow handling in order to keep
 * the RS485 transceiver in output state as long as the message is being sent.
 * This is done with UCSRxA register.
 * The CRC is appended to the buffer before starting to send it.
 *
 * @param nothing
 * @return nothing
 * @ingroup buffer
 */
void Modbus::sendTxBuffer() {
  uint8_t i = 0;

  // append CRC to message
  uint16_t u16crc = calcCRC( u8BufferSize );
  au8Buffer[ u8BufferSize ] = u16crc >> 8;
  u8BufferSize++;
  au8Buffer[ u8BufferSize ] = u16crc & 0x00ff;
  u8BufferSize++;

  // set RS485 transceiver to transmit mode
  if (u8txenpin > 1) {
    switch( u8serno ) {
#if defined(UBRR1H)
    case 1:
      UCSR1A=UCSR1A |(1 << TXC1);
      break;
#endif

#if defined(UBRR2H)
    case 2:
      UCSR2A=UCSR2A |(1 << TXC2);
      break;
#endif

#if defined(UBRR3H)
    case 3:
      UCSR3A=UCSR3A |(1 << TXC3);
      break;
#endif
    case 0:
    default:
      UCSR0A=UCSR0A |(1 << TXC0);
      break;
    }
    digitalWrite( u8txenpin, HIGH );
  }

  // transfer buffer to serial line
  port->write( au8Buffer, u8BufferSize );

  // keep RS485 transceiver in transmit mode as long as sending
  if (u8txenpin > 1) {
    switch( u8serno ) {
#if defined(UBRR1H)
    case 1:
      while (!(UCSR1A & (1 << TXC1)));
      break;
#endif

#if defined(UBRR2H)
    case 2:
      while (!(UCSR2A & (1 << TXC2)));
      break;
#endif

#if defined(UBRR3H)
    case 3:
      while (!(UCSR3A & (1 << TXC3)));
      break;
#endif
    case 0:
    default:
      while (!(UCSR0A & (1 << TXC0)));
      break;
    }

    // return RS485 transceiver to receive mode
    digitalWrite( u8txenpin, LOW );
  }
  port->flush();
  u8BufferSize = 0;

  // set time-out for master
  u32timeOut = millis() + (unsigned long) u16timeOut;

  // increase message counter
  u16OutCnt++;
}

/**
 * @brief
 * This method calculates CRC
 *
 * @return uint16_t calculated CRC value for the message
 * @ingroup buffer
 */
uint16_t Modbus::calcCRC(uint8_t u8length) {
  unsigned int temp, temp2, flag;
  temp = 0xFFFF;
  for (unsigned char i = 0; i < u8length; i++) {
    temp = temp ^ au8Buffer[i];
    for (unsigned char j = 1; j <= 8; j++) {
      flag = temp & 0x0001;
      temp >>=1;
      if (flag)
        temp ^= 0xA001;
    }
  }
  // Reverse byte order. 
  temp2 = temp >> 8;
  temp = (temp << 8) | temp2;
  temp &= 0xFFFF; 
  // the returned value is already swapped
  // crcLo byte is first & crcHi byte is last
  return temp; 
}

/**
 * @brief
 * This method validates slave incoming messages
 *
 * @return 0 if OK, EXCEPTION if anything fails
 * @ingroup buffer
 */
uint8_t Modbus::validateRequest() {
  // check message crc vs calculated crc
  uint16_t u16MsgCRC = 
    ((au8Buffer[u8BufferSize - 2] << 8) 
    | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
  if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC ) {
    u16errCnt ++;
    return NO_REPLY;
  }

  // check fct code
  boolean isSupported = false;
  for (uint8_t i = 0; i< sizeof( fctsupported ); i++) {
    if (fctsupported[i] == au8Buffer[FUNC]) {
      isSupported = 1;
      break;
    }
  }
  if (!isSupported) {
    u16errCnt ++;
    return EXC_FUNC_CODE;
  }

  // check start address & nb range
  uint16_t u16regs = 0;
  uint8_t u8regs;
  switch ( au8Buffer[ FUNC ] ) {
  case MB_FC_READ_COILS:
  case MB_FC_READ_DISCRETE_INPUT:
  case MB_FC_WRITE_MULTIPLE_COILS:
    u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
    u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]) /16;
    u8regs = (uint8_t) u16regs;
    if (u8regs > u8regsize) return EXC_ADDR_RANGE;
    break;
  case MB_FC_WRITE_COIL:
    u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]) / 16;
    u8regs = (uint8_t) u16regs;
    if (u8regs > u8regsize) return EXC_ADDR_RANGE;
    break;  
  case MB_FC_WRITE_REGISTER :
    u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
    u8regs = (uint8_t) u16regs;
    if (u8regs > u8regsize) return EXC_ADDR_RANGE;
    break;
  case MB_FC_READ_REGISTERS :
  case MB_FC_READ_INPUT_REGISTER :
  case MB_FC_WRITE_MULTIPLE_REGISTERS :
    u16regs = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ]);
    u16regs += word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ]);
    u8regs = (uint8_t) u16regs;
    if (u8regs > u8regsize) return EXC_ADDR_RANGE;    
    break;
  }
  return 0; // OK, no exception code thrown
}

/**
 * @brief
 * This method validates master incoming messages
 *
 * @return 0 if OK, EXCEPTION if anything fails
 * @ingroup buffer
 */
uint8_t Modbus::validateAnswer() {
  // check message crc vs calculated crc
  uint16_t u16MsgCRC = 
    ((au8Buffer[u8BufferSize - 2] << 8) 
    | au8Buffer[u8BufferSize - 1]); // combine the crc Low & High bytes
  if ( calcCRC( u8BufferSize-2 ) != u16MsgCRC ) {
    u16errCnt ++;
    return NO_REPLY;
  }

  // check exception
  if ((au8Buffer[ FUNC ] & 0x80) != 0) {
    u16errCnt ++;
    return ERR_EXCEPTION;
  }

  // check fct code
  boolean isSupported = false;
  for (uint8_t i = 0; i< sizeof( fctsupported ); i++) {
    if (fctsupported[i] == au8Buffer[FUNC]) {
      isSupported = 1;
      break;
    }
  }
  if (!isSupported) {
    u16errCnt ++;
    return EXC_FUNC_CODE;
  }

  return 0; // OK, no exception code thrown
}

/**
 * @brief
 * This method builds an exception message
 *
 * @ingroup buffer
 */
void Modbus::buildException( uint8_t u8exception ) {
  uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code

  au8Buffer[ ID ]      = u8id;
  au8Buffer[ FUNC ]    = u8func + 0x80;
  au8Buffer[ 2 ]       = u8exception;
  u8BufferSize         = EXCEPTION_SIZE;
}

/**
 * This method processes functions 1 & 2 (for master)
 * This method puts the slave answer into master data buffer 
 *
 * @ingroup register
 * TODO: finish its implementation
 */
void Modbus::get_FC1() {
  uint8_t u8byte, i;
  u8byte = 0;

  //  for (i=0; i< au8Buffer[ 2 ] /2; i++) {
  //    au16regs[ i ] = word( 
  //    au8Buffer[ u8byte ],
  //    au8Buffer[ u8byte +1 ]);
  //    u8byte += 2;
  //  }
}

/**
 * This method processes functions 3 & 4 (for master)
 * This method puts the slave answer into master data buffer 
 *
 * @ingroup register
 */
void Modbus::get_FC3() {
  uint8_t u8byte, i;
  u8byte = 3;

  for (i=0; i< au8Buffer[ 2 ] /2; i++) {
    au16regs[ i ] = word( 
    au8Buffer[ u8byte ],
    au8Buffer[ u8byte +1 ]);
    u8byte += 2;
  }
}

/**
 * @brief
 * This method processes functions 1 & 2
 * This method reads a bit array and transfers it to the master
 *
 * @return u8BufferSize Response to master length
 * @ingroup discrete
 */
int8_t Modbus::process_FC1( uint16_t *regs, uint8_t u8size ) {
  uint8_t u8currentRegister, u8currentBit, u8bytesno, u8bitsno;
  uint8_t u8CopyBufferSize;
  uint16_t u16currentCoil, u16coil;

  // get the first and last coil from the message
  uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
  uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );

  // put the number of bytes in the outcoming message
  u8bytesno = (uint8_t) (u16Coilno / 8);
  if (u16Coilno % 8 != 0) u8bytesno ++;
  au8Buffer[ ADD_HI ]  = u8bytesno;
  u8BufferSize         = ADD_LO;

  // read each coil from the register map and put its value inside the outcoming message
  u8bitsno = 0;

  for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++) {
    u16coil = u16StartCoil + u16currentCoil;
    u8currentRegister = (uint8_t) (u16coil / 16);
    u8currentBit = (uint8_t) (u16coil % 16);

    bitWrite(
    au8Buffer[ u8BufferSize ],
    u8bitsno,
    bitRead( regs[ u8currentRegister ], u8currentBit ) );
    u8bitsno ++;

    if (u8bitsno > 7) {
      u8bitsno = 0;
      u8BufferSize++;
    }
  }

  // send outcoming message
  if (u16Coilno % 8 != 0) u8BufferSize ++;
  u8CopyBufferSize = u8BufferSize +2;
  sendTxBuffer();
  return u8CopyBufferSize;
}

/**
 * @brief
 * This method processes functions 3 & 4
 * This method reads a word array and transfers it to the master
 *
 * @return u8BufferSize Response to master length
 * @ingroup register
 */
int8_t Modbus::process_FC3( uint16_t *regs, uint8_t u8size ) {

  uint8_t u8StartAdd = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
  uint8_t u8regsno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );
  uint8_t u8CopyBufferSize;
  uint8_t i;

  au8Buffer[ 2 ]       = u8regsno * 2;
  u8BufferSize         = 3;

  for (i = u8StartAdd; i < u8StartAdd + u8regsno; i++) {
    au8Buffer[ u8BufferSize ] = highByte(regs[i]);
    u8BufferSize++;
    au8Buffer[ u8BufferSize ] = lowByte(regs[i]);
    u8BufferSize++;
  }
  u8CopyBufferSize = u8BufferSize +2;
  sendTxBuffer();

  return u8CopyBufferSize;
}

/**
 * @brief
 * This method processes function 5
 * This method writes a value assigned by the master to a single bit
 *
 * @return u8BufferSize Response to master length
 * @ingroup discrete
 */
int8_t Modbus::process_FC5( uint16_t *regs, uint8_t u8size ) {
  uint8_t u8currentRegister, u8currentBit;
  uint8_t u8CopyBufferSize;
  uint16_t u16coil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );

  // point to the register and its bit
  u8currentRegister = (uint8_t) (u16coil / 16);
  u8currentBit = (uint8_t) (u16coil % 16);

  // write to coil
  bitWrite(
  regs[ u8currentRegister ],
  u8currentBit,
  au8Buffer[ NB_HI ] == 0xff );


  // send answer to master
  u8BufferSize = 6;
  u8CopyBufferSize = u8BufferSize +2;
  sendTxBuffer();

  return u8CopyBufferSize;
}

/**
 * @brief
 * This method processes function 6
 * This method writes a value assigned by the master to a single word
 *
 * @return u8BufferSize Response to master length
 * @ingroup register
 */
int8_t Modbus::process_FC6( uint16_t *regs, uint8_t u8size ) {

  uint8_t u8add = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
  uint8_t u8CopyBufferSize;
  uint16_t u16val = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );

  regs[ u8add ] = u16val;

  // keep the same header
  u8BufferSize         = RESPONSE_SIZE;

  u8CopyBufferSize = u8BufferSize +2;
  sendTxBuffer();

  return u8CopyBufferSize;
}

/**
 * @brief
 * This method processes function 15
 * This method writes a bit array assigned by the master
 *
 * @return u8BufferSize Response to master length
 * @ingroup discrete
 */
int8_t Modbus::process_FC15( uint16_t *regs, uint8_t u8size ) {
  uint8_t u8currentRegister, u8currentBit, u8frameByte, u8bitsno;
  uint8_t u8CopyBufferSize;
  uint16_t u16currentCoil, u16coil;
  boolean bTemp;

  // get the first and last coil from the message
  uint16_t u16StartCoil = word( au8Buffer[ ADD_HI ], au8Buffer[ ADD_LO ] );
  uint16_t u16Coilno = word( au8Buffer[ NB_HI ], au8Buffer[ NB_LO ] );


  // read each coil from the register map and put its value inside the outcoming message
  u8bitsno = 0;
  u8frameByte = 7;
  for (u16currentCoil = 0; u16currentCoil < u16Coilno; u16currentCoil++) {

    u16coil = u16StartCoil + u16currentCoil;
    u8currentRegister = (uint8_t) (u16coil / 16);
    u8currentBit = (uint8_t) (u16coil % 16);

    bTemp = bitRead(
    au8Buffer[ u8frameByte ],
    u8bitsno );

    bitWrite(
    regs[ u8currentRegister ],
    u8currentBit,
    bTemp );

    u8bitsno ++;

    if (u8bitsno > 7) {
      u8bitsno = 0;
      u8frameByte++;
    }
  }

  // send outcoming message
  // it's just a copy of the incomping frame until 6th byte
  u8BufferSize         = 6;
  u8CopyBufferSize = u8BufferSize +2;
  sendTxBuffer();
  return u8CopyBufferSize;
}

/**
 * @brief
 * This method processes function 16
 * This method writes a word array assigned by the master
 *
 * @return u8BufferSize Response to master length
 * @ingroup register
 */
int8_t Modbus::process_FC16( uint16_t *regs, uint8_t u8size ) {
  uint8_t u8func = au8Buffer[ FUNC ];  // get the original FUNC code
  uint8_t u8StartAdd = au8Buffer[ ADD_HI ] << 8 | au8Buffer[ ADD_LO ];
  uint8_t u8regsno = au8Buffer[ NB_HI ] << 8 | au8Buffer[ NB_LO ];
  uint8_t u8CopyBufferSize;
  uint8_t i;
  uint16_t temp;

  // build header
  au8Buffer[ NB_HI ]   = 0;
  au8Buffer[ NB_LO ]   = u8regsno;
  u8BufferSize         = RESPONSE_SIZE;

  // write registers
  for (i = 0; i < u8regsno; i++) {
    temp = word(
    au8Buffer[ (BYTE_CNT + 1) + i * 2 ],
    au8Buffer[ (BYTE_CNT + 2) + i * 2 ]);

    regs[ u8StartAdd + i ] = temp;
  }
  u8CopyBufferSize = u8BufferSize +2;
  sendTxBuffer();

  return u8CopyBufferSize;
}

 

Kvantos
Offline
Зарегистрирован: 29.08.2016

Большое спасибо 

Veoramid
Offline
Зарегистрирован: 20.07.2016

+1 больше спасибо! :)

James
Offline
Зарегистрирован: 26.02.2016

Не было ни у кого зависаний дуинки с модулем? не могу понять, в чем косяк. постоянно горит Rx, если отключить модуль, то мигает в ожидании, причем, без модуля, через уарт, та же история. 
п.с. было найдено, что при отключении 10 пина, дуинка начинает общаться через уарт нормально. 

Veoramid
Offline
Зарегистрирован: 20.07.2016

James пишет:

Не было ни у кого зависаний дуинки с модулем?

Я собрал проект в котором многое чего есть. И все в сумме начало зависать. За сутки раз 20-30. В чем дело - я не смог определить. Показалось, что дело в индикаторе TM1637 - он чаще всего "тух". Нашел статью про WDT - включил. Повезло - моя UNO поняла WDT без танцев с бубном. Однако частота зависаний однозначно зависит от работы modbus, а это в принципе не есть хорошо.  :(

Пока так оставил.

James
Offline
Зарегистрирован: 26.02.2016

заказал еще один модуль, через пару дней попробую. такое ощущение, что что-то держит линию и не дает переключится обратно. Сейчас попробую танцы с другим компом. другой контроллер пробовал, проблема остается. Пайка нормальная, проверил на макетке, ошибка повторяется. Не мог ли я случайно сжеч переходник uart-rs485? потому, что бага начала появляться после подключения к блоку питания

Veoramid
Offline
Зарегистрирован: 20.07.2016

James пишет:

бага начала появляться после подключения к блоку питания

Есть такая тема, как защитная подтяжка.

Описано что-то на английском тут: http://0xee.net/2014/07/02/pic-talks-uart-on-rs-485/

Картинка:

Резисторы R1 и R2.

Эта подтяжка предназначена рержать логический "1" на линии , когда все передатчики отключены. Если ни в одном из устройств такой подтяжки нет - возможны помехи приема, когда никто не передает. Но если такие подтяжки есть на всех устройствах, то может не хватить мощности выходного каскада микросхемы RS485 для того, что бы передавать сигнал. В одном из случаев пршлось в готовом устройстве эти резисторы откусить, т.к. когда их поставили на линию 4 шт в параллель - связь пропала.

Я же припоминаю, что у меня на столе возникли проблемы. Я смотрел что у меня происходит в линии терминалом... обнаружил, что-то на линии, когда никто не передает и "вылечил" нагрузочным резистором + резисторы подтяжки. Хотя в "заводских" решениях ни разу с такой проблемой не столкнулся.

В случае с представленной схемой как раз есть джампера что бы подтяжку и нагрузочные сопротивления включить на крайних устройствах и отключить на всех промежуточных.

James
Offline
Зарегистрирован: 26.02.2016

у меня вот такой

на вашей схеме заметил R4, нужно будет попробовать подтянуть на землю.
В итоге - не помогло, зависание полное

 

Veoramid
Offline
Зарегистрирован: 20.07.2016

James пишет:

у меня вот такой

на вашей схеме заметил R4, нужно будет попробовать подтянуть на землю.
В итоге - не помогло, зависание полное

То, о чем я говорил - это R5, R6, R7 (судя по прозвонке тестером).

James
Offline
Зарегистрирован: 26.02.2016

т.е. попробовать их закоротить?

Veoramid
Offline
Зарегистрирован: 20.07.2016

James пишет:

т.е. попробовать их закоротить?

Не! Нельзя.

Жалко, что нет осциллографа. Можно бы посмотреть что на линии.

Я когда тренировался - я сделал управление прием передача меандром на 5 секунд. Т.е. прием 5 секунд. Передача 5 секунд (скажем 0x55 или 0xFF). И смотреть что происходит на линии параллельным 485-USB преобразователем. Все должно происходить четко.

James
Offline
Зарегистрирован: 26.02.2016

чего нет, того нет:( есть только мультиметр:(

James
Offline
Зарегистрирован: 26.02.2016

пришли новые, теперь косяк в том, что при включении модуля и попытке опросить устройство горит RX и нет ответа, такое ощущение, что комп держит линию, хотя через ЮСБ шнурок без модуля все отлично работает. Никто не сталкивался?

походу Максимка сгорел:(

Veoramid
Offline
Зарегистрирован: 20.07.2016

Кто разбирался с библиотечкой? Тестирую с программы ModBus Tester v0.3

У меня нормально работает с адресами переменных с 1 по 10.

Если задать адрес 0 - программа на ардуино зависает.

Если обращаться к адресам более более 11 (?) Arduino в протокол выдает ошибку "не правильный адрес".

Я нашел, что буфер 64 байта. А где расширить диапазон возможных переменных - не нашел :(

monser
Offline
Зарегистрирован: 01.07.2017

А библиотека для связи с устройствами ОВЕН существует?

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

monser пишет:

А библиотека для связи с устройствами ОВЕН существует?

они все работают по ModBus RTU, а очень многие еще и по ASCII