写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节包括前几节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火B站账号:野火官方B站账号链接。
参考资料
《STM32F10x芯片参考手册-中文版》、《STM32F10x芯片数据手册-中文版》、《STM32F10x Cortex-M3编程手册-英文版》、《CM3权威指南-中文版》、《野火开发板相关资料》等;
本文中理论知识的梳理主要参考《野火开发板相关资料》,当然芯片参考手册中也有关于USART的介绍,不过理论性较强,在本文中可能不会大量引用,主要引用野火的资料;
本文仅为个人笔记,不严谨处还望指正,另外本文仅为初稿,细节问题后续修改。
学习目标
1.简单了解DMA,并完成从存储器到存储器、从外设到存储器和从存储器到外设实验。
一、DMA简介及功能框图
1.DMA简介
DMA(Direct Memory Access)—直接存储器存取,属于单片机的一个外设,它可以用来管理存储器到存储器(M→M)、外设到存储器(P→M)和存储器到外设(M→P)中间的数据传输,传输数据的过程中不需要CPU干预,即可以在DMA传输数据的同时,利用CPU进行其他的操作(多线程)。
DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,,DMA2有5个通道,每个通道专门来管理来自于一个或多个外设对存储器访问的请求,另外还有一个仲裁器来协调各个DMA请求的优先权。要注意的是DMA2只存在于大容量的单片机中,如STM32F103单片机。
2.DMA主要特性
该部分内容来自《STM32F10x中文参考手册》;
● 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过 软件来配置。
● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、 中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目 标地址必须按数据传输宽度对齐。
● 支持循环的缓冲器管理
● 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志 逻辑或成为一个单独的中断请求。
● 存储器和存储器间的传输
● 外设和存储器、存储器和外设之间的传输
● 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。
● 可编程的数据传输数目:最大为65535
3.DMA框图
图11-1:DMA框图
(1)DMA请求和通道
如果外设想通过DMA来传输数据,必须先向DMA控制器发送DMA请求,DMA收到请求信号后,控制器会给外设一个应答信号,当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。
DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置。
虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
表11-1和表11-2是DMA请求映像表:
表11-1:DMA1请求映像表
外设 | 通道1 | 通道2 | 通道3 | 通道4 | 通道5 | 通道6 | 通道7 |
---|---|---|---|---|---|---|---|
ADC1 | ADC1 | ||||||
SPI/I2S | SPI1_RX | SPI1_TX | SPI/I2S2_RX | SPI/I2S2_TX | |||
USART | USART3_TX | USART3_RX | USART1_TX | USART1_RX | USART2_RX | USART2_TX | |
I2C | I2C2_TX | I2C2_RX | I2C1_TX | I2C1_RX | |||
TIM1 | TIM1_CH1 | TIM1_CH2 | TIM1_TX4 TIM1_TRIG TIM1_COM | TIM1_UP | TIM1_CH3 | ||
TIM2 | TIM2_CH3 | TIM2_UP | TIM2_CH1 | TIM2_CH2 TIM2_CH4 | |||
TIM3 | TIM3_CH3 | TIM3_CH4 TIM3_UP | TIM3_CH1 TIM3_TRIG | ||||
TIM4 | TIM4_CH1 | TIM4_CH2 | TIM4_CH3 | TIM4_UP |
表11-2:DMA2请求映像表
外设 | 通道1 | 通道2 | 通道3 | 通道4 | 通道5 |
---|---|---|---|---|---|
ADC3 | ADC3 | ||||
SPI1/I2S3 | SPI/I2S3_RX | SPI/I2S3_TX | |||
UASRT4 | USART4_RX | USART4_TX | |||
SDIO | SDIO | ||||
TIM5 | TIM5_CH4 TIM5_TRIG | TIM5_CH3 TIM5_UP | TIM5_CH2 | TIM5_CH1 | |
TIM6/DAC通道1 | TIM6_UP/ DAC通道1 | ||||
TIM7/DAC通道2 | TIM7_UP/ DAC通道2 | ||||
TIM8 | TIM8_CH3 TIM8_UP | TIM8_CH4 TIM8_TRIG TIM8_COM | TIM8_CH1 | TIM8_CH2 |
注:ADC3、SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。
(2)仲裁器
仲裁器根据通道请求的优先级来启动外设/存储器的访问,优先级的管理分2个阶段:
●软件阶段:每个通道的优先级可以在DMA_CCRx寄存器的PL[1:0]位设置,有4个等级:
—最高优先级
—高优先级
—中等优先级
—低优先级
●硬件阶段:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道优先级高,如,通道2的优先级高于通道4。同时,在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
二、DMA数据配置(初始化结构体注释)
这部分内容将结合以下程序进行学习:
//来源:#include "stm32f10x_dma.h"
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; //外设地址
uint32_t DMA_MemoryBaseAddr; //存储器地址
uint32_t DMA_DIR; //传输方向
uint32_t DMA_BufferSize; //传输数目
uint32_t DMA_PeripheralInc; //外设地址增量模式
uint32_t DMA_MemoryInc; //存储器地址增量模式
uint32_t DMA_PeripheralDataSize; //外设数据宽度
uint32_t DMA_MemoryDataSize; //外设数据宽度
uint32_t DMA_Mode; //模式选择
uint32_t DMA_Priority; //通道优先级
uint32_t DMA_M2M; //存储器到存储器模式
}DMA_InitTypeDef;
1.数据从哪里来到哪里去
uint32_t DMA_PeripheralBaseAddr; //外设地址
uint32_t DMA_MemoryBaseAddr; //存储器地址
uint32_t DMA_DIR; //传输方向
//涉及到的寄存器
//外设地址:DMA_CPAR
//存储器地址:DMA_CMAR
//传输方向:DMA_CCR:DIR
DMA的数据传输方向有三个:从存储器到存储器、从外设到存储器、从存储器到外设。具体的方向可通过DMA_CCR寄存器的位4:DIR配置:0表示从外设到存储器,1表示从存储器到外设。这里涉及到的外设地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。
(1)存储器到外设
存储器到外设,以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储将要通过串口发送到电脑的数据)的地址,方向我们设置外设为目标地址。
(2)外设到存储器
外设到存储器,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址,DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。
(3)存储器到存储器
存储器到存储器,以内部FLASH向内部SRAM复制数据为例。DMA外设寄存器的地址对应的就是内部FLASH(我们这里需要将内部FLASH当作一个外设来看)的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M模式。
2.数据要传多少,传的单位是什么
uint32_t DMA_BufferSize; //传输数目
uint32_t DMA_PeripheralInc; //外设地址增量模式
uint32_t DMA_MemoryInc; //存储器地址增量模式
uint32_t DMA_PeripheralDataSize; //外设数据宽度
uint32_t DMA_MemoryDataSize; //外设数据宽度
//涉及到的寄存器
//传输数目:DMA_CNDTR
//外设地址是否递增:DMA_CCRx:PINC
//存储器地址事都递增:DMA_CCRx:MINC
//外设数据宽度:DMA_CCRx:PSIZE
//存储器数据宽度:DMA_CCRx:MSIZE
以串口向电脑发送数据为例,我们一次性可以向电脑发送数据的数量,由DMA_CNDTR寄存器配置,该寄存器是32为寄存器,一次最多传输65535个数据。
要想传输数据正确,源地址和目标地址的数据宽度必须一致,即串口数据寄存器是8位的,所以我们定义的要发送的数据也必须是8位的。外设的数据宽度由DMA_CCR的PSIZE[1:0]配置,可以是8/16/32位,存储器的数据宽度由DMA_CCR的MSIZE[1:0]配置,可以是8/16/32。
数据要想有条不紊的传输,必须正确配置两边数据指针的增量模式。外设的地址指针由DMA_CCRx的PINC配置,存储器的地址指针由MINC配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地 址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
3.什么时候传输完成
uint32_t DMA_Mode; //模式选择
//模式选择:DMA_CCRx:CIRC
//传输过半、传输完成、传输出错:DMA_ISR
数据什么时候传输完成,可以通过查询标志位或通过中断的方式来鉴别。每个DMA通道在DMA传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。
传输完成还分两种模式:一次传输和循环传输。
三、程序
本次实验会用到LED灯,关于LED灯的程序会放在本文最后,另外本次实验会用到USART,USART的程序也会放在最后,程序仅供参考。
1.DMA存储器到存储器模拟实验
实验目标:实现数据在两个内存的快速拷贝,先定义一个静态的源数据,存放在内部FLASH,然后使用DMA传输把源数据拷贝到目标地址上(内部SRAM),最后对比源数据和目标地址的数据,看传输是否准确,本实验通过LED灯指示传输是否准确。
编程要点:
(1)使能DMA时钟;
(2)配置DMA数据参数;
(3)使能DMA,进行传输;
(4)等待传输完成,并对源数据和目标地址数据进行比较。
程序11-1:bsp_dma_mtm.c
#include "bsp_dma_mtm.h"
/* 定义aSRC_Const_Buffer数组作为DMA传输数据源
* const关键字将aSRC_Const_Buffer数组变量定义为常量类型
* 表示数据存储在内部的FLASH中
*/
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
/* 定义DMA传输目标存储器
* 存储在内部的SRAM中
*/
uint32_t aDST_Buffer[BUFFER_SIZE];
#if 0 //DMA需要初始化的内容
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; /*!< 外设地址 */
uint32_t DMA_MemoryBaseAddr; /*!< 存储器地址 */
uint32_t DMA_DIR; /*!< 传输方向:外设是源还是目标 */
uint32_t DMA_BufferSize; /*!< 传输数目:以数据单位指定指定通道的缓冲区大小 */
uint32_t DMA_PeripheralInc; /*!< 外设地址增量模式 */
uint32_t DMA_MemoryInc; /*!< 存储器地址增量模式 */
uint32_t DMA_PeripheralDataSize; /*!< 外设数据宽度 */
uint32_t DMA_MemoryDataSize; /*!< 存储器数据宽度 */
uint32_t DMA_Mode; /*!< 模式选择 */
uint32_t DMA_Priority; /*!< 通道优先级 */
uint32_t DMA_M2M; /*!< 存储器到存储器模式 */
}DMA_InitTypeDef;
#endif
void MTM_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStruct;
//打开DMA时钟
RCC_AHBPeriphClockCmd(MTM_DMA_CLK, ENABLE);
//设置源数据地址
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
//设置目标源地址
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
//设置传输方向,外设地址为发送方
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
//设置传输数据的大小,32位数据
DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE;
//设置外设地址增量模式,递增
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
//设置内存地址增量模式,递增
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
//设置外设地址宽度,32位
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
//设置内存地址宽度,32位
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
//设置传输模式,只发送一次
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
//设置通道优先级,设置为高
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
//设置为存储器到存储器
DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;
//配置DMA通道
DMA_Init(MTM_DMA_CHANNEL, &DMA_InitStruct);
//清除DMA传输完成标志位
DMA_ClearFlag(MTM_DMA_FLAG_TC);
//使能DMA
DMA_Cmd(MTM_DMA_CHANNEL, ENABLE);
}
uint8_t Buffercmp(const uint32_t *pBuffer,uint32_t *pBuffer1,uint16_t Bufferlength)
{
while(Bufferlength--)
{
if(*pBuffer1 != *pBuffer)//指针的比较
{
return 0;
}
pBuffer++;
pBuffer1++;
}
return 1;
}
程序11-2:bsp_dma_mtm.h
#ifndef __BSP_DMA_MTM_H
#define __BSP_DMA_MTM_H
#include "stm32f10x.h"
#define BUFFER_SIZE 32
#define MTM_DMA_CHANNEL DMA1_Channel6
#define MTM_DMA_CLK RCC_AHBPeriph_DMA1
#define MTM_DMA_FLAG_TC DMA1_FLAG_TC6
void MTM_DMA_Config(void);
uint8_t Buffercmp(const uint32_t *pBuffer,uint32_t *pBuffer1,uint16_t Bufferlength);
#endif /*__BSP_DMA_MTM_H*/
程序11-3:main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_dma_mtm.h"
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];
void Delay(uint32_t count)
{
for(;count != 0;count--);
}
int main(void)
{
uint8_t transferstatu = 0;
LED_GPIO_Config();
MTM_DMA_Config();
LED_YELLOW;
Delay(0xfffff);
while(DMA_GetFlagStatus(MTM_DMA_FLAG_TC) == RESET);//等待传输完成
transferstatu = Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE);//判断是否传输完成
if(transferstatu == 0)
{
LED_RED;
}
else
{
LED_GREEN;
}
while(1)
{
}
}
2.DMA存储器到外设模拟实验
实验目标:预设一个数组,利用DMA,通过串口将数组的数据发送到电脑,并用串口接收器接收数据。
编程要点:
(1) 配置 USART 通信功能;
(2) 设置串口 DMA 工作参数;
(3) 使能 DMA;
(4) DMA 传输同时 CPU 可以运行其他任务。
程序11-4:bsp_dma_mtp.c
#include "bsp_dma_mtp.h"
uint8_t SendBuff[SENDBUFF_SIZE];
#if 0
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; /*!< 外设地址 */
uint32_t DMA_MemoryBaseAddr; /*!< 存储器地址 */
uint32_t DMA_DIR; /*!< 传输方向:外设是源还是目标 */
uint32_t DMA_BufferSize; /*!< 传输数目:以数据单位指定指定通道的缓冲区大小 */
uint32_t DMA_PeripheralInc; /*!< 外设地址增量模式 */
uint32_t DMA_MemoryInc; /*!< 存储器地址增量模式 */
uint32_t DMA_PeripheralDataSize; /*!< 外设数据宽度 */
uint32_t DMA_MemoryDataSize; /*!< 存储器数据宽度 */
uint32_t DMA_Mode; /*!< 模式选择 */
uint32_t DMA_Priority; /*!< 通道优先级 */
uint32_t DMA_M2M; /*!< 存储器到存储器模式 */
}DMA_InitTypeDef;
#endif
void MTP_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStruct;
//打开DMA时钟
RCC_AHBPeriphClockCmd(MTP_DMA_TX_CLK, ENABLE);
//设置DMA源地址,串口数据寄存器地址
DMA_InitStruct.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
//设置内存地址
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)SendBuff;
//设置传输方向,内存发送到外设
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
//设置传输数据的大小
DMA_InitStruct.DMA_BufferSize = SENDBUFF_SIZE;
//设置外设地址增量模式,不递增
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//设置内存地址增量模式,递增
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
//设置外设地址宽度
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
//设置内存地址宽度
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
//设置传输模式,只发送一次
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
//设置通道优先级,设置为中
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
//关闭存储器到存储器
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
//配置DMA通道
DMA_Init(MTP_DMA_TX_CHANNEL, &DMA_InitStruct);
//清除DMA传输完成标志位
DMA_ClearFlag(MTP_DMA_TX_FLAG_TC);
//使能DMA
DMA_Cmd(MTP_DMA_TX_CHANNEL, ENABLE);
}
程序11-5:bsp_dma_mtp.h
#ifndef __BSP_DMA_MTP_H
#define __BSP_DMA_MTP_H
#include "stm32f10x.h"
#define SENDBUFF_SIZE 5000
#define USART_DR_ADDRESS (USART1_BASE+0X04)
#define MTP_DMA_TX_CHANNEL DMA1_Channel4
#define MTP_DMA_TX_CLK RCC_AHBPeriph_DMA1
#define MTP_DMA_TX_FLAG_TC DMA1_FLAG_TC4
void MTP_DMA_Config(void);
#endif /*__BSP_DMA_MTP_H*/
程序11-6:main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_dma_mtp.h"
extern uint8_t SendBuff[SENDBUFF_SIZE];
void Delay(uint32_t count)
{
for(;count != 0;count--);
}
int main(void)
{
uint16_t i;
LED_GPIO_Config();
USART_Config();
MTP_DMA_Config();
for(i=0;i<SENDBUFF_SIZE;i++)
{
SendBuff[i] = 'p';
}
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
while(1)
{
LED_G_TOGGLE;
Delay(0xfffff);
}
}
3.DMA外设到存储器(这部分仅作记录,不做实验)
这部分内容在AD部分会用到,这里只简单对程序做个小实验记录,后续在AD部分(如果我更新到的话)再详细学习。
程序11-7:bsp_dma_ptm.c
#include "bsp_dma_ptm.h"
uint8_t ReceiveBuff[RECEIVEBUFF_SIZE];
#if 0
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; /*!< 外设地址 */
uint32_t DMA_MemoryBaseAddr; /*!< 存储器地址 */
uint32_t DMA_DIR; /*!< 传输方向:外设是源还是目标 */
uint32_t DMA_BufferSize; /*!< 传输数目:以数据单位指定指定通道的缓冲区大小 */
uint32_t DMA_PeripheralInc; /*!< 外设地址增量模式 */
uint32_t DMA_MemoryInc; /*!< 存储器地址增量模式 */
uint32_t DMA_PeripheralDataSize; /*!< 外设数据宽度 */
uint32_t DMA_MemoryDataSize; /*!< 存储器数据宽度 */
uint32_t DMA_Mode; /*!< 模式选择 */
uint32_t DMA_Priority; /*!< 通道优先级 */
uint32_t DMA_M2M; /*!< 存储器到存储器模式 */
}DMA_InitTypeDef;
#endif
void PTM_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStruct;
//打开DMA时钟
RCC_AHBPeriphClockCmd(PTM_DMA_RX_CLK, ENABLE);
//设置DMA源地址,串口数据寄存器地址
DMA_InitStruct.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
//设置内存地址
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)ReceiveBuff;
//设置传输方向,内存发送到外设
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
//设置传输数据的大小
DMA_InitStruct.DMA_BufferSize = RECEIVEBUFF_SIZE;
//设置外设地址增量模式,不递增
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//设置内存地址增量模式,递增
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
//设置外设地址宽度
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
//设置内存地址宽度
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
//设置传输模式,只发送一次
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
//设置通道优先级,设置为中
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
//关闭存储器到存储器
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
//配置DMA通道
DMA_Init(PTM_DMA_RX_CHANNEL, &DMA_InitStruct);
//清除DMA传输完成标志位
DMA_ClearFlag(PTM_DMA_RX_FLAG_TC);
//使能DMA
DMA_Cmd(PTM_DMA_RX_CHANNEL, ENABLE);
}
程序11-8:bsp_dma_ptm.h
#ifndef __BSP_DMA_PTM_H
#define __BSP_DMA_PTM_H
#include "stm32f10x.h"
#define RECEIVEBUFF_SIZE 5000
#define USART_DR_ADDRESS (USART1_BASE+0x04)
#define PTM_DMA_RX_CHANNEL DMA1_Channel5
#define PTM_DMA_RX_CLK RCC_AHBPeriph_DMA1
#define PTM_DMA_RX_FLAG_TC DMA1_FLAG_TC5
void PTM_DMA_Config(void);
#endif /*__BSP_DMA_PTM_H*/
程序11-9:stm32f10x_it.c(在该文件中加入下面的程序)
#include "bsp_usart.h"
#include "bsp_dma_ptm.h"
#if 1
void DEBUG_USART_IRQHandler(void)
{
uint16_t t;
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_IDLE) == SET) //如果产生中断
{
DMA_Cmd(PTM_DMA_RX_CHANNEL, DISABLE); //关闭DMA传输
t = DMA_GetCurrDataCounter(PTM_DMA_RX_CHANNEL); //获取剩余的数据量
Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-t); //向电脑返回接收到的数据
DMA_SetCurrDataCounter(PTM_DMA_RX_CHANNEL, RECEIVEBUFF_SIZE); //重新设置传输的数据量
DMA_Cmd(PTM_DMA_RX_CHANNEL, ENABLE); //打开DMA传输
USART_ReceiveData(DEBUG_USARTx); //接收一次数据,不然会一直进中断
USART_ClearFlag(DEBUG_USARTx, USART_FLAG_IDLE); //清除串口空闲标志位,结束中断
}
}
#endif
程序11-10:main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_dma_ptm.h"
extern uint8_t ReceiveBuff[RECEIVEBUFF_SIZE];
void Delay(uint32_t count)
{
for(;count != 0;count--);
}
int main(void)
{
LED_GPIO_Config();
USART_Config();
PTM_DMA_Config();
printf("\r\nDMA外设到存储器模式,用电脑向开发板串口发送数据,数据会返回到电脑。\r\n");
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
while(1)
{
LED_G_TOGGLE;
Delay(0xfffff);
}
}
4.LED和USART相关程序
程序11-11:bsp_usart.c
#include "bsp_usart.h"
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
//复位USART
USART_DeInit(DEBUG_USARTx);
//打开时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK,ENABLE);
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = DEBUG_USART_IRQ;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitStruct);
//初始化TX端口GPIO,复用推挽输出
GPIO_InitStruct.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStruct);
//初始化RX端口GPIO,浮空输入
GPIO_InitStruct.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStruct);
//初始化串口
USART_InitStruct.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_Init(DEBUG_USARTx, &USART_InitStruct);
//使能空闲中断,接收中断
USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);
//使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
//发送一个字节
void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data)
{
USART_SendData(DEBUG_USARTx, data);
while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
}
//发送一个8位数组
void Usart_SendArray(USART_TypeDef* pUSARTx,uint8_t *array,uint16_t num)
{
uint8_t i;
for(i=0;i<num;i++)
{
Usart_SendByte(pUSARTx,array[i]);
}
while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TC) == 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)
{}
}
//发送一个16位数
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
uint8_t temp_h, temp_l;
/* 取出高八位 */
temp_h = (ch&0XFF00)>>8;
/* 取出低八位 */
temp_l = ch&0XFF;
/* 发送高八位 */
USART_SendData(pUSARTx,temp_h);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
/* 发送低八位 */
USART_SendData(pUSARTx,temp_l);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == 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);
}
程序11-12:bsp_usart.h
#ifndef __BSP_USART_H
#define __BSP_USART_H
#include "stm32f10x.h"
#include <stdio.h>
//USART—GPIO定义
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 //TX
#define DEBUG_USART_TX_GPIO_PORT GPIOA //TX
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 //RX
#define DEBUG_USART_RX_GPIO_PORT GPIOA //RX
#define DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOA //定义GPIO时钟
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
//USART
#define DEBUG_USARTx USART1
#define DEBUG_USART_BAUDRATE 115200 //波特率
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
//NVIC
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler
void USART_Config(void);
void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data);
void Usart_SendArray(USART_TypeDef* pUSARTx,uint8_t *array,uint16_t num);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
#endif /* __BSP_USART_H */
程序11-13:bsp_led.c
#include "bsp_led.h"
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct; //定义变量,方便赋值
RCC_APB2PeriphClockCmd(LED_GPIO_CLK,ENABLE); //打开APB2时钟,GPIO挂载在APB2
//GPIO_PIN的部分用或将用到的管脚初始化为一个十六进制数据,原本是三个十六进制
GPIO_InitStruct.GPIO_Pin = (LED_G_GPIO_PIN); //设置需要用到的管脚,LED_G_GPIO_PIN看.h文件
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //设置输出模式为推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //设置输出速率为50MHz
GPIO_Init(LED_G_GPIO_PORT, &GPIO_InitStruct); //加上&,方便取值 //初始化GPIO
//初始化红灯和蓝灯,主要是管脚和GPIO
GPIO_InitStruct.GPIO_Pin = (LED_B_GPIO_PIN);
GPIO_Init(LED_B_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = (LED_R_GPIO_PIN);
GPIO_Init(LED_R_GPIO_PORT, &GPIO_InitStruct);
//初始化上电后关闭所有灯
GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
GPIO_SetBits(LED_B_GPIO_PORT,LED_B_GPIO_PIN);
GPIO_SetBits(LED_R_GPIO_PORT,LED_R_GPIO_PIN);
}
程序11-14:bsp_led.h
#ifndef _BSP_LED_H
#define _BSP_LED_H
#include "stm32f10x.h" //要包含固件库的.h文件
#define LED_G_GPIO_PIN GPIO_Pin_0 //定义绿灯管脚号
#define LED_G_GPIO_PORT GPIOB //定义用到的GPIO
#define LED_G_GPIO_CLK RCC_APB2Periph_GPIOB //定义GPIO时钟
#define LED_B_GPIO_PIN GPIO_Pin_1 //定义蓝灯管脚号
#define LED_B_GPIO_PORT GPIOB //定义用到的GPIO
#define LED_B_GPIO_CLK RCC_APB2Periph_GPIOB //定义GPIO时钟
#define LED_R_GPIO_PIN GPIO_Pin_5 //定义红灯管脚号
#define LED_R_GPIO_PORT GPIOB //定义用到的GPIO
#define LED_R_GPIO_CLK RCC_APB2Periph_GPIOB //定义GPIO时钟
#define LED_GPIO_CLK LED_G_GPIO_CLK|LED_B_GPIO_CLK|LED_R_GPIO_CLK
//下面几个声明可以不管,直接按照main中屏蔽掉的部分写也行
//下面用到的“\”符号为续行符,其后面不能由任何东西,意为这行下面的一行跟这行是一起的,分行写看起来比较清晰
#define ON 1
#define OFF 0
#define LED(a) if(a) \
GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN); \
else \
GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
//定义控制LED灯的宏
#define digitalHi(p,i) {p->BSRR=i;}//输出为高电平
#define digitalLo(p,i) {p->BRR=i;}//输出为低电平
#define digitalToggle(p,i) {p->ODR^=i;}//输出反转
#define LED_G_ON digitalLo(LED_G_GPIO_PORT,LED_G_GPIO_PIN)
#define LED_G_OFF digitalHi(LED_G_GPIO_PORT,LED_G_GPIO_PIN)
#define LED_G_TOGGLE digitalToggle(LED_G_GPIO_PORT,LED_G_GPIO_PIN)
#define LED_B_ON digitalLo(LED_B_GPIO_PORT,LED_B_GPIO_PIN)
#define LED_B_OFF digitalHi(LED_B_GPIO_PORT,LED_B_GPIO_PIN)
#define LED_B_TOGGLE digitalToggle(LED_B_GPIO_PORT,LED_B_GPIO_PIN)
#define LED_R_ON digitalLo(LED_R_GPIO_PORT,LED_R_GPIO_PIN)
#define LED_R_OFF digitalHi(LED_R_GPIO_PORT,LED_R_GPIO_PIN)
#define LED_R_TOGGLE digitalToggle(LED_R_GPIO_PORT,LED_R_GPIO_PIN)
//绿灯
#define LED_GREEN LED_G_ON;\
LED_B_OFF;\
LED_R_OFF;
//红灯
#define LED_RED LED_G_OFF;\
LED_B_OFF;\
LED_R_ON;
//蓝灯
#define LED_BLUE LED_G_OFF;\
LED_B_ON;\
LED_R_OFF;
//黄灯
#define LED_YELLOW LED_G_ON;\
LED_B_OFF;\
LED_R_ON;
//全关
#define LED_OFF LED_G_OFF;\
LED_B_OFF;\
LED_R_OFF;
void LED_GPIO_Config(void); //.c文件中的函数声明
#endif /*_BSP_LED_H*/
以上内容还有很多不足之处,笔者会进行进一步的学习,等再有新的心得会再次更新相关内容,同时如果笔者发现上述内容有错误,也会及时进行更改,感谢观看。