RT-Thread启动过程 :从汇编开始的启动流程
这个系列参考了《嵌入式实时操作系统RT-Thread设计与实现》,会详细介绍RT-Thread的启动流程,即是如何从零开始在开发板上运行起一个RTOS内核的。本文将会以 ch32v307VCT6 开发板为例展开进行详细介绍。主要包括:startup.S、初始化与系统相关的硬件、初始化系统内核对象(例如定时器、调度器、信号)、创建main线程、初始化线程并启动调度器 这五大部分。
在这一小节中,本文将讲述通过wsl烧录RT-Thread的基本流程。并在最后讲解startup_ch32v30x.S文件的执行过程。
一、系统运行环境
本文在Linux系统上通过命令行的方式实现RT-Thread的下载与调试。这有几个准备步骤:
- RT-Thread的源码获取, 在linux命令行中输入如下命令:
git clone https://gitee.com/rtthread/rt-thread.git- 交叉编译工具链的下载:MRS_Toolchain_Linux_x64_V1.92.1.tar.xz 下载地址。下载好后在linux环境中解压。记录好下载路径。
- Scons工具下载: 在Linux命令行中输入以下命令:
sudo apt install scons
准备完毕后,切换目录到:./RT_thread/rt-thread/bsp/wch/risc-v/ch32v307v-r1 下运行(下文称其为根目录):
scons --exec-path=../../../../../Toolchain_linux/MRS_Toolchain_Linux_x64_V1.92.1/RISC-V_Embedded_GCC/bin
其中--exec-path=后接下载的交叉编译工具链的路径。运行成功如下所示:

至此在当前目录下会生成rtthread.bin 和 rtthread.elf两个文件。前期的准备工作就到此结束。
二、烧录至开发板
本文使用wsl,安装教程:WSL2 最新最全帮助小白一步步详细安装教程。
由于wsl本身不支持usb连接,因此需要把插入电脑的usb(Windows下)接入到wsl(Linux下)中,所以还需要安装usbipd-win开源项目:usbipd-win安装。想要便捷操作的可以额外下载图形化界面wsl usb gui:wsl usb gui 下载。
下载完成,并且usb成功接入到wsl后,为了方便操作一般都会有一个Makefile执行相关命令,其中的相对路径均从之前下载的交叉编译工具链给出:
CROSS_COMPILE := ../../../../../Toolchain_linux/MRS_Toolchain_Linux_x64_V1.92.1/RISC-V_Embedded_GCC/bin/riscv-none-embed-
OPENOCD_DIR = ../../../../../Toolchain_linux/MRS_Toolchain_Linux_x64_V1.92.1/OpenOCD/bin
OPENOCD = ${OPENOCD_DIR}/openocd
CFG = ${OPENOCD_DIR}/wch-riscv.cfg
GDB := ${CROSS_COMPILE}gdb
OBJDUMP := ${CROSS_COMPILE}objdump
.DEFAULT_GOAL := allall:scons --exec-path=../../../../../Toolchain_linux/MRS_Toolchain_Linux_x64_V1.92.1/RISC-V_Embedded_GCC/bin.PHONY : flash
flash: @echo "------------------------"@echo "Flashing os.bin to board"@echo "------------------------"@${OPENOCD} -f ${CFG} -c init -c halt -c "program rtthread.bin" -c exit.PHONY : run_openocd
run_openocd: @echo "-----------------------------------------------"@echo "Please manually kill the openocd after gbd quit"@echo "-----------------------------------------------"@${OPENOCD} -f ${CFG} &.PHONY : run_gdb
run_gdb:@${GDB} rtthread.elf -q -ex "target remote : 3333".PHONY : code
code: @${OBJDUMP} -d rtthread.elf > rtthread.asm.PHONY : clean
clean:rm -rf *.o *.bin *.elf
把以上Makefile文件放在根目录下,并执行sudo make flash,即可成功烧录:

使用picocom串口工具观察现象,picocom工具直接命令行输入sudo apt install picocom下载。在这里波特率为115200,默认8个数据位,1个停止位,不启用校验位。 /dev/ttyACM0为Linux系统所识别的新插入的开发板USB设备文件。按下ctrl+A ctrl+X退出。
当出现如下图所示的 RT 字样即说明成功启动RT-Thread。

