====== Наиболее частые ошибки программирования ====== Здесь собраны основные ошибки, которые возникают при работе с микроконтроллерами //"Миландр"//. Если возникают проблемы с работоспособностью проекта, попробуйте бегло просмотреть представленные пункты. Возможно какие-то ответы помогут в решении задачи. =====Задание Тактирования===== **Тактирование всегда должно задаваться ПЕРЕД заданием конфигурации!** Пример инициализация порта: // 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