48430

[i] Пример Echo и смена скорости UART

Дата последнего изменения: 30.11.2023 09:32:36
Материал из настоящей статьи, относящийся к микросхеме К1986ВЕ1QI, распространяется в том числе на микроконтроллеры К1986ВЕ1FI и К1986ВЕ1GI

В данной статье рассматривается пример настроек контроллера 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 - Дерево проекта

Основной код - main.c

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 - Uart.c

В файле 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);
}

Смена скорости UART

Функция смены скорости представлена в фрагменте кода 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;
}

В этой функции есть сразу несколько особенностей, которые будут рассмотрены ниже.

Особенность 1

Для выставления скорости обмена по UART необходимо знать скорость работы ядра, поэтому этот параметр также передается в данную функцию.

Дело в том, что в паке нет штатной функции смены скорости UART - эта скорость задается только при вызове UART_Init(). В UART_Init() частота ядра высчитывается программно функцией RST_CLK_GetClocksFreq(), файл MDR32F9Qx_rst_clk.c. Для этого используются:

  1. Значения всех ключей тракта тактирования;
  2. Значения HSE_Value, заданного в MDR32F9Qx_config.h.

Функция 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 будет работать на неверной частоте!

Особенность 2

ВАЖНО! При записи новых делителей необходимо соблюдать последовательность записи регистров - IBRD, FBRD и завершающий LCR_H!

Как указано в спецификации, данные регистры образуют общий 30-разрядный регистр, который обновляется по стробу, формируемому при записи LCR_H. То есть при смене скорости запись в регистр LCR_H должна быть завершающей!

Особенность 3

При работе с 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.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. Но есть два важных момента:

  1. Выборка команд из EEPROM не может происходить быстрее, чем 25МГц, поэтому необходимо выставлять задержку доступа к EEPROM при работе ядра на больших частотах. Ядро останавливается на время этой задержки, пока считывается очередная порция инструкций. За раз из EEPROM извлекается 16 байт, где может быть закодировано от 4 до 8 инструкций процессора. Установка задержки происходит функцией EEPROM_SetLatency(), при этом предварительно на блок EEPROM подается тактирование.
  2. При работе на высоких частотах необходимо регулировать параметры внутреннего регулятора напряжения ядра(LDO). В спецификации это параметры SelectRI и LOW в регистре REG_0E, которые выбираются по частоте ядра и всегда должны быть равны. Раздельное управление этими параметрами возможно, но, согласно спецификации, не рекомендуется. Если используется много периферийных блоков, то случается, что без выставления данных параметров МК сбоит, поэтому рекомендуется выставлять эти параметры всегда - функция SetSelectRI().

    Фрагмент кода 10
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, алгоритм проверки следующий:

  1. Красная часть - работа на скорости 115200
    • Выставить начальную скорость 115200 и прочие параметры согласно настройкам UART в МК. Нажать Connect.
    • Послать символ 'A', в ответ получить символ 'A'. Эхо работает.
    • Запустить макрос М1 - в МК уходит посылка R$00, возвращается R<0>. Эхо подтверждение пришло, скорость в МК теперь другая.
  2. Синяя часть - работа на скорости 9600
    • Выставить скорость на 9600 и послать 'A', получить ответ 'A'. Скорость сменилась, эхо работает.
    • Запустить макрос М2 - в МК уходит посылка R$01, возвращаются бинарные 52 и 01. Бинарные данные показываются в правом столбце. Наблюдение бинарных данных удобнее, поскольку не все числа имеют "удобное" символьное представление.
  3. Зеленая часть - работа на скорости 56000
    • Выставить скорость на 56000 и послать 'A', получить ответ 'A'. Скорость сменилась, эхо работает.
    • Запустить макрос М3, в МК уходит посылка R$02, возвращаются бинарные 52 и 02.
  4. Красная часть - возвращение на скорость 115200
    • Выставить скорость на 115200 и посылать 'A', получить ответ 'A'. Скорость сменилась, эхо работает.

 


Сохранить статью в PDF

Файлы для скачивания

Теги

Была ли статья полезной?