UART и USART. COM-порт. Часть 2
Рассмотрим подключение компьютера к микроконтроллеру и пример программы для обмена данными между ними. Для подключения будем использовать COM-порт компьютера и UART/USART интерфейс микроконтроллера. Подробнее об UART, USART, COM, смотрите в первой части.
Подключение будем осуществлять с использованием схемы для гальванической развязки COM-порта.
Рис. %img:rss
В качестве микроконтроллера, как и раньше, будем использовать STM32F100RBT6B в составе оценочной платы STM32VLDISCOVERY. Возможности, функционирование USART в микроконтроллерах STM32, использование USART, набор регистров USART - всё это подробно описывается в руководстве пользователя для микроконтроллеров STM32. Наиболее важные вопросы обсуждаются в первой части статьи.
USART предоставляет возможность гибкой настройки конфигурации: можно в широком диапазоне изменять скорость передачи; изменять количество битов в передаваемом одним фреймом слове; использовать один из нескольких возможных вариантов контроля чётности или отключать контроль; настраивать количество стоп-битов; можно использовать или не использовать линии CTS/RTS. Безусловно, богатство возможностей по настройке придаёт USART большую гибкость и универсальность, но с другой стороны, создаёт определённые трудности при обмене данными. Ведь для того, чтобы обмен данными был возможным, подключённые между собой устройства должны иметь одинаковые настройки по всем параметрам.
После конфигурирования USART можно приступать к обмену данными. У микроконтроллера есть два варианта работы с USART: побайтная передача и приём или с использованием DMA. В первом случае, для приёма и передачи каждого байта процессор должен выполнить последовательность определённых действий. Так, он может обрабатывать прерывание, возникающие, когда опустошается регистр передаваемых данных или заполняется регистр принимаемых данных; установив причину прерывания, записать в регистр данных очередной байт для передачи или считать из регистра принятый байт и обработать его. Если используется режим работы с DMA, то процессору необходимо задать расположение буферов для приёма и передачи, их размер. USART и DMA далее будут осуществлять передачу и приём самостоятельно, не требуя затрат вычислительных ресурсов процессора.
Использование DMA разгружает процессор и позволяет достичь очень высокой скорости передачи. Но если не планируется передавать большие объёмы данных на высокой скорости, то можно использовать побайтную передачу. Этот вариант несколько проще в программировании. В этой статье рассмотрим побайтную передачу/приём.
Если требуется обрабатывать прерывания от устройства USART, то следует определить функцию-обработчик. Независимо от причины прерывания от USART, используется один и тот же обработчик
extern "C" void USARTx_IRQHandler()
{
// Код обработчика исключения.
}
Здесь символом "x" в имени USARTx_IRQHandler
обозначен номер устройства USART: 1, 2, ...
Причина прерывания определяется внутри обработчика путём анализа регистра состояния USARTx->SR
.
Не следует забывать включать генерацию прерывания при установке интересующих флагов путём настройки устройства USART, а также требуется настроить контроллер прерываний NVIC - задать приоритет и разрешить обработку - функциями
NVIC_SetPriority (USARTx_IRQn, PRIORITY);
NVIC_EnableIRQ(USARTx_IRQn);
Подробнее об исключениях, прерываниях, уровнях можно посмотреть здесь.
Для передачи байта через устройство USART достаточно выполнить запись в регистр USART_DR, но предварительно необходимо выполнить ряд действий для конфигурирования устройств. Алгоритм действий будет примерно следующий.
0. Включить тактирование для устройства USART и используемых GPIO (как обычно, иначе невозможно будет даже выполнить запись в регистры устройств).
1. Включить USART: USART_CR1.UE=1.
2. Задать длину слова с помощью бита USART_CR1.M (0 - 8 бит, 1 - 9 бит). Нужно учитывать, что бит чётности, если он используется, считается битом данных.
3. Задать количество стоп-битов с помощью битового поля USART_CR2.STOP (00 - 1 стоп-бит; 01 - 0.5; 10 - 2; 11 - 1.5 стоп бита).
4. Если используется DMA, установить бит USART_CR3.DMAT и сконфигурировать регистр DMA.
5. С помощью регистра USART_BRR выбрать скорость обмена данными.
6. Включить USART передатчик: USART_CR1.TE=1, в результате на выходе TX устройства USART появиться сигнал с уровнем лог. 1, "линия свободна" (если соответствующий вывод микроконтроллера настроен как выход для альтернативной функции).
7. Настроить вывод микроконтроллера, к которому привязан выход TX данного устройства USART как выход для альтернативной функции.
8. Записать байт посылаемых данных в регистр USART_DR для того, чтобы начать передачу. Запись в регистр сбрасывает флаг USART_SR.TXE (регистр передаваемых данных пуст). Когда байт будет помещён в передающий сдвигающий регистр, флаг будет снова установлен в 1 и если разрешены прерывания при установке флага (установлен бит USART_CR1.TXEIE), будет сгенерировано прерывание. Это означает, что можно записать следующий байт в USART_DR. Следует повторять действие для каждого байта предназначенных для передачи данных (записывать очередной байт данных после установки флага TXE).
9. После записи последнего байта нужно дождаться установки флага USART_SR.TC, который сигнализирует о том, что последний фрейм отправлен и передача завершена. После этого можно отключить USART или остановить процессор без риска нарушения передачи последнего фрейма.
Для приёма данных по одному байту выполняют конфигурирование USART и используемых GPIO, после чего производится чтение из регистра USART_DR, если он содержит принятые данные (наличие данных определяем по флагу USART_SR.RXNE). Используется следующий алгоритм.
0. Включить тактирование для устройства USART и используемых GPIO.
1. Включить USART: USART_CR1.UE=1.
2. Задать длину слова с помощью бита USART_CR1.M (0 - 8 бит, 1 - 9 бит). Нужно учитывать, что бит чётности, если он используется, считается битом данных.
3. Задать количество стоп-битов с помощью битового поля USART_CR2.STOP (00 - 1 стоп-бит; 01 - 0.5; 10 - 2; 11 - 1.5 стоп бита).
4. Если используется DMA, установить бит USART_CR3.DMAR и сконфигурировать регистр DMA.
5. С помощью регистра USART_BRR выбрать скорость обмена данными.
6. Настроить соответствующий входу RX вывод микроконтроллера как цифровой вход.
7. Включить USART приёмник: USART_CR1.RE=1. Приёмник начинает ждать получения страт-бита на входе RX.
8. Когда байт будет получен, будет установлен USART_SR.RXNE флаг. Это сигнализирует о том, что получение фрейма в приёмный сдвигающий регистр завершено, данные из сдвигающего регистра переданы в регистр RDR и регистр RDR может быть прочитан путём чтения из USART_DR. Если установлен бит USART_CR1.RXNEIE, то будет сгенерировано прерывание.
При приёме данных могут устанавливаться флаги NF (при обнаружении шума в сигнале), FE (ошибка фрейма, например, если не получены стоп-биты), PE (ошибка чётности); контроль над наличием шума может быть отключён, контроль чётности отключён по умолчанию.
Установленный после получения данных, флаг USART_SR.RXNE сбрасывается при чтении из регистра USART_DR. Если используется DMA, то флаг RXNE также устанавливается при получении каждого байта и сбрасывается, когда DMA считывает байт из USART_DR.
Данные должны быть считаны из USART_DR до того, как завершится приём следующего фрейма, иначе произойдёт ошибка переполнения. В случае переполнения устанавливается флаг USART.ORE. Установленный флаг говорит о том, что, по меньшей мере, 1 байт был потерян. Если установлен флаг USART.RXNE, то RDR содержит действительные данные, которые должны быть считаны чтением из регистра USART_DR. Если же флаг RXNE сброшен, это означает, что данные уже считаны - это может произойти, если чтение происходит как раз в тот момент, когда получен (и тут же потерян) новый фрейм. Вообще, в случае переполнения, данные в регистре RDR не теряются, теряются данные в приёмном сдвигающем регистре.
Приём посылки Break воспринимается как ошибка фрейма, после получения Break требуется произвести считывание из USART_DR для сброса флага FE. Переход линии в состояние "свободна" (Idle) после приёма порции данных воспринимается как получение "символа" Idle; при этом устанавливается флаг USART_SR.IDLE, а если установлен бит USART_CR1.IDLEIE, то также генерируется прерывание. Для сброса флага USART_SR.IDLE требуется произвести считывание из USART_DR.
Не следует отключать приёмник USART в процессе получения фрейма, иначе получаемый в данный момент байт будет потерян.
В этом примере микроконтроллер в ответ на каждый полученный от компьютера байт отправляет инвертированный байт. Компьютер последовательно посылает байты от 0 до 255 и проверяет правильность каждого ответа, после чего выводит сообщение о количестве ошибок. Программа может использоваться для проверки работоспособности линии связи.
Здесь можно скачать исходные коды в виде проекта VC6.0 (для компьютера) и в виде проекта Atollic TrueSTUDIO (для микроконтроллера). Проект для VC6.0 также может быть импортирован, например, в IDE Code::Blocks и скомпилирован с помощью MinGW.
Исходный код 1 для компьютера
Показать
/*
Пример программы для обмена данными с микроконтроллером чере COM-
порт с использованием схемы гальванической развязки (см.
http://www.rotr.info
). Предполагается, что на каждый полученный байт, микроконтроллер
сразу же отвечает инвертированным байтом.
Программа посылает микроконтроллеру байты со сзначениями от 0 до 255,
контролирует ответ, выводит сообщения об обнаруженных ошибках.
После перебора всех байтов, программа выводит сообщение об общем
количестве ошибок. Программа может использоваться для проверки
работоспособности линии связи и схемы гальванической развязки.
*/
#include <tchar.h>
#include <windows.h>
#include <iostream>
// Имя используемого COM-порта в Windows: \\.\COMx
#define COM_NAME _T("\\\\.\\COM2")
// Конфигурация COM-порта: скорость, размер слова,
// вариант контроля чётности, количество стоп-битов.
#define COM_BAUD_RATE 2400
#define COM_BYTE_SIZE 8
#define COM_PARITY NOPARITY
#define COM_STOP_BITS ONESTOPBIT
// Мксимальное время, отводимое для передачи/приёма одного байта, мс.
#define COM_TIME_OUT 100
class ComError
{
public:
DWORD err;
ComError(DWORD e):err(e) {}
};
// Объекты класса используются для обмена данными через
// COM-порт; реализованы методы конфигурирования;
// чтение/запись блокирующие с возможностью задать тайм-ауты.
class Com
{
private:
HANDLE hcom;
public:
Com():hcom(INVALID_HANDLE_VALUE) {}
~Com()
{
try
{
close();
}
catch(...){}
}
void open(const _TCHAR *name)
{
close();
hcom=CreateFile(name, GENERIC_READ|GENERIC_WRITE, 0, 0,
OPEN_EXISTING, 0, 0);
if(hcom==INVALID_HANDLE_VALUE)
throw ComError(GetLastError());
}
void close()
{
if(hcom==INVALID_HANDLE_VALUE)
return;
if(CloseHandle(hcom)==0)
throw ComError(GetLastError());
hcom=INVALID_HANDLE_VALUE;
}
void config(DWORD baud_rate, BYTE byte_size=8, BYTE parity=NOPARITY, BYTE stop_bits=ONESTOPBIT)
{
DCB dcb;
memset(&dcb, 0, sizeof dcb);
dcb.DCBlength=sizeof dcb;
dcb.BaudRate=baud_rate;
dcb.fBinary=1;
dcb.fParity=parity!=NOPARITY;
// DTR: enable = лог. 0 = +V
dcb.fDtrControl=DTR_CONTROL_ENABLE;
// RTS: disable = лог. 1 = -V
dcb.fRtsControl=RTS_CONTROL_DISABLE;
dcb.ByteSize=byte_size;
dcb.Parity=parity;
dcb.StopBits=stop_bits;
if(SetCommState(hcom, &dcb)==0)
throw ComError(GetLastError());
}
void get_timeouts(COMMTIMEOUTS &timeouts)
{
if(GetCommTimeouts(hcom, &timeouts)==0)
throw ComError(GetLastError());
}
void set_timeouts(COMMTIMEOUTS &timeouts)
{
if(SetCommTimeouts(hcom, &timeouts)==0)
throw ComError(GetLastError());
}
DWORD read(char *buf, DWORD size)
{
DWORD numofbytes=0;
if(ReadFile(hcom, buf, size, &numofbytes, 0)==0)
throw ComError(GetLastError());
return numofbytes;
}
DWORD write(const char *buf, DWORD size)
{
DWORD numofbytes=0;
if(WriteFile(hcom, buf, size, &numofbytes, 0)==0)
throw ComError(GetLastError());
return numofbytes;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
try
{
Com com;
// Открываем COM-порт.
com.open(COM_NAME);
// Конфигурируем COM-порт, задаём:
// скорость, количество бит в слове, вариант контроля
// чётности, количество стоп-битов.
com.config(COM_BAUD_RATE, COM_BYTE_SIZE, COM_PARITY, COM_STOP_BITS);
// Задаём тайм-ауты для операций чтения и записи в
// расчёте на 1 байт данных.
COMMTIMEOUTS timeouts;
com.get_timeouts(timeouts);
timeouts.ReadTotalTimeoutMultiplier=COM_TIME_OUT;
timeouts.WriteTotalTimeoutMultiplier=COM_TIME_OUT;
com.set_timeouts(timeouts);
// Проверка канала связи: передаём байт на UART MCU,
// MCU должен ответить инвертированным байтом.
// Контролируем ответ MCU для всех возможных 256 байтов.
char ch=0; // Байт для передачи.
unsigned int cnt=0; // Счётчик правильных передач.
bool last_transfer=false; // Успешность предыдущего обмена.
do
{
DWORD n=com.write(&ch, 1);
if(n!=1)
{
if(last_transfer)
std::cout<<std::endl;
last_transfer=false;
std::cout<<(int)(unsigned char)ch<<
": Timeout. The 'write' request was not completed."<<std::endl;
continue;
}
char answer=0;
n=com.read(&answer, 1);
if(n!=1)
{
if(last_transfer)
std::cout<<std::endl;
last_transfer=false;
std::cout<<(int)(unsigned char)ch<<
": Timeout. The 'read' request was not completed."<<std::endl;
continue;
}
char not_answer=~answer;
if(not_answer==ch)
{
last_transfer=true;
std::cout<<"*";
cnt++;
}
else
{
if(last_transfer)
std::cout<<std::endl;
last_transfer=false;
std::cout<<((int)ch&0xFF)<<": expected "<<((int)~ch&0xFF)<<"; recieved data: "<<((int)answer&0xFF)<<std::endl;
}
}while(++ch!=0);
std::cout<<std::endl;
std::cout<<"Error count: "<<256-cnt<<std::endl;
com.close();
}
catch(ComError &e)
{
std::cout<<"Error: "<<e.err<<std::endl;
}
std::cout<<"Press Enter to exit"<<std::endl;
std::cin.get();
return 0;
}
Исходный код 1 для MCU
Показать
// VECT_TAB_SRAM
// The symbol VECT_TAB_SRAM needs to be defined when building the project
// if code has been located to RAM and interrupts are used.
#include <stm32f10x.h>
// Будем использовать устройство UART2 микроконтроллера STM32F100RB.
// Если не использовать переназначения выводов, то для
// подключения используются (не FT-выводы):
// TX: PA2 (pin 16 для корпуса LQFP64);
// RX: PA3 (pin 17 для корпуса LQFP64).
// Используются настройки USART: 8 бит данных во фрейме;
// без контроля чётности; 1 стоп-бит; 16-кратный оверсэмплинг
// (бит OVER8=0).
// Скорость передачи данных:
#define BAUD_RATE 2400
extern "C" void USART2_IRQHandler()
{
// В этой функции определённо не хватает обработки
// ошибочных ситуаций.
if(USART2->SR&USART_SR_RXNE) // Если байт получен...
{
// Читаем полученный байт.
char d=~USART2->DR;
// Если регистр для передаваемых данных не пуст,
// ждём пока он освободится.
while((USART2->SR&USART_SR_TXE)==0) {}
// Отправляем инвертированный полученный байт.
USART2->DR=d;
}
}
int main()
{
// Включаем тактовый сигнал для используемых устройств.
RCC->APB1ENR|=RCC_APB1ENR_USART2EN;
RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;
// Включаем USART (бит USART_CR1_UE=1);
// используем слово длиной 8 бит (бит USART_CR1_M=0);
// используем 1 стоп-бит (биты USART_CR2_STOP=00).
USART2->CR1=USART_CR1_UE;
// Задаём скорость передачи (и приёма) с учётом используемого
// по умолчанию значения бита OVER8=0.
USART2->BRR=SystemCoreClock/BAUD_RATE;
// Включаем передатчик USART.
USART2->CR1|=USART_CR1_TE;
// Выход TX (PA2) - push-pull выход для альтернативной функции:
// MODE: bx10 (выход до 2 МГц), CNF: bx10 (push-pull, alt).
// Вход RX (PA3) - цифровой вход с подтягивающим к высокому
// уровню резистором: MODE: bx00 (вход);
// CNF: bx10 (с подтягивающим резистором); GPIO_ODR_ODR3=1 (к высокому уровню).
GPIOA->BSRR=GPIO_BSRR_BS3;
uint32_t mask=~(GPIO_CRL_MODE2|GPIO_CRL_CNF2|
GPIO_CRL_MODE3|GPIO_CRL_CNF3);
GPIOA->CRL=GPIOA->CRL&mask|
(GPIO_CRL_MODE2_1|GPIO_CRL_CNF2_1|
GPIO_CRL_CNF3_1);
// Настраиваем контроллер исключений:
// задаём приоритет прерывания (устанавливаем минимальный
// для данного процессора) и разрешаем обработку прерывания
// от USART2.
NVIC_SetPriority (USART2_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
NVIC_EnableIRQ(USART2_IRQn);
// Включаем приёмник и разрешаем прерывание при получении символа.
USART2->CR1|=USART_CR1_RE|USART_CR1_RXNEIE;
while(true);
}
В этом примере микроконтроллер в ответ на любую строку, полученную от компьютера, отвечает сообщением "Aha!", за исключением строки "Hello, MCU!", в ответ на которую микроконтроллер отправляет компьютеру строку "Hello, PC!". Имейте в виду, в строке после запятой идёт пробел.
Исходный код 2 для компьютера
Для работы с COM-портом в программе используется тот же самый класс, что и в первом примере. Он только вынесен в отдельный h-файл, чтобы не загромождать основной файл программы.
Показать
// file: com_port.h
#pragma once
#include <tchar.h>
#include <windows.h>
class ComError
{
public:
DWORD err;
ComError(DWORD e):err(e) {}
};
class Com
{
private:
HANDLE hcom;
public:
Com():hcom(INVALID_HANDLE_VALUE) {}
~Com()
{
try
{
close();
}
catch(...){}
}
void open(const _TCHAR *name)
{
close();
hcom=CreateFile(name, GENERIC_READ|GENERIC_WRITE, 0, 0,
OPEN_EXISTING, 0, 0);
if(hcom==INVALID_HANDLE_VALUE)
throw ComError(GetLastError());
}
void close()
{
if(hcom==INVALID_HANDLE_VALUE)
return;
if(CloseHandle(hcom)==0)
throw ComError(GetLastError());
hcom=INVALID_HANDLE_VALUE;
}
void config(DWORD baud_rate, BYTE byte_size=8, BYTE parity=NOPARITY, BYTE stop_bits=ONESTOPBIT)
{
DCB dcb;
memset(&dcb, 0, sizeof dcb);
dcb.DCBlength=sizeof dcb;
dcb.BaudRate=baud_rate;
dcb.fBinary=1;
dcb.fParity=parity!=NOPARITY;
// DTR: enable = лог. 0 = +V
dcb.fDtrControl=DTR_CONTROL_ENABLE;
// RTS: disable = лог. 1 = -V
dcb.fRtsControl=RTS_CONTROL_DISABLE;
dcb.ByteSize=byte_size;
dcb.Parity=parity;
dcb.StopBits=stop_bits;
if(SetCommState(hcom, &dcb)==0)
throw ComError(GetLastError());
}
void get_timeouts(COMMTIMEOUTS &timeouts)
{
if(GetCommTimeouts(hcom, &timeouts)==0)
throw ComError(GetLastError());
}
void set_timeouts(COMMTIMEOUTS &timeouts)
{
if(SetCommTimeouts(hcom, &timeouts)==0)
throw ComError(GetLastError());
}
DWORD read(char *buf, DWORD size)
{
DWORD numofbytes=0;
if(ReadFile(hcom, buf, size, &numofbytes, 0)==0)
throw ComError(GetLastError());
return numofbytes;
}
DWORD write(const char *buf, DWORD size)
{
DWORD numofbytes=0;
if(WriteFile(hcom, buf, size, &numofbytes, 0)==0)
throw ComError(GetLastError());
return numofbytes;
}
};
// ***************************************************************
// file: com_port.cpp
/*
Пример программы для обмена данными с микроконтроллером чере COM-
порт с использованием схемы гальванической развязки (см.
http://www.rotr.info
).
Программа считывает строку, введённую пользователем в консоли и
отправляет её микроконтроллеру. Строка - ответ от микроконтроллера
выводится на экран.
Для выхода из программы следует ввести строку "exit".
*/
#include "com_port.h"
#include <tchar.h>
#include <iostream>
#include <vector>
// Имя используемого COM-порта в Windows: \\.\COMx
#define COM_NAME _T("\\\\.\\COM2")
// Конфигурация COM-порта: скорость, размер слова,
// вариант контроля чётности, количество стоп-битов.
#define COM_BAUD_RATE 2400
#define COM_BYTE_SIZE 8
#define COM_PARITY NOPARITY
#define COM_STOP_BITS ONESTOPBIT
// Мксимальное время, отводимое для передачи/приёма одного байта, мс.
#define COM_TIME_OUT 100
int _tmain(int argc, _TCHAR* argv[])
{
try
{
Com com;
// Открываем COM-порт.
com.open(COM_NAME);
// Конфигурируем COM-порт, задаём:
// скорость, количество бит в слове, вариант контроля
// чётности, количество стоп-битов.
com.config(COM_BAUD_RATE, COM_BYTE_SIZE, COM_PARITY, COM_STOP_BITS);
// Задаём тайм-ауты для операций чтения и записи в
// расчёте на 1 байт данных.
COMMTIMEOUTS timeouts;
com.get_timeouts(timeouts);
timeouts.ReadTotalTimeoutMultiplier=COM_TIME_OUT;
timeouts.WriteTotalTimeoutMultiplier=COM_TIME_OUT;
com.set_timeouts(timeouts);
// Цикл обмена строками.
const unsigned int buf_size=128;
std::vector<char> buf(buf_size);
bool f=true;
while(f)
{
std::cout<<"Enter a line: ";
std::cin.getline(&buf[0], buf_size);
std::cin.clear(std::cin.rdstate()&~std::ios::failbit);
f=strcmpi(&buf[0], "exit")!=0;
DWORD size=strlen(&buf[0])+1;
DWORD n=com.write(&buf[0], size);
if(n<size)
std::cout<<"Timeout error. The 'write' request was not completed."<<std::endl;
// Длина ответа заранее неизвестна; считываем по 1 байту
// до получения символа конца строки - нулевого байта или
// до заполнения буфера.
for(unsigned int i=0; i<buf_size; i++)
{
n=com.read(&buf[i], 1);
if(n!=1)
{
std::cout<<"Timeout error. The 'read' request was not completed."<<std::endl;
buf[i]=0;
}
if(buf[i]==0)
break;
}
// Принудительное усечение строки для предотвращения
// ошибки из-за переполнения буфера.
buf[buf_size-1]=0;
std::cout<<"Response: "<<&buf[0]<<std::endl;
}
com.close();
}
catch(ComError &e)
{
std::cout<<"Error: "<<e.err<<std::endl;
}
return 0;
}
Исходный код 2 для MCU
Показать
// VECT_TAB_SRAM
// The symbol VECT_TAB_SRAM needs to be defined when building the project
// if code has been located to RAM and interrupts are used.
#include <stm32f10x.h>
#include <string.h>
// Будем использовать устройство UART2 микроконтроллера STM32F100RB.
// Если не использовать переназначения выводов, то для
// подключения используются (не FT-выводы):
// TX: PA2 (pin 16 для корпуса LQFP64);
// RX: PA3 (pin 17 для корпуса LQFP64).
// Используются настройки USART: 8 бит данных во фрейме;
// без контроля чётности; 1 стоп-бит; 16-кратный оверсэмплинг
// (бит OVER8=0).
// Скорость передачи данных:
#define BAUD_RATE 2400
// Буфер для отправляемых через USART строк.
const uint32_t tbuf_size=16;
char tbuf[tbuf_size];
// Позиция очередного передаваемого символа в буфере;
// -1 - в буфере нет данных для передачи.
volatile int tpos=-1;
// Функция для отправки строки (помещает строку
// в буфер передачи и настравает позицию передачи).
// Возвращает объём помещённых в буфер данных (учитывая завершающий 0).
// Если буфер не пуст, буфер не модифицируется и
// функция сразу возвращает 0.
// Если строка слишком длинная для буфера, она усекается.
uint32_t send_str(const char *str)
{
if(tpos>=0)
return 0;
uint32_t len=strlen(str);
if(len>=tbuf_size)
len=tbuf_size-1;
memcpy(tbuf, str, len);
tbuf[len]=0;
tpos=0;
// Разрешаем прерывание в случае пустого регистра
// передаваемых данных.
USART2->CR1|=USART_CR1_TXEIE;
return len+1;
}
// Функция для обработки события - получения строки.
void on_receive_str(const char *str)
{
if(strcmpi(str, "Hello, MCU!")==0)
send_str("Hello, PC!");
else
send_str("Aha!");
}
extern "C" void USART2_IRQHandler()
{
// В этой функции определённо не хватает обработки
// ошибочных ситуаций.
// Буфер для принимаемой строки и указатель текущей позиции в нём.
const uint32_t rbuf_size=16;
static char rbuf[rbuf_size];
static uint32_t pos=0;
uint16_t sr=USART2->SR;
if(sr&USART_SR_RXNE) // Принят символ.
{
rbuf[pos]=USART2->DR;
if(pos==rbuf_size-1)
rbuf[pos]=0;
if(rbuf[pos]==0)
{
pos=0;
on_receive_str(rbuf);
}
else
pos++;
}
if(sr&USART_SR_TXE) // Есть возможность отправить символ.
{
// Если нечего передавать, отключаем генерацию прерывания
// при пустом регистре передаваемых данных.
if(tpos<0)
USART2->CR1&=~USART_CR1_TXEIE;
else
{
USART2->DR=tbuf[tpos];
if(tbuf[tpos]==0)
tpos=-1;
else
tpos++;
}
}
}
int main()
{
// Включаем тактовый сигнал для используемых устройств.
RCC->APB1ENR|=RCC_APB1ENR_USART2EN;
RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;
// Включаем USART (бит USART_CR1_UE=1);
// используем слово длиной 8 бит (бит USART_CR1_M=0);
// используем 1 стоп-бит (биты USART_CR2_STOP=00).
USART2->CR1=USART_CR1_UE;
// Задаём скорость передачи (и приёма) с учётом используемого
// по умолчанию значения бита OVER8=0.
USART2->BRR=SystemCoreClock/BAUD_RATE;
// Включаем передатчик USART.
USART2->CR1|=USART_CR1_TE;
// Выход TX (PA2) - push-pull выход для альтернативной функции:
// MODE: bx10 (выход до 2 МГц), CNF: bx10 (push-pull, alt).
// Вход RX (PA3) - цифровой вход с подтягивающим к высокому
// уровню резистором: MODE: bx00 (вход);
// CNF: bx10 (с подтягивающим резистором); GPIO_ODR_ODR3=1 (к высокому уровню).
GPIOA->BSRR=GPIO_BSRR_BS3;
uint32_t mask=~(GPIO_CRL_MODE2|GPIO_CRL_CNF2|
GPIO_CRL_MODE3|GPIO_CRL_CNF3);
GPIOA->CRL=GPIOA->CRL&mask|
(GPIO_CRL_MODE2_1|GPIO_CRL_CNF2_1|
GPIO_CRL_CNF3_1);
// Настраиваем контроллер исключений:
// задаём приоритет прерывания (устанавливаем минимальный
// для данного процессора) и разрешаем обработку прерывания
// от USART2.
NVIC_SetPriority (USART2_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
NVIC_EnableIRQ(USART2_IRQn);
// Включаем приёмник и разрешаем прерывание при получении символа.
USART2->CR1|=USART_CR1_RE|USART_CR1_RXNEIE;
while(true);
}
author: hamper; date: 2016-01-24; modified: 2016-01-27