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);
复制代码 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光标所在的蓝色箭头右侧几乎不可见)。
|