xmodem.c By Zhi-Hern Loh

Home
Design
Results
A 2nd try
Schematics
Code
Files
Links
Contact

//xmodem.c
//Xmodem protocol                  
//#include "serial.h"           

/*
Xmodem protocol documentation
transfer sequence
 1.SOH byte
 2.packet number (1 byte)
 3.1's complement of packet number (1 byte)
 4.the packet (128 bytes)
 5.the checksum (1 byte)     

Reciever must
1.check packet number. 1st block has packet number 1.
if the packet number is wrong, send a CAN byte to cancel
transfer

2. add the packet number and the 1's complement to get
0xff, if not, cancel transfer(send CAN).

3.do CRC check, send NAK if failed

4.if nothing fails, add the recieved packet to the buffer
*/                
//Init_Xmodem intializes the Xmodem file transfer
void Init_Xmodem(void)
begin
unsigned int i;
//unsigned char tmpstr[BUF_SIZE];
//send a 'C'
//wait for reply (SOH byte)
//if no reply after 1 sec, send another 'C'
//wait for reply
//if no reply after 1 sec, send at least 2 CAN bytes
ENABLE_XRAM;
dataheadptr = BUF_START_ADD;
//need to verify that we can write to the sram location pointed by dataheadptr
/*
for (i=0;i<BUF_BYTE_SIZE;i++)
  begin
  dataheadptr[i]=(unsigned char) (i+1)%47;
  if (dataheadptr[i]!=(unsigned char) (i+1)%47)
    begin
    S_Printf("Error in SRAM, cannot write ");
    sprintf(tmpstr,"at addr %04x\n\r",&dataheadptr[i]);
    S_Print(tmpstr);
    end
  end       */     
//check other method
for (i=0;i<BUF_BYTE_SIZE;i++)
  begin
  *dataheadptr = (unsigned char) (i+1)%129;
  if (*dataheadptr!=(unsigned char) (i+1)%129)
    begin
    //S_Printf("Error in SRAM method 2, cannot write ");
    //sprintf(tmpstr,"at addr %04x\n\r",dataheadptr);
    //S_Print(tmpstr);
    lcd_gotoxy(0,1);lcd_putsf("SRAM err");
    end
  end                                 
dataheadptr = BUF_START_ADD;
datatailptr = BUF_START_ADD;
mp3ptr = datatailptr;
//S_Printf("SRAM validated\n\r");
//OCR1A = 15625U;//1 sec at CK 1024
//might be possible to cut down purge time to 10ms
//time for 1 packet(133bytes) at 115.2kbps
//1 CK = 6.4E-5s
OCR1A = 313;//20ms
TCNT1 = 0;//reset
//S_Printf("1 Second Timer set\n\r");
EOT_Number = 0;
Packet_Number = 0;
Byte_Number = 0;
retry=0;
end
   
//X modem start   
void X_Start(void)
begin           
unsigned char i;unsigned char tmpstr[BUF_SIZE];
i=0;
Init_Xmodem();
Byte_Number = 1; 
Packet_Number = 1;
sprintf(tmpstr,"dataheadptr %04x datatailptr %04x mp3ptr %04x\n\r",dataheadptr,datatailptr,mp3ptr);
S_Print(tmpstr);
sprintf(tmpstr,"START %04x END %04x\n\r",BUF_START_ADD, BUF_END_ADD);
S_Print(tmpstr);
S_Printf("Xmodem starts in 10 secs...\n\r");
delay_ms(10000U);   
USART1_RXCIDIS;//disable receive interrupt
SERIAL_MODE = Xmode;
lcd_clear();
while(!UDRE);
UDR1='C';
lcd_gotoxy(0,0);lcd_putsf("Playing...");                                            
end

void X_Start_Instant(void)
begin
Init_Xmodem(); 
Byte_Number = 1; 
Packet_Number = 1;
USART1_RXCIDIS;//disable receive interrupt
SERIAL_MODE = Xmode;
lcd_clear();
while(!UDRE);
UDR1='C';
lcd_gotoxy(0,0);lcd_putsf("Playing...");
end
                                    
//X_Activate_Purge() - only used by X_Receive
//Following function sets purge to 1
//so that X_Recieve will dump rx chars for 1 sec
void X_Activate_Purge(void)
begin                     
//debug
lcd_gotoxy(14,0);lcd_putsf("P!");
retry++;        
if (retry>100)
  begin//purged too many times
  lcd_gotoxy(0,0);lcd_putsf("Err,Retry>100  ");
  delay_ms(1000U);
  SERIAL_MODE=Nmode;
  USART1_RXCIEN;//turn on receive
  return;
  end        
