STM32F4 SPI DMA
张贴的代码显示如何配置我的GPIO,定时器,SPI, DMA和NVIC模块,以及一些系统如何工作的解释。注意,我使用的是STM32F4标准外设库。第一步通过RCC(复位和时钟控制)模块使时钟信号到达所需模块:RCC// 为所需模块配置时钟// 启用GPIO外围时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);RCC_AHB1PeriphC
文章目录
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);
// 开启接收 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光标所在的蓝色箭头右侧几乎不可见)。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)