完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》 关注官方微信号公众号,获取更多资料:正点原子 第三十七章Linux内核移植 前两章我们简单了解了一下Linux内核顶层Makefile和Linux内核的启动流程,本章我们就来学习一下如何将NXP官方提供的Linux内核移植到正点原子的I.MX6U-ALPHA开发板上。通过本章的学习,我们将掌握如何将半导体厂商提供的Linux BSP包移植到我们自己的平台上。 37.1 创建VSCode工程这里我们使用NXP官方提供的Linux源码,将其移植到正点原子I.MX6U-ALPHA开发板上。NXP官方原版Linux源码已经放到了开发板光盘中,路径为:1、例程源码->4、NXP官方原版Uboot和Linux->linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2。使用FileZilla将其发送到Ubuntu中并解压,得到名为linux-imx-rel_imx_4.1.15_2.1.0_ga的目录,为了和NXP官方的名字区分,可以使用“mv”命令对其重命名,我这里将其重命名为“linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek”,命令如下: mv linux-imx-rel_imx_4.1.15_2.1.0_ga linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek 完成以后创建VSCode工程,步骤和Windows下一样,重点是.vscode/settings.json这个文件。 37.2 NXP官方开发板Linux内核编译NXP提供的Linux源码肯定是可以在自己的I.MX6ULL EVK开发板上运行下去的,所以我们肯定是以I.MX6ULL EVK开发板为参考,然后将Linux内核移植到I.MX6U-ALPHA开发板上的。 37.2.1修改顶层Makefile修改顶层Makefile,直接在顶层Makefile文件里面定义ARCH和CROSS_COMPILE这两个的变量值为arm和arm-linux-gnueabihf-,结果如图37.2.1所示: 图37.2.1 修改顶层Makefile 图37.2.1中第252和253行分别设置了ARCH和CROSS_COMPILE这两个变量的值,这样在编译的时候就不用输入很长的命令了。 37.2.2 配置并编译Linux内核和uboot一样,在编译Linux内核之前要先配置Linux内核。每个板子都有其对应的默认配置文件,这些默认配置文件保存在arch/arm/configs目录中。imx_v7_defconfig和imx_v7_mfg_defconfig都可作为I.MX6ULL EVK开发板所使用的默认配置文件。但是这里建议使用imx_v7_mfg_defconfig这个默认配置文件,首先此配置文件默认支持I.MX6UL这款芯片,而且重要的一点就是此文件编译出来的zImage可以通过NXP官方提供的MfgTool工具烧写!!imx_v7_mfg_defconfig中的“mfg”的意思就是MfgTool。 进入到Ubuntu中的Linux源码根目录下,执行如下命令配置Linux内核: make clean //第一次编译Linux内核之前先清理一下 make imx_v7_mfg_defconfig //配置Linux内核 配置完成以后如图37.2.2.1所示: 图37.2.2.1 配置Linux内核 配置完成以后就可以编译了,使用如下命令编译Linux内核: make-j16 //编译Linux内核 等待编译完成,结果如图37.2.2.2所示: 图37.2.2.2 Linux编译完成 Linux内核编译完成以后会在arch/arm/boot目录下生成zImage镜像文件,如果使用设备树的话还会在arch/arm/boot/dts目录下开发板对应的.dtb(设备树)文件,比如imx6ull-14x14-evk.dtb就是NXP官方的I.MX6ULL EVK开发板对应的设备树文件。至此我们得到两个文件: ①、Linux内核镜像文件:zImage。 ②、NXP官方I.MX6ULL EVK开发板对应的设备树文件:imx6ull-14x14-evk.dtb。 37.2.3 Linux内核启动测试在上一小节我们已经得到了NXP官方I.MX6ULL EVK开发板对应的zImage和imx6ull-14x14-evk.dtb这两个文件。这两个文件能不能在正点原子的I.MX6U-ALPHA EMMC版开发板上启动呢?测试一下不就知道了,在测试之前确保uboot中的环境变量bootargs内容如下: console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw 将上一小节编译出来的zImage和imx6ull-14x14-evk.dtb复制到Ubuntu中的tftp目录下,因为我们要在uboot中使用tftp命令将其下载到开发板中,拷贝命令如下: cp arch/arm/boot/zImage /home/zuozhongkai/linux/tftpboot/ -f cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /home/zuozhongkai/linux/tftpboot/ -f 拷贝完成以后就可以测试了,启动开发板,进入uboot命令行模式,然后输入如下命令将zImage和imx6ull-14x14-evk.dtb下载到开发板中并启动: tftp 80800000 zImage tftp 83000000 imx6ull-14x14-evk.dtb bootz 80800000 – 83000000 结果图37.2.3.1所示: 图37.2.3.1 启动Linux内核 从图37.2.3.1可以看出,此时Linux内核已经启动了,如果EMMC中的根文件系统存在,我们就可以进入到Linux系统里面使用命令进行操作如图37.2.3.2所示: 图37.2.3.2 进入Linux根文件系统 37.2.4 根文件系统缺失错误Linux内核启动以后是需要根文件系统的,根文件系统存在哪里是由uboot的bootargs环境变量指定,bootargs会传递给Linux内核作为命令行参数。比如上一小节设置root=/dev/mmcblk1p2,也就是说根文件系统存储在/dev/mmcblk1p2中,也就是EMMC的分区2中。这是因为正点原子的EMMC版本开发板出厂的时候已经EMMC的分区2中烧写好了根文件系统,所以设置root=/dev/mmcblk1p2。如果我们不设置根文件系统路径,或者说根文件系统路径设置错误的话会出现什么问题?这个问题是很常见的,我们在实际的工作中开发一个产品,这个产品的第一版硬件出来以后我们是没有对应的根文件系统可用的,必须要自己做根文件系统。在构建出对应的根文件系统之前Linux内核是没有根文件系统可用的,此时Linux内核启动以后会出现什么问题呢?带着这个问题,我们将uboot中的bootargs环境变量改为“console=ttymxc0,115200”,也就是不填写root的内容了,命令如下: setenv bootargs 'console=ttymxc0,115200' 修改完成以后重新从网络启动,启动以后会有如图37.2.4.1所示错误: 图37.2.4.1 根文件系统缺失错误 在图37.2.4.1中最后会有下面这一行: Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) 也就是提示内核崩溃,因为VFS(虚拟文件系统)不能挂载根文件系统,因为根文件系统目录不存在。即使根文件系统目录存在,如果根文件系统目录里面是空的依旧会提示内核崩溃。这个就是根文件系统缺失导致的内核崩溃,但是内核是启动了的,只是根文件系统不存在而已。 37.3 在Linux中添加自己的开发板在37.2小节中我们通过编译NXP官方I.MX6ULL EVK开发板对应的Linux内核,发现其可以在正点原子的EMMC版本开发板启动,所以我们就参考I.MX6ULL EVK开发板的设置,在Linux内核中添加正点原子的I.MX6U-ALPHA开发板。 37.3.1 添加开发板默认配置文件将arch/arm/configs目录下的imx_v7_mfg_defconfig重新复制一份,命名为imx_alientek_emmc_defconfig,命令如下: cd arch/arm/configs cp imx_v7_mfg_defconfig imx_alientek_emmc_defconfig 以后imx_alientek_emmc_defconfig就是正点原子的EMMC版开发板默认配置文件了。完成以后如图37.3.1.1所示: 图37.3.1.1 新添加的默认配置文件 以后就可以使用如下命令来配置正点原子EMMC版开发板对应的Linux内核了: makeimx_alientek_emmc_defconfig 37.3.2 添加开发板对应的设备树文件添加适合正点原子EMMC版开发板的设备树文件,进入目录arch/arm/boot/dts中,复制一份imx6ull-14x14-evk.dts,然后将其重命名为imx6ull-alientek-emmc.dts,命令如下: cdarch/arm/boot/dts cp imx6ull-14x14-evk.dts imx6ull-alientek-emmc.dts .dts是设备树源码文件,编译Linux的时候会将其编译为.dtb文件。imx6ull-alientek-emmc.dts创建好以后我们还需要修改文件arch/arm/boot/dts/Makefile,找到“dtb-$(CONFIG_SOC_IMX6ULL)”配置项,在此配置项中加入“imx6ull-alientek-emmc.dtb”,如下所示: 示例代码37.3.2.1 arch/arm/boot/dts/Makefile代码段 400 dtb-$(CONFIG_SOC_IMX6ULL)+= 401 imx6ull-14x14-ddr3-arm2.dtb 402 imx6ull-14x14-ddr3-arm2-adc.dtb 403 imx6ull-14x14-ddr3-arm2-cs42888.dtb 404 imx6ull-14x14-ddr3-arm2-ecspi.dtb 405 imx6ull-14x14-ddr3-arm2-emmc.dtb 406 imx6ull-14x14-ddr3-arm2-epdc.dtb 407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb 408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb 409 imx6ull-14x14-ddr3-arm2-lcdif.dtb 410 imx6ull-14x14-ddr3-arm2-ldo.dtb 411 imx6ull-14x14-ddr3-arm2-qspi.dtb 412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb 413 imx6ull-14x14-ddr3-arm2-tsc.dtb 414 imx6ull-14x14-ddr3-arm2-uart2.dtb 415 imx6ull-14x14-ddr3-arm2-u***.dtb 416 imx6ull-14x14-ddr3-arm2-wm8958.dtb 417 imx6ull-14x14-evk.dtb 418 imx6ull-14x14-evk-btwifi.dtb 419 imx6ull-14x14-evk-emmc.dtb 420 imx6ull-14x14-evk-gpmi-weim.dtb 421 imx6ull-14x14-evk-u***-certi.dtb 422 imx6ull-alientek-emmc.dtb 423 imx6ull-9x9-evk.dtb 424 imx6ull-9x9-evk-btwifi.dtb 425 imx6ull-9x9-evk-ldo.dtb 第422行为“imx6ull-alientek-emmc.dtb”,这样编译Linux的时候就可以从imx6ull-alientek-emmc.dts编译出imx6ull-alientek-emmc.dtb文件了。 37.3.3 编译测试经过37.3.1和37.3.2两个小节,Linux内核里面已经添加了正点原子I.MX6UL-ALIPHA EMMC版开发板了,接下接编译测试一下,我们可以创建一个编译脚本,imx6ull_alientek_emmc.sh,脚本内容如下: 示例代码37.3.2.1 imx6ull_alientek_emmc.sh编译脚本 1 #!/bin/sh 2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean 3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-imx_alientek_emmc_defconfig 4 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig 5 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16 第2行,清理工程。 第3行,使用默认配置文件imx_alientek_emmc_defconfig来配置Linux内核。 第4行,打开Linux的图形配置界面,如果不需要每次都打开图形配置界面可以删除此行。 第5行,编译Linux。 执行shell脚本imx6ull_alientek_emmc.sh编译Linux内核,编译完成以后就会在目录arch/arm/boot下生成zImage镜像文件。在arch/arm/boot/dts目录下生成imx6ull-alientek-emmc.dtb文件。将这两个文件拷贝到tftp目录下,然后重启开发板,在uboot命令模式中使用tftp命令下载这两个文件并启动,命令如下: tftp 80800000 zImage tftp 83000000 imx6ull-alientek-emmc.dtb bootz 80800000 – 83000000 只要出现如图37.3.3.1所示内容就表示Linux内核启动成功: 图37.3.3.1 Linux内核启动 Linux内核启动成功,说明我们已经在NXP提供的Linux内核源码中添加了正点原子I.MX6UL-ALPHA开发板。 37.4 CPU主频和网络驱动修改37.4.1 CPU主频修改1、设置I.MX6U-ALPHA开发板工作在528MHz 确保EMMC中的根文件系统可用!然后重新启动开发板,进入终端(可以输入命令),如图37.4.1.1所示: 图37.4.1.1 进入命令行 进入图37.4.1所示的命令行以后输入如下命令查看cpu信息: cat /proc/cpuinfo 结果如图37.4.2所示: 图37.4.1.2 cpu信息 在图37.4.2.1中有BogoMIPS这一条,此时BogoMIIS为3.00,BogoMIPS是Linux系统中衡量处理器运行速度的一个“尺子”,处理器性能越强,主频越高,BogoMIPS值就越大。BogoMIPS只是粗略的计算CPU性能,并不十分准确。但是我们可以通过BogoMIPS值来大致的判断当前处理器的性能。在图37.4.1.2中并没有看到当前CPU的工作频率,那我们就转变另一种方法查看当前CPU的工作频率。进入到目录/sys/bus/cpu/devices/cpu0/cpufreq中,此目录下会有很多文件,如图37.4.1.3所示: 图37.4.1.3 cpufreq目录 此目录中记录了CPU频率等信息,这些文件的含义如下: cpuinfo_cur_freq:当前cpu工作频率,从CPU寄存器读取到的工作频率。 cpuinfo_max_freq:处理器所能运行的最高工作频率(单位: KHz)。 cpuinfo_min_freq:处理器所能运行的最低工作频率(单位: KHz)。 cpuinfo_transition_latency:处理器切换频率所需要的时间(单位:ns)。 scaling_available_frequencies:处理器支持的主频率列表(单位: KHz)。 scaling_available_governors:当前内核中支持的所有governor(调频)类型。 scaling_cur_freq:保存着cpufreq模块缓存的当前CPU频率,不会对CPU硬件寄存器进行检查。 scaling_driver:该文件保存当前CPU所使用的调频驱动。 scaling_governor:governor(调频)策略,Linux内核一共有5中调频策略, ①、Performance,最高性能,直接用最高频率,不考虑耗电。 ②、Interactive,一开始直接用最高频率,然后根据CPU负载慢慢降低。 ③、Powersave,省电模式,通常以最低频率运行,系统性能会受影响,一般不会用这个! ④、Userspace,可以在用户空间手动调节频率。 ⑤、Ondemand,定时检查负载,然后根据负载来调节频率。负载低的时候降低CPU频率,这样省电,负载高的时候提高CPU频率,增加性能。 scaling_max_freq:governor(调频)可以调节的最高频率。 cpuinfo_min_freq:governor(调频)可以调节的最低频率。 stats目录下给出了CPU各种运行频率的统计情况,比如CPU在各频率下的运行时间以及变频次数。 使用如下命令查看当前CPU频率: cat cpuinfo_cur_freq 结果如图37.4.1.4所示: 图37.4.1.4 当前CPU频率 从图37.4.1.4可以看出,当前CPU频率为198MHz,工作频率很低!其他的值如下: cpuinfo_cur_freq = 198000 cpuinfo_max_freq = 528000 cpuinfo_min_freq = 198000 scaling_cur_freq = 198000 scaling_max_freq = 528000 cat scaling_min_freq = 198000 scaling_available_frequencies = 198000 396000 528000 cat scaling_governor = ondemand 可以看出,当前CPU支持198MHz、396MHz和528Mhz三种频率切换,其中调频策略为ondemand,也就是定期检查负载,然后根据负载情况调节CPU频率。因为当前我们开发板并没有做什么工作,因此CPU频率降低为198MHz以省电。如果开发板做一些高负载的工作,比如播放视频、播放视频等操作那么CPU频率就会提升上去。查看stats目录下的time_in_state文件可以看到CPU在各频率下的工作时间,命令如下: cat /sys/bus/cpu/devices/cpu0/cpufreq/stats/time_in_state 结果如图37.4.1.5所示: 图37.4.1.5 CPU运行频率统计 从图37.4.1.5中可以看出,CPU在198MHz、396MHz和528MHz都工作过,其中198MHz的工作时间最长!假如我们想让CPU一直工作在528MHz那该怎么办?很简单,配置Linux内核,将调频策略选择为performance。或者修改imx_alientek_emmc_defconfig文件,此文件中有下面几行: 示例代码37.4.1.1 调频策略 41 CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y 42 CONFIG_CPU_FREQ_GOV_POWERSAVE=y 43 CONFIG_CPU_FREQ_GOV_USERSPACE=y 44 CONFIG_CPU_FREQ_GOV_INTERACTIVE=y 第41行,配置ondemand为默认调频策略。 第42行,使能powersave策略。 第43行,使能userspace策略。 第44行,使能interactive策略。 将示例代码37.4.1.1中的第41行屏蔽掉,然后在44行后面添加: CONFIG_CPU_FREQ_GOV_ONDEMAND=y 结果下所示: 示例代码37.4.1.2 修改调频策略 41 #CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y 42 CONFIG_CPU_FREQ_GOV_POWERSAVE=y 43 CONFIG_CPU_FREQ_GOV_USERSPACE=y 44 CONFIG_CPU_FREQ_GOV_INTERACTIVE=y 45 CONFIG_CPU_FREQ_GOV_ONDEMAND=y 修改完成以后重新编译Linux内核,编译之前先清理一下工程!因为我们重新修改过默认配置文件了,编译完成以后使用新的zImage镜像文件重新启动Linux。再次查看/sys/devices/system/cpu/cpu0/cpufreq/ cpuinfo_cur_freq文件的值,如图37.4.1.6所示: 图37.4.1.6 当前CPU频率 从图37.4.1.6可以看出,当前CPU频率为528MHz了。查看scaling_governor文件看一下当前的调频策略,如图37.4.1.7所示: 图37.4.1.7 调频策略 从图37.4.1.7可以看出当前的CPU调频策略为preformance,也就是高性能模式,一直以最高主频运行。 我们再来看一下如何通过图形化界面配置Linux内核的CPU调频策略,输入“makemenuconfig”打开Linux内核的图形化配置界面,如图37.4.1.8所示: 图37.4.1.8 Linux内核图形化配置界面 进入如下路径: CPU Power Management -> CPU Frequency scaling -> CPU Frequency scaling -> Default CPUFreq governor 打开默认调频策略选择界面,选择“performance”,如图37.1.4.9所示: 图37.1.4.9 默认调频策略选择 在图37.1.4.9中选择“performance”即可,选择以后退出图形化配置界面,然后编译Linux内核,一定不要清理工程!否则的话我们刚刚的设置就会被清理掉。编译完成以后使用新的zImage重启Linux,查看当前CPU的工作频率和调频策略。 我们学习的时候为了高性能,大家可以使用performance模式。但是在以后的实际产品开发中,从省电的角度考虑,建议大家使用ondemand模式,一来可以省电,二来可以减少发热。 2、超频至700MHz I.MX6ULL有多种型号,按照工作频率可以分为528MHz、700Mhz(实际696MHz),800MHz(实际792MHz)和900MHz(实际频率未知,应该在900MHz左右)。其中900MHz的不支持RGB屏幕,所以正点原子Linux团队没有选择,而700MHz的没有工业级(工作温度-40~85°C以上),因此也没有选择700MHz的型号。最后就剩下了528MHz和800MHz的,目前正点原子只提供了528MHz的版本(型号为MCIMX6Y2CVM08AB),至于800MHz版本(型号为MCIMX6Y2CVM08AB)的核心板后续就看客户的需求,如果有需求就会加上,但是800MHz版本的I.MX6ULL芯片会贵一些,成本会上涨。 虽然正点原子的I.MX6U-ALPHA开发板所选择的I.MX6ULL标称最高只能工作在528MHz,但是其是可以超频的700MHz的(这里的700MHz实际上只有696MHz,但是NXP官方宣传其为700MHz,所以我们就统一称为700MHz吧)。 声明: 对于想体验一下高性能的朋友体验一下超频,虽然笔者一直在用700MHz来测试,而且正点原子的I.MX6U-ALPHA开发板目前还没有出现过超频不稳定的现象发生,但是!毕竟是超频了的,肯定没有工作在528MHz稳定。 如果因为超频带来任何损坏,正点原子不负任何责任! 如果因为超频带来任何损坏,正点原子不负任何责任! 如果因为超频带来任何损坏,正点原子不负任何责任! 在实际的产品中,禁止任何超频!务必严格按照I.MX6ULL手册上给出的标准工作频率来运行!!如果想要更高的性能,请购买相应型号的处理器! 看到这里,如果您还是执意要超频,那么就接着往下看,如果要放弃超频,那就跳过本小节,看下一小节。 超频设置其实很简单,修改一下设备树文件imx6ull.dtsi即可,打开imx6ull.dtsi,找到下面代码: 示例代码37.4.1.3 imx6ull.dtsi文件代码段 54 cpu0: cpu@0 { 55 compatible ="arm,cortex-a7"; 56 device_type ="cpu"; 57 reg =<0>; 58 clock-latency =<61036>;/* two CLK32 periods */ 59 operating-points =< 60/* kHz uV */ 619960001275000 627920001225000 635280001175000 643960001025000 65198000950000 66>; 67 fsl,soc-operating-points =< 68 /* KHz uV */ 69 9960001175000 70 7920001175000 71 5280001175000 72 3960001175000 73 1980001175000 74 >; 示例代码37.4.1.3就是设置CPU频率的,第61~65行和第69~73行就是I.MX6ULL所支持的频率,单位为KHz,可以看出I.MX6ULL(视具体型号而定)支持996MHz、792MHz、528MHz、396MHz和198MHz。在上一小节中,我们知道Linux内核默认支持198MHz、396MHz和528MHz,并不能支持到792MHz,说明MCIMX6Y2CVM05AB这颗芯片不能跑到792MHz。我们在示例代码37.4.2.1中加入针对696MHz的支持,修改以后代码如下: 示例代码37.4.1.4 增加696MHz的支持 54 cpu0: cpu@0 { 55 compatible ="arm,cortex-a7"; 56 device_type ="cpu"; 57 reg =<0>; 58 clock-latency =<61036>;/* two CLK32 periods */ 59 operating-points =< 60/* kHz uV */ 619960001275000 627920001225000 636960001225000 645280001175000 653960001025000 66198000950000 67>; 68 fsl,soc-operating-points =< 69 /* KHz uV */ 70 9960001175000 71 7920001175000 72 6960001175000 73 5280001175000 74 3960001175000 75 1980001175000 76 >; 第63行,加入了“696000 1225000”,这个就是696MHz的支持。 第72行,加入了“696000 1175000”,也是对696MHz的支持。 修改好以后保存,并且编译设备树,在Linux内核源码根目录下输入如下命令编译设备树: makedtbs 命令“makedtbs”只编译设备树文件,也就是将.dts编译为.dtb,编译完成以后使用新的设备树文件imx6ull-alientek_emmc.dtb启动Linux。重启以后查看文件/sys/devices/system/cpu/cpu0/cpufreq/ scaling_available_frequencies的内容,如图37.4.1.10所示: 图37.4.1.10文件scaling_available_frequencies内容 从图37.4.1.11可以看出,此时支持了696MHz。如果设置调频策略为performance,那么处理器就会一直工作在696MHz。可以对比一下工作在528MHz和696MHz下的BogoMIPS的值,528MHz主频下的BogoMIPS值如图37.4.1.12所示: 图37.4.1.12528MHz主频下的BogoMIPS 696MHz主频下的BogoMIPS值如图37.4.1.13所示: 图37.4.1.13696MHz主频下的BogoMIPS 从图37.4.1.12和图37.2.1.13中可以看到,528MHz和696MHz下的BogoMIPS值分别为8.00和10.54,相当于性能提升了(10.54/8)-1=31.75%。 37.4.2使能8线EMMC驱动正点原子EMMC版本核心板上的EMMC采用的8位数据线,原理图如图37.4.2.1所示: 图37.4.2.1 EMMC原理图 Linux内核驱动里面EMMC默认是4线模式的,4线模式肯定没有8线模式的速度快,所以本节我们将EMMC的驱动修改为8线模式。修改方法很简单,直接修改设备树即可,打开文件imx6ull-alientek-emmc.dts,找到如下所示内容: 示例代码37.4.2.1 imx6ull-alientek-emmc.dts代码段 734&usdhc2 { 735 pinctrl-names ="default"; 736 pinctrl-0=<&pinctrl_usdhc2>; 737 non-removable; 738 status ="okay"; 739}; 关于设备树的原理以及内容我们后面会有专门的章节讲解,示例代码37.4.2.1中的代码含义我们现在不去纠结,只需要将其改为如下代码即可: 示例代码37.4.2.1 imx6ull-alientek-emmc.dts代码段 734&usdhc2 { 735 pinctrl-names ="default","state_100mhz","state_200mhz"; 736 pinctrl-0=<&pinctrl_usdhc2_8bit>; 737 pinctrl-1=<&pinctrl_usdhc2_8bit_100mhz>; 738 pinctrl-2=<&pinctrl_usdhc2_8bit_200mhz>; 739 bus-width =<8>; 740 non-removable; 741 status ="okay"; 742}; 修改完成以后保存一下imx6ull-alientek-emmc.dts,然后使用命令“makedtbs”重新编译一下设备树,编译完成以后使用新的设备树重启Linux系统即可。 37.4.3 修改网络驱动因为在后面学习Linux驱动开发的时候要用到网络调试驱动,所以必须要把网络驱动调试好。在讲解uboot移植的时候就已经说过了,正点原子开发板的网络和NXP官方的网络硬件上不同,网络PHY芯片由KSZ8081换为了LAN8720A,两个网络PHY芯片的复位IO也不同。所以Linux内核自带的网络驱动是驱动不起来I.MX6U-ALPHA开发板上的网络的,需要做修改。 1、修改LAN8720的复位引脚驱动 ENET1复位引脚ENET1_RST连接在I.M6ULL的SNVS_TAMPER7这个引脚上。ENET2的复位引脚ENET2_RST连接在I.MX6ULL的SNVS_TAMPER8上。打开设备树文件imx6ull-alientek-emmc.dts,找到如下代码: 示例代码37.4.3.1 imx6ull-alientek-emmc.dts代码段 584 pinctrl_spi4: spi4grp { 585 fsl,pins =< 586 MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10 0x70a1 587 MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 0x70a1 588 MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x70a1 589 MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x80000000 590>; 591}; 示例代码37.4.3.1中第588和589行就是初始化SNVS_TAMPER7和SNVS_TAMPER8这两个引脚的,不过看样子好像是作为了SPI4的IO,这不是我们想要的,所以将588和589这两行删除掉!删除掉以后继续在imx6ull-alientek-emmc.dts中找到如下所示代码: 示例代码37.4.3.2 imx6ull-alientek-emmc.dts代码段 125 spi4 { 126 compatible ="spi-gpio"; 127 pinctrl-names ="default"; 128 pinctrl-0=<&pinctrl_spi4>; 129 pinctrl-assert-gpios =<&gpio5 8 GPIO_ACTIVE_LOW>; ...... 133 cs-gpios =<&gpio5 70>; 第129行,设置GPIO5_IO08为SPI4的一个功能引脚(我也不清楚具体作为什么功能用),而GPIO5_IO08就是SNVS_TAMPER8的GPIO功能引脚。 第133行,设置GPIO5_IO07作为SPI4的片选引脚,而GPIO5_IO07就是SNVS_TAMPER7的GPIO功能引脚。 现在我们需要GPIO5_IO07和GPIO5_IO08分别作为ENET1和ENET2的复位引脚,而不是SPI4的什么功能引脚,因此将示例代码37.4.3.2中的第129行和第133行处的代码删除掉!!否则会干扰到网络复位引脚! 继续在imx6ull-alientek-emmc.dts中找到如下所示代码: 示例代码37.4.3.3 imx6ull-alientek-emmc.dts代码段 309 pinctrl_enet1: enet1grp { 310 fsl,pins =< 311 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0 312 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0 313 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0 314 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0 315 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0 316 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0 317 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0 318 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b031 319>; 320}; 321 322 pinctrl_enet2: enet2grp { 323 fsl,pins =< 324 MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0 325 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0 326 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0 327 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0 328 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0 329 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0 330 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0 331 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0 332 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0 333 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031 334>; 335}; 第309~320行,pinctrl_enet1是ENET1的IO初始化配置。 第322~335行,pinctrl_enet2是ENET2的IO初始化配置。 根据示例代码37.4.3.3,我们需要将ENET1的复位IO初始化配置添加到pinctrl_enet1中,将ENET2的复位IO初始化配置添加到pinctrl_enet2中,添加完成以后的代码如下所示: 示例代码37.4.3.4 imx6ull-alientek-emmc.dts代码段 309 pinctrl_enet1: enet1grp { 310 fsl,pins =< 311 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0 312 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0 ... 318 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b031 319 MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0/* ENET1 RESET */ 320>; 321}; 322 323 pinctrl_enet2: enet2grp { 324 fsl,pins =< 325 MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0 326 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0 ... 334 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031 335 MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0/* ENET2 RESET */ 336>; 337}; 第319行,ENET1复位引脚SNVS_TAMPER7的配置代码。 第335行,ENET2复位引脚SNVS_TAMPER8的配置代码。 修改完成以后记得保存一下imx6ull-alientek-emmc.dts,网络的复位引脚驱动就修改好了。 2、修改LAN8720A的PHY地址 在uboot移植章节中,我们说过ENET1的LAN8720A地址为0x0,ENET2的LAN8720A地址为0x1。在imx6ull-alientek-emmc.dts中找到如下代码: 示例代码37.4.3.5 imx6ull-alientek-emmc.dts代码段 171&fec1 { 172 pinctrl-names ="default"; 173 pinctrl-0=<&pinctrl_enet1>; 174 phy-mode ="rmii"; 175 phy-handle =<ðphy0>; 176 status ="okay"; 177}; 178 179&fec2 { 180 pinctrl-names ="default"; 181 pinctrl-0=<&pinctrl_enet2>; 182 phy-mode ="rmii"; 183 phy-handle =<ðphy1>; 184 status ="okay"; 185 186 mdio { 187 #address-cells =<1>; 188 #size-cells =<0>; 189 190 ethphy0: ethernet-phy@0 { 191 compatible ="ethernet-phy-ieee802.3-c22"; 192 reg =<2>; 193}; 194 195 ethphy1: ethernet-phy@1 { 196 compatible ="ethernet-phy-ieee802.3-c22"; 197 reg =<1>; 198}; 199}; 200}; 第171~177行,ENET1对应的设备树节点。 第179~200行,ENET2对应的设备树节点。但是第186~198行的mdio节点描述了ENET1和ENET2的PHY地址信息。将示例代码37.4.3.5改为如下内容: 示例代码37.4.3.6 imx6ull-alientek-emmc.dts代码段 171&fec1 { 172 pinctrl-names ="default"; 173 pinctrl-0=<&pinctrl_enet1>; 174 phy-mode ="rmii"; 175 phy-handle =<ðphy0>; 176 phy-reset-gpios =<&gpio5 7 GPIO_ACTIVE_LOW>; 177 phy-reset-duration =<26>; 178 status ="okay"; 179}; 180 181&fec2 { 182 pinctrl-names ="default"; 183 pinctrl-0=<&pinctrl_enet2>; 184 phy-mode ="rmii"; 185 phy-handle =<ðphy1>; 186 phy-reset-gpios =<&gpio5 8 GPIO_ACTIVE_LOW>; 187 phy-reset-duration =<26>; 188 status ="okay"; 189 190 mdio { 191 #address-cells =<1>; 192 #size-cells =<0>; 193 194 ethphy0: ethernet-phy@0 { 195 compatible ="ethernet-phy-ieee802.3-c22"; 196 smsc,disable-energy-detect; 197 reg =<0>; 198}; 199 200 ethphy1: ethernet-phy@1 { 201 compatible ="ethernet-phy-ieee802.3-c22"; 202 smsc,disable-energy-detect; 203 reg =<1>; 204}; 205}; 206}; 第176和177行,添加了ENET1网络复位引脚所使用的IO为GPIO5_IO07,低电平有效。复位低电平信号持续时间为26ms。 第186和187行,ENET2网络复位引脚所使用的IO为GPIO5_IO08,同样低电平有效,持续时间同样为26ms。 第196和202行,“smsc,disable-energy-detect”表明PHY芯片是SMSC公司的,这样Linux内核就会找到SMSC公司的PHY芯片驱动来驱动LAN8720A。 第194行,注意“ethernet-phy@”后面的数字是PHY的地址,ENET1的PHY地址为0,所以“@”后面是0(默认为2)。 第197行,reg的值也表示PHY地址,ENET1的PHY地址为0,所以reg=0。 第200行,ENET2的PHY地址为1,因此“@”后面为1。 第203行,因为ENET2的PHY地址为1,所以reg=1。 至此,LAN8720A的PHY地址就改好了,保存一下imx6ull-alientek-emmc.dts文件。然后使用“makedtbs”命令重新编译一下设备树。 3、修改fec_main.c文件 要在I.MX6ULL上使用LAN8720A,需要修改一下Linux内核源码,打开drivers/net/ethernet/freescale/fec_main.c,找到函数fec_probe,在fec_probe中加入如下代码: 示例代码37.4.3.7 imx6ull-alientek-emmc.dts代码段 3438staticint 3439 fec_probe(struct platform_device *pdev) 3440{ 3441struct fec_enet_private *fep; 3442struct fec_platform_data *pdata; 3443struct net_device *ndev; 3444int i, irq, ret =0; 3445struct resource *r; 3446conststruct of_device_id *of_id; 3447staticint dev_id; 3448struct device_node *np = pdev->dev.of_node,*phy_node; 3449int num_tx_qs; 3450int num_rx_qs; 3451 3452/* 设置MX6UL_PAD_ENET1_TX_CLK和MX6UL_PAD_ENET2_TX_CLK 3453 * 这两个IO的复用寄存器的SION位为1。 3454 */ 3455void __iomem *IMX6U_ENET1_TX_CLK; 3456void __iomem *IMX6U_ENET2_TX_CLK; 3457 3458 IMX6U_ENET1_TX_CLK = ioremap(0X020E00DC,4); 3459 writel(0X14, IMX6U_ENET1_TX_CLK); 3460 3461 IMX6U_ENET2_TX_CLK = ioremap(0X020E00FC,4); 3462 writel(0X14, IMX6U_ENET2_TX_CLK); 3463 ...... 3656return ret; 3657} 第3455~3462就是新加入的代码,如果要在I.MX6ULL上使用LAN8720A就需要设置ENET1和ENET2的TX_CLK引脚复位寄存器的SION位为1。 4、配置Linux内核,使能LAN8720驱动 输入命令“makemenuconfig”,打开图形化配置界面,选择使能LAN8720A的驱动,路径如下: -> Device Drivers -> Network device support -> PHY Device support and infrastructure -> Drivers for SMSC PHYs 如图37.4.3.1所示: 图37.4.3.1 使能LAN8720A驱动 图37.4.3.1中选择将“Drivers for SMSC PHYs”编译到Linux内核中,因此“<>”里面变为了“*”。LAN8720A是SMSC公司出品的,因此勾选这个以后就会编译LAN8720驱动,配置好以后退出配置界面,然后重新编译一下Linux内核。 5、修改smsc.c文件 在修改smsc.c文件之前先说点题外话,那就是我是怎么确定要修改smsc.c这个文件的。在写本书之前我并没有修改过smsc.c这个文件,都是使能LAN8720A驱动以后就直接使用。但是我在测试NFS挂载文件系统的时候发现文件系统挂载成功率很低!老是提示NFS服务器找不到,三四次就有一次挂载失败!很折磨人。NFS挂载就是通过网络来挂载文件系统,这样做的好处就是方便我们后续调试Linux驱动。既然老是挂载失败那么可以肯定的是网络驱动有问题,网络驱动分两部分:内部MAC+外部PHY,内部MAC驱动是由NXP提供的,一般不会出问题,否则的话用户早就给NXP反馈了。而且我用NXP官方的开发板测试网络是一直正常的,但是NXP官方的开发板所使用的PHY芯片为KSZ8081。所以只有可能是外部PHY,也就是LAN8720A的驱动可能出问题了。鉴于LAN8720A有“前车之鉴”,那就是在uboot中需要对LAN8720A进行一次软复位,要设置LAN8720A的BMCR(寄存器地址为0)寄存器bit15为1。所以我猜测,在Linux中也需要对LAN8720A进行一次软复位。 首先需要找到LAN8720A的驱动文件,LAN8720A的驱动文件是drivers/net/phy/smsc.c,在此文件中有个叫做smsc_phy_reset的函数,看名字都知道这是SMSC PHY的复位函数,因此,LAN8720A肯定也会使用到这个复位函数,此函数内容如下: 示例代码37.4.3.8 smsc_phy_reset函数 60staticint smsc_phy_reset(struct phy_device *phydev) 61{ 62 int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES); 63 if(rc <0) 64 return rc; 65 66 /* If the SMSC PHY is in power down mode, then set it 67 * in all capable mode before using it. 68 */ 69 if((rc & MII_LAN83C185_MODE_MASK)== MII_LAN83C185_MODE_POWERDOWN){ 70 int timeout =50000; 71 72 /* set "all capable" mode and reset the phy */ 73 rc |= MII_LAN83C185_MODE_ALL; 74 phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc); 75 phy_write(phydev, MII_BMCR, BMCR_RESET); 76 77 /* wait end of reset (max 500 ms) */ 78 do{ 79 udelay(10); 80 if(timeout--==0) 81 return-1; 82 rc = phy_read(phydev, MII_BMCR); 83 }while(rc & BMCR_RESET); 84 } 85 return0; 86} 第69行,只有PHY处于powerdown模式的时候第70~83行的代码段才会执行。 第75行,向LAN872A0的BMCR寄存器写入BMCR_RESET,也就是对LAN8720A进行软件初始化,所以smsc_phy_reset函数会对LAN8720A进行软件初始化。 看到没,只有LAN8720A处于powerdown模式的时候才会对LAN8720A进行软复位,但是我们在uboot中已经“唤醒”了LAN8720A,LAN8720A也已经工作了,因此它不可能处于powerdown模式,进而就没法对LAN8720A进行软复位。因此,我们要对smsc_phy_reset函数函数进行修改,将复位相关的代码从条件语句中提出来,不管LAN8720A有没有工作在powerdown模式下,只要调用smsc_phy_reset函数就对LAN8720A进行软复位,修改后的smsc_phy_reset函数内容如下: 示例代码37.4.3.9 修改后的smsc_phy_reset函数 60staticint smsc_phy_reset(struct phy_device *phydev) 61{ 62 int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES); 63 if(rc <0) 64 return rc; 65 66 /* If the SMSC PHY is in power down mode, then set it 67 * in all capable mode before using it. 68 */ 69 if((rc & MII_LAN83C185_MODE_MASK)== MII_LAN83C185_MODE_POWERDOWN){ 70 71 /* set "all capable" mode and reset the phy */ 72 rc |= MII_LAN83C185_MODE_ALL; 73 phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc); 74 } 75 76 phy_write(phydev, MII_BMCR, BMCR_RESET); 77 /* wait end of reset (max 500 ms) */ 78 int timeout =50000; 79 do{ 80 udelay(10); 81 if(timeout--==0) 82 return-1; 83 rc = phy_read(phydev, MII_BMCR); 84 }while(rc & BMCR_RESET); 85 86 return0; 87} 重点是76~84行,我们将软复位代码移出来,这样每次调用smsc_phy_reset函数LAN8720A都会被软复位。修改以后基本上每次通过NFS挂载根文件系统都会成功。 6、网络驱动测试 修改好设备树和Linux内核以后重新编译一下,得到新的zImage镜像文件和imx6ull-alientek-emmc.dtb设备树文件,使用网线将I.MX6U-ALPHA开发板的两个网口与路由器或者电脑连接起来,最后使用新的文件启动Linux内核。启动以后使用“ifconfig”命令查看一下当前活动的网卡有哪些,结果如图37.4.3.2所示: 图37.4.3.2 ifconfig命令结果 从图37.4.3.2可以看出,当前没有活动的网卡。输入命令“ifconfig-a”来查看一下开发板中存在的所有网卡,结果如图37.4.3.3所示: 图37.4.3.3 开发板所有网卡 图37.4.3.3中can0和can1为CAN接口的网卡,eth0和eth1才是网络接口的网卡,其中eth0对应于ENET1,eth1对应于ENET2。使用如下命令依次打开eth0和eth1这两个网卡: ifconfig eth0 up ifconfig eth1 up 网卡的打开过程如图37.4.3.4所示: 图37.4.3.4 两个网卡打开过程 从图37.4.3.4中可以看到“SMSC LAN8710/LAN8720”字样,说明当前的网络驱动使用的就是我们前面使能的SMSC驱动。 再次输入“ifconfig”命令来查看一下当前活动的网卡,结果如图37.4.3.5所示: 图37.4.3.5 当前活动的网卡。 可以看出,此时eth0和eth1两个网卡都已经打开,并且工作正常,但是这两个网卡都还没有IP地址,所以不能进行ping等操作。使用如下命令给两个网卡配置IP地址: ifconfig eth0 192.168.1.251 ifconfig eth1 192.168.1.252 上述命令配置eth0和eth1这两个网卡的IP地址分别为192.168.1.251和192.168.1.252,注意IP地址选择的合理性,一定要和自己的电脑处于同一个网段内,并且没有被其他的设备占用!设置好以后,使用“ping”命令来ping一下自己的主机,如果能ping通那说明网络驱动修改成功!比如我的Ubuntu主机IP地址为192.168.1.250,使用如下命令ping一下: ping 192.168.1.250 结果如图37.4.3.6所示: 图37.4.3.6 ping结果 可以看出,ping成功,说明网络驱动修改成功!我们在后面的构建根文件系统和Linux驱动开发中就可以使用网络调试代码啦,好嗨森! 37.4.4 保存修改后的图形化配置文件在修改网络驱动的时候我们通过图形界面使能了LAN8720A的驱动,使能以后会在.config中存在如下代码: CONFIG_SMSC_PHY=y 打开drivers/net/phy/Makefile,有如下代码: 示例代码37.4.4.1 drivers/net/phy/Makefile代码段 11 obj-$(CONFIG_SMSC_PHY)+= smsc.o 当CONFIG_SMSC_PHY=y的时候就会编译smsc.c这个文件,smsc.c就是LAN8720A的驱动文件。但是当我们执行“makeclean”清理工程以后.config文件就会被删除掉,因此我们所有的配置内容都会丢失,结果就是前功尽弃,一“删”回到解放前!所以我们在配置完图形界面以后经过测试没有问题,就必须要保存一下配置文件。保存配置的方法有两个。 1、直接另存为.config文件 既然图形化界面配置后的配置项保存在.config中,那么就简单粗暴,直接将.config文件另存为imx_alientek_emmc_defconfig,然后其复制到arch/arm/configs目录下,替换以前的imx_alientek_emmc_defconfig。这样以后执行“makeimx_alientek_emmc_defconfig”重新配置Linux内核的时候就会使用新的配置文件,默认就会使能LAN8720A的驱动。 2、通过图形界面保存配置文件 相比于第1种直接另存为.config文件,第2种方法就很“文雅”了,在图形界面中保存配置文件,在图形界面中会有“< Save >”选项,如图37.4.4.1所示: 图37.4.4.1 保存配置 通过键盘的“→”键,移动到“< Save >”选项,然后按下回车键,打开文件名输入对话框,如图37.4.4.2所示: 图37.4.4.2 输入文件名 在图37.4.4.2中输入要保存的文件名,可以带路径,一般是相对路径(相对于Linux内核源码根目录)。比如我们要将新的配置文件保存到目录arch/arm/configs下,文件名为imx_alientek_emmc_defconfig,也就是用新的配置文件替换掉老的默认配置文件。那么我们在图37.4.4.2中输入“arch/arm/configs/imx_alientek_emmc_defconfig”即可,如图37.4.4.3所示: 图37.4.4.3 输入文件名 设置好文件名以后选择下方的“< Ok >”按钮,保存文件并退出。退出以后再打开imx_alientek_emmc_defconfig文件,就会在此文件中找到“CONFIG_SMSC_PHY=y”这一行,如图37.4.4.4所示: 图37.4.4.4 新的配置文件 同样的,使用“makeimx_alientek_emmc_defconfig”重新配置Linux内核的时候,LAN8720A的驱动就会使能,并被编译进Linux镜像文件zImage中。 关于Linux内核的移植就讲解到这里,简单总结一下移植步骤: ①、在Linux内核中查找可以参考的板子,一般都是半导体厂商自己做的开发板。 ②、编译出参考板子对应的zImage和.dtb文件。 ③、使用参考板子的zImage文件和.dtb文件在我们所使用的板子上启动Linux内核,看能否启动。 ④、如果能启动的话就万事大吉,如果不能启动那就悲剧了,需要调试Linux内核。不过一般都会参考半导体官方的开发板设计自己的硬件,所以大部分情况下都会启动起来。启动Linux内核用到的外设不多,一般就DRAM(Uboot都初始化好的)和串口。作为终端使用的串口一般都会参考半导体厂商的Demo板。 ⑤、修改相应的驱动,像NAND Flash、EMMC、SD卡等驱动官方的Linux内核都是已经提供好了,基本不会出问题。重点是网络驱动,因为Linux驱动开发一般都要通过网络调试代码,所以一定要确保网络驱动工作正常。如果是处理器内部MAC+外部PHY这种网络方案的话,一般网络驱动都很好处理,因为在Linux内核中是有外部PHY通用驱动的。只要设置好复位引脚、PHY地址信息基本上都可以驱动起来。 ⑥、Linux内核启动以后需要根文件系统,如果没有根文件系统的话肯定会崩溃,所以确定Linux内核移植成功以后就要开始根文件系统的构建。 |
|
相关推荐
|
|
AI模型部署边缘设备的奇妙之旅:如何在边缘端部署OpenCV
1209 浏览 0 评论
tms320280021 adc采样波形,为什么adc采样频率上来波形就不好了?
1058 浏览 0 评论
1444 浏览 0 评论
1371 浏览 0 评论
1023 浏览 0 评论
74582 浏览 21 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-18 11:24 , Processed in 0.659666 second(s), Total 62, Slave 44 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号