//======================================================
//   (c)2008-9 Matthew Ford
//   Forward Computing and Control Pty. Ltd.
//   www.forward.com.au
//   All rights reserved
//
//   You may freely copy and distribute this program for 
//           commercial and non-commercial use provided:-
//   i) this copyright notice remains attached
//   ii) you clearly mark any changes you make to the code
//======================================================
//
// The constants N and C determine the data rate. C selects clock frequency
// the following expression yields the data rate:
//
//            Clk_freq
//     BAUD =  ------     x 1/N 
//             C        
//  
//
// Absolute minimum value for N*C is about 10 (which causes the 
// timer to interrupt to be set again before the interrupt is finished).
//  Absolute maximum is 170.
// (Caused by the 1.5bit-lenght that is necessary to receive bits correctly.)
//
// The RS232 uses SERIAL_OUT_PIN as the transmit pin 
// The receive pin is  SERIAL_IN_PIN 
// You need to adjust the other .equ to match the pins you choose.
//
//
// Since this RS232 module is half duplex, it can either send or recieve data.
// It can't do both simoutaneausly.
//
// To use first call RS232_INIT. (with interrupts disabled)
// Then either call RS232_SET_TO_RECEIVE to wait for a character to arrive
// or RS232_TRANSMIT to transmit the bytes to have previously stored in the
// RS232_BUFFER_?? To transmit you also need to set the RS232_SEND_COUNT
// which is the number of bytes to be send from 1 to RS232_MAX_SEND_BUFFER
//
// Mod 1 -- added cli to RS232_SET_TO_RECEIVE and RS232_TRANSMIT
// Mod 2 -- Corrected test at start of PIN_CHANGE_INT, Corrected number of Globals, corrected minor comments, added more defines 
//
//
//*************************************************************************
//
.include "tn84def.inc"

// set the uC clock calibration
// I have allowed for two steps incase you need to move the default more the 0x20
// my default was 0x9E so only one step was needed
// in this case just set step 2 to equal step 1
.equ OSC_CAL_STEP_1 = 0xA5  // first adjustment from the default
.equ OSC_CAL_STEP_2 = 0xA5  // second adjustment from the default.

// 9600 at 8MHz 
.equ    N=103    // == ((8000000/9600) / C) - 1   
.equ    C=8      //    counter prescaler == Divisor
                 // ((0<<CS02)|(1<<CS01)|(0<<CS00))  for divide by 8
                 // the -1 in the above equation is because the counter
                 // counts from 0 to N inclusive (i.e. N+1 counts)

// *********** SERIAL OUTPUT DEFINES
.equ SERIAL_OUT_PIN = PA7 // define name for rs232 output
 // set these to match SERIAL_OUT_PIN
.equ SERIAL_OUT_PORT = PORTA  
.equ SERIAL_OUT_DDR = DDRA

// ************ SERIAL INPUT DEFINES
.equ SERIAL_IN_PIN = PB2  // define name for rs232 input
// set these to match SERIAL_IN_PIN
.equ SERIAL_IN_PORT = PORTB 
.equ SERIAL_IN_PORTIN = PINB
.equ SERIAL_IN_DDR = DDRB
.equ SERIAL_IN_GROUP_INT = PCIE1 // Group enable interrupt for SERIAL_IN_PIN (PCINT10)
.equ SERIAL_IN_PIN_INT = PCINT10 // pin interrupt to PB2
.equ SERIAL_IN_INTERRUPT_MASK = PCMSK1 // where serial_in_pin_int is set
.equ SERIAL_IN_GROUP_INT_FLAG = PCIF1 
// flag set if any of enabled pin in PCINT8-11 changes level 
// PIN_CHANGE_INT has to be set appropiate interrupt addres either PCI1addr or PCI0addr
// in this case it is PCI1addr see //  INTERRUPT VECTORS below



// sets the maximum number of bytes that can sent at one time
.equ RS232_MAX_SEND_BUFFER = 20

.equ NO_GLOBAL_VARS = 30 // number of globals available
// this MUST be greater then the number of globals defined below
// otherwise the stack will be overridded by the globals

