当前位置: 首页 > news >正文

【驱动】SPI驱动分析(五)-模拟SPI驱动

简介

模拟SPI驱动是一种软件实现的SPI总线驱动。在没有硬件SPI控制器的系统中,通过软件模拟实现SPI总线的功能。它允许在不修改硬件的情况下,通过GPIO(通用输入/输出)引脚模拟SPI总线的通信,从而与SPI设备进行数据交换。

模拟SPI驱动相对于硬件SPI来说,可能会有一定的性能损失,因为软件模拟不如硬件实现的SPI控制器快速和高效。

模拟SPI驱动相比硬件SPI控制器存在一些缺点,包括:

  1. 性能较低:软件模拟SPI需要通过GPIO引脚进行数据的输入和输出,并进行相应的时序控制。相比硬件SPI控制器,软件模拟SPI的速度较慢,通信效率较低,特别是在高速数据传输和频繁通信的场景下。
  2. 占用CPU资源:模拟SPI驱动在内核空间运行,需要通过CPU执行软件代码来模拟SPI总线的功能。这会占用一定的CPU资源,可能导致系统性能下降,并且可能影响其他任务的响应时间。
  3. 时序控制的挑战:软件模拟SPI需要准确控制数据的时序,包括数据的传输速率、时钟边沿和信号延迟等。需要仔细处理时序相关的问题,确保正确的数据传输和可靠性。
  4. 受限于GPIO资源:模拟SPI驱动需要使用系统中的GPIO引脚来模拟SPI总线的通信,因此受限于可用的GPIO资源数量。如果系统中可用的GPIO引脚有限,可能会限制同时连接的SPI设备数量或引起硬件扩展的困难。

内核中模拟SPI驱动的实现

在Linux内核中,SPI子系统提供了用于管理SPI总线和设备的功能和接口。虽然SPI子系统本身不直接提供模拟SPI驱动的功能,但它提供了一些接口和框架,可以用于实现模拟SPI驱动。

  1. SPI GPIO框架:SPI子系统提供了一个名为spi-gpio的框架,可使用GPIO引脚模拟SPI总线,gpio模拟spi代码在drivers/spi/spi-gpio.c中。这个框架允许将GPIO引脚配置为SPI总线的时钟、片选、输入和输出信号,并提供了对应的接口函数供驱动程序使用。
  2. spi-bitbang:spi-bitbang是Linux内核中提供的一个通用框架,用于在没有硬件SPI控制器或需要灵活控制SPI时序和配置的系统中模拟SPI总线的通信。代码在spi-bitbang.c

下面我们分别分析下这drivers/spi/spi-gpio.cspi-bitbang.c两个文件。

spi-gpio

platform_driver

spi_gpio_driver 属于总线设备驱动模型中的一种。当设备树中的compatible字段与spi_gpio_dt_idscompatible匹配时,spi_gpio_probe将被调用,在probe函数中初始化并注册设备。

static struct platform_driver spi_gpio_driver = {.driver = {.name	= DRIVER_NAME,.of_match_table = of_match_ptr(spi_gpio_dt_ids),},.probe		= spi_gpio_probe,.remove		= spi_gpio_remove,
};
module_platform_driver(spi_gpio_driver);

spi_gpio_dt_ids

设备树需要添加 spi-gpio节点,这样才能probe成功。

static const struct of_device_id spi_gpio_dt_ids[] = {{ .compatible = "spi-gpio" },{}
};

spi_gpio_probe_dt

spi_gpio_probe_dt主要作用是解析设备树中的SPI GPIO设备信息,并将其存储在platform_data结构体中。这样,在SPI GPIO驱动的探测函数中,可以通过pdev设备的platform_data字段获取这些信息,并根据需要进行相应的配置和操作。

static int spi_gpio_probe_dt(struct platform_device *pdev)
{int ret;u32 tmp;struct spi_gpio_platform_data	*pdata;struct device_node *np = pdev->dev.of_node;const struct of_device_id *of_id =of_match_device(spi_gpio_dt_ids, &pdev->dev);if (!of_id)return 0;pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);if (!pdata)return -ENOMEM;ret = of_get_named_gpio(np, "gpio-sck", 0);if (ret < 0) {dev_err(&pdev->dev, "gpio-sck property not found\n");goto error_free;}pdata->sck = ret;ret = of_get_named_gpio(np, "gpio-miso", 0);if (ret < 0) {dev_info(&pdev->dev, "gpio-miso property not found, switching to no-rx mode\n");pdata->miso = SPI_GPIO_NO_MISO;} elsepdata->miso = ret;ret = of_get_named_gpio(np, "gpio-mosi", 0);if (ret < 0) {dev_info(&pdev->dev, "gpio-mosi property not found, switching to no-tx mode\n");pdata->mosi = SPI_GPIO_NO_MOSI;} elsepdata->mosi = ret;ret = of_property_read_u32(np, "num-chipselects", &tmp);if (ret < 0) {dev_err(&pdev->dev, "num-chipselects property not found\n");goto error_free;}pdata->num_chipselect = tmp;pdev->dev.platform_data = pdata;return 1;error_free:devm_kfree(&pdev->dev, pdata);return ret;
}

spi_gpio_probe

spi_gpio_probe使用了bitbang模式实现SPI协议的位操作传输,在Linux内核的SPI子系统中注册并初始化一个SPI GPIO设备。

static int spi_gpio_probe(struct platform_device *pdev)
{int				status;struct spi_master		*master;struct spi_gpio			*spi_gpio;struct spi_gpio_platform_data	*pdata;u16 master_flags = 0;bool use_of = 0;int num_devices;// 解析设备树中的SPI GPIO设备信息并初始化platform_data结构体status = spi_gpio_probe_dt(pdev);if (status < 0)return status;if (status > 0)use_of = 1;// 获取设备的platform_data结构体pdata = dev_get_platdata(&pdev->dev);
#ifdef GENERIC_BITBANG// 如果没有platform_data或者设备树中没有定义num_chipselect属性,返回错误码if (!pdata || (!use_of && !pdata->num_chipselect))return -ENODEV;
#endifif (use_of && !SPI_N_CHIPSEL)num_devices = 1;elsenum_devices = SPI_N_CHIPSEL;// 请求和配置SPI GPIO相关的GPIO资源status = spi_gpio_request(pdata, dev_name(&pdev->dev), &master_flags);if (status < 0)return status;// 分配spi_master结构体,并保存spi_gpio结构体指针master = spi_alloc_master(&pdev->dev, sizeof(*spi_gpio) +(sizeof(unsigned long) * num_devices));if (!master) {status = -ENOMEM;goto gpio_free;}spi_gpio = spi_master_get_devdata(master);platform_set_drvdata(pdev, spi_gpio);spi_gpio->pdev = pdev;if (pdata)spi_gpio->pdata = *pdata;// 设置spi_master结构体的一些字段master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);master->flags = master_flags;master->bus_num = pdev->id;master->num_chipselect = num_devices;master->setup = spi_gpio_setup;master->cleanup = spi_gpio_cleanup;
#ifdef CONFIG_OFmaster->dev.of_node = pdev->dev.of_node;if (use_of) {int i;struct device_node *np = pdev->dev.of_node;/** In DT environments, take the CS GPIO from the "cs-gpios"* property of the node.*/if (!SPI_N_CHIPSEL)spi_gpio->cs_gpios[0] = SPI_GPIO_NO_CHIPSELECT;elsefor (i = 0; i < SPI_N_CHIPSEL; i++) {status = of_get_named_gpio(np, "cs-gpios", i);if (status < 0) {dev_err(&pdev->dev,"invalid cs-gpios property\n");goto gpio_free;}spi_gpio->cs_gpios[i] = status;}}
#endifspi_gpio->bitbang.master = master;spi_gpio->bitbang.chipselect = spi_gpio_chipselect;// 设置SPI传输相关的回调函数if ((master_flags & (SPI_MASTER_NO_TX | SPI_MASTER_NO_RX)) == 0) {spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0;spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1;spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2;spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3;} else {spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0;spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1;spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2;spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3;}spi_gpio->bitbang.setup_transfer = spi_bitbang_setup_transfer;spi_gpio->bitbang.flags = SPI_CS_HIGH;// 启动SPI GPIO位操作传输status = spi_bitbang_start(&spi_gpio->bitbang);if (status < 0) {
gpio_free:if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)gpio_free(SPI_MISO_GPIO);if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)gpio_free(SPI_MOSI_GPIO);gpio_free(SPI_SCK_GPIO);spi_master_put(master);}return status;
}

函数所做工作如下:

  1. 首先,函数调用spi_gpio_probe_dt()函数来解析设备树(Device Tree)中的SPI GPIO设备信息,并初始化platform_data结构体。设备树中包含了SPI GPIO设备的属性,如时钟引脚、数据输入引脚、数据输出引脚等。
  2. 接下来,函数获取设备的platform_data结构体,并进行一些合法性检查。
  3. 函数通过调用spi_gpio_request()函数请求和配置SPI GPIO相关的GPIO资源。该函数会申请所需的GPIO引脚,并设置引脚的方向和电平。
  4. 然后,函数使用spi_alloc_master()函数分配一个spi_master结构体,并保存了指向spi_gpio结构体的指针。同时,通过调用platform_set_drvdata()函数将spi_gpio结构体指针保存在platform_device结构体的driver_data字段中。
  5. 接着,函数对spi_master结构体的各个字段进行设置。这些字段包括SPI传输的参数,如数据位宽、传输模式、片选信号数量等。此外,还会设置回调函数,用于数据传输的配置和清理。
  6. 如果设备树支持(即配置了CONFIG_OF),函数会获取设备树中的片选信号的GPIO引脚信息。通过遍历设备树中的cs-gpios属性,获取每个片选信号的GPIO引脚。
  7. 接下来,函数设置spi_gpio结构体中的bitbang字段,将之前设置的spi_master结构体以及自定义的片选信号处理函数指定给它。
  8. 根据SPI的传输模式,函数选择不同的回调函数进行数据的传输。如果SPI设备同时支持数据发送和接收,则使用通用的传输函数;否则,使用特定的传输函数。
  9. 最后,函数调用spi_bitbang_start()函数启动SPI GPIO的位操作传输。该函数会根据之前设置的参数,开始进行SPI数据的传输。
  10. 如果启动失败,函数会释放之前请求的GPIO资源,并释放分配的spi_master结构体的内存空间。

spi_gpio_remove

spi_gpio_remove函数,它的目的是从SPI GPIO平台设备中移除驱动程序并释放相关的资源,如GPIO引脚和SPI主设备。

static int spi_gpio_remove(struct platform_device *pdev)
{struct spi_gpio			*spi_gpio;struct spi_gpio_platform_data	*pdata;spi_gpio = platform_get_drvdata(pdev);pdata = dev_get_platdata(&pdev->dev);/* stop() unregisters child devices too */spi_bitbang_stop(&spi_gpio->bitbang);if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)gpio_free(SPI_MISO_GPIO);if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)gpio_free(SPI_MOSI_GPIO);gpio_free(SPI_SCK_GPIO);spi_master_put(spi_gpio->bitbang.master);return 0;
}
  1. 首先,函数接受一个指向struct platform_device类型的指针pdev作为参数。

  2. 接下来定义了两个指针变量spi_gpiopdata,分别指向struct spi_gpiostruct spi_gpio_platform_data类型的数据结构。

  3. platform_get_drvdata(pdev)用于获取存储在平台设备中的私有数据指针,将其赋值给spi_gpio指针。这个私有数据指针通常在设备的probe函数中设置。

  4. dev_get_platdata(&pdev->dev)用于获取与设备相关的平台数据,将其赋值给pdata指针。平台数据是在设备的设备树绑定或者通过platform_set_drvdata()函数设置的。

  5. spi_bitbang_stop(&spi_gpio->bitbang)调用函数spi_bitbang_stop(),停止SPI位操作的传输。这个函数将注销子设备。

  6. 接下来的一系列if语句用于检查是否为每个GPIO引脚分配了一个有效的引脚号,并释放这些引脚。

  • if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)检查是否为MISO引脚分配了一个非零的引脚号,如果是,则调用gpio_free(SPI_MISO_GPIO)释放该引脚。

  • if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)检查是否为MOSI引脚分配了一个非零的引脚号,如果是,则调用gpio_free(SPI_MOSI_GPIO)释放该引脚。

  • 最后,调用gpio_free(SPI_SCK_GPIO)释放SCK引脚。

  1. spi_master_put(spi_gpio->bitbang.master)调用函数spi_master_put(),释放对SPI主设备的引用。

  2. 最后,函数返回0,表示成功执行函数。

spi_gpio_request

spi_gpio_request的函数,它用于请求并分配SPI GPIO引脚的资源,并根据硬件配置设置SPI主设备的传输和接收功能标志。如果引脚分配成功,函数返回0,否则返回一个非零值表示分配失败。

