【正点原子STM32连载】第三十二章 DMA实验 摘自【正点原子】APM32E103最小系统板使用指南

1)实验平台:正点原子APM32E103最小系统板
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第三十二章 DMA实验

本章介绍APM32E103直接存储访问(DMA)的使用,DMA能够在无CPU干预的情况下,实现外设与存储器或存储器与存储器之间数据的高速传输,从而节省CPU资源来做其他操作。通过本章的学习,读者将学习到DMA的使用。
本章分为如下几个小节:
32.1 硬件设计
32.2 程序设计
32.3 下载验证

32.1 硬件设计
32.1.1 例程功能

  1. 按下KEY0按键,USART1以DMA方式发送数据,同时在LCD上显示DMA传输的进度
  2. LED0闪烁,指示程序正在运行
    32.1.2 硬件资源
  3. LED
    LED0 - PB5
  4. 按键
    KEY0 - PE4
  5. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  6. USART1(PA9、PA10连接至板载USB转串口芯片上)
  7. DMA(DMA1通道4)
    32.1.3 原理图
    本章实验使用的DMA为APM32E103的片上资源,因此没有对应的连接原理图。
    32.2 程序设计
    32.2.1 Geehy标准库的DMA驱动
    在使用DMA之前,需要先根据需求对DMA的通道进行配置,例如本章实验要使用DMA进行串口数据的发送,因此应将DMA的通道配置为存储器到外设的模式等等。使用DMA的具体步骤如下:
    ①:根据需求配置DMA通道
    ②:使能DMA通道进行数据传输
    ③:读DMA通道的状态标志判断传输是否完成
    ④:传输完成后清除DMA通道的传输完成标志
    在Geehy标准库中对应的驱动函数如下:
    ①:配置DMA通道
    该函数用于配置DMA通道的各项参数,其函数原型如下所示:
    void DMA_Config(DMA_Channel_T* channel, DMA_Config_T* dmaConfig)
    该函数的形参描述,如下表所示:
    形参 描述
    channel 指定的DMA通道
    例如:DMA1_channelx,其中x可以从1到7或DMA2_channely,同样的y可以从1至5(在apm32e10x.h文件中有定义)
    dmaConfig 指向DMA通道配置结构体的指针
    需自行定义,并根据DMA通道的配置参数填充结构体中的成员变量
    表32.2.1.1 函数DMA_Config()形参描述
    该函数的返回值描述,如下表所示:
    返回值 描述
    无 无
    表32.2.1.2 函数DMA_Config()返回值描述
    该函数使用DMA_Config_T类型的结构体变量传入DMA通道的配置参数,该结构体的定义如下所示:
typedef enum
{DMA_DIR_PERIPHERAL_SRC,DMA_DIR_PERIPHERAL_DST
} DMA_DIR_T;typedef enum
{DMA_PERIPHERAL_INC_DISABLE,						/* 禁止外设增量模式 */DMA_PERIPHERAL_INC_ENABLE						/* 使能外设增量模式 */
} DMA_PERIPHERAL_INC_T;typedef enum
{DMA_MEMORY_INC_DISABLE,							/* 禁用存储器递增模式 */DMA_MEMORY_INC_ENABLE							/* 使能存储器递增模式 */
} DMA_MEMORY_INC_T;typedef enum
{DMA_PERIPHERAL_DATA_SIZE_BYTE,					/* 外设数据大小为字节 */DMA_PERIPHERAL_DATA_SIZE_HALFWORD,				/* 外设数据大小为半字 */DMA_PERIPHERAL_DATA_SIZE_WORD					/* 外设数据大小为字 */
} DMA_PERIPHERAL_DATA_SIZE_T;typedef enum
{DMA_MODE_NORMAL,								/* 禁用循环模式 */DMA_MODE_CIRCULAR								/* 使能循环模式 */
} DMA_LOOP_MODE_T;typedef enum
{DMA_PRIORITY_LOW,								/* 优先级低 */DMA_PRIORITY_MEDIUM,							/* 优先级中 */DMA_PRIORITY_HIGH,								/* 优先级高 */DMA_PRIORITY_VERYHIGH							/* 优先级非常高 */
} DMA_PRIORITY_T;typedef struct
{uint32_t					peripheralBaseAddr;	/* 外设基地址 */uint32_t					memoryBaseAddr;		/* 存储器基地址 */DMA_DIR_T					dir;				/* 数据传输方向 */uint32_t					bufferSize;			/* 传输的数据项数目 */DMA_PERIPHERAL_INC_T		peripheralInc;		/* 外设增量模式 */DMA_MEMORY_INC_T			memoryInc;			/* 存储器递增模式 */DMA_PERIPHERAL_DATA_SIZE_T	peripheralDataSize;	/* 外设数据大小 */DMA_MEMORY_DATA_SIZE_T		memoryDataSize;		/* 存储器数据大小 */DMA_LOOP_MODE_T				loopMode;			/* 循环模式 */
DMA_PRIORITY_T				priority;			/* 优先级 */
DMA_M2MEN_T                M2M;
} DMA_Config_T;
该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_dma.h"static uint8_t membuf[256];void example_fun(void)
{DMA_Config_T dma_init_struct;/* 配置DMA1通道0通道0 */dma_init_struct.channel				= DMA_CHANNEL_0;dma_init_struct.peripheralBaseAddr	= (uint32_t)&USART1->DATA;dma_init_struct.memoryBaseAddr		= (uint32_t)&membuf[0];dma_init_struct.dir					= DMA_DIR_MEMORYTOPERIPHERAL;dma_init_struct.bufferSize			= sizeof(membuf) / sizeof(membuf[0]);dma_init_struct.peripheralInc		= DMA_PERIPHERAL_INC_DISABLE;dma_init_struct.memoryInc			= DMA_MEMORY_INC_ENABLE;dma_init_struct.peripheralDataSize	= DMA_PERIPHERAL_DATA_SIZE_BYTE;dma_init_struct.memoryDataSize		= DMA_MEMORY_DATA_SIZE_BYTE;dma_init_struct.loopMode			= DMA_MODE_NORMAL;dma_init_struct.priority			= DMA_PRIORITY_MEDIUM;dma_init_struct.fifoMode			= DMA_FIFOMODE_DISABLE;dma_init_struct.fifoThreshold		= DMA_FIFOTHRESHOLD_FULL;dma_init_struct.memoryBurst			= DMA_MEMORYBURST_SINGLE;dma_init_struct.peripheralBurst	= DMA_PERIPHERALBURST_SINGLE;DMA_Config(DMA1_Stream0, &dma_init_struct);
}

②:使能DMA通道
该函数用于使能DMA通道进行数据传输,其函数原型如下所示:
void DMA_Enable(DMA_Channel_T *channel);
该函数的形参描述,如下表所示:
形参 描述
channel 指定的DMA通道
表32.2.1.3 函数DMA_Enable()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表32.2.1.4 函数DMA_Enable()返回值描述
该函数的使用示例,如下所示:

#include "apm32e10x.h"
#include "apm32e10x_dma.h"void example_fun(DMA_Channel_T *dma_chx)
{/* 使能DMA通道 */DMA_Enable(dma_chx);
}

③:读取DMA通道的状态标志
该函数用于读取DMA通道的指定状态标志,其函数原型如下所示:
uint8_t DMA_ReadStatusFlag(DMA_FLAG_T flag);
该函数的形参描述,如下表所示:
形参 描述
flag 指定DMA通道的状态标志
例如:DMA1_FLAG_GINT1、DMA1_FLAG_TC1等(在apm32e10x_dma.h文件中有定义)
表32.1.2.5 函数DMA_ReadStatusFlag()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
SET 事件标志发生
RESET 事件标志未发生
表32.1.2.6 函数DMA_ReadStatusFlag()返回值描述
该函数的使用示例,如下所示:

#include "apm32e10x.h"
#include "apm32e10x_dma.h"void example_fun(void)
{uint8_t flag;/* 读取DMA1通道4的数据传输完成标志 */flag = DMA_ReadStatusFlag(DMA1_FLAG_TC4);if (flag == SET){/* Do something. */}else{/* Do something. */}
}

④:清除DMA通道状态标志
该函数用于清除DMA通道的状态标志,其函数原型如下所示:
void DMA_ClearStatusFlag(uint32_t flag);
该函数的形参描述,如下表所示:
形参 描述
flag 清除指定DMA通道的标志
例如:DMA1_FLAG_GINT1、DMA1_FLAG_TC1等(在apm32e10x_dma.h文件中有定义)
表32.2.1.7 函数DMA_ClearStatusFlag()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表32.2.1.8 函数DMA_ClearStatusFlag()返回值描述
该函数的使用示例,如下所示:

#include "apm32e10x.h"
#include "apm32e10x_dma.h"void example_fun(void)
{/* 清除DMA1通道1的传输完成标志 */DMA_ClearStatusFlag(DMA1_FLAG_TC1);
}

