最近老板有个项目,其中要做一个Zigbee的无线接入点,即将ZigBee无线传感网络中的数据通过TCP/IP协议传输到以太网上。传统的这种无线接入点即网关都是上位机加下位机模式做成的,即主控芯片(如ARM)加无线模块(如cc2530),ARM与cc2530通过UARST通信,cc2530建立WSN网络,ARM与PC机通过TCP/IP通信,考虑到此系统的成本以及其中的数据传输量不大,就不用ARM,直接将cc2530做成网关,这就需要将TCP/IP协议栈移植到cc2530上,同时与ZigBee协议栈能很好的协同运行。

因为cc2530的FLASH有256K,Z-stack占用了大部分,所以要用TCP/IP,只能移植一个轻量型网络协议栈,现在比较流行的就是Adam Dunkels写的lwIP和uIP,还有Micrium的uc/IP,lwip和uc/ip所占空间较大,移植较为麻烦,所以就用uip。uip是一种免费可的极小的TCP/IP协议栈,主要实现了ARP,ICMP,TCP,UDP协议,在8位或16位单片机上用的较多,对rom和ram要求很少。

在网上看了一些uip移植到51或STM32的文章,同时也花了两天时间看了uip的实现源码,如果不熟悉TCP/IP协议的话读起来还是很吃力,所以先看看TCP/IP,建议看TCP/IP协议详解——卷一。看完之后大概知道移植过程了。

移植之前先要写网络芯片驱动程序,我用的是enc28j60,独立控制的SPI接口,因为cc2530的spi接口用来下载调试了,另一个spi被串口复用了,所以只用用GPIO模拟SPI。

写驱动程序之前认真读了enc28j60的datasheet,在网上也找到了相关的驱动程序,可以稍加修改拿来用。下面贴出spi程序和enc28j60的程序。

[cpp] view plaincopy
  1. <span style="font-size:18px;">#include "spi.h"
  2. void WriteByte(u8_t dat)
  3. {
  4. u8_t i;
  5. for(i=0;i<8;i++)
  6. {
  7. SCKN = 0;
  8. asm("nop");
  9. if(dat&0x80)
  10. {
  11. SIN = 1;
  12. }
  13. else
  14. SIN = 0;
  15. dat <<= 1;
  16. asm("nop");
  17. SCKN = 1;
  18. asm("nop");
  19. }
  20. SCKN=0; //空闲状态为低电平
  21. }
  22. u8_t ReadByte(void)
  23. {
  24. u8_t i,dat;
  25. SCKN=0;
  26. dat1=0;
  27. for(i=0;i<8;i++)
  28. {
  29. SCKN=1;
  30. dat1 <<=1;
  31. dat1 |= SON;
  32. SCKN=0;
  33. }
  34. return dat;
  35. }
  36. </span>

spi.h文件定义了与enc28j60spi接口的GPIO,

[cpp] view plaincopy
  1. <span style="font-size:18px;">#ifndef SPI_H
  2. #define SPI_H
  3. #include <ioCC2530.h>
  4. #define SON   P0_5    // MISO
  5. #define SIN   P0_6    // MOSI
  6. #define SCKN  P0_7    // SCK
  7. #define CSN   P1_3    // 28J60-- CS
  8. #define RESET P1_2    // Reset
  9. void WriteByte(u8_t dat);
  10. u8_t ReadByte(void);
  11. #endif
  12. </span>

enc28j60.c文件:

[cpp] view plaincopy
  1. <span style="font-size:18px;">#include "enc28j60.h"
  2. #include "spi.h"
  3. #define MIN(a,b) (a) < (b) ? (a) : (b)
  4. XDATA u8_t Enc28j60Bank;
  5. XDATA u16_t NextPacketPtr;
  6. void delay_100ns()
  7. {
  8. asm("nop");
  9. asm("nop");
  10. asm("nop");
  11. }
  12. void delay_ms(int t1)
  13. {
  14. int i;
  15. while(t1--)
  16. {
  17. for(i=10;i;--i)
  18. {
  19. delay_100ns();
  20. }
  21. }
  22. }
  23. //*******************************************************************************************
  24. //
  25. // Function : enc28j60ReadOp
  26. // Description :
  27. //
  28. //*******************************************************************************************
  29. u8_t enc28j60ReadOp(u8_t op, u8_t address)
  30. {
  31. u8_t dat1;
  32. // activate CS
  33. CSN =0;
  34. // issue read command
  35. delay_100ns();
  36. WriteByte(op | (address & ADDR_MASK));
  37. dat1 = ReadByte();
  38. // do dummy read if needed (for mac and mii, see datasheet page 29)
  39. if(address & 0x80)  dat1 = ReadByte();
  40. // release CS
  41. CSN=1;
  42. return(dat1);
  43. }
  44. //*******************************************************************************************
  45. //
  46. // Function : enc28j60WriteOp
  47. // Description :
  48. //
  49. //*******************************************************************************************
  50. void enc28j60WriteOp(u8_t op, u8_t address, u8_t mydat)
  51. {
  52. CSN=0;
  53. // issue write command
  54. delay_100ns();
  55. WriteByte( op | (address & ADDR_MASK));
  56. // write data
  57. WriteByte(mydat);
  58. CSN=1;
  59. delay_100ns();
  60. }
  61. //*******************************************************************************************
  62. //
  63. // Function : icmp_send_request
  64. // Description : Send ARP request packet to destination.
  65. //
  66. //*******************************************************************************************
  67. void enc28j60SetBank(u8_t address)
  68. {
  69. // set the bank (if needed)
  70. if((address & BANK_MASK) != Enc28j60Bank)
  71. {
  72. // set the bank
  73. enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
  74. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
  75. Enc28j60Bank = (address & BANK_MASK);
  76. }
  77. }
  78. //*******************************************************************************************
  79. //
  80. // Function : icmp_send_request
  81. // Description : Send ARP request packet to destination.
  82. //
  83. //*******************************************************************************************
  84. u8_t enc28j60Read(u8_t address)
  85. {
  86. // select bank to read
  87. enc28j60SetBank(address);
  88. // do the read
  89. return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
  90. }
  91. //*******************************************************************************************
  92. //
  93. // Function : icmp_send_request
  94. // Description : Send ARP request packet to destination.
  95. //
  96. //*******************************************************************************************
  97. void enc28j60Write(u8_t address, u8_t mydat)
  98. {
  99. // select bank to write
  100. enc28j60SetBank(address);
  101. // do the write
  102. enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, mydat);
  103. }
  104. //*******************************************************************************************
  105. //
  106. // Function : icmp_send_request
  107. // Description : Send ARP request packet to destination.
  108. //
  109. //*******************************************************************************************
  110. u16_t enc28j60_read_phyreg(u8_t address)
  111. {
  112. u16_t mydat;
  113. // set the PHY register address
  114. enc28j60Write(MIREGADR, address);
  115. enc28j60Write(MICMD, MICMD_MIIRD);
  116. // Loop to wait until the PHY register has been read through the MII
  117. // This requires 10.24us
  118. while( (enc28j60Read(MISTAT) & MISTAT_BUSY) );
  119. // Stop reading 这里应该清零
  120. //enc28j60Write(MICMD, MICMD_MIIRD);
  121. enc28j60Write(MICMD, 0x0);
  122. // Obtain results and return
  123. mydat = enc28j60Read ( MIRDL );
  124. //PrintHex(mydat);
  125. //mydat |= enc28j60Read ( MIRDH ); //此地方源代码有误 改成
  126. mydat |= (enc28j60Read ( MIRDH )<<8);
  127. // PrintHex(mydat);
  128. return mydat;
  129. }
  130. //*******************************************************************************************
  131. //
  132. // Function : icmp_send_request
  133. // Description : Send ARP request packet to destination.
  134. //
  135. //*******************************************************************************************
  136. void enc28j60PhyWrite(u8_t address, u16_t mydat)
  137. {
  138. // set the PHY register address
  139. enc28j60Write(MIREGADR, address);
  140. // write the PHY data
  141. enc28j60Write(MIWRL, mydat & 0x00ff);
  142. enc28j60Write(MIWRH, mydat >> 8);
  143. // wait until the PHY write completes
  144. while(enc28j60Read(MISTAT) & MISTAT_BUSY)
  145. {
  146. delay_100ns();
  147. }
  148. }
  149. void enc28j60ReadBuffer(u16_t len, u8_t* dat)
  150. {
  151. // assert CS
  152. CSN = 0;
  153. // issue read command
  154. delay_100ns();
  155. WriteByte(ENC28J60_READ_BUF_MEM);
  156. while(len--)
  157. {
  158. *dat++ = ReadByte();
  159. }
  160. // release CS
  161. CSN = 1;
  162. }
  163. void enc28j60WriteBuffer(u16_t len, u8_t* dat)
  164. {
  165. // assert CS
  166. CSN = 0;
  167. // issue write command
  168. WriteByte(ENC28J60_WRITE_BUF_MEM);
  169. // while(!(SPSR & (1<<SPIF)));
  170. while(len--)
  171. {
  172. WriteByte(*dat++);
  173. }
  174. // release CS
  175. CSN = 1;
  176. }
  177. #define ETHERNET_MIN_PACKET_LENGTH  0x3C
  178. #define ETHERNET_HEADER_LENGTH      0x0E
  179. #define IP_TCP_HEADER_LENGTH 40
  180. #define TOTAL_HEADER_LENGTH (IP_TCP_HEADER_LENGTH+ETHERNET_HEADER_LENGTH)
  181. void enc28j60PacketSend(u16_t len, u8_t* packet)
  182. {
  183. // Set the write pointer to start of transmit buffer area
  184. enc28j60Write(EWRPTL, TXSTART_INIT);
  185. enc28j60Write(EWRPTH, TXSTART_INIT>>8);
  186. // Set the TXND pointer to correspond to the packet size given
  187. enc28j60Write(ETXNDL, (TXSTART_INIT+len));
  188. enc28j60Write(ETXNDH, (TXSTART_INIT+len)>>8);
  189. // write per-packet control byte
  190. enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
  191. // TODO, fix this up
  192. if( uip_len <= TOTAL_HEADER_LENGTH )
  193. {
  194. // copy the packet into the transmit buffer
  195. enc28j60WriteBuffer(len, packet);
  196. }
  197. else
  198. {
  199. len -= TOTAL_HEADER_LENGTH;
  200. enc28j60WriteBuffer(TOTAL_HEADER_LENGTH, packet);
  201. enc28j60WriteBuffer(len, (unsigned char *)uip_appdata);
  202. }
  203. // send the contents of the transmit buffer onto the network
  204. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
  205. }
  206. u16_t enc28j60PacketReceive(u16_t maxlen, u8_t* packet)
  207. {
  208. u16_t rxstat,len;
  209. if (enc28j60Read(EPKTCNT) == 0)
  210. {
  211. return 0;
  212. }
  213. // Set the read pointer to the start of the received packet
  214. enc28j60Write(ERDPTL, (NextPacketPtr));
  215. enc28j60Write(ERDPTH, (NextPacketPtr)>>8);
  216. // read the next packet pointer
  217. NextPacketPtr  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
  218. NextPacketPtr |= (enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8);
  219. // read the packet length
  220. len  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
  221. len |= (enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8);
  222. len -= 4; //以太帧最小46字节 减去4字节的FCS校验和 加上帧头14字节 共64字节
  223. //PrintHex(len);
  224. // read the receive status
  225. rxstat  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
  226. rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
  227. // limit retrieve length
  228. // (we reduce the MAC-reported length by 4 to remove the CRC)
  229. len = MIN(len, maxlen);
  230. // copy the packet from the receive buffer
  231. enc28j60ReadBuffer(len, packet);
  232. // Errata workaround #13. Make sure ERXRDPT is odd
  233. u16_t rs,re;
  234. rs = enc28j60Read(ERXSTH);
  235. rs <<= 8;
  236. rs |= enc28j60Read(ERXSTL);
  237. re = enc28j60Read(ERXNDH);
  238. re <<= 8;
  239. re |= enc28j60Read(ERXNDL);
  240. if (NextPacketPtr - 1 < rs || NextPacketPtr - 1 > re)
  241. {
  242. enc28j60Write(ERXRDPTL, (re));
  243. enc28j60Write(ERXRDPTH, (re)>>8);
  244. }
  245. else
  246. {
  247. enc28j60Write(ERXRDPTL, (NextPacketPtr-1));
  248. enc28j60Write(ERXRDPTH, (NextPacketPtr-1)>>8);
  249. }
  250. // decrement the packet counter indicate we are done with this packet
  251. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
  252. return len;
  253. }
  254. void dev_init(void)
  255. {
  256. enc28j60_init();
  257. }
  258. void dev_send(void)
  259. {
  260. enc28j60PacketSend(uip_len, uip_buf);
  261. }
  262. u16_t dev_poll(void)
  263. {
  264. return enc28j60PacketReceive(UIP_BUFSIZE, uip_buf);
  265. }
  266. void enc28j60_init(void)
  267. {
  268. //SPI INIT
  269. SpiInit();
  270. // perform system reset
  271. enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
  272. // check CLKRDY bit to see if reset is complete
  273. //while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));
  274. // Errata workaround #2, CLKRDY check is unreliable, delay 1 mS instead
  275. delay_ms(5);
  276. // lamp test
  277. // enc28j60PhyWrite(PHLCON, 0x0AA2);
  278. // do bank 0 stuff
  279. // initialize receive buffer
  280. // 16-bit transfers, must write low byte first
  281. // set receive buffer start address
  282. NextPacketPtr = RXSTART_INIT;
  283. enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);
  284. enc28j60Write(ERXSTH, RXSTART_INIT>>8);
  285. // set receive pointer address
  286. enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);
  287. enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);
  288. // set receive buffer end
  289. // ERXND defaults to 0x1FFF (end of ram)
  290. enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);
  291. enc28j60Write(ERXNDH, RXSTOP_INIT>>8);
  292. // set transmit buffer start
  293. // ETXST defaults to 0x0000 (beginnging of ram)
  294. enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);
  295. enc28j60Write(ETXSTH, TXSTART_INIT>>8);
  296. // do bank 2 stuff
  297. // enable MAC receive
  298. enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
  299. // bring MAC out of reset
  300. enc28j60Write(MACON2, 0x00);
  301. // enable automatic padding and CRC operations
  302. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
  303. // enc28j60Write(MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
  304. // set inter-frame gap (non-back-to-back)
  305. enc28j60Write(MAIPGL, 0x12);
  306. enc28j60Write(MAIPGH, 0x0C);
  307. // set inter-frame gap (back-to-back)
  308. enc28j60Write(MABBIPG, 0x12);
  309. // Set the maximum packet size which the controller will accept
  310. enc28j60Write(MAMXFLL, MAX_FRAMELEN&0xFF);
  311. enc28j60Write(MAMXFLH, MAX_FRAMELEN>>8);
  312. // do bank 3 stuff
  313. // write MAC address
  314. // NOTE: MAC address in ENC28J60 is byte-backward
  315. enc28j60Write(MAADR5, UIP_ETHADDR0);
  316. enc28j60Write(MAADR4, UIP_ETHADDR1);
  317. enc28j60Write(MAADR3, UIP_ETHADDR2);
  318. enc28j60Write(MAADR2, UIP_ETHADDR3);
  319. enc28j60Write(MAADR1, UIP_ETHADDR4);
  320. enc28j60Write(MAADR0, UIP_ETHADDR5);
  321. // no loopback of transmitted frames
  322. enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);
  323. // switch to bank 0
  324. enc28j60SetBank(ECON1);
  325. // enable interrutps
  326. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
  327. // enable packet reception
  328. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
  329. }
  330. </span>

在上面文件中有一个delay_100ns函数,为什么要写这个函数呢,看了enc28j60的输入输出的时序之后发现在cs置低之后只要要保持100ns,所以每次在写入之前都要延迟100ns左右。cc2530的时钟是32MHz,所以差不多3个机器周期就是100ns了。这个是要注意的地方,我在这之前,调试了半天,一直都没发现有网络数据包出来,然后再仔细读了datasheet才发现的。
       这个驱动根据datasheet写,或者参考网上的,哈哈,只要能实现就行了。          
       有了以上驱动程序就很好移植了! 废话不多说,直接给移植步骤。
       在网上下载源码,我用的是uip0.9版本。编译环境是IAR,因为cc2530用的是IAR开发环境。

接下来建立工程,如下图所示:

将spi.c enc28j60.c即相关头文件放在driver目录下,将uip.c uip_arp.c uip_arch.c还有相关头文件放在uip目录下,
linker目录下放的是IAR8051的连接文件(可以不用),将应用程序main.c app.c app.h文件放在user目录下。
      将uip源代码中unix文件夹中的main函数改一下,其中tapdev_read函数替换成我们的dev_poll,将tapdev_send替换成dev_send,然后将http相关的东西删去,我们先实现简单的功能。
      根据源码文件夹中的doc文档,写个简单的历程。

