《FreeRTOS的配置与临界段》

目录

1.FreeRTOS配置的重要性

2.初学者使用FreeRTOSConfig.h 文件

3.“INCLUDE_”开始的宏 

4.FreeRTOS 中断配置和临界段

4.1 中断简介

4.2 中断优先级分组定义

4.3优先级设置 

 4.4 重要的中断屏蔽寄存器

一、PRIMASK 和 FAULTMASK 寄存器

二、BASEPRI 寄存器

4.5 FreeRTOS 重要的中断配置宏

configMAX_SYSCALL_INTERRUPT_PRIORITY

 4.6 FreeRTOS 开关中断

4.7FreeRTOS临界端代码

一、任务级临界段代码保护

二、中断级临界段代码保护 

总结:


1.FreeRTOS配置的重要性

为什么要关心FreeRTOS的配置问题?

FreeRTOS 的系统配置文件为 FreeRTOSConfig.h,在实际使用 FreeRTOS 的时候我们时常需要根据自己需求来配置 FreeRTOS,而且不同架构的 MCU 在使用的时候配置也不同,在此配置文件中可以完成 FreeRTOS 的裁剪和配置,这是非常重要的一个文件,必须要熟悉。所以这也是我们学习FreeRTOS配置的意义。

2.初学者使用FreeRTOSConfig.h 文件

我们初学者先不必要大刀阔斧的从头编写FreeRTOSConfig文件,在 FreeRTOS 的官方 demo 中,每个工程都有一个 FreeRTOSConfig.h 文件,我们在使用的时候可以参考这个文件,甚至直接复制粘贴使用。

这里说明FreeRTOS 的配置基本是通过在 FreeRTOSConfig.h 中使用“#define”这样的语句来定义宏定义实现的。

3.“INCLUDE_”开始的宏 

使用“INCLUDE_”开头的宏用来表示使能或除能 FreeRTOS 中相应的 API 函数,作用就是用来配置 FreeRTOS 中的可选 API 函数的。比如当宏 INCLUDE_vTaskPrioritySet 设置为 0 的时候 表示不能 使用函数 vTaskPrioritySet() ,当设置 为 1 的时 候就表示可 以使用函 数vTaskPrioritySet()。这个功能其实就是条件编译,在文件 tasks.c 中有如下图代码

 

 从图,可以看出当满足条件:NCLUDE_vTaskPrioritySet == 1 的 时 候 ,函数vTaskPrioritySet()才会被编译,注意,这里为了缩小篇幅将函数 vTaskPrioritySet()的内容进行了折叠。FreeRTOS 中的裁剪和配置就是这种用条件编译的方法来实现的,不止FreeRTOS这么干,很多的协议栈、RTOS 系统和 GUI 库等都是使用条件编译的方法来完成配置和裁剪的。条件编译的好处就是节省空间,不需要的功能就不用编译,这样就可以根据实际需求来减少系统占用 的 ROM 和 RAM 大小,根据自己所使用的 MCU 来调整系统消耗,降低成本。

综上所述,当你的RTOS代码里突然出现找不到函数,函数没有调用,函数在实际情况下没有起到作用时,那就要思考有没有可能是有关函数没有配置的问题了。

上图中这样的,需要改写0和1控制是否配置的函数还有很多,且在FreeRTOS.h、task.h等文件都有,大家可以自行查阅有关手册指南,查找相关配置文件。

下面着重讲解一下中断配置文件

4.FreeRTOS 中断配置和临界段

4.1 中断简介

中断是微控制器一个很常见的特性,中断由硬件产生,当中断产生以后 CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC)。Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个 Systick(滴答定时器)定时器中断和多个系统异常。

4.2 中断优先级分组定义

当多个中断来临的时候处理器应该响应哪一个中断是由中断的优先级来决定的,高优先级的中断(优先级编号小)肯定是首先得到响应,而且高优先级的中断可以抢占低优先级的中断,这个就是中断嵌套。Cortex-M 处理器的有些中断是具有固定的优先级的,比如复位、NMI、 HardFault,这些中断的优先级都是负数,优先级也是最高的。

Cortex-M 处理器有三个固定优先级和 256 个可编程的优先级,最多有 128 个抢占等级,但是实际的优先级数量是由芯片厂商来决定的。但是,绝大多数的芯片都会精简设计的,以致实际上支持的优先级数会更少,如 8 级、16 级、32 级等,比如 STM32 就只有 16 级优先级。在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,所以不管用多少位来表达优先级,都是 MSB 对齐的。如下图就是使用三位来表达优先级。

在图中,Bit0~Bit4 没有实现,所以读它们总是返回零,写如它们的话则会忽略写入的值。因此,对于 3 个位的情况,可是使用的优先级就是 8 个:0X00(最高优先级)、0X20、0X40、0X60、0X80、0XA0、0XC0 和 0XE0。

注意,这个是芯片厂商来决定的!不是我们能决定的,比如 STM32 就选择了 4 位作为优先级!

有读者可能就会问,优先级配置寄存器是 8 位宽的,为什么却只有 128 个抢占等级?8 位不应该是 256 个抢占等级吗?为了使抢占机能变得更可控,Cortex-M 处理器还把 256 个优先级按位分为高低两段:抢占优先级(分组优先级)和亚优先级(子优先级),NVIC 中有一个寄存器是“应用程序中断及复位控制寄存器(AIRCR)”,AIRCR 寄存器里面有个位段名为“优先级组”,AIRCR寄存器如下图:

 图中PRIGROUP 就是优先级分组,它把优先级分为两个位段:MSB 所在的位段(

边的)对应抢占优先级,LSB 所在的位段(右边的)对应亚优先级,如下图所示:

 

特别注意:这里解释一下【7:1】的意思,即共8位的优先级配置寄存器中第7位到第1位都是抢占优先级,以此类推......

 在看一下 STM32 的优先级分组情况,我们前面说了 STM32 使用了 4 位,因此最多有 5 组优先级分组设置,这 5 个分组在 msic.h 中有定义,如下:

#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority 
4 bits for subpriority */ 
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority 
3 bits for subpriority */ 
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority 
2 bits for subpriority */ 
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority 
1 bits for subpriority */ 
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority 
0 bits for subpriority */

可以看出 STM32 有 5 个分组,但是一定要注意!STM32 中定义的分组 0 对应的值是 7!

如果我们选择分组 4,即 NVIC_PriorityGroup_4 的话,那 4 位优先级就都全是抢占优先级了,没有亚优先级,那么就有 0~15 共 16 个优先级。而移植 FreeRTOS 的时候我们配置的就是组 4,相关代码如下图:

如果使用 ALIENTEK 的基础例程的话默认配置的组 2,所以在将基础例程中的外设驱动移植到 FreeRTOS 下面的时候需要修改优先级配置。主要是 FreeRTOS 的中断配置没有处理亚优先级这种情况,所以只能配置为组 4,直接就 16 个优先级,使用起来也简单!

4.3优先级设置 

每个外部中断都有一个对应的优先级寄存器,每个寄存器占 8 位,因此最大宽度是 8 位,但是最小为3位。4 个相临的优先级寄存器拼成一个 32 位寄存器。如前所述,根据优先级组的设置,优先级又可以分为高、低两个位段,分别抢占优先级和亚优先级。STM32 我们已经设置位组 4,所以就只有抢占优先级了。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问,有意义的优先级寄存器数目由芯片厂商来实现

下表是中断优先级寄存器阵列(地址:0xE000_E400~0xE000_E4EF)

下表为系统异常优先级阵列(地址:0XE000_ED18~0xE000_ED23)

上面说了,4个相临的寄存器可以拼成一个32位的寄存器,

因此地址0xE000_ED20~0xE000_ED23 这四个寄存器就可以拼接成一个地址为 0xE000_ED20 32 位寄存器。 

这一点很重要!因为 FreeRTOS 在设置 PendSV SysTick 的中断优先级的时候都是直接

操作的地址 0xE000_ED20

 4.4 重要的中断屏蔽寄存器

我们重点关心的是是三个中断屏蔽寄存器:PRIMASKFAULTMASK BASEPRI

一、PRIMASK 和 FAULTMASK 寄存器

在许多应用中,需要暂时屏蔽所有的中断一执行一些对时序要求严格的任务,这个时候就

可以使用 PRIMASK 寄存器,PRIMASK 用于禁止除 NMI HardFalut 外的所有异常和中断,

汇编编程的时候可以使用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器的数值:

下面为有关代码:

PRIMASK 寄存器还可以通过 MRS MSR 指令访问,如下:

以及:

UCOS 中的临界区代码代码保护就是通过开关中断实现的(UCOSIII 也可以使用禁止任务调度的方法来实现临界区代码保护,这里不讨论这种情况),而开关中断就是直接操作 PRIMASK寄存器的,所以在 UCOS 中关闭中断的时候时关闭了除复位、NMI HardFault 以外的所有中断!FAULTMASK PRIMASK 更狠,它可以连 HardFault 都屏蔽掉,使用方法和 PRIMASK 类似,FAULTMASK 会在退出时自动清零。

汇编编程的时候可以利用 CPS 指令修改 FAULTMASK 的当前状态

还可以利用 MRS MSR 指令访问 FAULTMASK 寄存器:
以及:

二、BASEPRI 寄存器

PRIMASK FAULTMASK 寄存器太粗暴了,直接关闭除复位、 NMI HardFault 以外的
其他所有中断,但是在有些场合需要对中断屏蔽进行更细腻的控制,比如只屏蔽优先级低于某
一个阈值的中断。那么这个作为阈值的优先级值存储在哪里呢?在 BASEPRI 寄存器中,不过
如果向 BASEPRI 0 的话就会停止屏蔽中断。比如,我们要屏蔽优先级不高于 0X60 的中断,
则可以使用如下汇编编程:
如果需要取消 BASEPRI 对中断的屏蔽,可以使用如下代码:
注意!FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈
值的中断,高于这个阈值的中断就不会被关闭!

4.5 FreeRTOS 重要的中断配置宏

configMAX_SYSCALL_INTERRUPT_PRIORITY

这里着重讲一下configMAX_SYSCALL_INTERRUPT_PRIORITY,理解它,与使用stm32中断关系非常大。

此宏是 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 左移 4 位而来的,原因和宏configKERNEL_INTERRUPT_PRIORITY 一样。此宏设置好以后,低于此优先级的中断可以安全的调用 FreeRTOS API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服务函数也不能调用 FreeRTOS API 函数!

STM32 为例,有 16 个优先级,0 为最高优先级,15 为最低优先级,配置如下:

configMAX_SYSCALL_INTERRUPT_PRIORITY==5

configKERNEL_INTERRUPT_PRIORITY==15

由于高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的优先级不会被 FreeRTOS 内核屏蔽,因此那些对实时性要求严格的任务就可以使用这些优先级,比如四轴飞行器中的壁障检测。

 4.6 FreeRTOS 开关中断

FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()portDISABLE_INTERRUPTS(),这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:

函数 vPortSetBASEPRI()是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI

递进来,portENABLE_INTERRUPTS()是开中断,它传递了个 0 vPortSetBASEPRI(),根据我们前面讲解 BASEPRI 寄存器可知,结果就是开中断。

函数vPortRaiseBASEPRI()是向寄存器 BASEPRI写入宏configMAX_SYSCALL_INTERRUPT_PRIORITY,那么优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断就会被屏蔽!

4.7FreeRTOS临界端代码

临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设

的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。

FreeRTOS与临界段代码保护有关的函数有4个:

taskENTER_CRITICAL()

taskEXIT_CRITICAL()

taskENTER_CRITICAL_FROM_ISR() 

taskEXIT_CRITICAL_FROM_ISR()

这四个函数其实是宏定义,在 task.h 文件中有定义。这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。

一、任务级临界段代码保护

taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个函数是成对使用的。

用快捷键进行追根溯源后会看到这下图代码:

可以看出在进入函数 vPortEnterCritical()以后会首先关闭中断,然后给变量uxCriticalNesting加一,uxCriticalNesting 是个全局变量,用来记录临界段嵌套次数的。函数 vPortExitCritical()是退出临界段调用的,函数每次将 uxCriticalNesting 减一,只有当 uxCriticalNesting 0 的时候才会调用函数 portENABLE_INTERRUPTS()使能中断。这样保证了在有多个临界段代码的时候不会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后才会使能中断!

  任务级临界代码保护往往使用在执行任务之中,如创建、运算、判断时会用到这对临界代码

 

注意临界区代码一定要精简!因为进入临界区会关闭中断,这样会导致优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及时的响应!

二、中断级临界段代码保护 

函数 taskENTER_CRITICAL_FROM_ISR()taskEXIT_CRITICAL_FROM_ISR()中断级别

临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于

configMAX_SYSCALL_INTERRUPT_PRIORITY!该原理是因为向寄存器 BASEPRI写入宏configMAX_SYSCALL_INTERRUPT_PRIORITY,那么优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断才会被屏蔽!

