Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Точки останова

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

Разбираем цикл светодиода

Вернёмся к дизассемблированному коду функции __cortex_m_rt_main. Нам нужны инструкции bl. bl расшифровывается как «branch and link» — это инструкции, которые вызывают другие функции. Нас интересуют вызовы функций set_high и set_low.

#![allow(unused)]
fn main() {
...
// Это вызов set_high()
0x100002f8 <+404>:   bl      0x10000c48 <rp235x_hal::gpio::eh1::{impl#1}::set_high<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::pull::PullDown>>
...
// Это вызов delay_ms()
0x10000306 <+418>:   bl      0x100006b8 <rp235x_hal::timer::{impl#7}::delay_ms<rp235x_hal::timer::CopyableTimer0>>
...
// Это вызов set_low()
0x1000030c <+424>:   bl      0x10000c38 <rp235x_hal::gpio::eh1::{impl#1}::set_low<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::pull::PullDown>>
...
// Это вызов delay_ms()
0x10000314 <+432>:   bl      0x100006b8 <rp235x_hal::timer::{impl#7}::delay_ms<rp235x_hal::timer::CopyableTimer0>>
...
}

Посмотрите на адреса слева: 0x100002f8 и 0x1000030c. Это адреса в памяти, где происходит управление светодиодом. По первому адресу вызывается set_high, по второму — set_low. Мы поставим точки останова на эти адреса, чтобы программа приостанавливалась прямо перед выполнением этих инструкций.

Установка точек останова в цикле

Установим первую точку останова. Введите в GDB:

(gdb) break *0x100002f8

Вы увидите: Breakpoint 1 at 0x100002f8: file src/main.rs, line 63.

Это значит, что GDB создал точку останова по этому адресу, и она соответствует строке 63 в нашем файле main.rs.

(gdb) break *0x1000030c

Вы увидите: Breakpoint 2 at 0x1000030c: file src/main.rs, line 65.

Теперь сбросим всё, чтобы начать заново:

monitor reset halt

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

Обзор регистров GPIO

Прежде чем продолжить, нужно объяснить, на что мы будем смотреть. Когда вы вызываете set_high или set_low в коде на Rust, на самом деле меняются определённые ячейки памяти. Эти ячейки называются регистрами, и они напрямую управляют оборудованием.

В чипе RP2350 есть регистр GPIO_OUT, который управляет всеми выводами GPIO. Его можно найти в даташите RP2350 (глава 3.1.11, страница 55) в разделе SIO (Single-cycle IO).

Вот где этот регистр находится в памяти:

  • Периферия SIO начинается с базового адреса 0xd0000000
  • Регистр GPIO_OUT находится по смещению 0x010 от этого базового адреса
  • Поэтому полный адрес: 0xd0000000 + 0x010 = 0xd0000010

Представьте GPIO_OUT как 32-битное число, где каждый бит управляет одним выводом GPIO. Бит 0 управляет GPIO0, бит 1 — GPIO1 и так далее. Бит 25 управляет GPIO25 — именно к нему подключён встроенный светодиод. Когда бит 25 равен 0, светодиод выключен. Когда бит 25 равен 1, светодиод включён.

Запуск до первой точки останова

Запустим программу, пока она не дойдёт до первой точки останова:

(gdb) continue

Когда точка останова сработает, GDB покажет примерно следующее:

Continuing.

Thread 1 hit Breakpoint 1, 0x100002f8 in pico_debug::__cortex_m_rt_main () at src/main.rs:63
63              led_pin.set_high().unwrap();

Программа остановилась прямо перед вызовом set_high. Это идеальный момент, чтобы посмотреть, что находится в регистре, до того как мы включим светодиод.

Проверка регистров GPIO перед set_high

Посмотрим, что сейчас находится в регистре GPIO_OUT:

(gdb) x/x 0xd0000010

Команда x/x означает «исследовать эту ячейку памяти и показать значение в шестнадцатеричном формате».

Скорее всего, вы получите сообщение об ошибке «Cannot access memory at address 0xd0000010». Это происходит потому, что GDB не знает о регистрах периферии по умолчанию. Нужно сказать GDB, что чтение из этой области памяти разрешено.

Делаем периферию SIO доступной в GDB

Чтобы это исправить, нужно сообщить GDB об области памяти периферии. Согласно даташиту RP2350, область SIO на самом деле простирается от 0xd0000000 до 0xdfffffff. Однако нам не нужно отображать всю область SIO — достаточно той части, которая покрывает регистры, с которыми мы будем работать.

Поэтому можно ввести:

(gdb) mem 0xD0000000 0xD0001000 rw nocache

Здесь мы отображаем примерно 4 КБ области SIO (от 0xD0000000 до 0xD0001000), что более чем достаточно, чтобы покрыть GPIO_OUT и другие регистры SIO, которые мы будем рассматривать при отладке.

Если хотите отобразить меньше и быть точнее, можно использовать:

(gdb) mem 0xD0000000 0xD0000100 rw nocache

Это дает всего 256 байт, которых хватает для всех основных регистров SIO, которые нам нужны, включая GPIO_OUT по адресу 0xD0000010. Главное — отобразить ровно столько памяти, сколько нужно для чтения нужных регистров, без отображения всей области SIO.

Теперь попробуйте снова прочитать GPIO_OUT:

(gdb) x/x 0xd0000010
0xd0000010:     0x00000000

Мы получили значение 0x00000000. Это значит, что все 32 бита равны нулю, то есть все выводы GPIO сейчас выключены. Наш светодиод выключен.

Продолжение до второй точки останова

Теперь продолжим выполнение и посмотрим, что происходит после выполнения set_high:

(gdb) continue
Continuing.

Thread 1 received signal SIGINT, Interrupt.
rp235x_hal::gpio::eh1::{impl#1}::set_high<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::pull::PullDown> (self=0x2007ffbd)
    at /home/implrust/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rp235x-hal-0.3.1/src/gpio/mod.rs:1549
1549            fn set_high(&mut self) -> Result<(), Self::Error> {

Мы были прерваны внутри функции set_high. Продолжим снова:

(gdb) continue
Continuing.

Thread 1 hit Breakpoint 2, pico_debug::__cortex_m_rt_main () at src/main.rs:65
65              led_pin.set_low().unwrap();

Теперь программа прошла через set_high и задержку и остановилась на второй точке останова, в строке 65, прямо перед вызовом set_low. Проверим GPIO_OUT ещё раз:

(gdb) x/x 0xd0000010
0xd0000010:     0x02000000

Значение изменилось с 0x00000000 на 0x02000000. К этому моменту вы также должны увидеть, что светодиод включился.

Разберём, что означает 0x02000000. В двоичном виде это 00000010 00000000 00000000 00000000. Если считать справа, начиная с 0, бит 25 теперь равен 1. Именно это сделал set_high: он установил бит 25 регистра GPIO_OUT, что включило GPIO25 и зажгло светодиод.

Продолжаем, чтобы увидеть set_low в действии

Теперь продолжим ещё раз, чтобы увидеть, что происходит при выполнении set_low. Но сначала отметим, что светодиод сейчас включён, а GPIO_OUT показывает 0x02000000 с битом 25, установленным в 1.

Продолжим:

(gdb) continue
Continuing.

Thread 1 received signal SIGINT, Interrupt.
rp235x_hal::gpio::eh1::{impl#1}::set_low<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::pull::PullDown> (self=0x2007ffbd)
    at /home/implrust/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rp235x-hal-0.3.1/src/gpio/mod.rs:1544
1544            fn set_low(&mut self) -> Result<(), Self::Error> {

Мы были прерваны внутри функции set_low. Продолжим снова:

(gdb) continue
Continuing.

Thread 1 hit Breakpoint 1, 0x100002f8 in pico_debug::__cortex_m_rt_main () at src/main.rs:63
63              led_pin.set_high().unwrap();

Программа прошла через set_low и задержку и вернулась к первой точке останова в строке 63. Проверим GPIO_OUT ещё раз:

(gdb) x/x 0xd0000010
0xd0000010:     0x00000000

Значение вернулось к 0x00000000. Бит 25 теперь равен 0, а значит, GPIO25 выключен и светодиод не горит. На вашей плате светодиод должен погаснуть.

Что мы узнали

Из наблюдений:

  • Когда мы вызываем led_pin.set_high(), бит 25 регистра GPIO_OUT меняется с 0 на 1 (0x00000000 → 0x02000000)
  • Когда мы вызываем led_pin.set_low(), бит 25 меняется с 1 на 0 (0x02000000 → 0x00000000)