ПРОГРАММНЫЙ UART В MICROCHIP STUDIO

Для проверки программы потребуется UART-монитор и USB-TTL конвертр:

USB TTL

 

Сколько же раз я писал эту хрень на разных микроконтроллерах, пока не начал раздуплять что к чему... ух... А на самом деле все просто. Если в ваш UART-монитор сыпется мусор, это значит что у вас какой-то косяк с таймингами. Надо посидеть и посчитать задержки BIT_DELAY. Иногда их настройка бывает не очень простой в связи с какими-то нюансами конкретного микроконтроллера... Например, с частотой 8 МГц или 16 МГц проблем возникнуть не должно. А вот при 20 МГц - хз... Возможно, придется вводить поправки при помощи ассемблерных вставок, пропускать такты по одному и так далее. На моей практике случалось такое, что программный UART работает в шаблонном варианте, но фаршмачил в большой программе. Причем, как-то странно: например, на скоростях в 9600, 38400 или в 1000000 БОД все было Ок, а на 115200 хуево. Вероятно, дело в проценте погрешности при рассчете по формуле F_CPU/16/BAUD-1. Число MY_UBRR должно быть  максимально ровным, то есть, ближе всего к целому значению. То есть, по-хорошему, для каждого конкретного случая это число необходимо выбирать заниженно, а затем добирать до целого значения при помощи вяких ухищрений. Кроме того, проблема может крыться в  обработчиках прерываний, срабатывающих во время выполнения функций отправки или приема данных. Логично, что во втором случае необходимо их своевременно выключать и включать, учитывая заодно, что это может сбивать работу алгоритмов, зависящих от аппаратных таймеров. Еще бывало такое, что на RX и на TX значение BIT_DELAY немного отличалось. А в целом, для  AVR, для 16 МГц до 115200 бод включительно, тут даже нехуй думать - все просто. Вот, тупо по нижепредставленному шаблону:

#define F_CPU 16000000UL // Тактовая частота микроконтроллера 16 МГц
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
// Настройки UART
#define BAUD 9600
#define BIT_DELAY F_CPU/16/BAUD-1 //MY_UBRR
#define TX_PIN PB1
#define TX_DDR DDRB    //Вот это гениальная идея, предложенная глупым роботом-пиздаболом. Пожалуй, теперь во всех своих программах буду использовать такой подход.
#define TX_PORT PORTB
// Прототипы функций
void software_uart_init(void);
void software_uart_transmit(unsigned char data);
void software_uart_putstring(const char *s);
void setup(void);
void loop(void);
// Функция для инициализации программного UART
void software_uart_init(void) {
    TX_DDR |= (1 << TX_PIN); // Настраиваем TX как выход
    TX_PORT |= (1 << TX_PIN); // Устанавливаем уровень HIGH (период покоя)
}
// Функция для передачи одного байта данных через программный UART
void software_uart_transmit(unsigned char data) {
    // Start bit
    TX_PORT &= ~(1 << TX_PIN); // Передаем LOW (стартовый бит)
    _delay_us(BIT_DELAY); // Длительность стартового бита
    // Data bits (8 бит данных)
    for (uint8_t i = 0; i < 8; i++) {
        if (data & (1 << i)) {
            TX_PORT |= (1 << TX_PIN); // Передаем HIGH
            } else {
            TX_PORT &= ~(1 << TX_PIN); // Передаем LOW
        }
        _delay_us(BIT_DELAY); // Длительность каждого бита
    }
    // Stop bit
    TX_PORT |= (1 << TX_PIN); // Передаем HIGH (стоповый бит)
    _delay_us(BIT_DELAY); // Длительность стопового бита
}
// Функция для передачи строки данных через программный UART
void software_uart_putstring(const char *s) {
    while (*s) {
        software_uart_transmit(*s++);
    }
}
// Функция для начальных настроек
void setup(void) {
    software_uart_init(); // Инициализируем программный UART
}
// Основной цикл
void loop(void) {
    software_uart_putstring("Hello World\n"); // Отправляем "Hello World" по программному UART
    _delay_ms(10); // Ждем 10 миллисекунд. Если возникнут какие-то ошибки, то они будут хорошо заметны в UART-мониторе.
}
// Основная функция
int main(void) {
    setup(); // Вызываем функцию для начальных настроек
    while (1) {
        loop();
    }
}

Тут есть нюансик... Вся эта радость нормально работает до 115200 бод. При увеличении частоты будет сплошное серево. Проблемма в побитовых таймингах.

И, кстати, при тактовой частоте 16 МГц функция _delay_ms(1000); пропускает... Ну, где-то около 16 тысяч тактов... При этом микроконтроллер больше нихуя не делает. Ардуинщикам-то похеру на такие вещи, потому что они наподобие обезьян или хохлов, которым числа не важны, но это надо учитывать при написании нормальной программы, таких вещей не должно быть. За исключением каких-то особых случаев, когда какая-нибудь задержка, скажем, в 10 миллисекунд, никак не влияет на итоговый результат. Нормальные мужики вместо подобной чепухи используют прерывания по аппаратному таймеру.