5. STM32. Програмування STM32F103. USART


17.08.2016

5. STM32. Программирование STM32F103. USART
(на русском языке)

Ми вже використовували послідовний порт для програмування мікроконтролера. Тепер застосуємо його за прямим призначенням. STM32F103 Має 3 послідовних USART порти. Ми розглянемо приклад з USART1. Решта портів працюють аналогічно. У цьому прикладі ми підключимо мікроконтролер до комп`ютера за допомогою UART-USB перехідника. Та будемо використовувати термінальну програму для передачі команд мікроконтролеру.

Схема

STM32F103_UART

На комп`ютері будемо використовувати термінальну програму Putty. Можна будь-яку іншу яка працює з послідовним портом. Розглянемо приклад для STM32, який буде працювати як простенький термінал: приймати команди з комп’ютера і обробляти їх. Команди будуть дві: ON, OFF. Звісно, вони будуть вмикати і вимикати світлодіод на платі. Повний текст програми наведений у кінці статті, а зараз розберемо як це працює.

Ініціалізація USART1

Ініціалізація USART1 виконується наступним чином:
  • Вмикаємо тактування на модуль USART1 та порт GPIOA (ноги TX і RX USART1 підключені до - PA9, PA10). Про тактування йшлося у попередній статті.
  • Конфігуруємо NVIC. NVIC - контролер пріоритетних векторних переривань. Про нього буде окрема розмова. Сьогодні треба засвоїти, що всі переривання налаштовуються у NVIC.
  • Конфігуруємо GPIO (PA9, PA10). Це ноги TX і RX.
  • Конфігуруємо USART1
  • Вмикаємо USART1
  • Вмикаємо переривання, яке спрацьовує при надходженні байту у USART1.
Код ініціалізації (дивись функцію usart_init):


