ATTiny85 - Программное обеспечение UART с таймером 1 - PullRequest
1 голос
/ 02 апреля 2020

Поэтому недавно я попытался внедрить Software UART (только TX) для ATTiny85. Я хочу управлять им с помощью внутреннего Timer1 .

Таймер должен прерываться с частотой передачи данных. Каждый бит ISR будет передаваться до тех пор, пока не останется ничего для отправки, и прерывание не будет снова отключено.

(Примечание: 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 и иногда какой-то другой мусор (в зависимости от скорости передачи)

В конце концов я попытался чтобы достичь того же самого без прерываний:

#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 мкс с помощью:

  • Бод = 600 Гц
  • CK = 1 МГц
  • Timer1DIV = 8
  • Timer1CK = CK / Timer1DIV = 125 кГц
  • OCR1A = таймер 1CK / бод = 208
  • задержка = (Timer1DIV * OCR1A) / CK = (8 * 208) / 1 МГц = 1664 мкс

Может кто угодно Скажите, почему подход прерывания не работает должным образом?


Дополнительная информация:

1 Ответ

0 голосов
/ 02 апреля 2020

Мне кажется, я обнаружил проблему, которая может вызвать то, что вы видите.

В режиме CT C этот таймер очищает счетчик после того, как он достигнет OCR1C ...

enter image description here

Похоже, вы не устанавливаете OCR1C в своем коде, поэтому период таймера будет зависеть от того, что происходит в OCR1C (что является 0 при включении).

Это легко упустить (OCR1C против OCR1A)!

Я думаю, вы можете заставить свой код работать, добавив строку для установки этого регистра здесь ...

    /* 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

Сообщите, если это так - если нет, мы продолжим искать!

...