ATMEGA - RS-485 - SN75176BDR / MAX485

 

⚙️ Проект: ATmega + RS-485 (SN75176BDR / MAX485)

🧠 Назначение: Программа реализует приём сообщений по UART, буферизацию с тайм-аутом, и ответ (эхо) по шине RS-485. Предусмотрена автоматическая активация режима передачи и возврат в приём.


🔌 Аппаратная часть:

🧩 МК: ATmega328P (или совместимый)
🔁 Интерфейс: RS-485 через SN75176BDR / MAX485
📶 Скорость: 9600 бод
📍 DE (driver enable): PC0

🧩 Основные блоки кода:

  • 🔧 UART: аппаратный, с прерыванием при приёме
  • ⏱️ TIMER1: создаёт 1 мс тики для отслеживания окончания пакета
  • 🧵 Буферизация: два буфера 64 байта — один на приём, один на отправку
  • ↔️ RS-485 переключение: DE активен при передаче, пассивен при приёме

📜 Поведение программы:

  1. При включении инициализируются UART, таймер и режим RS-485.
  2. Печатается приветствие madmentat.ru.
  3. МК ожидает данные по UART.
  4. Если приходят байты, они накапливаются в текущем буфере и сбрасывается таймер.
  5. Если в течение 5 мс не пришло новых байтов — пакет считается завершённым.
  6. Буфер меняется, предыдущий отправляется как echo: ... с возвратом каретки.

📦 Структура буферов и логика:

БуферНазначение
rx_buffer1 Рабочий буфер приёма
rx_buffer2 Буфер для отправки (когда первый занят)

📡 Передача по RS-485:

Перед каждой передачей вызывается:

rs485_begin_tx();  // DE = 1
uart_send_byte(...);
rs485_end_tx();    // DE = 0

Задержка в _delay_us(30) перед началом — для SN75176 стабилизации сигнала.


🔄 Прерывания:

  • USART_RX_vect: срабатывает при приёме байта, помещает его в буфер, сбрасывает таймер
  • TIMER1_COMPA_vect: отслеживает "тишину" и переключает буферы

📎 Комментарии:

  • 🎯 Позволяет надёжно получать короткие пакеты по RS-485 без контроля символов завершения
  • 🧪 Прекрасно подходит для устройств опроса — например, MODBUS-like
  • 🛡️ Можно адаптировать под CRC-контроль, если нужно

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>
#include <stdio.h>
 
// ------------------------ UART SETTINGS ------------------------
#define BAUD        9600
#define MYUBRR      (F_CPU / 16 / BAUD - 1)
 
// ------------------------ BUFFERS ------------------------------
#define BUFFER_SIZE     64          // bytes per buffer
#define TIMEOUT_LIMIT   5           // ms of silence to finish a message
 
static char rx_buffer1[BUFFER_SIZE];
static char rx_buffer2[BUFFER_SIZE];
 
volatile char *current_rx_buffer = rx_buffer1;
volatile char *sending_buffer    = NULL;
 
volatile uint8_t rx_index         = 0;   // write index in active buffer
volatile uint8_t message_length   = 0;   // bytes to send back
volatile uint8_t message_complete = 0;   // flag: buffer ready to echo
volatile uint8_t timeout_counter  = 0;   // ms since last byte
 
// ------------------------ RS‑485  --------------------------------
#define RS485_DE_DDR   DDRC
#define RS485_DE_PORT  PORTC
#define RS485_DE_PIN   PC0
 
static inline void rs485_begin_tx(void)
{
    RS485_DE_PORT |=  (1 << RS485_DE_PIN);   // DE = 1 (driver enable)
    _delay_us(30);                           // t(EN) for SN75176B
}
 
static inline void rs485_end_tx(void)
{
    while (!(UCSR0A & (1 << TXC0)));         // wait for last stop‑bit
    UCSR0A |=  (1 << TXC0);                  // clear TXC flag
    RS485_DE_PORT &= ~(1 << RS485_DE_PIN);   // DE = 0 (receive)
}
 
