Умный дом на микроконтроллере ATMEGA1280 часть-4

Итак в этой статье пойдет речь об описании протокола MODBUS и его реализация в Atmeg1280. Сам протокол модбас по сравнению с DCON протоколом имеет конечно намного больше возможностей. Но его реализация посложнее будет и требует намного больше ресурсов от микроконтроллера. Да и сам модбас, тормознутый протокол при обмене данными между устройствами. DCON во много раз по шустрее будет. Итак модбас работает так же как и DCON. Есть главное устройство (MASTER) обычно это контроллер(PLC) к которому подключены ведомые устройства. Например те же модули ICPCON (есть поддерживающие и модбас). Только мастер выдает запросы к ведомым устройствам. Ведомые же отвечают мастеру. Без запроса от мастера ведомые устройства всегда молчат в тряпочку. При запросе от мастера все ведомые получают команду, но отвечает только то ведомое устройство к которому запрос предназначался.

Итак в этом протоколе есть такое понятие как функция. Под функцией в модбас подразумевается скажем так, тип команды передаваемой от мастера к ведомому. В этой статье рассмотрим только 4 функции модбаса. Потому что в своем проекте я использовал только 4 функции, поэтому их и рассмотрим. Необязательно писать в проекте поддержку всех функций этого протокола если они не нужны.

Итак рассмотрим такие функции:
01 мастер хочет считать значения некоторых битов из памяти Atmega1280.
05 мастер хочет изменить значение некоторых битов в памяти Atmega1280.
04 мастер хочет считать значения некоторых байтов из памяти Atmega1280.
16 (0x10) мастер хочет изменить значение некоторых байтов в памяти Atmega1280.

Маленький нюанс. В литературе описывающей модбас есть такое выражение как считывание регистра. Так вот под считыванием регистра подразумевается считывание 2х байтов, то есть слова(word). По началу это может вызывать некоторую путаницу в мозге. И чтоб в этой статье ее пресечь будем пользоваться двумя терминами.
Регистр_2 — это будет иметься ввиду два байта.
Регистр — просто обычный регистр (байт) состоящий из 8 битов.


Рассмотрим функцию 01 и 05.
Чтобы пользоваться этими функциями, в памяти ОЗУ Атмеги я завел массив mod[64]. В этом массиве получается 512 битов. В этом массиве и хранятся те биты которые считывает или изменяет панель. Через этот массив и происходит обмен данными между панелью и микроконтроллером. Максимальная глубина такого массива по стандарту модбас может быть равна 65535 битов. Нехило. В графической панели каждый объект привязан либо к биту либо к регистру_2 в памяти микроконтроллера Atmega1280. Привязка осуществляется в настройках каждого объкта в панели. Итак есть у нас на панели графический объект лампочка, которая привязана к биту mod[0],0. Первый бит первого байта в массиве mod[64]. Если бит равен 0 то лампочка тусклая, если равен 1, тогда лампочка яркая. Как только мы откроем ту страницу в панели, где находится эта лампочка, панель сразу же начнет выдавать такую команду микроконтроллеру:
01 01 00 00 00 10 3D C6.
После этой команды должен прийти ответ от нашего ведомого устройства Atmega1280. Потом опять запрос и снова ответ. И так бесконечно пока мы находимся на данной страничке панели. Как только мы уйдем с этой странички, данный запрос/ответ прекратятся. Команда эта высылается для того чтоб считать этот бит mod[0],0 и обновить состояние лампочки на панели. Итак сама команда:

01 — адрес ведомого устройства, которому эта команда предназначена.
01 — Функция 01. Атмега понимает, что Мастер хочет считать биты из mod[64].
00 — старший байт начала точки отсчета считывания битов
00 — младший байт начала точки отсчета считывания битов
00 — старший байт количества считываемых битов
10 — младший байт количества считываемых битов
3D — CRC младший байт контрольной суммы
C6 — CRC старший байт контрольной суммы

Теперь детально. Первые два байта пню понятно. Байт 3 и 4 равны 00 00. Как раз таки это число и указывает с какого байта в массиве mod[64] начнется считывание битов. Согласно этому значению, считывание начнется с самого первого байта mod[0].
Байт 5 и 6 равны 00 10. Это 16 (0x10). Это значит что панель хочет считать всего 16 битов. Ну и последние два байта тоже пню понятно, контрольная сумма. О ней позже поговорим в этой же статье. Но возникает вопрос. Панели же нужно считать только значение 1го бита для своей лампочки. А зачем она считала сразу 16 битов? Ответ: так сделана панель. Она так работает. Панель всегда считывает биты такими порциями 16 бит минимум, потом 32бита, 48, 64, 80 и т.д. Это никак не нарушает стандарт модбаса. Но разработчики молодцы, что сделали считывание кратное 16. Это значительно упрощает написание функции ответа панели. Ну и понятно что панель может считать биты с любого места массива mod[]. Не обязательно с начала массива. Например команда может быть такой 01 01 00 0B 01 00 4C 58.
Согласно этой команде:
00 0В — считывание начнется с 11 байта массива mod[64]. Тобиш с байта mod[10].
01 00 — будет считано 256 битов или 32 байтa.