static int spi_gpio_request(struct spi_gpio_platform_data *pdata,const char *label, u16 *res_flags)
{int value;/* NOTE:  SPI_*_GPIO symbols may reference "pdata" */if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI) {value = spi_gpio_alloc(SPI_MOSI_GPIO, label, false);if (value)goto done;} else {/* HW configuration without MOSI pin */*res_flags |= SPI_MASTER_NO_TX;}if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO) {value = spi_gpio_alloc(SPI_MISO_GPIO, label, true);if (value)goto free_mosi;} else {/* HW configuration without MISO pin */*res_flags |= SPI_MASTER_NO_RX;}value = spi_gpio_alloc(SPI_SCK_GPIO, label, false);if (value)goto free_miso;goto done;free_miso:if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)gpio_free(SPI_MISO_GPIO);
free_mosi:if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)gpio_free(SPI_MOSI_GPIO);
done:return value;
}
  1. 首先,函数接受一个指向struct spi_gpio_platform_data类型的指针pdata、一个指向字符常量的指针label和一个指向u16类型的指针res_flags作为参数。
  2. 首先,通过比较SPI_MOSI_GPIOSPI_GPIO_NO_MOSI的值来判断是否为MOSI引脚分配了一个有效的引脚号。
    • 如果SPI_MOSI_GPIO不等于SPI_GPIO_NO_MOSI,表示为MOSI引脚分配了一个有效的引脚号,接下来调用spi_gpio_alloc(SPI_MOSI_GPIO, label, false)函数分配该引脚,并将返回值赋给value变量。如果返回值不为0,则表示分配失败,直接跳转到done标签处。
    • 如果SPI_MOSI_GPIO等于SPI_GPIO_NO_MOSI,表示硬件配置中没有使用MOSI引脚,这时将设置*res_flags中的SPI_MASTER_NO_TX标志,表示SPI主设备没有传输功能。
  3. 接下来,通过比较SPI_MISO_GPIOSPI_GPIO_NO_MISO的值来判断是否为MISO引脚分配了一个有效的引脚号。
    • 如果SPI_MISO_GPIO不等于SPI_GPIO_NO_MISO,表示为MISO引脚分配了一个有效的引脚号,接下来调用spi_gpio_alloc(SPI_MISO_GPIO, label, true)函数分配该引脚,并将返回值赋给value变量。如果返回值不为0,则表示分配失败,直接跳转到free_mosi标签处。
    • 如果SPI_MISO_GPIO等于SPI_GPIO_NO_MISO,表示硬件配置中没有使用MISO引脚,这时将设置*res_flags中的SPI_MASTER_NO_RX标志,表示SPI主设备没有接收功能。
  4. 最后,调用spi_gpio_alloc(SPI_SCK_GPIO, label, false)函数分配SCK引脚,并将返回值赋给value变量。如果返回值不为0,则表示分配失败,直接跳转到free_miso标签处。
  5. 如果程序成功执行到这里,表示所有引脚分配都成功,直接跳转到done标签处。
  6. free_miso标签处,如果之前为MISO引脚分配了一个有效的引脚号,调用gpio_free(SPI_MISO_GPIO)函数释放该引脚。
  7. free_mosi标签处,如果之前为MOSI引脚分配了一个有效的引脚号,调用gpio_free(SPI_MOSI_GPIO)函数释放该引脚。
  8. done标签处,函数返回value变量的值,表示引脚分配的结果。如果返回值为0,表示成功执行函数,否则表示分配引脚失败。

spi_gpio_alloc

spi_gpio_alloc函数用于分配和配置一个GPIO引脚的资源。它首先通过gpio_request()函数请求分配GPIO资源,并根据is_in参数来配置引脚的输入或输出模式。如果分配和配置成功,函数返回0,否则返回一个非零值表示分配和配置失败。

static int spi_gpio_alloc(unsigned pin, const char *label, bool is_in)
{int value;value = gpio_request(pin, label);if (value == 0) {if (is_in)value = gpio_direction_input(pin);elsevalue = gpio_direction_output(pin, 0);}return value;
}
  1. 首先,函数接受一个无符号整数pin作为GPIO引脚号,一个指向字符常量的指针label作为引脚的标签,以及一个布尔值is_in来指示引脚是否用于输入。
  2. gpio_request(pin, label)调用函数gpio_request()来请求分配指定引脚号的GPIO资源,并将返回值赋给value变量。如果返回值为0,表示成功分配GPIO资源;如果返回值不为0,表示分配失败。
  3. 如果gpio_request()成功执行,进入条件判断语句块:
    • 如果is_in为真,表示该引脚是一个输入引脚,调用gpio_direction_input(pin)函数将该引脚配置为输入模式,并将返回值赋给value变量。如果返回值为0,表示成功配置引脚为输入模式;如果返回值不为0,表示配置失败。
    • 如果is_in为假,表示该引脚是一个输出引脚,调用gpio_direction_output(pin, 0)函数将该引脚配置为输出模式,并将输出电平设置为低电平(0),将返回值赋给value变量。如果返回值为0,表示成功配置引脚为输出模式并设置输出电平;如果返回值不为0,表示配置失败。
  4. 最后,函数返回value变量的值,表示引脚分配和配置的结果。如果返回值为0,表示成功执行函数;如果返回值不为0,表示分配和配置引脚失败。

spi_gpio_cleanup

spi_gpio_cleanup的函数,用于清理和释放SPI GPIO相关资源。它首先检查特定的SPI片选引脚是否分配了有效的GPIO资源,如果是,则释放该GPIO资源。然后,调用spi_bitbang_cleanup(spi)函数清理和释放与SPI位操作相关的资源。

static void spi_gpio_cleanup(struct spi_device *spi)
{struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi);unsigned long cs = spi_gpio->cs_gpios[spi->chip_select];if (cs != SPI_GPIO_NO_CHIPSELECT)gpio_free(cs);spi_bitbang_cleanup(spi);
}

spi_gpio_setup

pi_gpio_setup的函数,用于设置SPI GPIO相关的配置。它首先根据设备树环境或SPI控制器数据获取片选引脚的值,然后根据情况请求分配并配置片选引脚的GPIO资源。如果片选引脚成功分配和配置,将进行SPI位操作的设置。如果设置失败,将释放之前分配的GPIO资源。函数最终返回设置的状态,0表示成功,非零表示失败。

static int spi_gpio_setup(struct spi_device *spi)
{unsigned long		cs;int			status = 0;struct spi_gpio		*spi_gpio = spi_to_spi_gpio(spi);struct device_node	*np = spi->master->dev.of_node;if (np) {/** In DT environments, the CS GPIOs have already been* initialized from the "cs-gpios" property of the node.*/cs = spi_gpio->cs_gpios[spi->chip_select];} else {/** ... otherwise, take it from spi->controller_data*/cs = (uintptr_t) spi->controller_data;}if (!spi->controller_state) {if (cs != SPI_GPIO_NO_CHIPSELECT) {status = gpio_request(cs, dev_name(&spi->dev));if (status)return status;status = gpio_direction_output(cs,!(spi->mode & SPI_CS_HIGH));}}if (!status) {/* in case it was initialized from static board data */spi_gpio->cs_gpios[spi->chip_select] = cs;status = spi_bitbang_setup(spi);}if (status) {if (!spi->controller_state && cs != SPI_GPIO_NO_CHIPSELECT)gpio_free(cs);}return status;
}
  1. 首先,函数接受一个指向struct spi_device类型的指针spi作为参数。
  2. 代码中定义了一个无符号长整型变量cs,用于存储片选引脚(Chip Select,CS)的值。
  3. 定义了一个整型变量status,用于存储函数执行的状态,默认为0。
  4. 定义了一个指向struct spi_gpio类型的指针spi_gpio,通过spi_to_spi_gpio(spi)宏将spi转换为spi_gpio结构体。
  5. 定义了一个指向struct device_node类型的指针np,用于存储SPI主设备的设备树节点。
  6. 如果np非空(非NULL),表示在设备树(Device Tree)环境中,片选引脚已经从节点的"cs-gpios"属性中进行了初始化。
    • spi_gpio->cs_gpios[spi->chip_select]的值赋给cs,表示获取对应片选引脚的GPIO资源。
  7. 如果np为空,表示不在设备树环境中,从spi->controller_data中获取片选引脚的值。
    • spi->controller_data的值转换为无符号整数并赋给cs,表示获取对应片选引脚的GPIO资源。
  8. 如果spi->controller_state为空,表示SPI控制器的状态未初始化。
    • 如果 cs不等于 SPI_GPIO_NO_CHIPSELECT,表示为该片选引脚分配了有效的GPIO资源。
      • 调用gpio_request(cs, dev_name(&spi->dev))函数请求分配片选引脚的GPIO资源,并将返回值赋给status
      • 如果gpio_request()执行失败(返回值非零),直接返回status表示设置失败。
      • 调用gpio_direction_output(cs, !(spi->mode & SPI_CS_HIGH))函数将片选引脚配置为输出模式,并根据SPI模式中的SPI_CS_HIGH标志设置输出电平。
    • 如果spi->controller_state为空且status仍然为0,表示片选引脚成功分配和配置。
  9. 如果status为0,表示片选引脚成功分配和配置。
    • cs的值存储到spi_gpio->cs_gpios[spi->chip_select]中,以便后续使用。
    • 调用spi_bitbang_setup(spi)函数进行SPI位操作相关的设置,将返回值赋给status
  10. 如果status非零,表示片选引脚或SPI位操作设置发生错误。
  • 如果spi->controller_state为空且cs不等于SPI_GPIO_NO_CHIPSELECT,表示之前为片选引脚分配了GPIO资源,现在需要释放它。
    • 调用gpio_free(cs)函数释放片选引脚的GPIO资源。
  1. 最后,函数返回status,表示设置的结果。如果返回值为0,表示成功执行函数;如果返回值非零,表示设置失败。

spi_gpio_chipselect

spi_gpio_chipselect作用是根据传入的参数控制 SPI 设备的片选信号的 GPIO 引脚状态,以实现 SPI 通信中的片选功能。同时,根据 SPI 协议的要求,可以设置初始时钟极性。

static void spi_gpio_chipselect(struct spi_device *spi, int is_active)
{struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi);unsigned long cs = spi_gpio->cs_gpios[spi->chip_select];/* set initial clock polarity */if (is_active)setsck(spi, spi->mode & SPI_CPOL);if (cs != SPI_GPIO_NO_CHIPSELECT) {/* SPI is normally active-low */gpio_set_value_cansleep(cs, (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);}
}
  1. 首先,代码定义了一个静态函数 spi_gpio_chipselect,该函数接受两个参数:spi 是指向 spi_device 结构体的指针,表示要控制的 SPI 设备;is_active 是一个整数,表示片选信号的状态(激活或非激活)。
  2. 接下来,代码通过使用 spi_to_spi_gpio 函数将 spi_device 结构体转换为 spi_gpio 结构体,并将结果保存在 spi_gpio 变量中。这个转换是为了获取与 GPIO 控制相关的信息。
  3. 然后,代码使用 spi_device 结构体中的 chip_select 字段来确定当前片选信号对应的 GPIO 引脚编号,并将其保存在 cs 变量中。
  4. 代码接着根据 is_active 参数来设置初始时钟极性。如果 is_active 为非零值(表示片选信号激活),则通过 spi_device 结构体中的 mode 字段的 SPI_CPOL 位来确定初始时钟极性,然后调用 setsck 函数进行设置。
  5. 接下来,代码检查 cs 变量是否等于 SPI_GPIO_NO_CHIPSELECT。如果 cs 不等于该值,说明有有效的片选信号 GPIO 引脚配置。
  6. 在 SPI 协议中,通常片选信号是低电平有效的。因此,代码使用 gpio_set_value_cansleep 函数来设置片选信号 GPIO 引脚的电平。根据 spi_device 结构体中的 mode 字段的 SPI_CS_HIGH 位,如果该位为真,则表示片选信号是高电平有效,那么根据 is_active 参数的值直接传递给 gpio_set_value_cansleep 函数;如果该位为假,则表示片选信号是低电平有效,那么取 is_active 参数的反值作为电平状态传递给 gpio_set_value_cansleep 函数。

spi-bitbang

bitbang_txrx_32

bitbang_txrx_32用于进行 SPI 位移传输。通过调用传入的函数指针 txrx_word 实现实际的发送和接收操作。

static unsigned bitbang_txrx_32(struct spi_device	*spi,u32			(*txrx_word)(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits),unsigned		ns,struct spi_transfer	*t
) {unsigned		bits = t->bits_per_word;unsigned		count = t->len;const u32		*tx = t->tx_buf;u32			*rx = t->rx_buf;while (likely(count > 3)) {u32		word = 0;if (tx)word = *tx++;word = txrx_word(spi, ns, word, bits);if (rx)*rx++ = word;count -= 4;}return t->len - count;
}
  1. 函数开始时,从传输结构体 t 中获取位数 bits,数据长度 count,发送缓冲区指针 tx 和接收缓冲区指针 rx
  2. 进入 while 循环,当数据长度 count 大于 3(32 位数据的字长)时,循环继续。
  3. 在每次循环中,首先定义一个变量 word,用于保存当前要发送或接收的数据字。
  4. 如果发送缓冲区存在 (tx 不为空),则将发送缓冲区中的数据字赋值给 word,并将指针 tx 向后移动。
  5. 调用函数指针 txrx_word,将 SPI 设备指针 spi、传输时间 ns、数据字 word 和位数 bits 作为参数传递给该函数。函数 txrx_word 将执行实际的发送和接收操作,并返回接收到的数据字,该数据字将被赋值给 word
  6. 如果接收缓冲区存在 (rx 不为空),则将 word 的值存储到接收缓冲区中,并将指针 rx 向后移动。
  7. 减少数据长度 count 的值,表示已经处理了一个数据字。
  8. 循环回到第 2 步,继续处理下一个数据字,直到剩余数据长度不足以组成完整的 32 位数据。
  9. 返回传输结构体的长度 t->len 减去剩余的未处理数据长度 count,表示成功发送和接收的数据长度。

spi_bitbang_setup_transfer

