基于STM32,TB6612,TCRT5000的简易红外循迹小车

news2024/12/25 12:20:18

        提醒:本文章只叙述此小车相关大概内容(如模块的设置,C语言基础实现等),单片机详细教学不涉及。

摘要

        循迹小车是学习单片机的“地基”,它能够让初学者认识单片机内部硬件结构及其功能,熟悉单片机的一些基础操作,如I/O的应用,定时中断与外部中断的应用等,同时也能让初学者对于C语言编程有更深的认识。我采用STM32F103C8T6、TB6612、TCRT5000三个主要模块进行小车组装,刚开始确实有很多问题,随着进一步深入,问题也迎刃而解了,所以我们学习这个小车,主要在于思想的转变和善于去研究,我相信很多过程中遇到的难题都会被我们解决的。

目录

摘要

一、材料选择

二、模块思维导图

三、主要材料概述

1、STM32F103C8T6最小系统板

2、TB6612电机驱动

3、LM2596S DC-DC直流可调降压模块

 4、TCRT5000红外循迹模块

5、显示屏模块

三、相关代码


一、材料选择

        最近也是参加电赛,所以所有的材料都是运用学校实验室提供的。

  1. 单片机:STM32F103C8T6;
  2. 电机驱动:TB6612或者L298(我选用TB6612);
  3. 降压模块:(可随意)LM2596;
  4. 循迹模块:TCRT5000(5个);
  5. 显示屏模块:四针脚I²C版本OLED;
  6. 四个电机加轮子;
  7. 杜邦线若干;
  8. 开关一个;
  9. 电池(12V)。

二、模块思维导图

    

         注意:本思维导图只针对于代码中各模块部分。

三、主要材料概述

1、STM32F103C8T6最小系统板

        STM32是一个微控制器产品系列的总称,目前这个系列中已经包含了多个子系列,分别是: STM32 小容量产品、STM32 中容量产品、 STM32 大容量产品和 STM32 互联型产品;按照功能上的划分,又可分为STM32F101xx、 STM32F102xx STM32F103xx 系列。
        STM32F103 C8T6引脚定义图:
        注意:
        (1)单片机有些引脚对应功能是特有的,如定时器的通道输出口,它们在使用的时候只需要初始化I/O即可;
        (2)单片机最大承受电压为5V,注意通电之前一定要查看降压模块的连接是否正确或者是否有降压模块。

2、TB6612电机驱动

        TB6612电机驱动共有16个个引脚。

        VM最大接15V电源,本博客接12V足以;

        VCC接3.3V或5V;

        GND不用说了吧,接地就行;

        PWMA、PWMB需要PWM波(方波),以控制A电机或B电机的速度,连接单片机时要注意PWM波输出的端口对应好,本文采用的是定时器TIM2的3、4通道,所以PWMA、PWMB分别连接PA2、PA3口,具体要参考你用的哪个定时器,然后根据引脚定义图对应好引脚位置;

        AIN1、AIN2、BIN1、BIN2用来控制电机的正反转,需要连接单片机的I/O口来给予它们高低电平,AIN控制A电机,BIN控制B电机,具体控制如下表(以控制A电机为例):

AIN1010
AIN2001
停止正转反转

        AO1、A02、BO1、BO2可以驱动电机,所以直接连接电机来让它们转起来!(注意别连反了,不然你的电机总是逆天的反转或者转圈~)

        其实对于电机驱动模块最麻烦的就是PWM波的输入以及脉宽调制,具体体现在代码中。

3、LM2596S DC-DC直流可调降压模块

        因为在电路中有许多器件不能承受12V电压,所以我们需要将电源电压降到5V或者3.3V,这时我们就要用到降压模块,具体连接方法很明显,以LM2596为例,IN+连接电源正极,IN-连接电源负极,OUT+输出3.3V或者5V电压,OUT-输出GND。

 4、TCRT5000红外循迹模块

        相较于其它循迹方法,红外循迹较为简单,但也无法做到十分精准,本文以红外循迹为例,讲述循迹方法,希望各位同学能举一反三,继续学习!

        

       它有四个引脚:VCC(接3~5V电压)、GND(接地)、D0(接单片机I/O口)、A0(模拟信号输出,一般不接)

        话不多说,直接讲原理:当检测到黑线时,传感器上的指示灯灭掉,D0输出高电平返回到单片机上;当未检测到黑线时,传感器上的指示灯亮,D0输出低电平返回到单片机上。

5、显示屏模块


三、相关代码

motor.c

#include "stm32f10x.h"                  // Device header
#include "pwm.h"

//注意:增加某个函数要在对应.h文件中声明,否则会报错。

void Motor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;	//定义I/O口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	PWM_Init();
}

