Советы и вопросы по программированию на STM32

#embedded #arm #cortex-m3 #stm32

#встроенный #arm #cortex-m3 #stm32

Вопрос:

Я не смог найти в Интернете ни одного хорошего документа о программировании на STM32. Собственные документы STM не объясняют ничего, кроме функций регистрации. Я буду очень признателен, если кто-нибудь сможет объяснить мои следующие вопросы?

  1. Я заметил, что во всех примерах программ, которые предоставляет STM, локальные переменные для main () всегда определяются вне функции main () (со случайным использованием ключевого слова static). Есть ли для этого какая-либо причина? Должен ли я следовать аналогичной практике? Должен ли я избегать использования локальных переменных внутри main?

  2. У меня есть переменная gloabal, которая обновляется в дескрипторе тактового прерывания. Я использую ту же переменную внутри другой функции в качестве условия цикла. Не нужно ли мне обращаться к этой переменной, используя какую-либо форму операции атомарного чтения? Как я могу узнать, что тактовое прерывание не изменяет свое значение в середине выполнения функции? Должен ли я отменять прерывание синхронизации каждый раз, когда мне нужно использовать эту переменную внутри функции? (Однако мне это кажется крайне неэффективным, поскольку я использую это как условие цикла. Я считаю, что должны быть лучшие способы сделать это).

  3. Keil автоматически вставляет код запуска, который написан на ассемблере (т.е. startup_stm32f4xx.s). Этот код запуска содержит следующие инструкции импорта: ИМПОРТИРОВАТЬ SystemInit ИМПОРТИРОВАТЬ __main .В «C» это имеет смысл. Однако в C и main, и system_init имеют разные имена (например, _int_main__void). Как этот код запуска может все еще работать на C даже без использования «внешнего» C»» (я попробовал, и это сработало). Как компоновщик c (armcc —cpp) может связать эти инструкции с правильными функциями?

Комментарии:

1. Все эти вопросы практически не связаны между собой. Почему бы не разбить их на три разных вопроса в stackoverflow, чтобы, если кто-то знает ответ на один из них, но не на все, он все равно мог дать вам совет?

2. @DavidGrayson, я согласен. Как запоздалый читатель, я бы также предпочел, чтобы они были разделены, чтобы я мог более эффективно находить то, что меня интересует.

Ответ №1:

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

Я бы сделал копию глобальной задачи на переднем плане, которая ее использует.

 unsigned int myglobal;

