суббота, 20 декабря 2014 г.

Удаленный промышленный терминал-индикатор по Modbus RTU

Данный универсальный терминал-индикатор индустриал-класса был разработан на базе МК ATMega128. ПО обеспечивает поддержку MASTER и SLAVE-режимов обмена по протоколу Modbus RTU в сети RS-485 c динамической визуализацией данных на четырех-символьном цифровом индикаторе, с возможностью расширения шины. Проект создан в IAR и опубликован циклом материалов в журнале Радиолюбитель "MODBUS на привязи" №7, 8, 9, 10, 11 от 2009 года.



Вместо введения...

В качестве протокола связи в промышленных сетях RS-485 или 422-го чаще всего используется Modbus ...в случае компьютера - можно использовать конверторы интерфейсов, например USB/RS-485/422 (ICP DAC I-7556 и т.п. других производителей) и вы опять-же работаете просто как COM -портом (виртуальным). Тем кто не понимает разницы между RS-485 и RS-422 как каналами связи: по большей части EIA-422 - это не протокол, а способ соединения и ничем не отличается от RS-485 интерфейса за исключением однонаправленности линии передачи, даже драйверы можно от 485-го задействовать, т.е. основная особенность 1 дифференциальный передатчик и до 10-ти приемников. В 485-м до 32 приемников.

Теория и практика работы с Modbus (статьи в РЛ)
  1. MODBUS на привязи. Удаленный промышленный индикатор
  2. MODBUS на привязи. UART или... установка скорости микроконтроллера
  3. MODBUS на привязи. Рабочая лошадка ATMEGA
  4. MODBUS на привязи. Мониторинг и контроль на ПК
  5. MODBUS на привязи. Конвертор архивов
Прикинем время передачи фрейма UART и Modbus-пакета на скорости 19200 бод: 19200 бит/сек соответствует 52 мкс на бит. Фрейм UART на 8/e/1 включает 11 бит (старт-бит, 8 дата-бит, паритет, стоп-бит), итого ~572 мкс. Расстояние между фреймами не более 1.5 символа, символ = 4 бит, имеем 6*52 = 312 мкс IDLE состояния. Пусть будет даже 300, итого 872 мкс.

Пакет Modbus RTU включает 8 байт:
  1. Адрес
  2. Функция
  3. lo регистр
  4. hi регистр
  5. hi value
  6. lo value
  7. lo CRC
  8. hi CRC.
Передача пакета отнимет 8 фреймов = 8*872 мкс = 6 976 мкс. С интервалом 3.5 символа (3.5*4*52=728 мкс) между пакетами время на один пакет составит ~ 7.7 мс.

Схемотехника 

Схема модуля с гальванической развязкой интерфейса RS-485 представлена ниже:


Зачем шинник, а не просто напрямую к портам МК через токоограничивающие резюки? Давайте прикинем нагрузку в данной схеме: при динамической индикации в один момент времени может быть зажжен один индикатор со всеми сегментами, т.е. 8 светодиодов (пусть по 20 мА каждый), собственное токопотребление меги на частоте 8 МГц и питании 5 В порядка 12 мА, плюс два оптрона пусть по 8 мА. Суммируем: (8*20) + 12 + (2*8) = 188 мА. Близко к критическому режиму для токов через питающие ноги в 200 мА. Снижение надежности? Очевидно. А если захотим поставить более яркие и соответственно более токопотребляющие 7-мисегментники? Что, плату переразводить? С шинником зависимости от перегрузочной способности МК AVR не будет. Теперь понятно? И читайте комментарии внимательнее )

Печатная плата оттрассирована в OrCad:





Фото шелкографии



Второй вариант конструктивного исполнения индикатора с Modbus



Пример примитивного одиночного запроса (побайтно)
#include  "system.h"
#include  "iom128v.h"
#include <delay.h>
#include <stdio.h>

void main(void) {    // USART initialization  
// Communication Parameters: 8 Data, 1 Stop, No Parity  
// USART Receiver: Off  
// USART Transmitter: On  
// USART Mode: Asynchronous  
// USART Baud Rate: 9600         UCSRA=0x00;        UCSRB=0x08;        UCSRC=0x8E;       
UBRRH=0x00;        UBRRL=0x19; while(1) {
        UDR=0x01;
        delay_ms(1);
        UDR=0x03;
        delay_ms(1);
        UDR=0x00;
        delay_ms(1);
        UDR=0x00;
        delay_ms(1);
        UDR=0xFF;
        delay_ms(1);
        UDR=0x00;
        delay_ms(1);
        UDR=0x8C;
        delay_ms(1);
        UDR=0x3A;
        delay_ms(1000);
    }
}
Нижний уровень
#include  "system.h"
#include  "iom128v.h"
#include  "timers.h"
#include  "display.h"

unsigned char AddrNet;

unsigned int NetBuf[256],NetBufX,NumQuest,pil;
unsigned char NetAddr,NetError,NetCommand,NetLength,NetByte,NetRegs,CRCError;
unsigned char RxOn,RxIn,TxOn,TxCnt,ConnectFlag;
unsigned int  RxTCnt,pRxTCnt,ByteTCnt,NetCRC;
unsigned char QCount[16],fDataRead[2];
unsigned char UARTRequest,StartRequest,StopRequest;

//Net data buffer
#define _ShiftAddr 0x64 
//  Slave #1
#define _Addr_Slave1 0
// [1] - Zadannyi temp prokatki
#define _Addr_ZTP 1 //0
// [2] - Obratnyi otschet       
#define _Addr_OO 0 //1
// [3] - Zapret raboty         
#define _Addr_ZR 2
// [4] - dlinnyi raskat        
#define _Addr_DR 4 //3
// [0] - Raschetnoe vremya pauzy
#define _Addr_RVP 3 //4

//  Slave #2         
#define _Addr_Slave2 1

unsigned int Netx40[16];
unsigned int ZTP,OO,ZR,DR,RVP;

#define _MaxQuest 2

//--------------------------- UART ---------------------------------

//---- Oscillator--
#define _OscX   8.192

//---- 8MHz -------
//#define BAUD_115200       3
#define BAUD_57600        8
#define BAUD_38400        12
#define BAUD_28800        16
#define BAUD_19200        25
#define BAUD_14400        34
#define BAUD_09600        51
#define BAUD_04800        103

//----- Parity ------
#define PRTY_NONE     0
#define PRTY_ODD      1
#define PRTY_EVEN     2

//----- Bits ---------
#define RSFIVE_BITS   0
#define RSSIX_BITS    1
#define RSSEVEN_BITS  2
#define RSEIGHT_BITS  3
#define RSSTOP_1BIT   0
#define RSSTOP_2BIT   1

#define _Bot         11.5  
#define _HoldLine    1
#define _UnHoldLine  0

#define _TimeOutTxStart    5
#define _TimeOutTxStop     2
#define _UARTRequest       20
//-------------------------------------------------------------------


void  SetBaudRate(unsigned char  baud){
 switch(baud){
  //case BAUD_115200:
  case BAUD_57600:
  case BAUD_38400:
  case BAUD_28800:
  case BAUD_19200:
  case BAUD_14400:
  case BAUD_09600:
  case BAUD_04800: UBRR0L=baud; break;
  default: UBRR0L=BAUD_09600; break;
 }
}

void  SetParity(unsigned char  prty)  {
 UCSR0C&=~((1 << UPM00) | (1 << UPM01));
 switch(prty)  {
  case  PRTY_NONE:  break;
  case  PRTY_ODD:   UCSR0C|=(1 << UPM00);
  case  PRTY_EVEN:  UCSR0C|=(1 << UPM01);  break;
 }
}

void  SetStopBits(unsigned char  sb)  {
 UCSR0C&=~(1 << USBS0);
 if(sb == RSSTOP_2BIT) UCSR0C|=(1 << USBS0);
}

