目录
一、引言
STM32 作为一款广泛应用的微控制器,其开发离不开 C 语言的支持。C 语言凭借其高效、灵活和可移植性,成为了嵌入式系统开发的首选语言。本文将对 STM32 开发中涉及的 C 语言知识点进行详细总结,帮助大家更好地掌握 STM32 的开发。
二、C 语言基础
1.数据类型
基本数据类型:包括整型(int
、short
、long
)、浮点型(float
、double
)、字符型(char
)等。
stdint.h中的类型:在STM32开发中,经常使用stdint.h头文件中的类型定义,如int8_t
、uint16_t
、int32_t
等,以确保数据类型的跨平台一致性。
派生数据类型:指针(*
)、数组([]
)、结构体(struct
)、共用体(union
)等。
示例:
int num = 10;
float pi = 3.14;
char ch = 'A';
int8_t=10;
struct Point {
int x;
int y;
};
union Data {
int i;
float f;
};
2.变量与常量
- 变量的定义和初始化:变量在使用前必须先定义,并可以在定义时进行初始化。
- 常量的定义:使用
#define
宏定义或const
关键字定义常量。
示例:
int a = 5; // 定义并初始化变量
#define PI 3.14 // 宏定义常量
const float E = 2.718; // 使用 const 定义常量
3.控制结构
- 顺序结构:程序按照语句的书写顺序依次执行。
- 选择结构:包括
if-else
语句和switch-case
语句。 - 循环结构:
for
循环、while
循环和do-while
循环。
示例:
int num = 10;
if (num > 5) {
printf("Num is greater than 5\n");
} else {
printf("Num is less than or equal to 5\n");
}
int choice = 2;
switch (choice) {
case 1:
printf("Choice is 1\n");
break;
case 2:
printf("Choice is 2\n");
break;
default:
printf("Invalid choice\n");
break;
}
for (int i = 0; i < 5; i++) {
printf("%d ", i);
}
4.数组与指针
- 数组的定义、初始化和访问:数组是一组相同类型元素的集合。
- 指针的概念和操作:指针是一个变量,其值为另一个变量的地址。
指针与数组的关系:
数组名在很多情况下会被当作指向数组首元素的指针来使用。例如,当将数组名传递给函数时,实际上传递的是一个指向数组首元素的指针。
通过指针的算术运算,可以实现类似数组下标的操作来访问数组元素。例如,如果有一个指针
p
指向一个数组的首元素,那么p + i
就指向了数组的第i
个元素。
示例:
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr; // 此时数组名相当于指向首元素的指针
printf("%d\n", arr[0]); // 通过数组下标访问
printf("%d\n", *(ptr + 0)); // 通过指针的算术运算访问
ptr = &arr[2]; // 指针可以改变指向
printf("%d\n", *ptr); // 输出 3
// 不能修改数组名的指向
// arr = &arr[1]; // 这是错误的
return 0;
}素
5.字符串
- 字符串的表示:使用字符数组或字符指针。
- 字符串操作函数:如
strcpy
、strcat
、strcmp
等。
示例:
char str1[] = "Hello";
char *str2 = "World";
strcpy(str1, str2); // 复制字符串
6. extern变量声明
extern
声明只是告诉编译器该变量在其他地方已经定义,并不为变量分配内存空间,通常用于在多个源文件之间共享全局变量。
示例:
假设有两个源文件 file1.c
和 file2.c
。
在 file1.c
中定义一个全局变量:
int global_variable = 10; // 定义并初始化全局变量
在 file2.c
中使用 extern
声明来访问这个全局变量:
extern int global_variable; // 声明该变量在其他文件中已定义
int main() {
printf("%d\n", global_variable); // 可以使用该全局变量
return 0;
}
这样,在 file2.c
中就可以通过 extern
声明来使用在 file1.c
中定义的全局变量 global_variable
。
需要注意的是,使用 extern
声明变量时,要确保在其他地方确实有该变量的定义,否则会导致链接错误。
7.内存管理
malloc
、memset
和 free
是三个常用的库函数,用于动态内存管理。
(1) malloc
函数:
malloc
函数用于在堆上动态分配内存。它的函数原型为:
void *malloc(size_t size);
size
参数指定要分配的字节数。malloc
函数返回一个指向分配的内存块的指针,如果分配失败则返回 NULL
。
示例:
int *ptr = (int *)malloc(sizeof(int) * 10); // 分配 10 个整数大小的内存
(2)memset
函数:
memset
函数用于将一段内存空间设置为指定的值。它的函数原型为:
void *memset(void *str, int c, size_t n);
str
是要设置的内存块的指针,c
是要设置的值(以 int
形式传递,实际设置时会转换为 unsigned char
类型),n
是要设置的字节数。
示例:
memset(ptr, 0, sizeof(int) * 10); // 将之前分配的内存初始化为 0
(3)free
函数:
free
函数用于释放之前由 malloc
等函数分配的内存。它的函数原型为:
void free(void *ptr);
ptr
是要释放的内存块的指针。
示例:
free(ptr); // 释放之前分配的内存
三、STM32 中的 C 语言特性
1.位操作
- 位域:用于定义结构体中的位变量。
- 位运算:与(
&
)、或(|
)、异或(^
)、取反(~
)、左移(<<
)、右移(>>
)。
示例:
struct Flags {
unsigned int flag1 : 1;
unsigned int flag2 : 1;
};
unsigned int num = 5;
num = num << 2; // 左移操作
2.寄存器操作
- 直接访问寄存器:通过指针或宏定义来访问寄存器地址。
- 寄存器位操作:使用位掩码和位运算进行寄存器位的设置和清除。
volatile
关键字用于修饰可能被意外修改的变量,数据每次从内存中直接读取,不会被编译器优化导致数据不同步的问题。
示例:
#define GPIOA_BASE (0x40020000UL)
#define GPIOA_MODER (*((volatile unsigned int *)(GPIOA_BASE + 0x00)))
GPIOA_MODER |= (1 << 10); // 设置 GPIOA 引脚 5 的模式