完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
LWIP
LWIP简介 lwip,是由Adam Dunkels开发的小型开源的TCP/IP协议栈;支持TCPIP协议族的核心协议;主要包括:ARP/ICMP/TCP/UDP/IPV4/IPV6/DHCP等;最主要的特点是运行所需的RAM和ROM少(只需十几KB的RAM和四十KB的ROM就可以运行),这使LWIP协议栈适合在低端的嵌入式系统中使用。 LWIP主要特性 ARP 协议,以太网地址解析协议; IP 协议,包括 IPv4 IPv4 和 IPv6 IPv6 ,支持 IP 分片与重装,支持多网络接口下数据转发; ICMP 协议,用于网络调试与维护; IGMP IGMP 协议,用于网络组管理可以实现多播数据的接收; UDP 协议,用户数据报; TCP 协议,支持 TCPTCP 拥塞控制, 拥塞控制, RTT估计,快速恢复与重传等; 提供三种用户编程接口方式: raw/callback APIraw/callback; DNS ,域名解析; SNMP ,简单网络管理协议 DHCP,动态主机配置协议; AUTOTP,IP地址自动配置; PPP,点对点协议,支持PPPoE ENC28J60 ENC28J60简介 对于stm32f103来说没有网口,通信就得靠enc28j60 ENC28J60特点 兼容 IEEE802.3 IEEE802.3 协议的以太网控制器 。 集成 MAC MAC和 10 BASE 10 BASE 10 BASE 10 BASE-T物理层 。 支持全双工和半模式 。 数据冲突时可编程自动重发 。 SPI 接口速度可达 接口速度可达 10Mbps 10Mbps 。 8K 数据接收和发送双端口 数据接收和发送双端口 RAM 。 提供快速数据移动的内部 DMA 控制器 控制器 。 可配置的接收和发送缓冲区大小 。 两个可编程 LED 输出 。 带 7个中断源的两引脚 个中断源的两引脚 。 TTL TTL 电平输入 。 提供多种封装: SOIC/SSOP/SPDIP/QFN SOIC/SSOP/SPDIP/QFN 等。 SPI接口,充当主控制器和ENC28J60之间的通道 控制寄存器,用于控制和监视ENC28J60. 双端口RAM缓冲器,用于接收和发送数据包。 判优器,当DMA、发送和接受模块发出请求时对RAM缓冲器的访问进行控制。 总线接口,对通过SPI接收的数据和命令进行解析。 MAC(Medium Access Control),实现符合IEEE802.3标准的MAC逻辑。 PHY(物理层)模块,对双绞线上的模拟数据进行编码和译码。 无操作系统LWIP移植 在说移植之前,先说下几个重要的函数功能和数据结构 enc28j60.c文件 主要函数及功能 主要结构体 因为LWIP可以支持的结构体,因为LWIP可以支持多个网络接口,当设备 有多个网络接口的话LWIP就会把所有的netif结构体组成链表来管理这些网络接口 netif结构体定义(netif.h)只列出了比较重要的字段 struct netif { struct netif *next; //指向下过一个netif 结构体 ip_addr_t ip_addr; //网络接口IP 地址 ip_addr_t netmask; //子网掩码 ip_addr_t gw; //默认网关 netif_input_fn input; //IP 层接收数据函数 netif_output_fn output; //IP 层发送数据包调用 netif_linkoutput_fn linkoutput; //底层数据包发送 void *state; //设备的状态信息 u16_t mtu; //该网络接口最大允许传输的数据长度 u8_t hwaddr_len; //物理地址长度 u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //该网络接口的物理地址 u8_t flags; //该网络接口的状态和属性 char name[2]; //该网络接口的名字 u8_t num; //该网络接口的编号 } input:此字段为一个函数,这个函数将网卡接收到的数据交给IP 层。 output:此字段为一个函数,当IP 层向接口发送一个数据包时调用此函数。这个函数通常首先解析硬件地址,然后发送数据包。此字段我们一般使用etharp.c 中的etharp_output()函数。 linkoutput:此字段为一个函数,该函数被ARP 模块调用,完成网络数据的发送。上面说的etharp_output 函数将IP 数据包封装成以太网数据帧以后就会调用linkoutput 函数将数据发送出去。 lwip__comm.c文件 __lwip_dev(lwip__commm.h)存放一些重要信息 struct netif { struct netif *next; //指向下过一个netif 结构体 ip_addr_t ip_addr; //网络接口IP 地址 ip_addr_t netmask; //子网掩码 ip_addr_t gw; //默认网关 netif_input_fn input; //IP 层接收数据函数 netif_output_fn output; //IP 层发送数据包调用 netif_linkoutput_fn linkoutput; //底层数据包发送 void *state; //设备的状态信息 u16_t mtu; //该网络接口最大允许传输的数据长度 u8_t hwaddr_len; //物理地址长度 u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //该网络接口的物理地址 u8_t flags; //该网络接口的状态和属性 char name[2]; //该网络接口的名字 u8_t num; //该网络接口的编号 } lwip_comm.c文件的API u8 lwip_comm_mem_malloc(void) //完成对mem.c和memp.c中内存堆ram_heap和内存池memp_memory的内存分配。 void lwip_comm_mem_free(void)//释放上个函数申请的内存 mem.c 和memp.c void lwip_comm_default_ip_set( lwip_dev *lwipx) //设置本地默认地址和远端默认地址。网关 IP MAC 子网掩码。和DHCP状态 u8 lwip_comm_init(void)//内核初始化,设置网卡并打开指定的网卡。,下面重点说此函数。 void lwip_pkt_handle(void) //从网络缓冲区接受数据包并将其发送给LWIP处理 void lwip_periodic_handle()//完成对LWIP内核的定时器处理函数的周期性调用 void lwip_dhcp_process_handle(void)//DHCP处理函数,为设备分配IP地址,网关等信息 u8 lwip_comm_init(void) //LWIP初始化(LWIP启动的时候使用) //返回值:0,成功 // 1,内存错误 // 2,DM9000初始化失败 // 3,网卡添加失败。 u8 lwip_comm_init(void) { struct netif *Netif_Init_Flag; //调用netif_add()函数时的返回值,用于判断网络初始化是否成功 struct ip_addr ipaddr; //ip地址 struct ip_addr netmask; //子网掩码 struct ip_addr gw; //默认网关 if(lwip_comm_mem_malloc())return 1; //内存申请失败 if(ENC28J60_Init())return 2; //初始化ENC28J60 lwip_init(); //初始化LWIP内核 lwip_comm_default_ip_set(&lwipdev); //设置默认IP等信息 #if LWIP_DHCP //使用动态IP ipaddr.addr = 0; netmask.addr = 0; gw.addr = 0; #else //使用静态IP IP4_ADDR(&ipaddr,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); IP4_ADDR(&netmask,lwipdev.netmask[0],lwipdev.netmask[1] ,lwipdev.netmask[2],lwipdev.netmask[3]); IP4_ADDR(&gw,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); printf(“网卡en的MAC地址为:。..。..。..。..。...%d.%d.%d.%d.%d.%drn”,lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]); printf(“静态IP地址。..。..。..。..。..。..。..。..%d.%d.%d.%drn”,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); printf(“子网掩码。..。..。..。..。..。..。..。..。.%d.%d.%d.%drn”,lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]); printf(“默认网关。..。..。..。..。..。..。..。..。.%d.%d.%d.%drn”,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); #endif Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,ðernet_input);//向网卡列表中添加一个网口 #if LWIP_DHCP //如果使用DHCP的话 lwipdev.dhcpstatus=0; //DHCP标记为0 dhcp_start(&lwip_netif); //开启DHCP服务 #endif if(Netif_Init_Flag==NULL)return 3;//网卡添加失败 else//网口添加成功后,设置netif为默认值,并且打开netif网口 { netif_set_default(&lwip_netif); //设置netif为默认网口 netif_set_up(&lwip_netif); //打开netif网口 } return 0;//操作OK. } 上面这个函数主要完成以下功能: 1 、调用 lwip_comm_mem_malloc() 函数完成前面提到的内存堆 ram_heap 和内存池 memp_memory 的内存分配。 2、调用 ENC28J60_Init ()函数完成对 ENC28J60 的初始化,ENC28J60_Init ()函数在 enc28j60.c 文件中,由 ALIENTEK 提供,前面已经介绍过了。 3、调用 lwip_init 函数完成 LWIP 的内核初始化,lwip_init()通过调用各个模块的初始化函数来完成各个模块的初始化,比如内存初始化函数、数据包结构初始化函数、网络接口初始化函数、IP 和 TCP 等的初始化函数,lwip_init()在 init.c 文件中,属于 LWIP 源码。 4、调用 ip_comm_default_ip_set()函数设置静态地址等信息,此函数由 ALIENTEK 提供。 5、判断是否使用 DHCP,如果使用 DHCP 的话就通过 DHCP 服务来获取 IP 地址、子网掩码和默认网关等信息,如果使用静态 IP 地址的话就用 ip_comm_default_ip_se()函数设置的地址信息。 6、我们通过 netif_add()函数来完成网卡的注册,netif_add()在 netif.c 文件中,此函数属于LWIP 源码。netif_add()函数中的参数 lwip_netif 是我们定义的一个网络接口,这个函数除了使用上面说的 IP 地址,子网掩码和默认网关作为参数外,还使用了两个函数地址作为参数:ethernetif_init 和 ethernet_input , 这两个函数地址会被赋值给 netif 结构体的相关字段, ethernetif_init()在下一节会讲到,这个函数由 ALIENTEK 提供,ethernet_input()函数在 etharp.c文件中,属于 LWIP 源码,是 ARP 层数据包输入函数。 7、如果使用 DHCP 的话就开启 DHCP 服务,通过调用 dhcp_start()函数开启 DHCP 服务。 8、当网卡注册成功后使用netif_set_default()设置此网卡为默认网卡,并且使用netif_set_up()函数打开此网卡。 此函数也是我们所写的函数硬件驱动完后,的第一个函数。也是对LWIP初始化的函数,此函数运行成功则LWIP初始化成功。此函数完后就要定时去处理内核维护函数。lwip_periodic_handle(); 然后看一个DHCP动态分配IP过程: //DHCP处理任务 void lwip_dhcp_process_handle(void) { u32 ip=0,netmask=0,gw=0; switch(lwipdev.dhcpstatus) { case 0: //开启DHCP dhcp_start(&lwip_netif); lwipdev.dhcpstatus = 1; //等待通过DHCP获取到的地址 printf(“正在查找DHCP服务器,请稍等。..。..。..。.rn”); break; case 1: //等待获取到IP地址 { ip=lwip_netif.ip_addr.addr; //读取新IP地址 netmask=lwip_netif.netmask.addr;//读取子网掩码 gw=lwip_netif.gw.addr; //读取默认网关 if(ip!=0) //正确获取到IP地址的时候 { lwipdev.dhcpstatus=2; //DHCP成功 printf(“网卡en的MAC地址为:。..。..。..。..。...%d.%d.%d.%d.%d.%drn”,lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]); //解析出通过DHCP获取到的IP地址 lwipdev.ip[3]=(uint8_t)(ip》》24); lwipdev.ip[2]=(uint8_t)(ip》》16); lwipdev.ip[1]=(uint8_t)(ip》》8); lwipdev.ip[0]=(uint8_t)(ip); printf(“通过DHCP获取到IP地址。..。..。..。..。.%d.%d.%d.%drn”,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); //解析通过DHCP获取到的子网掩码地址 lwipdev.netmask[3]=(uint8_t)(netmask》》24); lwipdev.netmask[2]=(uint8_t)(netmask》》16); lwipdev.netmask[1]=(uint8_t)(netmask》》8); lwipdev.netmask[0]=(uint8_t)(netmask); printf(“通过DHCP获取到子网掩码。..。..。..。..%d.%d.%d.%drn”,lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]); //解析出通过DHCP获取到的默认网关 lwipdev.gateway[3]=(uint8_t)(gw》》24); lwipdev.gateway[2]=(uint8_t)(gw》》16); lwipdev.gateway[1]=(uint8_t)(gw》》8); lwipdev.gateway[0]=(uint8_t)(gw); printf(“通过DHCP获取到的默认网关。..。..。...%d.%d.%d.%drn”,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); }else if(lwip_netif.dhcp-》tries》LWIP_MAX_DHCP_TRIES) //通过DHCP服务获取IP地址失败,且超过最大尝试次数 { lwipdev.dhcpstatus=0XFF;//DHCP超时失败。 //使用静态IP地址 IP4_ADDR(&(lwip_netif.ip_addr),lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); IP4_ADDR(&(lwip_netif.netmask),lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]); IP4_ADDR(&(lwip_netif.gw),lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); printf(“DHCP服务超时,使用静态IP地址!rn”); printf(“网卡en的MAC地址为:。..。..。..。..。...%d.%d.%d.%d.%d.%drn”,lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]); printf(“静态IP地址。..。..。..。..。..。..。..。..%d.%d.%d.%drn”,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); printf(“子网掩码。..。..。..。..。..。..。..。..。.%d.%d.%d.%drn”,lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]); printf(“默认网关。..。..。..。..。..。..。..。..。.%d.%d.%d.%drn”,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); } } break; default : break; } } 我们通过判断 lwipdev 结构体的 dhcpstatus 字段判断是否使用 DHCP 服务,当 dhcpstatus=0 时表示开启 DHCP,我们调用 dhcp_start()函数开启相应网络接口的 DHCP 服务 dhcp_start()函数由 LWIP 源码提供。开启 DHCP 以后让 dhcpstatus=1,表示开始进行 DHCP,等待 DHCP 完成。当 DHCP 完成以后让 dhcpstatus=2 ,表示 DHCP 成功。下次执行此函数直default,但是当 DHCP 重试次数大于LWIP_MAX_DHCP_TRIES 时,意味着 DHCP 失败,这时dhcpstatus=0XFF,表示 DHCP 失败, 并且使用静态 IP 地址。 ethernetif.c文件 主要函数 static err_t low_level_init(struct netif *netif)//网卡的复位、协议栈网络接口管理结构体netif中相关字段的初始化 static err_t low_level_output(struct netif *netif, struct pbuf *p) static struct pbuf * low_level_input(struct netif *netif) //从网卡中提取数据,并将其封装在pbuf结构体中工LWIP使用。函数 low_level_input()首先调用函数 ENC28J60_Packet_Receive()接收网络数据,然后将数据打包成 pbuf 结构,然后将这个 pbuf 返回,low_level_input 函数就是完成这个功能的, err_t ethernetif_input(struct netif *netif)//对 low_level_input()函数做封装,然后将接收到的数据送入指定的网卡结构中 err_t ethernetif_init(struct netif *netif)//ethernetif_init()函数为 low_level_init()函数的简单封装,并且初始化了 netif 的相关字段, LWIP 的裁剪与配置 在 LWIP 的源码中有个 opt.h 的文件,这个文件是裁剪和配置 LWIP 的,不过我们最好不要直接在 opt.h 里面做修改,我们可以打开 opt.h 文件看一下,里面的配置都是条件编译的,如果我们在其他地方有定义过的话那么在 opt.h 里面的定义不起作用了。所以我们可以新建一个.h 的文件来裁剪和配置 LWIP,我们前面提过在 LWIP-》lwip_app-》lwip_comm 下有一个 lwipopts.h 的文件,这个文件就是用来裁剪与配置LWIP RAW编程接口UDP UDP协议 ——UDP 协议是 TCP/IP 协议栈中传输层协议,是一个简单的面向数据报的协议,在传输层中还有另一个重要的协议,那就是 TCP 协议。UDP 不提供数据包分组、组装,不能对数据包进行排序,当报文发送出去后无法知道是否安全、完整的到达。UDP 除了这些缺点外肯定有它自身的优势,由于 UDP 不属于连接型协议,因而消耗资源小,处理速度快,所以通常在音频、视频和普通数据传输时使用 UDP 较多。 数据报结构如下图所示 校验和首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算和将不会相符,由此 UDP 协议可以检测是否出错。 UDP中的协议函数 ——在 LWIP 源码中 udp.c 和 udp.h 这两个文件是关于 UDP 协议的,这两个文件中有很多函数, 要想对 UDP 有详细的了解的话可以查阅这两个文件。在这里推荐一本我本人认为很好的 LWIP 参考书《嵌入式网络那些事 LWIP 协议栈深度剖析与实战演练》,作者朱升林,在这本书中对于 LWIP 的源码做了非常详细的讲解,想要研究 LWIP 源码的可以看看这本书,不过这本书中采用的是 1.3.2 版本的 LWIP,本教程采用的是 1.4.1 版本的 LWIP,所以有些函数会有差别,这一点大家一定要注意一下。 udp.c 中与 UDP 报文处理有关的函数之间的关系如图 LWIP中RAW API编程接口中与UDP相关的函数。 LWIP的RAW API编程方式是基于回调机制,当我们初始化应用的时候我们必须为内核中的不同时间注册相应的回调函数,当相应的时间发生的时候这些回调函数就会被调用。 RAW API 函数 描述 ———— ———————UDP 的 RAW API 功能函数 udp_demo.c里面的一些重要的函数 这里我们着重介绍一下 udp_demo_test() 这个函数。 udp_demo_test() //UDP测试 void udp_demo_test(void) { err_t err; struct udp_pcb *udppcb; //定义一个UDP服务器控制块 struct ip_addr rmtipaddr; //用来保存远端ip地址 u8 *tbuf; u8 key; u8 res=0; // u8 t=0; //initsardata(); udp_demo_set_remoteip();//设置远端IP tbuf=mymalloc(SRAMIN,200); //申请内存 if(tbuf==NULL) return ; //内存申请失败了,直接退出 sprintf((char*)tbuf,“Local IP:%d.%d.%d.%d”,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);//服务器IP //LCD_ShowString(30,150,210,16,16,tbuf); sprintf((char*)tbuf,“Remote IP:%d.%d.%d.%d”,lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);//远端IP //LCD_ShowString(30,170,210,16,16,tbuf); sprintf((char*)tbuf,“Remote Port:%d”,UDP_DEMO_PORT);//客户端端口号 //LCD_ShowString(30,190,210,16,16,tbuf); POINT_COLOR=BLUE;//字体为蓝色 //LCD_ShowString(30,210,210,16,16,“STATUS:Disconnected”); //如果本地准备成功会覆盖此数据 //创建一个UDP控制块 udppcb=udp_new();//他自己的内存池创建 initsardata(); if(udppcb)//创建成功 { IP4_ADDR(&rmtipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);//打包成u32 位 err=udp_connect(udppcb,&rmtipaddr,UDP_DEMO_PORT);//UDP客户端连接到指定IP地址和端口号的服务器 if(err==ERR_OK) { err=udp_bind(udppcb,IP_ADDR_ANY,UDP_DEMO_PORT);//绑定本地IP地址与端口号 if(err==ERR_OK) //绑定完成 { udp_recv(udppcb,udp_demo_recv,NULL);//注册接收回调函数 给相关的udppcb结构体元素赋值 printf(“STATUS:Connected rn”);//标记连接上了(UDP是非可靠连接,这里仅仅表示本地UDP已经准备好) udp_demo_flag |= 1《《5; //标记已经连接上 POINT_COLOR=RED; //LCD_ShowString(30,230,lcddev.width-30,lcddev.height-190,16,“Receive Data:”);//提示消息 POINT_COLOR=BLUE;//蓝色字体 }else res=1; }else res=1; }else res=1; while(res==0)//连接成功 { //Open_Elengine_2(); key=KEY_Scan(0); if(key==WKUP_PRES)break; if(key==KEY0_PRES)//KEY0按下了,发送数据 { udp_demo_senddata(udppcb); } // if((sendrecv.humi&0x0f)==0x01)//如果humi标志位为1则发送数据到上位机 // { // // sendrecv.humi=sendrecv.humi&0xf0;//将该位清零 // } if(udp_demo_flag&1《《6)//是否收到数据? { udp_demo_senddata(udppcb); elengine_wyx = 1; //LCD_Fill(30,250,lcddev.width-1,lcddev.height-1,WHITE);//清上一次数据 //LCD_ShowString(30,250,lcddev.width-30,lcddev.height-230,16,udp_demo_recvbuf);//显示接收到的数据 udp_demo_flag&=~(1《《6);//标记数据已经被处理了。 } lwip_periodic_handle();//轮询任务 维持LWIP内核 delay_ms(2); //DHT11_test(); } udp_demo_connection_close(udppcb); myfree(SRAMIN,tbuf); } 在上面代码中我们完成了一下几个功能: 1、调用 udp_new()函数创建一个 UDP 控制块 upcb。 2、当创建成功以后,调用 udp_connect()函数连接到指定的 IP 地址和端口号的主机,如果 UDP 控制块 upcb 创建失败的话 res 等于 1。 3、连接成功以后调用 udp_bind()函数绑定本地 IP 地址和端口号,连接失败的话就让 res 等于 1。 4、绑定成功以后使用 udp_recv()函数注册 UDP 的接收函数,并且置位 udp_demo_flag 的bit5,表示已经连接上(UDP 是非可靠连接,这里仅仅表示本地 UDP 已经准备好),如果绑定失败的话令 res 等 1。 5、如果 res 等于 0 的话那么表示以上 4 个步骤都成功了,成功以后就进入 while 循环,失败的话就调用 udp_demo_connection_close()函数断开连接,并且释放内存。 6、进入 while 以后我们通过按键来做不同的处理,当按下 KWY_UP 键的时 候退出循环,当按下 KWY0 键的时候发送数据。通过读取 udp_demo_flag 的 bit6 来判断是否接收到数据,当接收到数据的时候就在 LCD 上显示接收到的数据。 7、这点特别重要!在 while 循环中一定要调用函lwip_periodic_handle(),如果不调用这两个函数那么 LWIP 的内核就不能运行,那么网络肯定不会工作! 至此,我们关于 LWIP的RAW API UDP通信就说完了,关于RAW编程接口UDP的代码及实现部分请看。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1907 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1678 浏览 1 评论
1171 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
770 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1730 浏览 2 评论
1970浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
806浏览 4评论
stm32f4下spi+dma读取数据不对是什么原因导致的?
254浏览 3评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
624浏览 3评论
634浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-1-23 12:05 , Processed in 0.882085 second(s), Total 77, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号