指令含义:
EXPORT : 表示本程序里面用到的变量提供给其他模块调用的。
IMPORT : 定义表示这是一个外部变量的标号,不是在本程序定义的。
PROC 和 ENDP 是一起组合使用的,表示一段子程序。
MRS : 将状态寄存器的内容传送至通用寄存器。
MSR : 将通用寄存器的内容传送至状态寄存器。
CPSID I : 对于Cortex-M3来说是关闭中断的汇编指令
中断的含义与注意事项 :
1、不要在中断中执行发送接收过程,存在中断嵌套的问题,会造成只执行一次的现象。
2、串口的发送中断等同一个TCIE与TC两个开关串联一起控制,TCIE是总开关,由USART_ITConfig()控制,默认此位为0,开关断开状态。而TC默认为高,开关闭合态,
由USART_ClearITPendingBit清除。串口上任一B数据的发送完成都会使此位置1,任意时刻两开关同时闭合就会进入中断服务函数, 且只要不手动关闭和清零,开关状态就不会改变。一个闭合,另一个由开转向闭合的一瞬间就会进入中断。
3、TCIE是中断使能位,设置了此位,并且当事件发生时会自动触发标志位TC,两者共同作用中断请求才会被响应;但不论中断使能位开否,事件发生了都会触发标志位的改变,软件可以利用标志位控制程序的运行。
4、只要是开启了中断ppp_ITConfig( ) 就会进入中断服务程序,而默认中断为死循环,所以必须写具体的服务程序,清除标志位。同时在发送数据的最开始要先清除下TC标志位。
什么是“线与”:
在一个结点(线)上, 连接一个上拉电阻到电源 VCC 或 VDD 和 n 个 NPN 或 NMOS 晶体管的集电极 C 或漏极 D, 这些晶体管的发射极 E 或源极 S 都接到地线上, 只要有一个晶体管饱和, 这个结点(线)就被拉到地线电平上。 因为这些晶体管的基极注入电流(NPN)或栅极加上高电平(NMOS),晶体管就会饱和,所以这些基极或栅极对这个结点(线)的关系是 或非 NOR 逻辑。 如果这个结点后面加一个反相器, 就是 或 OR 逻辑。
其实可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为0,只有都为高电平时,与的结果才为逻辑1。
杂项知识要点:
1、二极管没有0805或1206封装,有SMA(DO-214AC),SMB(DO-214AA),SMC(DO-214AB),SOD-123,SOD-323,SOD-80等封装。
2、全局变量的定义 / 引用须在.C文件中,声明须放在 .h文件中,在对外部变量扩展本文件外的作用域声明时,不可以再赋值。例:
错误代码: | 正确代码:
int i=0; | int i=0;
extern int i= 0; | extern int i;
3、 字符‘0’:char c=‘0’;它的ASCII码实际上是0x30=48。内存中存放表示:00110000=0x30。
字符'\0': ASCII码为0,表示一个字符串结束的标志。这是转义字符。内存中存放00000000。整数0:ASCII码为0,字符表示为空字符,NULL;数值表示为0;内存中表示为:00000000。
5、多路复用器 (MULTIPLEXER 也称为数据选择器)是用来选择数字信号通路的;
模拟开关是传递模拟信号的,因为数字信号也是由高低两个模拟电压组成的, 所以模拟开关也能传递数字信号。
6、调用子函数的过程中,涉及到的指针变量实际上是存储在全局变量中的,子函数可能会修改它,但一旦返回主函数后,指针变量是不会被销毁的,因此能够保留下来。相对而言,子函数内部声明的变量,在运行过程中,通常情况下是直接使用位于CPU寄存器中的,函数在返回后,寄存器中的数据会被覆盖,使用权回归主函数(或上级调用函数)使用,因此,函数内部的变量是不能够保留的。
7、如果函数参数直接传递的是指针类型,那么在函数内改变指针指向,并不能影响函数外的指针指向,最多能改变本指针所指向变量的值。只有传入指针的指针,才能改变本指针的指向。在子函数中修改主函数传过来的指针的指向:如果函数参数直接传递的是指针类型,那么在函数内改变指针指向,并不能影响函数外的指针实例。只有传入指针的指针,才能改变指针的指向。示例:
void ff(Bitnode **x,Bitnode *bt)
{
*x=bt;
}便可实现改变指针bt的指向。
同时指针和数组是通用的,都可以拥有下标元素;比如P指向数组A,则A[i]==P[i]==*(P+i),且P指向不变。
8、服务器助手socket_tool只显示常用字符及标点符号,对特殊转义字符及数值0(0x00)不显示(可以接收到)。
9、还原现场的问题验证方式,而不是自以为是的验证方式。
10、全局变量的值直接在函数中使用即可,而不要用形参传入全局变量的值,这样在函数中形参的值是不会变得。
11、MDK左侧加灰的,是编译后有用的代码。 真正执行后会变绿!
12、(1)a >> 3表示将a右移3位,空位补0,移位只是借用a的值做个计算得到一个新值,取表达式的值,而a本身的值不会变。(2)类型转换时低位对齐,空位补0,比如u8->u16则低8位值不变,高8位全为0;u16->u8则只保留低八位的值,高8位全抹去。(3)'\'为续行符,其后不能有空格注释等一切符号,只能是一个回车,之前可以有其他的。(4)模运行就是取余运算 c = a 模 b = a % b = [0, b-1], c的取值在[0, b-1]间.
a &= ~(1<<6); //将a的第七位(bit6)置0 ;&按位与,1&A=A,0&A=0
a |= (1<<6); //将a的第七位(bit6)置1 ;|按位或,1|A=1,0|A=A
a ^= (1<<6); //将a的第七位(bit6)取反 ;^按位异或,相同为0,不同为1,0^A=A,1^A=!A
mask = 1 << n (n表示寄存器中的位号,比如第七位(bit6))
mask = 5 << n 因为5=101b,表示操作bitn和bitn+2位
if((flags & maks) == mask) 检查位是否设置成功
13、int *a, b; a是整形指针;b是整形变量;(书写规范)
Int* a,b; a是整形指针;b是整形变量;
14、for(i=0 ; i<n; i++)
特点:(1)循环n次后退出循环,for循环结束后i==n;
(2)中途break退出循环的i恒小于n;
15、RS485的vcc和gnd如果与主控相连时信号会丢失,所以485都是单独的隔离供电和地线。
16、i++与++i,都是取整体表达式的值,前者整体表达式的值是先用i,后自加1;而后者整体表达式的值是先自加1,后用加1后的i值。
17、#if 0
……
#endif 中间的句子在预编译时就被删除了,是不被编译没用的语句。
相反:
#if 1
……
#endif 中间的句子必然会被编译。
18、一切都没问题,串口死活调不通,试着把串口号改到1-5.
19、大端模式:高字节放入低地址;小端模式:高字节放入高地址。
20、用于做比较的宏定义等符号常量的值最好不要取0,从1开始递增,因为变量的初始化后为0,可能误打误撞与之匹配。
2 1、左边的图中两个二极管串联,可以保护中间的引线的电压不会超过(+3.3V+VF 、-VF)的电压范围;右边的图中,电阻与二极管串联,可以保证引线的电压不会低于-VF。
如果上图中所用的二极管是普通的开关二极管1N4148的话,那么上面左图中二极管可以保护引线电压不高于(大约)3.9V,不低于-0.6V
上面右图中的电阻和二极管的作用是保护引线中的电压不会低于 -0.6V。
22、数码管的后缀是BW的是共阳极的、后缀是AW的是共阴极的。
23、%x是输出一个16进制数
%8x是输出8位
%#x是带格式输出, 效果为在输出前加0x.
24、atof(),atoi(),atol(),strtod(),strtol(),strtoul()
atoi与atof可以自动识别符号,但如果被空格隔开则不能了。
printf("d=%d, f=%f", atoi("+1A"), atof("-0.001"));
d=1, f=-0.001000
printf("d=%d, f=%f", atoi(" +1A"), atof(" -0.001"));
d=1, f=-0.001000
printf("d=%d, f=%f", atoi("+ 1A"), atof("- 0.001"));
d=0, f=0.000000 (错误)
atof中不能出现字符D和d或E和e. 例: 1 = atoi(“+1E+2F”),
1 = atoi(“+1E + 2F”), 1.0 = atof(“+1E + 2F”), 100.0 = atof(“+1E+2F”)=1*10^2, 20.0=atof(“+2D+1F”)=2*10^1
25、不能用带U的数据做差取判断 <。数据都是按0/1存,但定义时有无U会决定最高位是作为数据位/符号位去使用。
Uint8 a=0xFF; 则a=255,int8 a=0xff; 则a=-1;uint8 a 与int8 a在内存中都是0xFF格式存储。如果a定义为uint8则
a=0-a;if(a>0)判断是成立的;如果定义为int8则判断不成立
26、strtok(); 会把索引字符变成0x00, 比如索引字符A,即可按“A”也可按‘A’索引。
27、常量没有地址,只有变量才有地址
28、中断处理函数不能有输入参数也不能有函数返回。因为中断都是些硬件自动调用,没有程序去给他传递参数,也没有硬件去接收参数。 那其它函数怎么传参数给它呢?一般都是通过全局变量方式。不过为了避免中断函数重入,我们可以在进入中断服务函数之后将中断关闭,处理完后再开中断。
29、任何以“0”(零)开始的整型常量都被看做是八进制的, 比如 42 = 052 != 52
30、调试/发行版本控制:
遵守C99标准下
#ifdef DEBUG
#define DEBUG_LOG(...) (void)printf(__VA_ARGS__)
#else
#define DEBUG_LOG(...) (void)0
#endif
不遵守C99标准
#ifdef DEBUG
#define DEBUG_LOG (void)printf
#else
#define DEBUG_LOG (void)sizeof
#endif
35、先对原始数据进行加密再调整端序。
36、在windows里面,一个路径的长度不能超过256个字符。在文件属性的位置里如果路径前出现 \\?就是此问题。
37、有符号与无符号整型数在做除了值大小比较外,诸如传输,存储和加减乘除时,完全等同。 Char = uchar; int = uint
38、char a = -1, char b = 0xff, char c = 255 => a == b == c;
char a = -1, int b = 0xff, char c = 255 => a == b == c;
int a = -1, char b = 0xff, int c = 255 => a != b == c;
40、程序编译完分为Code,RO,RW,ZI 共4部分,Code即代码段(.text);RW存放已初始化的全局变量(包含static声明的局部变量)/数组,ZI存放定义时初始化为0的全局变量/数组,RO存放常量(常数值,const声明的常变量,字符串)且RW和RO存入ROM中,上电后RW和ZI加载到RAM中,&Image$$RW_IRAM1$$ZI$$Limit既为堆区起始地址,也为栈区结束地址,堆区与局部变量、函数调用所使用的栈在这个范围内浮动,2者相遇时程序出错。
|
A B C D
RAM:------------ | | | |
A->D为芯片的RAM总大小,每一个程序编译后RW+ZI即A->C是固定值,堆和栈共用C->D的空间,栈从D开始往下用,堆从C开始往上用,2者动态浮动。同时ZI必须在RW上面,即ZI的结尾就是堆起始,不存在RW$$Limit。RW+ZI就等于C处的值即&Image$$RW_IRAM1$$ZI$$Limit,也是生成bin文件的第一个U32数,即MSP。bin中的第二个U32数即为复位函数的函数地址。函数偏移只需要调整VECT_TAB_OFFSET和编译器选项卡中的IROM1起始地址即可,由fromelf --bin !L --output xxx.bin命令生成的.bin文件就会带上偏移信息。
正解:
|0 -------------- 100|
|.data(RO+RW) .bss(ZI) 预留1K左右启动代码的栈空间 主程序堆主程序栈 |
Bin文件的第一个u32数或存在0x08000000地址处的u32数即MSP,即栈底值;假设.data+.bss+启动栈=40,余下的60B都是堆栈空间,如果文件中定义了堆为10,栈为30,则SP=40+10+30=80,余下[80,100]的空间不会被访问到,堆和栈共用[40,80]的空间。
裸机的堆栈按定义的执行,有系统后任务栈来在于堆区,从堆区中分配出TCB.
41、A % B 等同于 A & (B-1)
42、for(i=n; i<m; i++) 会循环m-n次,结束后i==m,共计执行1次i=n, m-n+1次i<m, m-n次i++。
While(a++ < b){} 退出循环前会再判断一次,故结束时a==b+1.把a++放{}内。
While(n--) 会循环n次,结束后n==-1,共计执行n+1次n--。
While(--n) 会循环 n-1次,结束后n==0,共计执行n次--n。
Do while(--n), 若n!=0,会循环n次,结束后n==0, 共计执行n次--n。若起始n==0,会多循环一次,共循环256^sizeof(n)次幂。
Do while(n--), 若n==0,只循环一次,执行一次n--,结束后n==-1;
若n!=0, 则循环n+1次,共计执行n+1次n--,结束后n==-1.
43、任何c写的工程,可以没有main.c文件,但必须要有main()函数;main()函数所在文件的路径为项目目录或工程目录,之后所有的.h的位置都是基于此路径的相对路径,用#include“包含”。除非用#include<包含>的标准库,其所在的路径是固定的,用户不需要关心,除非需要更换标准库路径,则需要明确指出库文件所在路径。
Eg:#include “../include/a.h” 引用工程目录上级目录里的include里的a.h
并不是只有标准头文件才可以使用<>, 自定义头文件也可以使用<>
如果你有一个非标准的头文件myheader.h
位于/my/custom/path
目录下,你可以这样编译你的程序: gcc -I/my/custom/path myprogram.c -o myprogram
这样在代码中,你仍然可以使用尖括号来包含这个头文件:#include <myheader.h>
44、char **argv == char *argv[]
45、 :data 全局初始化的 :bss 全局未初始化的 。 比如:
A:if( a == 1) 中的1属于text/code段,存于ROM中
B:const int a = 1; 中的1属于RO段,存于RAM中a变量所对应的内存地址中
C:int b = 2; 中的2属于RW段,存于RAM中b变量所对应的内存地址中
C:static int e = 0;中的0属于RW段,存于RAM中e变量所对应的内存地址中
D:int c, d = 0; c,d变量属于ZI段,断电不保存,通电后所对应的内存区全置为0
E:"hello" 字符串属于text/code段,存于ROM中
上电后从低地址开始按序存放B+C+D,所占用的总大小叫内存静态区,余下的RAM用于堆和栈
46、task=process 即进程,进程有进程控制块但没有进程堆栈,一个进程包含多个thread线程,每个线程对应自己的线程控制块和线程堆栈,每一个线程对应一个处理核心,即CPU被抽象为线程。
47、实时性的保证是通过在中断里释放唤醒某项线程的机制,然后再中断结束后立即执行调度。
48、(1)临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
(2)互斥量:为协调共同对一个共享资源的单独访问而设计的。如果有多个应用(多个进程)同时存取相应资源,则用互斥量。
(3)信号量:为控制一个具有有限数量用户资源而设计。如果一个应用同时可以有多个线程存取相应资源,则用信号量。信号量的初值表示不用释放就可以有的个数,之后释放就+1,获取就-1,减到0再获取就会被挂起。
(4)事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。如果某个线程必须等待某些事件发生后才能存取相应资源,则用事件。
50、一个队列只能发一种类型的数据,队列中的每个成员大小都是相同的。
51、之前的2个IC间用i2c通信,但是现在中间断开了,提供了一个USB入口的带ic1的板子,则ic2想再和ic1通信就需要把自己的i2c接口也包成USB接口,这样就可以通信了
由 Ic1 ----i2c ====== i2c ----- ic2 变为
Ic1 -- i2c ---usb ===== usb--- i2c ---ic2
52、*永远与前面的类型捆绑为整体,形参与定义处相比,在行参处少*补*,多*补&
少/多几个就补几个*/&。
Int num + fun(int a) -> (int)num + fun((int)a)–> fun(num)
Int *num + fun(int *a) -> (int *)num + fun((int *)a) -> fun(num)
Int num + fun(int *a) -> (int)num + fun((int *)a) -> fun(&num)
Int *num + fun(int a) -> (int *)num + fun((int)a) -> fun(*num)
Int **num + fun(int **a) -> (int **)num + fun((int **)a)-> fun(num)
Int **num + fun(int *a) -> (int **)num + fun((int *)a) -> fun(*num)
Int *num + fun(int **a) -> (int *)num + fun((int **)a) -> fun(&num)
Int num + fun(int **a) -> (int)num + fun((int **)a) -> fun(&(&a))
Int **num + fun(int a) -> (int **)num + fun((int)a) -> fun(*(*num))
变量名前带 * 表明这个变量是个地址(指针)变量,* 前面表示的是该指针变量的类型,指针变量的+-都是相对于 * 前面类型的偏移,即+-sizeof(类型);指针变量和普通的变量完全一样,可以对其取值,赋值和取址。变量内放的是变量的值,那么地址变量内放的就是一个地址值,这个地址值可以是任意一个变量、指针变量、结构体变量、数组成员、函数的地址。
53、在C语言中,局部变量的作用域只在函数内部,在函数返回后,局部变量的内存就会被释放。如果函数只是返回局部变量,那么这个局部变量会被复制一份传回被调用处。但是如果函数返回的是局部变量的地址,那么就会报错,因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放,这样指针指向的内容就是不可预料的内容,程序就会出错。准确的来说,函数不能通过返回指向栈内存的指针(返回指向堆内存的指针是可以的)。
54、小端模式为高字节在高地址; 大端模式为高字节在低地址。16进制数据串类似10进制,靠左为高位,靠右为低位。0x12345678 = 305419896
Eg: Union test
{
Unsigned int val;
Unsigned char arr[4];
Unsigned char ch;
}; unsigned patch[4]={0x12, 0x34, 0x56, 0x78};
不论大小端的cpu,在内存中patch[0]的物理地址值绝对要小于patch[4]的物理地址值,数组中数据的物理地址从上到小,从左到右依次增加。如果是个小端的cpu定义的共用体test在内存中数据分布为patch,则val=0x78563412,arr[0]=0x12,arr[1]=0x34,arr[2]=0x56,arr[3]=0x78, ch = 0x12 = val & 0xFF; 而如果是大端cpu则val=0x12345678, arr同上,ch=0x12 != val & 0xFF。共用体中u8数永远为共用体所占内存地址最小的数据。
56、提示非对齐操作时,往往是共用体的锅,此时弃用地址强转式的访问,改用字节数据的访问,逐个操作数组元素。
57、阶梯错位打印是没有\r导致的。Unix下的\n等同于\r\n,但win下不是。
58、用枚举实现可变的数组长度 enum {AA1, AA2} str[AA2].
59、全局变量容易被复用修改,所以需要互斥访问;但局部变量往往不会被复用修改,如果局部变量被复用修改,则意味着函数调用栈已被覆盖操作。
60、int a = 1; int b = a; a++; --- a != b ; if(1 == (a = 3)) 等同于 a = 3; if (1 == a)
61、extern引用了函数声明后仍提示错误,原因是函数所在文件未被编译。想要被别的文件extern引用函数,需要将该函数提前放在某个.h中声明下。
62、从调用方式上看,可以分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,也是我们在写程序中经常使用的;回调是一种双向的调用模式,也就是说,被调用的接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,解决了同步阻塞的问题,举例来讲:A通知B后,他们各走各的路,互不影响,不用像同步调用那样,A通知B后,非得等到B走完后,A才继续走(同步调用能保证执行的确定性)。回调函数通常不会直接调用,而是将函数首地址作为参数传给别人,对方将该地址保存起来,在需要的情况下再调用该地址指向的函数。和普通函数的区别在于普通函数的调用在编译时编译器就知道要调用的地址并生成相关直接调用语句。而回调函数则在运行时才能决定具体调用的是谁。编译器在编译时只知道某个变量中保存的某个地址是一个函数入口,它只管生成间接调用那个变量中地址的语句。
64、va_list详解
VA_LIST 是在C语言中解决变参问题的一组宏 ,需包含 #include <stdarg.h>,形参从右向左入栈,最左边的参数在栈底,最右边的在栈顶。
他有这么几个成员:
1) va_list型变量:
#ifdef _M_ALPHA
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
#else
typedef char * va_list;
#endif
2)_INTSIZEOF 宏,获取类型占用的空间长度,最小占用长度为int的整数倍(4n, n = 1, 2, 3, 4, …):
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 等同于
#define _INTSIZEOF(n) ( (sizeof(n) + 3) & b11111100)
如果sizeof(n)= [1,4],则_INTSIZEOF(n)=4;sizeof(n)= [5,8],则_INTSIZEOF(n)=8;
3)VA_START宏,获取可变参数列表的第一个可变参数的地址(ap是类型为va_list的指针,v是可变参数中最左边的参数),功能:以固定参数的地址为起点确定变参的内存起始地址,获取第一个变参的首地址,ap这个变量是指向变参地址的指针,得到变参的起始地址之后,再结合此变参的类型,就能得到此变参的值 :
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 等同于
#define va_start(ap,v) ( ap = (char *)&v + _INTSIZEOF(v) ),v可能存放在地址2000-2003,而char只占一个字节,因此(char *)&v取出的就是地址2000,再加上v占用的空间,此时ap=2004
4)VA_ARG宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(t参数描述了当前参数的类型)
功能:获取下一个变参的起始地址,并根据本变参的类型返回本变参的值 :
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 等同于
do{
ap = ap + _INTSIZEOF(t); //此时ap指向下一个参数的起始地址
int val = ap - _INTSIZEOF(t); //val 指向t本参数的起始地址,假设把t替换为int
#define va_arg(ap, int) ( *(int *)(val))
}while(0);
5)VA_END宏,清空va_list可变参数列表:
#define va_end(ap) ( ap = (va_list)0 )
VA_LIST的用法:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)最后用VA_END宏结束可变参数的获取。
使用VA_LIST应该注意的问题:
(1)可变参数的类型和个数完全由程序代码控制,它并不能智能地识别不同参数的个数和类型;
(2)如果我们不需要一一详解每个参数,只需要将可变列表拷贝至某个缓冲,可用vsprintf函数;
(3)因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码;
65、⊕此符号表示按位异或运算,即模2运算,由0/1构成的方波等同于正弦波,适用载波相位
66、c++的重构,一者可以把class内的函数单独定义为一个新的函数,二者把class当成一个大的结构体,内部函数全变成函数指针成员,在init 此calss时,彼此对应绑定。
67、二维数据中 *c[0] == **c == c[0][0]
68、10`h101 意:10位2进制数的16进制(h)表示值为0x101(共占用10位)
2`b01 意:2位2进制数的2进制(b)表示值为0b01(共占用2位)
69、printf中引用宏,需要给宏包一对双引号“MACRO”
70、uint32 和关键字int的区别,c标准中只定义了short占位不长于int,int占位不短于short,但具体short和int占多少位并未规定,因此你在代码中判断一个short变量和65536的比较时就会有歧义,而如果定义了uint16就会明确很多,不论什么架构的cpu,占16位的最大无符号数就是65535,就不会有歧义。uint32和int也同理。
71、带*与不带*都是变量,即内存中的一个小室,内部放的都是数据,带*的变量内部放的是地址数据,走地址总线,去地址数据对应的小室拿数据,而不带*的变量内部放的就是具体的数值,走数据总线获得其值。
72、const char a[] =“1.11”; //a是常量,其值恒等于“1.11”只占用ROM,rom中 “1.11”的存放地址就是a的值
const char * const b =“2.22”; //b是常量,值恒定不变;只占用ROM,rom中“2.22”的存放地址就是a的值
char c[] =“3.33”; //c 是变量,只是在定义时赋予了一个常量“3.33”的值,之后c的值可变,c占用data+rom
const char * d = “4.44” //d是变量,只是在定义时赋予了一个常量“4.44”的值,之后d的值可变,d占用data+rom; 烧录时会将“4.44”写到ROM中,同时main前的初始化会为d在data段中分配空间并将“4.44”写入。
29. uint32 和关键字int的区别,c标准中只定义了short占位不长于int,int占位不短于short,但具体short和int占多少位并未规定,因此你在代码中判断一个short变量和65536的比较时就会有歧义,而如果定义了uint16就会明确很多,不论什么架构的cpu,占16位的最大无符号数就是65535,就不会有歧义。uint32和int也同理。
30. 何谓串口,广义上是指串行通信的接口,比如uart,iic,spi,区别于并口;狭义上是指遵循1位起始位(tx默认高)+ 8位数据位(LSB)+ 1位校验位 + 1位停止位的通信方式的通信接口,而具体在逻辑电平的实现上又分为TTL串口,232串口。但是由于电脑一般没串口,只有usb口,故而有了串口转usb口的设备,比如usb转ttl,USB转232等
31. 单片机 其实就是小电脑,只不过单片机的外设(gpio,adc,dac,i2c,spi,时钟(可产生计时中断,或和gpio做pwm等,dma等等),最重要的它内含ram,flash,简单外设就可启动。电脑其实就是把单片机所集成的东西都独立化,cpu只管计算,北桥管内存等高速总线,南桥管gpio,usb,dma,i2c,spi等等,并有ics等芯片的时钟分发。树莓派介于两者之间,有内部外设接口但ram flash主要由树莓派芯片数据总线连接独立器件完成。但是现在这三者的边界不是很明显,都有跨界现象。系统这三个都能上只是硬件资源所限,系统规模不一。
32. 合理分配栈空间与堆空间;对于一些较小的空间可以在栈空间中分配,而非必须在堆空间中申请。只要ps后能保证任务栈空间的最大使用率小于80%即可。
33. *.c文件中的所有函数编译完之后都只有代码段和数据段(包含data段和bss段)两部分,代码运行时只会把数据段的内容移入RAM中,代码段依旧在ROM中,由指令总线取指。函数内定义的栈变量只有当函数执行/被调用时才会在栈中开辟空间,并在结束时释放。函数调用时主调函数首先把自身涉及的栈变量和寄存器压栈,之后将被调函数的栈变量拷贝并入栈,调用结束后出栈到调用发生前的状态。多个主调函数调用同一个被调时,会在每一个主调的栈内生成一份被调的栈拷贝,彼此互不影响。
34. 启动文件中定义的是系统栈,创建任务时定义的是任务栈,两者空间不重叠;由于 Cortex-M3 和 M4 内核具有双堆栈指针,MSP 主堆栈指针和 PSP 进程堆栈指针,或者叫任务堆栈指针。在 FreeRTOS 操作系统中,主堆栈指针 MSP 是给系统栈空间使用的,进程堆栈指针 PSP 是给任务栈使用的。 也就是说,在 FreeRTOS 任务中,所有栈空间的使用都是通过 PSP 指针进行指向的。 一旦进入了中断函数以及可能发生的中断嵌套都是用的 MSP 指针。
35. 连接寄存器LR用来存储函数调用时下一条指令的地址,即跳过此被调函数后原函数的下一条指令的地址;如果有多层调用,则每一层调用开始压栈时先把上次的LR压入栈;调用结束后逐层还原LR。
36. mask 即掩码也叫屏蔽码,是指保留想要的区段同时把不在意的区段屏蔽掉的码, 比如0x00F0就是用来检查[bit7-bit4]的屏蔽码。
37. 变量名前带 * 表明这个变量是个地址(指针)变量,* 前面表示的是该指针变量的类型,指针变量的+-都是相对于 * 前面类型的偏移,即+-sizeof(类型);指针变量和普通的变量完全一样,可以对其取值,赋值和取址。变量内放的是变量的值,那么地址变量内放的就是一个地址值,这个地址值可以是任意一个变量、指针变量、结构体变量、数组成员、函数的地址。
38. 函数调用时的参数由寄存器R0-R3传递,即将实参的值写入到R0-R3中,不占用栈空间
39. 局部栈变量定义时不要赋初值,因为=号右边的数值会存入ROM的常量区(不是data段)占用ROM空间;变量尽量使用bss段,因为data段的数据会在ROM和RAM中各占一份空间。定义时不赋值则只占用RAM,不占用ROM;用const声明后则只占用ROM,不占用RAM。
40. 数组不指定长度而只用 [] 时,则定义时赋值的个数就是数组的长度
41. 函数内部一定要用定义的局部变量去接传进来的形参值,之后都处理局部变量;尽量不要直接操作全局变量或全局的结构体变量中的成员,会有临界区的风险。
42. 所有的外设控制器的初始化都是固定的4步,RCC->NVIC->GPIO->UART,即先打开外设控制器的rcc,再配置外设控制器使用的gpio为外设的功能,再配置外设控制器,再打开外设控制器需要的中断,最后使能外设控制器,必须按此顺序操作,尤其最后一步必须放在最后;初始化的过程中init_type类型的变量一般用作局部变量用来初始化ppp_init,handle_type类型的变量一般作为全局实体,在内存中只保存一份,维护状态机的运行。
43. DMA的发送一般为单次模式,发送完毕后发送计数归0,发送buff自加到buff的尾部,下次发数时重置发送buff和len,开启dma即可发送;DMA的接收一般为循环模式,循环模式下接收计数归0(即触发传输完成标志)后,硬件会自动重装载接收计数值并重新指向接收buff的开头,完全由硬件控制,不需要在DMA_TCIT中重新设置。DMA的通道设置为循环模式后mem数据放满后会自动返回mem的起始地址继续放数。DMA中的传输不是单指发送的过程,而是接收和发送都叫传输的过程,因此接收通道和发送通道收/发数完成后都会分别触发自己通道的传输完成标志。
44. const *是个指向常量的指针,其本身是个变量可以用来指向不同的常量
维护两套结构体变量,一套物理外设的寄存器,一套外设状态机;物理外设本身就存在,所以必须定义外设状态机这个实体结构体变量,静态动态均可
45. 影子寄存器即重装载寄存器,计时器的更新分为直接更新(立即操作原始寄存器,当前周期值立马改变)和间接更新(立即操作影子寄存器,但必须当前周期完后才会按更新值执行)
46. char a = 0x41 ; char *b = a 则 b 等于 &a ,*b 等于 0x41;即对一个变量地址的读写等同于读写该变量,这里的变量指广义上的一片连续内存空间,可以是char int struct等
47. dma的发送长度减到0就不发了,不用发送完成中去关闭dma,dma的接受类似中断接受,只是少了cpu搬运的过程。
48. 每次启动DMA发送,重新配置DMA通道所有参数
https://baijiahao.baidu.com/s?id=1715820165842458402&wfr=spider&for=pc&searchword=dma收发数有什么触发
0. DMA接收不需要中断
2.DMA发送完毕触发的是DMA发送中断,DMA接收完成触发的是串口空闲中断,都会触发一个中断标志位,可以判断该flag位
49. printf中参数的执行是从右向左的,即先执行最右边参数值的获取和解析
50. 函数的参数会根据个数决定使用栈还是寄存器去存取,这就导致可变参的处理会不同!可变参数用第一个参数的sp值和最后一个参数压入后sp的值得差值除以4的到参数的个数
51. data段即rw段用来存放已初始化的全局变量和局部静态变量的初始值,之后变量值变了这里会同步跟着变,但变量的地址是固定的,同时在flash中变量值所占的空间和空间内的值会同步映射(copy)到sram中,这样data段即解决了全局变量的值又解决了在sram中占用的空间。秒
52. 符号和实际的存储地址是一一对应的
53. 一个段从a移动到b,a叫加载地址,b虚拟地址/运行时地址
链接文件的符号和c文件的符号的区别是:c文件的符号会占用内存的地址,并显示指出符号的值,未指出符号所在地址,而链接文件的符号不会占用内存地址,符号的值是内存的一个地址值,往符号写数就是往内存地址写数,链接文件的符号仅仅用来记录某个内存地址值
54. a想写一个n对齐 a=(a+3)&(~3)
55. 将短小简单的代码对应的汇编语言和其上下文的汇编整合(插入)在一起的过程叫内联,内联有可能会忽略push和pop的操作
56. svc指令允许用户态应用程序请求系统内核的服务,即系统调用syscall,一共256个
57. spi和iic属于同步通信,因此时钟可以不是方波,主机控制下的时钟翻转就会硬件触发从机的数据输出,因此io模拟的方式下只读取数据是不用发数据的,只要翻转clk即可。
58. 芯片的fifo可以理解为缓存区,就是把收发的数据都在fifo里攒着,等攒到了设置的阈值后,在一次性收发出去,而不用每一字节都收发一次。
59. 指针变量的宽度:
为啥32位机器下所有指针变量的sizeof值都是4,因为指针变量是用来寻址的,而32位机器的可寻址范围可以被一个32位数完整的覆盖,因此一个32位宽的数据就可以寻址32位机器的所有地址,所以地址用一个32位数来表示,所以sizeof指针的是(32除以4)。
60. unsigned int 与 size_t 的区别:
int 小于等于数据线宽度,size_t 大于等于地址线宽度。
size_t 存在的最大原因可能是因为:地址线宽度历史中经常都是大于数据线宽度的。在数据只有8位的年代,地址率先进入10位,12位,在数据16位的年代,地址也已经进入了20位,24位。
目前的 int 普遍是32位,而 size_t 在主流平台中都是64位。
size_t 为什么存在?因为无论 int 还是 unsigned int 都很可能小于 size_t 需要的大小,所以必须有个 size_t 。
61. 对于数组 a[10], a == &a == &a[0]:
以int a[10]为例, &a 与 &a[0]的不同点是:数组名取地址(&a)的类型为 数组指针(int(*)[10]) 类型;数组首元素取地址(&a[0])的数据类型为 整型指针(int *) 类型;
所以不能直接用于比较,想要比较是否为同一位置必须继续强制类型转换。
$(patsubst %.xdm, %, $(TEMP2)) : a.xdm -> a
$(patsubst %.xdm, %/, $(TEMP2)) : a.xdm -> a/
62. PWM: 假设PWM的时钟主频是 PWM_CLK_FREQ Hz,则如果需要输出频率为 xHz,占空比为 y% 的波形时,
则只需要在定时器的周期寄存器中写入(PWM_CLK_FREQ / x),在占空比寄存器中写入((PWM_CLK_FREQ / x)* y / 100u)即可。
Pwm_SetPeriodAndDuty(channel, period, duty); // period =(PWM_CLK_FREQ / x); duty = (PWM_CLK_FREQ / x)* y / 100u)(注意:占空比会因为除法导致数据不完整,丢失小数部分)
64. SMP(对称多处理器)就是多个处理器核心共享一块内存,由一个操作系统负责统一管理多个核心(而不是每个cpu上都跑一个os),多个任务运行时,由操作系统根据每个核心的空闲状态,决定某个任务应该运行在哪个CPU核心上,动态调度,实现负载均衡,使CPU使用效率最大化; AMP 则是各处理访问彼此独立的内存,各处理器上均运行各自独立的os。
65. printf 具有延时的作用:出现如下这种删掉第2行会使第4行val的值为0,而加上第2行则值正确的情形,可以尝试把第2行换成一个延时函数,以延长第1行的执行时间
1:val= fun();
2:printf("\n\t12212 > %d\n", val);
3:val = fun();
4:printf("\nValue> %d\n", val);
66. 在C语言中,(*p)++
和 *p++
是不同的。
-
(*p)++
表示先取指针p
指向的值,然后将这个值加1。这个操作是后置递增,即先读取*p
的值,然后将这个值加1,但p
本身指向的地址不变。 -
*p++
表示先取指针p
指向的值,然后将指针p
向前移动一个单位(通常是4个字节,取决于指针指向的数据类型)。这个操作是后置递增,即先读取*p
的值,然后将p
向前移动,但*p
的值本身不变。
举个例子,假设 p
是一个整数指针,指向一个整数变量 a
。
- 如果执行
(*p)++
,那么a
的值会加1,但p
仍然指向a
。 - 如果执行
*p++
,那么a
的值不变,但p
会指向a
后面的下一个整数位置。
因此,(*p)++
和 *p++
是不同的。
67. 在C语言中,""
和 NULL
是不同的。
-
""
是一个空字符串字面量。它是一个字符串常量,其内容是一个空的字符数组,以空字符'\0'
结尾。在内存中,它占用一个字节的空间,存储的是一个空字符'\0'
。空字符串的指针指向这个空字符。 -
NULL
是一个宏定义,通常定义为((void*)0)
或0
。它用来表示空指针,即一个不指向任何对象的指针值。NULL
用于初始化指针变量,表示该指针不指向任何有效的内存地址。
因此,""
和 NULL
是不同的概念:
""
是一个有效的字符串(尽管是空的),有一个确定的内存地址。NULL
表示一个无效的地址,用于指针的初始化或比较。
在使用时,""
可以用作字符串操作的参数,而 NULL
通常用于指针操作。