mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-27 11:02:40 +00:00
0cb413697b
Co-authored-by: jyy <jiangyeying@m5stack.com>
279 lines
12 KiB
C
279 lines
12 KiB
C
#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);
|
||
}
|
||
}
|
||
} |