MyTetra Share
Делитесь знаниями!
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;


Полный код доступен на гитхабе.


Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования