diff --git a/src/A2DP.ino b/src/A2DP.ino index 4d458bf..5824447 100644 --- a/src/A2DP.ino +++ b/src/A2DP.ino @@ -7,26 +7,26 @@ volatile bool md_album_recvd=0, md_artist_recvd=0, md_title_recvd=0; // updates the buffers void avrc_metadata_callback(uint8_t md_type, const uint8_t *data2) { // fills the song title buffer with data, updates text_lenght with the amount of chars - xSemaphoreTake(CAN_MsgSemaphore, portMAX_DELAY); // take the semaphore as a way to prevent the buffers being accessed elsewhere + xSemaphoreTake(BufferSemaphore, portMAX_DELAY); // take the semaphore as a way to prevent the buffers being accessed elsewhere switch(md_type){ case 0x1: memset(title_buffer, 0, sizeof(title_buffer)); snprintf(title_buffer, sizeof(title_buffer), "%s", data2); - DEBUG_PRINTF("\nA2DP: Received title: \"%s\"", data2); + //DEBUG_PRINTF("\nA2DP: Received title: \"%s\"", data2); md_title_recvd=1; break; case 0x2: memset(artist_buffer, 0, sizeof(artist_buffer)); snprintf(artist_buffer, sizeof(artist_buffer), "%s", data2); - DEBUG_PRINTF("\nA2DP: Received artist: \"%s\"", data2); + //DEBUG_PRINTF("\nA2DP: Received artist: \"%s\"", data2); md_artist_recvd=1; break; case 0x4: memset(album_buffer, 0, sizeof(album_buffer)); snprintf(album_buffer, sizeof(album_buffer), "%s", data2); - DEBUG_PRINTF("\nA2DP: Received album: \"%s\"", data2); + //DEBUG_PRINTF("\nA2DP: Received album: \"%s\"", data2); md_album_recvd=1; break; default: break; } - xSemaphoreGive(CAN_MsgSemaphore); + xSemaphoreGive(BufferSemaphore); if(md_title_recvd && md_artist_recvd && md_album_recvd){ DIS_forceUpdate=1; // lets the main loop() know that there's a new song title in the buffer md_title_recvd=0; @@ -71,8 +71,8 @@ void a2dp_init(){ a2dp_sink.start("EHU32"); // setting up bluetooth audio sink a2dp_started=1; DEBUG_PRINTLN("A2DP: Started!"); - disp_mode=-1; // set display mode to audio metadata on boot - writeTextToDisplay(1, "EHU32 v0.9.2 started!", "Bluetooth on", "Waiting for connection..."); + disp_mode=0; // set display mode to audio metadata on boot + writeTextToDisplay(1, "EHU32 v0.9.3", "Bluetooth on", "Waiting for connection..."); } // handles events such as connecion/disconnection and audio play/pause @@ -81,27 +81,23 @@ void A2DP_EventHandler(){ a2dp_init(); } - if(bt_state_changed){ // mute external DAC when not playing + if(bt_state_changed && disp_mode==0){ // mute external DAC when not playing if(bt_connected){ a2dp_sink.set_volume(127); // workaround to ensure max volume being applied on successful connection writeTextToDisplay(1, "Bluetooth connected", "", (char*)a2dp_sink.get_peer_name()); - if(disp_mode==-1) disp_mode=0; } else { writeTextToDisplay(1, "Bluetooth disconnected", "", ""); - if(disp_mode==0) disp_mode=-1; } bt_state_changed=0; } - if(audio_state_changed && bt_connected){ // mute external DAC when not playing; bt_connected ensures no "Connected, paused" is displayed, seems that the audio_state_changed callback comes late + if(audio_state_changed && bt_connected && disp_mode==0){ // mute external DAC when not playing; bt_connected ensures no "Connected, paused" is displayed, seems that the audio_state_changed callback comes late if(bt_audio_playing){ digitalWrite(PCM_MUTE_CTL, HIGH); DIS_forceUpdate=1; // force reprinting of audio metadata when the music is playing - if(disp_mode==-1) disp_mode=0; } else { digitalWrite(PCM_MUTE_CTL, LOW); writeTextToDisplay(1, "Bluetooth connected", "", "Paused"); - if(disp_mode==0) disp_mode=-1; } audio_state_changed=0; } @@ -109,6 +105,8 @@ void A2DP_EventHandler(){ // ID 0x501 DB3 0x18 indicates imminent shutdown of the radio and display; disconnect from source void a2dp_shutdown(){ + vTaskSuspend(canMessageDecoderTaskHandle); + vTaskSuspend(canWatchdogTaskHandle); ESP.restart(); // very crude workaround until I find a better way to deal with reconnection problems after end() is called delay(1000); a2dp_sink.disconnect(); diff --git a/src/CAN.ino b/src/CAN.ino index 69a683e..f4e4023 100644 --- a/src/CAN.ino +++ b/src/CAN.ino @@ -1,6 +1,9 @@ #include "driver/twai.h" void OTAhandleTask(void* pvParameters); +// CAN-related variables +volatile uint8_t canISO_frameSpacing=1; // simple implementation of ISO 15765-2 variable frame spacing, based on flow control frames by the receiving node + // defining static CAN frames for simulation const twai_message_t simulate_scroll_up={ .identifier=0x201, .data_length_code=3, .data={0x08, 0x6A, 0x01}}, simulate_scroll_down={ .identifier=0x201, .data_length_code=3, .data={0x08, 0x6A, 0xFF}}, @@ -13,6 +16,7 @@ const twai_message_t simulate_scroll_up={ .identifier=0x201, .data_length_code= Msg_MeasurementRequestDIS={ .identifier=0x246, .data_length_code=8, .data={0x07, 0xAA, 0x03, 0x01, 0x0B, 0x0B, 0x0B, 0x0B}}, Msg_MeasurementRequestECC={ .identifier=0x248, .data_length_code=7, .data={0x06, 0xAA, 0x01, 0x01, 0x07, 0x10, 0x11}}; +// can't initialize the values of the union inside the twai_message_t type struct, which is why it's defined here, then the transmit task sets the .ss flag twai_message_t Msg_PreventDisplayUpdate={ .identifier=0x2C1, .data_length_code=8, .data={0x30, 0x0, 0x7F, 0, 0, 0, 0, 0}}; // initializing CAN communication @@ -51,7 +55,7 @@ void canReceiveTask(void *pvParameters){ switch(Recvd_CAN_MSG.identifier){ case 0x6C1: { if(disp_mode!=-1){ // don't bother checking the data if there's no need to update the display - if(Recvd_CAN_MSG.data[0]==0x10 && (Recvd_CAN_MSG.data[1]<0x40 || (Recvd_CAN_MSG.data[1]<0x4F && Recvd_CAN_MSG.data[2]==0x50))){ // we check if the total payload of radio's message is small, if yes assume it's an Aux message + if(Recvd_CAN_MSG.data[0]==0x10 && (Recvd_CAN_MSG.data[2]==0x40 || Recvd_CAN_MSG.data[2]==0xC0) && Recvd_CAN_MSG.data[5]==0x03 && (disp_mode!=0 || CAN_allowDisplay)){ // another task processes the data since we can't do that here DEBUG_PRINTLN("CAN: Received display update, trying to block"); twai_transmit(&Msg_PreventDisplayUpdate, pdMS_TO_TICKS(30)); // radio blocking msg has to be transmitted ASAP, which is why we skip the queue twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(10)); // read stats @@ -86,6 +90,7 @@ void canReceiveTask(void *pvParameters){ // this task processes filtered CAN frames read from canRxQueue void canProcessTask(void *pvParameters){ static twai_message_t RxMsg; + uint8_t payload_size=0, payload_bytes_queued=0, payload_type=0; while(1){ xQueueReceive(canRxQueue, &RxMsg, portMAX_DELAY); // receives data from the internal queue switch(RxMsg.identifier){ @@ -118,7 +123,7 @@ void canProcessTask(void *pvParameters){ break; } case 0x206: { // decodes steering wheel buttons - if(bt_connected && RxMsg.data[0]==0x0){ + if(bt_connected && RxMsg.data[0]==0x0 && CAN_allowDisplay){ // makes sure "Aux" is displayed, otherwise forward/next buttons will have no effect switch(RxMsg.data[1]){ case 0x81: if(bt_audio_playing){ // upper left button (box with waves) a2dp_sink.pause(); @@ -131,7 +136,7 @@ void canProcessTask(void *pvParameters){ case 0x92: a2dp_sink.previous(); // lower right button (arrow down) break; default: break; - } + } } break; } @@ -141,8 +146,11 @@ void canProcessTask(void *pvParameters){ } break; } - case 0x2C1: if(CAN_MessageReady) xTaskNotifyGive(canDisplayTaskHandle); // let the display update task know that the data is ready to be transmitted - break; + case 0x2C1: { + if(CAN_MessageReady) xTaskNotifyGive(canDisplayTaskHandle); // let the display update task know that the data is ready to be transmitted + canISO_frameSpacing=RxMsg.data[2]; // dynamically adjust ISO 15765-2 frame spacing delay + break; + } case 0x501: { // CD30MP3 goes to sleep -> disable bluetooth connectivity if(a2dp_started && RxMsg.data[3]==0x18){ a2dp_shutdown(); @@ -150,33 +158,35 @@ void canProcessTask(void *pvParameters){ break; } case 0x546: { // display measurement blocks (used as a fallback) - if(disp_mode==1 || disp_mode==2) xSemaphoreTake(CAN_MsgSemaphore, portMAX_DELAY); - DEBUG_PRINT("CAN: Got measurements from DIS: "); - switch(RxMsg.data[0]){ // measurement block ID -> update data which the message is referencing, I may implement more cases in the future which is why switch is there - case 0x0B: { // 0x0B references coolant temps - DEBUG_PRINT("coolant\n"); - int CAN_data_coolant=RxMsg.data[5]-40; - snprintf(voltage_buffer, sizeof(voltage_buffer), "No additional data available"); - snprintf(coolant_buffer, sizeof(coolant_buffer), "Coolant temp: %d%c%cC ", CAN_data_coolant, 0xC2, 0xB0); - //snprintf(speed_buffer, sizeof(speed_buffer), "ECC not present"); // -> speed received as part of the 0x4E8 msg - CAN_coolant_recvd=1; - #ifdef DEBUG - CAN_speed_recvd=1; - #endif - break; + if(disp_mode==1 || disp_mode==2){ + xSemaphoreTake(BufferSemaphore, portMAX_DELAY); + DEBUG_PRINT("CAN: Got measurements from DIS: "); + switch(RxMsg.data[0]){ // measurement block ID -> update data which the message is referencing, I may implement more cases in the future which is why switch is there + case 0x0B: { // 0x0B references coolant temps + DEBUG_PRINT("coolant\n"); + int CAN_data_coolant=RxMsg.data[5]-40; + snprintf(voltage_buffer, sizeof(voltage_buffer), "No additional data available"); + snprintf(coolant_buffer, sizeof(coolant_buffer), "Coolant temp: %d%c%cC ", CAN_data_coolant, 0xC2, 0xB0); + //snprintf(speed_buffer, sizeof(speed_buffer), "ECC not present"); // -> speed received as part of the 0x4E8 msg + CAN_coolant_recvd=1; + #ifdef DEBUG + CAN_speed_recvd=1; // a workaround for my desktop setup (can't sniff the speed from 0x4E8) + #endif + break; + } + default: break; } - default: break; - } - if(CAN_coolant_recvd && CAN_speed_recvd){ - CAN_speed_recvd=0; - CAN_coolant_recvd=0; - CAN_new_dataSet_recvd=1; + if(CAN_coolant_recvd && CAN_speed_recvd){ + CAN_speed_recvd=0; + CAN_coolant_recvd=0; + CAN_new_dataSet_recvd=1; + } + xSemaphoreGive(BufferSemaphore); // let the message processing continue } - if(disp_mode==1 || disp_mode==2) xSemaphoreGive(CAN_MsgSemaphore); // let the message processing continue break; } case 0x548: { // AC measurement blocks - if(disp_mode==1 || disp_mode==2) xSemaphoreTake(CAN_MsgSemaphore, portMAX_DELAY); // if we're in body data mode, take the semaphore to prevent the buffer being modified while the display message is being compiled + if(disp_mode==1 || disp_mode==2) xSemaphoreTake(BufferSemaphore, portMAX_DELAY); // if we're in body data mode, take the semaphore to prevent the buffer being modified while the display message is being compiled DEBUG_PRINT("CAN: Got measurements from ECC: "); switch(RxMsg.data[0]){ // measurement block ID -> update data which the message is referencing case 0x07: { // 0x10 references battery voltage @@ -212,28 +222,48 @@ void canProcessTask(void *pvParameters){ CAN_speed_recvd=0; CAN_new_dataSet_recvd=1; } - if(disp_mode==1 || disp_mode==2) xSemaphoreGive(CAN_MsgSemaphore); // let the message processing continue + if(disp_mode==1 || disp_mode==2) xSemaphoreGive(BufferSemaphore); // let the message processing continue break; } - case 0x4E8: { // this provides speed and RPMs right from the bus, only for disp_mode==1 + case 0x4E8: { // this provides speed and RPMs right from the bus, only for 3-line measurement mode (disp_mode 1) IF there's no ECC module detected if((disp_mode==1) && !ECC_present){ - if(disp_mode==1) xSemaphoreTake(CAN_MsgSemaphore, portMAX_DELAY); + if(disp_mode==1) xSemaphoreTake(BufferSemaphore, portMAX_DELAY); CAN_data_rpm=(RxMsg.data[2]<<8 | RxMsg.data[3]); - CAN_data_speed=RxMsg.data[4]; + CAN_data_rpm/=4; // realized this thanks to testing done by @KingSilverHaze + if(RxMsg.data[6]=0x01){ // vehicle is standing still -> bytes 4 and 5 not updated, assume 0 km/h + CAN_data_speed=0; // when not moving, the speed value will not reflect 0 km/h + } else { + CAN_data_speed=(RxMsg.data[4]<<8 | RxMsg.data[5]); // speed is a 16-bit integer multiplied by 128 + CAN_data_speed/=128; + } snprintf(speed_buffer, sizeof(speed_buffer), "%d km/h %d RPM ", CAN_data_speed, CAN_data_rpm); CAN_speed_recvd=1; - if(disp_mode==1) xSemaphoreGive(CAN_MsgSemaphore); // let the message processing continue + if(disp_mode==1) xSemaphoreGive(BufferSemaphore); // let the message processing continue } break; } case 0x6C1: { // radio requests a display update - if(!a2dp_started){ // if the total payload is less than 0x40 bytes the message will not be sent (so FM radio or other stuff isn't overwritten) + if(!a2dp_started){ ehu_started=1; // start the bluetooth A2DP service after first radio display call disp_mode=0; } else if(a2dp_started && !ehu_started){ a2dp_sink.reconnect(); ehu_started=1; } + if(disp_mode==0){ // if not a consecutive frame, then we queue that data for decoding by another task + if(RxMsg.data[0]==0x10 && (RxMsg.data[2]==0x40 || RxMsg.data[2]==0xC0)){ + payload_size=RxMsg.data[1]-6; + payload_bytes_queued=0; // reset the counter + payload_type=RxMsg.data[5]; // this is a hack for CD70/DVD90, because they utilize an audio menu, they send messages "under the hood" which don't contain "Aux" + if(payload_type==0x03) xQueueSend(canDispQueue, &payload_size, portMAX_DELAY); // queue payload size decreased by 6 since we don't count the 6 bytes in first frame + } else { + for(int i=1; i<=7 && payload_bytes_queued0){ // if there are bytes left to be processed but are not enough for a complete message, process them now + CAN_MsgArray[packetCount][0]=frameIndex; + memcpy(&CAN_MsgArray[packetCount][1], &buffer_to_read[packetCount*7], bytesToProcess); + packetCount++; + } + CAN_MsgArray[packetCount+1][0]=0x0; // remove the next frame label if there was any, as such it will not be transmitted +} + // sends a message request to the display void sendMultiPacket(){ // main loop shall decide when to send the following data static twai_message_t MsgToTx; @@ -458,7 +531,7 @@ void sendMultiPacketData(){ // should only be executed after the display ackno for(int i=1;i<64 && (CAN_MsgArray[i][0]!=0x00 && !CAN_prevTxFail);i++){ // this loop will stop sending data once the next packet doesn't contain a label memcpy(MsgToTx.data, CAN_MsgArray[i], 8); xQueueSend(canTxQueue, &MsgToTx, portMAX_DELAY); - vTaskDelay(pdMS_TO_TICKS(1)); + vTaskDelay(pdMS_TO_TICKS(canISO_frameSpacing)); // receiving node can request a variable frame spacing time, we take it into account here, so far I've seen BID request 2ms while GID/CID request 0ms (no delay) } DEBUG_PRINTLN(); if(CAN_prevTxFail){ diff --git a/src/EHU32.ino b/src/EHU32.ino index f7319da..955bc8b 100644 --- a/src/EHU32.ino +++ b/src/EHU32.ino @@ -1,11 +1,13 @@ #include "AudioTools.h" #include "BluetoothA2DPSink.h" +#include "esp_sleep.h" #include "driver/twai.h" -#include + // defining DEBUG enables Serial I/O for simulating button presses or faking measurement blocks through a separate RTOS task //#define DEBUG #ifndef DEBUG +#include #include #include #endif @@ -27,16 +29,16 @@ #endif // pin definitions -const int PCM_MUTE_CTL=23; // this pin controls PCM5102s soft-mute function +const int PCM_MUTE_CTL=23, PCM_ENABLE=27; // D23 controls PCM5102s soft-mute function, D27 enables PCM5102s power // RTOS stuff -TaskHandle_t canReceiveTaskHandle, canDisplayTaskHandle, canProcessTaskHandle, canTransmitTaskHandle, canWatchdogTaskHandle, canAirConMacroTaskHandle; -QueueHandle_t canRxQueue, canTxQueue; -SemaphoreHandle_t CAN_MsgSemaphore=NULL; +TaskHandle_t canReceiveTaskHandle, canDisplayTaskHandle, canProcessTaskHandle, canTransmitTaskHandle, canWatchdogTaskHandle, canAirConMacroTaskHandle, canMessageDecoderTaskHandle, eventHandlerTaskHandle; +QueueHandle_t canRxQueue, canTxQueue, canDispQueue; +SemaphoreHandle_t CAN_MsgSemaphore=NULL, BufferSemaphore=NULL; // TWAI driver stuff uint32_t alerts_triggered; twai_status_info_t status_info; // CAN related flags -volatile bool DIS_forceUpdate=0, CAN_MessageReady=0, CAN_prevTxFail=0, CAN_flowCtlFail=0, CAN_speed_recvd=0, CAN_coolant_recvd=0, CAN_voltage_recvd=0, CAN_new_dataSet_recvd=0, CAN_measurements_requested=0, disp_mode_changed=0; +volatile bool DIS_forceUpdate=0, CAN_MessageReady=0, CAN_prevTxFail=0, CAN_flowCtlFail=0, CAN_speed_recvd=0, CAN_coolant_recvd=0, CAN_voltage_recvd=0, CAN_new_dataSet_recvd=0, CAN_measurements_requested=0, disp_mode_changed=0, CAN_allowDisplay=0; // measurement related flags volatile bool ECC_present=0; // global bluetooth flags @@ -65,140 +67,178 @@ void sendMultiPacket(); void sendMultiPacketData(); void setup(){ + esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, 0); // this will wake the ESP32 up if there's CAN activity pinMode(PCM_MUTE_CTL, OUTPUT); + pinMode(PCM_ENABLE, OUTPUT); // control PCM5102 power setting digitalWrite(PCM_MUTE_CTL, HIGH); - vTaskDelay(pdMS_TO_TICKS(100)); + digitalWrite(PCM_ENABLE, HIGH); DEBUG_SERIAL(921600); // serial comms for debug - twai_init(); + twai_init(); // sets up everything CAN-bus related + twai_message_t testMsg; + if(twai_receive(&testMsg, pdMS_TO_TICKS(100))!=ESP_OK){ // if there's no activity on the bus, assume the vehicle is off, go to sleep and wake up after 5 seconds + DEBUG_PRINTLN("CAN inactive. Back to sleep!"); + #ifdef DEBUG + vTaskDelay(pdMS_TO_TICKS(10)); // wait for a bit for the buffer to be transmitted when debugging + #endif + esp_deep_sleep_start(); // enter deep sleep + } + + digitalWrite(PCM_ENABLE, LOW); // enable PCM5102 and wake SN65HVD230 up from standby (active low in case of KF50BD) + CAN_MsgSemaphore=xSemaphoreCreateMutex(); // as stuff is done asynchronously, we need to make sure that the message will not be transmitted when its being written to - canRxQueue=xQueueCreate(QUEUE_LENGTH, MSG_SIZE); - canTxQueue=xQueueCreate(QUEUE_LENGTH, MSG_SIZE); + BufferSemaphore=xSemaphoreCreateMutex(); // CAN_MsgSemaphore is used when encoding the message and transmitting it, while BufferSemaphore is used when acquiring new data or encoding the message + canRxQueue=xQueueCreate(QUEUE_LENGTH, MSG_SIZE); // internal EHU32 queue for messages to be handled by the canProcessTask + canTxQueue=xQueueCreate(QUEUE_LENGTH, MSG_SIZE); // internal EHU32 queue for messages to be transmitted + canDispQueue=xQueueCreate(255, sizeof(uint8_t)); // queue used for handling of raw ISO 15765-2 data that's meant for the display (Aux string detection) // FreeRTOS tasks xTaskCreate(canReceiveTask, "CANbusReceiveTask", 4096, NULL, 1, &canReceiveTaskHandle); xTaskCreate(canTransmitTask, "CANbusTransmitTask", 4096, NULL, 2, &canTransmitTaskHandle); xTaskCreate(canProcessTask, "CANbusMessageProcessor", 8192, NULL, 3, &canProcessTaskHandle); xTaskCreate(canDisplayTask, "DisplayUpdateTask", 8192, NULL, 3, &canDisplayTaskHandle); - xTaskCreatePinnedToCore(canWatchdogTask, "WatchdogTask", 2048, NULL, 20, &canWatchdogTaskHandle, 0); vTaskSuspend(canDisplayTaskHandle); + xTaskCreatePinnedToCore(canWatchdogTask, "WatchdogTask", 2048, NULL, 20, &canWatchdogTaskHandle, 0); + xTaskCreatePinnedToCore(canMessageDecoder, "MessageDecoder", 2048, NULL, 5, &canMessageDecoderTaskHandle, 0); + vTaskSuspend(canMessageDecoderTaskHandle); #ifdef DEBUG xTaskCreate(CANsimTask, "CANbusSimulateEvents", 2048, NULL, 4, NULL); // allows to simulate button presses through serial #endif - xTaskCreate(canAirConMacroTask, "AirConMacroTask", 2048, NULL, 10, &canAirConMacroTaskHandle); + xTaskCreatePinnedToCore(canAirConMacroTask, "AirConMacroTask", 2048, NULL, 10, &canAirConMacroTaskHandle, 0); vTaskSuspend(canAirConMacroTaskHandle); // Aircon macro task exists solely to execute simulated button presses asynchronously, as such it is only started when needed + + xTaskCreatePinnedToCore(eventHandlerTask, "eventHandler", 8192, NULL, 4, &eventHandlerTaskHandle, 0); } // this task monitors radio messages and resets the program if the radio goes to sleep or CAN dies void canWatchdogTask(void *pvParameters){ static BaseType_t notifResult; while(1){ - if(ehu_started){ - notifResult=xTaskNotifyWait(0, 0, NULL, pdMS_TO_TICKS(15000)); // wait for a notification that display packet from the radio unit has been received - if(notifResult==pdFAIL){ // if the notification has not been received in the specified timeframe (radio sends its display messages each 5s, specified timeout of 15s for safety) we assume the radio is off - DEBUG_PRINTLN("WATCHDOG: Triggering software reset..."); - vTaskDelay(pdMS_TO_TICKS(100)); - a2dp_shutdown(); // this or disp_mode=-1? - } else { - DEBUG_PRINTLN("WATCHDOG: Reset successful."); - } + notifResult=xTaskNotifyWait(0, 0, NULL, pdMS_TO_TICKS(15000)); // wait for a notification that display packet from the radio unit has been received + if(notifResult==pdFAIL){ // if the notification has not been received in the specified timeframe (radio sends its display messages each 5s, specified timeout of 15s for safety) we assume the radio is off + DEBUG_PRINTLN("WATCHDOG: Triggering software reset..."); + vTaskDelay(pdMS_TO_TICKS(100)); + a2dp_shutdown(); // this or disp_mode=-1? + } else { + DEBUG_PRINTLN("WATCHDOG: Reset successful."); + xTaskNotifyStateClear(NULL); } vTaskDelay(pdMS_TO_TICKS(1000)); } } // processes data based on the current value of disp_mode or prints one-off messages by specifying the data in arguments; message is then transmitted right away +// it acts as a bridge between UTF-8 text data and the resulting CAN messages meant to be transmitted to the display void writeTextToDisplay(bool disp_mode_override=0, char* up_line_text=nullptr, char* mid_line_text=nullptr, char* low_line_text=nullptr){ // disp_mode_override exists as a simple way to print one-off messages (like board status, errors and such) xSemaphoreTake(CAN_MsgSemaphore, portMAX_DELAY); // take the semaphore as a way to prevent any transmission when the message structure is being written + xSemaphoreTake(BufferSemaphore, portMAX_DELAY); // we take both semaphores, since this task specifically interacts with both the internal data buffers and the CAN message buffer if(!disp_mode_override){ - if(disp_mode==0 && (album_buffer[0]!='\0' || title_buffer[0]!='\0' || artist_buffer[0]!='\0')){ + if(disp_mode==0 && (album_buffer[0]!='\0' || title_buffer[0]!='\0' || artist_buffer[0]!='\0')){ // audio metadata mode prepareMultiPacket(processDisplayMessage(album_buffer, title_buffer, artist_buffer), DisplayMsg); // prepare a 3-line message (audio Title, Album and Artist) } else { - if(disp_mode==1){ + if(disp_mode==1){ // vehicle data mode (3-line) prepareMultiPacket(processDisplayMessage(coolant_buffer, speed_buffer, voltage_buffer), DisplayMsg); // vehicle data buffer } - if(disp_mode==2){ - prepareMultiPacket(processDisplayMessage(nullptr, coolant_buffer, nullptr), DisplayMsg); // vehicle data buffer + if(disp_mode==2){ // coolant mode (1-line) + prepareMultiPacket(processDisplayMessage(nullptr, coolant_buffer, nullptr), DisplayMsg); // vehicle data buffer (single line) } } } else { // overriding buffers prepareMultiPacket(processDisplayMessage(up_line_text, mid_line_text, low_line_text), DisplayMsg); } xSemaphoreGive(CAN_MsgSemaphore); + xSemaphoreGive(BufferSemaphore); // releasing semaphores DIS_forceUpdate=0; vTaskResume(canDisplayTaskHandle); // resume display task, it will suspend itself after its job is done } -// loop will process interrupt flags and print text based on what happened -void loop(){ - if(OTA_begin){ - disp_mode=-1; - writeTextToDisplay(1, "OTA initiated", "BT off, OTA on", "Waiting for FW..."); - vTaskDelay(1000); - vTaskSuspend(canReceiveTaskHandle); - vTaskSuspend(canTransmitTaskHandle); - vTaskSuspend(canProcessTaskHandle); - vTaskSuspend(canDisplayTaskHandle); - vTaskSuspend(canWatchdogTaskHandle); // so I added the watchdog but forgot to suspend it when starting OTA. result? Couldn't update it inside the car and had to take the radio unit out to do it manually - #ifndef DEBUG - OTA_Handle(); - #endif - } - - if(disp_mode==1 && ehu_started){ // if running in measurement block mode, check time and if enough time has elapsed ask for new data - if((last_millis_req+250) REINSTALLING"); + vTaskSuspend(canReceiveTaskHandle); + vTaskSuspend(canTransmitTaskHandle); + vTaskSuspend(canProcessTaskHandle); + vTaskSuspend(canDisplayTaskHandle); + vTaskSuspend(canWatchdogTaskHandle); + //twai_initiate_recovery(); // twai_initiate_recovery(); leads to hard crashes - it's something that ESP-IDF need to fix + twai_stop(); + if(twai_driver_uninstall()==ESP_OK){ + DEBUG_PRINTLN("CAN: TWAI DRIVER UNINSTALL OK"); + } else { + DEBUG_PRINTLN("CAN: TWAI DRIVER UNINSTALL FAIL!!! Rebooting..."); // total fail - just reboot at this point + vTaskDelay(pdMS_TO_TICKS(100)); + ESP.restart(); + } + vTaskDelay(100); + twai_init(); + vTaskDelay(100); + vTaskResume(canReceiveTaskHandle); + vTaskResume(canTransmitTaskHandle); + vTaskResume(canProcessTaskHandle); + vTaskResume(canDisplayTaskHandle); + vTaskResume(canWatchdogTaskHandle); } - } - twai_get_status_info(&status_info); - // this will try to get CAN back up in case it fails - if((status_info.state==TWAI_STATE_BUS_OFF) || (twai_get_status_info(&status_info)==ESP_ERR_INVALID_STATE)){ - DEBUG_PRINTLN("CAN: DETECTED BUS OFF. TRYING TO RECOVER -> REINSTALLING"); - vTaskSuspend(canReceiveTaskHandle); - vTaskSuspend(canTransmitTaskHandle); - vTaskSuspend(canProcessTaskHandle); - vTaskSuspend(canDisplayTaskHandle); - vTaskSuspend(canWatchdogTaskHandle); - //twai_initiate_recovery(); // twai_initiate_recovery(); leads to hard crashes - it's something that ESP-IDF need to fix - twai_stop(); - if(twai_driver_uninstall()==ESP_OK){ - DEBUG_PRINTLN("CAN: TWAI DRIVER UNINSTALL OK"); - } else { - DEBUG_PRINTLN("CAN: TWAI DRIVER UNINSTALL FAIL!!! Rebooting..."); // total fail - just reboot at this point - vTaskDelay(pdMS_TO_TICKS(100)); - ESP.restart(); + if(DIS_forceUpdate && disp_mode==0 && CAN_allowDisplay){ // handles data processing for A2DP AVRC data events + writeTextToDisplay(); } - vTaskDelay(100); - twai_init(); - vTaskDelay(100); - vTaskResume(canReceiveTaskHandle); - vTaskResume(canTransmitTaskHandle); - vTaskResume(canProcessTaskHandle); - vTaskResume(canDisplayTaskHandle); - vTaskResume(canWatchdogTaskHandle); + + A2DP_EventHandler(); // process bluetooth and audio flags set by interrupt callbacks + vTaskDelay(10); } +} - if(DIS_forceUpdate && disp_mode==0){ // handles data processing for A2DP AVRC data events - writeTextToDisplay(); - } - - A2DP_EventHandler(); // process bluetooth and audio flags set by interrupt callbacks +// loop will do nothing +void loop(){ + vTaskDelay(pdMS_TO_TICKS(1000)); } \ No newline at end of file diff --git a/src/TextHandler.ino b/src/TextHandler.ino index 68c25c6..37ac313 100644 --- a/src/TextHandler.ino +++ b/src/TextHandler.ino @@ -1,7 +1,7 @@ // below is data required to be included in every line - text formatting is based on those const char DIS_leftadjusted[14]={0x00,0x1B,0x00,0x5B,0x00,0x66,0x00,0x53,0x00,0x5F,0x00,0x67,0x00,0x6D}, DIS_smallfont[14]={0x00,0x1B,0x00,0x5B,0x00,0x66,0x00,0x53,0x00,0x5F,0x00,0x64,0x00,0x6D}, DIS_centered[8]={0x00, 0x1B, 0x00, 0x5B, 0x00, 0x63, 0x00, 0x6D}, DIS_rightadjusted[8]={0x00, 0x1B, 0x00, 0x5B, 0x00, 0x72, 0x00, 0x6D}; -// process an UTF-8 buffer, returns the amount of chars processed +// converts an UTF-8 buffer to UTF-16, filters out unsupported chars, returns the amount of chars processed unsigned int utf8_to_utf16(const char* utf8_buffer, char* utf16_buffer){ unsigned int utf16_bytecount=0; while (*utf8_buffer!='\0'){ @@ -53,7 +53,7 @@ unsigned int utf8_to_utf16(const char* utf8_buffer, char* utf16_buffer){ int processDisplayMessage(char* upper_line_buffer, char* middle_line_buffer, char* lower_line_buffer){ static char utf16_middle_line[128], utf16_lower_line[128], utf16_upper_line[128]; int upper_line_buffer_length=0, middle_line_buffer_length=0, lower_line_buffer_length=0; - if(upper_line_buffer!=nullptr){ // calculating string lengths to keep track of processed data + if(upper_line_buffer!=nullptr){ // converting UTF-8 strings to UTF-16 and calculating string lengths to keep track of processed data upper_line_buffer_length=utf8_to_utf16(upper_line_buffer, utf16_upper_line); } if(middle_line_buffer!=nullptr){ @@ -63,7 +63,7 @@ int processDisplayMessage(char* upper_line_buffer, char* middle_line_buffer, cha lower_line_buffer_length=utf8_to_utf16(lower_line_buffer, utf16_lower_line); } - #ifdef DEBUG // debug stuff + #ifdef DEBUG_STRINGS // debug stuff Serial.printf("\nTitle length: %d", middle_line_buffer_length); Serial.printf("\nAlbum length: %d", upper_line_buffer_length); Serial.printf("\nArtist length: %d", lower_line_buffer_length); @@ -101,12 +101,12 @@ int processDisplayMessage(char* upper_line_buffer, char* middle_line_buffer, cha //DisplayMsg[3]= message size -3 DisplayMsg[4]=0x03; //type - int last_byte_written=4; // this tracks the current position in buffer + int last_byte_written=4; // this tracks the current position in buffer, 4 because of the first four bytes which go as follows: [size] 40 00 [size-3] // SONG TITLE FIELD last_byte_written++; - DisplayMsg[last_byte_written]=0x10; + DisplayMsg[last_byte_written]=0x10; // specifying "title" field (middle line, or the only line of text in case of displays such as 1-line GID/BID/TID) last_byte_written++; // we skip DisplayMsg[6], its filled in the end (char count for id 0x10) - if(middle_line_buffer_length>1){ // if the upper line data is just a space, don't apply formatting - saves 2 frames of data + if(middle_line_buffer_length>1){ // if the middle line data is just a space, don't apply formatting - saves 2 frames of data memcpy(DisplayMsg+last_byte_written+1, DIS_leftadjusted, sizeof(DIS_leftadjusted)); last_byte_written+=sizeof(DIS_leftadjusted); DisplayMsg[6]=sizeof(DIS_leftadjusted)/2; @@ -119,7 +119,7 @@ int processDisplayMessage(char* upper_line_buffer, char* middle_line_buffer, cha int album_count_pos=10; // ALBUM FIELD last_byte_written++; - DisplayMsg[last_byte_written]=0x11; + DisplayMsg[last_byte_written]=0x11; // specifying "album" field (upper line) last_byte_written++; album_count_pos=last_byte_written; if(upper_line_buffer_length>=1){ // if the upper line data is just a space, don't apply formatting - saves 2 frames of data @@ -134,10 +134,10 @@ int processDisplayMessage(char* upper_line_buffer, char* middle_line_buffer, cha int artist_count_pos=album_count_pos; // ARTIST FIELD last_byte_written++; - DisplayMsg[last_byte_written]=0x12; + DisplayMsg[last_byte_written]=0x12; // specifying "artist" field (lower line) last_byte_written++; artist_count_pos=last_byte_written; - if(lower_line_buffer_length>=1){ // if the upper line data is just a space, don't apply formatting - saves 2 frames of data + if(lower_line_buffer_length>=1){ // if the lower line data is just a space, don't apply formatting - saves 2 frames of data memcpy(DisplayMsg+last_byte_written+1, DIS_smallfont, sizeof(DIS_smallfont)); last_byte_written+=sizeof(DIS_smallfont); DisplayMsg[artist_count_pos]=sizeof(DIS_smallfont)/2; @@ -147,7 +147,7 @@ int processDisplayMessage(char* upper_line_buffer, char* middle_line_buffer, cha DisplayMsg[artist_count_pos]+=lower_line_buffer_length; if((last_byte_written+1)%7==0){ // if the amount of bytes were to result in a full packet (ie no unused bytes), add a char to overflow into the next packet - DisplayMsg[artist_count_pos]+=1; // workaround because if the packets are full the display would ignore the message + DisplayMsg[artist_count_pos]+=1; // workaround because if the packets are full the display would ignore the message. This is explained on the EHU32 wiki DisplayMsg[last_byte_written+1]=0x00; DisplayMsg[last_byte_written+2]=0x20; last_byte_written+=2; }