STM32 СВЯЗКА UART - RS485

 

Собственно, микроконтроллеер мы тут будем использовать семейства STM32, в частности STM32F030K6T6. У нас на производстве он обкатан на платах, управляющих термоусадочным упаковочным оборудованием. Вещь надежная, хорошая, дешманская. Вот, собственно та самая плата, а если быть точнее, то аккуратно слизанный с нее гербер с некоторыми добаботками (покажу, но не поделюсь):

 Mercury_v1.3.png

 

Изолированный трансивер RS485 ISO3082DW у нас используется для обмена данными с HMI панелью DWIN по протоколу MODBUS, но вообще, конечно, такое устроство связи можно использовать как душе угодно (Бога нет, но вы держитеесь). Подключение осуществляется через ноги PA2 - TX, PA3 - RX и еще один пин PF1 - RE, DE. Здесь RE и DE объеденины, эти пины отвечают за режим направления работы приемо-передачи.

ISO3082DW 1

m32f030k6t6 pinout 1

Что это вообще такое и зачем тебе?

  • Отправляет данные: Каждую секунду твой STM32 будет кричать в линию RS485: "madmenat.ru\r\n". Типа "привет, я живой!".
  • Принимает данные: Если кто-то что-то пришлёт, он поймает это в буфер и, как только увидит конец строки (\n), ответит: "ECHO: " плюс то, что прислали. Как попугай, только умнее.
  • Управляет RS485: Сам переключается между "говорю" и "слушаю", чтобы не было путаницы на линии. Всё автоматом, как в лучших домах!

И всё это на прерываниях — микроконтроллер не пыхтит зря, а работает как ленивый гений: только когда надо.


Как это настроено? Всё по полочкам

  • Частота: 8 МГц, внутренний генератор (HSI). Не быстро, не медленно — в самый раз для старта.
  • Пины для UART: PA2 — это выход (TX), PA3 — вход (RX). Они подключены к USART1. Всё по стандарту, ничего выдумывать не надо.
  • Пин для RS485: PF1 решает, передаём мы или принимаем. HIGH — говорим, LOW — слушаем. Как выключатель: щёлк, и готово!

Как это работает? Шаг за шагом, для балбесов

  1. Отправка:
    • Таймер TIM3 каждую секунду орёт: "Пора!". Тогда STM32 ставит PF1 в HIGH (режим "говорю"), шлёт "madmenat.ru\r\n" и сразу возвращает PF1 в LOW (режим "слушаю"). Всё само, как по таймеру будильника!
  2. Приём:
    • Если что-то приходит на линию (и PF1 в LOW), данные летят в PA3 (RX).
    • Каждый байт вызывает прерывание UART, и он падает в буфер. Как только прилетает \n, STM32 шлёт обратно "ECHO: " и всё, что накопил. Например, прислали "hi\r\n" — в ответ "ECHO: hi\r\n". Просто эхо, только круче!
  3. Прерывания — это магия:
    • Микроконтроллер не сидит и не ждёт, как дурак, а занимается своими делами. Как только что-то приходит — бах, прерывание, и всё само обрабатывается. Это как уведомления на телефоне — не надо тыкать каждую секунду!

Что важно знать, чтобы не сесть в лужу

  • Трансивер RS485: Без него ничего не выйдет! Используй что-то типа ISO3082DW или SN75176BDR, и подключи его пин DE к PF1. Это как педали для машины - без них никуда.
  • Подтяжки: В шаблоне их нет, но если в реальном проекте кнопки или входы начнут глючить, добавь резисторы. Это как ремень безопасности — лучше с ним.
  • Коллизии: Если два устройства начнут орать одновременно, будет каша. Шаблон это не лечит, но ты можешь добавить проверку: "тихо на линии? тогда говорю!".

Что ты увидишь в терминале? Простой пример

  • Каждую секунду: "madmenat.ru\r\n".
  • Отправил "test\r\n" — получил "ECHO: test\r\n".
  • Это как крикнуть в лесу, а в ответ тебе то же самое, но с приколом!

Почему этот шаблон — твой лучший друг?

  • Просто: Скопируй код, зашей в STM32, и оно сразу заработает. Даже думать не надо!
  • Прерывания: Научишься работать с ними, а это как супергеройская сила для любых проектов с данными.
  • RS485: Разберёшься, как рулить направлением передачи/приёма — пригодится в куче полудуплексных штук.
  • Гибкость: Хочешь поменять текст, буфер или добавить команды? Легко — всё как конструктор LEGO.

Итог: твой билет в мир RS485

Этот шаблон — как велосипед с дополнительными колёсиками: простой, но мощный. Бери его, пробуй, добавляй свои фишки, и скоро ты будешь делать крутые системы обмена данными. А если что-то не пашет — проверь трансивер и пины. Это как убедиться, что шнур в розетке, когда телик не включается!

#include "main.h"
#include <string.h>
 
#define SYSCLK_HZ 8000000UL   // Тактовая частота системы: 8 МГц
#define BAUDRATE  115200UL    // Скорость UART: 115200 бод
 
// Пин для управления направлением RS485
#define RS485_CONTROL_PIN GPIO_PIN_1
#define RS485_CONTROL_PORT GPIOF
 
// Буфер для приёма данных
#define RX_BUFFER_SIZE 64
char rx_buffer[RX_BUFFER_SIZE];
uint8_t rx_index = 0;
 