32.2.2 DMA驱动
本章实验的DMA驱动主要负责向应用层提供DMA的初始化和开启传输的函数。本章实验中,DMA的驱动代码包括dma.c和dma.h两个文件。
DMA驱动中,DMA的初始化函数,如下所示:

/*** @brief       初始化DMA* @param       dma_chx   : DMA通道* @param       par       : 外设地址* @param       mar       : 存储器0地址* @param       ndtr      : 传输的数据项数目* @retval      无*/
void dma_init(	DMA_Channel_T *dma_chx,uint32_t par, uint32_t mar,uint16_t ndtr)
{DMA_Config_T dma_init_struct;RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);          /* 使能DMA时钟 */DMA_Reset(dma_chx);                                     /* 复位DMA *//* 配置DMA */dma_init_struct.peripheralBaseAddr = par;               /* 外设基地址 */dma_init_struct.memoryBaseAddr = mar;                   /* DMA内存基地址 */dma_init_struct.dir = DMA_DIR_PERIPHERAL_DST;           /* 数据传输方向 */
dma_init_struct.bufferSize = ndtr;                      /* 传输的数据项数目 */
/* 外设增量模式 */dma_init_struct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
dma_init_struct.memoryInc = DMA_MEMORY_INC_ENABLE;      /* 存储器递增模式 */
/* 外设数据大小 */
dma_init_struct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE; 
/* 存储器数据大小 */dma_init_struct.memoryDataSize = DMA_MEMORY_DATA_SIZE_BYTE;         dma_init_struct.loopMode = DMA_MODE_NORMAL;             /* 正常工作模式 */
dma_init_struct.priority = DMA_PRIORITY_MEDIUM;         /* 优先级 */
/* DMA通道x没有设置为内存到内存传输 */dma_init_struct.M2M = DMA_M2MEN_DISABLE;DMA_Config(dma_chx, &dma_init_struct);                  /* 配置DMA通道 */DMA_Enable(dma_chx);                                    /* 使能DMA通道 */
}

从上面的代码中可以看到,DMA的初始化函数提供了DMA通道、外设基地址、存储器基地址、传输数据项数据的配置参数,结合这些传入的参数,通过DMA通道配置函数DMA_Config()配置了指定的DMA通道。
DMA驱动中,开启DMA传输的函数,如下所示:

/*** @brief       开启一次DMA传输* @param       dma_chx   : DMA通道* @param       ndtr      : 传输的数据项数目* @retval      无*/
void dma_enable(DMA_Channel_T *dma_chx, uint16_t ndtr)
{DMA_Disable(dma_chx);                    /* 关闭USART1 TX DMA1 所指示的通道 */DMA_ConfigDataNumber(dma_chx, ndtr);     /* 配置传输的数据项数目 */DMA_Enable(dma_chx);                     /* 使能USART1 TX DMA1 所指示的通道 */
}

从上面的代码中可以看到,开启DMA数据传输实际上就是调用函数DMA_Enable()使能DMA通道,但考虑到每次传输的数据量可能不同,因此在使能DMA通道前,还配置了DMA通道传输数据项目的数量。
32.2.3 实验应用代码
本实验的应用代码,如下所示:
/* 要循环发送的字符串 */

const uint8_t text_to_send[] = {"正点原子 APM32E103最小系统板 DMA实验\r\n"};
#define SEND_BUF_SIZE (sizeof(text_to_send) * 200)  /* 发送数据长度 */
uint8_t g_sendbuf[SEND_BUF_SIZE];                   /* 发送数据缓冲区 */int main(void)
{uint8_t key;uint16_t i;uint16_t len;uint16_t pro;NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4);/* 设置中断优先级分组为组4 */sys_apm32_clock_init(15);                       /* 配置系统时钟 */delay_init(120);                                /* 初始化延时功能 */usart_init(115200);                             /* 初始化串口 */led_init();                                     /* 初始化LED */lcd_init();                                     /* 初始化LCD */
dma_init( DMA1_Channel4, (uint32_t) & USART1->DATA, (uint32_t)g_sendbuf, SEND_BUF_SIZE);                       /* 初始化DMA */lcd_show_string(30, 50, 200, 16, 16, "APM32", RED);lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);len = sizeof(text_to_send);                                                             /* 填充发送数据缓冲区 */for (i = 0; i < SEND_BUF_SIZE; i++){g_sendbuf[i] = text_to_send[i % len];}i = 0;while (1){key = key_scan(0);if (key == KEY0_PRES){lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);lcd_show_string(30, 150, 200, 16, 16, "   %", BLUE);USART_EnableDMA(USART_UX, USART_DMA_TX);                                        /* 使能USART的DMA发送 */dma_enable(DMA1_Channel4, SEND_BUF_SIZE);                                       /* 开启一次DMA传输 *//* 等待DMA传输完成* 实际应用中,在等待DMA传输完成期间,可以执行另外的任务*/while (1){   /* 判断通道4传输完成 */if (DMA_ReadStatusFlag(DMA1_FLAG_TC4) != RESET){   /* 清除通道4传输完成标志 */DMA_ClearStatusFlag(DMA1_FLAG_TC4);break;}pro = DMA_ReadDataNumber(DMA1_Channel4);                                    /* 得到当前还剩余多少个数据  */pro = ((SEND_BUF_SIZE - pro) * 100) / SEND_BUF_SIZE;                        /* 获得百分比 */lcd_show_num(30, 150, pro, 3, 16, BLUE);                                    /* LCD显示DMA传输进度 */}pro = DMA_ReadDataNumber(DMA1_Channel4);pro = ((SEND_BUF_SIZE - pro) * 100) / SEND_BUF_SIZE;lcd_show_num(30, 150, pro, 3, 16, BLUE);lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE);             /* LCD显示传送完成 */}i++;if ((i % 20) == 0){i = 0;LED0_TOGGLE();                                                                  }delay_ms(10);}
}