Итак вернемся к нашей первоначальной команде выданной панелью 01 01 00 00 00 10 3D C6. Как только эта команда получена, надо выслать ответ, иначе на панели появится сообщение NO PLC RESPONSE и внизу череп с костями (правда череп я сам дорисовал). И все накрывается медным тазом.
А ответ на команду будет такой: 01 01 02 FC AA 78 83
01 — ответ пришел от устройства с адресом 01.
01 — функция модбас.
02 — количество переданных байт с данными. Два байта, это FC и AA
FC — наши считанные биты (данные)
AA — наши считанные биты (данные)
78 — CRC
83 — CRC

Итак, то что нужно было панели это два байта со значением считанных битов FC и AA. В бинарном варианте: 11111100 и 10101010. Причем 11111100(FC)- младший байт, а 10101010(AA) старший. Итак значение нашей лампочки находится в байте FC и оно равно 0. Значит лампочка на панели будет тусклая.

Смотрим ниже раскладку битов:
1 — 8й бит адрес 7
1 — 7й бит адрес 6
1 — 6й бит адрес 5
1 — 5й бит адрес 4
1 — 4й бит адрес 3
1 — 3й бит адрес 2
0 — 2й бит адрес 1
0 — 1й бит адрес 0 — вот тут значение нашей лампочки для панели

1 — 16й бит адрес 15
0 — 15й бит адрес 14
1 — 14й бит адрес 13
0 — 13й бит адрес 12
1 — 12й бит адрес 11
0 — 11й бит адрес 10
1 — 10й бит адрес 9
0 — 9й бит адрес 8

Как видно из команды, за раз можно сделать запрос не более чем на считывание 2040 битов. Если панель сделает запрос на такое число битов, команда: 01 01 00 00 07 А8 3F FC, то ответ будет такой.
01 01 FF AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA CRC — итого ответ составляет 260 байтов. Нехило.
Вместо AA значение может быть любым от 0 до FF.
Теперь понятно почему модбас не такой быстрый как DCON в котором длина команды запроса/ответа к устройству 4-6 символов. Это была только функция 01.

Функция 05.
Другая ситуация. У нас на панели есть битовый тумблер, который расположен рядом с битовой лампочкой. Тумблер привязываем к этому же биту, что и лампочка к mog[0],0. При нажатии пальчиком на тумблер, панель выдаст команду 01 05 00 00 FF 00 8C 3A.

01 — адрес ведомого устройства, которому эта команда предназначена.
05 — Функция 05. Панель хочет изменить состояние 1го бита в mod[64].
00 — адрес модифицируемого бита, старший байт (max до 65535).
00 — адрес модифицируемого бита, младший байт.
FF — если здесь FF значит бит устанавливаем в 1, если 00 тогда в 0. Другие значения игорируются.
00 — этот байт всегда равен 00.
8С — СRС
3A — СRС

Байт 3 и 4 указывает какой конкретно бит в массиве mod[] будем менять. 00 00 указывает на самый первый бит массива. Если бы было например 01 3В значит изменить нужно 315 бит массива. Если все чики-пуки, то ответ панели должен быть таким же как и запрос. В итоге панель выстрелила так 01 05 00 00 FF 00 8C 3A, а в ответ получила то же самое 01 05 00 00 FF 00 8C 3A. Получается что за раз мы можем изменить только один бит в массиве. При этом придется прокачать аж целых 16 байтов. Не очень оптимально и быстро. На одной из страничек панели у меня находится таких 50 битовых тумблеров. Чтобы панели обновлять эту страничку надо постоянно прокачивать аж 800 байтов. Быстрым модбас не назовешь.

Когда мы убрали пальчик от кнопочки на панели, то панель вышлет Атмеге такую команду 01 05 00 00 00 00 CD CA. А в ответ нужно выслать то же самое. Вот в общем то с функциями 01 и 05 разобрались.

Функция 04.
Теперь предположим на панели находится байтовый индикатор. Который может принимать до 255 разных значений. Байтовый индикатор привязывается уже к регистру_2 в памяти озу. Пришлось завести второй массив.
int8_t  reg[762];
Обычный байтовый массив. Но из этого массива всегда по два байта выделяется для графического объекта в панели типа WORD. Итак открываем страничку в панели с нашим индикатором WORD и панель сразу шлет Атмеге команду 01 04 00 00 00 01 31 CA.

01 — адрес ведомого устройства, которому эта команда предназначена.
04 — Функция 04. Панель хочет считать из массива reg[762] значение регистров_2
00 — старший байт адреса первого регистра_2 (с которого начинается считывание инфо)
00 — младший байт адреса первого регистра_2
00 — кол. регистров_2 для считывания старший байт
01 — кол. регистров_2 для считывания младший байт
31 — CRC
CA — CRC

