用户工具

站点工具


icore4t_18
银杏科技有限公司旗下技术文档发布平台
技术支持电话0379-69926675-801
技术支持邮件Gingko@vip.163.com
版本 日期 作者 修改内容
V1.0 2020-03-02 gingko 初次建立




STM32CubeMX教程十八——SPI实验


1.在主界面选择File–>New Project 或者直接点击ACCEE TO MCU SELECTOR。 2.出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置,在搜索栏的下面,提供的各种查找方式,可以选择芯片内核,型号,等等,可以帮助你查找芯片。本实验选取的芯片型号。为:STM32H750IBKx。 3.配置RCC,使用外部时钟源。 4.时基源选择SysTick。 5.将PA10,PB7,PB8设置为GPIO_Output。 6.引脚模式配置。 7.设置串口。 8.在NVIC Settings一栏使能接收中断。 9.配置SPI。 10.配置SPI DMA,选择SPI_TX,SPI_RX。 11.时钟源设置,选择外部高速时钟源,配置为最大主频。 12.工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可 IDE我们使用的是 MDK V5.27。 13.点击Code Generator,进行进一步配置。

  • Copy all used libraries into the project folder
  • 将HAL库的所有.C和.H都复制到所建工程中
    • 优点:这样如果后续需要新增其他外设又可能不再用STM32CubeMX的时候便会很方便
    • 缺点:体积大,编译时间很长
  • Copy only the necessary library files
  • 只复制所需要的.C和.H(推荐)
    • 优点:体积相对小,编译时间短,并且工程可复制拷贝
    • 缺点:新增外设时需要重新用STM32CubeMX导入
  • Add necessary library files as reference in the toolchain project configuration file
  • 不复制文件,直接从软件包存放位置导入.C和.H
    • 优点:体积小,比较节约硬盘空间
    • 缺点:复制到其他电脑上或者软件包位置改变,就需要修改相对应的路径
  • 自行选择方式即可

14.然后点击GENERATE CODE 创建工程。 创建成功,打开工程。

实验十八:SPI实验——读写FPGA

一、 实验目的与意义

  1. 了解STM32 SPI结构。
  2. 了解STM32 SPI DMA特征。
  3. 掌握SPI的使用方法。
  4. 掌握STM32 HAL库中SPI属性的配置方法。
  5. 掌握KEILMDK 集成开发环境使用方法。

二、 实验设备及平台

  1. iCore4T 双核心板。点击购买
  2. JLINK(或相同功能)仿真器。点击购买
  3. Micro USB线缆。
  4. Keil MDK 开发平台。
  5. STM32CubeMX开发平台。
  6. 装有WIN XP(及更高版本)系统的计算机。

三、 实验原理

