初看YTM32B1ME的手册时,一眼看上去,竟然没有找到UART模块的章节,心想这车规MCU的产品定义也太激进了,直接把工业和消费类微控制器中最常用的通信外设给整没了,但想想手头上的EVB-YTM32B1ME-Q144板子上,确实有通过CH340转接出来的串口接口。一顿上下求索之后,才发现,是用LINFlexD外设模块"兼容"了UART的功能。本文将从LINFlexD外设模块的介绍中,专门抽出同UART相关的内容,结合笔者调试过程中整理的知识点形成一份材料,对YTM32微控制器平台上基于LINFlexD实现UART外设功能的专题详解。
YTM32微控制器平台的LINFlexD外设,是专用于汽车电子系统中常用的LIN通信协议而设计的通信引擎,但因LIN通信协议中的“字节域”格式同UART相同,因此在硬件设计上,从而为LIN通信引擎兼容UART通信引擎提供了可能。实际上,在一些没有专门设计LIN通信引擎的微控制器平台上,大多是通过更常见的UART通信引擎配合软件来模拟LIN通信引擎实现LIN通信的。
当配置LINFlexD的寄存器LINFlexD_UARTCR[UART]=1
之后,LINFlexD变身为UART外设,可以支持常规的UART功能:
9-bit
、13-bit
、16-bit
、17-bit
。当配置LINFlexD的寄存器LINFlexD_UARTCR[UART]=1
之后,LINFlexD变身为UART引擎,LINFlexD模块中,很多LIN引擎专属的寄存器,就不再起作用了,对应地,一些专属UART引擎的寄存器开始发挥作用。当然,还有一些寄存器,是LIN引擎和UART引擎共用的。实际上,当变身成UART引擎后,LINFlexD外设原本众多的寄存器,仅有少数会被用到,如图x所示。
其中,UART功能的配置寄存器LINFlexD_UARTCR
中,设计了配置UART属性的绝大多数寄存器位,当不适用特殊功能(波特率的过采样、超时控制)的情况下,也仅需配置一小部分寄存器位,即可把UART功能用起来。如图x所示。
在寄存器LINFlexD_UARTSR
中,能在UART模式下用到的状态标志位也比较简单,由于实际可用的缓冲区长度仅为1,所以实际能用的标志位也只有两个,分别对应发送缓冲区空和接收缓冲区满。如图x所示。
另外,在寄存器LINFlexD_LINIER
中,还可以通过寄存器位LINFlexD_LINIER[DTIE]
和LINFlexD_LINIER[DRIE]
分别启用对应的中断触发开关。
计算UART波特率的方式同LIN引擎相同。LINFlexD虽然为UART引擎设计了数据缓冲区的FIFO模式,甚至DMA功能,如果用户对应用性能要求不高的情况下,稳妥起见,可使用数据缓冲区的Buffer模式,并配合中断或者轮询方式工作。详见下文有具体阐述。
还要特别注意的是,LINFlexD的“初始化模式”的安全机制仍在起作用,很多配置寄存器仍仅在初始化模式下才能配置,配置完毕后切回正常工作模式,之前的配置才正式生效。
在手册中IPC(IP Controller)章节里,表格Table 13.1: IP Clock Control Table
的备注中,有明确说明,LINFlexD的波特率时钟源来自于FAST_BUS_CLK
。同LIN引擎计算波特率的方式相同,大体上是使用LINFlexD_LINIBRR
和LINFlexD_LINFBRR
两个寄存器作为波特率时钟的分频器,最终算得UART通信过程中的波特率。
如果不使用可编程的过采样功能(即使用固定16位的过采样率),计算UART波特率的功能将非常简单。在绝大多数应用中,固定16位的过采样足够满足用户需求。此时为了获得波特率f_baudrate,可以配置:
若要达到更高的波特率,或者在时钟源频率被降低后,仍要保持较高的波特率,就需要减少过采样点的数量,可以通过配置LINFlexD_UARTCR[ROSE]=1
和LINFlexD_UARTCR[OSR]
,来选择使用8次、6次、5次甚至4次过采样,并且可通过LINFlexD_UARTCR[CSP]
指定采样点的位置。此时,计算波特率的公式就要引入过采样次数OSR
,取代之前固定的16,另外,表示波特率分频因子分数部分的LINFlexD_LINFBBR
寄存器也就不再生效了:
还需要注意的是,在LIN模式下,可以基于LIN通信帧的同步段实现硬件自动同步波特率的功能,在UART模式下也是不生效的(硬件上没有测量的依据)。
LINFlexD为LIN通信设计了8个字节的数据帧缓冲区,刚好够LIN通信的应答数据包存放8字节的数据负载。在UART模式下,也复用了这8个字节的缓冲寄存器,作为UART通信的数据缓冲区。但是,在UART模式下使用这8个字节的数据缓冲区,有一些需要特别注意的地方。
阅读手册可知,使用LINFlexD_DATA[]
缓冲区有两种模式,字面上称为FIFO
和Buffer
模式,可通过寄存器LINFlexD_UARTCR[TDFL_TFC]
配置。当LINFlexD变身为UART后,这个8字节缓冲区就变成了两个4字节的缓冲区,分别对应发送缓冲区和接收缓冲区。如图x所示。
值得注意的是,这里4字节的缓冲区,其实就是1个常规的32位寄存器,这样看起来,就同一般普通的UART数据寄存器没差别了。但这里还额外设计了一个LINFlexD_UARTCR[TDLF_TFC]
字段,配置其中有效的数据长度(8字节、16字节或是32字节),这相当于指定了将发送数据寄存器从低位开始串行送上总线的时钟脉冲数量,或者说设定了当使用32位数据寄存器时,有效的数据段长度。
当配置为FIFO模式时,锁死了可配置的数据长度为1字节,然后由硬件利用这块存储空间实现了一个先入先出的机制,可以从DATA0这个入口一直“压弹”。这种固定数据入口地址的机制比较适合同DMA搭配使用,但如果是多字节的FIFO,还需要设计可产生DMA触发的阈值。但实际上,此时可以利用的LINFlexD_UARTCR[TDLF_TFC]
字段,竟然变成了只读属性,而并不是配置触发阈值。这样算下来,届时能触发DMA的,只有缓冲区空这个事件了,并可约定每次触发连续搬运4个字节(直接将缓冲区填满)并要小心处理最后小于4个字节的部分(需要人为计算最后实际要搬运的数据量),或者不厌其烦地让DMA每次仅搬运一个数,相对于有FIFO数量阈值提前触发的方式,这样都有“断线”的风险。但对于小传输负载的应用,目前实现的简单的FIFO(相当于还是Buffer)已经足够应对了。
仅当LINFlexD_UARTCR[TxEN]=1
时,UART引擎才能发送数据。然后,通过向寄存器BRD0(DATA0)写数,触发传输过程。如果一次要传输多个字节,则需要先写寄存器BRD1-BRD3(需要提前配置好LINFlexD_UARTCR[TDLF_TFC]
,激活可以发送更多的数据位),最后写寄存器BRD0以启动传输。
发送缓冲区中所有的数据发送完毕后,发送数据缓冲区被清空,LINFlexD_UARTSR[DTF]
标志位由硬件自动置位。
但需要注意是,再次向发送数据缓冲区中写数,并不能自动清零LINFlexD_UARTSR[DTF]
标志位。在写新数之前,需要人工清发送完成标志位。如此看来,这个标志位并不是缓冲区的状态标志位,而是事件的标志位(类似于LIN通信帧的管理机制)。
在手册中有限的描述中,也未细分数据从缓冲区送至发送数据的串行移位器之间的时序(通常人为发送缓冲区空的时候就可以向其中送入新数,而不必等移位器把数移到总线之后再启动新的写数过程),这可能会在实际传输过程中插入很多间隙,从而损失传输效率。
当发送缓冲区为FIFO模式时,写数入口固定为BRD0寄存器,并且固定写数的长度为1字节。根据手册描述,此时,当FIFO被数据填满后,LINFlexD_UARTSR[DTF]
表示发送缓冲区满???这个功能仅作为调试功能使用。如果是这样,笔者心中不免冒出一个疑问,当发送FIFO中的数据即将清空之际,用什么信号触发DMA搬运新数补充FIFO呢?
The LINFlexD uses a single transmit DMA channel (0) to move data from RAM to the UART transmit buffer. To enable DMA for UART transmit mode, both DMATXE[0] and input signal dma_enb_req_tx[0] must be asserted.
In response to assertion of ipd_req_tx[0], the DMA controller transfers up to 4 bytes or 2 halfwords from RAM to the UART transmit FIFO through successive writes to the DATA0~DATA3 registers.
In the cycle after the data phase of the last write for the transfer, the DMA controller must assert both dma_done_lw_tx[n] and dma_ipd_complete_tx[n] to acknowledge the DMA request.
FIFO配合轮询和中断模式,可用的触发事件和标志位略显单薄,FIFO模式在设计之初似乎就是给DMA使用的。
接收过程同发送过程类似。仅当LINFlexD_UARTCR[RxEN]=1
时,UART引擎才能监控总线上的信号,一旦检测到START信号就开始启动接收过程,向接收缓冲区中送数。当接收缓冲区中有数后,标志位LINFlexD_UARTSR[DRF_RFE]
起来,软件要么用中断,要么用轮询,赶紧把接收缓冲区中的数读走,否则新数来了之后会触发缓冲区溢出错误的同时会丢数(根据LINFlexD_LINCR1[RBLM]
配置确定覆盖旧数还是抛弃新数)。
接收缓冲区寄存器是从LINFlexD_DATA[4]
开始的。另外,接收缓冲区同发送缓冲区一样,也有数据缓冲区长度的说法。发送过程的节奏是由MCU自己控制的,所以设置多少,只要符合自己需要,就都可以。但接收缓冲区的长度是不好确定的,其接收过程是由外部的发送方决定的。因此,建议的做法是,在约定使用8位数据格式的情况下,始终将接收缓冲区的有效长度设置为1字节(如果约定基本的通信单元是16位,也可以设置数据长度为2字节),就是收到一个单元的数据就直接触发接收事件。
将接收缓冲区中的数读走之后,硬件也不会自动清零标志位LINFlexD_UARTSR[DRF_RFE]
,也需要软件清零,这个要注意一下。
另外,关于FIFO模式和DMA,与发送过程相似,尚待笔者亲自验证。
YTMicro-SDK中设计了linflexd_uart
驱动,但并未提供专门的uart应用样例(轮询收发、中断收发、DMA收发),可通过hello_world
工程了解 linflexd_uart
驱动的用法。
YTM32B1ME0x_RM.pdf
YTM32B1Mx_SDK_RTM_1.0.4.zip