嵌入式下的c语言(一)

为什么是C

> 实际上现在科技这么发达,板子的资源往往是有溢出的,使得C的优势名不是那么的明显, 就比如直接使用MicroPython进行开发,但毫无疑问这类型的开发环境体现出高度的封装和集成性, 导致出现问题时往往一脸懵逼,并且在追求效率的情况下开发底层驱动C语言操作底层的性能无法代替(当然不讨论智能硬件),况且用还是有助于我们学习底层的,所以还是从c开始吧(基于库函数)。

我就是想水1000字领个黑洞

必要的C:

想要操作寄存器直接写代码是这样的:

*unsigned int*)(0x40010c0c) = 0xffff //GPIOB_ODR置位/地址一定是无符号的

显然可读性不强。我们需要达到以下效果:

GPIOB->ODR = 0Xffff

涉及到gcc , 宏等

c语言的编译过程:

预处理

将一些宏定义转换成具体的操作或数;linux shell:

cpp -o *.i(目标文件) *.c

gcc -E -o *.i *.c

编译

将.i文件转换为.s文件;

gcc -S -o *.s(汇编语言) *.c

汇编

讲.s 文件转换为.o文件

gcc -c -o *.o(机器码) *.c

链接

直接链接除.o文件之外还有一些库文件链接成项目 build;

gcc -o build(unix可执行文件) *c

所以从道理上讲,宏定义 define/include/ifdef 等等不算关键字,在预处理阶段就被转换为具体的操作了,完全可以:

int define;

基本语句

常用的宏有:

#include "stm32f10x_xx.h"	//包含头文件
#define ABC *(unsign int*)0x01234	//替换
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE) //带括号保护代码
#ifdef
#elif
#endif//常用于代码调试

预定义的宏:

A = __FUNCTION__  //函数名 %s
B = __LINE__	//行数 %d
C = __FILE__	//文件名  %s
#define ABC(x) #x 	//字符串化
#define ABC(x) day##x  //连接字符(宏名中可以含有变量)

而实现GPIOB->ODR这种代码,如下:

#define __IO volatile
/*关键字volatile,cpu一般认为没有cpu命令介入时,寄存器数值时不变的,关键字volatile表示寄存器的值是易变的(会被外设修改),严格要求编译器每次访问时都扫描其中数值,拒绝从某个缓存直接调用数值(优化)。*/
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
//GPIO结构体的定义
typedef struct{
    __IO unit32_t CRL;
    __IO unit32_t CRH;
    __IO unit32_t IDR;
    __IO unit32_t ODR;    
    __IO unit32_t BSRR;
    __IO unit32_t BRR;
    __IO unit32_t LCKR;
    
}GPIO_TypeDef;
//强制类型转换,让GPIO的基地址指向该结构体之后就可以继续操作了。
#define GPIOA		((GPIO_TypeDef *)GPIOA_BASE) 
GPIOA ->ODR |=(1<<3);
//只修改位的操作;置位 OR 1;复位 AND 0;
//| = ; & = ~

但是,此时存在的移位操作还是难懂,所以我们再定义位操作函数(用brr,bsrr来操作odr以达到控制输出的目的):

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)//宏 GPIO_Pin_0-15 X是变量(省略GPIO_PIN的宏定义)
{
    GPIOx->BRR = GPIO_pin;//令对应引脚的brr置1,清0输出低电平。
}
void GPIO_SetBits (GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    GPIOx ->BSRR =GPIO_Pin;//bsrr 0
}

 在这里基本上就完成了对寄存器的一系列操作,初学底层开发时对这些代码依旧一知半解,但其实这其中实有紧密的联系。stm32的cortex-m系列内核能一次性处理32位数据,int在此编译环境下是32位,所以定义了uint32_t 和 uint16_t (short);GPIO属于外设,在block 2映射区间,被挂载到APB2总线上,所以将GPIOA_BASE通过总线偏移量定义;而他内部的寄存器由struct的语法规则依次定义,每个寄存器占用32位存储空间,CRL / CRH寄存器,每四位定义一个引脚的输入/输出方式和速度,共64位,定义了16个引脚。ODR,BSRR, BRR 寄存器都是一位对应操作一个引脚的值,只占用16位,所以可以直接对应GPIO_PIN_x去配置输出值。这就是操作基本输入输出的原理了。

 但同样,这样的操作依旧需要去读手册(如配置CRL/CRH输出模式的时候),所以stm32的固件库为我们提供了重要的底层驱动开发的思想,使用enum函数去限定(枚举)芯片的功能。(芯片的功能往往有限,enum的限制作用使得直接设置芯片的功能成为可能)。

typedef enum
 {
 GPIO_Speed_10MHz = 1, // 10MHZ (01)b
 GPIO_Speed_2MHz, // 2MHZ (10)b
 GPIO_Speed_50MHz // 50MHZ (11)b
 } GPIOSpeed_TypeDef;
//GPIO 工作模式枚举定义
typedef enum
 {
 GPIO_Mode_AIN = 0x0, // 模拟输入 (0000 0000)b
 GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 (0000 0100)b
 GPIO_Mode_IPD = 0x28, // 下拉输入 (0010 1000)b
 GPIO_Mode_IPU = 0x48, // 上拉输入 (0100 1000)b
 GPIO_Mode_Out_OD = 0x14, // 开漏输出 (0001 0100)b
 GPIO_Mode_Out_PP = 0x10, // 推挽输出 (0001 0000)b
 GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 (0001 1100)b
 GPIO_Mode_AF_PP = 0x18 // 复用推挽输出 (0001 1000)b
 } GPIOMode_TypeDef;

 这样子我们可以直接调用这些值来做控制输出速度和输出模式。

 这就是在库函数中所常见的C语句及其思想了。enum关键字在C中实际上是一个存在感很低的东西,基本上我们可以去用结构体之类的去代替enum,但是enum和struct最大的区别就在于enum只占用4个字节,而struct在如此多数据定义之后占用的空间会明显大得多。enum表示我们可以取这些值,并不是所有值都存在,当我们

enum a {A,B,C,…}

printf(’%lu’,size of(a))

 其输出结果总是4,因此效率和可读性都有了很大的提高。

 在底层驱动开发中enum出现的频率更是频繁,像:

 实际上就是命令行工具的本质体现。

 其他的语法或者什么关键的东西以后见到在补充吧,掌握这些基本上就可以读懂标准库了。

你可能感兴趣的:(嵌入式)