基于STM32CubeMx的FLASH和SD卡文件读取
实验任务:
(一)Flash地址空间的数据读取。stm32f103c8t6只有20KB 内存(RAM)供程序代码和数组变量存放,因此,针对内部Flash的总计64KB存储空间(地址从0x08000000开始),运行一次写入8KB数据,总计复位运行代码8次,将64KB数据写入Flash,并验证写入数据的正确性和读写速率。此外,继续往后续地址写入数据,检验stm32f103c8t6 实际FlashROM是否超过64KB。
(二)掌握SD卡协议原理,用STM32F103完成对SD卡的数据读取(fat文件模式)。
(一)Flash原理
不同型号的 STM32,其 FLASH 容量也有所不同,最小的只有 16K 字节,最大的则达到了 1024K 字节。市面上 STM32F1 开发板使用的芯片是 STM32F103系列,其 FLASH 容量一般为 512K 字节,属于大容量芯片。
Flash的编程原理都是只能将1写为0,而不能将0写为1,所以在进行Flash编程前,必须将对应的块擦除,即将该块的每一位都变为1,块内所有字节变为0xFF。
STM32F1 的闪存(Flash)模块:主存储器、信息块、闪存存储器接口寄存器。
- 主存储器:该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2K 字节。注意,小容量和中容量产品则每页只有 1K 字节。
- 信息块:该部分分为 2 个小部分,其中启动程序代码,是用来存储 ST 自带的启动程序,用于串口下载代码,当 BOOT0 接 V3.3, BOOT1 接 GND 的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能。
- 闪存存储器接口寄存器:该部分用于控制闪存读写等,是整个闪存模块的控制机构。对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理;编程与擦除的高电压由内部产生。
在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。
(二)SD卡协议原理
1.SD卡简述
很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有 U 盘,FLASH 芯片,SD 卡等。他们各有优点,综合比较,最适合单片机系统的莫过于 SD 卡了,它不仅容量可以做到很大(32GB 以上),支持 SPI/SDIO 驱动,而且有多种体积的尺寸可供选择(标准的 SD 卡尺寸,以及 TF 卡尺寸等),能满足不同应用的要求。
只需要少数几个 IO 口即可外扩一个高达 32GB 以上的外部存储器,容量从几十 M 到几十G 选择尺度很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。
2.SD卡物理结构
&emsp一般SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器 5个部分。
- 存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;
- 电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和 存储单元接口复位;
- 卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器;
- 接口驱动器控制SD卡引脚的输入输出。
3.SD卡寄存器
&emspSD卡总共有8个寄存器,用于设定或表示SD卡信息。这些寄存器只能通过对应的命令访问,SDIO定义64个命令,每个命令都有特殊意义,可以实现某一特定功能,SD卡接收到命令后,根据命令要求对SD卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。
以下是使用Markdown格式编写的表格:
名称 | bit宽度 | 描述 |
---|---|---|
CID | 128 | 卡识别号(Card Identification number):用来识别的卡的个体号码(唯一的) |
RCA | 16 | 相对地址(Relative card address):卡的本地系统地址,初始化时,动态地由卡建议,主机核准 |
DSR | 16 | 驱动级寄存器(Driver Stage Register):配置卡的输出驱动 |
CSD | 128 | 卡的特定数据(Card Specific Data):卡的操作条件信息 |
SCR | 64 | SD配置寄存器(CD Configuration Register):SD卡特殊特性信息 |
OCR | 32 | 操作条件寄存器(Operation conditings register) |
SSR | 512 | SD状态(SD Status):SD卡专有特征的信息 |
CSR | 32 | 卡状态(Card Status):卡状态信息 |
4.SD卡下SPI操作模式
4.1SD卡初始化
- SPI操作模式下:在SD卡收到复位命令时,CS为有效电平(低电平),则SPI模式被启用,在发送CMD之前要先发送74个时钟,64个为内部供电上升时间,10个用于SD卡同步;才能开始CMD操作,在初始化时CLK时钟不能超过400KHz。
- 初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置);
- 上电延时(>74个CLK);
- 复位卡(CMD0),进入IDLE状态;
- 发送CMD8,检查是否支持2.0协议;
- 根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58和CMD1等);
- 取消片选,发多8个CLK,结束初始化。
这样我们就完成了对SD卡的初始化,注意末尾发送的8个CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1、V2、V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。
4.2SD卡读取与写入
SD卡读取
- 发送CMD17;
- 接收卡响应R1;
- 接收数据起始令牌0XFE;
- 接收数据;
- 接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
- 禁止片选之后,发多8个CLK;
以上就是一个典型的读取SD卡数据过程,SD卡的写与读数据差不多,写数据通过CMD24来实现,具体过程如下:
SD卡写入
- 发送CMD24;
- 接收卡响应R1;
- 发送写数据起始令牌0XFE;
- 发送数据;
- 发送2字节的伪CRC;
- 禁止片选之后,发多8个CLK;
(三)HAL库配置
1.基本配置
(1)打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:
(2)选择单片机型号以及点击开始工程项目:
(3)点击system core,进入SYS,在debug下选择serial wire:
(4)配置时钟,进入上面的RCC,有两个时钟,一个是HSEe和LSE,将HSE那里设为Crystal/Ceramic Resonator:
(5)主要介绍整套配置过程,可以配置完后在生成工程与代码。
进入Project Manager(工程管理),进行工程设置点击生成工程与代码:
2.FLASH
- 管脚配置,对应外设c8t6本身设计好的PC13 LED灯
对应的GPIO设置
- 设置堆栈大小
- 进入CLK Configuration (时钟配置)中,进行时钟配置:
开发板的外部晶振为8MHz,我们填入8;通道选择LSE;PLLM选择为/1;倍频系数N选择为x9;系统时钟选择PLLCLK;系统时钟设定为72Mz;APB1分频系数选择为/2即PCLK1位36MHz;APB2分频系数选择为/1即PCLK2位72MHz。
3.读取SD卡
- 点击
Middleware
,选择FATFS
模式。
- 配置
PA4
为GPIO_Output
模式。
- 点击
Connectivity
,配置SPI1
为Full-Duplex Master
模式。
- 配置
USART1
为异步模式
- 修改最小栈容量为0x1400,否则会导致调试时死机。
(四) keil设置
1.FLASH数据读取
1.1添加文件
flash.h
#ifndef _flash_H_
#define _flash_H_
#include "stm32f1xx_hal.h"
/*********************************************************************/
// 变量定义
/*********************************************************************/
//-- 用途划分
// 0x0800FC00-0x0800FFFF -- 使用最后4k 个字节用来存放电机信息
#define FMC_FLASH_BASE 0x08000000 // FLASH的起始地址
#define APP_MAX_SIZE 0x00010000 //
#define FMC_FLASH_END 0x08010000 // FLASH的结束地址 256
#define DEVICE_INFO_ADDRESS 0x0800C000 //(STM32_FLASH_END - DEVICE_INFO_SIZE) // 设备信息起始地址
#define DEVICE_LOG_ADDRESS 0x0800E000 //(STM32_FLASH_END - 2*DEVICE_INFO_SIZE) // 设备日志起始地址
#define FMC_FLASH_SIZE 64 // 定义Flash大小,单位KB
#if FMC_FLASH_SIZE < 256
#define FMC_SECTOR_SIZE 1024 // 字节
#define MOD_SECTOR_SIZE 0X3FF
#define PAGE_COUNT_BY16 512
#else
#define FMC_SECTOR_SIZE 2048
#define MOD_SECTOR_SIZE 0X7FF
#define PAGE_COUNT_BY16 1024
#endif
/*********************************************************************/
// 函数声明
/*********************************************************************/
void FlashWriteBuff( const uint32_t destination_address, uint8_t *const buffer,uint32_t length );
void FlashReadBuff(const uint32_t source_address,uint8_t *const buffer,uint16_t length);
#endif
flash.c
#include "flash.h"
// 不检查的写入
// WriteAddr:起始地址
// pBuffer: 数据指针
// NumToWrite:字节数数
void FlashWriteNoCheck( uint32_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite )
{
uint16_t i;
for( i=0; i<NumToWrite; i+=4 )
{
while( HAL_OK != HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr+i,*(uint32_t *)(pBuffer+i) ) );
}
}
extern void FLASH_PageErase(uint32_t PageAddress);
void FlashWriteBuff( const uint32_t destination_address, uint8_t *const buffer,uint32_t length )
{
uint16_t i;
uint8_t FlashBuff[FMC_SECTOR_SIZE];
uint32_t StartAddress = destination_address - destination_address%FMC_SECTOR_SIZE;
uint16_t Offset = destination_address - StartAddress;
uint8_t *pBuf = buffer;
uint32_t remaindNum = length;
HAL_StatusTypeDef status = HAL_ERROR;
// 地址检查
if( (destination_address < FMC_FLASH_BASE) || ( destination_address + length >= FMC_FLASH_END) || (length <= 0) )
return;
HAL_FLASH_Unlock(); // 解锁
do
{
// 读出一页数据
for(i=0; i < FMC_SECTOR_SIZE; i += 4 )
*(uint32_t *)(FlashBuff+i) = *(uint32_t *)(StartAddress+i);
// 修改要改入的数据
for ( i=0; (i+Offset)<FMC_SECTOR_SIZE && i< remaindNum; i++ )
*(FlashBuff+Offset+i) = *(pBuf+i);
// 擦除一ROW数据
FLASH_PageErase( StartAddress );
// HAL库 FLASH_PageErase有BUFF,要加上下面三行代码
while( status != HAL_OK )
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
CLEAR_BIT(FLASH->CR, FLASH_CR_PER);
// 写入数据
FlashWriteNoCheck( StartAddress,FlashBuff,FMC_SECTOR_SIZE );
// 为下一页做准备
StartAddress += FMC_SECTOR_SIZE;
remaindNum -= i;
pBuf += i;
Offset = 0;
} while( remaindNum > 0 );
HAL_FLASH_Lock(); // 上锁
}
// 从FLASH中读指定长度数据
void FlashReadBuff(const uint32_t source_address,uint8_t *const buffer,uint16_t length)
{
uint16_t i;
uint8_t Offset = 0;
uint32_t StartAddress = source_address;
uint16_t data;
// 地址检测
if( source_address + length > FMC_FLASH_END ) return;
// 如果没有对16齐
if( source_address & 1 )
{
Offset = 1;
StartAddress = source_address-1;
}
// flash的操作要求16对齐 最小读写操作16个比特
if ( Offset )
{
data = *(uint16_t *)(StartAddress);
buffer[0] = data >> 8;
StartAddress += 2;
}
for ( i = 0; i < (length-Offset); i += 2)
{
data = *(uint16_t *)(StartAddress+i);
buffer[i+Offset] = (data & 0xFF);
if ( (i+Offset) < (length - 1) )
buffer[i + Offset + 1] = (data >> 8);
}
}
1.2主函数
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "flash.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t FlashWBuff [255];
uint8_t FlashRBuff [255];
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t i;
uint8_t FlashTest[] ="Hello World hello pupu";
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
FlashWriteBuff(DEVICE_INFO_ADDRESS, FlashTest, sizeof(FlashTest));//写入数据到Flash
for(i=0;i<255;i++)
FlashWBuff[i]=i;
FlashWriteBuff(DEVICE_INFO_ADDRESS + sizeof(FlashTest), FlashWBuff, 255);//
FlashReadBuff(DEVICE_INFO_ADDRESS + sizeof(FlashTest), FlashRBuff, 255);
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {
0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {
0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {
0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
/*Configure GPIO pin : PC13 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct