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

发布者:w2628203123最新更新时间:2022-01-13 来源: eefocus关键字: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

// 串口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;

[1] [2]
关键字:STM32  单片机 引用地址:STM32单片机实现DMA+ADC+UART功能

上一篇:STM32F103单片机ADC功能使用
下一篇:记一次ST-LINK维修及刷固件过程

推荐阅读最新更新时间:2024-11-08 15:34

晶振与STM32的那些小关系
01、晶体的压电效应 压电效应:某些电介质在沿一定方向上受到外力的作用而变形时,其内部会产生极化现象,同时在它的两个相对表面上出现正负相反的电荷。 正压电效应:当外力去掉后,电介质又会恢复到不带电的状态。 逆压电效应:当作用力的方向改变时,电荷的极性也随之改变。相反,当在电介质的极化方向上施加电场,这些电介质也会发生变形,电场去掉后,电介质的变形随之消失。 当在晶体表面上施加机械压力时,与机械压力成比例的电压出现在晶体上。该电压会导致晶体失真,失真的量将与施加的电压成比例,并且还与施加在晶体上的交流电压成正比,从而导致晶体以其固有频率振动。这种压电效应会产生机械振动或振荡,可用来代替以前的振荡器中的标准LC振荡电路。 下图
[单片机]
晶振与<font color='red'>STM32</font>的那些小关系
ST收购专注于Arm微控制器集成开发环境的嵌入式公司Atollic
电子网消息,横跨多重电子应用领域、全球领先的半导体供应商意法半导体(STMicroelectronics,简称ST)今天宣布收购软件开发工具专家Atollic公司。 Atollic开发出了业内知名的获得高度好评的TrueSTUDIO®集成开发环境(IDE),专注Arm® Cortex®-M微控制器的嵌入式开发社区,例如,意法半导体的市场领先的STM32系列微控制器(MCU)。 意法半导体是世界顶级的32位微控制器厂商,拥有强大的软硬件开发生态系统,能够帮助开发者加快并简化应用开发设计,而TrueSTUDIO的加入将会进一步强化意法半导体的生态系统。由一支资深、敬业的世界一流软件工具专家设计开发,TrueSTUDIO是业界
[半导体设计/制造]
51单片机常用的特殊功能寄存器
PSW(Program Status Word) 中断: IP(Interrupt Priority) IE(Interrupt Enable) 串口: SBUF(Serial Buffer) SCON(Serial Control) 定时器: TMOD(Timer Mode) TCON(Timer Control) 电源: PCON(Power Control)
[单片机]
STM32 PB3或者PB4不能正常使用的讲解
最近用STM32F103T8做项目,发现PB3和PB4这两个IO不可控,一直是高电平; 原因是PB3和PB4在系统复位时候,分别默认为SYS_JIDO和SYS_HJTRST; 所以需要通过用户自行禁止其功能; 也就是想要正常使用PB3和PB4的主功能的时候。 在初始化IO时候,增加代码如下:(这里使用J-Link的SWD模式烧录程序) //打开时钟函数 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE); //打开GPIO口时钟,先打开复用才能修改复用功能 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTA
[单片机]
<font color='red'>STM32</font> PB3或者PB4不能正常使用的讲解
嵌入式TCP/IP协议栈在单片机上的实现
  随着嵌入式设备与网络的日益结合,在单片机系统中引入TCP/IP协议栈,以支持单片机接入网络,成为嵌入式领域的一个重要方向。在此对基于SST89E516RD单片机的TCP/IP协议栈的实现方法给予讨论。选用SST89E516RD单片机实现了在线仿真和编程的功能,大大节约了开发成本。采用VB 6.0语言与Window 98/2000/XP等为软件开发平台,对系统进行了测试。经过几个月的软硬件测试表明:系统设计合理、稳定可靠,已基本实现了最初的设计目标。对其他类似系统移植该项技术奠定了基础,有很好的参考价值。    1 系统硬件实现   整个系统以SST89E516RD单片机为核心,通过RTL8019AS以太网控制芯片实现远
