В прошлом посте, посвященном 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 ] = ;
status = HAL_UART_Transmit ( huart , ( uint8_t * ) newline ,
sizeof ( newline ) 1 , Timeout ) ;
if ( status != HAL_OK )
return status ;
break ;
}

// last character is reserved for ‘’, 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 .

EnglishRussianUkrainian