文章目录


一、数据类型

C语言里有很多数据类型,有整型、浮点型、字符型、结构体、枚举类型等等。
在那么多数据类型中,整型和字符型用的更频繁一些,其中一个字符型占一个字节(8位),而一个整型占4个字节(32位)。根据数据范围,他们又分成好几种数据类型,下面列举51、stm32等单片机开发常用的几个字符型数据类型。

C语言的数据类型 stm32对应的数据类型 数值范围
signed char int8_t -128~+127
unsigned char uint8_t (u8) 0-255
signed char int16_t -32768~+32767
unsigned char uint16_t (u16) 0-65535
signed char int32_t -231~231-1
unsigned char uint32_t (u32) 0~232-1

很多情况下,字符型数据也够用了。

二、C语言关键字

1.static

static用来修饰变量或者函数,可以修饰全局变量或者局部变量。
static用来修饰函数的时候,只能在这个.c文件里调用。
static用来修饰变量的时候,只能在这个.c文件或者这个函数里调用。
比如下面比较典型的,在定时器中断服务函数里使用static 修饰变量Count,每次进入这个函数时Count 不会被重新赋值,是上一次累加的值,这样每进入一次中断就加1。因此static 可以做数据统计使用。

void TIM2_IRQHandler(void)   //TIM2中断服务函数		
{
	static u32 Count = 0; //Count初始值是0,往后都是上一次累加的值。
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) 	
	{
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update  );  		
		Count++;      //每进入一次中断就加1
		if(Count>=30)			
		{
			Beep_on();
		}
	
	}
}

2.const

const修饰的变量的值不可以被修改,只能读,不可写。
比如我们要读取某个传感器输出的数值或者gpio的电平时,也可以用

const u8 Re_val =GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2); //读取PA2引脚的电平

const修饰一般常量及数组

int const a = 100;
const int a = 100; //与上面等价
int const arr [3] = {1,2,3};
const int arr [3] = {1,2,3};//与上面等价

对于这些基本的数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。

const修饰指针(*),主要有以下几种类型:

char *p = "hello";     // 非const指针,
                       // 非const数据

const char *p = "hello";  // 非const指针,
                          // const数据

char * const p = "hello";   // const指针,
                            // 非const数据

const char * const p = "hello";  // const指针,
                                 // const数据

当为常量指针时,不可以通过修改所指向的变量的值,但是指针可以指向别的变量。

int a = 5;
const int *p =&a;
*p = 20;   //error  不可以通过修改所指向的变量的值

int b =20;
p = &b; //right  指针可以指向别的变量

当为指针常量时,指针常量的值不可以修改,就是不能指向别的变量,但是可以通过指针修改它所指向的变量的值。

int a = 5;
int *const p = &a;
*p = 20;     //right 可以修改所指向变量的值

int b = 10;
p = &b;      //error 不可以指向别的变量

3.extern

extern的作用:它通常的作用是用来修饰全局变量或者函数。被修饰的变量和函数在别的地方已经声明定义过,在另一个文件.c文件引用可以它的对应.h头文件引入,就用extern来实现。
例如在ui.c中定义了这个action_fig变量:

 //ui.c
u8 action_fig =0;

我们要在另一个eye.c使用到action_fig变量,就可以在eye.h文件下添加:

 //eye.c
 extern u8 action_fig;

为了代码的美观可读性,我们通常把extern修饰的语句放在ui.h或者eye.h文件里,这样只要其他.c或者.h包含了这个ui.h文件就可以直接使用了。

 //ui.h
 extern u8 action_fig;

还有另一个作用就是在C++文件中,对嵌入C语言的部分指定使用C编译器解析程序时,用使用:extern “C” 来指明。

4.volatile

Volatile关键字的主要有两个作用:

1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
2. 顺序一致性:禁止指令重排序。

5.typedef

作用是为一种数据类型定义一个新名字,这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。
比如前面stm32对应的数据类型里的uint8_t 其实就是unsigned char。

typedef unsigned char uint8_t   

6.enum

枚举类型,例如stm32的引脚速度配置:

typedef enum
{ 
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz, 
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

列举了三个变量,GPIO_Speed_10MHz 的值为1,GPIO_Speed_2MHz默认赋值为2,GPIO_Speed_50MHz默认赋值为3.

7.struct

结构体,就类似于一个数组,与数组不同的是,结构体中的元素可以是我们自定义的不同类型的变量,结构体元素可以是int,float,char等不同类型,更确切地来说,结构体就像是个大集合,这个集合中能够包含我们想要的任何元素。

typedef struct
{
  uint16_t GPIO_Pin;             
  GPIOSpeed_TypeDef GPIO_Speed;  
  GPIOMode_TypeDef GPIO_Mode;   
}GPIO_InitTypeDef;

这段代码包含了两步操作:首先是定义了一个结构体,其中包含了GPIO_Pin、GPIO_Speed、GPIO_Mode等不同类型成员,然后将本结构体取一个新的变量名为GPIO_InitTypeDef。
在使用的时候,先定义一个结构体变量,然后用成员运算符变量名.成员名

 GPIO_InitTypeDef  GPIO_InitStructure; //定义一个引脚初始化的结构体
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				      				
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		  		

这里提一下成员运算符 . 与指向运算符 -> 的区别
1、当使用成员运算符的时候,定义普通的变量,同样在C++中就是定义类的普通对象,然后就可以用成员运算符访问变量或者
对象的数据成员和成员函数。

   struct Student  
    {  
    int   a;  
    }stu1;//stu1是一个变量
       
   stu1.a; 		

使用时候可以直接进行访问 stu1.a;使用的就是成员运算符。
2、当使用指向运算符的时候,需要定义一个指针。->是一个整体,它是用于指向结构体、C++中的class等含有子数据的指针用来取子数据。换种说法,如果我们在C语言中定义了一个结构体,然后申明一个指针指向这个结构体,那么我们要用指针取出结构体中的数据,就要用到“->”.

  struct   Student  
    {  
    int   a;  
    };/*定义结构体*/
    
struct Student * p; /*定义结构体指针*/

       stu2->a;

三、指针

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。玩单片机底层的肯定要对它有一定了解,关于指针可以参考一下B站的一个视频,讲的通俗易懂:Bilibili_从计算机底层认识指针!深入理解C语言指针
他用了一个译码器(4输入16输出)做比喻,如下图:

&a 的运算结果是一个指针,指针的类型是a 的类型加个*, 指针所指向的类型是a 的类型,指针所指向的地址,也就是a 的地址。

p=0xf;对应上面的111B
*p;对应上面的变量a,就是第16个字节

也可以参考一下这篇文章:让你不再害怕指针——C指针详解(经典,非常详细)

四、条件编译

条件编译,直接看下面代码

#define out_fig 1
#if out_fig 
语句1();
#elseif
语句2();
#endif

这段代码的意思就是,如果out_fig 为真则编译执行语句1(),如果out_fig 为假则编译执行语句2()。现在我们用#define 宏定义out_fig的值为1,所以编译器会编译语句1()的内容,语句2()的内容就相当于被注释掉了。

总结

以上就是简单梳理一下嵌入式单片机C语言基础,包括了常用的数据类型、几个常见的关键字,指针,还有列举一个条件编译的例子。关于C语言的知识还有很多,这里主要是梳理嵌入式单片机C语言基础,顺便记录一下所学,希望能对你有帮助。