Байт 3 и 4 указывает откуда надо начать считывание данных. Так как 00 00, то с самого первого байта массива. Байт 5 и 6 указываю сколько надо считать регистров_2 из массива. Так как их значение 00 и 01 то надо считать 1 регистр_2 (word) ну или два байта. Ответ на команду будет таким. 01 04 02 00 АС B9 35.

01 — ответ пришел от устройства с адресом 01
04 — функция модбас.
02 — прими панель два байта данных 00 и AС.
00 — сами данные старший байт
АС — сами данные младший байт
B9 — CRC
35 — СRС

Хотя панель скачала два байта 00 и AC, использован будет только байт AC для нашего графического объекта байтовый индикатор.

Как будет выглядеть команда запроса скажем на считывание 10 регистров_2? А так: 01 04 BC BD 00 0A 31 CA.
BC BD — считывать надо начать с 48317 байта массива reg[].
00 0А — считать надо 20 байт, ну или то же что и 10 WORD.
А каким будет ответ? Ну вот такой 01 04 14 АА АА AA AA AA АА АА AA AA AA АА АА AA AA AA АА АА AA AA AA D3 51. AA — может быть от 00 до FF.

Фунуция 16.
Рядом с байтовым индикатором есть байтовый тумблер. Если начать молотить пальцем по этому байтовому тумблеру, то его значение будет меняться от 0 до 255 и так по кругу. Ну и при каждом прикосновении к тумблеру панель будет отсылать команду Атмеге. Вот так:
Запрос 01 10 00 00 00 01 02 00 01 67 90 ответ панели 01 10 00 00 00 01 01 С9
Запрос 01 10 00 00 00 01 02 00 02 27 91 ответ панели 01 10 00 00 00 01 01 С9
Запрос 01 10 00 00 00 01 02 00 03 E6 51 ответ панели 01 10 00 00 00 01 01 С9
Запрос 01 10 00 00 00 01 02 00 04 A7 93 ответ панели 01 10 00 00 00 01 01 С9
Запрос 01 10 00 00 00 01 02 00 05 66 53 ответ панели 01 10 00 00 00 01 01 С9
Запрос 01 10 00 00 00 01 02 00 06 26 52 ответ панели 01 10 00 00 00 01 01 С9
Запрос 01 10 00 00 00 01 02 00 07 E7 92 ответ панели 01 10 00 00 00 01 01 С9
… последний 255й удар пальцем…
Запрос 01 10 00 00 00 01 02 00 FF E6 10 ответ панели 01 10 00 00 00 01 01 С9

А сейчас детально:
01 — адрес ведомого устройства, которому эта команда предназначена.
10 — Функция 16(0x10). Панель хочет записать новые значения байтов в массив reg[].
00 — ст.байт. Начальный адрес регистра_2 с которого начнется запись данных.
00 — мл.байт. Начальный адрес регистра_2 с которого начнется запись данных.
00 — ст.байт. Количество регистров_2 в которые будут записываться данные.
01 — мл.байт. Количество регистров_2 в которые будут записываться данные.
02 — количество байт данных. В этой команде 2 байта данных это байт 8 и 9. 00 01.
00 — сами данные.
01 — сами данные.
67 — CRC
90 — CRC

При ответе мы включаем адрес ведущего, функцию, адрес первого регистра, количество записанных регистров. Вот это:
01 — адрес ведущего
10 — функция
00 — ст.байт. Адрес первого регистра_2 с которого начиналась запись данных.
00 — мл.байт. Адрес первого регистра_2 с которого начиналась запись данных.
00 — ст.байт. Количество регистров_2 в которые записывались данные.
01 — мл.байт. Количество регистров_2 в которые записывались данные.
01 — CRC
С9 — CRC

Ну вроде бы разобрались. Что то я уже запарился писать.
В модбас еще есть функции 2,3,6,7,8,17. Есть еще широковещательные команды, не требующие ответа.

А вот карта регистров модбас в панели.


CRC в модбас.
CRC вычисляется по эдакому алгоритму x^16 + x^15 + x^2 + 1.
Я нашел два варианта вычисления срц.
Первый:

/* Функция рассчета CRC 16 в Moudbus RTU.
  Name  : CRC-16
  Poly  : 0x8005    x^16 + x^15 + x^2 + 1
  Init  : 0xFFFF
  Revert: true
  XorOut: 0x0000
  Check : 0x4B37 ("123456789")
  MaxLen: 4095 байт (32767 бит) - обнаружение
    одинарных, двойных, тройных и всех нечетных ошибок
* pcBlock - Указатель на исходный буфер в памяти
  len	  - Длина буфера в байтах
Функция не очень хороша так как таблица торчит в ОЗУ.*/