void usart_init(void)
{
	/* Enable USART1 and GPIOA clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

	/* NVIC Configuration */
	NVIC_InitTypeDef NVIC_InitStructure;
	/* Enable the USARTx Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* Configure the GPIOs */
	GPIO_InitTypeDef GPIO_InitStructure;

	/* Configure USART1 Tx (PA.09) as alternate function push-pull */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* Configure USART1 Rx (PA.10) as input floating */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* Configure the USART1 */
	USART_InitTypeDef USART_InitStructure;

	/* USART1 configuration ------------------------------------------------------*/
	/* USART1 configured as follow:
		- BaudRate = 115200 baud
		- Word Length = 8 Bits
		- One Stop Bit
		- No parity
		- Hardware flow control disabled (RTS and CTS signals)
		- Receive and transmit enabled
		- USART Clock disabled
		- USART CPOL: Clock is active low
		- USART CPHA: Data is captured on the middle
		- USART LastBit: The clock pulse of the last data bit is not output to
			the SCLK pin
	 */
	USART_InitStructure.USART_BaudRate = 115200;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

	USART_Init(USART1, &USART_InitStructure);

	/* Enable USART1 */
	USART_Cmd(USART1, ENABLE);

	/* Enable the USART1 Receive interrupt: this interrupt is generated when the
		USART1 receive data register is not empty */
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

Як видно з коду ініціалізації, порт USART1 буде працювати на швидкості 115200. У налаштуванні консольної програми треба зазначати таку саму швидкість порту.

Розбір програми

Тепер дивимось що відбувається, коли мікроконтролер отримує від комп’ютера байт (дивись USART1_IRQHandler).


void USART1_IRQHandler(void)
{
    if ((USART1->SR & USART_FLAG_RXNE) != (u16)RESET)
	{
    		RXc = USART_ReceiveData(USART1);
    		RX_BUF[RXi] = RXc;
    		RXi++;

    		if (RXc != 13) {
    			if (RXi > RX_BUF_SIZE-1) {
    				clear_RXBuffer();
    			}
    		}
    		else {
    			RX_FLAG_END_LINE = 1;
    		}

			//Echo
    		USART_SendData(USART1, RXc);
	}
}

Перш за все ми перевіряємо, прапорець USART_FLAG_RXNE


if ((USART1->SR & USART_FLAG_RXNE) != (u16)RESET)

і впевнюємося, що прилетів байт. Бо обробник переривань USART1_IRQHandler може викликатися і іншими подіями. Потім отриманий символ складаємо у RX_BUF, перевіряючи переповнення буфера. Якщо прилітає кінець рядка (символ з кодом 13), тобто у консолі термінала натисли клавішу Enter, тоді виставляємо наш прапорець RX_FLAG_END_LINE. По ньому ми будемо знати, що треба розібрати рядок, що був набраний у консолі термінала (тобто, прийшов час розібрати вміст буфера RX_BUF), і, якщо в буфері буде відома команда (ON або OFF), - виконати її.

Зверніть увагу, що у обробнику переривань USART1_IRQHandler ми не будемо виконувати досить важку процедуру обробки команд. Ми тільки встановимо прапорець, а обробку будемо робити у основному циклі програми. Це робиться для того, щоб як найшвидше завершити обробку переривання, тому що можуть чекати інші переривання. STM32 мають пріоритети переривань і обробка переривання може бути перервана перериванням з більшим пріоритетом. Та, незважаючи на це, краще, щоб обробка переривань виконувалась так швидко, як це можливо.

Остання команда у процедурі обробки переривання:


...
USART_SendData(USART1, RXc);
...

відправляє прийнятий символ назад у термінал, тобто виконує функцію Echo.

Тепер роздивимось що відбувається у головному циклі програми.


...
    while (1)
    {
        if (RX_FLAG_END_LINE == 1) {
            // Reset END_LINE Flag
            RX_FLAG_END_LINE = 0;
 
            USARTSend("\r\nI has received a line:\r\n");
            USARTSend(RX_BUF);
            USARTSend("\r\n");
 
            if (strncmp(strupr(RX_BUF), "ON\r", 3) == 0) {
                USARTSend("\r\nTHIS IS A COMMAND \"ON\"!!!\r\n");
                GPIO_ResetBits(GPIOC, GPIO_Pin_13);
            }
 
            if (strncmp(strupr(RX_BUF), "OFF\r", 4) == 0) {
                USARTSend("\r\nTHIS IS A COMMAND \"OFF\"!!!\r\n");
                GPIO_SetBits(GPIOC, GPIO_Pin_13);
            }
 
            clear_RXBuffer();
        }
    }
...

Ми очікуємо, коли буде натиснутий Enter, перевіряючи наш прапорець RX_FLAG_END_LINE:


if (RX_FLAG_END_LINE == 1)

Коли він прилітає, у консоль термінала виводиться рядок який ми будемо аналізувати:


USARTSend(RX_BUF);

А потім перевіряємо прийнятий рядок і, якщо це одна з відомих команд, - вмикаємо чи вимикаємо світлодіод:


if (strncmp(strupr(RX_BUF), "ON\r", 3) == 0) {

if (strncmp(strupr(RX_BUF), "OFF\r", 4) == 0) {

Повний текст програми


#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include "misc.h"
#include <string.h>
 
#define RX_BUF_SIZE 80
volatile char RX_FLAG_END_LINE = 0;
volatile char RXi;
volatile char RXc;
volatile char RX_BUF[RX_BUF_SIZE] = {'\0'};
volatile char buffer[80] = {'\0'};
 
void clear_RXBuffer(void) {
    for (RXi=0; RXi<RX_BUF_SIZE; RXi++)
        RX_BUF[RXi] = '\0';
    RXi = 0;
}
 
void usart_init(void)
{
    /* Enable USART1 and GPIOA clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
 
    /* NVIC Configuration */
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable the USARTx Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
 
    /* Configure the GPIOs */
    GPIO_InitTypeDef GPIO_InitStructure;
 
    /* Configure USART1 Tx (PA.09) as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
 
    /* Configure USART1 Rx (PA.10) as input floating */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
 
    /* Configure the USART1 */
    USART_InitTypeDef USART_InitStructure;
 
    /* USART1 configuration ------------------------------------------------------*/
    /* USART1 configured as follow:
        - BaudRate = 115200 baud
        - Word Length = 8 Bits
        - One Stop Bit
        - No parity
        - Hardware flow control disabled (RTS and CTS signals)
        - Receive and transmit enabled
        - USART Clock disabled
        - USART CPOL: Clock is active low
        - USART CPHA: Data is captured on the middle
        - USART LastBit: The clock pulse of the last data bit is not output to
            the SCLK pin
     */
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 
    USART_Init(USART1, &USART_InitStructure);
 
    /* Enable USART1 */
    USART_Cmd(USART1, ENABLE);
 
    /* Enable the USART1 Receive interrupt: this interrupt is generated when the
        USART1 receive data register is not empty */
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}
 
void USART1_IRQHandler(void)
{
    if ((USART1->SR & USART_FLAG_RXNE) != (u16)RESET)
    {
            RXc = USART_ReceiveData(USART1);
            RX_BUF[RXi] = RXc;
            RXi++;
 
            if (RXc != 13) {
                if (RXi > RX_BUF_SIZE-1) {
                    clear_RXBuffer();
                }
            }
            else {
                RX_FLAG_END_LINE = 1;
            }
 
            //Echo
            USART_SendData(USART1, RXc);
    }
}
 
void USARTSend(const unsigned char *pucBuffer)
{
    while (*pucBuffer)
    {
        USART_SendData(USART1, *pucBuffer++);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
        {
        }
    }
}
 
void SetSysClockTo72(void)
{
    ErrorStatus HSEStartUpStatus;
    /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();
 
    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);
 
    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
 
    if (HSEStartUpStatus == SUCCESS)
    {
        /* Enable Prefetch Buffer */
        //FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);
 
        /* Flash 2 wait state */
        //FLASH_SetLatency( FLASH_Latency_2);
 
        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);
 
        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);
 
        /* PCLK1 = HCLK/2 */
        RCC_PCLK1Config( RCC_HCLK_Div2);
 
        /* PLLCLK = 8MHz * 9 = 72 MHz */
        RCC_PLLConfig(0x00010000, RCC_PLLMul_9);
 
        /* Enable PLL */
        RCC_PLLCmd( ENABLE);
 
        /* Wait till PLL is ready */
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }
 
        /* Select PLL as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK);
 
        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */
 
        /* Go to infinite loop */
        while (1)
        {
        }
    }
}
 
int main(void)
{
    // Set System clock
    SetSysClockTo72();
 
    /* Initialize LED which connected to PC13 */
    GPIO_InitTypeDef  GPIO_InitStructure;
    // Enable PORTC Clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    /* Configure the GPIO_LED pin */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
 
    GPIO_ResetBits(GPIOC, GPIO_Pin_13); // Set C13 to Low level ("0")
 
    // Initialize USART
    usart_init();
    USARTSend(" Hello.\r\nUSART1 is ready.\r\n");
 
    while (1)
    {
        if (RX_FLAG_END_LINE == 1) {
            // Reset END_LINE Flag
            RX_FLAG_END_LINE = 0;
 
            USARTSend("\r\nI has received a line:\r\n");
            USARTSend(RX_BUF);
            USARTSend("\r\n");
 
            if (strncmp(strupr(RX_BUF), "ON\r", 3) == 0) {
                USARTSend("\r\nTHIS IS A COMMAND \"ON\"!!!\r\n");
                GPIO_ResetBits(GPIOC, GPIO_Pin_13);
            }
 
            if (strncmp(strupr(RX_BUF), "OFF\r", 4) == 0) {
                USARTSend("\r\nTHIS IS A COMMAND \"OFF\"!!!\r\n");
                GPIO_SetBits(GPIOC, GPIO_Pin_13);
            }
 
            clear_RXBuffer();
        }
    }
}

Бажаю успіхів!

Дивись також:

STM32
Коментарі:
Борис говорить:
14.12.2016 16:44
Доброго дня! виникло питання: чи не потрібно скидати прапор USART_IT_RXNE у IRQHandler?
andre говорить:
14.12.2016 16:54
Доброго дня. Дякую за запитання. Це один з небагатьох флагів, які скидуються контролером при зчитуванні з USARTn->DR (функція USART_ReceiveData). Якщо у обробнику переривання не виконувати USART_ReceiveData, тоді треба скидади вручну.
Володимир говорить:
26.02.2017 00:28
Привіт. Моє запитання стосується першого виводу тексту через USARTSend а саме
USARTSend(" Hello.\\
USART1 is ready.\\
");
У мене при виконанні даного коду втрачається перший символ. У приведеному коді строка починається з пробілу і якщо його видалити то втратиться відповідно буква H. Чи є цьому якесь пояснення?
andre говорить:
26.02.2017 13:07
Доброго дня. Дякую що звернули на це увагу. Я не знайшов цьому пояснення. Тому взяв за звичку відразу після ініціалізації відправляти пустий рядок. Звісно, це "костиль", але коли найду цьому рішення, обов`язково повідомлю.

Додати коментар

* - обов'язкові поля

Архіви