S32K外设Usart

目录

S32DSLPUART

Usart基本概念

S32DS配置LPUART

S32DS添加LPUART库函数

S32DS引脚映射LPUART

S32DS串口相关函数

LPUART_DRV_Init初始化函数

LPUART_DRV_InstallRxCallback串口接收回调函数

LPUART_DRV_SetRxBuffer设置数据存放函数

LPUART_DRV_GetReceiveStatus获取LPUART状态函数函数

memcpy复制数据函数

LPUART_DRV_SendDataBlocking发送阻塞函数

S32DSLPUART

Usart基本概念

S32K144芯片的Usart串口,是低功耗串口,英文单词LPUART0然后是异步通信没有时钟线,然后Usart的区别是带有时钟线,收发双方使用同一根时钟线连接是同步通信,同时S32K144的特别在于芯片进入STOP或者VLPS模式之后,外设LPUART依旧可以工作。

同时LPUART在芯片上有三个分别是LPUART0、LPUART1、LPUART2芯片内置了三个串口模块。

串口初始化流程

LPUART_DRV_Init->LPUART_DRV_InstallCallback->LPUART_DRV_ReceiveData

串口初始化外设 串口回调函数 开启串口数据接受

S32DS配置LPUART

S32DS添加LPUART库函数

首先在S32DS的组件库中添加LPUART库。

这里添加完成之后,在双击对应组件,在组件中配置LPUART的参数具体如下图所示。

然后这个是组件库中的外设实际参数,这里能够选择芯片中LPUART的串口几,然后选择外设的传输方式是中断还是DMA方式,以及配置串口的波特率,同时能够看到串口的实际波特率,具体见下图。

在图形化配置完成之后,这里通过S32DS生成一下代码,具体见下图。

S32DS引脚映射LPUART

这里添加LPUART然后配置基本LPUART基本参数之后,需要打开Pinmux映射一下引脚用来使用LPUART功能,具体见下图。

然后对LPUART1进行引脚映射,选择对应的引脚进行使用。

S32DS串口相关函数

LPUART_DRV_Init初始化函数

这里在LPUART的库中找到初始化函数,拖拽到代码中进行初始化,这里初始化代码中,参数已经根据当前配置自动生成了,具体图片如下所示。

这个是初始化函数,这里填写的参数是生成代码生成的,具体见下函数原型。

cpp 复制代码
status_t LPUART_DRV_Init(uint32_t instance, lpuart_state_t * lpuartStatePtr,
                         const lpuart_user_config_t * lpuartUserConfig)

LPUART_DRV_InstallRxCallback串口接收回调函数

这里函数里面有个RX是代表接收的意思,然后这个函数的作用是,LPUART是配套中断进行使用的,在中断函数里面,调用LPUART_DRV_InstallRxCallback这个接收回调函数用来完成数据帧的读写等操作,同样的还有一个TX发送使用的函数,具体函数见下图。

然后再函数参数中,对应的芯片外设参数是函数自动填写进去的,但是回调函数是仍然需要自己去编写函数原型的,具体函数原型解析如下。

cpp 复制代码
uart_callback_t LPUART_DRV_InstallRxCallback(uint32_t instance,
                                             uart_callback_t function,
                                             void * callbackParam)
{
    DEV_ASSERT(instance < LPUART_INSTANCE_COUNT);

    lpuart_state_t * lpuartState = (lpuart_state_t *)s_lpuartStatePtr[instance];

    uart_callback_t currentCallback = lpuartState->rxCallback;
    lpuartState->rxCallback = function;
    lpuartState->rxCallbackParam = callbackParam;

    return currentCallback;
}

然后这里回调函数的参数类型如下所示。

cpp 复制代码
typedef void (*uart_callback_t)(void *driverState, uart_event_t event, void *userData);

所以编写的入参,类型也必须跟函数声明里面的保持一致。

cpp 复制代码
void Serial_Rx(void *driverState, uart_event_t event, void *userData)
{
	(void)driverState;
	(void)userData;

}

然后再代码中因为,只有event参数有使用到,有两个参数没有进行使用,但是如果没有使用编译器会提示警告,具体如下所示。

warning: unused parameter 'driverState' [-Wunused-parameter]
warning: unused parameter 'userData' [-Wunused-parameter]

然后这里为了消除警告,通过使用强转的方式,将参数转换类型,然后不接收这个数据,但是在这个过程中又使用了数据,从而消除编译器发出的警告。

