单片机向上位机发送数据有两种方式:

(1)使用CPU发送:包括使用USART_SendData(DEBUG_USARTx, (uint8_t) ch)函数、printf()函数、中断,但使用CPU会占用CPU资源,负荷大,一般不使用CPU发送

(2)使用DMA直接存储器发送数据,不占用CPU资源,让CPU去做其他的事情,而且使用DMA发送数据速度比较快,实际项目中使用这个。

数据传输方向:

dma qos优先级_串口

P->M:外设到内存(RAM)

M->P:内存到外设(RAM)

M->M:内存到内存,如果是单片机,就是Flash到内存(RAM)

dma qos优先级_引脚_02

dma qos优先级_串口_03

dma qos优先级_#define_04

dma qos优先级_引脚_05

(1)DMA1永远比DMA2的优先级高

(2)如果是同一个DMA,则通道序号小的优先级高,比如DMA1的通道2比DMA1的通道4优先级高

 

dma qos优先级_dma qos优先级_06

dma qos优先级_引脚_07

dma qos优先级_引脚_08

dma qos优先级_#define_09

实验:
串口的DMA实验,证明使用DMA进行串口通信的同时,不占用CPU资源,即:单片机不断给上位机发送数据的同时,让LED灯不断地闪烁。
数据传输方向:M->P,即内存到外设

(1)工程结构:

dma qos优先级_引脚_10

代码:

(1)bsp_usart_dma.h

#ifndef __BSP_USART_DMA_H__
#define __BSP_USART_DMA_H__

#include "stm32f10x.h"
#include "stdio.h"

// ----------------------- 串口1-USART1
// 使用哪个串口(串口1..5)
#define  DEBUG_USARTx                   USART1					
// APB2串口的同步时钟
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1	
// APB2系统时钟(因为串口USART1是挂载到APB2总线上的,所以要打开APB2总线的时钟)
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd	
// 串口通信的波特率
#define  DEBUG_USART_BAUDRATE           19200


// ----------------------- USART GPIO 引脚宏定义
// GPIO引脚
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)	
// APB2系统时钟(因为串口USART1是挂载到APB2总线上的,所以要打开APB2总线的时钟)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd	

// GPIO引脚,发送接PA9,接收接PA10   
#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

#define  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandler

/* --------------------------- USART的DMA通信宏定义 ------------------------- */
// 串口对应的DMA请求通道:要使用USART1,查资料,需要配置到DMA1的第4通道
#define  USART1_TX_DMA1_CHANNEL4     	DMA1_Channel4
// 直接存储器1
#define  RCC_AHBPeriph_DMA_1			RCC_AHBPeriph_DMA1
// 外设寄存器地址:查资料,数据寄存器(USART_DR)地址偏移是:0x04
#define  USART1_DR_ADDRESS				(USART1_BASE+0x04)
// 一次发送的数据量:5000个字节,小于2^16=65535都可以
#define  SENDBUFF_SIZE            		5000
/* --------------------------- USART的DMA通信宏定义 ------------------------- */

/* 串口调试配置函数:配置串口的相关参数,使能串口 */
void DEBUG_USART_Config(void);

/* 发送一个字节 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch);

/* 发送字符串 */
void Usart_SendString(USART_TypeDef* pUSARTx, char* str);

/* 串口的DMA通信配置函数 */
void USARTx_DMA_Config(void);

#endif		/* __BSP_USART_DMA_H__ */

(2)bsp_usart_dma.c

#include "./usart_dma/bsp_usart_dma.h"

/* 存放数据的数组 */
uint8_t SendBuff[SENDBUFF_SIZE];

/* 串口中断配置函数 */
static void NVIC_Configuration(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
  
	/* 嵌套向量中断控制器组选择 */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
	/* 配置USART为中断源 */
	NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
	/* 抢断优先级*/
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	/* 子优先级 */
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	/* 使能中断 */
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	
	/* 初始化配置NVIC */
	NVIC_Init(&NVIC_InitStructure);
}

/* 串口调试配置函数:配置串口的相关参数,使能串口 */
void DEBUG_USART_Config(void)
{
	/* 结构体变量声明 */
	GPIO_InitTypeDef GPIO_InitStructure;		// GPIO
	USART_InitTypeDef USART_InitStructure;		// USART
	
	/* ------------ 第一步:初始化GPIO */
	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_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);
	
	/* ------------ 第二步:配置串口的初始化结构体 */
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
	/* 配置串口的工作参数 */
	// 波特率
	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);
	
	/* -------------------------------------------------------- */
	// 串口中断优先级配置
	NVIC_Configuration();	
	
	// 使能串口接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
	/* -------------------------------------------------------- */
	
	/* ------------ 第三步:使能串口 */
	USART_Cmd(DEBUG_USARTx, ENABLE);
}

