From 5867fdb15c97ab5ead310ed427adcd9bdd5bb363 Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 16 Nov 2025 00:25:51 +0100 Subject: [PATCH] add support for 12-bit dualchannel ADC (AD9238) --- include/format_convert.h | 1 + include/hsdaoh_private.h | 25 ++++++++++- src/format_convert.c | 91 +++++++++++++++++++++++++++++++++++++--- src/libhsdaoh.c | 12 ++++-- 4 files changed, 119 insertions(+), 10 deletions(-) diff --git a/include/format_convert.h b/include/format_convert.h index be95ebb..cd337b6 100644 --- a/include/format_convert.h +++ b/include/format_convert.h @@ -2,6 +2,7 @@ #define __FORMAT_CONVERT_H void hsdaoh_unpack_pio_12bit(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info); +void hsdaoh_unpack_pio_dualchan_12bit(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info); void hsdaoh_unpack_pio_12bit_dual(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info); void hsdaoh_unpack_pio_10bit_iq(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info); void hsdaoh_unpack_pio_8bit_iq(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info); diff --git a/include/hsdaoh_private.h b/include/hsdaoh_private.h index 2279aff..9e992c3 100644 --- a/include/hsdaoh_private.h +++ b/include/hsdaoh_private.h @@ -45,7 +45,7 @@ struct hsdaoh_dev { unsigned int width, height, fps; bool output_float; - iqconverter_float_t *cnv_f; + iqconverter_float_t *cnv_f1, *cnv_f2; /* status */ int dev_lost; @@ -108,6 +108,29 @@ enum PIO_32BIT, PIO_32BIT_IQ, PIO_PCM1802_AUDIO, + PIO_AUDIO_PLACEHOLDER1, + PIO_AUDIO_PLACEHOLDER2, + PIO_AUDIO_PLACEHOLDER3, + PIO_DUALCHAN_1BIT, + PIO_DUALCHAN_1BIT_IQ, + PIO_DUALCHAN_2BIT, + PIO_DUALCHAN_2BIT_IQ, + PIO_DUALCHAN_4BIT, + PIO_DUALCHAN_4BIT_IQ, + PIO_DUALCHAN_8BIT, + PIO_DUALCHAN_8BIT_IQ, + PIO_DUALCHAN_10BIT, + PIO_DUALCHAN_10BIT_IQ, + PIO_DUALCHAN_12BIT, + PIO_DUALCHAN_12BIT_IQ, + PIO_DUALCHAN_14BIT, + PIO_DUALCHAN_14BIT_IQ, + PIO_DUALCHAN_16BIT, + PIO_DUALCHAN_16BIT_IQ, + PIO_DUALCHAN_24BIT, + PIO_DUALCHAN_24BIT_IQ, + PIO_DUALCHAN_32BIT, + PIO_DUALCHAN_32BIT_IQ, // Placeholder for internal ADC data from pico FPGA_1BIT = 256, FPGA_2BIT, diff --git a/src/format_convert.c b/src/format_convert.c index 212a62a..f62c09a 100644 --- a/src/format_convert.c +++ b/src/format_convert.c @@ -32,10 +32,9 @@ #include #include -static inline void hsdaoh_16bit_to_float(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info, uint16_t *buf, size_t length, float scale, bool duplicate) +static inline void hsdaoh_16bit_to_float(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info, uint16_t *buf, size_t length, float scale, bool conv_iq) { unsigned int i, j = 0; - float *floats = malloc(sizeof(float) * dev->width * dev->height * 2 * 2); if (!floats) @@ -44,12 +43,45 @@ static inline void hsdaoh_16bit_to_float(hsdaoh_dev_t *dev, hsdaoh_data_info_t * for (unsigned int i = 0; i < length; i++) { float sample_i = buf[i]; floats[j++] = (sample_i - scale) * (1/scale); - - if (duplicate) - floats[j++] = (sample_i - scale) * (1/scale); } -// iqconverter_float_process(dev->cnv_f, (float *) floats, j); + if (conv_iq) { + if (data_info->stream_id == 0) + iqconverter_float_process(dev->cnv_f1, (float *) floats, j); + else + iqconverter_float_process(dev->cnv_f2, (float *) floats, j); + } + + data_info->buf = (uint8_t *)floats; + data_info->len = j * sizeof(float); + data_info->bits_per_samp = 32; + data_info->nchans = 1; + data_info->is_signed = true; + data_info->is_float = true; + dev->cb(data_info); + + free(floats); +} + +static inline void hsdaoh_signed_12bit_to_float(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info, uint16_t *buf, size_t length, float scale, bool conv_iq) +{ + unsigned int i, j = 0; + float *floats = malloc(sizeof(float) * dev->width * dev->height * 2 * 2); + + if (!floats) + return; + + for (unsigned int i = 0; i < length; i++) { + int16_t samp = (int16_t)(buf[i] << 4); + floats[j++] = (samp >> 4) * (1/scale); + } + + if (conv_iq) { + if (data_info->stream_id == 0) + iqconverter_float_process(dev->cnv_f1, (float *) floats, j); + else + iqconverter_float_process(dev->cnv_f2, (float *) floats, j); + } data_info->buf = (uint8_t *)floats; data_info->len = j * sizeof(float); @@ -96,6 +128,53 @@ void hsdaoh_unpack_pio_12bit(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info) free(out); } +// Same format as PIO 12 bit, but with two interleaved ADC channels +void hsdaoh_unpack_pio_dualchan_12bit(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info) +{ + uint16_t *in = (uint16_t *)data_info->buf; + size_t inlen = data_info->len / sizeof(uint16_t); + uint16_t *out16_1 = malloc(sizeof(uint16_t) * dev->width * dev->height); + uint16_t *out16_2 = malloc(sizeof(uint16_t) * dev->width * dev->height); + unsigned int j = 0; + + for (unsigned int i = 0; i < inlen; i += 3) { + out16_1[j ] = (in[i+2] & 0xf000) >> 4 | (in[i+1] & 0xf000) >> 8 | (in[i] >> 12); + out16_2[j++] = in[i ] & 0x0fff; + out16_1[j ] = in[i+1] & 0x0fff; + out16_2[j++] = in[i+2] & 0x0fff; + } + + data_info->bits_per_samp = 12; + data_info->nchans = 1; + + if (dev->output_float) { + hsdaoh_signed_12bit_to_float(dev, data_info, out16_1, j, 2047.5, true); + } else { + data_info->buf = (uint8_t *)out16_1; + data_info->len = j * sizeof(uint16_t); + data_info->bits_per_samp = 12; + data_info->nchans = 1; + data_info->is_signed = true; + data_info->is_float = false; + + dev->cb(data_info); + } + + data_info->stream_id += 1; + + if (dev->output_float) { + hsdaoh_16bit_to_float(dev, data_info, out16_2, j, 2047.5, true); + } else { + data_info->buf = (uint8_t *)out16_2; + + dev->cb(data_info); + } + + free(out16_1); + free(out16_2); +} + + // We receive three 32-bit words containing four 24-bit samples (sample A - D) // First word: A07 A06 A05 A04 A03 A02 A01 A00 B23 B22 B21 B20 B19 B18 B17 B16 B15 B14 B13 B12 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 // Second word: A15 A14 A13 A12 A11 A10 A09 A08 C23 C22 C21 C20 C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 C07 C06 C05 C04 C03 C02 C01 C00 diff --git a/src/libhsdaoh.c b/src/libhsdaoh.c index e554ce0..03ce1c7 100644 --- a/src/libhsdaoh.c +++ b/src/libhsdaoh.c @@ -490,7 +490,8 @@ int hsdaoh_open(hsdaoh_dev_t **out_dev, uint32_t index) goto err; dev->dev_lost = 0; - dev->cnv_f = iqconverter_float_create(HB_KERNEL_FLOAT, HB_KERNEL_FLOAT_LEN); + dev->cnv_f1 = iqconverter_float_create(HB_KERNEL_FLOAT, HB_KERNEL_FLOAT_LEN); + dev->cnv_f2 = iqconverter_float_create(HB_KERNEL_FLOAT, HB_KERNEL_FLOAT_LEN); found: *out_dev = dev; @@ -522,7 +523,8 @@ int hsdaoh_close(hsdaoh_dev_t *dev) uvc_unref_device(dev->uvc_dev); uvc_exit(dev->uvc_ctx); - iqconverter_float_free(dev->cnv_f); + iqconverter_float_free(dev->cnv_f1); + iqconverter_float_free(dev->cnv_f2); free(dev); return 0; @@ -564,6 +566,9 @@ void hsdaoh_output(hsdaoh_dev_t *dev, uint16_t sid, int format, uint32_t srate, case PIO_12BIT_DUAL: hsdaoh_unpack_pio_12bit_dual(dev, &data_info); break; + case PIO_DUALCHAN_12BIT: + hsdaoh_unpack_pio_dualchan_12bit(dev, &data_info); + break; case PIO_PCM1802_AUDIO: hsdaoh_unpack_pio_pcm1802_audio(dev, &data_info); break; @@ -837,7 +842,8 @@ int hsdaoh_start_stream(hsdaoh_dev_t *dev, hsdaoh_read_cb_t cb, void *ctx, unsig if (HSDAOH_INACTIVE != dev->async_status) return -2; - iqconverter_float_reset(dev->cnv_f); + iqconverter_float_reset(dev->cnv_f1); + iqconverter_float_reset(dev->cnv_f2); dev->async_status = HSDAOH_RUNNING; dev->async_cancel = 0;