STM32F4 SPI DMA

2024-03-03 01:18
文章标签 spi dma stm32f4

本文主要是介绍STM32F4 SPI DMA,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • STM32F4 SPI DMA
    • 自己整理(存储器到外设模式)
      • SPI结构体
      • SPI引脚编号
      • SPI配置
      • DMA结构体
      • DMA请求映射
      • DMA传输模式
      • SPI 发送DMA配置
      • DMA发送中断服务函数
      • SPI 接收DMA 配置
      • DMA接收中断服务函数
      • DMA请求使能
      • SPI_DMA 读写一个buf
      • SPI_DMA 发送一个buf
    • 网上内容
      • RCC
      • GPIO
      • SPI
      • DMA
      • NVIC
      • Timers
        • ISRs
      • Logic Analysis


STM32F4 SPI DMA

自己整理(存储器到外设模式)

SPI结构体

typedef struct
{uint16_t SPI_Direction; /*设置SPI 的单双向模式 */uint16_t SPI_Mode; 		/*设置SPI 的主/从机端模式 */uint16_t SPI_DataSize; 	/*设置SPI 的数据帧长度,可选8/16 位 */uint16_t SPI_CPOL; 		/*设置时钟极性CPOL,可选高/低电平*/uint16_t SPI_CPHA; 		/*设置时钟相位,可选奇/偶数边沿采样 */uint16_t SPI_NSS; 		/*设置NSS 引脚由SPI 硬件控制还是软件控制*/uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子,fpclk/分频数=fSCK */uint16_t SPI_FirstBit; 			/*设置MSB/LSB 先行 */uint16_t SPI_CRCPolynomial; 	/*设置CRC 校验的表达式 */
}SPI_InitTypeDef;

SPI引脚编号

image-20200921203037344

img image-20200922002811569

SPI配置

void SPI_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;SPI_InitTypeDef SPI_InitStructure;//1.初始化GPIO   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB,ENABLE);  /* 连接 引脚源*/GPIO_PinAFConfig(GPIOA,GPIO_PinSource15,GPIO_AF_SPI1);GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);/* 使能 SPI 时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);    		/* GPIO初始化 */GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;/* 配置SCK引脚为复用功能  */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3  ;  GPIO_Init(GPIOB, &GPIO_InitStructure);/* 配置MISO引脚为复用功能 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_Init(GPIOB, &GPIO_InitStructure);/* 配置MOSI引脚为复用功能 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_Init(GPIOA, &GPIO_InitStructure);  /*CS引脚 */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;/* 配置CS(NSS,自动控制SPI的片选信号)引脚为复用功能  */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15  ;  GPIO_Init(GPIOA, &GPIO_InitStructure);/* 停止信号: CS 引脚高电平 */GPIO_SetBits(GPIOA, GPIO_Pin_15);//2.配置SPI工作模式// 分频SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 数据捕获于第二个时钟沿SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 时钟空闲idle时是低电平SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 不需要使用CRC校验SPI_InitStructure.SPI_CRCPolynomial = 0; // 数据帧长度为8位SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 双线全双工模式SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//数据传输从 MSB 位开始SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //NSS 信号由软件管理SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//设置为主设备SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			SPI_Init(SPI1,&SPI_InitStructure);  /* 使能 FLASH_SPI */	SPI_Cmd(SPI1, ENABLE);   
}

DMA结构体

typedef struct {uint32_t DMA_Channel; 				//通道选择uint32_t DMA_PeripheralBaseAddr; 	//外设地址uint32_t DMA_Memory0BaseAddr; 		//存储器0 地址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_FIFOMode; 				//FIFO 模式uint32_t DMA_FIFOThreshold; 		//FIFO 阈值uint32_t DMA_MemoryBurst; 			//存储器突发传输uint32_t DMA_PeripheralBurst; 		//外设突发传输
} DMA_InitTypeDef;

DMA请求映射

img

img

DMA传输模式

image-20200921195436820

SPI 发送DMA配置

#define SENDBUFF_SIZE (1024*20)	    // 一次发送的数据	
uint8_t TX_Buff[SENDBUFF_SIZE];		// 发送缓存
void SPI2_TX_DMA_Config(void)
{// 中断结构体NVIC_InitTypeDef NVIC_InitStructure;		// DMA结构体DMA_InitTypeDef DMA_InitStructure;  		/* 使能DMA时钟 */  		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);	/* 复位初始化DMA数据流 */  DMA_DeInit(DMA2_Stream5);								/* 确保DMA数据流复位完成 */  while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE);	/* 配置 DMA Stream *//* 通道3,数据流5 */	  DMA_InitStructure.DMA_Channel = DMA_Channel_3;/* 外设地址 */  DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C);	/* 内存地址(要传输的变量的指针) ,DMA存储器0地址*/  	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)TX_Buff;	/* 方向:存储器到外设 */			DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;/* 数据传输量 ,可设置为0, 实际发送时会重新设置*/	    DMA_InitStructure.DMA_BufferSize = (uint32_t)SENDBUFF_SIZE;		/* 外设非增量模式 */		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;/* 存储器增量模式 */  	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;/* 外设数据长度:8位 */	 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;/* 内存数据长度:8位 */DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;/* DMA模式:正常模式 */  		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;/* 优先级:高 */	 		DMA_InitStructure.DMA_Priority = DMA_Priority_High;/* 禁用FIFO */DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;        		DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;   /* 外设突发单次传输 */  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;    		/* 存储器突发单次传输 */  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /* 初始化DMA Stream */		DMA_Init(DMA2_Stream5, &DMA_InitStructure);/* 开启传输完成中断  */		DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE);// 中断初始化 /* DMA发送中断源 */  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream5_IRQn;	/* 抢断优先级 */  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;/* 响应优先级 */  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;				/* 使能外部中断通道 */ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;						 /* 配置NVIC */		NVIC_Init(&NVIC_InitStructure);
}

DMA发送中断服务函数

void DMA2_Stream5_IRQHandler(void)
{// DMA 发送完成if(DMA_GetITStatus(DMA2_Stream5, DMA_IT_TCIF5))	{// 清除DMA发送完成标志DMA_ClearITPendingBit(DMA2_Stream5, DMA_IT_TCIF5);	// 片选拉高,数据发送完毕	GPIO_SetBits(GPIOA, GPIO_Pin_15);	}
}

SPI 接收DMA 配置

#define RECEIVE_SIZE  		800  	// 接收大小
uint8_t RX_Buff[RECEIVE_SIZE];		// 接收到缓存
void SPI2_RX_DMA_Config(void)
{// 中断结构体NVIC_InitTypeDef NVIC_InitStructure;	// DMA结构体  DMA_InitTypeDef DMA_InitStructure;		/* 使能DMA时钟*/  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);					/* 复位初始化DMA数据流 */ DMA_DeInit(DMA2_Stream2);												/* 确保DMA数据流复位完成 */while(DMA_GetCmdStatus(DMA2_Stream2)!=DISABLE);/* 配置 DMA Stream *//* 通道3,数据流2*/	  DMA_InitStructure.DMA_Channel = DMA_Channel_3;  			/* 设置DMA源:串口数据寄存器地址*/  DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C)		;/* 内存地址(要传输的变量的指针)*/  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RX_Buff;			/* 方向:存储器到外设模式 */			DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;/* 数据传输量 ,需要最大可能接受的数据量[不能为0],实际发送时会重新设置*/	  DMA_InitStructure.DMA_BufferSize = (uint32_t)RECEIVE_SIZE;/* 外设非增量模式 */	  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 	  /* 存储器增量模式 */    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;/* 外设数据长度:8位 */	  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;/* 内存数据长度:8位 */DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;		/* DMA模式:正常模式 */  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;/* 优先级:高 */	   DMA_InitStructure.DMA_Priority = DMA_Priority_High;/*禁用FIFO*/    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;/* 外设突发单次传输 */  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;/* 存储器突发单次传输 */  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /* 初始化DMA Stream */		DMA_Init(DMA2_Stream2, &DMA_InitStructure);							   /* 开启传输完成中断  */DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);  					// 中断初始化 /* 配置 DMA接收为中断源 */NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;  	/* 抢断优先级 */  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;		/* 响应优先级 */  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;				/* 使能外部中断通道 */  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;/* 配置NVIC */	NVIC_Init(&NVIC_InitStructure);
}

