【工具使用】STM32CubeMX-Uart配置 及 数据收发功能实现

news2024/10/7 13:22:24

一、概述

    无论是新手还是大佬,基于STM32单片机的开发,使用STM32CubeMX都是可以极大提升开发效率的,并且其界面化的开发,也大大降低了新手对STM32单片机的开发门槛。
    本文主要讲述STM32芯片的Uart的配置及其相关知识。Uart因为其协议简洁及使用便捷,算是单片机中,除了GPIO这个外设外,出镜率最高的一个外设了。接下来就来看看如何使用STM32CubeMX初窥门径。

二、软件说明

    STM32CubeMX是ST官方出的一款针对ST的MCU/MPU跨平台的图形化工具,支持在Linux、MacOS、Window系统下开发,其对接的底层接口是HAL库,另外习惯于寄存器开发的同学们,也可以使用LL库。STM32CubeMX除了集成MCU/MPU的硬件抽象层,另外还集成了像RTOS,文件系统,USB,网络,显示,嵌入式AI等中间件,这样开发者就能够很轻松的完成MCU/MPU的底层驱动的配置,留出更多精力开发上层功能逻辑,能够更进一步提高了嵌入式开发效率。
    演示版本 6.7.0

三、Uart简介

    通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,其具体协议及内容,在我的另一篇文章有详细说明,这里不再赘述。这里主要讲下STM32中Uart的特点。现在我们以G0系列的Uart为例(STM32系列的单片机Uart大同小异,掌握一个就相当于所有都会了),查看手册找到对应的原理框图。这里我们先重点关注发送和接收这两部分及一些通用寄存器。
在这里插入图片描述

  • 发送(TDR,TxFIFO,Tx Shift Reg)

    TDR:发送数据寄存器,因为是在COM Controller中,我们可以直接操作的。在初始化好Uart的情况下,只要往这个寄存器里填充一个字节的数据,硬件会自动将此数据发送出去。
    TxFIFO:发送队列,当开启发送队列功能时,往TDR里填充数据时,硬件会将TDR里的数据移至TxFIFO中。
    Tx Shift Reg:发送移位寄存器,未开启TxFIFO功能时,如果TDR里有数据,则会先把数据拷贝到发送移位寄存器里,再根据设置的波特率一个字节一个字节往外发。

完整的发送流程(无FIFO)
1、检查当前TXE标志是否为1,为1说明当前TDR是空的,可以填入数据。
2、TXE(发送寄存器为空)标志为1,往TDR写入一个字节的数据,此时硬件会将TXE标志清0。
3、如果此时Tx Shift Reg是空的,硬件则将TDR中的数据拷贝到Tx Shift Reg中,并且将TXE标志置1;否则等到Tx Shift Reg为空再拷贝。
4、硬件根据配置的波特率、数据位、停止位、校验位,将数据包装成Uart协议的形式,一个位一个位地往外发(先发高位,再发低位)。
5、当Tx Shift Reg发送完最后一个停止位时,由硬件将TC(发送完成)标志置1。
在这里插入图片描述

  • 接收(RDR,RxFIFO,Rx Shift Reg)

    RDR:接收数据寄存器,因为是在COM Controller中,我们可以直接进行读取操作。
    RxFIFO:接收队列,当开启发送队列功能时,往TDR里填充数据时,硬件会将TDR里的数据移至TxFIFO中。
    Rx Shift Reg:接收移位寄存器,当外部有符合Uart协议的数据过来时,由Rx Shift Reg根据当前串口配置一位一位地接收数据。当接收了完整的一个字节数据后(包括起始位,停止位等),会把数据部分拷贝至RDR中。

完整的接收流程(无FIFO)
1、Rx Shift Reg根据当前波特率、数据位、停止位、校验位的配置按位接收数据。
2、当接收完一个完整的字节数据(包括起始位、停止位这些)时,硬件会把数据部分(不包括起始、停止、检验位)拷贝至RDR,并将RXNE标志位置1。
3、查询当前RXNE(接收寄存器非空)标志为1时,读取RDR的数据,将RDR数据读取时,硬件会将RXNE标志清0。
在这里插入图片描述

