/* mics_in_headset_out.xc * * (AN00220_app_phase_aligned_example.xc) (names) * * Created on: 22. apr. 2021 * Author: Much based on XMOS with and AN00219 AN00220 */ #define INCLUDES #ifdef INCLUDES #include #include #include // memmove #include // not etc. #include // INT_MAX #include // int32_t etc #include // xScope logging #include #include #include #include "mic_array.h" #include "mic_array_board_support.h" #include "_globals.h" #include "maths_fix16.h" // Needs stdint.h #include "F_Pattern_KnockCome.h" #include "mics_in_headset_out.h" #define DO_DEBUG_PRINT 1 #define DO_WARNINGS 0 #include "_print_macros_xc.h" #endif // ============ // MICS_IN_PART // ============ #if (WARNINGS==1) #ifndef MIC_ARRAY_WORD_LENGTH_SHORT #warning MIC_ARRAY_WORD_LENGTH_SHORT not defined #else // in mic_array_frame.h lib_mic_array #if (MIC_ARRAY_WORD_LENGTH_SHORT==0) // THIS #warning int32_t bits mic array word #elif (MIC_ARRAY_WORD_LENGTH_SHORT==1) #warning int16_t bits mic array word #endif #endif #ifndef MIC_ARRAY_NUM_MICS #warning MIC_ARRAY_NUM_MICS not defined #elif (MIC_ARRAY_NUM_MICS == 8) // THIS #warning 8 mics (7 used) // Magically from mic_array_conf.h <- mic_array_frame.h #endif #ifndef USE_XSCOPE #error USE_XSCOPE not defined in makefile #elif (USE_XSCOPE == 0) #warning NO XSCOPE #elif (USE_XSCOPE==1) #warning USING XSCOPE #endif #endif #define IOF_MIC_0 0 // Only the center mic just now #define IOF_BUF_0 0 void set_headset_gain ( const do_headset_gain_e do_headset_gain, headset_gain_context_t &ctx) { switch (do_headset_gain) { case do_headset_gain_zero: { ctx.iof_gain = IOF_HEADSET_GAIN_DB_OFF; } break; case do_headset_gain_default: { const mic_data_t amplitude_fix16_unity_factors_table_ [NUM_AMPLITUDE_STEPS] = AMPLITUDE_FIX16_UNITY_FACTORS_TABLE; const signed amplitude_db_table_ [NUM_AMPLITUDE_STEPS] = AMPLITUDE_DB_TABLE; // I can understand the use of memcpy when it's this much code not to use it (but I don't like memcpy) for (unsigned ix=0; ix < NUM_AMPLITUDE_STEPS; ix++) { ctx.amplitude_fix16_unity_factors_table [ix] = amplitude_fix16_unity_factors_table_ [ix]; ctx.amplitude_db_table [ix] = amplitude_db_table_ [ix]; } ctx.iof_gain = IOF_HEADSET_GAIN_DB_DEFAULT; ctx.headset_gain_is_off = HEADSET_GAIN_IS_OFF_VALUE_DEFAULT; } break; case do_headset_gain_max: { ctx.iof_gain = IOF_HEADSET_GAIN_DB_MAX; } break; case do_headset_gain_increase: { if (ctx.iof_gain < IOF_HEADSET_GAIN_DB_MAX) { ctx.iof_gain++; // Into IOF_HEADSET_GAIN_DB_MAX } } break; case do_headset_gain_decrease: { if (ctx.iof_gain > IOF_HEADSET_GAIN_DB_OFF) { ctx.iof_gain--; // Into IOF_HEADSET_GAIN_DB_OFF } } break; } ctx.headset_gain_is_off = (ctx.iof_gain == IOF_HEADSET_GAIN_DB_OFF); ctx.gain = ctx.amplitude_fix16_unity_factors_table[ctx.iof_gain]; } #if (USE_BEAMFORMING == 1) void set_7_mics_listening_direction_delay ( // "set_dir" in AN00219 const directive_mics_t directive_mics, const unsigned one_meter_thirty_degrees [NUM_CIRCULAR_MICS], delay_mics_num_samples_t delay [NUM_PHYSICAL_MICS]) // This is updated { if (directive_mics.use_beamforming) { delay[0] = DELAY_SAMPLE_COUNT_OF_0; for (unsigned i=0; i< NUM_CIRCULAR_MICS; i++) { // 3 6 6 delay[i+1] = one_meter_thirty_degrees [(i - directive_mics.mics_direction + START_CIRCULAR_MICS + NUM_CIRCULAR_MICS) % NUM_CIRCULAR_MICS]; // ((8 - 1) - 1) no matter what the IDE says // in mics_in_headset_out.xi (shift cmd .) to make .build visible in Finder // delay[0] = 43; // for (unsigned i=0; i< ((8 - 1) - 1); i++) { // delay[i+1] = one_meter_thirty_degrees [(i - directive_mics.mics_direction + 3 + ((8 - 1) - 1)) % ((8 - 1) - 1)]; // mics_direction=0 // i=0: one_meter_thirty_degrees[ 9%6]=[3] // i=1: one_meter_thirty_degrees[10%6]=[4] // i=2: one_meter_thirty_degrees[11%6]=[5] // i=3: one_meter_thirty_degrees[12%6]=[0] // i=4: one_meter_thirty_degrees[13%6]=[1] // i=5: one_meter_thirty_degrees[12%6]=[2] // mics_direction=1 // i=0: one_meter_thirty_degrees[ 8%6]=[2] // i=1: one_meter_thirty_degrees[ 9%6]=[3] // i=2: one_meter_thirty_degrees[10%6]=[4] // i=3: one_meter_thirty_degrees[11%6]=[5] // i=4: one_meter_thirty_degrees[12%6]=[0] // i=5: one_meter_thirty_degrees[13%6]=[1] // etc.. } } else { for (unsigned i=0; i< NUM_PHYSICAL_MICS; i++) { delay[i] = 0; } } } #endif bool mics_handler ( const unsigned iof_buffer, mic_array_frame_time_domain audio[FRAME_BUFFER_COUNT], // double-word aligned with long long first mic_result_t &mic_result) { // Also contains metadata[2/1 when MIC_ARRAY_NUM_MICS == 8/4] with // unsigned sig_bits[4]; A bit mask of the absolute values of all samples in a channel. Seems to be four zeros! // unsigned frame_number; The frame number /* if ((audio[iof_buffer].metadata->frame_number % (SAMPLING_FREQUENCY_HZ/2)) == 0) { debug_print ("cnt:%u iof_buffer:%u sig_bits:%x %x %x %x\n", // formatted %8x does not work with lib_logging I think audio[iof_buffer].metadata->frame_number, iof_buffer, audio[iof_buffer].metadata->sig_bits[0], // masks: audio[iof_buffer].metadata->sig_bits[1], audio[iof_buffer].metadata->sig_bits[2], audio[iof_buffer].metadata->sig_bits[3]); } */ mic_result.output_to_dac = audio[iof_buffer].data[IOF_MIC_0][0]; // TODO correct? // The frame index used 0 for the oldest samples and increasing indices for newer samples. for (unsigned iof_frame = 0; iof_frame < MIC_ARRAY_NUM_SAMPLES_PER_MIC; iof_frame++) { // iof_sample is 0 for the oldest samples and increasing indices for newer samples // Only [0] us "current" sample (see lores_DAS_fixed in AN00219) mic_sample_t sample = audio[iof_buffer].data[IOF_MIC_0][iof_frame]; DO_XSCOPE_INT (VALUE, sample); } return ((audio[iof_buffer].metadata->frame_number % USE_EVERY_N_SAMPLE) == 0); // true = send data } // ================ // HEADSET_OUT_PART // ================ // https://www.xcore.com/viewtopic.php?f=8&t=7680&p=36665&hilit=AN00219&sid=fe2fd6161bedaa637ae83af815dcf522#p36665 // mon2 // #if (TEST_SINE_480HZ==1) #define SINE_TABLE_SIZE 100 // 480Hz exactly only if SAMPLING_FREQUENCY_HZ is 48000 const int32_t sine_table[SINE_TABLE_SIZE] = // Complete sine. observe SINE_480HZ_DIV_FACTOR or SINE_480HZ_SHL_BITS_FACTOR { 0x0100da00,0x0200b000,0x02fe8100,0x03f94b00,0x04f01100, 0x05e1da00,0x06cdb200,0x07b2aa00,0x088fdb00,0x09646600, 0x0a2f7400,0x0af03700,0x0ba5ed00,0x0c4fde00,0x0ced5f00, 0x0d7dd100,0x0e00a100,0x0e754b00,0x0edb5a00,0x0f326700, 0x0f7a1800,0x0fb22700,0x0fda5b00,0x0ff28a00,0x0ffa9c00, 0x0ff28a00,0x0fda5b00,0x0fb22700,0x0f7a1800,0x0f326700, 0x0edb5a00,0x0e754b00,0x0e00a100,0x0d7dd100,0x0ced5f00, 0x0c4fde00,0x0ba5ed00,0x0af03700,0x0a2f7400,0x09646600, 0x088fdb00,0x07b2aa00,0x06cdb200,0x05e1da00,0x04f01100, 0x03f94b00,0x02fe8100,0x0200b000,0x0100da00,0x00000000, 0xfeff2600,0xfdff5000,0xfd017f00,0xfc06b500,0xfb0fef00, 0xfa1e2600,0xf9324e00,0xf84d5600,0xf7702500,0xf69b9a00, 0xf5d08c00,0xf50fc900,0xf45a1300,0xf3b02200,0xf312a100, 0xf2822f00,0xf1ff5f00,0xf18ab500,0xf124a600,0xf0cd9900, 0xf085e800,0xf04dd900,0xf025a500,0xf00d7600,0xf0056400, 0xf00d7600,0xf025a500,0xf04dd900,0xf085e800,0xf0cd9900, 0xf124a600,0xf18ab500,0xf1ff5f00,0xf2822f00,0xf312a100, 0xf3b02200,0xf45a1300,0xf50fc900,0xf5d08c00,0xf69b9a00, 0xf7702500,0xf84d5600,0xf9324e00,0xfa1e2600,0xfb0fef00, 0xfc06b500,0xfd017f00,0xfdff5000,0xfeff2600,0x00000000, }; #endif [[distributable]] void i2s_task ( // (i2s_handler) server i2s_callback_if i2s, // From i2s_master0 client i2c_master_if i2c, port p_rst_shared, chanend ch_headset_out) // Coming from "mics_in_headset_out_task_a" having "out" semantics { #if (DAC_OUTPUT_SAMPLE_FREQUENCY_HZ != SAMPLING_FREQUENCY_HZ) #error FAULT? #endif /* TODO 30Dec2021 https://electronics.stackexchange.com/a/602191/300753 by Justme As shown in the datasheet, the CS43L21 does not automatically support 8 kHz sampling rate with 24.576 MHz MCLK. Either halve the MCLK output from the PLL, or enable MCLK dividing by 2 at the DAC in hardware or software, or disable auto-detection and set the MCLK to FS ratio manually. */ const unsigned mclk_bclk_ratio = MCLK_BCLK_RATIO; // (MASTER_CLOCK_FREQUENCY_HZ/DAC_OUTPUT_SAMPLE_FREQUENCY_HZ)/64; debug_print ("mclk_bclk_ratio %u\n", mclk_bclk_ratio); // Nice to know to understand // APPLICATION_NOTE == 219 -> 48 kHz (WORKS): (24576000/((24576000 / (2 * (4))) / (32 * 2)))/64; // 48/8=6 // APPLICATION_NOTE == NONE -> 8 kHz (xSCOPE shows curves, but nothing on headset) (24576000/((24576000 / (2 * (4))) / (32 * 12)))/64; // mclk_bclk_ratio 48 // audio [bufs:2]data_32[mics:8][samples:1] and mic_data int[NUM_DATA_X:8][NUM_DATA_Y:384] mabs_init_pll (i2c, ETH_MIC_ARRAY); // Lots of I2C plus a delay of 1 ms (BEEP-001: so moved it before the reset) p_rst_shared <: CS41L21_DAC_ETH_RESET; // Teig: scope: low for ages before this! With R71 to GND. // From the CS43L21 manual: // .".once RESET is high and the desired register settings can be loaded per the interface descriptions // in ÒSoftware ModeÓ on page 34. If a valid write sequence to the control port is not made within // approximately 10 ms, the device will enter Hardware Mode." i2c_regop_res_t res; // The first thing I had done when I suspected the I2C was to print out all the I2CÊreturnsÊ // i2c_regop_res_tÊat the init section of i2s_task. They were allÊI2C_REGOP_SUCCESS, // even with i2c_lib 4.0.0. But then, there is no parity or CRC checks here, and what // would still be open would be single bit errors in the mic_data, even if the start and stop // conditions were successful. // INIT THE DAC AND PLL FOR THE HEADSET uint8_t i2c_data; // CS41L21_DAC_REG_01_CHIP_ID_AND_REVISION // Bit7 6 5 4 3 2 1 0 // Chip_ID4 Chip_ID3 Chip_ID2 Chip_ID1 Chip_ID0 Rev_ID2 Rev_ID1 Rev_ID0 i2c_data = i2c.read_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_01_CHIP_ID_AND_REVISION, res); // CS41L21_DAC_REG_02_POWER_CONTROL_1 = POWER DOWN // Bit7 6 5 4 3 2 1 0 // Reserved PDN_DACB PDN_DACA Reserved Reserved Reserved Reserved PDN i2c_data = i2c.read_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_02_POWER_CONTROL_1, res); i2c_data |= 1; // Set Power Down (PDN) bit0 to 1=Enable res = i2c.write_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_02_POWER_CONTROL_1, i2c_data); // Power down // CS41L21_DAC_REG_03_SPEED_CONTROL: Setting MCLKDIV2 high if using 24.576MHz. // Bit7 6 5 4 3 2 1 0 // AUTO SPEED1 SPEED0 3-ST_SP Reserved Reserved Reserved MCLKDIV2 i2c_data = i2c.read_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_03_SPEED_CONTROL, res); i2c_data |= 0x01; // 0x01 Set MCLK Divide By 2 (MCLKDIV2) bit0 to 1=Divide by 2. With AUTO=0 thi bit is not required to be high for 8 kHz (page 59) // 0x00 BEEP-001 tested with not setting that bit, did not help. Provided mclk_bclk_ratio is correct 48 // 0x81 BEEP-001 did not help res = i2c.write_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_03_SPEED_CONTROL, i2c_data); // CS41L21_DAC_REG_10_MIXER_VOLUME_CONTROL_PCMA // Bit7 6 5 4 3 2 1 0 // MUTE_ PCMMIXx PCMMIXx_ PCMMIXx_ PCMMIXx_ PCMMIXx_ PCMMIXx_ PCMMIXx_ // PCMMIXx_ VOL6 VOL5 VOL4 VOL3 VOL2 VOL1 VOL0 // 0 1 1 1 0 0 0 0 i2c_data = 0b01110000; // 0x70 = 112. * 0.5 dB = 56 dB gain res = i2c.write_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_10_MIXER_VOLUME_CONTROL_PCMA, i2c_data); // CS41L21_DAC_REG_02_POWER_CONTROL_1 POWER UP // Bit7 6 5 4 3 2 1 0 // Reserved PDN_DACB PDN_DACA Reserved Reserved Reserved Reserved PDN i2c_data = i2c.read_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_02_POWER_CONTROL_1, res); i2c_data &= ~1; // and_eq 0xFE is clear Power Down (PDN) bit0 0=Disable res = i2c.write_reg (I2C_ADDRESS_OF_DAC, CS41L21_DAC_REG_02_POWER_CONTROL_1, i2c_data); // Power up // CS2100CP_PLL_REG_03_DEVICE_CONFIGURATION_1 INIT THE PLL. // Low jitter clock source - 24.576MHz oscillator, used as reference clock to the CS2100-CP (CirrusLogic) Fractional-N PLL (U22). // From the manual: // Low jitter clock source - 24.576MHz oscillator, used as reference clock to the // CS2100-CP (CirrusLogic) Fractional-N PLL (U22). // The CS2100 generates a low-jitter output signal that is distributed to the xCORE- 200 device (Tile1 & MCLK) // and DAC (MIC-CLK). The CS2100 device is configured using the I2C interface. // IF FOR NOTHING ELSE; GOOD FOR ENSURING WORKING CHIP! // Bit7 6 5 4 3 2 1 0 // RModSel2 RModSel1 RModSel0 Reserved Reserved AuxOutSrc1 AuxOutSrc0 EnDevCfg1 // 0 0 0 0 0 0 0 0 is init value anyhow! // 0 0 Timing Reference Clock = RefClk res = i2c.write_reg (I2C_ADDRESS_OF_PLL, CS2100CP_PLL_REG_03_DEVICE_CONFIGURATION_1, 0); // RefClk to AUX_AOUT while (1) { select { // Coming from i2s_master as "callbacks" (only i2s.send is a callback I think) case i2s.init ( i2s_config_t &?i2s_config, // ? no if (!isnull(i2s_config)) tdm_config_t &?tdm_config): // ? no if (!isnull(tdm_config)) { i2s_config.mode = I2S_MODE_LEFT_JUSTIFIED; // CousinItt at https://www.xcore.com/viewtopic.php?f=8&t=8205&p=39135#p39135 // The default power-on state for the CS43L21 is to accept left-justified audio data, // and this is not modified by the AN00219 initialisation code (at the start of i2s_handler). // With justified formats the audio data words are in phase with the word clock // (shifted to the front or back end as appropriate). In I2S there's a one-bit delay. // One of the functions of this priming code is to set the relative timing of the word // clock and the audio data, and it's much simpler for left-justified. // (MASTER_CLOCK_FREQUENCY_HZ/DAC_OUTPUT_SAMPLE_FREQUENCY_HZ)/64 rewriten as: // i2s_config.mclk_bclk_ratio = DECIMATION_FACTOR * MASTER_PLL_24576KHZ_TO_PDM_CLOCK_DIVIDER; // DF * 4 // // BEEP-002 Page 59 says division of 512 (here 8) to get 48 kHz, 3072 (6 times more 48 = ok) to get 8 kHz. AUTO or not (page 58). Seems OK // BEEP-001 tested with 8, 16, 24, 36, -48-, 60, 96, but none give any sound to the headset. IT'S NOT HERE } break; case i2s.restart_check() -> i2s_restart_t restart: { restart = I2S_NO_RESTART; // outputter just loops } break; case i2s.receive (size_t index, int32_t sample): { // No code } break; case i2s.send (size_t index) -> int32_t sample: { // When "mics_in_headset_out_task_a" sends the value here, the // lib_i2s task "output_word" I think (several would do i2s_i.send), has done this code: // p_dout[index] <: bitrev(i2s_i.send(index*FRAME_WORDS + offset)); // and is waiting for data coming in on "ch_headset_out" here. // In other word, it hangs here. I assume this is because of output // frequency having to be stable, controlled from lib_i2s. // But "mics_in_headset_out_task_a" needs to be fast enough here, to keep pace // From lib_i2s PDF: "Òsample-basedÓ versions make a callback per channel within a sample period." ch_headset_out :> sample; // sample is returned (or "sent") from here } break; } } } // =========== // COMMON PART // =========== void zero_ix_of_all_samples_buffer (ch_ab_all_samples_t &data_ch_ab_all_samples) { data_ch_ab_all_samples.samples.rec_buf.nums.num_samples = 0; // So all unions get zero here data_ch_ab_all_samples.samples.rec_buf.nums.num_lost_overflowed = 0; // Same } chan_mutual_packet_dim_e get_chan_mutual_packet_dim (const unsigned num_samples) { chan_mutual_packet_dim_e chan_mutual_packet_dim; // No math here, simply because this list may be made non-mathematic if (num_samples > NUM_064) { // Also > NUM_NORM chan_mutual_packet_dim = NUM_NORM; // Will also send NUM_128, but some may not have been filled } else if (num_samples > NUM_032) { chan_mutual_packet_dim = NUM_064; } else if (num_samples > NUM_016) { chan_mutual_packet_dim = NUM_032; } else if (num_samples > NUM_008) { chan_mutual_packet_dim = NUM_016; } else if (num_samples > NUM_004) { chan_mutual_packet_dim = NUM_008; } else if (num_samples > NUM_002) { chan_mutual_packet_dim = NUM_004; } else if (num_samples > NUM_001) { chan_mutual_packet_dim = NUM_002; } else if (num_samples > NUM_000) { chan_mutual_packet_dim = NUM_001; } else if (num_samples == NUM_000) { // If no fill_sample_into_buffer call since last single-sample chan_mutual_packet_dim = NUM_000; // Meaning no extra mutual packet needed this time } else { xassert (false); } return chan_mutual_packet_dim; } // Based heavily in the fact that ch_ab_all_samples_t contains a union and that rec_buf overlays all // void send_variant_protocol_then_shift_down ( streaming chanend sch_out, const chan_mutual_packet_dim_e chan_mutual_packet_dim, ch_ab_all_samples_t &data_ch_ab_all_samples) // shifted down if necessary { const unsigned num_samples = data_ch_ab_all_samples.samples.rec_buf.nums.num_samples; const bool is_none_left = (num_samples <= chan_mutual_packet_dim); // --------------------------------------------------------------- // ALL THIS COMPLEXITY COMES FROM THE FACT THAT SINCE THE PRODUCER // get_chan_mutual_packet_dim "PROMISED" chan_mutual_packet_dim // THEN num_samples MAY HAVE INCREASED! // --------------------------------------------------------------- if (is_none_left) { // Keep data_ch_ab_all_samples.samples.rec_buf.nums.num_samples as it is } else { data_ch_ab_all_samples.samples.rec_buf.nums.num_samples = chan_mutual_packet_dim; // "seen" by all .rec_xxx: } switch (chan_mutual_packet_dim) { case NUM_001: { sch_out <: data_ch_ab_all_samples.samples.rec_001; } break; case NUM_002: { sch_out <: data_ch_ab_all_samples.samples.rec_002; } break; case NUM_004: { sch_out <: data_ch_ab_all_samples.samples.rec_004; } break; case NUM_008: { sch_out <: data_ch_ab_all_samples.samples.rec_008; } break; case NUM_016: { sch_out <: data_ch_ab_all_samples.samples.rec_016; } break; case NUM_032: { sch_out <: data_ch_ab_all_samples.samples.rec_032; } break; case NUM_064: { sch_out <: data_ch_ab_all_samples.samples.rec_064; } break; case NUM_NORM: { sch_out <: data_ch_ab_all_samples.samples.rec_norm; } break; // No default, let run-time crash } if (is_none_left) { zero_ix_of_all_samples_buffer (data_ch_ab_all_samples); // none available after sending } else { const unsigned shift_down_num_samples = num_samples - chan_mutual_packet_dim; const unsigned shift_down_num_samples_from = chan_mutual_packet_dim; // Shift samples that we did not send off, down to index zero // TODO: circular buffer or filling into the union buffer type? I chose the latter: // Instead of memmove. The field rec_001.ix in front stays the same // Doing this instead of a circular buffer because I would have needed to line that one up as well. // This is a trade-off between doing moving of samples on every occasion, or only now or then. // Also, if SAMPLES_BUF_DIM becomes much larger than NUM_NORM, then this will not be as smart, // since this would then move all the rest data, while another method mey move all the not so much // data every time. But if SAMPLES_BUF_DIM is 264 it's ok, it will then move max 264-128=136 samples elements // // Verify: num_samples = 130, move 130-128=2: number 129=[128] and 130=[129] // #define DO_MEMMOVE 2 // 0,1,2. Measured with 0411: // timer tmr; // time32_t timeout_ticks_start; // time32_t timeout_ticks_stop; // tmr :> timeout_ticks_start; #if (DO_MEMMOVE == 0) // Tics 1366 = 13.66 us // Tics 86 = 0.86 us for (unsigned ix=0; ix < shift_down_num_samples; ix++) { // [0],[1] <- [128],[129] data_ch_ab_all_samples.samples.rec_buf.mic_samples.samples[ix] = data_ch_ab_all_samples.samples.rec_buf.mic_samples.samples[shift_down_num_samples_from+ix]; } #elif (DO_MEMMOVE == 1) // Tics 3288 = 32.88 us // Tics 216 = 2.16 us memmove (&data_ch_ab_all_samples.samples.rec_buf.mic_samples.samples[0], &data_ch_ab_all_samples.samples.rec_buf.mic_samples.samples[shift_down_num_samples_from], shift_down_num_samples * (sizeof (mic_sample_t))); #elif (DO_MEMMOVE == 2) // Tics 232 = 2.32 us // Tics 40 = 0.40 us memcpy (&data_ch_ab_all_samples.samples.rec_buf.mic_samples.samples[0], &data_ch_ab_all_samples.samples.rec_buf.mic_samples.samples[shift_down_num_samples_from], shift_down_num_samples * (sizeof (mic_sample_t))); #endif // tmr :> timeout_ticks_stop; //debug_print ("Tics %d\n", timeout_ticks_stop - timeout_ticks_start); data_ch_ab_all_samples.samples.rec_buf.nums.num_samples = num_samples - shift_down_num_samples_from; // The number available after sending } } // Based heavily on the fact that the larger union members of ch_ab_all_samples_t overlays the smaller // void receive_variant_protocol ( streaming chanend sch_in, const chan_mutual_packet_dim_e chan_mutual_packet_dim, ch_ab_all_samples_t &data_ch_ab_all_samples) { switch (chan_mutual_packet_dim) { case NUM_001: { sch_in :> data_ch_ab_all_samples.samples.rec_001; } break; case NUM_002: { sch_in :> data_ch_ab_all_samples.samples.rec_002; } break; case NUM_004: { sch_in :> data_ch_ab_all_samples.samples.rec_004; } break; case NUM_008: { sch_in :> data_ch_ab_all_samples.samples.rec_008; } break; case NUM_016: { sch_in :> data_ch_ab_all_samples.samples.rec_016; } break; case NUM_032: { sch_in :> data_ch_ab_all_samples.samples.rec_032; } break; case NUM_064: { sch_in :> data_ch_ab_all_samples.samples.rec_064; } break; case NUM_NORM: { sch_in :> data_ch_ab_all_samples.samples.rec_norm; } break; // No default, let run-time crash } } // Returns is_buffer_full // bool fill_sample_into_buffer ( const mic_sample_t mic_sample, ch_ab_all_samples_t &data_ch_ab_all_samples) { unsigned num_samples = data_ch_ab_all_samples.samples.rec_buf.nums.num_samples; const bool has_buffer_space = (num_samples < NUM_BUF); if (has_buffer_space) { num_samples++; data_ch_ab_all_samples.samples.rec_buf.mic_samples.samples[num_samples-1] = mic_sample; data_ch_ab_all_samples.samples.rec_buf.nums.num_samples = num_samples; } else { data_ch_ab_all_samples.samples.rec_buf.nums.num_lost_overflowed++; } return (has_buffer_space == false); // = is_buffer_full } void mics_in_headset_out_task_a ( streaming chanend ds_output_is_ptr_chan[NUM_4MIC_DECIMATORS], // (c_ds_output) mic_data_t mics_data [NUM_DATA_X] [NUM_DATA_Y], // (data) Double-word aligned. For mic_array_decimator_config_t dc out port p_scope_gray, out port p_scope_violet, chanend ch_headset_out, streaming chanend sch_ab, // sch_ab_sample_001_t, ... chanend ch_ba) // ch_ba_t, ch_ba_init_synch_t { { // To avoid unnecassary num_lost_overflowed at start ch_ba_init_synch_t synch; // Value discarded ch_ba :> synch; } xassert ((sizeof (sch_ab_one_sample_t)) <= MAX_SIZEOF_NONBLOCKING_STREAMING_CHAN); #if (TEST_SINE_480HZ==1) size_t iof_sine = 0; debug_print ("%s\n", "mics_in_headset_out_task_a started, using sine out"); #else debug_print ("%s\n", "mics_in_headset_out_task_a started"); #endif { // print block #if (MIC_ARRAY_WORD_LENGTH_SHORT==0) // THIS #define MIC_DATA_WORD_BITS 32 #elif (MIC_ARRAY_WORD_LENGTH_SHORT==1) #define MIC_DATA_WORD_BITS 16 #endif debug_print ("audio [bufs:%u]data_%u[mics:%u][samples:%u] and mic_data int[NUM_DATA_X:%u][NUM_DATA_Y:%u]\n", FRAME_BUFFER_COUNT, MIC_DATA_WORD_BITS, MIC_ARRAY_NUM_MICS, 1<buffering_type in decimator_interface.xc in lib_mic_array // // Common to all microphones: mic_array_decimator_conf_common_t dcc_ = { // asm in decimate_to_pcm_4ch.S probably used as (in mic_array_decimate_to_pcm_4ch) MIC_ARRAY_MAX_FRAME_SIZE_LOG2, // frame_size_log2 S_FRAME_SIZE_LOG2 Frame size log 2 is set to 0, i.e. one sample per channel will be present in each frame 1, // apply_dc_offset_removal S_DC_OFFSET_REMOVAL_ENABLED DC offset elimination is turned on. With single pole IIR filter: // Y[n] = Y[n-1] * alpha + x[n] - x[n-1] which mutes DC 0, // index_bit_reversal S_INDEX_BITREVERSING_ENABLED Index bit reversal is off, set to 1 if FFT to be used here 0, // ptr windowing_function S_WINDOWING_ENABLED No windowing function is being applied DECIMATION_FACTOR, // output_decimation_factor S_DECIMATION_FACTOR_is_now_S_THIRD_STAGE_PHASE_COUNT The decimation factor is set to 6. Added to the fixed factor of 4 COEF_ARRAYS, // ptr coefs S_THIRD_STAGE or S_THIRD_STAGE_PHASE_COUNT This is a pointer to an array of arrays containing the coefficients for the final stage of decimation. // This corresponds to a 16kHz output hence this coef array is used 0, // apply_mic_gain_compensation S_MIC_GAIN_COMP Gain compensation is turned off FIR_GAIN_COMPENSATION, // fir_gain_compensation S_FIR_GAIN_COMP FIR compensation is set to the corresponding coefficients DECIMATOR_NO_FRAME_OVERLAP, // buffering_type S_D_FRAME_NO_OVERLAPPING Frame overlapping is turned off (then FRAME_BUFFER_COUNT min 2) FRAME_BUFFER_COUNT // number_of_frame_buffers ? The number of buffers in the audio array // The count of frames used between the decimators and the application }; // Each part (of the two) is specific to the batch of 4 microphones it interfaces to: // mic_array_decimator_conf_common_t * unsafe dcc; // int * unsafe mic_data; // The mic_data for the FIR decimator // int mic_gain_compensation[4]; // An array describing the relative gain compensation to apply to the microphones. // // The microphone with the least gain is defined as 0x7fffffff (INT_MAX), // // all others are given as INT_MAX*min_gain/current_mic_gain. // unsigned channel_count; // The count of enabled channels (0->4). // mic_array_decimator_config_t dc_[NUM_4MIC_DECIMATORS] = { { &dcc, // ptr dcc Defined above mics_data[0], // ptr mic_data The storage area for the output decimator {INT_MAX, INT_MAX, INT_MAX, INT_MAX}, // mic_gain_compensation Microphone gain compensation (turned off) NUM_4MICS_DECIMATOR_CHANS // channel_count Enabled channel count (currently must be 4) } #if (MIC_ARRAY_NUM_MICS == 8) , { &dcc, // ptr dcc Defined above mics_data[MIC_ARRAY_NUM_MICS/NUM_4MIC_DECIMATORS], // ptr mic_data The storage area for the output decimator {INT_MAX, INT_MAX, INT_MAX, INT_MAX}, // mic_gain_compensation Microphone gain compensation (turned off) NUM_4MICS_DECIMATOR_CHANS // channel_count Enabled channel count (currently must be 4) } #endif }; memcpy (&dcc, &dcc_, sizeof(dcc_)); memcpy ( dc, dc_, sizeof(dc_)); } // unsafe mic_array_decimator_configure (ds_output_is_ptr_chan, NUM_4MIC_DECIMATORS, dc); // all params input mic_array_init_time_domain_frame (ds_output_is_ptr_chan, NUM_4MIC_DECIMATORS, iof_buffer, audio, dc); ch_headset_out <: FIX16_UNITY; // TODO New 0245, need this "half-pair" for the next pair not to take to many microseconds. Why? // If false not this initially then running time after mic_array_get_next_time_domain_frame // becomes excessively long (0244, but still ok inside 48 kHz 20.83 us), // then I had to set DAC off and on by buttons before it woul never return to this. I have no idea // why this is so, but when I stopped the debugger it would all the time hang on the DAC out channel outs tmr :> time_when_samples_ready_ticks; while (1) { [[ordered]] // TODO needed? select { case ch_ba :> data_ch_ba : { // This chan_mutual_packet_dim now has been on a round trip. It was generated here, // in get_chan_mutual_packet_dim and then sent off to ..task_b and then returned now: if (data_ch_ba.chan_mutual_packet_dim == NUM_000) { // No mutual packet now } else { // === ATOMIC TRANSACTION WITH select case ABOVE (BEGIN) === send_variant_protocol_then_shift_down ( sch_ab, data_ch_ba.chan_mutual_packet_dim, data_ch_ab_all_samples); // Includes data_ch_ab_all_samples.samples.rec_buf.nums.num_samples // TO BE RECEIVED BY receive_variant_protocol // === ATOMIC TRANSACTION (END) === } DEBUG_1PORT_LOW (p_scope_violet); xassert (allow_to_send_on_sch_ab==false); allow_to_send_on_sch_ab = true; if (data_ch_ba.menu_result.headset_gain_updated) { headset_gain = data_ch_ba.menu_result.headset_gain; headset_gain_is_off = data_ch_ba.menu_result.headset_gain_is_off; } else {} tmr :> timeout_ticks; // Immediately now, only if sent all } break; case tmr when timerafter (timeout_ticks) :> void: { // select case new with version 0219 DEBUG_1PORT_HIGH (p_scope_gray); // Observe that the below function also has a timerafter or a port to set its termination // ============================================================= // See "Intended usage model" in in lib_mic_array user guide PDF // ============================================================= // mic_array_frame_time_domain * latest_frame; // A ptr to raw audio data data (as mic_sample_t = MIC_ARRAY_WORD_LENGTH_SHORT = int16_t or int32_t) // which is double-word aligned with a long long initially. Declared as alias at function. // Same mic_data as in audio (below) latest_frame = // The ptr to the frame now owned by the application. That is, the most recently written samples mic_array_get_next_time_domain_frame ( // in lib_mic_array, file decimator_interface.xc ds_output_is_ptr_chan, // streaming chan chan_ds_ptr_output [NUM_4MIC_DECIMATORS] // The channels used to transfer pointers between the application and the mic_array_decimate_to_pcm_4ch() tasks NUM_4MIC_DECIMATORS, // The count of mic_array_decimate_to_pcm_4ch() tasks. iof_buffer, // return: The buffer index (Used internally) audio, // return: An array of mic_array_frame_time_domain audio frames. // All frame types contain a two-dimensional mic_data array // Same data as in latest_frame (above) dc); // The array containing the decimator configuration for each decimator DEBUG_1PORT_LOW (p_scope_gray); tmr :> time_when_samples_ready_ticks; // =============================================================== // Do time measurment both with scope and timer clocks (for debug) // // Diff 2082 or 2083 provided DEBUG_PRINT_GLOBAL_APP == 0 (printed to display instead) // f = (1/48000) = 20.833 us // // Example with SAMPLING_FREQUENCY_HZ = 48000 // // ###### 20.8 us (1/48000) // // |----| |--- // ----| |----| // // S S Two samples per period, therefore f = 24 kHz (t = 41.6 us) on the scope // // Uses these, found in in xs1.h: // void schkct(streaming chanend c, unsigned char val); // void soutct(streaming chanend c, unsigned char val); send_this_sample = mics_handler (iof_buffer, audio, mic_result); if (send_this_sample) { #if (DEBUG_TEST_MIC_SAMPLES_INCREASING == 1) data_sch_ab_one_sample.mic_sample++; #else data_sch_ab_one_sample.mic_sample = (mic_sample_t) mic_result.output_to_dac; #endif if (allow_to_send_on_sch_ab) { // scope_violet_pin = 1; // DEBUG_1PORT_VAL(p_scope_violet,scope_violet_pin); data_sch_ab_one_sample.chan_mutual_packet_dim = get_chan_mutual_packet_dim ( data_ch_ab_all_samples.samples.rec_buf.nums.num_samples); DEBUG_1PORT_HIGH (p_scope_violet); sch_ab <: data_sch_ab_one_sample; allow_to_send_on_sch_ab = false; // By contract is_buffer_full = false; } else { is_buffer_full = fill_sample_into_buffer ( data_sch_ab_one_sample.mic_sample, data_ch_ab_all_samples); } } else {} if (headset_gain_is_off) { // Do nothing here } else { // ..but 11-18 us with this. With SAMPLING_FREQUENCY_HZ=48000 (T = 20.833 us) and USE_BEAMFORMING=1 this is essential int output_to_dac = 0; #if (TEST_SINE_480HZ==0) #if (DECIMATION_FACTOR != 2) #warning There is no data here if not 48 kHz #endif #if (USE_BEAMFORMING == 1) // From AN00219 (also see "Accessing the samples" in the lib_mic_array PDF) // // Copy the latest_frame sample to the delay_mics_num_samples iof_buffer // The frame index used 0 for the oldest samples and increasing indices for newer samples. // "Newer" not used now, would have been like mic_data[i][1]. // Observe NUM_SAMPLES_PER_MIC // for (unsigned i=0; idata[i][0]; data_mic_samples_delayed [delay_mics_head][i] = mic_sample; } for (unsigned i=0; i> MIC_ARRAY_NUM_MICS_LOG2); // 1/MIC_ARRAY_NUM_MICS per mic } delay_mics_head++; delay_mics_head %= MAX_DELAY_SAMPLE_CNT; #elif (USE_BEAMFORMING == 0) output_to_dac = latest_frame->data[IOF_MIC_0][0] >> MIC_ARRAY_NUM_MICS_LOG2; #endif // output_to_dac = output_mic_0_to_dac; // Avoids divide/division // Avoids dsp_math_divide from lib_dsp, since the xCORE-200 will not divide in one instruction // output_to_dac = (int) ((((int64_t) output_to_dac * (int64_t) headset_gain)) >> _HEADSET_GAIN_UNITY_BITPOS); #elif (TEST_SINE_480HZ==1) // 480Hz exactly (if SAMPLING_FREQUENCY_HZ is 48000) output_to_dac = sine_table[iof_sine]; // Not divide here with SINE_480HZ_DIV_FACTOR even if this div here seems to cost "nothing"? TODO why? iof_sine = (iof_sine + 1) % SINE_TABLE_SIZE; headset_gain_t headset_gain_dac = headset_gain >> SINE_480HZ_SHL_BITS_FACTOR; // Saves 0.3 us with shift (!) // Avoids divide/division // Avoids dsp_math_divide from lib_dsp, since the xCORE-200 will not divide in one instruction // output_to_dac = (int) ((((int64_t) output_to_dac * (int64_t) headset_gain_dac)) >> _HEADSET_GAIN_UNITY_BITPOS); #else #error #endif // https://www.xcore.com/viewtopic.php?f=8&t=8205&p=39128#p39128 CousinItt: // i2s_master takes the number of output ports as an argument. Each output port delivers two channels of // data per cycle of the left/right (aka word select) clock. Hence you have to deliver an even number of // outputs on every cycle. If you only try to deliver one channel, it will see a long delay between the // samples it needs, miss its timing deadlines, and go badly wrong. ch_headset_out <: output_to_dac; // Steroo left or right ch_headset_out <: output_to_dac; // The opposite side } #if (DO_WAITS_IN_MIC_ARRAY_GET_NEXT_TIME_DOMAIN_FRAME == 1) tmr :> timeout_ticks; // IMMEDIATE #elif (DO_WAITS_IN_MIC_ARRAY_GET_NEXT_TIME_DOMAIN_FRAME == 0) // XS1_TIMER_MHZ 100 // 10 ns ticks per us // XS1_TIMER_KHZ 100000 // 10 ns ticks per ms // XS1_TIMER_HZ 100000000 // 10 ns ticks per s // #define PERIOD_TICKS (XS1_TIMER_HZ / SAMPLING_FREQUENCY_HZ) #define LIMIT_USED_TICKS (PERIOD_TICKS - SEND_VALUE_NEEDED_TICKS) tmr :> timeout_ticks; // Now. Keep or push ahead const time32_t used_tics = timeout_ticks - time_when_samples_ready_ticks; mic_result.used_ticks = used_tics; mic_result.max_used_ticks = max (mic_result.max_used_ticks, used_tics); if (used_tics < LIMIT_USED_TICKS) { timeout_ticks = time_when_samples_ready_ticks + LIMIT_USED_TICKS; } else { // No code // Used up the time with calculations // Keep IMMEDIATE (same as DO_WAITS_IN_MIC_ARRAY_GET_NEXT_TIME_DOMAIN_FRAME == 1) } #endif } break; } } }