简体   繁体   English

ATTiny85 - 带 Timer1 的软件 UART

[英]ATTiny85 - Software UART with Timer1

So recently I tried to implement Software UART (TX only) for the ATTiny85.所以最近我尝试为 ATTiny85 实现软件 UART(仅限 TX)。 I want to drive it with the internal Timer1 .我想用内部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.每个 ISR 都会发送一位,直到没有任何东西可以发送并且中断将再次被禁用。

(Note: F_CPU=1000000 ; Fuses are the factory default (E:FF, H:DF, L:62) ) (注:F_CPU=1000000 ;保险丝为出厂默认值(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)使用此设置,接收器仅接收 0x00 或 0xFF,偶尔还会收到其他一些垃圾(取决于波特率)

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.令人惊讶的是,这两个函数都按预期工作,所以我想我在搞砸 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.但总的来说,使用 Timer1 时信号似乎有点慢。 The setup should interrupt every 1664µs with:设置应该每 1664µs 中断一次:

  • Baud = 600Hz波特率 = 600Hz
  • CK = 1MHz CK = 1MHz
  • Timer1DIV = 8定时器 1DIV = 8
  • Timer1CK = CK/Timer1DIV = 125kHz Timer1CK = CK/Timer1DIV = 125kHz
  • OCR1A = Timer1CK/Baud = 208 OCR1A = Timer1CK/波特率 = 208
  • delay = (Timer1DIV * OCR1A)/CK = (8*208)/1MHz = 1664µs延迟 = (Timer1DIV * OCR1A)/CK = (8*208)/1MHz = 1664µs

Can anyone tell, why the interrupt approach isn't working as expected?谁能告诉,为什么中断方法没有按预期工作?


Some more info:更多信息:

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 ...在 CTC 模式下,该定时器在达到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).您似乎没有在代码中设置OCR1C ,因此计时器的周期将基于OCR1C发生的任何事情(通电时为0 )。

This is an easy thing to miss ( OCR1C vs OCR1A )!这是一件容易错过的事情( OCR1COCR1A )!

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!如果是这样,请报告 - 如果不是,我们将继续寻找!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM