【嵌入式学习】嵌入式-C语言基础(一)

发布时间:2026/9/15 19:00:25
【嵌入式学习】嵌入式-C语言基础(一)
本文主要介绍了嵌入式中需要掌握的C语言内容。一. stdint.h 的简介stdint.h是从C99中引入的一个标准C库文件在电脑中的位置为文件中的内容就是把signed char 定义为int8_t这样可以使得代码更加直观其中_t 代表这个数据是一个typedef 的数据类型/* exact-width signed integer types */typedefsignedcharint8_t;typedefsignedshortintint16_t;typedefsignedintint32_t;typedefsigned__INT64int64_t;/* exact-width unsigned integer types */typedefunsignedcharuint8_t;typedefunsignedshortintuint16_t;typedefunsignedintuint32_t;typedefunsigned__INT64uint64_t;二. 位操作C语言中主要有如下的位操作符运算符含义运算符含义按位与~按位取反|按位或左移^按位异或右移按位异或表示当对应位不同时为1反之为0.其中这里只着重讲一下移位符1.有符号数移位最高位为符号位其中0为正数1为负数。C 标准对有符号数移位有很多未定义行为UB工程里尽量避免2.无符号数移位:溢出的丢弃空位补零。x00001010;x100010100;x100000101;3. 给寄存器某个位赋值对于uint32_t temp 0方法一按位或temp|0x000000040方法二按位或-移位操作表示数temp|16三. 宏定义1. 宏定义格式宏定义 提高效率可读性易改性核心是简单粗暴的替换。宏定义后面不需要加;#define 标识符 字符串标识符宏定义的名字字符串常数表达式格式串等等宏定义在预处理过程已经处理。2. 无参的宏定义// 易改性#definePI4.14159// 可读性#defineHSE_VALUE80000003. 有参的宏定义//#defineHAL_Toggle_Pin()LED_T()// 循环条件永远为假这个循环只会执行 1 次执行完直接退出不是循环// 使用do{}while(0)可以避免受到分号优先级的影响。#defineLED1(x)do{x?HAL_GPIO_WritePin(LED1_GPIO_PORT,LED1_GPIO_PIN,GPIO_PIN_SET):HAL_GPIO_WritePin(LED1_GPIO_PORT,LED1_GPIO_PIN,GPIO_PIN_RESET);}while(0)四. 条件编译1. 定义让编译器只对满足条件的代码进行编译不满足条件的不编译。指令作用#if编译预处理条件指令类似if#ifdef判断某个宏是否已被定义#ifndef判断某个宏是否未被定义#elif若前面的条件不满足则判定新的条件类似else if#else若前面的条件不满足则执行后面的语句类似else#endif#if#ifdef#ifndef的结束标志主要是为了防止头文件被多次引用。条件编译是预处理阶段生效不是程序运行时判断。不满足条件的代码会直接被删掉不会参与编译2. 举例说明1. #if 示例预处理时VERSION2只会保留printf(版本2代码\n);其余代码直接删除。#defineVERSION2#ifVERSION1printf(版本1代码\n);#elifVERSION2printf(版本2代码\n);#elseprintf(未知版本\n);#endif2. #ifdef 示例发布版本关闭调试打印不用一行行删代码。#defineDEBUG// 定义DEBUG宏#ifdefDEBUG// 只有定义DEBUG这段代码才会被编译printf(调试当前LED状态\r\n);#endif3. #ifndef 示例防止同一个头文件被多次重复包含造成重复定义报错。第一次包含__LED_H未定义 → 执行里面代码并定义宏第二次再包含__LED_H已经存在 → 直接跳过中间全部代码。#ifndef__LED_H// 如果 __LED_H 没有被定义#define__LED_H// 立刻定义 __LED_H// 头文件内容函数声明、宏定义voidLED_Init(void);#endif// 结束4. 官方实例只编译对应板子的代码实现一份源码适配多个硬件。#defineBOARDSTM32F103#ifBOARDSTM32F407printf(F4芯片代码);#elifBOARDSTM32F103printf(F1芯片代码);#elseprintf(不支持的板子);#endif五. extern声明1.作用作用跨 C 文件共享全局变量 / 全局函数放在函数/变量前表示此函数/变量在其他文件定义。告诉编译器要到其他文件寻找这个变量或者函数。extern是「声明」不是「定义」。告诉编译器这个变量 / 函数在别的.c文件里面已经定义好了本文件只是拿来用。可以多处声明.2. 举例说明文件 1usart.c定义唯一本体#includestm32f1xx_hal.h// 【定义】分配内存全局变量本体在这里uint16_tg_usart_rx_sta0;文件 2main.c使用文件extern 声明#includestm32f1xx_hal.h// extern 声明告诉编译器这个变量在其他文件externuint16_tg_usart_rx_sta;intmain(void){// 直接使用usart.c里定义的全局变量if(g_usart_rx_sta1){// 收到串口数据}在这里插入代码片while(1);}3. 易错点不要在头文件里写 extern 全局变量定义头文件如果写uint16_t g_usart_rx_sta;多个文件 include 会造成重复定义报错。函数默认自带 externextern只在编译阶段起作用链接阶段才去找到变量 / 函数本体。找不到就报链接错误undefined reference六. 类型别名typedef1. 定义为现有的数据类型创建一个新的名字又称为类型别名。用来简化变量的定义。typedef 现有类型 新名字2. 举例说明1. 基础类型别名typedefsignedcharint8_t;typedefsignedshortintint16_t;typedefsignedintint32_t;typedefsigned__INT64int64_t;2. 结构体typedef-简化结构体不使用typedef每次都要写structstructGPIO_CONFIG{uint16_tpin;uint8_tmode;};// 使用时必须带structstructGPIO_CONFIGgpio1;使用typedef 简化typedefstruct{uint16_tpin;uint8_tmode;}GPIO_CONFIG;// 直接写不用加structGPIO_CONFIG gpio1;3. typedef和define的区别编译器检查你代码里变量的类型匹不匹配类型不对就警告 / 报错防止类型混用带来 bug这个过程就是类型检查。typedef编译器处理是类型别名有类型检查#define预处理直接文本替换单纯字符串替换无类型检查七. 结构体1. 定义由若干基本数据类型集合组合而成的一种自定义数据类型。包括char、float、int等等结构struct 结构体名{成员列表}结构体变量名列表可选2. 举例说明1. 基础写法// 定义结构体类型学生structStudent{charname[20];// 姓名intage;// 年龄floatscore;// 分数};// 用结构体类型定义结构体变量structStudentstu1;2. 在结构体定义末尾直接创建变量structStudent{charname[20];intage;floatscore;}stu1,stu2;// 直接定义stu1、stu2两个结构体变量3. 搭配 typedeftypedefstruct{uint16_tpin;uint8_tmode;uint8_tspeed;}GPIO_TypeDef;GPIO_TypeDef gpioA;gpioA.pinGPIO_PIN_0;4. ST源码typedefstruct{uint32_tPin;/* 引脚号 */uint32_tMode;/* 工作模式 */uint32_tPull;/* 上下拉 */uint32_tSpeed;/* IO速度 */}GPIO_InitTypeDef;GPIO_InitTypeDef GPIO_InitStruct{0};GPIO_InitStruct.PinGPIO_PIN_0;GPIO_InitStruct.ModeGPIO_MODE_OUTPUT_PP;GPIO_InitStruct.PullGPIO_NOPULL;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA,GPIO_InitStruct);八. 指针1. 定义指针就是内存的地址。指针变量 就是保存了指针的变量。语法模板类型名 * 指针变量名;2. 举例说明char *定义一个字符型指针变量p_str这个指针变量保存字符串首字符的内存地址This is a test!字符串常量放在内存中编译器把它首地址赋值给 p_strchar*p_strThis is a test!;两个符号的说明变量拿到变量的地址-指针变量p_str的地址*指针拿到指针所指向地址里的数据-也就是T3. 练习题这里有个前提p_buff[i]*(p_buffi)// 定义uint8_t类型数组buf一共5个元素buf[0]1buf[1]3buf[2]5buf[3]7buf[4]9uint8_tbuf[5]{1,3,5,7,9};// 定义指针p_buf把数组buf的首地址赋值给p_buf此时p_buf指向buf[0]元素1uint8_t*p_bufbuf;*p_buf1;// *是解引用取出p_buf指向地址里面的值。p_buf指向buf[0]值为1p_buf[0]1;// p_buf[0]等价于*(p_buf0)代表指针当前位置第0个元素也就是buf[0]p_buf[1]3;// p_buf[1]等价于*(p_buf1)指针向后偏移1个uint8_t取buf[1]的值3p_buf;// 指针自增p_buf p_buf 1向后移动1个uint8_t现在p_buf指向buf[1]元素3*p_buf3;// 现在p_buf指向buf[1]解引用取出里面的值等于3p_buf[0]3;// 指针已经移动到buf[1]p_buf[0]就是当前指针位置的元素即buf[1]34. 指针使用过程中的两大问题1.未分配内存使用uint8_t*p;// 只定义了指针变量p没有给p赋值有效地址p里面是随机垃圾地址*p10;// 错误未分配内存直接向随机地址写数据野指针正确方法uint8_tval;uint8_t*pval;// 指针指向已经存在的变量val内存有效*p10;// 安全2. 越界使用uint8_tbuf[5]{1,3,5,7,9};//下标0,1,2,3,4 一共5个元素uint8_t*p_bufbuf;p_bufp_buf5;//指针向后偏移5此时指向buf[5]已经超出数组范围*p_buf99;// 越界buf只有0~4buf[5]不属于数组内存