This is the first day of my participation in the More text Challenge. For details, see more text Challenge
Suddenly, I want to test the SAMPLING rate of STM32 MICROcontroller ADC. According to the conventional method, sampling can be done by ADC, and then the sampled value is printed out. However, this approach takes up a lot of time in processing and printing the data, resulting in data processing time exceeding the ADC sampling time. Therefore, the ADC sampling data is stored with DMA function and printed through serial port. But the serial port printing still takes up the time of single chip microcomputer, that can the serial port data output also use DMA function? In this way, the ADC samples are stored directly through DMA, and the serial port outputs the sampled data directly through THE DMA function. This speed program execution speed is not greatly improved? Say and do, use STM32F103C8T6 microcontroller, standard library functions, KEIL5 software, write a test program.
First implement ADC sampling and storage through DMA
#ifndef __ADC_H #define __ADC_H #include "stm32f10x.h" The IO used for ADC acquisition must not be reused, Or acquisition voltage will have impact / * * * * * * * * * * * * * * * * * * * * ADC1 input channel (pin) configuration * * * * * * * * * * * * * * * * * * * * * * * * * * / # define ADC_APBxClock_FUN RCC_APB2PeriphClockCmd #define ADC_CLK RCC_APB2Periph_ADC1 #define ADC_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd #define ADC_GPIO_CLK RCC_APB2Periph_GPIOA #define ADC_PORT GPIOA #define NOFCHANEL 1 // Test #define ADC_PIN1 GPIO_Pin_0 using a channel #define ADC_CHANNEL1 ADC_Channel_0 #define ADC_CHANNEL1 ADC_Channel_0 #define ADC_CHANNEL1 ADC_Channel_0 ADC2 has no DMA function #define ADC_x ADC1 #define ADC_DMA_CHANNEL DMA1_Channel1 #define ADC_DMA_CLK RCC_AHBPeriph_DMA1 // The voltage value converted by ADC1 is transmitted to SRAM extern __IO uint16_t ADC_ConvertedValue[NOFCHANEL] by MDA; / * * * * * * * * * * * * * * * * * * * * * * * * * * function declarations * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / void ADCx_Init (void); #endif /* __ADC_H */Copy the code
First define the clock and port used in the header file, if you want to change the sampling AD port, directly in the header file to change the line, the program does not need to change, convenient code migration. Let’s write the ADC code.
#include "bsp_adc.h" __IO uint16_t ADC_ConvertedValue[NOFCHANEL] = {1000}; /** * @brief ADC GPIO initialization * @param None * @retval None */ static void ADCx_GPIO_Config(void) {GPIO_InitTypeDef GPIO_InitStructure; // ENABLE the ADC IO port clock ADC_GPIO_APBxClock_FUN (ADC_GPIO_CLK, ENABLE); // Configure the ADC IO pin mode. Gpio_initstructure. GPIO_Pin = ADC_PIN1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // Initialize ADC IO GPIO_Init(ADC_PORT, &GPIO_InitStructure); } /** * @brief Configure the ADC working mode * @parAM None * @RETval None */ static void ADCx_Mode_Config(void) {DMA_InitTypeDef DMA_InitStructure; ADC_InitTypeDef ADC_InitStructure; // Open the DMA clock RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE); // Open ADC_APBxClock_FUN (ADC_CLK, ENABLE); // Open ADC_APBxClock_FUN (ADC_CLK, ENABLE); // Reset the DMA controller DMA_DeInit(ADC_DMA_CHANNEL); Dma_initstructure.dma_peripheralbaseaddr = (u32) (& (ADC_x->DR)); DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue; DMA_DIR = DMA_DIR_PeripheralSRC; // The buffer size should be equal to the size of the data destination. Dma_initstruct. DMA_BufferSize = NOFCHANEL; Dma_initstruct. DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_PeripheralDataSize_HalfWord; Dma_initstruct. DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_MemoryDataSize_HalfWord; DMA_Mode = DMA_Mode_Circular; // Circular transmission mode dma_initstructure. DMA_Mode = DMA_Mode_Circular; Dma_initstruct. DMA_Priority = DMA_Priority_High; DMA_Priority = DMA_Priority_High; Dma_initstruct. DMA_M2M = DMA_M2M_Disable; // Initialize DMA DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure); // ENABLE DMA_Cmd(ADC_DMA_CHANNEL, ENABLE); Adc_initstructure. ADC_Mode = ADC_Mode_Independent; // Scan mode adc_initstructure. ADC_ScanConvMode = ENABLE; Adc_initstructure. ADC_ContinuousConvMode = ENABLE; ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_ExternalTrigConv_None = ADC_ExternalTrigConv_None; Adc_initstructure. ADC_DataAlign = ADC_DataAlign_Right; Adc_initstructure. ADC_NbrOfChannel = NOFCHANEL; // Initialize ADC ADC_Init(ADC_x, &ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div8); // Set the ADC clock N to CLK2 8, namely 9MHz RCC_ADCCLKConfig(RCC_PCLK2_Div8); ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5); // ENABLE ADC DMA to request ADC_DMACmd(ADC_x, ENABLE); ADC_Cmd(ADC_x, ENABLE); // Initialize the ADC calibration register ADC_ResetCalibration(ADC_x); / / wait for the calibration register initialization is complete while (ADC_GetResetCalibrationStatus (ADC_x)); // ADC_StartCalibration(ADC_x); // Wait for calibration to complete while(ADC_GetCalibrationStatus(ADC_x)); ADC_SoftwareStartConvCmd(ADC_x, ENABLE); // ADC_SoftwareStartConvCmd(ADC_x, ENABLE); } /** * @brief ADC initialization * @param None * @retval None */ void ADCx_Init(void) {ADCx_GPIO_Config(); ADCx_Mode_Config(); } /*********************************************END OF FILE**********************/Copy the code
Set ADC to DMA transfer, and the sampled data is automatically stored in the ADC_ConvertedValue[] array by DMA. Although only one ADC sampling channel is used here, an array is defined to store the sampled results. If you want to implement multi-channel sampling values, you only need to add the initialization code for other channels. At the same time, the array length, that is, the number of channels can be changed to use. After the ADC and DMA are initialized, the ADC’s ability to sample and transfer through DMA is available. Then the serial port outputs the data directly from the ADC sample array.
The following code to write serial port
#ifndef __USART_H #define __USART_H #include "stm32f10x.h" #include <stdio.h> #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 921600 // USART GPIO pin macro definition #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10Copy the code
Set the port and clock of the serial port to macros. If you want to change the output of other serial ports, modify the header file directly. Down to initialize the serial port.
void USART_Config( void ) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); // Open the clock of the serial port peripheral DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); Gpio_initstructure. GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; // Set the GPIO of USART Tx to push-pull multiplexing mode. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure ); Gpio_initstructure. GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init( DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure ); Usart_initstructure. USART_BaudRate = DEBUG_USART_BAUDRATE; // Set the working parameters of the serial port. USART_WordLength = USART_WordLength_8b; // Configure the stop bit usart_initstructure. USART_StopBits = USART_StopBits_1; USART_Parity = USART_Parity_No; // Set the parity bit. // Configure hardware flow control usart_initStructure. USART_HardwareFlowControl = USART_HardwareFlowControl_None; / / configuration mode, transceiver USART_InitStructure together. USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // Complete the initial configuration of the serial port USART_Init(DEBUG_USARTx, &USART_InitStructure); // ENABLE the serial port USART_Cmd(DEBUG_USARTx, ENABLE); }Copy the code
Set the serial port to DMA output
#define USART_TX_DMA_CHANNEL DMA1_Channel4 #define USART_TX_DMA_CHANNEL DMA1_Channel4 #define USART_DR_ADDRESS ((u32)&USART1->DR) #define USART_DR_ADDRESS ((u32)&USART1->DR #define SENDBUFF_SIZE 6 extern uint8_t SendBuff[SENDBUFF_SIZE]; void USARTx_DMA_Config(void); void USARTx_DMA_Restart( void ); #endifCopy the code
Define the DMA channel at the sending end of the serial port in the header file, with the serial port data register as the DMA source address, the contents of the SendBuff[] array as the memory address, and the data direction as memory to source, thus directly transferring the data in the SendBuff[] array to the serial port through DMA directly.
void USARTx_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // ENABLE the DMA clock RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Set DMA source address: serial port data register address */ dMA_initstructure. DMA_PeripheralBaseAddr = USART_DR_ADDRESS; // Memory address (pointer to the variable to be transferred) dma_initstruct.dMA_MemoryBaseADDR = (u32)SendBuff; DMA_DIR = DMA_DIR_PeripheralDST; // Direction: from memory to peripheral dMA_initstructure. DMA_DIR = DMA_DIR_PeripheralDST; // Transfer size dma_initstruct. DMA_BufferSize = SENDBUFF_SIZE; Dma_initstruct. DMA_PeripheralInc = DMA_PeripheralInc_Disable; Dma_initstructure. DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Memory data unit dMA_initstruct. DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; Dma_initstructure. DMA_Mode = DMA_Mode_Normal; Dma_initstructure. DMA_Mode = DMA_Mode_Circular; Dma_initstructure. DMA_Priority = DMA_Priority_Medium; Dma_initstructure. DMA_M2M = DMA_M2M_Disable; // Configure DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); // ENABLE DMA DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE); } // restart the serial port of DMA void USARTx_DMA_Restart(void) {// restart DMA_Cmd(USART_TX_DMA_CHANNEL, DISABLE); DMA_SetCurrDataCounter( USART_TX_DMA_CHANNEL, SENDBUFF_SIZE ); DMA_Cmd(USART_TX_DMA_CHANNEL, ENABLE); // Open DMA transferCopy the code
Sets the DMA transfer to a single transport pattern, because the transformed data for hexadecimal data, in order to convenient to watch the data on the serial port assistant, need to convert hexadecimal to string printed, so every time every time before a serial port DMA transfers data, converts the data to a string, and then open the DMA transfer function, the data via a serial port to send out. Therefore, the DMA transfer cannot be set to send in a loop, and the data format needs to be converted each time before sending. After a data transfer, you need to reset the amount of DMA data transferred, and then restart a DMA transfer.
int main( void ) { char str[5]; /* LED_GPIO_Config(); /* Initialize the USART configuration mode to 115200 8-n-1, interrupt receiving */ USART_Config(); ADCx_Init(); /* USARTx_DMA_Config(); /* USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, USART_DMAReq_Tx, ENABLE); SendBuff[4] = '\r'; SendBuff[4] = '\r'; SendBuff[5] = '\n'; While (1) {sprintf(STR, "%d", ADC_ConvertedValue[0]); SendBuff[0] = STR [0]; SendBuff[0] = STR [0]; SendBuff[1] = STR [1]; SendBuff[1] = STR [1]; SendBuff[2] = str[2]; SendBuff[3] = str[3]; USARTx_DMA_Restart(); SysTick_Delay_Us( 45 ); }}Copy the code
The AD of STM32F103 MCU is 12 bits, and the range of sampling value is 0-4095. After conversion to a string, it can be up to 4 bits, plus carriage return and line feed, a total of 6 characters. Thus the serial PORT DMA data transfer length is 6 bits.
To test the maximum speed of the DMA transfer, set the serial port baud rate to 921600. Let’s test the code by using the function generator to generate a 0–2V sine wave at 100HZ. Then observe the sampled data through the serial assistant.
The output data waveform observed by the serial port waveform display assistant is as follows
It can be seen from the above test that the ADC DMA acquisition and serial DMA sending functions have been successfully implemented on STM32F103C8T6.