C语言
变量内存分配
①栈区–向低地址生长–编译器自动开辟和自动释放
一级缓存
由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。》》》0x01
②堆区–向高地址生长–手动分配手动释放
二级缓存
用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多》》》0xFFFF
使用完之后不会进行自动释放,重新调用不会乱码等等情况出现。
③静态区(数据区,全局区)–全局变量,静态变量
BSS段(未初始化数据区):通常用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态分配,程序结束后静态变量资源由系统自动释放。
④代码区–存放函数体的二进制代码
存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量
栈溢出递归过程的局部变量过多、递归深度过大,是造成系统栈溢出的原因,特别是递归列循环时肯定会发生系统栈溢出。
预处理
程序编译过程:预编译,编译,汇编,链接
static
-
static修饰全局变量,static修饰的全局变量的连接属性是内连接,普通的是外连接。即:static修饰的全局变量不能给文件调用——这也是静态变量和全局变量的区别。
-
static修饰局部变量,分配在全局区,普通局部变量在栈上。当静态局部变量离开作用域后,并没有销毁,而是仍然驻留在内存当中**,只不过我们不能够再对它进行访问,直到该函数被再次调用,并且值不变。
-
static修饰全局变量,static修饰的全局变量的连接属性是内连接,普通的是外连接。即:static修饰的全局变量不能给外部文件调用——这也是静态变量和全局变量的区别。
-
static修饰局部变量,放在全局区,生命周期变长,函数执行后不会被立即释放掉
-
static修饰函数:被static修饰的函数成为静态函数,只能在当前文件中被调用
extern
可以在一个文件中引用另一个文件中定义的变量或者函数
在c语言中,extern用在变量或者函数的声明前,说明此函数/变量是在别处定义,可以用于别的.c文件里面引用,但是要在此处引用。extern不能修饰局部变量。【被sytatic修饰过之后,用extern也不能调用】
extern"c" : 指示编译器这部分代码按C语言的方式进行编译
const
-
const是定义只读变量的关键字,定义常变量。const定义的变量是不允许改变的,不允许重新赋值。
-
const修饰的全局变量存储在全局区/常量区【静态区】
-
const修饰的局部变量在栈上
const与define的区别
-
数据类型:const修饰的变量有明确的类型,define没有明确的数据类型
-
安全方面:const修饰的变量会被编译器检查,而宏没有安全检查。
-
内存分配:const修饰的变量会在第一次赋值时分配内存,而宏是直接替换,每次替换的变量都会分配内存
-
作用场所:const修饰的变量作用在编译和运行过程中,而宏作用在预编译中
-
代码调试:const方便调试,宏在预编译过程中没办法进行调试
const修饰指针
防止指针的值在传输过程中改变。
const int *p //const修饰 *p ,*p代表指针变量所指向的内存单元的值,*p的值不可被更改,p所代表的地址可以被改变 常量指针
int* const p //const修饰p,p所存放的内存单元地址不可变,但是所指向内存单元的内容可被改变 指针常量
volatile
-
并发编程 - 保证变量的内存可见性 - 禁止指令重排序
-
volatile是一个特征修饰符,作为指令关键字,确保本条指令不会因为编译器的优化而省略,且要求每次直接读值。
-
在编译器编译成汇编时会有确定的一步操作
sizeof 与 strlen
sizeof:返回一个对象或者类型所占的内存字节数
sizeof是运算符,其值在编译时即计算好了,参数可以是数组,指针,类型,对象,函数,返回的结果类型是size_t,无符号整形
strlen是函数,要在运行时才计算,功能是返回字符串的长度,返回的长度大小不包括NULL,参数必须是字符型指针(char*)
typedef
为类型起一个新的别名,把typedef看成一种彻底的“封装”类型,声明后不能再往里面增加别的东西。
auto 与 register
register 寄存器变量,对变量名操作的结果是直接对寄存器进行访问,速度飞快
auto 用于局部变量,自动默认为auto,自动变量
if - endif
#if 条件成立,进行预编译
#endif 结束预编译
避免重定义变量
malloc和free(new C++)
在堆区开辟数据内存,返回空指针(没有类型)
malloc()是动态内存分配函数,用来向系统请求分配内存空间。
当无法知道内存具体的位置时,想要绑定真正的内存空间,就要用到malloc()函数。因为malloc只管分配内存空间,并不能对分配的空间进行初始化,所以申请到的内存中的值是随机的,经常会使用memset()进行置0填充之后再使用。
在嵌入式系统以及一些其他的场景下,不会使用malloc,因为动态申请内存空间时间是不确定的,内存空间够的情况就会快,不够就会慢,而且很有可能失败
free是释放内存,但是可能会导致内存碎片。就是内存地址不连续……后面就会导致内存利用率低
new会自己计算内存空间,然后分配
指针
1.指针大小:在32位系统位4字节,在64位系统是8字节
2.指针和数组名的区别:数组名是数组第一个元素的地址,内存偏移量是保存数据类型的内存偏移量。指针保存的是目标数据的地址内存访问偏移量为4个字节。
1.数组名代表了一个指向数组首元素的常量指针,一经定义,不可更改,数组名作为常量指针,其类型与数组元素类型相同。指针是变量指针,定义之后仍可更改,其类型在定义时确定。
2.当出现sizeof,和&操作符时,数组名不再当成指向一个元素的常量指针来使用,而指针仍当成指向一个元素的变量指针来使用。
3.指针数组与数组指针
指针数组是一个数组里面存放的全是指针,数组指针就是一个指针指向数组的首位
4.怎么用指针数组表示二维数组/怎么用数组指针表示二维数组
5.什么是函数指针
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址【函数名】)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
6.什么是指针函数
函数返回值为一个指针的,就叫(返回)指针的函数
7.什么是野指针
指针变量中的值是非法内存地址,进而形成野指针
野指针不是NULL指针,是指向不可用内存地址的指针(未定义)
8.如何避免野指针
绝不返回局部变量和局部数组的地址
任何变量在定义后必须0初始化,定义指针时,同时初始化为NULL
字符数组必须确认0结束符后才能成为字符串,
任何使用与内存操作相关的函数必须指定长度信息,在指针使用之前,将其赋值绑定给一个可用地址空间
在指针解引用(*)之前,先去判断这个指针是不是Null
指针使用完之后,将其赋值为NULL
8.引用和指针的区别
指针可以为空,引用不能为空,引用在c++内部实现是一个指针常量
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
字符串
strcmp
strstr
strcpy
结构体和联合体
结构体对齐的原则就是牺牲空间的方式来减少时间的消耗,空间用完还可以复用,而时间过去了就再也不会回来了。简而言之,就是按照字节大小排列,从小到大排列
https://www.mycode.net.cn/language/cpp/1489.html#comments
结构体对齐原则:
原则一:结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)
原则二:在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。
由于结构体所占空间与其内部元素的类型有关,而且与不同类型元素的排列有关,因此在定义结构体时,在元素类型及数量确定之后,我们还应该注意一下其内部元素的定义顺序。
结构体和联合体的区别:
-
struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成员, 而struct的所有成员都存在。在struct中,各成员都占有自己的内存空间,它们是同时存在的。一个struct变量的总长度等于所有成员长度之和。在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度。
-
对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的。
什么是左值,什么是右值
左值是用来指明一个对象的表达式。最简单的左值就是变量名称。左值(lvalue)之所以称为“左”(以首字母为 L,代表 left),是因为一个左值表示一个对象,它可以出现在赋值运算符(assignment operator)的左边,例如“左表达式=右表达式”。
其他表达式(那些表示一个值但不指明一个对象的),被类似地称为右值(rvalue)。右值是可以出现在赋值运算符右边而不是左边的表达式。例如,常量和算术表达式。
从一个左值中必定可以解析出对应对象的地址,除非该对象是位字段(bit-field)或者被声明为寄存器存储类。生成左值的运算符包括下标运算符(subscript operator)[]和间接运算符(indirection operator)*,如下表所示(如果 array 已被声明为数组,而 ptr 被声明为指针变量)。
大小端
大端(存储)模式:一个数据的低字节内容存放在高地址中,高字节的内容存放在低地址中。(简单的说就是:低字节,高地址。高字节,低地址。----->大端)
小端(存储)模式:一个数据的低字节内容存放在低地址中,高字节的内容存放在高地址中。(简单的说就是:小小小----->低字节,低地址,小端)
大端就是指高位值在内存中放低位地址,所谓小端是指低位值在内存中放低位地址。比如 0x12345678 在大端机上是 12345678,在小端机上是 78564312,而一个主机是大端还是小端要看CPU类型以及运行在上面的操作系统。
可以采用联合体或者指针的方法来判断该芯片是大端存储还是小端存储。
stm32默认是小端,51是大端
arm是小端,dsp是大端,电脑是小端,网络数据一般为大端,当arm需要和dsp通信的时候就会存在大小端转化的问题
c语言中的声明和定义
函数和变量的声明不会分配内存,但是定义会分配内存
函数的声明可以有很多次,但定义只有一次。
函数的定义和声明方式都是默认extern的,即函数默认都是全局的。可以采用static实现对函数的隐藏
变量的声明和定义默认是局部的,在当前编译单元或文件中使用
C语言的变量
五种基本类型:char int float double void ,void是空类型,一般用于限制函数的返回值和参数以及用来定义空指针
局部变量和全局变量的区别
-
生存周期不一样,局部变量存在与模块中,生存期从模块开始到结束。
-
储存位置不一样,局部变量存在栈上,栈地址是不固定的,由编译器自动分配和释放,全局变量保存在静态存储区的数据端中。
局部变量和全局变量是否可以重名
可以,但是局部变量会屏蔽全局变量,如果要使用全局变量需要在变量前加::
头文件中双引号和尖括号的区别
#include < > 到保存系统标准头文件的位置查找头文件
#include " " 查找当前目录是否有指定名称的头文件,然后再从标准头文件目录中查找
函数默认全局的,变量默认全局的
操作系统
QNX商用的
内存管理【RAM,固定分区法,内存控制块,MemCreate】先把内存进行分区,然后对每个区等分小块。系统管理分区,任务控制内存块
时间管理【TimeDly节拍延时,TimeDlyResume时钟延时,TimeDlyHMSM唤醒延时,TimeSet设置时钟】
事件管理【TCB和ECB,信号量,消息队列邮箱】
通信管理【】
模块(FS,TCP/IP,其它组件)
任务是由程序(任务代码)、数据和任务控制块3部分组成的。 任务常常是双向链表连接起来的。
FreeRTOS
RTOS操作系统的核心内容在于实时内核
1. 临界段代码保护
2. 任务的四种状态(3,4,5都有)
![在这里插入图片描述](https://img-blog.csdnimg.cn/e218bba7a4cb48
1NETiBAQeWViuS5i-iKneWQsQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
4个状态的含义如下:
就绪态:已经可以运行,等待调度器的切入
运行态:正在占用CPU运行
阻塞态:等待某个事件的到来,定时或者同步
挂起态:退出调度系统,调度器不可见,只能使用vTaskSuspend()挂起和vTaskResume()唤醒后进入就绪态
while(1)和for(;;)一样都是死循环
任务管理流程:
start》找出内核》找出最高优先级任务》找到任务控制块》启动
时钟 “起搏器”
main函数
初始化》创建任务》启动多任务调度【任务交由OS托管】
就绪列表设计思路:
源码用了三句很简单的语句,保证了每次查找的时候使用的时间是一样的,而且尽可能短。8*8的方表
3. 任务优先级
4. 任务创建与删除
没有使用限制
支持抢占
支持优先级
每个任务都拥有堆栈(需要分配内存)导致RAM内存使用量加大
使用抢占必须考虑重入的问题(可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的 话,可能会出现问题,这类函数是不能运行在多任务环境下的)
xTaskCreate()函数(动态方法创建):
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数:
pxTaskCode: 任务函数【上面提到的任务代码】
pcName: 任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN
usStackDepth: 任务堆栈大小,注意实际申请到的堆栈是usStackDepth 的 4 倍。其中空闲任务的任务堆栈大小为
configMINIMAL_STACK_SIZE
pvParameters: 传递给任务函数的参数
uxPriotiry: 任务优先级,范围 0~ configMAX_PRIORITIES-1
pxCreatedTask: 任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他 API函数可能会使用到这个句柄
- 任务挂起与恢复
有时候我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了!FreeRTOS 给我们提供了解决这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。
改变任务优先级
查询任务信息
-
列表与列表项
-
队列……邮箱
任务之间传输的数据称作“消息”。
TCB(任务控制块)》》ECB(消息控制块)
单一消息用邮箱(传递消息缓冲区的指针,假如有一种情况任务1有一个消息要发给任务2,然后我们先找一个任务控制块,声明为消息邮箱,然后里面有一个指针,让那个指针指向消息缓冲区,然后把任务控制块发给任务2,任务2通过任务控制块的指针找到消息邮箱)【邮箱也有ECB的】
邮箱:MboxCreate 创建消息邮箱……
大量的消息用队列。软件设计模式!
(传递消息缓冲区的指针,假如有一种情况任务1有一个消息要发给任务2,然后我们先找一个任务(EVENT)控制块,声明为消息队列,然后消息缓冲区通过数组排列起来,然后里面有一个指针指向一个队列控制块,队列(Q)控制块控制消息指针数组(通过好多个指针控制数组大小什么的),数组再保存指向具体消息的指针,让那个指针指向数组头消息缓冲区,然后把任务控制块发给任务2,任务2通过任务控制块的指针找到消息队列)
又由于队列控制块的存在,数组构成了一个循环队列。
队列:QCreate 创建消息队列…… -
4种信号量
-
事件标志组
-
时钟
通过时钟滴答计算间隔的时间,将任务延时若干个整数时钟节拍以及任务挂起等待的时候可以使用,时间片轮转的算法依赖 -
任务通知,任务之间的通信
-
系统启动
内核初始化,多任务用户程序启动 -
调度方式
可抢占方式和不可抢占方式,可抢占的任务调度方式一定要有好的剥夺原则(优先原则和XX原则)
任务调度函数:StartHighRdy 运行最高优先级任务 Sched任务级任务调度 IntExit中断级任务调度
在任务程序内部加入delay就可以防止死锁,就是延时让出高优先级任务占用CPU资源给低的优先级
调度原则:
- 及时响应:尽可能缩短事件的响应时间
- 支持优先:对于紧急的任务优先处理
- 公平:任务不能被无限延期
- 有效:CPU的利用率要尽可能高
调度算法:
- 先进先出算法(FIFO)
- 短作业优先算法
- 优先级调度算法
- 时间片 轮转 调度算法(轮询)
调度时机:
有五种情况
- 时间片用完
- 任务创建和删除了
- 任务因为请求资源而被等待挂起
- 操作系统刚刚开始运行的时候
- 系统开放中断,产生了一个中断事件
- 空闲任务和统计任务
任务初始化》创建任务(没有写任务函数)》启动多任务调度
空闲任务是系统在【启动调度器】的时候创建的优先级最低的任务,空闲任务主体主要是 做一些系统内存的清理工作。
统计任务使用这个计数器以确定现行应用软件实际消耗的CPU时间。
二、单片机
GPIO
三种常用方式:输出,输入,悬 空
中断
什么是中断:意思就是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
中断的触发方式:外部中断依靠电平触发方式和跳沿触发方式。电平触发方式适合于外部中断以低电平输入而且中断服务程序能清除外部中断请求源的情况。外部中断若定义为跳沿触发方式,外部中断申请触发器能锁存外部中断输入线上的负跳变。内部中断由一条指令INT n产生中断类型码或者由指令规定。
哪些函数不能在中断中使用:printf,malloc,以及会导致睡眠的函数
什么叫不可重入函数:一个可重入的函数简单来说,就是:可以被中断的函数。就是说,你可以在这个函数执行的任何时候中断他的运行,在任务调度下去执行另外一段代码而不会出现什么错误。而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等等,所以他如果被中断的话,可能出现问题,所以这类函数是 不能运行在多任务环境下的。
中断与异常有何区别:异常是由于执行了现行指令所引起的。由于系统调用引起的中断属于异常。而中断则是由于系统中某事件引起的,该事件与现行指令无关
中断的响应执行流程是什么:保护断点,寻找中断入口,执行中断处理程序,中断返回
写一个中断服务需要注意哪些:中断函数不能进行参数传递,中断函数没有返回值
中断和轮询哪个效率高?怎样决定是采用中断方式还是采用轮询方式去实现驱动?:CPU要和外设进行通信,可以采用轮询和中断两种方式。因为轮询方式需要CPU轮询外设,查询外设是否发生中断,效率不高显而易见。于是增加了如下图的中断系统来减轻CPU负担,但是这样做效率就高了吗?本质上,采用中断系统后,CPU仍然需要每隔一小段时间去查询中断控制寄存器TCON的各位状态,以判断是否有外设中断发生,否则CPU仍旧无法知道外设的当前状态。
如上所述,中断和轮询,好像又没啥区别,CPU仍旧摆脱不了查询的命运。但是你知道让CPU直接和各个外设逐一沟通,和让CPU只与中断控制系统机构沟通,效率是完全不一样的。为了证明我的推断,我们假设,CPU外接20个不同的设备,这20个外设中在某一刻有两个外设同时中断,正好这个时候CPU来查看外设的状态,如果是轮询方式,CPU需要一一遍历20种不同的外设控制器,才能判断哪些外设刚才申请过中断,哪些外设没有申请中断。如果采用中断方式处理呢?CPU只需查询一下中断标志位,处理最高优先级的那个中断,其他的事情全交给中断系统去处理,效率提高了20倍!
中断优先级有哪些:多个中断同时请求的时候需要分配优先级,有抢占优先级和响应优先级,抢占优先级>响应优先级 ,数值越小,等级越高。
定时器
通讯协议
什么是异步传输和同步传输:
- 异步传输是面向字符的传输,而同步传输是面向比特的传输。
- 异步传输的单位是字符,而同步传输的单位是帧。
- 异步传输通过字符起止的开始和停止码抓住再同步的机会,而同步传输则是以数据中抽取同步信息。
- 异步传输对时序的要求较低,同步传输往往通过特定的时钟线路协调时序。
- 异步传输相对于同步传输效率较低。
uart,spi,iic 区别:https://blog.csdn.net/weixin_45568186/article/details/115382977
UART
usart和uart的区别:一般而言,单片机中,名称为UART的接口一般只能用于异步串行通讯,而名称为USART的接口既可以用于同步串行通讯,也能用于异步串行通讯(有延迟的就是异步,没延时重合的就是同步)
uart优缺点
优点:只使用两根电线不需要时钟信号有一个奇偶校验位只要双方设置后,就可以改变数据包的结构有完整的文档并且具有广泛的使用。
缺点:数据帧的大小限制为最多9位,不支持多个从属或多个主系统,每个UART的波特率必须在10%之内。
串口在嵌入式系统当中是一类重要的数据通信接口,其本质功能是作为 CPU 和串行设备间的编码转换器。当数据从 CPU 经过串行端口发送出去时,字节数据转换为串行的位;在接收数据时,串行的位被转换为字节数据。应用程序要使用串口进行通信,必须在使用之前向操作系统提出资源申请要求(打开串口),通信完成后必须释放资源(关闭串口)。典型地,串口用于 ASCII 码字符的传输。通信使用3根线完成:(1)地线,(2)发送数据线,(3)接收数据线。
串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配:波特率是一个衡量通信速度的参数,它表示每秒钟传送的 bit 的个数;数据位是衡量通信中实际数据位的参数,当计算机发送一个信息包,标准的值是 5,7 和 8 位。如何设置取决于你的需求;停止位用于表示单个包的最后一位,典型的值为 1,1.5和 2 位,停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会;奇偶校验位是串口通信中一种简单的检错方式,有四种检错方式——偶、奇、高和低,也可以没有校验位。
奇校验:校核数据完整性的一种方法,一个字节的8个数据位与校验位(parity bit )加起来之和有奇数个1。校验线路在收到数后,通过发生器在校验位填上0或1,以保证和是奇数个1。因此,校验位是0时,数据位中应该有奇数个1;而校验位是1时,数据位应该有偶数个1。如果读取数据时发现与此规则不符,CPU会下令重新传输数据。 奇/偶校验(ECC)是数据传送时采用的一种校正数据错误的一种方式,分为奇校验和偶校验两种。 如果是采用奇校验,在传送每一个字节的时候另外附加一位作为校验位,当实际数据中“1”的个数为偶数的时候,这个校验位就是“1”,否则这个校验位就是“0”,这样就可以保证传送数据满足奇校验的要求。在接收方收到数据时,将按照奇校验的要求检测数据中“1”的个数,如果是奇数,表示传送正确,否则表示传送错误。 同理偶校验的过程和奇校验的过程一样,只是检测数据中“1”的个数为偶数。
IIC
半双工的
- 要开始通信,首先要发送起始条件
- 要结束通信,要发送一个停止条件
- 通信时,不希望其他主机进来打断我,我就不发送停止条件,可以多次发送起始条件完成多段通信,避免其他主机把总线抢走。
SPI
四条线,两条数据传输线(全双工,两根串行线传输信号),一条时钟线,一条片选线(连别的设备)
主机从机都有一个串行移位寄存器,主》从,从》主
- 写读操作是同时进行的。通信速率较高,可达几兆至几十兆。
- 有时钟线进行同步,总线出错率低。
- 可以一对多,一主多从。
- 缺点:不同的从机需要不同的片选线。(不同的外设芯片需要接不同的线)只能使用一个主机、
CAN总线
CAN通信是以五种不同类型的帧进行的:
数据帧,遥控,错误,过载,间隔
- 数据帧标准格式11个位,
- 设置数据帧
- 总线仲裁(设置优先级)
- 设置波特率(位时序)
DMA
直接存储器访问。
数据传输不经过CPU,直接传到另一个地址空间。DMA1有7个通道,
四级优先级,
DMA的出现就是为了解决批量数据的输入/输出问题。DMA是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。这样数据的传送速度就取决于存储器和外设的工作速度。
DMA的数据传送分为预处理、数据传送和后处理3个阶段。
3.1使用DMA大约步骤
1、使能DMA时钟,并等待数据流可配置。 使能DMA时钟,才可配置DMA相关的寄存器。要对DMA的配置寄存器DMA_SxCR进行配置,则要等其最低位为0----即DMA传输禁止,才可配置。
2、DMA_SxCR(指定的外设–x)设置该流x,包括配置通道,外设基地址,存储器地址,传输数据量、优先级,数据传输方向。
3、使能外设的DMA功能。
4、使能DMA数据流,启动传输。
5、查询DMA传输状态。
3.2DMA流程步骤
外设发起DMA请求》进入AHB从设备》进入仲裁器》连接相应通道》通过DMA总线》访问存储器Flash(顺序反过来是一样的,外设和存储器两两组合共三种情况)
存储
Flash用来存储编译好的程序文件,SRAM用来存储运行程序时所创建的临时数据。所以如果不加入外置存储器,那么程序里的东西就会出现在这两个存储器中。
存储器是单片机结构的重要组成部分,存储器是用来存储编译好的程序代码和数据的,有了存储器单片机系统才具有记忆功能。
cpu的基本动作是取指,译码,执行,从第一条指令开始执行,循环往复。
IMU温漂怎么处理
温补的本质是系统辨识。之前我们在介绍标定的时候,也说过一句类似的话“标定的本质是参数辨识”。这两者之间相似又不同。参数辨识是指已知误差模型,去估计各误差量的实际值是多少,而系统辨识在做参数辨识之前,还要做模型辨识,因为器件偏差随温度变化的模型是未知的,不知道有几个变量,也不知道是几阶模型。
- 多项式拟合方法
- 基于分段拟合的方法
3)深度学习方法
4)卡尔曼滤波……
三、RTOS
什么是RTOS
能在指定或确切的时间内完成系统功能和对外部或内部、同步或异步时间做出响应的系统。
实时系统的特点是,如果逻辑和时序出现偏差将会引起严重后果的系统。有两种类型的实时系统:软实时系统和硬实时系统。
在软实时系统中系统的宗旨是使各个任务运行得越快越好,并不要求限定某一任务必须在多长时间内完成。
在硬实时系统中,各任务不仅要执行无误而且要做到准时。大多数实时系统是二者的结合。
代码的临界段
代码的临界段也称为临界区,指处理时不可分割的代码。一旦这部分代码开始执行,则不允许任何中断打入。为确保临界段代码的执行,在进入临界段之前要关中断,而临界段代码执行完以后要立即开中断。
任务的五种状态
一个任务,也称作一个线程,是一个简单的程序,该程序可以认为CPU完全只属该程序自己。实时应用程序的设计过程,包括如何把问题分割成多个任务,每个任务都是整个应用的某一部分,每个任务被赋予一定的优先级,有它自己的一套CPU寄存器和自己的栈空间。
典型地、每个任务都是一个无限的循环。每个任务都处在以下5种状态之一的状态下,这5种状态是==休眠态,就绪态、运行态、挂起态(等待某一事件发生)和被中断态==
休眠态相当于该任务驻留在内存中,但并不被多任务内核所调度。
就绪意味着该任务已经准备好,可以运行了,但由于该任务的优先级比正在运行的任务的优先级低,还暂时不能运行。
运行态的任务是指该任务掌握了CPU的控制权,正在运行中。
挂起状态也可以叫做等待事件态WAITING,指该任务在等待,等待某一事件的发生,(例如等待某外设的I/O操作,等待某共享资源由暂不能使用变成能使用状态,等待定时脉冲的到来或等待超时信号的到来以结束目前的等待,等等)。
最后,发生中断时,CPU提供相应的中断服务,原来正在运行的任务暂不能运行,就进入了被中断状态。图2.3表示μC/OS-Ⅱ中一些函数提供的服务,这些函数使任务从一种状态变到另一种状态。
可剥夺型内核
当系统响应时间很重要时,要使用可剥夺型内核。最高优先级的任务一旦就绪,总能得到CPU的控制权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态,当前任务的CPU使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。如图2.5所示。
使用可剥夺型内核时,应用程序不应直接使用不可重入型函数。调用不可重入型函数时,要满足互斥条件,这一点可以用互斥型信号量来实现。如果调用不可重入型函数时,低优先级的任务CPU的使用权被高优先级任务剥夺,不可重入型函数中的数据有可能被破坏。综上所述,可剥夺型内核总是让就绪态的高优先级的任务先运行,中断服务程序可以抢占CPU,到中断服务完成时,内核让此时优先级最高的任务运行(不一定是那个被中断了的任务)。任务级系统响应时间得到了最优化,且是可知的。μC/OS-Ⅱ属于可剥夺型内核。
任务优先级与调度
每个任务都有其优先级。任务越重要,赋予的优先级应越高。
μC/OS-Ⅱ总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高,下面该哪个任务运行了的工作是由**调度器(Scheduler)完成的。任务级的调度是由函数OSSched()**完成的。中断级的调度是由另一个函数OSIntExt()完成的
如果在中断服务子程序中调用OSSched(),此时中断嵌套层数OSIntNesting>0,或者由于用户至少调用了一次给任务调度上锁函数OSSchedLock(),使OSLockNesting>0。如果不是在中断服务子程序调用OSSched(),并且任务调度是允许的,即没有上锁,则任务调度函数将找出那个进入就绪态且优先级最高的任务(2),进入就绪态的任务在就绪任务表中有相应的位置位。一旦找到那个优先级最高的任务,OSSched()检验这个优先级最高的任务是不是当前正在运行的任务,以此来避免不必要的任务调度
任务切换很简单,由以下两步完成,将被挂起任务的微处理器寄存器推入堆栈,然后将较高优先级的任务的寄存器值从栈中恢复到寄存器中
空闲任务(Idle Task)
μC/OS-Ⅱ总是建立一个空闲任务,这个任务在没有其它任务进入就绪态时投入运行。这个空闲任务[OSTaskIdle()]永远设为最低优先级,即OS_LOWEST_PRI0。空闲任务OSTaskIdle()什么也不做,只是在不停地给一个32位的名叫OSIdleCtr的计数器加1,统计任务使用这个计数器以确定现行应用软件实际消耗的CPU时间。
任务之间的通讯与同步
实现任务间通讯最简便到办法是使用共享数据结构。。虽然共享数据区法简化了任务间的信息交换,但是必须保证每个任务在处理共享数据时的排它性,以避免竞争和数据的破坏。与共享资源打交道时,使之满足互斥条件最一般的方法有:
关中断,使用测试并置位指令,禁止做任务切换,利用信号量
在µC/OS-II中,有多种方法可以保护任务之间的共享数据和提供任务之间的通讯。在前面的章节中,已经讲到了其中的两种:
一是利用宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()来关闭中断和打开中断。当两个任务或者一个任务和一个中断服务子程序共享某些数据时。
二是利用函数OSSchedLock()和OSSchekUnlock()对µC/OS-II中的任务调度函数上锁和开锁。用这种方法也可以实现数据的共享,给调度器上锁和开锁。
将介绍另外三种用于数据共享和任务通讯的方法:信号量、邮箱和消息队列。
μC/OS-Ⅱ初始化
内存管理
在ANSI C中可以用malloc()和free()两个函数动态地分配内存和释放内存。但是,在嵌入式实时操作系统中,多次这样做会把原来很大的一块连续内存区域,逐渐地分割成许多非常小而且彼此又不相邻的内存区域,也就是内存碎片。由于这些碎片的大量存在,使得程序到后来连非常小的内存也分配不到。由于内存管理算法的原因,malloc()和free()函数执行时间是不确定的。
在µC/OS-II中,操作系统把连续的大块内存按分区来管理。每个分区中包含有整数个大小相同的内存块。利用这种机制,µC/OS-II对malloc()和free()函数进行了改进,使得它们可以分配和释放固定大小的内存块。这样一来,malloc()和free()函数的执行时间也是固定的了。
在一个系统中可以有多个内存分区。这样,用户的应用程序就可以从不同的内存分区中得到不同大小的内存块。但是,特定的内存块在释放时必须重新放回它以前所属于的内存分区。显然,采用这样的内存管理算法,上面的内存碎片问题就得到了解决。
任务调度
uCOS II提供最简单的实时内核任务调度,算法简单,因此也只支持优先级抢占任务调度,不支持时间片轮训调度算法,不支持优先级逆转。
uCOS II总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高,下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。任务级的调度是由函数OSSched()完成的。中断级的调度是由另一个函数OSIntExt()完成的
任务切换
Context Switch 在有的书中翻译成上下文切换,实际含义是任务切换,或CPU寄存器内容切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态(Context),即CPU寄存器中的全部内容。这些内容保存在任务的当前状况保存区(Task’s Context Storage area),也就是任务自己的栈区之中。(见图2.2)。入栈工作完成以后,就是把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU的寄存器,并开始下一个任务的运行。这个过程叫做任务切换。任务切换过程增加了应用程序的额外负荷。CPU的内部寄存器越多,额外负荷就越重。做任务切换所需要的时间取决于 CPU有多少寄存器要入栈。实时内核的性能不应该以每秒钟能做多少次任务切换来评价。
信号量
ucos ii对共享资源提供了保护机制。正如上文所提到的,ucos ii是一个支持多任务的操作系统。一个完整的程序可以划分成几个任务,不同的任务执行不同的功能。这样,一个任务就相当于模块化设计中的一个子模块。在任务中添加代码时,只要不是共享资源就不必担心互相之间有影响。而对于共享资源(比如串口),ucos ii也提供了很好的解决办法。一般情况下使用的是信号量的方法。简单地说,先创建一个信号量并对它进行初始化。当一个任务需要使用一个共享资源时,它必须先申请得到这个信号量,而一旦得到了此信号量,那就只有等使用完了该资源,信号量才会被释放。在这个过程中即使有优先权更高的任务进入了就绪态,因为无法得到此信号量,也不能使用该资源。
互斥信号量
解决优先级翻转问题
邮箱机制
邮箱机制(mailbox)的通信机制可以使一个任务或者中断服务函数向另一个任务发送一个指针型变量。通常,这个指针指向了一个包含了消息的数据结构,即所谓的邮件
消息队列
- FIFO:First Input First Output,即先进先出。
- LIFO:Last Input First Output,即后进先出。
消息队列的通信机制可以使一个任务或者中断服务函数向另一个任务发送多个指针型变量作为消息体的消息。通常,每个指针指向了一个包含了消息的数据结构,即所谓的消息内容。