void Motor_LEFT_SetSpeed(int8_t Speed)		//左电机正反转
{
	if (Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_4);
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(Speed);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(-Speed);
	}
}
void Motor_RIGHT_SetSpeed(int8_t Speed)		//右电机正反转
{
	if (Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_6);
		GPIO_ResetBits(GPIOA, GPIO_Pin_7);
		PWM_SetCompare4(Speed);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_6);
		GPIO_SetBits(GPIOA, GPIO_Pin_7);
		PWM_SetCompare4(-Speed);
	}
}

pwm.c

#include "stm32f10x.h"                  // Device header

extern uint16_t Num;			//调用.c文件定义的Num变量
extern uint16_t t;
extern int FLAG;
void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;		//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;		//CCR
	
	
	TIM_OC3Init(TIM2, &TIM_OCInitStructure);

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;		//CCR
	
	TIM_OC4Init(TIM2, &TIM_OCInitStructure);
	
	
  
	TIM_Cmd(TIM2, ENABLE);
}
void Timer_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM3, ENABLE);
}
void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
	{
		Num ++;
		if(FLAG == 3)
		{
			t = Num;
		}
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2, Compare);
}

void PWM_SetCompare4(uint16_t Compare)
{
	TIM_SetCompare4(TIM2, Compare);
}

main.c

#include "stm32f10x.h"
#include "delay.h"
#include "motor.h"
#include "tcrt5000.h"
#include "OLED.h"
#include "pwm.h"

uint16_t t;
uint16_t Num;
uint16_t LEFT1,RIGHT1,LEFT2,RIGHT2,MIDDLE;
int FLAG = 0;
int main(void)
{	
	Timer_Init();	//初始化计时函数
	OLED_Init();	//初始化显示屏函数
	Motor_Init();	//初始化电机驱动函数
	tcrt5000_init();	//初始化红外循迹函数
    while (1)
    {
		OLED_ShowString(2, 1, "TIME:");
		OLED_ShowNum(2, 6, Num, 5);
		RIGHT1 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5);
		LEFT1 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6);
		RIGHT2 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7);
		LEFT2 = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8);
		MIDDLE = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9);
		if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 1 && RIGHT2 == 0 && RIGHT1 == 0)				//直走
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(65);
		}

		else if((LEFT1 == 1 || LEFT2 == 1) && MIDDLE == 0 && RIGHT2 == 0 && RIGHT1 == 0)		//左转
		{
			Motor_LEFT_SetSpeed(-50);
			Motor_RIGHT_SetSpeed(65);
		}

		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 0 && (RIGHT1 == 1 || RIGHT2 == 1))		//右转
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(-50);
		}

		else if(LEFT1 == 0 && LEFT2 == 1 && MIDDLE == 1 && RIGHT2 == 0 && RIGHT1 == 0)			//左转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(-50);
			Motor_RIGHT_SetSpeed(65);
		}
		
		else if(LEFT1 == 1 && LEFT2 == 1 && MIDDLE == 1 && RIGHT2 == 0 && RIGHT1 == 0)			//左转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(-50);
			Motor_RIGHT_SetSpeed(65);
		}
		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 1 && RIGHT2 == 1 && RIGHT1 == 0)			//右转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(-50);
		}
		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 1 && RIGHT2 == 1 && RIGHT1 == 1)			//右转(增加灵敏度)
		{
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(-50);
		}
		
		else if(LEFT1 == 0 && LEFT2 == 0 && MIDDLE == 0 && RIGHT2 == 0 && RIGHT1 == 0)			//未探测到黑线的时候直走(防止未检测到黑线时不动)
		{	
			Motor_LEFT_SetSpeed(65);
			Motor_RIGHT_SetSpeed(65);
		}

		else if(LEFT1 == 1 && LEFT2 == 1 && MIDDLE == 1 && RIGHT2 == 1 && RIGHT1 == 1)			//检测到横线
		{	
			if(FLAG == 0)							//标志位1(停止5秒后前进)
			{
				Motor_LEFT_SetSpeed(0);
				Motor_RIGHT_SetSpeed(0);
				delay_init();
				OLED_Clear();
				OLED_ShowString(1,7,"READY");
				OLED_ShowNum(3,1,5,1);
				delay_ms(1000);
				OLED_ShowNum(3,3,4,1);
				delay_ms(1000);
				OLED_ShowNum(3,5,3,1);
				delay_ms(1000);
				OLED_ShowNum(3,7,2,1);
				delay_ms(1000);
				OLED_Clear();
				OLED_ShowString(3,8,"GO!");
				delay_ms(1000);
				OLED_Clear();
				Motor_LEFT_SetSpeed(30);
				delay_ms(100);
				Motor_RIGHT_SetSpeed(30);
				delay_ms(100);
				FLAG ++;
			}
			else if(FLAG == 1)					//标志位2(停止5秒后前进)
			{
				Motor_LEFT_SetSpeed(0);
				Motor_RIGHT_SetSpeed(0);
				delay_init();
				OLED_Clear();
				OLED_ShowString(1,7,"READY");
				OLED_ShowNum(3,1,5,1);
				delay_ms(1000);
				OLED_ShowNum(3,3,4,1);
				delay_ms(1000);
				OLED_ShowNum(3,5,3,1);
				delay_ms(1000);
				OLED_ShowNum(3,7,2,1);
				delay_ms(1000);
				OLED_Clear();
				OLED_ShowString(3,8,"GO!");
				delay_ms(1000);
				OLED_Clear();
				Motor_LEFT_SetSpeed(80);
				delay_ms(500);
				Motor_RIGHT_SetSpeed(80);
				delay_ms(500);
				FLAG ++;
			}
			else if(FLAG == 2)					//标志位3(直走)
			{
				delay_init();
				OLED_ShowString(1,5,"STRINGHT");
				OLED_ShowString(2, 1, "TIME:");
				OLED_ShowNum(2, 6, Num, 5);
				Motor_LEFT_SetSpeed(85);
				Motor_RIGHT_SetSpeed(85);
				delay_ms(500);
				OLED_Clear();
				FLAG ++;
			}
			else if(FLAG == 3)					//标志位4(比赛结束,停止)
			{	
				FLAG = 0;
				int i;
				for(i = 0;i >= 0;i++)
				{
					OLED_ShowString(1,5,"STOP");
					OLED_ShowString(2, 1, "TIME:");
					OLED_ShowNum(2, 6, t, 5);
					Motor_LEFT_SetSpeed(0);
					Motor_RIGHT_SetSpeed(0);
				}
			}
		}	
    }
}

        代码中有很多冗余,由于时间紧张所以就没有仔细去改写,各位同学可以根据实际情况去修改内容。

       ( 另附:想要工程文件的同学可以评论邮箱,内含小车视频哦~转载请标明出处。)


        本次分享就到这里了,博主其实也是初学者,所以也非常希望各路大佬来批评指正,当然,如果我的文章能帮到您,请在评论区积极发言(手动狗头),这也是对我最大的鼓舞,谢谢!

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

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

