1)实验平台:正点原子Linux开发板

2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南关注官方微信号公众号,获取更多资料:正点原子

第六十二章Linux SPI驱动实验

上一章我们讲解了如何编写Linux下的I2C设备驱动,SPI也是很常用的一个串行通信协议,本章我们就来学习一下如何在Linux下编写SPI设备驱动。本章实验的最终目的就是驱动I.MX6U-ALPHA开发板上的ICM-20608这个SPI接口的六轴传感器,可以在应用程序中读取ICM-20608的原始传感器数据。

62.1 Linux下SPI驱动框架简介

SPI驱动框架和I2C很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是发SOC的SPI控制器接口。比如在裸机篇中的《第二十七章SPI实验》,我们编写了bsp_spi.c和bsp_spi.h这两个文件,这两个文件是I.MX6U的SPI控制器驱动,我们编写好SPI控制器驱动以后就可以直接使用了,不管是什么SPI设备,SPI控制器部分的驱动都是一样,我们的重点就落在了种类繁多的SPI设备驱动。

62.1.1 SPI主机驱动

SPI主机驱动就是SOC的SPI控制器驱动,类似I2C驱动里面的适配器驱动。Linux内核使用spi_master表示SPI主机驱动,spi_master是个结构体,定义在include/linux/spi/spi.h文件中,内容如下(有缩减):

示例代码62.1.1.1 spi_master结构体

315struct spi_master {

316struct device dev;

317

318 struct list_head list;

......

326 s16 bus_num;

327

328/* chipselects will be integral to many controllers; some others

329 * might use board-specific GPIOs.

330 */

331 u16 num_chipselect;

332

333/* some SPI controllers pose alignment requirements on DMAable

334 * buffers; let protocol drivers know about these requirements.

335 */

336 u16 dma_alignment;

337

338/* spi_device.mode flags understood by this controller driver */

339 u16 mode_bits;

340

341/* bitmask of supported bits_per_word for transfers */

342 u32 bits_per_word_mask;

......

347/* limits on transfer speed */

348 u32 min_speed_hz;

349 u32 max_speed_hz;

350

351/* other constraints relevant to this driver */

352 u16 flags;

......

359/* lock and mutex for SPI bus locking */

360 spinlock_t bus_lock_spinlock;

361struct mutex bus_lock_mutex;

362

363/* flag indicating that the SPI bus is locked for exclusive use */

364bool bus_lock_flag;

......

372int(*setup)(struct spi_device *spi);

373

......

393int(*transfer)(struct spi_device *spi,

394struct spi_message *mesg);

......

434 int(*transfer_one_message)(struct spi_master *master,

435struct spi_message *mesg);

......

462};

第393行,transfer函数,和i2c_algorithm中的master_xfer函数一样,控制器数据传输函数。

第434行,transfer_one_message函数,也用于SPI数据发送,用于发送一个spi_message,SPI的数据会打包成spi_message,然后以队列方式发送出去。

也就是SPI主机端最终会通过transfer函数与SPI设备进行通信,因此对于SPI主机控制器的驱动编写者而言transfer函数是需要实现的,因为不同的SOC其SPI控制器不同,寄存器都不一样。和I2C适配器驱动一样,SPI主机驱动一般都是SOC厂商去编写的,所以我们作为SOC的使用者,这一部分的驱动就不用操心了,除非你是在SOC原厂工作,内容就是写SPI主机驱动。

SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后向Linux内核注册spi_master。

1、spi_master申请与释放

spi_alloc_master函数用于申请spi_master,函数原型如下:

struct spi_master *spi_alloc_master(struct device *dev,

unsigned size)

函数参数和返回值含义如下:

dev:设备,一般是platform_device中的dev成员变量。

size:私有数据大小,可以通过spi_master_get_devdata函数获取到这些私有数据。

返回值:申请到的spi_master。

spi_master的释放通过spi_master_put函数来完成,当我们删除一个SPI主机驱动的时候就需要释放掉前面申请的spi_master,spi_master_put函数原型如下:

void spi_master_put(struct spi_master *master)

函数参数和返回值含义如下:

master:要释放的spi_master。

返回值:无。

2、spi_master的注册与注销

当spi_master初始化完成以后就需要将其注册到Linux内核,spi_master注册函数为spi_register_master,函数原型如下:

int spi_register_master(struct spi_master *master)

函数参数和返回值含义如下:

master:要注册的spi_master。

返回值:0,成功;负值,失败。

I.MX6U的SPI主机驱动会采用spi_bitbang_start这个API函数来完成spi_master的注册,spi_bitbang_start函数内部其实也是通过调用spi_register_master函数来完成spi_master的注册。

如果要注销spi_master的话可以使用spi_unregister_master函数,此函数原型为:

void spi_unregister_master(struct spi_master *master)

函数参数和返回值含义如下:

master:要注销的spi_master。

返回值:无。

如果使用spi_bitbang_start注册spi_master的话就要使用spi_bitbang_stop来注销掉spi_master。

62.1.2 SPI设备驱动

spi设备驱动也和i2c设备驱动也很类似,Linux内核使用spi_driver结构体来表示spi设备驱动,我们在编写SPI设备驱动的时候需要实现spi_driver。spi_driver结构体定义在include/linux/spi/spi.h文件中,结构体内容如下:

示例代码62.1.1.2 spi_driver结构体

180struct spi_driver {

180conststruct spi_device_id *id_table;

180int(*probe)(struct spi_device *spi);

180int(*remove)(struct spi_device *spi);

180void(*shutdown)(struct spi_device *spi);

180struct device_driver driver;

180};

可以看出,spi_driver和i2c_driver、platform_driver基本一样,当SPI设备和驱动匹配成功以后probe函数就会执行。

同样的,spi_driver初始化完成以后需要向Linux内核注册,spi_driver注册函数为spi_register_driver,函数原型如下:

int spi_register_driver(struct spi_driver *sdrv)

函数参数和返回值含义如下:

sdrv:要注册的spi_driver。

返回值:0,注册成功;赋值,注册失败。

注销SPI设备驱动以后也需要注销掉前面注册的spi_driver,使用spi_unregister_driver函数完成spi_driver的注销,函数原型如下:

void spi_unregister_driver(struct spi_driver *sdrv)

函数参数和返回值含义如下:

sdrv:要注销的spi_driver。

返回值:无。

spi_driver注册示例程序如下:

示例代码62.1.1.3 spi_driver注册示例程序

1 /* probe函数 */

2staticint xxx_probe(struct spi_device *spi)

3{

4 /* 具体函数内容 */

5 return0;

6}

7

8/* remove函数 */

9staticint xxx_remove(struct spi_device *spi)

10{

11 /* 具体函数内容 */

12 return0;

13}

14/* 传统匹配方式ID列表 */

15staticconststruct spi_device_id xxx_id[]={

16 {"xxx",0},

17 {}

18};

19

20/* 设备树匹配列表 */

21staticconststruct of_device_id xxx_of_match[]={

22 {.compatible ="xxx"},

23 {/* Sentinel */}

24};

25

26/* SPI驱动结构体 */

27staticstruct spi_driver xxx_driver ={

28 .probe = xxx_probe,

29 .remove = xxx_remove,

30 .driver ={

31 .owner = THIS_MODULE,

32 .name ="xxx",

33 .of_match_table = xxx_of_match,

34 },

35 .id_table = xxx_id,

36};

37

38/* 驱动入口函数 */

39staticint __init xxx_init(void)

40{

41 return spi_register_driver(&xxx_driver);

42}

43

44/* 驱动出口函数 */

45staticvoid __exit xxx_exit(void)

46{

47 spi_unregister_driver(&xxx_driver);

48}

49

50 module_init(xxx_init);

51 module_exit(xxx_exit);

第1~36行,spi_driver结构体,需要SPI设备驱动人员编写,包括匹配表、probe函数等。和i2c_driver、platform_driver一样,就不详细讲解了。

第39~42行,在驱动入口函数中调用spi_register_driver来注册spi_driver。

第45~48行,在驱动出口函数中调用spi_unregister_driver来注销spi_driver。

62.1.3 SPI设备和驱动匹配过程

SPI设备和驱动的匹配过程是由SPI总线来完成的,这点和platform、I2C等驱动一样,SPI总线为spi_bus_type,定义在drivers/spi/spi.c文件中,内容如下:

