完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
STM32F103C8T6通过SD卡加载固件
前面写了通过Uart加载固件,这次就使用SD卡来尝试一下加载固件吧。 要实现SD卡加载固件的功能,需要完成以下三项工作:
这个前面的文章详细地写了,这里不展开,直接引用以前的代码即可。 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文件系统非常容易移植,只需两步:
#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()的参数。
插入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,这两步完成后,剩下的逻辑就非常简单了,特别是对于有文件编程经验的人来说。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1781 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1621 浏览 1 评论
1085 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
728 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1680 浏览 2 评论
1938浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
732浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
570浏览 3评论
596浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
557浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-24 09:15 , Processed in 0.876996 second(s), Total 79, Slave 63 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号