Опубликован: 24.11.2024 | Доступ: свободный | Студентов: 2 / 0 | Длительность: 05:47:00
Лекция 4:

GPIO

< Лекция 3 || Лекция 4: 123456 || Лекция 5 >

Параметр gpio является указателем на struct metal_gpio. Эта функция сначала проверяет, действителен ли дескриптор gpio, а затем вызывает другую функцию с именем enable_output(), которая находится в таблице виртуальных функций в дескрипторе gpio.

Обратите внимание, что второй аргумент enable_output() - это маска, созданная путем сдвига константы 1 столько раз влево, сколько указано в аргументе pin (1 << pin). Это потому, что регистр output_en (как и практически любой другой регистр конфигурации) работает побитовым образом. Так, например, чтобы включить вывод для вывода 4 в GPIO0, требуемая маска равна (1 << 4) или 0x10.

32-разрядный регистр конфигурации для FE310

Рис. 3.8. 32-разрядный регистр конфигурации для FE310

Теперь мы ищем реализацию функции enable_output() чтобы убедиться, что она активна-high. Чтобы найти его, нам нужно проследить за структурой, к которой он принадлежит. Вот определение struct metal_gpio:

/*!
 * @struct metal_gpio
 * @brief The handle for a GPIO interface
 */
struct metal_gpio {
    const struct __metal_gpio_vtable *vtable;
};

Это обычная практика: использовать указатель на таблицу виртуальной функции в качестве первого элемента в структуре для дескриптора. В этом случае указатель vtable является единственным членом структуры.

Отлично, давайте посмотрим на определение struct __metal_gpio_vtable :

struct __metal_gpio_vtable {
    int (*disable_input)(struct metal_gpio *, long pins);
    int (*enable_input)(struct metal_gpio *, long pins);
    long (*input)(struct metal_gpio *);
    long (*output)(struct metal_gpio *);
    int (*disable_output)(struct metal_gpio *, long pins);
    int (*enable_output)(struct metal_gpio *, long pins);
    int (*output_set)(struct metal_gpio *, long value);
    int (*output_clear)(struct metal_gpio *, long value);
    int (*output_toggle)(struct metal_gpio *, long value);
    int (*enable_io)(struct metal_gpio *, long pins, long dest);
    int (*disable_io)(struct metal_gpio *, long pins);
    int (*config_int)(struct metal_gpio *, long pins, int intr_type);
    int (*clear_int)(struct metal_gpio *, long pins, int intr_type);
    struct metal_interrupt *(*interrupt_controller)(struct metal_gpio *gpio);
    int (*get_interrupt_id)(struct metal_gpio *gpio, int pin);
};

И это все, к чему нас приведет код, потому что это таблица указателей на функции (технически таблица виртуальных функций).

Продолжаем углубляться

Чтобы найти фактические функции для модуля GPIO, нам нужно изучить конфигурационную функцию metal_gpio_get_device(), которая вызывается в строке 46 (или рядом с ней) hello.c:

 
led0 = metal_gpio_get_device(0);

Оглядываясь назад, можно сказать, что название переменной led0 немного неправильное, поскольку оно, по-видимому, относится к выходной строке светодиода, но это не так. led0 на самом деле является дескриптором для всего устройства GPIO, которое содержит все функции и регистры для всех 19 контактов. Если бы мы хотели использовать другой светодиод в этом приложении, нам пришлось бы использовать ту же переменную led0 для ссылки на gpio0. Лучшим именем для этой переменной было бы gpio_0.

Двигаясь дальше, эта функция является единственной, определенной в ./freedom-metal/src/gpio.c. Вот код:

struct metal_gpio *metal_gpio_get_device(unsigned int device_num) {
    if (device_num > __MEE_DT_MAX_GPIOS) {
        return NULL;
    }
    return (struct metal_gpio *)__metal_gpio_table[device_num];
}

Функция возвращает указатель на запрошенное устройство GPIO. Помните, что наш микроконтроллер FE310 имеет только один экземпляр GPIO, которым является GPIO0. Вот почему вызов функции metal_gpio_get_device(0), поэтому он возвращает адрес metal_gpio_table, который определен в ./bsp/install/include/metal/machine.h:

struct __metal_driver_sifive_gpio0 *__metal_gpio_table[] = {
                              &__metal_dt_gpio_10012000};

Опять же, поскольку в FE310 есть только одно устройство GPIO, этот массив содержит только один элемент, и, похоже, мы приближаемся к нему из-за его названия: __metal_dt_gpio_10012000. Это определено в ./bsp/install/include/metal/machine/inline.h:

/* From gpio@10012000 */
struct __metal_driver_sifive_gpio0 __metal_dt_gpio_10012000 = {
    .gpio.vtable = &__metal_driver_vtable_sifive_gpio0.gpio,
};

Мы почти на месте. __metal_driver_vtable_sifive_gpio0 определена в ./freedom-metal/src/drivers/sifive_gpio0.c:

__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_gpio0) = {
    .gpio.disable_input = __metal_driver_sifive_gpio0_disable_input,
    .gpio.enable_input = __metal_driver_sifive_gpio0_enable_input,
    .gpio.input = __metal_driver_sifive_gpio0_input,
    .gpio.output = __metal_driver_sifive_gpio0_output,
    .gpio.disable_output = __metal_driver_sifive_gpio0_disable_output,
    .gpio.enable_output = __metal_driver_sifive_gpio0_enable_output,
    .gpio.output_set = __metal_driver_sifive_gpio0_output_set,
    .gpio.output_clear = __metal_driver_sifive_gpio0_output_clear,
    .gpio.output_toggle = __metal_driver_sifive_gpio0_output_toggle,
    .gpio.enable_io = __metal_driver_sifive_gpio0_enable_io,
    .gpio.disable_io = __metal_driver_sifive_gpio0_disable_io,
    .gpio.config_int = __metal_driver_sifive_gpio0_config_int,
    .gpio.clear_int = __metal_driver_sifive_gpio0_clear_int,
    .gpio.interrupt_controller =
__metal_driver_gpio_interrupt_controller,
    .gpio.get_interrupt_id = __metal_driver_gpio_get_interrupt_id,
};

Функция, которую мы ищем, - это __metal_driver_sifive_gpio0_enable_output() в том же исходном файле. И, наконец, вот оно:

int __metal_driver_sifive_gpio0_enable_output(struct metal_gpio *ggpio, long source) {
    long base = __metal_driver_sifive_gpio0_base(ggpio);
    __METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_OUTPUT_EN)) |= source;
    return 0
}

Вот оно, у вас получилось! Длинное выражение слева от присваивания OR - это регистр output_en, который задается вторым аргументом OR. Чтобы записать 1 в отдельный бит в слове, вы можете взять маску с 1 в нужной позиции бита и применить операцию OR к слову. Ну, а второй аргумент - это маска, которая была создана несколько шагов назад как (1 << pin).

Это означает, что вызов функции metal_gpio_enable_output(led0, 5) устанавливает бит 5 регистра output_en в GPIO0.

Итак, чтобы окончательно развеять все сомнения, регистр output_en действительно активен - высокий.

< Лекция 3 || Лекция 4: 123456 || Лекция 5 >