|  **银杏科技有限公司旗下技术文档发布平台**  ||||
|技术支持电话|**0379-69926675-801**|||
|技术支持邮件|Gingko@vip.163.com|||
^  版本  ^  日期  ^  作者  ^  修改内容  ^
|  V1.0  |  2020-03-02  |  gingko  |  初次建立  | 
\\
\\
\\
===== STM32CubeMX教程十八——SPI实验 =====
\\
1.在主界面选择File-->New Project   或者直接点击ACCEE TO MCU SELECTOR。  
{{ :icore4t:icore4t_cube_18_1.png?direct |}}
2.出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置,在搜索栏的下面,提供的各种查找方式,可以选择芯片内核,型号,等等,可以帮助你查找芯片。本实验选取的芯片型号。为:STM32H750IBKx。
{{ :icore4t:icore4t_cube_18_2.png?direct |}}
3.配置RCC,使用外部时钟源。
{{ :icore4t:icore4t_cube_18_3.png?direct |}}
4.时基源选择SysTick。
{{ :icore4t:icore4t_cube_18_4.png?direct |}}
5.将PA10,PB7,PB8设置为GPIO_Output。
{{ :icore4t:icore4t_cube_18_5.png?direct |}}
6.引脚模式配置。
{{ :icore4t:icore4t_cube_18_6.png?direct |}}
{{ :icore4t:icore4t_cube_18_7.png?direct |}}
7.设置串口。
{{ :icore4t:icore4t_cube_18_8.png?direct |}}
8.在NVIC Settings一栏使能接收中断。
{{ :icore4t:icore4t_cube_18_9.png?direct |}}
9.配置SPI。
{{ :icore4t:icore4t_cube_18_10.png?direct |}}
10.配置SPI DMA,选择SPI_TX,SPI_RX。
{{ :icore4t:icore4t_cube_18_11.png?direct |}}
11.时钟源设置,选择外部高速时钟源,配置为最大主频。
{{ :icore4t:icore4t_cube_18_12.png?direct |}}
{{ :icore4t:icore4t_cube_18_13.png?direct |}}
12.工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可  IDE我们使用的是 MDK V5.27。
{{ :icore4t:icore4t_cube_18_14.png?direct |}}
13.点击Code Generator,进行进一步配置。
{{ :icore4t:icore4t_cube_18_15.png?direct |}}
  * **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  创建工程。
{{ :icore4t:icore4t_cube_18_16.png?direct |}}
创建成功,打开工程。
===== 实验十八:SPI实验——读写FPGA =====
==== 一、 实验目的与意义 ====
  - 了解STM32 SPI结构。
  - 了解STM32 SPI  DMA特征。
  - 掌握SPI的使用方法。
  - 掌握STM32 HAL库中SPI属性的配置方法。
  - 掌握KEILMDK 集成开发环境使用方法。
==== 二、 实验设备及平台 ====
  - iCore4T 双核心板。[[https://item.taobao.com/item.htm?spm=a1z10.1-c.w137644-251734891.3.5923532fDrMDOe&id=610595120319|点击购买]]
  - JLINK(或相同功能)仿真器。[[https://item.taobao.com/item.htm?id=554869837940|点击购买]]
  - Micro USB线缆。
  - Keil MDK 开发平台。
  - STM32CubeMX开发平台。
  - 装有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主模块和与之通信的外设备时钟相位和极性应该一致。
{{ :icore4t:icore4t_arm_hal_18_1.png?direct |}}
=== 3.DMA工作原理: ===
  * DMA 允许不同速度的硬件装置来沟通,而不需要依于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把他们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
  * DMA 传输主要地将一个内存区从一个装置复制到另外一个。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存去。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。所以,DMA传输对于高效能嵌入式系统算法和网络是很重要的。
=== 4.SPI通信指令表 ===
{{ :icore4t:icore4t_arm_hal_18_2.png?direct |}}
  * 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 位在前
==== 五、 实验步骤 ====
  - 把仿真器与iCore4T的SWD调试口相连(直接相连或者通过转接器相连);
  - 把iCore4T通过Micro USB线与计算机相连,为iCore4T供电;
  - 打开Quartus ll 开发环境,并打开本实验工程;
  - 烧写程序到iCore4T上;
  - 打开Keil MDK 开发环境,并打开本实验工程;
  - 烧写程序到iCore4T上;
  - 也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。
==== 六、 实验现象 ====
{{ :icore4t:icore4t_arm_hal_18_3.png?direct |}}