CW32系列单片机SPI读写外部FLASH介绍

cathy的头像
cathy 发布于:周三, 03/29/2023 - 11:04 ,关键词:

串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信。CW32L083 内部集成 2 个串行外设 SPI 接口,支持双向全双工、单线半双工和单工通信模式,可配置 MCU 作为 主机或从机,支持多主机通信模式,支持直接内存访问(DMA)。

一、主要功能

• 支持主机模式、从机模式 

• 支持全双工、单线半双工、单工 

• 可选的 4 位到 16 位数据帧宽度 

• 支持收发数据 LSB 或 MSB 在前 

• 可编程时钟极性和时钟相位

• 主机模式下通信速率高达 PCLK/2 

• 从机模式下通信速率高达 PCLK/4 

• 支持多机通信模式 

• 8 个带标志位的中断源 

• 支持直接内存访问 (DMA)

1.功能框图

1.png

SPI 一般通过 4 个引脚与外部设备相连:

• MOSI 主机输出 / 从机输入,用于主机模式下的数据发送和从机模式下的数据接收; 

• MISO 主机输入 / 从机输出,用于主机模式下的数据接收和从机模式下的数据发送; 

• SCK 同步串行时钟,主机时钟输出和从机时钟输入,发送或接收主机同步时钟; 

• CS 从机选择,也用于多机通信时总线冲突检测

CW32L083 支持用户灵活选择 GPIO 作为 SPI 通信引脚,通过 AFR 功能复用实现,具体 SPI 引脚请参考数据手册中的引脚定义。

2.全双工模式

SPI 支持全双工通信模式,在该模式下,主机和从机的移位寄存器通过 MOSI、MISO 两条单向数据线进行连接, 在主机提供的 SCK 时钟信号的控制下同时进行两个方向的数据传输。设置控制寄存器 SPIx_CR1 的 MODE 位域为 0x0,使 SPI 工作于全双工通信模式。

2.png

二、FLASH存储器及电路连接

3.png

W25Q64JVSSIQ为华邦NOR型FLASH,存储容量为8Mbytes,每页256bytes,总共有32768个可编程页,最大一次可编程大小为256bytes。一次擦除大小可以为4K、32K、64K字节(K=1024)或者全擦除。支持标准SPI、Dual SPI、Quad SPI,最大操作时钟为133MHz。

芯片SPI接口时序要求如下:

4.png

由接口时序图可知,空闲电平可高可低,数据在第1个时钟上升沿采样。对应的可以选择用模式0或者模式3和MCU进行对接。

三、实例演示

本实例演示MCU(CW32L083)芯片对外部SPI FLASH(W25Q64JVSSIQ)进行读写访问,SPI接口选择模式0。

1.配置RCC和GPIO

void RCC_Configuration(void)
{
  RCC_HSI_Enable(RCC_HSIOSC_DIV2); //SYSCLK = HSI = 24MHz = HCLK = PCLK
  //外设时钟使能
  RCC_AHBPeriphClk_Enable(DEBUG_UART_GPIO_CLK | RCC_AHB_PERIPH_GPIOC, ENABLE);
  DEBUG_UART_APBClkENx(DEBUG_UART_CLK, ENABLE);
}
             
void GPIO_Configuration(void)//包含UART TX RX 复用和LED开关
{
  GPIO_InitTypeDef GPIO_InitStructure = {0};
  DEBUG_UART_AFTX; //UART TX RX 复用
  DEBUG_UART_AFRX;
  GPIO_InitStructure.Pins = DEBUG_UART_TX_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_Init(DEBUG_UART_TX_GPIO_PORT, &GPIO_InitStructure);
  GPIO_InitStructure.Pins = DEBUG_UART_RX_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
  GPIO_Init(DEBUG_UART_RX_GPIO_PORT, &GPIO_InitStructure);
  GPIO_InitStructure.Pins = GPIO_PIN_3 | GPIO_PIN_2; //PC3 LED1 / PC2 LED2
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_Init(CW_GPIOC, &GPIO_InitStructure);
  PC03_SETLOW();//LED灭
  PC02_SETLOW();
}

2.配置UART 

void UART_Configuration(void)
{
   UART_InitTypeDef UART_InitStructure = {0};
   UART_InitStructure.UART_BaudRate = DEBUG_UART_BaudRate; // 波特率
   UART_InitStructure.UART_Over = UART_Over_16; // 采样方式
   UART_InitStructure.UART_Source = UART_Source_PCLK; // 传输时钟源UCLK
   UART_InitStructure.UART_UclkFreq = DEBUG_UART_UclkFreq; // 传输时钟UCLK频率
   UART_InitStructure.UART_StartBit = UART_StartBit_FE; // 起始位判定方式
   UART_InitStructure.UART_StopBits = UART_StopBits_1; // 停止位长度
   UART_InitStructure.UART_Parity = UART_Parity_No ; // 校验方式
   UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None; 
   //硬件流控
   UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; // 发送/接收使能
   UART_Init(DEBUG_UARTx, &UART_InitStructure);
}

3.配置SPI接口

void SPI_FLASH_Init(void)
{
  SPI_InitTypeDef  SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  /************************GPIO Configuration***********************/
  RCC_AHBPeriphClk_Enable(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK | FLASH_SPI_MOSI_GPIO_CLK | FLASH_SPI_CS_GPIO_CLK, ENABLE);
  FLASH_SPI_APBClkENx(FLASH_SPI_CLK, ENABLE);
  //SPI SCK MOSI MISO 复用
  FLASH_SPI_AF_SCK;
  FLASH_SPI_AF_MISO;  
  FLASH_SPI_AF_MOSI;     
  //CS
  GPIO_InitStructure.Pins = FLASH_SPI_CS_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  GPIO_Init(FLASH_SPI_CS_GPIO_PORT, &GPIO_InitStructure);
  //SCK   
  GPIO_InitStructure.Pins = FLASH_SPI_SCK_GPIO_PIN;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
  //MOSI
  GPIO_InitStructure.Pins = FLASH_SPI_MOSI_GPIO_PIN;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
  //MISO
  GPIO_InitStructure.Pins = FLASH_SPI_MISO_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
   //拉高CS
  FLASH_SPI_CS_HIGH();
  /************************SPI Configuration***********************/
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;    // 双线全双工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;              // 主机模式
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   // 帧数据长度为8bit
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;       // 时钟空闲电平为低
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;     // 第1边沿采样
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        // 片选信号由SSI寄存器控制
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; 
  // 波特率为PCLK的8分频
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    // 最高有效位 MSB 收发在前
  SPI_InitStructure.SPI_Speed = SPI_Speed_High;    // 低速SPI
  SPI_Init(FLASH_SPIx, &SPI_InitStructure);
}

4.比较两个缓冲区数据

TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
  while(BufferLength--)
  {
     if(*pBuffer1 != *pBuffer2)
     {
       return FAILED;                 
     }
     pBuffer1++;
     pBuffer2++;               
  }
  return PASSED;
}

5.将C库printf函数重新定位到UART

PUTCHAR_PROTOTYPE
{
   UART_SendData_8bit(DEBUG_UARTx, (uint8_t)ch);// 通过UARTx发送一个数据(8bit)
   while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
   return ch;
}
size_t __write(int handle, const unsigned char * buffer, size_t size)
{
   size_t nChars = 0;     
                 
   if (buffer == 0)
   {//应该刷新内部缓冲区 "handle" == -1
     return 0;
   }
   for (size != 0; --size)
   {
     UART_SendData_8bit(DEBUG_UARTx, *buffer++);
     while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
     ++nChars;
   }
   return nChars;
}

6.主程序实现SPI对FLASH的读写(具体对FLASH读写可参考cw32_eval_spi_flash.c)

int32_t main(void)
{ 
  RCC_Configuration();//配置RCC
  GPIO_Configuration();//配置GPIO
  UART_Configuration();//配置UART
  SPI_FLASH_Init();//配置SPI
  printf("\r\nCW32L083 SPI FLASH Example\r\n");
  DeviceID = SPI_FLASH_DeviceID();//读取Device ID
  ManufactDeviceID = SPI_FLASH_ManufactDeviceID();//读取Manufacturer / Device ID
  JedecID = SPI_FLASH_JedecID();//读取JEDEC ID
  SPI_FLASH_UniqueID(UniqueID);// 读取Unique ID (64bit)
  printf("\r\nDeviceID = 0x%X\r\nManufactDeviceID = 0x%X\r\nJedecID = 0x%X", DeviceID, ManufactDeviceID, JedecID);
  printf("\r\nUniqueID = 0x ");
  for(uint8_t i = 0; i<8; i++)
  {
     printf("%X ", UniqueID[i]);
  }
  
  if(JedecID == sJedecID)  /* Check JEDEC ID */
  {
     printf("\r\n\nFLASH Detected\r\n");;
     SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress); //擦除扇区 4KB
     SPI_FLASH_BufferWrite(TxBuffer, FLASH_WriteAddress, BufferSize); //写数据
     printf("\r\n写入的数据为:%s\r\n", TxBuffer);
     SPI_FLASH_BufferRead(RxBuffer, FLASH_ReadAddress, BufferSize); //读数据
     printf("\r\n读出的数据为:%s\r\n", RxBuffer);
     //检查写入的数据与读出的数据是否一致
     TransferStatus = Buffercmp(TxBuffer, RxBuffer, BufferSize);
     if(TransferStatus == PASSED)
     {
        PC03_SETHIGH();
        printf("\r\nFLASH Success\r\n");
     }
     else
     {
        PC02_SETHIGH();
        printf("\r\nFLASH Error 1\r\n");
     }
     SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress); //擦除扇区 4KB
     SPI_FLASH_BufferRead(RxBuffer, FLASH_ReadAddress, BufferSize); //读数据
     for(uint8_t j = 0; j < BufferSize; j++)
     {
        if(RxBuffer[j] != 0xFF)
        {
           PC02_SETHIGH();
           printf("\r\nFLASH Error 2\r\n");
        }
     }                
   }
   else// (FlashID != sFLASH_ID)
   {
     PC02_SETHIGH();
     printf("\r\nFLASH Error 3\r\n");
   }
   while(1)
   {
   }
}

7.演示说明

程序读外部FLASH的DeviceID、ManufactDeviceID、JedecID、UniqueID并打印读取到的芯片相关信息,然后依次进行写入数据、读取数据、比较数据、擦除SECTOR、读取数据等操作,当读写正确时,通过UART打印“FLASH Success”,LED1亮。当读取的JedecID不正确,写入读取内容不一致,擦除后读取数据不为全FF时,打印“FLASH Error 2”,LED2亮。如下图所示:

5.png

关于CW32更多详细信息,请访问官方网站www.whxy.com。

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 281