所以只能关闭低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断。

中断级临界代码保护使用方法如下:

//定时器 3 中断服务函数 
void TIM3_IRQHandler(void) 
{ 
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断 
{ 
status_value=taskENTER_CRITICAL_FROM_ISR();//进入临界区。
total_num+=1; 
printf("float_num 的值为: %d\r\n",total_num); 
taskEXIT_CRITICAL_FROM_ISR(status_value); //退出临界区
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位 
}

总结:

学习完FreeRTOS的配置后,更加熟悉了使用FreeRTOS时的注意事项,在做相关实验时也出现了,因为FreeConfig.h没有配置而导致的中断失灵现象,而且临界段代码也是非常重要的一部分,常常出现在创建任务以及执行需要不被打断的任务之时,必须掌握!

希望我的文章能帮助到大家,相关FreeRTOS指南资料可以看我以前发的博客xun'zhao

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

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

相关文章

Vue:模板 MVVM

Vue&#xff1a;模板 & MVVM 模板插值语法指令语法 MVVMdefineProperty数据代理 模板 Vue实例绑定一个容器&#xff0c;想要向容器中填入动态的值&#xff0c;就需要使用模板语法。模板语法分为插值语法和指令语法。 插值语法 插值语法很简单&#xff0c;使用{{}}包含一…

极简实现酷炫动效:Flutter隐式动画指南第三篇自定义Flutter隐式动画

目录 前言 一、TweenAnimationBuilder 二、使用TweenAnimationBuilder实现的一些动画效果 1.调整透明度的动画 2.稍微复杂点的组合动画 3.数字跳动的动画效果 前言 上两节博客分别介绍了Flutter中的隐式动画的基础知识以及使用隐式动画实现的一些动画效果。当系统提供的隐…

熵基ZKTeco考勤机门禁如何重置密码(适用于大多数彩屏门禁机)

公司的一台门禁忘记密码了打不开&#xff0c;找了很久终于找到了密码重置的方法。 1、断电拆机(机器底部的螺丝,将机器从墙上拿下来) 2、插电重启&#xff08;或者杵下底部reset小孔&#xff09; 3、机器屏幕显示被拆除&#xff08;或右上角红色小感叹号闪烁&#xff0c;后者启…

​基于学习的地铁客流动态预测智能调度方法

1 文章信息 文章题为“A Learning Based Intelligent Train RegulationMethod With Dynamic Prediction forthe Metro Passenger Flow”&#xff0c;该文于2023年发表至“IEEE TRANSACTIONS ON INTELLIGENT TRANSPORTATION SYSTEMS”。文章的核心观点是提出了一种基于学习的智…

RNA-seq 差异分析的点点滴滴(1)

引言 本系列[1])将开展全新的转录组分析专栏&#xff0c;主要针对使用DESeq2时可能出现的问题和方法进行展开。 为何使用未经标准化的计数数据&#xff1f; DESeq2 工具包在接收输入时&#xff0c;期望得到的是未经处理的原始计数数据&#xff0c;比如从 RNA-seq 或其他高通量测…

Python初始环境搭建和Pycharm的安装

Python和PyCharm安装步骤 刚学习Python编程&#xff0c;按照书上的方法安装了Python和PyCharm&#xff0c;并做练习。但是今天PyCharm软件忽然不能使用了&#xff0c;不知道什么原因。于是&#xff0c;将这两个软件全部卸载&#xff0c;在网上查找软件安装方法&#xff0c;重新…

云上拼团GO指南——腾讯云博客部署案例,双11欢乐GO

知孤云出岫-CSDN博客 目录 腾讯云双11活动介绍 一.双十一活动入口 二.活动亮点 &#xff08;一&#xff09;双十一上云拼团Go (二&#xff09;省钱攻略 &#xff08;三&#xff09;上云&#xff0c;多类型服务器供您选择 三.会员双十一冲榜活动 (一)活动内容 &#x…

[ 常用工具篇 ] 使用 kali 实现 ARP 攻击 -- arpspoof 实战详解(ARP欺骗-断网攻击中间人攻击)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

无人机之飞行管控平台篇

无人机的飞行管控平台是一种基于互联网和物联网技术的智能管理系统&#xff0c;旨在实现对无人机飞行任务的全自动化、全过程化管理。 一、主要功能 飞行计划管理&#xff1a;用户可以通过平台提前设置好无人机的飞行计划&#xff0c;包括起飞时间、航线、飞行高度等信息。平…

