|
|||||||
STM32 с нуля. Часть 3. Моргаем светодиодом
Время создания: 11.09.2023 13:55
Автор: Vladimir Bezhenar
Текстовые метки: linux, stm32, stm, светодиод, pin, gpio, моргание, ассемблер, assembler
Раздел: Компьютер - Аппаратное обеспечение - Микроконтроллеры ARM
Запись: xintrea/mytetra_syncro/master/base/16944297419nnx5uv73l/text.html на raw.github.com
|
|||||||
|
|||||||
Моргание светодиодом - это традиционный hello world для микроконтроллеров. Это один из самых простых способов взаимодействия с окружающей средой без помощи отладчика. В этой части именно этим мы и займёмся. Сразу оговоримся, что эта часть и далее уже очень сильно зависят от конкретного процессора и даже платы. Все адреса приведены со ссылками на reference manual, что должно помочь в переводе кода на другие процессоры. На плате blue pill синий светодиод подключен катодом (плюсом) к напряжению на 3.3 вольта, а анодом (минусом) к выводу микроконтролера, обозначенному C13 / 2 / PC13 / TAMPERRTC. Большинство выводов микроконтроллера способны выполнять разные задачи, поэтому несколько названий. Теперь откроем Datasheet на микроконтроллер, и найдём этот вывод там. На плате видно, что у микросхемы имеется 48 выводов, по 12 на каждой стороне. Этому соответствует тип корпуса LQFP48, его можно увидеть в разделе 3, изображении 8. Там же видно, что у этого корпуса действительно есть вывод с номером 2 и функционалом PC13-TAMPER-RTC. Соответствие между платы и datasheet контролера мы провели и теперь знаем, с каким выводом будем работать. Для того, чтобы реализовать этот функционал, микроконтроллер имеет набор GPIO портов. Порт может быть сконфигурирован в режиме входа (input), в этом случае он определяет напряжение между соответствующим выводом и "землёй" в бинарном виде (0 это отсутствие напряжения, 1 это номинальное напряжение). Но нас интересует конфигурация порта в режиме выхода (output). В этом случае микроконтроллер для состояния 0 подключает вывод к земле, а для состояния 1 либо соединяет вывод с источником напряжения в режиме двухтактного выхода (push-pull output), либо отключает вывод от схемы в режиме выхода с открытым коллектором (open drain output). В применении к нашей схеме 0 соединит выход микроконтроллера с землёй, а значит и анод светодиода. Через светодиод потечёт ток и он включится. 1 в любом режиме отключит светодиод и он выключится. Интересующий нас вывод микроконтроллера называется PC13, это можно раскрыть как Port C 13. У микроконтроллера имеется несколько портов ввода-вывода и каждый порт способен работать с несколькими выводами. Другое обозначение порта C это GPIOC. Если посмотреть в Reference Manual, разделе 3.1 изображение 1 (System Architecture), можно увидеть, что устройство GPIOC подключено через шину APB2 (Advanced Peripheral Bus). Это важно понимать, чтобы потом найти нужные пункты в документации. При загрузке у микроконтроллера отключены практически все периферийные устройства. Поэтому первое, что нужно сделать, это включить порт GPIOC. Далее нужно настроить вывод C13 как выход с открытым коллектором. По сути это все подготовительные действия. После этого можно управлять выводом C13, включая и выключая светодиод. Для задержки между включением и выключением будем использовать "пустой" цикл, который займёт процессор работой на нужное число миллисекунд. Проект возьмём из второй части, переименовав loop в blink. Итак первое, что нужно сделать, это включить порт GPIOC. Для включения периферийных устройств в микроконтроллере имеется RCC (reset and clock control, управление сбросом и тактированием). Взаимодействие с RCC осуществляется через запись нужных значений в регистры RCC. Подробное описание этих регистров можно почитать в Reference Manual, разделе 8.3. В разделе 8.3.7 описан регистр RCC_APB2ENR, включающий устройства на шине APB2. В частности бит 4 IOPCEN отвечает именно за порт ввода-вывода C. Регистр RCC_APB2ENR имеет смещение 0x18 от начала адресного пространства, выделенного для RCC. Если посмотреть в Reference Manual, разделе 3.3, можно найти, что для RCC выделено адресное пространство 0x4002_1000-0x4002_13FF. Резюмируя: нам нужно проставить единицу в 4-й бит по адресу 0x4002_1000 + 0x18. На ассемблере это действие будет записываться следующим образом: # enable I/O port C clock ldr r0, =0x40021000 + 0x18 ldr r1, [r0] orr r1, r1, 0x00000010 str r1, [r0] Этому коду соответствует псевдокод # enable I/O port C clock r0 := 0x4002_1000 + 0x18; r1 := memory[r0]; r1[5] := 1; memory[r0] := r1; После этого нужно настроить вывод С13. В reference manual, разделе 9.2.2 описан регистр GPIOx_CRH со смещением 0x04, который позволяет настраивать этот вывод: биты 20 и 21 с названием MODE13 и биты 22 и 23 с названием CNF13. В разделе 9.1, таблице 21 подробно описаны все сочетания значений. Для нашего случая нужно установить CNF1 в 0, CNF0 в 1, MODE1 в 1, MODE0 в 0. Это настроит порт в режим general purpose output, open-drain, maximum output speed 2 MHz. В reference manual, разделе 3.3 можно найти адреса для GPIO Port C: 0x4001 1000 - 0x4001 13FF. Таким образом нам нужно модифицировать машинное слово по адресу 0x4001 1000 + 0x04. Для наглядности приведём то, что нам нужно сделать. Символ ? означает, что мы не меняем этот бит. +------------------------------ CNF13, bit 1 |+----------------------------- CNF13, bit 0 ||+---------------------------- MODE13, bit 1 |||+--------------------------- MODE13, bit 0 |||| 0x4001 1004: ???? ???? 0110 ???? ???? ???? ???? ???? На ассемблере это запишется так: # configure PC13 as open-drain output with 10 MHz speed ldr r0, =0x40011000 + 0x04 ldr r1, [r0] orr r1, r1, 0x00600000 bic r1, r1, 0x00900000 str r1, [r0] Этому коду соответствует псевдокод: # configure PC13 as open-drain output with 10 MHz speed r0 := 0x4001_1000 + 0x04; r1 := memory[r0]; r1[21] := 1; r1[22] := 1; r1[20] := 0; r1[23] := 0; memory[r0] := r1; Этого кода хватает, чтобы включить светодиод (т.к. по умолчанию порт имеет состояние 0). Для того, чтобы светодиод начал моргать, нужно ещё немножко кода. Управление портом выхода осуществляется через регистр GPIOx_ODR, имеющий смещение 0x0c. Порту 13, как несложно догадаться, соответствует бит 13. Для задержки используем пустой цикл из миллиона итераций, он занимает примерно половину секунды. В итоге у нас получился такой код для моргания: blink_loop: # wait loop ldr r0, =1000000 delay_loop: subs r0, r0, #1 bne delay_loop # toggle PC13 ldr r0, =0x40011000 + 0x0c ldr r1, [r0] eor r1, r1, 0x00002000 str r1, [r0] b blink_loop Ему соответствует псевдокод blink_loop: # wait loop r0 := 1000000; delay_loop: r0 := r0 - 1; if r0 != 0 goto delay_loop; # toggle PC13 r0 := 0x4001_1000 + 0x0c; r1 := memory[r0]; r1[13] := ~r1[13]; memory[r0] := r1; goto blink_loop; Полный код доступен на гитхабе. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|