SPI

SPI

新版cubeMX里面的SPI配置,scs脚没有了?

问题困惑admin 回复了问题 • 2 人关注 • 1 个回复 • 34 次浏览 • 2019-11-03 12:00 • 来自相关话题

为什么用SPI读取flash的id号是多少 通过串口显示的id号为什么会是ffffff

回复

问题困惑起个目标 发起了问题 • 1 人关注 • 0 个回复 • 79 次浏览 • 2019-09-11 08:04 • 来自相关话题

怎么实现timer 触发DMA

回复

问题困惑lost_resister 发起了问题 • 1 人关注 • 0 个回复 • 112 次浏览 • 2019-09-08 10:13 • 来自相关话题

我用SPI硬件通信写数据到OLED屏,屏幕只是乱码,调用的HAL_SPI_Transmit,是我调用的函数不对,还是代入参数写的不对?

问题困惑V-Robot 回复了问题 • 3 人关注 • 2 个回复 • 193 次浏览 • 2019-08-13 15:19 • 来自相关话题

使用DMA 发送 SPI 数据发现,HAL_SPI_Transmit_DMA发送 数据给TFt240*240 的分辨率数据

回复

问题困惑V-Robot 发起了问题 • 1 人关注 • 0 个回复 • 292 次浏览 • 2019-08-13 15:16 • 来自相关话题

STM32作为SPI从机,调用HAL_SPI_Transmit发送数据后,后续主机发送SCK,MISO总线依然有数据

回复

问题困惑wuss1021521@163.com 发起了问题 • 1 人关注 • 0 个回复 • 422 次浏览 • 2019-07-24 11:18 • 来自相关话题

STM32如何通过全双工或者双线SPI与高速(1MHz以上)外部ADC通信?

回复

问题困惑hlpingygy 发起了问题 • 1 人关注 • 0 个回复 • 605 次浏览 • 2019-06-20 09:20 • 来自相关话题

f427 spi issue

回复

问题困惑eone 发起了问题 • 1 人关注 • 0 个回复 • 141 次浏览 • 2019-06-18 19:02 • 来自相关话题

STM32L476 使用SPI 函数HAL_SPI_TransmitReceive()读出数据错误,求解!

问题困惑777、 回复了问题 • 2 人关注 • 1 个回复 • 643 次浏览 • 2019-06-04 22:29 • 来自相关话题

关于L071HAI库使用硬件SPI出现的死循环等问题?

回复

问题困惑 K . 发起了问题 • 1 人关注 • 0 个回复 • 316 次浏览 • 2019-04-26 15:34 • 来自相关话题

1.44寸tft屏刷屏速度慢的问题,希望大神看看是什么问题

问题困惑此ID已注册 回复了问题 • 3 人关注 • 2 个回复 • 716 次浏览 • 2019-04-16 10:06 • 来自相关话题

STM32F205RCT6,外部中断EXIT4初始化必须在SPI1和SPI3之后。

回复

问题困惑jeff_fh 发起了问题 • 1 人关注 • 0 个回复 • 284 次浏览 • 2019-03-27 10:12 • 来自相关话题

STM32HAL库SPI的16位数据中断发送与接收

经验分享老是忙 发表了文章 • 0 个评论 • 2122 次浏览 • 2019-01-29 11:56 • 来自相关话题

HAL库的SPI发送接收函数的确令人迷惑,明明支持16位传输,却必须使用8位的指针。如果没能正确理解SPI发送接收函数,很容易导致程序接入HardFault_Handler中断死循环。    最近用到STM32F407的HAL编程,SPI通讯的外设要求16位通讯,对HAL库的SPI的16位通讯做了个深入研究。发现HAL库提供的函数除了入口参数含义不太明确,还是很好用的。下面以中断方式发送接收
uint16_t 数据为例,阐述函数调用的过程和要点。   1、
首先,建立发送和接收缓冲区,用16位的数组: uint16_t SPI_TxBuff[1],
SPI_RxBuff[1];   2、SPI初始化: HAL_SPI_DeInit(&hspi1);   //SPI1 复位 hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction =
SPI_DIRECTION_2LINES;   //双向通讯 hspi1.Init.DataSize =
SPI_DATASIZE_16BIT;       //16位传输 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler =
SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation =
SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi1) != HAL_OK) {   Error_Handler(); } 其中,DataSize 设为16位。  3、在程序中的调用: SPI_TxBuff[0] =
data;   //data
是uint16_t 类型,复制到发送缓存。 HAL_SPI_TransmitReceive_IT(&hspi1,
(uint8_t*)SPI_TxBuff, (uint8_t*)SPI_RxBuff, 1);
//中断方式的传输 注意1:一定要用 (uint8_t*)
临时改变SPI_TxBuff和SPI_RxBuff的类型。 经对函数内部分析,实际执行时,将把这两个指针重新变换为( uint16_t *) 。 注意2:DataSize 必须是按 uint16_t
数据的数量来设置,而不是按uint8_t, 。此例中,要传输一个16位数据,所以 DataSize 设为1 。.  4、修改SPI中断程序(在stm32f4xx_it.c中)。void SPI1_IRQHandler(void){  HAL_SPI_IRQHandler(&hspi1);    //如果接收完毕,处理数据   if(HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY)   {      IT_ProcessingData();   //接收完成的数据已经保存在 SPI_RxBuff[0], 调用此函数进行处理   } }  注意:
HAL_SPI_TransmitReceive_IT() 函数将产生两次中断!
第一次开始发送(接收)数据;当发送(接收)完成后产生第二次中断。我们在中断程序中,用     HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY来判断是否是第二次,也就是传输完成后的中断,是的话,就调用数据处理函数。 查看全部

HAL库的SPI发送接收函数的确令人迷惑,明明支持16位传输,却必须使用8位的指针。如果没能正确理解SPI发送接收函数,很容易导致程序接入HardFault_Handler中断死循环。

    最近用到STM32F407的HAL编程,SPI通讯的外设要求16位通讯,对HAL库的SPI的16位通讯做了个深入研究。发现HAL库提供的函数除了入口参数含义不太明确,还是很好用的。下面以中断方式发送接收
uint16_t 数据为例,阐述函数调用的过程和要点。

   1、
首先,建立发送和接收缓冲区,用16位的数组:

 uint16_t SPI_TxBuff[1],
SPI_RxBuff[1];


   2、SPI初始化:

 HAL_SPI_DeInit(&hspi1);   //SPI1 复位

 hspi1.Instance = SPI1;
 hspi1.Init.Mode = SPI_MODE_MASTER;
 hspi1.Init.Direction =
SPI_DIRECTION_2LINES;   //双向通讯
 hspi1.Init.DataSize =
SPI_DATASIZE_16BIT;       //16位传输
 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
 hspi1.Init.NSS = SPI_NSS_SOFT;
 hspi1.Init.BaudRatePrescaler =
SPI_BAUDRATEPRESCALER_16;
 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
 hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
 hspi1.Init.CRCCalculation =
SPI_CRCCALCULATION_DISABLE;
 hspi1.Init.CRCPolynomial = 10;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
   Error_Handler();
 }

 其中,DataSize 设为16位。


  3、在程序中的调用:

 SPI_TxBuff[0] =
data;   //data
是uint16_t 类型,复制到发送缓存。
 HAL_SPI_TransmitReceive_IT(&hspi1,
(uint8_t*)SPI_TxBuff, (uint8_t*)SPI_RxBuff, 1);
//中断方式的传输


 注意1:一定要用 (uint8_t*)
临时改变SPI_TxBuff和SPI_RxBuff的类型。 经对函数内部分析,实际执行时,将把这两个指针重新变换为( uint16_t *) 。

 注意2:DataSize 必须是按 uint16_t
数据的数量来设置,而不是按uint8_t, 。此例中,要传输一个16位数据,所以 DataSize 设为1 。.


  4、修改SPI中断程序(在stm32f4xx_it.c中)。

void SPI1_IRQHandler(void)
{
  HAL_SPI_IRQHandler(&hspi1);
 
   //如果接收完毕,处理数据
   if(HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY)
   {
      IT_ProcessingData();   //接收完成的数据已经保存在 SPI_RxBuff[0], 调用此函数进行处理
   }
 
}

  注意:
HAL_SPI_TransmitReceive_IT() 函数将产生两次中断!
第一次开始发送(接收)数据;当发送(接收)完成后产生第二次中断。我们在中断程序中,用

     HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY

来判断是否是第二次,也就是传输完成后的中断,是的话,就调用数据处理函数。



STM32F3系列运用HAL库实现SPI读写铁电FM25CL64

经验分享牙子男男 发表了文章 • 3 个评论 • 1709 次浏览 • 2019-01-13 15:55 • 来自相关话题

