add remote source code (#12)

Co-authored-by: jyy <jiangyeying@m5stack.com>
This commit is contained in:
Jiangyy
2026-04-20 16:27:57 +08:00
committed by GitHub
parent dd34f9e0ec
commit 0cb413697b
29 changed files with 10886 additions and 0 deletions
@@ -0,0 +1,29 @@
#ifndef _JOYSTICK_BASIC_H_
#define _JOYSTICK_BASIC_H_
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int channel;
int id;
int8_t bat;
uint16_t joyX;
uint16_t joyY;
uint8_t screen_mode;
uint8_t select_mode;
bool btnB_status;
float accel_x;
float accel_y;
float accel_z;
} joystick_data_t;
extern joystick_data_t joystick_data;
#ifdef __cplusplus
}
#endif
#endif
+279
View File
@@ -0,0 +1,279 @@
#include "joystick_handle.h"
i2c_bus_device_handle_t i2c_device1; // i2c device handle
/**
* @brief Initialize joystick via I2C interface
* @note This is an internal static function that configures I2C_NUM_0 as master with SDA on GPIO0 and SCL on GPIO26
* @details
* 1. Configures I2C master mode with 100kHz clock speed
* 2. Creates I2C bus handle using I2C_NUM_0
* 3. Scans the I2C bus to detect connected devices and logs their addresses
* 4. Creates device handle for joystick at I2C address 0x54
* 5. Assigns the device handle to global variable [i2c_device1]
* @warning This function assumes the joystick device is at I2C address 0x54
*/
static void joystick_i2c_init()
{
i2c_config_t conf;
{
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = 0;
conf.scl_io_num = 26;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = 100000;
conf.clk_flags = 0;
};
i2c_bus_handle_t i2c0_bus = i2c_bus_create(I2C_NUM_0, &conf);
uint8_t buf[128];
memset(buf, 0, sizeof(buf));
i2c_bus_scan(i2c0_bus, buf, sizeof(buf));
for (size_t i = 0; i < sizeof(buf); i++) {
if (buf[i] != 0) {
ESP_LOGI("I2C Scanner", "Found device at address 0x%02X", buf[i]);
}
}
i2c_device1 = i2c_bus_device_create(i2c0_bus, 0x54, 0);
}
/**
* @brief Read X and Y axis values from the joystick via I2C
* @param joyX Pointer to store X-axis value (16-bit unsigned integer)
* @param joyY Pointer to store Y-axis value (16-bit unsigned integer)
* @return void
* @details
* 1. Reads 2 bytes from register address 0x00 (X-axis low/high bytes)
* 2. Waits 10ms to ensure data stability
* 3. Reads 2 bytes from register address 0x02 (Y-axis low/high bytes)
* 4. Combines high and low bytes for both X and Y axes using bit shifting
* 5. Stores the combined values in the provided pointers
* @warning This function assumes the joystick provides 16-bit data in little-endian format
*/
static void joystick_read_xy(uint16_t *joyX, uint16_t *joyY)
{
uint8_t data[4];
esp_err_t ret = i2c_bus_read_bytes(i2c_device1, 0x00, 2, data);
vTaskDelay(20 / portTICK_PERIOD_MS);
ret |= i2c_bus_read_bytes(i2c_device1, 0x02, 2, &data[2]);
if (ret == ESP_OK) {
*joyX = (data[1] << 8) | data[0];
*joyY = (data[3] << 8) | data[2];
} else {
// ESP_LOGE("I2C Joystick", "Failed to read joystick data");
}
}
/**
* @brief Public interface to initialize the joystick and return default configuration
* @return joystick_data_t Structure containing initialized joystick parameters
* @note This is the main initialization function exposed to users
* @details
* 1. Calls internal device_joystick_init()
* 2. Initializes all fields of 'joystick_data_t'
* - channel: 1 (default communication channel)
* - id: 0 (default target ID)
* - bat: 0 (battery level, to be updated later)
* - joyX, joyY: 0 (initial joystick positions)
* - screen_mode: MODE_SETUP (start in setup mode)
* - select_mode: CHANNEL_SELECT (default selection mode)
* @return joystick_data_t
*/
joystick_data_t joystick_init()
{
joystick_i2c_init();
joystick_data_t tmp;
tmp.channel = 1;
tmp.id = 0;
tmp.bat = 0;
tmp.joyX = 0;
tmp.joyY = 0;
tmp.accel_x = 0.0f;
tmp.accel_y = 0.0f;
tmp.accel_z = 0.0f;
tmp.screen_mode = MODE_SETUP;
tmp.select_mode = CHANNEL_SELECT;
tmp.btnB_status = false;
return tmp;
}
/**
* @brief Task to handle joystick setup screen
* @param pvParam Pointer to joystick data, pointing to joystick_data_t structure
* @note This function runs an infinite loop that continuously reads joystick XY coordinates
* and updates the setup screen when the screen mode is MODE_SETUP
* @details Reads raw joystick data and then calls update_setup_screen function to update screen display
* Each loop iteration has a 50ms delay to ensure interface responsiveness
*/
void handle_setup_screen(void *pvParam)
{
joystick_data_t *joystick_data = (joystick_data_t *)pvParam;
while (1) {
if (joystick_data->screen_mode == MODE_SETUP) {
joystick_read_xy(&joystick_data->joyX, &joystick_data->joyY);
update_setup_screen(joystick_data);
vTaskDelay(50 / portTICK_PERIOD_MS);
} else {
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
}
/**
* @brief Task to handle joystick running screen, responsible for reading joystick data and sending ESP-NOW control
* packets
* @param pvParam Pointer to joystick data, pointing to joystick_data_t structure
* @note This function runs an infinite loop that reads joystick input, processes data, and sends control packets in
* running mode
* @details
* 1. Reads raw X/Y values from the joystick via I2C
* 2. Updates the running screen display with current values
* 3. Applies deadzone correction to center the joystick values
* 4. Maps raw values to yaw/pitch angle ranges (-1280 to 1280 for yaw, 0 to 900 for pitch)
* 5. Only sends data when changes exceed threshold (5 units) to reduce network traffic
* 6. Constructs and sends ESP-NOW packet containing target ID, yaw, pitch, speed and button status
* 7. Each loop iteration has a 30ms delay when in running mode
*/
void handle_running_screen(void *pvParam)
{
joystick_data_t *joystick_data = (joystick_data_t *)pvParam;
// communicate packet
uint8_t pkt[8];
pkt[0] = joystick_data->id; // id: 0 for broadcast
int16_t yaw_angle = 0;
int16_t pitch_angle = 0;
int16_t last_yaw = 0;
int16_t last_pitch = 0;
int16_t speed_val = 600;
while (1) {
// update screen and send packet when in running mode
if (joystick_data->screen_mode == MODE_RUNNING) {
joystick_read_xy(&joystick_data->joyX, &joystick_data->joyY);
if (running_screen != NULL && lv_obj_is_valid(running_screen)) {
update_running_screen(joystick_data->joyX, joystick_data->joyY, joystick_data->channel,
joystick_data->id, joystick_data->bat);
}
// handle data from joystick
if ((joystick_data->joyX < X_CENTER + DEAD_ZONE) && (joystick_data->joyX > X_CENTER - DEAD_ZONE)) {
joystick_data->joyX = X_CENTER;
}
if ((joystick_data->joyY < Y_CENTER + DEAD_ZONE) && (joystick_data->joyY > Y_CENTER - DEAD_ZONE)) {
joystick_data->joyY = Y_CENTER;
}
yaw_angle = (int16_t)map(joystick_data->joyX, X_MIN, X_MAX, 1280, -1280);
pitch_angle = (int16_t)map(joystick_data->joyY, Y_MIN, Y_MAX, 0, 900);
// send pitch_angle and yaw_angle only when changes exceed threshold
if (abs(yaw_angle - last_yaw) < 5 && abs(pitch_angle - last_pitch) < 5) {
if (pkt[7] != joystick_data->btnB_status) {
pkt[7] = joystick_data->btnB_status;
espnow_send_data(pkt, sizeof(pkt));
}
vTaskDelay(30 / portTICK_PERIOD_MS);
continue;
}
pkt[0] = joystick_data->id;
memcpy(&pkt[1], &yaw_angle, sizeof(int16_t));
memcpy(&pkt[3], &pitch_angle, sizeof(int16_t));
memcpy(&pkt[5], &speed_val, sizeof(int16_t));
pkt[7] = joystick_data->btnB_status;
#if 0
ESP_LOGI("handle_running_screen", "Yaw: %d, Pitch: %d, Speed: %d, id: %u, Button: %u",
yaw_angle, pitch_angle, speed_val, joystick_data->id, joystick_data->btnB_status);
#endif
last_yaw = yaw_angle;
last_pitch = pitch_angle;
espnow_send_data(pkt, sizeof(pkt));
vTaskDelay(30 / portTICK_PERIOD_MS);
} else {
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
}
/**
* @brief Task to handle joystick IMU screen functionality, processing IMU sensor data and sending ESP-NOW control
* packets
*
* This function runs an infinite loop that reads IMU sensor data (accelerometer and gyroscope),
* updates the IMU visualization screen, processes the angle data to control remote devices,
* and sends ESP-NOW packets with the processed control information.
* The function maps the IMU pitch and roll angles to yaw and pitch values for remote control,
* applies filtering to reduce unnecessary transmissions, and sends control packets at regular intervals.
*
* @param pvParam Pointer to joystick data structure containing IMU sensor values, battery level,
* device ID, communication channel, and other control parameters
* @details
* 1. Continuously reads IMU data (acceleration and gyro values) from the joystick_data structure
* 2. Updates the IMU screen visualization with current sensor values
* 3. Limits roll values to range [-1.5, 1.5] and pitch values to range [0, 1.5]
* 4. Maps limited angle values to appropriate yaw/pitch ranges for remote control (-1280 to 1280 for yaw, 900 to 0
* for pitch)
* 5. Only sends control packets when changes exceed threshold (10 units) to minimize network traffic
* 6. Constructs and transmits ESP-NOW packet with device ID, yaw, pitch, speed, and button status
* 7. Includes a 50ms delay between iterations when in IMU mode, 200ms otherwise
*/
void handle_imu_screen(void *pvParam)
{
joystick_data_t *joystick_data = (joystick_data_t *)pvParam;
static IMU_Angle_t last_imu_angle = {0.0f, 0.0f};
// communicate packet
uint8_t pkt[8];
pkt[0] = joystick_data->id; // id: 0 for broadcast
int16_t yaw_angle = 0;
int16_t pitch_angle = 0;
int16_t last_yaw = 0;
int16_t last_pitch = 0;
int16_t speed_val = 600;
while (1) {
// update screen and send packet when in running mode
if (joystick_data->screen_mode == MODE_IMU) {
IMU_Angle_t imu_angle =
update_imu_screen(joystick_data->accel_x, joystick_data->accel_y, joystick_data->accel_z,
joystick_data->bat, joystick_data->id, joystick_data->channel);
// Limit the roll value to the range of -1.5 to 1.5
float limited_roll = fmaxf(-1.5f, fminf(1.5f, imu_angle.roll));
// Limit the pitch value to the range of 0 to 1.5
float limited_pitch = fmaxf(0.0f, fminf(1.5f, imu_angle.pitch));
yaw_angle = (int16_t)map(limited_roll, -1.5, 1.5, -1280, 1280);
pitch_angle = (int16_t)map(limited_pitch, 0, 1.5, 900, 0);
if (abs(yaw_angle - last_yaw) < 10 && abs(last_pitch - pitch_angle) < 10) {
vTaskDelay(30 / portTICK_PERIOD_MS);
continue;
}
last_yaw = yaw_angle;
last_pitch = pitch_angle;
pkt[0] = joystick_data->id;
memcpy(&pkt[1], &yaw_angle, sizeof(int16_t));
memcpy(&pkt[3], &pitch_angle, sizeof(int16_t));
memcpy(&pkt[5], &speed_val, sizeof(int16_t));
pkt[7] = joystick_data->btnB_status;
espnow_send_data(pkt, sizeof(pkt));
#if 0
// ESP_LOGI("handle_imu_screen", "yaw_angle: %.2f, pitch_angle:%.2f, yaw: %d, pitch: %d\n",
imu_angle.roll, imu_angle.pitch, yaw_angle, pitch_angle);
#endif
vTaskDelay(30 / portTICK_PERIOD_MS);
} else {
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
}
@@ -0,0 +1,28 @@
#ifndef __JOYSTICK_HANDLE_H__
#define __JOYSTICK_HANDLE_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "driver/i2c.h"
#include "i2c_bus.h"
#include "hal/i2c_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "../esp_now/esp_now_init.h"
#include "../ui/ui_setup_screen.h"
#include "../ui/ui_running_screen.h"
#include "../ui/ui_imu_screen.h"
#include "joystick_basic.h"
joystick_data_t joystick_init();
void handle_setup_screen(void *pvParam);
void handle_running_screen(void *pvParam);
void handle_imu_screen(void *pvParam);
#ifdef __cplusplus
}
#endif
#endif