嵌入式

本文介绍了嵌入式C编程的基础知识,包括C的关键词如static、define、const、inline和volatile的使用,大小端的概念,以及手写库函数的实践。还涉及了STM32-ARM的知识,CPU架构,Linux启动过程,编程题和汇编等内容,是嵌入式软件工程师的经典笔试题。
摘要由CSDN通过智能技术生成

目录

1、嵌入式c编程基础知识

1.1 C的关键词

关于static:

关于define:

关于const:

关于inline:

关于volatile:

1.2 关于大小端

手写库函数 strcpy,strncpy,memcpy,memset

https://blog.csdn.net/tsh123321/article/details/52263707

 

题目:

 1、在一台64位的机器上,使用32位编译,Garfield 变量占用多少内存空间?64位编译又是如何?

2. 描述下面XXX 这个宏的作用。(总分10分)

1.3 总线回顾

1.3.1 SPI

1.3.2 I2C

1.4 CPU架构(x86与x86_64,Intel与ARM)

1.4.1 RISC与CISC造成的区别

1.5 STM32-ARM知识

1.5.1 stm32启动分析

面试问题:如果单片机启动不了,可能的原因?

1.6 Linux启动过程

1.6.1 BIOS自检

1.6.2 bootloader

1.6.3 系统初始化

2、编程

2.1、输入与输出

2.2、编程题

请写一个函数,将一个16进制字符串转换为数字

3、简述

4、汇编


 


 

嵌入式软件工程师经典笔试题

1、嵌入式c编程基础知识

程序编译时各部分介绍:

动态存储区、静态存储区、堆和栈的区别

空指针和void *类型指针

联合体判断大小端 

1.1 C的关键词

关于static:

修饰函数:使函数只能被其所在的文件访问,其他c文件不能访问static修饰的函数;

修饰变量

  • static修饰的变量会被保存在静态存储区而不是栈中(程序运行在栈中,运行完会被释放);
  • 但static修饰的变量虽然不会被释放,但跟全局 变量不同,static修饰的变量只能在其被定义的作用域内被访问;

关于define:

  • 避免了意义模糊的数字出现,使得程序语义流畅清晰
  • 便于修改函数
  • 提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。

define 的括号里的参数表示变量

关于const:

  • const修饰的常量具有不可变性
  • 编译器不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

常量指针和指针常量:

	char *str = "Hello World";
	//定义常量指针,即指针指向的对象是常量
	const char *pstr = str;
	printf("%c\n", *pstr);
	//*pstr = 'S';//error,指针指向的对象不可变
	printf("%c\n", *++pstr);//right,指针本身可变
	//定义指针常量 ,指针是 常量
	char* const ppstr = str;
	//ppstr++;//error
	*ppstr = 'N';//right

关于inline:

参考:inline

简单概括,inline修饰的内联函数在编译器编译时,将原本程序中的函数 调用步骤直接替换成了函数本体,因此减少了原本调用步骤产生的额外开销;但是缺点就是导致了代码膨胀!

关于volatile:

volatile为什么要修饰中断里的变量

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量 

1.2 关于大小端

大端:数据的高位存在内存的低字节,典型的如ARM、PowerPC

小端:数据的低位存在内存的高字节,典型的如Intel

注意:大端小端是字节存储位置的不同,而不是每一个bit不同

例如:a=0x12345678

将a分别在大端处理器和小端处理器中写入内存0x000,那么其存储如下:

内存地址 0x0000 0x0001 0x0002 0x0003
大端 0x78 0x56 0x34 0x12
小端 0x12 0x34 0x56 0x78

那么如果在内存地址0x0000-0x0003中分别存储0x12 0x34 0x56 0x78,

则小端读取得到:0x12345678

而大端读取得到:0x78563412