相关文章

安装RabbitMQ的各种问题(包括已注册成windows服务后,再次重新安装,删除服务重新注册遇到的问题)

一、安装Erlang(傻瓜式安装) 安装完成之后,配置环境变量: 1.新建系统变量名为:ERLANG_HOME 变量值为erlang安装地址 2. 双击系统变量path,点击“新建”,将%ERLANG_HOME%\bin加入到path中。 …

学习笔记——Java入门第一季

1.1 Java的介绍与前景 Java语言最早期的制作者:James Gosling(詹姆斯高斯林) 1995年5月23日,Sun Microsystems公司宣布Java语言诞生。 1.2 Java的特性与版本 跨平台 开源(开放源代码) Java代码&#xff…

酷开系统游戏空间,开启大屏娱乐新玩法

在这个充满科技感和无限创意的时代,游戏已经成为我们生活的一部分。而随时着科技的不断发展,以及游戏爱好者的游戏需求在不断提高,促使游戏体验也向更加丰富多彩的方向发展。显然,酷开科技早已经认识到游戏发展的新蓝图&#xff0…

金鸣识别名片识别模块 ,名片扫描仪的神仙“伴侣”

名片扫描仪是现代办公中常见的设备,其作用是将纸质名片转换为电子格式并进行识别。在实现这一功能方面,使用自带OCR功能和金鸣识别两种方式均具有各自的优势。 一方面,自带OCR功能的名片扫描仪具有便捷性和即时性的优势。通过设备内置的OCR技…

国产信创服务器如何进行安全可靠的文件传输?

信创,即信息技术应用创新,2018年以来,受“华为、中兴事件”影响,国家将信创产业纳入国家战略,并提出了“28n”发展体系。从产业链角度,信创产业生态体系较为庞大,主要包括基础硬件、基础软件、应…

SpringMVC综合案例

目录 一、SpringMVC常用注解 二、传递参数 2.1 基础类型String 2.2 复杂类型 2.3 RequestParam 2.4 PathVariable 2.5 RequestBody 2.6 RequestHeader 2.7 请求方法 三、返回值 3.1 void 3.2 String 3.3 StringModel 3.4 ModelAndView 四、页面跳转 4.1 转发 4…

iPhone用户的价值是安卓用户的4倍?难以置信,研究发现竟是7.4倍

据Asymco机构分析师Horace Dediu发布的最新报告,苹果用户在应用上的平均支出是安卓用户的7.4倍,远高于此前提出的4倍观点。这意味着,尽管安卓用户数量是iPhone用户的两倍,但iPhone应用商店开发者的收入是谷歌PlayStore的两倍。 在…