const uint16_t Crc16Table[256] = {0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
				0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
				0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
				0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
				0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
				0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
				0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
				0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
				0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
				0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
				0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
				0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
				0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
				0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
				0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
				0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
				0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
				0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
				0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
				0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
				0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
				0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
				0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
				0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
				0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
				0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
				0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
				0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
				0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
				0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
				0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
				0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040};
unsigned short Crc16(uint8_t * pcBlock, uint16_t len)
{
    uint16_t crc = 0xFFFF;
    while (len--)
	{
           crc = (crc >> 8) ^ Crc16Table[(crc & 0xFF) ^ *pcBlock++];
	}
	return crc;
}

Второй вариант, которым я и воспользовался ибо таблица торчит во флеше.

//Функция рассчета CRC 16 в Moudbus RTU.
unsigned short CRC16(unsigned char *p, unsigned short len)
{
	unsigned char crc_hi;
	unsigned char crc_lo;
	unsigned char n;

	if (len>256U)
	{
		return (0);
	}

	n = (unsigned char)len;

	crc_hi = 0xFF;                    // high byte of CRC initialized
	crc_lo = 0xFF;                    // low byte of CRC initialized

	do
	{
		unsigned char i = crc_hi ^ *p++;        // will index into CRC lookup table
		crc_hi = crc_lo ^ (unsigned char)pgm_read_byte(&auchCRCHi[i]);    // calculate the CRC
		crc_lo =          (unsigned char)pgm_read_byte(&auchCRCLo[i]);
	}
	while (--n);                                    // pass through message buffer (max 256 items)
	
	return ((crc_hi << 8) | crc_lo);
}
/*
Вызов функции в программе.
unsigned char mess[3] = {1,108,8};
volatile unsigned short res1 = CRC16(&mess[0],3);
res1 будет равен 0СС6 при подстановке в конце команды менять местами 
старший и младший байты не надо. Эта функция при занесении значения в 
res1 автоматически меняет местами старший и младший байты.
*/

//Ну и таблицу расчета вклиниваем во флеш.
//Таблица для рассчета CRC16 Moudbus RTU.	  
static const unsigned char PROGMEM auchCRCHi[256]=
{
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
	0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
	0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
	0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
	0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
	0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
	0x40
};


В процессе я так же использовал программу вычисляющую CRC. Называется она ModbusMAT 1.1



Ну и напоследок код реализации модбаса в Atmega1280. Сразу скажу что не претендую на высший класс кода, так как я еще учусь писать на си. Сам знаю, что есть в чем улучшаться. Поэтому комментарии типа «ужос», «вау», «кто так пишет» — оставьте при себе. Приветствуются конкретные и по делу комментарии с конкретным объяснением где и что лучше подправить. Итак вот что получилось.
В озу завел 4 массива:

uint8_t buff_3[50];//сюда загоняем принимаемые данные от панелей.
uint8_t buff_4[100]; //сюда загоняем данные для ответа панелям
uint8_t mod[64];//Здесь находится наш битовый буфер. Ответ запрос на функции 01, 05 Modbus
int8_t  reg[762];//здесь находится наш словный буфер. Ответ запрос на функции 04, 16 Modbus


Обработчик прерывания приема данных от панели:

//------------------Здесь принимаем байты от панелей WEINTEK---------------//

SetBit(PORTF, RXD_1);	//Зажгли LED приема байта
c_2=0;//обнулили счетчик
f_4=1;//запустили счетчик с_2 включения отправки ответа панелям.
//==================================================================================
//фун. 5 - запись бита
//фун. 1 - чтение бита
//Фун.  4 - чтение регистров
//Фун. 16 - запись регистров


buff_3[d_6]=UDR1;//загоняем принимаемые данные в буфер.
d_6++;

if (d_6==2)//проверяем номер функции MODBUS
{
	
	if (buff_3[1]==0x05)  //функция 05? Тогда, то что приняли отправляем обратно
	{
		f_5=1;
		return;
	}
	
	if (buff_3[1]==0x01)  //функция 01?
	{
		f_6=1;
		return;
	}
	
	if (buff_3[1]==0x04)  //функция 04?
	{
		f_7=1;
		return;
	}
	
	if (buff_3[1]==0x10)  //функция 16 (0x10)?
	{
		f_8=1;
		return;
	}
}


Обработчик отправки байтов панели.

//------------------Здесь отвечаем на запрос от панели WEINTEK---------------//

SetBit(PORTF, TXD_1);	//Зажгли LED отправки байта


if (f_5==1)	//ответ на функцию 5
{
	UDR1=buff_3[d_6];
	buff_3[d_6]=0;
	d_6++;
	if (d_6==8)
	{
		d_6=0;
		f_5=0;
		ClearBit(UCSR1B,UDRIE1);//отключили передачу данных панелям.
		return;
	}
	return;
}
	
if (f_6==1)	//ответ на функцию 1
{
	UDR1=buff_4[d_6];
	d_6++;
	c_3--;
	if (c_3==0)
	{
		f_6=0;
		d_6=0;
		c_3=0;
		ClearBit(UCSR1B,UDRIE1);//отключили передачу данных панелям.
	}
	return;
}	


if (f_7==1)	//ответ на функцию 04
{
	UDR1=buff_4[d_6];
	d_6++;
	c_3--;
	if (c_3==0)
	{
		f_7=0;
		d_6=0;
		c_3=0;
		ClearBit(UCSR1B,UDRIE1);//отключили передачу данных панелям.
	}
	return;
}


if (f_8==1)	//ответ на функцию 16
{//01 10 00 05 00 01 CRC - ответ
	
	UDR1=buff_3[d_6];
	d_6++;
	c_3--;
	if (c_3==0)
	{
		f_8=0;	
		d_6=0;
		c_3=0;
	    ClearBit(UCSR1B,UDRIE1);//отключили передачу данных панелям.
	}
	return;
}

ClearBit(UCSR1B,UDRIE1);//отключили передачу данных панелям.		


в главном коде.
if (c_2>3)
{
	f_4=0;
	c_2=0;
	d_6=0;
	
	if (f_7==1)//ответ на функцию 04
	{
		//buff_3[2]-старший байт откуда считываем
		//buff_3[3]-младший байт откуда считываем
		uint16_t val_16= (uint16_t)buff_3[3]|((uint16_t)buff_3[2]<<8);//склеиваем два байта в val_16
		fun_04(val_16,buff_3[5]);
		c_3=buff_4[2]+5;
	}
	
	if (f_8==1)//ответ на функцию 16
	{
		uint16_t val_16= (uint16_t)buff_3[3]|((uint16_t)buff_3[2]<<8);//склеиваем два байта в val_16
		fun_16(val_16,buff_3[6]);
		c_3=8;
	}
	
	if (f_6==1)	//ответ на функцию 1
	{
		buff_4[0]=0x01;					//адрес контроллера
		buff_4[1]=0x01;					//номер функции		
		int16_t a;  //адрес 1го бита с которого начнется считывание значений, всегда кратно 16
		uint8_t  b;  //счетчик отсчитываемых байтов
		int16_t  c;  //количество считываемых битов, всегда кратно 16
		int8_t   d;
//панель всегда запрашивает кол-во битов кратное 16 и адрес тоже кратный 16.		
		b=0;
		c=(buff_3[4]<<8) | buff_3[5];//записали количество считываемых битов.
		while(c>8)//Узнаем во сколько байт влезут запрашиваемые биты.
		{
			c=c-16;
			b=b+2;
			if (c<0)
			{
				break;
			}
		}
		buff_4[2]=b;//Узнали и заныкали в буфер отправки.

		b=0;
		a=(buff_3[2]<<8) | buff_3[3];//записали начальный адрес считываемого бита.
		while(a>=8)//узнаем начальный адрес откуда считывать значения битов.
		{
			a=a-16;
			b=b+2;
			if (a<0)
			{
				break;
			}
		}


		a=3;//начальная точка
		d=7;
		c=(buff_3[4]<<8) | buff_3[5];//записали опять количество считываемых битов.
		while(c!=0) //в этом цикле запихиваем значение битов в байты
		{
			c--;//когда с станет = 0 выйдем из цикла, значит все биты упакованы к отправке
			
			if (BitIsSet(mod[b],d))
			{
				SetBit(buff_4[a],d);
			}
			else
			{
				ClearBit(buff_4[a],d);
			}
			
			d--;//каждую итерацию цикла записываем считанный бит в соответствующую ячейку.
			
			if (d==-1)
			{
				b++;
				a++;
				d=7;
			}
		}
		
		//вычислим CRC
		volatile unsigned short res1 = CRC16(&buff_4[0],buff_4[2]+3);
		hight=hibyte(res1);
		low=lobyte(res1);
		buff_4[buff_4[2]+3]=hight;
		buff_4[buff_4[2]+4]=low;
		c_3=buff_4[2]+5;//сколько вего байтов надо выплюнуть в порт.
	}
	
	if (f_5==1)//Ответ на функцию 5. Записываем значение бита в память МК.
	{
		uint16_t a;  //адрес модифицируемого бита
		uint16_t b=0;//счетчик отсчитываемых байтов
		a=(buff_3[2]<<8) | (buff_3[3]);//записали адрес модифицируемого бита.
		
		while(a>=8)
		{
			a=a-8;
			b++;
		}
		
		if (buff_3[4]==0xFF)
		{
			SetBit(mod[b],a);
		}
		else
		{
			ClearBit(mod[b],a);
		}
	}
	
	SetBit(UCSR1B,UDRIE1);//Даем добро на ответ панелям.
}


