linux i2c adapter 增加设备_「正点原子Linux连载」第六十二章Linux SPI驱动实验(一)...
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驱动实验(一)...相关推荐
- 【正点原子STM32连载】第四十二章 FLASH模拟EEPROM实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...
- 【正点原子STM32连载】 第六十二章 UCOSII实验2-信号量和邮箱 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...
- qq自定义diy名片代码复制_「正点原子FPGA连载」第六章自定义IP核-呼吸灯实验
1)摘自[正点原子]领航者 ZYNQ 之嵌入式开发指南 2)实验平台:正点原子领航者ZYNQ开发板 3)平台购买地址:https://item.taobao.com/item.htm?&id= ...
- 【正点原子Linux连载】第六十二章 Linux SPI驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...
- 【正点原子FPGA连载】第三十五章高速AD/DA实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1
1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...
- 【正点原子FPGA连载】第三十二章RTC实时时钟LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1
1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...
- 【正点原子STM32连载】第五十四章 手写识别实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...
- 【正点原子STM32连载】第三十九章 DS18B20数字温度传感器实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...
- 【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2
第四十五章 FLASH模拟EEPROM实验 STM32本身没有自带EEPROM,但是STM32具有IAP(在应用编程)功能,所以我们可以把它的FLASH当成EEPROM来使用.本章,我们将利用STM3 ...
最新文章
- coc跑团san数值规则_为什么B站上有些coc跑团7版规则san值四五十,但是掉的时候只是掉1D3,是不是应该是20上限?...
- python语言入门教程-菜鸟学Python入门教程大盘点|7个多月的心血总结
- 找字符串中第一个只出现一次的字符
- Storm的acker确认机制
- 【万众期待】左盟主688页QT教程震撼发布!88个例程,一大波酷炫UI+项目实战案例来袭,让您久等了!!!...
- 实现流水灯以间隔500ms的时间闪烁(系统定时器SysTick实现的精确延时)
- docker4dotnet #1 – 前世今生 amp; 世界你好
- 添加几个手机联系人_One UI 3.0: 更细腻、更好用,这才是手机系统该有的样子
- tomcat7 加载el表达式 报错 使用tomcat8得以解决
- MFC浅析 8 CArchive 原理
- ElementUI使用问题记录:设置路由+iconfont图标+自定义表单验证
- Informix常用操作方法命令
- vscode中安装webpack_leaflet-webpack 入门开发系列一初探篇(附源码下载)
- c语言 滑窗法_滑窗算法
- 阿里云ECS搭建在线IDE
- “自贸云”+“自贸大数据”将加速辽宁自贸区建设与创新
- 【比特熊故事汇】X Microsoft Build 2022——微软专家+MVP,技术亮点全解析
- C++中空类占一字节原因详解
- linux一体机如何调整亮度,一体机在哪里设置亮度|一体机电脑怎么调节屏幕亮度...
- 小试牛刀 python股票查询程序
热门文章
- Mysql事件的创建和使用
- Form.close与Application.Exit()的区别
- python中模块导入问题(已解决)
- PHP中表单没有问题但是$_FILES为空的解决办法
- 【报告分享】抖音品牌主页运营官方指导手册.pdf(附下载链接)
- 推荐系统之协同过滤算法分布式实现(附代码实现)
- pyhthon下中文报错问题
- 用摸鱼学来解释隐马尔可夫模型(HMM)
- vue滚动条禁止_vue.js中实现禁止浏览器滚动方法
- ROS入门-5.认识ROS及ROS的基本概念