.equ RS232_STATUS = RAMEND-0 // the STATUS of the RS232
//**** RS232_STATUS bits
   .equ RS232_STATUS_NEXT_BIT = 0 // the next bit to send or the bit just received
   .equ RS232_STATUS_TX_COMPLETE = 3 // trigger, set if have completed send, cleared by RS232_TRANSMIT and RS232_INIT and RS232_SET_TO_RECEIVE
   .equ RS232_STATUS_SET_TO_RECEIVE = 4 // set if SET_TO_RECEIVE has been called and have not started to receive a byte, cleared by call to RS232_TRANSMIT and RS232_INIT
   .equ RS232_STATUS_DATA_RECEIVED=5    // trigger, set when data byte received, cleared by call to RS232_SET_TO_RECEIVE or RS232_INIT
   .equ RS232_STATUS_SEND=6    // Sending data ==1, receiving == 0  only valid when RS232_STATUS_BUSY == 1
   .equ RS232_STATUS_BUSY=7    // 1== busy sending or receiving see RS232_STATUS_SEND to find out which
//**** END RS232_STATUS bits
.equ RS232_BIT_COUNT = RAMEND-1 // the number of bits sent/recieved on the RS232
.equ RS232_RECEIVE_BYTE = RAMEND-2 // byte being received
.equ RS232_SEND_COUNT = RAMEND-3 // the number of bytes to send from RS232_BUFFER_??
.equ RS232_SEND_BYTE = RAMEND-4 // the byte being sent
.equ RS232_BUFFER_PTR_HIGH = RAMEND-5  // holds the pointer to the send buffer high
.equ RS232_BUFFER_PTR_LOW  = RAMEND-6  // holds the pointer to the send buffer low
// send buffer runs from RS232_SEND_BYTE -1 to RS232_SEND_BYTE - RS232_MAX_SEND_BUFFER 
.equ RS232_BUFFER_1 = RAMEND-7 // the first to send
.equ RS232_BUFFER_2 = RAMEND-8 // the second to send
.equ RS232_BUFFER_3 = RAMEND-9 
.equ RS232_BUFFER_4 = RAMEND-10 
.equ RS232_BUFFER_5 = RAMEND-11
.equ RS232_BUFFER_6 = RAMEND-12 
.equ RS232_BUFFER_7 = RAMEND-13 
.equ RS232_BUFFER_8 = RAMEND-14 
.equ RS232_BUFFER_9 = RAMEND-15 
.equ RS232_BUFFER_10 = RAMEND-16 
.equ RS232_BUFFER_11 = RAMEND-17 
.equ RS232_BUFFER_12 = RAMEND-18 
.equ RS232_BUFFER_13 = RAMEND-19 
.equ RS232_BUFFER_14 = RAMEND-20 
.equ RS232_BUFFER_15 = RAMEND-21 
.equ RS232_BUFFER_16 = RAMEND-22 
.equ RS232_BUFFER_17 = RAMEND-23 
.equ RS232_BUFFER_18 = RAMEND-24 
.equ RS232_BUFFER_19 = RAMEND-25 
.equ RS232_BUFFER_20 = RAMEND-26 // 20th byte to send
.equ GLOBAL_27 = RAMEND-27
.equ GLOBAL_28 = RAMEND-28
.equ GLOBAL_29 = RAMEND-29  // 30th global counting 0 


.def Temp2 = r15 // temp reg
.def Temp = r16// Temporary register

// start interrupt routine
.macro SAVE_SREG
   push Temp
   in Temp, SREG    // save the SREG
   push Temp
.ENDMACRO

// end interrupt routine
.macro RESTORE_SREG
   pop Temp
   out SREG, Temp    // restore the SREG
   pop Temp
.ENDMACRO

//*************************************************************************
//
//    PROGRAM START - EXECUTION STARTS HERE
//
//*************************************************************************
.cseg
//  INTERRUPT VECTORS 
.org 0x0000
   rjmp RESET
.org PCI1addr  // pin change interrupt 1 for PCINT10 
   rjmp  PIN_CHANGE_INT
.org OC0Aaddr   // counter time 0 Compare A match
   rjmp TIMER0_CMP_A_INT


