【工具使用】STM32CubeMX-DMA配置(ADC+DMA 和 UART+DMA)

news2024/11/25 15:58:19

一、概述

    无论是新手还是大佬,基于STM32单片机的开发,使用STM32CubeMX都是可以极大提升开发效率的,并且其界面化的开发,也大大降低了新手对STM32单片机的开发门槛。
    本文主要讲述STM32芯片的DMA的配置及其相关知识。

二、软件说明

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

三、DMA简介

    DMA(Direct Memory Access)直接内存访问,其实就是一个数据搬运工,负责将数据从一个地方搬运到另一个地方而不需要内核介入。STM32里的DMA支持从外设到内存,从内存到外设和从内存到内存三种传输方式。
    老规矩,先来看下ST芯片手册里DMA的框架图。
在这里插入图片描述
    从此图可以看出,DMA的数据来源与去向可以源自于各种外设,当然这只限于目前的这款芯片,有些芯片DMA不能访问部分外设,如ST的H750,具体能否访问需要看芯片手册里的总线框架。
    我们先把这图拆成四部分:
    首先是中间的"Bus matrix"(总线矩阵),是这个图的核心,所有的外设及内核,都是通过总线矩阵进行数据交互的。
    左上角就是F072这款芯片的内核,用的是Cortex-M0。内核需要通过总线矩阵才能跟其他外设进行数据交互。
    右侧是与本文关系不大的其他外设,包括Flash、SRAM及GPIO等其他外设。
    而左下角就是本文的主角——DMA。
在这里插入图片描述

    前面也说了,DMA就是一个数据搬运工,那什么时候需要这个搬运工呢?一般有两种场景,一是有大量数据需要传输,二是需要内核频繁切换搬运的数据传输。
    比如现在要做一个显示屏,用到了一个GUI开源库LVGL,这个库用于显示的接口是操作一段缓存数据,这段缓存叫作显存。如果要操作一个16位480*272分辨率的RGB屏,那这段显存的大小就是480*272*2=255k。如果使用内核来填充这段显存,以48M单片机主频来算,假设一个指令周期填充四个字节的数据,那么需要48000000/(255*1024/4)=735us时间。如果屏更大,那花费的时间就更长了。此时内核用于显示占用了过多的时间,就会导致其他操作无法进行。所以像这种无聊单调的搬运工作,完全没必要内核来进行,只需要有个搬运工,简单地配置后,让它自己按时搬运数据,解放内核的双手,让内核可以去做其他更有意义的事。
在这里插入图片描述

    再比如现在要实现一个串口的数据收发,ST的串口只提供了一个字节的收发(新的系列提供一段FIFO队列,这里先不考虑这种情况),那如果需要收发100个字节的数据,意味着需要搬运100次数据。相较于上面的255k,可能觉得这100次没什么大不了,但是串口通信是有速率限制的,比如一个9600波特率(1s传输9600个位),传一个字节就需要1ms左右,100个字节就需要100ms。如果使用的是阻塞型发送,即意味着内核需要阻塞100ms的时间。如果使用的中断式发送,那也需要在中断跟主循环中来回切换100次,效率太低。所以像这种重复搬运的,也可以使用DMA来帮忙。
在这里插入图片描述

四、功能配置 及 代码实现

    这里我们整两个比较常用的实例吧,实例一:使用ADC+DMA。实例二:使用Uart+DMA。

4.1 ADC+DMA

4.1.1 功能配置

    这里我们试着一次采三个通道,分别是片内温度、参考电压和备份电源电压。
    配置好ADC,ADC的配置可以参考《STM32CubeMX-单ADC模式规则通道配置》。然后在ADC配置的基础上增加DMA的配置。注意,用HAL库的时候,千万不要配置连续转换!!因为HAL库的DMA中断操作时间过长,比ADC转换一次的时间还长,导致程序会一直频繁进DMA中断。
在这里插入图片描述
在这里插入图片描述
DMA Setting(DMA配置):DMA的基本功能配置窗口。
DMA Request(DMA请求来源):这个一般从哪个外设点进来就默认用哪个外设。
Channel(DMA通道ID):DMA一般有16个通道,当使用了多个DMA通道进行传输时,CubeMX会自动跳过已选择的通道,不用担心会选重。但如果不是通过CubeMX配置的话就要注意去重了。另外,不是所有通道都可以选,芯片手册有写明哪些外设只能用哪些通道,CubeMX会自动给你去除掉不可配置的通道。
Direction(数据传输方向):DMA本身是可以支持"外设到内存"、“内存到外设"和"内存到内存"这三个数据传输方向的,但因为这里选择了源数据为ADC,所以只能选择"外设到内存”。
Priority(传输优先级):这里区分了低、中、高、非常高四种优先级。前面说了因为DMA会有多个通道,所以当同时配多个通道时,并且同时有多个传输请求时,这时候就需要区分优先级看哪个先传哪个后传。如果都是一样的优先级,那就按通道的顺序执行。
Add/Delete(添加或删除):用于添加或删除DMA通道,因为使用的是ADC,只需要一个DMA通道即可解决,所以无法添加第二个DMA通道。
Mode(请求模式):可以选择单次或循环,如果选择了单次,那DMA会在一轮数据传输后停止传输;如果选择的是循环,则在传输完一轮数据之后自动进行下一轮的传输。
Increment Address(递增地址):勾选则表示每传一个数据,其对应的物理地址要递增一次。因为这里ADC多个通道采集后的数据都存放在DR这一个寄存器里,所以其物理地址不需要变化,而传输到内存后,如果内存地址不向上加1,则多个通道的数据会被相互覆盖。所以为了达成多个通道的数据自动采集放到不同内存地址,这里需要勾选内存地址递增。
Data Width(数据带宽):每传输一个数据的位数,可以选择8位、16位和32位。需要注意的是,对应的内存地址需要与数据类型的位数保持对齐,也就是说如果选择了16位数据传输,则用于存放的内存地址必须能被2整除;传输32位的数据则其内存地址需要被4整除。如果地址不对齐会导致数据传输后错位。
    上面的DMA功能配置完后,还需要翻到前面ADC的设置页,使能DMA的请求功能。
在这里插入图片描述

4.1.2 代码实现

    查看手册,找到温度的换算公式如下,其中TS_CAL1和TS_CAL2可以在数据手册中找到对应存放的内存地址,而TS_DATA则是当前的采集值。
在这里插入图片描述
在这里插入图片描述

    VREFINT(参考电压)及VBAT(备份电源电压)的换算公式,其中VREFINT_CAL可以在数据手册中找到对应存放的内存地址,VREFINT_DATA就是采集VREFINT的ADC值。
在这里插入图片描述
在这里插入图片描述
    把VREFINT_DATA换成VBAT_DATA * 2得出来的结果就是VBAT的值。乘2是因为手册里写的,VBAT有可能会大于VDD,为了防止输入的备份电源过高损坏单片机,所以单片机内部做了分压,最终给到ADC采集的值是二分后的电压。
在这里插入图片描述

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* ADC采集类型 */
enum emAdcType
{
    ADC_TYPE_temperature = 0,
    ADC_TYPE_vrefint = 1,
    ADC_TYPE_vbat = 2,

    ADC_TYPE_max
};
/* 换算结果缓存 */
float AdcData[ADC_TYPE_max] = {0, 0, 0};
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* 采集结果缓存 */
  uint16_t adc_buff[ADC_TYPE_max] = {0, 0, 0};
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	/* 启动采集 */
	HAL_ADC_Start_DMA(&hadc, (uint32_t *)adc_buff, ADC_TYPE_max);
	/* 温度换算 */
	#define TS_CAL1 ((uint16_t *)0x1FFFF7B8)
	#define TS_CAL2 ((uint16_t *)0x1FFFF7C2)
	AdcData[ADC_TYPE_temperature] = (float)(110 - 30) / ((*TS_CAL2) - (*TS_CAL1)) * (adc_buff[ADC_TYPE_temperature] - (*TS_CAL1)) + 30;
	
	/* 参考电压换算 */
	#define VREFINT_CAL ((uint16_t *)0x1FFFF7BA)
	AdcData[ADC_TYPE_vrefint] = (float)3 * (*VREFINT_CAL) / adc_buff[ADC_TYPE_vrefint];
	
	/* 备份电源电压换算 */
	AdcData[ADC_TYPE_vbat] = (float)3 * (*VREFINT_CAL) * 2 / adc_buff[ADC_TYPE_vbat];
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

4.1.3 效果演示

在这里插入图片描述
注:很奇怪这里的温度按手册的公式算出来一直是50几度,换了几块开发板都这样,后面有时间研究一下。

4.2 Uart+DMA

    这里我们来实现一个比较好玩的功能——串口透传,也就是开启两个串口,当一个串口接收到数据时,把数据给到另一个串口,由另一个串口发送出去;反过来同理。有人可能会说了,这样为什么不用板子直接把两个线接起来就行?我只能说这个功能,实际项目中就会用到,比如串口网关或控制器带透传功能等。话不多说,重点来看下实现。(由于F072的DMA不支持交叉通道配置,所以这里咋偷偷切换个C031来实现外设到外设的配置)。
在这里插入图片描述

4.2.1 功能配置

    这里我们来实现一个接收和发送都用DMA搬运的例子。
    同样的先配置好Uart,Uart的配置可以参考《STM32CubeMX-Uart配置 及 数据收发功能实现》。

在这里插入图片描述

注:这里串口1的发送口不要配在PC14,因为C031的PC14脚跟烧录引脚复用,需要有其他配置才能作为串口发送。
    然后在Uart配置的基础上增加DMA的配置。不同于ADC,Uart有收跟发两个传输方向,所以这里DMA可以配置两个通道,一个用于数据接收,一个用于数据发送。透传这里我们打算当接收到一个字节的数据时,使用DMA传输至另一个串口的TDR寄存器发送出去,所以这里只需要配置接收的DMA通道。
在这里插入图片描述

    因为这里串口的收发寄存器都只有一个,所以Memory地址也不需要递增。另外打开串口中断是为了在发送完一帧数据后,重置一下DMA的配置(因为现在DMA配置的是单次发送,所以每发完一次需要重新配置一次)。

4.2.2 代码实现

/***************************************main.c*********************************************/
/* USER CODE BEGIN 0 */
void Uart_PassThroughEn1(void)
{
    /* DMA失能 */
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);

    /* 设置DMA数据源 */
    LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_1, LL_USART_DMA_GetRegAddr(USART1, LL_USART_DMA_REG_DATA_RECEIVE));

    /* 设置DMA目标数据地址为另一路串口的发送寄存器地址 */
    LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, LL_USART_DMA_GetRegAddr(USART2, LL_USART_DMA_REG_DATA_TRANSMIT));

    /* 设置DMA数据长度-这里设置的是一次性最大能传输的数量为255 */
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 255);

    /* 打开接收的DMA传输使能 */
    LL_USART_EnableDMAReq_RX(USART1);

    /* 开DMA使能前清除标志 */
    LL_DMA_ClearFlag_TC1(DMA1);
    LL_DMA_ClearFlag_HT1(DMA1);

    /* DMA使能 */
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);

    /* 清除TC标志 */
    LL_USART_ClearFlag_TC(USART1);

    /* 使能TC中断 */
    LL_USART_EnableIT_TC(USART1);
}
void Uart_PassThroughEn2(void)
{
    /* DMA失能 */
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);

    /* 设置DMA数据源 */
    LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_2, LL_USART_DMA_GetRegAddr(USART2, LL_USART_DMA_REG_DATA_RECEIVE));

    /* 设置DMA目标数据地址为另一路串口的发送寄存器地址 */
    LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_2, LL_USART_DMA_GetRegAddr(USART1, LL_USART_DMA_REG_DATA_TRANSMIT));

    /* 设置DMA数据长度-这里设置的是一次性最大能传输的数量为255 */
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 255);

    /* 打开接收的DMA传输使能 */
    LL_USART_EnableDMAReq_RX(USART2);

    /* 开DMA使能前清除标志 */
    LL_DMA_ClearFlag_TC1(DMA1);
    LL_DMA_ClearFlag_HT1(DMA1);

    /* DMA使能 */
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);

    /* 清除TC标志 */
    LL_USART_ClearFlag_TC(USART2);

    /* 使能TC中断 */
    LL_USART_EnableIT_TC(USART2);
}

/* USER CODE END 0 */

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);

  /* SysTick_IRQn interrupt configuration */
  NVIC_SetPriority(SysTick_IRQn, 3);

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  Uart_PassThroughEn1();
  Uart_PassThroughEn2();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
/***************************************stm32f0xx_it.c*********************************************/
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern void Uart_PassThroughEn1(void);
extern void Uart_PassThroughEn2(void);
/* USER CODE END 0 */

/**
  * @brief This function handles USART1 interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	if ( (LL_USART_IsEnabledIT_TC(USART1))
    && (LL_USART_IsActiveFlag_TC(USART1))
    )
  {
    Uart_PassThroughEn1();
    LL_USART_ClearFlag_TC(USART1);
  }
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

/**
  * @brief This function handles USART2 interrupt.
  */
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
	if ( (LL_USART_IsEnabledIT_TC(USART2))
    && (LL_USART_IsActiveFlag_TC(USART2))
    )
  {
    Uart_PassThroughEn2();
    LL_USART_ClearFlag_TC(USART2);
  }
  /* USER CODE END USART2_IRQn 0 */
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}


4.2.3 效果演示

在这里插入图片描述

五、注意事项

1、使用ADC+DMA时,要留意一下生成代码的初始化顺序,之前出现过生成的代码ADC与DMA的初始化顺序错了,导致ADC配置的通道数被异常修改。
2、DMA传输的内存地址必须与传输的数据类型对齐,如果传输的是16位的数据,则其源和目标内存的地址都必须是2的整数倍;如果传输的是32位的数据,则必须是4的整数倍。如果不对齐DMA会强行访问对齐的地址,结果就是数据错误。
3、使用ADC+DMA时,如果要用HAL库,不能开启连续转换模式。因为HAL库里DMA的中断处理时间长于ADC的采样时间,导致开启传输后,程序几乎一直在DMA中断里出不来,造成程序“假死”的现象。

六、相关链接

【知识分享】异步串行收发器Uart(串口)-通信协议详解
【工具使用】STM32CubeMX-单ADC模式规则通道配置
【工具使用】STM32CubeMX-Uart配置 及 数据收发功能实现

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

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

相关文章

离散制造企业如何打造MES管理系统

在当今制造业中,MES生产管理系统越来越受到关注,但在实际应用中也遇到了一些问题。本文分析了离散制造业和流程生产行业的MES应用现状,指出了这两个行业在部署MES管理系统时存在差异的原因,并探讨了如何在离散制造业提升生产效率&…

本地缓存 guava

缓存接口 集成guava本地缓存

前端Javascript模块化

🎬 岸边的风:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 引言 前端模块化的发展历程 1.全局函数式编程 2.命名空间模式 3.CommonJS require函数 module.exports 4.AM…

笔记(一)斯坦福CS224W图机器学习、图神经网络、知识图谱

节点和连接构成的图 如何对图数据进行挖掘? 传统机器学习,数据是独立同分布的,解决表格、矩阵、序列等问题 图机器学习处理连接的数据,需要满足以下几个方面: 1、图是任意尺寸输入2、图是动态变化的,有时…

Haproxy集群与常见的Web集群调度器

文章目录 1. Web集群调度器概述1.1 Web集群调度器简介1.2 调度器类别1.2.1 常用软件类1.2.2 常用硬件类 2. Haproxy软件介绍2.1 Haproxy简介2.2 支持功能2.3 主要特性2.4 常用调度算法2.4.1 轮询:RR(Round Robin)2.4.2 最小连接数&#xff1a…

软件测试与开发实训室建设方案

一 、系统概述 软件测试与开发实训室是软件开发过程中的一项重要测试活动,旨在验证不同软件模块或组件之间的集成与交互是否正常。综合测试确保各个模块按照设计要求正确地协同工作,以实现整个软件系统的功能和性能。以下是软件测试与开发实训室的一般流…

第一章 SQL Server 数据库部署

个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。座右铭:海不辞水,故能成其大;山不辞石,故能成其高。 个人主页:小李会科技的…

【黄色手套22】9话:栈和队列

目录 栈和队列的基本概念: 数据结构中的栈和队列: 栈和队列的基本结构: 1.栈和队列的结构示意图 2.栈和队列中数据的插入和删除 栈和队列的实现: 栈的实现 栈.c stack.h 源stack.c 队列的实现 队列.c queue.h queue.c…

使用branch and bound分支定界算法选择UTXO

BnB算法原理 分支定界算法始终围绕着一颗搜索树进行的,我们将原问题看作搜索树的根节点,从这里出发,分支的含义就是将大的问题分割成小的问题。 大问题可以看成是搜索树的父节点,那么从大问题分割出来的小问题就是父节点的子节点…

【办公自动化】用Python批量从上市公司年报中获取主要业务信息

🤵‍♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞&#x1f4…

电力系统优化:数字孪生的革新方法

数字孪生技术在电力系统中能够发挥令人瞩目的作用。这项技术不仅可以提高电力系统的可靠性和效率,还有助于降低能源浪费和环境影响。本文将深入探讨数字孪生技术在电力领域的多个关键应用领域,以展示其潜力和重要性。 数字孪生技术可以创建电力设备的虚拟…

农村怎样利用光伏发电?

随着经济快速发展,光伏、风电等可再生能源在农村地区普及率越来越高,极大推动了农业产业发展,提高了农民收入,改善了农村生态环境,促进了乡村各项工作健康发展。 光伏+农业是一种新兴的农业形式&#xff0…

如何获取美团的热门商品和服务

导语 美团是中国最大的生活服务平台之一,提供了各种各样的商品和服务,如美食、酒店、旅游、电影、娱乐等。如果你想了解美团的热门商品和服务,你可以使用爬虫技术来获取它们。本文将介绍如何使用Python和BeautifulSoup库来编写一个简单的爬虫…

文件上传漏洞~操作手册

目录 上传文件一般过滤方式 客服端校验 服务端校验 黑白名单机制 常规文件上传漏洞绕过 客户端绕过 1.游览器禁用JavaScript 2.正常burp suite抓包改包 服务端绕过 1.Content-Type绕过 2.黑名单绕过 1)命名规则绕过 2)大小写绕过 3&#x…

jvm的调优工具

1. jps 查看进程信息 2. jstack 查看进程的线程 59560为进程id 产生了死锁就可以jstack查看了 详细用途可以看用途 3. jmap 如何使用dump文件看下 查看 4.jstat 空间占用和次数 5. jconsole可视化工具 各种使用情况,以及死锁检测 6. visualvm可视化工具…

如何使用ArcGIS Pro提取河网水系

DEM数据除了可以看三维地图和生成等高线之外,还可以用于水文分析,这里给大家介绍一下如何使用ArcGIS Pro通过水文分析提取河网水系,希望能对你有所帮助。 数据来源 本教程所使用的数据是从水经微图中下载的DEM数据,除了DEM数据&a…

导入发运地点wsh_locations

当客户上的地址变更之后,发运事务处理上的地址还是原来的地址,发运上的地址来源table wsh_locations. 解决办法:运行接口请求,导入发运地点,日期可以范围包括地点变更的日期。 请求完成后再次查看地址发现地址已变更…

iwebsec靶场 文件包含漏洞通关笔记10-data伪协议利用

目录 前言 1.data伪协议 2.使用条件 第10关 data://伪协议利用 1.打开靶场 2.源码分析 3.渗透 (1)明文渗透 (2)base64编码渗透 前言 1.data伪协议 data协议和input协议差不多,指定data是get方法,…

从零基础到精通Flutter开发:一步步打造跨平台应用

💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 导言 Flutter是一种流行…

QLabel 显示圆图并且含有边框 border-image

在一个label上流动显示头像,并且不能遮挡边框(如头像1-3),不想放两个label堆叠,所以最后采用了下述方法解决 1.设置边框还有图片 2.设置图片自适应 3.将头像切割成圆形 QPixmap pixmap QPixmap::fromImage(img); …