1.SPI简介

  • SPI是串行外设接口(Serial PeripheralInterface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议。SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
  • SPI硬件接口:
  • MISO :主设备数据输入,从设备数据输出。
  • MOSI :主设备数据输出,从设备数据输入。
  • SCLK :时钟信号,由主设备产生。
  • CS :从设备片选信号,由主设备控制。

2.SPI功能说明

  • SPI时钟极性和相位:
    • CPOL决定时钟空闲时的稳定电平,对主/从都有效。
    • CPOL=0:空闲时低电平。
    • CPOL=1:空闲时高电平。
    • CPHA决定数据采样时刻。
    • CPHA=0:第一个时钟延开始采样MSBit。
    • CPHA=1:第二个时钟延开始采样MSBit。
  • SPI总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设备时钟相位和极性应该一致。

3.DMA工作原理:

  • DMA 允许不同速度的硬件装置来沟通,而不需要依于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把他们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
  • DMA 传输主要地将一个内存区从一个装置复制到另外一个。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存去。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。所以,DMA传输对于高效能嵌入式系统算法和网络是很重要的。

4.SPI通信指令表

  • ARM与FPGA通信采用的是半双工式通信,FPGA通过识别指令完成与ARM的交互。
  • 写数据指令为04h,接下来为两字节的地址指令,后为要写入的数据,数据写入完毕以伪指令00h结束数据传输。
  • 读数据指令为07h,接下来为两字节的地址指令,其后为伪指令00h开始读取数据进行数据传输,第五字节以后为要读取的数据。
  • 器件ID指令为01h,接下来为两字节的伪指令,第四字节仍为伪指令开始读取ID标志,第五字节为读取的器件ID。

四、 实验程序

1.主函数

int main(void)
{
  int i;
  int temp;
  int error;
  unsigned char buffer[4096];
  HAL_Init();
  SystemClock_Config();
  i2c.initialize();
  axp152.initialize();
  axp152.set_dcdc1(3500);//[ARM & FPGA BK1/2/6 &OTHER]
  axp152.set_dcdc2(1200);//[FPGA INT & PLL D]
  axp152.set_aldo1(2500);//[FPGA PLL A]
  axp152.set_dcdc4(3300);//[POWER_OUTPUT]
  axp152.set_dcdc3(3300);//[FPGA BK4][Adjustable]
  axp152.set_aldo2(3300);//[FPGA BK3][Adjustable]
  axp152.set_dldo1(3300);//[FPGA BK7][Adjustable]
  axp152.set_dldo2(3300);//[FPGA BK5][Adjustable]
 
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_SPI4_Init();
 
  usart2.initialize(115200);                               //串口波特率设置
  usart2.printf("\x0c");                                   //清屏
  usart2.printf("\033[1;32;40m");                          //设置终端字体为绿色
  usart2.printf("Hello,I am iCore4T!\r\n\r\n");            //串口信息输出 
 
  //SPI TEST
  error = 0;
  usart2.printf("\033[1;32;40m"); //显示绿色 
  usart2.printf(" *Write FPGA 10MByte & Read......");
  //SPI写数据指令
  buffer[0] = 0x04;          //写指令
  buffer[1] = 0x00;          //地址
  buffer[2] = 0x00;          //地址
  for(i = 0;i < 1024;i ++){
    buffer[i+3] = i%256;    //数据
  }
  buffer[1027] = 0x00;      //伪指令
  temp = HAL_GetTick();
  for(i = 0;i < 10240;i ++){
    HAL_SPI_Transmit_DMA(&hspi4,buffer,1028); //通过SPI DMA发送数据
    while(!spi4_tc_flag);
    spi4_tc_flag = 0;
  }
  temp = HAL_GetTick() - temp;      //计算数据传输时间
 
  memset(buffer,0,sizeof(buffer));  //清空缓存区
    //SPI读数据指令
  buffer[0] = 0x07;                 //读指令
  buffer[1] = 0x00;                 //地址
  buffer[2] = 0x00;                 //地址
  buffer[3] = 0x00;                 //伪指令,开始数据传输
 
  HAL_SPI_TransmitReceive_DMA(&hspi4,buffer,buffer,1028); //通过SPI DMA接收数据
  while(!spi4_tc_flag);
  spi4_tc_flag = 0; 
 
  for(i = 0;i < 1023;i ++){
    if(buffer[i+4] != i%256){
      error ++;
      break;
    }
  }
  if(error == 0){
    usart2.printf("\t\t\033[1;32;40m[OK]\r\n");
    usart2.printf("*Write time:%dms\r\n*Data size :10MByte\r\n",temp);   //打印传输1MB数据的时间
    usart2.printf("*SPI Speed :%.2fMBytes/s\r\n",10000./temp);           //打印SPI传输速度
  }else{
    usart2.printf("\t\t\033[1;31;40m[Fail]\r\n");
  }
  while (1)
  {
  }
}

2.SPI初始化函数

使能SPIx和IO口时钟,初始化IO口为复用功能并配置SPI_DMA,SPI_RX,SPI_TX。

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI4)
  {
    __HAL_RCC_SPI4_CLK_ENABLE();    //使能SPI时钟
    __HAL_RCC_GPIOE_CLK_ENABLE();   //使能GPIO时钟
    /**SPI4 GPIO Configuration    
    PE2     ------> SPI4_SCK
    PE4     ------> SPI4_NSS
    PE5     ------> SPI4_MISO
    PE6     ------> SPI4_MOSI 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI4;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
 
    /* SPI4 DMA Init */
    /* SPI4_TX Init */
    hdma_spi4_tx.Instance = DMA1_Stream0;               //stream0可以被配置成channel 0、1、2、3、4、6任意一个。
    hdma_spi4_tx.Init.Request = DMA_REQUEST_SPI4_TX;    //SPI4使用DMA接收
    hdma_spi4_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; //存储器到外设
    hdma_spi4_tx.Init.PeriphInc = DMA_PINC_DISABLE;     //外设非增量模式
    hdma_spi4_tx.Init.MemInc = DMA_MINC_ENABLE;         //外设增量模式
    hdma_spi4_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;//外设数据长度:8位
    hdma_spi4_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi4_tx.Init.Mode = DMA_NORMAL;                //正常模式
    hdma_spi4_tx.Init.Priority = DMA_PRIORITY_LOW;      //设置 DMA 的优先级别
    hdma_spi4_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi4_tx) != HAL_OK)
    {
      Error_Handler();
}
 	__HAL_LINKDMA(spiHandle,hdmatx,hdma_spi4_tx); 
    /* SPI4_RX Init */
    hdma_spi4_rx.Instance = DMA1_Stream1;
    hdma_spi4_rx.Instance = DMA1_Stream1;
    hdma_spi4_rx.Init.Request = DMA_REQUEST_SPI4_RX;      //SPI4使用DMA发送
 
    hdma_spi4_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;   //外设到存储器
    hdma_spi4_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi4_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi4_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi4_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;  
    if (HAL_DMA_Init(&hdma_spi4_rx) != HAL_OK)
    {
      Error_Handler();
    }
    __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi4_rx);
    /* SPI4 interrupt Init */
    HAL_NVIC_SetPriority(SPI4_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(SPI4_IRQn);
  }
}