spi_bitbang_setup_transfer用于设置 SPI 位移传输的参数的函数。根据传入的传输结构体 t 中的参数设置位移传输的位数和速率,并选择相应的位移传输函数进行数据的发送和接收。

int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
{struct spi_bitbang_cs	*cs = spi->controller_state;u8			bits_per_word;u32			hz;if (t) {bits_per_word = t->bits_per_word;hz = t->speed_hz;} else {bits_per_word = 0;hz = 0;}/* spi_transfer level calls that work per-word */if (!bits_per_word)bits_per_word = spi->bits_per_word;if (bits_per_word <= 8)cs->txrx_bufs = bitbang_txrx_8;else if (bits_per_word <= 16)cs->txrx_bufs = bitbang_txrx_16;else if (bits_per_word <= 32)cs->txrx_bufs = bitbang_txrx_32;elsereturn -EINVAL;/* nsecs = (clock period)/2 */if (!hz)hz = spi->max_speed_hz;if (hz) {cs->nsecs = (1000000000/2) / hz;if (cs->nsecs > (MAX_UDELAY_MS * 1000 * 1000))return -EINVAL;}return 0;
}
  1. 首先,从 SPI 设备结构体中获取位移传输相关的控制器状态结构体 cs
  2. 接着,定义变量 bits_per_wordhz,用于保存位数和传输速率。
  3. 如果传输结构体 t 不为空,将从传输结构体中获取位数和速率,并分别赋值给 bits_per_wordhz。如果 t 为空,则将 bits_per_wordhz 设置为 0。
  4. 如果位数 bits_per_word 为零,将从 SPI 设备结构体中获取默认的位数 spi->bits_per_word
  5. 根据位数 bits_per_word 的大小,决定使用不同的位移传输函数。
  6. 如果传输速率 hz 为零,将从 SPI 设备结构体中获取最大速率 spi->max_speed_hz
  7. 如果速率 hz 不为零,计算每个位移传输的时间间隔 nsecs。这里假设时钟周期为传输速率的倒数的一半(即半周期)。计算公式为 nsecs = (clock period)/2 = (1000000000/2) / hz
  8. 如果计算得到的时间间隔 nsecs 超过最大延迟时间,则返回错误码 -EINVAL,表示时间间隔过大。
  9. 如果都设置成功,返回 0,表示设置传输参数的函数执行成功。

spi_bitbang_setup

spi_bitbang_setup在进行实际的数据传输之前,设置 SPI 设备的位移传输相关参数。它通过获取位移传输函数、调用传输参数设置函数、设置片选信号状态等步骤来完成设置。

int spi_bitbang_setup(struct spi_device *spi)
{struct spi_bitbang_cs	*cs = spi->controller_state;struct spi_bitbang	*bitbang;bitbang = spi_master_get_devdata(spi->master);if (!cs) {cs = kzalloc(sizeof(*cs), GFP_KERNEL);if (!cs)return -ENOMEM;spi->controller_state = cs;}/* per-word shift register access, in hardware or bitbanging */cs->txrx_word = bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)];if (!cs->txrx_word)return -EINVAL;if (bitbang->setup_transfer) {int retval = bitbang->setup_transfer(spi, NULL);if (retval < 0)return retval;}dev_dbg(&spi->dev, "%s, %u nsec/bit\n", __func__, 2 * cs->nsecs);/* NOTE we _need_ to call chipselect() early, ideally with adapter* setup, unless the hardware defaults cooperate to avoid confusion* between normal (active low) and inverted chipselects.*//* deselect chip (low or high) */mutex_lock(&bitbang->lock);if (!bitbang->busy) {bitbang->chipselect(spi, BITBANG_CS_INACTIVE);ndelay(cs->nsecs);}mutex_unlock(&bitbang->lock);return 0;
}
  1. 首先,从 SPI 设备结构体中获取控制器状态结构体 cs 和位移传输相关的控制结构体 bitbang
  2. 使用函数 spi_master_get_devdata 从 SPI 主设备结构体中获取位移传输控制结构体 bitbang
  3. 如果控制器状态结构体 cs 为空,说明还没有为该 SPI 设备分配控制器状态结构体,此时需要为其分配内存并初始化。使用函数 kzalloc 分配内存,并将分配的内存赋值给 cs。如果内存分配失败,则返回错误码 -ENOMEM
  4. 将控制器状态结构体 cs 赋值给 SPI 设备结构体中的 controller_state 字段。
  5. 从位移传输控制结构体 bitbang 中根据 SPI 设备的模式(spi->mode)获取相应的位移传输函数 txrx_word。根据 SPI 设备的模式中的 SPI_CPOL(时钟极性)和 SPI_CPHA(时钟相位)位进行位运算,获取相应的位移传输函数。如果获取的函数为空,则返回错误码 -EINVAL
  6. 如果位移传输控制结构体 bitbang 中的 setup_transfer 函数存在,则调用该函数设置传输参数。传输参数通过传递 SPI 设备指针 spi 和空指针 NULL 来实现。如果 setup_transfer 函数返回的值小于 0,则表示设置传输参数失败,此时返回该错误码。
  7. 在进行实际的数据传输之前,需要先选择片选信号,并确保片选信号处于非活动状态。
  8. 通过互斥锁 bitbang->lock 来保护对位移传输控制结构体的访问。首先获取互斥锁。
  9. 检查位移传输控制结构体 bitbang 中的 busy 标志。如果该标志为假,则表示当前没有其他数据传输操作正在进行,此时调用 chipselect 函数将片选信号设置为非活动状态,并通过 ndelay 函数延迟一段时间,以确保片选信号稳定。
  10. 最后,释放互斥锁 bitbang->lock

spi_bitbang_transfer_one

spi_bitbang_transfer_one作用是执行单个传输操作,包括设置传输参数和进行数据的发送和接收。它通过调用位移传输控制结构体中的函数来完成传输操作,并根据传输的结果来设置传输操作的状态。最后,执行收尾工作并返回传输操作的状态。

static int spi_bitbang_transfer_one(struct spi_master *master,struct spi_device *spi,struct spi_transfer *transfer)
{struct spi_bitbang *bitbang = spi_master_get_devdata(master);int status = 0;if (bitbang->setup_transfer) {status = bitbang->setup_transfer(spi, transfer);if (status < 0)goto out;}if (transfer->len)status = bitbang->txrx_bufs(spi, transfer);if (status == transfer->len)status = 0;else if (status >= 0)status = -EREMOTEIO;out:spi_finalize_current_transfer(master);return status;
}
  1. 首先,从 SPI 主设备结构体中获取位移传输相关的控制结构体 bitbang
  2. 定义变量 status 来保存传输操作的状态,默认为 0。
  3. 如果位移传输控制结构体 bitbang 中的 setup_transfer 函数存在,则调用该函数设置传输参数。传输参数通过传递 SPI 设备指针 spi 和传输结构体指针 transfer 来实现。如果设置传输参数的函数返回的值小于 0,则表示设置传输参数失败,此时跳转到标签 out 处进行处理。
  4. 检查传输结构体 transfer 的数据长度 len 是否非零。如果非零,则调用位移传输控制结构体 bitbang 中的 txrx_bufs 函数进行数据的发送和接收。
  5. 判断传输操作的状态 status 是否等于传输结构体 transfer 的长度 len。如果相等,则表示传输操作成功完成,将状态 status 设置为 0。如果状态 status 大于等于 0 但不等于传输结构体 transfer 的长度 len,则表示传输操作未完成,将状态 status 设置为 -EREMOTEIO
  6. 跳转到标签 out 处,执行 spi_finalize_current_transfer 函数,以完成当前传输的收尾工作。
  7. 返回传输操作的状态 status

spi_bitbang_bufs

spi_bitbang_bufs封装了对控制器状态结构体中的位移传输函数的调用,以执行数据缓冲区的传输。它通过获取位移传输所需的时间间隔和调用位移传输函数来完成传输操作,并返回传输操作的状态。

static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t)
{struct spi_bitbang_cs	*cs = spi->controller_state;unsigned		nsecs = cs->nsecs;return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t);
}
  1. 首先,从 SPI 设备结构体中获取控制器状态结构体 cs
  2. 从控制器状态结构体 cs 中获取位移传输所需的时间间隔 nsecs
  3. 调用控制器状态结构体 cs 中的 txrx_bufs 函数来执行数据缓冲区的传输。该函数接受 SPI 设备指针 spi、位移传输函数 txrx_word、时间间隔 nsecs 和传输结构体指针 t 作为参数,并返回传输操作的状态。
  4. 将传输操作的状态作为函数的返回值。

spi_bitbang_prepare_hardware

spi_bitbang_prepare_hardware作用是在进行数据传输之前,准备 SPI 硬件。它通过设置位移传输控制结构体中的 busy 标志来指示硬件正在忙于数据传输操作。这样做可以确保在进行数据传输之前,其他线程不会干扰硬件的正常操作。

static int spi_bitbang_prepare_hardware(struct spi_master *spi)
{struct spi_bitbang	*bitbang;bitbang = spi_master_get_devdata(spi);mutex_lock(&bitbang->lock);bitbang->busy = 1;mutex_unlock(&bitbang->lock);return 0;
}
  1. 首先,从 SPI 主设备结构体中获取位移传输相关的控制结构体 bitbang
  2. 使用互斥锁 bitbang->lock 来保护对位移传输控制结构体的访问。首先获取互斥锁。
  3. 将位移传输控制结构体中的 busy 标志设置为 1,表示硬件正在忙于数据传输操作。
  4. 释放互斥锁 bitbang->lock,以允许其他线程访问位移传输控制结构体。
  5. 返回 0,表示硬件准备操作执行成功。

spi_bitbang_unprepare_hardware

spi_bitbang_unprepare_hardware在完成数据传输后释放 SPI 硬件资源。通过将位移传输控制结构体中的 busy 标志设置为 0,表示硬件不再忙于数据传输操作。这样做可以确保在释放硬件资源之前,其他线程可以正常访问硬件。

static int spi_bitbang_unprepare_hardware(struct spi_master *spi)
{struct spi_bitbang	*bitbang;bitbang = spi_master_get_devdata(spi);mutex_lock(&bitbang->lock);bitbang->busy = 0;mutex_unlock(&bitbang->lock);return 0;
}
  1. 首先,从 SPI 主设备结构体中获取位移传输相关的控制结构体 bitbang
  2. 使用互斥锁 bitbang->lock 来保护对位移传输控制结构体的访问。首先获取互斥锁。
  3. 将位移传输控制结构体中的 busy 标志设置为 0,表示硬件不再忙于数据传输操作。
  4. 释放互斥锁 bitbang->lock,以允许其他线程访问位移传输控制结构体。
  5. 返回 0,表示硬件释放操作执行成功。

spi_bitbang_set_cs

spi_bitbang_set_cs作用是根据输入参数的值来控制 SPI 设备的片选信号。它通过检查 SPI 设备的传输模式和输入参数的值,判断是否需要使能片选信号,并调用位移传输控制结构体中的函数来设置片选信号的状态。同时,使用延迟函数确保在改变片选信号状态前后有适当的延迟时间。

static void spi_bitbang_set_cs(struct spi_device *spi, bool enable)
{struct spi_bitbang *bitbang = spi_master_get_devdata(spi->master);/* SPI core provides CS high / low, but bitbang driver* expects CS active* spi device driver takes care of handling SPI_CS_HIGH*/enable = (!!(spi->mode & SPI_CS_HIGH) == enable);ndelay(SPI_BITBANG_CS_DELAY);bitbang->chipselect(spi, enable ? BITBANG_CS_ACTIVE :BITBANG_CS_INACTIVE);ndelay(SPI_BITBANG_CS_DELAY);
}
  1. 首先,从 SPI 设备结构体的主设备结构体中获取位移传输相关的控制结构体 bitbang
  2. 通过检查 SPI 设备的传输模式中的 SPI_CS_HIGH 标志位,以及函数输入参数 enable 的值,来确定是否需要使能片选信号。这是为了确保兼容片选信号的极性。
  3. 使用 ndelay 函数引入延迟,以确保在改变片选信号状态之前有足够的时间。
  4. 调用位移传输控制结构体 bitbang 中的 chipselect 函数,以控制片选信号的状态。根据 enable 的值,将片选信号设置为活动状态或非活动状态。
  5. 再次使用 ndelay 函数引入延迟,以确保在改变片选信号状态后有足够的时间。

spi_bitbang_start

spi_bitbang_start进行了一系列的设置和配置,包括初始化互斥锁、设置主设备结构体中的函数指针、注册 SPI 主设备等。通过这些操作,可以使得 SPI 位移传输准备就绪,并可以进行数据传输操作。

