App accessor: init (#433)

This commit is contained in:
SG 2021-04-28 22:13:25 +10:00 committed by GitHub
parent dfcf0ea0eb
commit c3350990c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 779 additions and 0 deletions

View file

@ -0,0 +1,176 @@
#include "accessor-app.h"
#include <furi.h>
#include <api-hal.h>
#include <stdarg.h>
void AccessorApp::run(void) {
AccessorEvent event;
bool consumed;
bool exit = false;
notify_init();
wiegand.begin();
onewire_master.start();
scenes[current_scene]->on_enter(this);
while(!exit) {
view.receive_event(&event);
consumed = scenes[current_scene]->on_event(this, &event);
if(!consumed) {
if(event.type == AccessorEvent::Type::Back) {
exit = switch_to_previous_scene();
}
}
};
scenes[current_scene]->on_exit(this);
}
AccessorApp::AccessorApp()
: onewire_master{&ibutton_gpio} {
api_hal_power_insomnia_enter();
}
AccessorApp::~AccessorApp() {
api_hal_power_insomnia_exit();
}
AccessorAppViewManager* AccessorApp::get_view_manager() {
return &view;
}
void AccessorApp::switch_to_next_scene(Scene next_scene) {
previous_scenes_list.push_front(current_scene);
if(next_scene != Scene::Exit) {
scenes[current_scene]->on_exit(this);
current_scene = next_scene;
scenes[current_scene]->on_enter(this);
}
}
void AccessorApp::search_and_switch_to_previous_scene(std::initializer_list<Scene> scenes_list) {
Scene previous_scene = Scene::Start;
bool scene_found = false;
while(!scene_found) {
previous_scene = get_previous_scene();
for(Scene element : scenes_list) {
if(previous_scene == element || previous_scene == Scene::Start) {
scene_found = true;
break;
}
}
}
scenes[current_scene]->on_exit(this);
current_scene = previous_scene;
scenes[current_scene]->on_enter(this);
}
bool AccessorApp::switch_to_previous_scene(uint8_t count) {
Scene previous_scene = Scene::Start;
for(uint8_t i = 0; i < count; i++) {
previous_scene = get_previous_scene();
if(previous_scene == Scene::Exit) break;
}
if(previous_scene == Scene::Exit) {
return true;
} else {
scenes[current_scene]->on_exit(this);
current_scene = previous_scene;
scenes[current_scene]->on_enter(this);
return false;
}
}
AccessorApp::Scene AccessorApp::get_previous_scene() {
Scene scene = previous_scenes_list.front();
previous_scenes_list.pop_front();
return scene;
}
/***************************** NOTIFY *******************************/
void AccessorApp::notify_init() {
// TODO open record
const GpioPin* vibro_record = &vibro_gpio;
gpio_init(vibro_record, GpioModeOutputPushPull);
gpio_write(vibro_record, false);
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = PB3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
HAL_GPIO_Init(PB3_GPIO_Port, &GPIO_InitStruct);
}
void AccessorApp::notify_green_blink() {
api_hal_light_set(LightGreen, 0xFF);
delay(10);
api_hal_light_set(LightGreen, 0x00);
}
void AccessorApp::notify_green_on() {
api_hal_light_set(LightGreen, 0xFF);
}
void AccessorApp::notify_green_off() {
api_hal_light_set(LightGreen, 0x00);
}
void AccessorApp::notify_success() {
api_hal_light_set(LightBacklight, 0xFF);
hal_pwm_set(0.5, 1760 / 2, &htim2, TIM_CHANNEL_2);
notify_green_on();
delay(100);
hal_pwm_stop(&htim2, TIM_CHANNEL_2);
notify_green_off();
delay(100);
hal_pwm_set(0.5, 1760, &htim2, TIM_CHANNEL_2);
notify_green_on();
delay(100);
hal_pwm_stop(&htim2, TIM_CHANNEL_2);
notify_green_off();
}
/*************************** TEXT STORE *****************************/
char* AccessorApp::get_text_store() {
return text_store;
}
uint8_t AccessorApp::get_text_store_size() {
return text_store_size;
}
void AccessorApp::set_text_store(const char* text...) {
va_list args;
va_start(args, text);
vsnprintf(text_store, text_store_size, text, args);
va_end(args);
}
/*************************** APP RESOURCES *****************************/
WIEGAND* AccessorApp::get_wiegand() {
return &wiegand;
}
OneWireMaster* AccessorApp::get_one_wire() {
return &onewire_master;
}

View file

@ -0,0 +1,58 @@
#pragma once
#include <map>
#include <list>
#include "accessor-view-manager.h"
#include "scene/accessor-scene-start.h"
#include "helpers/wiegand.h"
#include <one_wire_master.h>
class AccessorApp {
public:
void run(void);
AccessorApp();
~AccessorApp();
enum class Scene : uint8_t {
Exit,
Start,
};
AccessorAppViewManager* get_view_manager();
void switch_to_next_scene(Scene index);
void search_and_switch_to_previous_scene(std::initializer_list<Scene> scenes_list);
bool switch_to_previous_scene(uint8_t count = 1);
Scene get_previous_scene();
void notify_init();
void notify_green_blink();
void notify_green_on();
void notify_green_off();
void notify_success();
char* get_text_store();
uint8_t get_text_store_size();
void set_text_store(const char* text...);
WIEGAND* get_wiegand();
OneWireMaster* get_one_wire();
private:
std::list<Scene> previous_scenes_list = {Scene::Exit};
Scene current_scene = Scene::Start;
AccessorAppViewManager view;
std::map<Scene, AccessorScene*> scenes = {
{Scene::Start, new AccessorSceneStart()},
};
static const uint8_t text_store_size = 128;
char text_store[text_store_size + 1];
WIEGAND wiegand;
OneWireMaster onewire_master;
};

View file

@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
class AccessorEvent {
public:
// events enum
enum class Type : uint8_t {
Tick,
Back,
};
// payload
union {
uint32_t menu_index;
} payload;
// event type
Type type;
};

View file

@ -0,0 +1,79 @@
#include "accessor-view-manager.h"
#include "accessor-event.h"
#include <callback-connector.h>
AccessorAppViewManager::AccessorAppViewManager() {
event_queue = osMessageQueueNew(10, sizeof(AccessorEvent), NULL);
view_dispatcher = view_dispatcher_alloc();
auto callback = cbc::obtain_connector(this, &AccessorAppViewManager::previous_view_callback);
// allocate views
submenu = submenu_alloc();
add_view(ViewType::Submenu, submenu_get_view(submenu));
popup = popup_alloc();
add_view(ViewType::Popup, popup_get_view(popup));
gui = static_cast<Gui*>(furi_record_open("gui"));
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
// set previous view callback for all views
view_set_previous_callback(submenu_get_view(submenu), callback);
view_set_previous_callback(popup_get_view(popup), callback);
}
AccessorAppViewManager::~AccessorAppViewManager() {
// remove views
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Submenu));
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Popup));
// free view modules
submenu_free(submenu);
popup_free(popup);
// free dispatcher
view_dispatcher_free(view_dispatcher);
// free event queue
osMessageQueueDelete(event_queue);
}
void AccessorAppViewManager::switch_to(ViewType type) {
view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type));
}
Submenu* AccessorAppViewManager::get_submenu() {
return submenu;
}
Popup* AccessorAppViewManager::get_popup() {
return popup;
}
void AccessorAppViewManager::receive_event(AccessorEvent* event) {
if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) {
event->type = AccessorEvent::Type::Tick;
}
}
void AccessorAppViewManager::send_event(AccessorEvent* event) {
osStatus_t result = osMessageQueuePut(event_queue, event, 0, 0);
furi_check(result == osOK);
}
uint32_t AccessorAppViewManager::previous_view_callback(void* context) {
if(event_queue != NULL) {
AccessorEvent event;
event.type = AccessorEvent::Type::Back;
send_event(&event);
}
return VIEW_IGNORE;
}
void AccessorAppViewManager::add_view(ViewType view_type, View* view) {
view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view);
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <furi.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include "accessor-event.h"
class AccessorAppViewManager {
public:
enum class ViewType : uint8_t {
Submenu,
Popup,
Tune,
};
osMessageQueueId_t event_queue;
AccessorAppViewManager();
~AccessorAppViewManager();
void switch_to(ViewType type);
void receive_event(AccessorEvent* event);
void send_event(AccessorEvent* event);
Submenu* get_submenu();
Popup* get_popup();
private:
ViewDispatcher* view_dispatcher;
Gui* gui;
uint32_t previous_view_callback(void* context);
void add_view(ViewType view_type, View* view);
// view elements
Submenu* submenu;
Popup* popup;
};