//**************************************************************************
// PIN_CHANGE_INT 
//
// Called when level changes on SERIAL_IN_PIN, can be a change up or down
// so need to check at the top of this call.
// Looking for start bit ==> low -> high on pin input (ie 1 -> 0) idle is 1
// So check for port input == 1 at top and just return if not.
// Otherwise start the timer to sample in 1.5 bits
// Disable this pin interrupt and clear the interrupt flag
//
// RS232_STATUS_SET_TO_RECEIVE is cleared by this routine
// RS232_STATUS_BUSY is set by this routine
// RS232_STATUS_SEND is cleared by this routine
// 
// When receive completes RS232_STATUS_DATA_RECEIVED will be set
// Clear this flag when you have handled the data
//
//*************************************************************************
PIN_CHANGE_INT:
    SAVE_SREG  // save Temp and SREG

    // see if pin low or high
    sbic SERIAL_IN_PORTIN, SERIAL_IN_PIN   // if SERIAL_IN_PIN low == 1 (set)  just return 
    rjmp END_PIN_CHANGE_INT
    // else want to get the timer started as close as possible
    // to the leading edge on the input

    // start it  with divide by 8
    // this assumes that OCR0A is set to some resonably high value
    // and that timer0 is cleared, RS232_INIT and RS232_STOP should do this
    in Temp, TCCR0B
    sbr Temp, (1<<CS01)  // CS02==0,CS01=1,CS00=0
    out TCCR0B , Temp   // clock select /8 == 1mhZ
    // OK now counting
   
    // set the upper
    ldi Temp,((N+N/2)-(33/C))
    //Set timer reload-value to 1.5 time the bit length
    // and subtract the clocks used so far divided by 
    // the time clock divisor
    // the 1.5 is so that the fisrt sample is 
    // in the middle of the first bit after the start bit
    out OCR0A , TEMP  // set top counter

    in Temp, TIMSK0
    sbr Temp, (1<<OCIE0A)
    out TIMSK0, Temp      // enable COMPARE A overflow interrupt
    in  Temp, TIFR0
    sbr Temp, (1<<OCIE0A)
    out    TIFR0,Temp     // to clear COMPARE A overflow flag

    // the following checks could be added
    // note if already busy here then still receiving/sending !! error should not happen
    // if already receiving here then have not read previous byte received PROCESSING TOO SLOW
    ldi Temp, (1<<RS232_STATUS_BUSY) // set the busy flag and clear the rest
    // clears RS232_STATUS_SET_TO_RECEIVE as have disabled pin interrupt in this interrupt
    sts RS232_STATUS, Temp  // save the status byte

    clr Temp
    sts RS232_BIT_COUNT, Temp  // 0 is first bit which is start bit which is skipped
    // so have already processed it here

    in Temp, SERIAL_IN_INTERRUPT_MASK 
    cbr Temp, (1<<SERIAL_IN_PIN_INT) // PCINT10 pb2 disable pin chage interrupt
    out SERIAL_IN_INTERRUPT_MASK, temp 

    in Temp, GIFR
    sbr Temp,(1<<SERIAL_IN_GROUP_INT_FLAG) 
    out GIFR,Temp ; clear pending pin chage 1 int. 

END_PIN_CHANGE_INT:
    RESTORE_SREG  // restore Temp and SREG and reti
reti              // return from interrupt


//*************************************************************************
//
// TIMER0_CMP_A_INT
// 
// This interrupt is called when the timer reaches its Compare A value
// The time then restarts from 0 automatically while these statements are
// being executed so it stays in time.
//
// If sending i.e. RS232_STATUS_SEND set
// set the output pin based on the RS232_STATUS_NEXT_BIT flag
// if RS232BitCount is 9 this is the 9th bit output the stop bit
// if RS232BitCount is 10 then next byte to send or stop if no more
// otherwise set the RS232_STATUS_NEXT_BIT based on bit 0 of the RS232_SEND_BYTE
// and shift the bit out.
// When transmit complete set RS232_STATUS_TX_COMPLETE
// and clear RS232_STATUS_SET_TO_RECEIVE, RS232_STATUS_BUSY and RS232_STATUS_SEND
//
// If receiving 
// sample the pin level and set the carry flag and save the SREG
// because the next comparison will update the carry flag.
// if this is the 9th bit, stop
// otherwise restore SREG and the carry and rotate into the RS232_RECEIVE_BYTE
// When 9th bit (the stop bit is sampled)
// set RS232_STATUS_DATA_RECEIVED
// and clear RS232_STATUS_SET_TO_RECEIVE, RS232_STATUS_BUSY and RS232_STATUS_SEND 
// 
// 
//*************************************************************************
// These two registers are used locally in this interrupt
// They are saved and restored
// Temp r16 also used and restored
.def RS232StatusReg = r17  // used in interrupt saved and restored
.def RS232BitCount = r18   // used in interrupt saved and restored

