Микроконтроллеры STM32: обмен данными по UART

29 января 2018

В прошлом посте, посвященном STM32, мы познакомились с платами Nucleo, программой STM32CubeMX, узнали, как программировать под STM32 в Linux, а также осилили базовые операции с GPIO. Сегодня же мы поговорим об использовании аппаратной реализации UART. В рамках данного поста мы будем использовать UART исключительно для обмена данными с компьютером. Однако с тем же успехом его можно применять и для взаимодействия с внешними модулями.

Создадим новый проект в STM32CubeMX. Как и в прошлый раз, я буду использовать отладочную плату Nucleo-F411RE, однако для других плат отличия не будут большими.

Во вкладке Pinout находим пины с пометками USART2_RX и USART2_TX — это пины PA2 и PA3. Они уже выбраны, как пины, которые будут использованы для UART, но соответствующая периферия на данный момент отключена. Включить ее можно, найдя в дереве слева USART2 и выбрав Asynchronous в выпадающем списке Mode:

Настройка UART в STM32CubeMX

В том же дереве можно заметить периферии USART1 и USART6. Здесь мы используем USART2, так как именно она идет к компьютеру по USB. После включения периферии цвет пинов PA2 и PA3 сменится с желтого на зеленый.

Fun fact! Если в выпадающем списке выбрать Asynchronous, как это сделали мы, то получаем UART, если же выбрать Synchronous, то получим USART. Напомню, что отличие UART от USART заключается в наличии у последнего тактового сигнала (CK). По моим наблюдениям, на практике USART используется не часто.

Дополнительные настройки можно изменить во вкладке Configuration, кликнув на кнопку USART2 в блоке Connectivity. Я изменил Baud Rate на 9600, прочие же настройки оставил без изменений. Затем создаем проект в Project → Generate Code, как делали это в прошлый раз.

Как вы можете помнить, STM32CubeMX генерирует довольно фиговый Makefile. К счастью, можно скопировать Makefile из предыдущего проекта и дописать в список C_SOURCES строчку:

$(FIRMWARE)/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c \

В файле Src/main.c добавляем вызов процедур init() и loop() в окрестностях основного цикла, как делали это в прошлый раз:

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  init();
  while (1)
  {
  loop();
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

… а также добавляем следующий код:

/* USER CODE BEGIN 0 */

HAL_StatusTypeDef HAL_UART_ReceiveString(
        UART_HandleTypeDef *huart, uint8_t *pData,
        uint16_t Size, uint32_t Timeout) {
    const char newline[] = "\r\n";
    const char delete[] = "\x08 \x08";
    HAL_StatusTypeDef status;

    if(Size == 0)
        return HAL_ERROR;

    int i = 0;
    for(;;) {
        status = HAL_UART_Receive(huart, &pData[i], 1, Timeout);
        if(status != HAL_OK)
            return status;

        if((pData[i] == '\x08')||(pData[i] == '\x7F')) { // backspace
            if(i > 0) {
                status = HAL_UART_Transmit(huart, (uint8_t*)delete,
                                           sizeof(delete)-1, Timeout);
                if(status != HAL_OK)
                    return status;
                i--;
            }
            continue;
        }

        if((pData[i] == '\r') || (pData[i] == '\n')) {
            pData[i] = '\0';
            status = HAL_UART_Transmit(huart, (uint8_t*)newline,
                                       sizeof(newline)-1, Timeout);
            if(status != HAL_OK)
                return status;
            break;
        }

        // last character is reserved for '\0', ignore
        if(i == (Size-1))
            continue;

        status = HAL_UART_Transmit(huart, &pData[i], 1, Timeout);
        if(status != HAL_OK)
            return status;
        i++;
    }

    return HAL_OK;
}

void error(void) {
    HAL_Delay(HAL_MAX_DELAY);
}

void init(void) {
    const char ready[] = "Ready!\r\n";
    HAL_UART_Transmit(&huart2, (uint8_t*)ready,
                      sizeof(ready)-1, HAL_MAX_DELAY);
}

void loop(void) {
    HAL_StatusTypeDef status;
    const char question[] = "What is your name?\r\n";
    char answer[256];
    char name[32];

    status = HAL_UART_Transmit(&huart2, (uint8_t*)question,
                               sizeof(question)-1, HAL_MAX_DELAY);
    if(status != HAL_OK)
        error();

    status = HAL_UART_ReceiveString(&huart2, (uint8_t*)name,
                                    sizeof(name), HAL_MAX_DELAY);
    if(status != HAL_OK)
        error();

    int code = snprintf(answer, sizeof(answer),
                        "Hello, %s!\r\n", name);
    if(code < 0)
        error();

    status = HAL_UART_Transmit(&huart2, (uint8_t*)answer,
                               strlen(answer), HAL_MAX_DELAY);
    if(status != HAL_OK)
        error();

    HAL_Delay(100);
}

/* USER CODE END 0 */

Код не сложный. Процедура HAL_UART_Receive принимает данные, а процедура HAL_UART_Transmit — передает. Все остальное представляет собой мою обвязку вокруг этих двух процедур для создания диалогового режима.

Остается только сказать:

make flash

… и попытаться поговорить с платой по UART, воспользовавшись, например, утилитой screen:

screen /dev/ttyACM0

Интересно, что скажет нам Nucleo?

Ready!
What is your name?
Aleksander
Hello, Aleksander!
What is your name?
...

Полную версию исходного кода вы найдете на GitHub. Как видите, работать с UART оказалось весьма просто. Вооружившись полученными сегодня знаниями, мы можем использовать в проектах на базе STM32 радиомодуль HC-12, а также GSM, GPS, Bluetooth, да и вообще произвольный модуль, использующий UART. Стоит, правда, отметить, что мы не рассмотрели использование UART совместно с прерываниями и DMA, но это уже темы для отдельных заметок.

Дополнение: Пример работы с I2C вы найдете в заметках о работе с экранчиком 1602 с I2C-адаптером и внешним EEPROM, а пример работы с SPI — в посте, посвященном SPI flash.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.