View file

@ -0,0 +1,10 @@
#include "accessor-app.h"
// app enter function
extern "C" int32_t app_accessor(void* p) {
AccessorApp* app = new AccessorApp();
app->run();
delete app;
return 255;
}

View file

@ -0,0 +1,221 @@
#include "wiegand.h"
#include <furi.h>
#include <api-hal.h>
volatile unsigned long WIEGAND::_cardTempHigh = 0;
volatile unsigned long WIEGAND::_cardTemp = 0;
volatile unsigned long WIEGAND::_lastWiegand = 0;
unsigned long WIEGAND::_code = 0;
unsigned long WIEGAND::_codeHigh = 0;
volatile int WIEGAND::_bitCount = 0;
int WIEGAND::_wiegandType = 0;
constexpr uint32_t clocks_in_ms = 64 * 1000;
WIEGAND::WIEGAND() {
}
unsigned long WIEGAND::getCode() {
return _code;
}
unsigned long WIEGAND::getCodeHigh() {
return _codeHigh;
}
int WIEGAND::getWiegandType() {
return _wiegandType;
}
bool WIEGAND::available() {
bool ret;
__disable_irq();
ret = DoWiegandConversion();
__enable_irq();
return ret;
}
void input_isr(void* _pin, void* _ctx) {
// interrupt manager get us pin constant, so...
uint32_t pin = (uint32_t)_pin;
WIEGAND* _this = static_cast<WIEGAND*>(_ctx);
if(pin == ext_pa6_gpio.pin) {
_this->ReadD0();
}
if(pin == ext_pa7_gpio.pin) {
_this->ReadD1();
}
}
void WIEGAND::begin() {
_lastWiegand = 0;
_cardTempHigh = 0;
_cardTemp = 0;
_code = 0;
_wiegandType = 0;
_bitCount = 0;
const GpioPin* pinD0 = &ext_pa6_gpio;
const GpioPin* pinD1 = &ext_pa7_gpio;
gpio_init(pinD0, GpioModeInterruptFall); // Set D0 pin as input
gpio_init(pinD1, GpioModeInterruptFall); // Set D1 pin as input
api_interrupt_add(
input_isr, InterruptTypeExternalInterrupt, this); // Hardware interrupt - high to low pulse
}
void WIEGAND::ReadD0() {
_bitCount++; // Increament bit count for Interrupt connected to D0
if(_bitCount > 31) // If bit count more than 31, process high bits
{
_cardTempHigh |= ((0x80000000 & _cardTemp) >> 31); // shift value to high bits
_cardTempHigh <<= 1;
_cardTemp <<= 1;
} else {
_cardTemp <<= 1; // D0 represent binary 0, so just left shift card data
}
_lastWiegand = DWT->CYCCNT; // Keep track of last wiegand bit received
}
void WIEGAND::ReadD1() {
_bitCount++; // Increment bit count for Interrupt connected to D1
if(_bitCount > 31) // If bit count more than 31, process high bits
{
_cardTempHigh |= ((0x80000000 & _cardTemp) >> 31); // shift value to high bits
_cardTempHigh <<= 1;
_cardTemp |= 1;
_cardTemp <<= 1;
} else {
_cardTemp |= 1; // D1 represent binary 1, so OR card data with 1 then
_cardTemp <<= 1; // left shift card data
}
_lastWiegand = DWT->CYCCNT; // Keep track of last wiegand bit received
}
unsigned long WIEGAND::GetCardId(
volatile unsigned long* codehigh,
volatile unsigned long* codelow,
char bitlength) {
if(bitlength == 26) // EM tag
return (*codelow & 0x1FFFFFE) >> 1;
if(bitlength == 24) return (*codelow & 0x7FFFFE) >> 1;
if(bitlength == 34) // Mifare
{
*codehigh = *codehigh & 0x03; // only need the 2 LSB of the codehigh
*codehigh <<= 30; // shift 2 LSB to MSB
*codelow >>= 1;
return *codehigh | *codelow;
}
if(bitlength == 32) {
return (*codelow & 0x7FFFFFFE) >> 1;
}
return *codelow; // EM tag or Mifare without parity bits
}
char translateEnterEscapeKeyPress(char originalKeyPress) {
switch(originalKeyPress) {
case 0x0b: // 11 or * key
return 0x0d; // 13 or ASCII ENTER
case 0x0a: // 10 or # key
return 0x1b; // 27 or ASCII ESCAPE
default:
return originalKeyPress;
}
}
bool WIEGAND::DoWiegandConversion() {
unsigned long cardID;
unsigned long sysTick = DWT->CYCCNT;
if((sysTick - _lastWiegand) >
(25 * clocks_in_ms)) // if no more signal coming through after 25ms
{
if((_bitCount == 24) || (_bitCount == 26) || (_bitCount == 32) || (_bitCount == 34) ||
(_bitCount == 37) || (_bitCount == 40) || (_bitCount == 8) ||
(_bitCount ==
4)) // bitCount for keypress=4 or 8, Wiegand 26=24 or 26, Wiegand 34=32 or 34
{
_codeHigh = 0;
// shift right 1 bit to get back the real value - interrupt done 1 left shift in advance
_cardTemp >>= 1;
// bit count more than 32 bits, shift high bits right to make adjustment
if(_bitCount > 32) _cardTempHigh >>= 1;
if(_bitCount == 8) // keypress wiegand with integrity
{
// 8-bit Wiegand keyboard data, high nibble is the "NOT" of low nibble
// eg if key 1 pressed, data=E1 in binary 11100001 , high nibble=1110 , low nibble = 0001
char highNibble = (_cardTemp & 0xf0) >> 4;
char lowNibble = (_cardTemp & 0x0f);
_wiegandType = _bitCount;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;
if(lowNibble ==
(~highNibble & 0x0f)) // check if low nibble matches the "NOT" of high nibble.
{
_code = (int)translateEnterEscapeKeyPress(lowNibble);
return true;
} else {
_lastWiegand = sysTick;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;
return false;
}
// TODO: Handle validation failure case!
} else if(4 == _bitCount) {
// 4-bit Wiegand codes have no data integrity check so we just
// read the LOW nibble.
_code = (int)translateEnterEscapeKeyPress(_cardTemp & 0x0000000F);
_wiegandType = _bitCount;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;
return true;
} else if(40 == _bitCount) {
_cardTempHigh >>= 1;
_code = _cardTemp;
_codeHigh = _cardTempHigh;
_wiegandType = _bitCount;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;
return true;
} else {
// wiegand 26 or wiegand 34
cardID = GetCardId(&_cardTempHigh, &_cardTemp, _bitCount);
_wiegandType = _bitCount;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;
_code = cardID;
return true;
}
} else {
// well time over 25 ms and bitCount !=8 , !=26, !=34 , must be noise or nothing then.
_lastWiegand = sysTick;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;
return false;
}
} else
return false;
}