//set TIM1 to count 1 sec 
//enable TIM1_COMPA
purge = 1;//set X_Recieve to dump chars
TCNT1 = 0;//init TIM1 value to 0
OCR1A = 15625U;//set to 1 sec at prescalar 1024(16Mhz)
//Timer 1, CTC match OCR1A set the following
//WGM13=0,WGM12=1,WGM11=0,WGM10=0
SEC_TIMER_ISR_ON;//turn on TIM1_COMPA ISR
SEC_COUNTER_ON;//turn on counter
//Timer1 will occur after 1 sec and clear purge
end

//Receive the current packet
/*Xmodem CRC packet format is as follows
Byte	Description
1	SOH, start of header
2	Packet number
3	~(Packet number)
4-131	Packet data(128 bytes)
132-133 16 bit CRC, 132(Hi-Byte)
*/ 

//write the current packet to the data buffer
#define BURST_SIZE 0x050  //0x0100 bit jerky //Send a burst of at most n bytes to mp3 player
#define LEAD_SIZE  BUF_BYTE_SIZE/2 //0x1000 old val, if buffer overtaking mp3ptr, give mp3ptr a lead
#define WAIT_PACKET 8 //4, wait for n packets before starting playback
void X_Write_Packet(void)
begin                                        
unsigned int i;
unsigned int j;
unsigned char tmpstr[16], play;
i=0;
while(i<PACKET_SIZE)
  begin
  
  //dataheadptr points to the 1st free address
  //verify
  /*
  if (*dataheadptr!=buf[i])
    begin
    lcd_gotoxy(0,1);
    lcd_putsf("X_Write_Pkt Err");
    end  
  
  lcd_gotoxy(0,1);
  sprintf(tmpstr,"h%04xm%04xt%04x",dataheadptr,mp3ptr,datatailptr);
  lcd_puts(tmpstr);
  */            
  *dataheadptr=buf[i];i++;
  ++dataheadptr;
  if (dataheadptr==BUF_END_ADD) dataheadptr=BUF_START_ADD;//else ++
  if (dataheadptr==mp3ptr)
    begin//headptr overtaking mp3 ptr
    for (j=0;j<LEAD_SIZE;j++)
      begin
      while (!DATA_REQ);
      STA013_Audio_Send(*mp3ptr);
      if (++mp3ptr==BUF_END_ADD) mp3ptr=BUF_START_ADD;//else ++
      end//for   
    end
  else//if headptr is distant from mp3ptr
  if (Packet_Number>WAIT_PACKET)
  for (j=0;j<BURST_SIZE;j++)
    begin//this is problematic
    while(!DATA_REQ);
    //if (DATA_REQ)
    //  begin
      STA013_Audio_Send(*mp3ptr);
      if (++mp3ptr==BUF_END_ADD) mp3ptr=BUF_START_ADD;//else ++
      if (mp3ptr==dataheadptr) j=BURST_SIZE;//can't overtake dataheadptr
    //  end
    //else// DATA_REQ not high
    //  j=BURST_SIZE;
    end

  end//while
end              
    
//Shows the contents of the data buffer
void X_Show_Buffer(void)               
begin
unsigned char *tmpdataptr, i, tmpstr[BUF_SIZE];
// dataheadptr stores the end address
// datatailptr stores the start address
tmpdataptr = datatailptr;
while (tmpdataptr!=dataheadptr)
  begin
  for (i=0;i<BUF_SIZE-1;i++)
    begin        
    if (tmpdataptr==dataheadptr) 
      begin
      tmpstr[i]=0;//null terminate
      break;
      end
    tmpstr[i]=*tmpdataptr;
    tmpdataptr++;
    if (tmpdataptr==BUF_END_ADD) tmpdataptr = BUF_START_ADD;
    end
  tmpstr[BUF_SIZE-1]=0;//null terminate
  S_Print(tmpstr);
  end
end

//This interrupt delays packet reception for the length
//of 1 packet when the reciever detects that the packet
//is invalid
//at the end of the packet, a NACK is sent
//so that the transmitter re-sends that packet
interrupt[TIM1_COMPA] void TIM1_1_sec(void)
begin            
if (purge==1) 
  begin
  purge=0;          
  Byte_Number=1;//receive the 1st byte again
  //send NACK
  while(!UDRE){};
  UDR1=NACK;
  end
SEC_COUNTER_OFF;//turn off the counter
SEC_TIMER_ISR_OFF;//turn off this interrupt
TCNT1=0;//reset counter value
end                 
   
//Polling
void X_Poll(void)
begin
unsigned char c,i;
//debug
unsigned char tmpstr[16];
unsigned int *tmpptr;
while(SERIAL_MODE==Xmode)
  begin
  if (RXC)//if a byte is waiting in the buffer
    begin//X_Receive();       
    //Copied here to speed up, no function call
    c = UDR1;//current byte sent
    //if purge is on, then dump all bytes
    //until TIM0 ovrfls (1 sec) and sets purge to 0.
    if (purge==0)//dump the byte if purge==1
    if (Byte_Number==1)
      begin//1st Byte should be SOH
      switch(c)
        begin
        case SOH:
           //have the start byte
           //lcd_gotoxy(0,1);lcd_putsf("SOH rx         ");
           CRC=0;
           Byte_Number++;
           goto End_X;
    	   break;
        case EOT:
          //send a NACK, and expect a EOT as the next byte
          //we do not increment Byte_Number because
          //we want to check if the next Byte is also a EOT
    	  if (EOT_Number==0) 
    	    begin//1st EOT byte
    	    //return a NACK
    	    EOT_Number=1;
    	    //send NACK
	    while(!UDRE);//wait for output buffer
	    UDR1=NACK;
    	    end
    	  else
    	    begin//2nd EOT byte
    	    //need to end transmission
    	    //1.send a ACK
    	    //turn off Xmodem transfer
    	    EOT_Number=2;//2nd EOT byte
    	    //X_Transmit will check EOT_Number
    	    //if EOT_number == 2, then it sends ACK
    	    //and turns off Xmodem transfer
    	    //by setting SERIAL_MODE = Nmode;
    	    while(!UDRE);
    	    UDR1=ACK;
    	    SERIAL_MODE = Nmode;
    	    USART1_RXCIEN;
    	    return;
    	    end
    	    goto End_X;
    	    break;
        default:
    	  //we have a invalid byte so we
          //need to purge the buffer
    	  //i.e. get the remaining bytes then send NACK
	      X_Activate_Purge();
	      Byte_Number = 1;
	      //X_Activate_Purge will send NACK after 1 sec
	      goto End_X;
          break;    
        end//switch(c)
    end//if Byte_Number==1                  
  if (Byte_Number==2)//should get the packet number
    begin
    if (c == Packet_Number)
      begin
      //lcd_gotoxy(0,1);lcd_putsf("Packet# ok    ");
      Byte_Number++;
      end
    else//failed to get the 2nd byte
      begin//invalid byte, purge packet
      //lcd_gotoxy(0,0);lcd_putsf("Byte2 error   ");
      //sprintf(tmpstr,"c=%02x p#%02x",c,Packet_Number);
      //lcd_gotoxy(0,1);lcd_puts(tmpstr);delay_ms(1000U);
      X_Activate_Purge();
      Byte_Number=1;
      end        
    //return;
    goto End_X;
    end//if Byte_Number==2
  if (Byte_Number==3)//expect the 1's complement of packet number
    begin
    if (c + Packet_Number==255)
      begin
      //lcd_gotoxy(0,1);lcd_putsf("~Packet# ok    ");
      Byte_Number++;
      end                                    
    else//invalid byte
      begin
      //sprintf(tmpstr,"Err,B3=%02x,P=%02x",c,Packet_Number);
      //lcd_gotoxy(0,1);lcd_puts(tmpstr);
      //delay_ms(2000U);
      X_Activate_Purge();
      end  
    //return;
    goto End_X;
    end//if Byte_Number==3
  if ((Byte_Number>=4U)&&(Byte_Number<(4U + PACKET_SIZE)))
    begin//receiving packet data
    //We have a valid byte
    //write to buffer and increment Byte_Number
    buf[Byte_Number-4] = c;      
    
    //add to the crc calculations
    CRC = CRC ^ (unsigned int) c << 8;
    for (i=0;i<8;i++)
      begin
      if (CRC&0x8000) 
        CRC = CRC << 1 ^ 0x1021;
      else
        CRC = CRC << 1;
      end
    //verify
    /*
    if (buf[Byte_Number-4]!=c)
      begin
      lcd_gotoxy(0,1);
      lcd_putsf("X_Rx WriteErr");
      delay_ms(1000U);
      end         
    */
    Byte_Number++;
    /*this only makes it worse
    //Play a byte if possible to reduce jerkyness
    if (Packet_Number>4)
    begin
    tmpptr = mp3ptr+1;
    if (tmpptr==BUF_END_ADD) tmpptr = BUF_START_ADD;
    if (tmpptr!=dataheadptr)
      begin//try to play a byte only if we have data available
      if (DATA_REQ)
        begin//STA requests a byte
        STA013_Audio_Send(*mp3ptr);
        mp3ptr=tmpptr;
        end
      end
    end//packet>4
    */
    
    //return;
    goto End_X;
  end// 4<=Byte_Number<=131
if (Byte_Number==(4U+PACKET_SIZE))//Hi-Byte of CRC
  begin         
  CRCword = ((unsigned int) c) << 8;
  //lcd_putsf("CRC check");this runs
  Byte_Number++;
  //return;
  goto End_X;
  end
if (Byte_Number==(5U+PACKET_SIZE))//Lo-Byte of CRC
  begin
  Byte_Number = 1;//reset byte number, packet ended
  CRCword = CRCword + c;
  //lcd_putsf("CRC check"); this runs
  //now check to see that the CRC is correct
  //if (CRCword==calcrc(buf,PACKET_SIZE))
  if (CRCword == CRC)
    begin//CRC correct, accept packet
    //1.write packet to data buffer
    //2.increment packet number
    //3.reset Byte_Number
    //4.ACK packet
    X_Write_Packet();//write to and playback from buffer
    //debounce buttons
    if (T3_Ovf)
      begin
      
      debounce();
      if (New_Butnum)
        begin
        switch(Current_Butnum)
          begin
          case Btn_Stop:
            //send 4 CAN (cancel bytes)
            //go back to SERIAL_MODE = Nmode
            while(!UDRE);
    	    UDR1=CAN;
    	    while(!UDRE);
    	    UDR1=CAN;  
    	    SERIAL_MODE = Nmode;
    	    lcd_gotoxy(0,0);lcd_putsf("Stopped");
    	    USART1_RXCIEN;
    	    return;
            break;
          case Btn_Vol_Up:
              	lcd_gotoxy(0,1);lcd_putsf("Vol up:0x");
              	if (vol_attn_l>0) vol_attn_l=vol_attn_l>>1;//vol_attn_l--;
              	if (vol_attn_r>0) vol_attn_r=vol_attn_r>>1;//vol_attn_r--;
              	STA013_Write(DLA, vol_attn_l);
              	sprintf(tmpstr,"%02x",~vol_attn_l);
              	lcd_puts(tmpstr);
              	//Writes to STA013 should have some time seperation
              	STA013_Write(DRA, vol_attn_r);
              	break;
          case Btn_Vol_Dn:
              	lcd_gotoxy(0,1);lcd_putsf("Vol dn:0x");
              	if (vol_attn_l<255) 
              	  vol_attn_l=(vol_attn_l<<1)+1;//vol_attn_l++;
              	if (vol_attn_r<255) 
              	  vol_attn_r=(vol_attn_r<<1)+1;//vol_attn_r++;
             	STA013_Write(DLA, vol_attn_l);
              	sprintf(tmpstr,"%02x",~vol_attn_l);
              	lcd_puts(tmpstr);
              	STA013_Write(DRA, vol_attn_r);
              	break;  
          default:
        	break;
          end//switch
        Clr_Butnum;
        end//if new_butnum
      T3_Ovf_Clr;//clear int flag
      //T3_Reset;//set TCNT3 = 0
      TCNT3H=0;TCNT3L=0;
      end//if t3ovf
    
    Packet_Number++;      
    //lcd_putsf("CRC ok");   
    //X_Transmit_State=SEND_ACK;
    //USART1_UDRIEN;
    while(!UDRE);
    UDR1=ACK;
    end
  else
    begin
    //CRC is wrong, need to resend packet
    //send NACK 
    //lcd_putsf("CRC err");delay_ms(1000U);           
    //X_Transmit_State = SEND_NACK;
    //USART1_UDRIEN;//turn on transmit interrupt 
    while(!UDRE);
    UDR1=NACK;
    end  
  //return;
  goto End_X;
  end//if Byte_Number = CRC Lo-Byte 

End_X://reiterate
  end//X_Receive
end//while(SERIAL_MODE==Xmode)
end

Home | Design | Results | A 2nd try | Schematics | Code | Files | Links | Contact

For problems or questions regarding this web contact Zhi-Hern Loh.
Last updated: 05/02/02.