[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:
Georgii Surkov 2024-09-05 15:40:14 +01:00 committed by GitHub
parent b040db07f4
commit fa2d611652
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 160 additions and 21 deletions

View file

@ -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);

View file

@ -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)

View file

@ -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) {

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -47,6 +47,7 @@ def generate(env):
PVSOPTIONS=[
"@.pvsoptions",
"-j${PVSNCORES}",
"--disableLicenseExpirationCheck",
# "--incremental", # kinda broken on PVS side
],
PVSCONVOPTIONS=[