【GD32】从零开始学GD32单片机 | 基于SD卡的FatFs文件系统移植(GD32F470ZGT6)

1. 简介

FatFs是一个专门为微处理器设计的通用文件系统,像8051、AVR、PIC、ARM架构的微处理器都能兼容该文件系统。

FatFs文件系统最大的一个优点是它是DOS和Windows兼容的,这意味着你只需要再移植一个USB驱动就可以实现在电脑中访问单片机的储存结构,做一个小U盘或者实现文件拖拽升级这样的骚操作。

当然除了上面的优点,它还同时支持长文件名、多文件系统分区、线程安全等功能,同时开发者可以根据需要对FatFs进行裁切,使其满足嵌入式系统的要求。

更多的资料可以查看FatFs的官网:FatFs - Generic FAT Filesystem Module

2. 移植

2.1 准备

移植前先在官网下载源码:点击下载

目前FatFs的最新版本是R0.15

对于想在其他单片机中移植的同学可以在官网下载一份移植例程,里面提供了许多典型单片机的移植例程:点击下载

本次移植涉及到SDIO外设和RTC外设,没有学习的同学可以前往对应的文章提前学习一下。

2.2 文件结构

FatFs的文件还是比较简洁的。

diskio.c和diskio.h:与存储介质相关的函数接口,由用户进行移植

ff.c和ff.h:FatFs的核心代码,无需修改。

ffconf.h:FatFs的配置文件,用户可根据实际需要修改并配置文件系统的功能。

ffsystem.c:与嵌入式系统相关的函数接口,如获取时间、互斥锁、内存管理等函数,由用户进行移植

ffunicode.c:与文本编码相关的函数,如果我们需要文件系统支持除英文外的语言,那么FatFs就会调用里面的函数进行处理,用户无需修改。

可以看到我们只需要修改两个文件的内容即可完成移植。

2.3 配置项

FatFs有丰富的配置选项供用户选择,用户需要移植什么函数取决于我们的配置,因此这里先介绍一下常用的配置项。

FF_FS_READONLY:文件系统只读,默认为0,如果置1那么FatFs会关闭所有能修改文件的函数,大大减少FatFs的体积。

FF_FS_MINIMIZE:文件系统最小化,这个配置也可以减少FatFs的体积,它主要是通过关闭一些不常用的函数实现的;默认为0,即所有基础函数全开;置1的时候会关闭f_stat、f_getfree、f_unlink、f_mkdir、f_truncate和f_rename;置2的时候在上面的基础上再关闭f_opendir、f_readdir、f_closedir函数;置3的时候在上面的基础上再关闭f_lseek函数。

FF_USE_MKFS:文件系统格式化,默认为0,如果需要支持格式化可以置1开启这个功能。

FF_CODE_PAGE:文件系统语言,通过这个可以修改文件系统支持的语言,默认为437(英语),简体中文对应936,繁体中文对应950,语言全开就置0。

FF_USE_LFN:长文件名支持,默认为0,即不支持;这个配置一个有3种选项,区别在于文件名的储存方式;置1时,文件名储存在内存的BSS段中,此时是线程不安全的;置2时,文件名储存在栈中;置3时,文件名储存在堆中,这种方式是最推荐的

FF_MAX_LFN:文件名长度,这个是和上面的配置对应的,如果没有使能长文件名支持,那么可以忽略该配置,文件名的长度最大可以设置为255字节。

FF_VOLUMES:储存介质数量,默认为1,如果单片机挂载了多于1种储存介质并且都有挂载文件系统,那么可以根据需要设置。

FF_MULTI_PARTITION:多分区支持,默认为0,如果文件系统需要支持多分区可以开启该配置,开启后需要用户创建分区表。

FF_MIN_SS和FF_MAX_SS:最小最大扇区大小,默认都为512,一般的存储介质扇区大小都是512字节,如果有不同可以根据储存芯片参数修改。

FF_FS_NORTC:时间戳支持,默认为0,即支持时间戳。

FF_FS_LOCK:文件锁支持,默认为0,它可以控制文件系统同时可以开启多少文件,一般建议设置成1,即同时只能开启一个文件。

FF_FS_REENTRANT:可重入支持,默认为0,单片机中有操作系统的话建议开启,它可以防止操作系统对文件系统进行异常操作。

2.4 需要移植的函数

官方列出了一个表供开发者参考。

函数移植条件备注
disk_status
disk_initialize
disk_read
总是需要
disk_write
get_fattime
disk_ioctl (CTRL_SYNC)
FF_FS_READONLY == 0
disk_ioctl (GET_SECTOR_COUNT)
disk_ioctl (GET_BLOCK_SIZE)
FF_USE_MKFS == 1
disk_ioctl (GET_SECTOR_SIZE)FF_MIN_SS != FF_MAX_SS
disk_ioctl (CTRL_TRIM)FF_USE_TRIM == 1
ff_uni2oem
ff_oem2uni
ff_wtoupper
FF_USE_LFN != 0用户无需移植
ff_mutex_create
ff_mutex_delete
ff_mutex_take
ff_mutex_give
FF_FS_REENTRANT == 1
ff_mem_alloc
ff_mem_free
FF_USE_LFN == 3

2.5 开始移植

2.5.1 disk_initialize函数

这个函数负责存储介质的底层初始化,它有一个参数pdrv,指示物理硬盘号。

DSTATUS disk_initialize (BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{if (pdrv) return STA_NODISK;sd_error_enum status = SD_OK;uint32_t cardstate = 0;nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3);nvic_irq_enable(SDIO_IRQn, 0, 0);uint8_t retry = 5;while (retry--) {// 初始化SD卡if ((status = sd_init()) != SD_OK) {LOG(TAG, "sdcard init failed");Stat = STA_NOINIT;continue;}// 获取SD卡信息if(SD_OK != (status = sd_card_information_get(&sd_cardinfo))) {LOG(TAG, "get sdcard info failed");Stat = STA_NOINIT;continue;}// 片选SD卡if(SD_OK != (status = sd_card_select_deselect(sd_cardinfo.card_rca))) {LOG(TAG, "select card failed");Stat = STA_NOINIT;continue;}// 获取SD卡状态if (SD_OK != (status = sd_cardstatus_get(&cardstate))) {LOG(TAG, "get card status failed");Stat = STA_NOINIT;continue;} else if(cardstate & 0x02000000) {LOG(TAG, "the card is locked!");Stat = STA_PROTECT;continue;}// 设置4bit总线模式if(SD_OK != (status = sd_bus_mode_config(SDIO_BUSMODE_4BIT))) {LOG(TAG, "set bus mode failed");Stat = STA_NOINIT;continue;}// 设置DMA传输模式if(SD_OK != (status = sd_transfer_mode_config(SD_DMA_MODE))) {LOG(TAG, "set dma mode failed");Stat = STA_NOINIT;continue;}}if (retry) {Stat = FR_OK;printf("sdcard block count: %d\r\n", (sd_cardinfo.card_csd.c_size + 1) * 1024);printf("sdcard block size: %d\r\n", sd_cardinfo.card_blocksize);}return Stat;
}

因为例程中只有一个硬盘,所以pdrv一直都会是0,如果是非0我们就认为是操作异常。

接下来的初始化操作就是跟SDIO那篇文章的基本一致 ,初始化的过程最多重试5次,因为很多时候SD卡一次初始化不一定可以。

2.5.2 disk_write函数

这个函数有4个参数,pdrv是硬盘号,buff是指向要写入数据的指针,sector是扇区号,count是要写入的扇区数。

DRESULT disk_write (BYTE pdrv,			/* Physical drive nmuber to identify the drive */const BYTE *buff,	/* Data to be written */LBA_t sector,		/* Start sector in LBA */UINT count			/* Number of sectors to write */
)
{if (pdrv || !count) return RES_PARERR;		/* Check parameter */if (Stat & STA_NOINIT) return RES_NOTRDY;	/* Check drive status */if (Stat & STA_PROTECT) return RES_WRPRT;	/* Check write protect */if (count == 1) {if (SD_OK == sd_block_write((uint32_t*)buff, sector * sd_cardinfo.card_blocksize, sd_cardinfo.card_blocksize))return RES_OK;elsereturn RES_ERROR;} else {if (SD_OK == sd_multiblocks_write((uint32_t*)buff, sector * sd_cardinfo.card_blocksize, sd_cardinfo.card_blocksize, count))return RES_OK;elsereturn RES_ERROR;}return RES_ERROR;
}

 当只需要写一个扇区时调单块写函数,如果多于一个扇区那么调多块写函数,可以提高效率。

另外要注意的是,驱动库函数的第二个参数是写入的地址,因此要将扇区号×扇区大小得出写入的地址。

2.5.3 disk_read函数

这个就跟写的函数差不多了,直接看代码。

DRESULT disk_read (BYTE pdrv,		/* Physical drive nmuber to identify the drive */BYTE *buff,		/* Data buffer to store read data */LBA_t sector,	/* Start sector in LBA */UINT count		/* Number of sectors to read */
)
{if (pdrv || !count) return RES_PARERR;		/* Check parameter */if (Stat & STA_NOINIT) return RES_NOTRDY;	/* Check if drive is ready */if (count == 1) {if (SD_OK == sd_block_read((uint32_t*)buff, sector * sd_cardinfo.card_blocksize, sd_cardinfo.card_blocksize))return RES_OK;elsereturn RES_ERROR;} else {if (SD_OK == sd_multiblocks_read((uint32_t*)buff, sector * sd_cardinfo.card_blocksize, sd_cardinfo.card_blocksize, count))return RES_OK;elsereturn RES_ERROR;}return RES_ERROR;
}

2.5.4 disk_ioctl函数

这个函数主要是用来获取底层IO的信息返回上层的, 有3个参数,pdrv是硬盘号,cmd是命令,不同的命令会返回不同的数据,buff是数据缓冲区,用来存放要接收或发送的数据。

不同的存储介质和文件系统配置需要支持不同的命令,像我这里就支持了GET_SECTOR_COUNT(获取扇区数)、GET_BLOCK_SIZE(获取块大小)、MMC_GET_TYPE(获取卡类型)、MMC_GET_CSD(获取CSD寄存器)、MMC_GET_CID(获取CID寄存器)、MMC_GET_SDSTAT(获取卡状态)这几个命令。

DRESULT disk_ioctl (BYTE pdrv,		/* Physical drive nmuber (0..) */BYTE cmd,		/* Control code */void *buff		/* Buffer to send/receive control data */
)
{if (pdrv) return RES_PARERR;					/* Check parameter */if (Stat & STA_NOINIT) return RES_NOTRDY;	/* Check if drive is ready */switch (cmd){case GET_SECTOR_COUNT:*(LBA_t*)buff = (sd_cardinfo.card_csd.c_size + 1) * 1024;break;case GET_BLOCK_SIZE:*(DWORD*)buff = sd_cardinfo.card_blocksize;break;case MMC_GET_TYPE:*(BYTE*)buff = sd_cardinfo.card_type;break;case MMC_GET_CSD:memcpy(buff, &sd_cardinfo.card_csd, sizeof(sd_csd_struct));break;case MMC_GET_CID:memcpy(buff, &sd_cardinfo.card_cid, sizeof(sd_cid_struct));break;case MMC_GET_SDSTAT:if (SD_OK == sd_cardstatus_get((uint32_t*)buff))return RES_OK;elsereturn RES_ERROR;default:return RES_OK;}return RES_OK;
}

2.5.5 get_fattime函数

这个函数是用来获取时间戳的。

DWORD get_fattime (void)
{return time(NULL) - 8 * 3600;
}

因为我们直接调用time.h头文件里面的time函数获取时间戳,但是要注意的是time函数返回的时间戳是日期回归线的时间,像北京时间东8区的话,要减去8小时才行。time函数的话我进行了重定义,像下面这样。

time_t time(time_t *t)
{struct tm time_struct = {0};rtc_get_time(&time_struct);time_struct.tm_mon--;time_struct.tm_year -= 1900;time_struct.tm_wday--;time_struct.tm_yday--;if (t){*t = mktime(&time_struct);return *t;}return mktime(&time_struct);
}

rtc_get_time函数在RTC外设那篇文章有讲解。mktime函数也是time.h头文件自带的,这里不用重定义就可以用的,传入struct tm结构体它可以返回对应的时间戳,但要仔细看这个结构体每个成员的说明,是要做一丢丢转换的。

2.5.6 ff_memalloc函数和ff_memfree函数

这两个就是内存申请和释放的函数,沿用作者原始的代码即可,不用修改;如果单片机中移植了操作系统的话才可能需要修改。

void* ff_memalloc (	/* Returns pointer to the allocated memory block (null if not enough core) */UINT msize		/* Number of bytes to allocate */
)
{return malloc((size_t)msize);	/* Allocate a new memory block */
}void ff_memfree (void* mblock	/* Pointer to the memory block to free (no effect if null) */
)
{free(mblock);	/* Free the memory block */
}

2.6 测试

移植好上面的函数就可以来使用FatFs了,main函数里面简单写一个测试代码。

FATFS fs;
FIL file;
DIR dir;
FRESULT res;struct tm rtc_conf = {.tm_year = 2024,.tm_mon = 1,.tm_mday = 1,.tm_wday = RTC_MONDAY,.tm_hour = 0,.tm_min = 0,.tm_sec = 0
};/*!\brief    main function\param[in]  none\param[out] none\retval     none
*/
int main(void)
{debug_init();printf("fatfs demo\r\n");/* 初始化RTC */rtc_config(&rtc_conf);// 格式化SD卡if (FR_OK != (res = f_mkfs("", NULL, NULL, 1024))) {printf("mkfs failed, err: %d\r\n", res);goto __err;} else {printf("mkfs ok\r\n");}// 挂载SD卡if (FR_OK != (res = f_mount(&fs, "", 0))) {printf("mount sdcard failed, err: %d\r\n", res);goto __err;} else {printf("mount sdcard ok\r\n");}/* 查看容量 */DWORD space = 0;FATFS *pfs;if (FR_OK != (res = f_getfree("", &space, &pfs))) {printf("get free space failed, err: %d\r\n", res);goto __err;} else {printf("free space: %d KB\r\n", space * pfs->csize / 2);}// 创建文件夹if (FR_OK != (res = f_mkdir("dir"))) {printf("create dir failed, err: %d\r\n", res);goto __err;} else {printf("create dir\r\n");}/* 写文件 */if (FR_OK != (res = f_open(&file, "0:dir/test.txt", FA_CREATE_NEW | FA_WRITE))) {printf("open file failed, err: %d\r\n", res);goto __err;} else {printf("open file ok\r\n");}UINT bw = 0;char str[] = "This a test text";if (FR_OK != (res = f_write(&file, str, sizeof(str), &bw) || bw != sizeof(str))) {printf("write file failed, err: %d\r\n", res);goto __err;} else {printf("write text \"%s\" to file\r\n", str);}f_close(&file);/* 读文件 */if (FR_OK != (res = f_open(&file, "0:dir/test.txt", FA_READ))) {printf("open file failed, err: %d\r\n", res);goto __err;} else {printf("open file ok\r\n");}UINT br = 0;char text[64] = {0};if (FR_OK != (res = f_read(&file, text, sizeof(str), &br) || br != sizeof(str))) {printf("read file failed, err: %d\r\n", res);goto __err;} else {printf("read text \"%s\" from file\r\n", text);}f_close(&file);// 遍历文件夹if (FR_OK != (res = f_opendir(&dir, "dir"))) {printf("open dir filed, err: %d\r\n", res);goto __err;} else {printf("open dir ok\r\n");}while (1) {FILINFO fno = {0};if (FR_OK != f_readdir(&dir, &fno) || fno.fname[0] == 0) {break;}if (fno.fattrib & AM_DIR) {printf("<DIR> %s\r\n", fno.fname);} else {printf("%10u %s\r\n", fno.fsize, fno.fname);}}f_closedir(&dir);/* 卸载SD卡 */if (FR_OK != (res = f_mount(0, "", 0))) {printf("unmount sdcard failed\r\n");goto __err;} else {printf("unmount sdcard ok\r\n");}__err:while(1) {}
}

先初始化RTC外设。然后调f_mkfs格式化SD卡,它有4个参数,path是硬盘号,这里填空字符串,这样它就会选择默认的硬盘;opt是格式化的选项,像文件系统类型、数据对齐等等,这里给空指针,这样它就会选择默认的配置去格式化;work是工作缓冲区,用来存放格式化过程中的数据,这里给空指针,让它去申请堆区的内存;len是工作缓冲区的大小,我这里给了1024个字节,这里给得越大那么格式化的速度会越快,SD卡容量比较大的话建议给大点,可以缩减格式化时间。

然后调f_mount函数挂载SD卡,它有3个参数;fs是文件系统结构体;path是硬盘路径,这里我们传一个空字符串,就是指定默认的硬盘;opt是挂载选项,0的话是稍后挂载,就是有文件操作时才去挂载硬盘,1的话是立即挂载。

之后调了f_getfree获取文件系统的容量,看看移植有没有问题,它有3个参数;path是硬盘路径,这里同样传空字符串指定默认硬盘;nclst是空余的簇数量,簇其实就是块,结合块的大小就可以算出剩余容量;fatfs是文件系统指针,这个函数会返回指向这个硬盘的文件系统指针。

f_mkdir创建一个文件夹,用法跟libc一样。

f_open打开一个文件,用法也是跟libc一样,这里路径要最好加硬盘号。

f_write和f_read用法类似,它们最后一个参数是实际写入和读出的字节数,可以通过这个值判断函数执行是否成功。

每次读跟写建议都调f_close关一下文件;如果一定要在文件打开的时候又写又读,那么写文件后记得f_sync一下,因为f_write并不是每次都会立刻写入文件的;读的时候调f_lseek设置文件指针,因为读的时候是会偏移指针的。

最后我们遍历一下文件夹,具体操作也是跟libc差不多的。先f_opendir打开文件夹,用f_readdir可以按顺序读取文件夹内的文件和文件夹,一般是按文件名顺序,它会返回一个FILINFO结构体,里面有文件的一些信息如大小、日期等等,如果文件夹内的文件读完了,那么结构体内的文件名会为空,通过这个我们可以判断文件夹遍历完没有。最后记得f_closedir关闭文件夹。

如果硬盘不再用了,可以卸载掉,同样调f_mount函数,所有参数均为空,这样就可以卸载默认硬盘。

下面是整一个测试例程的输出。

前面说过,FatFs是兼容Windows系统的,因此我们把SD卡拔出插入电脑,可以看到电脑是成功识别的。文件系统为FAT32,可用空间也是正常的。

进到里面,可以看到刚才测试例程创建的文件夹,里面文件的内容也是正确的。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/48795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cookie与Session 实现登录操作

Cookie Cookie 是网络编程中使用最广泛的一项技术&#xff0c;主要用于辨识用户身份。 客户端&#xff08;浏览器&#xff09;与网站服务端通讯的过程如下图所示&#xff1a; 从图中看&#xff0c;服务端既要返回 Cookie 给客户端&#xff0c;也要读取客户端提交的 Cookie。所…

Domainim:一款高效的企业级网络安全扫描工具

关于Domainim Domainim是一款功能强大的企业级网络安全扫描工具&#xff0c;该工具运行效率高&#xff0c;功能完善&#xff0c;可以帮助广大研究人员针对企业或组织网络执行大规模安全扫描任务。 该工具可以快速执行网络安全扫描和域名/子域名网络侦查任务&#xff0c;旨在使…

python毕业设计选题协同过滤算法在音乐推荐系统

✌网站介绍&#xff1a;✌10年项目辅导经验、专注于计算机技术领域学生项目实战辅导。 ✌服务范围&#xff1a;Java(SpringBoo/SSM)、Python、PHP、Nodejs、爬虫、数据可视化、小程序、安卓app、大数据等设计与开发。 ✌服务内容&#xff1a;免费功能设计、免费提供开题答辩P…

暑期C++ 缺省参数

有任何不懂的问题可以评论区留言&#xff0c;能力范围内都会一一回答 1.缺省参数的概念 缺省参数是是声明或定义参数时为函数的参数指定一个缺省值。在调用该函数值时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参 看了上面定义后&#…

【零基础必看的前端教程】——JavaScript(七)数组

欢迎大家打开前端的新篇章——JavaScript&#xff0c;JavaScript与HTML、CSS合称为前端三大件&#xff0c;JavaScript是前端的重中之重&#xff0c;小洪将继续以零基础视角&#xff0c;带你循序渐进学习前端知识&#xff0c;一看就懂&#xff0c;小白也能转行做前端&#xff01…

vue3实现在新标签中打开指定的网址

有一个文件列表&#xff0c;如下图&#xff1a; 我希望点击查看按钮的时候&#xff0c;能够在新的标签页面打开这个文件的地址进行预览&#xff0c;该如何实现呢&#xff1f; 比如&#xff1a; 实际上要实现这个并不难&#xff0c;参考demo如下&#xff1a; 首先&#x…

渗透测试——利用公网反弹shell到本地的两种方式,vmware虚拟机与主机的端口转发,本地ssh无法上线的问题解决

解决问题&#xff1a; 因长期使用本地模拟靶场&#xff0c;实战护网时并非模拟靶场&#xff0c;shell反弹需要利用公网测试。解决目标站无法反弹到本地的情况。解决本地是windows&#xff0c;虚拟机是kail、linux&#xff0c;无法相互转换流量的情况。 环境搭建 靶机 centOS7 …

VScode 批量操作

VScode 批量操作 批量修改 按住 alt/option 键&#xff0c; 选择需要批量操作的位置 如果是多行&#xff0c;则按住 altshift 键 可以直接操作 但是有时候比如变量命名&#xff0c;可能需要递增操作的命名 需要下载插件 Increment Selection 按照1的方法多选光标之后&am…

html+css+js前端作业 王者荣耀官网5个页面带js

htmlcssjs前端作业 王者荣耀官网5个页面带js 下载地址 https://download.csdn.net/download/qq_42431718/89574989 目录1 目录2 目录3 项目视频 王者荣耀5个页面&#xff08;带js&#xff09; 页面1 页面2 页面3 页面4 页面5

php接口返回的json字符串,json_decode()失败,原来是多了红点

问题&#xff1a; 调用某个接口返回的json&#xff0c;json_decode()失败&#xff0c;返回数据为null&#xff0c; echo json_last_error();返回错误码 4 经过多次调试发现&#xff1a;多出来一个红点&#xff0c;预览是看不到的。 解决&#xff1a;要去除BOM头部 $resul…

vue 搜索框

效果 创建搜索组件&#xff1a; 在Vue项目中&#xff0c;首先需要创建一个搜索组件。这个组件通常包含一个输入框和一个搜索按钮。使用v-model指令将输入框与组件的数据属性&#xff08;如searchKeyword&#xff09;进行双向绑定&#xff0c;以便获取用户输入的关键词。处理搜索…

Linux网络:传输层协议TCP(二)三次挥手四次握手详解

目录 一、TCP的连接管理机制 1.1三次握手 1.2四次挥手 二、理解 TIME_WAIT 状态 2.1解决TIME_WAIT 状态引起的 bind 失败的方法 三、理解CLOSE_WAIT状态 一、TCP的连接管理机制 在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接 1.1三次握手 三次握手顾名思…

Docker从零开始:安装、部署到卸载,一文搞定全流程

Docker是一种开源容器化平台&#xff0c;它允许开发者将应用程序及其依赖打包成轻量级、可移植的容器。这些容器能确保软件在任何环境中稳定运行&#xff0c;无论是开发者的笔记本电脑还是生产服务器。Docker流行的原因在于其提供的隔离性、可移植性和可扩展性&#xff0c;它简…

2024年展望:人工智能领域将呈现怎样的发展趋势?

2024年&#xff0c;人工智能&#xff08;AI&#xff09;领域将继续保持强劲的发展势头&#xff0c;并呈现出多个重要的发展趋势。以下是对该领域未来发展趋势的详细展望&#xff1a; 一、技术创新与融合 多模态生成式AI的崛起&#xff1a; 多模态生成式AI系统能够处理文本、声…

C# 将字符串数组以树型结构化

例如字符串数组&#xff1a; string[] arr { "1","3-4-5-6-7", "2","3-4","3-4-5","3-4-5-6", "3", "6", "4", "6-1", "6-2", "5", "6-1-1&…

李艳波医生怎么挂号?

对于想要预约李艳波医生的患者来说&#xff0c;北京仁爱堂提供了两种便捷的预约方式&#xff1a;来院面诊和视频会诊。来院面诊是传统的就诊方式&#xff0c;患者可以直接前往仁爱堂&#xff0c;与李艳波医生面对面交流&#xff0c;详细了解自己的病情并接受专业的治疗建议。这…

解决Github Copilot无效,无法使用的问题

如果是在Copilot的终端报错 Invalid copilot token: missing token: 403 原因有三种 1&#xff0c;你的账号没有订阅正版的服务&#xff0c;解决办法是购买正版服务 2&#xff0c;你在购买服务的时候&#xff0c;Github上 billing information 地址信息和支付卡片的地址信息不…

关卡1-3:Git

关卡1-3&#xff1a;Git Git基础fork并拉取本次课程的源创建一个gitee自己的仓库 这个是internLM的3期训练营的通关笔记。 任务&#xff1a; 熟悉git熟悉使用git托管平台&#xff0c;常见有github、giteefork官方的训练营的教程项目&#xff0c;提交文件到自己的项目&#xf…

openGauss触发器详解

openGauss 是一款开源关系型数据库管理系统&#xff0c;广泛应用于企业级应用中。随着数据量的增长和业务逻辑的复杂化&#xff0c;数据库管理和操作的自动化需求越来越高。触发器&#xff08;Triggers&#xff09;作为数据库中重要的编程工具&#xff0c;能够极大地简化复杂操…

【python】OpenCV—Point Polygon Test

文章目录 1、完整代码2、涉及到的库cv2.pointPolygonTestcv2.minMaxLoc 1、完整代码 from __future__ import print_function from __future__ import division import cv2 as cv import numpy as np # Create an image r 100 src np.zeros((4*r, 4*r), dtypenp.uint8) # 创…