【驱动】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

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

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

相关文章

单片机学习12——电容

电容的作用&#xff1a; 1&#xff09;降压作用&#xff1a; 容抗&#xff1a; Xc 1/2fc 串联分压原理。2100Ω的容量&#xff0c;50Hz的频率&#xff0c;可以得到1.5uF。断电之后&#xff0c;需要串联一个1MΩ的电阻放电。 那是不是可以使用2100欧姆的电阻来代替电容呢&am…

Redis基础系列-安装Redis

Redis基础系列-安装Redis 文章目录 Redis基础系列-安装Redis1. 环境要求2. 下载redis3. 安装4. 配置5 参考与感谢 1. 环境要求 &#xff08;安装C语言编译环境&#xff09;redis是用C语言开发的&#xff0c;所以需要安装C语言编译环境,中途可能会出现询问你是否需要可以安装&a…

useDispatch和store.dispatch

在 React 中&#xff0c;useDispatch 是 React Redux 提供的一个 hook&#xff0c;而 store.dispatch 是 Redux 中的一个方法。它们的作用是触发 Redux 中的 action&#xff0c;从而更新状态。下面是它们之间的主要区别&#xff1a; 使用场景&#xff1a; useDispatch: 这是 Re…

DS八大排序之直接选择排序和堆排序

前言 上一期我们已经介绍了&#xff0c;排序、为什么要有排序以及排序在实际生活中的应用。并且介绍并实现了直接插入排序和它的优化即希尔排序~&#xff01;本期我们再来学习一组排序 ---- "选择排序"即直接选择排序和堆排序~&#xff01; 本期内容介绍 直接选择排…

【机器视觉技术栈】- 机器视觉基础

1.1 为什么采用机器视觉 人眼与机器视觉对比 人眼机器视觉精确性差&#xff0c;64灰度级&#xff0c;不能分辨小于100微米的目标强&#xff0c;256灰度级&#xff0c;可检测微米级目标速度慢&#xff0c;无法看清间隔小于40毫秒的运动目标快&#xff0c;快门时间可达10微秒适…

操作系统背景知识

一、程序分类 程序按其运行环境分为&#xff1a; 裸机程序&#xff1a;直接运行在对应硬件上的程序 应用程序&#xff1a;只能运行在对应操作系统上的程序 二、计算机系统的层次结构 计算机系统两种层次结构&#xff1a; 2.1 无操作系统的简单的两层结构 2.2 有操作系统的…

企业软件的分类有哪些|app小程序定制开发

企业软件的分类有哪些|app小程序定制开发 企业软件是指为了满足企业运营和管理需求而开发的软件系统。根据不同的功能和应用领域&#xff0c;企业软件可以分为以下几个分类&#xff1a; 1. 企业资源计划&#xff08;Enterprise Resource Planning&#xff0c;ERP&#xff09;软…

Vue2升级到Vue3到底是不是一个正确的选择?

Vue2升级到Vue3是否是一个正确的选择&#xff0c;这取决于您的项目需求和团队的技术栈。以下是一些关于Vue2升级到Vue3的优缺点&#xff0c;供您参考&#xff1a; 优点&#xff1a; 1. 性能提升&#xff1a;Vue3在性能方面进行了优化&#xff0c;包括渲染速度、内存占用等方面…

云计算生成式 -给你不一样的音乐推荐新体验

目录 摘要&#xff1a; 正文&#xff1a; 一、亚马逊云与生成式 AI 结合的展望/总结 二、我用亚马逊云科技生成式 AI 产品打造了什么&#xff0c;解决了什么问题 三、未来云端技术发展趋势的见解 四、云端技术未来需要解决的问题 1、如何保护数据安全和隐私&#xff1f; …

空间连通区域@曲面积分为零问题@通量和散度@高斯公式物理意义