/* 发送一个字节 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx, ch);
		
	/* 等待发送数据寄存器为空 */
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

/* 发送字符串 */
void Usart_SendString(USART_TypeDef* pUSARTx, char* str)
{
	unsigned int k=0;
	do 
	{
		Usart_SendByte(pUSARTx, *(str + k));
		k++;
	} while(*(str + k)!='\0');
  
	/* 等待发送完成 */
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC) == RESET);
}

/* 重定向c库函数printf到串口,重定向后可使用printf函数 */
int fputc(int ch, FILE *f)
{
	/* 发送一个字节数据到串口 */
	USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
	/* 等待发送完毕 */
	while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
	return (ch);
}

/* 重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数 */
int fgetc(FILE *f)
{
	/* 等待串口输入数据 */
	while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

	return (int)USART_ReceiveData(DEBUG_USARTx);
}

/* 串口的DMA通信配置函数 */
void USARTx_DMA_Config(void)
{
	// 结构体
	DMA_InitTypeDef DMA_InitTStructure;
	
	// 打开时钟,DMA1和DMA2都挂在AHB总线上,所以打开AHB的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA_1, ENABLE);
	
	/* 结构体实例化 */
	// 设置DMA源地址:串口数据寄存器地址
	DMA_InitTStructure.DMA_PeripheralBaseAddr = USART1_DR_ADDRESS;
	// 内存地址(要传输的变量的指针):这里将数据存放到数组里
	DMA_InitTStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff;
	// 数据传输方向:从内存到外设
	DMA_InitTStructure.DMA_DIR = DMA_DIR_PeripheralDST;
	// 数据传输大小
	DMA_InitTStructure.DMA_BufferSize = SENDBUFF_SIZE;
	// 外设地址不增:因为只有一个外设,所以不需要自增
	DMA_InitTStructure.DMA_PeripheralInc = DMA_MemoryInc_Disable;
	// 内存地址自增:因为用数组在内存中存储数据,所以需要自增数组的指针
	DMA_InitTStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	// 外设数据单位
	DMA_InitTStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	// 内存数据单位
	DMA_InitTStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	// DMA模式,一次或者循环模式,DMA_Mode_Normal表示只发送一次数据,完了要等待下一次触发
	DMA_InitTStructure.DMA_Mode = DMA_Mode_Normal;
	// DMA_InitTStructure.DMA_Mode = DMA_Mode_Circular;
	// 通道优先级:中
	DMA_InitTStructure.DMA_Priority = DMA_Priority_Medium;
	// 禁止内存到内存的传输
	DMA_InitTStructure.DMA_M2M = DMA_M2M_Disable;	
	
	// 结构体初始化:配置DMA通道
	DMA_Init(USART1_TX_DMA1_CHANNEL4, &DMA_InitTStructure);
	
	// 使能DMA
	DMA_Cmd(USART1_TX_DMA1_CHANNEL4, ENABLE);
}

(3)bsp_led.h

/* 和LED功能模块相关的程序 */
 
#ifndef __BSP_LED_H__
#define __BSP_LED_H__
 
#include "stm32f10x.h"
 
/*宏定义*/
#define GPIO_CLK_D4         RCC_APB2Periph_GPIOC        // 时钟
#define GPIO_PORT_D4        GPIOC                       // C端口
#define GPIO_PIN_D4         GPIO_Pin_2                  // PC2引脚
 
#define GPIO_CLK_D5         RCC_APB2Periph_GPIOC        // 时钟
#define GPIO_PORT_D5        GPIOC                       // C端口
#define GPIO_PIN_D5         GPIO_Pin_3                  // PC2引脚
 
/*参数宏定义*/
/*
digitalTOGGLE(p,i)是参数宏定义,p表示LED的端口号,ODR是数据输出寄存器,
查stm32f10x的官方中文手册的第8.2章的ODR寄存器,要点亮LED,根据原理图,要输出低电平0,
C语言中,^表示异或,即a^b表示a和b不同时输出为1,相同时输出为0,比如0^1=1,1^1=0,0^0=0,
这里为什么操作ODR,p是什么?查看stm32f10x.h文件,搜索GPIO_TypeDef就会明白,
i是LED的引脚对应的位电平,经过digitalTOGGLE(p,i) {p->ODR ^= i;}之后,
第一次p为0,i一直为1,第一次异或结果输出1,第二次输出0,第三次输出1,这样间断输出010101,灯不断亮灭
*/
// LED灯的状态翻转
#define digitalTOGGLE(p,i)		{p->ODR ^= i;}	
// 输出高电平(让LED端口置1,BSRR寄存器用于位置1)
#define digitalHi(p,i)			{p->BSRR = i;}
// 输出低电平(让LED端口置0,BRR寄存器用于位清除)
#define digitalLo(p,i)			{p->BRR = i;}