示例代码62.1.3.1 spi_bus_type结构体

131struct bus_type spi_bus_type ={

132.name ="spi",

133.dev_groups = spi_dev_groups,

134.match = spi_match_device,

135.uevent = spi_uevent,

136};

可以看出,SPI设备和驱动的匹配函数为spi_match_device,函数内容如下:

示例代码62.1.3.2 spi_match_device函数

99staticint spi_match_device(struct device *dev,

struct device_driver *drv)

100{

101conststruct spi_device *spi = to_spi_device(dev);

102conststruct spi_driver *sdrv = to_spi_driver(drv);

103

104/* Attempt an OF style match */

105if(of_driver_match_device(dev, drv))

106return1;

107

108/* Then try ACPI */

109if(acpi_driver_match_device(dev, drv))

110return1;

111

112if(sdrv->id_table)

113return!!spi_match_id(sdrv->id_table, spi);

114

115return strcmp(spi->modalias, drv->name)==0;

116}

spi_match_device函数和i2c_match_device函数的对于设备和驱动的匹配过程基本一样。

第105行,of_driver_match_device函数用于完成设备树设备和驱动匹配。比较SPI设备节点的compatible属性和of_device_id中的compatible属性是否相等,如果相当的话就表示SPI设备和驱动匹配。

第109行,acpi_driver_match_device函数用于ACPI形式的匹配。

第113行,i2c_match_id函数用于传统的、无设备树的I2C设备和驱动匹配过程。比较I2C设备名字和i2c_device_id的name字段是否相等,相等的话就说明I2C设备和驱动匹配。

第115行,比较spi_device中modalias成员变量和device_driver中的name成员变量是否相等。

62.2 I.MX6U SPI主机驱动分析

和I2C的适配器驱动一样,SPI主机驱动一般都由SOC厂商编写好了,打开imx6ull.dtsi文件,找到如下所示内容:

示例代码62.2.1 imx6ull.dtsi文件中的ecspi3节点内容

1 ecspi3: ecspi@02010000 {

2 #address-cells =<1>;

3 #size-cells =<0>;

4 compatible ="fsl,imx6ul-ecspi","fsl,imx51-ecspi";

5 reg =<0x020100000x4000>;

6 interrupts =<GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;

7 clocks =clks IMX6UL_CLK_ECSPI3>,

8clks IMX6UL_CLK_ECSPI3>;

9 clock-names ="ipg","per";

10 dmas =sdma 771>,sdma 872>;

11 dma-names ="rx","tx";

12 status ="disabled";

13};

重点来看一下第4行的compatible属性值,compatible属性有两个值"fsl,imx6ul-ecspi"和"fsl,imx51-ecspi",在Linux内核源码中搜素这两个属性值即可找到I.MX6U对应的ECSPI(SPI)主机驱动。I.MX6U的ECSPI主机驱动文件为drivers/spi/spi-imx.c,在此文件中找到如下内容:

示例代码62.2.2 spi_imx_driver结构体

694staticstruct platform_device_id spi_imx_devtype[]={

695{

696.name ="imx1-cspi",

697.driver_data =(kernel_ulong_t)&imx1_cspi_devtype_data,

698},{

699.name ="imx21-cspi",

700.driver_data =(kernel_ulong_t)&imx21_cspi_devtype_data,

......

713},{

714.name ="imx6ul-ecspi",

715.driver_data =(kernel_ulong_t)&imx6ul_ecspi_devtype_data,

716},{

717/* sentinel */

718}

719};

720

721staticconststruct of_device_id spi_imx_dt_ids[]={

722{.compatible ="fsl,imx1-cspi",.data =

&imx1_cspi_devtype_data,},

......

728{.compatible ="fsl,imx6ul-ecspi",.data =

&imx6ul_ecspi_devtype_data,},

729{/* sentinel */}

730};

731 MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);

......

1338staticstruct platform_driver spi_imx_driver ={

1339.driver ={

1340.name = DRIVER_NAME,

1341.of_match_table = spi_imx_dt_ids,

1342.pm = IMX_SPI_PM,

1343},

1344.id_table = spi_imx_devtype,

1345.probe = spi_imx_probe,

1346.remove = spi_imx_remove,

1347};

1348 module_platform_driver(spi_imx_driver);

第714行,spi_imx_devtype为SPI无设备树匹配表。

第721行,spi_imx_dt_ids为SPI设备树匹配表。

第728行,"fsl,imx6ul-ecspi"匹配项,因此可知I.MX6U的ECSPI驱动就是spi-imx.c这个文件。

第1338~1347行,platform_driver驱动框架,和I2C的适配器驱动一行,SPI主机驱动器采用了platfom驱动框架。当设备和驱动匹配成功以后spi_imx_probe函数就会执行。

spi_imx_probe函数会从设备树中读取相应的节点属性值,申请并初始化spi_master,最后调用spi_bitbang_start函数(spi_bitbang_start会调用spi_register_master函数)向Linux内核注册spi_master。

对于I.MX6U来讲,SPI主机的最终数据收发函数为spi_imx_transfer,此函数通过如下层层调用最终实现SPI数据发送:

spi_imx_transfer

-> spi_imx_pio_transfer

-> spi_imx_push

-> spi_imx->tx

spi_imx是个spi_imx_data类型的机构指针变量,其中tx和rx这两个成员变量分别为SPI数据发送和接收函数。I.MX6U SPI主机驱动会维护一个spi_imx_data类型的变量spi_imx,并且使用spi_imx_setupxfer函数来设置spi_imx的tx和rx函数。根据要发送的数据数据位宽的不同,分别有8位、16位和32位的发送函数,如下所示:

spi_imx_buf_tx_u8

spi_imx_buf_tx_u16

spi_imx_buf_tx_u32

同理,也有8位、16位和32位的数据接收函数,如下所示:

spi_imx_buf_rx_u8

spi_imx_buf_rx_u16

spi_imx_buf_rx_u32

我们就以spi_imx_buf_tx_u8这个函数为例,看看,一个自己的数据发送是怎么完成的,在spi-imx.c文件中找到如下所示内容:

示例代码62.2.3 spi_imx_buf_tx_u8函数

152 #define MXC_SPI_BUF_TX(type)

153staticvoid spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx)

154{

155 type val =0;

156

157if(spi_imx->tx_buf){

158 val =*(type *)spi_imx->tx_buf;

159 spi_imx->tx_buf +=sizeof(type);

160}

161

162 spi_imx->count -=sizeof(type);

163

164 writel(val, spi_imx->base + MXC_CSPITXDATA);

165}

166

167 MXC_SPI_BUF_RX(u8)

168 MXC_SPI_BUF_TX(u8)

从示例代码62.2.3可以看出,spi_imx_buf_tx_u8函数是通过MXC_SPI_BUF_TX宏来实现的。第164行就是将要发送的数据值写入到ECSPI的TXDATA寄存器里面去,这和我们SPI裸机实验的方法一样。将第168行的MXC_SPI_BUF_TX(u8)展开就是spi_imx_buf_tx_u8函数。其他的tx和rx函数都是这样实现的,这里就不做介绍了。关于I.MX6U的主机驱动程序就讲解到这里,基本套路和I2C的适配器驱动程序类似。

62.3 SPI设备驱动编写流程

62.3.1 SPI设备信息描述

1、IO的pinctrl子节点创建与修改

首先肯定是根据所使用的IO来创建或修改pinctrl子节点,这个没什么好说的,唯独要注意的就是检查相应的IO有没有被其他的设备所使用,如果有的话需要将其删除掉!

2、SPI设备节点的创建与修改

采用设备树的情况下,SPI设备信息描述就通过创建相应的设备子节点来完成,我们可以打开imx6qdl-sabresd.dtsi这个设备树头文件,在此文件里面找到如下所示内容:

示例代码62.3.1.1 m25p80设备节点

308&ecspi1 {

309 fsl,spi-num-chipselects =<1>;

310 cs-gpios =gpio4 90>;

311 pinctrl-names ="default";

312 pinctrl-0=pinctrl_ecspi1>;

313 status ="okay";

314

315 flash: m25p80@0 {

316 #address-cells =<1>;

317 #size-cells =<1>;

318 compatible ="st,m25p32";

319 spi-max-frequency =<20000000>;

320 reg =<0>;

321};

322};