最近项目用到了STM32F3系列芯片,需要通过SPI总线实现对铁电FM25CL64的读写。以前完全没用过SPI,所以前期遇到了不少坑,特开此贴进行记录。正题开始:SPI配置:因为硬件上,铁电与芯片的SPI2连接,所以配置了SPI2的参数。我用的是软件触发模式。DMA和中断模式还没来得及尝试引脚:模式(Mode):全双工硬件片选(NSS): 禁用(不太懂这个,后面单独配置了一个GPIO当片选用)基本参数: 帧格式: Motorola (还有个TI的,得配合硬件片选信号,没用过) 数据大小:8bits (这个FM25CL64内部存储是按照8bit存储的) 第一位:MSB First (和FM25CL64的技术手册保持一致)时钟参数: 分频:8 (这个好像影响不大。 FM25CL64最高读写率是20MBit/s ,确保分频后下面的BaudRate小于20就行。)第一个坑要来了:CPOL和CPHA这两个参数有四种组合模式,这个参数配置一定要满足FM25CL64的要求。 刚开始,就是这个没配置对,能看到SPI总线上有数据发出去,但是铁电不返回数据,下面这组数据经测试没问题。 CPOL:  LOW CPHA: 1 Edge高级参数:因为我的SPI总线上只有一个从机,就没用到这些参数。 至此SPI的配置就完成了。生成的代码为:void MX_SPI2_Init(void){  hspi2.Instance = SPI2;  hspi2.Init.Mode = SPI_MODE_MASTER;  hspi2.Init.Direction = SPI_DIRECTION_2LINES;  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;  hspi2.Init.NSS = SPI_NSS_SOFT;  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;  hspi2.Init.CRCPolynomial = 7;  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;  hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;  if (HAL_SPI_Init(&hspi2) != HAL_OK)  {    Error_Handler();  }}void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){  GPIO_InitTypeDef GPIO_InitStruct = {0};  if(spiHandle->Instance==SPI2)  {  /* USER CODE BEGIN SPI2_MspInit 0 */  /* USER CODE END SPI2_MspInit 0 */    /* SPI2 clock enable */    __HAL_RCC_SPI2_CLK_ENABLE();      __HAL_RCC_GPIOA_CLK_ENABLE();    /**SPI2 GPIO Configuration        PA8     ------> SPI2_SCK    PA9     ------> SPI2_MISO    PA10     ------> SPI2_MOSI     */    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;    GPIO_InitStruct.Pull = GPIO_NOPULL;    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);  /* USER CODE BEGIN SPI2_MspInit 1 */  /* USER CODE END SPI2_MspInit 1 */  }}下面是写FM25CL64的读写数据程序static uint8_t WRITEFRAME[4];     //向FRAM写数据的数据流数组static uint8_t READFRAME[3];      //从FRAM读数据的数据流数组static uint8_t WRITE_SR_FRAME[2];  //向FRAM写状态寄存器的数据流数组uint8_t op_code;                          //操作码static uint8_t i;片选操作代码:GPIO_PORT_CS是片选信号GPIO的组,GPIO_PIN_CS是片选信号GPIO的PIN号。void FRAMCS()    //CS信号拉低{ HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_RESET);   }void nFRAMCS() //CS信号拉高{ HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_SET);}使能写操作:根据FM25CL64的技术手册,进行任何写操作(写状态寄存器和写数据)之前,都需要先使能写操作。void FRAM_WREN(void){ op_code = FM25CL64_WREN; FRAMCS();                                                                                        //将CS信号拉低 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);            //  通过SPI2,向铁电发送使能写操作的操作码0000 0110b。 nFRAMCS();                                                                                      //将CS信号拉高}禁止写操作:void FRAM_WRDI(void){ op_code = FM25CL64_WRDI; FRAMCS();                     HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);           // 原理和使能写操作一样,只是发送的操作码是 禁止写操作码:0000 0100b nFRAMCS();                    }读状态寄存器:uint8_t FRAM_RDSR(void){ uint8_t SRData; op_code = FM25CL64_RDSR;  FRAMCS();                     HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);          // 发送读状态寄存器操作码 00000101b HAL_SPI_Receive(&hspi2,&SRData,1,FRAM_TIMEOUT) ;           //将接收到的数据放在SRData  nFRAMCS();                       // Disable FRAM CS return(SRData);                     }写状态寄存器:void FRAM_WRSR(uint8_t Data){  FRAM_WREN();                   //使能写操作  一定要有!!! FRAMCS();                        // Enable FRAM CS WRITE_SR_FRAME[0] = FM25CL64_WRSR;      //写状态寄存器的数据流是先发写状态寄存器的操作码,紧跟着发送要写的数据。  WRITE_SR_FRAME[1] = Data;                             //所以,建立一个数组,存放操作码和数据。 用SPI将这两个数据连续发送 HAL_SPI_Transmit(&hspi2,&WRITE_SR_FRAME[0],2,FRAM_TIMEOUT);    // &WRITE_SR_FRAME[0]表示第一个数的起始地址,这里的“2”表示要发送两个8位数。 nFRAMCS();                       // Disable FRAM CS FRAM_WRDI();                   // Disable Write operation   //写操作结束后,记得调用禁止写操作}初始化状态寄存器:void InitFRAM(void){ FRAM_WRSR(0x80);               // 其实就是向状态寄存器写0x80这个数。至于为什么是这个,看技术手册就知道了。}读FRAM数据:uint8_t ReadFRAM(uint16_t Addr)   //Addr是要读取的数据在FRAM中存放的地址。 FM25CL64的地址是0000到1FFF。{  uint8_t Data;                                   //声明一个无符号8位数,准备存放读取到的数据 uint8_t AddrH,AddrL;                      //将16位的地址分为两个8位数 AddrH = (Addr>>8); AddrL = (uint8_t)Addr;  FRAMCS();                           // Enable FRAM CS  READFRAME[0] = FM25CL64_READ;    //读FRAM数据的数据流为,向FRAM发送读数据操作码,紧接着为数据地址高8位,紧接着为数据地址低8位。 READFRAME[1] = AddrH;                        //声明一个数组存放读数据操作码,地址高八位,地址低八位 READFRAME[2] = AddrL;  HAL_SPI_Transmit(&hspi2,&READFRAME[0],3,FRAM_TIMEOUT);    // 调用发送函数,将数组里的三个数连续发送 HAL_SPI_Receive(&hspi2,&Data,1,FRAM_TIMEOUT);        //  铁电收到读数据操作后,会返回地址对应的数据。用SPI的接收函数接收数据,存放在Data里。 nFRAMCS();                          // Disable FRAM CS  return(Data);                                                                       }向FRAM写数据:void WriteFRAM(uint16_t Addr, uint8_t Data)    //将Data写在FRAM内部Addr这个地址{  uint8_t AddrH,AddrL;                                       //将16位地址分为两个8位数 AddrH = (Addr>>8); AddrL = (uint8_t)Addr;  FRAM_WREN();                   // 一定要使能写操作 FRAMCS();                        // Enable FRAM CS  WRITEFRAME[0] = FM25CL64_WRITE;            //FM25CL64的写操作数据流是:先发操作码,再发地址高八位,再发地址低八位,再发数据。 WRITEFRAME[1] = AddrH;                                 //用数组依次存放操作码,地址高八位,地址低八位,数据 WRITEFRAME[2] = AddrL; WRITEFRAME[3] = Data;  HAL_SPI_Transmit(&hspi2,&WRITEFRAME[0],4,FRAM_TIMEOUT);           // 调用SPI发送函数,将数组里的四个数据连续发送                   nFRAMCS();                       // Disable FRAM CS FRAM_WRDI();                   // 禁止写操作}头文件#define GPIO_PORT_CS         GPIOA                             //铁电的CS信号连接到了芯片的PA11,在此定义#define GPIO_PIN_CS          GPIO_PIN_11//FM25CL64指令定义#define        FM25CL64_WREN                0x06                //使能#define        FM25CL64_WRDI                0x04                //失能#define        FM25CL64_RDSR                0x05                //读状态#define        FM25CL64_WRSR                0x01                //写状态#define        FM25CL64_READ                0x03                //读数据#define        FM25CL64_WRITE            0x02                //写数据#define FRAM_TIMEOUT   100                                      //SPI超时检测#define READ_WRITE_ADDR    ((uint16_t)0x00FF)     //我自己定义的一个读写数据的起始地址               void InitFRAM(void);                                                    //初始化FRAM状态寄存器函数声明void WriteFRAM(uint16_t Addr, uint8_t Data);            //向FRAM写数据函数声明uint8_t ReadFRAM(uint16_t Addr);                             //从FRAM读数据函数声明写到这里基本就结束了。 在合适的地方,调用合适的读写函数就好。 查看全部

最近项目用到了STM32F3系列芯片,需要通过SPI总线实现对铁电FM25CL64的读写。以前完全没用过SPI,所以前期遇到了不少坑,特开此贴进行记录。正题开始:


SPI配置:因为硬件上,铁电与芯片的SPI2连接,所以配置了SPI2的参数。我用的是软件触发模式。DMA和中断模式还没来得及尝试

引脚:

image.png

模式(Mode):全双工

硬件片选(NSS): 禁用(不太懂这个,后面单独配置了一个GPIO当片选用)

基本参数:

 帧格式: Motorola (还有个TI的,得配合硬件片选信号,没用过)

 数据大小:8bits (这个FM25CL64内部存储是按照8bit存储的)

 第一位:MSB First (和FM25CL64的技术手册保持一致)

时钟参数:

 分频:8 (这个好像影响不大。 FM25CL64最高读写率是20MBit/s ,确保分频后下面的BaudRate小于20就行。)

第一个坑要来了:CPOL和CPHA这两个参数有四种组合模式,这个参数配置一定要满足FM25CL64的要求。 刚开始,就是这个没配置对,能看到SPI总线上有数据发出去,但是铁电不返回数据,下面这组数据经测试没问题。

 CPOL:  LOW

 CPHA: 1 Edge

高级参数:

因为我的SPI总线上只有一个从机,就没用到这些参数。 

image.png

至此SPI的配置就完成了。

生成的代码为:

void MX_SPI2_Init(void)
{

  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 7;
  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */

  /* USER CODE END SPI2_MspInit 0 */
    /* SPI2 clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();
 
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**SPI2 GPIO Configuration   
    PA8     ------> SPI2_SCK
    PA9     ------> SPI2_MISO
    PA10     ------> SPI2_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI2_MspInit 1 */

  /* USER CODE END SPI2_MspInit 1 */
  }
}

下面是写FM25CL64的读写数据程序

static uint8_t WRITEFRAME[4];     //向FRAM写数据的数据流数组
static uint8_t READFRAME[3];      //从FRAM读数据的数据流数组
static uint8_t WRITE_SR_FRAME[2];  //向FRAM写状态寄存器的数据流数组
uint8_t op_code;                          //操作码
static uint8_t i;


