diff --git a/alles-flashing.md b/alles-flashing.md index 7009c20..5286bdb 100644 --- a/alles-flashing.md +++ b/alles-flashing.md @@ -20,12 +20,12 @@ To do this, you need to set up the following things: ESP-IDF is the set of open source tools and libraries that work on the CPU powering Alles, the ESP32. You should first install ESP-IDF on your system if you haven't already. -Download the supported version of ESP-IDF. That is currently 5.1-rc2. [You can download it directly here.](https://dl.espressif.com/github_assets/espressif/esp-idf/releases/download/v5.1-rc2/esp-idf-v5.1-rc2.zip) Unpack it to a folder. I like to keep them in `~/esp/`, as you'll likely want to use different versions eventually. So we'll assume it's in `~/esp/esp-idf-v5.1-rc2`. +Download the supported version of ESP-IDF. That is currently 5.2. [You can download it directly here.](https://dl.espressif.com/github_assets/espressif/esp-idf/releases/download/v5.2/esp-idf-v5.2.zip) Unpack it to a folder. I like to keep them in `~/esp/`, as you'll likely want to use different versions eventually. So we'll assume it's in `~/esp/esp-idf-v5.2`. ``` # install ESP-IDF that comes with our repository -~/esp/esp-idf-v5.1-rc2/install.sh esp32 -source ~/esp/esp-idf-v5.1-rc2/export.sh +~/esp/esp-idf-v5.2/install.sh esp32 +source ~/esp/esp-idf-v5.2/export.sh ``` If you have trouble, for more info, or for other platforms, you should follow [the detailed instructions to download and set up `esp-idf`](http://esp-idf.readthedocs.io/en/latest/get-started/). @@ -44,7 +44,7 @@ The cable you may have received from us for the hardware speaker is charge only. ### Flash -Then, in the `esp` folder you created during installing the ESP-IDF above (e.g. `cd ~/esp`), run `. ./esp-idf-v5.1-rc2/export.sh`. Now, in the same terminal window (export.sh sets some environment variables), cd back into the alles repository folder you downloaded and run `idf.py flash` to build and flash to the board. It will take a couple of minutes and show you progress. The board will reboot into the latest firmware. +Then, in the `esp` folder you created during installing the ESP-IDF above (e.g. `cd ~/esp`), run `. ./esp-idf-v5.2/export.sh`. Now, in the same terminal window (export.sh sets some environment variables), cd back into the alles repository folder you downloaded and run `idf.py flash` to build and flash to the board. It will take a couple of minutes and show you progress. The board will reboot into the latest firmware. (If the flashing process doesn't work, it's likely not finding your UART location. Type `ls /dev/*usb*` to find something like `/dev/tty.usbserial.XXXXX` or `/dev/cu.usbserialXXXX`. You'll want to find the tty that appears when you connect the speaker to computer. Copy this location and try flashing again with `idf.py -p /dev/YOUR_SERIAL_TTY flash`.) diff --git a/alles.py b/alles.py index 46e2084..7d454f5 100644 --- a/alles.py +++ b/alles.py @@ -1,4 +1,4 @@ -import socket, struct, datetime, os, time, sys +import socket, struct, datetime, os, time, sys, datetime sys.path.append('amy') import amy from amy import * @@ -28,20 +28,19 @@ def flush(retries=1): transmit(send_buffer) send_buffer = "" -def send(retries=1, **kwargs): +def alles_send(message, retries=1): global send_buffer - m = message(**kwargs) if(buffer_size > 0): - if(len(send_buffer + m) > buffer_size): + if(len(send_buffer + message) > buffer_size): transmit(send_buffer, retries=retries) - send_buffer = m + send_buffer = message else: send_buffer = send_buffer + m else: - transmit(m,retries=retries) + transmit(message,retries=retries) # We override AMY's send function to send out to the mesh instead of locally -amy.override_send = send +amy.override_send = alles_send @@ -118,6 +117,12 @@ def decode_battery_mask(mask): if (mask & 0x80): level = 1 return(state, level) +def millis(): + now = datetime.datetime.now() + midnight = datetime.datetime.combine(now.date(), datetime.time.min) + delta = now - midnight + milliseconds = (delta.total_seconds() * 1000) + (delta.microseconds / 1000) + return int(milliseconds) def sync(count=10, delay_ms=100): global sock @@ -136,7 +141,7 @@ def sync(count=10, delay_ms=100): if((tic - last_sent) > delay_ms): time_sent[i] = millis() #print ("sending %d at %d" % (i, time_sent[i])) - output = "s%di%dZ" % (time_sent[i], i) + output = "U%di%dZ" % (time_sent[i], i) sock.sendto(output.encode('ascii'), get_multicast_group()) i = i + 1 last_sent = tic @@ -147,7 +152,7 @@ def sync(count=10, delay_ms=100): if(data[0] == '_'): data = data[:-1] try: - [_, client_time, sync_index, client_id, ipv4, battery] = re.split(r'[sicry]',data) + [_, client_time, sync_index, client_id, ipv4, battery] = re.split(r'[Uigry]',data) except ValueError: print("What! %s" % (data)) if(int(sync_index) <= i): # skip old ones from a previous run diff --git a/amy b/amy index f2acbfa..33fb9b4 160000 --- a/amy +++ b/amy @@ -1 +1 @@ -Subproject commit f2acbfa6e4384c53caaa39dfd8f9317dfe698db5 +Subproject commit 33fb9b4363c2302557fbd29b0869b9ab8c139419 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 69c43e3..1d5a95f 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -4,7 +4,11 @@ idf_component_register(SRCS alles.c buttons.c sounds.c power.c + ../amy/src/log2_exp2.c ../amy/src/amy.c + ../amy/src/custom.c + ../amy/src/delay.c + ../amy/src/patches.c ../amy/src/algorithms.c ../amy/src/oscillators.c ../amy/src/pcm.c @@ -20,7 +24,10 @@ list(APPEND IDF_COMPONENTS mdns ) -target_compile_definitions(${COMPONENT_TARGET} PUBLIC "-DALLES") +target_compile_options(${COMPONENT_TARGET} PUBLIC + -DALLES + -Wno-uninitialized +) set_source_files_properties(alles_esp32.c alles.c ../amy/src/amy.c PROPERTIES COMPILE_FLAGS diff --git a/main/Makefile b/main/Makefile index eba4f46..9b3bf2a 100644 --- a/main/Makefile +++ b/main/Makefile @@ -13,7 +13,8 @@ CC = gcc CFLAGS = -g -Wall -Wno-strict-aliasing -I$(AMY) -I. OBJECTS = $(patsubst %.c, %.o, multicast_desktop.c alles_desktop.c alles.c sounds.c $(AMY)/algorithms.c $(AMY)/delay.c \ - $(AMY)/amy.c $(AMY)/envelope.c $(AMY)/filters.c $(AMY)/oscillators.c $(AMY)/pcm.c $(AMY)/partials.c $(AMY)/libminiaudio-audio.c) + $(AMY)/amy.c $(AMY)/envelope.c $(AMY)/filters.c $(AMY)/oscillators.c $(AMY)/pcm.c $(AMY)/partials.c $(AMY)/libminiaudio-audio.c \ + $(AMY)/log2_exp2.c $(AMY)/custom.c $(AMY)/patches.c) HEADERS = alles.h $(wildcard amy/*.h) UNAME_S := $(shell uname -s) diff --git a/main/alles.c b/main/alles.c index 1db94a5..f286ae6 100644 --- a/main/alles.c +++ b/main/alles.c @@ -21,7 +21,7 @@ amy_err_t sync_init() { -void update_map(uint8_t client, uint8_t ipv4, int64_t time) { +void update_map(int16_t client, uint8_t ipv4, int64_t time) { // I'm called when I get a sync response or a regular ping packet // I update a map of booted devices. @@ -72,7 +72,7 @@ void handle_sync(int64_t time, int8_t index) { // Before I send, i want to update the map locally update_map(client_id, ipv4_quartet, sysclock); // Send back sync message with my time and received sync index and my client id & battery status (if any) - sprintf(message, "_s%lldi%dc%dr%dy%dZ", sysclock, index, client_id, ipv4_quartet, battery_mask); + sprintf(message, "_U%lldi%dg%dr%dy%dZ", sysclock, index, client_id, ipv4_quartet, battery_mask); mcast_send(message, strlen(message)); // Update computed delta (i could average these out, but I don't think that'll help too much) //int64_t old_cd = computed_delta; @@ -81,10 +81,11 @@ void handle_sync(int64_t time, int8_t index) { //if(old_cd != computed_delta) printf("Changed computed_delta from %lld to %lld on sync\n", old_cd, computed_delta); } +// It's ok that r & y are used by AMY, this is only to return values void ping(int64_t sysclock) { char message[100]; //printf("[%d %d] pinging with %lld\n", ipv4_quartet, client_id, sysclock); - sprintf(message, "_s%lldi-1c%dr%dy%dZ", sysclock, client_id, ipv4_quartet, battery_mask); + sprintf(message, "_U%lldi-1g%dr%dy%dZ", sysclock, client_id, ipv4_quartet, battery_mask); update_map(client_id, ipv4_quartet, sysclock); mcast_send(message, strlen(message)); last_ping_time = sysclock; @@ -101,18 +102,18 @@ void alles_parse_message(char *message, uint16_t length) { uint16_t c = 0; // Parse the AMY stuff out of the message first - struct i_event e = amy_parse_message(message); + struct event e = amy_parse_message(message); uint8_t sync_response = 0; - // Then pull out any alles-specific modes in this message - c,i,r,s, _ + // Then pull out any alles-specific modes in this message while(c < length+1) { uint8_t b = message[c]; if(b == '_' && c==0) sync_response = 1; if( ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) || b == 0) { // new mode or end - if(mode=='c') client = atoi(message + start); - if(mode=='i') sync_index = atoi(message + start); - if(mode=='r') ipv4=atoi(message + start); - if(mode=='s') sync = atol(message + start); + if(mode=='g') client = atoi(message + start); + if(sync_response) if(mode=='i') sync_index = atoi(message + start); + if(sync_response) if(mode=='r') ipv4=atoi(message + start); + if(mode=='U') sync = atol(message + start); mode = b; start = c + 1; } @@ -120,6 +121,7 @@ void alles_parse_message(char *message, uint16_t length) { } if(sync_response) { // If this is a sync response, let's update our local map of who is booted + //printf("got sync response client %d ipv4 %d sync %lld\n", client, ipv4, sync); update_map(client, ipv4, sync); length = 0; // don't need to do the rest } @@ -150,7 +152,7 @@ void alles_parse_message(char *message, uint16_t length) { if(client_id % (client-255) == 0) for_me = 1; } } - if(for_me) amy_add_i_event(e); + if(for_me) amy_add_event(e); } } } \ No newline at end of file diff --git a/main/alles.h b/main/alles.h index ca621b8..988e207 100644 --- a/main/alles.h +++ b/main/alles.h @@ -22,7 +22,7 @@ #include "driver/gpio.h" -#define MAX_TASKS 9 +#define MAX_TASKS 8 // Pins & buttons #define BUTTON_WAKEUP 34 @@ -94,7 +94,7 @@ extern int16_t client_id; void ping(int64_t sysclock); amy_err_t sync_init(); -extern void update_map(uint8_t client, uint8_t ipv4, int64_t time); +extern void update_map(int16_t client, uint8_t ipv4, int64_t time); extern void handle_sync(int64_t time, int8_t index); extern void mcast_send(char * message, uint16_t len); #ifndef ESP_PLATFORM diff --git a/main/alles_desktop.c b/main/alles_desktop.c index 1e5ee3b..20d1e41 100644 --- a/main/alles_desktop.c +++ b/main/alles_desktop.c @@ -10,7 +10,7 @@ uint8_t status = RUNNING; uint8_t debug_on = 0; // AMY synth states -extern struct state global; +extern struct state amy_global; extern uint32_t event_counter; extern uint32_t message_counter; extern int16_t amy_device_id; @@ -27,9 +27,9 @@ char *local_ip, *raw_file; int main(int argc, char ** argv) { sync_init(); - amy_start(); + amy_start(1,0,1); amy_reset_oscs(); - global.latency_ms = ALLES_LATENCY_MS; + amy_global.latency_ms = ALLES_LATENCY_MS; // For now, indicate ip address via commandline local_ip = (char*)malloc(sizeof(char)*1025); diff --git a/main/alles_esp32.c b/main/alles_esp32.c index 73f009a..262d35a 100644 --- a/main/alles_esp32.c +++ b/main/alles_esp32.c @@ -74,45 +74,83 @@ extern xQueueHandle gpio_evt_queue; TaskHandle_t mcastTask = NULL; TaskHandle_t parseTask = NULL; TaskHandle_t upgradeTask = NULL; -TaskHandle_t amy_render_handle[AMY_CORES]; // one per core -static TaskHandle_t fillbufferTask = NULL; -static TaskHandle_t idleTask0 = NULL; -static TaskHandle_t idleTask1 = NULL; + +TaskHandle_t alles_handle; +TaskHandle_t alles_parse_handle; +TaskHandle_t alles_receive_handle; +TaskHandle_t amy_render_handle; +TaskHandle_t alles_fill_buffer_handle; +TaskHandle_t idle_0_handle; +TaskHandle_t idle_1_handle; + + +#define ALLES_TASK_COREID (1) +#define ALLES_PARSE_TASK_COREID (0) +#define ALLES_RECEIVE_TASK_COREID (1) +#define ALLES_RENDER_TASK_COREID (0) +#define ALLES_FILL_BUFFER_TASK_COREID (1) +#define ALLES_PARSE_TASK_PRIORITY (ESP_TASK_PRIO_MIN +2) +#define ALLES_RECEIVE_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 3) +#define ALLES_RENDER_TASK_PRIORITY (ESP_TASK_PRIO_MAX-1 ) +#define ALLES_FILL_BUFFER_TASK_PRIORITY (ESP_TASK_PRIO_MAX-1) +#define ALLES_TASK_NAME "alles_task" +#define ALLES_PARSE_TASK_NAME "alles_par_task" +#define ALLES_RECEIVE_TASK_NAME "alles_rec_task" +#define ALLES_RENDER_TASK_NAME "alles_r_task" +#define ALLES_FILL_BUFFER_TASK_NAME "alles_fb_task" +#define ALLES_TASK_STACK_SIZE (4 * 1024) +#define ALLES_PARSE_TASK_STACK_SIZE (8 * 1024) +#define ALLES_RECEIVE_TASK_STACK_SIZE (4 * 1024) +#define ALLES_RENDER_TASK_STACK_SIZE (8 * 1024) +#define ALLES_FILL_BUFFER_TASK_STACK_SIZE (8 * 1024) + // Battery status for V2 board. If no v2 board, will stay at 0 uint8_t battery_mask = 0; // AMY synth states -extern struct state global; +extern struct state amy_global; extern uint32_t event_counter; extern uint32_t message_counter; - -// Wrap AMY's renderer into 2 FreeRTOS tasks, one per core +// Render the second core void esp_render_task( void * pvParameters) { - uint8_t which = *((uint8_t *)pvParameters); - uint8_t start = (AMY_OSCS/2); - uint8_t end = AMY_OSCS; - if(which == 0) { start = 0; end = (AMY_OSCS/2); } - printf("I'm renderer #%d on core #%d and i'm handling oscs %d up until %d\n", which, xPortGetCoreID(), start, end); while(1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - render_task(start, end, which); - xTaskNotifyGive(fillbufferTask); + amy_render(0, AMY_OSCS/2, 1); + xTaskNotifyGive(alles_fill_buffer_handle); } } - // Make AMY's FABT run forever , as a FreeRTOS task void esp_fill_audio_buffer_task() { while(1) { - int16_t *block = fill_audio_buffer_task(); + AMY_PROFILE_START(AMY_ESP_FILL_BUFFER) + + // Get ready to render + amy_prepare_buffer(); + // Tell the other core to start rendering + xTaskNotifyGive(amy_render_handle); + // Render me + amy_render(AMY_OSCS/2, AMY_OSCS, 0); + // Wait for the other core to finish + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + // Write to i2s + int16_t *block = amy_fill_buffer(); + AMY_PROFILE_STOP(AMY_ESP_FILL_BUFFER) + + // We turn off writing to i2s on r10 when doing on chip debugging because of pins + #ifndef TULIP_R10_DEBUG size_t written = 0; - i2s_channel_write(tx_handle, block, AMY_BLOCK_SIZE * BYTES_PER_SAMPLE, &written, portMAX_DELAY); - if(written != AMY_BLOCK_SIZE*BYTES_PER_SAMPLE) { - printf("i2s underrun: %d vs %d\n", written, AMY_BLOCK_SIZE*BYTES_PER_SAMPLE); + i2s_channel_write(tx_handle, block, AMY_BLOCK_SIZE * BYTES_PER_SAMPLE * AMY_NCHANS, &written, portMAX_DELAY); + if(written != AMY_BLOCK_SIZE * BYTES_PER_SAMPLE * AMY_NCHANS) { + fprintf(stderr,"i2s underrun: %d vs %d\n", written, AMY_BLOCK_SIZE * BYTES_PER_SAMPLE * AMY_NCHANS); } + #endif + + } } @@ -128,26 +166,23 @@ void esp_parse_task() { // init AMY from the esp. wraps some amy funcs in a task to do multicore rendering on the ESP32 amy_err_t esp_amy_init() { - amy_start(); - global.latency_ms = ALLES_LATENCY_MS; + amy_start(2, 0, 1); + amy_global.latency_ms = ALLES_LATENCY_MS; // We create a mutex for changing the event queue and pointers as two tasks do it at once xQueueSemaphore = xSemaphoreCreateMutex(); - // Create rendering threads, one per core so we can deal with dan ellis FIXED POINT math - static uint8_t zero = 0; - static uint8_t one = 1; - xTaskCreatePinnedToCore(&esp_render_task, "render_task0", 8192, &zero, (ESP_TASK_PRIO_MAX - 1), &amy_render_handle[0], 0); - xTaskCreatePinnedToCore(&esp_render_task, "render_task1", 8192, &one, (ESP_TASK_PRIO_MAX - 1), &amy_render_handle[1], 1); + // Create the second core rendering task + xTaskCreatePinnedToCore(&esp_render_task, ALLES_RENDER_TASK_NAME, ALLES_RENDER_TASK_STACK_SIZE, NULL, ALLES_RENDER_TASK_PRIORITY, &amy_render_handle, ALLES_RENDER_TASK_COREID); // Wait for the render tasks to get going before starting the i2s task delay_ms(100); // And the fill audio buffer thread, combines, does volume & filters - xTaskCreatePinnedToCore(&esp_fill_audio_buffer_task, "fill_audio_buff", 8192, NULL, (ESP_TASK_PRIO_MAX - 1), &fillbufferTask, 0); + xTaskCreatePinnedToCore(&esp_fill_audio_buffer_task, ALLES_FILL_BUFFER_TASK_NAME, ALLES_FILL_BUFFER_TASK_STACK_SIZE, NULL, ALLES_FILL_BUFFER_TASK_PRIORITY, &alles_fill_buffer_handle, ALLES_FILL_BUFFER_TASK_COREID); // Grab the idle handles while we're here, we use them for CPU usage reporting - idleTask0 = xTaskGetIdleTaskHandleForCPU(0); - idleTask1 = xTaskGetIdleTaskHandleForCPU(1); + idle_0_handle = xTaskGetIdleTaskHandleForCPU(0); + idle_1_handle = xTaskGetIdleTaskHandleForCPU(1); return AMY_OK; } @@ -156,13 +191,20 @@ amy_err_t esp_amy_init() { void esp_show_debug(uint8_t type) { TaskStatus_t *pxTaskStatusArray; volatile UBaseType_t uxArraySize, x, i; - const char* const tasks[] = { "render_task0", "render_task1", "mcast_task", "parse_task", "main", "fill_audio_buff", "wifi", "idle0", "idle1", 0 }; + const char* const tasks[] = { ALLES_PARSE_TASK_NAME, ALLES_RECEIVE_TASK_NAME, ALLES_RENDER_TASK_NAME, ALLES_FILL_BUFFER_TASK_NAME, "main", "wifi", "IDLE0", "IDLE1", 0 }; + const uint8_t cores[] = {ALLES_PARSE_TASK_COREID, ALLES_RECEIVE_TASK_COREID, ALLES_RENDER_TASK_COREID, ALLES_FILL_BUFFER_TASK_COREID, 0, 0, 0, 1, 0}; + uxArraySize = uxTaskGetNumberOfTasks(); pxTaskStatusArray = pvPortMalloc( uxArraySize * sizeof( TaskStatus_t ) ); uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, NULL ); unsigned long counter_since_last[MAX_TASKS]; - unsigned long ulTotalRunTime = 0; - TaskStatus_t xTaskDetails; + //unsigned long ulTotalRunTime = 0; + unsigned long ulTotalRunTime_per_core[2]; + ulTotalRunTime_per_core[0] = 0; + ulTotalRunTime_per_core[1] = 0; + + //TaskStatus_t xTaskDetails; + // We have to check for the names we want to track for(i=0;i