• 在 imx6q 上适配 YT8531S


    一、环境介绍

    我适配的设备使用的是 RJ45 网口,phy 和 soc 之间的接口为 RGMII;

    linux 版本:4.1.15

    soc:imx6q 

    二、硬件检查

    1. 检查电源;

    VDDL = DVDDL = AVDDL = 1.1V;由 32 脚 LX 输出;
    AVDD33 检查是否为 3.3V 供电;
    DVDD_RGMII 电压是否符合下表,由 36、37 脚来选择电压;

    2. 检查时钟;

    检查晶振能否正常输出 25Mhz 的信号;

    3. 检查接线;

    MDI、RGMII、MDIO、MDC 等是否正确连接;

    4. 检查 phy 地址;

    28、17 脚没有外部上拉的情况下,默认使用的是内部下拉,即值为 0;

     三、软件调试

    1. phy id

    phy 匹配过程大致如下,可以在 get_phy_device 函数里面判断是否正确读取 phy id;

    1. fec_probe
    2. fec_enet_mii_init
    3. of_mdiobus_register Loop over the child nodes and register a phy_device for each one
    4. of_mdiobus_register_phy
    5. get_phy_device ---get_phy_id(phy_device_create匹配phy_driver_register) ---- phy_device_create
    6. phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);

    可以在 u-boot 处,进入命令行模式,用 mii info 命令来查看是否正确获取 phy id,如果没有获取,多半是 phy 硬件有问题;

    正常情况下,在 u-boot 时候,可以获取两个 phy id,一个广播地址 00,一个 phy 地址;
    对于 YT8531S 来说,操作广播地址与操作 phy 地址没差别;

    硬件复位后,需要延时 100 ms,才能进行 MDIO 读写;

     在 drivers/net/ethernet/freescale/fec_main.c 的 fec_reset_phy 函数尾部加一个 100ms 延时;

    1. static void fec_reset_phy(struct platform_device *pdev)
    2. {
    3. int err, phy_reset;
    4. int msec = 1;
    5. struct device_node *np = pdev->dev.of_node;
    6. if (!np)
    7. return;
    8. err = of_property_read_u32(np, "phy-reset-duration", &msec);
    9. /* A sane reset duration should not be longer than 1s */
    10. if (!err && msec > 1000)
    11. msec = 1;
    12. phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0);
    13. if (!gpio_is_valid(phy_reset))
    14. return;
    15. err = devm_gpio_request_one(&pdev->dev, phy_reset,
    16. GPIOF_OUT_INIT_LOW, "phy-reset");
    17. if (err) {
    18. dev_err(&pdev->dev, "failed to get phy-reset-gpios: %d\n", err);
    19. return;
    20. }
    21. msleep(msec);
    22. gpio_set_value(phy_reset, 1);
    23. msleep(100);
    24. }

    2. ENET_TX_CLK

    对于 imx6q,使用 RGMII 的时候,还需要一个 125M 的时钟;
    将 phy 的外部寄存器 0xA012 改为 0x50 即可;

    3. dts 和 driver

    fec 节点如下:

    1. &fec {
    2. pinctrl-names = "default";
    3. pinctrl-0 = <&pinctrl_enet>;
    4. phy-mode = "rgmii";
    5. phy-reset-gpios = <&gpio1 25 GPIO_ACTIVE_LOW>;
    6. phy-reset-duration = <2>;
    7. fsl,magic-packet;
    8. status = "okay";
    9. };
    10. pinctrl_enet: enetgrp {
    11. fsl,pins = <
    12. MX6QDL_PAD_ENET_MDIO__ENET_MDIO 0x1b0b0
    13. MX6QDL_PAD_ENET_MDC__ENET_MDC 0x1b0b0
    14. MX6QDL_PAD_RGMII_TXC__RGMII_TXC 0x1b0b0
    15. MX6QDL_PAD_RGMII_TD0__RGMII_TD0 0x1b0b0
    16. MX6QDL_PAD_RGMII_TD1__RGMII_TD1 0x1b0b0
    17. MX6QDL_PAD_RGMII_TD2__RGMII_TD2 0x1b0b0
    18. MX6QDL_PAD_RGMII_TD3__RGMII_TD3 0x1b0b0
    19. MX6QDL_PAD_RGMII_TX_CTL__RGMII_TX_CTL 0x1b0b0
    20. MX6QDL_PAD_ENET_REF_CLK__ENET_TX_CLK 0x1b0b0
    21. MX6QDL_PAD_RGMII_RXC__RGMII_RXC 0x1b0b0
    22. MX6QDL_PAD_RGMII_RD0__RGMII_RD0 0x1b0b0
    23. MX6QDL_PAD_RGMII_RD1__RGMII_RD1 0x1b0b0
    24. MX6QDL_PAD_RGMII_RD2__RGMII_RD2 0x1b0b0
    25. MX6QDL_PAD_RGMII_RD3__RGMII_RD3 0x1b0b0
    26. MX6QDL_PAD_RGMII_RX_CTL__RGMII_RX_CTL 0x1b0b0
    27. /* Phy reset */
    28. MX6QDL_PAD_ENET_CRS_DV__GPIO1_IO25 0x000b0
    29. >;

     motorcomm.c 修改如下:

    1. /*
    2. * drivers/net/phy/motorcomm.c
    3. *
    4. * Driver for Motorcomm PHYs
    5. *
    6. * Author: Leilei Zhao
    7. *
    8. * Copyright (c) 2019 Motorcomm, Inc.
    9. *
    10. * This program is free software; you can redistribute it and/or modify it
    11. * under the terms of the GNU General Public License as published by the
    12. * Free Software Foundation; either version 2 of the License, or (at your
    13. * option) any later version.
    14. *
    15. * Support : Motorcomm Phys:
    16. * Giga phys: yt8511, yt8521
    17. * 100/10 Phys : yt8512, yt8512b, yt8510
    18. * Automotive 100Mb Phys : yt8010
    19. * Automotive 100/10 hyper range Phys: yt8510
    20. */
    21. #include
    22. #include
    23. #include
    24. #include
    25. #include
    26. #include
    27. #ifndef LINUX_VERSION_CODE
    28. #include
    29. #else
    30. #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
    31. #endif
    32. /*for wol, 20210604*/
    33. #include
    34. #include "yt8614-phy.h"
    35. /**** configuration section begin ***********/
    36. /* if system depends on ethernet packet to restore from sleep, please define this macro to 1
    37. * otherwise, define it to 0.
    38. */
    39. #define SYS_WAKEUP_BASED_ON_ETH_PKT 0
    40. /* to enable system WOL of phy, please define this macro to 1
    41. * otherwise, define it to 0.
    42. * NOTE: this macro will need macro SYS_WAKEUP_BASED_ON_ETH_PKT to set to 1
    43. */
    44. #define YTPHY_ENABLE_WOL 0
    45. /* some GMAC need clock input from PHY, for eg., 125M, please enable this macro
    46. * by degault, it is set to 0
    47. */
    48. #define GMAC_CLOCK_INPUT_NEEDED 0
    49. #define YT8521_PHY_MODE_FIBER 1 //fiber mode only
    50. #define YT8521_PHY_MODE_UTP 2 //utp mode only
    51. #define YT8521_PHY_MODE_POLL 3 //fiber and utp, poll mode
    52. /* please make choice according to system design
    53. * for Fiber only system, please define YT8521_PHY_MODE_CURR 1
    54. * for UTP only system, please define YT8521_PHY_MODE_CURR 2
    55. * for combo system, please define YT8521_PHY_MODE_CURR 3
    56. */
    57. #define YT8521_PHY_MODE_CURR 2
    58. /**** configuration section end ***********/
    59. /* no need to change below */
    60. #if (YTPHY_ENABLE_WOL)
    61. #undef SYS_WAKEUP_BASED_ON_ETH_PKT
    62. #define SYS_WAKEUP_BASED_ON_ETH_PKT 1
    63. #endif
    64. /* workaround for 8521 fiber 100m mode */
    65. static int link_mode_8521 = 0; //0: no link; 1: utp; 32: fiber. traced that 1000m fiber uses 32.
    66. static int link_mode_8614[4] = {0}; //0: no link; 1: utp; 32: fiber. traced that 1000m fiber uses 32.
    67. /* for multiple port phy, base phy address */
    68. static unsigned int yt_mport_base_phy_addr = 0xff; //0xff: invalid;
    69. static unsigned int yt_mport_base_phy_addr_8614 = 0xff; //0xff: invalid;
    70. static int ytphy_read_ext(struct phy_device *phydev, u32 regnum)
    71. {
    72. int ret;
    73. int val;
    74. ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
    75. if (ret < 0)
    76. return ret;
    77. val = phy_read(phydev, REG_DEBUG_DATA);
    78. return val;
    79. }
    80. static int ytphy_write_ext(struct phy_device *phydev, u32 regnum, u16 val)
    81. {
    82. int ret;
    83. ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
    84. if (ret < 0)
    85. return ret;
    86. ret = phy_write(phydev, REG_DEBUG_DATA, val);
    87. return ret;
    88. }
    89. int yt8521_soft_reset(struct phy_device *phydev)
    90. {
    91. int ret;
    92. int val;
    93. ytphy_write_ext(phydev, 0xa000, 0);
    94. ret = genphy_soft_reset(phydev);
    95. if (ret < 0)
    96. return ret;
    97. ytphy_write_ext(phydev, 0xa000, 2);
    98. ret = genphy_soft_reset(phydev);
    99. if (ret < 0) {
    100. ytphy_write_ext(phydev, 0xa000, 0);
    101. return ret;
    102. }
    103. ret = ytphy_write_ext(phydev, 0xA012, 0x50);
    104. if (ret < 0)
    105. return ret;
    106. return 0;
    107. }
    108. #if (YTPHY_ENABLE_WOL)
    109. static int ytphy_switch_reg_space(struct phy_device *phydev, int space)
    110. {
    111. int ret;
    112. if (space == YTPHY_REG_SPACE_UTP) {
    113. ret = ytphy_write_ext(phydev, 0xa000, 0);
    114. } else {
    115. ret = ytphy_write_ext(phydev, 0xa000, 2);
    116. }
    117. return ret;
    118. }
    119. static int ytphy_wol_en_cfg(struct phy_device *phydev, ytphy_wol_cfg_t wol_cfg)
    120. {
    121. int ret = 0;
    122. int val = 0;
    123. val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG);
    124. if (val < 0)
    125. return val;
    126. if (wol_cfg.enable) {
    127. val |= YTPHY_WOL_CFG_EN;
    128. if (wol_cfg.type == YTPHY_WOL_TYPE_LEVEL) {
    129. val &= ~YTPHY_WOL_CFG_TYPE;
    130. val &= ~YTPHY_WOL_CFG_INTR_SEL;
    131. } else if (wol_cfg.type == YTPHY_WOL_TYPE_PULSE) {
    132. val |= YTPHY_WOL_CFG_TYPE;
    133. val |= YTPHY_WOL_CFG_INTR_SEL;
    134. if (wol_cfg.width == YTPHY_WOL_WIDTH_84MS) {
    135. val &= ~YTPHY_WOL_CFG_WIDTH1;
    136. val &= ~YTPHY_WOL_CFG_WIDTH2;
    137. } else if (wol_cfg.width == YTPHY_WOL_WIDTH_168MS) {
    138. val |= YTPHY_WOL_CFG_WIDTH1;
    139. val &= ~YTPHY_WOL_CFG_WIDTH2;
    140. } else if (wol_cfg.width == YTPHY_WOL_WIDTH_336MS) {
    141. val &= ~YTPHY_WOL_CFG_WIDTH1;
    142. val |= YTPHY_WOL_CFG_WIDTH2;
    143. } else if (wol_cfg.width == YTPHY_WOL_WIDTH_672MS) {
    144. val |= YTPHY_WOL_CFG_WIDTH1;
    145. val |= YTPHY_WOL_CFG_WIDTH2;
    146. }
    147. }
    148. } else {
    149. val &= ~YTPHY_WOL_CFG_EN;
    150. val &= ~YTPHY_WOL_CFG_INTR_SEL;
    151. }
    152. ret = ytphy_write_ext(phydev, YTPHY_WOL_CFG_REG, val);
    153. if (ret < 0)
    154. return ret;
    155. return 0;
    156. }
    157. static void ytphy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
    158. {
    159. int val = 0;
    160. wol->supported = WAKE_MAGIC;
    161. wol->wolopts = 0;
    162. val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG);
    163. if (val < 0)
    164. return;
    165. if (val & YTPHY_WOL_CFG_EN)
    166. wol->wolopts |= WAKE_MAGIC;
    167. return;
    168. }
    169. static int ytphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
    170. {
    171. int ret, pre_page, val;
    172. ytphy_wol_cfg_t wol_cfg;
    173. struct net_device *p_attached_dev = phydev->attached_dev;
    174. memset(&wol_cfg, 0, sizeof(ytphy_wol_cfg_t));
    175. pre_page = ytphy_read_ext(phydev, 0xa000);
    176. if (pre_page < 0)
    177. return pre_page;
    178. /* Switch to phy UTP page */
    179. ret = ytphy_switch_reg_space(phydev, YTPHY_REG_SPACE_UTP);
    180. if (ret < 0)
    181. return ret;
    182. if (wol->wolopts & WAKE_MAGIC) {
    183. /* Enable the WOL interrupt */
    184. val = phy_read(phydev, YTPHY_UTP_INTR_REG);
    185. val |= YTPHY_WOL_INTR;
    186. ret = phy_write(phydev, YTPHY_UTP_INTR_REG, val);
    187. if (ret < 0)
    188. return ret;
    189. /* Set the WOL config */
    190. wol_cfg.enable = 1; //enable
    191. wol_cfg.type = YTPHY_WOL_TYPE_PULSE;
    192. wol_cfg.width = YTPHY_WOL_WIDTH_672MS;
    193. ret = ytphy_wol_en_cfg(phydev, wol_cfg);
    194. if (ret < 0)
    195. return ret;
    196. /* Store the device address for the magic packet */
    197. ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR2,
    198. ((p_attached_dev->dev_addr[0] << 8) |
    199. p_attached_dev->dev_addr[1]));
    200. if (ret < 0)
    201. return ret;
    202. ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR1,
    203. ((p_attached_dev->dev_addr[2] << 8) |
    204. p_attached_dev->dev_addr[3]));
    205. if (ret < 0)
    206. return ret;
    207. ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR0,
    208. ((p_attached_dev->dev_addr[4] << 8) |
    209. p_attached_dev->dev_addr[5]));
    210. if (ret < 0)
    211. return ret;
    212. } else {
    213. wol_cfg.enable = 0; //disable
    214. wol_cfg.type = YTPHY_WOL_TYPE_MAX;
    215. wol_cfg.width = YTPHY_WOL_WIDTH_MAX;
    216. ret = ytphy_wol_en_cfg(phydev, wol_cfg);
    217. if (ret < 0)
    218. return ret;
    219. }
    220. /* Recover to previous register space page */
    221. ret = ytphy_switch_reg_space(phydev, pre_page);
    222. if (ret < 0)
    223. return ret;
    224. return 0;
    225. }
    226. #endif /*(YTPHY_ENABLE_WOL)*/
    227. static int yt8521_config_init(struct phy_device *phydev)
    228. {
    229. int ret;
    230. int val;
    231. phydev->irq = PHY_POLL;
    232. ytphy_write_ext(phydev, 0xa000, 0);
    233. ret = genphy_config_init(phydev);
    234. if (ret < 0)
    235. return ret;
    236. /* disable auto sleep */
    237. val = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1);
    238. if (val < 0)
    239. return val;
    240. val &= (~BIT(YT8521_EN_SLEEP_SW_BIT));
    241. ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, val);
    242. if (ret < 0)
    243. return ret;
    244. /* enable RXC clock when no wire plug */
    245. ret = ytphy_write_ext(phydev, 0xa000, 0);
    246. if (ret < 0)
    247. return ret;
    248. val = ytphy_read_ext(phydev, 0xc);
    249. if (val < 0)
    250. return val;
    251. val &= ~(1 << 12);
    252. ret = ytphy_write_ext(phydev, 0xc, val);
    253. if (ret < 0)
    254. return ret;
    255. //printk("8521 init call out...\n");
    256. return ret;
    257. }
    258. static int yt8521_adjust_status(struct phy_device *phydev, int val, int is_utp)
    259. {
    260. int speed_mode, duplex;
    261. int speed = SPEED_UNKNOWN;
    262. //printk("8521 status adjust call in...\n");
    263. duplex = (val & YT8512_DUPLEX) >> YT8521_DUPLEX_BIT;
    264. speed_mode = (val & YT8521_SPEED_MODE) >> YT8521_SPEED_MODE_BIT;
    265. switch (speed_mode) {
    266. case 0:
    267. if (is_utp)
    268. speed = SPEED_10;
    269. break;
    270. case 1:
    271. speed = SPEED_100;
    272. break;
    273. case 2:
    274. speed = SPEED_1000;
    275. break;
    276. case 3:
    277. break;
    278. default:
    279. speed = SPEED_UNKNOWN;
    280. break;
    281. }
    282. phydev->speed = speed;
    283. phydev->duplex = duplex;
    284. //printk("8521 status adjust call out,regval=0x%04x,mode=%s,speed=%dm...\n", val,is_utp?"utp":"fiber", phydev->speed);
    285. return 0;
    286. }
    287. int yt8521_aneg_done (struct phy_device *phydev)
    288. {
    289. //printk("phy..YT8521 AN_done callin,speed=%dm,linkmoded=%d\n", phydev->speed,link_mode_8521);
    290. if ((32 == link_mode_8521) && (SPEED_100 == phydev->speed))
    291. {
    292. return 1/*link_mode_8521*/;
    293. }
    294. return genphy_aneg_done(phydev);
    295. }
    296. static int yt8521_read_status(struct phy_device *phydev)
    297. {
    298. int ret;
    299. volatile int val;
    300. volatile int link;
    301. int link_utp = 0;
    302. /* reading UTP */
    303. ret = ytphy_write_ext(phydev, 0xa000, 0);
    304. if (ret < 0)
    305. return ret;
    306. val = phy_read(phydev, REG_PHY_SPEC_STATUS);
    307. if (val < 0)
    308. return val;
    309. link = val & (BIT(YT8521_LINK_STATUS_BIT));
    310. if (link) {
    311. link_utp = 1;
    312. link_mode_8521 = 1;
    313. yt8521_adjust_status(phydev, val, 1);
    314. } else {
    315. link_utp = 0;
    316. }
    317. if (link_utp) {
    318. phydev->link = 1;
    319. } else {
    320. phydev->link = 0;
    321. link_mode_8521 = 0;
    322. }
    323. if (link_utp) {
    324. ytphy_write_ext(phydev, 0xa000, 0);
    325. }
    326. //printk("8521 read status call out, link=%d, linkmode=%d\n", phydev->link, link_mode_8521 );
    327. return 0;
    328. }
    329. int yt8521_suspend(struct phy_device *phydev)
    330. {
    331. #if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
    332. int value;
    333. #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    334. mutex_lock(&phydev->lock);
    335. #else
    336. /* no need lock in 4.19 */
    337. #endif
    338. ytphy_write_ext(phydev, 0xa000, 0);
    339. value = phy_read(phydev, MII_BMCR);
    340. phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
    341. ytphy_write_ext(phydev, 0xa000, 2);
    342. value = phy_read(phydev, MII_BMCR);
    343. phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
    344. ytphy_write_ext(phydev, 0xa000, 0);
    345. #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    346. mutex_unlock(&phydev->lock);
    347. #else
    348. /* no need lock/unlock in 4.19 */
    349. #endif
    350. #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
    351. return 0;
    352. }
    353. int yt8521_resume(struct phy_device *phydev)
    354. {
    355. #if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
    356. int value;
    357. #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    358. mutex_lock(&phydev->lock);
    359. #else
    360. /* no need lock/unlock in 4.19 */
    361. #endif
    362. ytphy_write_ext(phydev, 0xa000, 0);
    363. value = phy_read(phydev, MII_BMCR);
    364. phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
    365. ytphy_write_ext(phydev, 0xa000, 2);
    366. value = phy_read(phydev, MII_BMCR);
    367. phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
    368. ytphy_write_ext(phydev, 0xa000, 0);
    369. #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    370. mutex_unlock(&phydev->lock);
    371. #else
    372. /* no need lock/unlock in 4.19 */
    373. #endif
    374. #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
    375. return 0;
    376. }
    377. static struct phy_driver ytphy_drvs[] = {
    378. {
    379. /* same as 8521 */
    380. .phy_id = PHY_ID_YT8531S,
    381. .name = "YT8531S Ethernet",
    382. .phy_id_mask = MOTORCOMM_PHY_ID_MASK,
    383. .features = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
    384. .flags = PHY_POLL,
    385. .soft_reset = yt8521_soft_reset,
    386. .config_aneg = genphy_config_aneg,
    387. .aneg_done = yt8521_aneg_done,
    388. .config_init = yt8521_config_init,
    389. .read_status = yt8521_read_status,
    390. .suspend = yt8521_suspend,
    391. .resume = yt8521_resume,
    392. #if (YTPHY_ENABLE_WOL)
    393. .get_wol = &ytphy_get_wol,
    394. .set_wol = &ytphy_set_wol,
    395. #endif
    396. }
    397. };
    398. module_phy_driver(ytphy_drvs);
    399. MODULE_DESCRIPTION("Motorcomm PHY driver");
    400. MODULE_AUTHOR("Leilei Zhao");
    401. MODULE_LICENSE("GPL");
    402. static struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
    403. { PHY_ID_YT8010, MOTORCOMM_PHY_ID_MASK },
    404. { PHY_ID_YT8510, MOTORCOMM_PHY_ID_MASK },
    405. { PHY_ID_YT8511, MOTORCOMM_PHY_ID_MASK },
    406. { PHY_ID_YT8512, MOTORCOMM_PHY_ID_MASK },
    407. { PHY_ID_YT8512B, MOTORCOMM_PHY_ID_MASK },
    408. { PHY_ID_YT8521, MOTORCOMM_PHY_ID_MASK },
    409. { PHY_ID_YT8531S, MOTORCOMM_PHY_ID_MASK },
    410. { PHY_ID_YT8531, MOTORCOMM_PHY_ID_MASK },
    411. { PHY_ID_YT8618, MOTORCOMM_MPHY_ID_MASK },
    412. { PHY_ID_YT8614, MOTORCOMM_MPHY_ID_MASK_8614 },
    413. { }
    414. };
    415. MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);

  • 相关阅读:
    使用acme.sh配置https证书
    JavaScript学习笔记(一)
    Winform开源布局组件DockPanelSuite使用
    学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践
    设计模式课件
    JSON数据获取指南!
    2022.07.31(LC_6133_分组的最大数量)
    Day 7.UDP编程、不同主机之间用网络进行通信
    NFT入门:部署示例等
    【Python】一个最基础,但是超难看出来的类声明为tuple的BUG
  • 原文地址:https://blog.csdn.net/liangtao_1996/article/details/126079048