GPIO
Параметр gpio является указателем на struct metal_gpio. Эта функция сначала проверяет, действителен ли дескриптор gpio, а затем вызывает другую функцию с именем enable_output(), которая находится в таблице виртуальных функций в дескрипторе gpio.
Обратите внимание, что второй аргумент enable_output() - это маска, созданная путем сдвига константы 1 столько раз влево, сколько указано в аргументе pin (1 << pin). Это потому, что регистр output_en (как и практически любой другой регистр конфигурации) работает побитовым образом. Так, например, чтобы включить вывод для вывода 4 в GPIO0, требуемая маска равна (1 << 4) или 0x10.
Теперь мы ищем реализацию функции 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 действительно активен - высокий.