从上面的代码中可以看到,在DMA的初始化中,初始化了DMA1通道4,这是因为USART1的发送就对应了DMA1通道4,完成初始化工作后,就不断地扫描按键,若扫描到KEY0按键,并使用DMA进行USART1发送数据,在DMA进行数据传输的此期间,LCD上不断刷新显示DMA传输的进度,直至DMA传输完毕。
32.3 下载验证
在完成编译和烧录操作后,可以看到LCD上显示了本实验的实验信息,此时若按下KEY0按键,可以看到串口调试助手不断打印“正点原子 APM32E103最小系统板 DMA实验\r\n”(200次),在此期间也能看到LCD上不断地实时刷新串口打印的进度。

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

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

相关文章

LLM论文:ALCE (Enabling Large Language Models to Generate Text with Citations)

这是一篇RAG领域的文章&#xff0c;原文在这&#xff1a;https://aclanthology.org/2023.emnlp-main.398.pdf 时间[Submitted on 24 May 2023 (v1), last revised 31 Oct 2023 (this version, v2)]背景LLM在信息搜索、生成带引用的文本时存在幻觉问题&#xff0c;即事实准确性…

【Axure高保真原型】树控制内联框架

今天和大家分享树控制内联框架的原型模板&#xff0c;点击树的箭头可以打开或者收起子节点&#xff0c;点击最后一级人物节点&#xff0c;可以切换右侧内联框到对应的页面&#xff0c;左侧的树是通过中继器制作的&#xff0c;使用简单&#xff0c;只需要按要求填写中继器表格即…

艾瑞报告:HR数字化需关注体系化能力,红海云等标杆厂商引领一体化趋势

新全球化时代背景下&#xff0c;企业经营所面临的国内外环境的不确定性增强&#xff0c;如何从不确定性中找到确定性成了大多数企业的关注要点。近日&#xff0c;艾瑞咨询发布《2023中国人力资源数字化研究报告》&#xff0c;从数字化转型的角度切入&#xff0c;探讨数字化如何…

基于传统机器学习模型算法的项目开发详细过程

1 场景分析 1.1 项目背景 描述开发项目模型的一系列情境和因素&#xff0c;包括问题、需求、机会、市场环境、竞争情况等 1.2. 解决问题 传统机器学习在解决实际问题中主要分为两类&#xff1a; 有监督学习&#xff1a;已知输入、输出之间的关系而进行的学习&#xff0c;从而…

导航相关知识梳理

接下来轮到了专业知识---》android知识--》通用的知识 导航相关的知识整理开始了&#xff0c;应该是要一个星期差不多的。每天加一点 百度地图&#xff0c;高德地图&#xff0c;百度地图相关的虽然比较全面&#xff0c;每个模块都有了解&#xff0c;毕竟自己带了多个相关项目…

【Dart】=> [04] Dart初体验-基础语法(流程控制-if-switch-case-for循环

目录 基础语法-流程控制if 语句补充&#xff1a;三元运算符switch – case 语句switch – case 语句for 循环计算购物车中被选中商品的总金额 基础语法-流程控制 学习内容&#xff1a; if 语句switch – case 语句for 循环语句 if 语句 if 语句让代码有选择的执行&#xff…

二叉树DFS

基础知识 二叉树遍历 二叉搜索树BST 二叉树三种深度遍历 LeetCode 94. 二叉树的中序遍历 class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans new ArrayList<>();inorder(root, ans);return ans;}public void in…