View file

@ -0,0 +1,27 @@
#pragma once
class WIEGAND {
public:
WIEGAND();
void begin();
bool available();
unsigned long getCode();
unsigned long getCodeHigh();
int getWiegandType();
static void ReadD0();
static void ReadD1();
private:
static bool DoWiegandConversion();
static unsigned long
GetCardId(volatile unsigned long* codehigh, volatile unsigned long* codelow, char bitlength);
static volatile unsigned long _cardTempHigh;
static volatile unsigned long _cardTemp;
static volatile unsigned long _lastWiegand;
static volatile int _bitCount;
static int _wiegandType;
static unsigned long _code;
static unsigned long _codeHigh;
};

View file

@ -0,0 +1,13 @@
#pragma once
#include "../accessor-app.h"
class AccessorApp;
class AccessorScene {
public:
virtual void on_enter(AccessorApp* app) = 0;
virtual bool on_event(AccessorApp* app, AccessorEvent* event) = 0;
virtual void on_exit(AccessorApp* app) = 0;
private:
};

View file

@ -0,0 +1,88 @@
#include "../accessor-app.h"
#include "../accessor-view-manager.h"
#include "../accessor-event.h"
#include <callback-connector.h>
#include "accessor-scene-start.h"
void AccessorSceneStart::on_enter(AccessorApp* app) {
AccessorAppViewManager* view_manager = app->get_view_manager();
Popup* popup = view_manager->get_popup();
popup_set_header(popup, "Accessor App", 64, 16, AlignCenter, AlignBottom);
app->set_text_store("[??????]");
popup_set_text(popup, app->get_text_store(), 64, 22, AlignCenter, AlignTop);
view_manager->switch_to(AccessorAppViewManager::ViewType::Popup);
}
bool AccessorSceneStart::on_event(AccessorApp* app, AccessorEvent* event) {
bool consumed = false;
if(event->type == AccessorEvent::Type::Tick) {
WIEGAND* wiegand = app->get_wiegand();
Popup* popup = app->get_view_manager()->get_popup();
OneWireMaster* onewire = app->get_one_wire();
uint8_t data[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t type = 0;
if(wiegand->available()) {
type = wiegand->getWiegandType();
for(uint8_t i = 0; i < 4; i++) {
data[i] = wiegand->getCode() >> (i * 8);
}
for(uint8_t i = 4; i < 8; i++) {
data[i] = wiegand->getCodeHigh() >> ((i - 4) * 8);
}
} else {
__disable_irq();
if(onewire->reset()) {
type = 255;
onewire->write(0x33);
for(uint8_t i = 0; i < 8; i++) {
data[i] = onewire->read();
}
for(uint8_t i = 0; i < 7; i++) {
data[i] = data[i + 1];
}
}
__enable_irq();
}
if(type > 0) {
if(type == 255) {
app->set_text_store(
"[%02X %02X %02X %02X %02X %02X DS]",
data[5],
data[4],
data[3],
data[2],
data[1],
data[0]);
} else {
app->set_text_store(
"[%02X %02X %02X %02X %02X %02X W%u]",
data[5],
data[4],
data[3],
data[2],
data[1],
data[0],
type);
}
popup_set_text(popup, app->get_text_store(), 64, 22, AlignCenter, AlignTop);
app->notify_success();
}
}
return consumed;
}
void AccessorSceneStart::on_exit(AccessorApp* app) {
Popup* popup = app->get_view_manager()->get_popup();
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
}

View file

@ -0,0 +1,9 @@
#pragma once
#include "accessor-scene-generic.h"
class AccessorSceneStart : public AccessorScene {
public:
void on_enter(AccessorApp* app) final;
bool on_event(AccessorApp* app, AccessorEvent* event) final;
void on_exit(AccessorApp* app) final;
};

View file

@ -35,6 +35,7 @@ int32_t gui_test(void* p);
int32_t keypad_test(void* p);
int32_t scene_app(void* p);
int32_t passport(void* p);
int32_t app_accessor(void* p);
const FlipperApplication FLIPPER_SERVICES[] = {
#ifdef APP_CLI
@ -149,6 +150,10 @@ const FlipperApplication FLIPPER_SERVICES[] = {
{.app = keypad_test, .name = "keypad_test", .icon = A_Plugins_14},
#endif
#ifdef APP_ACCESSOR
{.app = app_accessor, .name = "accessor", .stack_size = 4096, .icon = A_Plugins_14},
#endif
};
const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication);
@ -230,6 +235,10 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = {
{.app = keypad_test, .name = "keypad_test", .icon = A_Plugins_14},
#endif
#ifdef BUILD_ACCESSOR
{.app = app_accessor, .name = "accessor", .stack_size = 4096, .icon = A_Plugins_14},
#endif
};
const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication);