C++ 继承

一. 继承的概念与定义 1.1. 继承的概念 继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段&#xff0c;它允许程序员在 保 持原有类特性的基础上进行扩展 &#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承 呈现了面向对象…

【启程Golang之旅】深入理解 Protocol Buffers 及其应用

如果你是 Go 语言的开发者&#xff0c;理解如何在 Go 中使用 Protobuf&#xff0c;将帮助你大幅提升数据传输的效率&#xff0c;并实现更高性能的系统设计。 本篇文章将深入探讨 Go 语言中使用 Protobuf 的基础知识、常见应用以及最佳实践&#xff0c;带你一步步了解如何在项目…

vue3.5+版本 defineProps响应式解构,保留数据响应式

正确写法&#xff1a;直接通过 defineProps 结构可以保留响应式 let {num:numNew} defineProps({num: {} }) console.log(具有响应式,numNew); 错误写法&#xff1a;这样会丢失响应式 const props defineProps({num: {} }) let {num:numNew} props console.log(会丢失响…

直播 鸿蒙5.0面试必修技能之鸿蒙性能优化技术

一&#xff1a;行业分析&#xff1a; PC时代---互联网---移动互联网---大数据/人工智能---物联网 c/c/java/php--- andorid/ios/前端/hadoop(存储)/spark/flink【12-14年】 --- ArkTS 李兴平 hao123.com/ 网站:6w/day 06年 5000w卖给了百度 盛大传奇/ 腾讯 互联…

怎么能更好的通过驾考呢?

充分准备&#xff1a; 提前了解驾考内容和要求&#xff0c;包括理论知识、场地驾驶技能、道路驾驶技能和安全文明驾驶常识等。合理安排学习时间&#xff0c;确保有足够的时间进行学习和练习。理论学习&#xff1a; 认真阅读和理解驾考相关书籍和资料&#xff0c;特别是交通法规…

Notion + Python + scholarly = 超强文献管理助手

摘要&#xff1a;在科研文献管理中&#xff0c;研究人员常常需要维护自己的文献数据库&#xff0c;我使用 Notion-database 作为的文献数据库管理工具&#xff08;开源模板&#xff09;。Notion-based 的方法无法实时更新文章的引用量信息。我结合了 Notion Integration 和 sch…

Git遇到“fatal: bad object refs/heads/master - 副本”问题的解决办法

Git遇到“fatal: bad object refs/heads/master - 副本”问题的解决办法 起源 让我们从一个常见的Git错误开始&#xff1a; fatal: bad object refs/heads/master - 副本这个错误提示通常意味着Git在引用&#xff08;ref&#xff09;中发现了不一致或损坏的数据。引用是Git用…

LinkedIn怎么养号:2024最新养号技巧揭秘

LinkedIn领英作为全球最大的职场社交平台&#xff0c;是跨境外贸企业与潜在客户、业务伙伴和同事进行交流的重要平台。然而&#xff0c;许多人在注册和使用LinkedIn时&#xff0c;常常会遇到账户受限甚至被封的困扰。想要拥有一个安全稳定的LinkedIn账户&#xff0c;养号是必不…

RHCE—web服务器

一、web服务器简介 web服务器一般指的是“网站服务器”&#xff0c;是某种驻留在因特网上的计算机程序&#xff0c;可以向请求终端提供服务&#xff0c;主要功能时存储、处理和传递网页给“客户”&#xff0c;传递内容一般是HTML文档、图像、样式表或脚本等&#xff0c;也可以…

块存储、文件存储和对象存储详细介绍

块存储、文件存储和对象存储介绍 块存储&#xff1a;像跑车&#xff0c;因为它们都能提供快速的响应和高性能&#xff0c;适合需要即时数据访问的场景&#xff0c;比如数据库和虚拟化技术。 文件存储&#xff1a;像货车&#xff0c;因为它们都能承载大量货物&#xff08;文件&…

ARM base instruction -- umaddl

Unsigned Multiply-Add Long multiplies two 32-bit register values, adds a 64-bit register value, and writes the result to the 64-bit destination register. 将两个32位寄存器值相乘&#xff0c;添加一个64位寄存器值&#xff0c;并将结果写入64位目标寄存器。 64-bit…