本文主要是介绍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引脚编号


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请求映射
DMA传输模式
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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!