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

【实测教程】STM32L431之SPI Flash读写实验

[复制链接]
STMCU小助手 发布时间:2023-1-13 19:00
   一、开发板平台简介:
1、开发板资源简介

(1)开发板主芯片型号:STM32L431RCT6

(2)开发板主芯片封装:LQFP-64_10x10x05P

(3)开发板主芯片内核:ARM® Cortex®-M4

(4)开发板主芯片主频:80MHz

(5)开发板主芯片Flash大小:256KB

(6)开发板主芯片RAM大小:64KB

bb91345227434ee7a43a34bf3fe366e6.png

929c0cce04aa4115a8623703982bf3e1.png

2、LED灯资源
(1) STM32L431RCT6开发板共5个LED灯资源,其中一个红色LED为系统指示灯,指示开发板供电系统是否正常,如供电系统正常,红色LED为上电常亮状态,硬件原理图如下图所示:

0f177691c5814f16a508bcfa747f87ac.png

(2)其他四个LED灯为黄绿色可控LED,高电平点亮、低电平熄灭,计划用LED常亮验证看门狗的作用,硬件原理图如下图所示:

e923ad767b8448b7ac37c4ed693724bd.png

​3、串口printf打印工作原理
 串口全称为串行通讯接口,即数据在通信线上一次传输一位,按先后一定顺序传输。我们通常所说的单片机串口准确来说应该是串行异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART),使用TTL电平,串口需要RXD、TXD、GND三根线进行通信。

     (1)我们选用的STM32L431RCT6开发板串口1已通过USB转TLL串口芯片CH340G引出,使用时,只需要用公对公USB线连接电脑即可(注意也得需要安装CH340G驱动),后期验证试验也可使用该串口作为debug串口。

     (2)开发板上的其他串口已通过排针引出,为TTL电平,通信的时候需要注意选择对应的电平模块,如USB转TTL串口模块等。

TTL转CH340串口,硬件原理图如下所示:

4df1b489cb944c26b55013001061675d.png

4、SPI Flash简介
(1)SPI总线介绍
SPI分为主从工作模式,通常有一个主设备和一个或多个从设备,本文中MCU为主机,W25Q16为从机。

SPI通信有以下四根线:
MISO:主设备数据输入,从设备数据输出。
MOSI:主设备数据输出,从设备数据输入。
SCLK:时钟信号,由主设备产生。

CS:从设备片选信号,由主设备控制,低电平为选中。

(2)SPI的特点:
高位先发送,共有四种工作模式。
CPOL(时钟极性):规定了SCK时钟信号空闲状态的电平(0-低电平,1-高电平)
CPHA(时钟相位):规定了数据是在SCK时钟的上升沿还是下降沿被采样(0-第一个时钟边沿开始采样,1-第二个时钟边沿开始采样)

模式0:CPOL=0,CPHA =0  SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据)
模式1:CPOL=0,CPHA =1  SCK空闲为低电平,数据在SCK的下降沿被采样(提取数据)
模式2:CPOL=1,CPHA =0  SCK空闲为高电平,数据在SCK的下降沿被采样(提取数据)
模式3:CPOL=1,CPHA =1  SCK空闲为高电平,数据在SCK的上升沿被采样(提取数据)

(3)开发板 SPI Flash原理图如下:

da82f3d13fef48dd8d90aa170757e360.png


  二、串口printf实验过程
1、新建STM32CubeMX基础工程

(1)打开STM32CubeMX,点击“File”-->"New Project"

2399170347904e959c855fefd5877f84.png

(2)等待打开主芯片选项界面(大约1分钟时间)。

d74d2fdc60c9440483e2b7cf8d928e7f.png

(3)昨天搜索框中输入(或选择)所需的主芯片型号(因为我们用的是STM32L431RCT6开发板,所以此处选择STM32L431RC),然后在右下角选择STM32L431RCTx(因为开发板主芯片是STM32L431RCT6),左键双击即可打开新建的项目。

7b80345238d74bea82ce70e1a348f7b4.png

(4)选择时钟源。
(1)因为开发板上有8M外部时钟,硬件原理图如下所示,所以此处选择使用外部高速时钟(HSE)。

3f65707eca104663a3dede6d25dfa961.png

