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);	
    // 开启接收 DMA
    DMA_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);	
    // 开启发送 DMA
    DMA_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 =     PB9
    GPIO_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 =    PC3
    GPIO_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,另一条用于Tx
    SPI_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;                                          // 没有使用CRC

    SPI_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通道0
    volatile 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/Channel0
    DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)&(SPI2->DR);                // 设置SPI2 Tx
    DMA_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;                 // 操作在“直接模式”没有FIFO
    DMA_Init(DMA1_Stream4, &DMA_InitStructure);

    // 操作在“直接模式”没有FIFO
    DMA_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和4
    TIM_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 transfer
                    SPI_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光标所在的蓝色箭头右侧几乎不可见)。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