[标准库]STM32F103R8T6 DMA的初始化以及利用DMA+UART1发送数据

前言

这篇的博客实现的功能其实跟上个月那篇HAL库实现串口收发实现的是大同小异的功能,在使用HAL库的时候,因为不习惯HAL库高度的封装以致于需要花大量的精力去熟悉HAL库的使用方法,于是转回标准库学习。
当时是因为HAL库默认开启DMA传输的所有中断,也就是传输过半、传输完成、传输错误这三个中断,在使用CubeMx初始化之后默认是全部开启,而且苦于在CubeMx里面找不到设置的地方,所以我终止了HAL库学习。
在标准库中,需要DMA中断根据哪种情况才进入中断是很容易来选择和配置的。
就是通过库里面的这个函数:

/**
  * @brief  Enables or disables the specified DMAy Channelx interrupts.
  * @param  DMAy_Channelx: where y can be 1 or 2 to select the DMA and 
  *   x can be 1 to 7 for DMA1 and 1 to 5 for DMA2 to select the DMA Channel.
  * @param  DMA_IT: specifies the DMA interrupts sources to be enabled
  *   or disabled. 
  *   This parameter can be any combination of the following values:
  *     @arg DMA_IT_TC:  Transfer complete interrupt mask
  *     @arg DMA_IT_HT:  Half transfer interrupt mask
  *     @arg DMA_IT_TE:  Transfer error interrupt mask
  * @param  NewState: new state of the specified DMA interrupts.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
  assert_param(IS_DMA_CONFIG_IT(DMA_IT));
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    /* Enable the selected DMA interrupts */
    DMAy_Channelx->CCR |= DMA_IT;
  }
  else
  {
    /* Disable the selected DMA interrupts */
    DMAy_Channelx->CCR &= ~DMA_IT;
  }
}

根据项目的需要,选择相应的标志位就可以打开相应的中断了。

下面详细写一下如何初始化DMA并开启DMA把数据搬运到串口的发送寄存器,并通过串口把数据发送到上位机。

bsp_dma.h内容:

#ifndef __BSP_DMA_H
#define __BSP_DMA_H

#include "stm32f10x.h"

void USART1_DMA_Config(void);

#define USART1_DR_ADDR  (USART1_BASE+0x04)

#endif

就是声明了一下初始化函数和宏定义了串口1发送寄存器(基地址偏移0x04)的地址。

bsp_dma.c内容:

#include "bsp_dma.h"

uint32_t send_array[256];												//新建一个数组用来放需要发送的内容

void USART1_DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStruct;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);                   //开外设时钟    DMA跟USART、GPIO这种外设不一样,并不是挂载在APB1或者APB2时钟线上的,而是挂载在总线矩阵上的

    DMA_InitStruct.DMA_PeripheralBaseAddr = USART1_DR_ADDR;             //目标寄存器地址 串口1的发送寄存器
    DMA_InitStruct.DMA_MemoryBaseAddr     = (uint32_t)send_array;       //从内存取数据  内存的地址  (数组的名字就是数组的第一位地址)
    DMA_InitStruct.DMA_DIR                = DMA_DIR_PeripheralDST;      //DMA_DIR_PeripheralDST:外设是作为搬运的目标位置  DMA_DIR_PeripheralSRC:外设是作为搬运的源位置
    DMA_InitStruct.DMA_BufferSize         = 256;                      	//搬运65535个字节  这个DMA_BufferSize最大为65536
    DMA_InitStruct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;  //外设地址不递增,因为串口发送寄存器位置是固定的
    DMA_InitStruct.DMA_MemoryInc          = DMA_MemoryInc_Enable;       //内存地址要递增,因为发送的是数组的内容,发完当前内容之后,要发下一位的内容了
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据大小是1word即32位
    DMA_InitStruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_Word;    //内存数据大小是1word
    DMA_InitStruct.DMA_Mode               = DMA_Mode_Normal;            //是否循环发送,即传完65565个内存数据之后又把指针重新指向要发送的内存地址的第一位
    DMA_InitStruct.DMA_Priority           = DMA_Priority_High;          //优先级高,只有一个DMA通道的情况下,优先级配置没什么意义
    DMA_InitStruct.DMA_M2M                = DMA_M2M_Disable;            //DMA传输数据从内存到内存模式 不使能

    DMA_Init(DMA1_Channel4,&DMA_InitStruct);                            //使用DMA1的通道4 因为这条通道就是用来搬运数据到串口1的TX

    DMA_Cmd(DMA1_Channel4,ENABLE);
}