JAVA JNA调用动态链接库

1.JAVA 中pom坐标的引入 <dependency><groupId>net.java.dev.jna</groupId><artifactId>jna-platform</artifactId><version>5.2.0</version></dependency> 2.首先根据系统判断动态连接库的调用的类型 String os this.syste…

Pod的控制器

Pod的控制器是什么&#xff1f; pod控制器&#xff1a;工作负载均衡。workload。用于管理pod的中间层。确保pod资源符合预期的状态。 预期状态&#xff1a; 副本数 容器的重启策略 镜像拉取策略 pod出现故障时的重启等等 Pod控制器的类型 1、 replicaSet&#xff1a;指…

NLP论文阅读记录 - WOS | 2023 TxLASM:一种新颖的与语言无关的文本文档摘要模型

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.文献综述及相关工作三.本文方法四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 TxLASM: A novel language agnostic summarization mo…

用了这8个 Python 数据挖掘工具包,生产力明显得到了提升!

在进行数据科学时&#xff0c;可能会浪费大量时间编码并等待计算机运行某些东西。所以我选择了一些 Python 库&#xff0c;可以帮助你节省宝贵的时间。 喜欢记得收藏、关注、点赞&#xff0c;文末送技术交流和资料 1、Optuna Optuna 是一个开源的超参数优化框架&#xff0c;…

ArkTS中自定义组件

ArkTS中自定义组件 一、组件位置二、Hello.ets自定义组件自定义组件 三、Second.ets父组件 一、组件位置 一个项目下所有的自定义的组件名不可以重复&#xff0c;无论是否在一个ets文件中 二、Hello.ets自定义组件 自定义组件 1&#xff1a;组件必须使用Component装饰 2&#…

stable-diffusion-webui的逐步部署教程

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

IIC学习之SHT30温湿度传感器(基于STM32)

简介 附上SHT30资料和逻辑分析仪源文件&#xff0c;点击下载 关于IIC的介绍网上已经非常详尽&#xff0c;这里只说重点&#xff1a; 双线&#xff08;SDA&#xff0c;SCL&#xff09;&#xff0c;半双工采用主从结构&#xff0c;支持一主多从&#xff0c;通过地址寻址&#…

USB转串口芯片 国产GP232RL 低成本兼容替代FT232RL 数据手册资料参考

随着工业物联网的不断发展&#xff0c;USB接口正在逐渐替代老式电脑的各种低俗外围接口&#xff0c;然而目前的工业环境中&#xff0c;许多重要的设备仍然使用RS232、RS422、RS485接口界面设计&#xff0c;因此许多用户使用USB转RS232、RS422、RS485转换器或者是转换线&#xf…

Overmind平台推出Sui任务,帮助开发者学习Move并构建强大的应用程序

Overmind与Sui基金会合作&#xff0c;推出了其首个任务系列&#xff0c;旨在帮助开发者学习Move并开始在Sui上构建。这些任务通过提供赢取奖励的机会来将学习体验变成游戏&#xff0c;激励开发者构建高质量的代码并向Sui社区展示他们的技能。 去年推出的Overmind平台正在扩展到…

从零学算法46

46.给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例 2&#xff1a; 输入&#xff1a;nums…

Leetcode20-唯一摩尔斯密码词(804)

1、题目 国际摩尔斯密码定义一种标准编码方式&#xff0c;将每个字母对应于一个由一系列点和短线组成的字符串&#xff0c; 比如: ‘a’ 对应 “.-” &#xff0c; ‘b’ 对应 “-…” &#xff0c; ‘c’ 对应 “-.-.” &#xff0c;以此类推。 为了方便&#xff0c;所有 26…

new mars3d.graphic.PolylineEntity({在固定高度开启编辑模式的坐标自定义处理

功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 问题场景&#xff1a; 1.在编辑航线时&#xff0c;需要指定航线坐标在固定高度值上开启编辑模式。 2.关键代码是&#xff1a; graphicLayer.startEditing({开启编辑模式&#xff0c;进行航线的编辑。 解决方案&#xf…

大模型实战营Day4 XTuner 大模型单卡低成本微调实战 作业

按照文档操作&#xff1a; 单卡跑完训练&#xff1a; 按照要求更改微调的数据&#xff1a; 完成微调数据的脚本生成&#xff1a; 修改配置文件&#xff1a; 替换好文件后启动&#xff1a; 启动后终端如图&#xff1a; 用于微调的一些数据显示&#xff1a; 训练时间&#x…