SPI FLASH+FATFS+USB MASS STORAGE硬件驱动

一、背景
1.1 硬件驱动层
工作也有几年了,越来越觉得有必要做一个类似于linux的硬件驱动层,可以屏蔽所有硬件差异,再做新的项目,需要更改主控或者外设时,只需要更改相关的芯片支持包,让我们更加专注于业务逻辑层的开发。
1.2 目标
rtthread已经有现成的相关软件框架,到时候可以借鉴一下,目前还是想自己写代码实现一遍以下的东西,只有真正做起来的才会发现问题,才会有进步的可能:
bootloader+
hardware_driver_layer+
fatfs+
usb host,usb device+
lcd+touch+lvgl+
freertos(rtthread)+
memory_manage+
(nandflash or norflash)+(psramor+sdram)+
script(java,lua,python,jerryscript)+
openharmony_ui_kit_ace_lite
二、目标状态
目前已经实现了
bootloader+
fatfs+
usb device(usb mass storage)+
script(java)
下一个目标是lcd+touch+lvgl
三、spi flash(nor flash)
nor flash操作起来非常方便,不需要关系nand flash的虚拟地址映射、坏块管理、ecc等问题。
3.1 w25q128
w25q128是winbond公司的,顾名思义有128mbit容量,也就是16m字节大小,每页有256字节,每个扇区有4096字节,每个块有64k,每次可擦除一个扇区字节大小。
3.2 驱动程序
这里使用的是spi2作为通信接口
#include stdio.h#include common.h#include stm32_kernel.h#include stm32f10x_spi.h#include stm32_spi.h #include deviceaccess_spi.h #include deviceaccess_gpio.h #define w25q80 0xef13 #define w25q16 0xef14#define w25q32 0xef15#define w25q64 0xef16#define w25q128 0xef17//////////////////////////////////////////////////////////////////////////////指令表#define w25x_writeenable 0x06 #define w25x_writedisable 0x04 #define w25x_readstatusreg 0x05 #define w25x_writestatusreg 0x01 #define w25x_readdata 0x03 #define w25x_fastreaddata 0x0b #define w25x_fastreaddual 0x3b #define w25x_pageprogram 0x02 #define w25x_blockerase 0xd8 #define w25x_sectorerase 0x20 #define w25x_chiperase 0xc7 #define w25x_powerdown 0xb9 #define w25x_releasepowerdown 0xab #define w25x_deviceid 0xab #define w25x_manufactdeviceid 0x90 #define w25x_jedecdeviceid 0x9f void w25qxx_init(void);u16 w25qxx_readid(void); //读取flash idu8 w25qxx_readsr(void); //读取状态寄存器 void w25qxx_write_sr(u8 sr); //写状态寄存器void w25qxx_write_enable(void); //写使能 void w25qxx_write_disable(void); //写保护void w25qxx_write_nocheck(u8* pbuffer,u32 writeaddr,u16 numbytetowrite);void w25qxx_read(u8* pbuffer,u32 readaddr,u16 numbytetoread); //读取flashvoid w25qxx_write(u8* pbuffer,u32 writeaddr,u16 numbytetowrite);//写入flashvoid w25qxx_erase_chip(void); //整片擦除void w25qxx_erase_sector(u32 dst_addr); //扇区擦除void w25qxx_wait_busy(void); //等待空闲void w25qxx_powerdown(void); //进入掉电模式void w25qxx_wakeup(void); //唤醒//gpiox = p*32+ndeviceaccess_spi_t spi_2 = {spi_2,spi_mode_master,8,spi_bit_order_msb,spi_cpol_h,spi_cpha_edge_2};deviceaccess_gpio_t gpio_ext_flash_cs = {44,gpio_mode_pushpull,gpio_direction_output,true,null};//#define w25qxx_cs pbout(12) //w25qxx的片选信号#define spi_flash_cs_h {g_deviceaccess_gpio_control(&gpio_ext_flash_cs,cmd_write,true);}#define spi_flash_cs_l {g_deviceaccess_gpio_control(&gpio_ext_flash_cs,cmd_write,false);}uint16_t get_flash_id = 0; u16 w25qxx_readid(void){ u16 temp = 0; uint8_t set_tx_buffer[] = {0x90,0xff,0xff,0xff}; uint8_t set_rx_buffer[2]; spi_flash_cs_l; g_deviceaccess_spi_control(&spi_2,cmd_write,set_tx_buffer,length_of_array(set_tx_buffer),null);//发送读取id命令 g_deviceaccess_spi_control(&spi_2,cmd_read,set_rx_buffer,length_of_array(set_rx_buffer),null); temp|=set_rx_buffer[0]< >16),(u8)((readaddr) >>8),(u8)readaddr}; g_deviceaccess_spi_control(&spi_2,cmd_write,set_tx_buffer,length_of_array(set_tx_buffer),null); g_deviceaccess_spi_control(&spi_2,cmd_read,pbuffer,numbytetoread,null); spi_flash_cs_h; } //spi在一页(0~65535)内写入少于256个字节的数据//在指定地址开始写入最大256字节的数据//pbuffer:数据存储区//writeaddr:开始写入的地址(24bit)//numbytetowrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!! void w25qxx_write_page(u8* pbuffer,u32 writeaddr,u16 numbytetowrite){ u16 i; uint8_t set_tx_buffer[] = {w25x_pageprogram,(u8)((writeaddr) >>16),(u8)((writeaddr) >>8),(u8)(writeaddr)}; w25qxx_write_enable(); //set wel spi_flash_cs_l; //使能器件 g_deviceaccess_spi_control(&spi_2,cmd_write,set_tx_buffer,length_of_array(set_tx_buffer),null); g_deviceaccess_spi_control(&spi_2,cmd_write,pbuffer,numbytetowrite,null); spi_flash_cs_h; //取消片选 w25qxx_wait_busy(); //等待写入结束} //无检验写spi flash //必须确保所写的地址范围内的数据全部为0xff,否则在非0xff处写入的数据将失败!//具有自动换页功能 //在指定地址开始写入指定长度的数据,但是要确保地址不越界!//pbuffer:数据存储区//writeaddr:开始写入的地址(24bit)//numbytetowrite:要写入的字节数(最大65535)//check okvoid w25qxx_write_nocheck(u8* pbuffer,u32 writeaddr,u16 numbytetowrite) { u16 pageremain; pageremain=256-writeaddr%256; //单页剩余的字节数 if(numbytetowritepageremain { pbuffer+=pageremain; writeaddr+=pageremain; numbytetowrite-=pageremain; //减去已经写入了的字节数 if(numbytetowrite >256)pageremain=256; //一次可以写入256个字节 else pageremain=numbytetowrite; //不够256个字节了 } } } //写spi flash //在指定地址开始写入指定长度的数据//该函数带擦除操作!//pbuffer:数据存储区//writeaddr:开始写入的地址(24bit) //numbytetowrite:要写入的字节数(最大65535) u8 w25qxx_buffer[4096]; void w25qxx_write(u8* pbuffer,u32 writeaddr,u16 numbytetowrite) { u32 secpos; u16 secoff; u16 secremain; u16 i; u8 * w25qxx_buf; w25qxx_buf=w25qxx_buffer; secpos=writeaddr/4096;//扇区地址 secoff=writeaddr%4096;//在扇区内的偏移 secremain=4096-secoff;//扇区剩余空间大小 //printf(ad:%x,nb:%xrn,writeaddr,numbytetowrite);//测试用 if(numbytetowrite<=secremain)secremain=numbytetowrite;//不大于4096个字节 while(1) { w25qxx_read(w25qxx_buf,secpos*4096,4096);//读出整个扇区的内容 for(i=0;i< secremain;i++)//校验数据 { if(w25qxx_buf[secoff+i]!=0xff)break;//需要擦除 } if(i< secremain)//需要擦除 { w25qxx_erase_sector(secpos); //擦除这个扇区 for(i=0;i4096)secremain=4096;//下一个扇区还是写不完 else secremain=numbytetowrite; //下一个扇区可以写完了 } }; }//擦除整个芯片 //等待时间超长...void w25qxx_erase_chip(void) { uint8_t set_tx_buffer[1] = {w25x_chiperase}; w25qxx_write_enable(); //set wel w25qxx_wait_busy(); spi_flash_cs_l; //使能器件 w25qxx_write_enable(); //set wel spi_flash_cs_l; //使能器件 g_deviceaccess_spi_control(&spi_2,cmd_write,set_tx_buffer,length_of_array(set_tx_buffer),null); spi_flash_cs_h; //取消片选 w25qxx_wait_busy(); //等待芯片擦除结束} //擦除一个扇区//dst_addr:扇区地址 根据实际容量设置//擦除一个山区的最少时间:150msvoid w25qxx_erase_sector(u32 dst_addr) { uint8_t set_tx_buffer[] = {w25x_sectorerase,(u8)((dst_addr) >>16),(u8)((dst_addr) >>8),(u8)dst_addr}; //监视falsh擦除情况,测试用 //printf(fe:%xrn,dst_addr); dst_addr*=4096; w25qxx_write_enable(); //set wel w25qxx_wait_busy(); spi_flash_cs_l; //使能器件 g_deviceaccess_spi_control(&spi_2,cmd_write,set_tx_buffer,length_of_array(set_tx_buffer),null); spi_flash_cs_h; //取消片选 w25qxx_wait_busy(); //等待擦除完成} //等待空闲void w25qxx_wait_busy(void) { while((w25qxx_readsr()&0x01)==0x01); // 等待busy位清空} //进入掉电模式void w25qxx_powerdown(void) { uint8_t set_tx_buffer[] = {w25x_powerdown}; spi_flash_cs_l; //使能器件 g_deviceaccess_spi_control(&spi_2,cmd_write,set_tx_buffer,length_of_array(set_tx_buffer),null); spi_flash_cs_h; //取消片选 //delay_us(3); //等待tpd } //唤醒void w25qxx_wakeup(void) { uint8_t set_tx_buffer[] = {w25x_releasepowerdown}; spi_flash_cs_l; //使能器件 g_deviceaccess_spi_control(&spi_2,cmd_write,set_tx_buffer,length_of_array(set_tx_buffer),null); spi_flash_cs_h; //取消片选 //delay_us(3); //等待tres1}四、 fatfs
4.1 c源文件
这里就用到了2个c文件:diskio.c,ff.c
4.2 配置
打开ffconf.h头文件,作以下更改
#define _use_lfn 0#define _min_ss 512#define _max_ss 40964.3 接口代码
dstatus disk_initialize ( byte pdrv /* physical drive nmuber to identify the drive */){ u8 res=0; switch(pdrv) { case ex_flash://外部flash w25qxx_init(); break; default: res=1; } if(res)return sta_noinit; else return 0; //初始化成功 } dresult disk_read ( byte pdrv, /* physical drive nmuber to identify the drive */ byte *buff, /* data buffer to store read data */ dword sector, /* sector address in lba */ uint count /* number of sectors to read */){ u8 res=0; if (!count)return res_parerr;//count不能等于0,否则返回参数错误 switch(pdrv) { case ex_flash://外部flash sector+=config_start_sector; for(;count >0;count--) { w25qxx_read(buff,sector*flash_sector_size,flash_sector_size); sector++; buff+=flash_sector_size; } res=0; break; default: res=1; } if(res==0x00)return res_ok; else return res_error; }dresult disk_write ( byte pdrv, /* physical drive nmuber to identify the drive */ const byte *buff, /* data to be written */ dword sector, /* sector address in lba */ uint count /* number of sectors to write */){ u8 res=0; if (!count)return res_parerr;//count不能等于0,否则返回参数错误 switch(pdrv) { case ex_flash://外部flash sector+=config_start_sector; swdata_detfromflash(sector*flash_sector_size,count*flash_sector_size); for(;count >0;count--) { // w25qxx_write((u8*)buff,sector*flash_sector_size,flash_sector_size); w25qxx_write_nocheck((u8*)buff,sector*flash_sector_size,flash_sector_size); sector++; buff+=flash_sector_size; } res=0; break; default: res=1; } //处理返回值,将spi_sd_driver.c的返回值转成ff.c的返回值 if(res == 0x00)return res_ok; else return res_error; }dresult disk_ioctl ( byte pdrv, /* physical drive nmuber (0..) */ byte cmd, /* control code */ void *buff /* buffer to send/receive control data */){dresult res; switch(cmd) { case ctrl_sync: res = res_ok; break; case get_sector_size: *(word*)buff = flash_sector_size; res = res_ok; break; case get_block_size: *(word*)buff = flash_block_size; res = res_ok; break; case get_sector_count: *(dword*)buff = flash_sector_count; res = res_ok; break; default: res = res_parerr; break; } return res;}4.4 注意事项
返回给文件系统的扇区大小必须是flash实际的扇区大小,可以自定义文件系统的大小,
格式化文件系统时要选择带引导分区的类型即:res=f_mkfs(,0,4096):
写操作时不应使用w25qxx_write,而是先计算需要擦除的大小,来进行擦除再进行写操作
#define flash_sector_size 4096 #define flash_block_size 16 u16 flash_sector_count= 2696;4.5 测试代码
u8 exf_getfree(u8 *drv,u32 *total,u32 *free){ fatfs *fs1; u8 res; u32 fre_clust=0, fre_sect=0, tot_sect=0; //得到磁盘信息及空闲簇数量 res =(u32)f_getfree((const tchar*)drv, (dword*)&fre_clust, &fs1); if(res==0) { tot_sect=(fs1- >n_fatent-2)*fs1- >csize; //得到总扇区数 fre_sect=fre_clust*fs1- >csize; //得到空闲扇区数 #if _max_ss!=512 //扇区大小不是512字节,则转换为512字节 tot_sect*=fs1- >ssize/512; fre_sect*=fs1- >ssize/512;#endif *total=tot_sect >>1; //单位为kb *free=fre_sect >>1; //单位为kb } return res;} res = f_mount(&temp_fs,,1); if(res!=fr_ok)//flash磁盘,fat文件系统错误,重新格式化flash { printf(flash disk formatting...); //格式化flash w25qxx_erase_chip(); res=f_mkfs(,0,4096); delay_ms(1000); } while(exf_getfree(,&total,&free)) //得到sd卡的总容量和剩余容量 { printf(extflash fatfs error!); delay_ms(200); } printf(fatfs ok!); printf(sd total size:%dmb,total >>10); printf(sd free size:%dmb,free >>10); char *filepath = 1.txt; uint br; open_result = f_mount(&temp_fs,,1); open_result =f_open(&f_txt,(const tchar*)filepath,fa_create_always|fa_write); if(open_result==fr_ok) { printf(open file okn); read_result=f_write(&f_txt,data,sizeof(data)/sizeof(data[0]),(uint*)&br); if(read_result==fr_ok) { printf(write okn); printf(br=%dn,br); } f_close(&f_txt); open_result =f_open(&f_txt,(const tchar*)filepath,fa_read); printf(open_result=%dn,open_result); if(open_result==fr_ok) { read_result=f_read(&f_txt,data_read,sizeof(data)/sizeof(data[0]),(uint*)&br); printf(read_result=%dn,read_result); if(read_result==fr_ok) { int i = 0; printf(read okn); printf(br=%dn,br); for(i=0;ictrl_info.usb_wlength = lun_data_length; return 0; } else { return((uint8_t*)(&max_lun)); }}5.2 usb缓存大小定义
更改usb缓存大小为flash的实际扇区大小4k
在memory.c中定义data_bufferw为1024,也就是4096u32 data_buffer[1024];5.3 接口代码
uint16_t mal_init(uint8_t lun){ u16 status=mal_ok; switch (lun) { case 0: break; case 1: break; default: return mal_fail; } return status;}uint16_t mal_write(uint8_t lun, uint64_t memory_offset, uint32_t *writebuff, uint16_t transfer_length){ u8 sta; switch (lun) //这里,根据lun的值确定所要操作的磁盘 { case 0: //磁盘0为 spi flash盘 sta=0; swdata_detfromflash(config_fatfs_start_address+memory_offset,transfer_length); w25qxx_write_nocheck((u8*)writebuff,config_fatfs_start_address+memory_offset,transfer_length); break; default: return mal_fail; } if(sta!=0)return mal_fail; return mal_ok; }uint16_t mal_read(uint8_t lun, uint64_t memory_offset, uint32_t *readbuff, uint16_t transfer_length){ u8 sta; switch (lun) //这里,根据lun的值确定所要操作的磁盘 { case 0: //磁盘0为 spi flash盘 sta=0; w25qxx_read((u8*)readbuff, config_fatfs_start_address+memory_offset, transfer_length); break; default: return mal_fail; } if(sta!=0)return mal_fail; return mal_ok;}uint16_t mal_getstatus (uint8_t lun){ switch(lun) { case 0: return mal_ok; case 1: return mal_ok; default: return mal_fail; } }4 初始化代码mass_memory_size[0]=1024*1024*10; //前12m字节 mass_block_size[0] =4096; //设置spi flash的操作扇区大小为512 mass_block_count[0]=mass_memory_size[0]/mass_block_size[0]; delay_ms(1800); usb_port_set(0); //usb先断开 delay_ms(300); usb_port_set(1); //usb再次连接 //usb配置 usb_interrupts_config(); set_usbclock(); usb_init(); delay_ms(1800);六、遇到的坑
6.1 id信息
可能是w25q128的硬件有问题,在上电时需要读两次才能读到id信息,但id信息与数据手册上的对不上,可能ic是盗版的吧。
6.2 文件系统格式化失败
在格式化时最好加上整个flash或者指定大小的擦除操作再进行格式化
6.3 文件系统读写失败
在进行读操作时,可能是出现读到的数据全是错的问题,后来排查发现是w25qxx_write的问题,更改为先查出再写后问题得到解决。
6.4 文件系统读不到正确的容量大小
将最大扇区和返回扇区大小更改为4096
6.5 usb mass storage打不开磁盘
将usb缓存区更改为总大小为flash实际扇区大小4096,在usb初始化前设置好扇区大小为实际的flash扇区大小,扇区数量,磁盘容量更改为实际需要
6.6 烧录文件系统到外部flash的方法
6.6.1 制作镜像文件
使用diskgenius工具创建虚拟磁盘镜像文件,由于现有的磁盘分区工具只兼容512字节扇区的硬件,所以在文件系统所使用的写flash接口需要使用带检查的写操作,也就是在写的时候不影响其他扇区的数据,这样的话就可以兼容fatfs最大扇区为512字节的配置
6.6.2 烧录镜像文件到外部flash
6.7 加速flash访问
使用dma方式进行spi的读写
if(send_ptr != rt_null) { volatile uint8_t dummy_data; dma_init_type dma_init_struct; dma_reset(dma2_channel5); dma_reset(dma2_channel4); dma_default_para_init(&dma_init_struct); dma_init_struct.buffer_size = size; dma_init_struct.direction = dma_dir_peripheral_to_memory; dma_init_struct.memory_base_addr = (uint32_t)&dummy_data; dma_init_struct.memory_data_width = dma_memory_data_width_byte; dma_init_struct.memory_inc_enable = false; dma_init_struct.peripheral_base_addr = (uint32_t)(&spi_instance- >config- >spi_x- >dt); dma_init_struct.peripheral_data_width = dma_peripheral_data_width_byte; dma_init_struct.peripheral_inc_enable = false; dma_init_struct.priority = dma_priority_very_high; dma_init_struct.loop_mode_enable = false; dma_init(dma2_channel4, &dma_init_struct); dma_init_struct.buffer_size = message- >length; dma_init_struct.direction = dma_dir_memory_to_peripheral; dma_init_struct.memory_base_addr = (uint32_t)send_ptr; dma_init_struct.memory_data_width = dma_memory_data_width_byte; dma_init_struct.memory_inc_enable = true; dma_init_struct.peripheral_base_addr = (uint32_t)(&spi_instance- >config- >spi_x- >dt); dma_init_struct.peripheral_data_width = dma_peripheral_data_width_byte; dma_init_struct.peripheral_inc_enable = false; dma_init_struct.priority = dma_priority_very_high; dma_init_struct.loop_mode_enable = false; dma_init(dma2_channel5, &dma_init_struct); spi_i2s_dma_transmitter_enable(spi_instance- >config- >spi_x, true); spi_i2s_dma_receiver_enable(spi_instance- >config- >spi_x, true); dma_channel_enable(dma2_channel4, true); dma_channel_enable(dma2_channel5, true); while(dma_flag_get(dma2_fdt4_flag) == reset); dma_flag_clear(dma2_fdt4_flag); dma_channel_enable(dma2_channel5, false); dma_channel_enable(dma2_channel4, false); spi_i2s_dma_receiver_enable(spi_instance- >config- >spi_x, false); spi_i2s_dma_transmitter_enable(spi_instance- >config- >spi_x, false); } else if(recv_ptr != rt_null) { uint8_t write_value = 0xa5; dma_init_type dma_init_struct; dma_reset(dma2_channel5); dma_reset(dma2_channel4); dma_default_para_init(&dma_init_struct); dma_init_struct.buffer_size = size; dma_init_struct.direction = dma_dir_memory_to_peripheral; dma_init_struct.memory_base_addr = (uint32_t)&write_value; dma_init_struct.memory_data_width = dma_memory_data_width_byte; dma_init_struct.memory_inc_enable = false; dma_init_struct.peripheral_base_addr = (uint32_t)(&spi_instance- >config- >spi_x- >dt); dma_init_struct.peripheral_data_width = dma_peripheral_data_width_byte; dma_init_struct.peripheral_inc_enable = false; dma_init_struct.priority = dma_priority_very_high; dma_init_struct.loop_mode_enable = false; dma_init(dma2_channel5, &dma_init_struct); dma_init_struct.buffer_size = size; dma_init_struct.direction = dma_dir_peripheral_to_memory; dma_init_struct.memory_base_addr = (uint32_t)recv_ptr; dma_init_struct.memory_data_width = dma_memory_data_width_byte; dma_init_struct.memory_inc_enable = true; dma_init_struct.peripheral_base_addr = (uint32_t)(&spi_instance- >config- >spi_x- >dt); dma_init_struct.peripheral_data_width = dma_peripheral_data_width_byte; dma_init_struct.peripheral_inc_enable = false; dma_init_struct.priority = dma_priority_very_high; dma_init_struct.loop_mode_enable = false; dma_init(dma2_channel4, &dma_init_struct); // spi_i2s_dma_transmitter_enable(spi_instance- >config- >spi_x, true); spi_i2s_dma_receiver_enable(spi_instance- >config- >spi_x, true); // dma_channel_enable(dma2_channel4, true); dma_channel_enable(dma2_channel5, true); while(dma_flag_get(dma2_fdt4_flag) == reset); dma_flag_clear(dma2_fdt4_flag); dma_channel_enable(dma2_channel5, false); dma_channel_enable(dma2_channel4, false); spi_i2s_dma_receiver_enable(spi_instance- >config- >spi_x, false); spi_i2s_dma_transmitter_enable(spi_instance- >config- >spi_x, false); }#endif

聊天机器人在教育行业有什么优势
32核、64线程Ryzen Threadripper 2990WX芯片正在进行预订,价格高达1,799美
中国军团AI芯片哪家强?
考种仪的应用可以有效提升工作效率
如何制定一个适合的监控和数据采集(SCADA)系统总体规划
SPI FLASH+FATFS+USB MASS STORAGE硬件驱动
霍尔元件如何选型如何使用
新思科技发布业界首款全栈式AI驱动型EDA解决方案Synopsys.ai
plc如何进行选购
矽杰微:打造中国“芯”,助力雷达传感器多场景应用
恒温恒湿箱,恒温恒湿试验箱,高低温试验箱
无线智能电表采集网关支持modbus协议采集
武汉裕诚科汇推动AloT物联网芯片行业赋能
Android 11可提示手机未正确放置在无线充电板
微雪电子BME280环境传感器温湿度简介
测量绝缘电阻可用什么仪器
互联网+是什么意思_是谁提出的_有什么意义
新一代多路复用ADC如何简化复杂系统设计
AMD Zen3相比初代架构游戏性能提升达81%
余压传感器(探测器)的安装高度是多少