ESP32外设学习部分--SPI篇
SPI学习
前言
我个人以为开始学习一个新的单片机最好的方法就是先把他各个外设给跑一遍,整体了解一下他的功能,由此记录一下我学习ESP32外设的过程,防止以后忘记。
SPI 配置步骤
SPI总线初始化
spi_bus_config_t buscfg = {.miso_io_num = PIN_NUM_MISO,.mosi_io_num = PIN_NUM_MOSI,.sclk_io_num = PIN_NUM_CLK,.quadwp_io_num = -1,.quadhd_io_num = -1,.max_transfer_sz = PARALLEL_LINES * 320 * 2 + 8};
点击进入可以查看结构体中的内容如下
typedef struct {union {int mosi_io_num; ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.int data0_io_num; ///< GPIO pin for spi data0 signal in quad/octal mode, or -1 if not used.};union {int miso_io_num; ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.int data1_io_num; ///< GPIO pin for spi data1 signal in quad/octal mode, or -1 if not used.};int sclk_io_num; ///< GPIO pin for SPI Clock signal, or -1 if not used.union {int quadwp_io_num; ///< GPIO pin for WP (Write Protect) signal, or -1 if not used.int data2_io_num; ///< GPIO pin for spi data2 signal in quad/octal mode, or -1 if not used.};union {int quadhd_io_num; ///< GPIO pin for HD (Hold) signal, or -1 if not used.int data3_io_num; ///< GPIO pin for spi data3 signal in quad/octal mode, or -1 if not used.};int data4_io_num; ///< GPIO pin for spi data4 signal in octal mode, or -1 if not used.int data5_io_num; ///< GPIO pin for spi data5 signal in octal mode, or -1 if not used.int data6_io_num; ///< GPIO pin for spi data6 signal in octal mode, or -1 if not used.int data7_io_num; ///< GPIO pin for spi data7 signal in octal mode, or -1 if not used.int max_transfer_sz; ///< Maximum transfer size, in bytes. Defaults to 4092 if 0 when DMA enabled, or to `SOC_SPI_MAXIMUM_BUFFER_SIZE` if DMA is disabled.uint32_t flags; ///< Abilities of bus to be checked by the driver. Or-ed value of ``SPICOMMON_BUSFLAG_*`` flags.esp_intr_cpu_affinity_t isr_cpu_id; ///< Select cpu core to register SPI ISR.int intr_flags; /**< Interrupt flag for the bus to set the priority, and IRAM attribute, see* ``esp_intr_alloc.h``. Note that the EDGE, INTRDISABLED attribute are ignored* by the driver. Note that if ESP_INTR_FLAG_IRAM is set, ALL the callbacks of* the driver, and their callee functions, should be put in the IRAM.*/} spi_bus_config_t;
可以看到上面可以配置的内容非常多,可以配置三线四线SPI,但是我们本次主要学习两线的就可以了,用不到的管脚直接配置-1就OK了。
添加SPI设备
spi_device_interface_config_t devcfg = {#ifdef CONFIG_LCD_OVERCLOCK.clock_speed_hz = 26 * 1000 * 1000, //Clock out at 26 MHz#else.clock_speed_hz = 10 * 1000 * 1000, //Clock out at 10 MHz#endif.mode = 0, //SPI mode 0.spics_io_num = PIN_NUM_CS, //CS pin.queue_size = 7, //We want to be able to queue 7 transactions at a time.pre_cb = lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line};
然后配置spi_device_interface_config_t
这个结构体,老规矩继续看一下这个结构体中的内容
typedef struct {uint8_t command_bits; ///< Default amount of bits in command phase (0-16), used when ``SPI_TRANS_VARIABLE_CMD`` is not used, otherwise ignored.uint8_t address_bits; ///< Default amount of bits in address phase (0-64), used when ``SPI_TRANS_VARIABLE_ADDR`` is not used, otherwise ignored.uint8_t dummy_bits; ///< Amount of dummy bits to insert between address and data phaseuint8_t mode; /**< SPI mode, representing a pair of (CPOL, CPHA) configuration:- 0: (0, 0)- 1: (0, 1)- 2: (1, 0)- 3: (1, 1)*/spi_clock_source_t clock_source;///< Select SPI clock source, `SPI_CLK_SRC_DEFAULT` by default.uint16_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.uint16_t cs_ena_pretrans; ///< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions.uint8_t cs_ena_posttrans; ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16)int clock_speed_hz; ///< SPI clock speed in Hz. Derived from `clock_source`.int input_delay_ns; /**< Maximum data valid time of slave. The time required between SCLK and MISOvalid, including the possible clock delay from slave to master. The driver uses this value to give an extradelay before the MISO is ready on the line. Leave at 0 unless you know you need a delay. For better timingperformance at high frequency (over 8MHz), it's suggest to have the right value.*/int spics_io_num; ///< CS GPIO pin for this device, or -1 if not useduint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flagsint queue_size; ///< Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_device_queue_trans but not yet finished using spi_device_get_trans_result) at the same timetransaction_cb_t pre_cb; /**< Callback to be called before a transmission is started.** This callback is called within interrupt* context should be in IRAM for best* performance, see "Transferring Speed"* section in the SPI Master documentation for* full details. If not, the callback may crash* during flash operation when the driver is* initialized with ESP_INTR_FLAG_IRAM.*/transaction_cb_t post_cb; /**< Callback to be called after a transmission has completed.** This callback is called within interrupt* context should be in IRAM for best* performance, see "Transferring Speed"* section in the SPI Master documentation for* full details. If not, the callback may crash* during flash operation when the driver is* initialized with ESP_INTR_FLAG_IRAM.*/} spi_device_interface_config_t;
可以看到这个结构体里面的参数也是比较多的,但是我们主要关注的其实就是clock_speed_hz
,mode
,spics_io_num
,这几个参数,这几个也是我们在STM32上最熟悉的;当然如果需要驱动LCD我们可能还需要控制一个DC脚,这时候也可以关注一下pre_cb
这个参数,这个是在启用SPI传输前的回调,我们可以用它来控制下个发的是command还是data。
初始化总线
//Initialize the SPI busret = spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
用上面的函数就可以初始化我们配置好的SPI总线。
入参的选择
typedef enum {SPI_DMA_DISABLED = 0, ///< Do not enable DMA for SPI#if CONFIG_IDF_TARGET_ESP32SPI_DMA_CH1 = 1, ///< Enable DMA, select DMA Channel 1SPI_DMA_CH2 = 2, ///< Enable DMA, select DMA Channel 2#endifSPI_DMA_CH_AUTO = 3, ///< Enable DMA, channel is automatically selected by driver} spi_common_dma_t;
前两个其实都不用讲,都是我们配置好的。最后一个要看下如果选择了DMA通道,发送的数据就要需要是被存储在DMA可以访问的内存中,不然容易出现问题。
添加设备
//Attach the LCD to the SPI busret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi);
用上面的函数就可以添加我们的SPI设备了
数据发送
数据发送的时候我们可以使用两个函数来发送我们的数据
spi_device_queue_trans 函数和spi_device_polling_transmit函数的区别
spi_device_queue_trans
和 spi_device_polling_transmit
都是ESP32 SPI主机驱动程序中用于发送SPI事务的函数,但它们在传输方式和使用场景上有所不同:
esp_err_t SPI_MASTER_ATTR spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait)esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc)
-
spi_device_queue_trans:
- 这个函数用于将SPI事务添加到中断传输队列中。
- 调用这个函数后,当前线程可以继续执行其他任务,而SPI传输将在后台通过中断服务程序异步处理。
- 它允许多个事务排队,适合于需要连续发送多个SPI事务的场景,或者当需要在传输之间插入其他代码时。
- 事务完成后,需要使用
spi_device_get_trans_result
函数来获取事务的结果。 - 这种方式适用于非阻塞传输,可以提高CPU的利用率。
-
spi_device_polling_transmit:
- 这个函数用于发送轮询模式下的SPI事务,调用后会等待事务完成并返回结果。
- 在轮询模式下,CPU会一直等待直到SPI事务完成,期间不能执行其他任务,这可能会导致CPU资源的浪费。
- 它适合于对实时性要求较高的场合,或者当需要在事务之间精确控制时序时。
- 如果需要在传输中间插入其他代码,可以使用
spi_device_polling_start
和spi_device_polling_end
两个函数来实现。
总结来说,spi_device_queue_trans
提供了一种非阻塞的、异步的SPI传输方式,适合于多任务环境和需要高CPU利用率的场景;而spi_device_polling_transmit
提供了一种同步的、阻塞的传输方式,适合于对时序要求严格的场合。开发者可以根据具体的应用需求选择合适的函数。
struct spi_transaction_t {uint32_t flags; ///< Bitwise OR of SPI_TRANS_* flagsuint16_t cmd; /**< Command data, of which the length is set in the ``command_bits`` of spi_device_interface_config_t.** <b>NOTE: this field, used to be "command" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF 3.0.</b>** Example: write 0x0123 and command_bits=12 to send command 0x12, 0x3_ (in previous version, you may have to write 0x3_12).*/uint64_t addr; /**< Address data, of which the length is set in the ``address_bits`` of spi_device_interface_config_t.** <b>NOTE: this field, used to be "address" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0.</b>** Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000).*/size_t length; ///< Total data length, in bitssize_t rxlength; ///< Total data length received, should be not greater than ``length`` in full-duplex mode (0 defaults this to the value of ``length``).void *user; ///< User-defined variable. Can be used to store eg transaction ID.union {const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phaseuint8_t tx_data[4]; ///< If SPI_TRANS_USE_TXDATA is set, data set here is sent directly from this variable.};union {void *rx_buffer; ///< Pointer to receive buffer, or NULL for no MISO phase. Written by 4 bytes-unit if DMA is used.uint8_t rx_data[4]; ///< If SPI_TRANS_USE_RXDATA is set, data is received directly to this variable};} ; //the rx data should start from a 32-bit aligned address to get around dma issue.
同样的使用上面两个函数我们都需要传递这个结构体来指示一些东西
flags:SPI 事务标志位,可以使用 SPI_TRANS_*
宏定义进行设置。这些标志位用于控制事务的行为,例如是否使用 DMA、是否在事务结束后保持 CS 信号等
length:本次发送的总数据长度,以位为单位。SPI 最多一次可传输 64 字节(65536 位)数据,如果需要传输更多数据,建议使用 DMA
user:用户定义的变量,可以用于存储事务 ID 等信息
tx_buffer:发送数据缓冲区指针,如果不需要发送数据,则设置为 NULL
rx_buffer:接收数据缓冲区指针,如果不需要接收数据,则设置为 NULL,在 DMA 使用时,每次读取 4 字节
最终代码
void lcd_spi_pre_transfer_callback(spi_transaction_t *t){int dc=(int)t->user;gpio_set_level(PIN_NUM_DC, dc);// printf("DC get 1\n");}void send_non_blocking(spi_device_handle_t spi,uint8_t *data,uint16_t len){esp_err_t ret;static spi_transaction_t trans;memset(&trans, 0, sizeof(trans)); //Zero out the transactiontrans.length=8*len;trans.flags = 0; //undo SPI_TRANS_USE_TXDATA flagtrans.tx_buffer = data;ret=spi_device_queue_trans(spi, &trans, portMAX_DELAY);}spi_device_handle_t spi;uint8_t spi_send[5] DMA_ATTR = {0x23,0x12,0x37,0x44,0x55};void tx_spi_init(void){esp_err_t ret;spi_bus_config_t buscfg = {.miso_io_num = PIN_NUM_MISO,.mosi_io_num = PIN_NUM_MOSI,.sclk_io_num = PIN_NUM_CLK,.quadwp_io_num = -1,.quadhd_io_num = -1,.max_transfer_sz = 16 * 320 * 2 + 8};spi_device_interface_config_t devcfg = {#ifdef CONFIG_LCD_OVERCLOCK.clock_speed_hz = 26 * 1000 * 1000, //Clock out at 26 MHz#else.clock_speed_hz = 1 * 1000 * 1000, //Clock out at 10 MHz#endif.mode = 3, //SPI mode 0.spics_io_num = PIN_NUM_CS, //CS pin.queue_size = 7, //We want to be able to queue 7 transactions at a time.pre_cb = lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line};//Initialize the SPI busret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);ESP_ERROR_CHECK(ret);//Attach the LCD to the SPI busret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi);ESP_ERROR_CHECK(ret);}void spi_test(void)
{send_non_blocking(spi,spi_send,5);
}
相关文章:

ESP32外设学习部分--SPI篇
SPI学习 前言 我个人以为开始学习一个新的单片机最好的方法就是先把他各个外设给跑一遍,整体了解一下他的功能,由此记录一下我学习ESP32外设的过程,防止以后忘记。 SPI 配置步骤 SPI总线初始化 spi_bus_config_t buscfg {.miso_io_num …...

Tomcat的下载和使用,配置控制台输出中文日志
目录 1. 简介2. 下载3. 使用3.1 文件夹展示3.1.1 控制台输出乱码 3.2 访问localhost:80803.3 访问静态资源 4. 总结 1. 简介 Tomcat,全称为Apache Tomcat,是一个开源的Web应用服务器和Servlet容器,由Apache软件基金会的Jakarta项目开发。它实…...

MySQL不能被其他地址访问,授权问题解决(8.x,,5.x)
首先强调的是两个版本,5版本和8版本问题反馈不一样 Linux系统部署mysql8.4版本 MySQL官网地址写的很清楚了,不多介绍 直接进入主题,恶心了我三个多小时的问题,翻阅大量国内外资料,结果并不是个多么难得问题࿰…...

四、个人项目系统搭建
文章目录 一、python写的后端代码二、html代码三、index.css四、js代码 效果图: 一、python写的后端代码 后端代码使用Flask编写,如下所示: # app.py from flask import Flask, render_template, request, jsonify, g import sqlite3 import…...

CV(4)--边缘提取和相机模型
前言 仅记录学习过程,有问题欢迎讨论 边缘提取(涉及语义分割): 图象的边缘是指图象局部区域亮度变化显著的部分,也有正负之分,暗到亮为正 求边缘的幅度:sobel,Canny算子 图像分高频分量和低…...

SORT算法详解及Python实现
目录 SORT算法详解及Python实现第一部分:SORT算法概述与原理1.1 SORT算法简介1.2 应用场景1.3 算法流程第二部分:数学公式与主要模块2.1 卡尔曼滤波模型2.2 目标关联与匈牙利算法2.3 新建与移除机制第三部分:Python实现:SORT算法基础代码3.1 安装依赖3.2 基础代码实现第四部…...

图计算之科普:BSP计算模型、Pregel计算模型、
一、BSP计算模型 BSP计算模型,即整体同步并行计算模型(Bulk Synchronous Parallel Computing Model),又名大同步模型或BSP模型,是由哈佛大学L.G. Valiant教授(2010年图灵奖得主)在1992年提出的…...

pytest入门一:用例的执行范围
从一个或多个目录开始查找,可以在命令行指定文件名或目录名。如果未指定,则使用当前目录。 测试文件以 test_ 开头或以 _test 结尾 测试类以 Test 开头 ,并且不能带有 init 方法 测试函数以 test_ 开头 断言使用基本的 assert 即可 所有的…...

22. 正则表达式
一、概述 正则表达式(regular expression)又称 规则表达式,是一种文本模式(pattern)。正则表达式使用一个字符串来描述、匹配具有相同规格的字符串,通常被用来检索、替换那些符合某个模式(规则&…...

Flink Python作业快速入门
Flink Python快速入门_实时计算 Flink版(Flink)-阿里云帮助中心 import argparse # 用于处理命令行参数和选项,使程序能够接收用户通过命令行传递的参数 import logging import sysfrom pyflink.common import WatermarkStrategy, Encoder, Types from pyflink.data…...

自定义函数库
求两点距离 double dis(double x1, double y1, double x2, double y2){return sqrt(pow(x2-x1, 2)pow(y2-y1, 2)); }判断闰年 bool isLeapYear(int year){return year%40 && year%100!0 || year%4000; }判断素数 bool isPrime(int num){if(num<2) return false;f…...

FreeRTOS例程2-任务挂起恢复与使用中断遇到的坑!
任务挂起简单点理解就是现在不需要执行这个任务,让它先暂停,就是挂起。恢复就是从刚才挂起的状态下继续运行。 API函数 任务挂起vTaskSuspend() 函数原型(tasks.c中): void vTaskSuspend( TaskHandle_t xTaskToSuspend ) 1. 参数: xTaskTo…...

L23.【LeetCode笔记】验证回文串(剖析几种解法)
目录 1.题目 2.自解 提交结果 反思 大小写之间的位运算 提交结果 3.代码优化 提交结果 编辑 4.LeetCode网友提供的解法 1.题目 https://leetcode.cn/problems/XltzEq/description/ 给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数…...

FPGA 17 ,FPGA 与 SR-IOV虚拟化技术,高性能计算与虚拟化技术的结合(FPGA 与 SR-IOV 和 PCI,高性能计算与虚拟化的完美融合)
目录 前言 一. SR-IOV 的起源与发展 1. SR-IOV 的起源与时间线 2. SR-IOV 的诞生原因 3. SR-IOV 的详细介绍 二. SR-IOV 和 PCI 之间的关系 三. PCI 的起源与演进 1. PCI 的起源与时间线 2. PCI 的关键特性 四. FPGA 的独特魅力 1. FPGA 的定义与特性 2. FPGA 的内…...

解决navicat 导出excel数字为科学计数法问题
一、原因分析 用程序导出的csv文件,当字段中有比较长的数字字段存在时,在用excel软件查看csv文件时就会变成科学技术法的表现形式。 其实这个问题跟用什么语言导出csv文件没有关系。Excel显示数字时,如果数字大于12位,它会自动转化…...

[Unity] AppLovin Max接入Native 广告 Android篇
把下载下来的maxnativelibrary-release-文件放在Plugins/Android下 将这一行加入到mainTemplate.gradle文件中 implementation androidx.constraintlayout:constraintlayout:2.1.4添加下面的两个脚本 using System; using System.Collections; using System.Collections.Gener…...

Source Insight 4.0的安装
一、安装与破解 1、下载Source Insight 4.0安装包 https://pan.baidu.com/s/1t0u1RM19am0lyzhlNTqK9Q?pwdnvmk 2、下载程序破解补丁包 https://pan.baidu.com/s/1irvH-Kfwjf4zCCtWJByqJQ 其中包含文件si4.pediy.lic 和 sourceinsight4.exe。 3、安装下载的Source Insight …...

远程调试软件对比与使用推荐
远程调试软件对比与使用推荐 远程调试是现代软件开发中不可或缺的一部分,尤其是在处理分布式系统、云端服务或远程服务器上的问题时。以下是对几种常见远程调试工具的详细对比和推荐使用场景。 1. GDB (GNU Debugger) 特点 开源:完全免费且开源&…...

鸿蒙项目云捐助第二讲鸿蒙图文互动基本程序实现
鸿蒙项目云捐助第二讲鸿蒙图文互动基本程序实现 结合第一讲建立的“Hello World”程序,得到如下图所示的界面。 这里的“Hello World”是通过“Priview”显示出来的。在这个界面中进行开发的前奏曲,可以通过点击更换图片的案例来体会一下鸿蒙Next的开发…...

求解球面的一组正交标架
目录 求解球面的一组正交标架 求解球面的一组正交标架 球面 r ( u , v ) ( a cos u cos v , a cos u sin v , a sin u ) \mathbf{r}(u,v)\left(a\cos u\cos v,a\cos u\sin v,a\sin u\right) r(u,v)(acosucosv,acosusinv,asinu), 求得 r u ( − a sin u c…...

php.ini 文件上传/执行时间/部分配置新手教程
1、上传文件大小配置 一般需要同时配置“upload_max_filesize”、“post_max_size”,配置格式如下: file_uploads On ;是否允许HTTP文件上传 upload_max_filesize 2M ;设置单个文件上传的最大尺寸 post_max_size 8M ;设置 POST 请求体的最大尺寸&am…...

【Leetcode Top 100】102. 二叉树的层序遍历
问题背景 给你二叉树的根节点 r o o t root root,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 数据约束 树中节点数目在范围 [ 0 , 2000 ] [0, 2000] [0,2000] 内 − 1000 ≤ N o d e . v a l ≤ 1000 -1…...

【C++笔记】AVL树
前言 各位读者朋友们大家好,上期我们讲解了map和set这两大容器的使用,这一期我们讲解最早的平衡二叉搜索树——AVL树。 目录 前言一. AVL树的概念二. AVL树的实现2.1 AVL树的结构2.2 AVL树的插入2.2.1 AVL树插入一个值的大致过程2.2.2 平衡因子的更新2…...

【竞技宝】LOL:JDG官宣yagao离队
北京时间2024年12月13日,在英雄联盟S14全球总决赛结束之后,各大赛区都已经进入了休赛期,目前休赛期也快进入尾声,LPL大部分队伍都开始陆续官宣转会期的动向,其中JDG就在近期正式官宣中单选手yagao离队,而后者大概率将直接选择退役。 近日,JDG战队在官方微博上连续发布阵容变动消…...

双目摄像头标定方法
打开matlab 找到这个标定 将双目左右目拍的图像上传(左右目最好不少于20张) 等待即可 此时已经完成标定,左下角为反投影误差,右边为外参可视化 把这些误差大的删除即可。 点击导出 此时回到主页面,即可看到成功导出 Ca…...

相差不超过k的最多数,最长公共子序列(一),排序子序列,体操队形,青蛙过河
相差不超过k的最多数 链接:相差不超过k的最多数 来源:牛客网 题目描述: 给定一个数组,选择一些数,要求选择的数中任意两数差的绝对值不超过 𝑘 。问最多能选择多少个数? 输入描述: 第一行输入两个正整…...

【自然语言处理与大模型】使用llama.cpp将HF格式大模型转换为GGUF格式
llama.cpp的主要目标是在本地和云端的各种硬件上以最小的设置和最先进的性能实现LLM推理。是一个专为大型语言模型(LLM)设计的高性能推理框架,完全使用C和C编写,没有外部依赖,这使得它可以很容易地被移植到不同的操作系…...

MongoDB存储照片和文件存储照片的区别在那里?
一、维度对比 比较维度MongoDB存储照片文件系统存储照片数据模型使用文档存储数据,可以存储不同结构的照片。以文件的形式存储照片,每个文件独立存在。性能高效的数据检索,适用于大规模应用程序中的高效检索和访问。但在处理大量高分辨率图片…...

协变量的概念
协变量的概念 协变量的概念 协变量(Covariate)是在统计分析和研究中,与因变量(被研究的主要变量)相关,并且可能对因变量产生影响的其他变量。它不是研究的主要关注对象,但需要在分析过程中被考虑进去,因为它可能会混淆或改变自变量与因变量之间的关系。举例说明 教育研…...

【[LeetCode每日一题】Leetcode 1768.交替合并字符串
Leetcode 1768.交替合并字符串 题目描述: 给定两个字符串 word1 和 word2,以交替的方式将它们合并成一个新的字符串。即,第一个字符来自 word1,第二个字符来自 word2,第三个字符来自 word1,依此类推。如果…...