上面注释都比较齐全,不作多解释了,不过这里我遇到两个坑。
第一个:在赋成员值的时候,从内存取数据,内存的地址,也就是我定义的那个数组,在赋值的时候必须强制转换类型为32位的数据类型,尽管我初始化的时候已经定义好了也需要强转,搞不清楚为什么。
第二个:因为我的串口是一次只能发送8bit数据,也就是1个byte,但是我在定义的时候定义的是32位的数组,如果在DMA_MemoryDataSize这个地方填的是DMA_MemoryDataSize_Byte的话,DMA在搬运数组的时候,每次只会搬走8bit数据,但是数组的每一位都是32bit的数据,所以搬一位数据就需要DMA搬4次,那么在上位机中显示出来的内容,像我这个程序显示的就是00 00 00 00 00 00 00 01 等等……我后来改为DMA_MemoryDataSize_Word之后,上位机收到的内容就是00 01 02 03……功能就属于是正常了。原理就是DMA一次性搬一个word(32位)数据过去发送寄存器,发送寄存器一次性收32位数据,但是因为发送寄存器的大小限制,会只保留低八位,所以功能就正确了。

关于第二个坑,另一种简单的解决办法,定义数组的时候还是定义一个八位的 uint8_t send_array[255]; 在给结构体成员赋值的时候,只需要加一个强制转换,初始化的时候就没问题了,在填DMA_PeripheralDataSize 和 DMA_MemoryDataSize 这两个成员的值的时候,填入DMA_PeripheralDataSize_Byte 和 DMA_MemoryDataSize_Byte,那么DMA搬数据还是一次搬8位,但是数组成员现在也是uint8_t类型的,就不会产生上面的那种问题。

在调用初始化函数初始化好了DMA1通道4之后,再调用DMA_Cmd函数把DMA1通道4的DMA打开。
这里使用的是DMA1通道4,原因是串口1的tx就是使用DMA1通道4来传输数据的,如果用其他的就无法把数据搬运到串口1的发送寄存器。同理,串口1的接收也有专门的DMA通道负责把接受寄存器的内容搬运到用户设定的地方,就好比每个装卸口(寄存器)都有一个专门的搬运工(DMA通道),这些搬运工都是同一个运输公司(DMA1或者DMA2)的,某些公司(即外设)规定好了公司的货车需要停到哪个装卸口,所以搬运工是固定的,如果没有叫对搬运工(开错DMA通道),那么数据是没法搬运到指定位置的。

当然了这种规定只针对外设到内存和内存到外设这两种数据流方向,如果是内存到内容的数据搬运,任意一个DMA的任意一个DMA通道都可以搬数据

在写好了初始化DMA的函数之后,可以在主函数中调用他:

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_rcc.h"
#include "bsp_usart.h"
#include "bsp_dma.h"

uint16_t i=0;
// uint8_t	 j=0;

uint16_t data =0x02;
extern uint8_t	send_array[256];

int main(void)
{
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	
	rcc_systempclock_init( RCC_PLLMul_9 );
	
	led_gpio_init();

	for(i=0;i<256;i++)
	{
		send_array[i]=i;
	}

	GPIO_ResetBits(GPIOA,GPIO_Pin_8);
	USART1_Config();
	USART1_DMA_Config();

	USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);				//开启串口1的发送DMA

	while (DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET)			//等待DMA搬运完数据,搬运完不等于发送完
	{
		;
	}
	GPIO_SetBits(GPIOA,GPIO_Pin_8);

	while(1)
	{

	}
}

在主函数中,首先我按照0-255的值把数组的每一位都定义好了初值,然后初始化串口,初始化串口DMA,并开启DMA1的通道4,还需要再调用一个函数USART_DMACmd 使能串口的DMA接口(注释是这么写的),从这一句命令开始DMA就开始搬运数据了,搬运完之后会把刚刚初始化的时候点亮的红灯给熄灭掉,过程非常快,串口的发送寄存器在收到数据之后就会发送到移位寄存器,然后通过编码器把数据发送出去。

通过DMA做这个事情的好处就是,CPU不再需要参与数据的搬运,因为如果不使用DMA,我们如果要发送数据,CPU是需要先把需要传输的数据从ROM里面拿出来放到内部的一些R1 R2之类的寄存器,然后再把R1 R2这种寄存器的值赋值到外设的发送寄存器去,整个过程CPU是干不了其他事情的。使用了DMA之后,CPU可以去处理其他事情,比如点灯、频谱的FFT计算之类的事情。起码不会阻塞在串口这里。

留个坑在这里,以后把DMA的三种中断实现并验证一下, 再把最后这点内容补充一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值