10.1 初学者重要提示 磨刀不误砍柴工,初学者务必要对TCP的基础知识点有个认识,不是特别理解没有关系,随着后面逐渐的实战操作,会有比较全面的认识。# z: X, c, @) X- v" c 10.2 TCP基础知识参考资料 首次搞TCP通信,需要对TCP的一些基础知识有个了解。大家可以从以下地址获得TCP基础知识: Z% O9 v c: [' a0 |! u. m + Z! T) _9 x } e7 I 对于初学者来说,学习上面四个参考资料就够了。如果大家有网络方面的书籍,比如《TCP/IP详解》,也可以直接看书籍。; `$ N/ t1 ]+ D( y# o0 P 10.3 TCP基础知识点 (这里的知识点整理自上面的参考资料地址) 1 |4 g U4 Q$ d. y2 ^ 教程这里也对TCP的基础知识做个介绍,方便大家快速上手操作。# I: j3 Z# U6 Y # ^$ h8 F2 }6 u! U 10.3.1 TCP简要说明) n& N8 a1 _. a+ ]* ?9 `5 | TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议UDP是同一层内另一个重要的传输协议。在因特网协议族中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。 应用层向TCP层发送用于网间传输的数据流,然后TCP把数据流分成适当长度的报文段(受最大传输单元MTU的限制)。之后TCP把数据包传给IP层,由它来通过网络将数据包传送给接收端的TCP层。TCP为了保证不发生丢包,会给每个包一个序号,同时序号也保证了传送到接收端时,数据包的按序接收。然后接收端对已成功收到的包发回一个应答(ACK),如果发送端在设置的重传时间内未收到应答,那么对应的数据包会被认为已丢失将会进行重传。TCP使用校验和来检验数据是否有错误,在发送和接收时都要计算校验和。 [8 G- y) l3 E% q2 S ) I, P' q- i5 b 10.3.2 TCP数据格式 ( P/ D5 W, Z1 ^ F! J6 f Source Port(源端口16bits) - P$ J+ L Q Z" O7 f# s K# | 发送端口。6 ?3 W$ v1 m6 {5 f( x. i7 z6 b2 ?4 D 6 D% t5 ?& M9 M4 g" S' b Destination Port(目的端口16bits)$ P& P% Z/ V6 }. H& w$ M . u6 X' @3 Y6 B2 | 接收端口。 & ~) A; n) h- }. r) ] Sequence Number(序列号32bits)0 t% Y' {1 I- ~$ y1 |$ t6 a 如果SYN标志 = 1,那么这是初始序列号。第一个数据的序列号为本序列号加1。4 d3 m" t. D, {2 S 如果SYN标志 = 0,那么这是第一个数据的序列号。$ m: t+ \9 S" O4 s Acknowledgement Number(确认号32bits) 4 T G: [7 u6 E9 H+ I$ P: q 如果设置了ACK标志,则该字段的值是期望收到的数据的开始序列号,也即已经收到数据的字节长度加1。 Data Offset(报头长度4bits): N* L8 m) z ?. B. f4 p2 X 8 E( u& B2 ~) d1 K; R4 A" O. I 以4字节为单位计算出的数据段开始地址的偏移值。 Reserved(保留) * y* R- B3 Z, g) d( c7 V8 ^) f; ? 必须置0。4 l, }) e" h, `5 l' P, G 标志符- [6 O- P1 q$ H4 ? b- T/ `; ~ 7 R: x1 S" M) }" l/ H URG(1bit)—为1表示高优先级数据包,紧急指针字段有效。 J1 I% V- \- g \' I3 \8 ` ACK(1bit)—为1表示确认字段有效。客户端发送的初始SYN包后的所有数据包都应该设置此标志。5 H& j" J# i# o2 Z7 ]/ h PSH(1bit)—为1表示是带有PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。6 W: I1 |. U* T& ?4 _& m RST(1bit)—为1表示出现严重差错,可能需要重新创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。6 U. B9 H3 d6 `$ u, Y( ] SYN(1bit)—为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步。 FIN(1bit)—为1表示发送方没有数据要传输了,要求释放连接。/ `' i9 M4 V7 L& `# \1 { Window(窗口16bits) 表示从确认号开始,本报文的源方可以接收的字节数,即源方接收窗口大小,用于流量控制。. j7 V6 u( k; b2 X$ L( z3 v: } 0 }( g( w: l& t Checksum(校验和16bits)2 b4 M5 d5 Z" m8 G 对整个的TCP报文段,包括TCP头部和TCP数据,以16bits为单位进行计算所得。这是一个强制性的字段。& a' E. I. J2 R- M! _1 E5 F, R Urgent Pointer(紧急指针16bits) 本报文段中紧急数据的最后一个字节的序号。2 A; ~+ D$ Z& c) f9 H8 w . n# u& [$ U( Y8 Z1 ^% s Options(选项字段) ; D+ M& O0 Y# w 最多40字节。每个选项的开始是1字节的类型字段,说明选项的类型。 0:选项表结束(1字节)。! U6 V0 L$ \& d! } 1:无操作(1字节)用于选项字段之间的字边界对齐。 O8 i( ]& [7 f; o) f 2:最大报文段长度(4字节,Maximum Segment Size,MSS)通常在创建连接时设置SYN标志的数据包中指明这个选项,指明本端所能接收的最大长度的报文段。通常将MSS设置为MTU 减去40字节,携带TCP报文段的IP数据报的长度就不会超过MTU,从而避免本机发生IP分片。此选项只能出现在同步报文段中,否则将被忽略。 3:窗口扩大因子(4字节,wscale),取值0-14。用来把TCP的窗口的值左移的位数。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。 4:sackOK发送端支持并同意使用SACK选项。, {( N4 H H( }# \" N( V 5:SACK实际工作的选项。- K M8 G7 F# q e6 |/ O 8:时间戳(10字节,TCP Timestamps Option,TSopt)。6 q( T& h4 @# q u1 ^% l 发送端的时间戳(Timestamp Value field,TSval,4字节)。& ^/ ^% e8 b+ g+ [" Y( l ( N/ h; }) ]( |' p# Y0 n+ r 时间戳回显应答(Timestamp Echo Reply field,TSecr,4字节)。 ( H% r6 Y4 z" K& t9 o# ]8 H& o% F Padding(零填充) d. I0 [- x2 A/ T6 x - J" }$ g) E& o TCP头填充用于确保TCP头数据是4字节的整数倍,填充零。1 x& _ }. h8 U6 L7 b " _, ]! \; I1 X. X) V TCP Data(TCP数据段) 剩下就是TCP数据字段了。& u$ a( K: P7 j) h9 a2 \7 g/ j 10.3.3 TCP建立连接 TCP使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的SYN执行ACK确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。TCP三次握手的过程如下:2 t- F" D: W* E9 k5 ^% V 0 p. }5 @( |* R" n l* ~: c( O- @+ [9 w 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。 服务器端收到SYN报文,回应一个SYN(SEQ=y)+ ACK(ACK=x+1)报文,进入SYN_RECV状态。 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。 0 @5 n- a$ z+ n' q9 T% k " ?4 @$ G" x6 l6 s$ o9 x0 Z 三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。" |3 ^! o6 D# l1 p: s* z 4 v* G( o3 e7 h x 10.3.4 TCP数据传输3 \: U$ K" x% @' U% R7 G 在TCP的数据传送状态,很多重要的机制保证了TCP的可靠性和强壮性。它们包括:2 e3 K+ a! i. w9 I l* Z 使用序号,对收到的TCP报文段进行排序以及检测重复的数据。 使用校验和来检测报文段的错误。8 d7 K- ~/ x, N0 |( Y 使用确认和计时器来检测和纠正丢包或延时。 在TCP的连接创建状态,两个主机的TCP层间要交换初始序号(ISN:initial sequence number)。这些序号用于标识字节流中的数据,并且还是对应用层的数据字节进行记数。通常在每个TCP报文段中都有一对序号和确认号。TCP报文发送者认为自己的字节编号为序号,而认为接收者的字节编号为确认号。TCP报文的接收者为了确保可靠性,在接收到一定数量的连续字节流后才发送确认。这是对TCP的一种扩展,通常称为选择确认(Selective Acknowledgement)。选择确认使得TCP接收者可以对乱序到达的数据块进行确认。每一个字节传输过后,ISN号都会递增1。 通过使用序号和确认号,TCP层可以把收到的报文段中的字节按正确的顺序交付给应用层。序号是32位的无符号数,在它增大到2^32 - 1时,便会回绕到0。对于ISN的选择是TCP中关键的一个操作,它可以确保强壮性和安全性。 : Z# k- s& }$ t: w h7 y4 l( M 数据传输举例: 3 { S/ p7 _+ S+ P. |/ `. [5 o ' K+ ^9 e/ T, `2 F 发送方首先发送第一个包含序列号为1(可变化)和1460字节数据的TCP报文段给接收方。接收方以一个没有数据的TCP报文段来回复(只含报头),用确认号1461来表示已完全收到并请求下一个报文段。 发送方然后发送第二个包含序列号为1461和1460字节数据的TCP报文段给接收方。正常情况下,接收方以一个没有数据的TCP报文段来回复,用确认号2921(1461+1460)来表示已完全收到并请求下一个报文段。发送接收这样继续下去。 然而当这些数据包都是相连的情况下,接收方没有必要每一次都回应。比如,它收到第1到5条TCP报文段,只需回应第五条就行了。在例子中第3条TCP报文段被丢失了,所以尽管它收到了第4和第5条,然而它只能回应第2条。 发送方在发送了第3条以后,没能收到回应,因此当时钟超时后,它重发第3条。(每次发送者发送一条TCP报文段后,都会再次启动一次时钟:RTT)。 P( x5 m" I2 e 这次第3条被成功接收,接收方可以直接确认第5条,因为4,5两条已收到。 10.3.5 TCP终止连接 建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。具体过程如下图所示。# f' N G- ?/ e. j5 A& O. ~ 5 u4 U3 W3 x7 V8 e- L; { 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。于是该端的TCP发送一个FIN分节,表示数据发送完毕。 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。 注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。( Y% O5 \0 \% i$ [& w 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。$ l9 e5 ]% C; z* l% a" A4 B 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。 - q" w3 L$ _4 ~ r 既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。 ) N: t* ]# x0 e- k4 n% f$ L 注意: " G& ?/ z% [1 k! I3 @2 S “通常”是指,某些情况下,步骤1的FIN随数据一起发送,另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。 在步骤2与步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的,这称为“半关闭”(half-close)。 当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。' O0 c; g3 t2 o% q& O' ^ 无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭,但是某些协议,例如,HTTP/1.0却由服务器执行主动关闭。* S+ q# @) `$ D" B1 _5 \ 6 S2 w o/ u/ a. r" a+ S 10.3.6 TCP校验和 TCP的16位校验和(checksum)的计算和检验过程如下:发送者将TCP报文段的头部和数据部分的和计算出来,再对其求反码(一的补数),就得到了校验和,然后将结果装入报文中传输。(这里用反码的原因是这种方法的循环进位使校验和可以在16位、32位、64位等情况下的计算结果再叠加后相同。)接收者在收到报文后再按相同的算法计算一次校验和。这里使用的反码使得接收者不用再将校验和字段保存起来后清零,而可以直接将报文段连同校验加总。如果计算结果是全部为一,那么就表示了报文的完整性和正确性。 注意:TCP校验和也包括了96位的伪头部,其中有源地址、目的地址、协议以及TCP的长度。这可以避免报文被错误地路由。, F4 B$ N' S) h$ r 4 h2 G) w8 G L$ `2 r4 Y0 @6 r 10.4 TCP可靠性实现 10.4.1 可靠性& u, V. F- b4 b7 g r8 t# x d TCP提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据包之前必须先建立一个TCP连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。在一个TCP连接中,仅有两方进行彼此通信。广播和多播不能用于TCP。 TCP通过下列方式来提供可靠性: 应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据长度将保持不变。由TCP传递给IP的信息单位称为报文段或段(segment)。 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。当TCP收到发自TCP连接另一端的数据,它将发送一个确认。TCP有延迟确认的功能,在此功能没有打开,则是立即确认。功能打开,则由定时器触发确认时间点。8 X7 c3 v0 N, d6 }. X L* G TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的校验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。 既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。6 ~- _, [/ w/ A, e" O, J- X( U% _ 既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据。3 N% Z( o9 d2 S! X& n- S+ g TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。$ k+ l3 i$ A( s 3 Y+ S8 J3 h& X2 |; f3 R+ U 9 o) l8 _2 |3 Y2 h- Z9 Z c 两个应用程序通过TCP连接交换8bit字节构成的字节流。TCP不在字节流中插入记录标识符。我们将这称为字节流服务(bytestreamservice)。如果一方的应用程序先传10字节,又传20字节,再传50字节,连接的另一方将无法了解发送方每次发送了多少字节。只要自己的接收缓存没有塞满,TCP 接收方将有多少就收多少。一端将字节流放到TCP连接上,同样的字节流将出现在TCP连接的另一端。8 V% [0 B5 m5 \& q& Z6 x $ C G% z3 [6 l" M& R5 j) ~" a 另外,TCP对字节流的内容不作任何解释。TCP不知道传输的数据字节流是二进制数据,还是ASSⅡ字符或者其他类型数据。对字节流的解释由TCP连接双方的应用层解释。2 r, R( S9 @# }: j 10.4.2 重传策略 TCP协议用于控制数据段是否需要重传的依据是设立重发定时器。在发送一个数据段的同时启动一个重传,如果在重传超时前收到确认(Acknowlegement)就关闭该重传,如果重传超时前没有收到确认,则重传该数据段。在选择重发时间的过程中,TCP必须具有自适应性。它需要根据互联网当时的通信情况,给出合适的重发时间。 ' l8 n4 G8 m( m* Z: Q 这种重传策略的关键是对定时器初值的设定。采用较多的算法是Jacobson于1988年提出的一种不断调整超时时间间隔的动态算法。其工作原理是对每条连接TCP都保持一个变量RTT(Round Trip Time),用于存放当前到目的端往返所需要时间最接近的估计值。当发送一个数据段时,同时启动连接的定时器,如果在定时器超时前确认到达,则记录所需要的时间,并修正RTT的值,如果定时器超时前没有收到确认,则将RTT的值增加1倍。通过测量一系列的RTT值,TCP协议可以估算数据包重发前需要等待的时间。在估计该连接所需的当前延迟时通常利用一些统计学的原理和算法(如Karn算法),从而得到TCP重发之前需要等待的时间值。* X$ N4 f1 a4 @) O7 V3 c ) S- E/ g8 z' i3 _9 _4 O" C; E+ Q 10.4.3 窗口确认 TCP的一项功能就是确保每个数据段都能到达目的地。位于目的主机的TCP服务对接受到的数据进行确认,并向源应用程序发送确认信息。3 M- a9 L# b4 S+ K 使用数据报头序列号以及确认号来确认已收到包含在数据段的相关数据。 TCP在发回源设备的数据段中使用确认号,指示接收设备期待接收的下一字节。这个过程称为期待确认。源主机在收到确认消息之前可以传输的数据大小称为窗口大小,用于管理丢失数据和流量控制。0 k4 E; n( i3 k9 X( k# ?9 s; l% | 如下图所示(来自wiki)。3 J# }9 `/ M, E6 t' j 3 Q7 X; P! b. u" I1 T 10.4.4 配置TCP 修改建立TCP连接的超时时间4 c `* f8 v1 q$ \. E2 | 建立TCP连接需要经过三次握手:主动端先发送SYN报文,被动端回应SYN+ACK报文,然后主动端再回应ACK。 在主动端发送SYN后,如果被动端一直不回应SYN+ACK报文,主动端会不断的重传SYN报文直到超过一定的重传次数或超时时间。 / K/ Y. P' D' s6 M" ]7 Q/ j6 y1 D7 C: G; k 在主动端发送SYN后,被动端回应SYN+ACK报文,但主动端不再回复ACK,被动端也会一直重传直到超过一定的重传次数或超时时间。(SYN报文攻击会出现这种情况)9 m. Q! l9 _# d! ? 因此,用户可以配置SYN报文的超时时间(发送SYN报文到三次握手成功的最大时间),也就是建立TCP连接的超时时间,来达到更好的连接效果。( u, N( b3 z) W+ A! E: \* a , C' B4 G8 y3 M+ o 修改缓冲区大小 TCP的接收缓冲区是用来缓存从对端接收到的数据,这些数据后续会被应用程序读取。一般情况下,TCP报文的窗口值反映接收缓冲区的空闲空间的大小。对于带宽比较大、有大批量数据的连接,增大接收缓冲区的大小可以显著提供TCP传输性能。TCP的发送缓冲区是用来缓存应用程序的数据,发送缓冲区的每个字节都有序列号,被应答确认的序列号对应的数据会从发送缓冲区删除掉。增大发送缓冲区可以提高TCP与应用程序的交互能力。但是增大接收和发送缓冲区会导致TCP占用比较多的内存。7 I3 H2 w3 |) ] O; U 2 I- h/ k3 f9 y7 A 10.5 TCP端口号 如果把IP地址比作一间房子 ,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536(即:2^16)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)。 $ Z% F% }% _5 o% z( Z( K 在Internet上,各主机间通过TCP/IP协议发送和接收数据包,各个数据包根据其目的主机的IP地址来进行互联网络中的路由选择,把数据包顺利地传送到目的主机。大多数操作系统都支持多程序(进程)同时运行,那么目的主机应该把接收到的数据包传送给众多同时运行的进程中的哪一个呢?显然这个问题有待解决,端口机制便由此被引入进来。; i, m% m6 l- U* b 10.5.1 端口分类 按照端口号的大小分类,可分为如下几类:6 N/ z1 u6 G# }; E 公认端口(WellKnown Ports):) l9 |4 }" Q5 f2 S+ `1 X) J) ^ 公认端口是众所周知的端口号,范围从0到1023,其中80端口分配给WWW服务,21端口分配给FTP服务等。我们在IE的地址栏里输入一个网址的时候是不必指定端口号的,因为在默认情况下WWW服务的端口是80。- s G$ m" I. n M9 Y 网络服务是可以使用其他端口号的,如果不是默认的端口号则应该在地址栏上指定端口号,方法是在地址后面加上冒号”:”(半角),再加上端口号。比如使用8080作为WWW服务的端口,则需要在地址栏里输入“网址:8080”。2 y; y$ ]) L2 e* s2 f+ N: {# a . I+ V. B( B' {- ? 但是有些系统协议使用固定的端口号,它是不能被改变的,比如139 端口专门用于NetBIOS与TCP/IP之间的通信,不能手动改变。* f, y, r- `, ^2 E8 u. w 注册端口(Registered Ports):4 K/ Y4 Q& G( {1 U 端口1024到49151,分配给用户进程或应用程序。这些进程主要是用户选择安装的一些应用程序,而不是已经分配好了公认端口的常用程序。这些端口在没有被服务器资源占用的时候,可以被用户端动态选用为源端口。) ?& O2 J9 V" h, p T 动态端口(Dynamic Ports): 动态端口的范围是从49152到65535。之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。. h; L4 I3 z. t3 c$ i( h. C 1 ?6 r* W! E/ o3 t, i# G 具体到端口的实际分配情况,可以看如下两个地址:3 W9 z' L5 B0 M8 T6 v# [ Wiki百科中文:地址链接(这个是超链接)。 IANA:地址链接(这个是超链接)。 w: q i. c% O$ M 10.5.2 端口作用, s/ X. F* ^: l1 s; w 我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。需要注意的是,端口并不是一 一对应的。比如你的电脑作为客户机访问一台WWW服务器时,WWW服务器使用80端口与你的电脑通信,但你的电脑则可能使用“3457”这样的端口。 10.6 总结8 y; e& X# ^0 B. S# Y+ J4 w+ v 本章节就为大家讲解这么多,更多TCP的相关知识需要大家查阅相关书籍进行学习,或者网上搜索相关资料进行学习。$ Y5 |) Z. ]5 |9 T5 L8 U; G & C2 ^& x+ ]% c8 z; B8 D# B |
stm32使用定时器触发dma传输,启动dma没反应的几种情况的解决方法
【Wio Lite AI视觉开发套件】+cube.ai与食物识别
【STM32H7S78-DK】汽车仪表系统
【STM32H7S78-DK】基于 rtthread 适配 lcd 驱动移植 lvgl
【STM32H7S78-DK评测】TouchGFX (QR Code)二维码生成器
【STM32H7S78-DK】rtthread 增加 psram 内存管理
【STM32H7S78-DK】开箱与rtthread工程初体验
【STM32H7S78-DK评测】-5 LVGL&DMA2D DEMO测试
【STM32H7S78-DK评测】-4 LTDC&DMA2D 基本测试
【STM32H7S78-DK评测】CoreMark移植和优化--兼记printf重定向实现方法及常见问题