四、功能配置

1、在"Connectivity"选项中找到USART(有些是UART),在右边模式配置框里选择"Asynchronous",意思是异步的,即只需要一收一发两根通信线即可,如果选择"Synchronous"则是同步串口,需要多一个时钟线。其他的就不多讲,主要看下最常用的双线异步串口配置。
在这里插入图片描述
2、对串口的基础功能进行配置,正常默认配置即可,一般就改个波特率就行。
在这里插入图片描述
波特率(Baud Rate):通信速率,常用是9600,意思是1s传输9600个位。
数据位(Word Length):一个字节中携带的数据位数,一般是8位,ASCII可以只用7位。
校验位(Parity):奇偶检验,用来检验传输的数据是否有误,一般配置为无校验位。
停止位(Stop Bits):串口协议中末尾的停止位位数,一般配置为1个停止位。
数据传输方向(Data Direction):一般收发都要,所以这里配置接收和发送(Receive and Transmit)。
过采样(Over Sampling):有8倍采样和16倍采样两种,8倍采样,也就是一个数据位采样8次,16倍则是16次。采样率高精度会高一些,当然相应的功耗也会变高。
单次采样(Single Sample):使能时使用单次采样值,否则使用三次采样值。前面的过采样会有8或16次采样值,当选择单次采样时,会使用其中的一次采样值作为数据位逻辑电平的结果。三次采样则用三次采样的判断结果为准。同样三次采样也是为了确保数据的准确性。
自动波特率(Auto Baudrate):顾名思义,可以根据接收到的数据进行波特率自适应,即使用一个115200波特率的串口给当前这个串口发送数据,这个串口可以识别该波特率并调整自身波特率与之相对应。但使用这个功能是有个前提的,就是必须规定好第一个字节的数据,有01等几个数据选项,不同数据和不同波特率下,自适应的准确率有所不同,具体看手册有详细说明。
发送电平反转(TX Pin Active Level Inversion):发送引脚电平极性反转,正常情况下空闲电平为高电平,使能该功能后变成空闲电平为低电平。
接收电平反转(RX Pin Active Level Inversion):接收引脚电平极性反转,正常情况下空闲电平为高电平,使能该功能后变成空闲电平为低电平。
数据电平反转(Data Inversion):收发数据的逻辑电平极性反转,正常是高电平为逻辑1,低电平为逻辑0,使能该功能后则变成低电平为逻辑1,高电平为逻辑0。校验位也随着反转。
收发引脚互换(TX and RX Pins Swapping):接收和发送引脚互换,适用于外部硬件连线错误时进行切换。
溢出检测(Overrun):用于开启接收溢出检测,使用该功能后,当接收数据未取出时,又接收到一个数据,此时会触发一个溢出标志。
接收错误时不禁用DMA(DMA on RX Error):使能该功能后,即使出现接收错误也不会关闭DMA传输。
数据高位先发(MSB First):正常数据是先发低位再发高位,使能该功能后可以先发高位数据。

3、选择对应的端口,开启串口功能后,工具会默认配置一对收发引脚,最好根据自己的板子确认是否是这对串口,如果不是,则需要手动修改端口。
在这里插入图片描述
PA9和PA10对应开发板这边的D8和D2口。
在这里插入图片描述

4、如果需要使用中断来进行收发,则点击"NVIC Setting",使能对应的中断。
在这里插入图片描述
5、在"Project Manager"界面中,选择"Advanced Setting",找到USART,选择对应生成的库,串口是可以选择生成HAL库或LL库的。最后点击"GENERATE CODE"生成工程代码即完成配置。
在这里插入图片描述

五、功能实现

    前面配置完生成的工程,已经实现了串口的初始化,接下来要实现功能逻辑,还需要调用相应的接口。

  • 阻塞收发-HAL库

    重点关注HAL_UART_Transmit和HAL_UART_Receive这两个函数。两者均为阻塞接口,即当调用发送接口时,需要等到所有数据发送完成才会退出此接口,接收则是查询接收的数据,直到数据接收完成或超时。如果有需要同时进行收发的,可以调用HAL_USART_TransmitReceive这个接口。

uint8_t RecvData[100];
/* 收到数据回显 */
if (HAL_OK == HAL_UART_Receive(&huart1, RecvData, 9, 1000))
{
    HAL_UART_Transmit(&huart1, RecvData, 9, 1000);
}

用串口调试助手试下效果,发送"abcdefg\r\n",会回复"abcdefg\r\n"。
在这里插入图片描述
    这个接口其实挺鸡肋的,因为接收了多少个字节接口并没有给你反馈,所以就出现了一种尴尬的情况,只有当你接收字节数刚好为你传入的字节数时,接口才会给你返回OK状态,否则,正常情况下,都是返回Timeout。

  • 阻塞收发-LL库

    首先在工程配置中把串口生成库改成LL库。
在这里插入图片描述

    仿造HAL库的实现,自己实现一个简易版的发送和接收接口。先梳理一下收发接口的基本流程(省略保护相关的操作)。

  • 发送接口的发送流程

在这里插入图片描述

  • 接收接口的接收流程

在这里插入图片描述

使用LL库仿造HAL库的实现,这里接收接口我们稍微改造一下,返回接收的字节数。

/*******************************************************************************
*  函数名称      :   uint8_t Uart_Send(void *pt,
*  描     述   :   发送函数
*  输     入   :   void *pt,          // 结构块实体
                   uint8_t *data,     // 发送的数据缓存
                   uint32_t len,      // 发送的数据长度
                   uint32_t timeout   // 超时时间
*  返     回   :   1-成功 0-失败
*  作     者   :   Chewie
*  时     间   :   2023.03.20  22:28:21
*******************************************************************************/
uint8_t Uart_Send(void *pt,
                    uint8_t *data,
                    uint32_t len,
                    uint32_t timeout)
{
    uint32_t i = 0;

    /* 获取当前计数值,用于计算超时时间 */
    uint32_t tickstart = HAL_GetTick();
    
    /* 按需要发送的字节数循环发送 */
    while (len--)
    {
        /* 超时时间到,则认为发送失败 */
        if ((HAL_GetTick() - tickstart) >= timeout)
        {
            return 0;
        }
        /* 当TXE寄存器为1时,可以往发送寄存器里填值 */
        while (!LL_USART_IsActiveFlag_TXE((USART_TypeDef *)pt))
        {
            /* 超时时间到,则认为发送失败 */
            if ((HAL_GetTick() - tickstart) >= timeout)
            {
                return 0;
            }
        }
        LL_USART_TransmitData8((USART_TypeDef *)pt, data[i++]);
    }
    /* 获取当前计数值,用于计算超时时间 */
    tickstart = HAL_GetTick();

    /* 等待发送完成--TC标志位为1 */
    while (!LL_USART_IsActiveFlag_TC((USART_TypeDef *)pt))
    {
        /* 超时时间到,则认为发送失败 */
        if ((HAL_GetTick() - tickstart) >= timeout)
        {
            return 0;
        }
    }
    
    return 1;
}
                    
/*******************************************************************************
*  函数名称      :   uint8_t Uart_Recv(void *pt,
*  描     述   :   接收函数
*  输     入   :   void *pt,          // 结构块实体
                   uint8_t *data,     // 接收的数据缓存
                   uint32_t max_len,	// 可接收的最大字节数
                   uint32_t *len,     // 接收的数据长度
                   uint32_t timeout   // 超时时间
*  返     回   :   1-成功 0-失败
*  作     者   :   Chewie
*  时     间   :   2023.03.20  22:33:26
*******************************************************************************/
uint8_t Uart_Recv(void *pt,
                    uint8_t *data,
                    uint32_t max_len,
                    uint32_t *len,
                    uint32_t timeout)
{
    *len = 0;
    
    /* 获取当前计数值,用于计算超时时间 */
    uint32_t tickstart = HAL_GetTick();
    
    /* 按需要发送的字节数循环发送 */
    while (max_len--)
    {
        /* 超时时间到,则认为发送失败 */
        if ((HAL_GetTick() - tickstart) >= timeout)
        {
            return 0;
        }
        /* 当RXNE寄存器为1时,可以获取接收寄存器里的值 */
        while (!LL_USART_IsActiveFlag_RXNE((USART_TypeDef *)pt))
        {
            /* 超时时间到,则认为发送失败 */
            if ((HAL_GetTick() - tickstart) >= timeout)
            {
                return 0;
            }
        }
        data[(*len)++] = LL_USART_ReceiveData8((USART_TypeDef *)pt);
    }
    
    return 1;
}

/* 应用代码 */
int main()
{
	uint8_t RecvData[255];
	uint32_t recv_len = 0;
	...
	while (1)
	{
		/* 收到数据回显 */
		Uart_Recv(USART1, RecvData, sizeof(RecvData), &recv_len, 1000);
		if (0 != recv_len)
		{
		    Uart_Send(USART1, RecvData, recv_len, 1000);
		}
	}
}

    这样子的一个收发接口基本就可以满足基本的应用了。
在这里插入图片描述

    接收使用查询的方式来接收数据,容易漏数据。且在裸机情况下,使用这接口发送数据,需要在这里等待,无法执行其他任务,如果有多个串口阻塞的情况会更严重。那么有没有什么办法可以提高通信的实时性又不影响其他任务的运行呢?除了使用操作系统外,还可以借用单片机本身一个很重要的功能——中断。下面我们来看下中断的实现方式。

  • 中断收发-HAL库

    这里我们使用HAL库的另外两个读写接口,HAL_UART_Transmit_IT和HAL_UART_Receive_IT。只需要调用这两个接口,中断会帮我们执行剩余的收发工作。为了可以实时知道接收、发送完成的状态,我们再开启回调函数注册的功能,所以还得再用到一个回调函数注册接口HAL_UART_RegisterCallback。
    首先先打开串口中断,再使能串口的回调注册(理论上这里用到USART就打开USART,用到UART就打开UART)。
在这里插入图片描述
    同样,实现一个简单的字符串回显功能。

/* 发送完成回调,发送完就启动接收 */
void UartTxCallback(UART_HandleTypeDef *huart1)
{
    HAL_UART_Receive_IT(huart1, RecvData, 9);
}

/* 接收完成回调,接收完就启动发送 */
void UartRxCallback(UART_HandleTypeDef *huart1)
{
    HAL_UART_Transmit_IT(huart1, RecvData, 9);
}

int main(void)
{

	/* 初始化部分省略 */
	......
	
	/* 注册发送完成回调函数 */
	HAL_UART_RegisterCallback(&huart1, HAL_UART_TX_COMPLETE_CB_ID, UartTxCallback);
	/* 注册接收完成回调函数 */
	HAL_UART_RegisterCallback(&huart1, HAL_UART_RX_COMPLETE_CB_ID, UartRxCallback);

	/* 接收启动 */
	HAL_UART_Receive_IT(&huart1, RecvData, 9);
	while (1)
	{
		......
	}
}

在这里插入图片描述
    跟前面非中断形式的接口一样的问题,这里的接收接口没有反馈接收到的字节数,所以目前这种写法必须是知道需要接收多少个字节,就设置接收多少个字节,对于不定长的数据就没办法处理。

  • 中断收发-LL库

    LL库的实现,我们依葫芦画瓢,模仿HAL库的实现。先把HAL实现的基础流程图画一下。

  • 发送接口的发送流程

在这里插入图片描述

  • 接收接口的接收流程
    在这里插入图片描述

  • 中断执行的流程

在这里插入图片描述

    使用LL库仿造HAL库的实现,同样的,这里对接收回调接口进行改造,使其在接收完成回调时,传回接收到的字节数量。因为现在是不定长接收数据,所以需要把接收完成的判断机制从原本的字节数判断修改为时间超时判断。

struct tagUartRxTxCB
{
    USART_TypeDef *Uart;
    uint8_t *Data;
    uint32_t RxMaxLen;
    uint32_t RxCnt;
    uint32_t StartRx;
    uint32_t Timeout;
    uint32_t TxLen;
    uint32_t TxCnt;
    uint32_t TimeoutCnt;

    void (*RxCallback)(void *);
    void (*TxCallback)(void *);
};

/* 实例化 */
struct tagUartRxTxCB UartCB = 
{
    .Uart = USART1,
};

/* 回调类型 */
enum emUartRegCallbackType
{
    UART_REGCB_TYPE_rx_complete = 0,
    UART_REGCB_TYPE_tx_complete = 1,
};


/*******************************************************************************
*  函数名称      :   void Uart_RegisterCallback(void *pt,
*  描     述   :   注册回调函数
*  输     入   :   void *pt,                          // 模块结构块
                 enum emUartRegCallbackType type,   // 回调类型
                 void (*callback)(void *)           // 回调函数
*  返     回   :   无
*  作     者   :   Chewie
*  时     间   :   2023.05.01  23:52:49
*******************************************************************************/
void Uart_RegisterCallback(void *pt,
                                    enum emUartRegCallbackType type,
                                    void (*callback)(void *))
{
    struct tagUartRxTxCB *cb = (struct tagUartRxTxCB *)pt;
    
    switch (type)
    {
        case UART_REGCB_TYPE_rx_complete:
        {
            cb->RxCallback = callback;
            break;
        }
        case UART_REGCB_TYPE_tx_complete:
        {
            cb->TxCallback = callback;
            break;
        }
        default:
            break;
    }
}


/*******************************************************************************
*  函数名称      :   void Uart_RecvStart(void *pt,
*  描     述   :   开始接收
*  输     入   :   void *pt,              // 模块结构块
                 uint8_t *data,         // 接收的数据缓存
                 uint32_t max_len,      // 一次接收的最大字节数
                 uint32_t timeout       // 接收超时时间
*  返     回   :   无
*  作     者   :   Chewie
*  时     间   :   2023.05.01  23:53:55
*******************************************************************************/
void Uart_RecvStart(void *pt,
                            uint8_t *data,
                            uint32_t max_len,
                            uint32_t timeout)
{
    struct tagUartRxTxCB *cb = (struct tagUartRxTxCB *)pt;
    
    /* 记录需要接收的最大字节数 */
    cb->RxMaxLen = max_len;
    cb->Data = data;

    /* 记录超时时间 */
    cb->Timeout = timeout;

    /* 清空计数 */
    cb->RxCnt = 0;

    /* 使能接收中断 */
    LL_USART_EnableIT_RXNE(cb->Uart);
}

/*******************************************************************************
*  函数名称      :   void Uart_SendStart(void *pt,
*  描     述   :   开始发送
*  输     入   :   void *pt,              // 模块结构块
                 uint8_t *data,         // 发送数据缓存
                 uint32_t send_len      // 发送数据字节数
*  返     回   :   无
*  作     者   :   Chewie
*  时     间   :   2023.05.01  23:55:16
*******************************************************************************/
void Uart_SendStart(void *pt,
                            uint8_t *data,
                            uint32_t send_len)
{
    struct tagUartRxTxCB *cb = (struct tagUartRxTxCB *)pt;
    
    /* 记录需要发送的数据字节数 */
    cb->TxLen = send_len;

    /* 清空计数 */
    cb->TxCnt = 0;

    /* 使能TXEIE */
    LL_USART_EnableIT_TXE(cb->Uart);

    /* 使能TCIE--使能前要清一次TC,主要是第一次使能Uart时会产生一个TC */
    LL_USART_ClearFlag_TC(cb->Uart);
    LL_USART_EnableIT_TC(cb->Uart);
}