Ответ на запрос MODBUS, функция 04. Чтение регистров.
void fun_04(uint16_t a,uint8_t b)//эта функция может считать количество байт из глубины буфера не
{								 //более чем в 32767 байт. А за раз может считать не более 127 байт
	//a - адрес в буфере откуда будем считывать.
	//b - количество считываемых слов, нужно умножить на 2 получим кол. байт.
	uint16_t c=0;
	buff_4[c++]=0x01;
	buff_4[c++]=0x04;
	buff_4[c++]=b*2;
	a=a*2;
	b=b*2;
	while (b!=0)
	{
		buff_4[c++]=reg[a++];
		b--;
	}
	b=(buff_3[5]*2)+3;
	volatile unsigned short res1 = CRC16(&buff_4[0],b);
	hight=hibyte(res1);
	low=lobyte(res1);
	b=(buff_3[5]*2)+3;
	buff_4[b]=hight;
	b++;
	buff_4[b]=low;
	return ;
}
//вызов fun_04(buff_3[3],buff_3[5]);


Ответ на запрос MODBUS, функция 16 (0x10). Запись регистров.
void fun_16(uint16_t a,uint8_t b)
{
	uint8_t c=7;
	a=a*2;
	while (b!=0)
	{	
		reg[a++]=buff_3[c++];
		b--;
	}
	volatile unsigned short res1 = CRC16(&buff_3[0],6);
	hight=hibyte(res1);
	low=lobyte(res1);
	buff_3[6]=hight;
	buff_3[7]=low;
	return ;
}//fun_16(buff_3[3],buff_3[6]); вызов функции

Все.

Комментарии (28)

RSS свернуть / развернуть
Кат потерялся…
0
Да под cut же ж, ёлы-палы!
0
cunt…
+1
  • avatar
  • xar
  • 26 мая 2013, 21:52
If you know what I am talking about :D
0
00 0В — считывание начнется с 11 байта массива mod[64]. Тобиш с байта mod[10].
Может, все же с 11-го бита? Это четвертый бит mod[1] (т.к. нумерация идет с нуля).
будет считано 256 битов или 16 байтов.
32 байта.
Приветствуются конкретные и по делу комментарии с конкретным объяснением где и что лучше подправить.
Сложно дать конкретный комментарий, когда плохо все. Это примерно как если блюдо сгорело до угольков — нельзя дать какие-то комментарии по его качествам, кроме как «это угли» — и уже неважно, что это было и какие особенности в нем закладывались.
Ну вот, например, именование сущностей. Названия в духе variable1, variable2, variable3. Где что — непонятно, да и запутаться в этих индексах — раз плюнуть. Нужно вместо buf_3 — MODBUSReceiveBuffer, вместо d_6 — MODBUS_Receive_Counter, вместо f_6 — IsFunction01.
Во вторых — вместо if'ов можно было использовать кейс. Хотя на самом деле ни они, ни кейс не нужны.
Вместо кучи переменных «это функция 1», «это функция 5» лучше было завести переменную CurrentFunction. Тогда будет if (d_6==2) CurrentFunction = buff_3[1]. А в отправке вместо множества if(f_x==1) будет case(CurrentFunction). Сам if(d_6) тоже не нужен — разумнее принимать в буфер и в главном цикле разбирать его содержимое.
В целом все то же самое — копипаста, неинформативные названия сущностей, вместо выделения задач и их раздельной реализации (принцип «разделяй и властвуй») они делаются вперемешку и затем раскопипащиваются, что усложняет измение отдельной задачи — ее нужно из этой каши вычленить, модифицировать, а затем растащить модификации по всем копиям.
Еще одно характерное следствие этого винегрета — вместо того, чтобы показывать код в виде законченных, офорленных и реюзабельных функций — ты пастишь вырезки кода без начала и без конца. Во всей статье только две приличных вставки кода — это две функции расчета CRC16.
+2
  • avatar
  • Vga
  • 26 мая 2013, 22:42
Нужно вместо buf_3 — MODBUSReceiveBuffer, вместо d_6 — MODBUS_Receive_Counter, вместо f_6 — IsFunction01.
А здесь меня самого немного переклинило и я поименовал все в разном стиле. В проекте, разумеется, именовать нужно в одном стиле.
+1
Ну и по поводу «где и что лучше подправить» — прежде всего перепроектировать в общем так, чтобы избавиться от копипасты (в первую очередь — это выделение и разделение задач). Переименовать сущности соответсвенно выполняемым им задачам. Везде, где есть взаимодействие с внешним миром и сетью — добавить контроль корректности данных (это не только проверка CRC, но и такие проверки, как проверка символа на принадлежность [0-9a-fA-f] в ASCII_TO_HEX) и контроль таймаутов.
+1
Приветствуются конкретные и по делу комментарии с конкретным объяснением где и что лучше подправить. Итак вот что получилось.
-3
Уж какой код, такие и комментарии.
+1
Может, все же с 11-го бита? Это четвертый бит mod[1] (т.к. нумерация идет с нуля).
Не с бита, а с байта. Считывание битов из буфера mod[], начинается всегда только с 0го бита(какого либо байта). Так работает панель. Запись 00 0B указывает как раз таки с какого байта в буфере и начнется считывание. А запись 01 00 уже указывает сколько надо отсчитать битов.
0
Во первых, это нелогично. А во вторых — это противоречит спецификации MODBUS (стр. 11). Тем более, что с байтами она вообще не работает — только с битами и 16-битными регистрами.
+1
В литературе зпт. описывающей модбас зпт. есть такое выражение как считывание регистра. Так вот зпт. под считыванием регистра подразумевается считывание 2х байтов, то есть слова(word).
В литературе написано правильно, потому что среди регистров есть пара битовых и пара двухбайтовых типов. Сам же используешь чтение битовых регистров.
В тех же модулях ICPCON с цифровыми входами и выходами используются битовые регистры, а для счётчика цифрового входа — сразу два двухбайтовых.