app.c的代码如下:

[cpp] view plaincopy
  1. <span style="font-size:18px;">#include "app.h"
  2. #include "uip.h"
  3. void example1_init(void)
  4. {
  5. uip_listen(HTONS(1234));
  6. }
  7. void example1_appcall(void)
  8. {
  9. struct example1_state *s;
  10. s = (struct example1_state *)uip_conn->appstate;
  11. if(uip_connected()) {
  12. s->state = WELCOME_SENT;
  13. uip_send("Welcome!\n", 9);
  14. return;
  15. }
  16. if(uip_acked() && s->state == WELCOME_SENT) {
  17. s->state = WELCOME_ACKED;
  18. }
  19. if(uip_newdata()) {
  20. uip_send("ok\n", 3);
  21. }
  22. if(uip_rexmit()) {
  23. switch(s->state) {
  24. case WELCOME_SENT:
  25. uip_send("Welcome!\n", 9);
  26. break;
  27. case WELCOME_ACKED:
  28. uip_send("ok\n", 3);
  29. break;
  30. }
  31. }
  32. }</span>

对了,还有个重要的问题,就是大小端的问题,这个也是我调试好久未果的一个经验。cc2530是8051内核的,是小端,所以在uipopt.h文件中做如下改动
#ifndef BYTE_ORDER
#define BYTE_ORDER     LITTLE_ENDIAN
#endif /* BYTE_ORDER */  
      还有个编译设置的问题,具体按照如下来:

好了,然后编译连接,过程中可能有一些警告和错误,一个一个耐心排除,很容易。
     用网线连上电脑,然后打开命令终端,输入ping 219.223.173.242  我主机ip是219.223.173.243
    如下结果:

然后打开网络调试助手

如程序需要的结果相同,哈哈!过几天实现一个web服务器!
    接下来的工作就是将uip移植到z-stack上,在cc2530上实现网关的功能!
    有需要源码的可以联系我!

UIP移植到CC2530上相关推荐

  1. UIP成功移植到CC2530上

    最近老板有个项目,其中要做一个Zigbee的无线接入点,即将ZigBee无线传感网络中的数据通过TCP/IP协议传输到以太网上.传统的这种无线接入点即网关都是上位机加下位机模式做成的,即主控芯片(如A ...

  2. 关于uIP移植以及部分特性解析和勘误

    关于uIP的移植以及部分特性解析和勘误 原文:http://www.cnblogs.com/CodeHXH/archive/2012/01/19/2327426.html 关于嵌入式网络的领域,uIP ...

  3. DM9015网卡uip移植到stm32

    DM9015网卡是用于嵌入式设备的某网卡,使用spi接口通讯.图片某宝偷的. 这个模块内部不带网络协议栈,像TCP/IP  UDP啥的都是没有的,需要自己编写或者,移植开源的协议栈. 我这边主要参考官 ...

  4. 第十九期 基于HG255d_U-Boot的uIP移植《路由器就是开发板》

    前面三期我们大略了解了U-Boot的三个常用功能,串口调试输入输出,flash操作,和网络连接,这一期我们来进行U-Boot的改造,我们改造的主题是优化U-Boot的网络功能,我们最终的目的是优化U- ...

  5. Lua移植到arm上 并实现在arm上 可以让lua脚本调c语言,C语言调用lua脚本

    Lua移植到arm上 并实现在arm上 可以让lua脚本调c语言,C语言调用lua脚本 首先参考http://wiki.chumby.com/index.php?title=Lua&print ...

  6. qt5.3.2移植到arm上出undefined reference to '__sync_sub_and_fetch_4的错

    qt5.3.2移植到arm上出undefined reference to '__sync_sub_and_fetch_4的错.解决办法如下, 使用工具:GCC4.4.1 QT源码:qt5.3.2 前 ...

  7. 将c程序移植到linux,各位大侠:我把原来在linux运行的c程序移植到HPUNIX上出现了错误...

    各位大侠:我把原来在linux运行的c程序移植到HPUNIX上出现了错误 (2012-04-11 00:43:47) 标签: linux c程序 杂谈 各位大侠:我把原来在linux运行的c程序移植到 ...

  8. Qt工作笔记-Qt creator如何生成dll,以及如何移植到vs上

    首先用Qt Creator创建一个库项目: 在类中添加一个add函数,并实现他: 直接就可以生成为一个dll 因为是使用MinGW的编译器所以会有.a文件: 把程序移动过去! 接着用另外一个项目进行调 ...

  9. STM32F103ZE工程移植到STM32F107VC上软件调试时死循环在while((RCC-CR RCC_CR_PLL2RDY) == 0) { }

    STM32F103ZE工程移植到STM32F107VC上软件调试时死循环在while((RCC->CR & RCC_CR_PLL2RDY) == 0) { }@TOC 第一次移植不知道改 ...

最新文章

  1. 中科院5nm激光光刻弯道超车?95后本科生DIY纳米级光刻机?背后的真实情况
  2. Windows核心编程 第27章 硬件输入模型和局部输入状态
  3. 各种SQL在Pig中实现
  4. DevExpress的下拉框控件ComboBoxEdit控件的使用
  5. 2Python全栈之路系列之MysQl基本数据类型
  6. 九度OJ 1547 动态规划
  7. Linux运维从入门到进阶
  8. 计算机桌面倒计时,Mamsds桌面倒计时
  9. js正则验证身份证号码
  10. linux 内核书籍记录
  11. oracle c# 插入中文乱码,C#写入Oracle 中文乱码问题
  12. flex布局兼容性问题
  13. 【深度学习】视频分类技术整理
  14. 解决安卓手机DNS被污染(刷新手机DNS)
  15. php数独,详解PHP如何实现数独求解
  16. M0、M1、M2、M3
  17. 移动端rpx px,rem em区别
  18. 做数据必知的十本书,你读过几本?
  19. 肉蛋堡记 - 符皓冉
  20. 西瓜书习题 - 8.集成学习

热门文章

  1. 用 Java Servlet 实现文件上载(老文新发)
  2. MongoDB 模糊查询包含/不包含字符串
  3. 微软CRM4.0 页面表单和腾讯QQ在线整合
  4. 以“新IT”助“数智融合”,联想推开“智能化转型”下半场的大门
  5. linux下nginx修改ip,通过域名ip进入网页
  6. Windows 7 安装或更改显示语言
  7. SpringBoot集成Redis并使用Knife4j测试
  8. AI智能缺陷检测系统
  9. SeaBIOS(1) - 简介
  10. 几何学小课堂:几何的发展史