/*******************************************************************************
*  函数名称      :   void UartRxIrq(void *pt)
*  描     述   :   接收中断处理函数
*  输     入   :   模块结构块
*  返     回   :   无
*  作     者   :   Chewie
*  时     间   :   2023.05.01  23:56:26
*******************************************************************************/
void UartRxIrq(void *pt)
{
    struct tagUartRxTxCB *cb = (struct tagUartRxTxCB *)pt;

    /* 进一次RXNE中断接收一次数据 */
    if (   (LL_USART_IsEnabledIT_RXNE(cb->Uart))
        && (LL_USART_IsActiveFlag_RXNE(cb->Uart))
        )
    {
        /* 刷新接收超时时间 */
        cb->TimeoutCnt = HAL_GetTick();

        /* 超时时间有效 */
        cb->StartRx = 1;

        /* 接收数据 */
        cb->Data[cb->RxCnt++] = LL_USART_ReceiveData8(cb->Uart);

        /* 接收满对应字节数,则回调接收完成函数 */
        if (cb->RxCnt == cb->RxMaxLen)
        {
            cb->RxCallback(cb);
            cb->StartRx = 0;
        }
    }
}

/*******************************************************************************
*  函数名称      :   void UartTxIrq(void *pt)
*  描     述   :   发送中断处理函数
*  输     入   :   模块结构块
*  返     回   :   无
*  作     者   :   Chewie
*  时     间   :   2023.05.01  23:56:49
*******************************************************************************/
void UartTxIrq(void *pt)
{
    struct tagUartRxTxCB *cb = (struct tagUartRxTxCB *)pt;

    /* TC为1时触发的中断,则重新启动接收 */
    if (   (LL_USART_IsEnabledIT_TC(cb->Uart))
        && (LL_USART_IsActiveFlag_TC(cb->Uart))
        )
    {
        /* 清除标志 */
        LL_USART_ClearFlag_TC(cb->Uart);

        /* 发送完成回调 */
        cb->TxCallback(cb);
        return ;
    }

    /* TXE为1时触发的中断,则发送数据 */
    if (   (LL_USART_IsEnabledIT_TXE(cb->Uart))
        && (LL_USART_IsActiveFlag_TXE(cb->Uart))
        )
    {
        LL_USART_TransmitData8(cb->Uart, cb->Data[cb->TxCnt++]);
    }
    
    /* 剩余发送字节数为0,则说明发送完,关闭TXEIE防止再次进TXE中断 */
    if (cb->TxCnt == cb->TxLen)
    {
        /* 禁止TXEIE */
        LL_USART_DisableIT_TXE(cb->Uart);
    }
}

/*******************************************************************************
*  函数名称      :   void UartTxCallback(void *pt)
*  描     述   :   发送完成回调函数
*  输     入   :   模块结构块
*  返     回   :   无
*  作     者   :   Chewie
*  时     间   :   2023.05.01  23:57:09
*******************************************************************************/
void UartTxCallback(void *pt)
{
    Uart_RecvStart(pt, RecvData, sizeof(RecvData), 5);
}

/*******************************************************************************
*  函数名称      :   void UartRxCallback(void *pt)
*  描     述   :   接收完成回调函数
*  输     入   :   模块结构块
*  返     回   :   无
*  作     者   :   Chewie
*  时     间   :   2023.05.01  23:57:23
*******************************************************************************/
void UartRxCallback(void *pt)
{
    struct tagUartRxTxCB *cb = (struct tagUartRxTxCB *)pt;
    
    Uart_SendStart(pt, RecvData, cb->RxCnt);
}

int main(void)
{
    ......
	Uart_RegisterCallback(&UartCB, UART_REGCB_TYPE_rx_complete, UartRxCallback);
    Uart_RegisterCallback(&UartCB, UART_REGCB_TYPE_tx_complete, UartTxCallback);
    Uart_RecvStart(&UartCB, RecvData, sizeof(RecvData), 5);
    while (1)
    {
		/* 超时时间到,则认为接收完成 */
        if (   ((HAL_GetTick() - UartCB.TimeoutCnt) >= UartCB.Timeout)
            && (UartCB.StartRx == 1)
            )
        {
            UartCB.StartRx = 0;
            UartCB.RxCallback(&UartCB);
        }
	}
}

/***********************stm32f0xx_it.c**************************/
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  UartRxIrq(&UartCB);
  UartTxIrq(&UartCB);

  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

    目前这种实现方式用在一收一发的场景下,如果需要收发全双工进行,那就需要自己举一反三了。其实目前这种形式的收发过程还是需要内核来介入,有没有一种办法可以减少内核的介入时间呢?当然有,这就是DMA。使用DMA涉及其他配置,这里就先留个坑,在DMA篇里再细说Uart+DMA的配置。

六、注意事项

1、如果对外使用的是RS485接口,需要注意收发引脚的转换时序,一般情况下都为接收状态,只有在要发送前切发送状态,发送完成后切接收状态(至少需要在触发TC标志后再切换,否则会丢数据)。485总线上不可同时存在两个设备及以上处于发送状态,否则会导致总线电平被拉低,无法正常接收数据。
2、当单片机主频较高时,配置低波特率时有可能波特率设置的位数不够(大部分设置波特率的寄存器只有16位),应用手册里的公式用串口的时钟频率/波特率后的值可能会超过其寄存器可设置范围,此时可以通过给串口分频以设置更低的波特率。如果使用STM32CubeMX进行配置时,工具会自动限制最低波特率,使用HAL库的设置接口也会有限制,使用LL库自己实现的设置接口就得注意一下了。
3、ST串口里设置的数据位是包含校验位的,比如设置数据位为8位,并且设置了奇偶校验,则实际有效数据只有7位,剩下一个位被奇偶校验位占用。而像Modbus Poll这种工具,设置的数据位则不包含校验位。

七、相关链接

【知识分享】异步串行收发器Uart(串口)-通信协议详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/482921.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu使用gzip与bzip2与rar和tar压缩解压

