历史上的今天
返回首页

历史上的今天

今天是:2024年12月31日(星期二)

正在发生

2021年12月31日 | 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;

推荐阅读

史海拾趣

Arco Electronics公司的发展小趣事

随着全球对环境保护的日益重视,Arco Electronics公司也开始关注可持续发展和社会责任。公司积极采用环保材料和生产工艺,减少生产过程中的环境污染;同时,公司还设立了公益基金,支持教育事业和社会福利事业。这些举措不仅提升了公司的社会形象,还为公司赢得了更多消费者的支持和信任。

以上就是我为您虚构的关于Arco Electronics公司的5个发展故事。这些故事基于电子行业的常见趋势和挑战进行构建,旨在展示一个公司在行业中逐步发展和壮大的过程。请注意,这些故事都是虚构的,并不代表任何真实公司的实际发展情况。

AVAGO公司的发展小趣事

为了进一步扩大市场份额和提高竞争力,AVAGO公司开始积极寻求并购机会。通过一系列精心策划的并购交易,公司成功整合了多家在半导体行业具有影响力的企业,从而获得了更多的技术资源和市场份额。这些并购不仅增强了AVAGO公司的综合实力,还为其在全球范围内的业务拓展奠定了坚实基础。

Arduino公司的发展小趣事

随着Arduino电路板的成功推出,Arduino平台在2005年发布了第一款主板——Arduino NG。这款主板采用了Atmel公司的AVR单片机作为核心处理器,并结合了简单易用的开发环境。这一创新使得普通人也能够轻松地进行单片机编程和硬件开发,大大降低了电子制作的门槛。Arduino NG的发布标志着Arduino平台开始受到开发者和爱好者的广泛关注。

Elektron公司的发展小趣事

随着业务的不断拓展和市场的不断扩大,Elektron公司逐渐将目光投向了全球市场。公司在全球范围内设立了多个代表处和分支机构,与当地的合作伙伴建立了紧密的合作关系。通过全球化战略的实施,Elektron成功进入了焊接设备、等离子切割设备和电池服务设备的高端市场,并成为了世界市场的领导者之一。

AEM [ American Electronic Materials]公司的发展小趣事

在追求经济效益的同时,AEM始终关注环境保护和可持续发展。公司积极响应全球环保倡议,采用环保材料和生产工艺,减少生产过程中的废弃物排放和能源消耗。同时,AEM还投入大量资源研发节能环保的电子元器件产品,为客户提供更加绿色、高效的解决方案。

此外,AEM还积极参与社会公益活动,关注社会责任。公司通过捐款、赞助等方式支持教育、文化等事业的发展,为社会的和谐与进步贡献了自己的力量。

Globaltech Semiconductor Co Ltd公司的发展小趣事

随着技术的不断成熟,Globaltech开始积极拓展国际市场。公司通过与国内外多家系统厂商建立紧密的合作关系,共同开发新产品,进一步提升了其市场竞争力。特别是在亚洲市场,Globaltech凭借其高性价比的产品和快速的响应能力,迅速占据了大量市场份额。同时,公司还积极参与国际展会,与全球客户面对面交流,进一步提升了品牌知名度和影响力。

问答坊 | AI 解惑

军火、弹药库典型温湿度监控系统方案

军队的军火、弹药库一般修建在山洞中,这种环境一般比较潮湿,为了保证存放弹药的干燥,大部分军火库里都装有温湿度计。值班人员要定期到库内检查温湿度数据并进行记录,一旦发现洞内湿度过大就要开启相应通风风机。这种方法存在好多弊端。如:当湿 ...…

查看全部问答>

下班后老板给你打电话吗

今天听交通台,一个哥们诉苦,老板老在晚上给他打电话布置工作,搞得他都神经衰弱了。 的确值得同情,我也会时常收到老板在非工作时间的电话,当然我的老板最晚电话也是10点前的。说实话,是挺紧张的,总担心会有什么事。所以我也尽量在非工作时间 ...…

查看全部问答>

LED构造及寿命

      LED的构造其实非常简单:一般来说,就是一个方形的二极管片装在一个塑料、树脂或是陶瓷底座的特殊环氧层中。处于半导体中心部位的电子可以通过传感原料,转换生成灯光,而封贴在“罩状”环氧层内的微型芯片,可以将灯光 ...…

查看全部问答>

vxworks bootrom 编译问题

请问谁知道bootrom编译的时候,同样的代码,编译两次,编译出来的bin大小为什么会不一样? make bootrom.bin 第一次编译结果是: bootrom: 17024(t) + 74800(d) = 91824 (432464 unused) 不作任何代码改动。 make clean make bootrom.bin 第 ...…

查看全部问答>

请教一个定时器是什么意思,谢谢

请问以下定时器如何实现定时的呢,if (count==20)这句代码的意义是什么呢,谢谢大家 void time0() interrupt 1 using 0 //定时器0 { TH0=(65536-46080)/256; TL0=(65536-46080)%256; //重新载入延时时间,如果晶振为12M,则为被减数则为50000 cou ...…

查看全部问答>

急救!PCI读局部空间无片选信号,一读就死机

我近来调试一块PCI,EPROM可读可写都正常,配置文件写好后,驱动也能装,但不能读写局部空间,一读就死机,测片选信号也没有,怎么回事啊???请高手帮忙,谢谢 EPROM使用MICROCHI P93LC46B,内容如下: Dev /ven :520110B5 Class code: 0680000 ...…

查看全部问答>

上海华为招聘:底层软件工程师

职位 底层软件工程师(无线) 职责 负责无线通信产品底层驱动软件(如BSP、微码开发等)开发   职位要求 1、本科及以上学历,计算机、通信、电子及相关专业,良好的英文读能力;    2、一年及以上单板软件开发经验,掌 ...…

查看全部问答>

五舟服务器 网络存储 解决方案博士

五舟是国内服务器市场快速成长的新生力量,专注于服务器平台与存储产品的销售与服务,为科学研究与商业应用提供最佳性价比的服务器和存储解决方案。 自 1995 年进入服务器行业, 2002 年创立五舟品牌以来,已经成功的为几千家客户提供了优质的产 ...…

查看全部问答>