单片机
返回首页

STM32单片机实现DMA+ADC+UART功能

2021-12-31 来源:eefocus

突然想测试一下STM32单片机ADC采样速率问题,按照常规方法,可以通过ADC采样,然后将采样值打印出来。但是这种方法在处理和打印数据的时候会占用很多时间,导致处理数据的时间超过了ADC的采样时间。于是想到了ADC采样的数据用DMA功能存储,并通过串口打印。但是串口打印依然要占用单片机时间,那能不能串口数据的输出也采用 DMA功能呢?这样ADC采样的数据通过DMA直接存储,然后串口通过DMA功能直接输出采样到的数据。这样速度程序执行速度不就极大的提升了吗?说干就干,使用STM32F103C8T6单片机,标准库函数,keil5软件,编写一个测试程序。


首先实现ADC采样并通过DMA存储


#ifndef __ADC_H

#define __ADC_H

 

#include 'stm32f10x.h'

// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响

/********************ADC1输入通道(引脚)配置**************************/

#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 //使用一个通道测试

 

#define    ADC_PIN1                      GPIO_Pin_0

#define    ADC_CHANNEL1                  ADC_Channel_0

 

// ADC1 对应 DMA1通道1,ADC3对应DMA2通道5,ADC2没有DMA功能

#define    ADC_x                         ADC1

#define    ADC_DMA_CHANNEL               DMA1_Channel1

#define    ADC_DMA_CLK                   RCC_AHBPeriph_DMA1

 

// ADC1转换的电压值通过MDA方式传到SRAM

extern __IO uint16_t ADC_ConvertedValue[NOFCHANEL];

 

/**************************函数声明********************************/

void               ADCx_Init                               ( void );

#endif /* __ADC_H */


首先在头文件中定义用到的时钟和端口,如果要修改采样的AD口时,直接在头文件中修改就行,程序中就不需要修改了,方便代码的移植。下面编写ADC代码。


#include 'bsp_adc.h'

__IO uint16_t ADC_ConvertedValue[NOFCHANEL] = {1000};

/**

  * @brief  ADC GPIO 初始化

  * @param  无

  * @retval 无

  */

static void ADCx_GPIO_Config( void )

{

    GPIO_InitTypeDef GPIO_InitStructure;

    // 打开 ADC IO端口时钟

    ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );

    // 配置 ADC IO 引脚模式

    GPIO_InitStructure.GPIO_Pin = ADC_PIN1;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

    // 初始化 ADC IO

    GPIO_Init( ADC_PORT, &GPIO_InitStructure );

}

/**

  * @brief  配置ADC工作模式

  * @param  无

  * @retval 无

  */

static void ADCx_Mode_Config( void )

