STM32 с нуля. USART. Пример программы.

Продолжаем нашу рубрику STM32 с нуля, диодиком помигали, таймером поиграли, пора наладить связь с внешним миром ) Для этого сегодня будем разбираться как работает в микроконтроллерах STM32 USART. И, собственно, напишем для USART пример программы.

Что такое USART и зачем он нужен думаю объяснять не надо ) Так что перейдем сразу к реализации протокола в STM32. Предлагаю сначала посмотреть какие там регистры за что отвечают, а потом уже набросать какой-нибудь проектик для наглядности.

Небольшое лирическое отступление ) Как вы уже догадываетесь модуль USART STM32, как и все остальное в этих микроконтроллерах, имеет множество настроек и режимов. Тут тебе и обычный прием/передача и поддержка LIN (об этом протоколе как-нибудь поговорим отдельно). И если в AVR частенько приходилось мутить софтовые UART’ы, поскольку аппаратных просто не хватало, то в STM32F103CB их как минимум три штуки! А это уже немало. Итак, начинаем копаться в даташите.

Регистр USART_SR.
Статусный регистр. Тут содержатся флаги, отражающие текущее состояние дел, то есть текущее состояние модуля USART. Поглядим на некоторые флаги поближе:

LBD — Lin Break Detection flag.
Выставляется при обнаружении брейка при использовании LIN (о том, что это такое обязательно расскажу в статье про LIN)

TXE — Transmit data register empty
Регистр данных пуст, пора его заполнить!

TC — Transmission complete
Передача завершена

RXNE — Read data register not empty
Приемный регистр не пуст, надо срочно читать!
Есть там еще 4 флага для классификации ошибок – ошибка кадра, наличие шума, ошибка переполнения.

Регистр USART_DR.
Регистр данных.
Именно тут мы будем забирать ценнейшую информацию, которая к нам пришла извне, а также будем сюда сливать свою информацию.

Регистр USART_BRR.
Сюда записывается скорость обмена, которую мы хотим получить.

Регистр USART_CR1.
Регистр контроля – контролирует весь процесс. Биты по порядку:

UE — Разрешение работы USART

M — Длина посылки: 0 – 8 бит данных, 1 – 9 бит данных

Wake — Будильник для USART’a – то есть метод его пробуждения

PCE — Контроль паритета – ВКЛ или ВЫКЛ

PS — Тип паритета: 0 – четный, 1 – нечетный

PEIE — Разрешение прерывания при обнаружении ошибки паритета

TXEIE — Прерывание от TXE

RXNEIE — Прерывание от RXNE
Ну и там еще парочка битов, разрешающих/запрещающих прием/передачу, а также бит для отправки брейка. В общем, это все есть в даташите, все наглядно, красиво и понятно ) Тут просто на всякий случай это пишу.

Регистр USART_CR2.
Тут у нас спрятались такие биты как, включение/выключение режима работы по LIN, установка количества стоп-бит, настройки clock’а, установка параметров LIN брейка и некоторые другие.

Регистр USART_CR3.
А здесь биты для использования DMA и SmartCard.

Регистр USART_GTPR.
А в нем находятся биты, отвечающие за предделитель.

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

Собственно, пришло время немного попрограммировать. Пусть в нашем USART примере программка опрашивает кнопку, и если ее кто-нибудь ткнет, то контроллер вышлет в USART сообщение «Pressed», а если кнопка так и будет скучать не нажатой, то во внешний мир полетит «Not Pressed».

Итак, как же мы будем это реализовывать? А очень просто (вот на всякий случай статья про создание нового проекта). Создадим функцию, которая будет опрашивать кнопку и записывать в буфер соответствующее сообщение. Кроме того, она будет в специальную переменную записывать количество байт, которые надо передать. В прерывании мы будем совать очередной байт в USART и увеличивать счетчик переданных байт. Когда значение счетчика станет равно количеству байт, которые необходимо передать (это означает, что все сообщение передано), снова будем вызывать функцию-опросчик кнопки. И так бесконечно. Перейдем к реализации алгоритма (не забываем добавить в проект файлы stm32f10x_usart.h и stm32f10x_usart.c из Standard Peripheral Library). Ниже приведен полный код примера с пояснениями:

/****************************usart.c*********************************/
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_usart.h"
 
/*******************************************************************/
#define BAUDRATE 9600
 
/*******************************************************************/
GPIO_InitTypeDef port;
USART_InitTypeDef usart;
//Переменная для хранения передаваемых данных
uint8_t usartData[10];
uint16_t button;
//Счетчик переданных байт
uint16_t usartCounter = 0;
//А тут будет количество байт, которые нужно передать
uint16_t numOfBytes;
 
/*******************************************************************/
void initAll()
{
    //Включаем тактирование
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
 
    //Пины PA9 и PA10 в режиме альтернативных функций –
    //Rx и Tx USART’а
    GPIO_StructInit(&port);
    port.GPIO_Mode = GPIO_Mode_AF_PP;
    port.GPIO_Pin = GPIO_Pin_9;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);
 
    port.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    port.GPIO_Pin = GPIO_Pin_10;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);
 
    //Настройка USART, все поля оставляем дефолтными, кроме скорости обмена
    USART_StructInit(&usart);
    usart.USART_BaudRate = BAUDRATE;
    USART_Init(USART1, &usart);
 
    //Здесь будет висеть наша кнопка
    port.GPIO_Mode = GPIO_Mode_IPD;
    port.GPIO_Pin = GPIO_Pin_2;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB, &port);
}
 
/*******************************************************************/
//Вот она, функция-опросчик кнопки
void setData()
{
    button = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2);
    if (button == 0)
    {
        usartData[0] = 'N';
        usartData[1] = 'o';
        usartData[2] = 't';
        usartData[3] = 'P';
        usartData[4] = 'r';
        usartData[5] = 'e';
        usartData[6] = 's';
        usartData[7] = 's';
        usartData[8] = 'e';
        usartData[9] = 'd';
        numOfBytes = 10;
    }
    else
    {
        usartData[0] = 'P';
        usartData[1] = 'r';
        usartData[2] = 'e';
        usartData[3] = 's';
        usartData[4] = 's';
        usartData[5] = 'e';
        usartData[6] = 'd';
        numOfBytes = 7;
    }
    usartCounter = 0;
}
 
/*******************************************************************/
int main()
{
    __enable_irq ();
    initAll();
    //Включаем прерывания по приему байта и по окончанию передачи
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_ITConfig(USART1, USART_IT_TC, ENABLE);
    //Запускаем сам USART
    USART_Cmd(USART1, ENABLE);
    //Первая установка сообщения, вне цикла
    setData();
    NVIC_EnableIRQ(USART1_IRQn);
    while(1)
    {
        //Собственно вот он весь, описанный ранее алгоритм
        if (usartCounter == numOfBytes)
        {
            setData();
        }
        else __NOP();
    }
}
 
/*******************************************************************/
//А в прерывании выдаем байт и увеличиваем счетчик
void USART1_IRQHandler()
{
    if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)
    {
        USART_SendData(USART1, usartData[usartCounter]);
        usartCounter++;
    }
    USART_ClearITPendingBit(USART1, USART_IT_TC);
}
 
/****************************End of file****************************/

Как видите все довольно просто и понятно =) Давайте посмотрим что получилось — компилируем и запускаем отладчик. Там идем в View->Serial Windows и выбираем наш USART – то есть USART номер один. В правом нижнем углу появится окошко, в котором будем видеть все сообщения, которые выдает USART. Запускаем программу и эмулируем нажатие кнопки, выставляя нужный бит в окне GPIOB. И вот что получилось:

Использование USART

Согласен, не слишком наглядно и красиво, но то, что программа работает как мы и задумали, не вызывает ни малейших сомнений ) С передачей все прошло гладко, давайте попробуем что-нибудь принять. Итак, реализуем еще один пример.

Пусть будет такая задача – в зависимости от принятого байта зажигать один из двух диодов. То есть приняли «1» — зажгли первый, приняли «2» — зажгли второй. Тут мы снова прибегнем к языку сценариев (об этом писалось в одной из предыдущих статей — вот в этой) для эмуляции приему сообщения по USART. Создаем файл usart.ini и заполняем его следующим кодом:

/*******************************************************************/
func void usart1()
{
    S1IN = '1';
}
/*******************************************************************/
func void usart2()
{
    S1IN = '2';
}
/*******************************************************************/

Кода немного, но достаточно для нашей цели =)
S1IN – вход для эмуляции входа последовательного порта. Таким образом, вызвав в командной строке функцию usart1() получим на вход USART’a – «1», а вызвав usart2() – получим «2».
Теперь код основной программы:

/****************************usart1.c*********************************/
#include "stm32f10x.h" 
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_usart.h"
 
/*******************************************************************/
#define BAUDRATE	9600
 
/*******************************************************************/
GPIO_InitTypeDef port;
USART_InitTypeDef usart;
uint8_t usartData;
 
/*******************************************************************/
void initAll()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
 
    GPIO_StructInit(&port);
    port.GPIO_Mode = GPIO_Mode_AF_PP;
    port.GPIO_Pin = GPIO_Pin_9;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);
 
    port.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    port.GPIO_Pin = GPIO_Pin_10;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);
 
    USART_StructInit(&usart);
    usart.USART_BaudRate = BAUDRATE;
    USART_Init(USART1, &usart);
 
    port.GPIO_Mode = GPIO_Mode_Out_PP;
    port.GPIO_Pin = GPIO_Pin_1;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB, &port);
 
    port.GPIO_Mode = GPIO_Mode_Out_PP;
    port.GPIO_Pin = GPIO_Pin_2;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB, &port);
}
 
//Пока все как обычно, ничего нового
 
/*******************************************************************/
int main()
{
    __enable_irq ();
    initAll();
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_Cmd(USART1, ENABLE);
    NVIC_EnableIRQ(USART1_IRQn);
    while(1)
    {
    	//Если приняли «1» зажигаем диод 1 и гасим диод 2,
	//приняли «2» - все наоборот
	if (usartData == '1')
	{
            GPIO_SetBits(GPIOB, GPIO_Pin_1);
	    GPIO_ResetBits(GPIOB, GPIO_Pin_2);
	}
	if (usartData == '2')
	{
	    GPIO_SetBits(GPIOB, GPIO_Pin_2);
	    GPIO_ResetBits(GPIOB, GPIO_Pin_1);
	}
    }
}
 
/*******************************************************************/
void USART1_IRQHandler()
{
    //Проверяем, действительно ли прерывание вызвано приемом нового байта
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {	
	usartData = USART_ReceiveData(USART1);	
    }
}
 
/****************************End of file****************************/

Опять все очень несложно, даже комментировать толком не пришлось )

Запускаем отладчик Keil, настраиваем логический анализатор на порты PB1 и PB2, подключаем наш файл .ini (написав в командной строке include usart.ini) и поочередно вызываем функции usart1() и usart2(). Вот что из этого безобразия вышло:

Прием байта по USART
(Тыкайте на картинку для увеличения, а то так ничего толком не видно)

То есть не вышло абсолютно ничего. Можно долго ломать голову, где же косяк в программе, а ответ прост. Почему-то логический анализатор иногда отказывается работать правильно. В общем, раз на раз не приходится. В таких случаях, прежде чем мучать себя отлавливанием несуществующей ошибки, открываем окно GPIOB  и видим, что на самом деле все работает корректно:

GPIOB

Диоды мигают так как надо )

Итак, мы разобрались с работой USART, с приемом и передачей, следите за развитием событий в следующих статьях )

Понравилась статья? Поделись с друзьями!

STM32 с нуля. USART. Пример программы.: 17 комментариев
  1. У меня такая херотень с USART: Имеется код, который работает, но инициируется не понятно. Я использую библиотечные функции для инициирования USART, но при установке скорости 9600 в терминале наблюдаю 4 непонятных байта, когда я высылал 1. И прикол в том что у меня появился нормальный прием, когда я выставил ему в коде скорость 28800, т.е 3 раза большую, хотя в терминалке стоит 9600, остальное стандартно и дефолтно. Смотрел по асциллу и на самом деле скорость USART у меня была в 3 раза занижена. В чем может быть дело, если при вызове библиотечных функций скорость должна быть 9600 при любой частоте контроллера?

    • Вибачте, що українською (переведете)))))
      це може бути пов’язано з тим, що у файлі system_stm32бла.c частота HSE_VALUE зовнішнього джерела встановлена 25 МГц, а на платі кварц стоїть 8 МГц. Це приблизно дає різницю втричі.

      • Да, на плате стоит внешний кварц на 8. Я тоже подозреваю на стартапный файл. Программирую я под Keil 4, и, кстати, контроллеру похер на то что в настройках проекта у меня стоит 8 Мгц если подключен опять же стартапный файл — он будет ориентировать тактирование по этому файлу

      • Кстати, спасибо за помощь. Проблема была решена. Дело было действительно в этом параметре

  2. Здравствуйте
    на сайте ST в мануалах на контроллеры отсутствует информация о регистрах периферийных устройств с их описанием. Видимо это вынесено в отдельные документы. Напишите, пожалуйста, в каких документах можно найти описание периферии контроллеров, а так же библиотек

  3. Буду признателен если объясните:
    1) написал этот пример для STM32f050 где все таймеры и нужный мне USART сидят на одной единственной шине APB.
    когда делал Шим тактировал соответственно: RCC_APB1Periph_TIM2 все работало.
    также по идее надо и для USART RCC_APB1Periph_USART1 , но тут компилятор начинает ругаться и говорит что нужно тактировать APB2, эксперемента ради затактировал APB2 заработало. но как так получается если по даташиту там одна единственная шина????
    2) в терминале после запуска выходит
    NotPressed и дальше хаотичный набор символов,
    т.е. я так понимаю при первом вызове setData() все нормально, а в цикле уже совсем не то…..???

    • Видимо в библиотеке определены дефайны под APB2, судя по документации там действительно только шины AHB и APB есть.

  4. Вопрос может глупый, но есть желание полностью разобраться, в первом примере передаем usartData,
    8битная переменная типа «unsigned int»
    а как например передать результат с 12битного АЦП,
    строку char[4]=»Start»; или просто int c=500; ?

  5. Пытаюсь разобраться с Usart ( у вас используется связь rs252-usb ) при этом usb mini для контроллера rs идет в комп так ведь?

    • Здесь используется отладка только в симуляторе без железа. Если подключать физически, то надо переходник USART-RS232, к примеру, далее либо сразу в ПК (при наличии ком-порта), либо через переходник RS232-USB. Это самый традиционный метод подключения.

  6. Кажется для полноты картины надо добавить тактирование альтернативной функции RCC_APB2PERIPH_AFIO

  7. Есть такая задача. Надо организовать двухсторонний обмен данными между двумя устройствами по радиоканалу. Радиомодемы подключаются по UART. Решил использовать UART DMA режим. Настройку и сборку проекта делал через Cube. Проблема заключается в следующем: после некоторого времени работы устройства прекращается прием данных. В тоже время если на одном устройстве оставить только передающую, а на другом приемную часть программы – все прекрасно работает.
    Прошу помощи в решении этого вопроса (к сожалению большого опыта в программировании этих процессоров не имею). Может подскажите готовое решение для двухстороннего обмена данными по UART.
    Для примера и обсуждения выкладываю часть программы одного устройства (на втором все организовано аналогично, есть небольшие отличия в обработке принимаемого сигнала).
    Инициализация UART
    [code]/* USART1 init function */
    void MX_USART1_UART_Init(void)
    {

    huart1.Instance = USART1;
    huart1.Init.BaudRate = 19200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    huart1.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED ;
    huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    HAL_UART_Init(&huart1);

    }
    [/code]
    Инициализация DMA
    [code]void MX_DMA_Init(void)
    {
    /* DMA controller clock enable */
    __DMA1_CLK_ENABLE();
    __DMA2_CLK_ENABLE();

    /* DMA interrupt init */
    HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); // DMA1_Channel4 — передача uart
    HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
    HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); // DMA1_Channel5 – прием UART
    HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
    HAL_NVIC_SetPriority(DMA2_Channel1_IRQn, 0, 0);// DMA2_Channel1 – ADC
    HAL_NVIC_EnableIRQ(DMA2_Channel1_IRQn);

    }
    [/code]
    В main делаю отправку данных вот так:
    [code]
    if(TX_Ready == 1)
    {
    TX_Ready = 0;
    HAL_UART_Transmit_DMA(&huart1, (uint8_t *)uartTX, 9);
    }
    [/code]
    Флаг сбрасывается вот здесь TX_Ready:
    [code]void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
    {
    TX_Ready = 1;
    }
    [/code]
    Прием данных делаю вот здесь:
    [code]void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
    {
    if(huart1.RxXferSize == 1)
    {
    if(RXbyte[0] == START_BYTE)
    HAL_UART_Receive_IT(&huart1, (uint8_t *)uartRX, 3);
    else
    HAL_UART_Receive_IT(&huart1, (uint8_t *)RXbyte, 1);
    }
    else
    {
    if (uartRX[2] == 2*START_BYTE)
    IRdist = (uartRX[1] << 8) | uartRX[0];
    HAL_UART_Receive_IT(&huart1, (uint8_t *)RXbyte, 1);
    }
    }
    [/code]
    Немного опишу теорию приема.
    Запускаю прием одного байта командой HAL_UART_Receive_IT(&huart1, (uint8_t *)RXbyte, 1).
    Если принятый байт равен стартовому байту в отправленной пачке, то принимаю пачку из 3 байт HAL_UART_Receive_IT(&huart1, (uint8_t *)uartRX, 3). Если не равен, снова принимаю байт (ожидаю нужный мне байт)
    Если принял пачку и контрольный третий байт равен ожидаемому, то формирую сигнал IRdist и снова принимаю один байт. if(huart1.RxXferSize == 1) этим смотрю что я принимал один байт или пачку.
    В ответном устройстве аналогично принимается пачка из 9 байт, а отправляется 3 байта.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *