通讯协议学习之路(实践部分):UART开发实践

通讯协议之路主要分为两部分,第一部分从理论上面讲解各类协议的通讯原理以及通讯格式,第二部分从具体运用上讲解各类通讯协议的具体应用方法。

后续文章会同时发表在个人博客(jason1016.club)、CSDN;视频会发布在bilibili(UID:399951374)

本文前缀:

通讯协议专栏:通讯协议_JASON丶LI的博客-CSDN博客

UART理论部分:通讯协议学习之路:UART协议理论-CSDN博客

具体实践方案选择:

1、轮询模式

程序必须轮询状态位以检查是否已收到新字符并以足够快的速度读取它以获得所有字节

优点

很容易实现,但在真正项目中的应用很少

缺点

在突发数据中很容易错过接收到的字符

仅适用于低波特率

应用程序必须非常快速地检查是否收到新的数据

2、中断模式

UART触发中断,CPU跳转到服务程序处理数据接收

优点

目前程序中最常用的方法

在低速率下工作良好,115200 波特

缺点

为每个接收到的字符执行中断服务程序

可能会在具有许多中断的高性能 MCU 中停止其他任务

一次接收突发数据时可能会停止操作系统

3、DMA模式

🍀🍀🍀注意这里的DMA模式接收不定长数据时是检测IDLE空闲中断标志位来判断DMA接收是否完成的,但是本人在使用proteus仿真中,IDLE一直都不会挂起导致仿真的DMA接收无法实现,后续会想方案解决

DMA 用于在硬件级别将数据从 USART RX 数据寄存器传输到用户存储器。 除了在必要时由应用程序处理接收到的数据,此时不需要应用程序交互

优点

i.从 USART 外设到内存的传输是在硬件完成的,无需 CPU干涉

ii.可以很容易地与操作系统一起工作

iii.针对最高波特率 > 1Mbps 和低功耗应用进行了优化

vi.在大量数据突发的情况下,增加数据缓冲区大小可以改进功能

缺点

i.DMA 硬件必须事先知道要传输的字节数

ii.如果通信失败,DMA 可能不会通知应用程序所有传输的字节

本文仅关注接收未知数据长度的 DMA 模式。

开发实践

对于usart的开发实践,其实并没有学习理论时预想的那么负责,因为目前市面上绝大部分单片机芯片内核都已经配备了完整的U(S)ART固件,相当于厂家已经配置好了对应的协议传输方案,我们要做的就是简单地配置一下其已存在的固件以及对应数据传输的规则即可。

本文以STM32F103C8为例,分两种方式进行usart协议通讯的配置,分别给标准库用户和HAL库用户详细的配置解决方案(ps:寄存器开发没有,作者寄存器开发不太熟练...)

一、标准库

中断模式:

单片机知识巩固

流控:【STM32学习笔记】USART 硬件流控 - 知乎 (zhihu.com)

usart协议的使用核心在与配置与数据的收发处理,对于标准库而言,USART的配置核心在与IO口的配置,而数据的收发核心在于数据的发送格式和接收缓存标注位,接收数据筛选。

1、UART的配置

USART协议硬件通道,在单片机的配置,本人有自己的一点想法。之前在理论中提到了单片机拟人化的概念,在这章我将继续延续这个概念进行介绍。先让我们回忆一下:之前提到晶振就是单片机的心跳信号,每实现一次晶振的跳变信号,单片机就执行一次指令周期;而各类的总线就是血管和供血,各类的IO口和IO协议就是单片机感知世界与世界交流的感官。

不知道大家有没有听过捕食者效应,那就是当你饥饿的时候,你会发现你的感官会变得更加灵敏(比如我考试之前一般都不吃饭的),这是什么原因呢?原因就是当我们空腹时,我们的消化系统就不需要工作,血液的占用就少了,但是血液的总量是不变的,因此心脏每一次泵血,血液就可以流向人身体内更需要他的地方。

回归单片机,正常单片机这么多IO口,当他每一个都开启工作的时候,是不是就得持续给他们供电以及随时随地管理信号呀,这会导致单片机的能耗变得非常高,那怎么解决呢?因此单片机所有的外设默认都是失能的,什么意思呢,就像刚刚提到的空腹状态,就是单片机不需要消化这个外设通讯,因此我们就可以失能消化系统这类的外设时钟,就是不给他供血了。那当我们发现我们需要消化食物了,那我们就重新开启这个外设的时钟就行了。这种按需开启,有利于单片机大大节省能耗以及降低CPU占用率,通常我们都是用啥就开啥,其他的就不管了。

而单片机GPIO、USART、SPI、NVIC等这类型外设的配置,以及各类的配置选项,就需要大家各自去掌握了,这里附上STM32F103的库函数编程文档供参考。

配置阶段分为:

  1. 使能GPIO时钟
  2. 使能USART时钟
  3. 配置GPIO口
  4. 配置USART口
  5. 开启USART中断模式
  6. 配置NVIC中断模式
  7. 使能usart

具体配置方案以及配置原因参考下列代码。

void USART1_Init(u32 bound)                //USART1初始化函数
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);    //使能USART1(ck=PB8,TX=PA9,RX=PA10)RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    //使能GPIOAGPIO_InitTypeDef GPIO_Initstructure;                    //定义GPIO结构体GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF_PP;            //设置GPIO为复用推挽输出GPIO_Initstructure.GPIO_Pin = GPIO_Pin_9;                //设置为Pin9GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;        //设置为50MHZ速度GPIO_Init(GPIOA, &GPIO_Initstructure);                    //按照上述结构体配置初始化GPIOAGPIO_Initstructure.GPIO_Mode = GPIO_Mode_IPU;            //设置GPIO为浮空输入GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;                //设置为Pin9GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;        //设置为50MHZ速度GPIO_Init(GPIOA, &GPIO_Initstructure);                    //按照上述结构体配置初始化GPIOAUSART_InitTypeDef USART_Initstructure;                                            //定义UASRT结构体USART_Initstructure.USART_BaudRate = bound;                                        //配置波特率USART_Initstructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    //配置流控USART_Initstructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;                    //配置通讯方式,一般选择双控USART_Initstructure.USART_Parity = USART_Parity_No;                                //配置是否需要校验位(需要对应下面的数据长度WordLength)USART_Initstructure.USART_StopBits = USART_StopBits_1;                                            //配置停止位USART_Initstructure.USART_WordLength = USART_WordLength_8b;                        //配置数据长度(可选8位和9位,若设置校验位则配置9位,否则配置8位即可)USART_Init(USART1, &USART_Initstructure);                                        //按照上述结构体配置初始化USART1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);            //使能USART中断NVIC_InitTypeDef NVIC_Initstructure;                    //定义NVIC结构体NVIC_Initstructure.NVIC_IRQChannel = USART1_IRQn;        //设置NVIC管理USART1中断NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;            //确定使能NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority = 1;    //设置抢占优先级为0NVIC_Initstructure.NVIC_IRQChannelSubPriority = 1;            //设置等候优先级为0NVIC_Init(&NVIC_Initstructure);                            //按照上述结构体配置初始化NVICUSART_Cmd(USART1, ENABLE);                                //使能UASRT1
}

库函数配置关键点:

  • @param USART_FLAG:指定要检查的标志。
  • @arg USART_FLAG_CTS: CTS更改标志(不适用于UART4和UART5)
  • @arg USART_FLAG_LBD: LIN中断检测标志
  • @arg USART_FLAG_TXE:传输数据寄存器空标志
  • @arg USART_FLAG_TC:传输完成标志
  • @arg USART_FLAG_RXNE:接收数据寄存器不空标志
  • @arg USART_FLAG_IDLE:空闲线检测标志
  • @arg USART_FLAG_ORE:超限错误标志
  • @arg USART_FLAG_NE:噪声错误标志
  • @arg USART_FLAG_FE:帧错误标志
  • @arg USART_FLAG_PE:奇偶校验错误标志

  • @param USART_IT:指定USART中断源要启用或禁用。
  • @arg USART_IT_CTS: CTS更改中断(不适用于UART4和UART5)
  • @arg USART_IT_LBD: LIN中断检测中断
  • @arg USART_IT_TXE:传输数据寄存器空中断
  • @arg USART_IT_TC:传输完成中断
  • @arg USART_IT_RXNE:接收数据寄存器不空中断
  • @arg USART_IT_IDLE:空闲线检测中断
  • @arg USART_IT_PE:奇偶校验错误中断
  • @arg USART_IT_ERR:中断错误(帧错误,噪声错误,溢出错误)
2、UART的发送

USART发送发本质就是调用USART_SendData()函数发送信息,各类的信息发送都是基于该函数变形而得的。

void Serial_SendByte(uint8_t Byte)    //编写发送函数
{USART_SendData(USART1, Byte);     //发送字节函数while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);    //检验发送是否完成
}//发送一个数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++){Serial_SendByte(Array[i]);}
}//发送一个字符串
void Serial_SendString(char *String)
{uint8_t i;for (i = 0; String[i] != 0; i++){Serial_SendByte(String[i]);}
}//取X的Y次方(用于下列运算)
uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{uint32_t Result =1;while (Y--){Result *=X;}return Result;
}//发送一串数字(数字需要针对每一位数字进行对应的除法和求余)
void Serial_SendNumber(uint32_t Number,uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i ++){Serial_SendByte(Number/Serial_Pow(10, Length - i - 1) % 10 + '0');}
}//重写fputc函数
int fputc(int ch, FILE *f)
{Serial_SendByte(ch);return ch;
}//封装printf可变参数格式
void Serial_Printf(char *format, ...)
{char String[100];va_list arg;va_start(arg, format);vsprintf(String, format, arg);va_end(arg);Serial_SendString(String);
}
3、UART的接收

UART数据接收的本质

当已使能的UART信道接收到信息的时候触发usart中断,

然后在中断事件中调用USART_GetITStatus()函数检查接收数据寄存器是否为空

若检查到数据则将数据转移到Serial_RxData中进行数据的存储,同时挂起标志位Serial_RxFlag,方便后续要轮询模式中进行数据的打印或调用操作.

最后调用USART_ClearITPendingBit()函数进行中断挂起状态的清除,退出中断并方便下次再次进入中断.

uint8_t Serial_RxData;                     //定义UASRT1_RX数据缓存    
uint8_t Serial_RxFlag;                    //定义USART1_RX接收标志位,用于后续TX特定数据的接收
char Serial_RxPacket[100];//调用标志位和重置标志位
uint8_t Serial_GetFlag(void)
{if (Serial_RxFlag == 1){Serial_RxFlag = 0;return 1;}return 0;
}uint8_t Serial_GetRxData(void)
{return Serial_RxData;
}//无限制接收//void USART1_IRQHandler(void)
//{
//    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
//    {
//        Serial_RxData = USART_ReceiveData(USART1);
//        Serial_RxFlag = 1;
//        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//    }
//}//接收筛选,接收数据包
void USART1_IRQHandler(void)        //编写USART1中断函数处理接收事件
{//static为静态变量,只需定义一次(只能再规定函数中执行的全局变量)static uint8_t RxStare = 0;static uint8_t pRxPacket = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET){uint8_t RxData = USART_ReceiveData(USART1);if (RxStare == 0) //状态1{if (RxData == '@' && Serial_RxFlag == 0){RxStare = 1;pRxPacket = 0;}}else if (RxStare == 1) //状态2{if (RxData == '\r'){RxStare =2;}else{Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;}}else if (RxStare == 2) //状态3{if (RxData == '\n'){RxStare = 0;Serial_RxPacket[pRxPacket] = '\0';Serial_RxFlag = 1;}}USART_ClearITPendingBit(USART1, USART_FLAG_RXNE);}
}

DMA模式:

参考文档:

STM32 DMA串口发送模式配置及使用简单分享 - 知乎 (zhihu.com)

STM32 | 串口DMA很难?其实就是如此简单!(超详细、附代码)-CSDN博客

对于DMA转运,核心就在于DMA通道的配置与选取,发送不需要用到cpu,接收需要进入cpu的中断模式进行数据的处理

对于DMA的发送,核心在于调用DMA_Cmd(DMA1_Channel7, ENABLE);函数,将原先设定好的CNDTR(数据长度)的CMAR(数据地址)数据发送出去

对于DMA的接收,分为定长与不定长两种接收方式

定长方式采用DMA1_Channel6_IRQHandler中断方式,当接收数据缓存满了之后就会发生中断,将数据缓存内的所有数据读取。

不定长方式采用USART2_IRQHandler(IDLE方式)的中断方式,接收串口空闲标志位,当数据接收完存入数据缓存之后就会触发IDLE中断,这是我们将读取数据缓存内的数据,同时检查数据缓存内的剩余容量,这样数据长度就等于数据缓存总容量-数据缓存剩余容量,当得知数据长度与数据地址后就可以实现不定长数据的接收了。

配置:
#include "usart2.h"//USART2_MAX_TX_LEN和USART2_MAX_RX_LEN在头文件进行了宏定义,分别指USART2最大发送长度和最大接收长度
u8 USART2_TX_BUF[USART2_MAX_TX_LEN];     //发送缓冲,最大USART2_MAX_TX_LEN字节
u8 u1rxbuf[USART2_MAX_RX_LEN];                //发送数据缓冲区1
u8 u2rxbuf[USART2_MAX_RX_LEN];                //发送数据缓冲区2
u8 witchbuf=0;                              //标记当前使用的是哪个缓冲区,0:使用u1rxbuf;1:使用u2rxbuf
u8 USART2_TX_FLAG=0;                                    //USART2发送标志,启动发送时置1
u8 USART2_RX_FLAG=0;                                    //USART2接收标志,启动接收时置1void Initial_UART2(unsigned long baudrate)
{//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA, ENABLE);        //使能USART2,GPIOA时钟//USART2_TX   GPIOA.2初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                                                                                //PA.2GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                                                                    //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                                                                //GPIO速率50MHzGPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                    //初始化GPIOA.2//USART2_RX      GPIOA.3初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;                                                                                //PA.3GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                                                        //浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                    //初始化GPIOA.3//USART 初始化设置USART_InitStructure.USART_BaudRate = baudrate;                                                                    //串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;                                            //字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;                                                    //一个停止位USART_InitStructure.USART_Parity = USART_Parity_No ;                                                        //无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    //无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                                    //收发模式USART_Init(USART2, &USART_InitStructure);                                                                             //初始化串口2//中断开启设置USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);                                                                    //开启检测串口空闲状态中断USART_ClearFlag(USART2,USART_FLAG_TC);                                                                                    //清除USART2标志位USART_Cmd(USART2, ENABLE);                                                                                                            //使能串口2NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;                                                                //NVIC通道设置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8;                                                //抢占优先级8NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                                                            //响应优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                                    //IRQ通道使能NVIC_Init(&NVIC_InitStructure);                                                                                                    //根据指定的参数初始化NVIC寄存器DMA1_USART2_Init();                                                                                                                            //DMA1_USART2初始化
}void DMA1_USART2_Init(void)
{DMA_InitTypeDef DMA1_Init;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);                                //使能DMA1时钟//DMA_USART2_RX  USART2->RAM的数据传输DMA_DeInit(DMA1_Channel6);                                                                            //将DMA的通道6寄存器重设为缺省值 DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                    //启动传输前装入实际RAM地址DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf;                            //设置接收缓冲区首地址DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC;                                            //数据传输方向,从外设读取到内存DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN;                                        //DMA通道的DMA缓存的大小DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                //外设地址寄存器不变DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                    //内存地址寄存器递增DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    //数据宽度为8位DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                    //数据宽度为8位DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                        //工作在正常模式DMA1_Init.DMA_Priority = DMA_Priority_High;                                         //DMA通道 x拥有高优先级 DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                        //DMA通道x没有设置为内存到内存传输DMA_Init(DMA1_Channel6,&DMA1_Init);                                                         //对DMA通道6进行初始化//DMA_USART2_TX  RAM->USART2的数据传输DMA_DeInit(DMA1_Channel7);                                                                            //将DMA的通道7寄存器重设为缺省值 DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                    //启动传输前装入实际RAM地址DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF;              //设置发送缓冲区首地址DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST;                                             //数据传输方向,从内存发送到外设DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN;                                        //DMA通道的DMA缓存的大小DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                //外设地址寄存器不变DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                    //内存地址寄存器递增DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    //数据宽度为8位DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                    //数据宽度为8位DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                        //工作在正常模式DMA1_Init.DMA_Priority = DMA_Priority_High;                                         //DMA通道 x拥有高优先级 DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                        //DMA通道x没有设置为内存到内存传输DMA_Init(DMA1_Channel7,&DMA1_Init);                                                         //对DMA通道7进行初始化//DMA1通道6 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;                //NVIC通道设置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;            //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                            //子优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                    //IRQ通道使能NVIC_Init(&NVIC_InitStructure);                                                                    //根据指定的参数初始化NVIC寄存器//DMA1通道7 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;                //NVIC通道设置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;            //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                            //子优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                    //IRQ通道使能NVIC_Init(&NVIC_InitStructure);                                                                    //根据指定的参数初始化NVIC寄存器DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE);                                        //开USART2 Rx DMA中断DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE);                                        //开USART2 Tx DMA中断DMA_Cmd(DMA1_Channel6,ENABLE);                                                           //使DMA通道6停止工作DMA_Cmd(DMA1_Channel7,DISABLE);                                                       //使DMA通道7停止工作USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);                            //开启串口DMA发送USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);                            //开启串口DMA接收
}
发送函数
//DMA 发送应用源码
void DMA_USART2_Tx_Data(u8 *buffer, u32 size)
{while(USART2_TX_FLAG);                                        //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)USART2_TX_FLAG=1;                                                    //USART2发送标志(启动发送)DMA1_Channel7->CMAR  = (uint32_t)buffer;    //设置要发送的数据地址DMA1_Channel7->CNDTR = size;                        //设置要发送的字节数目DMA_Cmd(DMA1_Channel7, ENABLE);                        //开始DMA发送
}void USART2_printf(char *format, ...)
{//VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件:#include <stdarg.h>,用于获取不确定个数的参数。va_list arg_ptr;                                                                                                                //实例化可变长参数列表while(USART2_TX_FLAG);                                                                                                    //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)va_start(arg_ptr, format);                                                                                             //初始化可变参数列表,设置format为可变长列表的起始点(第一个元素)// USART2_MAX_TX_LEN+1可接受的最大字符数(非字节数,UNICODE一个字符两个字节), 防止产生数组越界vsnprintf((char*)USART2_TX_BUF, USART2_MAX_TX_LEN+1, format, arg_ptr);    //从USART2_TX_BUF的首地址开始拼合,拼合format内容;USART2_MAX_TX_LEN+1限制长度,防止产生数组越界va_end(arg_ptr);                                                                                                                //注意必须关闭DMA_USART2_Tx_Data(USART2_TX_BUF,strlen((const char*)USART2_TX_BUF));        //发送USART2_TX_BUF内容
}
接收函数
//处理DMA1 通道6的接收完成中断
void DMA1_Channel6_IRQHandler(void)
{u8 *p;if(DMA_GetITStatus(DMA1_IT_TC6)!= RESET)        //DMA接收完成标志{DMA_ClearITPendingBit(DMA1_IT_TC6);             //清除中断标志 USART_ClearFlag(USART2,USART_FLAG_TC);        //清除USART2标志位DMA_Cmd(DMA1_Channel6, DISABLE );               //关闭USART2 TX DMA1 所指示的通道if(witchbuf)                                    //之前用的u2rxbuf,切换为u1rxbuf{p=u2rxbuf;                                                            //先保存前一次数据地址再切换缓冲区DMA1_Channel6->CMAR=(u32)u1rxbuf;                //切换为u1rxbuf缓冲区地址witchbuf=0;                                     //下一次切换为u2rxbuf}else                                           //之前用的u1rxbuf,切换为u2rxbuf{p=u1rxbuf;                                                            //先保存前一次数据地址再切换缓冲区DMA1_Channel6->CMAR=(u32)u2rxbuf;                //切换为u2rxbuf缓冲区地址witchbuf=1;                                     //下一次切换为u1rxbuf}DMA1_Channel6->CNDTR = USART2_MAX_RX_LEN;    //DMA通道的DMA缓存的大小DMA_Cmd(DMA1_Channel6, ENABLE);                 //使能USART2 TX DMA1 所指示的通道//******************↓↓↓↓↓这里作数据处理↓↓↓↓↓******************//DMA_USART2_Tx_Data(p,USART2_MAX_RX_LEN);//******************↑↑↑↑↑这里作数据处理↑↑↑↑↑******************//}
}//DMA1通道7中断
void DMA1_Channel7_IRQHandler(void)
{if(DMA_GetITStatus(DMA1_IT_TC7)!= RESET)    //DMA接收完成标志{DMA_ClearITPendingBit(DMA1_IT_TC7);         //清除中断标志 USART_ClearFlag(USART2,USART_FLAG_TC);    //清除串口2的标志位DMA_Cmd(DMA1_Channel7, DISABLE );           //关闭USART2 TX DMA1 所指示的通道USART2_TX_FLAG=0;                                                //USART2发送标志(关闭)}
}//串口2中断函数
void USART2_IRQHandler(void)                    
{u8 *p;u8 USART2_RX_LEN = 0;                                                                                //接收数据长度if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)                //串口2空闲中断{USART_ReceiveData(USART2);                                                                 //清除串口2空闲中断IDLE标志位USART_ClearFlag(USART2,USART_FLAG_TC);                                        //清除USART2标志位DMA_Cmd(DMA1_Channel6, DISABLE );                                               //关闭USART2 TX DMA1 所指示的通道USART2_RX_LEN = USART2_MAX_RX_LEN - DMA1_Channel6->CNDTR;    //获得接收到的字节数if(witchbuf)                                                                    //之前用的u2rxbuf,切换为u1rxbuf{p=u2rxbuf;                                                                                            //先保存前一次数据地址再切换缓冲区DMA1_Channel6->CMAR=(u32)u1rxbuf;                                                //切换为u1rxbuf缓冲区地址witchbuf=0;                                                                     //下一次切换为u2rxbuf}else                                                                           //之前用的u1rxbuf,切换为u2rxbuf{p=u1rxbuf;                                                                                            //先保存前一次数据地址再切换缓冲区DMA1_Channel6->CMAR=(u32)u2rxbuf;                                                //切换为u2rxbuf缓冲区地址witchbuf=1;                                                                     //下一次切换为u1rxbuf}DMA1_Channel6->CNDTR = USART2_MAX_RX_LEN;                                    //DMA通道的DMA缓存的大小DMA_Cmd(DMA1_Channel6, ENABLE);                                                 //使能USART2 TX DMA1 所指示的通道//******************↓↓↓↓↓这里作数据处理↓↓↓↓↓******************//DMA_USART2_Tx_Data(p,USART2_RX_LEN);//******************↑↑↑↑↑这里作数据处理↑↑↑↑↑******************//}USART_ClearITPendingBit(USART2,USART_IT_ORE);                                //清除USART2_ORE标志位
}

二、HAL库

这里先放上HAL库的串口句柄

typedef struct __UART_HandleTypeDef
{USART_TypeDef                 *Instance;        /*!< UART registers base address        */UART_InitTypeDef              Init;             /*!< UART communication parameters      */uint8_t                       *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */__IO uint16_t                 TxXferCount;      /*!< UART Tx Transfer Counter           */uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */__IO uint16_t                 RxXferCount;      /*!< UART Rx Transfer Counter           */__IO HAL_UART_RxTypeTypeDef ReceptionType;      /*!< Type of ongoing reception          */DMA_HandleTypeDef             *hdmatx;          /*!< UART Tx DMA Handle parameters      */DMA_HandleTypeDef             *hdmarx;          /*!< UART Rx DMA Handle parameters      */HAL_LockTypeDef               Lock;             /*!< Locking object                     */__IO HAL_UART_StateTypeDef    gState;           /*!< UART state information related to global Handle managementand also related to Tx operations.This parameter can be a value of @ref HAL_UART_StateTypeDef */__IO HAL_UART_StateTypeDef    RxState;          /*!< UART state information related to Rx operations.This parameter can be a value of @ref HAL_UART_StateTypeDef */__IO uint32_t                 ErrorCode;        /*!< UART Error code                    */#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);        /*!< UART Tx Half Complete Callback        */void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart);            /*!< UART Tx Complete Callback             */void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);        /*!< UART Rx Half Complete Callback        */void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart);            /*!< UART Rx Complete Callback             */void (* ErrorCallback)(struct __UART_HandleTypeDef *huart);             /*!< UART Error Callback                   */void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart);         /*!< UART Abort Complete Callback          */void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart);  /*!< UART Abort Receive Complete Callback  */void (* WakeupCallback)(struct __UART_HandleTypeDef *huart);            /*!< UART Wakeup Callback                  */void (* RxEventCallback)(struct __UART_HandleTypeDef *huart, uint16_t Pos); /*!< UART Reception Event Callback     */void (* MspInitCallback)(struct __UART_HandleTypeDef *huart);           /*!< UART Msp Init callback                */void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart);         /*!< UART Msp DeInit callback              */
#endif  /* USE_HAL_UART_REGISTER_CALLBACKS */} UART_HandleTypeDef;

1、串口发送/接收函数

HAL_UART_Transmit():串口发送数据,使用超时管理机制
HAL_UART_Receive():串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT():串口中断模式发送
HAL_UART_Receive_IT():串口中断模式接收
HAL_UART_Transmit_DMA():串口DMA模式发送
HAL_UART_Transmit_DMA():串口DMA模式接收
串口发送数据 HAL_UART_Transmit
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

功能:串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)。

参数:

  • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
  • *pData 需要发送的数据
  • Size 发送的字节数
  • Timeout 最大发送时间,发送数据超过该时间退出发送
举例:   HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff);   //串口发送三个字节数据,最大传输时间0xffff
中断接收数据 HAL_UART_Receive_IT
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

功能:串口中断接收,以中断方式接收指定长度数据。

大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断。

再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)

参数:

UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1

*pData 接收到的数据存放地址

Size 接收的字节数

举例:    HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1);   //中断接收一个字符,存储到value中

2、串口中断函数

HAL_UART_IRQHandler(UART_HandleTypeDef *huart);  //串口中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);  //串口发送中断回调函数
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);  //串口发送一半中断回调函数(用的较少)
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);  //串口接收中断回调函数
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(用的较少)
HAL_UART_ErrorCallback();串口接收错误函数

a.串口中断服务函数 USART1_IRQHandler(void)【不需要配置】

USART1_IRQHandler(void);

功能:当我们使能了中断并且中断发生时就会执行这里的中断服务函数。

这个函数在MX配置后会自行进行下面所说的一系列判断,不需要额外配置,因此HAL库不同于标准库,标准库是在这个函数里面进行响应的中断处理事件的配置,而标准库则在这里面进行接收和发送两个事件类型的判断,再跳转到对应的函数进行对应的处理。

b.串口中断处理函数 HAL_UART_IRQHandler(UART_HandleTypeDef *huart)【不需要配置】

HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

功能:对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用

如果接收数据,则会进行接收中断处理函数

/* UART in mode Receiver ---------------------------------------------------*/
if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
UART_Receive_IT(huart);
}

如果发送数据,则会进行发送中断处理函数

/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
c.❤️串口接收中断回调函数 HAL_UART_RxCpltCallback(huart)【需要配置】

HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

功能:HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改,

参数:

  • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1

举例: HAL_UART_RxCpltCallback(&huart1){ //用户设定的代码 }

3、串口查询函数

 HAL_UART_GetState();  判断UART的接收是否结束,或者发送数据是否忙碌

  举例:     

while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX) //检测UART发送结束

三、 HAL库具体实现过程

1、发送函数(重定向)

引入printf重定向代码块

代码最适合加在CubeMX自动生成后的usart.c文件的 / * USER CODE BEGIN 0 * / 和 / * USER CODE END 0 * / 中间

/* USER CODE BEGIN 0 */
#include <stdio.h>#ifdef __GNUC__#define PUTCHAR_PROTOTYPE int _io_putchar(int ch)#else#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)#endif /* __GNUC__*//*******************************************************************@brief  Retargets the C library printf  function to the USART.*@param  None*@retval None******************************************************************/PUTCHAR_PROTOTYPE{HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1,0xFFFF);return ch;}
/* USER CODE END 0 */
添加#include<stdio.h>

比较全局的办法就是将#include直接加入main.h中,因为Cube生成文件大部分都是包含了main.h的,所以除了自建文件几乎都可以全局包含到stdio.h,而且自建文件也可以直接包含main.h,我的习惯是把工程用的共性的概率高的头文件都放在main.h里面,具体位置如下:

//main.h/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include<stdio.h>
/* USER CODE END Includes */

在自建文件使用printf函数时记得#include

main.c内测试的代码:

  while (1){/* USER CODE END WHILE */printf("串口打印测试\n");HAL_Delay(1000);/* USER CODE BEGIN 3 */}

注意!!!使用此代码时还要在魔术棒那个选项中打勾“UseMicroLIB”,否则stdio.h是编译不了的,但它又不会报错。

参考文章:STM32-HAL库-printf函数重定向(USART应用实例)_hal库printf重定向_Calvin Haynes的博客-CSDN博客

2、⭐接收函数(中断)

因为中断接收函数只能触发一次接收中断,所以我们需要在中断回调函数中再调用一次中断接收函数

具体流程:

1、初始化串口

2、在main中第一次调用接收中断函数

3、进入接收中断,接收完数据 进入中断回调函数

4、修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,

5 、回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断

函数流程图:

HAL_UART_Receive_IT(中断接收函数) -> USART2_IRQHandler(void)(中断服务函数) -> HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数) -> UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数) -> HAL_UART_RxCpltCallback(huart);(中断回调函数)

HAL_UART_RxCpltCallback函数就是用户要重写在main.c里的回调函数。

代码实现:

在main.c中添加下列定义:

#include <string.h>#define RXBUFFERSIZE  256     //最大接收字节数
char RxBuffer[RXBUFFERSIZE];   //接收数据
uint8_t aRxBuffer;            //接收中断缓冲
uint8_t Uart1_Rx_Cnt = 0;        //接收缓冲计数

在main()主函数中,调用一次接收中断函数

/* USER CODE BEGIN 2 */HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
/* USER CODE END 2 */

在main.c下方添加中断回调函数

/* USER CODE BEGIN 4 */void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{/* Prevent unused argument(s) compilation warning */UNUSED(huart);/* NOTE: This function Should not be modified, when the callback is needed,the HAL_UART_TxCpltCallback could be implemented in the user file*/if(Uart1_Rx_Cnt >= 255)  //溢出判断{Uart1_Rx_Cnt = 0;memset(RxBuffer,0x00,sizeof(RxBuffer));HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF);     }else{RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位{HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束Uart1_Rx_Cnt = 0;memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组}}HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);   //再开启接收中断
}
/* USER CODE END 4 */

参考文章:【STM32】HAL库 STM32CubeMX教程四---UART串口通信详解_hal_uart_transmit-CSDN博客

3、发送函数(DMA转运)

UART DMA函数库

HAL_UART_Transmit();串口发送数据,使用超时管理机制
HAL_UART_Receive();串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT();串口中断模式发送
HAL_UART_Receive_IT();串口中断模式接收
HAL_UART_Transmit_DMA();串口DMA模式发送
HAL_UART_Transmit_DMA();串口DMA模式接收
HAL_UART_DMAPause() 暂停串口DMA
HAL_UART_DMAResume(); 恢复串口DMA
HAL_UART_DMAStop(); 结束串口DMA

在main.C中添加:

/* USER CODE BEGIN Init /
uint8_t Senbuff[] = "\r\n*** Serial Output Message by DMA **\r\n   UART DMA Test \r\n   Zxiaoxuan";  //定义数据发送数组
/ USER CODE END Init */

while循环:

while (1)
{
/* USER CODE END WHILE */
HAL_UART_Transmit_DMA(&huart1, (uint8_t )Senbuff, sizeof(Senbuff));HAL_Delay(1000);/ USER CODE BEGIN 3 */
}

参考文章:【STM32】HAL库 STM32CubeMX教程十一---DMA (串口DMA发送接收)_hal库dma串口接收-CSDN博客

4、接收函数(DMA)

STM32的IDLE的中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断

使用DMA+串口接受空闲中断 实现将接收的数据完整发送到上位机的功能

uart.c

volatile uint8_t rx_len = 0;  //接收一帧数据的长度
volatile uint8_t recv_end_flag = 0; //一帧数据接收完成标志
uint8_t rx_buffer[100]={0};  //接收数据缓存数组
void MX_USART1_UART_Init(void)
{huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}
//下方为自己添加的代码__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断//DMA接收函数,此句一定要加,不加接收不到第一次传进来的实数据,是空的,且此时接收到的数据长度为缓存器的数据长度HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);}

uart.h

extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
/* USER CODE BEGIN Private defines */#define BUFFER_SIZE  100  
extern  volatile uint8_t rx_len ;  //接收一帧数据的长度
extern volatile uint8_t recv_end_flag; //一帧数据接收完成标志
extern uint8_t rx_buffer[100];  //接收数据缓存数组

main.c

/*
*********************************************************************************************************
* 函 数 名: DMA_Usart_Send
* 功能说明: 串口发送功能函数
* 形  参: buf,len
* 返 回 值: 无
*********************************************************************************************************
*/
void DMA_Usart_Send(uint8_t *buf,uint8_t len)//串口发送封装
{if(HAL_UART_Transmit_DMA(&huart1, buf,len)!= HAL_OK) //判断是否发送正常,如果出现异常则进入异常中断函数{Error_Handler();}}/*
*********************************************************************************************************
* 函 数 名: DMA_Usart1_Read
* 功能说明: 串口接收功能函数
* 形  参: Data,len
* 返 回 值: 无
*********************************************************************************************************
*/
void DMA_Usart1_Read(uint8_t *Data,uint8_t len)//串口接收封装
{HAL_UART_Receive_DMA(&huart1,Data,len);//重新打开DMA接收
}

while循环

 while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(recv_end_flag == 1)  //接收完成标志{DMA_Usart_Send(rx_buffer, rx_len);rx_len = 0;//清除计数recv_end_flag = 0;//清除接收结束标志位
//            for(uint8_t i=0;i<rx_len;i++)
//                {
//                    rx_buffer[i]=0;//清接收缓存
//                }memset(rx_buffer,0,rx_len);}HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打开DMA接收
}

main.c中的 HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{/* Prevent unused argument(s) compilation warning */UNUSED(huart);/* NOTE: This function Should not be modified, when the callback is needed,the HAL_UART_TxCpltCallback could be implemented in the user file*/if(huart == &huart2)//目前用不了IDLE标志位在Proteus中不知道为什么无法检测{uint32_t tmp_flag = 0;uint32_t temp;tmp_flag =__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE); //获取IDLE标志位if((tmp_flag != RESET))//idle标志被置位{ LED_turn(GPIO_PIN_2);__HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除标志位//temp = huart1.Instance->SR;  //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能//temp = huart1.Instance->DR; //读取数据寄存器中的数据//这两句和上面那句等效HAL_UART_DMAStop(&huart2); //  停止DMA传输,防止temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 获取DMA中未传输的数据个数   //temp  = hdma_usart1_rx.Instance->NDTR;// 读取NDTR寄存器,获取DMA中未传输的数据个数,rx_len =  BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数recv_end_flag = 1;    // 接受完成标志位置1    }}}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Xmind 24 for Mac思维导图软件

XMind是一款流行的思维导图软件&#xff0c;可以帮助用户创建各种类型的思维导图和概念图。 以下是XMind的主要特点&#xff1a; - 多样化的导图类型&#xff1a;XMind提供了多种类型的导图&#xff0c;如鱼骨图、树形图、机构图等&#xff0c;可以满足不同用户的需求。 - 强大…

中小企业数字化转型进程加速,CRM系统前景如何?

自疫情不断反复之后&#xff0c;中小企业数字化转型进程开始加速。作为当下最热门的企业级应用&#xff0c;CRM客户管理系统的前景还是被看好的。相比于美国企业CRM系统7成的使用率&#xff0c;中国的CRM市场还有很大的发展空间。下面来详细说说&#xff0c;CRM系统的前景如何&…

通信世界扫盲基础二(原理部分)

上次我们刚学习了关于通信4/G的组成和一些通识&#xff0c;今天我们来更深层次了解一些原理以及一些新的基础~ 目录 专业名词 LTE(4G系统) EPC s1 E-UTRAN UE UU X2 eNodeB NR(5G系统) NGC/5GC NG NG-RAN Xn gNodeB N26接口 手机的两种状态 空闲态 连接态 …

Python照片压缩教程:如何轻松减小图片大小

在日常的编程工作中&#xff0c;我们经常需要处理图像&#xff0c;例如上传、下载、显示、编辑等。有时候&#xff0c;我们需要对图像进行压缩&#xff0c;以减少占用的空间和带宽&#xff0c;提高加载速度和用户体验。那么&#xff0c;如何用Python来实现图像压缩呢&#xff1…

C51--PC通过串口(中断)点亮LED

B4中的&#xff1a;REN允许 / 禁止串行接收控制位 REN 1为允许串行接收状态。 接收数据必须开启。所以SCON&#xff1a;0101 0000 &#xff1b;即0x50 如何知道数据已经接收 RI位&#xff1a;当收到数据后 RI 1&#xff08;由硬件置一&#xff09; 硬件置一后必须用软件…

解决npm报错Error: error:0308010C:digital envelope routines::unsupported

解决npm报错Error: error:0308010C:digital envelope routines::unsupported。 解决办法&#xff1b;终端执行以下命令&#xff08;windows&#xff09;&#xff1a; set NODE_OPTIONS--openssl-legacy-provider然后再执行 npm命令成功&#xff1a;

算法——图——bsf 广度优先搜索算法 (Breadth First Search)

图遍历算法——bsf 广度优先搜索算法 &#xff08;Breadth First Search&#xff09; 算法 概述算法过程步骤一&#xff1a;初始化原点到队列步骤二&#xff1a;将队列的头顶点放入到已完成集合步骤三&#xff1a;将订单的关联顶点放入到队列中步骤四&#xff1a;将u顶点设置为…

【正点原子STM32连载】 第五十章 FATFS实验 摘自【正点原子】APM32F407最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html## 第五…

【JAVA学习笔记】69 - 多用户通信系统

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/QQClient https://github.com/yinhai1114/Java_Learning_Code/tree/main/QQServer 〇、环境设置以及前言 该项目内会弱化UI界面的设计&#xff0c;因为JAVA本质不是用来开发界面的。 项目开发流程 对于…

汽车标定技术(九)--标定常量与#pragma的趣事

目录 1. 不添加#pragma语句 2. 添加#pragma语句 3. 标定量只给flash空间&#xff0c;不给ram指定空间 4. 总结 在之前不会使用overlay机制的时候&#xff0c;我们想要做汽车标定&#xff0c;标定常量编译出来的地址一般都应该是ram的地址&#xff0c;而且在链接文件中都会指…

深度学习AI识别人脸年龄

以下链接来自 落痕的寒假 GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise import cv2 as cv import time import argparsedef getFaceBox(net, frame, conf_thresh…

基于C#+WPF编写的调用讯飞星火大模型工具

工具源码&#xff1a;https://github.com/lishuangquan1987/XFYun.SparkChat 工具效果截图&#xff1a; 支持流式输出: 其中ApiKey/ApiSecret/AppId需要自己到讯飞星火大模型官网去注册账号申请&#xff0c;免费的。 申请地址&#xff1a;https://xinghuo.xfyun.cn/ 注册之…

代码随想录算法训练营第23期day49| 123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

目录 一、&#xff08;leetcode 123&#xff09;买卖股票的最佳时机III 二、&#xff08;leetcode 188&#xff09;买卖股票的最佳时机IV 一、&#xff08;leetcode 123&#xff09;买卖股票的最佳时机III 力扣题目链接 增加了两次的限制&#xff0c;相应的就是需要考虑的状…

【AI】自回归 (AR) 模型使预测和深度学习变得简单

自回归 (AR) 模型是统计和时间序列模型&#xff0c;用于根据数据点的先前值进行分析和预测。这些模型广泛应用于各个领域&#xff0c;包括经济、金融、信号处理和自然语言处理。 自回归模型假设给定时间变量的值与其过去的值线性相关&#xff0c;这使得它们可用于建模和预测时…

R语言将向量横向转换为单行数据框,随后整合数量不确定的数据框

vector1 c(1, “karthik”, “IT”) names(vector1) c(“id”, “name”, “branch”) df data.frame(as.list(vector1)) print(df) 先给向量的元素命名&#xff0c;然后转换为列表&#xff0c;最后转换为数据框。 我的需求大概是这个样子&#xff1a;数量不确定的仅有单行…

JL-29 雪深监测仪

技术参数 太阳能功率&#xff1a;10W 电池供电&#xff1a;3.7V/20AH 测量量程&#xff1a;0&#xff5e;5m&#xff08;探头处有15cm的盲区&#xff09; 分 辨 率&#xff1a;1mm 随机误差&#xff1a;1.5mm 非线性误差&#xff1a;<0.7mm 温度误差&#xff1a;<0…

PostgreSQL 入门教程

PostgreSQL 入门教程 1. 历史背景2. 概念3. 特点4. 用法4.1 数据库连接4.2 数据库创建4.3 表创建4.4 数据插入4.5 数据查询4.6 数据更新4.7 数据删除 5. 安装步骤6. 简单示例7. 扩展7.1 数据类型7.2 查询优化7.3 并发控制7.4 数据备份和恢复7.5 扩展性和高可用性7.6 安全性加固…

数据复现-企业数字化转型与中国实体经济发展分析

数据简介&#xff1a;在当今快速发展的数字化时代&#xff0c;数字技术已经成为企业数字化转型的核心驱动力之一。尤其对于中国这样一个拥有庞大实体经济的国家而言&#xff0c;结合数字技术的应用&#xff0c;可以为企业带来前所未有的巨大机遇和挑战。在中国&#xff0c;实体…

大促期间治理品牌窜货的诀窍

渠道问题中&#xff0c;最常见的是窜货&#xff0c;窜货还会伴随低价&#xff0c;会影响其他经销商的利益&#xff0c;同时窜货还可能带来假货&#xff0c;所以治理窜货是品牌的责任&#xff0c;对于出货量巨大的双十一大促&#xff0c;品牌更应重视对窜货问题的治理。 力维网络…

MySQL binlog 日志解析后的exec_time导致表示什么时间?

1. exec_time 到底表示什么时间&#xff1f; MySQL binlog日志解析后&#xff0c;我们能看到会有 exec_time &#xff0c;从字面意思理解这个记录的是执行时间&#xff0c;那这个记录的到底是单条sql的执行时间&#xff1f;还是事务的执行时间&#xff1f;下面通过测试来解读一…