嵌入式C学习第一课

嵌入式C学习第一课

define 宏定义

Define是C语言中的预处理指令,用于宏定义可以提高源代码的可读性。常见的格式为:#define 标示符 字符串其中,“标识符”为所定义的宏名;“字符串”可以是常数、表达式和格式串等。
例如:
#define PLL_Q 7 //定义标识符PLL_Q的值为7

ifdef 条件编译

条件编译命令可以实现:当满足某条件时对一组语句编译,而条件不满足时编译另一组语句的功能。
常见的格式为:

#ifdef 标识符
     程序段1
#else
     程序段2
#endif

其功能为:当标识符已经被定义过,(一般是用#define命令定义),则对程序端1进行编译,否则编译程序段2。

位运算操作

嵌入式系统编程中常见的几个应用实例:

1、不改变其他位值,只对某几位设值

方法:先对需要设置的位使用与操作进行清零,然后使用或操作设置相应值。
GPIOA->BSRRL &=0XFF0F; //将寄存器的第4-7位清零
GPIOA-> BSRRL |=0X0040; //设置相应位,不改变其他位值

2、移位操作提高代码可读性和可重用性
GPIOx->ODR = (((uint32_t)0x01) << pinpos); //将ODR寄存器第pinpos位设置为1。

3、取反操作提高代码可读性
TIMx->SR = (uint16_t)~TIM_FLAG_Update;
其中TIM_FLAG_Update是通过宏定义的值:
#define TIM_FLAG_Update ((uint16_t)0x0001)
上述代码实际是将SR寄存器的第0位清零,其他位置1。之所以这样写,主要是为了提高代码的可读性。

extern声明变量
C语言中, extern可以放在变量或函数之前,以表示该变量或函数的定义在别的文件中,提示编译器在其他模块中寻找其定义。通常extern申明变量可以有多次,但是该变量的定义只能有一次。该申明的变量事实上就是全局变量。
例如:extern u16 USART_RX_STA; //声明的USART_RX_STA已在其他文件中定义

typedef 类型别名
typedef用于为现有类型创建一个新的名字,称为类型别名,可简化变量的定义。在嵌入式系统开发中, typedef常用于定义结构体别名和枚举类型了。
struct _GPIO

{

        __IO uint32_t MODER;

        __IO uint32_t OTYPER;

        ......
};

上述指令定义了一个结构体 _GPIO。

内存操作

 内存分配
 数据放哪里
栈空间:局部变量、函数形参、自动变量(调用后释放)
堆空间:malloc、realloc、calloc分配空间
数据段:.bss:保存未初始化的全局变量
.rodata:常量
.data(静态数据区):全局变量、static修饰的局部变量
 内存分配方式
(1)从全局数据区分配
(2)在栈上创建
(3)在堆上创建
 常见的内存错误
(1)内存分配未成功,却使用了它
(2)内存分配虽然成功,但是尚未初始化就引用它
(3)内存分配成功并已经初始化,但操作越过了内存的边界
(4)忘记了释放内存,造成了内存泄漏
(5) 释放了内存却继续使用它
 字节序和位序、位域
 比特序 / 位序

我们知道一个字节有8位,也就是8个比特位。从第0位到第7位共8位。比特序就是用来描述比特位在字节中的存放顺序的。
(1)比特序分为两种:LSB 0 位序和MSB 0 位序。
LSB是指 least significant bit,MSB是指 most significant bit。
LSB 0 位序是指:字节的第0位存放数据的least significant bit,即我们的数据的最低位存放在字节的第0位。
MSB 0 位序是指:字节的第0位存放数据的most significant bit,即我们的数据的最高位存放在字节的第0位。
所以说对于代码:char *ch = 0x96; // 0x96 = 1001 0110
在这里插入图片描述
指针ch到底指向哪里呢?不难知道,如果是LSB 0 位序则显然指针ch指向最右边的也是最低位的0.
而如果是MSB 0 位序则显然指针ch指向最左边的也是最高位的1.
(2)小端CPU通常采用的是LSB 0 位序,但是大端CPU却有可能采用LSB 0 位序也有可能采用的是MSB 0 位序
(3)推荐的标准是MSB 0 位序。

 数据指针
在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的 MOV 指令,而除 C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调试中,多借助 C 语言指针所具有的对绝对地址单元内容的读写能力。
以指针直接操作内存多发生在如下几种情况:
a) 某I/O 芯片被定位在CPU的存储空间而非I/O 空间,而且寄存器对应于某特定地址;
b) 两个 CPU 之间以双端口 RAM 通信,CPU 需要在双端口 RAM 的特定单元(称为 mail box)书写内容以在对方 CPU 产生中断;
c) 读取在 ROM或 FLASH 的特定单元所烧录的汉字和英文字模。
unsigned char *p = (unsigned char *)0xF000FF00;
*p=11;
以上程序的意义为在绝对地址0xF0000+0xFF00(80186使用16位段地址和16 位偏移地址)写入 11。
在使用绝对地址指针时,要注意指针自增自减操作的结果取决于指针指向的数据类别。上例中 p++后的结果是 p= 0xF000FF01,若p 指向 int,即:int *p = (int *)0xF000FF00;
p++(或++p)的结果等同于:p = p+sizeof(int),而 p-(或-p)的结果是 p = p-sizeof(int)。
同理,若执行:long int *p = (long int *)0xF000FF00;
则p++(或++p)的结果等同于:p = p+sizeof(long int) ,而p-(或-p)的结果是p = p-sizeof(long int)。
记住:CPU 以字节为单位编址,而 C 语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。

 函数指针
首先要理解以下三个问题:
a) C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;
b) 调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给 CPU的 PC寄存器;
c) 因为函数调用的本质是跳转到某一个地址单元的 code 去执行,所以可以"调用"一个根本就不存在的函数实体。

typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的函数指针类型*/ 
/* 定义一个函数指针,指向 CPU 启动后所执行第一条指令的位置*/ 
lpFunction lpReset = (lpFunction)0xF000FFF0; 
lpReset(); /* 调用函数 */

在以上的程序中,我们根本没有看到任何一个函数实体,但是我们却执行了这样的函数调用:lpReset()。

记住: 函数就是指令的集合;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!

关键字 volatile
volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。 volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
譬如如下代码:

int a,b,c; 
a = inWord(0x100); /*读取 I/O 空间 0x100 端口的内容存入 a 变量*/ 
b = a; 
a = inWord (0x100); /*再次读取 I/O 空间0x100端口的内容存入 a 变量*/ 
c = a;
很可能被编译器优化为:
int a,b,c; 
a = inWord(0x100); /*读取 I/O 空间 0x100 端口的内容存入 a 变量*/ 
b = a; 
c = a;

但是这样的优化结果可能导致错误,如果 I/O 空间 0x100 端口的内容在执行第一次读操作后被其它程序写入新值,则其实第2次读操作读出的内容与第一次不同,b和c 的值应该不同。在变量a的定义前加上 volatile 关键字可以防止编译器的类似优化。
volatile 变量可能用于如下几种情况:
• 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);
• 一个中断服务子程序中会访问到的非自动变量(也就是全局变量);
• 多线程应用中被几个任务共享的变量。

关键字 static
static在面向过程编程中的使用场景包括三种:

  1. 修饰函数体内的变量(局部)
  2. 修饰函数体外的变量(全局)
  3. 修饰函数
    第一种情况,static延长了局部变量的生命周期,static的局部变量,并不会随着函数的执行结束而被销毁,当它所在的函数被再次执行时,该静态局部变量会保留上次执行结束时的值。即改变了局部变量的存储域,从栈变为了全局(静态)存储区。
    对于后面的两种情况,static是对它修饰的对象进行了作用域限定,static修饰的函数以及函数外的变量,都是只能在当前的源文件中被访问,其它的文件不能直接访问。

 

参考文章:https://blog.csdn.net/zhzht19861011/article/details/45508029
                  https://blog.csdn.net/xylizj/article/details/93618620
感谢大佬!!!!!!!!

 

 

 

 


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值