where : ibrtses embedded

AVR Pascal (AVRCo) UART Interrupt

Despite AVRCo offering the UART as library function, without the source it sooner or later leads to problems, eg when longer buffers are considered, and is therefore to be avoided. Here, a replacement with source is provided. Contrary to the provided library UART, some more decision have to be made. Here, I found it convenient to have the whole protocol in the UART_Rx_Interrupt. The protocol is an RS422 master-slave protocol, that has to shut the sender off after the message being sent, and therefor allows for multiple devices on one bus.
; protocol :
;
; index   0   1   2   3    4   5
; [SYN]  STX LEN SRC DST  MSG [DATA] CRChi CRClo
;  0x16 0x02 ..  ..  ..   ..       
;
; LEN = lenght including STX ... CRCHi
;
; CRC:=crc(STX..data);
; CRC poly:=2^16+2^15+2^2+1

the main

In the main, the ADC is handled in the procedure COMLoop.

{ Type Declarations }

type
  ComBuffer = Array[0..50] of byte; //or whatever


VAR 
 ComState:byte;
 rxptr:byte;          // the pointers and presets
 txptr:byte;          // the pointers and presets
 rxlen:byte;          // the pointers and presets
 txlen:byte;          // the pointers and presets
 rxbyte:byte;         // temporary interrupt storage
 DeviceID:byte;       // from EEPROM
 tx_en[@portd,2]:bit; // enable the transceiver

// buffers
 RxBuf:ComBuffer;
 TxBuf:ComBuffer;

begin // the main
 ..
 SetupVariables;
 ..
 EnableInts;
 ..
loop
  COMLoop;
  ..
  ..
endloop;
end; // main
The statemachine being conscious about the state is the Var Comstate:byte, above.
; rx : COMState
;  state 0 := wait for STX
;  state 1 := get len
;  state 2 := count down
;  state F := got message
;
.EQU	STX	=0x02
.EQU	SYN	=0x16

The commandset and its excution

The concept of the protocol is that each command has a reply with the same command code. The content of the reply may be empty, in this case the command is just sent back with SRC and DST swapped.
//  0   1    2   3  4    5
//  STX LEN SRC DST MSG [Data] CRCHi CRCLo
//Len includes STX & CRC
procedure COMLoop; // does something when comstate = $0F
begin
 if (ComState=$0F) then
  RxCRCMessage(rxptr-2); // CRC is updated
  if (CRChigh=RxBuf[rxptr-2])and
     (CRCLow=RxBuf[rxptr-1]) then // crc ok.
   if (RxBuf[3]=DeviceID)or(RxBuf[3]=$FF) then
    src:=RxBuf[2];
    dst:=RxBuf[3];
    cmd:=RxBuf[4];
    case cmd of
     $00:// Echo
         CopyRxTx;
         AddCrc;
         SetupTx;|
     $01:// read measurements
         txlen:=measurebytes+9;
         SetupTxFrame;
         CopyBlock(@Pot,@TxBuf+6,word(MeasureBytes));
         AddCrc;
         SetupTx;|
     $02://read config
         txlen:=CurrentConfigSize+9;
         SetupTxFrame;
         CopyBlock(@ConfigSize,@TxBuf+6,word(currentconfigsize));
         AddCrc;
         SetupTx;|
     $03://read settings
         ErrorCodeb:=ErrorCode;
         txlen:=SettingBytes+8;
         SetupTxFrame;
         CopyBlock(@SetCurrentb,@TxBuf+6,word(SettingBytes));
         AddCrc;
         SetupTx;|
     $04://ramp current right
         rampright;
         CopyRxTx;
         AddCrc;
         SetupTx;|
     $05://ramp current left
         rampleft;
         CopyRxTx;
         AddCrc;
         SetupTx;|
     $06://read math
         txlen:=MathBytes+9;
         SetupTxFrame;
         CopyBlock(@DeltaValue,@TxBuf+6,word(MathBytes));  // no
         AddCrc;
         SetupTx;|
     $08://read calibration
         txlen:=CalibrationSize+8;
         SetupTxFrame;
         CopyBlock(@Calib1,@TxBuf+6,word(CalibrationSize));
         AddCrc;
         SetupTx;|
     $10://current zero
         setcurrentzero2612;
         CopyRxTx;
         AddCrc;
         SetupTx;|
     $11://write current left
         tb1:=RxBuf[5];
         tb2:=RxBuf[6];
         SetCurrent:=til;
         if SetCurrent<0 then SetCurrent:=-SetCurrent; endif;
         SetCurrentLeft;
         CopyRxTx;
         AddCrc;
         SetupTx;|
     ..............
     else

    Endcase;
   endif; // ID checked
  endif; // crc checked
  ComState:=0;     // SetupRx
  RxPtr:=0;
 endif;
end;

Messages and CRC

CRCs are used to verify the content of the messages and are handled here.
VAR
 CRC:Word;
 CRCLow[@CRC]:byte;
 CRCHigh[@CRC+1]:byte;

procedure CRCByte(b:byte); // crc for one byte
var i:byte;
begin
 for i:= 0 to 7 do
  if (((b and $01)XOR(CRClow and $01))<>0) then
   CRC:=CRC shr 1;
   CRC:=CRC XOR $A001;
  else
   CRC:=CRC shr 1;
  endif;
  b:=b shr 1;
 endfor;
end;

procedure RxCRCMessage(len:byte); // updates global CRC variable
var u:byte;
begin
 CRC:=0;
 for u:=0 to len-1 do
  CRCByte(RxBuf[u]);
 endfor;
end;

procedure TxCRCMessage(len:byte); // updates global CRC variable
var u:byte;
begin
 CRC:=0;
 for u:=1 to len do
  CRCByte(TxBuf[u]);
 endfor;
end;

procedure AddCrc; // CRC to the complete TxMessage, incl TxLen=#bytes
var i:byte;
begin
 i:=txlen-3; // not over SYN, CRC
 TxCRCMessage(i);     //
 TxBuf[txlen-2]:=CrcHigh;
 TxBuf[TxLen-1]:=CRCLow;
end;

procedure SetupTx;
begin
 txptr:=0;
 tx_en:=true;
 ASM;
  sbi UCSRB,5  ;UDRIE
 ENDASM;
end;

procedure CopyRxTx;
var i,j:byte;
begin
 j:=Rxbuf[1]+1;
 TxLen:=j;
 TxBuf[0]:=SYN;
 TxBuf[1]:=STX;
 TxBuf[2]:=j-1; //len
 TxBuf[3]:=DST;
 TxBuf[4]:=SRC;
 TxBuf[5]:=CMD;
 for i:=6 to j-1 do
  TxBuf[i]:=RxBuf[i-1];
  endfor;
end;

procedure SetupTxFrame; // txlen is set = #bytes to tx, incl SYN.
begin
 TxBuf[0]:=SYN;
 TxBuf[1]:=STX;
 TxBuf[2]:=txlen-1;
 Txbuf[3]:=DST;
 TxBuf[4]:=SRC;
 txBuf[5]:=CMD;
end;

The UART interrupt

The UART_Rx is handled with a statemachine incoporating the protocol, described above. The UART_Tx uses the UDRE interrupt to fetch the next byte until the is no more. Then the TxRdy interrupt is enable to await the last bit of the last byte being shifted out. Note that this approach tends to disable the transceiver before the stopbit of this last byte was sent. Since I never experienced problems, I was never forced to change it. A timer could be used await the stopbit being shifted.
Interrupt RxRdy;   // RxRdy of Serial 1
begin
 rxbyte:=UDR1;
 if (rxptr=buflen)then Comstate:=$0F;
 else
  case ComState of
   0: if (rxbyte=STX) then // wait for STX
       Comstate:=1;
       RxBuf[0]:=rxbyte;
       rxptr:=1;
       endif;
      if (rxbyte=SYN) then
       rxptr:=0;
      endif;|
   1: rxlen:=rxbyte;       // take len
      ComState:=2;
      RxBuf[1]:=rxbyte;
      inc(rxptr); |
   2: RxBuf[rxptr]:=rxbyte;
      inc(rxptr);
      if (rxptr=rxlen) then
       Comstate:=$0F;
      endif; |
   else Comstate:=$0F;
  endcase;
 endif; // buffer full
end;

interrupt  UDRE; // tx data register empty
begin
 if (txptr=txlen) then
  ASM;
   cbi UCSRB,5  ;UDRIE1
   sbi UCSRB,6  ;TXCIE1
  ENDASM;
 else
  UDR1:=TxBuf[txptr];
  inc(txptr);
 endif;
end;

interrupt TxRdy; // tx complete
begin
 tx_en:=false; //RS485 line deactivate
 ASM;
  cbi UCSRB,6  ;txcie1
  sbi UCSRA,6  ;txc1
 ENDASM;
end;

Setup the UART

Setting up the UART is straight forward. First the variables, then the registers. Enabling the interrupts happens later in the main.
procedure InitUART; begin CRC:=0; Comstate:=0; rxptr:=0; rxlen:=0; txptr:=0; txlen:=0; // uart UBRRL:=51; // 4800 baud @ 4MHz UBRRH:=0; UCSRC:=%10000110; // async, no parity, 1 stop, 8bit, UCSRB:=%10011000; // rx enable, rxie, tx enable, 8bits only UCSRA:=%00000000; // single speed, single processor end;



Questions ?
Suggestions?
Feedback ?






sponsored links




AVR
embedded
home



last updated : 30.aug.04, or perhaps later



Copyright (99,2004) Ing.Büro R.Tschaggelar