Long time listener, first time caller. I am by no means an expert with microcontrollers. I've done a lot of googling to try to solve this issue but alas, I suspect the solution may be something that I don't know enough to look for.
Description of my setup and what I'm trying to do: I have a function generator (used for debugging but will eventually be replaced with our actual detector) connected to the portenta's A0 pin. The goal is to have a 100 kHz sampling rate of a 16 bit ADC value. Currently, I have set the code so that it samples data into a 1000 long buffer, prints each data point as it samples, and then runs through the whole process a certain number of times until the counter hits the max value and it stops collecting data.
I previously tried printing the buffer only after it filled up but the time required to print resulted in huge awkward gaps between data sets. I want to be able to collect ~1 minute of data, but more is better. Since we need a 100 kHz sampling rate, one issue is that I can't simply create a buffer long enough to hold 1 minute of 100 kHz data.
Now, we've accomplished our desired sampling rate of 100 kHz by following this very helpful thread: "Portenta H7 ADC DMA first steps" My code is directly taken from this thread, though I removed/modified a few sections to suit my project.
I measured the sampling speed using Serial.print and the micros() function minus the time at before sampling the data. This sampling time was then divided by the number of data points (1000) to give a little over 2 us per data point.
I am currently printing data points as they are collected. This slows down our sampling speed considerably to ~10 kHz because Serial.print takes around 100 us. I have also tried writing to a SD card but that takes anywhere from 100-200 us per data point. Writing bytes with Serial.write and receiving them with the Processing app takes 50 us per Serial.write. However, I need to send 3 separate Serial.write messages: the start bit, the lowByte of our 16 bit reading, and the highByte. This gives a total of ~150 us to write the binary.
By my math, for a baud rate of 2 million and sampling rate of 100 kHz, we should have a transfer bandwidth of 20 bytes. Each ADC reading will be between 0-65535, so that's 4 bytes per number. One question: although I set the baud rate to 2 million, is it actually operating at 2 million??
I would love to be able to utilize this beautifully fast ADC sampling rate but the speed at which I can send that reading via Serial.print, etc, is a huge bottleneck.
And, of course, here is the entirety of my code:
/*
Based off this forum thread: https://forum.arduino.cc/t/portenta-h7-adc-dma-first-steps/931669/12
For my setup, we are reading the A0 pin
*/
#include "main.h"
#include <SD.h>
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim16;
void SystemClock_Config(void);
static void MX_ADC1_Init(void);
static void MX_TIM16_Init(void);
const int TxPin = 0; // transmitter pin
const int ledPin = LED_BUILTIN; // pin to use for the LED
uint16_t raw = 0;
const uint32_t data_len = 1000;
uint16_t data[data_len];
uint16_t data_bool[data_len];
uint8_t adc_delay = 2;
File myFile;
void setup() {
HAL_Init();
// SystemClock_Config();
MX_ADC1_Init();
MX_TIM16_Init();
MX_GPIO_Init();
HAL_TIM_Base_Start(&htim16);
pinMode(ledPin, OUTPUT);
Serial.begin(2000000);
while (!Serial);
// Initialize SD card and open file
// Serial.print("Initializing SD card...");
// pinMode(7, OUTPUT);
// if (!SD.begin(7)) {
// Serial.println("initialization failed!");
// return;
// }
// Serial.println("initialization done.");
// myFile = SD.open("testfile1.txt", FILE_WRITE);
}
//Since we can only hold so much data in data[] at once, run through the void loop a certain number of times
//During each cycle, send collected ADC values to SD card/print to serial monitor/whatever you're doing
int counter = 0;
int mycount = 100;
void loop() {
counter = counter + 1;
while (raw < 1000) { // stay in here until theshold is reached
HAL_ADC_Start(&hadc1); // Start ADC conversion
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); // Wait until conversion is finished
raw = HAL_ADC_GetValue(&hadc1); // Get ADC value
}
raw = 0;
for (int i = 0; i < data_len; i++) {
// Serial.println("reset timer 16 to 0");
__HAL_TIM_SET_COUNTER(&htim16, 0); // Reset Timer 16 to 0
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_SET); // Set PH15 to HIGH
HAL_ADC_Start(&hadc1); // Start ADC conversion
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); // Wait until conversion is finished
data[i] = HAL_ADC_GetValue(&hadc1); // Get ADC value
Serial.println(data[i]);
// Serial.println(micros()); // use to test the time needed to print to serial monitor
while (__HAL_TIM_GET_COUNTER(&htim16) < adc_delay) {} // wait until counter has reached adc_delay counts
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_15, GPIO_PIN_RESET); // Reset PH15 to LOW
}
//If code has run for a certain number of cycles==mycount, stop collecting data
if (counter > mycount)
{
// myFile.close();
digitalWrite(LEDG, LOW);
delay(500);
digitalWrite(LEDG, HIGH);
Serial.println("done recording");
exit(0);
}
}
//void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim16){
// HAL_GPIO_TogglePin(GPIOH, GPIO_PIN_15);
// }
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
static void MX_TIM16_Init(void)
{
// TIM_ClockConfigTypeDef sClockSourceConfig;
// TIM_MasterConfigTypeDef sMasterConfig;
__HAL_RCC_TIM16_CLK_ENABLE(); // f*cking important
htim16.Instance = TIM16;
htim16.Init.Prescaler = 200 - 1; // Prescale the system frequency of the processor -> SystemCoreClock is 200 MHz (probably)... at least with 200-1 the timings are correct
htim16.Init.CounterMode = TIM_COUNTERMODE_UP;
htim16.Init.Period = 65536 - 1; // count to maximum (16 bit timer)
htim16.Init.ClockDivision = 0;
// htim16.Init.ClockDivision = TIM_CLOCKDIVISION_DIV5;
htim16.Init.RepetitionCounter = 0;
htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim16) != HAL_OK)
{
Error_Handler();
}
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_MultiModeTypeDef multimode = {0};
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
// hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
hadc1.Init.Resolution = ADC_RESOLUTION_16B;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure the ADC multi-mode
*/
multimode.Mode = ADC_MODE_INDEPENDENT;
if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
sConfig.OffsetSignedSaturation = DISABLE;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
void MX_GPIO_Init()
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOH_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
/*Configure GPIO pin : PH15 */
GPIO_InitStruct.Pin = GPIO_PIN_15; // Pin15
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // digital Output, push-pull configuration
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct); // GPIO-H
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
// __disable_irq();
while (1)
{
digitalWrite(LEDR, LOW);
delay(500);
digitalWrite(LEDR, HIGH);
}
/* USER CODE END Error_Handler_Debug */
}
Thank you very much for your help.