三、startup_ch32v30x.S
现在本文将从汇编文件开始,讲述RT-Thread是如何一步一步启动的。startup_ch32v30x.S位置在:RT_thread/rt-thread/bsp/wch/risc-v/Libraries/ch32v30x_libraries/bmsis/source。
汇编文件中的入口地址为 _start (由链接脚本ENTRY( _start )指定)。汇编文件中第13行至332行均在设置中断向量入口地址。
第345行在设置堆栈指针:
![]()
第348行至357行在将.data段中的数据从flash搬移至ram:

第360行至366行在清零 .bss 段中的数据:

第368行至373行在写控制状态寄存器(具体作用不知):

第377行至378行在写mstatus寄存器,mstatus寄存器结构如下图,其中比较重要的位有:

MIE:全局trap(中断和异常)开关。
MPIE:trap前系统MIE位的状态,也可以理解为:执行mret时,MIE会被赋值位MPIE。
MPP:trap前系统运行的模式。也可以理解为:mret时系统会运行于哪种模式。 为11则表示系统会运行在机器模式。
FS:11表示启动浮点运算
因此这里在把MPP写为11后,执行mret就打开了全局trap(中断和异常)的开关 。

第380行至382行在设置中断向量入口基地址,其中_vector_base在此汇编文件第29行指定。

第384行至387行在执行开发板时钟初始化(SystemInit),并进入RT-Thread的初始化函数:

在这里,mepc寄存器中存入了entry函数地址,在mret时,系统会跳转到mepc寄存器存放的地址继续执行,从而实现了entry函数的跳转。
SystemInit函数位置:
RT_thread/rt-thread/bsp/wch/risc-v/Libraries/ch32v30x_libraries/bmsis/source/system_ch32v30x.c:85首先执行SystemInit,个人认为由于系统复位,不管是哪种复位形式,寄存器都会被设置为复位值,所以SystemInit中,除了SetSysClock()函数的跳转,其他操作意义不明(我太菜了)。本文使用的ch32v307VCT6属于CH32V30x_D8C类别。
除非是软件直接跳转到handel_reset执行复位逻辑。
void SystemInit (void) {RCC->CTLR |= (uint32_t)0x00000001; #ifdef CH32V30x_D8CRCC->CFGR0 &= (uint32_t)0xF8FF0000; #elseRCC->CFGR0 &= (uint32_t)0xF0FF0000; #endif RCC->CTLR &= (uint32_t)0xFEF6FFFF;RCC->CTLR &= (uint32_t)0xFFFBFFFF;RCC->CFGR0 &= (uint32_t)0xFF80FFFF;#ifdef CH32V30x_D8CRCC->CTLR &= (uint32_t)0xEBFFFFFF;RCC->INTR = 0x00FF0000;RCC->CFGR2 = 0x00000000; #elseRCC->INTR = 0x009F0000; #endif SetSysClock(); }在SetSysClock中将系统时钟设置为定义好的144MHz:
static void SetSysClock(void) { #ifdef SYSCLK_FREQ_HSESetSysClockToHSE(); #elif defined SYSCLK_FREQ_24MHzSetSysClockTo24(); #elif defined SYSCLK_FREQ_48MHzSetSysClockTo48(); #elif defined SYSCLK_FREQ_56MHzSetSysClockTo56(); #elif defined SYSCLK_FREQ_72MHzSetSysClockTo72(); #elif defined SYSCLK_FREQ_96MHzSetSysClockTo96(); #elif defined SYSCLK_FREQ_120MHzSetSysClockTo120(); #elif defined SYSCLK_FREQ_144MHz //仅144MHz有宏定义SetSysClockTo144();#endif/* If none of the define above is enabled, the HSI is used as System clock* source (default after reset) */ }在SetSysClockTo144()函数中。主要工作在 启用HSE时钟,配置PLL倍频、设置系统时钟、HCLK、PB1\PB2外设时钟。
逻辑是先启用HSE外部8MHz高速时钟,并PLL 18倍频至 144MHz 作为SYSCLOCK系统时钟。 系统时钟不分频作为HCLK的输入。 HCLK不分频作为PB2CLK输入, HCLK 2分频作为PB1CLK输入。
static void SetSysClockTo144(void) {__IO uint32_t StartUpCounter = 0, HSEStatus = 0;RCC->CTLR |= ((uint32_t)RCC_HSEON); //启用外部高速时钟/* 等待HSE时钟准备就绪 */do{HSEStatus = RCC->CTLR & RCC_HSERDY;StartUpCounter++;} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));/* 判断HSE是否准备就绪 */if ((RCC->CTLR & RCC_HSERDY) != RESET){HSEStatus = (uint32_t)0x01;}else{HSEStatus = (uint32_t)0x00;}/* 如果HSE准备就绪则配置PLL倍频、SYSCLK、HCLK、PB1CLK、PB2CLK */if (HSEStatus == (uint32_t)0x01){/* HCLK = SYSCLK */RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1; //设置HCLK为SYSCLK不分频输入/* PCLK2 = HCLK */RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1; //设置PB2CLK为HCLK不分频输入/* PCLK1 = HCLK/2 */RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2; //设置PB1CLK为HCLK2分频输入/* PLL 配置: PLLCLK = HSE * 18 = 144 MHz */RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLMULL));#ifdef CH32V30x_D8RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL18); #elseRCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLMULL18_EXTEN); //配置PLL时钟源为HSE,并设置18倍频。 实际上这里配置的PLL时钟源是prediv1,只不过prediv1系统默认输入是HSE不分频输入。 #endif/* 启用 PLL */RCC->CTLR |= RCC_PLLON;/* 等待 PLL 准备就绪 */while((RCC->CTLR & RCC_PLLRDY) == 0){}/* 设置 PLL倍频时钟 作为系统时钟 */RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;/* Wait till PLL is used as system clock source */while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08){}}else{/** If HSE fails to start-up, the application will have wrong clock* configuration. User can add here some code to deal with this error*/} }
至此系统从汇编文件启动,到跳转到RT-Thread的初始化入口entry函数的逻辑就讲解完毕。这一部分主要是开发板启动的常规操作(数据段搬运、bss段清零、中断向量设置),以及 系统时钟的配置。接下来将介绍entry函数中是如何对RT-Thread进行初始化的。
四、RT-Thread初始化
单核初始化过程包括:中断关闭、板级初始化、定时器初始化、调度器初始化、信号初始化、初始化创建应用main线程、初始化创建定时器线程、空闲线程初始化、僵尸线程初始化、打开中断并进行线程调度。
int rtthread_startup(void)
{
#ifdef RT_USING_SMPrt_hw_spin_lock_init(&_cpus_lock);
#endifrt_hw_local_irq_disable(); //关中断/* board level initialization* NOTE: please initialize heap inside board initialization.*/rt_hw_board_init(); //板级初始化/* show RT-Thread version */rt_show_version();/* timer system initialization */rt_system_timer_init(); //定时器初始化/* scheduler system initialization */rt_system_scheduler_init(); //调度器初始化#ifdef RT_USING_SIGNALS/* signal system initialization */rt_system_signal_init();
#endif /* RT_USING_SIGNALS *//* create init_thread */rt_application_init(); //创建用户线程/* timer thread initialization */rt_system_timer_thread_init(); //定时器线程初始化/* idle thread initialization */ rt_thread_idle_init(); //空闲线程初始化/* defunct thread initialization */rt_thread_defunct_init(); //僵尸线程初始化#ifdef RT_USING_SMPrt_hw_spin_lock(&_cpus_lock);
#endif /* RT_USING_SMP *//* start scheduler */rt_system_scheduler_start(); //线程调度,开中断并执行main函数/* never reach here */return 0;
}
其中rt_hw_boart_init板级初始化过程需要我们根据具体的开发板进行修改。需要在其中完成系统时钟配置、为系统提供心跳、串口初始化、将系统输入输出终端绑定到这个串口。
extern uint32_t SystemCoreClock;static uint32_t _SysTick_Config(rt_uint32_t ticks)
{NVIC_SetPriority(SysTicK_IRQn, 0xf0);NVIC_SetPriority(Software_IRQn, 0xf0);NVIC_EnableIRQ(SysTicK_IRQn);NVIC_EnableIRQ(Software_IRQn);SysTick->CTLR = 0;SysTick->SR = 0;SysTick->CNT = 0;SysTick->CMP = ticks - 1;SysTick->CTLR = 0xF;return 0;
}/*** This function will initial your board.*/
void rt_hw_board_init()
{/* System Tick Configuration */_SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)rt_system_heap_init((void *) HEAP_BEGIN, (void *) HEAP_END);
#endif/* USART driver initialization is open by default */
#ifdef RT_USING_SERIALrt_hw_usart_init();
#endif
#ifdef RT_USING_CONSOLErt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
#ifdef RT_USING_PIN/* pin must initialized before i2c */rt_hw_pin_init();
#endif/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INITrt_components_board_init();
#endif}void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SysTick_Handler(void)
{GET_INT_SP();/* enter interrupt */rt_interrupt_enter();SysTick->SR = 0;rt_tick_increase();/* leave interrupt */rt_interrupt_leave();FREE_INT_SP();}
相关文章:
RT-Thread启动过程 :从汇编开始的启动流程
这个系列参考了《嵌入式实时操作系统RT-Thread设计与实现》,会详细介绍RT-Thread的启动流程,即是如何从零开始在开发板上运行起一个RTOS内核的。本文将会以 ch32v307VCT6 开发板为例展开进行详细介绍。主要包括:startup.S、初始化与系统相关的…...
Scala—“==“和“equals“用法(附与Java对比)
Scala 字符串比较—""和"equals"用法 Scala 的 在 Scala 中, 是一个方法调用,实际上等价于调用 equals 方法。不仅适用于字符串,还可以用于任何类型,并且自动处理 null。 Demo: Java 的 在 J…...
$route和$router的区别
在 Vue.js 中,$route 和 $router 都是 Vue Router 提供的对象,但它们有不同的用途和功能。 1. $router $router 是 Vue Router 实例的引用,它允许你通过 JavaScript 进行路由的控制和导航。你可以通过 $router 来执行路由的操作,…...
[工具升级问题] 钉钉(linux版)升级带来的小麻烦
本文由Markdown语法编辑器编辑完成。 1. 背景: 今日钉钉又发布了新的升级版本。由于我工作时使用的是Ubuntu 20.04版本,收到的升级推送信息是,可以升级到最新的7.6.25-Release版本。根据钉钉官方给出的历次更新版说明,这个新的版本…...
Leetcode经典题13--接雨水
题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 输入输出示例 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1…...
yarn修改缓存位置
查看缓存位置 以下三个命令分别为:bin是yarn存储命令的二进制文件,global存储全局node_modules ,cache存储用下下载缓存,查看本机目前的目录: 查看bin目录命令:yarn global bin 查看global目录命令&…...
OpenHarmony-3.HDF input子系统(5)
HDF input 子系统OpenHarmony-4.0-Release 1.Input 概述 输入设备是用户与计算机系统进行人机交互的主要装置之一,是用户与计算机或者其他设备通信的桥梁。常见的输入设备有键盘、鼠标、游戏杆、触摸屏等。本文档将介绍基于 HDF_Input 模型的触摸屏器件 IC 为 GT91…...
RabbitMQ 消息持久化/镜像队列/lazy对时延影响
测试背景: 不同条件下RabbitMQ不同队列类型的生产时延测试: 测试环境: 机型:rabbimtq.2u4g.cluster 背景流量:1000 TPS 测试条件: 消息大小 4k,消息条数为1000条,时延取值为平均…...
【深度学习】深刻理解Swin Transformer
Swin Transformer 是一种基于 Transformer 的视觉模型,由 Microsoft 研究团队提出,旨在解决传统 Transformer 模型在计算机视觉任务中的高计算复杂度问题。其全称是 Shifted Window Transformer,通过引入分层架构和滑动窗口机制,S…...
[2015~2024]SmartMediaKit音视频直播技术演进之路
技术背景 2015年,因应急指挥项目需求,我们实现了RTMP推送音视频采集推送(采集摄像头和麦克风数据)模块,在我们做好了RTMP推送模块后,苦于没有一个满足我们毫秒级延迟诉求的RTMP播放器,于是第一…...
redis 使用Lettuce 当redis挂掉重启之后 网络是怎么重新连接
Lettuce是一个高性能的Java Redis客户端,支持同步、异步和反应式编程模式 Lettuce的核心功能包括: 高性能:通过使用Netty作为底层网络通信框架,实现了非阻塞IO,提高了性能。丰富的API:提供了丰富…...
【IntelliJ IDEA 集成工具】TalkX - AI编程助手
前言 在数字化时代,技术的迅猛发展给软件开发者带来了更多的挑战和机遇。为了提高技术开发群体在繁多项目中的编码效率和质量,他们需要一个强大而专业的工具来辅助开发过程,而正是为了满足这一需求,TalkX 应运而生。 一、概述 1…...
二叉搜索树Ⅲ【东北大学oj数据结构8-3】C++
二叉搜索树 III B:在二叉搜索树II中加入delete指令,创建程序对二叉搜索树T执行如下指令。 插入 k:将key k 插入到 T 中。 find k:报告T中是否存在key k。 delete k:删除key为 k 的节点。 打印:使用中序树遍…...
【面试笔记】CPU 缓存机制
CPU 缓存机制 1. CPU Cache 与 MMU1.1 MMU 是什么?TLB 又是什么?他们是怎么工作的?2.2 简述 Cache 与 MMU 的协作关系?2.3 简述 Cache 与 MMU 的协作工作流程? 2. CPU 多层次缓存2.1 什么是 CPU 的多层次缓存结构&…...
MySQL基础函数使用
目录 简介 1. 单行函数 1.1 字符串函数 1.2 日期函数 1.3 数值函数 1.4 转换函数 1.5 其他函数 2. 多行函数 示例: 3. 数据分组 示例: 4. DQL单表关键字执行顺序 示例: 5. 多表查询 示例: 6. 表与表的外连接 示例…...
解决docker环境下aspose-words转换word成pdf后乱码问题
描述 环境:docker 部署工具:Jenkins 需求:本地上传的word文档需要转换成pdf 问题:转换之后的pdf文档出现小框框(乱码) 转换成PDF的操作 pom: <dependency><groupId>org.apach…...
C# 生成随机数的方法
C# 提供了一种强大而方便的工具类 Random ,用于生成随机数。这里将分类讨论如何通过 C# 实现随机数生成,以及应用于实际情况中的一些具体方案。 一、Random 类概述 Random 类表示一个伪随机数生成器,用于生成满足随机性统计要求的数字序列。…...
ip_done
文章目录 路由结论 IP分片 数据链路层重谈Mac地址MAC帧报头局域网的通信原理MSS,以及MAC帧对上层的影响ARP协议 1.公司是不是这样呢? 类似的要给运营商交钱,构建公司的子网,具有公司级别的入口路由器 2.为什么要这样呢?? IP地…...
3D可视化引擎HOOPS Visualize与HOOPS Luminate Bridge的功能与应用
HOOPS Visualize HPS / HOOPS Luminate Bridge为开发者提供了强大的工具,用于在CAD应用中集成逼真的渲染能力。本文旨在梳理该桥接产品的核心功能、使用方法及应用场景,为用户快速上手并充分利用产品特性提供指导。 桥接产品的核心功能概述 HOOPS Lumi…...
Docder 搭建Redis分片集群 散片插槽 数据分片 故障转移 Java连接
介绍 使多个 Redis 实例共同工作,实现数据的水平扩展。通过将数据分片到多个节点上,Redis 集群能够在不牺牲性能的前提下扩展存储容量和处理能力,从而支持更高并发的请求。Redis 集群不仅支持数据分片,还提供了自动故障转移和高可…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