View file

@ -25,6 +25,7 @@ BUILD_GPIO_DEMO = 1
BUILD_MUSIC_PLAYER = 1
BUILD_FLOOPPER_BLOOPPER = 1
BUILD_IBUTTON = 1
endif
APP_DEBUG ?=0
@ -32,6 +33,7 @@ ifeq ($(APP_DEBUG), 1)
CFLAGS += -DAPP_DEBUG
BUILD_GUI_TEST = 1
BUILD_KEYPAD_TEST = 1
BUILD_ACCESSOR = 1
BUILD_SD_TEST = 1
BUILD_VIBRO_DEMO = 1
BUILD_SPEAKER_DEMO = 1
@ -209,6 +211,17 @@ CFLAGS += -DBUILD_KEYPAD_TEST
BUILD_KEYPAD_TEST = 1
endif
APP_ACCESSOR ?= 0
ifeq ($(APP_ACCESSOR), 1)
CFLAGS += -DAPP_ACCESSOR
BUILD_ACCESSOR = 1
endif
BUILD_ACCESSOR ?= 0
ifeq ($(BUILD_ACCESSOR), 1)
CFLAGS += -DBUILD_ACCESSOR
BUILD_ACCESSOR = 1
endif
APP_GPIO_DEMO ?= 0
ifeq ($(APP_GPIO_DEMO), 1)
CFLAGS += -DAPP_GPIO_DEMO