因为字节序只是对内置的数据类型而言(int、short、double、long....char),而对于char而言由于其本身只有一个字节则字节序和存储模式对其不影响。(因此字符串在跨平台传输时不用考虑字节序

因此网络通讯时常常要将字节序转化成网络字节序(大端)

再举一个例子:

struct stucA{
    unsigned int a:1;
    unsigned int b:2;
    unsigned int c:3;
    unsigned int d:4;
    unsigned int e:5;
    unsigned int f:6;
    unsigned int g:11;
}A;
memset(&A,0,sizeof(A));
A.d=11;

在小端中:a:0,b:00,c:000,d:1101,e,f,g都为0,因为小端中数据低位存在内存低位,因此数据存储(高位地址->低位地址)为:

0000 0000 0000 0000 0000 0010 1100 0000,即使从低位到高位分别为 0xc0,0x02,0x00,0x00;

在大端中:因为数据的高位存在内存的低位,则数据存储(高位地址->低位地址)为:

0000 0011 0100 0000 0000 0000 0000 0000,按照每位数字来看,刚好与小端模式完全相反,但是!,大端中字节的读取方式也是高位数据在低位地址,因此 得到数据(从低位地址到高位地址依次显示)为  0x00 0x00 0x02 0xc0.

可以用以下代码测试:

#include <stdio.h>
#include <memory.h>
#include <iostream>
using namespace std;
struct strA {
	unsigned int a : 1;
	unsigned int b : 2;
	unsigned int c : 3;
	unsigned int d : 4;
	unsigned int e : 5;
	unsigned int f : 6;
	unsigned int g : 11;
};
struct ch {
	char c[4];
};
union test
{
	strA A;
	ch   C;
}T;
int main()
{
	memset(&T, 0, sizeof(T));
	T.A.d = 11;
	for (int i = 0; i < 4; i++)
		printf("%02x\n", (unsigned)(unsigned char)T.C.c[i]);//以16进制输出,不足两位前面补0
	getchar();
}

 

手写库函数 strcpy,strncpy,memcpy,memset

https://blog.csdn.net/tsh123321/article/details/52263707

 

题目:

 1、在一台64位的机器上,使用32位编译,Garfield 变量占用多少内存空间?64位编译又是如何?

struct CAT_s{
int ld;//32
char Color;、、
unsigned short Age;
char *Name;
void(*Jump)(void);
}Garfield;
数据类型 32位编译器/字节 64位编译器/字节 备注
char     1 1 例如:0xff
short 2 2 例如:0xffff
int 4 4 例如:0xffff ffff
long 4 8  
float 4 4  
char* 4 8 实际指向的是一个地址,地址字节数由编译器决定
long long 4 8  
double 8 8  
long double 10/12 10/16 有效字节10位,但 为了对齐实际分配12/16字节

所以使用32位编译器时:

  • 1个int 4字节;
  • char、unsigned shore 共3字节,为了对齐,算4字节;
  • char* 4字节;void * 4字节,

合计16字节

所以使用64位编译器时(8字节/64位对齐):

  • 1个int 4字节,char、unsigned shore 共3字节,为了对齐,合计算8字节;
  • char* 8字节;
  • void * 8字节,

合计24字节

2. 描述下面XXX 这个宏的作用。(总分10分)

 

#define offsetof(TYPE,MEMBER)   ((size_t)&((TYPE*)0)->MEMBER)
#define XXX(ptr,type,member)      ({\
                                 const typeof(((type*)0)->member)*__mptr=(ptr);\
                                 (type*)(char*)__mptr – offsetof(type,member));})offsetof(type,member));})

第一条语句:

#define offsetof(TYPE,MEMBER)   ((size_t)&((TYPE*)0)->MEMBER)

         TYPE代表一个结构体,MEMBER代表结构体成员,因此这条宏定义强行将结构体起始地址定义为0,那么member就代表成员地址的偏移量;

步骤如下:

  • (TYPE*)0  //将0转型为TYPE类型,即TYPE类型首地址为0
  • &((TYPE*)0)->MEMBER//取TYPE类型下MEMBER成员的地址,因为首地址为 0,因此可以看做成员地址的偏移量
  • (size_t)&((TYPE*)0)->MEMBER)//将改地址转型为(size_t)类型

第二条语句:

  • typeof(((type*)0)->member)  //取type类型下成员member的类型,例如:member是char,那么就代表char
  • const typeof(((type*)0)->member)*__mptr=(ptr);//将ptr指向的地址赋值给_mptr,_mptr的类型是const typeof(typeof到底是char,还是其他由member的类型决定!)
  • (type*)(char*)__mptr – offsetof(type,member))//将mptr转化成结构体下成员member的指针,而offsetof()是member的地址偏移量,因此相减得到结构体的起始地址!

 3、在一个多任务嵌入式系统中,有一个CPU 可直接寻址的32位寄存器REGn ,地址为 0x1F000010,编写一个安全的函数,将寄存器REGn 的指定位反转(要求保持其他bit 的值不变)

void bit_reversal(uint32_t nbit)
{
      *((volatial unsigned int *)0x1F000010)^=0x01<<nbit;
}

((volatial unsigned int *)0x1F000010)为地址定义,例如(int *)0x1F111111,代表可寻址的地址。

4、 有10000个正整数,每个数的取值范围均在1到1000之间,变成找出从小到大排在第 3400(从0开始算起)的那个数,将此数的值返回,要求不使用排序实现。(总分10分)

输入参数 数组a,数组长度n ,要查找的第index个数;返回:0 -未找到   i-第index数的数值

int fun_find(int *a, int n, int index)
{
	int c = 0;
	int count[1001] = {0};
	for (int i = 0; i < n; i++)
	{
		count[a[i]]++;
	}
	for (int i = 0; i < 1001; i++)
	{
		c += count[i];
		if (c >= index)
			return i;
	}
	return 0;
}

1.3 总线回顾

1.3.1 SPI

接口:SDI、SDO、SCLK、CS

特点:有主从之分、全双工、高速、速度遵从事实标准

面试题之坑:SPI速率是多少?

答:。SPI是一种事实标准,由Motorola开发,并没有一个官方标准。已知的有的器件SPI已达到50Mbps。具体到产品中SPI的速率主要看主从器件SPI控制器的性能限制。

  1. SPI的最大时钟频率
  2. CPU处理SPI数据的能力
  3. 输出端驱动能力(PCB所允许的最大信号传输速率)

1.3.2 I2C

  • 总线:SCL、SDL
  • 传输:按照字节传输,每个字节必须是八位;传输一个字节后主机必须发送一个响应位;先发送高位,即MSB传输
  •  I2C地址:共8位,前四位为每种设备的固定地址,后三位可以由用户自己配置,最后一位为R/W;主地址(4bit)+从地址(3bit)+读写位(R/W)
  • 速率:S(标准模式)测量与控制场合,100kb/s; F(快速模式),速率为 400kb/s;Hs(高速模式),速率为 3.4Mb/s。 

因此最多可以挂载2^7=128个设备

  • 7
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值