cpp 复制代码
	(void)driverState;
	(void)userData;

然后回调函数的原型如下所示,在原型中通过判断event的状态查看是否接受数据,然后通过限制BUFF_SIZE的大小保留最后一位,用来接受\0字符串内存结束标识符号,同时在判断到没有到'\n'的时候,继续接受数据将数据继续保存在buff[]中具体函数原型如下。

cpp 复制代码
void Serial_Rx(void *driverState, uart_event_t event, void *userData)
{
	(void)driverState;
	(void)userData;
	if(event == UART_EVENT_RX_FULL)
	{
		if(buff[idx]!='\n' && idx<BUFF_SIZE-2)
		{
			idx++;
			LPUART_DRV_SetRxBuffer(INST_LPUART1,&buff[idx],1);
		}

	}


}

如果使用 BUFF_SIZE - 1(即 254)会发生什么?

我们来推演边界情况(假设一直没有收到 \n):

接收轮次 接收前的 idx 数据存入位置 判断 idx < 254 判断后操作
第 253 轮 252 buff[252] 252 < 254 ✅ idx++ 变为 253,设置下一次收到数据存入 buff[253]
第 254 轮 253 buff[253] 253 < 254 ✅ idx++ 变为 254,设置下一次收到数据存入 buff[254]
第 255 轮 254 buff[254] 254 < 254 ❌ 不递增也不设置下一次接收地址

结果

  • 最后一轮(第 255 次)数据被存入了 buff[254],这是数组的最后一个合法位置

  • 此时 idx 停在了 254,且接收地址没有被重新设置(意味着后续数据不会再进缓冲区)。

问题出在哪里?

当你想把接收到的数据当作 C 语言字符串处理时,通常需要追加一个空字符 '\0'。此时你会这样写:

c

复制代码
buff[idx] = '\0';   // 如果 idx = 254,这一行会写入 buff[254](覆盖掉最后一字节数据)
// 或者
buff[idx + 1] = '\0'; // 如果 idx = 254,这一行会写入 buff[255] —— 数组越界,程序崩溃!

使用 BUFF_SIZE - 2 是如何规避的?

我们再次推演(条件改为 idx < 253):

接收轮次 接收前的 idx 数据存入位置 判断 idx < 253 判断后操作
第 253 轮 252 buff[252] 252 < 253 ✅ idx++ 变为 253,设置下一次存入 buff[253]
第 254 轮 253 buff[253] 253 < 253 ❌ 不递增,不设置

结果

  • 数据最多存到 buff[253]

  • idx 最终停留在 253

  • 此时数组最后一个字节 buff[254] 从头到尾没有被使用过,是完全干净的

LPUART_DRV_SetRxBuffer设置数据存放函数

该函数的作用是告诉驱动,下一次硬件收到数据时,应该把数据存放在哪里,具体截图如下。

然后具体该函数的驱动原型如下,这里三个参数分别填入对应芯片外设,将数据存放的数组,然后长度。

cpp 复制代码
status_t LPUART_DRV_SetRxBuffer(uint32_t instance,
                                uint8_t * rxBuff,
                                uint32_t rxSize)
{
    DEV_ASSERT(instance < LPUART_INSTANCE_COUNT);
    DEV_ASSERT(rxBuff != NULL);
    DEV_ASSERT(rxSize > 0U);

    lpuart_state_t * lpuartState = (lpuart_state_t *)s_lpuartStatePtr[instance];
    lpuartState->rxBuff = rxBuff;
    lpuartState->rxSize = rxSize;

    return STATUS_SUCCESS;
}

然后再调用该函数的过程中,函数会将接收到的数据存放在,函数填写参数的 uint8_t * rxBuff 中来进行保存。

LPUART_DRV_GetReceiveStatus获取LPUART状态函数函数

该函数用来返回RX的接收状态,然后根据当前状态来知道RX具体的情况,具体函数原型如下所示。

|-----------|----------------------------------------------------------------------------------|
| 数作用 | 查询 LPUART 接收状态,并可选获取剩余待接收字节数。 |
| 第二个参数 | 输出参数,返回还有多少字节没收到 。如果接收完成,值为 0。 |
| 返回值 | STATUS_SUCCESS(完成)、STATUS_BUSY(进行中)、STATUS_ERROR(出错)、STATUS_TIMEOUT(超时)。 |

