cancel
Showing results for 
Search instead for 
Did you mean: 

[SOLVED] I want to sample analog values and write these into an array but in the end there are not enough samples in the array as expected.

DSoll.1
Associate II

I expect at least 22937 samples until the array is full. My guess now is, that my index counter "number_of_samples" rises faster than that the actual samples that are written into the array. After the index counter reaches the theoretical max value of samples the array would be written on the SD card. But before that the array is not full. Only around 3780 samples are written of the max. available 22937 samples.

I can' t get my head around why me code isn't working.

Specs:

Board: Nucleo 64 F411RE

actual CPU freq.: 64 MHz

sampling frequ.: 8130 Hz

Array size: 110592 Bytes

I appreciate any help!

Best regards Dietrich

//---------------for FATFS----------------
FATFS fs; // file system
FATFS *pfs;
FIL fil; // file
FRESULT fresult; // to store the result
UINT br, bw; // file read/write count
DWORD fre_clust;
uint32_t total, free_space;
//----------------------------------------
 
//--------------user----------------------
uint8_t blue_button_abort = 0;
uint16_t ADCval = 0;
uint32_t number_of_samples = 0;
uint8_t write_samples_array_yes = 0;
char buffer1_samples_adc[27*4096] = { 0 }; // buffer with adc values (char) who is written on SD
// 4 KByte = 1 * 4096 // 80 KByte = 20 * 4 KByte // 120 KByte = 30 * 4 // geht nicht -> 128 KByte = 32 * 4 KByte
// char one_sample[6] = { 0 };
char one_sample[6] = { 0 };
//----------------------------------------
/* USER CODE END PV */
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_SPI2_Init(void);
static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */
 
/* USER CODE END PFP */
 
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
 
// ################## CLEAR BUFFER ##################################################
/*
function to clear buffers
values to put in:
1. the actual buffer
2. the buffer length for example with sizeof(buffer)
*/
void clearbuffer (char *buffer, int buffer_length)
/*clearbuffer(actual buffer to be cleared, sizeof(buffer)) */
{
  for (int i = 0; i < buffer_length; i++)
    {
      *buffer = '\0';
      buffer++;
    }
}
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
 
	// get new ADC value
	ADCval = HAL_ADC_GetValue(&hadc1);
 
	// increase number of samples that have been read
	number_of_samples++;
 
	// marker -> write sample in array
	write_samples_array_yes=1;
 
	// activate ADC interrupt
	HAL_ADC_Start_IT(&hadc1);
}
/* USER CODE END 0 */
 
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
 
  /* USER CODE END 1 */
  
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_SPI2_Init();
  MX_FATFS_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
 
//##### Mount SD Card #############################################################################
 
	fresult = f_mount(&fs, "", 0);
	fresult = f_getfree("", &fre_clust, &pfs);
	fresult = f_open(&fil, "Data_ADC.txt", FA_OPEN_ALWAYS | FA_WRITE);
 
//#################################################################################################
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
 
	HAL_ADC_Start_IT(&hadc1);
 
	  while (1)
	  {
 
		// 1.) make adc values to 5 Byte char
		// 2.) append adc values (char) in samples buffer
        if (write_samples_array_yes == 1)
        {
        	sprintf(one_sample, "%4i\n", ADCval); // onesample size: 5 * 8 Bit = 5 Byte
        	strncat(buffer1_samples_adc, one_sample, sizeof(one_sample)); // buffer1 size: 5 * 819 + 1 = 4096
        	clearbuffer(one_sample, sizeof(one_sample));
        	write_samples_array_yes=0;
        }
 
 
        // if buffer is full (to be precise, RAM), write buffer on SD
		if (number_of_samples > 458740) // max. number of samples
		{
			fresult = f_write(&fil, buffer1_samples_adc, sizeof(buffer1_samples_adc), &bw); // "Buffer" has to be 4096 Bytes -> for performance
			clearbuffer(buffer1_samples_adc, sizeof(buffer1_samples_adc));
			fresult = f_close(&fil); // close the latest file
			fresult = f_mount(0, "", 0); // unmount the SD Card from the FATFS file system
			while (1)
			{
				HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
				HAL_Delay(250);
			}

1 ACCEPTED SOLUTION

Accepted Solutions
KnarfB
Principal III

> Is there a function that can change my whole array of raw adc values into strings?

Don't think so. But since each converted value has a fixed length, you could use index calculation in a for-loop like:

for( int i=0; i<...; ++i ) {
  sprintf( buffer1_samples_adc+5*i, "%4i\n", ADCval[i] );
}

Note that the raw value array needs some extra space. You might break the f_write into smaller blocks to (over)compensate for this.

hth

KnarfB

View solution in original post

8 REPLIES 8
KnarfB
Principal III

strncat has to find the end of buffer1_samples_adc on each call. As that string gets longer each time, that means quadratic runtime. On the other hand, you do ADC conversion as fast as possible, hmm. You could check that strncat finished by testing write_samples_array_yes==0 in the interrupt handler and raise an error if not.

I wouldn't use strncat but define an array of raw ADC values and convert and write out the whole array at the end.

Consider also that f_write will take a long time compared to ADC measurements. You might end up with introducing an interrupt safe queue or even FreeRTOS if you want to support more than just one f_write.

Thank you for your fast reply.

I am a little bit rilled up because your theory of the quadratic run time. This fits tow my observations:

I have done some tests and increased the index "number_of_samples" to fill the array. The result was the number increased but the ration between the "number_of_samples" and the final number of samples written in the array increased. So the conclusion of this is, that if I write more samples, more samples wouldn't be written.

Here some numbers:

max number of samples | Samples written in array | Ration

--------- 22937 ------------------|-------------- 2678 ----------------| 22937 / 2678 = 8,6

--------- 22937*10 -------------|-------------- 8702 ----------------| 229370 / 8702 = 26,3

--------- 22937*20 -------------|--------------12353 ---------------| 458740 / 12535 = 37,1

I like your idea of storing the raw samples and change it afterwards. I will change the code to your proposal.

Is there a function that can change my whole array of raw adc values into strings? Because I have to write them on the SD Card.

Writing the data on SD Card is not slow and no problem. 120 KB would be written of an blink of an eye.

Best regards

Dietrich

KnarfB
Principal III

> Is there a function that can change my whole array of raw adc values into strings?

Don't think so. But since each converted value has a fixed length, you could use index calculation in a for-loop like:

for( int i=0; i<...; ++i ) {
  sprintf( buffer1_samples_adc+5*i, "%4i\n", ADCval[i] );
}

Note that the raw value array needs some extra space. You might break the f_write into smaller blocks to (over)compensate for this.

hth

KnarfB

Thank you!

That did the job.

In both ways: with your suggestion and your code snipped. I runs like a charm!

Now I must find the balance between these two arrays, when I run them with a fixed size. The one with the raw values and the one where the convertet string values are stored. With 100 - 120 KB RAM I have not much space to play.

The best solution would be to convert the values, and at the same time increase the size of the string array and decrease size of the raw array.

Best regards

Dietrich

KnarfB
Principal III

Good to hear. Don't know the details of your project, but two techniques to increase recording duration:

  • reduce ADC conversion rate: trigger ADC by a timer, not by itself
  • output ADC raw values per DMA to a larger circular buffer and use HalfFull+Full callbacks to overlap sample generation with sample processing (output to SD). Then, recording duration is only limited by the size of your SD card.

We want to show if we can do voice recording with a 12 Bit ADC resolution and a high sampling frequency of 44100 Hz.

With the current code setup it is not possible to do that. The RAM would be full after around 0,5 seconds or so.

We want to sample, store the samples and write them on SD "at the same time". Now with your help we are nearer then before to achieve this.

Our way is to store the adc samples as strings in one array until it's full. Then it would be written on SD and another array is filled with adc samples until that is full and so on.

We thought about using DMA but choose not to implement it.

Unfortunately our project will soon be over so there is not much time left.

"reduce ADC conversion rate: trigger ADC by a timer, not by itself"

What do you mean exactly be that?

Best regards

Dietrich

Here is an example: https://gitlab.com/stm32mcu/adc_dma_tim

The timer generates 48 kHz sample frequency. Every time when it rolls over, it generates an update event

sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE

The ADC is configured to make a measurement when that timer update event fires:

hadc.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO;

The ADC is instructed to fill a buffer with samples per DMA, without CPU

HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_buf, ADC_BUF_LEN);

The buffer is a circular (ring) buffer, which is filled with the samples.

As soon as the first half of the buffer is filled, a DMA interrupt is generated which calles a callback function:

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) {
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
  processBuffer( &adc_buf[0], ADC_BUF_LEN/2 );
}

The callback function must process the first half of the buffer before the second half is full. Analogously, for the second half, there is a another callback:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
  processBuffer( &adc_buf[ADC_BUF_LEN/2], ADC_BUF_LEN/2 );
}
 

When the buffer is large enough, there may be enough time for processBuffer do dump few sectors to the SD card.

hth

KnarfB

Thank you for the link. It looks interesting. I really want to test it with our project, but unfortunately the time runs out. Furthermore I am not that familiar with "STM32Cube" so that I can change all things easely in the main.c, so I want to do it with the GUI. but I can't even open the *.ioc so that I can change some things like adding FatFS or change the ADC GPIO.

Just the status:

With the status quo we can't write smoothly. If we plot our recorded samples (1k Hzsine wave) the signal is distorted (1 kHz sampling frequ.), like samples are missing and you can barely see that it should be a sine wave.

Best regards

Dietrich