// LED状态翻转
#define LED1_TOGGLE				digitalTOGGLE(GPIO_PORT_D4,GPIO_PIN_D4)
//#define LED2_TOGGLE         	digitalTOGGLE(GPIO_PORT_D5,GPIO_PIN_D5)
// D4这个LED亮
#define D4_LED_ON				digitalLo(GPIO_PORT_D4,GPIO_PIN_D4)
// D4这个LED灭
#define D4_LED_OFF				digitalHi(GPIO_PORT_D4,GPIO_PIN_D4)
// D5这个LED亮
#define D5_LED_ON				digitalLo(GPIO_PORT_D5,GPIO_PIN_D5)
// D5这个LED灭
#define D5_LED_OFF				digitalHi(GPIO_PORT_D5,GPIO_PIN_D5)

 
/*配置GPIO*/
void LED_GPIO_Config(void);
 
#endif  /*__BSP_LED_H__*/

(4)bsp_led.c

/* 和LED功能模块相关的程序头文件 */

/*绝对路径,也可在Options for target中设置头文件*/
#include "./led/bsp_led.h"  
 
/*GPIO初始化*/
void LED_GPIO_Config(void)
{
    /*外设结构体*/
    GPIO_InitTypeDef GPIO_InitStruct_D4, GPIO_InitStruct_D5;   
     
    /*第一步:打开外设的时钟,看stm32f10x_rcc.c这个文件的RCC_APB2PeriphClockCmd函数介绍*/
    RCC_APB2PeriphClockCmd(GPIO_CLK_D4, ENABLE);
     
    /*第二步:配置外设的初始化结构体*/
    GPIO_InitStruct_D4.GPIO_Pin = GPIO_PIN_D4;          // PC2的那盏LED灯(D4)的引脚
    GPIO_InitStruct_D4.GPIO_Mode = GPIO_Mode_Out_PP;    // 推挽输出模式
    GPIO_InitStruct_D4.GPIO_Speed = GPIO_Speed_10MHz;   // 引脚速率
     
    GPIO_InitStruct_D5.GPIO_Pin = GPIO_PIN_D5;          // PC3的那盏LED灯(D5)的引脚
    GPIO_InitStruct_D5.GPIO_Mode = GPIO_Mode_Out_PP;    // 推挽输出模式
    GPIO_InitStruct_D5.GPIO_Speed = GPIO_Speed_10MHz;   // 引脚速率
     
    /*第三步:调用外设初始化函数,把配置好的结构体成员写到寄存器里面*/
    GPIO_Init(GPIO_PORT_D4, &GPIO_InitStruct_D4);
    GPIO_Init(GPIO_PORT_D5, &GPIO_InitStruct_D5);
	
	/* 默认情况下D4和D5是不亮的 */
	D4_LED_OFF;
	D5_LED_OFF;
}

(5)main.c

/* 
实验:
串口的DMA实验,证明使用DMA进行串口通信的同时,不占用CPU资源,
即:单片机不断给上位机发送数据的同时,让LED灯不断地闪烁。
数据传输方向:M->P,即内存到外设
*/

#include "stm32f10x.h"
#include "./usart_dma/bsp_usart_dma.h"
#include "./led/bsp_led.h" 

/* 定义数组,extern关键字修饰变量,表示从其他的文件中引用,在bsp_usart_dma.c中定义 */
extern uint8_t SendBuff[SENDBUFF_SIZE];

// 延迟函数
void delay(unsigned int i)
{
    for(; i!=0; i--);
}

int main(void)
{
	uint32_t i;
	
	/* LED初始化 */	
	LED_GPIO_Config();
	
	/* USART串口通信初始化 */
	DEBUG_USART_Config();
	
	/* 串口的DMA通信配置函数 */
	USARTx_DMA_Config();
	
	/* 填充将要发送的数据,假如发送字符P */
	for(i=0; i<SENDBUFF_SIZE; i++)
	{
		SendBuff[i] = 'P';
	}
	
	/* USART1向DMA1发出请求,即:USART1开始向DMA1发送数据 */
	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
	
	/* 让LED不断闪烁 */
	while(1)
	{
		LED1_TOGGLE;
		delay(0XFFFFF);
	}
}

实验现象:

(1)板子上的D4的LED不断闪烁

图(略)

(2)按下板子上的RESET按钮,串口调试助手会接收到板子发来的5000个字符‘P’(每按一次就发5000个字符P)

dma qos优先级_dma qos优先级_11

以上实验结果说明,使用USART串口向DMA直接存储器发出请求,向上位机发送数据不占用CPU资源(因为这时候CPU负责LED灯的不断闪烁)