淘宝销量展示方式变更背后的逻辑

淘宝销量展示方式发生了调整,平台于8月16日将商品详情销量展示表达由【月销**件】全部换成展示【已售**件】,将30天销量改成了近365天销量。 【已售**件】统计口径:统计近365天支付的商品件数,数据更新请关注24-48小时。其中涉及销…

数据库模式迁移工具的演进:CLI,GUI,集成式协作数据库平台

数据库模式迁移可能是应用程序开发中最具风险的领域,它困难、有风险且令人痛苦。数据库模式迁移工具的存在就是为了减轻这些痛苦,并且已经取得了长足的进步:从基本的CLI工具到GUI工具,从简单的SQL GUI客户端到集成式协作数据库平台…

PROSOFT PTQ-PDPMV1网络接口模块

通信接口:PROSOFT PTQ-PDPMV1 网络接口模块通常配备了多种通信接口,以便与不同类型的设备和网络进行通信。常见的接口包括以太网、串行端口(如RS-232和RS-485)、Profibus、DeviceNet 等。 协议支持:该模块通常支持多种…

《向量数据库指南》——AI原生向量数据库Milvus Cloud 2.3 新功能ScaNN 索引和Iterator

ScaNN 索引 Milvus 目前支持了 Faiss 中的 FastScan 算法,在各项 benchmark 中有着不俗的表现,对比 HNSW 有 20% 左右提升,约为 IVFFlat 的 7 倍,同时构建索引速度更快。ScaNN 在算法上跟 IVFPQ 比较类似,聚类分桶,然后桶里的向量使用 PQ 做量化,区别是 ScaNN 对于量化比…

ATC模型转换动态shape问题案例

ATC(Ascend Tensor Compiler)是异构计算架构CANN体系下的模型转换工具:它可以将开源框架的网络模型(如TensorFlow等)以及Ascend IR定义的单算子描述文件转换为昇腾AI处理器支持的离线模型;模型转换过程中&a…

【C++】动态内存管理

【C】动态内存管理 new和delete用法内置类型自定义类型抛异常定位new 刨析new和delete的执行与实现逻辑功能执行顺序newdelete 功能实现operator new与operator delete malloc free与new delete的总结 在我们学习C之前 在C语言中常用的动态内存管理的函数为: mallo…

与数据库性能作斗争:间歇性超时问题

今年早些时候,当我们与数据库互动时,我们的应用程序在两周的时间里出现了间歇性的超时问题。 尽管我们尽了最大的努力,但我们不能立即确定一个明确的原因;我们并没有进行任何明显改变数据库使用方式的代码更改,也没有…

SSL证书只有收费的吗?有没有免费使用的?

首先明白SSL证书是什么SSL英文全称:英文全称: Secure Socket Layer Certificate,中文全称:安全套接字层证书。 SSL是一种由数字证书颁发机构(CA) 签发的数字证书。它用于建立安全的加密连接,确保通过网络传输的数据在客户端和服务器之间的安全性和完整性…

不同供电系统下SPD浪涌保护器的用途差异与选择

浪涌保护器(SPD)是一种用于保护电气设备免受电力系统突发的电压浪涌或过电压等干扰的重要装置。在选择浪涌保护器(SPD)时,会有1P、1PN、2P、3P、3PN、4P等不同类型的产品,其中“P”是低压电器的一个专业术语…

新旧混战,内衣竞争终局在哪里?

从2016到2023,内衣混战没有结束,反而愈演愈烈。 近日,包括都市丽人、汇洁股份、爱慕股份、安莉芳等在内的内衣服饰企业发布2023年中期业绩,多数在净利润等关键财务指标上表现亮眼。比如,都市丽人、爱慕股份、汇洁股份…

NOMA学习

NOMA(非正交多址接入技术) NOMA基本概念上行NOMA与下行NOMA上行NOMA(MAC信道)下行NOMA(BC广播信道) SIC解码顺序叠加编码(SC)与串行干扰消除(SIC)叠加编码&am…

TypeScript类型守卫

概念 在语句的块级作用域【if语句内或条目运算符表达式内】缩小变量类型的一种类型推断的行为。 类型守卫可以帮助我们在块级作用域中获得更为需要的精确变量类型,从而减少不必要的类型断言。 类型判断:typeof实例判断:instanceof字面量相等…

手写Spring:第1章-开篇介绍,手写Spring

文章目录 一、手写Spring二、Spring 生命周期 一、手写Spring 💡 目标:我们该对 Spring 学到什么程度?又该怎么学习呢? 手写简化版 Spring 框架,了解 Spring 核心原理,为后续再深入学习 Spring 打下基础。在…