сайт для палких паяльників

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();
    	}
    }
}

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

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

4 комментария: 5. STM32. Програмування STM32F103. USART

  • Борис говорить:

    Доброго дня! виникло питання: чи не потрібно скидати прапор USART_IT_RXNE у IRQHandler?

    • andre говорить:

      Доброго дня. Дякую за запитання. Це один з небагатьох флагів, які скидуються контролером при зчитуванні з USARTn->DR (функція USART_ReceiveData). Якщо у обробнику переривання не виконувати USART_ReceiveData, тоді треба скидади вручну.

  • Володимир говорить:

    Привіт. Моє запитання стосується першого виводу тексту через USARTSend а саме
    USARTSend(” Hello.\r\nUSART1 is ready.\r\n”);
    У мене при виконанні даного коду втрачається перший символ. У приведеному коді строка починається з пробілу і якщо його видалити то втратиться відповідно буква H. Чи є цьому якесь пояснення?

    • andre говорить:

      Доброго дня. Дякую що звернули на це увагу. Я не знайшов цьому пояснення. Тому взяв за звичку відразу після ініціалізації відправляти пустий рядок. Звісно, це “костиль”, але коли найду цьому рішення, обов’язково повідомлю.

Translate
Архіви

© 2011-2019 Андрій Корягін, Кременчук - Київ, Україна