STM32单片机实现DMA+ADC+UART功能_adc dma uart-程序员宅基地

技术标签: ADC  STM32  DMA  UART  STM32学习笔记  单片机  

        突然想测试一下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 <stdio.h>
// 串口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发送功能。

完整工程下载链接 STM32单片机实现DMA+ADC+UART功能

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_20222919/article/details/112007249

智能推荐

Android开发免积分资料(教程+工具+源码)下载汇总_android软件开发积分源码-程序员宅基地

文章浏览阅读667次。Android开发可谓如火如荼,越来越多开发人员进入这个行列,最近小弟为大家整理了一批免积分下载的Android优质资料,包括60个手册教程、10个相关工具和20套源码。资料众多,很难一一将资料上传分享,先将下载目录分享给大家,需要的就拿去,希望能帮助到大家~ 【免费】android界面效果全汇总.pdfhttp://down.51cto.com/data/209179Andro_android软件开发积分源码

Remote Debugging (2)-程序员宅基地

文章浏览阅读65次。use Eclipse| a Java application创建一个简单的maven项目Main.javapackage cn.zno;public class Main { public static void main(String[] args) { for (String arg : args) { ..._remote debugging for hadoop client

建立哈希表并进行插入删除查找元素操作_假设哈希表上删除操作已将记录关键字标记为delete 试设计一个查找及插入算法,-程序员宅基地

文章浏览阅读5.4k次,点赞3次,收藏7次。注意:1.本博客仅供参考交流使用,请读者务必自行实践,切勿生搬硬套2.由于笔者水平有限,若文中有错误或者可以改进之处,欢迎在评论区指出题目Description散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表..._假设哈希表上删除操作已将记录关键字标记为delete 试设计一个查找及插入算法,

解决git每次提交代码都要输入帐号和密码问题_gitlab每次都要输入账号密码-程序员宅基地

文章浏览阅读3.8k次。执行下面这条代码git config --global credential.helper store然后首次提交的时候输入一次帐号和密码之后就不需要再次输入了_gitlab每次都要输入账号密码

MySQL数据库优化的八种方式(经典必看)_mysql优化-程序员宅基地

文章浏览阅读578次。网上关于SQL优化的教程很多,但是比较杂乱。近日有空整理了一下,写出来跟大家分享一下,其中有错误和不足的地方,还请大家纠正补充。这篇文章我花费了大量的时间查找资料、修改、排版,希望大家阅读之后,感觉好的话推荐给更多的人,让更多的人看到、纠正以及补充。1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。复制最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库._mysql优化

回忆当初写过的代码_bebibb啥意思-程序员宅基地

文章浏览阅读1.7k次。只是想当初,怀旧一下_bebibb啥意思

随便推点

苹果ppt_惊艳!苹果发布会最爱用的PPT动画,居然这么简单-程序员宅基地

文章浏览阅读803次。前段时间的苹果发布会你看了吗?由于疫情的原因,改为线上发布会,但PPT页面还是一样的炫酷!这里我整理了一份PPT源文件,如果你看的话,私信回复【苹果发布会】,即可获取了:看了这么多次苹果发布会,我发现PPT设计师尤其喜欢这个动画效果——平滑切换。比如,去年苹果开发者大会中的PPT:苹果官网动画:都用到了这个超酷的效果!如果你的 office 软件是 2019 或者 365 版本,只需要鼠标点一点,..._苹果宣传片好几张ppt变成同一页动画叫什么

简单页面布局实例_javajfame页面布局例子-程序员宅基地

文章浏览阅读696次。http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/1999/xhtml">上中下三行布局,上下定高,中间栏自适应浏览器高度,且内容垂直居中* {margin:0;padding:0;}html,body,#box {height:100%;fo_javajfame页面布局例子

GCD定时器进入后台停止运行_dispatchsource 定时器不执行-程序员宅基地

文章浏览阅读3.7k次。ios7以后提供的后台接口模式1、Background Audio,这是后台的音频,这个很早之前便有,也是iOS设备中用得最多的后台应用,调用这个接口可以实现后台的音乐播放。2、Location Services,这是后台的定位,系统会拥有统一页面进行管理。3、VoIP,后台语音服务,类似Skype通话应用需要调用,可进行后台的语音通话。4、Newsstand,报刊杂志后台自动下载..._dispatchsource 定时器不执行

Intellij IDEA【模拟http请求】及示例_idea http请求-程序员宅基地

文章浏览阅读4.5k次。1.找到下面的工具位置**2.然后就可以看见这个东东,点击发送请求3.查看结果:requestParameters对应springmvc中的controller中的@RequestParamrequestBody中text中可以直接放入json串headers中放入 Accept:application/json(响应格式) Content-Type:appli..._idea http请求

二、逆向工程(Maven)_在 pom.xml 这一级目录的命令行窗口执行-程序员宅基地

文章浏览阅读124次。pom.xml <properties> <!-- ${basedir}引用工程根目录 --> <!-- targetJavaProject:声明存放源码的目录位置 --> <targetJavaProject>${basedir}/src/main/java</targetJavaProject> <!-- targetMapperPackage:声明MBG生成XxxMapper接口后存放的package位置 _在 pom.xml 这一级目录的命令行窗口执行

psql: 无法联接到服务器: 没有那个文件或目录 服务器是否在本地运行并且在 Unix 域套接字 “/var/run/postgresql/.s.PGSQL.5432“上准备接受联接?_psql默认从var里寻找socket-程序员宅基地

文章浏览阅读5.9k次,点赞3次,收藏7次。在postgres下启动psql的时候出现了这个问题:psql: 无法联接到服务器: 没有那个文件或目录服务器是否在本地运行并且在 Unix 域套接字"/var/run/postgresql/.s.PGSQL.5432"上准备接受联接?这是因为postgresql没有启动的原因,显示psql默认找的socket文件在/var/run/postgresql/下。用strace追踪一下学过Linux我们知道/var目录是一个运行时目录,而且和我的安装目录/usr/local/postgresql_psql默认从var里寻找socket

推荐文章

热门文章

相关标签