Files
StackChan/remote/code/main/joystick/joystick_handle.c
T
2026-04-20 16:27:57 +08:00

279 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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);
}
}
}