STM Урок 201. Assembler. Первый проект. Команды MOV, LDR, STR, B. Часть 2



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

Давайте теперь в нашу процедуру Start добавим хотя бы одну строку с кодом

 

 

Мы используем здесь команду MOV, с помощью которой мы помещаем число 0 в регистр общего назначения R0.

С помощью данной команды мы можем заносить константы в регистры, а также содержимое одного регистра в другой. Если используется синтаксис Intel, то приёмником является первый операнд команды, а источником — второй. Если используется константа, то перед ней ставится знак решетки (#).

Теперь аналогичным образом занесём в другой регистр единицу

 

 

Теперь мы можем смело собирать код и прошивать контроллер.

Если мы зайдём в отладку, то мы увидим, что код наш при дизассемблировании не меняется вообще, то есть практически никак не транслируется

 

 

Также мы видим, что после выполнения второй команды в регистрах находятся нужные нам величины

 

 

Давайте посмотрим размер получившегося файла прошивки

 

 

Файл с прошивкой весит всего лишь 16 байт. Впечатляет, неправда ли? Это как раз два адреса — вершины стека и точки входа, а также две 32-битные команды. Откроем и посмотрим содержимое нашего файла или его машинный код

 

 

Так как слова у нас следуют младшим байтом вперёд, то в первом слове у нас число 0x20000400, что и есть вершина нашего стека, во втором — 0x08000009, что является адресом 9 байта в программе, находящейся во флеш-памяти, которая мапится у нас, как известно на адрес 0x08000000. 9 байт — это байт, следующий после наших двух слов с адресами. А дальше идут инструкции MOV. Можно конечно загнаться и разобрать биты данных инструкций в машинном коде, что, впрочем, я уже давно проделал, но, думаю, не стоит тратить на это драгоценное время. Вы это можете проделать самостоятельно, если будет интересно.

Также для того, чтобы наш контроллер постоянно был в работе, давайте добавим бесконечный цикл, для чего добавим сначала строку, в которой будет только метка

 

 

А затем добавим следующую команду

 

 

Данная команда с мнемоникой B — это безусловный переход (branch — ветвь) к метке. Таким образом, код теперь у нас будет зациклен на данной метке, и теперь между меткой и переходом мы можем писать такой код, который будет выполняться непрерывно в нашем бесконечном цикле.

Соберём наш код, и он прибавится ещё на 4 байта, а также посмотрим наш переход в отладчике, не забыв, конечно, перед этим прошить контроллер

 

 

Так как машинный код перехода составит всего лишь 16 байт, то общий код дополнится нулями для выравнивания до величины слова.

Теперь приступим к выполнению нашей основной задачи, по условию которой нам нужно зажечь светодиод, который подключен к ножке GPIO PC13. По хорошему, мы должны сначала настроить регулятор напряжения, FLASH, RCC, но мы этого делать не будем, есть же значения регистров по умолчанию, они нам для начала подойдут, настраивать мы будем только GPIO.

 

 

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

Для того, чтобы пользоваться регистрами контроллера и их битами, удобно было бы создать имена со значениями, для чего мы создадим отдельный файл, назвав его stm32f103C8.asm и поместив в каталог inc и наполнив для начала следующим содержимым

 

 

Объявление данных констант чем-то напоминает макросы в C.

Мы объявили константы для адресов начала области памяти для периферии, начала области, куда бэндятся биты регистров периферии, адрес регистра RCC_APB2ENR и номер бита IOPCEN в данном регистре.

Подключим наш файл в main.s в самом начале файла

 

 

Теперь дадим следующую команду нашему контроллеру (ну или ядру, даже и не знаю, как правильно сказать, скорее всего, АЛУ)

 

 

Данная команда LDR подобна команде MOV, только она копирует содержимое не регистра и не константы в регистр, а содержимое ячейки памяти, находящейся по определённому адресу.

Таким образом мы скопировали в регистр R2 значение, находящееся по адресу, который находится во втором операнде.

 

 

Только тут можно впасть в некоторое заблуждение. По идее, мы же хотим не значение, а сам адрес памяти, куда отображается посредством бит-бэндинга (см прошлый урок) бит регистра контроллера, поместить в регистр R2. Оно, в принципе, так и произойдёт, для этого и используется знак равно. То есть, код наш скомпонуется так, что адрес, нужный нам, будет находиться в определённом месте памяти FLASH, о котором мы даже не знаем, возможно для этого добавится ещё секция, нам этого знать даже и не нужно. И поэтому, в регистр R2 мы скопируем значение, находящееся уже по именно адресу, а значением этим и будет наш адрес области памяти, отображающей бит регистра контроллера. Звучит запутанно, но так оно и есть.

Давайте опять соберём код, прошьём контроллер и выполним данную команду

 

 

Мы видим, что в регистр R2 мы поместили значение, находящееся по адресу FLASH-памяти 0x08000014.

Посмотрим содержимое R2

 

 

Если произвести необходимые подсчёты, то это и будет адрес области памяти, на которое отобразится бит IOPCEN регистра контроллера RCC_APB2ENR.

Можно также это проверить, посмотрев содержимое данного регистра. Только пока что рано, мы только лишь занесли адрес, но значение туда ещё не посылали. Для этого мы добавим следующую команду в наш код

 

 

Данная команда STR обратна по своей функциональности команде LDR. Она посылает значение, хранящееся в регистре по адресу, находящемуся во втором операнде. То, что мы посылаем значение не в регистр R2, а в память, адрес которой находится в регистре R2, обозначают квадратные скобки, в которых данный регистр обозначен.

Соберём код, прошьём контроллер и установим точку останова на данной команде.

Мы увидим, что нужный нам бит в нужном регистре пока не установлен

 

 

Выполним нашу команду, прошагав по коду 1 шаг и увидим, что теперь бит установился

 

 

Теперь у нас тактирование порта GPIOC включено.

Следующая задача — включить бит 0 в битовом поле MODE13 регистра GPIOC_CRH, а также включить бит ODR13 регистра GPIOC_ODR. Для чего мы это делаем мы знаем из уроков по CMSIS и повторять это в который раз нет смысла.

Для выполнения данной задачи добавим константы для данных регистров в файле stm32f103C8.asm

 

 

Также добавим константы для номеров наших битов, которые нам надо будет включить

 

 

Вернёмся в файл main.s и включим бит 0 в поле MODE13 регистра CRH нашего порта также посредством бит-бэндинга

 

 

И затем включим бит 13 регистра ODR, что должно заставить ножку перейти в высокий уровень

 

 

Если мы соберём код сейчас и прошьём контроллер, то никакой светодиод у нас не засветится, потому что светодиод у нас подключен к питанию, а не к общему проводу. Поэтому нам нужно либо не использовать последние 2 команды, чтобы ножка осталась в низком уровне по умолчанию, либо занести в слово в памяти, отображающей бит ODR13, ноль. Чтобы занести ноль, мы можем вместо регистра R1 использовать регистр R0, так как в нём и находится ноль. Так и сделаем, исправив последнюю команду

 

STR R0, [R2]

 

Вот теперь будет всё нормально и наш светодиод будет светиться

 

 

Итак, на данном уроке мы начали изучать очень интересный язык — ассемблер. Научились создавать проект с использованием данного языка для контроллера STM32F103, изучили несколько команд, которые позволили написать пусть несложный, но уже вполне работоспособный код, который заставил светиться светодиод, подключенный к ножке порта контроллера.

Всем спасибо за внимание!

 

 

Предыдущая часть Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM Assembler. Первый проект. Команды MOV, LDR, STR, B

9 комментариев на “STM Урок 201. Assembler. Первый проект. Команды MOV, LDR, STR, B. Часть 2
  1. Дмитрий:

    Хотедось бы выразить уважение автору. Занимаюсь программированием на ассемблере микроконтроллеров семейства ARM Cortex-M3, до многого приходилось приходить самому. Сейчас возникают проблемы только с правильной настройкой I2C, поэтому буду ждать информацию по этому вопросу. Ещё раз, большое уважение автору 🙂

    • Александр:

      Дмитрий добрый день! Я начинающий но давно пишу программы для AVR на Ассемблере. Не могу понять зачем здесь директивы PROC и другие, если они ничего не меняют в коде программы, можно ли писать программу без них?

  2. зачем переходить ассемблер есть СИ

    • А мы никуда не переходим, мы его изучаем, чтобы понимать, что творится в процессоре в результате написания кода на СИ.
      Аналогичный вопрос можно задать «Зачем нужен СИ, если есть СИ++» или «Зачем нужен СИ++, если есть Питон и Ява».

      • A-Soft:

        Narod Stream, не слушайте негатив. Это те люди, которые ещё не исчерпали себя на си, либо не потянули в свое время ассемблер (как и я). Таких много.
        Рано или поздно они тоже придут к тому что ассемблер все таки нужен и иногда незаменим.

    • A-Soft:

      на си уже все разжёвано и врот положено.

  3. A-Soft:

    А можно несколько подробнее рассматривать команды, режимы адресации и приёмы в ассемблере?
    Я пытался сделать задержку (не таймер) чтобы помигать светодиодом, но пришлось прилично поплясать с бубном.
    1. Переход по сравнению (суффиксы команды B) — не понял я PM0056 table23, в интернете примеры только BEQ и BNE. Подсмотрел в окне дизассемблера функции HAL_Delay:
    CMP R3, R4
    BCC label1 ; именно BCC сделает переход если R3 < R4
    2. команда CMP работает только с 16-битными константами (определил "методом тыка", но явного указания в PM0056 не увидел), а мне надо 4000000 (если не включили тактирование от внешнего кварца, то работаем от HSI 8МГц). Тогда CMP надо сравнивать 32-битные регистры.
    3. команда MOV R4, #imm16 (PM0056 пункт 3.5.6) не принимает 32-битную константу, от 16-битной записывает в регистр только младшие 16-бит, а старшие обнуляет (определил экспериментально)…
    В общем вот кусок кода после инициализации порта:
    ; 4000000 записываю в регистр r4
    MOV R4, #0x0900 ; младшие 16 бит от 4000000
    MOVT R4, #0x003D ; старшие 16 бит от 4000000
    loop
    LDR R2, =(PERIPH_BB_BASE + (GPIOC_ODR — PERIPH_BASE) * 32 + GPIO_ODR_ODR13_N * 4)
    STR R1, [R2] ; "плюс" в PC13

    MOV R3, #0 ; обнуляем счётчик
    pause1
    ADD R3, 3 ; считаю что add, cmp и bcc съедают по одному такту, поэтому каждый раз увеличиваем R3 на 3
    CMP R3 , R4
    BCC pause1 ; если R3 < R4 то прыгаем на метку pause1 (пока не наберем 4000000 тактов)

    LDR R2, =(PERIPH_BB_BASE + (GPIOC_ODR — PERIPH_BASE) * 32 + GPIO_ODR_ODR13_N * 4)
    STR R0, [R2] ; "минус" в PC13

    MOV R3, #0
    pause2
    ADD R3, 3
    CMP R3 , R4
    BCC pause2

    B loop ; B — это безусловный переход (branch) на метку loop

    Точность, наверно, не самая высокая, но по крайней мере светодиод замигал.
    Подскажите, если есть лучшие варианты исполнения (CMP без R4+MOV+MOVT).

    • Pavel:

      Моя функция задержки на 4*n-1 тактов:
      __forceinline void delay_m4(uint32_t cnt){ //пауза 4*cnt-1 тактов контроллера
      __asm {
      loc1:
      subs cnt, 1
      bne loc1
      }
      }

      То же самое на СИ:
      __forceinline void delay_m4b(uint32_t cnt) //пауза 4*cnt-1 тактов контроллера
      {
      do{cnt—;}while(cnt != 0);
      }

      Возможно, для кого то это очевидно, но я попался на эти грабли. Если функция используется в разных модулях программы, то она должна быть определена в *.h файле. Если же функцию определить в СИ файле, а в h-файле описать прототип, то во всех модулях, кроме того в котором определена функция, она не будет инлайниться, так как компилятор не будет знать, что функция должна быть «инлайн».

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*