Инструменты пользователя

Инструменты сайта


prog:debug:mosterror

Наиболее частые ошибки программирования

Здесь собраны основные ошибки, которые возникают при работе с микроконтроллерами "Миландр". Если возникают проблемы с работоспособностью проекта, попробуйте бегло просмотреть представленные пункты. Возможно какие-то ответы помогут в решении задачи.

Задание Тактирования

Тактирование всегда должно задаваться ПЕРЕД заданием конфигурации!

Пример инициализация порта:

  //  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 - проверьте очередность включения тактирования и инизиализации!

Это тот случай, когда тактирование в программе задано после настройки периферии. На примере настройки портов, вот что здесь происходит:

  1. После Reset или PowerOn cтартует программа пользователя
  2. В программе происходит инициализация портов.
  3. В программе включается тактирование портов.

В итоге порты не инициализированы, поскольку тактирование не было включено на момент конфигурирования. Далее,

  1. Отладчик подключается и перезапускает программу в отладочном режиме.
  2. В программе происходит инициализация портов, но тактирование осталось включенным с прошлого запуска!
  3. В программе включается тактирование портов.

Теперь порты полностью рабочие, поскольку инициализация происходила при включенном тактировании.

Переменные и флаги в прерываниях

Нельзя изменять значение переменной в основном потоке и в прерываниях без атомарного доступа!

Например, простейшая операция i++ происходит в несколько этапов:

  1. В регистр R0 загружается значение i из ячейки памяти.
  2. Значение регистра увеличивается на 1.
  3. Значение регистра сохраняется в ячейку памяти i.

В любой момент между этими шагами может возникнуть прерывание, которое может так же модифицировать ячейку памяти, занятую переменной i. При выходе из прерывания это новое значение будет затерто, и логика программы может быть нарушена.

В ядре Cortex существуют операции для защищенного обращения к памяти LDREX и STREX. Информацию по использованию можно найти в интернете. Например, на сайте 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 должны выполняться из ОЗУ

Как расположить код в ОЗУ рассказано здесь - Расположение функций в ОЗУ, программирование 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. Форум

Переход на абсолютный адрес приводит к 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! (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  
prog/debug/mosterror.txt · Последние изменения: 2019/10/08 11:06 — vasco