版权声明:本版面文章皆为原创、或参考其他技术网站、博客后自己动手做实验所得,转载请注明出处。

鸣谢:感谢eric硬汉

商务合作:lz_kwok@foxmail.com

易开嵌入式工作室

基于单片机系统的nandflash文件系统,一直是比较头疼的问题。由于nandflash存在硬件坏块,所以ecc动态均衡校验就显得十分重要。目前开源的ftl算法,都或多或少存在一些问题(没问题的也没开源,反正我没找到)。如此一来,keil的rl-flashfs就显得十足珍贵。

先来看看rl-flashfs的特点:

1. RL-FlashFS本身支持擦写均衡,坏块管理,ECC和掉电保护。
2. RL-FlashFS是FAT兼容的文件系统。
3. RL-FlashFS的文件名仅支持ASCII,不支持中文,这点要特别注意。
4. 首次格式化后使用,读速度2.3MB/S左右,写速度3.2MB/S左右,配置不同的文件系统缓冲大小,速度有区别。
5. RL-FlashFS的函数是标准的C库函数,跟电脑端的文件系统使用方法一样。
6. RL-FlashFS与FatFS的区别,FatFS仅是一个FAT类的文件件系统,擦写均衡,坏块管理,ECC和掉电保护都不支持。
    这些都需要用户自己去实现。
7. UFFS,YAFFS这两款文件系统是不兼容FAT的,也就是无法在Windows端模拟U盘。

但是比较讨厌的是,在MDK4.74之后,rl-flashfs与keil自家的操作系统做了比较深的耦合。虽然eric硬汉哥提供了freertos的教程,但是有没有可能把rl-flashfs与目前国内比较火的操作系统rt-thread做兼容呢?虽然rt-thread自带的uffs文件系统也能用,但是占用ram太高,且不兼容FAT。

说干就干,经过两天的移植和测试,初步完成rl-flashfs在rt-thread操作系统下的移植。

硬件平台:stm32f407zgtx

NANDFLASH:W29N02GVSIAA(FSMC)

(有需要开发板合作的可以私信邮箱)

从官网下载rl-arm的源码包,如下:

rt-thread有非常棒的scons构建工具,根据该构建语法,编写对应的配置文件Kconfig和SConscript。编写完成后,利用rt-thread提供的Env工具,输入menuconfig,按照下图进行选择。注意,由于时间关系,并未和rtt原生的虚拟文件系统做兼容适配,故选择关闭Device virtual file system

选择完毕后,在Env工具中 输入scons --target=mdk5,可自动将我配置好的源码目录加入到MDK中(本人使用的是mdk5.23)。工程如下:

工程中可以看到三个重要文件,寄File_Config.c、FS_NAND_FlashPrg.c和FSN_CM3.lib,注意FSN_CM3.lib只能用于keil下,对于使用IAR的同学,只能说声拜拜。

File_Config配置如下:

在FS_NAND_FlashPrg.c文件中,需要根据自己的硬件环境,实现如下五个函数:

const NAND_DRV nand0_drv = {Init,UnInit,PageRead,PageWrite,BlockErase,
};

为了实现这五个函数,首先新建drv_nand.c文件,实现W29N02GVSIAA驱动,代码如下:

static rt_uint8_t FSMC_NAND_ReadStatus(void)
{rt_uint8_t ucData;rt_uint8_t ucStatus = NAND_BUSY;NAND_CMD_AREA = NAND_CMD_STATUS;ucData = *(__IO rt_uint8_t *)(Bank_NAND_ADDR);if((ucData & NAND_ERROR) == NAND_ERROR){ucStatus = NAND_ERROR;}else if((ucData & NAND_READY) == NAND_READY){ucStatus = NAND_READY;}else{ucStatus = NAND_BUSY;}return (ucStatus);
}static rt_uint8_t FSMC_NAND_GetStatus(void)
{rt_uint32_t ulTimeout = 0x10000;rt_uint8_t ucStatus = NAND_READY;ucStatus = FSMC_NAND_ReadStatus();while ((ucStatus != NAND_READY) &&( ulTimeout != 0x00)){ucStatus = FSMC_NAND_ReadStatus();if(ucStatus == NAND_ERROR){return (ucStatus);}ulTimeout--;}if(ulTimeout == 0x00){ucStatus =  NAND_TIMEOUT_ERROR;}return (ucStatus);
}//读取NAND FLASH的ID
//不同的NAND略有不同,请根据自己所使用的NAND FALSH数据手册来编写函数
//返回值:NAND FLASH的ID值
static rt_uint32_t NAND_ReadID(void)
{NAND_IDTypeDef nand_id;HAL_NAND_Read_ID(&NAND_Handler,&nand_id);NAND_DEBUG("ID[%X,%X,%X,%X]\n",nand_id.Maker_Id,nand_id.Device_Id,nand_id.Third_Id,nand_id.Fourth_Id);return 0;
}  //复位NAND
//返回值:0,成功;
//    其他,失败
static rt_uint8_t NAND_Reset(void)
{ NAND_CMD_AREA = NAND_RESET;  //复位NANDif(FSMC_NAND_GetStatus()==NAND_READY)return 0;           //复位成功else return 1;         //复位失败
} void rt_hw_mtd_nand_deinit(void)
{HAL_NAND_DeInit(&NAND_Handler);
}//初始化NAND FLASH
rt_uint8_t rt_hw_mtd_nand_init(void)
{if(&NAND_Handler != NULL){rt_hw_mtd_nand_deinit();}FMC_NAND_PCC_TimingTypeDef ComSpaceTiming,AttSpaceTiming;NAND_Handler.Instance               = FMC_NAND_DEVICE;NAND_Handler.Init.NandBank          = FSMC_NAND_BANK2;                             //NAND挂在BANK2上NAND_Handler.Init.Waitfeature       = FSMC_NAND_PCC_WAIT_FEATURE_DISABLE;           //关闭等待特性NAND_Handler.Init.MemoryDataWidth   = FSMC_NAND_PCC_MEM_BUS_WIDTH_8;                //8位数据宽度NAND_Handler.Init.EccComputation    = FSMC_NAND_ECC_DISABLE;                        //不使用ECCNAND_Handler.Init.ECCPageSize       = FSMC_NAND_ECC_PAGE_SIZE_2048BYTE;             //ECC页大小为2kNAND_Handler.Init.TCLRSetupTime     = 1;                                            //设置TCLR(tCLR=CLE到RE的延时)=(TCLR+TSET+2)*THCLK,THCLK=1/180M=5.5nsNAND_Handler.Init.TARSetupTime      = 1;                                            //设置TAR(tAR=ALE到RE的延时)=(TAR+TSET+2)*THCLK,THCLK=1/180M=5.5n。   ComSpaceTiming.SetupTime        = 2;        //建立时间ComSpaceTiming.WaitSetupTime    = 5;        //等待时间ComSpaceTiming.HoldSetupTime    = 3;        //保持时间ComSpaceTiming.HiZSetupTime     = 1;        //高阻态时间AttSpaceTiming.SetupTime        = 2;        //建立时间AttSpaceTiming.WaitSetupTime    = 5;        //等待时间AttSpaceTiming.HoldSetupTime    = 3;        //保持时间AttSpaceTiming.HiZSetupTime     = 1;        //高阻态时间HAL_NAND_Init(&NAND_Handler,&ComSpaceTiming,&AttSpaceTiming); NAND_Reset();                     //复位NANDrt_thread_mdelay(100);return 0;
}rt_uint8_t FSMC_NAND_ReadPage(rt_uint8_t *_pBuffer, rt_uint32_t _ulPageNo, rt_uint16_t _usAddrInPage, rt_uint16_t NumByteToRead)
{rt_uint32_t i;NAND_CMD_AREA = NAND_AREA_A;//发送地址NAND_ADDR_AREA = _usAddrInPage;NAND_ADDR_AREA = _usAddrInPage >> 8;NAND_ADDR_AREA = _ulPageNo;NAND_ADDR_AREA = (_ulPageNo & 0xFF00) >> 8;NAND_ADDR_AREA = (_ulPageNo & 0xFF0000) >> 16;NAND_CMD_AREA = NAND_AREA_TRUE1;/* 必须等待,否则读出数据异常, 此处应该判断超时 */for (i = 0; i < 20; i++);while(rt_pin_read(NAND_RB)==0);/* 读数据到缓冲区pBuffer */for(i = 0; i < NumByteToRead; i++){_pBuffer[i] = NAND_DATA_AREA;}return RT_EOK;
}rt_uint8_t FSMC_NAND_WritePage(rt_uint8_t *_pBuffer, rt_uint32_t _ulPageNo, rt_uint16_t _usAddrInPage, rt_uint16_t NumByteToRead)
{rt_uint32_t i;rt_uint8_t ucStatus;NAND_CMD_AREA = NAND_WRITE0;//发送地址NAND_ADDR_AREA = _usAddrInPage;NAND_ADDR_AREA = _usAddrInPage >> 8;NAND_ADDR_AREA = _ulPageNo;NAND_ADDR_AREA = (_ulPageNo & 0xFF00) >> 8;NAND_ADDR_AREA = (_ulPageNo & 0xFF0000) >> 16;for (i = 0; i < 20; i++);for(i = 0; i < NumByteToRead; i++){NAND_DATA_AREA = _pBuffer[i];}NAND_CMD_AREA = NAND_WRITE_TURE1; for (i = 0; i < 20; i++);ucStatus = FSMC_NAND_GetStatus();if(ucStatus == NAND_READY)   {ucStatus = RTV_NOERR;}else if(ucStatus == NAND_ERROR){ucStatus = ERR_NAND_PROG;      }else if(ucStatus == NAND_TIMEOUT_ERROR){ucStatus = ERR_NAND_HW_TOUT;        }return (ucStatus);
}//擦除一个块
//BlockNum:要擦除的BLOCK编号,范围:0-(block_totalnum-1)
//返回值:0,擦除成功
//    其他,擦除失败
rt_uint8_t NAND_EraseBlock(rt_uint32_t _ulBlockNo)
{rt_uint8_t ucStatus;NAND_CMD_AREA = NAND_ERASE0;_ulBlockNo <<= 6;  NAND_ADDR_AREA = _ulBlockNo;NAND_ADDR_AREA = _ulBlockNo >> 8;NAND_ADDR_AREA = _ulBlockNo >> 16;NAND_CMD_AREA = NAND_ERASE1;ucStatus = FSMC_NAND_GetStatus();if(ucStatus == NAND_READY)   {ucStatus = RTV_NOERR;}else if(ucStatus == NAND_ERROR){ucStatus = ERR_NAND_PROG;        }else if(ucStatus == NAND_TIMEOUT_ERROR){ucStatus = ERR_NAND_HW_TOUT;        }return (ucStatus);
} //全片擦除NAND FLASH
void NAND_EraseChip(void)
{rt_uint8_t status;rt_uint16_t i=0;for(i=0;i<2048;i++)     //循环擦除所有的块{status=NAND_EraseBlock(i);if(status)NAND_DEBUG("Erase %d block fail!!,ERRORCODE %d\r\n",i,status);//擦除失败}
}

实现FS_NAND_FlashPrg.c中五个函数后,还需要对rt-thread原生的stubs.c文件进行修改,根据rl-flashfs重新映射IO输入输出,代码如下:

/** Copyright (c) 2006-2018, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date           Author       Notes* 2012-11-23     Yihui        The first version* 2013-11-24     aozima       fixed _sys_read()/_sys_write() issues.* 2014-08-03     bernard      If using msh, use system() implementation*                             in msh.*/#include <string.h>
#include <rt_sys.h>#include "rtthread.h"
#include "libc.h"#ifdef RT_USING_DFS
#include "dfs_posix.h"
#endif#ifdef RT_USING_RL_FLASHFS
#include <File_Config.h>
struct __FILE { int handle; /* Add whatever you need here */ };
#endif#ifdef __CLANG_ARM
__asm(".global __use_no_semihosting\n\t");
#else
#pragma import(__use_no_semihosting_swi)
#endif/* Standard IO device handles. */
#define STDIN       0x8001
#define STDOUT      0x8002
#define STDERR      0x8003/* Standard IO device name defines. */
const char __stdin_name[]  = "STDIN";
const char __stdout_name[] = "STDOUT";
const char __stderr_name[] = "STDERR";/*** required by fopen() and freopen().** @param name - file name with path.* @param openmode - a bitmap hose bits mostly correspond directly to*                     the ISO mode specification.* @return  -1 if an error occurs.*/
FILEHANDLE _sys_open(const char *name, int openmode)
{
#ifdef RT_USING_DFSint fd;int mode = O_RDONLY;
#endif/* Register standard Input Output devices. */if (strcmp(name, __stdin_name) == 0)return (STDIN);if (strcmp(name, __stdout_name) == 0)return (STDOUT);if (strcmp(name, __stderr_name) == 0)return (STDERR);#ifndef RT_USING_DFS#ifdef RT_USING_RL_FLASHFSreturn (__sys_open (name, openmode));#elsereturn -1;#endif
#else/* Correct openmode from fopen to open */if (openmode & OPEN_PLUS){if (openmode & OPEN_W){mode |= (O_RDWR | O_TRUNC | O_CREAT);}else if (openmode & OPEN_A){mode |= (O_RDWR | O_APPEND | O_CREAT);}elsemode |= O_RDWR;}else{if (openmode & OPEN_W){mode |= (O_WRONLY | O_TRUNC | O_CREAT);}else if (openmode & OPEN_A){mode |= (O_WRONLY | O_APPEND | O_CREAT);}}fd = open(name, mode, 0);if (fd < 0)return -1;elsereturn fd;
#endif
}int _sys_close(FILEHANDLE fh)
{
#ifndef RT_USING_DFS#ifdef RT_USING_RL_FLASHFSif (fh > 0x8000) {return (0);}return (__sys_close (fh));#elsereturn 0;#endif
#elseif (fh <= STDERR) return 0;return close(fh);
#endif
}/** Read from a file. Can return:*  - zero if the read was completely successful*  - the number of bytes _not_ read, if the read was partially successful*  - the number of bytes not read, plus the top bit set (0x80000000), if*    the read was partially successful due to end of file*  - -1 if some error other than EOF occurred** It is also legal to signal EOF by returning no data but* signalling no error (i.e. the top-bit-set mechanism need never* be used).** So if (for example) the user is trying to read 8 bytes at a time* from a file in which only 5 remain, this routine can do three* equally valid things:**  - it can return 0x80000003 (3 bytes not read due to EOF)*  - OR it can return 3 (3 bytes not read), and then return*    0x80000008 (8 bytes not read due to EOF) on the next attempt*  - OR it can return 3 (3 bytes not read), and then return*    8 (8 bytes not read, meaning 0 read, meaning EOF) on the next*    attempt** `mode' exists for historical reasons and must be ignored.*/
int _sys_read(FILEHANDLE fh, unsigned char *buf, unsigned len, int mode)
{
#ifdef RT_USING_DFSint size;
#endifif (fh == STDIN){
#ifdef RT_USING_POSIXsize = libc_stdio_read(buf, len);return len - size;
#else/* no stdin */return -1;
#endif}
#ifndef RT_USING_RL_FLASHFSif ((fh == STDOUT) || (fh == STDERR))return -1;
#endif#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFSif (fh > 0x8000) {return (-1);}return (__sys_read (fh, buf, len));
#elsereturn 0;
#endif
#elsesize = read(fh, buf, len);if (size >= 0)return len - size;elsereturn -1;
#endif
}/** Write to a file. Returns 0 on success, negative on error, and* the number of characters _not_ written on partial success.* `mode' exists for historical reasons and must be ignored.*/
int _sys_write(FILEHANDLE fh, const unsigned char *buf, unsigned len, int mode)
{
#ifdef RT_USING_DFSint size;
#endifif ((fh == STDOUT) || (fh == STDERR)){
#if !defined(RT_USING_CONSOLE) || !defined(RT_USING_DEVICE)return 0;
#else
#ifdef RT_USING_POSIXsize = libc_stdio_write(buf, len);return len - size;
#elseif (rt_console_get_device()){rt_device_write(rt_console_get_device(), -1, buf, len);return 0;}return -1;
#endif
#endif}
#ifndef RT_USING_RL_FLASHFSif (fh == STDIN) return -1;
#endif
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFSif (fh > 0x8000) {return (-1);}return (__sys_write (fh, buf, len));
#elsereturn 0;
#endif
#elsesize = write(fh, buf, len);if (size >= 0)return len - size;elsereturn -1;
#endif
}/** Move the file position to a given offset from the file start.* Returns >=0 on success, <0 on failure.*/
int _sys_seek(FILEHANDLE fh, long pos)
{
#ifndef RT_USING_RL_FLASHFSif (fh < STDERR)return -1;
#endif
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFSif (fh > 0x8000) {return (-1);}return (__sys_seek (fh, pos));
#elsereturn -1;
#endif
#else/* position is relative to the start of file fh */return lseek(fh, pos, 0);
#endif
}/*** used by tmpnam() or tmpfile()*/
int _sys_tmpnam(char *name, int fileno, unsigned maxlength)
{
#ifdef RT_USING_RL_FLASHFSreturn 1;
#elsereturn -1;
#endif
}char *_sys_command_string(char *cmd, int len)
{
#ifdef RT_USING_RL_FLASHFSreturn cmd;
#else/* no support */return RT_NULL;
#endif
}/* This function writes a character to the console. */
void _ttywrch(int ch)
{
#ifdef RT_USING_CONSOLEchar c;c = (char)ch;rt_kprintf(&c);
#endif
}RT_WEAK void _sys_exit(int return_code)
{/* TODO: perhaps exit the thread which is invoking this function */while (1);
}/*** return current length of file.** @param fh - file handle* @return file length, or -1 on failed*/
long _sys_flen(FILEHANDLE fh)
{struct stat stat;
#ifndef RT_USING_RL_FLASHFS   if (fh < STDERR)return -1;
#endif
#ifndef RT_USING_DFS
#ifdef RT_USING_RL_FLASHFSif (fh > 0x8000) {return (0);}return (__sys_flen (fh));
#elsereturn -1;
#endif
#elsefstat(fh, &stat);return stat.st_size;
#endif
}int _sys_istty(FILEHANDLE fh)
{
#ifdef RT_USING_RL_FLASHFSif (fh > 0x8000) {return (1);}return (0);
#elseif((STDIN <= fh) && (fh <= STDERR))return 1;elsereturn 0;
#endif
}int _sys_ensure (FILEHANDLE fh) {if (fh > 0x8000) {return (-1);}return (__sys_ensure (fh));
}int remove(const char *filename)
{
#ifndef RT_USING_DFSreturn -1;
#elsereturn unlink(filename);
#endif
}#if defined(RT_USING_FINSH) && defined(FINSH_USING_MSH) && defined(RT_USING_MODULE) && defined(RT_USING_DFS)
/* use system(const char *string) implementation in the msh */
#else
int system(const char *string)
{RT_ASSERT(0);for (;;);
}
#endif#ifdef __MICROLIB
#include <stdio.h>int fputc(int c, FILE *f)
{char ch[2] = {0};ch[0] = c;rt_kprintf(&ch[0]);return 1;
}int fgetc(FILE *f)
{
#ifdef RT_USING_POSIXchar ch;if (libc_stdio_read(&ch, 1) == 1)return ch;
#endifreturn -1;
}
#endif

到这里,移植工作告一段落。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

rt-thread吸引我的,除了强大配置功能之外,还有类似于linux的msh功能。既然是对文件系统移植,那就自定义几个shell命令来玩玩吧。参考linux的shell脚本,稍作修改:

static void glz_nand(int argc, char **argv)
{/* If the number of arguments less than 2 */if (argc < 2){
help:rt_kprintf("\n");rt_kprintf("glz_nand [OPTION] [PARAM ...]\n");rt_kprintf("         ls                           显示指定工作目录下之内容\n");rt_kprintf("         cat        <filename>        显示文件内容\n");rt_kprintf("         mkdir      <docname>         创建文件夹\n");rt_kprintf("         rm         <filename>        删除文件\n");rt_kprintf("         formatall                    磁盘格式化\n");rt_kprintf("         df                           显示磁盘空间\n");return ;}else if (!strcmp(argv[1], "ls")){if(argv[2]  != NULL){ViewRootDir(argv[2]);}else{ViewRootDir(NULL);}}else if (!strcmp(argv[1], "cat")){if (argc < 3){rt_kprintf("The input parameters are too few!\n");goto help;}ReadFileData(argv[2]);}else if (!strcmp(argv[1], "echo")){if (argc < 4){rt_kprintf("The input parameters are too few!\n");goto help;}if(!strcmp(argv[3], ">")){EchotextFile(argv[2],argv[4]);}else{rt_kprintf("bad parameters\n");}}else if (!strcmp(argv[1], "formatall")){Formatflash();}else if (!strcmp(argv[1], "df")){ViewNandCapacity();}else if (!strcmp(argv[1], "df")){ViewNandCapacity();}else if (!strcmp(argv[1], "mkdir")){if (argc < 2){rt_kprintf("The input parameters are too few!\n");goto help;}CreateNewFile(argv[2]);}else if (!strcmp(argv[1], "rm")){if (argc < 2){rt_kprintf("The input parameters are too few!\n");goto help;}DeleteDirFile(argv[2]);}else{rt_kprintf("Input parameters are not supported!\n");goto help;}
}
MSH_CMD_EXPORT(glz_nand, GLZ nand RL-FLASHFS test function);

实现上述所有函数后,编译、下载,我们来看下效果撒。

首先,demo板上电,从串口输出log:

在msd中输入glz_nand,回车,可以看到命令提示:

先试下df,看下磁盘空间:

可以看到 磁盘空间大小为256MB。

下面分别实现其他指令操作:

好了,再也不用担心在单片机下对nandflash进行操作了。。。。。。。。结束!

【易开嵌入式】rt-thread+stm32f407+nandflash,实现RL-FLASHFS文件系统移植相关推荐

  1. 基于rt thread smart构建EtherCAT主站

    我把源码开源到到了gitee,https://gitee.com/rathon/rt-thread-smart-soem 有兴趣的去可以下载下来跑一下 软件工程推荐用vscode 打开.rt thre ...

  2. stm32f407单片机rt thread 片外spi flash OTA升级配置示例

    参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...

  3. rt thread 使用FAL遇到fal_init() undefined reference

    rt thread FAL 0.5版,之前有没有不知道,遇到一个坑. 在main.cpp里面已经 #include <fal.h> fal_init() 编译报错,说 fal_init() ...

  4. 从南极到你家,易开得,一支“中国芯”的奇幻漂流

    2023年的AWE,精彩程度比我想象中还要夸张! 一方面是热度空前,现场人头攒动,有一家老中幼三代一起来观展的,有经销商.客户来问价采购的,还有行业媒体举着单反.手机.摄像机激情拍照的,让我们正经见识 ...

  5. 关于RT thread系统节拍时钟的配置

    关于RT thread系统节拍时钟的配置                  -----本文基于rt-thread-3.1.3版本编写 首先,使用RTthread OS时,要配置(或者明白)它的系统节拍 ...

  6. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

  7. RT Thread Free Modbus移植问题整理

    RT Thread Free Modbus移植问题整理 问题描述: 在读写寄存器中,写数据正常,只能读1个寄存器的值,多个值会异常. 在移植过程中发现串口(或RS485)数据接收长度异常. 一.环境描 ...

  8. Yeelink平台使用——远程控制 RT Thread + LwIP+ STM32

    1.前言     [2014年4月重写该博文]     经过若干时间的努力终于搞定了STM32+LwIP和yeelink平台的数据互通,在学习的过程中大部分时间花在以太网协议栈学习上,但是在RT Th ...

  9. RT Thread根据开发板制作BSP方法

    之前一直不懂怎么使用RT Thread的软件包,感谢网上的大神,看了你们的博客后大概了解一些,在此做下记录.用RT Thread软件包需要RT Thread的系统,但是RT Thread和RT Thr ...

最新文章

  1. ECCV2020 oral | 基于语义流的快速而准确的场景解析
  2. python安装用32位还是64位-我应该使用Python 32位还是Python 64位
  3. Zookeeper之Leader选举源码分析
  4. 搅动金融科技的人工智能,能否颠覆支付格局?
  5. ICCV 2019 | 可选择性与不变性:关注边界的显著性目标检测
  6. LuceneLucene简介
  7. [渝粤教育] 武汉理工大学 认识武理 参考 资料
  8. java面试题3 牛客:下面有关jdbc statement的说法错误的是
  9. android4.0.3去掉底部状态栏statusbar,全屏显示示例代码
  10. php代码审计系列教程,PHP代码审计工具——Rips详细使用教程
  11. 删除360UDiskGuardXX.dll
  12. gp数据库与pg数据库
  13. BIGEMAP如何添加在线第三方地图
  14. UEFI实战 gST、gBS和gImageHandle
  15. PV-RCNN: Point-Voxel Feature Set Abstraction for 3D Object Detection阅读
  16. 图像处理:图像特效之油画效果
  17. bert如何应用于下游任务_培训特定于法律域的BERT
  18. Python入门(一) —— 编程基础
  19. 页面滑动与锚点的“完美交互”
  20. Hyper-v 添加外部虚拟交换机失败显示 以太网端口绑定失败:灾难性故障 问题解决方案

热门文章

  1. h5底部输入框被键盘遮挡_总结几个移动端H5软键盘的大坑【实践】
  2. 8本必读关于人工智能伦理问题、社会价值及影响书籍推荐
  3. mybatis入门笔记(一)
  4. 阿里IOT云平台(二)---10分钟物联网设备接入阿里云IoT平台
  5. Java二维码编码识别
  6. 物联网项目开发实战案例
  7. 7-2 寻找大富翁 (25 分)
  8. Stopping service [Tomcat]和Disconnected from the target VM排查
  9. 使用System.setOut()
  10. matlab中ode指令,[转载]MATLAB中ODE的使用