TIMER0_CMP_A_INT:
    SAVE_SREG  // save Temp and SREG

    push RS232StatusReg
    push RS232BitCount

    lds RS232StatusReg, RS232_STATUS
    lds RS232BitCount, RS232_BIT_COUNT
    inc RS232BitCount  // sending/receiving next bit 

    // are we sending or receiving
    sbrs RS232StatusReg, RS232_STATUS_SEND
    rjmp TIMER0_CMP_A_RECEIVE  // jump if RS232_STATUS_SEND clear
    // else Sending

TIMER0_CMP_A_SEND:
    // start bit was set in transmit call
    // so here can send 
    // set next output bit from status RS232_STATUS_NEXT_BIT 
    sbrc RS232StatusReg, RS232_STATUS_NEXT_BIT    // if RS232_STATUS_NEXT_BIT is 1
    sbi  SERIAL_OUT_PORT,SERIAL_OUT_PIN        //   Set transmit to 1
    sbrs RS232StatusReg, RS232_STATUS_NEXT_BIT    // if RS232_STATUS_NEXT_BIT is 0
    cbi  SERIAL_OUT_PORT,SERIAL_OUT_PIN        //  Set transmit to 0

    // counter == number of bits sent
    // if counter == 9 then send stop bit
    // if counter > 9 stop
    // else set up to send next bit
    cpi RS232BitCount, 9
    breq TIMER0_CMP_A_STOP_BIT // == 9 send stop bit
    brsh TIMER0_CMP_A_STOP_SEND  // > 9 stop

    // else set next bit to next bit in byte
    // the code below takes the same time for either 0 or 1 next bit
    lds Temp, RS232_SEND_BYTE
    sbrc Temp,0
    sbr RS232StatusReg,(1<<RS232_STATUS_NEXT_BIT)
    sbrs Temp,0
    cbr RS232StatusReg,(1<<RS232_STATUS_NEXT_BIT)
    lsr Temp  // shift this bit out of byte
    sts RS232_SEND_BYTE, Temp
    rjmp END_TIMER0_CMP_A    //  jump to exit

TIMER0_CMP_A_STOP_BIT:
    // set next bit to 1
    sbr RS232StatusReg,(1<<RS232_STATUS_NEXT_BIT)
    rjmp END_TIMER0_CMP_A    //  jump to exit

TIMER0_CMP_A_STOP_SEND:
    // check if there is another byte to send
    lds Temp, RS232_SEND_COUNT
    dec Temp;
    sts RS232_SEND_COUNT, Temp

    breq TIMER0_CMP_A_SEND_END // zero so finished
    // else next byte      
    // clear next bit start bit
    andi RS232StatusReg, ~(1<<RS232_STATUS_NEXT_BIT)
    clr RS232BitCount

    // set up the byte to be sent
	// using ZH, ZL locally here so save and restore
    push ZH  // save register values
    push ZL
    lds ZH, RS232_BUFFER_PTR_HIGH
    lds ZL, RS232_BUFFER_PTR_LOW
    ld Temp, -Z // decrement before loading
    sts RS232_SEND_BYTE,Temp
    sts RS232_BUFFER_PTR_LOW, ZL
    sts RS232_BUFFER_PTR_HIGH, ZH
    pop ZL  // restore them
    pop ZH
    rjmp END_TIMER0_CMP_A    //  jump to exit

TIMER0_CMP_A_RECEIVE:
    sec            // Set carry
    sbis SERIAL_IN_PORTIN,SERIAL_IN_PIN    // <=== SAMPLE HERE
    clc        //  clear carry
    in Temp, SREG  // save this bit

    cpi RS232BitCount,9    //if RS232BitCount < 9 (must sample stop-bit)
    brlo TIMER0_CMP_A_RECEIVE_SAVE_BIT    // more bits to come

    // else finished do not save stop bit just set data received flag
    sbr RS232StatusReg,(1<<RS232_STATUS_DATA_RECEIVED)  // data receieved
    // leave receiving flag set until byte buffer read
    rjmp TIMER0_CMP_A_STOP  // stop timer and clear interrupts

TIMER0_CMP_A_RECEIVE_SAVE_BIT:
    out SREG,Temp
    lds Temp, RS232_RECEIVE_BYTE
    ror Temp    // rotate carry into data, right most bit goes to carry
    sts RS232_RECEIVE_BYTE, Temp
    rjmp END_TIMER0_CMP_A

TIMER0_CMP_A_SEND_END:
    sbr  RS232StatusReg, (1<<RS232_STATUS_TX_COMPLETE) // set trigger
    // rjmp TIMER0_CMP_A_STOP drop through

TIMER0_CMP_A_STOP:
    rcall RS232_STOP 
    cbr RS232StatusReg,(1<<RS232_STATUS_SET_TO_RECEIVE)|(1<<RS232_STATUS_BUSY)|(1<<RS232_STATUS_SEND)
    // not busy or sending or set to recieve
    // rjmp END_TIMER0_CMP_A drop through

END_TIMER0_CMP_A:
    ldi Temp, N
    out OCR0A, Temp  // set top compare for one byte time

    sts RS232_BIT_COUNT, RS232BitCount
    sts RS232_STATUS, RS232StatusReg  // save the current status

    pop RS232BitCount
    pop RS232StatusReg
    RESTORE_SREG  // restore Temp and SREG and reti
reti              // return from interrupt

//*****************************************************************
//
// RS232_SET_TO_RECEIVE
//
// If RS232_STATUS_BUSY or already RS232_SET_TO_RECEIVE then
// just return as nothing to do, either already receiving or sending
// and send has precedence, or have already set to receive
// Otherwise enable the pin change interrupt and clear the flag
// Set RS232_STATUS_SET_TO_RECEIVE, this will be cleared when start bit detected
// or when a transmit is started.
// Clear RS232_STATUS_TX_COMPLETE and RS232_STATUS_BUSY and RS232_STATUS_DATA_RECEIVED
// 
// Only Temp is used
// It is saved and restored here
//
//*****************************************************************
RS232_SET_TO_RECEIVE:
    SAVE_SREG  // save Temp and SREG
    cli // prevent interrupt here
	    
    lds Temp, RS232_STATUS
    sbrc Temp, RS232_STATUS_BUSY 
    rjmp END_RS232_SET_TO_RECEIVE // if still busy skip this
    // either still sending or receiving a char
    sbrc Temp, RS232_STATUS_SET_TO_RECEIVE // have already called this so just return
    // prevents clearing of pending pin change below
    rjmp END_RS232_SET_TO_RECEIVE // if already called skip this

    rcall RS232_STOP

    in Temp, SERIAL_IN_INTERRUPT_MASK // pin change mask reg 
    sbr Temp, 1<<SERIAL_IN_PIN_INT // pb2 enble pin chage interrupt
    out SERIAL_IN_INTERRUPT_MASK, Temp 

    in Temp, GIFR
    sbr Temp,1<<SERIAL_IN_GROUP_INT_FLAG 
    out GIFR,Temp ; clear pending pin change int. 

    // clear trigger and set to receive
    // set to receive clears all other status bits
    // including RS232_STATUS_TX_COMPLETE  trigger if set
    // and RS232_STATUS_DATA_RECEIVED and RS232_STATUS_BUSY
    ldi Temp, (1<<RS232_STATUS_SET_TO_RECEIVE)
    sts RS232_STATUS, Temp // clear RS232Status
END_RS232_SET_TO_RECEIVE:
    RESTORE_SREG  // restore Temp and SREG and global interrupt flag
ret

//*********************************************************
//  RS232_STOP 
// Should only be called inside interrupt disabled block
// This is a utility routine to disable the interrupts and
// stop the TIMER0 clock
//
// Only Temp is used
// It is assumed the caller has saved Temp
// prior to this call
//**********************************************************
RS232_STOP:
    sbi SERIAL_OUT_PORT,SERIAL_OUT_PIN        //   Set transmit to 1

    in Temp, SERIAL_IN_INTERRUPT_MASK 
    cbr Temp, (1<<SERIAL_IN_PIN_INT) // pb2 disable pin chage interrupt
    out SERIAL_IN_INTERRUPT_MASK, temp 

    in Temp, GIFR
    sbr Temp,(1<<SERIAL_IN_GROUP_INT_FLAG) 
    out GIFR,Temp ; clear pending pin chage 1 int. 

    // stop the clock
    in Temp, TCCR0B
    cbr Temp, (1<<CS02)|(1<<CS01)|(1<<CS00)
    out TCCR0B , Temp   // stop the clock

    // set CTC mode in the counter
    in Temp,TCCR0A
    sbr Temp, (1<<WGM01)
    cbr Temp, (1<<WGM00)
    out TCCR0A, Temp

    ldi Temp, N
    out OCR0A, Temp

    // clear the timer
    clr Temp 
    out    TCNT0,Temp    

    in    Temp, TIMSK0
    cbr Temp, (1<<OCIE0A) // disable Compare A interrupt on timer 0
    out    TIMSK0,Temp           
    
    in Temp, TIFR0
    sbr Temp, (1<<OCF0A) // clear Compare A interrupt flag on timer 0
    out TIFR0, Temp