{

    DMA_InitTypeDef DMA_InitStructure;

    ADC_InitTypeDef ADC_InitStructure;

 

    // 打开DMA时钟

    RCC_AHBPeriphClockCmd( ADC_DMA_CLK, ENABLE );

    // 打开ADC时钟

    ADC_APBxClock_FUN ( ADC_CLK, ENABLE );

    // 复位DMA控制器

    DMA_DeInit( ADC_DMA_CHANNEL );

    // 配置 DMA 初始化结构体

    // 外设基址为:ADC 数据寄存器地址

    DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 ) ( & ( ADC_x->DR ) );

    // 存储器地址

    DMA_InitStructure.DMA_MemoryBaseAddr = ( u32 )ADC_ConvertedValue;

    // 数据源来自外设

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

    // 缓冲区大小,应该等于数据目的地的大小

    DMA_InitStructure.DMA_BufferSize = NOFCHANEL;

    // 外设寄存器只有一个,地址不用递增

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

    // 存储器地址递增

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

    // 外设数据大小为半字,即两个字节

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

    // 内存数据大小也为半字,跟外设数据大小相同

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

    // 循环传输模式

    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

    // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响

    DMA_InitStructure.DMA_Priority = DMA_Priority_High;

    // 禁止存储器到存储器模式,因为是从外设到存储器

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    // 初始化DMA

    DMA_Init( ADC_DMA_CHANNEL, &DMA_InitStructure );

    // 使能 DMA 通道

    DMA_Cmd( ADC_DMA_CHANNEL, ENABLE );

    // ADC 模式配置

    // 只使用一个ADC,属于单模式

    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;

    // 扫描模式

    ADC_InitStructure.ADC_ScanConvMode = ENABLE ;

    // 连续转换模式

    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

    // 不用外部触发转换,软件开启即可

    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;

    // 转换结果右对齐

    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

    // 转换通道个数

    ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;

    // 初始化ADC

    ADC_Init( ADC_x, &ADC_InitStructure );

    // 配置ADC时钟N狿CLK2的8分频,即9MHz

    RCC_ADCCLKConfig( RCC_PCLK2_Div8 );

    // 配置ADC 通道的转换顺序和采样时间

    ADC_RegularChannelConfig( ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5 );

    // 使能ADC DMA 请求

    ADC_DMACmd( ADC_x, ENABLE );

    // 开启ADC ,并开始转换

    ADC_Cmd( ADC_x, ENABLE );

    // 初始化ADC 校准寄存器

    ADC_ResetCalibration( ADC_x );

    // 等待校准寄存器初始化完成

    while( ADC_GetResetCalibrationStatus( ADC_x ) );

    // ADC开始校准

    ADC_StartCalibration( ADC_x );

    // 等待校准完成

    while( ADC_GetCalibrationStatus( ADC_x ) );

    // 由于没有采用外部触发,所以使用软件触发ADC转换

    ADC_SoftwareStartConvCmd( ADC_x, ENABLE );

}

/**

  * @brief  ADC初始化

  * @param  无

  * @retval 无

  */

void ADCx_Init( void )

{

    ADCx_GPIO_Config();

    ADCx_Mode_Config();

}


/*********************************************END OF FILE**********************/

设置ADC为DMA传输,将采样的数据由DMA自动存储到 ADC_ConvertedValue[ ] 数组中,这里虽然只使用了一个ADC采样通道,但是定义了一个数组来存放采样结果,如果想要实现多通道采样值,只需要将其他通道的初始化代码添加上,同时将数组长度,也就是通道数修改一下就可以使用了。初始化ADC和DMA后,ADC采样并通过 DMA传输的功能就可使用了。然后串口输出数据的时候直接从ADC的采样结果的数组中取值就可以了。


下面编写串口相关代码


#ifndef __USART_H

#define __USART_H

 

#include 'stm32f10x.h'

#include

// 串口1-USART1

#define  DEBUG_USARTx                   USART1

#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1

#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd

#define  DEBUG_USART_BAUDRATE           921600

 

// USART GPIO 引脚宏定义

#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_10

 

将串口的端口和时钟也使用宏定义的方式,如果要改为其他串口输出时,直接修改头文件就行。下来初始化串口。


void USART_Config( void )

{

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

 

    // 打开串口GPIO的时钟

    DEBUG_USART_GPIO_APBxClkCmd( DEBUG_USART_GPIO_CLK, ENABLE );

 

    // 打开串口外设的时钟

    DEBUG_USART_APBxClkCmd( DEBUG_USART_CLK, ENABLE );

 

    // 将USART Tx的GPIO配置为推挽复用模式

    GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure );

 

    // 将USART Rx的GPIO配置为浮空输入模式

    GPIO_InitStructure.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;

    // 配置 针数据字长

    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    // 配置停止位

    USART_InitStructure.USART_StopBits = USART_StopBits_1;

    // 配置校验位

    USART_InitStructure.USART_Parity = USART_Parity_No ;

    // 配置硬件流控制

    USART_InitStructure.USART_HardwareFlowControl =

        USART_HardwareFlowControl_None;

    // 配置工作模式,收发一起

    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    // 完成串口的初始化配置

    USART_Init( DEBUG_USARTx, &USART_InitStructure );

 

    // 使能串口

    USART_Cmd( DEBUG_USARTx, ENABLE );

}


下来设置串口为DMA输出


#ifndef __DMA_H