cpp 复制代码
status_t LPUART_DRV_GetReceiveStatus(uint32_t instance,
                                     uint32_t * bytesRemaining)
{
    DEV_ASSERT(instance < LPUART_INSTANCE_COUNT);

    const lpuart_state_t * lpuartState = (lpuart_state_t *)s_lpuartStatePtr[instance];

    if (bytesRemaining != NULL)
    {
        if (lpuartState->isRxBusy)
        {
            /* Fill in the bytes transferred. */
            if (lpuartState->transferType == LPUART_USING_INTERRUPTS)
            {
                /* In interrupt-based communication, the remaining bytes are retrieved
                 * from the state structure
                 */
                *bytesRemaining = lpuartState->rxSize;
            }
#if FEATURE_LPUART_HAS_DMA_ENABLE
            else
            {
                /* In DMA-based communication, the remaining bytes are retrieved
                 * from the current DMA major loop count
                 */
                *bytesRemaining = EDMA_DRV_GetRemainingMajorIterationsCount(lpuartState->rxDMAChannel);
            }
#endif
        }
        else
        {
            *bytesRemaining = 0;
        }
    }

    return lpuartState->receiveStatus;
}

然后具体调用如下图所示。

memcpy复制数据函数

该函数的作用是将参数的数据复制到另一个函数参数中,具体函数原型跟调用如下所示。

cpp 复制代码
_PTR 	 _EXFUN(memcpy,(_PTR __restrict, const _PTR __restrict, size_t));
 memcpy(rx_buff,buff,idx);

然后代码会将buff中的数据转移到rx_buff中去,最后一个参数idx的作用是代表从buff复制到rx_buff中的字节总数,也就是吧buff中的前rdx内容复制到rx_buff中去。

LPUART_DRV_SendDataBlocking发送阻塞函数

然后在代码将buff中的数据复制到rx_buff中之后,通过这个LPUART_DRV_SendDataBlocking函数将数据发送出去,具体函数原型跟具体调用如下所示。

cpp 复制代码
status_t LPUART_DRV_SendDataBlocking(uint32_t instance,
                                     const uint8_t * txBuff,
                                     uint32_t txSize,
                                     uint32_t timeout)
LPUART_DRV_SendDataBlocking(INST_LPUART1,rx_buff,strlen(rx_buff),500);

然后函数的参数作用,具体如下所示。

参数 类型 作用 如何填写
instance uint32_t 指定硬件实例。告诉驱动用哪个 LPUART 外设发送数据。 填写 SDK 定义的宏,例如 INST_LPUART0INST_LPUART1
txBuff const uint8_t * 数据来源指针。指向存放待发送数据的数组或内存区域。 传入一个 uint8_t 类型数组的地址。如果是字符串常量,需要强转:(const uint8_t*)"Hello"
txSize uint32_t 发送长度 。从 txBuff 开始连续发送多少个字节。 必须小于等于实际缓冲区的有效数据长度。常用 strlen((char*)buff) 发送字符串(不含 \0)。
timeout uint32_t 超时阈值(单位:毫秒)。若在此时间内无法将全部数据写入硬件,函数放弃等待并返回错误。 典型值:1001000。若填 0,则尝试发送一次,若硬件忙则立即返回 STATUS_TIMEOUT
相关推荐
星夜夏空992 小时前
STM32单片机学习(24) —— 硬件I2C和软件I2C
stm32·单片机·学习
资深流水灯工程师3 小时前
嵌入式系统中的环形缓冲区:原理、应用与 STM32 实现
网络·stm32·嵌入式硬件
星夜夏空993 小时前
STM32单片机学习(16) —— 中断相关概念
stm32·单片机·学习
余生皆假期-3 小时前
配置 CodeX 环境的 Simlink AI 工具链
笔记·单片机·嵌入式硬件·算法
嵌入式-老费3 小时前
esp开发与应用(1602液晶显示屏)
单片机·嵌入式硬件
嵌入式小站3 小时前
STM32 临界区是什么:为什么有时候要用 __disable_irq() 保护变量
chrome·stm32·嵌入式硬件
leo_jk3 小时前
STM32单片机 空闲中断
stm32·单片机·嵌入式硬件
weyyhdke3 小时前
2026电源与MCU控制设计实战:用Gemini3.5镜像站免费优化开关电源环路与电机FOC算法硬核教程
单片机·嵌入式硬件·算法
星夜夏空993 小时前
STM32单片机学习(22) —— I2C通信协议
stm32·单片机·学习