目录
1.概述
LPUART顾名思义就是低功耗的串口模块,S32K144共有3个低功耗串口子模块,并带有DMA和缓存功能,此外,该模块还支持LIN总线的主从操作,手册上的介绍如下:

该模块不支持低泄露和等待模式,且在VLPS模式下RTS和CTS功能被禁用。
2.模块特点
2.1 基本特点
- 全双工,标准的不归零(NRZ)格式
- 可编程波特率(13位模分频器),可配置4倍至32倍的过采样率
- 传输和接收波特率可以与总线时钟异步运行:
- 波特率可以独立于总线时钟频率进行配置
- 支持在停止模式下运行
- 中断、DMA、轮询操作:
- 传输数据寄存器空或传输完成
- 接收数据寄存器满
- 接收超限、奇偶校验错误、帧错误和噪声错误
- 接收器空闲监测
- 接收引脚上的活动边缘
- 支持LIN的断线检测
- 接收数据匹配
- 硬件奇偶校验生成和检查
- 可编程的7位、8位、9位或10位数据长度
- 可编程的1位或2位停止位
- 三种接收器唤醒方法:
- 空闲线路唤醒
- 地址标记唤醒
- 接受数据唤醒
- 自动地址匹配以减少ISR开销
- 地址标记匹配
- 空闲线路地址匹配
- 地址匹配开始,地址匹配结束
- 可选的 13 位断开字符生成 / 11 位断开字符检测
- 可配置的空闲长度检测,支持 1、2、4、8、16、32、64 或 128 个空闲字符
- 可选择的发送器输出和接收器输入极性
- 硬件流控制支持请求发送(RTS)和允许发送(CTS)信号
- 可选择的 IrDA 1.4 归零反转(RZI)格式,具有可编程脉冲宽度
- 用于发送和接收的独立 FIFO 结构
- 用于接收和发送请求的单独可配置水印
如果接收 FIFO 不为空,在经过可配置数量的空闲字符后,接收器可进行请求确认的选项
2.2 不同模式下的特点
在停止模式期间,只要 CTRL[DOZEEN]位被清除,且异步发送和接收时钟保持启用,LPUART 将保持功能正常。LPUART 可以产生中断或 DMA 请求,以从停止模式中唤醒。
如果 LPUART 在停止模式中被禁用,那么如果接收器检测到一个有效边沿,它可以通过 STAT[RXEDGIF] 标志产生一个唤醒信号。
在调试模式下,LPUART可以正常工作。
2.3 模块框图

注意:写入只读(RO)寄存器或读取只读(WO)寄存器可能会导致总线错误。该模块不会检查寄存器中的编程值是否正确;应用软件必须确保写入有效的编程值。
3 LPUART模块的使用
3.1 模块的配置
LPUART的模块配置比较简单,因为共有三个模块,所以只需要选择使用的模块后配置相关的引脚复用功能、模块波特率、数据位、停止位、校验位后即可使用。配置方法大致如下:
引脚功能的配置,此处有个小窍门,没有必要在左侧的窗口中或者根据数据手册去找具体要配置哪个引脚,只需要在芯片图片处选择功能模块,点击这个功能模块后就可以直接配置可以复用的引脚,如下图:

模块的配置,模块的配置参数较少,这里有一点要注意一下,在传输方式的配置上只能选择中断或者DMA,由于笔者几乎不用DMA模式,此处配置为中断,配置如图:

模块的时钟选择也是可以配置,由几个时钟源可以选择,笔者使用了外部8MHz晶振不分频,如下图:

3.2 SDK中的操作函数
SDK中的操作函数如下:
//用默认值初始化 LPUART 配置结构。
void LPUART_DRV_GetDefaultConfig(lpuart_user_config_t * lpuartUserConfig);
//用户配置参数初始化
status_t LPUART_DRV_Init(uint32_t instance, lpuart_state_t * lpuartStatePtr,
const lpuart_user_config_t * lpuartUserConfig);
//通过禁用中断以及发送器/接收器来关闭LPUART。
status_t LPUART_DRV_Deinit(uint32_t instance);
//为 LPUART 接收安装回调函数。
//在安装回调后,它会绕过 LPUART IRQHandler 逻辑的一部分。因此,回调需要处理 txBuff 的索引和 txSize。
uart_callback_t LPUART_DRV_InstallRxCallback(uint32_t instance,
uart_callback_t function,
void * callbackParam);
//为 LPUART 传输安装回调函数。
//在安装回调后,它会绕过 LPUART IRQHandler 逻辑的一部分。因此,回调需要处理 txBuff 的索引和 txSize。
uart_callback_t LPUART_DRV_InstallTxCallback(uint32_t instance,
uart_callback_t function,
void * callbackParam);
//阻塞方式发送
status_t LPUART_DRV_SendDataBlocking(uint32_t instance,
const uint8_t * txBuff,
uint32_t txSize,
uint32_t timeout);
//轮询方式发送
status_t LPUART_DRV_SendDataPolling(uint32_t instance, const uint8_t *txBuff, uint32_t txSize);
//通过 LPUART 模块使用非阻塞方法将数据发送出去。
//这实现了一种异步的数据传输方法。当与非阻塞接收一起使用时,LPUART 可以执行全双工操作。
//非阻塞意味着该函数会立即返回。
status_t LPUART_DRV_SendData(uint32_t instance,
const uint8_t * txBuff,
uint32_t txSize);
//返回上一次传输是否完成,在 DMA 模式下,此参数可能不准确,如果在调用此函数后立即完成传输;
//在这种极端情况下,由于 DMA 传输描述符中主循环计数的自动重新加载,该参数将反映初始传输大小。
status_t LPUART_DRV_GetTransmitStatus(uint32_t instance, uint32_t * bytesRemaining);
//提前终止非阻塞式传输。
status_t LPUART_DRV_AbortSendingData(uint32_t instance);
//阻塞方式接收
status_t LPUART_DRV_ReceiveDataBlocking(uint32_t instance,
uint8_t * rxBuff,
uint32_t rxSize,
uint32_t timeout);
//轮询方式接收
status_t LPUART_DRV_ReceiveDataPolling(uint32_t instance, uint8_t *rxBuff, uint32_t rxSize);
//通过使用非阻塞方法从 LPUART 模块获取数据。
//这实现了一种异步接收数据的方法。当与非阻塞传输一起使用时,LPUART 可以执行全双工操作。
//非阻塞意味着该函数立即返回。
//应用程序必须获取接收状态以了解接收何时完成。
status_t LPUART_DRV_ReceiveData(uint32_t instance,
uint8_t * rxBuff,
uint32_t rxSize);
//返回前一个接收是否完成
//注意:在 DMA 模式下,如果在调用此函数后立即完成传输,此参数可能不准确;在这种极端情况下,由于 //DMA 传输描述符中主循环计数的自动重新加载,该参数将反映初始传输大小
status_t LPUART_DRV_GetReceiveStatus(uint32_t instance, uint32_t * bytesRemaining);
//提前终止一个非阻塞接收
status_t LPUART_DRV_AbortReceivingData(uint32_t instance);
//设置子模块波特率
status_t LPUART_DRV_SetBaudRate(uint32_t instance, uint32_t desiredBaudRate);
//获取指定子模块的波特率
void LPUART_DRV_GetBaudRate(uint32_t instance, uint32_t * configuredBaudRate);
//设置内部驱动参考为发送缓冲区
status_t LPUART_DRV_SetTxBuffer(uint32_t instance,
const uint8_t * txBuff,
uint32_t txSize);
//设置内部驱动参考为接收缓冲区
status_t LPUART_DRV_SetRxBuffer(uint32_t instance,
uint8_t * rxBuff,
uint32_t rxSize);
在LPUART部分的函数主要由初始化、设置、获取状态、发送数据和接收数据等几类。经过笔者的分析发现,LPUART模块由于官方的SDK并不是将寄存器的设置简单的封装了一下,而是做了一层应用的封装,包括中断和DMA的使用均有所封装,因此为了更好的使用该模块,下面对几个重要函数进行简要说明:
3.2.1 初始化函数
status_t LPUART_DRV_Init(uint32_t instance, lpuart_state_t * lpuartStatePtr,
const lpuart_user_config_t * lpuartUserConfig);
该函数共有三个参数,分别为子模块的索引、状态指针和配置结构体数组,这个索引在配置工具生成代码时会在.h文件中自动生成。使用的时候去.h文件中复制就可以了。笔者研究发现,如果想使用多个LPUART模块,需要在配置工具中启用多个配置,当然这个初始化函数也需要调用多次。
在初始化函数中,如果选择了中断传输,在初始化函数中会对中断进行初始化,并在SDK中也写好了中断处理函数,但是并没有对中断优先级进行配置,所以在使用给串口中断的时候需要注意在初始化后要设置中断优先级。
在这里笔者认为NXP的库有很有趣,它加入了RTOS中的信号量的概念,对于串口的发送接收的SDK函数有很多用了信号量,如果没有使用RTOS,那它是如何实现的呢,原来SDK中提供了一个裸机版本的RTOS接口,当使用LPUART时,SDK会自动添加这个模块,如下图所示。

本文对此接口组件不做深入解读,有关于osif组件的解读会单独开一篇文章。
3.2.2 状态结构体
在SDK中会定义一个结构体类型来表示实时更新LPUART的操作状态,代码如下:
typedef struct
{
const uint8_t * txBuff; /*!< The buffer of data being sent.*/
uint8_t * rxBuff; /*!< The buffer of received data.*/
volatile uint32_t txSize; /*!< The remaining number of bytes to be transmitted. */
volatile uint32_t rxSize; /*!< The remaining number of bytes to be received. */
volatile bool isTxBusy; /*!< True if there is an active transmit.*/
volatile bool isRxBusy; /*!< True if there is an active receive.*/
volatile bool isTxBlocking; /*!< True if transmit is blocking transaction. */
volatile bool isRxBlocking; /*!< True if receive is blocking transaction. */
lpuart_bit_count_per_char_t bitCountPerChar; /*!< number of bits in a char (8/9/10) */
uart_callback_t rxCallback; /*!< Callback to invoke for data receive
Note: when the transmission is interrupt based, the callback
is being called upon receiving a byte; when DMA transmission
is used, the bytes are copied to the rx buffer by the DMA engine
and the callback is called when all the bytes have been transferred. */
void * rxCallbackParam; /*!< Receive callback parameter pointer.*/
uart_callback_t txCallback; /*!< Callback to invoke for data send
Note: when the transmission is interrupt based, the callback
is being called upon sending a byte; when DMA transmission
is used, the bytes are copied to the tx buffer by the DMA engine
and the callback is called when all the bytes have been transferred. */
void * txCallbackParam; /*!< Transmit callback parameter pointer.*/
lpuart_transfer_type_t transferType; /*!< Type of LPUART transfer (interrupt/dma based) */
#if FEATURE_LPUART_HAS_DMA_ENABLE
uint8_t rxDMAChannel; /*!< DMA channel number for DMA-based rx. */
uint8_t txDMAChannel; /*!< DMA channel number for DMA-based tx. */
#endif
semaphore_t rxComplete; /*!< Synchronization object for blocking Rx timeout condition */
semaphore_t txComplete; /*!< Synchronization object for blocking Tx timeout condition */
volatile status_t transmitStatus; /*!< Status of last driver transmit operation */
volatile status_t receiveStatus; /*!< Status of last driver receive operation */
} lpuart_state_t;
配置工具在代码生成时也会自动在peripherals_lpuart_x.h中定义一个状态结构体数据用来保存操作状态。在LPUART模块初始化函数中会对该结构体的实力进行清零,并在函数执行的过程中更新这个结构体变量。
3.2.3 发送数据函数
SDK中提供了3个发送数据函数,下面分别解析这三个函数。
status_t LPUART_DRV_SendDataBlocking(uint32_t instance,
const uint8_t * txBuff,
uint32_t txSize,
uint32_t timeout);
status_t LPUART_DRV_SendDataPolling(uint32_t instance, const uint8_t *txBuff, uint32_t txSize);
status_t LPUART_DRV_SendData(uint32_t instance,
const uint8_t * txBuff,
uint32_t txSize);
第一个是“LPUART_DRV_SendDataBlocking”函数,顾名思义,它是在等待发送完成之后函数才会返回,为了防止程序卡死,在形参中可以设置一个超时时间“timeout”。该函数在发送数据前会等待上一次发送的信号量释放,如果信号量已经释放则启动此次发送,信号量释放的等待时间就是“timeout”。在函数中使用了中断或DMA发送数据。
第二个是“LPUART_DRV_SendDataPolling”函数,该函数是采用轮询方式进行发送。该函数是最常见的发送方式,并没有使用到中断和DMA,它的内部实现就是通过判断发送数据缓冲区的剩余数据以及发送寄存器是否为空来决定是否发送数据,很显然这也是一种阻塞的方式。
第三个是“LPUART_DRV_SendData”函数,该函数是非阻塞方式进行发送,实际上就是通过中断或DMA的方式发送数据,此函数其实就是直接发送了数据。如果操作过快,可能丢失发送数据,需要慎用。