ret

//*************************************************************************
// RS232_INIT 
// Initializes the RS232
// Should be called with interrupts disabled.
//
// Sets up input and output pin and timer and pin change interrupt group
// Need to call either 
// RS232_SET_TO_RECEIVE or 
// RS232_TRANSMIT
// to actually send or receive
//
// Saves and restores Temp register and SREG
//*************************************************************************
RS232_INIT:    
    SAVE_SREG
    // setup the output pin
    // leave other pins unchanged
    in Temp, SERIAL_OUT_PORT
    sbr Temp, (1<<SERIAL_OUT_PIN) // set pin high
    out SERIAL_OUT_PORT, Temp        
    in Temp, SERIAL_OUT_DDR
    sbr Temp, (1<<SERIAL_OUT_PIN) // set as output
    out SERIAL_OUT_DDR, Temp 

    // set the input pin
    // leave other pins unchanged
    in Temp, SERIAL_IN_PORT
    sbr Temp, (1<<SERIAL_IN_PIN) // set pull up  
    out SERIAL_IN_PORT,Temp
    in Temp, SERIAL_IN_DDR
    cbr Temp, (1<<SERIAL_IN_PIN) // clear to set as input 
    out SERIAL_IN_DDR,Temp

    rcall RS232_STOP  // setup the RS232 for next send or receive
    clr Temp // clear all flags and triggers 
    sts RS232_STATUS, Temp

    in Temp, GIMSK
    sbr Temp, (1<<SERIAL_IN_GROUP_INT)
    out GIMSK, Temp   // enable pin change interrup for this group if not already enabled

    in Temp, GIFR
    sbr Temp,1<<SERIAL_IN_GROUP_INT_FLAG 
    out GIFR,Temp ; clear pending pin change int. 

    clr Temp
    sts RS232_STATUS, Temp // clear RS232Status
    RESTORE_SREG
ret

//*************************************************************************
// RS232_TRANSMIT 
//  
// Assumes RS232_BUFFER_1 to n globals have already set up.
// and that RS232_SEND_COUNT contains the number of bytes to send.
// Load the first byte into RS232_SEND_BYTE
// Set up the buffer pointers to point to the first byte
// Clear RS232_STATUS_NEXT_BIT for the first start bit
// Clear RS232_STATUS_SET_TO_RECEIVE and RS232_STATUS_TX_COMPLETE
// Set RS232_STATUS_SEND and RS232_STATUS_BUSY
// Load half a bit time into the timer to clear the last half bit of any
// previous receive (for the other ends benifit)
// and enable the timer interrupt and start the timer.
//
// Transmit takes precedence over receive
// if receiving now then receiving will be abandoned and transmit will start
//
// Always send at least one byte and at most RS232_MAX_SEND_BUFFER bytes
//
// All registers (including Temp) preserved by this call
//
//*************************************************************************
RS232_TRANSMIT:
    // Temp holds byte to transmit
    SAVE_SREG
    cli // prevent interrupt here 

    lds Temp, RS232_SEND_COUNT
    // make sure tx count is within range of 1 to max send buffer
    cpi Temp, 1
    brsh RS232_TX_ONE
    ldi Temp, 1 // send one 
RS232_TX_ONE:
    cpi Temp, RS232_MAX_SEND_BUFFER+1
    brlo RS232_TX_MAX
    ldi Temp, RS232_MAX_SEND_BUFFER