// ------------------------ UART LOW‑LEVEL ------------------------
static inline void uart_send_byte(uint8_t data)
{
    while (!(UCSR0A & (1 << UDRE0)));        // wait TX register empty
    UDR0 = data;
}
 
static void uart_send_buf(const char *buf, uint8_t len)
{
    rs485_begin_tx();
    for (uint8_t i = 0; i < len; i++) {
        uart_send_byte(buf[i]);
    }
    rs485_end_tx();
}
 
static void uart_send_string(const char *s)
{
    rs485_begin_tx();
    while (*s) {
        uart_send_byte(*s++);
    }
    rs485_end_tx();
}
 
// ------------------------ TIMER1 : 1 ms TICK --------------------
static void timer1_init(void)
{
    TCCR1B  = (1 << WGM12);                  // CTC mode
    OCR1A   = 249;                           // 1 ms @ 16 MHz/64
    TCCR1B |= (1 << CS11) | (1 << CS10);     // prescaler 64
    TIMSK1 |= (1 << OCIE1A);                 // enable compare interrupt
}
 
static inline void timer1_reset(void)
{
    TCNT1 = 0;
}
 
// ------------------------ UART INIT -----------------------------
static void uart_init(void)
{
    UBRR0H = (uint8_t)(MYUBRR >> 8);
    UBRR0L = (uint8_t)MYUBRR;
 
    UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0); // RX, TX, RX interrupt
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);               // 8N1
}
 
// ------------------------ IO INIT -------------------------------
static void io_init(void)
{
    RS485_DE_DDR  |= (1 << RS485_DE_PIN);    // PC0 output
    RS485_DE_PORT &= ~(1 << RS485_DE_PIN);   // default: receive
}
 
// ------------------------ SETUP ---------------------------------
static void setup(void)
{
    io_init();
    uart_init();
    timer1_init();
    sei();                                   // global interrupts on
 
    memset(rx_buffer1, 0, BUFFER_SIZE);
    memset(rx_buffer2, 0, BUFFER_SIZE);
 
    uart_send_string("madmentat.ru\r\n");
}
 
// ------------------------ MAIN LOOP -----------------------------
static void loop(void)
{
    while (1) {
        if (message_complete) {
            cli();                           // protect shared vars
            volatile char *buf = sending_buffer;
            uint8_t len        = message_length;
            message_complete   = 0;
            sending_buffer     = NULL;
            sei();
 
            if (len && buf) {
                uart_send_string("echo: ");
                uart_send_buf((const char *)buf, len);
                uart_send_string("\r\n");
                memset((void *)buf, 0, BUFFER_SIZE);
            }
        }
    }
}
 
int main(void)
{
    setup();
    loop();
    return 0;
}
 
// ------------------------ INTERRUPTS ----------------------------
ISR(USART_RX_vect)
{
    static const uint8_t XON  = 0x11;
    static const uint8_t XOFF = 0x13;
 
    uint8_t data = UDR0;
 
    // simple ignore of software flow‑control
    if (data == XON || data == XOFF) return;
 
    if (rx_index < (BUFFER_SIZE - 1)) {
        current_rx_buffer[rx_index++] = data;
        timeout_counter = 0;
        timer1_reset();
    }
}
 
ISR(TIMER1_COMPA_vect)
{
    if (rx_index) {
        timeout_counter++;
        if (timeout_counter >= TIMEOUT_LIMIT) {
            sending_buffer    = current_rx_buffer;
            message_length    = rx_index;
            current_rx_buffer = (current_rx_buffer == rx_buffer1) ? rx_buffer2 : rx_buffer1;
            rx_index          = 0;
            timeout_counter   = 0;
            message_complete  = 1;
        }
    } else {
        timeout_counter = 0;
    }
}
 

🔚 Заключение:

Минималистичный, но рабочий подход к полудуплексной связи по RS-485 на AVR без RTOS. Отличный старт для внедрения более сложных протоколов.

👨‍💻 Автор: madmentat.ru