




版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
1、linux內(nèi)核I2C總線驅動實現(xiàn)談到在linux系統(tǒng)下編寫I2C驅動,目前主要有兩種方式,一種是把I2C設備當作一個普通的字符設備來處理,另一種是利用linux I2C驅動體系結構來完成。下面比較下這兩種驅動。第一種方法的好處(對應第二種方法的劣勢)有: 思路比較直接,不需要花時間去了解linux內(nèi)核中復雜的I2C子系統(tǒng)的操作方法。第一種方法問題(對應第二種方法的好處)有:
2、; 要求工程師不僅要對I2C設備的操作熟悉,而且要熟悉I2C的適配器操作; 要求工程師對I2C的設備器及I2C的設備操作方法都比較熟悉,最重要的是寫出的程序可移植性差; 對內(nèi)核的資源無法直接使用。因為內(nèi)核提供的所有I2C設備器及設備驅動都是基于I2C子系統(tǒng)的
3、格式。I2C適配器的操作簡單還好,如果遇到復雜的I2C適配器(如:基于PCI的I2C適配器),工作量就會大很多。本文針對的對象是熟悉I2C協(xié)議,并且想使用linux內(nèi)核子系統(tǒng)的開發(fā)人員。網(wǎng)絡和一些書籍上有介紹I2C子系統(tǒng)的源碼結構。但發(fā)現(xiàn)很多開發(fā)人員看了這些文章后,還是不清楚自己究竟該做些什么。究其原因還是沒弄清楚I2C子系統(tǒng)為我們做了些什么,以及我們怎樣利用I2C子系統(tǒng)。本文首先要解決是如何利用現(xiàn)有內(nèi)核支持的I2C適配器,完成對I2C設備的操作,然后再過度到適配器代碼的編寫。本文主要從解決問題的角度去寫,不會涉及特別詳細的代碼跟蹤。I2C的通信肯定至少要有2個芯片完成,所以它的驅動是由2大部
4、分組成:主芯片的i2c的驅動、從芯片的i2c的驅動。 注:萬一選的都不支持怎么辦?(只能2個芯片的驅動都得實現(xiàn)了,不過過程差不多)1 分析linux內(nèi)核中I2C驅動框架1 .1主芯片的I2C的驅動首先要查看linux內(nèi)核是否支持主芯片中i2c驅動器,如果支持就配置一下就ok了,否則要編寫主控芯片的i2c驅動器。編寫方法:(1)要有i2c總線驅動(首先要查查內(nèi)核i2c文件是否支持這種總線驅動,一般都有支持,如果沒有只好自己寫了)(2)i2c設備驅動(主控芯片的地址等等信息)這個過程都是差不多的,以后在分析。一般的主控芯片的i2c控制器linux內(nèi)核基本上支持的很好,如:2410的i2
5、c驅動器的支持。1.2從芯片的I2C的驅動下面主要分析從芯片的I2C驅動(也有2種方式,第一個是利用內(nèi)核提供的i2c-dev.c來構建,另一個是自己寫)主要分析第一種方式:利用系統(tǒng)給我們提供的i2c-dev.c來實現(xiàn)一個i2c適配器的設備文件。然后通過在應用層操作i2c適配器來控制i2c設備。i2c-dev.c并沒有針對特定的設備而設計,只是提供了通用的read()、write()和ioctl()等接口,應用層可以借用這些接口訪問掛接在適配器上的i2c設備的存儲空間或寄存器,并控制I2C設備的工作方式。需要特別注意的是:i2c-dev.c的read()、write()方法都只適合于如下方式的數(shù)
6、據(jù)格式(可查看內(nèi)核相關源碼)圖1 單開始信號時序所以不具有太強的通用性,如下面這種情況就不適用(通常出現(xiàn)在讀目標時)。圖2 多開始信號時序但是read和write方法適用性有限,只適用用于適配器支持i2c算法的情況,如:static const struct i2c_algorithm s3c24xx_i2c_algorithm = .master_xfer = s3c24xx_i2c_xfer,
7、0; .functionality = s3c24xx_i2c_func, 而不適合適配器只支持smbus算法的情況,如: static const struct i2c_algorithm smbus_algorithm =
8、60; .smbus_xfer = i801_access, .functionality = i801_func, 基于以上原因,一般都不會使用i2c-dev.c的read()、write()方法。最常用的是ioctl()方法。ioctl()方法可以實現(xiàn)上面所有的情況(兩種數(shù)據(jù)格式、以及I2C算法和smbus算法)。
9、;針對i2c的算法,需要熟悉struct i2c_rdwr_ioctl_data 、struct i2c_msg。使用的命令是I2C_RDWR。 struct i2c_rdwr_ioctl_data struct i2c_msg _user *msgs; /* pointers to i2c_msgs */
10、 _u32 nmsgs; /* number of i2c_msgs */ ; struct i2c_msg _ _u16 addr; /* slave address */ &
11、#160; _ _u16 flags; /* 標志(讀、寫) */ _ _u16 len; /* msg length */ _ _u8 *buf; /* pointer to msg data */
12、; 針對smbus算法,需要熟悉struct i2c_smbus_ioctl_data。使用的命令是I2C_SMBUS。對于smbus算法,不需要考慮“多開始信號時序”問題。 struct i2c_smbus_ioctl_data _u8 read_write; /讀、寫
13、0; _u8 command; /命令 _u32 size; /數(shù)據(jù)長度標識 union i2c_smbus_data _user *data; /數(shù)據(jù)
14、; 下面以一個實例講解操作的具體過程。通過S3C2410操作AT24C02 e2prom。實現(xiàn)在AT24C02中任意位置的讀、寫功能。首先在內(nèi)核中已經(jīng)包含了對s3c2410 中的i2c控制器(總線驅動)驅動的支持。提供了i2c算法(非smbus類型的,所以后面的ioctl的命令是I2C_RDWR) static const struct i2c_algorithm s3c24xx_i2c_algorithm =
15、0; .master_xfer = s3c24xx_i2c_xfer, .functionality = s3c24xx_i2c_func, 另外一方面需要確定為了實現(xiàn)對AT24C02 e2prom的操作,需要確定從機芯片的地址及讀寫訪問時序。 &
16、#160; AT24C02地址的確定原理圖上將A2、A1、A0都接地了,所以地址是0x50。 AT24C02任意地址字節(jié)寫的時序可見此時序符合前面提到的“單開始信號時序” AT24C02任意地址字節(jié)讀的時序可見此時序符合前面提到的“多開始信號時序”下面開始具體代碼的分析(代碼在2.6.22內(nèi)核上測試通過):
17、0;/*i2c_test.c * hongtao_liu <lht> */ #include <stdio.h> #include <linux/types.h> &
18、#160; #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> &
19、#160; #include <sys/ioctl.h> #include <errno.h> #define I2C_RETRIES 0x0701 #define I2C_TIMEOUT 0x0702
20、; #define I2C_RDWR 0x0707 /*定義struct i2c_rdwr_ioctl_data和struct i2c_msg,要和內(nèi)核一致*/ #define I2C_M_TEN 0x0010 #define I2C_M_RD 0x0001 struct i2c_msg
21、60; unsigned short addr; unsigned short flags;
22、 unsigned short len; unsigned char *buf; struct i2c_rd
23、wr_ioctl_data struct i2c_msg *msgs; int nmsgs;
24、; /* nmsgs這個數(shù)量決定了有多少開始信號,對于“單開始時序”,取1*/ /*主程序*/ int main()
25、 int fd,ret; struct i2c_rdwr_ioctl_data e2prom_data;
26、 fd=open("/dev/i2c-0",O_RDWR); /* 為什么是i2c-0呢?那就要到內(nèi)核里看啦,等會再說。open底層調用了i2c_get_adapter(int id)函數(shù),這個函數(shù)很重要,他可以識別占用了哪個i2c總線使用地0個i2c控制器 /dev/i2c-0是在注冊i2c-dev.c后產(chǎn)生的,代表一個可操作的適配器。如果不使用i2c-dev.c(這里說啦上面的為什么) 的方式,就沒有,也不需要這個節(jié),i2c_driver結構體中有attach_adapter方法:
27、里面用device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d",adap->nr);I2C_MAJOR=89,即i2c-dev.c針對每個i2c適配器生成一個主設備號位89的設備文件,次設備要自己定義。 /dev/i2c-0是在注冊i2c-dev.c后產(chǎn)生的,代表一個可操作的適配器。如果不使用i2c-dev.c 的方式,就沒有,也不需要這個節(jié)點。
28、0; */ if(fd<0)
29、60; perror("open error"); e2prom_data.nm
30、sgs=2; /* *因為操作時序中,最多是用到2個開始信號(字節(jié)讀操作中),所以此將 *e2prom_data.nmsgs配置為2 */
31、 e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg); if(!e2prom_data.msgs) &
32、#160; perror("malloc error");
33、0; exit(1); ioctl(fd,I2C_TIMEOUT
34、,1);/*超時時間*/ ioctl(fd,I2C_RETRIES,2);/*重復次數(shù)*/ /*write data to e2prom*/*/
35、160; e2prom_data.nmsgs=1; (e2prom_data.msgs0).len=2; /1個 e2prom 寫入目標的地址和1個數(shù)據(jù)
36、60; (e2prom_data.msgs0).addr=0x50;/e2prom 設備地址 (e2prom_data.msgs0).flags=0; /write (e2prom_da
37、ta.msgs0).buf=(unsigned char*)malloc(2); (e2prom_data.msgs0).buf0=0x10;/ e2prom 寫入目標的地址 (e2prom_data.msgs0).bu
38、f1=0x58;/the data to write ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data); if(ret<0)
39、60; perror("ioctl error1");
40、 sleep(1); /*read data from e2prom*/ e2
41、prom_data.nmsgs=2; (e2prom_data.msgs0).len=1; /e2prom 目標數(shù)據(jù)的地址 (e2prom_data.msgs0).addr=0x50; / e2prom 設備地址
42、0; (e2prom_data.msgs0).flags=0;/write (e2prom_data.msgs0).buf0=0x10;/e2prom數(shù)據(jù)地址 &
43、#160; (e2prom_data.msgs1).len=1;/讀出的數(shù)據(jù) (e2prom_data.msgs1).addr=0x50;/ e2prom 設備地址
44、60; (e2prom_data.msgs1).flags=I2C_M_RD;/read (e2prom_data.msgs1).buf=(unsigned char*)malloc(1);/存放返回值的地址。
45、; (e2prom_data.msgs1).buf0=0;/初始化讀緩沖 ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data); if(ret<0)
46、0; perror("ioctl error2"); &
47、#160; printf("buff0=%x/n",(e2prom_data.msgs1).buf0);/*打印讀出的值,沒錯的話,就應該是前面寫的0x58了*/ close(
48、fd); i2c_put_adapter(client->adapter);/釋放i2c總線return 0; 以上講述了一種比較常用的利用i2c-dev.c操作i2c設備的方法,這種方法可以說是在應用層完成了對具體i2c設備的驅動工作。計劃下一篇總結以下幾點:(1)在內(nèi)核里寫i2c設備驅動的兩種方式: Probe方式(new style),如:
49、 static struct i2c_driver pca953x_driver = .driver =
50、160; .name = "pca953x",
51、0; , .probe = pca953x_probe,
52、0; .remove = pca953x_remove, .id_table = pca953x_id,
53、0; Adapter方式(LEGACY),如: static struct i2c_driver pcf8575_driver =
54、160; .driver = .owner = THIS_MODULE, &
55、#160; .name = "pcf8575",
56、60; , .attach_adapter = pcf8575_attach_adapter,
57、60; .detach_client = pcf8575_detach_client, (2)適配器驅動編寫方法(3)分享一些項目中遇到的問題 希望大家多提意見,多多交流。2 I2C驅動
58、編寫下面具體分析如何寫第一部分:2.1 主芯片的I2C驅動編寫主控芯片的i2c驅動分為2個步驟:寫總線驅動選了個主控芯片,比如:S3C8900(自己瞎選的芯片),在driver/i2c/busses/i2c-s3c2410.c中沒有找到這個芯片的I2C支持(總線驅動支持)。(倒霉了,沒有選好芯片,也可能是最新型號的,linux內(nèi)核沒跟上)。在此之前先分析i2c-s3c2410.c中完成的工作(總線驅動):l 設計對應于i2c_adapter_xxx_init()模板的s3c8900的模塊加載函數(shù)和對應于i2c_adapter_xxx_exit()函數(shù)模板的模塊卸載函數(shù)。l 設計對應于i2c_a
59、dapter_xxx_xfer()模板的 s3c8900適配器的通信方法函數(shù),針對 s3c24xx、64xx、s5pc1XX、s5p64xx處理器functionality()函數(shù)s3c24xx_i2c-func()只需簡單的返回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能話說沒找到總線驅動支持,(這倒霉孩子)那就得編寫個類似的i2c-s3c8900.c的總線驅動支持,嘿嘿,照著上面的功能寫吧,反正是總線驅動。Ø I2C適配器驅動加載與卸載 1. 初始化i2c適配器
60、所使用的硬件資源,如申請I/O地址、中斷號等;2. 通過i2c_add_adapter添加i2c_adapter數(shù)據(jù)結構,當然這個數(shù)據(jù)結構的成員已經(jīng)被xxx適配器的相應的函數(shù)指針所初始化;3. i2c總線卸載模塊與裝載相反,是否i2c適配器使用的硬件資源,通過i2c_del_adapter刪除i2c_adapter的數(shù)據(jù)結構。 模板如下: static
61、int _init i2c_adapter_xxx_init(void) xxx_adapter_hw_init();/初始化硬件資源
62、60; i2c_add_adapter(&xxx_adapter); static vo
63、id _init i2c_adapter_xxx_exit(void) xxx_adapter_hw_free();/釋放硬件資源
64、 i2c_del_adapter(&xxx_adapter);
65、 具體CPU具體分析,有的用platform做的,可以參考6410的做法2.1.2 I2C總線的通信方法我們需要為特定的i2c適配器實現(xiàn)其通信方法,主要實現(xiàn)i2c_algorithm的master_xfer()函數(shù)和functionality()函數(shù)。functionality函數(shù)很簡單,用于返回algorithm所支持的通信協(xié)議,如:I2C_FUCN-_I2C, I2C_FUNC_10BIT_ADDR,I2C_FUNC_SMBUS
66、_READ_BYTE,I2C_FUNC_SMBUS_write_byte等master_xfer函數(shù)在i2c適配器上完成傳遞給他的i2c_msg數(shù)組中的每個i2c消息。 模板如下: static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs
67、, int num) .
68、60; for(i = 0; i<num ; i+) i2c_adapter_xxx_start(); /產(chǎn)生開始位
69、60; /如果是讀消息 if(msgi->falgs &I2C_M_RD)
70、160; i2c_adapter_xxx_setaddr(msg->addr<<1)|1); /發(fā)送從設備讀地址 i2c_adapter_xxx_wait_ack();/獲取從設備的ack信息
71、160; i2c_adapter_xxx_readbytes(msgsi->buf,msgsi->len);/讀取msgi->len長的數(shù)據(jù)到msgi->buf里
72、60; else /是寫消息
73、i2c_adapter_xxx_setaddr(msg->addr<<1)|1); /發(fā)送從設備寫地址 i2c_adapter_xxx_wait_ack();/獲取從設備的ack信息
74、160; i2c_adapter_xxx_readbytes(msgsi->buf,msgsi->len);/讀取msgi->len長的數(shù)據(jù)到msgi->buf里
75、60;
76、160; i2c_adapter_xxx_stop(); /產(chǎn)生停止位 好啦,完成了裝載和卸載,又完成了通信方法這兩個重要的東東,那么總線驅動結構已經(jīng)完成啦,累死了! 2.2 寫設備驅動四部曲:Ø 構建i2c_driverØ 注冊i2c_driverØ 構建i2c_clien
77、t ( 第一種方法:注冊字符設備驅動、第二種方法:通過板文件的i2c_board_info填充,然后注冊)Ø 注銷i2c_driver具體如下:構建i2c_driverstatic struct i2c_driver pca953x_driver = .driver =
78、 .name= "pca953x", /名稱
79、; , .id= ID_PCA9555,/id號
80、160;.attach_adapter= pca953x_attach_adapter, /調用適配器連接設備 .detach_client= pca953x_detach_client,/讓設備脫離適配器 注冊i2c_driver static int _init pca
81、953x_init(void) return i2c_add_driver(&pca953x_driver);
82、60;module_init(pca953x_init); 執(zhí)行i2c_add_driver(&pca953x_driver)后,如果內(nèi)核中已經(jīng)注冊了i2c適配器,則順序調用這些適配器來連接我們的i2c設備。此過程是通過調用i2c_driver中的attach_adapter方法完成的。具體實現(xiàn)形式如下: static int pca953x_attach_adapter(struct i2c_adapter *adapter) &
83、#160; return i2c_probe(adapter, &addr_data, pca953x_detect); /*
84、160; adapter:適配器 addr_data:地址信息 pca953x_detect:探測到設備后調用的函數(shù)
85、; */ 地址信息addr_data是由下面代碼指定的。 /* Addresses to scan */ static unsig
86、ned short normal_i2c = 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,I2C_CLIENT_END; I2C_CLIENT_INSMOD; 注意:normal_i2c里的地址必須是你i2c芯片的地址。否則將無法正確探測到設備。而I2C_ CLIENT_INSMOD是一個宏,它會利用normal_i2c構建addr_data。構建i2c_client,并注冊字符設備驅動i2c_probe在探測到目標設備后,后調用pca953x_
87、detect,并把當時的探測地址address作為參數(shù)傳入。static int pca953x_detect(struct i2c_adapter *adapter, int address, int kind) struct i2c_client *
88、new_client; struct pca953x_chip *chip; /設備結構體 int err = 0,result;
89、; dev_t pca953x_dev=MKDEV(pca953x_major,0);/構建設備號,根據(jù)具體情況設定,這里我只考慮了normal_i2c中只有一個地址匹配的情況。主次設備號來源 if (!i2c_check_functionality(adapter, I2C_FU
90、NC_SMBUS_BYTE_DATA| I2C_FUNC_SMBUS_WORD_DATA)/判定適配器能力 goto exit; if (!(chip = kzalloc(sizeof(struct&
91、#160;pca953x_chip), GFP_KERNEL) err = -ENOMEM;
92、160; goto exit; /*構建i2c-client*/ &
93、#160; chip->client=kzalloc(sizeof(struct i2c_client),GFP_KERNEL); new_client = chip->client; &
94、#160; i2c_set_clientdata(new_client, chip); new_client->addr = address;
95、new_client->adapter = adapter; new_client->driver = &pca953x_driver; new_client->flags = 0;
96、 strlcpy(new_client->name, "pca953x", I2C_NAME_SIZE); if (err = i2c_attach_client(new_client)/注冊i2
97、c_client goto exit_kfree; if (err)
98、 goto exit_detach; if(pca953x_major)
99、60; result=register_chrdev_region(pca953x_dev,1,"pca953x");
100、0; else result=alloc_chrdev_region(&pca953x_dev,0,1,"pca953x");
101、60; pca953x_major=MAJOR(pca953x_dev);
102、 if (result < 0) printk(KERN_NOTICE "Unable to get pca953x region, error %d/n", result);
103、60; return result;
104、160; pca953x_setup_cdev(chip,0); /注冊字符設備,此處不詳解 return 0; e
105、xit_detach: i2c_detach_client(new_client); exit_kfree: kfree(ch
106、ip); exit: return err; i2c_check_functionality用來判定設配器的能力,這一點非常重要。你也可以直接查看對應設配器的能力,如static const
107、struct i2c_algorithm smbus_algorithm = .smbus_xfer= i801_access, .functionality= i801_func, &
108、#160; static u32 i801_func(struct i2c_adapter *adapter)
109、160; return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA
110、 | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK|(isich4 ? I2C_FUNC_SMBUS_H
111、WPEC_CALC : 0); 字符驅動的具體實現(xiàn)struct file_operations pca953x_fops = .owner = THIS_MODULE,
112、160; .ioctl= pca953x_ioctl, .open= pca953x_open, .release =pca953x_release,
113、60; 字符設備驅動本身沒有什么好說的,這里主要想說一下,如何在驅動中調用i2c設配器幫我們完成數(shù)據(jù)傳輸。目前設配器主要支持兩種傳輸方法:smbus_xfer和master_xfer。一般來說,如果設配器支持了master_xfer那么它也可以模擬支持smbus的傳輸。但如果只實現(xiàn)smbus_xfer,則不支持一些i2c的傳輸。int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);int (*smbus_xfer) (struct
114、i2c_adapter *adap, u16 addr,unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data * data);master_xfer中的參數(shù)設置,和前面的用戶空間編程一致。現(xiàn)在只是要在驅動中構建相關的參數(shù)然后調用i2c_transfer來完成傳輸既可。int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)smbus_xfer中的參數(shù)設置及調用方法如下:static
115、160;int pca953x_write_reg(struct pca953x_chip *chip, int reg, uint16_t val) int ret;
116、; ret = i2c_smbus_write_word_data(chip->client, reg << 1, val); if (ret < 0)
117、0; dev_err(&chip->client->dev, "failed writing register/n");
118、; return -EIO;
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 廚房設備售后服務計劃及承諾
- 初中語法入門:go與going的區(qū)別及其用法
- 我的校園時光作文(9篇)
- 外語課堂交互模式的歷史發(fā)展與演變
- 網(wǎng)絡服務協(xié)議的具體內(nèi)容與條款規(guī)定
- 小學生如何提升學科思維能力
- 《時態(tài)的結構與運用:九年級英語語法》
- 秋日鄉(xiāng)村景色:寫景作文(14篇)
- 海底兩萬里中的幻想與現(xiàn)實交融:八年級語文小說賞析教案
- 能源工程新能源技術發(fā)展趨勢測試卷
- 物流公司合同范例范例
- 江蘇省揚州市2024年化學中考試題【附答案】
- 《無人機測繪技術》項目2任務3無人機航測正射影像
- 課后服務家長滿意度調查表
- DB43-T 1577-2024基于鎘含量的稻谷分級收儲技術規(guī)程
- (完整版)西泠印社出版社三年級下冊《書法練習指導》完整教案
- 信號完整性分析之1314
- DB11T 1855-2021 固定資產(chǎn)投資項目節(jié)能審查驗收技術規(guī)范
- 第1節(jié) 功、熱和內(nèi)能的改變 教學課件
- 古詩文聯(lián)讀 專項訓練-2025年中考語文復習突破(江蘇專用)(解析版)
- 課件:《中華民族共同體概論》第十五講:新時代與中華民族共同體建設
評論
0/150
提交評論