int spi_bitbang_start(struct spi_bitbang *bitbang)
{struct spi_master *master = bitbang->master;int ret;if (!master || !bitbang->chipselect)return -EINVAL;mutex_init(&bitbang->lock);if (!master->mode_bits)master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;if (master->transfer || master->transfer_one_message)return -EINVAL;master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;master->transfer_one = spi_bitbang_transfer_one;master->set_cs = spi_bitbang_set_cs;if (!bitbang->txrx_bufs) {bitbang->use_dma = 0;bitbang->txrx_bufs = spi_bitbang_bufs;if (!master->setup) {if (!bitbang->setup_transfer)bitbang->setup_transfer =spi_bitbang_setup_transfer;master->setup = spi_bitbang_setup;master->cleanup = spi_bitbang_cleanup;}}/* driver may get busy before register() returns, especially* if someone registered boardinfo for devices*/ret = spi_register_master(spi_master_get(master));if (ret)spi_master_put(master);return ret;
}
  1. 首先,从位移传输控制结构体中获取 SPI 主设备结构体 master
  2. 检查主设备结构体和位移传输控制结构体中的片选信号控制函数是否存在,如果不存在则返回错误码 -EINVAL。
  3. 初始化互斥锁 bitbang->lock,用于保护对位移传输控制结构体的访问。
  4. 如果主设备结构体中的传输模式位字段 mode_bits 未设置,则设置为默认的模式位,包括 SPI_CPOL、SPI_CPHA 和位移传输控制结构体中的标志位。
  5. 检查主设备结构体中的传输函数是否已经定义,如果已定义则返回错误码 -EINVAL。
  6. 设置主设备结构体中的准备硬件传输函数、释放硬件传输函数、单次传输函数和片选信号控制函数,分别对应位移传输控制结构体中的对应函数。
  7. 如果位移传输控制结构体中的数据缓冲区传输函数未定义,则设置使用 DMA 标志为 0,并将数据缓冲区传输函数设置为默认的位移传输函数 spi_bitbang_bufs。如果主设备结构体中的设置函数未定义,则设置使用默认的设置函数 spi_bitbang_setup 和清理函数 spi_bitbang_cleanup
  8. 注册 SPI 主设备,将其添加到系统中。如果注册失败,则释放主设备结构体并返回错误码。
  9. 返回注册结果,成功时返回 0。

spi_bitbang_stop

spi_bitbang_stop作用是停止 SPI 位移传输。它通过取消注册 SPI 主设备来停止相关的传输操作。这可以用于在不需要进行 SPI 位移传输时,将相关资源释放并从系统中移除相应的设备。

void spi_bitbang_stop(struct spi_bitbang *bitbang)
{spi_unregister_master(bitbang->master);
}
  1. 从位移传输控制结构体中获取 SPI 主设备结构体 master
  2. 调用 spi_unregister_master 函数来取消注册 SPI 主设备。这将从系统中移除该 SPI 主设备。

本文参考

https://blog.csdn.net/qq_16054639/article/details/106733956

https://www.cnblogs.com/TWL123/p/9516269.html

https://whycan.com/t_5012.html

https://www.imooc.com/article/33911

https://blog.csdn.net/Creator_Ly/article/details/109640572

相关文章:

【驱动】SPI驱动分析(五)-模拟SPI驱动

简介 模拟SPI驱动是一种软件实现的SPI总线驱动。在没有硬件SPI控制器的系统中&#xff0c;通过软件模拟实现SPI总线的功能。它允许在不修改硬件的情况下&#xff0c;通过GPIO&#xff08;通用输入/输出&#xff09;引脚模拟SPI总线的通信&#xff0c;从而与SPI设备进行数据交换…...

人工智能_机器学习056_拉格朗日乘子法原理推导_公式由来详解_原理详解---人工智能工作笔记0096

https://blog.csdn.net/Soft_Po/article/details/118332454 这里有老师的一篇文章介绍拉格朗日乘子法的原理推导 结合老师的这篇文章我们来看一下详细的推导过程 可以看到上一节我们说,一个有条件的,函数,可以转换为一个,无条件的函数, 根据拉格朗日乘子法,可以创建出一个等…...

记RocketMQ本地开发环境搭建始末

前言 最近工作中涉及到了RocketMQ的应用&#xff0c;为方便开发决定本地搭建一套RocketMQ的使用环境。 果然实践是个好东西... VMware虚拟环境搭建 这个网上有很多教程&#xff0c;只会比我写的详细有条理&#xff0c;这里就不在赘述了。 虚拟机搭建好之后每次重启电脑都无…...

2023年全国职业院校技能大赛“ 信息安全管理与评估” 测试题2

一.单选题 1、下列不属于口令安全威胁的是&#xff1f;&#xff08; &#xff09; A、 弱口令 B、 明文传输 C、 MD5 加密 D、 多账户共用一个密码 2、在学校或单位如果发现自己的计算机感染了病毒,应首先采取什么措施 ( )。 A、断开网络 B、告知领导 C、杀毒 D、重…...

flutter开发实战-readmore长文本展开和收缩控件

flutter开发实战-readmore长文本展开和收缩控件 当长文本展开和收缩控件&#xff0c;我们需要使用readmore来处理长文本展开和收缩&#xff0c;方便阅读 一、引入readmore 在工程的pubspec.yaml中引入插件 readmore: ^2.1.0ReadMoreText的属性如下 const ReadMoreText(this.…...

如何使用简单的分支策略来保护您的 Git 项目

良好的分支策略可以使项目源代码获得一致且安全的数据&#xff0c;所有协作者可以在更短的生命周期内共享和访问这些数据。 您必须以灵活的方式设计项目模型&#xff0c;以便对所有成员角色和权限进行良好的管理。 我要谈论的并没有什么令人惊讶的新鲜事。您可能已经知道一些…...

vue3的 nextTick()的使用

引言&#xff1a; 当你修改了响应式状态时&#xff0c;DOM 会被自动更新。但是需要注意的是&#xff0c;DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改&#xff0c;以确保不管你进行了多少次状态修改&#xff0c;每个组件都只会被更新一次。 要等待…...

Redis Lua沙盒绕过 命令执行(CVE-2022-0543)漏洞复现

Redis Lua沙盒绕过 命令执行(CVE-2022-0543)漏洞复现 Redis如果在没有开启认证的情况下&#xff0c;可以导致任意用户在可以访问目标服务器的情况下未授权访问Redis以及读取Redis的数据。–那么这也就是redis未授权访问了 Redis的默认端口是6379 可以用空间测绘搜索&#xff…...

react中useState、useRef、变量之间的区别

函数组件有函数作用域&#xff0c;每次render时&#xff0c;声明的方法会生成新的引用&#xff0c;声明的普通变量会重新声明并赋值初始值&#xff0c;而useRef和useState会保留状态。 useState、useRef、变量的区别 1. useState 组件更新不会改变之前的状态&#xff0c;可以保…...

企业软件的分类|app小程序网站定制开发

企业软件的分类|app小程序网站定制开发 企业软件是指为满足企业管理和运营需求而设计和开发的一类软件&#xff0c;它通常用于支持企业的各项业务活动和流程。根据其功能和应用领域的不同&#xff0c;可以将企业软件分为以下几类。 1. 企业资源计划&#xff08;ERP&#xff09…...

Flink(八)【窗口】

前言 终于忙完了四门专业课的期末&#xff0c;确实挺累啊。今天开始继续学习 Flink &#xff0c;接着上次的内容。 今日摘录&#xff1a; 他觉得一个人奋斗更轻松自在。跟没有干劲的人在一起厮混&#xff0c;只会徒增压力。 -《解忧杂货店》 1、窗口 之前我们已经了解了…...

云轴科技ZStack信创云平台助力国泰君安期货实现信创改造

信创是数字中国建设的重要组成部分&#xff0c;也是数字经济发展的关键推动力量。作为云基础软件企业&#xff0c;云轴科技ZStack 产品矩阵全面覆盖数据中心云基础设施&#xff0c;ZStack信创云首批通过可信云《一云多芯IaaS平台能力要求》先进级&#xff0c;是其中唯一兼容四种…...

C语言猜数字小游戏

本文将介绍如何使用C语言写一个猜数字的小游戏 具体代码如下&#xff1a; #include<stdio.h> #include<stdlib.h> #include<time.h>// 显示游戏菜单 void menu() {printf("**** 猜数字游戏! ****\n");printf("**** 按1开始游戏 ****\…...

自定义BeanPostProcessor之XssBeanPostProcessor

什么是BeanPostProcessor BeanPostProcessor是Spring框架中的一个重要的扩展点&#xff0c;它允许开发者在Bean初始化前后对Bean进行自定义处理。Spring中有很多内置的BeanPostProcessor&#xff0c;如AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcess…...

如何使用Windows自带的IIS服务搭建本地站点并远程访问

文章目录 1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试 3. Cpolar内网穿透3.1 下载安装Cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5.结语 1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xf…...

【微软技术栈】基于任务的异步编程

本文内容 隐式创建和运行任务显式创建和运行任务任务 ID任务创建选项任务、线程和区域性创建任务延续创建分离的子任务创建子任务等待任务完成组合任务处理任务中的异常取消任务TaskFactory 类无委托的任务自定义计划程序相关数据结构自定义任务类型 任务并行库 (TPL) 以“任…...

react hooks 学习之react router v6 路由表配置

/ 如果你是在ts中&#xff0c;那么这个这个文件名是router.ts那么这个<Home/>这里会报eslint错误&#xff0c;所以为了解决这个错误&#xff0c;直接改成router.tsx就行 import { RouteObject } from react-router-dom; import Home from ../page/Home; import About fr…...

Echarts 设置数据条颜色 宽度

设置数据条颜色&#xff08;推荐&#xff09; let yData [{value: 500,time: 2012-11-12,itemStyle: //设置数据条颜色{normal: { color: red }}},{value: 454,time: 2020-5-17},{value: 544,time: 2022-1-22},{value: 877,time: 2013-1-30}, {value: 877,time: 2012-11-12}]…...

2023-11-30 通过中缀表达式转换后缀表达式, 用C语言完成一个简单的计算器

点击 <C 语言编程核心突破> 快速C语言入门 通过中缀表达式转换后缀表达式, 用C语言完成一个简单的计算器 前言一、中缀表达式和后缀表达式 (AI辅助)二、中缀转后缀规则及后缀运算规则 (AI辅助)总结 前言 要解决问题: 在练习用Qt完成一个简单的计算器时, 需要将一个文本…...

设计模式总目录

目录 设计模式 1. 创建型模式 1.1 工厂方法模式 1.2 抽象工厂模式 1.3 单例模式 1.4 建造者模式 1.5原型模式 2. 结构型模式 2.1 适配器模式 2.2 装饰器模式 2.3 代理模式 2.4 外观模式 2.5 桥接模式 2.6 组合模式 2.7 享元模式 3. 行为型模式 3.1 策略模式 …...

通俗理解词向量模型,预训练模型,Transfomer,Bert和GPT的发展脉络和如何实践

最近研究GPT&#xff0c;深入的从transfomer的原理和代码看来一下&#xff0c;现在把学习的资料和自己的理解整理一下。 这个文章写的很通俗易懂&#xff0c;把transformer的来龙去脉&#xff0c;还举例了很多不错的例子。 Transformer通俗笔记&#xff1a;从Word2Vec、Seq2S…...

键入网址到网页显示,期间发生了什么?(计算机网络)

浏览器首先会对URL进行解析 下面以http://www.server.com/dir1/file1.html为例 当没有路径名时&#xff0c;就代表访问根目录下事先设置的默认文件&#xff0c;也就是 /index.html 或者 /default.html 对URL进行解析之后&#xff0c;浏览器确定了 Web 服务器和文件名&#x…...

python-GC机制、装饰器、生成器、迭代器、三元表达式、列表生成式、生成器表达式、函数递归、面向对象、

1 基础知识 1.1 GC机制 Python的垃圾回收&#xff0c;其实高级的语言都有自己的垃圾回收机制简称GC&#xff0c; python当中主要通过三种方式解决垃圾回收的方式&#xff0c;引用计数、标记清除、分代回收。引用计数&#xff1a;如果有新的引用指向对象&#xff0c;对象引用计…...

Linux命令--根据端口号查看进程号(PID)

Linux命令–根据端口号查看进程号&#xff08;PID&#xff09; 查找8080端口对应的进程号: netstat -nlp|grep :8297对应的进程号1061,如果想杀掉此进程&#xff0c;可以用一下命令&#xff1a; kill -9 1061...

LangChain 9 模型Model I/O 聊天提示词ChatPromptTemplate, 少量样本提示词FewShotPrompt

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…...

使用 Vue3 + Pinia + Ant Design Vue3 搭建后台管理系统

Vue3 & Ant Design Vue3基础 nodejs版本要求&#xff1a;node-v18.16.0-x64 nodejs基础配置 npm -v node -vnpm config set prefix "D:\software\nodejs\node_global" npm config set cache "D:\software\nodejs\node_cache"npm config get registry …...

SpringCloud核心组件

Eureka 注册中心&#xff0c;服务的注册与发现 Feign远程调用 Ribbon负载均衡&#xff0c;默认轮询 Hystrix 熔断 降级 Zuul微服务网关&#xff08;这个组件负责网络路由&#xff0c;可以做统一的降级、限流、认证授权、安全&#xff09; Eureka 微服务的功能主要有以下几…...

基于C++11实现将IP地址、端口号和连接状态写入文件

要基于C11实现将IP地址、端口号和连接状态写入文件&#xff0c;您可以使用std::ofstream类来打开文件并进行写入操作。以下是一个示例&#xff1a; #include <iostream> #include <fstream>void writeConnectionStatus(const std::string& ip, int port, bool…...

非空断言,

先看下TypeScript基础之非空断言操作符、可选链运算符、空值合并运算符-CSDN博客 我没有复现出来&#xff0c;但是我知道了它的作用 用 let str: string arg!; 代替 let str: string; if (arg) { str arg; } 非空断言&#xff08;!&#xff09;和不使用的区别在于对于…...

Spark---创建DataFrame的方式

1、读取json格式的文件创建DataFrame 注意&#xff1a; 1、可以两种方式读取json格式的文件。 2、df.show()默认显示前20行数据。 3、DataFrame原生API可以操作DataFrame。 4、注册成临时表时&#xff0c;表中的列默认按ascii顺序显示列。 df.createTempView("mytab…...