Using:
- STM32CubeMX Version: 6.10.0
- STM32CubeIDE Version: 1.13.1
- Nucleo-F767ZI
I am working on a DIY oscilloscope project where I want to try and push the speed and accuracy of the STM32F7 series' ADC as far as I can. I'm in the middle of reading the literature on the topic and gaining insights as I go, and what I've come to understand is that using a DMA pipeline is an efficient way to stream the data without bogging down the CPU. I am able to successfully fill a buffer with relatively accurate 12-bit ADC values and I can see them getting filled in the DMA register while I am debugging. The issue arises when I try and transmit the adc_buf array to huart3, the onboard uart port of my device.
I have verified that the uart3 port can transmit a buffer. I sent an array of chars,
char msg[] = "\n\rThe cow jumped over the moon\r\n"; with HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen(msg), 0xFFFF);. See my output below for the results on my putty window. I have also tried using a for loop to use snprintf and assign another buffer to transmit, but this neither works nor aligns with the objective of the project. I am guessing a for loop would not be the most efficient way to do this. I saw other answers that showed the order of initializing the DMA and UART are important, but the latest CubeMX update seems to have solved this problem. This is not the issue.
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART3_UART_Init();
MX_ADC1_Init();
MX_ETH_Init();
MX_USB_OTG_FS_PCD_Init();
Simplified, my workflow goes like this:
/* USER CODE BEGIN PD */
#define ADC_BUF_LEN 8
/* USER CODE END PD */
/* USER CODE BEGIN PV */
uint16_t adc_buf[ADC_BUF_LEN];
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 */
char msg[] = "\n\rThe cow jumped over the moon\r\n";
char buffer[50];
/* USER CODE END 1 */
//...Inits
/* USER CODE BEGIN 2 */
HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen(msg), 0xFFFF); // transmits cow message fine
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) &adc_buf, ADC_BUF_LEN);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
// wait for huart3 instance to write?
while (READ_BIT(huart3.Instance->ISR, USART_ISR_TC)==0){asm("nop");}
HAL_UART_Transmit_DMA(&huart3, (uint8_t*)adc_buf, ADC_BUF_LEN * sizeof(uint8_t));
// Must start ADC->DMA process again or values don't seem to be read continuously
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) &adc_buf, ADC_BUF_LEN);
}
/* USER CODE END WHILE */
}
I am expecting my results to look something like this:
The cow jumped over the moon
4095
4095
4095
4092
And so on. But instead I get garbage like:
/
The cow jumped over the moon
▒;▒▒Z*▒S=▒Z*▒S=▒P▒▒=KR2▒]▒▒RgG7▒]▒▒RgG7▒
▒▒
}▒:▒▒
▒▒▒4▒▒
▒▒▒4▒▒▒▒▒▒1▒▒▒▒
▒1▒▒▒▒
Further down, I tried writing some interrupts, and they seem to trigger perfectly fine.
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
When I set up breakpoints here, these interrupts appear to be functioning correctly. I have set up global interrupts on the usart3 peripheral, as well as made sure DMA, usart3, and ADC1 were set to normal instead of circular. DMA and USART appear to be functioning well independently, just not together.
Thanks for your time.
EDIT
I think I found my solution! Here's my main function:
int main(void)
{
/* USER CODE BEGIN 1 */
char msg[] = "\n\rThe cow jumped\r\n";
char buffer[ADC_BUF_LEN * 6]; // Assuming each value requires up to 6 characters (including null terminator)
memset(buffer, 0, sizeof(buffer));
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
/* 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_DMA_Init();
MX_USART3_UART_Init();
MX_ADC1_Init();
MX_ETH_Init();
MX_USB_OTG_FS_PCD_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen(msg), 0xFFFF); // works
// HAL_ADC_Start_DMA(&hadc1, (uint32_t *) adc_dma_result, adc_channel_count);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) &adc_buf, ADC_BUF_LEN);
//HAL_DMA_RegisterCallback(&hdma_usart3_tx, HAL_DMA_XFER_CPLT_CB_ID, &DMATransferComplete);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
// wait for huart3 instance to write
while (READ_BIT(huart3.Instance->ISR, USART_ISR_TC)==0) {asm("nop");}
// Wait until the previous DMA transfer is complete
if (__HAL_DMA_GET_FLAG(&hdma_usart3_tx, DMA_FLAG_TCIF0_4) == RESET) {
// TRY: convert adc_buf to string before transmitting
uint16_t len = 0;
for (uint8_t i = 0; i < ADC_BUF_LEN; i++) {
len += sprintf(buffer + len, "%d\r\n", adc_buf[i]); // Convert uint16_t value to ASCII string
}
HAL_UART_Transmit_DMA(&huart3, (uint8_t*)buffer, len);
}
// Wait for the transfer to complete
if (__HAL_DMA_GET_FLAG(&hdma_usart3_tx, DMA_FLAG_TCIF0_4) == RESET) {}
// Must start ADC->DMA process again or values don't seem to be read continuously
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) &adc_buf, ADC_BUF_LEN);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
I also had to change USART3 Data width from Half word to Byte. Now my output looks like this:
3839
4095
3899
3831
3845
And so on.
HAL_UART_Transmit_DMA(&huart3, (uint8_t*)adc_buf ...it will not print an ASCII representation of the ADC reading. You need to convertuint16_tnumbers to strings, then transmit the string. That conversion has to be synchronized with ADC DMA.It is not as easy to detect end of DMA transaction. TC flag does not mean that DMA transaction has finished. You need to check DMA flag and then TC. You can stand another transition then.