我是靠谱客的博主 寒冷爆米花,最近开发中收集的这篇文章主要介绍STM32F103C8T6通过SD卡加载固件STM32F103C8T6通过SD卡加载固件,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

STM32F103C8T6通过SD卡加载固件

前面写了通过Uart加载固件,这次就使用SD卡来尝试一下加载固件吧。

要实现SD卡加载固件的功能,需要完成以下三项工作:

  1. 能够对单片机内部FLASH进行编程。(前面写串口加载固件的时候写了)
  2. 完成SD卡驱动。
  3. 移植FATFS文件系统。

有了这三项的支持,编写从SD卡加载固件的程序就很简单了。程序思路如下:

  1. 初始化SD卡,挂载文件系统。
  2. 只读方式打开"download.bin"文件,获取文件描述符。
  3. 以512字节为单位,循环读取"download.bin"文件的内容,并写入APP程序的地址中。
  4. 跳转到APP程序。

1 对FLASH编程

这个前面的文章详细地写了,这里不展开,直接引用以前的代码即可。

2 编写SD卡驱动

2.0 准备硬件

由于STM32F103C8核心板上没有SD卡槽,需要买一个SD卡槽,需要买一个集成的模块。记住要买TF小卡的,日常使用小卡比较多。

在这里插入图片描述

还有就是需要一张TF卡,要支持HC协议的。一般不超过32G的卡都支持HC协议,在卡的表面丝印也会注明HC。

在这里插入图片描述

准备好硬件后可以看以下SD卡的协议和读写规范。粗略阅读后,还是建议从网上下载一份代码,移植到自己的工程。当然,时间充足的话,从头开始动手写没什么不好的。

2.1 创建工程

中等容量系列的C8T6是没有SDIO接口的,所以只能用SPI接口去驱动SD卡,缺点就是慢。在CUBEMX里面初始化一个SPI接口,如下图所示:

在这里插入图片描述

SPI设置为:全双工主机、软件NSS信号、Motorola帧协议、数据宽度8位、MSB格式、速率18MBit(在程序中会修改)、空闲状态为高电平、时钟相位为第2个时钟边缘、不使能CRC。这些设置可以从SD卡的读写时序得到,也可以简单地从别人的代码中得到。

配置完成后生成工程。

2.2 移植驱动代码

这里选择移植正点原子的SD卡驱动代码。因为这份代码写得很容易移植,工程里面的SD卡驱动和底层的SPI代码是分开的,因此我们只需要用HAL库SPI外设的代码去实现spi.c这个文件的接口就行。

#include "spi.h"

extern SPI_HandleTypeDef hspi1;

/*
 * description : 初始化SPI
 * param : none
 * return : none
 */
void SPI1_Init(void)
{
    SPI1_ReadWriteByte(0xff);   //启动传输	
}


/*
 * description : 发送&接收
 * param - TxData : 要发送的数据
 * return : 接收到的数据
 */
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
    uint8_t rxData;
    HAL_SPI_TransmitReceive(&hspi1, &TxData, &rxData, 1, 10);
    return rxData;
}

/*
 * description : 设置传输速度
 * param - SpeedSet : SPI_BAUDRATEPRESCALER_2   = 2分频   = 36M(不可选)
 *                    SPI_BAUDRATEPRESCALER_4   = 4分频   = 18M
 *                    SPI_BAUDRATEPRESCALER_8   = 8分频   = 9M
 *                    SPI_BAUDRATEPRESCALER_16  = 16分频  = 4.5M
 *                    SPI_BAUDRATEPRESCALER_256 = 256分频 = 281.25K
 * return : none
 */
void SPI1_SetSpeed(uint32_t SpeedSet)
{
    hspi1.Init.BaudRatePrescaler = SpeedSet;
    HAL_SPI_Init(&hspi1);
}

至于MMC_SD.C这里面的代码,基本不需要改动。只需要把代码中使用的u8、u16、u32替换成uin8_t、uint16_t、uint32_t即可。当然自己在头文件中手动定义一下u8、u16、u32也可以。

在main()中简单测试一下驱动:

int main(void)
{
  /* USER CODE BEGIN 1 */
  uint32_t sdSectorCount;
  /* 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 */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  while(SD_Initialize())//检测不到SD卡
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    HAL_Delay(100);
  }
  sdSectorCount = SD_GetSectorCount();
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

插卡后上电,如果LED闪烁的周期为1秒,那就可以表明驱动移植初步是成功的。

可以进调试看一下sdSectorCount的值。(PS:如果在调试中无法查看sdSectorCount局部变量,可以进设置将编译优化等级调为0)

sdSectorCount * 0.5 = 容量(KByte)。这是因为这份驱动把SD卡的扇区容量设置为512字节。

3 移植FATFS文件系统

如果只是用操作FLASH的方式来操作SD卡,组织起来会非常麻烦。因此使用了FATFS文件系统。因为FATFS与电脑的FAT32文件系统兼容,所以在使用了FATFS后,我们可以直接将bin文件拖到SD卡即可。否则,我们就要将bin文件的内容烧写到SD卡指定的地址,非常麻烦。

简而言之,FATFS文件系统可以帮我们管理SD卡。我们可以使用文件操作的API来使用SD卡。(PS:FATFS原则上是需要RTC的,但是为了简单,这里不使用RTC,读取时间的API直接返回0)

FATFS文件系统非常容易移植,只需两步:

  1. 配置ffconf.h。其实就是进行裁剪,文件里面的配置项不多,而且都配有非常完整的注释,很好配置。
  2. 实现diskio.c中的函数接口。可以参照着注释自己写,也可以参考网上的例程照着写。

ffconf.h文件配置内容如下:(去除注释)

#ifndef _FFCONF
#define _FFCONF 29000	/* Revision ID */
#define	_FS_TINY		0	/* 0:Normal or 1:Tiny */
#define _FS_READONLY	0	/* 0:Read/Write or 1:Read only */
#define _FS_MINIMIZE	0	/* 0 to 3 */
#define	_USE_STRFUNC	1	/* 0:Disable or 1-2:Enable */
#define	_USE_MKFS		1	/* 0:Disable or 1:Enable */
#define	_USE_FASTSEEK	1	/* 0:Disable or 1:Enable */
#define _USE_LABEL		1	/* 0:Disable or 1:Enable */
#define	_USE_FORWARD	0	/* 0:Disable or 1:Enable */
#define _CODE_PAGE	1		/* 不采用中文编码 */
#define	_USE_LFN	0		/* 0,不采用长文件名 */
#define	_MAX_LFN	255		/* Maximum LFN length to handle (12 to 255) */
#define	_LFN_UNICODE	0	/* 0:ANSI/OEM or 1:Unicode */
#define _STRF_ENCODE	3	/* 0:ANSI/OEM, 1:UTF-16LE, 2:UTF-16BE, 3:UTF-8 */
#define _FS_RPATH		0	/* 0 to 2 */
#define _VOLUMES	1	/* 只有1个盘符,就是SD卡 */
#define _STR_VOLUME_ID	0	/* 0:Use only 0-9 for drive ID, 1:Use strings for drive ID */
#define _VOLUME_STRS	"RAM","NAND","CF","SD1","SD2","USB1","USB2","USB3"
#define	_MULTI_PARTITION	0	/* 0:Single partition, 1:Enable multiple partition */
#define	_MIN_SS		512
#define	_MAX_SS		512
#define	_USE_ERASE	0	/* 0:Disable or 1:Enable */
#define _FS_NOFSINFO	0	/* 0 to 3 */
#define _WORD_ACCESS	0	/* 0 or 1 */
#define	_FS_LOCK	0	/* 0:Disable or >=1:Enable */
#define _FS_REENTRANT	0		/* 0:Disable or 1:Enable */
#define _FS_TIMEOUT		1000	/* Timeout period in unit of time ticks */
#define	_SYNC_t			HANDLE	/* O/S dependent sync object type. e.g. HANDLE, 
#endif

注意_CODE_PAGE、_USE_LFN、需要注意,由于C8T6的Flash比较小,只有64KByte,因此不推荐使用中文编码。因此_CODE_PAGE需要设置为1,同时_USE_LFN需要设置为0。此时我们的文件系统就不支持中文了,无论是文件名还是文件内容,都只能使用英文。对于读取bin二进制文件没有影响。(PS:取消长文件名后,不区分大小写文件名。也就是在单片机上创建一个hello.txt,在电脑中看可能是HELLO.TXT。文件里面的内容还是正常区分大小写的。)

_VOLUMES表示盘符个数,最大应该是可以有10个。我们这里只有一张SD卡,所以写1就行。

接下来修改diskio.c。由于取消了长文件名,那么可以不实现void ff_memalloc (UINT size) 和void ff_memfree (void mf)。

剩下几个函数的实现,参考正点原子的SPI驱动SD卡使用FATFS文件系统的例程即可。(例程里面有涉及到SPI FLASH的代码,可以删掉,当然保留也不会出错。)

修改完毕后,编写简单的main()函数来测试一下:

FATFS fs;
FIL filp;
UINT bw, br;
FRESULT ret;
uint8_t readBuf[512];
FILINFO fno;
DWORD fileSize;
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* 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 */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  while(SD_Initialize())//检测不到SD卡
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    HAL_Delay(100);
  }
  /* 1、挂载文件系统 */
  ret = f_mount(&fs,"0:",1);
  if(ret != FR_OK)
  {
    /* 挂载失败 */
    goto MOUNT_ERR;
  }
  f_open(&filp, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);  //创建文件
  f_write(&filp, "hello world", 11, &bw);   //写入文件
  f_close(&filp);   //关闭文件
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    HAL_Delay(1000);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

这里稍微解释一下f_mount()的参数。

  • 第一个参数&fs,用来保存文件系统数据的地址。这个简单,传入指定类型变量的指针即可。
  • 主要是这个“0:”。首先这个字符串会被f_mount()里面的get_ldnumber()函数解析,得到“:”前面的数字“0”,再将字符串数字“0”转换成整型数字0。get_ldnumber()函数也印证了FATFS只支持0~9的10个盘符。得到整数盘符0后,使用find_volume()函数去初始化SD卡或者读写SD卡。这个盘符0就是我们移植diskio.c里面的BYTE pdrv。我们在移植时定义了SD卡的盘符为0。
  • 最后一个参数1,表示立即挂载。

程序运行时,会依次初始化SD卡,挂载文件系统,创建“test.txt”,在“test.txt”中写入“hello world”这12个字符(后面的size写了11,也就是忽略了“”),关闭文件。

插入SD卡然后上电。LED闪烁周期为1s。断电,将SD卡插入电脑,查看是否产生了一个名为“TEST.TXT”的文件,并且文件里面的内容为“hello world”。

4 完成SDBootloader程序编写

有了上面的基础,编写SDBootloader就简单了。

首先上电检测KEY是否按下,如果按下了就开始接收程序,如果没有按下就正常运行APP程序。这里说明一下,SDBootloader程序的大小约为14KByte,因此这次把bootloader区域设置为了:0x08000000~0x08003FFF,共16KByte。也就是APP程序的开始地址改为了:0x08004000。

先看main()函数:

int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t ret = 0;
  /* 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 */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  /* 判断是否需要下载程序 */
  if(Boot_ReadKey(KEY_GPIO_Port, KEY_Pin) == KEY_PRESS)
  {
    ret = Boot_DownLoadBin();
  }
  else
  {
    Boot_RunApp(APP_ADDRESS);
    ret = 0;
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if(ret == 1)
    {
      Boot_RunApp(APP_ADDRESS);
      HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
      HAL_Delay(1000);
    }
    else
    {
      HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
      HAL_Delay(300);
    }
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

过程比较简单,按键按下了就从SD卡加载固件,加载成功后就跳转到APP_ADDRESS。如果跳转不成功,那LED就会以1s的周期闪烁。如果加载不成功,那LED就以500ms的周期闪烁。

接下来看关键的Boot_DownLoadBin()函数:

/*
 * description : 加载bin文件
 * param : none
 * return : 0 - downloading; 1 - complete;
 */
#define BUF_SIZE	512
uint8_t readBuf[BUF_SIZE];
FATFS fs;			//文件系统盘符
FIL filp;			//文件句柄
UINT bw, br;		//读写指针,读写出错时可以指示发生错误的位置
FRESULT ret;		//操作结果
FILINFO fno;		//文件信息
DWORD fileSize;		//文件大小
uint8_t Boot_DownLoadBin(void)
{
	uint32_t i;
	uint8_t checkSum;
    
	/* 1、初始化SD卡 */
	if(SD_Initialize() != 0)	//初始化SD卡失败
	{
		return 0;
	}
    
	/* 2、挂载文件系统 */
	ret = f_mount(&fs,"0:",1);
	if(ret != FR_OK)
	{
		goto MOUNT_ERR;	//挂载失败
	}
    
	/* 3、查看烧写文件是否存在 */
	ret = f_stat("0:/download.bin", &fno);
	if(ret != FR_OK)
	{
		goto FILE_ERR;
	}
	fileSize = fno.fsize;
    
	/* 4、打开文件 */
	ret = f_open(&filp, "0:/download.bin", FA_READ);  //读取模式
	if(ret != FR_OK)
	{
		goto OPEN_ERR;	//打开失败
	}
    
	/* 5、循环读取数据内容,并写入Flash */
	i=0;
	checkSum = 0;
	while(1)
	{
		if(i >= fileSize)
			break;
		if((i+BUF_SIZE) <= fileSize)
		{
			ret = f_read(&filp, readBuf, BUF_SIZE, &br);
			checkSum = Boot_CheckSum(readBuf, 0, BUF_SIZE - 1) ^ checkSum;
			Flash_RamToFlash(readBuf, APP_ADDRESS + i, BUF_SIZE);
			i += BUF_SIZE;
		}
		else
		{
			ret = f_read(&filp, readBuf, fileSize - i, &br);
			checkSum = Boot_CheckSum(readBuf, 0, fileSize - i - 1) ^ checkSum;
			Flash_RamToFlash(readBuf, APP_ADDRESS + i, fileSize - i);
			i += (fileSize - i);
		}
		if(ret != FR_OK)
		{
			goto READ_ERR;	//读取错误
		}
	}
    
	/* 5.5、检查检验和 */
	if(checkSum == 0x00)
	{
		goto ALL_OK;
	}
	else
	{
		goto CHECKSUN_ERR;
	}
/* 6、错误处理 */
CHECKSUN_ERR:
READ_ERR:
	f_close(&filp);       //关闭文件
OPEN_ERR:
FILE_ERR:
	f_mount(&fs,NULL,1);  //取消挂载
MOUNT_ERR:
	return 0;
/* 7、没有发生错误 */
ALL_OK:
    return 1;
}

步骤1~4跟前面测试FATFS的程序一样。

步骤5是以512字节为单位,依次读取“download.bin”文件内的内容。这样做的原因是比较节省RAM。不管程序有多大,烧录过程使用的RAM固定是512字节不变。

步骤5需要区分两种情况,一种是本次读取内容的大小是正常的512字节。另一种情况是最后一个块不足512字节了。这里简单判断一下,区分处理即可。

函数用了goto来实现错误处理,可能触犯了很多人坚决不用goto的信条。这东西见仁见智吧,好用是真的好用。

步骤5.5里面检查了校验和是否为0。这个操作的前提是,我把bin文件拖到SD卡之前,把整个bin文件的异或(^)校验和添加到了bin文件的最后一个字节(使用UltraEdit)。因此到单片机去计算整个文件的异或校验和时,就会得到0。

5 总结

整个过程下来还是比较顺利的。重点是移植SD卡驱动和移植FATFS,这两步完成后,剩下的逻辑就非常简单了,特别是对于有文件编程经验的人来说。

联系方式:489304195@qq.com

最后

以上就是寒冷爆米花为你收集整理的STM32F103C8T6通过SD卡加载固件STM32F103C8T6通过SD卡加载固件的全部内容,希望文章能够帮你解决STM32F103C8T6通过SD卡加载固件STM32F103C8T6通过SD卡加载固件所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部