/** * @file magnetometer_direction.h * @author Woon Jun Wei * @brief This file contains the functions to calculate the direction of * the car using the accelerometer and magnetometer data * * @details The direction of the car is calculated using the roll, pitch and yaw * The roll and pitch are calculated using the accelerometer data * The yaw is calculated using the magnetometer and accelerometer data * The roll, pitch and yaw are combined to calculate the direction * of the car with a complementary filter and compensating for the * temperature. * * The complementary filter is used to combine the accelerometer * and magnetometer data (yaw) to calculate the direction of the car * * Source: * https://www.nxp.com/docs/en/application-note/AN3461.pdf * https://ahrs.readthedocs.io/en/latest/filters/complementary.html * */ #ifndef MAGNETOMETER_DIRECTION_H #define MAGNETOMETER_DIRECTION_H #include "magnetometer_init.h" #include "map.h" /** * @brief Roll Calculation with Accelerometer Data * @param acceleration Accelerometer Data * @return */ static inline float calculate_roll(int16_t acceleration[3]) { return atan2(acceleration[1], acceleration[2]) * (180.0 / M_PI); } /** * @brief Pitch Calculation with Accelerometer Data * @param acceleration Accelerometer Data * @return */ static inline float calculate_pitch(int16_t acceleration[3]) { return atan2(-acceleration[0], sqrt((acceleration[1] * acceleration[1]) + (acceleration[2] * acceleration[2]))) * (180.0 / M_PI); } /** * @brief Yaw Calculation with Magnetometer Data * @param magnetometer Magnetometer Data * @return */ static inline float calculate_yaw_magnetometer(int16_t magnetometer[3]) { return atan2(magnetometer[1], magnetometer[0]) * (180.0f / M_PI); } /** * @brief Complementary Filter for Yaw * @param yaw_acc Yaw calculated from Accelerometer Data * @param yaw_mag Yaw calculated from Magnetometer Data * @return yaw Yaw calculated from Complementary Filter */ // static inline float // calculate_yaw_complementary(float yaw_acc, float yaw_mag) { // return ALPHA * yaw_acc + (1 - ALPHA) * yaw_mag; // } /** * @brief Compensate the magnetometer readings for temperature * @param yaw_mag Yaw calculated from Magnetometer Data * @param temperature Temperature in degrees Celsius * @return Compensated Yaw */ float compensate_magnetometer(float yaw_mag, int16_t temperature) { // Calculate temperature difference from the reference temperature uint delta_temp = temperature - TEMPERATURE_OFFSET; // Apply temperature compensation to each axis using macros float compensated_yaw_mag = yaw_mag - ((float)delta_temp * TEMPERATURE_COEFFICIENT_Z); // Apply scale and offset corrections using macros compensated_yaw_mag = (compensated_yaw_mag - OFFSET_Z) * SCALE_Z; return compensated_yaw_mag; } /** * @brief Adjust Yaw to be between 0 and 360 degrees * @param yaw Yaw calculated from Complementary Filter * @return yaw Yaw adjusted to be between 0 and 360 degrees */ static inline float adjust_yaw(float yaw) { if (yaw < 0) { yaw += 360; } if (yaw > 360) { yaw -= 360; } return yaw; } /** * @brief Calculate the Compass Direction (N, NE, E, SE, S, SW, W, NW) * 22.5 = 360 / 16, used to calculate the compass direction from * the compass direction enum * 45.0 = 360 / 8, used to calculate the compass direction from * the orientation (0 - 7) * @param yaw Yaw calculated * @return Compass Direction */ static inline compass_direction_t calculate_compass_direction(float yaw) { if (yaw >= 337.5 || yaw < 22.5) { return NORTH; } else { if (yaw >= 22.5 && yaw < 67.5) { return NORTH_EAST; } else { if (yaw >= 67.5 && yaw < 112.5) { return EAST; } else { if (yaw >= 112.5 && yaw < 157.5) { return SOUTH_EAST; } else { if (yaw >= 157.5 && yaw < 202.5) { return SOUTH; } else { if (yaw >= 202.5 && yaw < 247.5) { return SOUTH_WEST; } else { if (yaw >= 247.5 && yaw < 292.5) { return WEST; } else { if (yaw >= 292.5 && yaw < 337.5) { return NORTH_WEST; } } } } } } } } } /** * @brief Update the Orientation Data * @param roll Roll calculated from Accelerometer Data * @param pitch Pitch calculated from Accelerometer Data * @param yaw Yaw calculated from Complementary Filter * @param compass_direction Compass Direction */ static inline void update_orientation_data(float roll, float pitch, float yaw, compass_direction_t compass_direction, volatile direction_t *g_direction) { g_direction->roll = roll; g_direction->roll_angle = (roll > 0) ? LEFT : RIGHT; g_direction->pitch = pitch; g_direction->pitch_angle = (pitch > 0) ? UP : DOWN; g_direction->yaw = yaw; g_direction->orientation = compass_direction; } /** * @brief Read the Accelerometer and Magnetometer Data and * Calculate the Direction of the Car * @param acceleration Accelerometer Data * @param magnetometer Magnetometer Data */ static void read_direction(int16_t acceleration[3], int16_t magnetometer[3], volatile direction_t *g_direction) { float roll = calculate_roll(acceleration); float pitch = calculate_pitch(acceleration); float yaw_mag = calculate_yaw_magnetometer(magnetometer); yaw_mag = adjust_yaw(yaw_mag); compass_direction_t compass_direction = calculate_compass_direction(yaw_mag); update_orientation_data( roll, pitch, yaw_mag, compass_direction, g_direction); } /** * FreeRTOS Tasks */ /** * @brief Task to Monitor the Direction of the Car * @param params */ void print_orientation_data(volatile direction_t g_direction) { // printf("Roll: %f, Pitch: %f, Yaw: %f\n", printf("%f %f %f\n", g_direction.roll, g_direction.pitch, g_direction.yaw); } void print_direction(compass_direction_t direction) { switch (direction) { case NORTH: printf("North\n"); break; case NORTH_EAST: printf("North East\n"); break; case EAST: printf("East\n"); break; case SOUTH_EAST: printf("South East\n"); break; case SOUTH: printf("South\n"); break; case SOUTH_WEST: printf("South West\n"); break; case WEST: printf("West\n"); break; case NORTH_WEST: printf("North West\n"); break; } } void print_roll_and_pitch(angle_t roll_angle, angle_t pitch_angle) { switch (roll_angle) { case LEFT: printf("Your left wheel is in the air!\n"); break; case RIGHT: printf("Your right wheel is in the air!\n"); break; } switch (pitch_angle) { case UP: printf("You're Flying!\n"); break; case DOWN: printf("You're Plunging!\n"); break; } } void updateDirection(volatile direction_t * g_direction) { int16_t magnetometer[3]; int16_t accelerometer[3]; int16_t temperature[1]; static int cur_x = 0; static int cur_y = 0; read_magnetometer(magnetometer, g_direction); read_accelerometer(accelerometer, g_direction); read_temperature(temperature); read_direction(accelerometer, magnetometer, g_direction); print_orientation_data(*g_direction); // // switch (g_direction->orientation) // { // case NORTH: // cur_y++; // break; // case EAST: // cur_x++; // break; // case SOUTH: // cur_y--; // break; // case WEST: // cur_x--; // break; // case NORTH_EAST: // cur_x++; // cur_y++; // break; // case SOUTH_EAST: // cur_x++; // cur_y--; // break; // case SOUTH_WEST: // cur_x--; // cur_y--; // break; // case NORTH_WEST: // cur_x--; // cur_y++; // break; // } // Update the map based on the direction of the car (N, E, S, W) // update_map(g_direction.orientation, cur_x, cur_y); // printf("Current Position: (%d, %d)\n", cur_x, cur_y); // print_map(); // print_roll_and_pitch(g_direction.roll_angle, g_direction.pitch_angle); } void monitor_direction_task(void *pvParameters) { volatile direction_t *p_direction = NULL; p_direction = (direction_t *) pvParameters; for (;;) { updateDirection(p_direction); vTaskDelay(pdMS_TO_TICKS(DIRECTION_READ_DELAY)); } } void magnetometer_tasks_init(car_struct_t *car_struct) { TaskHandle_t h_direction_task = NULL; xTaskCreate(monitor_direction_task, "Direction Task", configMINIMAL_STACK_SIZE, (void *) car_struct->p_direction, PRIO, &h_direction_task); } #endif