初始化SPIx,设置SPIx工作模式

void MX_SPI4_Init(void)
{
  hspi4.Instance = SPI4;
  hspi4.Init.Mode = SPI_MODE_MASTER;              //设置 SPI4 为主模式
  hspi4.Init.Direction = SPI_DIRECTION_2LINES;    //设置双线单向模式
  hspi4.Init.DataSize = SPI_DATASIZE_8BIT;        //设置 8位数据位
  hspi4.Init.CLKPolarity = SPI_POLARITY_HIGH;     //时钟极性为高
  hspi4.Init.CLKPhase = SPI_PHASE_2EDGE;          //时钟相位为2
  hspi4.Init.NSS = SPI_NSS_HARD_OUTPUT;           //NSS 硬件控制
  hspi4.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi4.Init.FirstBit = SPI_FIRSTBIT_MSB;         //起始位为MSB
  hspi4.Init.TIMode = SPI_TIMODE_DISABLE;         //帧格式关闭
  hspi4.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//硬件CRC关闭
  hspi4.Init.CRCPolynomial = 0x0;
  hspi4.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;    //NSS脉冲关闭
  hspi4.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;  //NSS极性为低
  hspi4.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
  hspi4.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  hspi4.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  hspi4.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
  hspi4.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
  hspi4.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
  hspi4.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
  hspi4.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
  hspi4.Init.IOSwap = SPI_IO_SWAP_DISABLE;
  if (HAL_SPI_Init(&hspi4) != HAL_OK)
  {
    Error_Handler();
  }
}

3.SPI_InitTypeDef 的结构体

  assert_param(IS_SPI_ALL_INSTANCE(hspi->Instance));
  assert_param(IS_SPI_MODE(hspi->Init.Mode));
  assert_param(IS_SPI_DIRECTION(hspi->Init.Direction));
  assert_param(IS_SPI_DATASIZE(hspi->Init.DataSize));
  assert_param(IS_SPI_FIFOTHRESHOLD(hspi->Init.FifoThreshold));
  assert_param(IS_SPI_NSS(hspi->Init.NSS));
  assert_param(IS_SPI_NSSP(hspi->Init.NSSPMode));
  assert_param(IS_SPI_BAUDRATE_PRESCALER(hspi->Init.BaudRatePrescaler));
  assert_param(IS_SPI_FIRST_BIT(hspi->Init.FirstBit));
  assert_param(IS_SPI_TIMODE(hspi->Init.TIMode));
  • SPI_Direction :设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行发和 串行收方式
  • SPI_Mode :设置 SPI 的主从模式
  • SPI_DataSize :帧格式选择项,8 位还是 16 位
  • SPI_CPOL :设置时钟极性
  • SPI_CPHA:设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿数据被采样
  • SPI_NSS :设置 NSS 信号由硬件(NSS 管脚)还是软件控制
  • SPI_BaudRatePrescaler:设置 SPI 波特率预分频值
  • SPI_FirstBit :设置数据传输顺序是 MSB 位在前还是 LSB 位在前

五、 实验步骤

  1. 把仿真器与iCore4T的SWD调试口相连(直接相连或者通过转接器相连);
  2. 把iCore4T通过Micro USB线与计算机相连,为iCore4T供电;
  3. 打开Quartus ll 开发环境,并打开本实验工程;
  4. 烧写程序到iCore4T上;
  5. 打开Keil MDK 开发环境,并打开本实验工程;
  6. 烧写程序到iCore4T上;
  7. 也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。

六、 实验现象

icore4t_18.txt · 最后更改: 2022/03/22 10:41 由 sean