(2)因为我们没有用到外部低速时钟(LSE),此处不做处理,如下图所示。

72119b971f62410fa8344f7f9fb9f389.png

2、配置GPIO控制LED
(1)查开发板原理图得,LED1、LED2、LED3、LED4的控制引脚分别为:
LED1——PC0
LED2——PC1
LED3——PC2
LED4——PC3

(2)配置LED的控制引脚为输出,输出频率、输出方式默认即可。
鼠标左键点击PC0,选择“GPIO_Output”,表示设置该引脚为输出模式。
鼠标左键点击PC1,选择“GPIO_Output”,表示设置该引脚为输出模式。
鼠标左键点击PC2,选择“GPIO_Output”,表示设置该引脚为输出模式。
鼠标左键点击PC3,选择“GPIO_Output”,表示设置该引脚为输出模式。

492907c1e71149819adbaee4516a2af4.png

c75d3f10ba2e427d9c1a10fad9bf5471.png

(3)也根据自己的需求配置GPIO的参数,如输出方式、输出频率、上拉下拉等。因为GPIO控制LED的要求比较低,此处采用默认参数即可,不用修改。

38ff4b80e1c5495ba3076a5158fd91fa.png

​​3、配置PA9、PA10为串口
查原理图得知,串口1使用STM32L431RCT6引脚为PA9-USART1_TX,PA10-USART1_RX,引脚设置如下:

497b8ee0243e446784e64a3c31e8a26f.png

(1)序号1用来设置串口收发引脚的选择。
(2)序号2-3-4-5-6设置串口参数,如波特率115200、8位、NONE无奇偶校验等。

4、配置SPI Flash接口
(1)查看STM32L431RCT6开发板原理图得知,芯片原理图如下:

11aaba64f3d34f9ea1e4cbbfc0fdc102.png

(2)SPI Flash接口对应芯片的PB12、 PB13、 PB14  PB15,芯片引脚配置如下:
PB12:SPI2_NSS,此处设置普通输出IO即可。不能配置成NSS
PB13:SPI2_SCK
PB14:SPI2_MISO
PB15:SPI_MOSI

ade62d1658174af99a93e3928b62849c.png

(3)设置SPI引脚参数,并选择 Full-Duplex Master 全双工主模式,此处不开启 NSS 即不使用硬件片选信号。

c932367c299e46979e55a3e1a05d2c7e.png

(4)设置SPI基础参数以及时钟。

e5af1c8c0b0b4ac9a07d740b955028c0.png

dcc8d73ddcbe4082a09165905ac71cf1.png

5、配置项目工程参数
(1)配置时钟树,用于系统内部时钟,以及各个外设时钟等。此处选择外部8M晶振作为主时钟频率,内部最大倍频80MHz。

f3ecedd9323f4463b69539906b3c1199.png

(2)完成配置工程。
备注:需要注意代码生成过程中的继承关系,如图所示:需要保留开发者自己编写的代码时,请根据配置设置,不然生成代码后会删除自己编写的代码(从这个方面也可以看出开发者备份自己的代码是多么的重要。)

efdb616174f54925b6eac31109f227b5.png
​​
41c3f2716e4e4eda8f62fa3a1d871322.png

(3)生成代码。
备注:使用Generate CODE生成工程代码前,请确保文件路径无中文,否则会生成项目失败。

8eaa7d0babea40ee93f158481a6ee471.png

(4)工程代码生成成功。

e8f46d59a1fb4af589feb613fdff17b4.png


三、在KEIL 5中编写代码

1、使用KEIL 5(MDK)打开项目工程文件
源码使用说明:使用前必须把项目工程复制到无中文路径的文件夹下使用。
(1)找到刚才新建工程的存储路径,安装项目名称,打开项目工程.uvprojx。

37170661111547238cfcd51f580aab01.png

2、添加SPI Flash读写验证程序
(1)main.c文件中,初始化LED1、LED2、LED3、LED4默认为点亮,并在while循环中添加控制程序,如下所示:实现每隔500ms后LED1、LED2、LED3、LED4点亮和熄灭之间反转切换,并且串口每隔500ms打印一次。

