Problem with Soft UART for attiny45

Thread Starter

allahjane

Joined Sep 19, 2012
75
I've been trying hard on this. I have two avr AT-Tiny45 ,these lack hardware Uart so I'm designing my own using Timers and their interrupt to switch a GPIO pin to H/L state as required by the transmission format and protocol.

So far I have made these two test programs, Using async mode with 1 stop bit and no parity

Transmitter code
Rich (BB code):
/*
 * tiny45Tx.c
 *
 
 */ 
#define F_CPU 1000000
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
unsigned short byteToSend,bit;
unsigned int timerCount=0;

short state=0,idle=0x00,transmitting=0xff,bitCount=0;


void setTimer(int on){
if(on){
TCNT0=0;
TIMSK|=(1<<OCIE0A);
TCCR0A|=(1<<WGM01);
OCR0A=25;
TCCR0B|=(1<<CS01)|(1<<CS00);
}
else{
TIMSK&=~(1<<OCIE0A);
TCCR0B&=~((1<<CS01)|(1<<CS00));
}
				}


void setupIO(){
DDRB=(1<<PB4)|(1<<PB0);
PORTB|=(1<<PB3);
state=idle;
PORTB|=(1<<PB4)|(1<<PB0);
_delay_ms(500);
PORTB&=~((1<<PB4)|(1<<PB0));
_delay_ms(500);

}

void sendByte(unsigned short dataByte)
{
if(state!=idle)
return;
else
{
bitCount=-1;
state=transmitting;
byteToSend=dataByte;
}

}

int main(void)
	{
		sei();
	setupIO();
	setTimer(1);
    while(1)
    {	
		sendByte(0x0A);
		
    }
}

ISR(TIMER0_COMPA_vect){
if(state==idle)
bit=1;
else{
		
	if(bitCount<8)
	{if(bitCount==-1)
		bit=0;
		else
	bit=(byteToSend<<bitCount)&0x80;
	}
	else{
	bit=1;
	state=idle;
	}
	bitCount++;
}
if(bit){
PORTB|=(1<<PB4); //PB4 being used as Uart pin
}
else{
PORTB&=~(1<<PB4);
}
}
Receiver code

Rich (BB code):
/*
 * tiny45Rx.c
 *
 */ 
#define F_CPU 1000000
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
unsigned short byteToSend,bit;
unsigned int timerCount=0;

short state=0,idle=0x00,transmitting=0xff,bitCount=0;
unsigned short receivedByte;
unsigned int byte;

void setTimer(int on){
if(on){
TCNT0=0;
TIMSK|=(1<<OCIE0A);
TCCR0A|=(1<<WGM01);
OCR0A=25;
TCCR0B|=(1<<CS01)|(1<<CS00);
}
else{
TIMSK&=~(1<<OCIE0A);
TCCR0B&=~((1<<CS01)|(1<<CS00));
}
				}


void setupIO(){
DDRB=(1<<PB4);
PORTB|=(1<<PB4);
_delay_ms(500);
PORTB&=~(1<<PB4);
_delay_ms(500);

}


int main(void)
	{
		sei();
	setupIO();
	setTimer(1);
    while(1)
    {	
		if(receivedByte==0x0A)
		PORTB|=(1<<PB4);
		else
		PORTB&=~(1<<PB4);
    }
}

ISR(TIMER0_COMPA_vect){
	byte=byte<<1;
	
	if(~PINB&(1<<PB2))
	byte|=1;
	
	if((byte&(0b10<<10))&&(byte&1))
	receivedByte=0x0A;
	else
	receivedByte=0x00;
	
}
But for some reason it doesn't work , the if(receivedByte==0x0A) section never executes!

Can you help correct it?
 

tshuck

Joined Oct 18, 2012
3,534
I would suggest limiting your possible sources of error to one device. Meaning, try transmitting to a device you know can receive a UART message and respond properly. That way, you can narrow down the error to a single device, get that one working, and fix the other device if necessary.

Preferably, transmit to a UART to USB converter and display your receipt using a terminal program (i.e. TeraTerm, PuTTY, Hyperterminal, etc.), that way, you can see what the device is outputting.
 

MrChips

Joined Oct 2, 2009
30,712
If I were writing a soft UART I would do it in assembler so that I have full control of instruction cycle times. I'm sure I've already done this for Atmel AVR.
 

Thread Starter

allahjane

Joined Sep 19, 2012
75
If I were writing a soft UART I would do it in assembler so that I have full control of instruction cycle times. I'm sure I've already done this for Atmel AVR.
Have you done it with 16 or 8 bit timers or by just wasting cycles to create delay?
 

John P

Joined Oct 14, 2008
2,025
I've done this quite a few times on PIC processors, where the receive function triggers an interrupt when the start bit is seen, then there are 8 successive timer interrupts to grab the data. I wouldn't dream of using a delay instead of a timer interrupt; that would tie up the processor for an outrageously long time.

