ATTiny85-带有Timer1的软件UART [英] ATTiny85 - Software UART with Timer1

查看:234
本文介绍了ATTiny85-带有Timer1的软件UART的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以最近我尝试为ATTiny85实现软件UART(仅TX).我想使用内部

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

计时器应以波特率的频率中断. ISR的每一位都将被发送,直到没有剩余可发送的内容为止,并且该中断将再次被禁止.

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.

(注意: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);
        }
}

使用此设置,接收方只能接收0x00或0xFF,偶尔还会收到其他一些垃圾(取决于波特率)

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;
}

令人惊讶的是,这两个功能均按预期工作,因此我认为我正在使用Timer1弄乱某些东西.遗憾的是,我没有示波器,因此无法手动检查信号.但是总的来说,使用Timer1时,信号似乎有点慢.设置应每隔1664µs中断一次:

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:

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

谁能告诉我,为什么中断方法不能按预期工作?

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

更多信息:

  • 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

推荐答案

我认为我发现了一个可能导致您看到的问题.

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

在CTC模式下,此计时器在达到OCR1C ...

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

您似乎未在代码中设置OCR1C,因此计时器的时间将基于OCR1C(上电时为0)中发生的任何情况.

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).

这是一件容易错过的事情(OCR1COCR1A)!

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!

这篇关于ATTiny85-带有Timer1的软件UART的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