文章目录 沿任意闭曲面的曲面积分为0的条件空间连通区域概念小结例 充要条件定理证明 通量和散度流量(通量)例 散度和高斯公式的物理意义借助速度场讨论一般向量场的散度小结例 高斯公式的向量场的通量和散度向量形式 沿任意闭曲面的曲面积分为0的条件 与讨论曲线积分中闭曲线…

一个菜单两个二级路由的搭建

效果如下&#xff0c;而且这是最上方的菜单&#xff0c;需要进入以后重定向。 {path: /,name: HOME,component: ConsoleLayout, //这里也有router-viewmeta: {menu: false},redirect: {name: ManagerList},children: [{path: /rightsManage,name: RightsManage,component: () &…

【FMC140】 基于VITA57.4标准的双通道5.2GSPS(或单通道10.4GSPS)射频采样FMC+子卡模块

板卡概述 FMC140是一款具有缓冲模拟输入的低功耗、12位、双通道&#xff08;5.2GSPS/通道&#xff09;、单通道10.4GSPS、射频采样ADC模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.1规范&#xff0c;该模块可以作为一个理想的IO单元耦合至FPGA前端&#xff0c;8通道…

第三届大湾区跨境物流商交会 深航协华南会14周年庆盛大召开

2023年11月28日&#xff0c;华南物流商会年度盛典:第三届大湾区跨境物流商交会 暨 深航协华南会十四周年庆在深圳市龙华希尔顿逸林酒店隆重举行。长沙市驻深办事处刘永红主任、深圳市商务局鲁云帆处长、深圳市航空业协会朱庆峰会长、深圳市物流行业协会王利强会长、深圳市跨境电…

ComfiUI API调用随记

来进行知识接力了&#xff1a; 首先了解下ComfiUI的APIstable diffusion comfyui的api使用教程-CSDN博客 对于ComfiUI&#xff0c;接口比较简单。查询接口比较容易看明白。 对于发起prompt的请求&#xff0c;如果需要图片的&#xff0c;则需预先上传图片给ComfiUI&#xff0c…

基于深度学习的驾驶员状态监测预警系统(正文)

摘 要 近年来驾驶员因疲劳驾驶而造成的交通事故逐年增多&#xff0c;驾驶员的驾驶状态对道路和人身安全产生重大影响&#xff0c;因此做好驾驶员驾驶状态的管理及预警是非常有必要的。 随着深度学习在目标检测算法应用的不断深入&#xff0c;YOLOv5等目标检测算法也相继具有了广…

11.30固定成本,完全竞争市场,无差异曲线,帕累托最优

固定成本 固定成本是指不随生产量变化而变化的成本&#xff0c;它是企业在生产过程中无论产量高低都必须支付的成本。计算固定成本的方法如下&#xff1a; 确定固定成本的项目&#xff1a;首先需要确定固定成本的项目&#xff0c;例如租金、折旧费、固定人工费用等。 收集相关…

暗物质:揭秘宇宙的隐形奥秘

暗物质:揭秘宇宙的隐形奥秘 一、引言 在浩瀚的宇宙中,有一种神秘的存在,它虽然看不见、摸不着,但却对宇宙的结构和演化起着至关重要的作用。这种存在就是暗物质。暗物质的研究是天文学和物理学领域的热点之一,科学家们正在利用各种手段来揭示它的奥秘。在本文中,我们将一…

学习k8s的介绍(一)

一、kubernetes及Docker相关介绍 1、kubernetes是什么 1-1、简称为k8s或kube&#xff0c;是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化。 声明式配置语法&#xff1a; kubectl create/apply/delete -f xx…

11-30 SpringBoot2

热部署 开发过程中,修改代码,不需要重启,自动更新 项目上线,一定要关闭 SpringBoot热部署的实现&#xff1f;&#xff1f; ideal默认阻止class类更新 2&#xff0e;需要手动构建项目&#xff0c;可以使用快捷键激活此功能ctrl F9 / build project 自动构建项目 允许程序运行…

劲爆:Sam Altman 回归CEO专访确认Q*的存在

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…