RS232_TX_MAX:
    sts RS232_SEND_COUNT, Temp

    rcall RS232_STOP  // stop receive if runnning

    clr Temp
    sts RS232_BIT_COUNT, Temp

    lds Temp, RS232_BUFFER_1 // load first byte to send
    sts RS232_SEND_BYTE, Temp

    // set up the buffer pointers 
    ldi Temp, low(RS232_BUFFER_1)
    sts RS232_BUFFER_PTR_LOW, Temp
    ldi Temp, high(RS232_BUFFER_1)
    sts RS232_BUFFER_PTR_HIGH, Temp

    // if transmitting then ignore receives
    lds Temp, RS232_STATUS
    cbr Temp, ( (1<<RS232_STATUS_SET_TO_RECEIVE)|(1<<RS232_STATUS_NEXT_BIT)|(1<<RS232_STATUS_TX_COMPLETE))
    // clear trigger 
    sbr Temp, (1<<RS232_STATUS_SEND)|(1<<RS232_STATUS_BUSY)
    // leave  RS232_STATUS_DATA_RECEIVED as it is if have received char
    // it has been saved in RS232_RECEIVE_BYTE
    sts RS232_STATUS, Temp

    ldi    Temp,N/2  //Set timer delay before transmitting start bit
    // set half bit delay here assumes sender is half-duplex also
    // and it needs to finish sending its stop bit.
    out OCR0A , TEMP  // set top counter

    in Temp, TIMSK0
    sbr Temp, (1<<OCIE0A)
    out TIMSK0, Temp           // enable COMPARE A overflow interrupt
    in  Temp, TIFR0
    sbr Temp, (1<<OCIE0A)
    out    TIFR0,Temp        // to clear COMPARE A overflow flag
        
    // start the clock
    // start it  with divide by 8
    // this assumes that OCR0A is set to some resonably high value
    // and that timer0 is cleared
    in Temp, TCCR0B
    sbr Temp, (1<<CS01)  // CS02==0,CS01=1,CS00=0
    out TCCR0B , Temp   // clock select /8 == 1mhZ
    // OK now counting

    RESTORE_SREG // restores interrupt setting as well
ret

//*************************************************************************
// CONVERT_TEMP_TO_HEX 
// NOTE: Temp(r16) and SREG are changed by this call.  Result is returned in temp
//  All other registers are maitained
//
// Replace Temp with HEX char equal to lower nibble
// Use swap before this call to get HEX char for upper nibble
//*************************************************************************
CONVERT_TEMP_TO_HEX:
    andi Temp, 0x0f // clear out top nibble
    subi Temp,-48 // add '0' to convert to ASCII
    cpi Temp, 58 // is it > '9' then should be A..F?
    brlo END_CONVERT_TEMP_TO_HEX // no not > '9'
    subi Temp,-7 ; add 7 for A to F
END_CONVERT_TEMP_TO_HEX:
ret

/******************************************/
// LOAD_SEND_BUFFER
// Must call this with interrupts disabled
// as EEPROM reads should not be interrupted
//
// Utility function to initialize the transit buffer
// with the chars
// Received 0x  crlf
// 
// uses r16,r17,r26,r27,r30,r31
/******************************************/
LOAD_SEND_BUFFER:
    ldi ZH, high(RS232_BUFFER_1+1) // add 1 due to -X in  ST -X,Temp below
    ldi ZL, low(RS232_BUFFER_1+1) // add 1 due to -X in  ST -X,Temp below

    ldi XH, high(example_data)
    ldi XL, low(example_data)     // Set pointer to EEPROM data
    ldi Temp, 15
    mov Temp2, Temp
LOAD_SEND_BUFFER_LOOP:
    sbic EECR,EEPE    //if EEPW not clear
    rjmp LOAD_SEND_BUFFER_LOOP  // wait more

    out EEARH, XH
    out EEARL, XL   // Set the EEPROM's address
    sbi EECR,EERE   // Send the Read strobe
    in  Temp,EEDR   // Put data in temp register
    st -Z, Temp
    adiw XH:XL, 1 //inc eeprom address
    dec Temp2
    brne LOAD_SEND_BUFFER_LOOP
ret

//*************************************************************************
//
//    Test/Example Program
//
// An example program to test of the RS232 code
// If the 'a' key is pressed on the PC keyboard,the message
// Received 0x61
// will be sent back and displayed.
//
//*************************************************************************
RESET:
    // disable interrupts
    // set clock to 8Mhz i.e. remove div8 that is set by programmer
    ldi Temp, (1<<CLKPCE)
    out CLKPR, Temp  // enable clock change for next 4 cycles
    clr Temp
    out CLKPR,Temp  // set 8Mhz clock

    // ******** Stack Pointer Setup Code ********
    ldi Temp,high(RAMEND-(NO_GLOBAL_VARS+1)) ; Stack Pointer Setup
    out SPH,Temp ; Stack Pointer High Byte
    ldi Temp,low(RAMEND-(NO_GLOBAL_VARS+1)) ; Stack Pointer Setup
    out SPL,Temp ; Stack Pointer Low Byte

    // adjust the clock frequency   
    // for this chip
    //  default cal is 9E for 7.77Mhz, 0xA5 gives 8.04Mhz at about 26deg
    ldi Temp, OSC_CAL_STEP_1 //0xA5
    out OSCCAL, Temp
    ldi Temp, OSC_CAL_STEP_2 //0xA5 also in this case
    out OSCCAL, Temp

    // default port settings are all zeros i.e. input tri-state
    // start by clearing all pin interrupt masks
    clr Temp
    out PCMSK0,Temp
    out SERIAL_IN_INTERRUPT_MASK,Temp

    rcall LOAD_SEND_BUFFER  // setup the return message 15 chars long
    rcall RS232_INIT    // Initialize RS232 interrupts still disabled
    sei  // Enable interrupts

    rcall RS232_SET_TO_RECEIVE // sets to recieve 