View file

@ -42,3 +42,13 @@ const GpioPin gpio_spi_d_sck = { .port=SPI_D_SCK_GPIO_Port, .pin=SPI_D_SCK_Pin }
const GpioPin gpio_spi_r_miso = { .port=SPI_R_MISO_GPIO_Port, .pin=SPI_R_MISO_Pin };
const GpioPin gpio_spi_r_mosi = { .port=SPI_R_MOSI_GPIO_Port, .pin=SPI_R_MOSI_Pin };
const GpioPin gpio_spi_r_sck = { .port=SPI_R_SCK_GPIO_Port, .pin=SPI_R_SCK_Pin };
// external gpio's
const GpioPin ext_pc0_gpio = {.port = GPIOC, .pin = GPIO_PIN_0};
const GpioPin ext_pc1_gpio = {.port = GPIOC, .pin = GPIO_PIN_1};
const GpioPin ext_pc3_gpio = {.port = GPIOC, .pin = GPIO_PIN_3};
const GpioPin ext_pb2_gpio = {.port = GPIOB, .pin = GPIO_PIN_2};
const GpioPin ext_pb3_gpio = {.port = GPIOB, .pin = GPIO_PIN_3};
const GpioPin ext_pa4_gpio = {.port = GPIOA, .pin = GPIO_PIN_4};
const GpioPin ext_pa6_gpio = {.port = GPIOA, .pin = GPIO_PIN_6};
const GpioPin ext_pa7_gpio = {.port = GPIOA, .pin = GPIO_PIN_7};

View file

@ -74,6 +74,14 @@ extern const GpioPin gpio_spi_r_miso;
extern const GpioPin gpio_spi_r_mosi;
extern const GpioPin gpio_spi_r_sck;
extern const GpioPin ext_pc0_gpio;
extern const GpioPin ext_pc1_gpio;
extern const GpioPin ext_pc3_gpio;
extern const GpioPin ext_pb2_gpio;
extern const GpioPin ext_pb3_gpio;
extern const GpioPin ext_pa4_gpio;
extern const GpioPin ext_pa6_gpio;
extern const GpioPin ext_pa7_gpio;
#ifdef __cplusplus
}