示例代码62.3.1.1是I.MX6Q的一款板子上的一个SPI设备节点,在这个板子的ECSPI接口上接了一个m25p80,这是一个SPI接口的设备。

第309行,设置"fsl,spi-num-chipselects"属性为1,表示只有一个设备。

第310行,设置"cs-gpios"属性,也就是片选信号为GPIO4_IO09。

第311行,设置"pinctrl-names"属性,也就是SPI设备所使用的IO名字。

第312行,设置"pinctrl-0"属性,也就是所使用的IO对应的pinctrl节点。

第313行,将ecspi1节点的"status"属性改为"okay"。

第315~320行,ecspi1下的m25p80设备信息,每一个SPI设备都采用一个子节点来描述其设备信息。第315行的"m25p80@0"后面的"0"表示m25p80的接到了ECSPI的通道0上。这个要根据自己的具体硬件来设置。

第318行,SPI设备的compatible属性值,用于匹配设备驱动。

第319行,"spi-max-frequency"属性设置SPI控制器的最高频率,这个要根据所使用的SPI设备来设置,比如在这里将SPI控制器最高频率设置为20MHz。

第320行,reg属性设置m25p80这个设备所使用的ECSPI通道,和"m25p80@0"后面的"0"一样。

我们一会在编写ICM20608的设备树节点信息的时候就参考示例代码62.3.1.1中的内容即可。

62.3.2 SPI设备数据收发处理流程

SPI设备驱动的核心是spi_driver,这个我们已经在62.1.2小节讲过了。当我们向Linux内核注册成功spi_driver以后就可以使用SPI核心层提供的API函数来对设备进行读写操作了。首先是spi_transfer结构体,此结构体用于描述SPI传输信息,结构体内容如下:

示例代码62.3.2.1 spi_transfer结构体

603struct spi_transfer {

604/* it's ok if tx_buf == rx_buf (right?)

605 * for MicroWire, one buffer must be null

606 * buffers must work with dma_*map_single() calls, unless

607 * spi_message.is_dma_mapped reports a pre-existing mapping

608 */

609constvoid*tx_buf;

610void*rx_buf;

611unsigned len;

612

613 dma_addr_t tx_dma;

614 dma_addr_t rx_dma;

615struct sg_table tx_sg;

616struct sg_table rx_sg;

617

618unsigned cs_change:1;

619unsigned tx_nbits:3;

620unsigned rx_nbits:3;

621 #define SPI_NBITS_SINGLE 0x01/* 1bit transfer */

622 #define SPI_NBITS_DUAL 0x02/* 2bits transfer */

623 #define SPI_NBITS_QUAD 0x04/* 4bits transfer */

624 u8 bits_per_word;

625 u16 delay_usecs;

626 u32 speed_hz;

627

628struct list_head transfer_list;

629};

第609行,tx_buf保存着要发送的数据。

第610行,rx_buf用于保存接收到的数据。

第611行,len是要进行传输的数据长度,SPI是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以spi_transfer中也就没有发送长度和接收长度之分。

spi_transfer需要组织成spi_message,spi_message也是一个结构体,内容如下:

示例代码62.3.2.2 spi_message结构体

660struct spi_message {

661struct list_head transfers;

662

663struct spi_device *spi;

664

665unsigned is_dma_mapped:1;

......

678/* completion is reported through a callback */

679void(*complete)(void*context);

680void*context;

681unsigned frame_length;

682unsigned actual_length;

683int status;

684

685 /* for optional use by whatever driver currently owns the

686 * spi_message ... between calls to spi_async and then later

687 * complete(), that's the spi_master controller driver.

688 */

689struct list_head queue;

690void*state;

691};

在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init,函数原型如下:

void spi_message_init(struct spi_message *m)

函数参数和返回值含义如下:

m:要初始化的spi_message。

返回值:无。

spi_message初始化完成以后需要将spi_transfer添加到spi_message队列中,这里我们要用到spi_message_add_tail函数,此函数原型如下:

voidspi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

函数参数和返回值含义如下:

t:要添加到队列中的spi_transfer。

m:spi_transfer要加入的spi_message。

返回值:无。

spi_message准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待SPI数据传输完成,同步传输函数为spi_sync,函数原型如下:

int spi_sync(struct spi_device *spi, struct spi_message *message)

函数参数和返回值含义如下:

spi:要进行数据传输的spi_device。

message:要传输的spi_message。

返回值:无。

异步传输不会阻塞的等到SPI数据传输完成,异步传输需要设置spi_message中的complete成员变量,complete是一个回调函数,当SPI异步传输完成以后此函数就会被调用。SPI异步传输函数为spi_async,函数原型如下:

int spi_async(struct spi_device *spi, struct spi_message *message)

函数参数和返回值含义如下:

spi:要进行数据传输的spi_device。

message:要传输的spi_message。

返回值:无。

linux i2c adapter 增加设备_「正点原子Linux连载」第六十二章Linux SPI驱动实验(一)...相关推荐

  1. 【正点原子STM32连载】第四十二章 FLASH模拟EEPROM实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  2. 【正点原子STM32连载】 第六十二章 UCOSII实验2-信号量和邮箱 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  3. qq自定义diy名片代码复制_「正点原子FPGA连载」第六章自定义IP核-呼吸灯实验

    1)摘自[正点原子]领航者 ZYNQ 之嵌入式开发指南 2)实验平台:正点原子领航者ZYNQ开发板 3)平台购买地址:https://item.taobao.com/item.htm?&id= ...

  4. 【正点原子Linux连载】第六十二章 Linux SPI驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  5. 【正点原子FPGA连载】第三十五章高速AD/DA实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  6. 【正点原子FPGA连载】第三十二章RTC实时时钟LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  7. 【正点原子STM32连载】第五十四章 手写识别实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  8. 【正点原子STM32连载】第三十九章 DS18B20数字温度传感器实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  9. 【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

    第四十五章 FLASH模拟EEPROM实验 STM32本身没有自带EEPROM,但是STM32具有IAP(在应用编程)功能,所以我们可以把它的FLASH当成EEPROM来使用.本章,我们将利用STM3 ...

最新文章

  1. coc跑团san数值规则_为什么B站上有些coc跑团7版规则san值四五十,但是掉的时候只是掉1D3,是不是应该是20上限?...
  2. python语言入门教程-菜鸟学Python入门教程大盘点|7个多月的心血总结
  3. 找字符串中第一个只出现一次的字符
  4. Storm的acker确认机制
  5. 【万众期待】左盟主688页QT教程震撼发布!88个例程,一大波酷炫UI+项目实战案例来袭,让您久等了!!!...
  6. 实现流水灯以间隔500ms的时间闪烁(系统定时器SysTick实现的精确延时)
  7. docker4dotnet #1 – 前世今生 amp; 世界你好
  8. 添加几个手机联系人_One UI 3.0: 更细腻、更好用,这才是手机系统该有的样子
  9. tomcat7 加载el表达式 报错 使用tomcat8得以解决
  10. MFC浅析 8 CArchive 原理
  11. ElementUI使用问题记录:设置路由+iconfont图标+自定义表单验证
  12. Informix常用操作方法命令
  13. vscode中安装webpack_leaflet-webpack 入门开发系列一初探篇(附源码下载)
  14. c语言 滑窗法_滑窗算法
  15. 阿里云ECS搭建在线IDE
  16. “自贸云”+“自贸大数据”将加速辽宁自贸区建设与创新
  17. 【比特熊故事汇】X Microsoft Build 2022——微软专家+MVP,技术亮点全解析
  18. C++中空类占一字节原因详解
  19. linux一体机如何调整亮度,一体机在哪里设置亮度|一体机电脑怎么调节屏幕亮度...
  20. 小试牛刀 python股票查询程序

热门文章

  1. Mysql事件的创建和使用
  2. Form.close与Application.Exit()的区别
  3. python中模块导入问题(已解决)
  4. PHP中表单没有问题但是$_FILES为空的解决办法
  5. 【报告分享】抖音品牌主页运营官方指导手册.pdf(附下载链接)
  6. 推荐系统之协同过滤算法分布式实现(附代码实现)
  7. pyhthon下中文报错问题
  8. 用摸鱼学来解释隐马尔可夫模型(HMM)
  9. vue滚动条禁止_vue.js中实现禁止浏览器滚动方法
  10. ROS入门-5.认识ROS及ROS的基本概念