Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

Using nRF52832 ADC for audio encoding

Hello,

I want to record audio with 8kHz sampling rate using ADC in nRF52832. Based on specification I should be able to reach up to 200ksps for ADC, but I'm getting a high delay on ADC conversion which lower the sample rate. I'm sending data through UART (later it should be send through BLE), which is fast enough to get all captured data in the receiver side (computer).

This is my main.c code :

/*
 * Copyright (c) 2020 Libre Solar Technologies GmbH
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/uart.h>


static const struct adc_dt_spec adc_channel = ADC_DT_SPEC_GET(DT_PATH(zephyr_user));

#define THREAD0_STACKSIZE 8192
#define THREAD0_PRIORITY 2


int err;
int16_t buf;
struct adc_sequence sequence = {
	.buffer = &buf,
	/* buffer size in bytes, not number of samples */
	.buffer_size = sizeof(buf),
};

static const struct device *uart_dev;
const struct uart_config uart_cfg = {
	.baudrate = 230400,
	.parity = UART_CFG_PARITY_NONE,
	.data_bits = UART_CFG_DATA_BITS_8,
	.stop_bits = UART_CFG_STOP_BITS_1,
	.flow_ctrl = UART_CFG_FLOW_CTRL_NONE,
};

void main(void)
{

	// UART Init
	uart_dev = DEVICE_DT_GET(DT_NODELABEL(uart0));

	if (!device_is_ready(uart_dev)) {
		printk("UART device is not ready");
		return;
	}

	if (uart_configure(uart_dev, &uart_cfg)) {
		printk("UART configuration failed");
	}

	// ADC Init
	if (!device_is_ready(adc_channel.dev)) {
		printk("ADC controller device not ready\n");
		return;
	}

	err = adc_channel_setup_dt(&adc_channel);
	if (err < 0) {
		printk("Could not setup channel \n");
		return;
	}

	(void)adc_sequence_init_dt(&adc_channel, &sequence);

	
}


void thread0 (void) {
	while(1){

		int32_t val_mv;
		uint8_t num_samples = 100;
		uint8_t pcg[num_samples*2];


		for (int i = 0; i < num_samples; i++) {

			err = adc_read(adc_channel.dev, &sequence);
			if (err < 0) {
				printk("Could not read (%d)\n", err);
				continue;
			} else {
				// printk("%"PRId16, buf);
			}

			pcg[i*2] = (uint8_t)buf;
			pcg[i*2 + 1] = (uint8_t)(buf >> 8);

		}
	
		
		uart_tx(uart_dev, pcg, sizeof(pcg), SYS_FOREVER_US);

	}
	
}

K_THREAD_DEFINE(thread0_id, THREAD0_STACKSIZE, thread0, NULL, NULL, NULL,
			THREAD0_PRIORITY, 0, 0);

I'm using zephyr ADC sample code located in zephyr/samples/drivers/adc and changed it based on my application. The maximum acceptable sample rate I'm getting is about 7.5 kHz which is not bad (the quality of signal is not still acceptable) but it is using all of my cpu load. I want the thread related to ADC run let say every 25 ms, to give cpu resource to another i2c sensor which is not added yet, but if ADC consumes all the cpu time it's not possible. I've changed parameters like zephyr,acquisition-time and zephyr,oversampling to lower numbers which increase the sample rate, but it will ruin the quality of signal. This is my devicetree overlay :

/ {
	zephyr,user {
		io-channels = <&adc 0>;
	};
};

&adc {
	#address-cells = <1>;
	#size-cells = <0>;

	channel@0 {
		reg = <0>;
		zephyr,gain = "ADC_GAIN_1_6";
		zephyr,reference = "ADC_REF_INTERNAL";
		zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 5)>;
		zephyr,input-positive = <NRF_SAADC_AIN6>;
		zephyr,resolution = <12>;
		zephyr,oversampling = <4>;
	};

};

and prj.conf :

CONFIG_ADC=y
CONFIG_LOG=y

# UART DRIVER
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y

I've measured the time it takes to acquire 100 samples using k_uptime_get and k_uptime_delta. With provided ADC paramteres (acq time=5us and oversampling=4), I'm measuring about 13 ms which technically gives me a maximum sample rate of 100/13ms = 7700 samples/sec (almost the rate I've had on the receiver side) ,and also I've measured the delta time for uart_tx which is less than a millisecond, so the bottle neck is adc conversion time. 

Is there any way to reduce the ADC conversion time without getting a noisy output ?

My setup :

HW : nRF52 DK

SW : NCS 2.1.1

  • Hello, and sorry about the delay. 

    Due to the summer vacations it is a bit hectic here on DevZone, and there might unfortunately be some delays. I'm looking into your question and will let you know once I know more.

    Regards,

    Elfving

  • Hi, 

    You're welcome, I'll wait for your answer.

    Thanks in advance.

  • Hello,

    Thank you for your extreme patience with this.

    Based on specification I should be able to reach up to 200ksps for ADC, but I'm getting a high delay on ADC conversion which lower the sample rate. I'm sending data through UART (later it should be send through BLE), which is fast enough to get all captured data in the receiver side (computer).

    What are the requirements to your samples, other than the 200 ksps frequency?
    For the next part you will need to make sure that the output will fit within the maximum throughput of BLE.

    I'm using zephyr ADC sample code located in zephyr/samples/drivers/adc and changed it based on my application. The maximum acceptable sample rate I'm getting is about 7.5 kHz

    The low speed of the sampling is due to the sampling being triggered by the CPU directly, which means it can only happen when the CPU has time to do so, and which can also be postponed in the case of higher priority threads or interrupts happening concurrently.
    Instead, you should use the PPI peripheral to connect an EVENT (such as a TIMER CC event) to the SAADC SAMPLE task, so that the event triggers the sampling without requiring CPU intervention. You can see an example of how you could do this here. The sample connects the SAADC sampling to the RTC peripheral, so you will have to exchange this to be the TIMER peripheral to get the desired sampling rate.
    Please give this approach a try, and verify that you are able to achieve 200 ksps.

    Best regards,
    Karl

  • Hi Karl,

    What are the requirements to your samples, other than the 200 ksps frequency?
    For the next part you will need to make sure that the output will fit within the maximum throughput of BLE.

    I need to encode audio with a 8kHz sampling rate, and by using oversampling I think I will need at least 64ksps from the ADC. I've check the BLE data throughput in practical, and that was sufficient for my application. I want to record 100 samples every specific interval and send them altogether through BLE with the MTU set on 200 bytes (every sample has 16 bits) 

    Instead, you should use the PPI peripheral to connect an EVENT (such as a TIMER CC event) to the SAADC SAMPLE task, so that the event triggers the sampling without requiring CPU intervention.

    That sounds like a great idea! I'll check that and inform any updates thanks :)

  • Hello again,

    Hossein Kasaei said:
    I need to encode audio with a 8kHz sampling rate, and by using oversampling I think I will need at least 64ksps from the ADC. I've check the BLE data throughput in practical, and that was sufficient for my application. I want to record 100 samples every specific interval and send them altogether through BLE with the MTU set on 200 bytes (every sample has 16 bits) 

    Thank you for elaborating, this is very helpful for me to know with regards to further advice :) 
    This definitely sounds doable, and I think the best approach would be to use the TIMER event connected to SAADC SAMPLE task through PPI approach as mentioned in my previous comment to achieve this.

    Hossein Kasaei said:
    That sounds like a great idea! I'll check that and inform any updates thanks :)

    Great! Please give this a try and dont hesitate to let me know if you should encounter any issues or questions :) 

    Best regards,
    Karl

Related