#include "dashboard.h" #include "font.h" #include "icons.h" //#include "monomaniacone12pt.h" #include "monomaniacone14pt.h" #include "monomaniacone20pt.h" #include "monomaniacone72pt.h" #include "usart.h" #include "crc.h" #include "float16.h" #include uint8_t initialized = 0; void run_dashboard_loop() { LCD_Init(); LCD_Fill_Screen(COLOR_OFF, 1); LCD_Fill_Screen(COLOR_BG, 0); HAL_Delay(1000); while (1) { update_values_setup(); update_adc(); HAL_Delay(100); } } void run_dashboard_loop_test() { LCD_Init(); LCD_Fill_Screen(COLOR_OFF, 1); LCD_Fill_Screen(COLOR_BG, 0); draw_init(); uint32_t i = 0; while (1) { draw_battery(654, i * 100 / 4, 1321, 343); draw_speed(i * 1000 / 4); int16_t duty = 1000 - (int16_t) i * 1000 / 200; draw_power_bars(duty); draw_power(100 * i / 20 * (duty < 0 ? -1 : 1), duty, 60 * 10); draw_adc(i % 34, (400 - i + 16) % 34, i % 34 >= 2.5, (400 - i + 16) % 34 >= 2.5); draw_temps((4 * i) % 900, ((10 * i) + 450) % 900); HAL_Delay(50); if (i == 400) { i = 0; } else { i++; } } } void update_values_setup() { // UART send 0201 2F D58D 03 uint8_t packet[1]; packet[0] = 0x2F;// COMM_GET_VALUES_SETUP USART1_SendPacket(packet, 1); // Read length (big-endian) uint8_t len = USART1_ReceiveUInt16() & 0xFF; if (len != 0x46) { HAL_Delay(500); USART1_Flush(); return;// Timed out } // Read packet USART1_ReceiveByte();// Packet id int16_t temp_fet = USART1_ReceiveInt16(); int16_t temp_motor = USART1_ReceiveInt16(); USART1_ReceiveInt32();// Current tot int32_t current_in_tot = USART1_ReceiveInt32(); int16_t duty_cycle = USART1_ReceiveInt16(); USART1_ReceiveInt32();// rpm int32_t speed = USART1_ReceiveInt32(); int16_t input_voltage_filtered = USART1_ReceiveInt16(); int16_t battery_level = USART1_ReceiveInt16(); USART1_ReceiveInt32();// ah tot USART1_ReceiveInt32();// ah charge tot USART1_ReceiveInt32();// wh tot USART1_ReceiveInt32();// wh charge tot USART1_ReceiveInt32();// distance int32_t distance_abs = USART1_ReceiveInt32(); USART1_ReceiveInt32();// pid pos now uint8_t fault_code = USART1_ReceiveByte(); USART1_ReceiveByte();// Controller Id USART1_ReceiveByte();// Num VESCs USART1_ReceiveInt32();// wh battery left uint32_t distance_life = USART1_ReceiveUInt32(); USART1_ReceiveUInt32();// System time USART1_ReceiveUInt16(); // CRC // End byte if (USART1_ReceiveByte() != 0x03) { return;// Invalid packet } // CRC check // if (crc != crc16(data, len)) { // return; // } if (!initialized) { draw_init(); initialized = 1; } draw_battery(input_voltage_filtered, battery_level, distance_abs, distance_life); draw_power_bars(duty_cycle); draw_speed(speed * 10); draw_power(current_in_tot, duty_cycle, input_voltage_filtered); draw_temps(temp_fet, temp_motor); } void update_adc() { // Replies to 0x24 0x65 for command id: 0, 1, 18, 19, 1B, 1D, C9, CA // 0 1 24 25 27 29 201 202 uint8_t packet[3]; packet[0] = 0x24;// COMM_CUSTOM_APP_DATA packet[1] = 0x65;// reFloat package interface packet[2] = 0x01;// command id USART1_SendPacket(packet, 3); uint8_t len = USART1_ReceiveUInt16() & 0xFF; if (len == 0xFF) {// len is usually 0x3A = 58, but may be shorter. HAL_Delay(500); USART1_Flush(); return;// Timed out } // Read packet USART1_ReceiveByte();// packet id USART1_ReceiveByte();// reFloat package interface USART1_ReceiveByte();// command id USART1_ReceiveInt32();// Balance current USART1_ReceiveInt32();// IMU Pitch USART1_ReceiveInt32();// IMU Roll USART1_ReceiveByte();// State uint8_t beep_fs_state = USART1_ReceiveByte();// Beep reason + footpad sensors state uint32_t adc1_f32 = USART1_ReceiveUInt32(); uint32_t adc2_f32 = USART1_ReceiveUInt32(); // Results if command id 31 (0x1F) was working: // uint8_t mask = USART1_ReceiveByte();// mask // 0x00 // uint8_t extra_flags = USART1_ReceiveByte();// extra flags // 0x02 // uint32_t time = USART1_ReceiveInt32();// time // 0x00 10 00 41 // uint8_t state_and_time = USART1_ReceiveByte();// state_and_mode // 0x9B // uint8_t flags_and_footpad = USART1_ReceiveByte();// flags_and_footpad // 0x8A // uint8_t stop_cond_and_sat = USART1_ReceiveByte();// stop_cond_and_sat // 0x29 // uint8_t alert_reason = USART1_ReceiveByte();// alert_reason // 0x41 // // USART1_ReceiveInt16();//motor.speed // USART1_ReceiveInt16();//motor.erpm // USART1_ReceiveInt16();//motor.current // USART1_ReceiveInt16();//motor.dir_current // USART1_ReceiveInt16();//motor.filt_current // USART1_ReceiveInt16();//motor.duty_cycle // USART1_ReceiveInt16();//motor.batt_voltage // USART1_ReceiveInt16();//motor.batt_current // USART1_ReceiveInt16();//motor.mosfet_temp // USART1_ReceiveInt16();//motor.motor_temp // USART1_ReceiveInt16();//imu.pitch // USART1_ReceiveInt16();//imu.balance_pitch // USART1_ReceiveInt16();//imu.roll // uint16_t adc1_f16 = USART1_ReceiveUInt16();//footpad.adc1 // uint16_t adc2_f16 = USART1_ReceiveUInt16();//footpad.adc2 // USART1_ReceiveInt16();//remote.input //// May have other ignored values. // int16_t adc1 = refloat_float16_to_int16_scaled(adc1_f16); // int16_t adc2 = refloat_float16_to_int16_scaled(adc2_f16); if (!initialized) { draw_init(); initialized = 1; } float adc1 = *((float*) &adc1_f32); float adc2 = *((float*) &adc2_f32); uint8_t fs_state = beep_fs_state & 0b11; uint8_t adc1_en = 0; uint8_t adc2_en = 0; if(fs_state == 1) { if (adc1 > adc2){ adc1_en = 1; } else { adc2_en = 2; } }else if(fs_state == 2) { adc1_en = 1; adc2_en = 1; } draw_adc((int32_t) 10 * adc1, (int32_t) 10 * adc2, adc1_en, adc2_en); // Make sure other values are discarded HAL_Delay(100); USART1_Flush(); } #define LEFT_CENTER_COL1 38 #define LEFT_CENTER_COL2 116 #define RIGHT_CENTER 400 void draw_init() { // Draw footpad sensors rect uint16_t sensor_width = 126; LCD_DrawHollowRoundRect(RIGHT_CENTER - sensor_width / 2, 70, sensor_width, 73, 15, 0, 6, COLOR_PRIMARY, 0, 0); LCD_Draw_Rectangle(RIGHT_CENTER - 3, 76, 6, 61, COLOR_PRIMARY); // Draw temp icons LCD_Draw_Icon_3(motort_icon, LEFT_CENTER_COL1 - MOTORT_ICON_WIDTH / 2, 73, MOTORT_ICON_WIDTH, MOTORT_ICON_HEIGHT, COLOR_SECONDARY, COLOR_ERROR, COLOR_BG); LCD_Draw_Icon_3(chipt_icon, LEFT_CENTER_COL2 - CHIPT_ICON_WIDTH / 2, 73, CHIPT_ICON_WIDTH, CHIPT_ICON_HEIGHT, COLOR_PRIMARY, COLOR_ERROR, COLOR_BG); // GFX_DrawChar(40, 40, '!', &monomaniacone20pt, COLOR_PRIMARY, COLOR_BG); // GFX_DrawChar(60, 40, '2', &monomaniacone20pt, COLOR_PRIMARY, COLOR_BG); // GFX_DrawChar(80, 40, '3', &monomaniacone20pt, COLOR_SECONDARY, COLOR_BG); // GFX_DrawChar(100, 40, '4', &monomaniacone20pt, COLOR_SECONDARY, COLOR_BG); // GFX_DrawChar(120, 40, '5', &monomaniacone20pt, COLOR_ERROR, COLOR_BG); // GFX_DrawText(LCD_WIDTH / 2, 50, "Bonjour Monsieur !", &monomaniacone12pt, // COLOR_SECONDARY, COLOR_BG, 1, -2); // GFX_DrawText(LCD_WIDTH / 2, 70, "Bonjour Monsieur !", &monomaniacone14pt, // COLOR_SECONDARY, COLOR_BG, 1, -2); // GFX_DrawText(LCD_WIDTH / 2, 100, "Bonjour MONSieur !", &monomaniacone20pt, // COLOR_SUCCESS, COLOR_BG, 1, -2); // Draw a rectangle with different top/bottom radii // LCD_DrawHollowRoundRect(10, 10, 100, 50, 10, 0, 4, BLACK, COLOR_PRIMARY, 1); //// Draw a pill-shaped rectangle (same radius for all corners) // LCD_DrawHollowRoundRect(20, 70, 80, 30, 15, 15, 2, COLOR_SUCCESS, 0, 0); //// Draw a square with rounded corners // LCD_DrawHollowRoundRect(50, 100, 60, 60, 10, 10, 6, COLOR_ERROR, 0, 0); // LCD_DrawHollowRoundRect(130, 100, 60, 60, 10, 10, 0, 0, COLOR_ERROR, 1); // LCD_DrawHollowRoundRect(136, 106, 48, 48, 4, 4, 0, 0, COLOR_PRIMARY, 1); // // LCD_DrawHollowRoundRect(200, 100, 4, 20, 6, 6, 0, 0, COLOR_PRIMARY, 1); // LCD_DrawHollowRoundRect(200, 125, 6, 20, 6, 6, 0, 0, COLOR_PRIMARY, 1); // LCD_DrawHollowRoundRect(200, 150, 8, 20, 6, 6, 0, 0, COLOR_PRIMARY, 1); } // Displays the battery voltage and percent, with the trip and life distances // Input voltage from COMM_GET_VALUES_SETUP, scale 10 // battery percent (level) from COMM_GET_VALUES_SETUP, scale 10 // trip distance from COMM_GET_VALUES_SETUP, scale 1000, in m // life distance (odometer) from COMM_GET_VALUES_SETUP, in m int16_t last_voltage = 0; int32_t last_percent = 0; int32_t last_trip_dist = 0; void draw_battery( int16_t voltage, int32_t percent, int32_t trip_dist, uint32_t life_dist) { if (voltage == last_voltage && percent / 10 == last_percent && trip_dist / 100000 == last_trip_dist) { return; } last_voltage = voltage; last_percent = percent / 10; last_trip_dist = trip_dist / 100000; uint16_t bar_width = LCD_WIDTH - 12; uint16_t bar_height = 22; uint16_t filled_bar_width = (((float) (percent / 10)) / 100.0) * bar_width; if (filled_bar_width < 6) { filled_bar_width = 6;// Must be at least the size of the border radius. } uint16_t filled_bar_end_x = 4 + 2 + filled_bar_width; //uint16_t text_y_12 = LCD_HEIGHT - 6 - (22 - 16) / 2; uint16_t text_y_14 = LCD_HEIGHT - 6 - (22 - 18) / 2; char voltage_text[8]; sprintf(voltage_text, "%.1fV", ((float) voltage) / 10.0); char percent_text[6]; sprintf(percent_text, "%lu%%", percent / 10); char distances_text[20]; sprintf(distances_text, "%.1fKm / %luKm", ((float) (trip_dist / 100000)) / 10.0, life_dist / 1000); // Drawing the bars LCD_DrawHollowRoundRect(4, LCD_HEIGHT - (bar_height + 4) - 4, bar_width + 4, bar_height + 4, 8, 8, 2, COLOR_FG, COLOR_BG, 1); if (filled_bar_width > 0) { LCD_DrawHollowRoundRect(4 + 2, LCD_HEIGHT - (bar_height + 2) - 4, filled_bar_width, bar_height, 6, 6, 0, 0, COLOR_SUCCESS, 1); } // Drawing the values uint16_t left_x = 10; uint16_t right_x = LCD_WIDTH - 10; if (percent > 20 * 10) { // Drawing the voltage to the left left_x = 10 + GFX_DrawText(left_x, text_y_14, voltage_text, &monomaniacone14pt, COLOR_BG, COLOR_SUCCESS, 0, -2); } else { // Drawing the voltage to the right right_x = -10 + GFX_DrawText(right_x, text_y_14, voltage_text, &monomaniacone14pt, COLOR_FG, COLOR_BG, 2, -2); } if (percent > 50 * 10) { // Drawing the distances on the left GFX_DrawText(left_x, text_y_14, distances_text, &monomaniacone14pt, COLOR_BG, COLOR_SUCCESS, 0, -3); } else { // Drawing the distances on the right GFX_DrawText(right_x, text_y_14, distances_text, &monomaniacone14pt, COLOR_FG, COLOR_BG, 2, -3); } if (percent > 80 * 10) { // Drawing the distances on the left GFX_DrawText(filled_bar_end_x - 4, text_y_14, percent_text, &monomaniacone14pt, COLOR_BG, COLOR_SUCCESS, 2, 0); } else { // Drawing the distances on the right GFX_DrawText(filled_bar_end_x + 4, text_y_14, percent_text, &monomaniacone14pt, COLOR_FG, COLOR_BG, 0, 0); } } // Displays the power bars at the top // Duty from COMM_GET_VALUES, scale 1000 uint16_t last_width = 0; uint8_t last_regen = 0; void draw_power_bars(int16_t dutyy) { uint16_t offset = LCD_WIDTH / 2; uint16_t max_width = LCD_WIDTH / 2; uint8_t regen = dutyy < 0; uint16_t duty = regen ? -dutyy : dutyy; duty *= 1 / 0.8;// 80% duty means the bar is at 100% uint16_t width = ((float) duty / 1000.0) * max_width; uint16_t last_origin = last_regen ? offset - last_width : offset; uint16_t origin = regen ? offset - width : offset; uint16_t color = regen ? COLOR_ERROR : COLOR_SUCCESS; if (regen != last_regen) { LCD_Draw_Rectangle(last_origin, 0, last_width, 20, COLOR_BG); LCD_Draw_Rectangle(origin, 0, width, 20, color); last_regen = regen; last_width = width; } else if (width != last_width) { if (regen) { if (last_origin < origin) { LCD_Draw_Rectangle(last_origin, 0, origin - last_origin, 20, COLOR_BG); } else { LCD_Draw_Rectangle(origin, 0, last_origin - origin, 20, color); } } else { if (last_width < width) { LCD_Draw_Rectangle(origin + last_width, 0, width - last_width, 20, color); } else { LCD_Draw_Rectangle(origin + width, 0, last_width - width, 20, COLOR_BG); } } last_width = width; } } // Displays the huge speed counter with avg and max values. // Speed from COMM_GET_VALUES_SETUP, scale 1000 uint32_t last_speed = 30; uint32_t max_speed = 0; uint32_t last_avg_speed = 30; uint32_t avg_speed_tot = 0;// You need to ride super fast for a super long time for it to overflow ;) uint32_t avg_speed_count = 0; void draw_speed(int32_t speedd) { uint32_t speed = speedd < 0 ? -speedd / 1000 : speedd / 1000; if (speed >= 100) { speed = 99; } // update max speed uint8_t update_stats = 0; if (speed > max_speed) { max_speed = speed; update_stats = 1; } // update avg speed avg_speed_tot += speed; avg_speed_count += 1; uint32_t avg_speed = avg_speed_tot / avg_speed_count; if (last_avg_speed != avg_speed) { last_avg_speed = avg_speed; update_stats = 1; } // Draw if (last_speed != speed) { last_speed = speed; uint16_t erase_width = 85;// width to erase from center char speed_text[4]; sprintf(speed_text, "%lu", speed); LCD_Draw_Rectangle(LCD_WIDTH / 2 - erase_width, 22, 2 * erase_width, 110, COLOR_BG); GFX_DrawText(LCD_WIDTH / 2, 32 + 95, speed_text, &monomaniacone72pt, COLOR_FG, COLOR_BG, 1, -5); } if (update_stats) { char stats_text[20]; uint16_t erase_width = 90;// width to erase from center sprintf(stats_text, "avg: %lu max: %lu", avg_speed, max_speed); LCD_Draw_Rectangle(LCD_WIDTH / 2 - erase_width, 140, 2 * erase_width, 25, COLOR_BG); GFX_DrawText(LCD_WIDTH / 2, 159, stats_text, &monomaniacone14pt, COLOR_SECONDARY, COLOR_BG, 1, -3); } } int16_t last_current = 99; uint16_t last_duty = 99; int32_t last_power = 1000; // Displays Current, Duty, Watts // Current from COMM_GET_VALUES, scale 100 // Duty from COMM_GET_VALUES, scale 1000 // Input voltage from COMM_GET_VALUES, scale 10 void draw_power(int32_t current_i, int16_t duty_i, int16_t voltage_i) { int16_t current = current_i / 100; uint16_t duty = duty_i < 0 ? -duty_i / 10 : duty_i / 10; int32_t power = ((int32_t) voltage_i) * ((int32_t) current_i) / 10000 * 10; if (power >= 1000) { power = power / 100 * 100; } else if (power <= -1000) { power = power / 100 * 100; } if (current != last_current) { last_current = current; if (current >= 100) { current = 99; } else if (current <= -100) { current = -99; } char text[8]; sprintf(text, "%dA", current); LCD_Draw_Rectangle(LEFT_CENTER_COL1 - 38, 30, 2 * 39, 35, COLOR_BG); GFX_DrawText(LEFT_CENTER_COL1, 60, text, &monomaniacone20pt, COLOR_PRIMARY, COLOR_BG, 1, -2); } if (duty != last_duty) { last_duty = duty; if (duty >= 100) { duty = 99; } char text[8]; sprintf(text, "%u%%", duty); LCD_Draw_Rectangle(LEFT_CENTER_COL2 - 39, 30, 2 * 39, 35, COLOR_BG); GFX_DrawText(LEFT_CENTER_COL2, 60, text, &monomaniacone20pt, COLOR_PRIMARY, COLOR_BG, 1, -2); } if (power != last_power) { last_power = power; if (power >= 10000) { power = 9999; } else if (power <= -10000) { power = -9999; } char text[10]; sprintf(text, "%ldW", power); LCD_Draw_Rectangle(RIGHT_CENTER - 70, 30, 2 * 70, 35, COLOR_BG); GFX_DrawText(RIGHT_CENTER, 60, text, &monomaniacone20pt, COLOR_PRIMARY, COLOR_BG, 1, -2); } } uint16_t last_adc1 = 1000; uint16_t last_adc2 = 1000; uint8_t last_adc1_en = 0; uint8_t last_adc2_en = 0; // Displays the two ADC voltages // 2 voltages from reFloat - COMM_CUSTOM_APP_DATA, scale 10, adcx_en are booleans. void draw_adc(int32_t adc1_scaled, int32_t adc2_scaled, uint8_t adc1_en, uint8_t adc2_en) { uint16_t adc1 = adc1_scaled < 0 ? -adc1_scaled : adc1_scaled; uint16_t adc2 = adc2_scaled < 0 ? -adc2_scaled : adc2_scaled; if (adc1 > 33) { adc1 = 33; } if (adc2 > 33) { adc2 = 33; } // Max filled height = 39 if (adc1 != last_adc1 || adc1_en != last_adc1_en) { uint16_t filled_height = adc1 * 39 / 33; uint16_t free_height = 61 - filled_height; // Reset the top part LCD_Draw_Rectangle(RIGHT_CENTER - 3 - 54 + 2, 76 + 2, 54 - 4, 2, COLOR_BG); LCD_Draw_Rectangle(RIGHT_CENTER - 3 - 54 + 0, 76 + 4, 54, free_height - 4, COLOR_BG); // Draw the colored zone uint16_t color = adc1_en ? COLOR_SUCCESS : COLOR_ERROR; LCD_Draw_Rectangle(RIGHT_CENTER - 3 - 54, 76 + free_height, 54, filled_height, color); // Draw the text char text[8]; sprintf(text, "%.1fV", ((float) adc1) / 10); GFX_DrawText(RIGHT_CENTER - 3 - 27, 76 + free_height - 2, text, &monomaniacone14pt, color, COLOR_BG, 1, -2); last_adc1 = adc1; last_adc1_en = adc1_en; } // Max filled height = 39 if (adc2 != last_adc2 || adc2_en != last_adc2_en) { uint16_t filled_height = adc2 * 39 / 33; uint16_t free_height = 61 - filled_height; // Reset the top part LCD_Draw_Rectangle(RIGHT_CENTER + 3 + 2, 76 + 2, 54 - 4, 2, COLOR_BG); LCD_Draw_Rectangle(RIGHT_CENTER + 3 + 0, 76 + 4, 54, free_height - 4, COLOR_BG); // Draw the colored zone uint16_t color = adc2_en ? COLOR_SUCCESS : COLOR_ERROR; LCD_Draw_Rectangle(RIGHT_CENTER + 3, 76 + free_height, 54, filled_height, color); // Draw the text char text[8]; sprintf(text, "%.1fV", ((float) adc2) / 10); GFX_DrawText(RIGHT_CENTER + 3 + 27, 76 + free_height - 2, text, &monomaniacone14pt, color, COLOR_BG, 1, -2); last_adc2 = adc2; last_adc2_en = adc2_en; } } uint16_t last_temp_fet = 1000; uint16_t last_temp_mot = 1000; // Displays the controller and motor temperatures // 2 temperatures from COMM_GET_VALUES_SETUP, scale 10 void draw_temps(int16_t temp_fet_scaled, int16_t temp_motor_scaled) { uint16_t temp_fet = temp_fet_scaled < 0 ? 0 : temp_fet_scaled / 10; uint16_t temp_mot = temp_motor_scaled < 0 ? 0 : temp_motor_scaled / 10; if (temp_fet > 99) { temp_fet = 99; } if (temp_mot > 99) { temp_mot = 99; } if (temp_mot != last_temp_mot) { last_temp_mot = temp_mot; char text[8]; sprintf(text, "%u>C", temp_mot); LCD_Draw_Rectangle(LEFT_CENTER_COL1 - 38, 138, 2 * 39, 25, COLOR_BG); GFX_DrawText(LEFT_CENTER_COL1, 159, text, &monomaniacone14pt, COLOR_SECONDARY, COLOR_BG, 1, -1); } if (temp_fet != last_temp_fet) { last_temp_fet = temp_fet; char text[8]; sprintf(text, "%u>C", temp_fet); LCD_Draw_Rectangle(LEFT_CENTER_COL2 - 39, 138, 2 * 39, 25, COLOR_BG); GFX_DrawText(LEFT_CENTER_COL2, 159, text, &monomaniacone14pt, COLOR_PRIMARY, COLOR_BG, 1, -1); } }