#define __DMA_H

#include 'stm32f10x.h'

// 串口对应的DMA请求通道

#define  USART_TX_DMA_CHANNEL     DMA1_Channel4

// 外设寄存器地址

//#define  USART_DR_ADDRESS        (USART1_BASE+0x04) //使用地址偏移值

#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 );

#endif


在头文件中定义串口发送端的 DMA通道,将串口数据寄存器作为DMA源地址,将SendBuff[ ]数组中的内容作为内存地址,数据方向为内存到源,这样就直接将SendBuff[ ]数组中的数据通过DMA直接传输到了串口中。


void USARTx_DMA_Config(void)

{

DMA_InitTypeDef DMA_InitStructure;

// 开启DMA时钟

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

// 设置DMA源地址:串口数据寄存器地址*/

        DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;

// 内存地址(要传输的变量的指针)

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;

// 方向:从内存到外设

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;

// 传输大小  

DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE ;

// 外设地址不增     

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

// 内存地址自增

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

// 外设数据单位

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

// 内存数据单位

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  

// DMA模式,一次或者循环模式

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;

// 配置DMA通道    

DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);

// 使能DMA

DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);

}

//重新启动串口 DMA

void USARTx_DMA_Restart( void )

{

    //重新开启DMA传输

    DMA_Cmd( USART_TX_DMA_CHANNEL, DISABLE );

    DMA_SetCurrDataCounter( USART_TX_DMA_CHANNEL, SENDBUFF_SIZE ); //重新设置传输的数据数量

    DMA_Cmd( USART_TX_DMA_CHANNEL, ENABLE );                       //开启DMA传输

}


将DMA传输设置为单次传输模式,因为转换后的数据为16进制数据,为了方便在串口助手上观看数据,需要将16进制转换为字符串打印出来,所以每次每次在串口DMA传输数据的前,将数据转换为字符串,然后再打开 DMA传输功能,将数据通过串口发送出去。所以DMA传输时不能设置为循环发送,需要每次将数据格式转换完成后才能发送。发送完一次数据后,需要重新设置DMA传输数据的数量,然后重新启动一次 DMA传输。


int main( void )

{

    char str[5];

    /* LED 端口初始化 */

    LED_GPIO_Config();

    /*初始化USART 配置模式为 115200 8-N-1,中断接收*/

    USART_Config();

    ADCx_Init();

    /* DMA传输配置 */

    USARTx_DMA_Config();

 

    /* USART1 向 DMA发出TX请求  单次发送模式 */

    USART_DMACmd( DEBUG_USARTx, USART_DMAReq_Tx, ENABLE );

    //串口发送缓冲区后面添加回车换行符

    SendBuff[4] = 'r';

    SendBuff[5] = 'n';

    while ( 1 )

    {

        //填充数据 只打印通道1数据

        sprintf( str, '%d', ADC_ConvertedValue[0] ); //将通道1采样值转换为字符串类型

        SendBuff[0] = str[0]; //将字符串数据存入串口DMA发送缓存区

        SendBuff[1] = str[1];

        SendBuff[2] = str[2];

        SendBuff[3] = str[3];

        USARTx_DMA_Restart();

 

        SysTick_Delay_Us( 45 );

    }

}


STM32F103单片机的AD为12位,采样值的范围为0---4095,转换为字符串后最多为4位,再加上回车换行符,总共6个字符。这样串口DMA传输数据的长度就为6位。


为了测试 DMA传输的最大速度,将串口波特率设置为921600。下面开始测试代码,用函数发生器产生一个0--2V的正弦波,频率为100HZ。然后通过串口助手观察采样的数据。

通过串口波形显示助手观察输出的数据波形如下

通过上面测试可以看出,在STM32F103C8T6单片机上成功实现了ADC的 DMA采集,和串口DMA发送功能。

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • PIC单片机控制的遥控防盗报警器电路

  • 红外线探测报警器

  • 用NE555制作定时器

  • 带有短路保护系统的5V直流稳压电源电路图

  • 如何构建一个触摸传感器电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章