====== Наиболее частые ошибки программирования ======
Здесь собраны основные ошибки, которые возникают при работе с микроконтроллерами //"Миландр"//. Если возникают проблемы с работоспособностью проекта, попробуйте бегло просмотреть представленные пункты. Возможно какие-то ответы помогут в решении задачи.
=====Задание Тактирования=====
**Тактирование всегда должно задаваться ПЕРЕД заданием конфигурации!**
Пример инициализация порта:
// 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);
Очень часто возникают ошибки связанные с тем, что настраивается какая-то периферия, а тактирование на нее не задано. Либо может быть так, что тактирование сбрасывается в каком-то из подключенных файлов. Необходимо быть внимательнее в данном вопросе.
====Программа работает с отладчиком, но не работает при рестарте====
**Если программа работает с отладчиком, но не работает после //Reset / PowerOn// - проверьте очередность включения тактирования и инизиализации!**
Это тот случай, когда тактирование в программе задано после настройки периферии. На примере настройки портов, вот что здесь происходит:
- После //Reset// или //PowerOn// cтартует программа пользователя
- В программе происходит инициализация портов.
- В программе включается тактирование портов.
В итоге порты не инициализированы, поскольку тактирование не было включено на момент конфигурирования. Далее,
- Отладчик подключается и перезапускает программу в отладочном режиме.
- В программе происходит инициализация портов, но **тактирование осталось включенным с прошлого запуска**!
- В программе включается тактирование портов.
Теперь порты полностью рабочие, поскольку инициализация происходила при включенном тактировании.
=====Переменные и флаги в прерываниях=====
**Нельзя изменять значение переменной в основном потоке и в прерываниях без атомарного доступа!**
Например, простейшая операция i++ происходит в несколько этапов:
- В регистр R0 загружается значение i из ячейки памяти.
- Значение регистра увеличивается на 1.
- Значение регистра сохраняется в ячейку памяти i.
В любой момент между этими шагами может возникнуть прерывание, которое может так же модифицировать ячейку памяти, занятую переменной i. При выходе из прерывания это новое значение будет затерто, и логика программы может быть нарушена.
В ядре Cortex существуют операции для защищенного обращения к памяти LDREX и STREX. Информацию по использованию можно найти в интернете. Например, на сайте [[http://www.keil.com/support/man/docs/armasm/armasm_dom1361289875835.htm|Keil]].
Вторым вариантом может быть использование bit-band обращения к памяти. Запись и стирание флагов в ячейках происходит атомарными операциями.
=====Работа с EEPROM, прерывания и системный таймер=====
При работе с Flash памятью процессор выполняет функции, расположенные в ОЗУ. Нельзя исполнять команды из Flash и в тоже время туда писать. При такой попытке процессор свалится в Hard Fault. Поэтому важно, чтобы при работе с Flash памятью не возникало никаких прерываний. Поскольку обработчики прерываний по умолчанию расположены в памяти Flash, то переход исполнения из ОЗУ в итоге вызовет выход процессора в Hard Fault.
Выключить прерывания от NVIC функцией NVIC_DisableIRQ() в данной ситуации бывает недостаточно.
**Если используется системный таймер и активированы прерывания от него, то при работе с EEPROM его необходимо отключить отдельно!**
Сделать это можно таким образом:
// Выключение системного таймера
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
или
// Выключение всех прерываний с изменяемым приоритетом. Равносильно __set_PRIMASK(1);
// Но NMI и HardFault остаются активны!
__disable_irq();
После работы с EEPROM необходимо вернуть разрешение прерываний.
**Функции работы с EEPROM должны выполняться из ОЗУ**
Как расположить код в ОЗУ рассказано здесь - [[prog:start:code_to_ram|Расположение функций в ОЗУ, программирование EEPROM]].
=====Выводы, совмещенные с JTAG=====
Иногда возникает необходимость использовать выводы, совмещенные с JTAG. Например в 1986ВЕ92У настраивается работа с выводами PD0 и PD1, которые совмещены с выводами Jtag_B. Но переключить состояние этих ножек не удается никакими командами SPL.
Дело в том, что функции SPL работы с портами, например PORT_Init(), проверяют выводы на принадлежность к JTAG_B и не дают их переназначать. Такое поведение задается по умолчанию. Чтобы отключить данную проверку, необходимо в файле //MDR32F9Qx_config.h// отключить макро-определение USE_JTAG_B. Строка 80.
#if (defined(USE_MDR1986VE9x) || defined (USE_MDR1901VC1T))
/* #define USE_JTAG_A */
#define USE_JTAG_B - То, что нужно закомментировать!
#endif
Библиотечный файл MDR32F9Qx_config.h защищен от записи, поэтому необходимо предварительно в проводнике снять с файла атрибут Read-Only (Правая клавиша мыши - Свойства - Только чтение). После этого данными выводами можно управлять как всеми прочими выводами GPIO.
**Помните, что после переопределения выводов, совмещенных с JTAG, этот интерфейс JTAG не будет работать. Программа при запуске будет переопределять эти выводы, и прошить МК через данный интерфейс будет невозможно.**
Для загрузки программы придется использовать второй Jtag, например JTAG_A в данном примере, или UART.
**Чтобы сохранить работоспособность Jtag, необходимо в начале функции //main// вставить пустой цикл на пару секунд!**
Этот цикл даст некоторую задержку, перед переопределением выводов (сменой частоты и прочих операций, которые могут "убить" работоспособность МК). В это время подключенный Jtag успеет перехватить управление и остановить исполнение программы. Таким образом сохраняется возможность стереть программу, зашить новую либо войти в режим отладки - даже если выводы Jtag в программе используются по другому назначению.
=====Запись в порты совмещенные с выводами JTAG и SWD=====
**В биты выводов Jtag регистра регистра PORT->RXTX можно писать только нули!**
При записи в регистр PORT->RXTX необходимо сбрасывать биты используемые в отладочных интерфейсах. Если этого не сделать, то работа данных интерфейсов будет нарушена, а отладка невозможна!
Для примера можно посмотреть как это делается в функциях //PORT_SetBits()// или //PORT_ResetBits()// библиотеки SPL.
=====Смена тактовой частоты=====
Для смены тактовой частоты на **более высокую** требуется совершить следующие операции:
* Переключиться с умножителя на другой источник, например HSI - Мультиплексор С3.
* Перенастроить умножитель PLL и дождаться пока он выйдет в рабочий режим.
* Настроить EEPROM_Delay до переключения на более высокую частоту.
* Настроить поля SelectRI и LOW в регистре MDR_BKP->REG_0E, если указано в спецификации.
* Переключиться обратно и работать на новой частоте - переключить мультиплексор С3.
При переходе на **более низкую** частоту, изменение EEPROM_Delay и SelectRI, LOW производят после смены частоты.
Параметр задержки обращения к памяти программ - EEPROM_Delay можно заранее настроить на наибольшее значение, тогда перестройка не требуется.
Таким образом, важно запомнить, что
**До перехода на новую частоту мультиплексором C3 необходимо, чтобы новая частота была полностью сформирована, и МК был полностью готов к работе на ней.**
**На момент смены частоты, задержка доступа к EEPROM и задание токов в батарейном домене должны соответствовать максимальной частоте из старого и нового значения.**
====Пример инициализации тактирования в 1986ВЕ9х====
// Инициализация системы тактирования микроконтроллера
void CPU_Initialize (void)
{
// Сброс настроек системы тактирования
RST_CLK_DeInit();
// Инициализация генератора на внешнем кварцевом резонаторе (HSE)
RST_CLK_HSEconfig (RST_CLK_HSE_ON);
while (RST_CLK_HSEstatus() != SUCCESS);
// Инициализация блока PLL
// Включение использования PLL
RST_CLK_CPU_PLLcmd (ENABLE);
// Настройка источника и коэффициента умножения PLL
// (CPU_C1_SEL = HSE)
RST_CLK_CPU_PLLconfig (RST_CLK_CPU_PLLsrcHSEdiv1,
RST_CLK_CPU_PLLmul10);
while (RST_CLK_CPU_PLLstatus() != SUCCESS);
// Подключение PLL к системе тактирования
// (CPU_C2_SEL = PLLCPUo)
RST_CLK_CPU_PLLuse (ENABLE);
// Настройка коэффициента деления блока CPU_C3_SEL
// (CPU_C3_SEL = CPU_C2)
RST_CLK_CPUclkPrescaler (RST_CLK_CPUclkDIV1);
// Использование процессором сигнала CPU_C3
// (HCLK = CPU_C3)
RST_CLK_CPUclkSelection (RST_CLK_CPUclkCPU_C3);
}
=====Отладка не доходит до main =====
Это особенность компилятора Keil заменяющего не реализованный функционал printf на инструкцию программной остановки BKPT. [[http://forum.milandr.ru/viewtopic.php?p=9073#p9073|Форум]]
=====Переход на абсолютный адрес приводит к HardFault=====
Иногда требуется перейти на функцию расположенную по известному адресу в памяти. Если в коде это выражают так, то происходит вылет в HardFault:
// Адрес функции в памяти
#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!** ([[http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka12544.html|ARM Info Center]])
На самом деле при переходе происходит исключение USAGE_Fault, но обработчик данного исключения по умолчанию выключен, поэтому выход происходит в HardFault. Включение некоторых обработчиков исключений можно сделать так:
#define SCB_SHCSR_USGFAULTENA (1 << 18)
#define SCB_SHCSR_BUSFAULTENA (1 << 17)
#define SCB_SHCSR_MEMFAULTENA (1 << 16)
SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA;
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA;
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA;
Итого, чтобы переход произошел правильно, код необходимо модифицировать так:
....
// Указатель на функцию в памяти по известному адресу
typedef void (*funcptr)();
funcptr funcInRAM = (funcptr) (BASE_ADDR_FUNC_IN_RAM + 1);
....
// Аналог в ассемблере
LDR R0,=(0x20005001)
BX R0