本文只是一个总结。在嵌入式面试过程中,面试官会提到一些问题。你可以根据问题自己找到解释和总结,一些常见的考试内容可以自己总结
C语言
sizeof()其实是关键词,因为它的用法sizeof()后面加了括号,误以为是函数。例如,我们声明一个变量 int i=0; 在程序中运行输出,sizeof(i)为4。再试一次sizeof i,结果也是4。所以不加括号和括号就能得到大小,很明显sizeof不是函数。为什么编程时习惯加括号? 比如输出,sizeof int,如果没有括号,编译就会出错,因为如果在数据类型之前没有括号,编译器将被误认为是一个修改符,类似const、unsigned。 因此,在编写代码时,建议添加括号
2.1、被const所有修改的对象都受到强制保护,不会意外改变,以提高程序的强度。 2.两种常见用法 const int i=10; ///修改变量i的值在内存中是不可变的 void fun(const int i); ///修饰函数参数在函数运行体内是不可变的 const int *p; //指针p是可变的,p指向内容不可变 int *const p; //指针p是不可变的,p指向的内容是可变的
理解英语字面意思是易变的。 例如,在多线程运行中常用的应用场景是共享变量,例如有一个变量int i=0.两个线程A,B. A线程运行:i = i 1; B线程运行:i = i 2; 无论是先操作A还是先操作AB。希望的结果i的值是3,那么就需要用volatile来修饰。 通过volatile修改后,当其中一个线程运行时,i的结果将直接刷新到内存中,销毁以前的缓存值,另一个线程将从内存中获得此值。 另一个场景是中断和线程配合。中断后,变量发生了变化。中断退出后,希望内存刷新此值。
指针函数:指函数返回的数据
int res=0; int *fun(int a,int b) {
res = a b; return &res; }
函数指针:将定义初始化为函数指针,
int (*p)(int a,int b); int fun(int a,int b) {
int res=0; res = a b; return res; } void main(void) {
int a=1,b=2; int sum; p = fun; sum = p(a,b); printf("%d",sum); }
函数指针最大的功能是在调用时直接找到函数地址,典型的应用是回调函数。函数指针可以作为参数传输。 例如,回调函数
int callback(int a,int b,int (*p)(a,b))
{
return p(a,b);
}
void main(void)
{
printf("%d",callback(2,3),fun);
}
接着上面的说,回调函数起始可以理解为一个软中断,可以直接调用把函数名当作函数指针参数传入,通过寻址的方式跳转到函数运行,提高代码运行效率。
错误的写法:
float x;
//错误的写法
if(x == 0.0)
if(x != 0.0)
//正确的写法
const float EPSINION = 0.00001 //需要比较的精度
if((x >= -EPSINION) && (x <= EPSINION))
int *p;
//错误的写法
if(!p) //这是BOOL变量的写法
if(p)
//正确的写法
if(p == NULL)
if(p != NULL)
- 为什么要字节对齐
struct TestStruct
{
char c1;
short s;
char c2;
int i;
};
当读取该结构体是,你以为在内存存储是紧凑的,假设c1在地址0,s就在地址00000001,c2在地址00000003,i在地址00000004. 但在Visual C++中
struct TestStruct1 a;
printf("c1 %p, s %p, c2 %p, i %p\n",
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a);
这就是内存对齐导致的问题,如果不对齐,系统访问时,short s会访问两次才能组合成s系统为了提高系统性能,自动将c1按两字节对齐,这样系统访问short s时就只用访问一次。
- 可以用什么样的方式对齐字节,使计算读取的字节数,对于定义 一般我们为了使定义的结构sizeof()获取的结构体长度准确,会对结构体做1字节对齐
#pragma pack(1)
struct TestStruct
{
char c1;
short s;
char c2;
int i;
};
#pragma pack()
这样在计算sizeof(TestStruct);就是实际的8,如果不加的话就是12.
通信协议
半双工、全双工
1、半双工:同一时刻只能发送或者接收 2、全双工:既可以接收也可以输出 可以通过硬件出线理解,比如IIC是SCL、SDA,只有一根数据线,肯定是半双工。串口TX、RX和4线SPI是MOSI\MISO,同时包含了输入输出肯定是全双工
同步、异步
1、同步:带有时钟线(IIC,SPI) 2、异步:不带有时钟线(uart,CAN)
通信协议 | 速率 | 节点通信 | 时序 | 通信模式 | 应用场景 | 节点总数 | 通讯距离 | 备注 |
---|---|---|---|---|---|---|---|---|
IIC | 100K~400K,也有高速3.4Mb/s对eeprom通讯 | 寻址的方式,注意地址位一般是7位,还有一个读写位 | 起始信号、应答、结束信号 | 半双工 | 加速度传感器芯片 | 根据地址来的。地址位7bit,可以挂127,也有8bit、10bit | 板上通讯 | 上拉电阻一般是1.5K,2.2K,4.7K,电阻的大小对时序有一定影响,对信号的上升时间和下降时间也有影响。公式:Rmin={Vdd(min)-o.4V}/3mA、Rmax=(T/0.874) *c, T=1us 100KHz, T=0.3us 400KHz,c为电容,一般会选用100pF、200pF |
SPI | 最大可达几十兆一般配置8M足够 | CS口片选 | 通过CLK时钟脉冲读写,还要注意配置极性模式,一般用模式0、3 | 3线半双工、4线全双工 | eeprom、flash芯片 | 任意个 | 板上通讯 | |
RS485 | 最高10M | 寻址的方式 | 串口协议 | 半双工,空闲时主机和从机都处于接收状态 | modbus通信 | 根据协议的地址码可以达到128个或者256个 | 距离可达1000多米,距离越远速率越低越稳定 | 传输距离较远时需要120欧的终端电阻 |
CAN | 低速一般250K,500K,高速可达1M | CANID寻址 | ISO11898标准 | 半双工 | 汽车上ECU节点,机器人节点通信 | 最大32个 | 40米 | 高速CAN需要120欧的终端电阻,注意是终端电阻,不是每个节点上接电阻 |
LIN | 20K | 寻址的方式 | ID寻址 | 半双工 | 上层主控接在can节点的ECU | 最多16个 |