mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-10 06:54:19 +00:00
[FL-3889] 5V on GPIO control for ext. modules (#3830)
* Make file extensions case-insensitive * Bump protobuf version * Add support for 5V control via RPC * Add support for 5V control via Expansion protocol * Update running instructions * Update expansion module documentation * Prettify condition * Test RPC OTG control as well * Assets: bump protobuf version * Disable PVS license expiration check, fix PVS warnings Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
parent
b040db07f4
commit
fa2d611652
8 changed files with 160 additions and 21 deletions
|
@ -6,18 +6,27 @@
|
|||
* 13 -> 16 (USART TX to LPUART RX)
|
||||
* 14 -> 15 (USART RX to LPUART TX)
|
||||
*
|
||||
* Optional: Connect an LED with an appropriate series resistor
|
||||
* between pins 1 and 8. It will always be on if the device is
|
||||
* connected to USB power, so unplug it before running the app.
|
||||
*
|
||||
* What this application does:
|
||||
*
|
||||
* - Enables module support and emulates the module on a single device
|
||||
* (hence the above connection),
|
||||
* - Connects to the expansion module service, sets baud rate,
|
||||
* - Enables OTG (5V) on GPIO via plain expansion protocol,
|
||||
* - Waits 5 cycles of idle loop (1 second),
|
||||
* - Starts the RPC session,
|
||||
* - Disables OTG (5V) on GPIO via RPC messages,
|
||||
* - Waits 5 cycles of idle loop (1 second),
|
||||
* - Creates a directory at `/ext/ExpansionTest` and writes a file
|
||||
* named `test.txt` under it,
|
||||
* - Plays an audiovisual alert (sound and blinking display),
|
||||
* - Waits 10 cycles of idle loop,
|
||||
* - Enables OTG (5V) on GPIO via RPC messages,
|
||||
* - Waits 5 cycles of idle loop (1 second),
|
||||
* - Stops the RPC session,
|
||||
* - Waits another 10 cycles of idle loop,
|
||||
* - Disables OTG (5V) on GPIO via plain expansion protocol,
|
||||
* - Exits (plays a sound if any of the above steps failed).
|
||||
*/
|
||||
#include <furi.h>
|
||||
|
@ -302,6 +311,22 @@ static bool expansion_test_app_handshake(ExpansionTestApp* instance) {
|
|||
return success;
|
||||
}
|
||||
|
||||
static bool expansion_test_app_enable_otg(ExpansionTestApp* instance, bool enable) {
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
const ExpansionFrameControlCommand command = enable ?
|
||||
ExpansionFrameControlCommandEnableOtg :
|
||||
ExpansionFrameControlCommandDisableOtg;
|
||||
if(!expansion_test_app_send_control_request(instance, command)) break;
|
||||
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
|
||||
if(!expansion_test_app_is_success_response(&instance->frame)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) {
|
||||
bool success = false;
|
||||
|
||||
|
@ -396,6 +421,27 @@ static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) {
|
|||
return success;
|
||||
}
|
||||
|
||||
static bool expansion_test_app_rpc_enable_otg(ExpansionTestApp* instance, bool enable) {
|
||||
bool success = false;
|
||||
|
||||
instance->msg.command_id++;
|
||||
instance->msg.command_status = PB_CommandStatus_OK;
|
||||
instance->msg.which_content = PB_Main_gpio_set_otg_mode_tag;
|
||||
instance->msg.content.gpio_set_otg_mode.mode = enable ? PB_Gpio_GpioOtgMode_ON :
|
||||
PB_Gpio_GpioOtgMode_OFF;
|
||||
instance->msg.has_next = false;
|
||||
|
||||
do {
|
||||
if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break;
|
||||
if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break;
|
||||
if(instance->msg.which_content != PB_Main_empty_tag) break;
|
||||
if(instance->msg.command_status != PB_CommandStatus_OK) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) {
|
||||
uint32_t num_cycles_done;
|
||||
for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) {
|
||||
|
@ -434,13 +480,18 @@ int32_t expansion_test_app(void* p) {
|
|||
if(!expansion_test_app_send_presence(instance)) break;
|
||||
if(!expansion_test_app_wait_ready(instance)) break;
|
||||
if(!expansion_test_app_handshake(instance)) break;
|
||||
if(!expansion_test_app_enable_otg(instance, true)) break;
|
||||
if(!expansion_test_app_idle(instance, 5)) break;
|
||||
if(!expansion_test_app_start_rpc(instance)) break;
|
||||
if(!expansion_test_app_rpc_enable_otg(instance, false)) break;
|
||||
if(!expansion_test_app_idle(instance, 5)) break;
|
||||
if(!expansion_test_app_rpc_mkdir(instance)) break;
|
||||
if(!expansion_test_app_rpc_write(instance)) break;
|
||||
if(!expansion_test_app_rpc_alert(instance)) break;
|
||||
if(!expansion_test_app_idle(instance, 10)) break;
|
||||
if(!expansion_test_app_rpc_enable_otg(instance, true)) break;
|
||||
if(!expansion_test_app_idle(instance, 5)) break;
|
||||
if(!expansion_test_app_stop_rpc(instance)) break;
|
||||
if(!expansion_test_app_idle(instance, 10)) break;
|
||||
if(!expansion_test_app_enable_otg(instance, false)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
|
|
|
@ -64,8 +64,28 @@ typedef enum {
|
|||
* @brief Enumeration of suported control commands.
|
||||
*/
|
||||
typedef enum {
|
||||
ExpansionFrameControlCommandStartRpc = 0x00, /**< Start an RPC session. */
|
||||
ExpansionFrameControlCommandStopRpc = 0x01, /**< Stop an open RPC session. */
|
||||
/** @brief Start an RPC session.
|
||||
*
|
||||
* Must only be used while the RPC session is NOT active.
|
||||
*/
|
||||
ExpansionFrameControlCommandStartRpc = 0x00,
|
||||
/** @brief Stop an open RPC session.
|
||||
*
|
||||
* Must only be used while the RPC session IS active.
|
||||
*/
|
||||
ExpansionFrameControlCommandStopRpc = 0x01,
|
||||
/** @brief Enable OTG (5V) on external GPIO.
|
||||
*
|
||||
* Must only be used while the RPC session is NOT active,
|
||||
* otherwise OTG is to be controlled via RPC messages.
|
||||
*/
|
||||
ExpansionFrameControlCommandEnableOtg = 0x02,
|
||||
/** @brief Disable OTG (5V) on external GPIO.
|
||||
*
|
||||
* Must only be used while the RPC session is NOT active,
|
||||
* otherwise OTG is to be controlled via RPC messages.
|
||||
*/
|
||||
ExpansionFrameControlCommandDisableOtg = 0x03,
|
||||
} ExpansionFrameControlCommand;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
|
|
@ -245,9 +245,18 @@ static bool expansion_worker_handle_state_connected(
|
|||
|
||||
do {
|
||||
if(rx_frame->header.type == ExpansionFrameTypeControl) {
|
||||
if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break;
|
||||
instance->state = ExpansionWorkerStateRpcActive;
|
||||
if(!expansion_worker_rpc_session_open(instance)) break;
|
||||
const uint8_t command = rx_frame->content.control.command;
|
||||
if(command == ExpansionFrameControlCommandStartRpc) {
|
||||
if(!expansion_worker_rpc_session_open(instance)) break;
|
||||
instance->state = ExpansionWorkerStateRpcActive;
|
||||
} else if(command == ExpansionFrameControlCommandEnableOtg) {
|
||||
furi_hal_power_enable_otg();
|
||||
} else if(command == ExpansionFrameControlCommandDisableOtg) {
|
||||
furi_hal_power_disable_otg();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break;
|
||||
|
||||
} else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) {
|
||||
|
@ -279,9 +288,14 @@ static bool expansion_worker_handle_state_rpc_active(
|
|||
if(size_consumed != rx_frame->content.data.size) break;
|
||||
|
||||
} else if(rx_frame->header.type == ExpansionFrameTypeControl) {
|
||||
if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break;
|
||||
instance->state = ExpansionWorkerStateConnected;
|
||||
expansion_worker_rpc_session_close(instance);
|
||||
const uint8_t command = rx_frame->content.control.command;
|
||||
if(command == ExpansionFrameControlCommandStopRpc) {
|
||||
instance->state = ExpansionWorkerStateConnected;
|
||||
expansion_worker_rpc_session_close(instance);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break;
|
||||
|
||||
} else if(rx_frame->header.type == ExpansionFrameTypeStatus) {
|
||||
|
|
|
@ -33,7 +33,7 @@ typedef struct {
|
|||
|
||||
static const uint8_t keyboard_origin_x = 7;
|
||||
static const uint8_t keyboard_origin_y = 31;
|
||||
static const uint8_t keyboard_row_count = 2;
|
||||
static const int8_t keyboard_row_count = 2;
|
||||
static const uint8_t enter_symbol = '\r';
|
||||
static const uint8_t backspace_symbol = '\b';
|
||||
static const uint8_t max_drawable_bytes = 8;
|
||||
|
@ -649,11 +649,11 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) {
|
|||
}
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
// Draw keyboard
|
||||
for(uint8_t row = 0; row < keyboard_row_count; row++) {
|
||||
for(int8_t row = 0; row < keyboard_row_count; row++) {
|
||||
const uint8_t column_count = byte_input_get_row_size(row);
|
||||
const ByteInputKey* keys = byte_input_get_row(row);
|
||||
|
||||
for(size_t column = 0; column < column_count; column++) {
|
||||
for(uint8_t column = 0; column < column_count; column++) {
|
||||
if(keys[column].value == enter_symbol) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "rpc_i.h"
|
||||
#include "gpio.pb.h"
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) {
|
||||
|
@ -188,6 +189,44 @@ void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) {
|
|||
free(response);
|
||||
}
|
||||
|
||||
void rpc_system_gpio_get_otg_mode(const PB_Main* request, void* context) {
|
||||
furi_assert(request);
|
||||
furi_assert(context);
|
||||
furi_assert(request->which_content == PB_Main_gpio_get_otg_mode_tag);
|
||||
|
||||
RpcSession* session = context;
|
||||
|
||||
const bool otg_enabled = furi_hal_power_is_otg_enabled();
|
||||
|
||||
PB_Main* response = malloc(sizeof(PB_Main));
|
||||
response->command_id = request->command_id;
|
||||
response->which_content = PB_Main_gpio_get_otg_mode_response_tag;
|
||||
response->content.gpio_get_otg_mode_response.mode = otg_enabled ? PB_Gpio_GpioOtgMode_ON :
|
||||
PB_Gpio_GpioOtgMode_OFF;
|
||||
|
||||
rpc_send_and_release(session, response);
|
||||
|
||||
free(response);
|
||||
}
|
||||
|
||||
void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) {
|
||||
furi_assert(request);
|
||||
furi_assert(context);
|
||||
furi_assert(request->which_content == PB_Main_gpio_set_otg_mode_tag);
|
||||
|
||||
RpcSession* session = context;
|
||||
|
||||
const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode;
|
||||
|
||||
if(mode == PB_Gpio_GpioOtgMode_OFF) {
|
||||
furi_hal_power_disable_otg();
|
||||
} else {
|
||||
furi_hal_power_enable_otg();
|
||||
}
|
||||
|
||||
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
|
||||
}
|
||||
|
||||
void* rpc_system_gpio_alloc(RpcSession* session) {
|
||||
furi_assert(session);
|
||||
|
||||
|
@ -212,5 +251,11 @@ void* rpc_system_gpio_alloc(RpcSession* session) {
|
|||
rpc_handler.message_handler = rpc_system_gpio_set_input_pull;
|
||||
rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler);
|
||||
|
||||
rpc_handler.message_handler = rpc_system_gpio_get_otg_mode;
|
||||
rpc_add_handler(session, PB_Main_gpio_get_otg_mode_tag, &rpc_handler);
|
||||
|
||||
rpc_handler.message_handler = rpc_system_gpio_set_otg_mode;
|
||||
rpc_add_handler(session, PB_Main_gpio_set_otg_mode_tag, &rpc_handler);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a
|
||||
Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7
|
|
@ -73,7 +73,7 @@ If the requested baud rate is supported by the host, it SHALL respond with a STA
|
|||
|
||||
### Control frame
|
||||
|
||||
CONTROL frames are used to control various aspects of the communication. As of now, the sole purpose of CONTROL frames is to start and stop the RPC session.
|
||||
CONTROL frames are used to control various aspects of the communication and enable/disable various device features.
|
||||
|
||||
| Header (1 byte) | Contents (1 byte) | Checksum (1 byte) |
|
||||
|-----------------|-------------------|-------------------|
|
||||
|
@ -81,10 +81,18 @@ CONTROL frames are used to control various aspects of the communication. As of n
|
|||
|
||||
The `Command` field SHALL have one of the followind values:
|
||||
|
||||
| Command | Meaning |
|
||||
|---------|-------------------|
|
||||
| 0x00 | Start RPC session |
|
||||
| 0x01 | Stop RPC session |
|
||||
| Command | Meaning | Note |
|
||||
|---------|--------------------------|:----:|
|
||||
| 0x00 | Start RPC session | 1 |
|
||||
| 0x01 | Stop RPC session | 2 |
|
||||
| 0x02 | Enable OTG (5V) on GPIO | 3 |
|
||||
| 0x03 | Disable OTG (5V) on GPIO | 3 |
|
||||
|
||||
Notes:
|
||||
|
||||
1. Must only be used while the RPC session NOT active.
|
||||
2. Must only be used while the RPC session IS active.
|
||||
3. See 1, otherwise OTG is to be controlled via RPC messages.
|
||||
|
||||
### Data frame
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ def generate(env):
|
|||
PVSOPTIONS=[
|
||||
"@.pvsoptions",
|
||||
"-j${PVSNCORES}",
|
||||
"--disableLicenseExpirationCheck",
|
||||
# "--incremental", # kinda broken on PVS side
|
||||
],
|
||||
PVSCONVOPTIONS=[
|
||||
|
|
Loading…
Reference in a new issue