片选操作代码:GPIO_PORT_CS是片选信号GPIO的组,GPIO_PIN_CS是片选信号GPIO的PIN号。

void FRAMCS()    //CS信号拉低
{
 HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_RESET);   
}

void nFRAMCS() //CS信号拉高
{
 HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_SET);
}

使能写操作:根据FM25CL64的技术手册,进行任何写操作(写状态寄存器和写数据)之前,都需要先使能写操作。

void FRAM_WREN(void)
{
 op_code = FM25CL64_WREN;

 FRAMCS();                                                                                        //将CS信号拉低

 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);            //  通过SPI2,向铁电发送使能写操作的操作码0000 0110b。
 nFRAMCS();                                                                                      //将CS信号拉高
}

禁止写操作:

void FRAM_WRDI(void)
{
 op_code = FM25CL64_WRDI;
 FRAMCS();                    

 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);           // 原理和使能写操作一样,只是发送的操作码是 禁止写操作码:0000 0100b

 nFRAMCS();                    
}

读状态寄存器:

uint8_t FRAM_RDSR(void)
{
 uint8_t SRData;
 op_code = FM25CL64_RDSR; 

 FRAMCS();                    

 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);          // 发送读状态寄存器操作码 00000101b
 HAL_SPI_Receive(&hspi2,&SRData,1,FRAM_TIMEOUT) ;           //将接收到的数据放在SRData 

 nFRAMCS();                       // Disable FRAM CS

 return(SRData);                    
}

写状态寄存器:

void FRAM_WRSR(uint8_t Data)

 FRAM_WREN();                   //使能写操作  一定要有!!!
 FRAMCS();                        // Enable FRAM CS

 WRITE_SR_FRAME[0] = FM25CL64_WRSR;      //写状态寄存器的数据流是先发写状态寄存器的操作码,紧跟着发送要写的数据。 

 WRITE_SR_FRAME[1] = Data;                             //所以,建立一个数组,存放操作码和数据。 用SPI将这两个数据连续发送
 HAL_SPI_Transmit(&hspi2,&WRITE_SR_FRAME[0],2,FRAM_TIMEOUT);    // &WRITE_SR_FRAME[0]表示第一个数的起始地址,这里的“2”表示要发送两个8位数。
 nFRAMCS();                       // Disable FRAM CS

 FRAM_WRDI();                   // Disable Write operation   //写操作结束后,记得调用禁止写操作
}

初始化状态寄存器:

void InitFRAM(void)
{
 FRAM_WRSR(0x80);               // 其实就是向状态寄存器写0x80这个数。至于为什么是这个,看技术手册就知道了。
}

读FRAM数据:

uint8_t ReadFRAM(uint16_t Addr)   //Addr是要读取的数据在FRAM中存放的地址。 FM25CL64的地址是0000到1FFF。

 uint8_t Data;                                   //声明一个无符号8位数,准备存放读取到的数据
 uint8_t AddrH,AddrL;                      //将16位的地址分为两个8位数
 AddrH = (Addr>>8);
 AddrL = (uint8_t)Addr; 
 FRAMCS();                           // Enable FRAM CS 
 READFRAME[0] = FM25CL64_READ;    //读FRAM数据的数据流为,向FRAM发送读数据操作码,紧接着为数据地址高8位,紧接着为数据地址低8位。
 READFRAME[1] = AddrH;                        //声明一个数组存放读数据操作码,地址高八位,地址低八位
 READFRAME[2] = AddrL; 

 HAL_SPI_Transmit(&hspi2,&READFRAME[0],3,FRAM_TIMEOUT);    // 调用发送函数,将数组里的三个数连续发送

 HAL_SPI_Receive(&hspi2,&Data,1,FRAM_TIMEOUT);        //  铁电收到读数据操作后,会返回地址对应的数据。用SPI的接收函数接收数据,存放在Data里。

 nFRAMCS();                          // Disable FRAM CS 

 return(Data);                                                                       
}

向FRAM写数据:

void WriteFRAM(uint16_t Addr, uint8_t Data)    //将Data写在FRAM内部Addr这个地址

 uint8_t AddrH,AddrL;                                       //将16位地址分为两个8位数
 AddrH = (Addr>>8);
 AddrL = (uint8_t)Addr; 
 FRAM_WREN();                   // 一定要使能写操作

 FRAMCS();                        // Enable FRAM CS 
 WRITEFRAME[0] = FM25CL64_WRITE;            //FM25CL64的写操作数据流是:先发操作码,再发地址高八位,再发地址低八位,再发数据。
 WRITEFRAME[1] = AddrH;                                 //用数组依次存放操作码,地址高八位,地址低八位,数据
 WRITEFRAME[2] = AddrL;
 WRITEFRAME[3] = Data; 
 HAL_SPI_Transmit(&hspi2,&WRITEFRAME[0],4,FRAM_TIMEOUT);           // 调用SPI发送函数,将数组里的四个数据连续发送                  

 nFRAMCS();                       // Disable FRAM CS

 FRAM_WRDI();                   // 禁止写操作
}


头文件

#define GPIO_PORT_CS         GPIOA                             //铁电的CS信号连接到了芯片的PA11,在此定义
#define GPIO_PIN_CS          GPIO_PIN_11
//FM25CL64指令定义
#define        FM25CL64_WREN                0x06                //使能
#define        FM25CL64_WRDI                0x04                //失能
#define        FM25CL64_RDSR                0x05                //读状态
#define        FM25CL64_WRSR                0x01                //写状态
#define        FM25CL64_READ                0x03                //读数据
#define        FM25CL64_WRITE            0x02                //写数据

#define FRAM_TIMEOUT   100                                      //SPI超时检测
#define READ_WRITE_ADDR    ((uint16_t)0x00FF)     //我自己定义的一个读写数据的起始地址
               

void InitFRAM(void);                                                    //初始化FRAM状态寄存器函数声明

void WriteFRAM(uint16_t Addr, uint8_t Data);            //向FRAM写数据函数声明
uint8_t ReadFRAM(uint16_t Addr);                             //从FRAM读数据函数声明



写到这里基本就结束了。 在合适的地方,调用合适的读写函数就好。



用SPI控制OLED 0.96怎么配置cube stm32f401re

问题困惑admin 回复了问题 • 2 人关注 • 1 个回复 • 500 次浏览 • 2018-11-10 21:49 • 来自相关话题

新版cubeMX里面的SPI配置,scs脚没有了?

回复

问题困惑admin 回复了问题 • 2 人关注 • 1 个回复 • 34 次浏览 • 2019-11-03 12:00 • 来自相关话题

为什么用SPI读取flash的id号是多少 通过串口显示的id号为什么会是ffffff

回复

问题困惑起个目标 发起了问题 • 1 人关注 • 0 个回复 • 79 次浏览 • 2019-09-11 08:04 • 来自相关话题

怎么实现timer 触发DMA

回复

问题困惑lost_resister 发起了问题 • 1 人关注 • 0 个回复 • 112 次浏览 • 2019-09-08 10:13 • 来自相关话题

使用DMA 发送 SPI 数据发现,HAL_SPI_Transmit_DMA发送 数据给TFt240*240 的分辨率数据

回复

问题困惑V-Robot 发起了问题 • 1 人关注 • 0 个回复 • 292 次浏览 • 2019-08-13 15:16 • 来自相关话题

STM32作为SPI从机,调用HAL_SPI_Transmit发送数据后,后续主机发送SCK,MISO总线依然有数据

回复

问题困惑wuss1021521@163.com 发起了问题 • 1 人关注 • 0 个回复 • 422 次浏览 • 2019-07-24 11:18 • 来自相关话题

STM32如何通过全双工或者双线SPI与高速(1MHz以上)外部ADC通信?

回复

问题困惑hlpingygy 发起了问题 • 1 人关注 • 0 个回复 • 605 次浏览 • 2019-06-20 09:20 • 来自相关话题

f427 spi issue

回复

问题困惑eone 发起了问题 • 1 人关注 • 0 个回复 • 141 次浏览 • 2019-06-18 19:02 • 来自相关话题

STM32L476 使用SPI 函数HAL_SPI_TransmitReceive()读出数据错误,求解!

回复

问题困惑777、 回复了问题 • 2 人关注 • 1 个回复 • 643 次浏览 • 2019-06-04 22:29 • 来自相关话题

关于L071HAI库使用硬件SPI出现的死循环等问题?

回复

问题困惑 K . 发起了问题 • 1 人关注 • 0 个回复 • 316 次浏览 • 2019-04-26 15:34 • 来自相关话题

1.44寸tft屏刷屏速度慢的问题,希望大神看看是什么问题

回复

问题困惑此ID已注册 回复了问题 • 3 人关注 • 2 个回复 • 716 次浏览 • 2019-04-16 10:06 • 来自相关话题

STM32F205RCT6,外部中断EXIT4初始化必须在SPI1和SPI3之后。

回复

问题困惑jeff_fh 发起了问题 • 1 人关注 • 0 个回复 • 284 次浏览 • 2019-03-27 10:12 • 来自相关话题

用SPI控制OLED 0.96怎么配置cube stm32f401re

回复

问题困惑admin 回复了问题 • 2 人关注 • 1 个回复 • 500 次浏览 • 2018-11-10 21:49 • 来自相关话题

调试stm32F103C8t6的SPI1遇到的问题和解决途径

回复

经验分享admin 回复了问题 • 2 人关注 • 3 个回复 • 5367 次浏览 • 2017-11-24 15:05 • 来自相关话题

(转)通过配置SPI1完成W25X16的读写

回复

软件教程²º¹5¾ 回复了问题 • 6 人关注 • 2 个回复 • 3174 次浏览 • 2017-06-07 12:00 • 来自相关话题

STM32HAL库SPI的16位数据中断发送与接收

经验分享老是忙 发表了文章 • 0 个评论 • 2122 次浏览 • 2019-01-29 11:56 • 来自相关话题

HAL库的SPI发送接收函数的确令人迷惑,明明支持16位传输,却必须使用8位的指针。如果没能正确理解SPI发送接收函数,很容易导致程序接入HardFault_Handler中断死循环。    最近用到STM32F407的HAL编程,SPI通讯的外设要求16位通讯,对HAL库的SPI的16位通讯做了个深入研究。发现HAL库提供的函数除了入口参数含义不太明确,还是很好用的。下面以中断方式发送接收
uint16_t 数据为例,阐述函数调用的过程和要点。   1、
首先,建立发送和接收缓冲区,用16位的数组: uint16_t SPI_TxBuff[1],
SPI_RxBuff[1];   2、SPI初始化: HAL_SPI_DeInit(&hspi1);   //SPI1 复位 hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction =
SPI_DIRECTION_2LINES;   //双向通讯 hspi1.Init.DataSize =
SPI_DATASIZE_16BIT;       //16位传输 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler =
SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation =
SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi1) != HAL_OK) {   Error_Handler(); } 其中,DataSize 设为16位。  3、在程序中的调用: SPI_TxBuff[0] =
data;   //data
是uint16_t 类型,复制到发送缓存。 HAL_SPI_TransmitReceive_IT(&hspi1,
(uint8_t*)SPI_TxBuff, (uint8_t*)SPI_RxBuff, 1);
//中断方式的传输 注意1:一定要用 (uint8_t*)
临时改变SPI_TxBuff和SPI_RxBuff的类型。 经对函数内部分析,实际执行时,将把这两个指针重新变换为( uint16_t *) 。 注意2:DataSize 必须是按 uint16_t
数据的数量来设置,而不是按uint8_t, 。此例中,要传输一个16位数据,所以 DataSize 设为1 。.  4、修改SPI中断程序(在stm32f4xx_it.c中)。void SPI1_IRQHandler(void){  HAL_SPI_IRQHandler(&hspi1);    //如果接收完毕,处理数据   if(HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY)   {      IT_ProcessingData();   //接收完成的数据已经保存在 SPI_RxBuff[0], 调用此函数进行处理   } }  注意:
HAL_SPI_TransmitReceive_IT() 函数将产生两次中断!
第一次开始发送(接收)数据;当发送(接收)完成后产生第二次中断。我们在中断程序中,用     HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY来判断是否是第二次,也就是传输完成后的中断,是的话,就调用数据处理函数。 查看全部

HAL库的SPI发送接收函数的确令人迷惑,明明支持16位传输,却必须使用8位的指针。如果没能正确理解SPI发送接收函数,很容易导致程序接入HardFault_Handler中断死循环。

    最近用到STM32F407的HAL编程,SPI通讯的外设要求16位通讯,对HAL库的SPI的16位通讯做了个深入研究。发现HAL库提供的函数除了入口参数含义不太明确,还是很好用的。下面以中断方式发送接收
uint16_t 数据为例,阐述函数调用的过程和要点。

   1、
首先,建立发送和接收缓冲区,用16位的数组:

 uint16_t SPI_TxBuff[1],
SPI_RxBuff[1];


   2、SPI初始化:

 HAL_SPI_DeInit(&hspi1);   //SPI1 复位

 hspi1.Instance = SPI1;
 hspi1.Init.Mode = SPI_MODE_MASTER;
 hspi1.Init.Direction =
SPI_DIRECTION_2LINES;   //双向通讯
 hspi1.Init.DataSize =
SPI_DATASIZE_16BIT;       //16位传输
 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
 hspi1.Init.NSS = SPI_NSS_SOFT;
 hspi1.Init.BaudRatePrescaler =
SPI_BAUDRATEPRESCALER_16;
 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
 hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
 hspi1.Init.CRCCalculation =
SPI_CRCCALCULATION_DISABLE;
 hspi1.Init.CRCPolynomial = 10;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
   Error_Handler();
 }

 其中,DataSize 设为16位。


  3、在程序中的调用:

 SPI_TxBuff[0] =
data;   //data
是uint16_t 类型,复制到发送缓存。
 HAL_SPI_TransmitReceive_IT(&hspi1,
(uint8_t*)SPI_TxBuff, (uint8_t*)SPI_RxBuff, 1);
//中断方式的传输


 注意1:一定要用 (uint8_t*)
临时改变SPI_TxBuff和SPI_RxBuff的类型。 经对函数内部分析,实际执行时,将把这两个指针重新变换为( uint16_t *) 。

 注意2:DataSize 必须是按 uint16_t
数据的数量来设置,而不是按uint8_t, 。此例中,要传输一个16位数据,所以 DataSize 设为1 。.


  4、修改SPI中断程序(在stm32f4xx_it.c中)。

void SPI1_IRQHandler(void)
{
  HAL_SPI_IRQHandler(&hspi1);
 
   //如果接收完毕,处理数据
   if(HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY)
   {
      IT_ProcessingData();   //接收完成的数据已经保存在 SPI_RxBuff[0], 调用此函数进行处理
   }
 
}

  注意:
HAL_SPI_TransmitReceive_IT() 函数将产生两次中断!
第一次开始发送(接收)数据;当发送(接收)完成后产生第二次中断。我们在中断程序中,用

     HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY

来判断是否是第二次,也就是传输完成后的中断,是的话,就调用数据处理函数。



STM32F3系列运用HAL库实现SPI读写铁电FM25CL64

经验分享牙子男男 发表了文章 • 3 个评论 • 1709 次浏览 • 2019-01-13 15:55 • 来自相关话题

最近项目用到了STM32F3系列芯片,需要通过SPI总线实现对铁电FM25CL64的读写。以前完全没用过SPI,所以前期遇到了不少坑,特开此贴进行记录。正题开始:SPI配置:因为硬件上,铁电与芯片的SPI2连接,所以配置了SPI2的参数。我用的是软件触发模式。DMA和中断模式还没来得及尝试引脚:模式(Mode):全双工硬件片选(NSS): 禁用(不太懂这个,后面单独配置了一个GPIO当片选用)基本参数: 帧格式: Motorola (还有个TI的,得配合硬件片选信号,没用过) 数据大小:8bits (这个FM25CL64内部存储是按照8bit存储的) 第一位:MSB First (和FM25CL64的技术手册保持一致)时钟参数: 分频:8 (这个好像影响不大。 FM25CL64最高读写率是20MBit/s ,确保分频后下面的BaudRate小于20就行。)第一个坑要来了:CPOL和CPHA这两个参数有四种组合模式,这个参数配置一定要满足FM25CL64的要求。 刚开始,就是这个没配置对,能看到SPI总线上有数据发出去,但是铁电不返回数据,下面这组数据经测试没问题。 CPOL:  LOW CPHA: 1 Edge高级参数:因为我的SPI总线上只有一个从机,就没用到这些参数。 至此SPI的配置就完成了。生成的代码为:void MX_SPI2_Init(void){  hspi2.Instance = SPI2;  hspi2.Init.Mode = SPI_MODE_MASTER;  hspi2.Init.Direction = SPI_DIRECTION_2LINES;  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;  hspi2.Init.NSS = SPI_NSS_SOFT;  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;  hspi2.Init.CRCPolynomial = 7;  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;  hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;  if (HAL_SPI_Init(&hspi2) != HAL_OK)  {    Error_Handler();  }}void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){  GPIO_InitTypeDef GPIO_InitStruct = {0};  if(spiHandle->Instance==SPI2)  {  /* USER CODE BEGIN SPI2_MspInit 0 */  /* USER CODE END SPI2_MspInit 0 */    /* SPI2 clock enable */    __HAL_RCC_SPI2_CLK_ENABLE();      __HAL_RCC_GPIOA_CLK_ENABLE();    /**SPI2 GPIO Configuration        PA8     ------> SPI2_SCK    PA9     ------> SPI2_MISO    PA10     ------> SPI2_MOSI     */    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;    GPIO_InitStruct.Pull = GPIO_NOPULL;    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);  /* USER CODE BEGIN SPI2_MspInit 1 */  /* USER CODE END SPI2_MspInit 1 */  }}下面是写FM25CL64的读写数据程序static uint8_t WRITEFRAME[4];     //向FRAM写数据的数据流数组static uint8_t READFRAME[3];      //从FRAM读数据的数据流数组static uint8_t WRITE_SR_FRAME[2];  //向FRAM写状态寄存器的数据流数组uint8_t op_code;                          //操作码static uint8_t i;片选操作代码:GPIO_PORT_CS是片选信号GPIO的组,GPIO_PIN_CS是片选信号GPIO的PIN号。void FRAMCS()    //CS信号拉低{ HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_RESET);   }void nFRAMCS() //CS信号拉高{ HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_SET);}使能写操作:根据FM25CL64的技术手册,进行任何写操作(写状态寄存器和写数据)之前,都需要先使能写操作。void FRAM_WREN(void){ op_code = FM25CL64_WREN; FRAMCS();                                                                                        //将CS信号拉低 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);            //  通过SPI2,向铁电发送使能写操作的操作码0000 0110b。 nFRAMCS();                                                                                      //将CS信号拉高}禁止写操作:void FRAM_WRDI(void){ op_code = FM25CL64_WRDI; FRAMCS();                     HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);           // 原理和使能写操作一样,只是发送的操作码是 禁止写操作码:0000 0100b nFRAMCS();                    }读状态寄存器:uint8_t FRAM_RDSR(void){ uint8_t SRData; op_code = FM25CL64_RDSR;  FRAMCS();                     HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);          // 发送读状态寄存器操作码 00000101b HAL_SPI_Receive(&hspi2,&SRData,1,FRAM_TIMEOUT) ;           //将接收到的数据放在SRData  nFRAMCS();                       // Disable FRAM CS return(SRData);                     }写状态寄存器:void FRAM_WRSR(uint8_t Data){  FRAM_WREN();                   //使能写操作  一定要有!!! FRAMCS();                        // Enable FRAM CS WRITE_SR_FRAME[0] = FM25CL64_WRSR;      //写状态寄存器的数据流是先发写状态寄存器的操作码,紧跟着发送要写的数据。  WRITE_SR_FRAME[1] = Data;                             //所以,建立一个数组,存放操作码和数据。 用SPI将这两个数据连续发送 HAL_SPI_Transmit(&hspi2,&WRITE_SR_FRAME[0],2,FRAM_TIMEOUT);    // &WRITE_SR_FRAME[0]表示第一个数的起始地址,这里的“2”表示要发送两个8位数。 nFRAMCS();                       // Disable FRAM CS FRAM_WRDI();                   // Disable Write operation   //写操作结束后,记得调用禁止写操作}初始化状态寄存器:void InitFRAM(void){ FRAM_WRSR(0x80);               // 其实就是向状态寄存器写0x80这个数。至于为什么是这个,看技术手册就知道了。}读FRAM数据:uint8_t ReadFRAM(uint16_t Addr)   //Addr是要读取的数据在FRAM中存放的地址。 FM25CL64的地址是0000到1FFF。{  uint8_t Data;                                   //声明一个无符号8位数,准备存放读取到的数据 uint8_t AddrH,AddrL;                      //将16位的地址分为两个8位数 AddrH = (Addr>>8); AddrL = (uint8_t)Addr;  FRAMCS();                           // Enable FRAM CS  READFRAME[0] = FM25CL64_READ;    //读FRAM数据的数据流为,向FRAM发送读数据操作码,紧接着为数据地址高8位,紧接着为数据地址低8位。 READFRAME[1] = AddrH;                        //声明一个数组存放读数据操作码,地址高八位,地址低八位 READFRAME[2] = AddrL;  HAL_SPI_Transmit(&hspi2,&READFRAME[0],3,FRAM_TIMEOUT);    // 调用发送函数,将数组里的三个数连续发送 HAL_SPI_Receive(&hspi2,&Data,1,FRAM_TIMEOUT);        //  铁电收到读数据操作后,会返回地址对应的数据。用SPI的接收函数接收数据,存放在Data里。 nFRAMCS();                          // Disable FRAM CS  return(Data);                                                                       }向FRAM写数据:void WriteFRAM(uint16_t Addr, uint8_t Data)    //将Data写在FRAM内部Addr这个地址{  uint8_t AddrH,AddrL;                                       //将16位地址分为两个8位数 AddrH = (Addr>>8); AddrL = (uint8_t)Addr;  FRAM_WREN();                   // 一定要使能写操作 FRAMCS();                        // Enable FRAM CS  WRITEFRAME[0] = FM25CL64_WRITE;            //FM25CL64的写操作数据流是:先发操作码,再发地址高八位,再发地址低八位,再发数据。 WRITEFRAME[1] = AddrH;                                 //用数组依次存放操作码,地址高八位,地址低八位,数据 WRITEFRAME[2] = AddrL; WRITEFRAME[3] = Data;  HAL_SPI_Transmit(&hspi2,&WRITEFRAME[0],4,FRAM_TIMEOUT);           // 调用SPI发送函数,将数组里的四个数据连续发送                   nFRAMCS();                       // Disable FRAM CS FRAM_WRDI();                   // 禁止写操作}头文件#define GPIO_PORT_CS         GPIOA                             //铁电的CS信号连接到了芯片的PA11,在此定义#define GPIO_PIN_CS          GPIO_PIN_11//FM25CL64指令定义#define        FM25CL64_WREN                0x06                //使能#define        FM25CL64_WRDI                0x04                //失能#define        FM25CL64_RDSR                0x05                //读状态#define        FM25CL64_WRSR                0x01                //写状态#define        FM25CL64_READ                0x03                //读数据#define        FM25CL64_WRITE            0x02                //写数据#define FRAM_TIMEOUT   100                                      //SPI超时检测#define READ_WRITE_ADDR    ((uint16_t)0x00FF)     //我自己定义的一个读写数据的起始地址               void InitFRAM(void);                                                    //初始化FRAM状态寄存器函数声明void WriteFRAM(uint16_t Addr, uint8_t Data);            //向FRAM写数据函数声明uint8_t ReadFRAM(uint16_t Addr);                             //从FRAM读数据函数声明写到这里基本就结束了。 在合适的地方,调用合适的读写函数就好。 查看全部

最近项目用到了STM32F3系列芯片,需要通过SPI总线实现对铁电FM25CL64的读写。以前完全没用过SPI,所以前期遇到了不少坑,特开此贴进行记录。正题开始:


SPI配置:因为硬件上,铁电与芯片的SPI2连接,所以配置了SPI2的参数。我用的是软件触发模式。DMA和中断模式还没来得及尝试

引脚:

image.png

模式(Mode):全双工

硬件片选(NSS): 禁用(不太懂这个,后面单独配置了一个GPIO当片选用)

基本参数:

 帧格式: Motorola (还有个TI的,得配合硬件片选信号,没用过)

 数据大小:8bits (这个FM25CL64内部存储是按照8bit存储的)

 第一位:MSB First (和FM25CL64的技术手册保持一致)

时钟参数:

 分频:8 (这个好像影响不大。 FM25CL64最高读写率是20MBit/s ,确保分频后下面的BaudRate小于20就行。)

第一个坑要来了:CPOL和CPHA这两个参数有四种组合模式,这个参数配置一定要满足FM25CL64的要求。 刚开始,就是这个没配置对,能看到SPI总线上有数据发出去,但是铁电不返回数据,下面这组数据经测试没问题。

 CPOL:  LOW

 CPHA: 1 Edge

高级参数:

因为我的SPI总线上只有一个从机,就没用到这些参数。 

image.png

至此SPI的配置就完成了。

生成的代码为:

void MX_SPI2_Init(void)
{

  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 7;
  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */

  /* USER CODE END SPI2_MspInit 0 */
    /* SPI2 clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();
 
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**SPI2 GPIO Configuration   
    PA8     ------> SPI2_SCK
    PA9     ------> SPI2_MISO
    PA10     ------> SPI2_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI2_MspInit 1 */

  /* USER CODE END SPI2_MspInit 1 */
  }
}

下面是写FM25CL64的读写数据程序

static uint8_t WRITEFRAME[4];     //向FRAM写数据的数据流数组
static uint8_t READFRAME[3];      //从FRAM读数据的数据流数组
static uint8_t WRITE_SR_FRAME[2];  //向FRAM写状态寄存器的数据流数组
uint8_t op_code;                          //操作码
static uint8_t i;


片选操作代码:GPIO_PORT_CS是片选信号GPIO的组,GPIO_PIN_CS是片选信号GPIO的PIN号。

void FRAMCS()    //CS信号拉低
{
 HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_RESET);   
}

void nFRAMCS() //CS信号拉高
{
 HAL_GPIO_WritePin(GPIO_PORT_CS,GPIO_PIN_CS,GPIO_PIN_SET);
}

使能写操作:根据FM25CL64的技术手册,进行任何写操作(写状态寄存器和写数据)之前,都需要先使能写操作。

void FRAM_WREN(void)
{
 op_code = FM25CL64_WREN;

 FRAMCS();                                                                                        //将CS信号拉低

 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);            //  通过SPI2,向铁电发送使能写操作的操作码0000 0110b。
 nFRAMCS();                                                                                      //将CS信号拉高
}

禁止写操作:

void FRAM_WRDI(void)
{
 op_code = FM25CL64_WRDI;
 FRAMCS();                    

 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);           // 原理和使能写操作一样,只是发送的操作码是 禁止写操作码:0000 0100b

 nFRAMCS();                    
}

读状态寄存器:

uint8_t FRAM_RDSR(void)
{
 uint8_t SRData;
 op_code = FM25CL64_RDSR; 

 FRAMCS();                    

 HAL_SPI_Transmit(&hspi2,&op_code,1,FRAM_TIMEOUT);          // 发送读状态寄存器操作码 00000101b
 HAL_SPI_Receive(&hspi2,&SRData,1,FRAM_TIMEOUT) ;           //将接收到的数据放在SRData 

 nFRAMCS();                       // Disable FRAM CS

 return(SRData);                    
}

写状态寄存器:

void FRAM_WRSR(uint8_t Data)

 FRAM_WREN();                   //使能写操作  一定要有!!!
 FRAMCS();                        // Enable FRAM CS

 WRITE_SR_FRAME[0] = FM25CL64_WRSR;      //写状态寄存器的数据流是先发写状态寄存器的操作码,紧跟着发送要写的数据。 

 WRITE_SR_FRAME[1] = Data;                             //所以,建立一个数组,存放操作码和数据。 用SPI将这两个数据连续发送
 HAL_SPI_Transmit(&hspi2,&WRITE_SR_FRAME[0],2,FRAM_TIMEOUT);    // &WRITE_SR_FRAME[0]表示第一个数的起始地址,这里的“2”表示要发送两个8位数。
 nFRAMCS();                       // Disable FRAM CS

 FRAM_WRDI();                   // Disable Write operation   //写操作结束后,记得调用禁止写操作
}

初始化状态寄存器:

void InitFRAM(void)
{
 FRAM_WRSR(0x80);               // 其实就是向状态寄存器写0x80这个数。至于为什么是这个,看技术手册就知道了。
}

读FRAM数据:

uint8_t ReadFRAM(uint16_t Addr)   //Addr是要读取的数据在FRAM中存放的地址。 FM25CL64的地址是0000到1FFF。

 uint8_t Data;                                   //声明一个无符号8位数,准备存放读取到的数据
 uint8_t AddrH,AddrL;                      //将16位的地址分为两个8位数
 AddrH = (Addr>>8);
 AddrL = (uint8_t)Addr; 
 FRAMCS();                           // Enable FRAM CS 
 READFRAME[0] = FM25CL64_READ;    //读FRAM数据的数据流为,向FRAM发送读数据操作码,紧接着为数据地址高8位,紧接着为数据地址低8位。
 READFRAME[1] = AddrH;                        //声明一个数组存放读数据操作码,地址高八位,地址低八位
 READFRAME[2] = AddrL; 

 HAL_SPI_Transmit(&hspi2,&READFRAME[0],3,FRAM_TIMEOUT);    // 调用发送函数,将数组里的三个数连续发送

 HAL_SPI_Receive(&hspi2,&Data,1,FRAM_TIMEOUT);        //  铁电收到读数据操作后,会返回地址对应的数据。用SPI的接收函数接收数据,存放在Data里。

 nFRAMCS();                          // Disable FRAM CS 

 return(Data);                                                                       
}

向FRAM写数据:

void WriteFRAM(uint16_t Addr, uint8_t Data)    //将Data写在FRAM内部Addr这个地址

 uint8_t AddrH,AddrL;                                       //将16位地址分为两个8位数
 AddrH = (Addr>>8);
 AddrL = (uint8_t)Addr; 
 FRAM_WREN();                   // 一定要使能写操作

 FRAMCS();                        // Enable FRAM CS 
 WRITEFRAME[0] = FM25CL64_WRITE;            //FM25CL64的写操作数据流是:先发操作码,再发地址高八位,再发地址低八位,再发数据。
 WRITEFRAME[1] = AddrH;                                 //用数组依次存放操作码,地址高八位,地址低八位,数据
 WRITEFRAME[2] = AddrL;
 WRITEFRAME[3] = Data; 
 HAL_SPI_Transmit(&hspi2,&WRITEFRAME[0],4,FRAM_TIMEOUT);           // 调用SPI发送函数,将数组里的四个数据连续发送                  

 nFRAMCS();                       // Disable FRAM CS

 FRAM_WRDI();                   // 禁止写操作
}


头文件

#define GPIO_PORT_CS         GPIOA                             //铁电的CS信号连接到了芯片的PA11,在此定义
#define GPIO_PIN_CS          GPIO_PIN_11
//FM25CL64指令定义
#define        FM25CL64_WREN                0x06                //使能
#define        FM25CL64_WRDI                0x04                //失能
#define        FM25CL64_RDSR                0x05                //读状态
#define        FM25CL64_WRSR                0x01                //写状态
#define        FM25CL64_READ                0x03                //读数据
#define        FM25CL64_WRITE            0x02                //写数据

#define FRAM_TIMEOUT   100                                      //SPI超时检测
#define READ_WRITE_ADDR    ((uint16_t)0x00FF)     //我自己定义的一个读写数据的起始地址
               

void InitFRAM(void);                                                    //初始化FRAM状态寄存器函数声明

void WriteFRAM(uint16_t Addr, uint8_t Data);            //向FRAM写数据函数声明
uint8_t ReadFRAM(uint16_t Addr);                             //从FRAM读数据函数声明



写到这里基本就结束了。 在合适的地方,调用合适的读写函数就好。