
2.1GPIO输出常见的四个GPIO输出函数分析1.GPIO_ResetBitsGPIO_ResetBits的作用是将指定引脚输出低电平而不影响同一端口上其他未指定引脚的状态void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);GPIOx指向 GPIO 端口的指针例如GPIOA、GPIOB、GPIOC等。该参数必须是一个有效的 GPIO 外设基地址。GPIO_Pin要设置低电平的引脚号可以是单个引脚如GPIO_Pin_5或多个引脚通过按位或运算组合如GPIO_Pin_5 | GPIO_Pin_6。每个引脚对应一个唯一的 16 位掩码例如GPIO_Pin_0为0x0001GPIO_Pin_5为0x0020。注意1.假设 LED 正极接 VCC负极通过限流电阻连接到 GPIO 引脚则低电平点亮 LED#include stm32f10x.h // Device header #include Delay.h int main(void) { RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO InitStructure.GPIO ModeGPIO Mode Out PP; GPIO InitStructure.GPIO PinGPIO Pin 0; GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init (GPIOA,GPIO_InitStructure); GPIO_ResetBits( GPIOA,GPIO_Pin_0); }2.同时置低多个引脚时GPIO_ResetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2); // 将 PA0、PA1、PA2 同时置低典型场景在中断中安全地置低、关闭设备2.GPIO_SetBits置位函数设置高电平将指定的一个或多个GPIO引脚输出高电平逻辑1。它与之前学习的GPIO_ResetBits是一对“镜像”操作void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);典型场景在中断中安全地置高、控制单个或多个灯3.GPIO_WriteBit位写入函数灵活的电平控制根据第三个参数BitVal的值有选择地将指定引脚设置为高电平或低电平。它本质上是GPIO_SetBits和GPIO_ResetBits的“二合一”封装void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);其中BitAction是一个枚举类型typedef enum { Bit_RESET 0, Bit_SET 1 } BitAction;典型场景需要根据变量动态决定高低电平、函数指针回调#include stm32f10x.h // Device header #include Delay.h int main(void) { RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO InitStructure.GPIO ModeGPIO Mode Out PP; GPIO InitStructure.GPIO PinGPIO Pin 0; GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init (GPIOA,GPIO_InitStructure); while (1) { GPIO_WriteBit( GPIOA,GPIO_Pin_0,Bit_RESET); Delay_ms (500) ; GPIO_WriteBit( GPIOA,GPIO_Pin_0,Bit_SET); Delay_ms (500) ; } }//呼吸灯4.GPIO_Write端口写入函数批量操作一次性将16位数据PortVal写入整个GPIO端口如PA0~PA15的输出数据寄存器ODR。这意味着它会同时改变端口上所有16个引脚的电平状态。void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);典型场景快速初始化端口、批量控制数据总线、流水灯#include stm32f10x.h // Device header #include Delay.h int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO InitStructure.GPIO Mode GPIO Mode Out PP; GPIO InitStructure.GPIO Pin GPIO Pin All; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); while (1) { GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001 Delay_ms(500); GPIO_Write(GPIOA, ~0x0002); //0000 0000 0000 0010 Delay_ms (500) ; GPIO_Write(GPIOA, ~0x0004); //0000 0000 0000 0100 Delay_ms(500) ; GPIO_Write(GPIOA, ~0x0008); //0000 0000 0000 1000 Delay_ms (500) ; GPIO_Write(GPIOA, ~0x0010); //0000 0000 0001 0000 Delay_ms (500) ; GPIO Write(GPIOA, ~0x0020); //0000 0000 0010 0000 Delay_ms (500) ; GPIO_Write(GPIOA, ~0x0040); //0000 0000 0100 0000 Delay_ms(500); GPIO_Write(GPIOA, ~0x0080); //0000 0000 1000 0000 Delay_ms(500) ; } }LED闪烁int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO InitStructure.GPIO Mode GPIO Mode Out PP; GPIO InitStructure.GPIO Pin GPIO Pin 12; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); while (1) { GPIO_ResetBits(GPIOB, GPIO_Pin_12); Delay_ms(500); GPIO_SetBits(GPIOB, GPIO_Pin_12); Delay_ms (500) ; } }2.2认识按键与传感器及学习C语言数据类型按键本质一种机械开关将用户的物理按压动作转换为电信号高电平或低电平。作用作为人机交互的输入单元用于触发特定功能、改变系统状态、输入数据等。常见类型轻触按键最常见手感清脆寿命长。自锁按键按下后锁定状态再次按下才弹起。薄膜按键常用于键盘、遥控器等成本低。微动开关常用于鼠标、限位开关等寿命极长触发力小。按键抖动由于按键内部使用的是机械式弹簧片来进行通断的所以在按下和松手的瞬间会伴随有一连串的抖动1. 硬件消抖物理层原理利用电容的充放电特性将抖动产生的毛刺脉冲平滑掉。电路在按键两端并联一个0.1uF (100nF)的电容电阻R上拉或下拉。2. 软件消抖逻辑层—— 最常用、最灵活原理通过延时或定时器避开抖动期只在稳定期读取按键状态。延时消抖延时20ms避开抖动期传感器模块1. 光学类传感器光敏传感器模块核心元件为光敏电阻用于检测环境光强常用于自动路灯、相机调光等场景。红外避障模块通过红外发射管和接收管检测前方障碍物发射红外线被障碍物反射后由接收管接收经比较电路输出信号。这是智能小车避障功能的经典方案。循迹传感器模块利用红外对管检测地面黑白线条的反射差异常用于循迹小车。2. 环境监测类传感器温湿度传感器如DHT11/DHT22单总线数字输出直接提供温度和湿度数据无需ADC转换非常适合STM32快速读取。烟雾/气体传感器如MQ-2检测可燃气体、烟雾浓度输出模拟量需配合ADC使用。BMP180气压传感器通过I2C接口输出气压和温度数据常用于海拔测量和天气预报。3. 运动与姿态类传感器MPU6050六轴陀螺仪加速度计通过I2C接口输出三轴加速度和三轴角速度数据是无人机、平衡车、姿态检测项目的核心传感器。超声波测距模块如HC-SR04通过声波往返时间计算距离STM32通过定时器捕获脉冲宽度即可完成测距。4. 开关与触控类传感器触摸开关模块基于电容感应原理无需物理按键即可检测触摸动作没有机械抖动问题。倾斜开关/振动传感器检测设备倾斜或振动状态输出开关量常用于防盗报警、姿态检测等场景。整数类型总结define——“先定义后替换”define 就是给某个值或某段代码取一个“外号”然后在代码里用这个“外号”代替原来的内容程序真正编译时所有“外号”都会被还原成原来的内容。什么是宏定义宏定义是C语言中一种预处理指令它允许程序员用一个简短的“名字”来代表一段代码、常量或表达式。在程序编译之前预处理器会自动将所有出现这个“名字”的地方替换成它代表的内容。用通俗的话说宏定义就像给一段代码或一个数值取了一个“外号”。比如你给圆周率3.1415926取个外号叫“PI”以后在代码中只要写PI编译器就会自动把它替换成3.1415926。define的两种核心用法1. 无参宏定义给常量取个“外号”#define 外号 真实内容2. 带参宏定义像函数一样工作但更“暴力”带参宏让define变得更强大了它看起来像一个函数但实际上只是“文本替换”的升级版#define 宏名(参数列表) 替换文本例如:#define SQUARE(x) ((x) * (x)) #define MAX(a, b) ((a) (b) ? (a) : (b))使用SQUARE(5)会被替换为((5) * (5))结果为25MAX(3, 5)会被替换为((3) (5) ? (3) : (5))结果为5。重要提醒类函数宏的参数和整个表达式必须用括号包围否则可能因运算符优先级导致意想不到的错误。typedef核心作用给已有的数据类型起一个新的名字别名在代码里int是本名你可以用typedef给它起个外号叫myint以后用myint定义变量就相当于用int定义。重要区别typedef并不创建新的类型它只是为现有类型添加一个同义字。就像“三哥”和“张三”是同一个人只是叫法不同。先写出变量声明int a;用新类型名替换变量名int myint_t;把a换成myint_t在开头加上 typedeftypedef int myint_t;搞定myint_t就是int的新别名。对比项typedef#define本质类型别名编译器处理文本替换预处理器处理处理时机编译时处理预处理阶段编译前类型检查有类型检查无类型检查只是机械替换指针场景安全符合预期容易出错C语言结构体——struct结构体是C语言中用于将不同类型的数据打包成一个整体的复合数据类型。它的核心思想是把逻辑上相关但类型不同的数据组合在一起作为一个整体来管理。用生活中的例子来理解你想记录一个学生的信息需要姓名字符串、年龄整数、成绩浮点数——这三个数据类型不同但逻辑上属于同一个学生。结构体就是用来“打包”这些相关数据的工具。方式一直接定义结构体变量结构体的定义方式方式一定义结构体类型struct Point { char x; int y; float z; }; struct Point p1, p2; // 定义两个Point类型的变量方式二配合typedef使用typedef struct { char x; int y; float z; } Point;结构体成员的访问1. 通过变量名访问点运算符.Point p1; // 定义结构体变量 p1.x A; // 访问成员x p1.y 66; // 访问成员y p1.z 1.23; // 访问成员z适用场景当你有一个直接的结构体变量时使用。2. 通过指针访问箭头运算符-Point *pPtr p1; // 定义结构体指针指向p1 pPtr-x A; // 等价于 (*pPtr).x A pPtr-y 66; pPtr-z 1.23;适用场景当你有一个指向结构体的指针时使用这在嵌入式开发中非常常见函数参数传递结构体时通常传指针避免拷贝整个结构体节省内存和时间链表、树等数据结构中节点之间通过指针连接访问硬件寄存器结构体如GPIO、TIM等外设寄存器注意pPtr-x完全等价于(*pPtr).x但箭头写法更直观、更常用。总结结构体 不同类型数据的打包器定义结构体类型时推荐用typedef配合使用简化类型名访问成员有两种方式变量名用.点运算符指针用-箭头运算符结构体占用的内存不等于各成员大小之和——因为存在内存对齐这是你下一步需要深入理解的关键点枚举——enum想象一下你一周有七天星期一、星期二……星期日。如果你在程序里用数字0代表星期一1代表星期二……那代码里到处都是if (day 0)别人根本看不懂“0”是什么意思。枚举Enum就是专门解决这个问题的——它允许你给这些数字起个有意义的名字比如Monday、Tuesday让代码像人话一样好懂。枚举是一个“选择题”它的值只能从你列出的选项里选一个不能乱填。比如你定义了一个“颜色”枚举里面只有红、绿、蓝那变量就只能取这三个值不能是“黄色”。enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };这里enum Weekday就是一个新的数据类型MONDAY等是它的“成员”。默认情况下MONDAY的值是0TUESDAY是1依次递增。枚举的用法1.声明变量并赋值enum Weekday today MONDAY; // 今天星期一你不能写today 8因为枚举里没有第8个选项编译器会报错——这就叫类型安全防止你乱写数字。2.配合 switch 语句使用switch (today) { case MONDAY: printf(开工了); break; case FRIDAY: printf(周末在望); break; case SUNDAY: printf(休息日); break; default: printf(普通工作日); }typedef与enum的区别:enum负责定义内容它创建了一组具名的整数常量如 0 和 1并给这组常量起个“家族名”Tag。typedef负责定义别名它为这个“家族名”创建一个更短、更好用的新类型名称让你以后声明变量时不用每次都写enum关键字。一句话理解enum造出了“砖块”数据值和类型规则typedef给这堆砖块贴上了一个方便的“标签”类型名让你搬砖声明变量时更轻松。