MAIN_LOOP:
    rcall PROCESS_RS232
    rjmp MAIN_LOOP  // loop forever processing chars as they arrive

//--------------------------------------------------------
// PROCESS_RS232
// Called from main program to process character received
// RS232_STATUS_TX_COMPLETE is a trigger to say last TX 
//  has completed and ready to set to receive again
//  the call to RS232_SET_TO_RECEIVE clears this trigger
//  as does a call to RS232_TRANSMIT
// If have not just completed a transmit then check if 
// a new char is available, RS232_STATUS_DATA_RECEIVED
// if not just return
// Otherwise have new char, save it in Temp2 register
// clear the RS232_STATUS_DATA_RECEIVED to say we have taken the data
// convert the upper and lower nibbles to HEX and put in the spaces
// in the transmit buffers _12 and _13
// set the send count to 15 and call RS232_TRANSMIT
// to start the transmission and then return.
//  
//--------------------------------------------------------
PROCESS_RS232:
    lds Temp, RS232_STATUS
    sbrc Temp, RS232_STATUS_TX_COMPLETE
    rjmp PROCESS_RS232_SET_RECEIVE  // if just completed transmit then set to receive

    // else have not just completed transmit do we have another char to process
    sbrs Temp,RS232_STATUS_DATA_RECEIVED
    rjmp END_PROCESS_RS232  // no new char so just return
 
    // get the char to send back
    lds Temp2, RS232_RECEIVE_BYTE

    // clear RS232_STATUS_DATA_RECEIVED
    // disable of interrupts to prevent some other interrupt change the RS232_STATUS
    // after we load it and before we save it. If so then that change would be lost.
    // this is not strictly necessary here but would be if used in conjunction with
    // other interrupts
    cli 
    lds Temp, RS232_STATUS
    cbr Temp, (1<<RS232_STATUS_DATA_RECEIVED)
    sts RS232_STATUS, Temp
    sei

    // put the HEX value in the return buffer in the space provided
    // top nibble
    mov Temp,Temp2         // Put data in temp register
    swap Temp    
    rcall CONVERT_TEMP_TO_HEX
    sts RS232_BUFFER_12, Temp // save in space pos 12

    // bottom nibble
    mov Temp,Temp2         // Put data in temp register
    rcall CONVERT_TEMP_TO_HEX
    sts RS232_BUFFER_13, Temp // save in space pos 13

    ldi Temp,15     // send back 15 chars
    sts RS232_SEND_COUNT, Temp
    rcall RS232_TRANSMIT  // output the whole buffer also clears RS232_STATUS_DATA_RECEIVED
    rjmp END_PROCESS_RS232  // return to wait for next Completed trigger

PROCESS_RS232_SET_RECEIVE:
    rcall RS232_SET_TO_RECEIVE  // clears transmit trigger
    // rjmp END_PROCESS_RS232 drop through
 
END_PROCESS_RS232:
ret

//*************************************************************************
// This is the data that will be sent back when a character is recieved.
// The spaces, 12, 13 will be filled in with the HEX digits of the 
// the character received.
// This data is in EEPROM, could also have been stored in FLASH RAM
// If in RAM need to use LPM instructions to load it. 
//  See Atmel application note AVR108 on using LPM 
//*************************************************************************
.eseg
example_data: // Received 0x  crlf
     //  1  2  3   4   5   6   7   8  9  10  11  12 13 14 15  
     //  R  e  c   e   i   v   e   d     0   x         cr lf
    .db 82,101,99,101,105,118,101,100,32,48,120,32,32, 13,10    


