立创梁山派--移植开源的SFUD万能的串行 Flash 通用驱动库

news2024/11/25 6:39:09

SFUD是什么

关于SFUD库的介绍,其开源链接(gitee,github)已经详细的阐述了.
这里是截取自它的一部分介绍:
SFUD 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。

主要特点:支持 SPI/QSPI 接口、面向对象(同时支持多个 Flash 对象)、可灵活裁剪、扩展性强、支持 4 字节地址
资源占用
标准占用:RAM:0.2KB ROM:5.5KB
最小占用:RAM:0.1KB ROM:3.6KB
设计思路:
什么是 SFDP :它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准,最新版 V1.6B (点击这里查看)。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
不支持 SFDP 怎么办 :如果该 Flash 不支持 SFDP 标准,SFUD 会查询配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息(添加方法详细见 2.5 添加库目前不支持的 Flash)。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。
详细的可以查看开源链接(gitee,github);

为什么选择 SFUD

避免项目因 Flash 缺货、Flash 停产或产品扩容而带来的风险;
越来越多的项目将固件存储到串行 Flash 中,例如:ESP8266 的固件、主板中的 BIOS 及其他常见电子产品中的固件等等,但是各种 Flash 规格及命令不统一。使用 SFUD 即可避免,在相同功能的软件平台基础下,无法适配不同 Flash 种类的硬件平台的问题,提高软件的可重用性;
简化软件流程,降低开发难度。现在只需要配置好 SPI 通信,即可畅快的开始玩串行 Flash 了;
可以用来制作 Flash 编程器/烧写器

开始copy代码

这篇文章的重点就是来移植这个库,所以我就不多介绍了,直接开搞。

  • 首先先下载好立创梁山派附带的资料,等下要用到里面的提供的demo来作为我们的工程模板。
  • 我们使用 005-串口打印信息 这个作为我们的工程模板,然后在从015_spi中复制一份spi的代码,作为我们的flash驱动代码,还要下载一份sfud的源代码。
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
总共就是以上三个东西就ok啦。

  • 接下啦,按照下图,将文件添加到你的工程中进来,如果这一部分操作不会的话,可以学一下立创推出的教程,我这里就不再赘述啦。
    在这里插入图片描述
  • 接下来就是对代码进行小小的修改
    对bsp_spi.c文件按照我自己的代码风格进行了小小的修改和裁剪,因为这个SFUD十分完善,只需要我们提供这个spi的读取接口函数就ok了。
#include "bsp_spi.h"

void bsp_spi4_init(void)
{
	//SPI参数定义结构体
	spi_parameter_struct spi_init_struct;
	
	rcu_periph_clock_enable(RCU_GPIOF);  // 使用F端口
    rcu_periph_clock_enable(RCU_SPI4);     // 使能SPI4
    //引脚复用
    gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_7);
    gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_8);
    gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_9);
    //引脚模式
    gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
    gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
    gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
    //输出模式
    gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
    gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
    gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
    
	//开启CS引脚时钟
	rcu_periph_clock_enable(RCU_GPIOF);
	//配置CS引脚模式
	gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_6);
	//配置CS输出模式
	gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
	//W25Q64不选中
	gpio_bit_write(GPIOF, GPIO_PIN_6, SET);

	spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX;  	// 传输模式全双工
	spi_init_struct.device_mode          = SPI_MASTER;                	// 配置为主机
	spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;        	// 8位数据
	spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;		//极性相位  
	spi_init_struct.nss                  = SPI_NSS_SOFT;              	//软件cs
	spi_init_struct.prescale             = SPI_PSC_2;                 	//SPI时钟预分频为2
	spi_init_struct.endian               = SPI_ENDIAN_MSB;            	//高位在前
	//将参数填入SPI4
	spi_init(SPI4, &spi_init_struct);
	//使能SPI
	spi_enable(SPI4);
}

/******************************************************************
 * 函 数 名 称:spi_read_write_byte
 * 函 数 说 明:硬件SPI的读写
 * 函 数 形 参:dat=发送的数据
 * 函 数 返 回:读取到的数据
 * 作       者:LCKFB
 * 备       注:无
******************************************************************/
uint8_t spi4_read_write_byte(uint8_t dat)
{
	//等待发送缓冲区为空
	while(RESET == spi_i2s_flag_get(SPI4,  SPI_FLAG_TBE) );
         spi_i2s_data_transmit(SPI4, dat);
	//等待接收缓冲区为空
	while(RESET == spi_i2s_flag_get(SPI4,  SPI_FLAG_RBNE) );
         return spi_i2s_data_receive(SPI4);
}
uint16_t spi4_flash_readID(void)
{
	uint16_t  temp = 0;	  	
	gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
		
	spi4_read_write_byte(0x90);//发送读取ID命令	    
	spi4_read_write_byte(0x00); 	    
	spi4_read_write_byte(0x00); 	    
	spi4_read_write_byte(0x00); 		
	//接收数据
	temp |= spi4_read_write_byte(0xFF)<<8;  
	temp |= spi4_read_write_byte(0xFF);	
		 
	gpio_bit_write(GPIOF, GPIO_PIN_6, SET);	
	return temp;
}

void spi4_w25qxx_cs(uint8_t enable)
{
	if(enable)
	gpio_bit_write(GPIOF, GPIO_PIN_6, SET);	
	else
	gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);	
}

对应的bsp_spi.h文件如下:

#ifndef _BSP_SPI_H
#define _BSP_SPI_H


#include "gd32f4xx.h"
#include "systick.h"

void bsp_spi4_init(void);

uint16_t spi4_flash_readID(void);
uint8_t spi4_read_write_byte(uint8_t dat);
void spi4_w25qxx_cs(uint8_t enable);
#endif
  • 接下来是对这个sfud的移植
    这个是对于sfud_cfg.h的修改
    在这里插入图片描述

  • 剩下对一个sfud_port.c文件进行修改


#include <sfud.h>
#include <stdarg.h>

#include "bsp_spi.h"
#include <string.h>

#define OS 0 // 0:不使用OS,1:使用OS

//这里使用的是freertos,你也可以换成你熟悉的rtos
#if OS  
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#endif

static char log_buf[256];

void sfud_log_debug(const char *file, const long line, const char *format, ...);

//这里是自添加的函数,实行对应接口的写buf函数
static void spi_flash_buf_write(const uint8_t *buf, size_t len)
{
    while (len--)
    {
        spi4_read_write_byte(*buf);
        buf++;
    }
}

//这里是自添加的函数,实行对应接口的读buf函数
static void spi_flash_buf_read(uint8_t *buf, size_t len)
{
    while (len--)
    {
        *buf = spi4_read_write_byte(0xff);
        buf++;
    }
}


/**
 * SPI write data then read data
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
                               size_t read_size)
{
    sfud_err result = SFUD_SUCCESS;
    //    uint8_t send_data, read_data;

    /**
     * add your spi write and read code
     */
    //这里就是我们要自行添加的代码
    if (write_size)
        SFUD_ASSERT(write_buf);
    if (read_size)
        SFUD_ASSERT(read_buf);
    spi4_w25qxx_cs(0);
    if (write_size)
    {
        spi_flash_buf_write(write_buf, write_size);
    }
    if (read_size)
    {
        spi_flash_buf_read(read_buf, read_size);
    }
    spi4_w25qxx_cs(1);
    return result;
}

#ifdef SFUD_USING_QSPI
/**
 * read flash data by QSPI
 */
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
                          uint8_t *read_buf, size_t read_size)
{
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your qspi read flash data code
     */

    return result;
}
#endif /* SFUD_USING_QSPI */

#if OS

static SemaphoreHandle_t sfudMutexSemaphor;
static void spi_lock_init(void)
{
    sfudMutexSemaphor = xSemaphoreCreateMutex();
    if (sfudMutexSemaphor == NULL)
    {
        SFUD_DEBUG("sfud semaphor create failed");
    }
}
#endif

static void spi_lock(const sfud_spi *spi)
{
#if OS
    if (sfudMutexSemaphor != NULL)
    {
        xSemaphoreTake(sfudMutexSemaphor, portMAX_DELAY);
    }
#else
    __disable_irq(); //? ?  ж 
#endif
}
static void spi_unlock(const sfud_spi *spi)
{
#if OS
    if (sfudMutexSemaphor != NULL)
    {
        xSemaphoreGive(sfudMutexSemaphor);
    }
#else
    __enable_irq();  //? ?  ж 
#endif
}
static void spi_delay(void)
{
#if OS
    vTaskDelay(1);
#else
   uint32_t delay = 120;
   while(delay--)
       ;
#endif
}
sfud_err sfud_spi_port_init(sfud_flash *flash)
{
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your port spi bus and device object initialize code like this:
     * 1. rcc initialize
     * 2. gpio initialize
     * 3. spi device initialize
     * 4. flash->spi and flash->retry item initialize
     *    flash->spi.wr = spi_write_read; //Required
     *    flash->spi.qspi_read = qspi_read; //Required when QSPI mode enable
     *    flash->spi.lock = spi_lock;
     *    flash->spi.unlock = spi_unlock;
     *    flash->spi.user_data = &spix;
     *    flash->retry.delay = null;
     *    flash->retry.times = 10000; //Required
     */
    //这里也是我们要添加的函数,这里要重点注意这个SPI4,要和sfud_cfg.h中保持一致
    if (!strcmp(flash->spi.name, "SPI4"))
    {
        bsp_spi4_init();
#if OS
        spi_lock_init();
#endif
        flash->spi.wr = spi_write_read;
        flash->spi.lock = spi_lock;
        flash->spi.unlock = spi_unlock;
        flash->retry.delay = spi_delay;
        flash->retry.times = 60*10000;
    }
    return result;
}

/**
 * This function is print debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 */
void sfud_log_debug(const char *file, const long line, const char *format, ...)
{
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD](%s:%ld) ", file, line);
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\n", log_buf);
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...)
{
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD]");
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\n", log_buf);
    va_end(args);
}


  • 上面的代码所示,其实也只需要修改 spi_write_readsfud_spi_port_init这两个函数,就移植完成啦。

接下来我们对移植完成的sfud库进行擦除,读,写功能的测试。

对应的main函数代码。


#include "main.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "gd32f4xx.h"
#include "sys.h"
#include "systick.h"
#include <stdio.h>

#include "bsp_spi.h"
#include <sfud.h>

#define SFUD_DEMO_TEST_BUFFER_SIZE 1024

static void sfud_demo(uint32_t addr, size_t size, uint8_t *data);

static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];

/*!
    \brief    main function
    \param[in]  none
    \param[out] none
    \retval     none
*/
int main(void)
{
    systick_config();
    led_gpio_config(); // led初始化
    usart_gpio_config(115200U);
    /* SFUD initialize */
    if (sfud_init() == SFUD_SUCCESS)
    {
       sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
    }
    while (1)
    {
    }
}
/**
 * SFUD demo for the first flash device test.
 *
 * @param addr flash start address
 * @param size test flash size
 * @param size test flash data buffer
 */
static void sfud_demo(uint32_t addr, size_t size, uint8_t *data) {
    sfud_err result = SFUD_SUCCESS;
    const sfud_flash *flash = sfud_get_device_table() + 0;
    size_t i;
    /* prepare write data */
    for (i = 0; i < size; i++) {
        data[i] = i;
    }
    /* erase test */
    result = sfud_erase(flash, addr, size);
    if (result == SFUD_SUCCESS) {
        printf("Erase the %s flash data finish. Start from 0x%08X, size is %ld.\r\n", flash->name, addr,
                size);
    } else {
        printf("Erase the %s flash data failed.\r\n", flash->name);
        return;
    }
    /* write test */
    result = sfud_write(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Write the %s flash data finish. Start from 0x%08X, size is %ld.\r\n", flash->name, addr,
                size);
    } else {
        printf("Write the %s flash data failed.\r\n", flash->name);
        return;
    }
    /* read test */
    result = sfud_read(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Read the %s flash data success. Start from 0x%08X, size is %ld. The data is:\r\n", flash->name, addr,
                size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
        for (i = 0; i < size; i++) {
            if (i % 16 == 0) {
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", data[i]);
            if (((i + 1) % 16 == 0) || i == size - 1) {
                printf("\r\n");
            }
        }
        printf("\r\n");
    } else {
        printf("Read the %s flash data failed.\r\n", flash->name);
    }
    /* data check */
    for (i = 0; i < size; i++) {
        if (data[i] != i % 256) {
            printf("Read and check write data has an error. Write the %s flash data failed.\r\n", flash->name);
			break;
        }
    }
    if (i == size) {
        printf("The %s flash test is success.\r\n", flash->name);
    }
}

效果如下

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
擦除读写1k字节数据正常,到这里就移植完成啦。
至于如何使用sfud库,可以参考这个测试demo的用法,记得要向flash写入数据,要先擦除哦!

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

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

相关文章

Linux云计算 |【第一阶段】SERVICES-DAY5

主要内容&#xff1a; 源码编译安装、rsync同步操作、inotify实时同步、数据库服务基础 实操前骤&#xff1a;&#xff08;所需tools.tar.gz与users.sql&#xff09; 1.两台主机设置SELinnx和关闭防火墙 setenforce 0 systemctl stop firewalld.service //停止防火墙 sy…

<数据集>水果识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;10012张 标注数量(xml文件个数)&#xff1a;10012 标注数量(txt文件个数)&#xff1a;10012 标注类别数&#xff1a;7 标注类别名称&#xff1a;[Watermelon, Orange, Grape, Apple, peach, Banana, Pineapple] 序…

常见的数据集格式

常见的数据集格式有三种&#xff0c;分别为voc(xml)、coco(json)、yolo(txt)。 1 VOC VOC数据集由五个部分构成&#xff1a;JPEGImages&#xff0c;Annotations&#xff0c;ImageSets&#xff0c;SegmentationClass以及SegmentationObject. . └── VOC #根目…

基于微信小程序+SpringBoot+Vue的微信平台签到系统(带1w+文档)

基于微信小程序SpringBootVue的微信平台签到系统(带1w文档) 基于微信小程序SpringBootVue的微信平台签到系统(带1w文档) 微信平台签到系统使用Java语言进行编码&#xff0c;使用Mysql创建数据表保存本系统产生的数据。系统可以提供信息显示和相应服务&#xff0c;其管理微信平台…

使用Diffusion Models进行街景视频生成

Diffusion Models专栏文章汇总&#xff1a;入门与实战 前言&#xff1a;街景图生成相当有挑战性&#xff0c;目前的文本到视频的方法仅限于生成有限范围的场景的短视频&#xff0c;文本到3D的方法可以生成单独的对象但不是整个城市。除此之外街景图对一致性的要求相当高&#x…

IDEA新建module后变为普通文件夹

问题描述&#xff1a; 在父项目中创建module并构建子父关系&#xff0c;但在创建module并配置后出现未生效问题 在父项目中的pom.xml文件中添加 <modules><module>***</module></modules>在新建Module中添加 <parent><groupId>com.***&l…

UFO:革新Windows操作系统交互的UI聚焦代理

人工智能咨询培训老师叶梓 转载标明出处 人机交互的便捷性和效率直接影响着我们的工作和生活质量。尽管现代操作系统如Windows提供了丰富的图形用户界面&#xff08;GUI&#xff09;&#xff0c;使得用户能够通过视觉和简单的点击操作来控制计算机&#xff0c;但随着应用程序功…

3.Fabric系统架构、网络拓扑图、交易流程

Hyperledger Fabric系统架构 Fabric网络拓扑图 Fabric交易流程 多通道

硅谷裸机云大宽带服务器连接不上是怎么回事?该如何处理

硅谷裸机云大宽带服务器连接不上的常见原因主要有网络设置、网络设备、服务端、软件和服务、物理层等&#xff0c;出现以上问题&#xff0c;RAK部落小编建议大家可以通过以下一系列的方法进行排查和解决。具体分析如下&#xff1a; 1.检查网络设置   核对配置信息&#xff1a…

017、Vue动态tag标签