备注:自己添加的代码需要在 /* USER CODE BEGIN 3 */和 /* USER CODE END 3 */之间添加,否则STM32CubeMX更新代码时,会造成自己添加的代码丢失。

d1194af353cd4b0b88cd9208b1ac7db1.png

3c771cb9b6094461aa8ec2befc9d12dc.png

cd59e19f79524446a278fe4f5fd7b81a.png

(2)main.c函数添加初始化代码和读取flash的代码,如下所示:
  1. int main(void)
  2. {
  3.     /* USER CODE BEGIN 1 */
  4.     uint32_t w25q_chip_id=0;                                                                                //读取芯片ID
  5.     uint8_t  onebyte_read[8]= {0};                                                                                //读取的字节内容
  6.     uint8_t  onebyte_write[8]= {0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80};                                //写入的字节内容
  7.     /* USER CODE END 1 */

  8.     /* MCU Configuration--------------------------------------------------------*/

  9.     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  10.     HAL_Init();

  11.     /* USER CODE BEGIN Init */

  12.     /* USER CODE END Init */

  13.     /* Configure the system clock */
  14.     SystemClock_Config();

  15.     /* USER CODE BEGIN SysInit */

  16.     /* USER CODE END SysInit */

  17.     /* Initialize all configured peripherals */
  18.     MX_GPIO_Init();
  19.     MX_DMA_Init();
  20.     MX_USART1_UART_Init();
  21.     MX_SPI2_Init();
  22.     /* USER CODE BEGIN 2 */
  23.     HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_SET);                                                                    //初始化默认LED灯为点亮
  24.     HAL_GPIO_WritePin(GPIOC,GPIO_PIN_1,GPIO_PIN_SET);                                                                    //初始化默认LED灯为点亮
  25.     HAL_GPIO_WritePin(GPIOC,GPIO_PIN_2,GPIO_PIN_SET);                                                                    //初始化默认LED灯为点亮
  26.     HAL_GPIO_WritePin(GPIOC,GPIO_PIN_3,GPIO_PIN_SET);                                                                    //初始化默认LED灯为点亮
  27.     w25q_chip_id=spi_flash_read_ID();                                                                                                        //读取flash芯片ID
  28.     HAL_Delay(100);
  29.     printf("hello world,spi_flash_read_ID==0x%0x!\r\n",w25q_chip_id);                    //printf 芯片ID
  30.     spi2_flash_sector_erase(0x0);                                                                                                                 //写之前需要先擦除扇区
  31.     spi2_flash_write(0x0,onebyte_write,8);                                                                                                 //写八个字节
  32.     spi2_flash_read(0x00,onebyte_read,8);                                                                                                 //读取写入的字节
  33.     /* USER CODE END 2 */

  34.     /* Infinite loop */
  35.     /* USER CODE BEGIN WHILE */
  36.     while (1)
  37.     {
  38.         /* USER CODE END WHILE */

  39.         /* USER CODE BEGIN 3 */
  40.         HAL_Delay(500);
  41.         HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);                                                                                 //每隔500ms闪烁一次,并打印flash区域的值
  42.         HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_1);                                                                                 //每隔500ms闪烁一次,并打印flash区域的值
  43.         HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_2);                                                                                 //每隔500ms闪烁一次,并打印flash区域的值
  44.         HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_3);                                                                                 //每隔500ms闪烁一次,并打印flash区域的值
  45.         printf("hello world,spi2_flash_read =0x%0x,0x%0x,0x%0x,0x%0x,0x%0x,0x%0x,0x%0x,0x%0x\r\n",onebyte_read[0],onebyte_read[1],\
  46.         onebyte_read[2],onebyte_read[3],onebyte_read[4],onebyte_read[5],onebyte_read[6],onebyte_read[7]);   //printf打印flash区域的值

  47.     }
  48.     /* USER CODE END 3 */
  49. }
复制代码

(3)新建w25qxx.c文件,并添加驱动层函数,如下所示:
  1. #include "main.h"
  2. #include "spi.h"
  3. #include "w25qxx.h"
  4. /**
  5.   * @brief SPI1 读一个字节
  6.   * @param None
  7.   * @retval None
  8.   */
  9. static uint8_t spi2_flash_read_byte(void)
  10. {
  11.     uint8_t t_data, r_data;

  12.     if(HAL_SPI_TransmitReceive(&hspi2, &t_data, &r_data, 1, 0xFFFFFF) != HAL_OK)
  13.     {
  14.         r_data = 0xff;
  15.     }
  16.     return r_data;
  17. }
  18. /**
  19.   * @brief SPI1 写一个字节
  20.   * @param byte 写入的字节
  21.   * @retval 写状态 0成功 1失败
  22.   */
  23. static uint8_t spi2_flash_send_byte(uint8_t byte)
  24. {
  25.     uint8_t r_data;

  26.     if(HAL_SPI_TransmitReceive(&hspi2, &byte, &r_data, 1, 0xFFFFFF) != HAL_OK)
  27.     {
  28.         return 1;
  29.     }
  30.     return 0;
  31. }
  32. /**
  33.   * @brief FLASH 写使能
  34.   * @param None
  35.   * @retval None
  36.   */
  37. static void spi2_flash_write_enable(void)
  38. {
  39.     SPI_FLASH_CS_LOW();
  40.     spi2_flash_send_byte(W25X_WriteEnable);
  41.     SPI_FLASH_CS_HIGH();
  42. }
  43. /**
  44.   * @brief FLASH 等待写结束
  45.   * @param None
  46.   * @retval None
  47.   */
  48. static void spi2_flash_wait_for_write_end(void)
  49. {
  50.     uint8_t state = 0;

  51.     SPI_FLASH_CS_LOW();

  52.     spi2_flash_send_byte(W25X_ReadStatusReg);

  53.     do
  54.     {
  55.         state = spi2_flash_read_byte();
  56.     }
  57.     while((state & 0x01) == SET);

  58.     SPI_FLASH_CS_HIGH();
  59. }
  60. /**
  61.   * @brief FLASH 读ID
  62.   * @param None
  63.   * @retval None
  64.   */
  65. uint32_t spi_flash_read_ID(void)
  66. {
  67.     uint32_t temp, temp0, temp1, temp2;

  68.     SPI_FLASH_CS_LOW();
  69.     spi2_flash_send_byte(W25X_JedecDeviceID);

  70.     temp0 = spi2_flash_read_byte();
  71.     temp1 = spi2_flash_read_byte();
  72.     temp2 = spi2_flash_read_byte();

  73.     SPI_FLASH_CS_HIGH();

  74.     temp = (temp0 << 16) | (temp1 << 8) | temp2;

  75.     return temp;
  76. }
  77. /**
  78.   * @brief 读FLASH
  79.   * @param addr 读flash的起始地址
  80.   * @param pdata 读到的数据存放起始地址
  81.   * pdata size 读数据大小
  82.   * @retval None
  83.   */
  84. void spi2_flash_read(uint32_t addr,uint8_t *pdata, uint16_t size)
  85. {
  86.     SPI_FLASH_CS_LOW();

  87.     spi2_flash_send_byte(W25X_ReadData);

  88.     spi2_flash_send_byte((addr & 0xFF0000) >> 16);
  89.     spi2_flash_send_byte((addr & 0xFF00) >> 8);
  90.     spi2_flash_send_byte(addr  & 0xFF);

  91.     while (size--)
  92.     {
  93.         *pdata = spi2_flash_read_byte();
  94.         pdata++;
  95.     }

  96.     SPI_FLASH_CS_HIGH();
  97. }
  98. /**
  99.   * @brief 按页写FLASH
  100.   * @param addr 写入flash的起始地址
  101.   * @param pdata 写入数据的起始地址
  102.   * pdata size 写数据大小
  103.   * @retval None
  104.   */
  105. void spi2_flash_page_write(uint32_t addr, uint8_t *pdata, uint16_t size)
  106. {
  107.     uint16_t i;

  108.     spi2_flash_write_enable();

  109.     SPI_FLASH_CS_LOW();

  110.     spi2_flash_send_byte(W25X_PageProgram);
  111.     spi2_flash_send_byte((uint8_t)((addr)>>16));
  112.     spi2_flash_send_byte((uint8_t)((addr)>>8));
  113.     spi2_flash_send_byte((uint8_t)addr);

  114.     for(i = 0; i < size; i++)
  115.     {
  116.         spi2_flash_send_byte(pdata[i]);
  117.     }

  118.     SPI_FLASH_CS_HIGH();
  119.     spi2_flash_wait_for_write_end();
  120. }
  121. /**
  122.   * @brief 写FLASH
  123.   * @param addr 写入flash的起始地址
  124.   * @param pdata 写入数据的起始地址
  125.   * pdata size 写数据大小
  126.   * @retval None
  127.   */
  128. void spi2_flash_write(uint32_t addr, uint8_t *pdata, uint32_t size)
  129. {
  130.     uint32_t page_remain;

  131.     page_remain = 256 - addr%256;

  132.     if(size <= page_remain)
  133.     {
  134.         page_remain = size;
  135.     }
  136.     while(1)
  137.     {
  138.         spi2_flash_page_write(addr, pdata, page_remain);

  139.         if(size == page_remain)
  140.             break;
  141.         else
  142.         {
  143.             pdata += page_remain;
  144.             addr += page_remain;

  145.             size -= page_remain;
  146.             if(size > 256)
  147.                 page_remain = 256;
  148.             else
  149.                 page_remain = size;
  150.         }
  151.     }
  152. }
  153. /**
  154.   * @brief 擦除FLASH扇区
  155.   * @param sector_addr 扇区的起始地址
  156.   * @retval None
  157.   */
  158. void spi2_flash_sector_erase(uint32_t sector_addr)
  159. {
  160.     spi2_flash_write_enable();
  161.     spi2_flash_wait_for_write_end();

  162.     SPI_FLASH_CS_LOW();
  163.     spi2_flash_send_byte(W25X_SectorErase);
  164.     spi2_flash_send_byte((sector_addr & 0xFF0000) >> 16);
  165.     spi2_flash_send_byte((sector_addr & 0xFF00) >> 8);
  166.     spi2_flash_send_byte(sector_addr & 0xFF);

  167.     SPI_FLASH_CS_HIGH();

  168.     spi2_flash_wait_for_write_end();
  169. }

  170. /**
  171.   * @brief 擦除FLASH块
  172.   * @param None
  173.   * @retval None
  174.   */
  175. void spi2_flash_block_erase(void)
  176. {
  177.     spi2_flash_write_enable();

  178.     SPI_FLASH_CS_LOW();
  179.     spi2_flash_send_byte(W25X_ChipErase);
  180.     SPI_FLASH_CS_HIGH();

  181.     spi2_flash_wait_for_write_end();
  182. }
复制代码

3、设置编程仿真下载模式
(1)选择Options for target ...>>Debug>>J-Link/J-JTRACE Cortex,点击Settings>>选择Port(SW),可以看到搜索成功SW Device,表示芯片可用,可以下载。

edefb6b9a2a14be3b1221ad6d5c7a8a2.png
​​
(2)点击编译,完成后提示“0 error(s),0 warning(s)”。

e0b7e3e353b345a7987fe5f821de2dda.png

(3)点击Download(或者快捷键F8),即可下载程序。

eb3f250f19e44862a7df80c2492fcd01.png
​​

(4) 如果下载程序后,没有看到LED1、LED2、LED3、LED4闪烁,可以按下述方式设置一下(Reset and run表示下载后自动复位和重启运行)。或者重新彻底断电再次上电(或按开发板的Reset按键复位MCU即可)。

9f8aa1a4e8f04041bcf5cb183facd100.png


4、SPI Flash实验效果展示
      (1)  程序烧录到开发板后,即可看到LED1、LED2、LED3、LED4初始化后每隔500ms闪烁一次,并且打开串口助手后(串口参数:波特率115200、N、8、1),可以看到printf每隔500ms打印一次log数据。

954468e85b9f44299916e9c6b3440ad2.png

(2)屏蔽写入flash的语句,可以看到此时串口log打印的全为FF,则证明SPI Flash读写有效。

b37dd10545e442c3924a75955386b09c.png

d36c42cb706f4a2d8103858b70c8d803.png

————————————————
版权声明:智能小屋ZYXC


收藏 评论0 发布时间:2023-1-13 19:00

举报

0个回答

所属标签

相似分享

官网相关资源

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