DMA接收中断服务函数

void DMA2_Stream2_IRQHandler(void)
{				// DMA接收完成if(DMA_GetITStatus(DMA2_Stream2, DMA_IT_TCIF2))	{		// 数据接收完成 拉高片选GPIO_SetBits(GPIOA, GPIO_Pin_15);	// 清除DMA接收完成标志位		DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2);	}
}

DMA请求使能

//SPI2 TX DMA请求使能
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);	
//SPI2 RX DMA请求使能
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);	

SPI_DMA 读写一个buf

#define BufSize		800
void SPI_DMA_WRITE_READ_BUF(void) 
{   // 关闭发送 DMA	DMA_Cmd(DMA2_Stream5, DISABLE);	// 关闭接收 DMA	DMA_Cmd(DMA2_Stream2, DISABLE);// 设置发送的数据量DMA_SetCurrDataCounter(DMA2_Stream5, BufSize);	// 设置接收的数据量DMA_SetCurrDataCounter(DMA2_Stream2, BufSize);// 清空数据SPI1->DR;			  	// 擦除DMA标志位DMA_ClearFlag(DMA2_Stream5, DMA_IT_TCIF5);	DMA_ClearFlag(DMA2_Stream2, DMA_IT_TCIF5);// 片选拉低,接收数据GPIO_ResetBits(GPIOA, GPIO_Pin_15);	// 开启接收 DMADMA_Cmd(DMA2_Stream5, ENABLE);	DMA_Cmd(DMA2_Stream2, ENABLE);
}

SPI_DMA 发送一个buf

void DMA_Write_buf(uint32_t SizeLen)
{   // 关闭发送 DMA		DMA_Cmd(DMA2_Stream5, DISABLE);	// 设置发送的数据量    DMA_SetCurrDataCounter(DMA2_Stream5,SizeLen);// 清空数据SPI1->DR;		// 擦除DMA标志位	DMA_ClearFlag(DMA2_Stream5,DMA_IT_TCIF5);// 片选拉低,接收数据GPIO_ResetBits(GPIOA, GPIO_Pin_15);	// 开启发送 DMADMA_Cmd(DMA2_Stream5, ENABLE);	
}

张贴的代码显示如何配置我的GPIO,定时器,SPI, DMA和NVIC模块,以及一些系统如何工作的解释。
注意,我使用的是STM32F4标准外设库。

网上内容

通过RCC(复位和时钟控制)模块使时钟信号到达所需模块:

RCC

// 为所需模块配置时钟// 启用GPIO外围时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);// 启用串行外围接口外围时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);// 启用直接内存访问外围时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);// 启用计时器外围时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); 

GPIO

接下来,配置所需的GPIO引脚:

  #define GPIO_SCAN_PORT            GPIOB#define GPIO_SCAN_PIN                GPIO_Pin_7#define GPIO_XLAT_PORT              GPIOA#define GPIO_XLAT_PIN                 GPIO_Pin_5#define GPIO_BLANK_PORT           GPIOB#define GPIO_BLANK_PIN              GPIO_Pin_6// 配置GPIO引脚GPIO_InitTypeDef GPIO_InitStructure;// Timer3&4 输出 (TLC5940 GSCLK and BLANK)GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_4 | GPIO_Pin_6);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_Init(GPIOB, &GPIO_InitStructure);// 连接计时器到GPIO引脚GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);   // 将TIM3 OC1输出连接到PortB Pin4 (GSCLK)GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);   // 将TIM4 OC1输出连接到PortB Pin6(BLANK)// TLC5940 XLAT 引脚GPIO_InitStructure.GPIO_Pin = GPIO_XLAT_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_Init(GPIO_XLAT_PORT, &GPIO_InitStructure);// 显示扫描 引脚GPIO_InitStructure.GPIO_Pin = GPIO_SCAN_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_Init(GPIO_SCAN_PORT, &GPIO_InitStructure);// SPI2 引脚//    SCLK =    PB10//    NSS =     PB9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_SPI2);GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);//    MISO =    PC2//    MOSI =    PC3GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_PinAFConfig(GPIOC, GPIO_PinSource2, GPIO_AF_SPI2);GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2); 

