software serial не работает со скоростью > 4800 бит/с.

Я собрал простую программную последовательную реализацию (только для отправки) для использования на своих микроконтроллерах attiny8[45]. Я понимаю, что существуют более эффективные реализации на основе USI; в основном это было образовательное упражнение. Код использует TIMER0 в режиме CTC для синхронизации отправки битов на доступный выходной контакт.

Он отлично работает со скоростью от 300 до 4800 бит/с, но все, что выше этого значения, приводит к мусору.

Полный код находится внизу этого ответа. Следующие настройки работают правильно:

  1. 4800 бит/с

    #define BPS 4800
    #define SCALE_FLAG 1
    #define SCALE_VAL 1
    
  2. 2400 бит/с

    #define BPS 2400
    #define SCALE_FLAG 2
    #define SCALE_VAL 8
    
  3. 1200 бит/с

    #define BPS 1200
    #define SCALE_FLAG 2
    #define SCALE_VAL 8
    
  4. 300 бит/с

    #define BPS 300
    #define SCALE_FLAG 3
    #define SCALE_VAL 64
    

Пока это не помогло:

  1. 9600 бит/с

    #define BPS 9600
    #define SCALE_FLAG 1
    #define SCALE_VAL 1
    

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

Есть ли в этом коде явные ошибки? Я думаю, что это наиболее вероятное объяснение, но, покопавшись в этом пару дней, я его не увидел.


#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <string.h>

#define BPS 9600
#define SCALE_FLAG 1  // Это значение, которое хранится в регистре TCCR0B.
#define SCALE_VAL 1   // Это делитель, выбранный SCALE_FLAG

#define TXPORT PORTB
#define TXDDR DDRB
#define TXPIN PORTB0

#define TICKS_PER_BIT (F_CPU/BPS/SCALE_VAL)
#define mS_PER_BIT (1000000/BPS/1000)
#define uS_PER_BIT ((1000000 / BPS) - (1000 * mS_PER_BIT))

typedef struct SERIAL_PORT {
    uint8_t data;
    uint8_t index;
    uint8_t busy;
} SERIAL_PORT;

void millis_init();
uint32_t millis();

void serial_init();
void serial_putchar(char c);
void serial_print(char *s);
void serial_println(char *s);
void delay(uint32_t m);

volatile SERIAL_PORT port;
volatile uint32_t _millis,
         _micros = 1000;

int main() {
    millis_init();
    serial_init();
    while (1) {
        serial_println("hello world");
        delay(500);
    }
}

void delay(uint32_t m) {
    uint32_t t_start = millis();
    while (millis() - t_start < m);
}

void millis_init() {
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        _millis = 0;
    }
}

uint32_t millis() {
    uint32_t x;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      x = _millis;
    }
    return x;
}

void serial_init() {
    DDRB |= 1<<TXPIN;       // Устанавливаем TXPIN как выход
    TXPORT |= 1<<TXPIN;     // Устанавливаем высокий уровень TXPIN (последовательный режим ожидания)
    TCCR0A = 1<<WGM01;      // Выбор режима CTC
    TCCR0B = SCALE_FLAG;    // Устанавливаем масштабатор часов
    OCR0A = TICKS_PER_BIT;  // Устанавливаем целевое значение CTC
    TIMSK0 |= 1<<OCIE0A;    // Включаем прерывание сравнения
    sei();
}

void serial_putchar(char c) {
    while (port.busy);
    port.data = c;
    port.index = 0;
    port.busy = 1;
}

void serial_print(char *s) {
    while (*s) serial_putchar(*s++);
}

void serial_println(char *s) {
    serial_print(s);
    serial_putchar('\r');
    serial_putchar('\n');
}

ISR(TIM0_COMPA_vect) {
    if (port.busy) {
        switch(port.index) {
            case 0:
                // отправляем стартовый бит
                TXPORT &= ~(1<<TXPIN);
                break;
            case 9:
                // отправляем стоповый бит
                TXPORT |= (1<<TXPIN);
                port.busy = 0;
                break;
            default:
                // отправляем бит данных
                if (port.data & 1) {
                    TXPORT |= 1<<TXPIN;
                } else {
                    TXPORT &= ~(1<<TXPIN);
                }
                port.data >>= 1;
                break;
        }
        port.index++;
    }

    _millis += mS_PER_BIT;

    if (uS_PER_BIT > _micros) {
        _millis++;
        _micros = 1000;
    } else {
        _micros -= uS_PER_BIT;
    }
}

, 👍0

Обсуждение

Какая у вас тактовая частота?, @Majenko

Извините, я должен был упомянуть. Attiny84 в настоящее время работает на частоте 1 МГц., @larsks

Вы считали, что у вас просто нет времени запускать ISR на такой скорости? Упростите его (удалите миллисы) и посмотрите, улучшится ли он., @Majenko

...и если вместо этого я установлю тактовую частоту на 8 МГц, он начнет работать. Я подозреваю, что вы правы., @larsks

Ну, для 9600 на частоте 1 МГц у вас 104 такта на бит. Это количество тактовых циклов, в которые вам нужно вложить свой ISR, и этот ISR составляет около 126 тактовых циклов (я не следил за ветвями, просто посчитал инструкции)., @Majenko

Вам следует переместить свои комментарии в ответ, потому что (а) вы нашли время, чтобы просмотреть его, (б) вы были в точку и (в) вы даже опередили меня в подсчете циклов, что я и делал только что. сейчас :), @larsks


1 ответ


Лучший ответ:

1

При частоте 1 МГц и скорости 9600 бод у вас есть в общей сложности 104 тактовых цикла, доступных для выполнения вашей процедуры прерывания. Быстрый подсчет сгенерированных ассемблерных инструкций дает больше (на первый взгляд около 126), так что вы можете не вписывается во время.

В результате ваша скорость передачи данных фактически будет ниже 9600, и чип будет полностью лишен тактовых циклов для выполнения каких-либо действий в вашем основном цикле.

,