[嵌入式]
单片机对modem要进行哪些初始化操
一般单片机的MODEM通讯必须要有两个背景知识,一个是AT命令集,另一个是通用非同步接收发送器(UART)。  AT命令集 下面介绍我通讯程式例子中涉及到的AT命令。 Dn:拨号命令。该命令使MODEM立即进入摘机状态,并拨出跟在后面的号码。D命令是基本的拨号命令,它受到其他命令的修饰可构成MODEM何时拨号以及如何拨号等操作。 T:音频拨号。例如,ATDT13632757314,其中13632757314为电话号码。 P:脉冲拨号。例如,ATDT13632757314,其中13632757314为电话号码。 ,:标准暂停。我们常常碰到拨打外线电话时需要暂停一下,等听到二次拨号音(外线)之后才能再拨后续的号码。缺省时暂
[单片机]
51单片机继电器使用方法
5V继电器 JQC-3F T73-5VD/10A前继继电器 5脚 五脚继电器的接法: 继电器是一种用电流控制的开关装置。继电器的工作原理是,当继电器线圈通电后,线圈中的铁芯产生强大的电磁力,吸动衔铁带动簧片,使触点1、2断开,1、3接通。当线圈断电后,弹簧使簧片复位,使触点1、2接通,1、3断开。只要把需要控制的电路接在触点1、2间(1、2称为常闭触点)或触点1、3间(称为常开触点),就可以利用继电器达到某种控制的目的2.将线圈引脚4、5两端加上直流电压。 三只脚的那一边中间脚是输出触点的公共端子,另外两个引脚是线圈,即接驱动端。另外2个脚那边分别是常开和常闭触点。 如下图:A、B脚接驱动电路端
[单片机]
STM32-1-STM32时钟系统
在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 ①HSI是高速内部时钟,RC振荡器,频率为8MHz。 ②HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。 ③LSI是低速内部时钟,RC振荡器,频率为40kHz。 ④LSE是低速外部时钟,接频率为32.768kHz的石英晶体。 ⑤PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。 在STM32上如果不使用外部晶振,OSC_IN和OSC_OUT的接法 如果使用内部RC振荡器而不使用外部晶振,请按照下面方法处理: 1)对于10
[单片机]
STM32-1-STM32时钟系统

推荐帖子

学电源的视频教程,为啥没有100%的进度?
今晚怎么打不开了视频教程?几个单元,前面几个都看了,中间也没落下什么,从头看到尾,为什么各个部分还是不到100%?学电源的视频教程,为啥没有100%的进度?多看几遍撒~如楼上所说这么智能啊,还能算好了你学到了百分之几十,多学习几遍就达到100%楼主不要太在意那几%了吧,,觉得都看完了,就赶快去考试,只要考过,就说明你学的好的。这和大学一样,不一定非听完老师的才行滴。活动要求至少学习进度在80%以上,就可以去考试。不一定非100%。只是觉得进度问题奇怪而已,差最后一节和没考试,
xingkong911 模拟与混合信号
【GD32307E-START】03-拓展板原理图规划
由于自己所在的是工控行业,所以基本都是做RS485接口,还有网口,自己在设计原理图的时候拓展了485接口、2个CAN接口、1个以太网接口(RMII),还有就是一个IIC的0.96寸的OLED接口,外加了SPI-Flash,做来测试芯片应该是差不多的。下面把原理图放上来,麻烦大佬们给个意见,有可能原理图有错的地方,希望大佬们指点一下。原理图文件【GD32307E-START】03-拓展板原理图规划兆易GD32307E-START测评汇总http://bbs.eew
申小林 GD32 MCU
verilog 如何才能学好?
我对verilog非常感兴趣!现在也在努力学习,可是不知道怎么才能学好!就像我在学c语言时,看了好多的程序,可是一上机就不知道该怎么做了!就连一个小程序写起来都很费劲!现在我就怕把verilog学到后期也成这样子了!那就不好了。我是学微电子的!现在在上大三,想在毕业前能做出点东西!我现在好些专业课如半导体物理都不怎么好!,就是想把这学好,出来能找到个好的专业!谢谢…………verilog如何才能学好?
lmx5078 嵌入式系统
错误1406 "无法创建最上层子窗口"怎么解决?附代码
boolCMyEdit::Create(CString&str,intiId){intres=CEdit::Create(WS_CHILD|WS_VISIBLE,CRect(0,0,0,0),this,iId);TRACE(L%d,GetLastError());//这里得到错误1406,无法创建最上层子窗口,为什么?returnres;}错误1406\"无法创建最上层子窗口\"怎么解决?附代码
bin8888 嵌入式系统
基于MB90F428的汽车仪表设计
引言汽车仪表是人和汽车的交互界面,为驾驶员提供所需的汽车运行参数、故障、里程等信息,是每一辆汽车必不可少的部件。它经历了机械式、电气式、模拟电路电子式的发展过程,随着汽车电子的网络化,CAN总线技术在汽车领域得到了越来越广泛的应用,因此,CAN总线、嵌入式就成为了汽车仪表未来发展的必然趋势。汽车仪表的基本结构和功能汽车上较常用的有四种指示仪表,即车速里程表、发动机水温表、发动机转速表、燃油表等。分别显示汽车行驶速度、单里程和总里程数、发动机冷却液温度、汽车行驶时发动机旋转速度及汽车
frozenviolet 汽车电子
地址和时序问题
用一个单片机stc89le52rc的p0口和cpld的8个i/o口相连,在寻址的时候为什么基地址从0xff00开始呢,cpld和单片机之间的数据传输是不是不虚言考虑时序啊?地址和时序问题1.可以肯定的说单片机与CPLD数据传递需要考虑时序。至于为啥P0寻址地址从0XFF00开始,这个与程序规划有关,程序规划的段地址为0XFF00,则访问P0可需要从0XFF00开始。2.CPLD访问单片机P0口的数据或传递数据给单片机P0,我想单片机的以下信号是需要给CPLD的,P0口8位
eeleader-mcu FPGA/CPLD
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved