Информационный портал технической поддержки Центра проектирования интегральных микросхем |
В данной статье рассматривается пример настроек контроллера UART для реализации режима Echo (эхо) - микроконтроллер в ответ на принятое по UART слово будет выдавать это же слово обратно. Также будет реализована возможность изменения скорости обмена. Это аналогично смене скорости обмена в Bootloader-e (см. статью Тестируем Bootloader в режиме UART ).
Проект будет реализован с использованием микроконтроллера К1986ВЕ1QI. Благодаря официальному установочному паку, этот проект может быть легко пересобран для других микроконтроллеров, выпускаемых компанией "Миландр".
Проект для скачивания доступен в конце статьи.
Проект создается аналогично статье Создаем новый проект. Для работы с UART потребуется подключить блоки - Startup, EEPROM, PORT, RST_CLK, UART.
Код работы с UART вынесен в отдельный файл - Uart.c. Код задания тактирования вынесен в файл Clock.c. Это позволит в следующих проектах использовать функции, реализованные в данных файлах, что позволяет избегать дублирования кода и ускоряет разработку. Для подключения реализованных в *.с файлах функций создадим заголовочные файлы - Uart.h и Clock.h. Основной функционал примера как всегда будет реализован в main.c.
Дерево проекта изображено на рисунке 1.
Рисунок 1 - Дерево проекта
Cначала настраивается частота ядра на 128 МГц - реализация вынесена в Clock.c, поэтому необходимо подключить Clock.h. Затем настраивается блок UART и включаются прерывания от него. Реализация также вынесена в Uart.c (необходимо подключить Uart.h.)
Фрагмент кода 1
#include <MDR32FxQI_uart.h>
#include "Clock.h"
#include "Uart.h"
// Перечень возможных задач
typedef enum {tskNoTask, tskChangeRate} UART_Task;
// Текущая задача
UART_Task ActiveTask = tskNoTask;
// Частоты для теста смена скорости
const uint32_t UART_Rates[] = {9600, 56000, 115200};
// Тактовая частота ядра
#define PLL_MUL 16 // = RST_CLK_CPU_PLLmul16 + 1
#define CPU_FREQ HSE_Value * PLL_MUL
// 8MHz * 16 = 128MHz int main(void)
{
// Тактирование ядра
Clock_Init_HSE_PLL(PLL_MUL - 1);
// Инициализация UART
UART_Initialize(UART_Rates[2]);
UART_InitIRQ(1); while (1);
}
//... продолжение в фрагменте кода 2
Далее микроконтроллер работает в бесконечном цикле, пока не возникнет прерывание от UART. На исследовательской демоплате есть возможность задать джамперами подключение к внешнему разъему RS-232 интерфейса UART1 или UART2 (см. рисунок 2).
Рисунок 2 - Подключение к внешнему разъему RS-232 интерфейса UART1 или UART2 на демоплате для МК К1986ВЕ1QI
В коде выбор используемого UART_X происходит в файле Uart.h. Для универсальности оба обработчика прерываний (UART1_IRQHandler() и UART2_IRQHandler()) вызывают одну и ту же функцию обработки UART_Handler_RX_TX(). При таком решении не нужно править код main.c при смене интерфейса в файле Uart.h.
Фрагмент кода 2
//... продолжение
void UART_Handler_RX_TX(void)
{
uint16_t receivedData;
// Обработка прерывания по приему данных
if (UART_GetITStatusMasked (UART_X, UART_IT_RX) == SET)
{
// Сброс прерывания
UART_ClearITPendingBit (UART_X, UART_IT_RX);
// Получаем данные и отвечаем - ЭХО
receivedData = UART_ReceiveData (UART_X);
UART_SendData (UART_X, receivedData);
// Если активная задача - смена скорости
if (ActiveTask == tskChangeRate)
{
ActiveTask = tskNoTask;
// Если индекс скорости в заданных пределах, то меняем скорость
if (receivedData < 3)
UartSetBaud(UART_Rates[receivedData], CPU_FREQ);
}
// При получении символа 'R' следующим байтом ожидаем индекс новой скорости
if (receivedData == 'R')
ActiveTask = tskChangeRate;
}
// Обработка прерывания от передачи данных
if (UART_GetITStatusMasked(UART_X, UART_IT_TX) == SET)
{
// Сброс прерывания
UART_ClearITPendingBit (UART_X, UART_IT_TX);
}
}
void UART1_IRQHandler (void)
{
UART_Handler_RX_TX();
}
void UART2_IRQHandler (void)
{
UART_Handler_RX_TX();
}
// Конец
Обработчик прерывания один на прием и передачу слова, поэтому в UART_Handler_RX_TX() необходимо проверять, что явилось источником текущего прерывания. В рассматриваемом примере вся работа происходит в прерывании по приему. Каждое принятое слово посылается обратно - это так называемый режим "Эхо".
Далее это же принятое слово обрабатывается и проверяется, является ли оно командой. Под командой воспринимается символ 'R', за которым должен прийти индекс новой скорости обмена. Этот индекс скорости также эхом отправляется назад, и затем новая скорость применяется в UART - функция UartSetBaud().
Поскольку UART настроен на обмен 8-ми битными словами, то для передачи конкретной скорости (например, значения 115200) потребуется приемка нескольких байт, а это усложнит код. Поэтому в примере доступные скорости ограничены массивом UART_Rates[] = {9600, 56000, 115200}. Количество значений в данном случае не принципиально - их можно сделать и больше. При работе с этим примером важно запомнить, что скоростей всего 3, следовательно, в микроконтроллер можно будет посылать команды 'R' с индексами - 0, 1 и 2.
В файле Uart.h используется макроопределение USE_UART2, с помощью которого можно выбрать, какой интерфейс будет использоваться в Uart.c. На исследовательской демоплате с помощью джамперов было определено подключение к выводу RS-232 интерфейса UART2, поэтому далее приводится часть файла Uart.h, описывающая настройки, необходимые при работе с UART2.
Фрагмент кода 3
#define USE_UART2
#ifdef USE_UART2
#define UART_X MDR_UART2
#define UART_IRQ UART2_IRQn
#define UART_CLOCK RST_CLK_PCLK_UART2
#define UART_CLOCK_TX RST_CLK_PCLK_PORTD
#define UART_CLOCK_RX RST_CLK_PCLK_PORTD
#define UART_PORT_TX MDR_PORTD
#define UART_PORT_PinTX PORT_Pin_13
#define UART_PORT_FuncTX PORT_FUNC_MAIN
#define UART_PORT_RX MDR_PORTD
#define UART_PORT_PinRX PORT_Pin_14
#define UART_PORT_FuncRX PORT_FUNC_MAIN
#endif
Эти определения используются далее в функциях работы с UART в Uart.c.
Фрагмент кода 4
#include <MDR32FxQI_port.h>
#include <MDR32FxQI_rst_clk.h>
#include <MDR32FxQI_uart.h>
#include "Uart.h"
// Инициализация модуля UART
void UART_Initialize (uint32_t uartBaudRate)
{
// Структура для инициализации линий ввода-вывода
PORT_InitTypeDef GPIOInitStruct;
// Структура для инициализации модуля
UART UART_InitTypeDef UARTInitStruct;
// Разрешение тактирования портов и модуля
UART RST_CLK_PCLKcmd (UART_CLOCK | UART_CLOCK_TX | UART_CLOCK_RX , ENABLE);
// Общая конфигурация линий ввода-вывода
PORT_StructInit (&GPIOInitStruct);
GPIOInitStruct.PORT_SPEED = PORT_SPEED_MAXFAST;
GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL;
// Конфигурация и инициализация линии для приема данных
GPIOInitStruct.PORT_FUNC = UART_PORT_FuncRX;
GPIOInitStruct.PORT_OE = PORT_OE_IN;
GPIOInitStruct.PORT_Pin = UART_PORT_PinRX;
PORT_Init (UART_PORT_RX, &GPIOInitStruct);
// Конфигурация и инициализация линии для передачи данных
GPIOInitStruct.PORT_FUNC = UART_PORT_FuncTX;
GPIOInitStruct.PORT_OE = PORT_OE_OUT;
GPIOInitStruct.PORT_Pin = UART_PORT_PinTX;
PORT_Init (UART_PORT_TX, &GPIOInitStruct);
// Конфигурация модуля UART
UARTInitStruct.UART_BaudRate = uartBaudRate; // Скорость передачи данных
UARTInitStruct.UART_WordLength = UART_WordLength8b; // Количество битов данных в сообщении
UARTInitStruct.UART_StopBits = UART_StopBits1; // Количество STOP-битов
UARTInitStruct.UART_Parity = UART_Parity_No; // Контроль четности
UARTInitStruct.UART_FIFOMode = UART_FIFO_OFF; // Включение/отключение буфера
UARTInitStruct.UART_HardwareFlowControl = UART_HardwareFlowControl_RXE // Аппаратный контроль за передачей и приемом данных
| UART_HardwareFlowControl_TXE;
// Инициализация модуля UART
UART_Init (UART_X, &UARTInitStruct);
// Выбор предделителя тактовой частоты модуля
UART UART_BRGInit (UART_X, UART_HCLKdiv1);
// Выбор источников прерываний (прием и передача данных)
UART_ITConfig (UART_X, UART_IT_RX | UART_IT_TX, ENABLE);
// Разрешение работы модуля UART
UART_Cmd (UART_X, ENABLE);
}
В фрагменте кода 4 описывается инициализация UART2 - cначала настраиваются порты GPIO, через которые работает UART2, a затем выставляются параметры работы самого UART2.
Включение прерываний вынесено в отдельную функцию, чтобы была возможность отдельного использования.
Фрагмент кода 5
void UART_InitIRQ(uint32_t priority) // priority = 1
{
// Назначение приоритета аппаратного прерывания от UART
NVIC_SetPriority (UART_IRQ, priority);
// Разрешение аппаратных прерываний от UART
NVIC_EnableIRQ (UART_IRQ);
}
Функция смены скорости представлена в фрагменте кода 6.
Фрагмент кода 6
void UartSetBaud(uint32_t baudRate, uint32_t freqCPU)
{
uint32_t divider = freqCPU / (baudRate >> 2);
uint32_t CR_tmp = UART_X->CR;
uint32_t LCR_tmp = UART_X->LCR_H;
// Так сделано в Bootloader - сбоит!
// while ( !(UART_X->FR & UART_FLAG_TXFE) ); // wait FIFO empty
// Так работает!
while ( (UART_X->FR & UART_FLAG_BUSY) ); // wait BUSY
UART_X->CR = 0;
UART_X->IBRD = divider >> 6;
UART_X->FBRD = divider & 0x003F;
UART_X->LCR_H = LCR_tmp;
UART_X->CR = CR_tmp;
}
В этой функции есть сразу несколько особенностей, которые будут рассмотрены ниже.
Для выставления скорости обмена по UART необходимо знать скорость работы ядра, поэтому этот параметр также передается в данную функцию.
Дело в том, что в паке нет штатной функции смены скорости UART - эта скорость задается только при вызове UART_Init(). В UART_Init() частота ядра высчитывается программно функцией RST_CLK_GetClocksFreq(), файл MDR32F9Qx_rst_clk.c. Для этого используются:
Функция RST_CLK_GetClocksFreq() вычисляет также частоты USB, ADC, RTCHSI, RTCHSE. Поэтому вместо использования этой функции частота CPU задана снаружи.
На исследовательской демоплате установлен резонатор 8МГц, поэтому значение HSE_Value верно:
Фрагмент кода 7
<code> #define HSE_Value ((uint32_t)8000000) </code>
ВАЖНО! Если на пользовательской плате для HSE используется резонатор c частотой отличной от 8 МГц, то необходимо указать эту частоту в значении HSE_Value, файл MDR32F9Qx_config.h. Иначе делители будут считаться неправильно, и обмен по UART будет работать на неверной частоте!
ВАЖНО! При записи новых делителей необходимо соблюдать последовательность записи регистров - IBRD, FBRD и завершающий LCR_H!
Как указано в спецификации, данные регистры образуют общий 30-разрядный регистр, который обновляется по стробу, формируемому при записи LCR_H. То есть при смене скорости запись в регистр LCR_H должна быть завершающей!
При работе с Bootloader (Тестируем Bootloader в режиме UART) были ситуации, когда при запросе смены скорости, подтверждение команды приходило уже на новой скорости. Согласно спецификации же, сначала должно прийти подтверждение на старой скорости, а затем произойти смена скорости UART. Причем даже при записи новой скорости сразу за записью отправляемых данных в регистр UARTx→DR, аппаратная часть должна сначала завершить пересылку, а затем сменить скорость.
Но так не происходит - при смене скорости подтверждение приходит то на новой, то на старой скорости. Ответ на старой скорости приходит значительно реже.
Например, в некоторых исходных файлах смена скорости в начальном загрузчике происходит, как показано в фрагменте кода 8.
Фрагмент кода 8
// Обработчик команды смены скорости в main
case CMD_BAUD :
{
u32 tmp = UartReceiveParam(Uart); // Считывание новой скорости
if ( tmp == ~0L ) { err = ERR_CHN; break; }
UartSendByte(Uart, CMD_BAUD); //
Uart->DR = CMD_BAUD;
UartSetBaud(Uart, tmp);
break;
}
// Смена скорости
void UartSetBaud(_uart * uart, u32 divider)
{
while ( !(uart->FR & mask_UART_FR_TXFE) ); // Ответ CMD_BAUD "пролетает" FIFO и уходит в передатчик
uart->CR = 0; // Выключение UART - передача ответа не успела пройти
uart->IBRD = divider >> 6;
uart->FBRD = divider & 0x3F;
uart->LCR_H = (3 << offs_UART_LCR_H_WLEN);
uart->CR = UART_MODE_TEST;
}
Перед сменой скорости логично использовать проверку с ожиданием, что UART уже все отправил. В загрузочной программе для этого используется флаг опустошения буфера.
while ( !(UART_X->FR & UART_FLAG_TXFE) );
Но при записи одиночного слова в DR оно сразу проскакивает в FIFO и уходит в передатчик, то есть данный флаг не дает необходимой задержки перед сменой скорости. Поэтому был использован вариант с применением флага занятости UART и, как показали дальнейшие тесты, такой вариант работает.
while ( (UART_X->FR & UART_FLAG_BUSY) );
Если в начальном загрузчике исправить код, то смена скорости работала бы согласно спецификации.
Настройка тактирования реализована в файле Clock.c, функция Clock_Init_HSE_PLL(). Данная функция настраивает частоту ядра на работу от генератора HSE c использованием умножителя PLL, который передается во входном параметре. В примере используется максимальный коэффициент умножения PLL_MUL = 16 (main.c).
Фрагмент кода 9
#include <MDR32FxQI_port.h>
#include <MDR32FxQI_rst_clk.h>
#include <MDR32FxQI_eeprom.h>
#include <MDR32FxQI_config.h>
#include "Clock.h"
void Clock_Init_HSE_PLL(uint32_t PLL_Mul) // 128 MHz
{
RST_CLK_DeInit();
/* Enable HSE (High Speed External) clock */
RST_CLK_HSEconfig(RST_CLK_HSE_ON);
while (RST_CLK_HSEstatus() != SUCCESS);
/* Configures the CPU_PLL clock source */
RST_CLK_CPU_PLLconfig(RST_CLK_CPU_PLLsrcHSEdiv1, PLL_Mul);
/* Enables the CPU_PLL */
RST_CLK_CPU_PLLcmd(ENABLE);
while (RST_CLK_CPU_PLLstatus() == ERROR);
/* Enables the RST_CLK_PCLK_EEPROM */
RST_CLK_PCLKcmd(RST_CLK_PCLK_EEPROM, ENABLE);
/* Sets the code latency value */
if (PLL_Mul * HSE_Value < 25E+6)
EEPROM_SetLatency(EEPROM_Latency_0);
else if (PLL_Mul * HSE_Value < 50E+6)
EEPROM_SetLatency(EEPROM_Latency_1);
else if (PLL_Mul * HSE_Value < 75E+6)
EEPROM_SetLatency(EEPROM_Latency_2);
else if (PLL_Mul * HSE_Value < 100E+6)
EEPROM_SetLatency(EEPROM_Latency_3);
else if (PLL_Mul * HSE_Value < 125E+6)
EEPROM_SetLatency(EEPROM_Latency_4);
else //if (PLL_Mul * HSE_Value <= 150E+6)
EEPROM_SetLatency(EEPROM_Latency_5);
// Additional Supply Power
if (PLL_Mul * HSE_Value < 40E+6)
SetSelectRI(RI_till_40MHz);
else if (PLL_Mul * HSE_Value < 80E+6)
SetSelectRI(RI_till_80MHz);
else SetSelectRI(RI_over_80MHz);
/* Select the CPU_PLL output as input for CPU_C3_SEL */
RST_CLK_CPU_PLLuse(ENABLE);
/* Set CPUClk Prescaler */
RST_CLK_CPUclkPrescaler(RST_CLK_CPUclkDIV1);
/* Select the CPU clock source */
RST_CLK_CPUclkSelection(RST_CLK_CPUclkCPU_C3);
}
Код легче понять, если ориентироваться на рисунок 3.
Рисунок 3 - структурная блок-схема формирования тактовой частоты
В основном, код состоит из включения мультиплексоров С1, С2, С3 и PLL. Но есть два важных момента:
typedef enum {
RI_till_10KHz, RI_till_200KHz, RI_till_500KHz, RI_till_1MHz,
RI_Gens_Off,
RI_till_40MHz, RI_till_80MHz, RI_over_80MHz
} SelectRI;
void SetSelectRI(SelectRI extraI)
{
uint32_t temp;
RST_CLK_PCLKcmd(RST_CLK_PCLK_BKP, ENABLE);
temp = MDR_BKP->REG_0E & 0xFFFFFFC0;
temp |= (extraI << 3) | extraI;
MDR_BKP->REG_0E = temp;
}
В приведенном списке SelectRI присутствует значение RI_Gens_Off. Оно выставляется, когда тактирование снаружи микросхемы задается не резонатором, а внешним генератором. В таком варианте необходимость во внутреннем генераторе отпадает и частота напрямую (режим ByPass) идет на вход схемы тактирования, мультиплексор С1 вход HSE.
Резонатор генерирует синусоидальный сигнал, который далее генератором преобразуется в прямоугольные импульсы.
Для посылки и приема данных по UART используется программу Terminal v1.9b (Тестируем Bootloader в режиме UART).
Создадим три макроса для смены скорости:
// макрос М1 - смена скорости на 9600 R$00
// макрос М2 - смена скорости на 56000 R$01
// макрос М3 - смена скорости на 115200 R$02
Для проверки эхо будем посылается символ 'A' (рисунок 4).
Рисунок 4 - Окно программы Terminal
Согласно рисунку 4, алгоритм проверки следующий:
Сайт: | https://support.milandr.ru |
E-mail: | support@milandr.ru |
Телефон: | +7 495 221-13-55 |