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 активен при передаче, пассивен при приёме
📜 Поведение программы:
- При включении инициализируются UART, таймер и режим RS-485.
- Печатается приветствие
madmentat.ru
. - МК ожидает данные по UART.
- Если приходят байты, они накапливаются в текущем буфере и сбрасывается таймер.
- Если в течение
5 мс
не пришло новых байтов — пакет считается завершённым. - Буфер меняется, предыдущий отправляется как
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