Информационный портал технической поддержки Центра проектирования интегральных микросхем |
В данной статье собраны основные ошибки, которые возникают при работе с 32-разрядными микроконтроллерами.
Очень часто возникают ошибки, когда настраивается какой-либо периферийный блок, при этом тактирование данного блока не разрешено в контроллере тактовых частот. Также возможна ситуация, когда тактирование сбрасывается в одном из подключенных файлов. Необходимо внимательно следить за последовательностью включения тактирования периферийного блока и его конфигурации. В фрагменте кода 1 приведена корректная настройка порта С.
Фрагмент кода 1 - Пример инициализации порта ввода-вывода
// 1 - Включаем тактирование порта С
RST_CLK_PCLKcmd (RST_CLK_PCLK_PORTC, ENABLE);
// 2 - Инициализируем порт в заданной конфигурации
PORT_StructInit(&GPIOInitStruct);
GPIOInitStruct.PORT_Pin = PORT_Pin_0;
GPIOInitStruct.PORT_OE = PORT_OE_OUT;
GPIOInitStruct.PORT_SPEED = PORT_SPEED_SLOW;
GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL;
PORT_Init(MDR_PORTC, &GPIOInitStruct);
Это тот случай, когда тактирование в программе задано после настройки периферии. На примере настройки портов рассмотрим ход выполнения программы:
Так как тактирование портов не было включено на момент их инициализации, то настройка портов не была произведена.
При запуске программы в режиме отладки происходит следующая последовательность действий:
В данном случае порты ввода-вывода функционируют корректно, поскольку их инициализация происходила при уже включенном тактировании.
Например, простейшая операция " i++; " происходит в несколько этапов:
В любой момент между этими шагами может возникнуть прерывание, в обработчике которого также возможно изменение переменной i. В этом случае при выходе из обработчика прерывания новое значение переменной i будет изменено в основной программе на шаге 3, при этом логика программы может быть нарушена.
В ядре Cortex M существуют операции для защищенного обращения к памяти LDREX и STREX. Информацию по их использованию можно найти, например, на официальном сайте Keil.
Вторым вариантом обращения к памяти может быть использование метода bit-band, который доступен для МК на базе Cortex M3/M4. Запись и стирание флагов в ячейках памяти происходит атомарными операциями типа чтение-модификация-запись.
Flash память МК может работать в двух режимах: в обычном режиме (доступ к памяти осуществляется через шины I Code и D Code) и в режиме программирования (доступ к памяти осуществляется через регистры контроллера Flash-памяти). В режиме программирования программный код должен выполняться из области системной шины (внешняя память) или ОЗУ. Выполнение программного кода из Flash-памяти в режиме программирования невозможно. При попытке доступа ядра к Flash-памяти, находящейся в режиме программирования, будет вызвано прерывание HardFault или BusFault в зависимости от настроек ядра (SCR регистры). Поэтому важно, чтобы при программировании Flash-памяти не возникало никаких прерываний, поскольку таблица векторов и обработчики прерываний по умолчанию расположены во Flash-памяти.
Чтобы запретить выбранное прерывание IRQn необходимо вызвать функцию NVIC_DisableIRQ(). При этом необходимо учитывать, что запрещение генерации запроса прерываний от системного таймера SysTick выполняется не в контроллере NVIC, а в регистре управления системным таймером.
Пример запрещения прерываний от системного таймера приведён в фрагменте кода 2.
Фрагмент кода 2 - Запрещение прерываний от системного таймера
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
Чтобы запретить сразу все прерывания, кроме HardFaut и NMI, необходимо выполнить специальную функцию, как показано в фрагменте кода 3.
Фрагмент кода 3 - Запрещение всех прерывания, кроме HardFaut и NMI
__disable_irq();
После работы с Flash-памятью необходимо вернуть разрешение прерываний.
Иногда возникает необходимость использовать выводы, совмещенные с интерфейсом JTAG и SWD (далее для краткости используется обозначение JTAG, но подразумевается JTAG и SWD). При использовании функций библиотеки SPL выводы, совмещенные с JTAG B, перенастроить не получится, так как для них по умолчанию установлена защита. Например, функция PORT_Init() проверяет конфигурируемые выводы на принадлежность к JTAG B и не даёт их переназначать. Разрешение данной проверки определено в файле MDR32FxQI_config.h с помощью макроопределения USE_JTAG_B, строка 80, как показано в фрагменте кода 4.
Фрагмент кода 4 - Макроопределения защиты выводов, совмещённых с JTAG, в файле MDR32FxQI_config.h
#if (defined(USE_MDR32F9Q2I) || defined (USE_MDR32FG16S1QI))
/* #define USE_JTAG_A */
#define USE_JTAG_B
#endif
Библиотечный файл MDR32FxQI_config.h защищен от записи, поэтому необходимо предварительно в свойствах файла снять атрибут "Только чтение" (Правая клавиша мыши -> Свойства -> Только чтение). Для снятия защиты с выводов, совмещённых с JTAG A или B необходимо закомментировать соответствующее макроопределение: USE_JTAG_A или USE_JTAG_B. После этого данными выводами можно управлять с помощью функций SPL.
В качестве примера можно посмотреть реализацию функций PORT_SetBits() или PORT_ResetBits() библиотеки SPL.
Для смены тактовой частоты на более высокую требуется совершить следующие операции:
При переходе на более низкую частоту, изменение значения Delay и SelectRI, LOW производят после смены частоты.
Таким образом, при смене тактовой частоты необходимо соблюдать следующие правила:
#include <MDR32FxQI_rst_clk.h>
#include <MDR32FxQI_eeprom.h>
#include <MDR32FxQI_power.h>
// Инициализация системы тактирования микроконтроллера
void CPU_Initialize (void)
{
// Сброс настроек системы тактирования
RST_CLK_DeInit();
// Инициализация генератора на внешнем кварцевом резонаторе (HSE = 8 МГц)
RST_CLK_HSEconfig (RST_CLK_HSE_ON);
if(RST_CLK_HSEstatus() != SUCCESS){
while (1);
}
// Инициализация блока PLL
// Настройка источника и коэффициента умножения PLL
// CPU_C1_SEL = HSE_CLK, PLLCPUo = HSE_CLK * 10 = 8 МГц * 10 = 80 МГц
RST_CLK_CPU_PLLconfig (RST_CLK_CPU_PLLsrcHSEdiv1, RST_CLK_CPU_PLLmul10);
// Включение PLL
RST_CLK_CPU_PLLcmd (ENABLE);
if(RST_CLK_CPU_PLLstatus() == ERROR) {
while (1);
}
// Подключение PLL к системе тактирования
// (CPU_C2_SEL = PLLCPUo = 80 МГц)
RST_CLK_CPU_PLLuse (ENABLE);
// Настройка коэффициента деления блока CPU_C3_SEL
// (CPU_C3_SEL = CPU_C2)
RST_CLK_CPUclkPrescaler (RST_CLK_CPUclkDIV1);
// Настройка числа тактов паузы Delay в контроллере Flash-памяти
// Тактовая частота до 100 МГц - Delay = 3
RST_CLK_PCLKcmd (RST_CLK_PCLK_EEPROM, ENABLE);
EEPROM_SetLatency(EEPROM_Latency_3);
RST_CLK_PCLKcmd (RST_CLK_PCLK_EEPROM, DISABLE);
// Настройка параметров регулятора напряжения SelectRI и LOW в контроллере BKP
// Тактовая частота 80 МГц
RST_CLK_PCLKcmd(RST_CLK_PCLK_BKP, ENABLE);
POWER_DUccMode(POWER_DUcc_upto_80MHz);
// Переключение тактовой частоты процессора на CPU_C3
// (HCLK = CPU_C3)
RST_CLK_CPUclkSelection (RST_CLK_CPUclkCPU_C3);
}
Это особенность компилятора Keil, заменяющего функционал printf() на инструкцию программной остановки BKPT для реализации механизма semihosting. При старте программы низкоуровневые библиотеки Си также выполняют инструкцию BKPT, что приводит к остановке исполнения программы. Чтобы Keil не реализовывал механизм semihosting необходимо выполнить один из указанных пунктов:
1) Исключить вызов printf() из проекта.
2) Описать функции, перенаправляющие стандартный поток ввода-вывода в требуемый интерфейс МК, например, как показано в статьях Printf через ITM и Printf через UART.
3) В настройках проекта "Options for Target -> Target" выбрать опцию "Use MicroLIB", которая позволяет использовать оптимизированную по размеру кода стандартную библиотеку Си, в которой исключен механизм semihosting. Подробнее про MicroLIB описано на официальном сайте Keil.
Иногда требуется перейти в функцию, расположенную по известному адресу в памяти. Если в коде это будет выражено так, как показано в фрагменте кода 6 или 7, то произойдёт вызов исключения HardFault:
Фрагмент кода 6 - Некорректный переход по заданному адресу на языке Си
// Адрес функции в памяти
#define BASE_ADDR_FUNC_IN_RAM 0x20005000
// Указатель на функцию в памяти по известному адресу
typedef void (*funcptr)();
funcptr funcInRAM = (funcptr) (BASE_ADDR_FUNC_IN_RAM);
// Вызов функции
funcInRAM();
LDR R0,=(0x20005000)
BX R0
Это происходит, потому что адрес перехода должен быть нечетным, чтобы указать ядру о переходе на инструкцию THUMB, а не ARM! Подробнее об этом описано на сайте ARM Info Center.
На самом деле при переходе в фрагментах кода 6 и 7 происходит исключение UsageFault, но данное исключение по сбросу запрещено, поэтому происходит вызов обработчика исключения HardFault. Разрешение исключений BusFault, MemManage fault и UsageFault выполняется в регистрах ядра SCB (System Control Block), как показано в фрагменте кода 8. О том, как работать с исключениями в Cortex-M3/M4 приведено в Application Note 209 от ARM.
Фрагмент кода 8 - Разрешение исключений BusFault, MemManage fault и UsageFault
#define SCB_SHCSR_USGFAULTENA (1 << 18)
#define SCB_SHCSR_BUSFAULTENA (1 << 17)
#define SCB_SHCSR_MEMFAULTENA (1 << 16)
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA;
SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA;
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA;
Таким образом, чтобы переход на заданный адрес произошел корректно, необходимо указывать в нулевом бите адреса перехода единицу, как показано в фрагментах кода 9 и 10.
// Адрес функции в памяти
#define BASE_ADDR_FUNC_IN_RAM 0x20005000
// Указатель на функцию в памяти по известному адресу
typedef void (*funcptr)();
funcptr funcInRAM = (funcptr) (BASE_ADDR_FUNC_IN_RAM + 1);
// Вызов функции
funcInRAM();
Фрагмент кода 10 - Корректный переход по заданному адресу на языке ассемблер
LDR R0,=(0x20005001)
BX R0
Сайт: | https://support.milandr.ru |
E-mail: | support@milandr.ru |
Телефон: | +7 495 221-13-55 |