Transmitting is easier, because all you need is a succession of timer interrupts. But if you need full-duplex communication, it's a much harder job, and most likely you'd need to keep the baud rate quite low.
 

Thread Starter

allahjane

Joined Sep 19, 2012
75
Maybe uses 10% of overall processing power of a 2 MIPS chip doing 9600 baud. It is reliable sending and receiving multibyte sequences, obviously as long as there aren't any other interrupts that take too long and mess the timing up.
can you please provide me some code snippets of your implementation :D
 

Markd77

Joined Sep 7, 2009
2,806
Not sure if it's much help to you - it's PIC assembler and not well commented - but here's the interrupt part anyway:
Rich (BB code):
    movlw 0xC5 
    movwf TMR0 
 
    incf txstatus, W            ;if txstatus=FF then recieve mode 
    btfss STATUS, Z 
    goto transmit 
 
 
receive 
    incf rxstatus, W 
    btfss STATUS, Z 
    goto rxstarted 
    btfsc PORTB, 1                ;test rx pin 
    goto endint 
;RECIEVE SECTION 
    clrf rxstatus                ;start of start bit reception 
    movlw 0x04                    ;4 counts after start bit detected 
    movwf rx3count                ;is closest to the centre of first data bit 
    clrf rxbyte 
    goto endint 
 
rxstarted                        ;data bits or stop bit 
    decf rx3count, F 
    btfss STATUS, Z 
    goto endint            ;probably not a good idea to transmit and recieve at the same time 
 
    movlw 0x03         
    movwf rx3count                     
 
    movlw 0x08            ;stop bit? 
    subwf rxstatus, W 
    btfsc STATUS, Z 
    goto rxstopbit 
 
    bcf STATUS, C        ;prepare for next data bit 
    rrf rxbyte, F 
    btfsc PORTB, 1        ;get current data bit and store 
    bsf rxbyte, 7 
 
    incf rxstatus, F 
    goto endint 
 
rxstopbit                    ;about halfway through stop bit is a  
    movf rxbyte, W            ;good time to start waiting for start bit 
    movwf rxbytebuffer        ;copy to use in main 
    movlw 0xff 
    movwf rxstatus 
    bsf flags, bytereceived 
    goto endint 
 
 
 
;TRANSMIT SECTION 
transmit 
    decf tx3count, F            ;bits start every 3 interrupts 
    btfss STATUS, Z 
    goto endint 
 
 
    movf PORTBtemp, W            ;putting this here means timing 
    movwf PORTB                    ;is always the same. PORTB temp was 
    movlw 0x03                    ;set up the last time through 
    movwf tx3count 
 
    decf txstatus, W            ;using 1 for start transmission 
    btfss STATUS, Z 
    goto notstartbit 
    bcf PORTBtemp, 0            ;start bit 
    incf txstatus, F 
    goto endint 
notstartbit 
    movf txstatus, W            ;if txstatus = 0 just waiting for 
    btfsc STATUS, Z                ;next transmission 
    goto endint 
     
 
    sublw d'10'                    ;10th bit is stop bit                     
    btfsc STATUS, Z 
    goto txstopbit     
 
    bsf PORTBtemp, 0 
    btfss txbyte, 0 
    bcf PORTBtemp, 0 
bcf STATUS, C 
    rrf txbyte, F 
    incf txstatus, F 
    goto endint 
 
txstopbit 
    bsf PORTBtemp, 0 
    clrf txstatus 
 
 
endint
 

Markd77

Joined Sep 7, 2009
2,806
Shouldn't be a problem to use C, but I don't use it so can't help with that part.
For something so simple that gets called about 30000 times a second, it might be worth considering assembler for the interrupt. Saving a few clock cycles each time gives the main program more time to work with.
 

THE_RB

Joined Feb 11, 2008
5,438
This serial TX code below uses C and makes perfect timing because any individual baud error is negated on the next timed baud. One constant (SER_BAUD) is used to set the baudrate;

Rich (BB code):
// a bit banged serial function, sends INVERTED serial data
// to PC serial port using just 2 resistors.

#define PIN_SER_OUT LATA.F3       // which pin for serial out (PORTA.F3)
#define SER_BAUD 51               // TMR1 (1Mhz/19200 baud) = 52
                                  // tested; works from 49 to 53, using 51

//---------------------------------------------------------
void send_serial_byte(unsigned char data)
{
  // this manually sends a serial byte out any PIC pin.
  // NOTE! serial is inverted to connect direct to PC serial port.
  // baud timing is done by using TMR1L and removing
  // timer error after each baud.
  unsigned char i;

  i=8;                            // 8 data bits to send

  PIN_SER_OUT = 1;                // make start bit
  TMR1L = (256 - SER_BAUD);       // load TMR1 value for first baud;
  while(TMR1L.F7);                // wait for baud

  while(i)                        // send 8 serial bits, LSB first
  {
    if(data.F0) PIN_SER_OUT = 0;  // invert and send data bit
    else    PIN_SER_OUT = 1;

    data = (data >> 1);           // rotate right to get next bit
    i--;
    TMR1L -= SER_BAUD;            // load corrected baud value
    while(TMR1L.F7);              // wait for baud
  }

  PIN_SER_OUT = 0;                // make stop bit
  TMR1L -= SER_BAUD;              // wait a couple of baud for safety
  while(TMR3L.F7);
  TMR1L -= SER_BAUD;
  while(TMR1L.F7);
}
//---------------------------------------------------------
That code assumes the 8bit timer TMR1L is running at 1MHz. You should be able to use any 8bit timer if you set the prescaler and set the SER_BAUD variable.

Also if you need to change to non-inverted data, change this;
if(data.F0) PIN_SER_OUT = 0; // invert and send data bit
else PIN_SER_OUT = 1;
to this;
if(!data.F0) PIN_SER_OUT = 0; // send data bit
else PIN_SER_OUT = 1;

This web page also has a serial RX function in C using the same self-correcting baud timing;
http://www.romanblack.com/bitbangserial.htm

Note! Interrupts are not needed or used. However you might want to use an "interrupt on pin change" to detect the start of a serial receive operation.
 

MrChips

Joined Oct 2, 2009
30,712
Here are some code snippets for a SW UART

Rich (BB code):
*********************************************************
*         Register Definitions
*********************************************************

A         EQU       16
B         EQU       17
C         EQU       18
timcnt    EQU       19
flags     EQU       20
DLO       EQU       21
DHI       EQU       22

;SCI registers
rxdata    EQU       23
txdata    EQU       24
txcnt     EQU       25

;index registers
Z         EQU       30
ZHI       EQU       31

*********************************************************
*         Software SCI
*********************************************************

;int0 (PB2) interrupt service
;rxdata start bit detected
int0      MOV       saveA A             ;save A
          IN        saveSR SREG         ;save SR
          CLR       rxdata
          OUT       GIMSK rxdata        ;disable int1 and int0
          LDI       A SEMIBAUD
          OUT       TCNT0 A
          LDI       A,$01
          OUT       TCCR0 A             ;start timer0
          OUT       SREG saveSR         ;restore SREG
          MOV       A saveA             ;restore A
          RETI

;timer0 overflow interrupt service
;timer0 is used by both tx and rx service
;only half-duplex operation is allowed
;only one of tx or rx must be active

;rxdata is accumulated @ 9600 baud
;flags RDA is set to 1 when start bit = 1 is shifted out
t0ovf     MOV       saveA A             ;save A
          IN        saveSR SREG         ;save SR
          SBRS      flags TXCLK
          JMP       rcvr
;transmitter is active
          DEC       txcnt
          BNE       t0ovf1
;end of transmission
          SBI       PORTB TD0
          CBI       PORTB TD
          CLR       A
          OUT       TCCR0 A
          CBR       flags TXmask
          JMP       t00
;send next bit
t0ovf1    SEC                 ;stop bit is 1
          ROR       txdata
          BCS       t0hi
          CBI       PORTB TD0
          SBI       PORTB TD
          JMP       t01
t0hi      SBI       PORTB TD0
          CBI       PORTB TD
          NOP
t01       LDI       A BAUD
          OUT       TCNT0 A
          JMP       t00

;receiver is active
;data received is inverted
;so that start bit = 1
;no checks on start or stop bits
;flags RDA bit is asserted during the middle of the MSB (parity bit)
rcvr      CLC
          SBIS      PINB RD   ;test RD input
          SEC
          ROR       rxdata
          BCS       rxdone    ;search for start bit = 1
          LDI       A BAUD
          OUT       TCNT0 A
          JMP       t00
;rxdata received
rxdone    COM       rxdata    ;now flip data
          CLR       A
          OUT       TCCR0 A   ;stop timer
          SBR       flags RDAmask
t00       MOV       A saveA             ;restore A
          OUT       SREG saveSR         ;restore status
          RETI

;put character
;enter with A = 8-bit char
;destroyed: A, txdata, txcnt
putc      TST       txcnt               ;wait for end of transmission
          BNE       putc
          MOV       txdata A
          LDI       txcnt 10
          SBR       flags TXmask        ;set tx clock flag
          CBI       PORTB TD0           ;start bit = 0
          SBI       PORTB TD            ;complementary start bit
          LDI       A BAUD
          OUT       TCNT0 A
          LDI       A $01
          OUT       TCCR0 A             ;start timer0
          RET
 
Top