Skip to content

Feat/acoustics - #42

Open
Wiacer wants to merge 131 commits into
mainfrom
feat/acoustics
Open

Feat/acoustics#42
Wiacer wants to merge 131 commits into
mainfrom
feat/acoustics

Conversation

@Wiacer

@Wiacer Wiacer commented Apr 24, 2026

Copy link
Copy Markdown

It works, but needs a bit of polish

1 FDCAN
1 SPI master
3 SPI slaves
1 Usart
allow cli make clean all build project
used to generate compile_json
remove all incompatible *f16.c from CMSIS.DSP
remove all incompatible ARM cortex A (neon) from CMSIS_DSP
remove all source files that include all source files in the same directory
First iteration, made completely by chatgtp
@Wiacer Wiacer self-assigned this Apr 24, 2026
@jorgenfj

Copy link
Copy Markdown
image bruh

@Wiacer

Wiacer commented Apr 25, 2026

Copy link
Copy Markdown
Author
image

bruh

Idk man, cyp ville ha deg med

@forrisdahl forrisdahl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know why, but the GitHub browser is insanely slow and difficult to mark where I want to comment, so I will make one larger comment instead.

My general impression of main is that it is big. It has a lot of functions, and it is not immediately clear which are important/used and which are not. I suggested splitting the debug or printf functions into another file. I understand that this might be challenging due to variable scope. The most important thing to see in the main.c file is the main function. I would like the main function to be as early as possible, with other functions defined below it but declared in the header or before main.

I think the code inside the while loop could also be split into functions. It does not have to be many functions, but as it is now, we end up with a lot of indentation. Function names could also make it easier to understand what each step is trying to achieve.

It is a bit random where there are comments and where there are not. For the most important functions, you should have Doxygen-style comments to explain what they do and what they expect.

Comment thread acoustics/acoustics_firmware2/Core/Src/main.c Outdated
Comment thread acoustics/acoustics_firmware2/Core/Src/main.c Outdated
Comment thread acoustics/acoustics_firmware2/Core/Src/main.c Outdated
Wiacer added 2 commits April 26, 2026 22:47
//NOT TESTED AT TIME OF COMMIT//
cleaned and modularized everything
all that remains should be to implement dumping functionality and test
Features added:
CAN interface
Proper state machine
Stale Data detection
Left to implement:
    Dump commands
    Making it not error due to stale data every 10 seconds
@Wiacer Wiacer closed this Apr 27, 2026
@Wiacer Wiacer reopened this Apr 27, 2026
@Wiacer

Wiacer commented Apr 27, 2026

Copy link
Copy Markdown
Author

Idk what i'm doing.
Anyway I fixed all the issues i think and majorly rebased and reorganized acoustics.
It errors out and resets every 10 seconds but it works (havent properly tested direction but i assume it works)

…SPI interface would fail. Fixed it. Acoustics is now megagood.

Dump still not implemented
@Wiacer

Wiacer commented Apr 27, 2026

Copy link
Copy Markdown
Author

Fixed the erroring out thingy now.
Only optional thing missing is dump functionality but we'll only need it if it doesn't work to begin with

Comment on lines +171 to +191
while(((idxs[0] < dead_space) || (idxs[0] > (WORKSPACE_LEN-(WORKSPACE_OFFSET-1)*BLOCK_LEN))) && max_retries--){
float32_t threshold = dsp_min_max_lerp(processing_workspace[0], WORKSPACE_LEN, linear_threshold, n_upper_average, dead_space);
idxs[0] = dsp_rl_under_threshold_search(processing_workspace[0],WORKSPACE_LEN, threshold , PROCESSING_PATIENCE);
linear_threshold *= 2;
}
times_of_arrival[0] = (float32_t)idxs[0];

for(int i = 1; i < N_HYDROPHONES; i++){
float32_t linear_threshold = lerp_threshold;
uint8_t max_retries = 6;
uint32_t dead_space = n_lower_average;
idxs[i] = 0;
while(((utils_abs_int32(idxs[0] - idxs[i])) > max_idx_difference) && max_retries--){
float32_t threshold = dsp_min_max_lerp(processing_workspace[i], WORKSPACE_LEN, linear_threshold, n_upper_average, dead_space);
idxs[i] = dsp_rl_under_threshold_search(processing_workspace[i],WORKSPACE_LEN, threshold , PROCESSING_PATIENCE);
linear_threshold *= 2;
}
times_of_arrival[i] = (float32_t)idxs[i];
}

valid_buffers = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these conditions are quite cryptic, not immediately obvious what they are checking. You could have a static inline function to have a human readable name for the conditions.

You probably should split the first part of the function, (the searching?) to its own function. Currently function does a lot of things. Could have one function that finds the TDOA and validates the data and one that solves the position

@Wiacer

Wiacer commented May 15, 2026

Copy link
Copy Markdown
Author

Aight now there are surely no other problems left

@forrisdahl

Copy link
Copy Markdown
Contributor

This looks a lot better, much easier to understand the acoustics processing.

What remains now is to clean up your branch. First of all I see that there are telemetry files I accidentally pushed on the branch that needs to be removed.

I think the old version of the firmware can be removed to save space. Both the old version and the new version includes CMSIS library which adds a lot more files to your branch than needs to be. In addition since I do not think we have done any changes cmsis library we should look for a way to avoid having it in our repo.

@forrisdahl forrisdahl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope your summer is going well so far! Here are some suggestions for improvement

Comment on lines +192 to +215
float32_t find_idx_of_arrival_in_buffer(uint8_t buffer_idx){
float32_t linear_threshold = lerp_threshold;
uint8_t max_retries = find_idx_of_arrival_max_retries;
idxs[buffer_idx] = 0;

while(is_idx_out_of_bounds(buffer_idx) && max_retries--){
float32_t threshold = dsp_min_max_lerp(processing_workspace[buffer_idx], WORKSPACE_LEN, linear_threshold, n_upper_average, dead_space);
idxs[buffer_idx] = dsp_rl_under_threshold_search(processing_workspace[buffer_idx],WORKSPACE_LEN, threshold , PROCESSING_PATIENCE);
linear_threshold *= 2;
}
return idxs[buffer_idx];
}

bool are_idxs_of_arrival_valid(uint8_t valid_buffers_array[N_HYDROPHONES]){
uint8_t valid_buffers = 0;

for(int i = 1; i < N_HYDROPHONES; i++){
bool valid = !regular_idx_out_of_bounds(idxs[i]);
valid_buffers += valid;
valid_buffers_array[i] &= valid;
}

return valid_buffers >= (MINIMUM_VALID_BUFFERS - 1);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_idx_of_arrival_in_buffer only process one index whereas are_idxs_of_arrival_valid processes all indexes. I think both functions should either process all indexes or process one index

Comment on lines +184 to +194
for (uint32_t i = 1; i < signal_len; i++)
{
float32_t key = sorted[i];
int32_t j = (int32_t)i - 1;
while (j >= 0 && sorted[j] > key)
{
sorted[j + 1] = sorted[j];
j--;
}
sorted[j + 1] = key;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could make this a static inline function, would express intent clearer and reduce the length of this function

Comment on lines +207 to +211
arm_add_f32( /* low + t*(high-low) */
&low_mean, /* not a vector call, so we */
&(float32_t){t * /* use scalar arithmetic below */
(high_mean - low_mean)},
&result, 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite confusing. Would instead of doing this inside the function call you instead

&(float32_t){t *  (high_mean - low_mean)}
float32_t temp = t *  (high_mean - low_mean)
arm_add_f32(&low_mean, &temp, &result, 1)

You should give it a better name than temp, but I was too lazy to come up with something clever

static bool is_vector_too_long(float32_t vec[3]){
float32_t sum = 0.0f;
for(int i = 0; i < 3; i++) sum += utils_abs_f32(vec[i]);
return sum > 1.732050807569f;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very specific number, probably should use a define here

Comment on lines +118 to +308
void hydrophone_interface_init(float32_t new_hydrophone_positions[N_HYDROPHONES][3]){

for(int i = 0; i < N_HYDROPHONES; i++){
hydrophone_valid[i] = false;
hydrophone_positions[i][0] = new_hydrophone_positions[i][0];
hydrophone_positions[i][1] = new_hydrophone_positions[i][1];
hydrophone_positions[i][2] = new_hydrophone_positions[i][2];
}

float32_t biggest_distance = 0;
for(int i = 1; i < N_HYDROPHONES; i++){
float32_t dist = utils_distance_3d(hydrophone_positions[0],hydrophone_positions[i]);
if(dist > biggest_distance){
biggest_distance = dist;
}
}
{
float32_t idx_distance = (biggest_distance/(WAVE_SPEED)*SAMPLING_FREQUENCY) + BLOCK_LEN/4;
uint16_t n = (uint16_t)ceilf(idx_distance);
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;
max_idx_difference = n;
}


struct ad7606_pins pins = {
.cs = {CS},
.busy = {BUSY},
.frstdata = {FRSTDATA},
.convst = {CONVST},
};

union ad7606_spi spi = {
.by_name = {
.douta = DOUTA,
.doutb = DOUTB,
.doutc = DOUTC,
.doutd = DOUTD,
.doute = DOUTE,
.doutf = NULL,
.doutg = NULL,
.douth = DOUTH,
.sdi = MASTER_SPI,
}
};

struct ad7606_config config = {
.status_header = false,
.external_oversampling_clock = false,
.dout_format = AD7606_DOUT_8,
.operation_mode = AD7606_OPERATION_NORMAL,
};

struct ad7606_channel channels[8];
for(int i = 0; i < 8; i++){
// AD7606_MUX_CTRL_TEMP,
// AD7606_MUX_CTRL_2V5_REF,
// AD7606_MUX_CTRL_1V8_ALDO,
// AD7606_MUX_CTRL_1V8_DLDO,
// AD7606_MUX_CTRL_V_DRIVE,
// AD7606_MUX_CTRL_A_GND,
// AD7606_MUX_CTRL_AV_CC;
AD7606_CHANNEL_MUX_CTRL mux_ctrl = AD7606_MUX_CTRL_A_IN; // = (i != 8) ? (AD7606_MUX_CTRL_A_IN) : (AD7606_MUX_CTRL_TEMP);
AD7606_CHANNEL_RANGE range = AD7606_RANGE_SE_PM_12_5V;
switch(i){
case(2):
range = AD7606_RANGE_SE_PM_2_5V;
break;
case(5):
mux_ctrl = AD7606_MUX_CTRL_A_GND;
range = AD7606_RANGE_SE_PM_2_5V;
break;
case(6):
mux_ctrl = AD7606_MUX_CTRL_AV_CC;
range = AD7606_RANGE_SE_0_TO_5V;
break;
case(7):
mux_ctrl = AD7606_MUX_CTRL_TEMP;
range = AD7606_RANGE_SE_PM_2_5V;
break;
}
struct ad7606_channel ch = {
.open_detect = false,
.high_bandwidth = true,
.range = range,
.gain = 0,
.phase = 0,
.offset = 0x80,
.mux_ctrl = mux_ctrl,
};
channels[i] = ch;
}

struct ad7606_oversampling oversampling = {
.oversampling_ratio = 3, // 2^N oversampling
.oversampling_padding = 0,
};

struct ad7606_digital_diagnostics digital_diagnostics = {
.rom_CRC_err_en = true,
.mm_CRC_err_en = false,
.int_CRC_err_en = false,
.spi_write_err_en = false,
.spi_read_err_en = false,
.busy_stuck_high_err_en = true,
.clk_fs_os_en = false,
.interface_check_en = false,
};

ADC_settings.config = config;
ADC_settings.digital_diagnostics = digital_diagnostics;
ADC_settings.oversampling = oversampling;

for(int i = 0; i < 8; i++){
ADC_settings.channels[i] = channels[i];
}
my_ADC.cooked = true;
ad7606_init(&my_ADC, &ADC_regs, pins, spi, &ADC_settings, &diagnostics_sample);
// if(verbose){
// printf("ADC initialized. Status register:\t");
// print_binary(ad7606_check_status(&my_ADC),8);
// printf("\r\n");
//
// printf("Digital diagnostics error register:\t");
// print_binary(ad7606_check_digital_error(&my_ADC),8);
// printf("\r\n");
// }

uint8_t interface_check_result[8];
ad7606_check_interface(&my_ADC, interface_check_result);
if(!hydrophone_valid[0]){
for(int i = 0; i < N_HYDROPHONES; i++){
hydrophone_valid[i] = interface_check_result[i];
}
}
// if(verbose){
// printf("Interface check result:\r\n");
// for(int i = 0; i < 8; i++){
// printf("Channel V%d: ",i+1);
// switch(interface_check_result[i]){
// case 0xFF:
// printf("Not configured");
// break;
// case 0:
// printf("Fail");
// break;
// case 1:
// printf("Pass");
// break;
// default:
// printf("Unknown result");
// break;
// }
// printf("\t\t\t");
// if(i%4 == 3) printf("\r\n");
// }
// printf("\r\n");
// }

int lengths[8] = {
BUFFER_LEN,
BUFFER_LEN,
BUFFER_LEN,
BUFFER_LEN,
BUFFER_LEN,
0,
0,
0,
};
int16_t* buffers[8] = {NULL};
for(int i = 0; i < 5; i++) buffers[i] = (int16_t*)&hydrophone_buffers[i][0][0];

ad7606_enter_adc_mode(&my_ADC);

for(int i = 0; i < 5; i++){
utils_clear_array_q15(hydrophone_buffers[i][0], BUFFER_LEN);
}

ad7606_init_output_buffers_DMA(&my_ADC, buffers, lengths);
HAL_MDMA_RegisterCallback(&hmdma_mdma_channel0_sw_0, HAL_MDMA_XFER_CPLT_CB_ID, MyMDMA_TransferCompleteCallback);
HAL_MDMA_RegisterCallback(&hmdma_mdma_channel0_sw_0, HAL_MDMA_XFER_ERROR_CB_ID, MyMDMA_ErrorCallback);
ad7606_dma_spi_init(&my_ADC, &hdma_spi6_rx, diagnostics_buffer, BLOCK_LEN);

mdma_half = 0;
mdma_done_flag = false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would clean this function up a bit, remove the commented code or have them in the code. Possible to have them conditially using #ifdef if you do not want them in your code all the time.

Comment on lines +135 to +143
float32_t idx_distance = (biggest_distance/(WAVE_SPEED)*SAMPLING_FREQUENCY) + BLOCK_LEN/4;
uint16_t n = (uint16_t)ceilf(idx_distance);
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite interesting trick, not immideately obvious what is going on here, so again I would recommend using static inline function to express intent clearer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants