你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32F4 SPI DMA

[复制链接]
STMCU小助手 发布时间:2021-10-12 13:31
STM32F4 SPI DMA
自己整理(存储器到外设模式)
SPI结构体


  1. typedef struct
  2. {
  3. uint16_t SPI_Direction; /*设置SPI 的单双向模式 */
  4. uint16_t SPI_Mode; /*设置SPI 的主/从机端模式 */
  5. uint16_t SPI_DataSize; /*设置SPI 的数据帧长度,可选8/16 位 */
  6. uint16_t SPI_CPOL; /*设置时钟极性CPOL,可选高/低电平*/
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */
  8. uint16_t SPI_NSS; /*设置NSS 引脚由SPI 硬件控制还是软件控制*/
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子,fpclk/分频数=fSCK */
  10. uint16_t SPI_FirstBit; /*设置MSB/LSB 先行 */
  11. uint16_t SPI_CRCPolynomial; /*设置CRC 校验的表达式 */
  12. }SPI_InitTypeDef;
复制代码
SPI引脚编号
14.png
15.png
16.png
SPI配置
  1. void SPI_Config(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStructure;
  4. SPI_InitTypeDef SPI_InitStructure;

  5. //1.初始化GPIO
  6. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB,ENABLE);
  7. /* 连接 引脚源*/
  8. GPIO_PinAFConfig(GPIOA,GPIO_PinSource15,GPIO_AF_SPI1);
  9. GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
  10. GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);

  11. /* 使能 SPI 时钟 */
  12. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

  13. /* GPIO初始化 */
  14. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;       
  15. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  16. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  17. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  18. /* 配置SCK引脚为复用功能 */
  19. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ;
  20. GPIO_Init(GPIOB, &GPIO_InitStructure);

  21. /* 配置MISO引脚为复用功能 */
  22. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
  23. GPIO_Init(GPIOB, &GPIO_InitStructure);

  24. /* 配置MOSI引脚为复用功能 */
  25. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  26. GPIO_Init(GPIOA, &GPIO_InitStructure);

  27. /*CS引脚 */
  28. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

  29. /* 配置CS(NSS,自动控制SPI的片选信号)引脚为复用功能 */
  30. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ;
  31. GPIO_Init(GPIOA, &GPIO_InitStructure);

  32. /* 停止信号: CS 引脚高电平 */
  33. GPIO_SetBits(GPIOA, GPIO_Pin_15);

  34. //2.配置SPI工作模式
  35. // 分频
  36. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
  37. // 数据捕获于第二个时钟沿
  38. SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
  39. // 时钟空闲idle时是低电平
  40. SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
  41. // 不需要使用CRC校验
  42. SPI_InitStructure.SPI_CRCPolynomial = 0;
  43. // 数据帧长度为8位
  44. SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  45. // 双线全双工模式
  46. SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  47. //数据传输从 MSB 位开始
  48. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  49. //NSS 信号由软件管理
  50. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  51. //设置为主设备
  52. SPI_InitStructure.SPI_Mode = SPI_Mode_Master;       
  53. SPI_Init(SPI1,&SPI_InitStructure);

  54. /* 使能 FLASH_SPI */       
  55. SPI_Cmd(SPI1, ENABLE);
  56. }
复制代码
DMA结构体
  1. typedef struct {
  2.     uint32_t DMA_Channel;                                 //通道选择
  3.     uint32_t DMA_PeripheralBaseAddr;         //外设地址
  4.     uint32_t DMA_Memory0BaseAddr;                 //存储器0 地址
  5.     uint32_t DMA_DIR;                                         //传输方向
  6.     uint32_t DMA_BufferSize;                         //数据数目
  7.     uint32_t DMA_PeripheralInc;                 //外设递增
  8.     uint32_t DMA_MemoryInc;                         //存储器递增
  9.     uint32_t DMA_PeripheralDataSize;         //外设数据宽度
  10.     uint32_t DMA_MemoryDataSize;                 //存储器数据宽度
  11.     uint32_t DMA_Mode;                                         //模式选择
  12.     uint32_t DMA_Priority;                                 //优先级
  13.     uint32_t DMA_FIFOMode;                                 //FIFO 模式
  14.     uint32_t DMA_FIFOThreshold;                 //FIFO 阈值
  15.     uint32_t DMA_MemoryBurst;                         //存储器突发传输
  16.     uint32_t DMA_PeripheralBurst;                 //外设突发传输
  17. } DMA_InitTypeDef;
复制代码

DMA请求映射
17.png
18.png
DMA传输模式
19.png
SPI 发送DMA配置
  1. #define SENDBUFF_SIZE (1024*20)            // 一次发送的数据       
  2. uint8_t TX_Buff[SENDBUFF_SIZE];                // 发送缓存
  3. void SPI2_TX_DMA_Config(void)
  4. {
  5.     // 中断结构体
  6.     NVIC_InitTypeDef NVIC_InitStructure;               
  7.     // DMA结构体
  8.     DMA_InitTypeDef DMA_InitStructure;                 
  9.     /* 使能DMA时钟 */                 
  10.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);       
  11.     /* 复位初始化DMA数据流 */  
  12.     DMA_DeInit(DMA2_Stream5);                                                               
  13.     /* 确保DMA数据流复位完成 */  
  14.     while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE);       

  15.     /* 配置 DMA Stream */
  16.     /* 通道3,数据流5 */          
  17.     DMA_InitStructure.DMA_Channel = DMA_Channel_3;
  18.     /* 外设地址 */  
  19.     DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C);       
  20.     /* 内存地址(要传输的变量的指针) ,DMA存储器0地址*/         
  21.     DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)TX_Buff;       
  22.     /* 方向:存储器到外设 */                       
  23.     DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  24.     /* 数据传输量 ,可设置为0, 实际发送时会重新设置*/            
  25.     DMA_InitStructure.DMA_BufferSize = (uint32_t)SENDBUFF_SIZE;               
  26.     /* 外设非增量模式 */               
  27.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  28.     /* 存储器增量模式 */         
  29.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  30.     /* 外设数据长度:8位 */         
  31.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  32.     /* 内存数据长度:8位 */
  33.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  34.     /* DMA模式:正常模式 */                 
  35.     DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  36.     /* 优先级:高 */                        
  37.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  38.     /* 禁用FIFO */
  39.     DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                       
  40.     DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;   
  41.     /* 外设突发单次传输 */  
  42.     DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;                   
  43.     /* 存储器突发单次传输 */  
  44.     DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

  45.     /* 初始化DMA Stream */               
  46.     DMA_Init(DMA2_Stream5, &DMA_InitStructure);
  47.     /* 开启传输完成中断  */               
  48.     DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE);

  49.     // 中断初始化
  50.     /* DMA发送中断源 */  
  51.     NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream5_IRQn;       
  52.     /* 抢断优先级 */  
  53.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  54.     /* 响应优先级 */  
  55.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;                               
  56.     /* 使能外部中断通道 */
  57.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                 
  58.     /* 配置NVIC */               
  59.     NVIC_Init(&NVIC_InitStructure);
  60. }
复制代码
DMA发送中断服务函数
  1. void DMA2_Stream5_IRQHandler(void)
  2. {
  3.     // DMA 发送完成
  4.     if(DMA_GetITStatus(DMA2_Stream5, DMA_IT_TCIF5))       
  5.     {
  6.         // 清除DMA发送完成标志
  7.         DMA_ClearITPendingBit(DMA2_Stream5, DMA_IT_TCIF5);       
  8.         // 片选拉高,数据发送完毕       
  9.         GPIO_SetBits(GPIOA, GPIO_Pin_15);       
  10.     }
  11. }
复制代码
SPI 接收DMA 配置
  1. #define RECEIVE_SIZE                  800          // 接收大小
  2. uint8_t RX_Buff[RECEIVE_SIZE];                // 接收到缓存
  3. void SPI2_RX_DMA_Config(void)
  4. {
  5.     // 中断结构体
  6.     NVIC_InitTypeDef NVIC_InitStructure;       
  7.     // DMA结构体  
  8.     DMA_InitTypeDef DMA_InitStructure;               
  9.     /* 使能DMA时钟*/  
  10.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);                                        /* 复位初始化DMA数据流 */
  11.     DMA_DeInit(DMA2_Stream2);                                                                                                /* 确保DMA数据流复位完成 */
  12.     while(DMA_GetCmdStatus(DMA2_Stream2)!=DISABLE);

  13.     /* 配置 DMA Stream */
  14.     /* 通道3,数据流2*/          
  15.     DMA_InitStructure.DMA_Channel = DMA_Channel_3;                         
  16.     /* 设置DMA源:串口数据寄存器地址*/  
  17.     DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C)                ;
  18.     /* 内存地址(要传输的变量的指针)*/  
  19.     DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RX_Buff;                       
  20.     /* 方向:存储器到外设模式 */                       
  21.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  22.     /* 数据传输量 ,需要最大可能接受的数据量[不能为0],实际发送时会重新设置*/          
  23.     DMA_InitStructure.DMA_BufferSize = (uint32_t)RECEIVE_SIZE;
  24.     /* 外设非增量模式 */          
  25.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;           
  26.     /* 存储器增量模式 */   
  27.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  28.     /* 外设数据长度:8位 */          
  29.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  30.     /* 内存数据长度:8位 */
  31.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;               
  32.     /* DMA模式:正常模式 */  
  33.     DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  34.     /* 优先级:高 */          
  35.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  36.     /*禁用FIFO*/   
  37.     DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  38.     DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  39.     /* 外设突发单次传输 */  
  40.     DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  41.     /* 存储器突发单次传输 */  
  42.     DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  43.     /* 初始化DMA Stream */               
  44.     DMA_Init(DMA2_Stream2, &DMA_InitStructure);                                                          
  45.     /* 开启传输完成中断  */
  46.     DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);                                         

  47.     // 中断初始化
  48.     /* 配置 DMA接收为中断源 */
  49.     NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;         
  50.     /* 抢断优先级 */  
  51.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;               
  52.     /* 响应优先级 */  
  53.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;                               
  54.     /* 使能外部中断通道 */  
  55.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  56.     /* 配置NVIC */       
  57.     NVIC_Init(&NVIC_InitStructure);
  58. }
复制代码
DMA接收中断服务函数
  1. void DMA2_Stream2_IRQHandler(void)
  2. {                               
  3.     // DMA接收完成
  4.     if(DMA_GetITStatus(DMA2_Stream2, DMA_IT_TCIF2))       
  5.     {               
  6.         // 数据接收完成 拉高片选
  7.         GPIO_SetBits(GPIOA, GPIO_Pin_15);       
  8.         // 清除DMA接收完成标志位               
  9.         DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2);       
  10.     }
  11. }
复制代码
DMA请求使能
  1. //SPI2 TX DMA请求使能
  2. SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);       
  3. //SPI2 RX DMA请求使能
  4. SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);       
复制代码
SPI_DMA 读写一个buf
  1. #define BufSize                800
  2. void SPI_DMA_WRITE_READ_BUF(void)
  3. {   
  4.     // 关闭发送 DMA       
  5.     DMA_Cmd(DMA2_Stream5, DISABLE);       
  6.     // 关闭接收 DMA       
  7.     DMA_Cmd(DMA2_Stream2, DISABLE);
  8.     // 设置发送的数据量
  9.     DMA_SetCurrDataCounter(DMA2_Stream5, BufSize);       
  10.     // 设置接收的数据量
  11.     DMA_SetCurrDataCounter(DMA2_Stream2, BufSize);
  12.         // 清空数据
  13.     SPI1->DR;                                 
  14.     // 擦除DMA标志位
  15.     DMA_ClearFlag(DMA2_Stream5, DMA_IT_TCIF5);       
  16.     DMA_ClearFlag(DMA2_Stream2, DMA_IT_TCIF5);

  17.     // 片选拉低,接收数据
  18.     GPIO_ResetBits(GPIOA, GPIO_Pin_15);       
  19.     // 开启接收 DMA
  20.     DMA_Cmd(DMA2_Stream5, ENABLE);       
  21.     DMA_Cmd(DMA2_Stream2, ENABLE);
  22. }
复制代码
SPI_DMA 发送一个buf
  1. void DMA_Write_buf(uint32_t SizeLen)
  2. {   
  3.     // 关闭发送 DMA               
  4.     DMA_Cmd(DMA2_Stream5, DISABLE);       
  5.     // 设置发送的数据量   
  6.     DMA_SetCurrDataCounter(DMA2_Stream5,SizeLen);
  7.     // 清空数据
  8.     SPI1->DR;               
  9.     // 擦除DMA标志位       
  10.     DMA_ClearFlag(DMA2_Stream5,DMA_IT_TCIF5);
  11.     // 片选拉低,接收数据
  12.     GPIO_ResetBits(GPIOA, GPIO_Pin_15);       
  13.     // 开启发送 DMA
  14.     DMA_Cmd(DMA2_Stream5, ENABLE);       
  15. }
复制代码
张贴的代码显示如何配置我的GPIO,定时器,SPI, DMA和NVIC模块,以及一些系统如何工作的解释。
注意,我使用的是STM32F4标准外设库。


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


RCC
  1. // 为所需模块配置时钟
  2.     // 启用GPIO外围时钟
  3.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  4.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  5.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  6.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

  7.     // 启用串行外围接口外围时钟
  8.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

  9.     // 启用直接内存访问外围时钟
  10.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

  11.    // 启用计时器外围时钟
  12.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  13.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
复制代码
GPIO
接下来,配置所需的GPIO引脚:
  1. #define GPIO_SCAN_PORT            GPIOB
  2.   #define GPIO_SCAN_PIN                GPIO_Pin_7
  3.   #define GPIO_XLAT_PORT              GPIOA
  4.   #define GPIO_XLAT_PIN                 GPIO_Pin_5
  5.   #define GPIO_BLANK_PORT           GPIOB
  6.   #define GPIO_BLANK_PIN              GPIO_Pin_6

  7. // 配置GPIO引脚
  8.     GPIO_InitTypeDef GPIO_InitStructure;

  9.     // Timer3&4 输出 (TLC5940 GSCLK and BLANK)
  10.     GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_4 | GPIO_Pin_6);
  11.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  12.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  13.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  14.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  15.     GPIO_Init(GPIOB, &GPIO_InitStructure);
  16.     // 连接计时器到GPIO引脚
  17.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);   // 将TIM3 OC1输出连接到PortB Pin4 (GSCLK)
  18.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);   // 将TIM4 OC1输出连接到PortB Pin6(BLANK)

  19.     // TLC5940 XLAT 引脚
  20.     GPIO_InitStructure.GPIO_Pin = GPIO_XLAT_PIN;
  21.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  22.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  23.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  24.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  25.     GPIO_Init(GPIO_XLAT_PORT, &GPIO_InitStructure);

  26.     // 显示扫描 引脚
  27.     GPIO_InitStructure.GPIO_Pin = GPIO_SCAN_PIN;
  28.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  29.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  30.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  31.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  32.     GPIO_Init(GPIO_SCAN_PORT, &GPIO_InitStructure);

  33.     // SPI2 引脚
  34.     //    SCLK =    PB10
  35.     //    NSS =     PB9
  36.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
  37.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  38.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  39.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  40.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  41.     GPIO_Init(GPIOB, &GPIO_InitStructure);
  42.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_SPI2);
  43.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);

  44.     //    MISO =    PC2
  45.     //    MOSI =    PC3
  46.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
  47.     GPIO_Init(GPIOC, &GPIO_InitStructure);
  48.     GPIO_PinAFConfig(GPIOC, GPIO_PinSource2, GPIO_AF_SPI2);
  49.     GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2);
  50. ————————————————
复制代码
这里的要点是,我将把TIM3的OC1输出直接连接到GPIO引脚(用于GSCLK),把TIM4的OC1输出连接到BLANK信号。


SPI
现在SPI模块可以初始化:
  1. // 初始化SPI模块
  2.     SPI_InitTypeDef SPI_InitStructure;
  3.     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;       // SPI总线设置使用两条线,一条用于Rx,另一条用于Tx
  4.     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                // STM32是主服务,tlc5940作为从服务
  5.     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                             // 使用8位数据传输
  6.     SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                                    // TLC5940时钟空闲时低
  7.     SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                                // TLC5940使用第一个时钟过渡作为“捕获边缘”
  8.     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                        // 软件slave-select操作
  9.     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 设置预定标器
  10.     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                             // TLC5940数据先从MSB传输
  11.     SPI_InitStructure.SPI_CRCPolynomial = 0;                                          // 没有使用CRC

  12.     SPI_Init(SPI2, &SPI_InitStructure);                                                    // 初始化SPI2外围设备
  13.     SPI_SSOutputCmd(SPI2, ENABLE);                                                 // 将SS Pin设置为输出(主模式)
  14.     SPI_Cmd(SPI2, ENABLE);
复制代码
我选择的SPI时钟分频器是相当随意的,但这里的关键点是,我已经配置了时钟相位和极性,根据TLC5940数据表,所有传输都将是8位(稍后详细介绍)。


DMA
用于SPI传输的DMA模块如下:
  1. // 初始化用于SPI2_TX DMA访问的DMA1流4通道0

  2.     #define DISP_SCAN_DATA_CNT     (24 * 3 * 2)    // 初始化用于SPI2_TX DMA访问的DMA1流4通道0
  3.     volatile uint8_t dispData0[DISP_SCAN_DATA_CNT];
  4.     volatile uint8_t dispData1[DISP_SCAN_DATA_CNT];

  5.     DMA_InitTypeDef DMA_InitStructure;

  6.     DMA_InitStructure.DMA_Channel = DMA_Channel_0;                   // SPI2 Tx DMA是DMA1/Stream4/Channel0
  7.     DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)&(SPI2->DR);                // 设置SPI2 Tx
  8.     DMA_InitStructure.DMA_Memory0BaseAddr  = (uint32_t)&dispData0;                   // 设置内存位置
  9.     DMA_InitStructure.DMA_DIR  = DMA_DIR_MemoryToPeripheral;                          // 从内存发送数据到外设的Tx寄存器
  10.     DMA_InitStructure.DMA_BufferSize  = DISP_SCAN_DATA_CNT;                          // 定义要发送的字节数
  11.     DMA_InitStructure.DMA_PeripheralInc  = DMA_PeripheralInc_Disable;                  // 不要增加外围“内存”
  12.     DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;                        // 增加内存位置
  13.     DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;    // 字节大小内存传输
  14.     DMA_InitStructure.DMA_MemoryDataSize  = DMA_MemoryDataSize_Byte;         // 字节大小内存传输
  15.     DMA_InitStructure.DMA_Mode  = DMA_Mode_Normal;                           // 正常模式(非循环)
  16.     DMA_InitStructure.DMA_Priority  = DMA_Priority_High;                // 优先级高,以避免使FIFO饱和,因为我们是在直接模式
  17.     DMA_InitStructure.DMA_FIFOMode  = DMA_FIFOMode_Disable;                 // 操作在“直接模式”没有FIFO
  18.     DMA_Init(DMA1_Stream4, &DMA_InitStructure);

  19.     // 操作在“直接模式”没有FIFO
  20.     DMA_ITConfig(DMA1_Stream4, DMA_IT_TC, ENABLE);
复制代码
NVIC
接下来,我已经为两个中断服务例程触发器配置了NVIC(嵌套矢量中断控制器):
  1. // 初始化嵌套矢量中断控制器
  2.     NVIC_InitTypeDef NVIC_InitStructure;

  3.     // 启用TIM4(BLANK)中断
  4.     NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
  5.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  6.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  7.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  8.     NVIC_Init(&NVIC_InitStructure);

  9.     // 启用DMA1 Stream4 (SPI2_TX)中断
  10.     NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;
  11.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  12.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  13.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  14.     NVIC_Init(&NVIC_InitStructure);
复制代码
BLANK中断用于产生空白脉冲,初始化DMA传输,并在每个扫描周期后锁定先前传输的数据。


Timers
最后,定时器模块配置:
  1.    #define TLC5940_GSCLK_COUNTS    256                   // GSCLK在BLANK 脉冲之间进行计数
  2.    #define TLC5940_GSCLK_FREQ        1000000            // GSCLK频率
  3.    #define TLC5940_BLANK_COUNT      50                     // 在切换到下一列之前,允许前一扫描列的正电源轨道关闭的填充
  4.    #define TIM_APB1_FREQ                  84000000          // 内部时钟频率(CK_INT)

  5. // 启动计时器模块
  6.     TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
  7.     TIM_OCInitTypeDef TIM_OCInitStructure;

  8.     // 反初始化计时器模块和初始化结构
  9.     TIM_DeInit(TIM3);
  10.     TIM_DeInit(TIM4);
  11.     TIM_TimeBaseStructInit(&TIM_BaseInitStructure);
  12.     TIM_OCStructInit(&TIM_OCInitStructure);

  13.     // 设置TIM3来生成“主时钟”
  14.     TIM_BaseInitStructure.TIM_Period = 1;
  15.     TIM_BaseInitStructure.TIM_Prescaler = (uint16_t) (((TIM_APB1_FREQ / TLC5940_GSCLK_FREQ)/4) - 1);    // 请注意,4的除法因子是由OC1频率vs CK_INT频率引起的
  16.     TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  17.     TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
  18.     TIM_TimeBaseInit(TIM3, &TIM_BaseInitStructure);
  19.     // 配置通道1输出比较作为触发器输出(用于生成‘GSCLK’信号)
  20.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
  21.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  22.     TIM_OCInitStructure.TIM_Pulse = 1;
  23.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  24.     TIM_OC1Init(TIM3, &TIM_OCInitStructure);
  25.     TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);

  26.    // 为对称计数器设置TIM4基准,最大计数指定为“GSCLK计数”(实际上是TLC5940的灰度分辨率)
  27.     TIM_BaseInitStructure.TIM_Period = TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT;                   // GSCLK溢出计数(对“阻塞”的空白信号额外加1)
  28.     TIM_BaseInitStructure.TIM_Prescaler = 0;
  29.     TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  30.     TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
  31.     TIM_TimeBaseInit(TIM4, &TIM_BaseInitStructure);
  32.     // 将Channel 1输出Compare配置为触发输出(TIM4用作时钟信号来生成'BLANK')
  33.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  34.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  35.     TIM_OCInitStructure.TIM_Pulse = TLC5940_BLANK_COUNT;
  36.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  37.     TIM_OC1Init(TIM4, &TIM_OCInitStructure);
  38.     TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);

  39.     // 将TIM3配置为主计时器
  40.     TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);                 // TRGO与TIM3的更新绑定
  41.     TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);    // TIM3作为master启用

  42.    // 将TIM4配置为从属
  43.     TIM_SelectInputTrigger(TIM4, TIM_TS_ITR2);                                     // 设置TIM4(从)触发TIM3(主)
  44.     TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_External1);                   // 使用主信号输入作为一个“外部时钟”

  45.     // 将TIM4模块配置为在捕获/比较1个事件时中断(向上计数和向下计数都匹配)
  46.     TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);

  47.     // 启用计时器3和4
  48.     TIM_Cmd(TIM4, ENABLE);
  49.     TIM_Cmd(TIM3, ENABLE);
  50. ————————————————
复制代码
在这里,定时器3被用作主时钟,在它的输出比较1线上产生GSCLK信号,和驱动定时器4被配置为一个中心对齐的PWM输出在OC1上。
BLANK计数是有效的填充脉冲,允许:


最小空白脉冲时间
XLAT和DMA传输触发
MOSFET输出在之前的扫描列完全放电(我已经通过观察放电时间在我的示波器调谐)
设置了GSCLK频率,并且在下降和上升的空白信号边缘之间的GSCLK脉冲数设置为256,因为我使用的是8位的颜色,而不是TLC5940芯片(12位)的全部功能。这意味着在空白脉冲之间将有256个GSCLK周期。
外围设备现在已经完全配置好了,所以最后要做的是查看中断服务例程,并调查结果:


ISRs
  1. #define DISP_SCAN_FREQ                 200                                                                                                                                                                           // 扫描信号的频率
  2. #define DISP_BLANK_CYCLE_LIMIT    ((((TLC5940_GSCLK_FREQ / (TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT)) / DISP_SCAN_FREQ) / 2) - 1)   // 扫描前要计数的BLANK循环数

  3.    void TIM4_IRQHandler(void)
  4.     {
  5.         // TIM4 IRQ处理程序有几个任务:
  6.         //    - 切换扫描信号
  7.        //    - 锁定新选择('扫描')列的先前传输的数据
  8.         //    - 设置并启动SPI2 DMA流来传输下一列的数据
  9.         // 所有这些都应该在BLANK信号(TIM4 OC1)高的窗口内执行(而不是完整的SPI传输)。

  10.         // 检查所生成的中断是否为OC1更新
  11.         if(TIM_GetFlagStatus(TIM4,TIM_IT_CC1))
  12.         {
  13.             // 清除TIM4 CC1中断位
  14.             TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);

  15.             // 仅在向下计数时执行事件(这确保在空白脉冲内触发XLAT脉冲、扫描更新和SPI传输)
  16.             if(TIM4->CR1 & TIM_CR1_DIR)
  17.             {
  18.                 // 检查是否需要'SCAN'更新(XLAT脉冲,扫描切换,下一次传输触发)
  19.                 if(dispBlankCycleCnt++ >= DISP_BLANK_CYCLE_LIMIT)
  20.                 {
  21.                     GPIO_SetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);                  // 设置XLAT引脚
  22.                     dispBlankCycleCnt = 0;                                        // 重置计数器

  23.                     // 确定当前列,并相应地移动
  24.                     if(dispCurrentCol)
  25.                     {
  26.                         dispCurrentCol = 0;                              // 更改为“0”列
  27.                        GPIO_SetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);      // 设置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)
  28.                         DMA1_Stream4->M0AR = (uint32_t)&dispData1;       // Send *next*列的数据(由于当前列现在是'0',因此将发送(针对下一个循环)
  29.                    }
  30.                    else
  31.                     {
  32.                         dispCurrentCol = 1;                                    // 更改为“1”列
  33.                         GPIO_ResetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);      // 重置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)
  34.                         DMA1_Stream4->M0AR = (uint32_t)&dispData0;         // Send *next*列的数据(由于当前列现在是'1',因此发送了(对于下一个循环)
  35.                     }

  36.                     GPIO_ResetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);             // 清除XLAT引脚

  37.                     //Trigger the next transfer
  38.                     SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);        // 启用DMA传输请求
  39.                     DMA_Cmd(DMA1_Stream4, ENABLE);                         // 启用分配给SPI2的DMA流
  40.                 }
  41.             }
  42.         }
  43.     }

  44.     void DMA1_Stream4_IRQHandler(void)
  45.     {
  46.         // 检查是否设置了传输完成中断标志
  47.         if(DMA_GetITStatus(DMA1_Stream4, DMA_IT_TCIF4) == SET)
  48.         {
  49.             // 清除DMA1 Stream4传输完成标志
  50.             DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);
  51.         }
  52.     }
复制代码
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显示板输入之间附加了一个逻辑分析器,如下所示:
20.png
现在我们知道GSCLK周期是预期的,我们可以研究空白时间来确定正在以8位分辨率记录的灰度数据。
下降和上升空白边之间的时间是305.6 - 49.6 = 256us,这是预期的。
我还调查了一个更接近的水平,以检查信号的相位是正确的2^8计数。
21.png
最后,检查扫描宽度,我们可以看到一个列启用了2.445ms。
即扫描速率为409Hz;考虑到2。5us =不能被256us整除,这很好。
上面的捕获还显示,当空白计数达到极限时,相关的ISR锁存前面的数据,
切换扫描行,然后触发SPI传输。
然后在锁定该数据之前计算所需的空白周期数(XLAT信号在2445ms光标所在的蓝色箭头右侧几乎不可见)。



收藏 评论0 发布时间:2021-10-12 13:31

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版