Add full automotive RTOS project
Add kernel (Cortex-M0/M3/M4, Tricore, S32K, RISC-V ports), drivers, middleware (CAN stack, diagnostics, safety), applications, board support, build/test tooling, and documentation.
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* @file display_task.c
|
||||
* @brief Dashboard display task
|
||||
*/
|
||||
|
||||
#include "kernel.h"
|
||||
#include "can_driver.h"
|
||||
#include "spi_driver.h"
|
||||
#include "gpio_driver.h"
|
||||
#include "engine_control.h"
|
||||
#include "brake_control.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Display State */
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
uint16_t display_buffer[1024]; /* Display frame buffer */
|
||||
uint8_t current_screen;
|
||||
bool backlight_on;
|
||||
uint8_t backlight_intensity;
|
||||
Mutex_t mutex;
|
||||
} DisplayState_t;
|
||||
|
||||
static DisplayState_t display;
|
||||
|
||||
/* Dashboard Data */
|
||||
typedef struct {
|
||||
uint16_t speed;
|
||||
uint16_t rpm;
|
||||
int16_t coolant_temp;
|
||||
uint16_t fuel_level;
|
||||
uint16_t odometer;
|
||||
uint16_t trip_meter;
|
||||
bool turn_left;
|
||||
bool turn_right;
|
||||
bool high_beam;
|
||||
bool check_engine;
|
||||
bool abs_warning;
|
||||
bool oil_pressure_warning;
|
||||
bool battery_warning;
|
||||
} DashboardData_t;
|
||||
|
||||
static DashboardData_t dashboard_data;
|
||||
|
||||
/* Initialize Display */
|
||||
KernelStatus_t display_init(void) {
|
||||
if (display.initialized) {
|
||||
return KERNEL_ERROR;
|
||||
}
|
||||
|
||||
memset(&display, 0, sizeof(DisplayState_t));
|
||||
display.current_screen = 0;
|
||||
display.backlight_on = true;
|
||||
display.backlight_intensity = 100;
|
||||
|
||||
mutex_create(&display.mutex, false);
|
||||
|
||||
/* Initialize SPI for display */
|
||||
spi_init(0, &(SpiConfig_t){
|
||||
.mode = SPI_MODE_0,
|
||||
.clock_speed = SPI_CLOCK_8MHZ,
|
||||
.data_order = SPI_DATA_ORDER_MSB_FIRST,
|
||||
.data_size = 8,
|
||||
.use_dma = true,
|
||||
.enable_hardware_cs = false,
|
||||
.cs_polarity = SPI_CS_ACTIVE_LOW,
|
||||
.cs_port = 0,
|
||||
.cs_pin = 15
|
||||
});
|
||||
|
||||
display.initialized = true;
|
||||
return KERNEL_OK;
|
||||
}
|
||||
|
||||
/* Dashboard Display Task */
|
||||
void display_task(void* parameters) {
|
||||
(void)parameters;
|
||||
|
||||
while (1) {
|
||||
/* Update dashboard data */
|
||||
update_dashboard_data();
|
||||
|
||||
/* Render display */
|
||||
render_display();
|
||||
|
||||
/* Update display */
|
||||
update_display_hardware();
|
||||
|
||||
/* 50ms refresh rate */
|
||||
kernel_delay(50);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update Dashboard Data */
|
||||
static void update_dashboard_data(void) {
|
||||
/* Get data from CAN bus */
|
||||
CanMessage_t message;
|
||||
|
||||
while (can_receive_message(&message, 0) == KERNEL_OK) {
|
||||
switch (message.id.id) {
|
||||
case 0x300: /* Engine data */
|
||||
dashboard_data.rpm = (message.data[0] << 8) | message.data[1];
|
||||
dashboard_data.coolant_temp = (int16_t)((message.data[2] << 8) |
|
||||
message.data[3]);
|
||||
dashboard_data.check_engine = message.data[4] & 0x01;
|
||||
dashboard_data.oil_pressure_warning = message.data[4] & 0x02;
|
||||
break;
|
||||
|
||||
case 0x301: /* Vehicle speed */
|
||||
dashboard_data.speed = (message.data[0] << 8) | message.data[1];
|
||||
dashboard_data.odometer = (message.data[2] << 16) |
|
||||
(message.data[3] << 8) |
|
||||
message.data[4];
|
||||
break;
|
||||
|
||||
case 0x302: /* Fuel level */
|
||||
dashboard_data.fuel_level = message.data[0];
|
||||
dashboard_data.battery_warning = message.data[1] & 0x01;
|
||||
break;
|
||||
|
||||
case 0x303: /* Turn signals */
|
||||
dashboard_data.turn_left = message.data[0] & 0x01;
|
||||
dashboard_data.turn_right = message.data[0] & 0x02;
|
||||
dashboard_data.high_beam = message.data[0] & 0x04;
|
||||
break;
|
||||
|
||||
case 0x304: /* ABS status */
|
||||
dashboard_data.abs_warning = message.data[0] & 0x01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Render Display */
|
||||
static void render_display(void) {
|
||||
mutex_lock(&display.mutex, 100);
|
||||
|
||||
/* Clear display buffer */
|
||||
memset(display.display_buffer, 0, sizeof(display.display_buffer));
|
||||
|
||||
/* Draw speedometer */
|
||||
draw_speedometer();
|
||||
|
||||
/* Draw tachometer */
|
||||
draw_tachometer();
|
||||
|
||||
/* Draw fuel gauge */
|
||||
draw_fuel_gauge();
|
||||
|
||||
/* Draw temperature gauge */
|
||||
draw_temperature_gauge();
|
||||
|
||||
/* Draw warning indicators */
|
||||
draw_warning_indicators();
|
||||
|
||||
/* Draw odometer */
|
||||
draw_odometer();
|
||||
|
||||
mutex_unlock(&display.mutex);
|
||||
}
|
||||
|
||||
/* Draw Speedometer */
|
||||
static void draw_speedometer(void) {
|
||||
/* Draw circular gauge */
|
||||
int center_x = 100;
|
||||
int center_y = 100;
|
||||
int radius = 80;
|
||||
|
||||
/* Draw arc */
|
||||
for (int angle = 0; angle < 270; angle++) {
|
||||
int x = center_x + (int)(radius * cos(angle * M_PI / 180));
|
||||
int y = center_y + (int)(radius * sin(angle * M_PI / 180));
|
||||
|
||||
if (x >= 0 && x < 240 && y >= 0 && y < 320) {
|
||||
display.display_buffer[y * 240 + x] = 0xFFFF; /* White */
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw speed needle */
|
||||
float speed_angle = (dashboard_data.speed * 270.0f) / 240.0f; /* 240 km/h max */
|
||||
int needle_x = center_x + (int)((radius - 10) * cos(speed_angle * M_PI / 180));
|
||||
int needle_y = center_y + (int)((radius - 10) * sin(speed_angle * M_PI / 180));
|
||||
|
||||
/* Draw line from center to needle tip */
|
||||
draw_line(center_x, center_y, needle_x, needle_y, 0xF800); /* Red */
|
||||
|
||||
/* Draw speed text */
|
||||
char speed_text[10];
|
||||
snprintf(speed_text, sizeof(speed_text), "%d km/h", dashboard_data.speed);
|
||||
draw_text(60, 200, speed_text, 0xFFFF);
|
||||
}
|
||||
|
||||
/* Draw Tachometer */
|
||||
static void draw_tachometer(void) {
|
||||
int center_x = 300;
|
||||
int center_y = 100;
|
||||
int radius = 60;
|
||||
|
||||
/* Draw arc */
|
||||
for (int angle = 0; angle < 270; angle++) {
|
||||
int x = center_x + (int)(radius * cos(angle * M_PI / 180));
|
||||
int y = center_y + (int)(radius * sin(angle * M_PI / 180));
|
||||
|
||||
if (x >= 0 && x < 480 && y >= 0 && y < 320) {
|
||||
display.display_buffer[y * 480 + x] = 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw RPM needle */
|
||||
float rpm_angle = (dashboard_data.rpm * 270.0f) / 8000.0f; /* 8000 RPM max */
|
||||
int needle_x = center_x + (int)((radius - 10) * cos(rpm_angle * M_PI / 180));
|
||||
int needle_y = center_y + (int)((radius - 10) * sin(rpm_angle * M_PI / 180));
|
||||
|
||||
draw_line(center_x, center_y, needle_x, needle_y, 0x07E0); /* Green */
|
||||
|
||||
/* Draw RPM text */
|
||||
char rpm_text[10];
|
||||
snprintf(rpm_text, sizeof(rpm_text), "%d RPM", dashboard_data.rpm);
|
||||
draw_text(260, 200, rpm_text, 0xFFFF);
|
||||
}
|
||||
|
||||
/* Draw Warning Indicators */
|
||||
static void draw_warning_indicators(void) {
|
||||
/* Check engine light */
|
||||
if (dashboard_data.check_engine) {
|
||||
draw_text(20, 280, "CHECK ENGINE", 0xF800); /* Red */
|
||||
}
|
||||
|
||||
/* ABS warning */
|
||||
if (dashboard_data.abs_warning) {
|
||||
draw_text(20, 300, "ABS", 0xF800);
|
||||
}
|
||||
|
||||
/* Oil pressure warning */
|
||||
if (dashboard_data.oil_pressure_warning) {
|
||||
draw_text(100, 300, "OIL", 0xF800);
|
||||
}
|
||||
|
||||
/* Battery warning */
|
||||
if (dashboard_data.battery_warning) {
|
||||
draw_text(150, 300, "BAT", 0xF800);
|
||||
}
|
||||
|
||||
/* Turn signals */
|
||||
if (dashboard_data.turn_left) {
|
||||
draw_text(400, 280, "<--", 0x07E0); /* Green */
|
||||
}
|
||||
if (dashboard_data.turn_right) {
|
||||
draw_text(440, 280, "-->", 0x07E0);
|
||||
}
|
||||
|
||||
/* High beam */
|
||||
if (dashboard_data.high_beam) {
|
||||
draw_text(400, 300, "HIGH", 0x001F); /* Blue */
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw Line */
|
||||
static void draw_line(int x1, int y1, int x2, int y2, uint16_t color) {
|
||||
int dx = abs(x2 - x1);
|
||||
int dy = abs(y2 - y1);
|
||||
int sx = (x1 < x2) ? 1 : -1;
|
||||
int sy = (y1 < y2) ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
while (1) {
|
||||
if (x1 >= 0 && x1 < 480 && y1 >= 0 && y1 < 320) {
|
||||
display.display_buffer[y1 * 480 + x1] = color;
|
||||
}
|
||||
|
||||
if (x1 == x2 && y1 == y2) {
|
||||
break;
|
||||
}
|
||||
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw Text */
|
||||
static void draw_text(int x, int y, const char* text, uint16_t color) {
|
||||
/* Simple 8x8 font rendering */
|
||||
while (*text) {
|
||||
char c = *text++;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (font_bitmap[(uint8_t)c][i] & (1 << j)) {
|
||||
int px = x + j;
|
||||
int py = y + i;
|
||||
|
||||
if (px >= 0 && px < 480 && py >= 0 && py < 320) {
|
||||
display.display_buffer[py * 480 + px] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x += 8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update Display Hardware */
|
||||
static void update_display_hardware(void) {
|
||||
mutex_lock(&display.mutex, 100);
|
||||
|
||||
/* Send frame buffer to display via SPI */
|
||||
spi_write(0, (uint8_t*)display.display_buffer, sizeof(display.display_buffer), 100);
|
||||
|
||||
mutex_unlock(&display.mutex);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* @file gauge_control.c
|
||||
* @brief Analog gauge control for dashboard
|
||||
*/
|
||||
|
||||
#include "kernel.h"
|
||||
#include "pwm_driver.h"
|
||||
#include "adc_driver.h"
|
||||
#include "can_driver.h"
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Gauge Control State */
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
uint16_t speed_gauge_position;
|
||||
uint16_t rpm_gauge_position;
|
||||
uint16_t fuel_gauge_position;
|
||||
uint16_t temp_gauge_position;
|
||||
uint16_t target_positions[4];
|
||||
Mutex_t mutex;
|
||||
} GaugeControlState_t;
|
||||
|
||||
static GaugeControlState_t gauge_control;
|
||||
|
||||
/* Initialize Gauge Control */
|
||||
KernelStatus_t gauge_control_init(void) {
|
||||
if (gauge_control.initialized) {
|
||||
return KERNEL_ERROR;
|
||||
}
|
||||
|
||||
memset(&gauge_control, 0, sizeof(GaugeControlState_t));
|
||||
|
||||
/* Initialize PWM for gauges */
|
||||
pwm_init(3, &(PwmConfig_t){
|
||||
.frequency_hz = 100, /* 100 Hz for smooth gauge movement */
|
||||
.channel_count = 4,
|
||||
.channels = {
|
||||
{.channel = 0, .duty_cycle = 0}, /* Speedometer */
|
||||
{.channel = 1, .duty_cycle = 0}, /* Tachometer */
|
||||
{.channel = 2, .duty_cycle = 0}, /* Fuel gauge */
|
||||
{.channel = 3, .duty_cycle = 0} /* Temperature gauge */
|
||||
}
|
||||
});
|
||||
|
||||
mutex_create(&gauge_control.mutex, false);
|
||||
|
||||
gauge_control.initialized = true;
|
||||
return KERNEL_OK;
|
||||
}
|
||||
|
||||
/* Gauge Control Task */
|
||||
void gauge_control_task(void* parameters) {
|
||||
(void)parameters;
|
||||
|
||||
while (1) {
|
||||
/* Read CAN messages for gauge data */
|
||||
CanMessage_t message;
|
||||
|
||||
while (can_receive_message(&message, 0) == KERNEL_OK) {
|
||||
process_gauge_message(&message);
|
||||
}
|
||||
|
||||
/* Smooth gauge movement */
|
||||
smooth_gauge_movement();
|
||||
|
||||
/* 10ms update rate */
|
||||
kernel_delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
/* Process Gauge Message */
|
||||
static void process_gauge_message(const CanMessage_t* message) {
|
||||
mutex_lock(&gauge_control.mutex, 100);
|
||||
|
||||
switch (message->id.id) {
|
||||
case 0x300: /* Engine data */
|
||||
/* RPM: 0-8000 RPM maps to 0-10000 duty cycle */
|
||||
gauge_control.target_positions[1] =
|
||||
((message->data[0] << 8) | message->data[1]) * 10000 / 8000;
|
||||
|
||||
/* Temperature: -40 to 120°C maps to 0-10000 */
|
||||
int16_t temp = (message->data[2] << 8) | message->data[3];
|
||||
gauge_control.target_positions[3] =
|
||||
(temp + 40) * 10000 / 160;
|
||||
break;
|
||||
|
||||
case 0x301: /* Vehicle speed */
|
||||
/* Speed: 0-240 km/h maps to 0-10000 */
|
||||
gauge_control.target_positions[0] =
|
||||
((message->data[0] << 8) | message->data[1]) * 10000 / 240;
|
||||
break;
|
||||
|
||||
case 0x302: /* Fuel level */
|
||||
/* Fuel: 0-100% maps to 0-10000 */
|
||||
gauge_control.target_positions[2] = message->data[0] * 100;
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&gauge_control.mutex);
|
||||
}
|
||||
|
||||
/* Smooth Gauge Movement */
|
||||
static void smooth_gauge_movement(void) {
|
||||
mutex_lock(&gauge_control.mutex, 100);
|
||||
|
||||
/* Smooth movement for each gauge */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint16_t current = 0;
|
||||
uint16_t target = gauge_control.target_positions[i];
|
||||
|
||||
/* Get current position */
|
||||
switch (i) {
|
||||
case 0:
|
||||
current = gauge_control.speed_gauge_position;
|
||||
break;
|
||||
case 1:
|
||||
current = gauge_control.rpm_gauge_position;
|
||||
break;
|
||||
case 2:
|
||||
current = gauge_control.fuel_gauge_position;
|
||||
break;
|
||||
case 3:
|
||||
current = gauge_control.temp_gauge_position;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Calculate new position with smoothing */
|
||||
int32_t delta = target - current;
|
||||
uint16_t new_position = current + (delta / 10); /* 10% movement per update */
|
||||
|
||||
/* Update position */
|
||||
switch (i) {
|
||||
case 0:
|
||||
gauge_control.speed_gauge_position = new_position;
|
||||
break;
|
||||
case 1:
|
||||
gauge_control.rpm_gauge_position = new_position;
|
||||
break;
|
||||
case 2:
|
||||
gauge_control.fuel_gauge_position = new_position;
|
||||
break;
|
||||
case 3:
|
||||
gauge_control.temp_gauge_position = new_position;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Update PWM output */
|
||||
pwm_set_duty_cycle(3, i, new_position);
|
||||
}
|
||||
|
||||
mutex_unlock(&gauge_control.mutex);
|
||||
}
|
||||
|
||||
/* Calibrate Gauges */
|
||||
KernelStatus_t gauge_calibrate(uint8_t gauge, uint16_t min_position,
|
||||
uint16_t max_position) {
|
||||
if (!gauge_control.initialized || gauge >= 4) {
|
||||
return KERNEL_ERROR;
|
||||
}
|
||||
|
||||
mutex_lock(&gauge_control.mutex, 100);
|
||||
|
||||
/* Set gauge to minimum position */
|
||||
pwm_set_duty_cycle(3, gauge, min_position);
|
||||
kernel_delay(1000); /* Wait 1 second */
|
||||
|
||||
/* Set gauge to maximum position */
|
||||
pwm_set_duty_cycle(3, gauge, max_position);
|
||||
kernel_delay(1000); /* Wait 1 second */
|
||||
|
||||
/* Return to zero */
|
||||
pwm_set_duty_cycle(3, gauge, min_position);
|
||||
|
||||
mutex_unlock(&gauge_control.mutex);
|
||||
|
||||
return KERNEL_OK;
|
||||
}
|
||||
|
||||
/* Self-Test Gauges */
|
||||
KernelStatus_t gauge_self_test(void) {
|
||||
if (!gauge_control.initialized) {
|
||||
return KERNEL_ERROR;
|
||||
}
|
||||
|
||||
/* Perform gauge sweep */
|
||||
for (uint16_t position = 0; position <= 10000; position += 100) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
pwm_set_duty_cycle(3, i, position);
|
||||
}
|
||||
kernel_delay(10);
|
||||
}
|
||||
|
||||
/* Return to zero */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
pwm_set_duty_cycle(3, i, 0);
|
||||
}
|
||||
|
||||
return KERNEL_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user