void fun ( void )
{
   unsigned int myg;

   myg=myglobal;
  

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

Keil знает, что они делают, если они поддерживают C , то на системном уровне они это разработали. Я не использую Keil, я использую gcc и llvm для микроконтроллеров, подобных этому.

Редактировать:

Вот пример того, о чем я говорю

https://github.com/dwelch67/stm32vld/tree/master/stm32f4d/blinker05

stm32 используя прерывания на основе таймера, обработчик прерываний изменяет переменную, совместно используемую с задачей переднего плана. Задача переднего плана делает один снимок общей переменной (для каждого цикла) и, при необходимости, использует снимок более одного раза в цикле, а не общую переменную, которая может изменяться. Я понимаю, что это C, а не C , и я использую gcc и llvm, а не Keil. (обратите внимание, что llvm имеет известные проблемы с оптимизацией жестких циклов while, очень старая ошибка, не знаю, почему они не заинтересованы в ее исправлении, llvm работает для этого примера).

Комментарии:

1. Спасибо за ответ. Однако у меня все еще есть один вопрос, который меня беспокоит. В программировании на основе ОС мы предполагаем, что даже один оператор присваивания (т. Е. a = b) не является атомарным. Следовательно, если «b» может измениться вне потока, мы должны использовать какой-то мьютекс. Во встроенном программировании (с подпрограммами прерывания) разве это не так? Даже если «b» можно изменить в обработчике прерываний, могу ли я предположить, что «a = b» является атомарной операцией? Могу ли я с уверенностью предположить, что «a» равно либо прошлому, либо текущему значению «b»? (В программировании на основе ОС «a» может быть чем угодно, не связанным с «b»)

2. Я не согласен с первым предложением dwelch. Когда вы меняете переменную с локальной на глобальную, вы увеличиваете использование оперативной памяти, потому что глобальная переменная занимает оперативную память постоянно, тогда как локальная переменная занимает оперативную память только тогда, когда функция находится в области видимости, и даже тогда она может храниться в регистре, а не в оперативной памяти. Поскольку вы увеличиваете использование оперативной памяти, это увеличивает вероятность столкновения вашего стека с вашими данными (переполнение стека).

3. @David-Grayson у вас есть «контроль» над использованием оперативной памяти, и это очень детерминировано, если вы используете глобальные переменные, с локальными, что требует гораздо больше работы и может варьироваться с незначительными изменениями. если вы готовы выполнить этот анализ, тогда дерзайте. Я, конечно, согласен, что локальные дают вам «больше оперативной памяти» в том смысле, что вы часто используете ее повторно и получаете намного больше переменных, доступных вашей программе, и тому подобное.

4. @user460153 Я должен был упомянуть, что разделяемую переменную может потребоваться объявить как volatile, чтобы гарантировать, что компилятор действительно считывает ее из оперативной памяти (все зависит от того, как выглядит ваш код). Когда вы считываете a = b и гарантируете, что b считывается из ОЗУ, а не из регистра переднего плана, И память — это то, что можно прочитать за одну передачу (32 бита или меньше, возможно 64 бита, но я сомневаюсь, что у этой штуки 64-разрядная шина AXI / amba) ЗАТЕМ вы получите полный образец этой переменной…

5. @user460531 да, это может произойти после или до того, как обработчик изменит его. Важно это или нет, зависит от конструкции вашей системы. Если оба пользователя общего ресурса выполняют чтение-изменение-запись этого элемента, то да, вам обоим нужно использовать атомарную операцию, и вам придется управлять этим с помощью стандартных методов, отключать прерывания, блокировки и т.д. Если один читает, а другой читает-изменяет-записывает, то вам это может и не понадобиться, зависит от того, как работает ваш код. Если это цикл, то в следующий раз вы получите новое значение.

Ответ №2:

Вопрос 1: Локальные переменные

Пример кода, предоставленный ST, не отличается особой эффективностью или элегантностью. Они выполняют свою работу, но иногда для того, что они делают, нет веских причин.

В общем, вы всегда хотите, чтобы ваши переменные имели как можно меньшую область видимости. Если вы используете переменную только в одной функции, определите ее внутри этой функции. Добавьте ключевое слово «static» к локальным переменным тогда и только тогда, когда вам нужно, чтобы они сохраняли свое значение после выполнения функции.

В некоторых встроенных средах, таких как архитектура PIC18 с компилятором C18, локальные переменные намного дороже (больше места в программе, медленнее время выполнения), чем глобальные. В Cortex M3 это неверно, поэтому вы можете свободно использовать локальные переменные. Проверьте список сборок и убедитесь сами.

Вопрос 2. Совместное использование переменных между прерываниями и основным циклом

Люди написали целые главы, объясняющие ответы на эту группу вопросов. Всякий раз, когда вы разделяете переменную между основным циклом и прерыванием, вы обязательно должны использовать для нее volatile ключевые слова. Доступ к переменным, состоящим из 32 или менее бит, возможен атомарно (если они не смещены).

Если вам нужно получить доступ к переменной большего размера или двум переменным одновременно из основного цикла, то вам придется отключить тактовое прерывание при обращении к переменным. Если ваше прерывание не требует точного определения времени, это не будет проблемой. Когда вы повторно включите прерывание, оно автоматически сработает, если это необходимо.

Вопрос 3: основная функция в C

Я не уверен. Вы можете использовать arm-none-eabi-nm (или то, что nm называется в вашем наборе инструментов) в вашем объектном файле, чтобы посмотреть, какое имя символа присваивает компилятор C main() . Я бы поспорил, что компиляторы C воздерживаются от искажения функции main именно по этой причине, но я не уверен.

Ответ №3:

Пример кода STM не является примером надлежащей практики кодирования, он просто предназначен для демонстрации использования их стандартной периферийной библиотеки (предполагая, что это те примеры, о которых вы говорите). В некоторых случаях может случиться так, что переменные объявляются внешними по отношению к main() , поскольку доступ к ним осуществляется из контекста прерывания (разделяемая память). Также, возможно, существует вероятность того, что это было сделано таким образом просто для того, чтобы позволить отладчику просматривать переменные из любого контекста; но это не причина копировать метод. Мое мнение о примере кода STM заключается в том, что он, как правило, довольно плох даже в качестве примера кода, не говоря уже о точке зрения разработки программного обеспечения.

В этом случае ваша переменная тактового прерывания является атомарной, если она 32-битная или меньше, если вы не используете семантику чтения-изменения-записи с несколькими записывающими устройствами. Независимо от этого, у вас может быть один писатель и несколько читателей. Это верно для данной конкретной платформы, но не обязательно универсально; ответ может отличаться, например, для 8 или 16-разрядных систем или для многоядерных систем. Переменная должна быть объявлена volatile в любом случае.

Я использую C на STM32 с Keil, и проблем нет. Я не уверен, почему вы думаете, что точки входа на C разные, их здесь нет (Keil ARM-MDK v4.22a). Запускающий код вызывает, SystemInit() который, например, инициализирует PLL и синхронизацию памяти, затем вызывает, __main() который выполняет глобальную статическую инициализацию, затем вызывает конструкторы C для глобальных статических объектов перед вызовом main() . Если сомневаетесь, просмотрите код в отладчике. Важно отметить, что __main() это не main() функция, которую вы пишете для своего приложения, это оболочка с различным поведением для C и C , но которая в конечном итоге вызывает вашу main() функцию.