这里的要点是,我将把TIM3的OC1输出直接连接到GPIO引脚(用于GSCLK),把TIM4的OC1输出连接到BLANK信号。

SPI

现在SPI模块可以初始化:

// 初始化SPI模块SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;       // SPI总线设置使用两条线,一条用于Rx,另一条用于TxSPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                // STM32是主服务,tlc5940作为从服务SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                             // 使用8位数据传输SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                                    // TLC5940时钟空闲时低SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                                // TLC5940使用第一个时钟过渡作为“捕获边缘”SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                        // 软件slave-select操作SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 设置预定标器SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                             // TLC5940数据先从MSB传输SPI_InitStructure.SPI_CRCPolynomial = 0;                                          // 没有使用CRCSPI_Init(SPI2, &SPI_InitStructure);                                                    // 初始化SPI2外围设备SPI_SSOutputCmd(SPI2, ENABLE);                                                 // 将SS Pin设置为输出(主模式)SPI_Cmd(SPI2, ENABLE); 

我选择的SPI时钟分频器是相当随意的,但这里的关键点是,我已经配置了时钟相位和极性,根据TLC5940数据表,所有传输都将是8位(稍后详细介绍)。

DMA

用于SPI传输的DMA模块如下:

// 初始化用于SPI2_TX DMA访问的DMA1流4通道0#define DISP_SCAN_DATA_CNT     (24 * 3 * 2)    // 初始化用于SPI2_TX DMA访问的DMA1流4通道0volatile uint8_t dispData0[DISP_SCAN_DATA_CNT];volatile uint8_t dispData1[DISP_SCAN_DATA_CNT]; DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_Channel = DMA_Channel_0;                   // SPI2 Tx DMA是DMA1/Stream4/Channel0DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)&(SPI2->DR);                // 设置SPI2 TxDMA_InitStructure.DMA_Memory0BaseAddr  = (uint32_t)&dispData0;                   // 设置内存位置DMA_InitStructure.DMA_DIR  = DMA_DIR_MemoryToPeripheral;                          // 从内存发送数据到外设的Tx寄存器DMA_InitStructure.DMA_BufferSize  = DISP_SCAN_DATA_CNT;                          // 定义要发送的字节数DMA_InitStructure.DMA_PeripheralInc  = DMA_PeripheralInc_Disable;                  // 不要增加外围“内存”DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;                        // 增加内存位置DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;    // 字节大小内存传输DMA_InitStructure.DMA_MemoryDataSize  = DMA_MemoryDataSize_Byte;         // 字节大小内存传输DMA_InitStructure.DMA_Mode  = DMA_Mode_Normal;                           // 正常模式(非循环)DMA_InitStructure.DMA_Priority  = DMA_Priority_High;                // 优先级高,以避免使FIFO饱和,因为我们是在直接模式DMA_InitStructure.DMA_FIFOMode  = DMA_FIFOMode_Disable;                 // 操作在“直接模式”没有FIFODMA_Init(DMA1_Stream4, &DMA_InitStructure);// 操作在“直接模式”没有FIFODMA_ITConfig(DMA1_Stream4, DMA_IT_TC, ENABLE);                           //操作在“直接模式”没有FIFO

NVIC

接下来,我已经为两个中断服务例程触发器配置了NVIC(嵌套矢量中断控制器):

// 初始化嵌套矢量中断控制器NVIC_InitTypeDef NVIC_InitStructure;// 启用TIM4(BLANK)中断NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 启用DMA1 Stream4 (SPI2_TX)中断NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);

BLANK中断用于产生空白脉冲,初始化DMA传输,并在每个扫描周期后锁定先前传输的数据。

Timers

最后,定时器模块配置:

   #define TLC5940_GSCLK_COUNTS    256                   // GSCLK在BLANK 脉冲之间进行计数#define TLC5940_GSCLK_FREQ        1000000            // GSCLK频率#define TLC5940_BLANK_COUNT      50                     // 在切换到下一列之前,允许前一扫描列的正电源轨道关闭的填充#define TIM_APB1_FREQ                  84000000          // 内部时钟频率(CK_INT)// 启动计时器模块TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;TIM_OCInitTypeDef TIM_OCInitStructure;// 反初始化计时器模块和初始化结构TIM_DeInit(TIM3);TIM_DeInit(TIM4);TIM_TimeBaseStructInit(&TIM_BaseInitStructure);TIM_OCStructInit(&TIM_OCInitStructure);// 设置TIM3来生成“主时钟”TIM_BaseInitStructure.TIM_Period = 1;TIM_BaseInitStructure.TIM_Prescaler = (uint16_t) (((TIM_APB1_FREQ / TLC5940_GSCLK_FREQ)/4) - 1);    // 请注意,4的除法因子是由OC1频率vs CK_INT频率引起的TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM3, &TIM_BaseInitStructure);// 配置通道1输出比较作为触发器输出(用于生成‘GSCLK’信号)TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse = 1;TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OC1Init(TIM3, &TIM_OCInitStructure);TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);// 为对称计数器设置TIM4基准,最大计数指定为“GSCLK计数”(实际上是TLC5940的灰度分辨率)TIM_BaseInitStructure.TIM_Period = TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT;                   // GSCLK溢出计数(对“阻塞”的空白信号额外加1)TIM_BaseInitStructure.TIM_Prescaler = 0;TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;TIM_TimeBaseInit(TIM4, &TIM_BaseInitStructure);// 将Channel 1输出Compare配置为触发输出(TIM4用作时钟信号来生成'BLANK')TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse = TLC5940_BLANK_COUNT;TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OC1Init(TIM4, &TIM_OCInitStructure);TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);// 将TIM3配置为主计时器TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);                 // TRGO与TIM3的更新绑定TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);    // TIM3作为master启用// 将TIM4配置为从属TIM_SelectInputTrigger(TIM4, TIM_TS_ITR2);                                     // 设置TIM4(从)触发TIM3(主)TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_External1);                   // 使用主信号输入作为一个“外部时钟”// 将TIM4模块配置为在捕获/比较1个事件时中断(向上计数和向下计数都匹配)TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);// 启用计时器3和4TIM_Cmd(TIM4, ENABLE);TIM_Cmd(TIM3, ENABLE);

在这里,定时器3被用作主时钟,在它的输出比较1线上产生GSCLK信号,和驱动定时器4被配置为一个中心对齐的PWM输出在OC1上。
BLANK计数是有效的填充脉冲,允许:

  • 最小空白脉冲时间
  • XLAT和DMA传输触发
  • MOSFET输出在之前的扫描列完全放电(我已经通过观察放电时间在我的示波器调谐)

设置了GSCLK频率,并且在下降和上升的空白信号边缘之间的GSCLK脉冲数设置为256,因为我使用的是8位的颜色,而不是TLC5940芯片(12位)的全部功能。这意味着在空白脉冲之间将有256个GSCLK周期。
外围设备现在已经完全配置好了,所以最后要做的是查看中断服务例程,并调查结果:

