ATTiny85 - Software UART with Timer1

拜拜、爱过 提交于 2020-12-06 15:47:31

问题


So recently I tried to implement Software UART (TX only) for the ATTiny85. I want to drive it with the internal Timer1.

The timer shall interrupt with the frequency of the Baudrate. Every ISR one bit will be transmitted, until there is nothing left to send and the interrupt will be disabled again.

(Note: F_CPU=1000000 ; Fuses are the factory default (E:FF, H:DF, L:62) )

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>

#define SET(reg, pos) (reg |= 1<<(pos))
#define FLP(reg, pos) (reg ^= 1<<(pos))
#define CLR(reg, pos) (reg &= ~(1<<(pos)))
#define GET(reg, pos) (reg &  1<<(pos))

#define UART_TX_BIT PB1
#define UART_BAUDRATE 600

static volatile uint16_t txframe;

/* Timer1A interrupt at BAUDRATE */
ISR(TIM1_COMPA_vect)
{
        /* Write current bit */
        if(txframe & 1) SET(PORTB, UART_TX_BIT);
        else            CLR(PORTB, UART_TX_BIT);

        /*
         * If the 1 mark at the end of txframe is reached,
         * disable interrupts (stop transmitting)
         */
        if(txframe == 1) CLR(TIMSK, OCIE1A);

        txframe >>= 1;
}

static void uart_putc(const char c)
{
        /* Wait until pending txframe is transmitted */
        do {
                sei();
                __asm__ __volatile__ ("nop");
                cli();
        }while(txframe);

        /* MARK . STOP | DATA | START */
        txframe = (0b11<<9) | ((uint16_t) c<<1) | 0;

        /* Enable timer interrupt and clear flag */
        SET(TIMSK, OCIE1A);
        SET(TIFR, OCF1A);
        sei();
}

static void uart_init()
{
        uint8_t sreg = SREG;

        cli();

        /* Set timer1 (CK) to CTC with divisor of 8 */
        TCCR1 = _BV(CTC1) | _BV(CS12);

        /* Set BAUDRATE clock divisor */
        OCR1A = (uint8_t) ((uint32_t) (F_CPU/8)/UART_BAUDRATE)-1;

        /* Enable and pull TX Pin to HIGH */
        SET(DDRB, UART_TX_BIT);
        SET(PORTB, UART_TX_BIT);

        txframe = 0;

        SET(TIFR, OCF1A);
        sreg = SREG;
}

int main()
{
        uart_init();

        for(;;) {
                uart_putc('A');
                _delay_ms(2000);
        }
}

With this setup the reciever only recieves 0x00 or 0xFF and occasionally some other garbage (depending on the baudrate)

Eventually I tried to achieve the same thing without interrupts:

#define UART_FALLBACK_DELAY() _delay_us(1000000UL/UART_BAUDRATE)

static void uart_putc_fallback(uint8_t c)
{
        uint8_t sreg = SREG;
        cli();

        /* Start */
        CLR(PORTB, UART_TX_BIT);
        UART_FALLBACK_DELAY();
        /* Data */
        for(int i = 0; i < 8; i++, c>>=1) {
                if(c&1) SET(PORTB, UART_TX_BIT);
                else    CLR(PORTB, UART_TX_BIT);
                UART_FALLBACK_DELAY();
        }
        /* Stop */
        SET(PORTB, UART_TX_BIT);
        UART_FALLBACK_DELAY();

        SREG = sreg;
}

static void uart_putc_fallback2(const char c)
{
        uint8_t sreg = SREG;
        cli();

        txframe = (0b11<<9) | ((uint16_t) c<<1) | 0;

        while(txframe) {
                if(txframe & 1) SET(PORTB,UART_TX_BIT);
                else            CLR(PORTB,UART_TX_BIT);
                txframe >>= 1;
                UART_FALLBACK_DELAY();
        }

        SREG = sreg;
}

Surprisingly both of these functions work as expected, so I think I'm messing up something with Timer1. Sadly I do not own an Oscilloscope, so I can't check the signal by hand. But in general the signal seems to be a little to slow when using Timer1. The setup should interrupt every 1664µs with:

  • Baud = 600Hz
  • CK = 1MHz
  • Timer1DIV = 8
  • Timer1CK = CK/Timer1DIV = 125kHz
  • OCR1A = Timer1CK/Baud = 208
  • delay = (Timer1DIV * OCR1A)/CK = (8*208)/1MHz = 1664µs

Can anyone tell, why the interrupt approach isn't working as expected?


Some more info:

  • Vcc = ~4.52V (~25°C) Oscillator should be accurate enough
  • Timer1 Prescale, Table 12-5
  • Timer1 Overview
  • By default Timer1 uses CK (1MHz) instead of PCK (I already tried setting it to CK by hand)
  • Toolchain: avr-gcc

回答1:


I think I found a problem that could cause what you are seeing.

In CTC mode, this timer clears the counter after it reaches OCR1C...

You do not seem to be setting OCR1C in your code so the period of the timer will be based on whatever happens to be in OCR1C (which is 0 at power up).

This is an easy thing to miss (OCR1C vs OCR1A)!

I think you can make your code work by adding a line to set this register here...

    /* Set BAUDRATE clock divisor */
    uint8_t match = (uint8_t) ((uint32_t) (F_CPU/8)/UART_BAUDRATE)-1;

    OCR1A = match;    // Generate interrupt on match to drive ISR 
    OCR1C = match;    // Clear counter on match to define period

Report back if this is it- if not we will keep looking!



来源:https://stackoverflow.com/questions/60997804/attiny85-software-uart-with-timer1

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!