void  SetBits(unsigned char  numb)  {
 UCSR0C&=~(RSEIGHT_BITS<<UCSZ00);
 UCSR0C|=((numb & RSEIGHT_BITS) << UCSZ00);
}

unsigned int CalcTCntPerByte(void){
 switch(UBRR0L){
  //case BAUD_115200: return((unsigned int)(_Bot / 0.1152 / TIMER3_FltTICK));
  case BAUD_57600: return((unsigned int)(_Bot / 0.0576 / TIMER3_FltTICK));
  case BAUD_38400: return((unsigned int)(_Bot / 0.0384 / TIMER3_FltTICK));
  case BAUD_28800: return((unsigned int)(_Bot / 0.0288 / TIMER3_FltTICK));
  case BAUD_19200: return((unsigned int)(_Bot / 0.0192 / TIMER3_FltTICK));
  case BAUD_14400: return((unsigned int)(_Bot / 0.0144 / TIMER3_FltTICK));
  case BAUD_09600: return((unsigned int)(_Bot / 0.0096 / TIMER3_FltTICK));
  case BAUD_04800: return((unsigned int)(_Bot / 0.0048 / TIMER3_FltTICK));
  default:return((unsigned int)(_Bot / 0.0096 / TIMER3_FltTICK));
 }


// Hold line RS-485
void HoldLine(unsigned char fHold){
 char lTemp;
 if (fHold) {UCSR0B&=~( 1 << RXCIE0 ); sbi(DIRECT);}
 else {{UCSR0B|=( 1 << RXCIE0 ); cbi(DIRECT);}}
 lTemp=UDR0;


/*function CRC(BufferData : TDataByte) : word;
var
  Data: byte;
  i,j: integer;
begin
  result:= $FFFF;
  for i:=0 to high(BufferData)- 2 do
  begin
    data:= BufferData[i];
    for j:=1 to 8 do
    begin
      if (((data xor result) and $0001) = 1) then
         result:=(result shr 1) xor $A001
      else
         result:=result shr 1;
      data:= data shr 1;
    end;
  end;
end;
*/

/* ПОДСЧЕТ КОНТРОЛЬНОЙ СУММЫ ПАКЕТА */
unsigned int CRC(unsigned int *lMessage, unsigned char lCount){
 unsigned int data;
 unsigned char lBits,lCnt;
 data = 0xFFFF;
 for(lCnt=0;lCnt<lCount;lCnt++){
  data^=lMessage[lCnt];
  for(lBits=0;lBits<8;lBits++){ //repeat 8
   if (data & 0x0001){
    data>>=1;
    data^=0xA001;
   }
   else data >>=1;
  }
 }
 return (data);
}

void MasterModBus(void){
 NumQuest=_MaxQuest;
 TxOn=0xFF;
 NetBufX=0;
 HoldLine(_HoldLine);
}



void InitUART(void) {
 pil = 0x00; //min
 MasterModBus();  
 SetBits(RSEIGHT_BITS);
 SetParity(PRTY_NONE); //PRTY_EVEN
 SetStopBits(RSSTOP_1BIT);
 SetBaudRate(BAUD_09600);
 ByteTCnt=CalcTCntPerByte()*1.5;
 //------
 UCSR0B = ( 1 << RXEN0 )+( 1 << TXEN0 )+( 1 << TXCIE0 );
 fDataRead[0]=fDataRead[1]=0;
 StartRequest=StopRequest=0xFF;
 UARTRequest=0;
 RxIn=RxOn=0; // net - na priem
 TxOn=0xFF;   // da - na peredachu
}

void HandleModBus(void){ // priem
 unsigned int i, lCRC,lNum;
 NetCRC=(NetBuf[NetByte-1]<<8)&0xFF00;
 NetCRC+=NetBuf[NetByte-2]&0xFF;
 lCRC=CRC(NetBuf,NetByte-2);
 if (NetCRC==lCRC){
  switch (NetAddr){ //0
   case _Addr_Slave1: //0
    switch (NetCommand) { //4
     case 4: {
      NetLength = NetBuf[2]; // nomer parametra
      Netx40[0] = NetBuf[3]; // hi val
      Netx40[1] = NetBuf[4]; // lo val
      /*if (NetLength == 0x02){
       Netx40[0] = (NetBuf[3]<<8)&0xFF00; // hi val
       Netx40[1] = NetBuf[4]&0xFF;        // lo val
      }*/
      break;
     }
     case 6: {
      //LEDControl(LED_Link,_Invert);
      break;
     }
    }
   break;
  
  }
 }
}                 

void SendRequest(void){ // po timery zaverwenija
 unsigned int lCRC;
 lCRC=CRC(NetBuf,6);
 NetBuf[6]=lCRC&0xFF;         //lo
 NetBuf[7]=(lCRC&0xFF00)>>8;  //hi
 TxCnt+=2;
 TxOn=0;          
 ConnectFlag=0;
 HoldLine(_HoldLine);
 UDR0=NetBuf[TxOn++];
}

void RequestModBus(void) { // zapros data to set po timery
 TxOn=0;
 if (NumQuest<_MaxQuest) NumQuest++; else NumQuest=1;

 switch (NumQuest){
  case 1:// Get Data Slave 1
    NetAddr=0x00;
    NetCommand=0x04;
    NetRegs=2;
   
    NetBuf[0]=NetAddr;    // 0
    NetBuf[1]=NetCommand; // 4
    NetBuf[2]=0xA0;       // RG 160
    NetBuf[3]=0x00;       // ---
    NetBuf[4]=0x00;       // PAR 1
    NetBuf[5]=0x01;       // --- 
    TxCnt=6;
    break;
  case 2:// Set Data Slave 1
    if (pil<100) pil++; else pil=0x00; //pila
   
    NetAddr=0x00;
    NetCommand=0x06;
       
    NetBuf[0]=NetAddr;    // 0
    NetBuf[1]=NetCommand; // 6
    NetBuf[2]=0xA4;       // RG 164
    NetBuf[3]=0x00;       // ---
   
    NetBuf[4]=0x00;
    NetBuf[5]=pil;
    //NetBuf[4]= Netx40[0]; // hi val
    //NetBuf[5]= Netx40[1]; // lo val
   
    TxCnt=6;
    break;


 default: return;      
 }
 StartRequest=_TimeOutTxStart;
}
  
void RecvModBus(void){ // po prerivanijy RX           
 unsigned char RxD,RxErr,lSREG;
 unsigned int  lRxTCnt;
 RxIn=1;
 UARTRequest=0;
 RxD=UDR0;              
 RxErr=UCSR0A;
 if (RxErr & ((1<<UPE0)+(1<<FE0))){NetBufX=0;pRxTCnt=TCNT3;return;}
 // Control frame --- nakoplenie
 switch (NetBufX){
  case 0: {if (RxD==NetAddr) { NetBuf[0]=RxD; RxOn=1; NetBufX=1; return;} return;}
  case 1: {if (RxD==NetCommand){NetBuf[1]=RxD;NetBufX=2; return;} else {RxOn=0; NetBufX=0;return;}}
  case 2: {if (NetCommand==4){NetBuf[2]=RxD; NetLength=RxD+2; NetBufX=3; NetByte=3; return;}
           if (NetCommand==6){NetBuf[2]=RxD; NetLength=3+2; NetBufX=3; NetByte=3; return;}
           NetBufX=0; RxOn=0; return;}
  case 3: {NetBuf[NetByte]=RxD;
           NetLength--;
           NetByte++;
           if (!NetLength){
            HandleModBus();
            ConnectFlag=1;
            NetBufX=0;
           }
           return;
          }
 }
}   

void TimeOutUART(void){ // po timery
 unsigned char lTemp;
 if (!StartRequest) {
  UARTRequest=0;
  StartRequest=0xFF;
  SendRequest(); // po timery
 }
 if (!StopRequest) {
  UARTRequest=0;
  StopRequest=0xFF;
  TxOn=0xFF;
  HoldLine(_UnHoldLine);
  lTemp=UDR0;
  lTemp=UDR0;
  lTemp=UDR0;
  lTemp=UDR0;
 }
}

void WaitUART(void){ // po timeru
 UARTRequest++;
 if ((StartRequest!=0xFF) && (StartRequest>0)) StartRequest--;
 if ((StopRequest!=0xFF) && (StopRequest>0)) StopRequest--;
 TimeOutUART(); // po timery
 if (ConnectFlag) {UARTRequest=0; ConnectFlag=0; RequestModBus();} // po timery
 if (UARTRequest<_UARTRequest) return;
 UARTRequest=0;
 if ((TxOn==0xFF)&&(StartRequest==0xFF)&&(StopRequest==0xFF)) RequestModBus(); // po timery
}

void HandleUART(void) { // po prerivanijy RX
 RecvModBus();
}

void SendUART(void) { // po prerivanijy TX         
 unsigned char lTemp;
 UARTRequest=0;   
 if (TxOn==TxCnt) {
  TxOn++;
  StopRequest=_TimeOutTxStop;
  return;}
 UDR0=NetBuf[TxOn++];
}

void ReadNetData(void){
 OO=Netx40[_Addr_OO];
 ZR=Netx40[_Addr_ZR];
 RVP=Netx40[_Addr_RVP];
}
Верхний уровень (C++)

Запись данных в порт RS-485:
DWORD SERPORT::WriteBlock485(char *Data,unsigned LenData)
{
unsigned long realCount;
// Ассинхронный доступ
if(PHandle == INVALID_HANDLE_VALUE)
  return 0;
SetLine(SETRTS);
if(WriteFile(PHandle, Data, LenData, &realCount, &wop) == false)
  if(GetLastError() == ERROR_IO_PENDING)
  // Если ошибка при переполнении
  // Ожидание окончания отправки данных

  WaitForSingleObject(wopOverlapped, INFINITE);
  SetLine(CLRRTS);
  return LenData;
}
Чтение данных из порта RS-485:
DWORD SERPORT::ReadBlock485(char *Data, unsigned LenData)
{
unsigned long realCount;
unsigned long ReadByte = 0;
if(PHandle == INVALID_HANDLE_VALUE)
  return 0;

if(ReadFile(PHandle, Data, LenData, &ReadByte, &rop) == false)
  if(GetLastError() == ERROR_IO_PENDING)
    {
    WaitForSingleObject(ropOverlapped, 50);
    GetOverlappedResult(PHandle, &rop, &ReadByte, false);
    }
return ReadByte;
}
Для контроля направления приема-передачи конвертора RS-485/USB (COM) дергаем сигналы DTR или RTS через API функцию EscapeCommFunction():
BOOL SERPORT::SetLine(DWORD Line)
{
return EscapeCommFunction(PHandle, Line);
}

Верхний уровень (Delphi)
function CRC(BufferData: TDataByte): word;
var
  Data: word;
  i,j : integer;
const polinomio: word= $A001;
begin
  result:= $FFFF;
  for i:=0 to high(BufferData)- 2 do
  begin
    data:= BufferData[i];
    for j:=1 to 8 do
    begin
      if (((data xor result) and $0001) = 1) then
         result:=(result shr 1) xor polinomio
      else
         result:=result shr 1;
      data:= data shr 1;
    end;
  end;
end;


// формирование MASTER пакета

procedure set_smh(net,kod,reg,val: word);
var Cadena: TDataByte;
    crcc  : word;
    i     : integer;
    s     : string;
begin
 Setlength(Cadena,8);
 Cadena[0]:= net;   // сет.адрес 0
 Cadena[1]:= kod;   // код 6
 Cadena[2]:= hi(reg);   // регистр параметра 164
 Cadena[3]:= lo(reg);   // ---
 Cadena[4]:= hi(val);   // число 100
 Cadena[5]:= lo(val);   // ---
 crcc:= CRC(Cadena);    // crc

 Cadena[6]:= lo(crcc);
 Cadena[7]:= hi(crcc);
 //log-
 s:= ''; for i:= 0 to 7 do s:= s + inttohex(cadena[i],2)+' '; //mf._log('TX: ' + s);
 s:= ''; for i:= 0 to 7 do s:= s + chr(cadena[i]);
 if en_com then
  mf.com.WriteCommData(pchar(s),length(s))
end;


// формирование запросного пакета в MASTER режиме

procedure get_smh(net,kod,reg,par: word);
var Cadena: TDataByte;
    crcc  : word;
    i     : integer;
    s     : string;
begin
 Setlength(Cadena,8);   // размер массива
 Cadena[0]:= net;        // сет.адрес 0
 Cadena[1]:= kod;       // код 4
 Cadena[2]:= hi(reg);   // регистр параметра 160
 Cadena[3]:= lo(reg);   // ---
 Cadena[4]:= hi(par);   // параметр 1
 Cadena[5]:= lo(par);   // ---
 crcc:= CRC(Cadena); // crc

 Cadena[6]:= lo(crcc);  // мл. байт crc
 Cadena[7]:= hi(crcc);  // ст. байт crc
 // запись в log- файл
 s:= ''; for i:= 0 to 7 do s:= s + inttohex(cadena[i],2)+' '; //mf._log('TX: ' + s);
 s:= ''; for i:= 0 to 7 do s:= s + chr(cadena[i]);
 if en_com then
  mf.com.WriteCommData(pchar(s),length(s))
end;
...

// расшифровка принятых пакетов
...
procedure Tmf.packet(Str: String);
var i,chn,val:integer;
    Cadena   : TDataByte;
    CadenaCRC: word;
    s        : string;
begin
 //индикатор пакета-
 set_tn(1,1,''); application.ProcessMessages;
 txt1.Clear; txt2.Clear; msg1.Text:='';
 msg1.Text:= str;

 //расшифровка пакета MODBUS
 if (length(str)=8)OR(length(str)=7) then begin //первичный контроль на длину пакета
  //наполнение массива перед проверкой-
  s:= '';
  for i:=1 To length(str) do begin
   modbuf[i-1]:= byte(char(str[i])); // наполняем массив-
   s:= s + ' ' + inttohex(modbuf[i-1],2)
  end;

   rg0.Text:= inttohex(modbuf[0],2); //распределяем по регистрам-
   rg1.Text:= inttohex(modbuf[1],2);
   rg2.Text:= inttohex(modbuf[2],2);
   rg3.Text:= inttohex(modbuf[3],2);
   rg4.Text:= inttohex(modbuf[4],2);
   rg5.Text:= inttohex(modbuf[5],2);
   rg6.Text:= inttohex(modbuf[6],2);
   rg7.Text:= inttohex(modbuf[7],2);

   // выделение тестовой пилы -
   if (modbuf[0]=p0.Value)and(modbuf[1]=p1.Value)and(modbuf[2]=p2.Value) then begin
    gd.Progress:= modbuf[4] + modbuf[5];
    vl.Caption:= inttostr(gd.Progress)
   end;

   //LCRC + CRC
   Setlength(Cadena,8);
   Cadena[0]:= modbuf[0];
   Cadena[1]:= modbuf[1];
   Cadena[2]:= modbuf[2];
   Cadena[3]:= modbuf[3];
   Cadena[4]:= modbuf[4];
   Cadena[5]:= modbuf[5];

   CadenaCRC:=CRC(Cadena);
   Cadena[high(Cadena)-1]:= lo(CadenaCRC);  // выделяем младший байт CRC
   Cadena[high(Cadena)]  := hi(CadenaCRC);  // выделяем старший байт CRC
   rg66.Text:= inttohex(lo(CadenaCRC),2);   // индикация для контроля-
   rg77.Text:= inttohex(hi(CadenaCRC),2);

   // проверка на правильность приема по CRC и индикация ошибки-
   err.Font.Color:= clsilver;
   if modbuf[1]<>4 then
    if (lo(CadenaCRC) <> modbuf[6])or(hi(CadenaCRC) <> modbuf[7]) then begin
     err.Font.Color:= clred; s:= s + ' ERROR' end;
   list.Lines.Add(s);
  end;
 end;

 // гасим индикатор-
 set_tn(0,1,'')
end;
Вопрос коллизий

Следует отметить, что для интерфейса RS-485 коллизия является нештатной ситуацией, что решается протоколом верхнего уровня, Modbus-ом например как у нас. Но если предполагается эксплуатация линии в тяжелых условиях и есть необходимость совместимости с шиной CAN, то есть стандарт J1708, оговариващий следующее схемное построение для защиты драйверов 485-го:



Схемотехника эта касается "прадедов" драйверов CAN-шины. Обусловлено тем, что драйвер 485-го в классическом включении тянет линии как при передаче нуля, так и единицы. Поэтому схема реорганизована на передачу нулем - TX посажен на землю, а передача идет на управляющий TXE (передает либо "ноль", либо передатчик отключен от линии). Резюки последовательные ограничивают ток в случае КЗ, подтягивающие для уравнивания напряжения в линии и автоформирования "1", а приемник драйвера постоянно включен и позволяет отслеживать во время передачи и себя и других (cтандарт J1708 оговаривает, что приемник всегда открыт - RE на общий). Емкости фильтрующие и посажены на изолированную землю (в стандарте обязательным является использование изолированной земли в шине).

Ресурсы
  1. Modicon Modbus Protocol. Reference Guide. PI-MBUS-300 Rev.J.
  2. Описание стандарта EIA485 (RS485).
  3. Правильная разводка сетей RS-485.
  4. RS-протоколы. Просто рассуждения о протоколах (тут ошибка по части 4-х проводного 485, см. оригинал Interface Circuits for TIA/EIA-485 Design Notes).
  5. Cкачать весь проект с моего SourceForge.
  6. 14 заповедей RS-485 и 5 реализаций Modbus CRC (в помощь разработчику)
  7. Удаленный промышленый терминал-индикатор по Modbus RTU
  8. Промышленный MODBUS/M-Link/OPC.WEB сервер-шлюз терминала оператора
  9. Libmodbus. Библиотека Modbus for Linux, Mac OS X, FreeBSD, QNX and Win32
  10. PascalScada. Библиотека для работы с ПЛК под Lazarus (Windows/Linux/FreeBSD)
  11. Библиотеки и классы протокола Modbus TCP/IP for C#/Delphi/Lazarus/PHP/Python
  12. Использование ПЛК в самогоноварении

1 комментарий:

  1. "Но как я понимаю 74ALS244A как раз и инвертирует?"
    - нет, это шинник (буферник, регистр) с возможностью перевода выходов в Z-состояние. Выполняет функцию токовой защиты выходов меги (и хотя допустимый ток через ее одиночный IO ограничен 40 мА, но суммарный ток через питающие ноги ограничен 200 мА), потому и стоит отдельный шинник. Для использования индикаторов с общим катодом потребуется коммутировать позицию индикаторов не плюсом, а общим питания, а управление сегментами a-h инвертировать программно.

    Вообще проекту уже лет 10 с лишним, он аппаратно и схемотехнически устарел. Если уж и брать мегу, то L, а шинник с бОльшим диапазоном питающих - SN74LVTH245A. Гальваническую развязку RS-485-го сейчас проще реализовать на драйвере ADM2582E (в ней и DC-DC встроен и развязка не опто, а по технологии оптокуплер - на микротрансах, что надежнее). По большому счету, сейчас проще и дешевле взять нано-буратину + пара сдвиговых регистров HC545 и 7-ми сегментник. Автопереключение драйвера RS-485 реализовать автоматом на дополнительном ключе по сигналу с зарядовой емкостью и освободить один порт МК.

    p.s.: попробовал зарегиться на вашем форуме, но требует подтверждения на белномер )

    ОтветитьУдалить