Modbus прост, последователен и лаконичен. А в DCON команды с дублями, ответы у разных комманд разные, считать сразу два байта нельзя. И да, мало где используется, со всеми последствиями. Мне не понравился.
+1
00 — старший байт начала точки отсчета считывания битов
00 — младший байт начала точки отсчета считывания битов
Байт 3 и 4 равны 00 00. Как раз таки это число и указывает с какого байта в массиве mod[64] начнется считывание битов.
WTF?
Дальше читать пропало желание.
0
Приветствуются конкретные и по делу комментарии с конкретным объяснением где и что лучше подправить.

А какой особый смысл писать замечания к Вашему коду, если Вы на эти замечания не реагируете?

Весь букет проблем из предыдущей части присутствует и в этой (общий стиль, названия переменных в духе a,b,c,d, ничем необоснованное использование volatile, отсутствие проверки входных значений. Вот например, в функции (обработчике прерывания) чтения – что будет если вторым байтом (buff_3[1]) придёт значение, которое Вы не ожидаете (отличное от 0x01, 0x04, 0x05, 0x10)?
+2
Потому что код уже написан пол года назад. И уже давно все работает. Поэтому выкладываю что давно уже есть. Предлагаете чтоб я сразу же после первого комментария начал рыть землю лопатой чтоб изменить код? Скорее всего, что то изменю, но не за один же день. Если что то менять, то нужно собрать сперва информацию, что и как менять. Надо все проанализировать. Потом засесть и менять. И это займет повторяюсь не один день.
Вот например, в функции (обработчике прерывания) чтения – что будет если вторым байтом (buff_3[1]) придёт значение, которое Вы не ожидаете (отличное от 0x01, 0x04, 0x05, 0x10)?
Глобального ничего не произойдет. Не выстявяться флаги на отправку ответа панели. Вот и все. Запрос будет, а ответа не будет. На панели появится сообщение PLC NO RESPONSE. Если на следующую команду будет ответ, сообщение исчезнет и панель возвращается в свой обычный режим. Да я это исправлю (может быть)в настоящем коде. Спасибо за совет. Длинна линии модбас в данном проекте 30 см. Панель находится в одном заземленном металлическом ящике с Атмегой. В общем то модбас и работает внутри этого ящика. Вероятность того что придет другой какой то байт в данной ситуации мал. Но я этого не отрицаю и не отмазываюсь, и глаза на это не закрываю. Не ошибается тот, кто вообще ничего не делает. Но я не из таких. Опыт приобретаю путем проб и ошибок. Все советы собираются и анализируются в мозгу. Через какое то время что то решу.
0
Есть ещё одна потенциальная проблема (если я правильно понял Ваш код). Насколько я понял, c_2 – это программный счётчик привязанный к таймеру. При получении байта вы его забрасываете, а обработку начинаете после некоторого «таймаута» (if (c_2>3) в «главном коде»).

Дык вот, ели в уарт придёт «пачка» из более чем 50 байт – то произойдет выход за границы buff_3[50]. По-хорошему, нужно добавить проверку для d_6 на выход за пределы массива.
0
Учтем, спасибо.
0
260 байтов. Нехило.
Вместо AA значение может быть любым от 0 до FF.
Теперь понятно почему модбас не такой быстрый как DCON в котором длина команды запроса/ответа к устройству 4-6 символов. Это была только функция 01.
Однако вы вводите людей в заблуждение. DCON при передаче тех же 255 информационных байт в лучшем случае просит 512 байт (т.к. на каждый байт по 2 символа HEX + начальный '>' и перевод строки). В худшем же по шине потребуется переслать 128 пакетов по 6 байт = 768 байт (если по 16 бит данных от модуля) или 255 пакетов по 4 байта = 1020 байт (если по 8 бит данных от модуля). Против 260 байт MODBUS не хило, да?
Еще судя по вашей статье DCON подразумевает передачу всех своих данных, в ответ на запрос. Т.е. там где MODBUS выкусит только пару регистров, DCON отправит весь свой пакет на все сотни байт.
Проигрыш MODBUS идет как раз на маленьких пакетах. Так минимум для DCON надо 4 байта запрос, 4 байта ответ (8 битный модуль без контрольной суммы), а для MODBUS 8 байт запрос, 7 байт ответ (16 битный модуль с контрольной суммой). Но уже при 16 битных модулях с контрольной суммой у DCON: 6 байт запрос, 8 байт ответ, не так ли? Разница практически отсутствует.
+2
Я имел ввиду взаимодействие между Мастер — слейв по модбас (всего два устройства) на шине и взаимодействие Мастер-слейв по DCON (всего два устройства на шине). Второй вариант быстрее работает.
0
Последний мой абзац рассматривает такую ситуацию.
При равных характеристиках (16 бит данных, CRC пдля проверки целостности) разница небольшая.
Если пожертвовать надежностью (без CRC), то достигаем 2-хкратной разницы (ещё и заложим что модули 8-битные). Это уже существеннее.
И как всегда возникают «но». Если у вас 1-2 модуля, то разницы 30 байт или 60 байт особой нет — ресурсов сети достаточно, нагрузка на МК небольшая. А если у вас 20-30 модулей, то без контрольных сумм уже не стоит — а там разница снова небольшая.

У DCON я вижу то преймущество, что при сбое потока данных его легче восстановить: просто ждем начало следующего пакета. В данных у нас не может содержаться символа начала команды/ответа.
MODBUS же требует более аккуратной функции поиска начала очередного пакета, ведь у нас уже нет определенного маркера начала/конца. У нас есть длина пакета и контрольная сумма. И всё бы ничего, да есть двоичные данные при определенных стечениях обстоятельств (на дискретных входах сложилась «магическая последовательность» состояний) значения которых погут быть расценены на длину и контрольную сумму.
Кстати, не понятно по коду (не сильно то и вникал — тяжко), Как обстоят дела у вас с «вклиниванием» атмеги в идущий обмен, что произойдет: панель дает запрос (в процессе, но заголовок уже уше), атмега сбросилась/включили питание?
0
Как обстоят дела у вас с «вклиниванием» атмеги в идущий обмен, что произойдет: панель дает запрос (в процессе, но заголовок уже уше), атмега сбросилась/включили питание?
Я уже так делал 15000 раз. Обмен данными всегда восстанавливается. Вот видео.
youtu.be/MmJpiEr2Nm4
0
Пришлось завести второй массив для данных типа WORD
int8_t  reg[762];
Уберите «типа WORD», массив у вас не этого типа. Вам просто потребовалось завести ещё один массив байтовый, предназначенный для хранения 16-битных значений. Каким макаром в 8-битном массиве будут храниться 16-битные данные опишите далее.

Выбор слова «тумблер» не удачный. Лучше «кнопка». Потому как «тумблер» больше ассоциируется с советским переключателем (уж простите, вы так много в тексте выделений делаете, что я не удержался). если же компонетн при отпускании возвращается в «исходное» состояние, то это уже «кнопка».
+2
WORD убрал.
Выбор слова «тумблер» не удачный. Лучше «кнопка».
Выбор удачный ибо это тумблеры. Причем в панели все очень гибко. Можно к объекту битовая кнопка повесить графический объект тумблера.
0
А, понял в чем дело. Применение разных терминов сбило с толку.
Когда мы убрали пальчик от кнопочки на панели, то панель вышлет Атмеге такую команду 01 05 00 00 00 00 CD CA.
Прочитал по инерции не как «кнопка» а как «тумблер» (до этого же жали именно тумблер). А тумблер (aka перекидной переключатель) при отпускании не имеет свойства возвращаться в исходное состояние, что происходит судя по пакету.
0
такие GUI убивают наповал. Совершенно непонятно: то ли тумблер выключен (т.к. красное как бы выделяет из общего фона), то ли туда надо нажать, чтоб его выключить. Если уж так хочется рисовать всякие тумблеры и кнопки, то тупо нарисуйте светодиод, чтоб он горел, либо не горел.
+2
причем тут «советский» ?Thumbler switch же…
0
Просто такие переключатели я в руках держал только совковые. Так то не спорю, что они были в каждой стране (и чуть ли не обязательный кадр в любом фильме где есть вертолет — переключение кучи тумблеров). А вот уже в постсоветские времена переключатели всё чаще стали быть китайские кнавишные и слайдеры. Не, советские слайдеры я тоже видел, в тех же калькуляторах. Но больше техники всё же ламповой — высокие напряжения — тумблеры.
0
Выхдав еще ровно 3 секунды, я быстро щелкнул серым и неказистым на вид советским тумблером с надписью «Пуск»(Fire) — 2 ракеты С-125(СЭМ «Гоа») мгновенно сорвались с направляющих и хищно светясь в ночи ушли во тьму. Через 5 минут горящие обломки F-117 Nighthawk рухнули на землю. Потом уже, кто-то из солдат принес мне маленький, никелированный и какой-то гламурный на вид, хотя и изрядно прокопченный, тумблер с надписью General Electric — он успел скрутить его с панели приборов Стелса.
-1
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.