ISRs
 #define DISP_SCAN_FREQ                 200                                                                                                                                                                           // 扫描信号的频率#define DISP_BLANK_CYCLE_LIMIT    ((((TLC5940_GSCLK_FREQ / (TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT)) / DISP_SCAN_FREQ) / 2) - 1)   // 扫描前要计数的BLANK循环数void TIM4_IRQHandler(void){// TIM4 IRQ处理程序有几个任务://    - 切换扫描信号//    - 锁定新选择('扫描')列的先前传输的数据//    - 设置并启动SPI2 DMA流来传输下一列的数据// 所有这些都应该在BLANK信号(TIM4 OC1)高的窗口内执行(而不是完整的SPI传输)。// 检查所生成的中断是否为OC1更新if(TIM_GetFlagStatus(TIM4,TIM_IT_CC1)){// 清除TIM4 CC1中断位TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);// 仅在向下计数时执行事件(这确保在空白脉冲内触发XLAT脉冲、扫描更新和SPI传输)if(TIM4->CR1 & TIM_CR1_DIR){// 检查是否需要'SCAN'更新(XLAT脉冲,扫描切换,下一次传输触发)if(dispBlankCycleCnt++ >= DISP_BLANK_CYCLE_LIMIT){GPIO_SetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);                  // 设置XLAT引脚dispBlankCycleCnt = 0;                                        // 重置计数器// 确定当前列,并相应地移动if(dispCurrentCol){dispCurrentCol = 0;                              // 更改为“0”列GPIO_SetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);      // 设置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)DMA1_Stream4->M0AR = (uint32_t)&dispData1;       // Send *next*列的数据(由于当前列现在是'0',因此将发送(针对下一个循环)}else{dispCurrentCol = 1;                                    // 更改为“1”列GPIO_ResetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);      // 重置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)DMA1_Stream4->M0AR = (uint32_t)&dispData0;         // Send *next*列的数据(由于当前列现在是'1',因此发送了(对于下一个循环)}GPIO_ResetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);             // 清除XLAT引脚//Trigger the next transferSPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);        // 启用DMA传输请求DMA_Cmd(DMA1_Stream4, ENABLE);                         // 启用分配给SPI2的DMA流}}}}void DMA1_Stream4_IRQHandler(void){// 检查是否设置了传输完成中断标志if(DMA_GetITStatus(DMA1_Stream4, DMA_IT_TCIF4) == SET){// 清除DMA1 Stream4传输完成标志DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);}}

DMA ISR目前没有被使用(我确实打算用它做一些无关的事情),但TIM4 ISR基本上控制了整个显示。
空白脉冲的上升边缘(有效地)触发中断。在确定正确的ISR触发了事件之后,使用TIM_CR1_DIR位检查计数器是否正在减少计数。这确保了我们只在BLANK脉冲的上升边缘执行以下任务。
每当ISR运行时,我们增加一个计数器,如果这个计数器超过扫描显示所需的数量,我们就使用XLAT信号锁存前面的数据,切换扫描信号,并传输下一个数据(在disdata0[]或disdata1[]数组中找到)。
在dis_blank_cycle_limit中计算扫描前等待的空循环数,其中考虑到:

  • TLC5940_GSCLK_FREQ -灰度时钟频率
  • TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT——上升的空白边之间的GSCLK脉冲数
  • expec_scan_freq -我们希望扫描数组的频率(此处设置为200Hz)

现在,更新disdatax[]数组中的数据将改变led上显示的内容。
GSCLK频率为1MHz,扫描频率为200Hz,我没有明显的LED闪烁,即使我听到人们谈论使用>5MHz来避免它与他们的设置。

Logic Analysis

我在STM32F407输出和TLC5940显示板输入之间附加了一个逻辑分析器,如下所示:
在这里插入图片描述
现在我们知道GSCLK周期是预期的,我们可以研究空白时间来确定正在以8位分辨率记录的灰度数据。
下降和上升空白边之间的时间是305.6 - 49.6 = 256us,这是预期的。
我还调查了一个更接近的水平,以检查信号的相位是正确的2^8计数。
在这里插入图片描述
最后,检查扫描宽度,我们可以看到一个列启用了2.445ms。
即扫描速率为409Hz;考虑到2。5us =不能被256us整除,这很好。
上面的捕获还显示,当空白计数达到极限时,相关的ISR锁存前面的数据,
切换扫描行,然后触发SPI传输。
然后在锁定该数据之前计算所需的空白周期数(XLAT信号在2445ms光标所在的蓝色箭头右侧几乎不可见)。

这篇关于STM32F4 SPI DMA的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/768005

相关文章

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

STM32 ADC+DMA导致写FLASH失败

最近用STM32G070系列的ADC+DMA采样时,遇到了一些小坑记录一下; 一、ADC+DMA采样时进入死循环; 解决方法:ADC-dma死循环问题_stm32 adc dma死机-CSDN博客 将ADC的DMA中断调整为最高,且增大ADCHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_Buffer_Size); 的ADC_Bu

学习硬件测试05:NTC(ADC)+正弦波(DAC)+DMA(ADC+DAC)(P73、P76、P78)

文章以下内容全部为硬件相关知识,鲜有软件知识,并且记的是自己需要的部分,大家可能看不明白。 一、NTC(ADC) 1.1实验现象 本实验用 NTC 采集温度,数码管实时显示温度数据(整数),左下角 USB 小串口每隔 1S 打印温度信息。 1.2硬件电路 NTC 电阻是一个模拟温度传感器,随着温度的升高,电阻值逐渐减小。电路简单介绍如下: 电源滤波电容在 25℃ 室温下 NTC 电

物联网——DMA+AD多通道

DMA简介 存储器映像 某些数据在运行时不会发生变化,则设置为常量,存在Flash存储器中,节省运行内存的空间 DMA结构图 DMA访问权限高于cpu 结构要素 软件触发源:存储器到存储器传输完成后,计数器清零 硬件触发源:ADC、定时器、串口 重写计数器时,需关闭DMA DMA请求 数据宽度与对齐 目标宽度小于传输带宽:高位补零,反之,舍弃高位 数据转运与D

stm32之软件SPI读写W25Q64存储器应用案例

系列文章目录 1. stm32之SPI通信协议 文章目录 系列文章目录前言一、电路接线图二、应用案例代码三、应用案例分析3.1 SPI通信模块3.2 W25Q64模块3.3 主程序 前言 提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者 本案例使用软件SPI通信的方式实现了STM32与W25Q64 Flas

STM32F103调试DMA+PWM 实现占空比逐渐增加的软启效果

实现效果:DMA+PWM 实现PWM输出时,从低电平到输出占空比逐渐增加再到保持高电平的效果,达到控制 MOS 功率开关软启的效果。 1.配置时钟 2.TIM 的 PWM 功能配置 选择、配置 TIM 注意:选择 TIM 支持 DMA 控制输出 PWM 功能的通道,有的TIM通道支持PWM 但不支持PWM注意选择。 PWM参数设置 Counter Period :

STM32F4按键状态机--单击、双击、长按

STM32F4按键状态机--单击、双击、长按 一、状态机的三要素二、使用状态机原因2.1资源占用方面2.2 执行效率方面:2.3 按键抖动方面: 三、状态机实现3.1 状态机分析3.1 程序实现 百度解析的状态机概念如下 状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。有限状态机简写为FSM(

Flink1.10基于工厂模式的任务提交与SPI机制

《2021年最新版大数据面试题全面开启更新》 Flink任务执行模式包含了yarn-session、standalone、per-job、local, 在1.10中又增加k8s的执行模式,那么在任务提交过程中如何根据不同的执行模式进行任务提交呢?主要通过两个接口来实现:PipelineExecutorFactory 与 PipelineExecutor。PipelineExecutorF

DMA引起数组越界

今遇到了内存越界问题,很隐蔽   EXTERN  __IO uint16_t RegularConvData_Tab[2*3]; 定义的DMA搬运工的buffer大小为6   实际上当时红线标注,改为了12,导致后续的变量被赋值 只要此值小于定义的buffer的大小就可以了   这个越界是非常不注意的问题,编译器不报错

OpenGL DMA接口

Opengl的DMA版本接口主要作用是解决以前访问opengl对象, 必须先将对象绑定到当前状态机下才能访问的问题,这会导致驱动层需要去频繁的进行对象的引用查找。  比如以前非DMA版本的接口操作顶点数据  glGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, sizeof