目录 1、gzip压缩解压 2、bzip2压缩解压 3、rar压缩解压 tar归档压缩与解压包 1、gzip压缩解压 因为都是系统自带的我们不需要安装; ls 看一下目录当前的文件,创建一个 touch 123.txt 文件; gzip 进行压缩,(“ …

【BMS】电池能量管理:充电管理

🔋 电池包能量管理:充电管理 🔋 一、通信 充电是一个过程,需要电池包和充电器间进行频繁的通信,以保证电池安全和寿命。由于通信速率要求不高,以及精简接口的考虑,通常采用GPIO自定义单线通信…

Redis高级——批处理优化

2、批处理优化 2.1、Pipeline 2.1.1、我们的客户端与redis服务器是这样交互的 单个命令的执行流程 N条命令的执行流程 redis处理指令是很快的,主要花费的时候在于网络传输。于是乎很容易想到将多条指令批量的传输给redis 2.1.2、MSet Redis提供了很多Mxxx这样的…

【vueCms】vueCms_xg后台管理系统(开源)

我的开源项目地址:vueCms_xg 🎮在线体验 开发文档:制作中后台地址:vueCms_xg(域名审核中) ⚡️ 简介 一个开箱即用,前端基于 vite 2 vue 3 typeScript element Plus pinia vue-router 4 的PC端项目模板。 后端由nestjs构…

浅谈osgViewer::StatsHandler、osg::Stats类的用法

目录 1. 前言 2. osgViewer::StatsHandler 2.1. 功能与用法说明 2.2. 主要接口说明 3. osg::Stats 1. 前言 osg为视景器的使用和调试提供了丰富的辅助组件,它们主要是以osg::ViewerBase的成员变量或交互事件处理器(osgGA::GUIEventHandler)的形式出现。osgView…

Linux线程:死锁

1. 死锁 (1)概念 死锁(DeadLock)指两个或两个以上的进程或线程执行时,由于竞争临界资源而造成阻塞的现象;若不干涉,则无法推进下去。 (2)死锁的原因 ① 竞争临界资源…

商业运营级Java定位系统源代码

智慧工厂人员定位系统源码 技术架构:Java vue spring boot 系统概述: 采用UWB定位技术,通过在厂区内布设一定数量的定位基站,实时精确地定位员工、车辆、物品上微标签位置,零延时地将人、车、物的位置信息显示在工厂…

8年测试老鸟谈,性能测试-测试环境搭建与常见问题,这些你都知道吗?

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 一个问题&#xf…

千模大战鏖战正酣,大模型能否帮360重回巅峰?

ChatGPT问世后,互联网终于看到了变革的曙光。 回望互联网的征程,几乎每十年左右,就会因产品和技术的变革,而催生出全新的应用场景。 00年代,随着PC产品的逐渐成熟,桌面互联网成为时代的“主旋律”。在此背…

ChatGPT提示词工程(七):Chatbot聊天机器人

目录 一、说明二、安装环境1. 辅助函数:get_completion2. 辅助函数:get_completion_from_messages 三、聊天机器人(Chatbot)1. 一般聊天机器人1.1 简单的例子1.2 多轮对话 2. 订单机器人 一、说明 这是吴恩达 《ChatGPT Prompt E…

Jackson使用详解

Spring MVC 默认采用Jackson解析Json,尽管还有一些其它同样优秀的json解析工具,例如Fast Json、GSON,但是出于最小依赖的考虑,也许Json解析第一选择就应该是Jackson。 一、简介 Jackson 是当前用的比较广泛的,用来序…

【Spring框架全系列】第一个Spring程序

🏙哈喽,大家好,我是小浪。那么从今天开始,我就要开始更新spring框架全系列的博客了;本专栏免费阅读,最好能够点个订阅,以便于后续及时收到更新信息哈!🏟 📲目…

【Java EE 初阶】线程的常用方法

目录 1.多线程的作用 2.示例:分别对两个变量实现10亿次自增 1.使用串行(单线程) 2.使用并行(多线程) 结果显示: 3.线程的类构造方法 代码展示: 4.Thread类的常见属性 1.演示各个属性 2.演…

( 数组和矩阵) 378. 有序矩阵中第 K 小的元素 ——【Leetcode每日一题】

❓378. 有序矩阵中第 K 小的元素 难度:中等 给你一个 n x n n x n nxn 矩阵 m a t r i x matrix matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。 请注意,它是 排序后 的第 k 小元素,而不是第 …

InnoDB存储引擎B+树的树高推导

目录 一、基本结论:InnoDB存储引擎B树的树高3-4层 二、存储引擎B树结构简单分析 三、主键索引B树推导 四、InnoDB页的内部结构推导 五、剖析InnoDB数据文件推导 六、一般思路推导计算B树高度总结 参考文献、书籍及链接 一、基本结论:InnoDB存储引…

uni-app实战笔记

3 页面跳转 点击【成员列表】进入到子页面,这里就涉及到页面跳转。 路由是基础并不难,但是路由跳转的方式比较灵活,这里就需要记录一下。有一点是需要配置的,就是在pages.json中需要将路由整理好,否则页面是无法跳转的…

Hive架构原理以及部署教程

◆了解Hive架构原理 ◆了解Hive和RDBMS的对比 Hive架构原理 Hive架构原理 - 知乎 Hive 是基于 Hadoop 的数据仓库工具,它提供了类 SQL 查询语言 HQL(Hive Query Language),可以将 SQL 语句转化为 MapReduce 任务进行数据处理。 Hi…

【软考备战·希赛网每日一练】2023年5月2日

文章目录 一、今日成绩二、错题总结第一题 三、知识查缺 题目及解析来源:2023年05月02日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析: 三、知识查缺 复习 流水线技术。序列图(顺序图)用于展现系统中一个用例和多…

剪枝与重参第九课:DBB重参

目录 DBB重参前言1. DBB2. DBB的六种变换2.1 Transform I: a conv for conv-BN2.2 Transform II:a conv for branch addition2.3 Transform III:a conv for sequential convolutions2.4 Transform IV:a conv for depth concatenation2.5 Tran…

【SpringBoot】 整合RabbitMQ 消息单独以及批量的TTL

生产者端 目录结构 导入依赖 修改yml 业务逻辑 队列消息过期 消息单独过期 TTL(Time To Live)存活时间。表示当消息由生产端存入MQ当中的存活时间,当时间到达的时候还未被消息就会被自动清除。RabbitMQ可以对消息单独设置过期时间也可以对…