// Прототипы функций
void SystemClock_Config(void);
void UART_Init(void);
void Timer_Init(void);
void UART_SendString(char *str);
static void MX_GPIO_Init(void);
void RS485_Init(void);
void RS485_SetTransmitMode(void);
void RS485_SetReceiveMode(void);
 
// Инициализация RS485
void RS485_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOF_CLK_ENABLE();  // Включаем тактирование порта F
    GPIO_InitStruct.Pin = RS485_CONTROL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(RS485_CONTROL_PORT, &GPIO_InitStruct);
    RS485_SetReceiveMode();  // По умолчанию — режим приёма
}
 
// Установка режима передачи для RS485
void RS485_SetTransmitMode(void) {
    HAL_GPIO_WritePin(RS485_CONTROL_PORT, RS485_CONTROL_PIN, GPIO_PIN_SET);
}
 
// Установка режима приёма для RS485
void RS485_SetReceiveMode(void) {
    HAL_GPIO_WritePin(RS485_CONTROL_PORT, RS485_CONTROL_PIN, GPIO_PIN_RESET);
}
 
// Инициализация UART
void UART_Init(void) {
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
 
    // Настраиваем PA2 (TX) и PA3 (RX) для альтернативной функции USART1
    GPIOA->MODER &= ~(GPIO_MODER_MODER2_Msk | GPIO_MODER_MODER3_Msk);
    GPIOA->MODER |= (GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1);  // Режим AF
    GPIOA->AFR[0] &= ~((0xF << GPIO_AFRL_AFSEL2_Pos) | (0xF << GPIO_AFRL_AFSEL3_Pos));
    GPIOA->AFR[0] |= ((1 << GPIO_AFRL_AFSEL2_Pos) | (1 << GPIO_AFRL_AFSEL3_Pos));  // AF1 для USART1
 
    // Настройка UART: 8 бит данных, 1 стоп-бит, без контроля чётности
    USART1->CR1 = 0;
    USART1->CR2 = 0;
    USART1->BRR = SYSCLK_HZ / BAUDRATE;  // Делитель для скорости
    USART1->CR1 |= (USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE);  // Включаем UART, TX, RX и прерывание по приёму
 
    // Включаем прерывание в NVIC
    NVIC_EnableIRQ(USART1_IRQn);
}
 
// Отправка строки через UART
void UART_SendString(char *str) {
    RS485_SetTransmitMode();
    while (*str) {
        while (!(USART1->ISR & USART_ISR_TXE)) {}  // Ждём, пока регистр передатчика освободится
        USART1->TDR = *str++;
    }
    while (!(USART1->ISR & USART_ISR_TC)) {}  // Ждём завершения передачи
    RS485_SetReceiveMode();
}
 
// Инициализация таймера TIM3
void Timer_Init(void) {
    __HAL_RCC_TIM3_CLK_ENABLE();
    TIM3->PSC = (SYSCLK_HZ / 1000) - 1;  // Предделитель для частоты 1 кГц
    TIM3->ARR = 1000 - 1;                // Период 1 секунда
    TIM3->DIER |= TIM_DIER_UIE;          // Разрешаем прерывание по обновлению
    NVIC_EnableIRQ(TIM3_IRQn);
    TIM3->CR1 |= TIM_CR1_CEN;            // Включаем таймер
}
 
// Обработчик прерывания таймера TIM3
void TIM3_IRQHandler(void) {
    if (TIM3->SR & TIM_SR_UIF) {
        TIM3->SR &= ~TIM_SR_UIF;         // Сбрасываем флаг прерывания
        UART_SendString("madmenat.ru\r\n");
    }
}
 
// Обработчик прерывания UART (приём данных)
void USART1_IRQHandler(void) {
    if (USART1->ISR & USART_ISR_RXNE) {  // Если данные приняты
        char received = USART1->RDR;     // Читаем принятый байт
        if (received == '\n') {          // Если получен символ конца строки
            RS485_SetTransmitMode();     // Переключаем в режим передачи
            UART_SendString("ECHO: ");   // Отправляем метку
            rx_buffer[rx_index] = '\0';  // Завершаем строку в буфере
            UART_SendString(rx_buffer);  // Отправляем принятые данные
            UART_SendString("\r\n");     // Добавляем перевод строки
            RS485_SetReceiveMode();      // Возвращаем в режим приёма
            rx_index = 0;                // Сбрасываем индекс буфера
        } else {
            if (rx_index < RX_BUFFER_SIZE - 1) {  // Проверяем, не переполнен ли буфер
                rx_buffer[rx_index++] = received; // Добавляем байт в буфер
            } else {
                rx_index = 0;            // Если буфер переполнен, сбрасываем
            }
        }
    }
}
 
// Основная функция
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();  // Инициализация GPIO (если нужно для других пинов)
    UART_Init();
    RS485_Init();
    Timer_Init();
 
    while (1) {
        // Основной цикл пуст, работа выполняется в прерываниях
    }
}
 
// Настройка системной частоты
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;  // Используем HSI
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;  // PLL не используется
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
 
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}
 
// Инициализация GPIO (если нужно для других пинов)
static void MX_GPIO_Init(void) {
    // Здесь можно добавить настройку дополнительных пинов, если потребуется
}
 
// Обработчик ошибок
void Error_Handler(void) {
    __disable_irq();
    while (1) {}
}
 
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line) {
}
#endif