Часть 1 Часть 2 Часть 3 Часть 4 Часть 5 Часть 6 Часть 7 Часть 8 Часть 9
Часть 3: мигаем светодиодом
Мигание светодиодом это традиционный 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 // RCC_APB2ENR
ldr r1, [r0]
orr r1, 1 << 4 // IOPCEN
str r1, [r0]
Этому коду соответствует псевдокод
// enable I/O port C clock
r0 := 0x4002_1000 + 0x18;
r1 := memory[r0];
r1[4] := 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 // GPIOC_CRH
ldr r1, [r0]
bic r1, 1 << 20 // MODE13:0
orr r1, 1 << 21 // MODE13:1
orr r1, 1 << 22 // CNF13:0
bic r1, 1 << 23 // CNF13:1
str r1, [r0]
Этому коду соответствует псевдокод
// configure PC13 as open-drain output with 10 MHz speed
r0 := 0x4001_1000 + 0x04;
r1 := memory[r0];
r1[20] := 0;
r1[21] := 1;
r1[22] := 1;
r1[23] := 0;
memory[r0] := r1;
Этого кода хватает, чтобы включить светодиод (т.к. по умолчанию порт имеет состояние 0).
Для того, чтобы светодиод начал мигать, нужно ещё немножко кода. Управление
портом выхода осуществляется через регистр GPIOx_ODR
, имеющий смещение 0x0c
.
Порту 13, как несложно догадаться, соответствует бит 13. Для задержки используем
пустой цикл из миллиона итераций, он занимает примерно половину секунды.
В итоге у нас получился такой код для мигания:
blink_loop:
// wait loop
ldr r0, =1000000
delay_loop:
subs r0, 1
bne delay_loop
// toggle PC13
ldr r0, =0x40011000 + 0x0c // GPIOC_ODR
ldr r1, [r0]
eor r1, 1 << 13 // ODR13
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;
Прошиваем его командой make flash
и если всё сделано правильно, то синий
светодиод замигает.
Полный код доступен на гитхабе.