本帖最后由 辛一_e1e 于 2022-3-26 11:43 编辑
Sipeed基于高云FPGA(型号GW1NR)打造的TangNano9K板载了TF(Micro SD)卡槽,通过4线SPI与FPGA的Pin脚进行连接。
在以往基于纯数字逻辑设计的FPGA开发中,会设计一个SPI Master模块,然后通过FSM状态机进行SPI的读写。但是由于SD复杂的驱动控制逻辑,势必会使得整个模块臃肿。而基于CPU设计的SD卡驱动,不仅可以利用C语言进行编程,使得控制逻辑清晰明了,而且还可以移植文件系统,使得SD卡资源管理更加人性化。
为了简化整个系统的开发难度,我选择了高云提供的软核IP——Gowin_PicoRV32。官方提供的IP不仅在资源利用率和时序优化上有所保证,还可以使用官方的IDE——GMD(Gowin MCU Designer)进行C语言开发。
那么首先需要设计硬件,使用的Gowin FPGA Designer,版本为1.9.8。在IP生成中选择Gowin_PicoRV32,软核最大可以跑到50MHz,这个频率做一些基本控制是绰绰有余的。
打开IP后,可以看到它不单单只有CPU,还挂载了TCM内存和一些常用外设,还提供了AHB和Wishbone总线的扩展,相当人性化。点击每一个模块还支持模块的功能裁剪。
为了留出更多资源更后续开发,我对CPU进行裁剪,去掉了RV32C和RV32M指令集的扩展,关闭了Jtag Debug功能,最后选项如下图。
然后是定制ITCM和DTCM,由于我选择将程序编译后直接放到ITCM中运行(MCU boot and run in ITCM),所以分给了ITCM 32KB的空间(有点奢侈~),DTCM保持默认16KB。
外设方面,启用UART来输出打印信息,SPI Master用于与SD卡通信,GPIO用来点灯。我还打开了AHB扩展,并在上面挂载了一片内存用于后续LCD的显存。
还需要调用PLL,为CPU提供50MHz的时钟,最后绑定好pin脚,生成FPGA的下载文件。
接下去的工作就要转到GMD中了。参考高云官方文档IPUG910进行开发环境搭建和程序编译,外设的驱动编写可以参考IPUG911,最后程序的下载可以参考IPUG913。
C的开发环境搭建完成后,就开始进行SD卡驱动和FatFs的移植,这里我将SD卡作为只读设备,编写了相应的驱动。
SD卡的通信,主要是通过Matser发送CMD命令进行的,驱动见下面代码。
- #define SPI_ID 0
- uint8_t sd_sendcmd(uint8_t cmd, uint32_t arg, uint8_t crc)
- {
- uint8_t r1, cnt;
- cnt = 0;
- wbspi_master_select_slave(PICO_WBSPI_MASTER,SPI_ID);
- wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- wbspi_master_txdata(PICO_WBSPI_MASTER,(cmd | 0x40));
- wbspi_master_txdata(PICO_WBSPI_MASTER,arg>>24);
- wbspi_master_txdata(PICO_WBSPI_MASTER,arg>>16);
- wbspi_master_txdata(PICO_WBSPI_MASTER,arg>>8);
- wbspi_master_txdata(PICO_WBSPI_MASTER,arg);
- wbspi_master_txdata(PICO_WBSPI_MASTER,crc);
- do{
- r1 = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- cnt++;
- if(cnt > 50) break;
- }while(r1 == 0xFF);
- return r1;
- }
复制代码
基于这个函数,就开始编写SD卡初始化函数,初始化的流程为1、发送大于74个周期的时钟信号,等待SD卡内部逻辑稳定;2、发送CMD0,让SD卡进入IDLE状态;3、发送CMD8,查询卡的型号是不是支持SD 2.0协议;4、这里只处理支持SD 2.0协议的卡,发送CMD55+ACMD41进行初始化;5、发送CMD58,查询卡支不支持SDHC;6、发送CMD9,CMD10,获取SD卡的CID和OCR信息。
- uint8_t sd_init(void)
- {
- uint32_t i;
- uint8_t r1;
- uint8_t buff[16];
- uint8_t cnt = 0;
- wbspi_master_select_slave(PICO_WBSPI_MASTER,SPI_ID);
- for(i=0; i<1000; i++);
- for(i=0; i<10; i++)
- wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- r1 = sd_sendcmd(0,0,0x95);
- r1 = sd_sendcmd(8,0x1aa,0x87);
- if(r1 == 0x01)
- {
- buff[0] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buff[1] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buff[2] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buff[3] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- do{
- r1 = sd_sendcmd(55,0,0);
- if(r1 != 0x01)
- return -1;
- r1 = sd_sendcmd(41,0x40000000,1);
- cnt++;
- if(cnt>100) return -1;
- }while(r1!=0);
- }
- r1 = sd_sendcmd(58,0,0);
- if(r1 != 0x00) return -1;
- buff[0] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buff[1] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buff[2] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buff[3] = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- if(buff[0]&0x40)
- printf("sdhc rdyrn");
- else
- printf("sd2.0 rdyrn");
- r1 = sd_sendcmd(9,0,0xFF);
- if(r1 != 0x00) return -1;
- do{
- r1 = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- }while(r1 != 0xFE);
- for(i=0; i<16; i++)
- {
- r1 = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- }
- r1 = sd_sendcmd(10,0,0xFF);
- if(r1 != 0x00) return -1;
- do{
- r1 = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- }while(r1 != 0xFE);
- for(i=0; i<16; i++)
- {
- r1 = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- }
- return 0;
- }
复制代码
下面是SD卡读单块和多块的驱动。
- BYTE SD_ReadSingleBlock(UINT sector, BYTE *buffer)
- {
- BYTE r1;
- WORD i;
- i=512;
- r1 = sd_sendcmd(17, sector, 1); //发送CMD17 读命令
- if(r1 != 0x00) return r1;
- do{
- r1 = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- }while(r1 != 0xFE);
- while(i!=0)
- {
- *buffer = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buffer++;
- i--;
- }
- wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- return 0; //读取正确,返回0
- }
- BYTE SD_ReadMultiBlock(UINT sector, BYTE *buffer, BYTE count)
- {
- BYTE r1;
- WORD i;
- r1 = sd_sendcmd(18, sector, 1); //读多块命令
- if(r1 != 0x00) return r1;
- while(count != 0){
- i = 512;
- do{
- r1 = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- }while(r1 != 0xFE);
- while(i!=0)
- {
- *buffer = wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- buffer++;
- i--;
- }
- buffer+=512;
- count--;
- wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- }
- sd_sendcmd(12, 0, 1); //全部传输完成,发送停止命令
- wbspi_master_txdata(PICO_WBSPI_MASTER,0xFF);
- if(count != 0)
- return count; //如果没有传完,返回剩余个数
- else
- return 0;
- }
复制代码
SD卡驱动完成后,开始移植FatFs文件系统,源码下载http://elm-chan.org/fsw/ff/archives.html,选择最新的FatFs R0.14b ,并加入工程。
ffconf.h用于FatFs的定制,这里需要将FF_FS_READONLY的宏改为1,将SD卡作为只读设备。
还需要改写diskio.c文件,适配SD卡。这里只做了最简单的适配,完成了初始化和读,查询状态和获取时间都是空函数。由于宏设置,这两个函数disk_ioctl和disk_write就不管了。
- #define SD_CARD 0
- DSTATUS disk_initialize (
- BYTE pdrv /* Physical drive nmuber (0..) */
- )
- {
- DRESULT status = STA_NOINIT;
- switch(pdrv)
- {
- case SD_CARD://SD卡
- status = sd_init();
- break;
- default:
- status = STA_NOINIT;
- }
- return status;
- }
- //获得磁盘状态
- DSTATUS disk_status (
- BYTE pdrv /* Physical drive nmuber (0..) */
- )
- {
- return 0;
- }
- //读扇区
- //drv:磁盘编号0~9
- //*buff:数据接收缓冲首地址
- //sector:扇区地址
- //count:需要读取的扇区数
- DRESULT disk_read (
- BYTE pdrv, /* Physical drive nmuber (0..) */
- BYTE *buff, /* Data buffer to store read data */
- DWORD sector, /* Sector address (LBA) */
- UINT count /* Number of sectors to read (1..128) */
- )
- {
- DRESULT status = RES_PARERR;
- if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误
- switch(pdrv)
- {
- case SD_CARD://SD卡
- if(count == 1)
- status=SD_ReadSingleBlock(sector, buff);
- else
- status=SD_ReadMultiBlock(sector, buff, count);
- break;
- default:
- status=RES_PARERR;
- }
- return status;
- }
- DWORD get_fattime (void)
- {
- return 0;
- }
复制代码
最后进行测试,在SD卡的根目录放一个txt文件,然后RV32 CPU通过串口,将文件大小和内容打印出来。
- res = f_mount(&fs, "", 1);
- res = f_open(&file, "top.txt", FA_READ);
- printf("rnfile size:%drn", file.obj.objsize);
- f_read(&file, fbuff, file.obj.objsize, &br);
- printf("%srn",fbuff);
- f_close(&file);
复制代码
结果如下图。貌似Welcome写成了Welcom,不过读取的结果是对的就行了。愿所有 电子工程师遇到问题不要放弃,方法总比困难多,“Don't Worry. Be HAPPY”。
总结,在TangNano9K 开发板上,基于软核RV32 CPU搭建了嵌入式系统平台,并移植了FatFS文件系统,使得平台能够读取FAT32文件系统中的文件。
|