文章目录 1、先看效果2、代码 1、先看效果 2、代码 <template><div class "tags"><el-tag size"medium"closable v-for"item,index in tags":key"item.path":effect"item.title$route.name?dark:plain"cl…

centos/Ubuntu安装Java/Maven

上图就是今天在Linux环境下安装好Java和Maven后&#xff0c;打包Spring Boot项目的截图&#xff01; 安装Java centos # 安装 yum install -y java-1.8.0-openjdk*# 查看版本检测是否成功安装 java -versionUbuntu # 更新软件包 sudo apt-get update# 安装 sudo apt-get in…

React Native 与 Flutter:你的应用该如何选择?

Flutter 和 React Native 都被认为是混合应用程序开发中的热门技术。然而&#xff0c;当谈到为你的项目使用框架时&#xff0c;你必须考虑哪一个是最好的&#xff1a;Flutter 还是 React Native&#xff1f; 本篇文章包含 Flutter 和 React Native 在各个方面的差异。因此&…

【数据结构】顺序表(ArrayList的具体使用)

&#x1f387;&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳&#xff0c;欢迎大佬指点&#xff01; 欢迎志同道合的朋友一起加油喔 &#x1f4aa;&#x1f4aa;&#x1f4aa; 谢谢你这么帅…

7 Vue3

相比 Vue2 1. 优点 vue3支持vue2的大多数特性&#xff0c;实现对vue2的兼容vue3对比vue2具有明显的性能提升 打包大小减少41%初次渲染快55%&#xff0c;更新快133%内存使用减少54% 更好的支持TypeScript使用Proxy代替defineProperty实现响应式数据 2. 性能提升的原因 静态标…

图解 HDFS 架构 |读写过程

HDFS HDFS 全称 Hadoop Distributed File System&#xff0c;是一个分布式文件系统。HDFS&#xff08;Hadoop Distributed File System&#xff09;是 Apache Hadoop 生态系统的一部分&#xff0c;它是一个分布式文件系统&#xff0c;用于存储和处理大规模数据集。HDFS 专门设…

微信小程序中Map组件Marker中把Label文字信息通过按钮显示或隐藏

wxml页面按钮 <button bindtap"toggleLabel">Toggle Label</button>js data:{labelMarkerId: null // 记录当前显示文本的标记的 id }, //按钮切换显示 toggleLabel() {// 判断当前是否有显示的文本标记if (this.data.labelMarkerId ! null) {// 如果…

springboot+vue+mybatis校园闲置品交换平台+PPT+论文+讲解+售后

校园闲置品交换平台是校园闲置品交换平台必不可少的一个部分。在校园闲置品交换平台的整个过程中&#xff0c;校园闲置品交换平台担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类校园闲置品交换平台管理程序也在不断改进。本课题所设计的校园闲置品交换平台…

思维+01背包,LeetCode LCP 47. 入场安检

一、题目 1、题目描述 「力扣挑战赛」 的入场仪式马上就要开始了&#xff0c;由于安保工作的需要&#xff0c;设置了可容纳人数总和为 M 的 N 个安检室&#xff0c;capacities[i] 记录第 i 个安检室可容纳人数。安检室拥有两种类型&#xff1a; 先进先出&#xff1a;在安检室中…

基于 PyTorch 的模型瘦身三部曲:量化、剪枝和蒸馏,让模型更短小精悍!

基于 PyTorch 的模型量化、剪枝和蒸馏 1. 模型量化1.1 原理介绍1.2 PyTorch 实现 2. 模型剪枝2.1 原理介绍2.2 PyTorch 实现 3. 模型蒸馏3.1 原理介绍3.2 PyTorch 实现 参考文献 1. 模型量化 1.1 原理介绍 模型量化是将模型参数从高精度&#xff08;通常是 float32&#xff0…

go语言Gin框架的学习路线(九)

GORM的CRUD教程 CRUD 是 "Create, Read, Update, Delete"&#xff08;创建、读取、更新、删除&#xff09;的缩写&#xff0c;代表了数据库操作的基本功能。在 GORM 的上下文中&#xff0c;CRUD 指的是使用 GORM 库来执行这些基本的数据库操作。 创建的 在 GORM 中…