Рано или поздно (лучше рано) в устройство необходимо «впихнуть» связь с внешним миром.
Можно конечно ограничится обычным выводом данных в UART, но представьте, что будет с тем, кто захочет их разобрать.
Слава богу уже давным давно все придумано и нам остается только подстроится.
Modbus — это открытый протокол, который обычно натягивают поверх RS-232/485.
Весь фокус в том, что формат посылки стандартизирован и вы можете предоставить карту регистров вашего устройства. И при этом все остальные запросто организуют связь с вашим устройством.
В случае записи Modbus предполагает, что такой формат посылки:
- 1Байт адреса устройства
- 1Байт функции
- 2Байта адреса регистра
- 2Байта данных
- 2Байта CRC
В случае чтения, 2Байта данных имеют смысл количества читаемых регистров.
Modbus предполагает, что устройства висят на одной шине. При этом есть одно ведущее устройство и множество ведомых. Ведомые устройства не могут вещать сами по себе. Они только отвечают на запросы мастера.
На данный момент предполагается два варианта Modbus сети — Modbus RTU и Modbus TCP.
Modbus TCP — это тот же старый и добрый протокол, только прокинутый через интернет сеть. При этом ведомое устройство открывает TCP сокет и ждет данные. А мастер создает сокет на нужный адрес и засылает туда команды.
Из-за специфики TCP сокетов в Modbus TCP нет байта, который указывает адрес устройства и CRC. Контрольная сумма подсчитывается в пакетах TCP/IP стеком.
Теперь рассмотрим формат посылки и ответа на примере.
Допустим мы хотим записать в регистр с адресом 0x10 устроства 0x05 данные 0xAA55.
0x05 0x06 0x00 0x10 0xAA 0x55 CRC1 CRC2
Итак мы послали устройсву адрес 5.
Затем функцию записи — 6.
Затем адрес регистра — 0x00 0x10
Затем данные старшим байтом вперед.
Затем два байта CRC младшим байтом вперед.
Отлично.
Как понять где начало посылки в потоке данных.
Я использую таймер. После каждой посылки обязательно идет задержка. Зная её размер можно четко отсекать начало.
Приступим к реализации.
Будем считать, что вы уже прочитали статью о Uart в tms320 и настроили SCI.
Нам понадобится прерывание по приему и буфер UartBuffer.
interrupt void SCIRXINTA_ISR(void) // SCI-A
{
int i;
// если достигли конца буфера
if (UartRxLen > 50 - 2)
UartRxLen = 0;
// Получаем 2 символа
for (i=0;iTCR.bit.TRB = 1;
SciaRegs.SCIFFRX.bit.RXFFOVRCLR=1; // Clear Overflow flag
SciaRegs.SCIFFRX.bit.RXFFINTCLR=1; // Clear Interrupt flag
// To receive more interrupts from this PIE group, acknowledge this interrupt
PieCtrlRegs.PIEACK.all = PIEACK_GROUP9;
}
В обработчике прерывания мы постоянно перебрасываем данные в буфер и сбрасываем таймер.
А вот вся магия будет происходить в обработчике прерывания таймера.
interrupt void cpu_timer0_isr(void)
{
// stop timer
CpuTimer0.RegsAddr->TCR.bit.TSS = 1;
// запрещаем прерывания на время обработки и передачи
DINT;
if (UartRxLen > 0)
{
Uint16 len = modbus_func(UartBuffer, UartRxLen, 2);
Uint16 i = 0;
// отправляем ответ
for (i = 0; i TCR.bit.TSS = 0;
// Acknowledge this interrupt to receive more interrupts from group 1
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
Тут мы смотрим есть ли в буфере данные и если они есть обрабатываем их и отправляем ответ.
Как же обработать модбас посылку?
Я делаю так. Если поняли, что в буфере есть данные — проверяем наш ли это адрес. Если наш обрабатываем, ну а если нет — молчим.
Uint16 modbus_func(Uint16 *Buffer, Uint16 len, Uint16 ModbusAddress)
{
Uint16 tmp;
// отвечаем только если нас спрашивают
if (*Buffer != ModbusAddress)
return 0;
// проверяем целосность посылки
if (Crc16(Buffer,len) != 0)
return 0;
// определяем функцию
switch (Buffer[1])
{
// чтение
case 0x03:
len = modbus_0x03_func(Buffer, len);
break;
// запись
case 0x06:
len = modbus_0x06_func(Buffer, len);
break;
default:
len = modbus_error(Buffer,MODBUS_FUNCTION_ERROR);
}
// добавляем к посылке CRC
tmp = Crc16(Buffer,len);
Buffer[len] = tmp & 0xFF;
Buffer[len+1] = tmp >> 8;
return len+2;
}
Когда уверены, что запрос нам, проверяем CRC для уверенности, что посылка верна.
Когда мы уже точно знаем, что нам пришел запрос проверяем номер функции. Если нужно нет — шлем отчет об ошибке.
Ошибка метится добавлением 0x80 в байт номера фунции и собственно номером ошибки.
Вот стандартные номера ошибок.
01 — Принятый код функции не может быть обработан
02 — Адрес данных, указанный в запросе, не доступен
03 — Величина, содержащаяся в поле данных запроса, является недопустимой величиной
04 — Невосстанавливаемая ошибка имела место, пока подчинённый пытался выполнить затребованное действие.
05 — Подчинённый принял запрос и обрабатывает его, но это требует много времени. Этот ответ предохраняет главного от генерации ошибки тайм-аута.
06 — Подчинённый занят обработкой команды. Главный должен повторить сообщение позже, когда подчинённый освободится.
07 — Подчинённый не может выполнить программную функцию, принятую в запросе. Этот код возвращается для неудачного программного запроса, использующего функции с номерами 13 или 14. Главный должен запросить диагностическую информацию или информацию об ошибках от подчинённого.
08 — Подчинённый пытается читать расширенную память, но обнаружил ошибку паритета. Главный может повторить запрос, но обычно в таких случаях требуется ремонт.
Я для ошибок заготовил вот такую функцию:
Uint16 modbus_error(Uint16 *Buffer, Uint16 err)
{
Buffer[1] |= 0x80;
Buffer[2] = err;
return 3;
}
В любом случае, если мы знаем что запрос наш нужно ответить.
Теперь обработаем данные.
Uint16 modbus_0x06_func(Uint16 *Buffer, Uint16 len)
{
Uint16 Addr, Value;
const Parameter_type *parameter;
Addr = (Buffer[2] ParametersCount)
return modbus_error(Buffer, MODBUS_ADDRESS_ERROR);
parameter = &ParametersTable[Addr];
// проверка доступности регистра для записи
if (parameter->Flags.bit.w == 1)
{
// проверяем пределы
if (Value >= parameter->LowerLimit && Value UpperLimit)
{
// записываем
*(parameter->Addr) = Value;
}
else return modbus_error(Buffer, MODBUS_DATA_VALUE_ERROR);
}
else return modbus_error(Buffer, MODBUS_DATA_VALUE_ERROR);
return 6;
}
Тут мы просто берем данные и адрес, проверяем возможность записи и возвращаем длину ответа. А данные складываем в буфер Uart.
Вот и все. Примерно также работает функция чтения регистра.
Uint16 modbus_0x03_func(Uint16 *Buffer, Uint16 len)
{
Uint16 Addr, size, tmp, s_tmp;
const Parameter_type *parameter;
Addr = (Buffer[2] ParametersCount)
return modbus_error(Buffer, MODBUS_ADDRESS_ERROR);
parameter = &ParametersTable[Addr];
// проверка читаемости регистра
if (parameter->Flags.bit.r != 1)
return modbus_error(Buffer, MODBUS_DATA_VALUE_ERROR);
// кладем в буфер Uart количество переменных
Buffer[2] = size;
s_tmp = size;
Buffer += 3;
for (; size>0; size--, Buffer+=2, Addr++)
{
tmp = *ParametersTable[Addr].Addr;
*Buffer = tmp >> 8;
Buffer[1] = tmp & 0xFF;
}
// возвращаем длину посылки
// адрес + функция + количество данных + данные
return (3 + s_tmp*2);
}
Весь код примера тут: https://github.com/lamazavr/tms320_modbus
Описания протоколов повсеместно в интернетах. На википедии довольно подробное описание.




