news 2026/4/25 23:06:51

STM32学习江协科技(持续更新)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32学习江协科技(持续更新)

一、介绍

二、新建工程

2.1添加文件

新建工程需要

Start文件中(一共六个):

启动文件地址:C:\Users\亲爱的玩家\Desktop\STM32F10x_StdPeriph_Lib_V3.5.0

其他描述文件地址:C:\Users\亲爱的玩家\Desktop\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries

library库文件:

uesr文件:

最终如图。红色为必要文件,蓝色是封装库

2.2必要步骤

点击魔法棒,点击C/C++。第一个框填写一模一样,第二个添加路径(头文件路径)

烧录器类型

点击setting,选择port

点击flashdownload,烧录之后自动运行

配置完成

2.3硬件连接

如图形成闭合回路

2.4.keilkill

如下工具可以删除编译数据,减少内存

三、实战案例(1)

3.1LED闪烁、LED流水灯、蜂鸣器

3.1.1

接线图

代码

3.1.2

接线图

代码

红框为GPIOA的二进制开口

3.1.3

接线

代码

3.2按键控制、光敏传感器控制蜂鸣器

3.2.1按键控制

接线图

代码(主函数和封装)

建立封装函数,一定建立 .C 和 .H 两个

3.2.1.1 LED

3.2.1.2 Key

3.2.1.3 Buzzer

3.2.1.4 Light Sensor

3.2.2光敏传感器控制蜂鸣器

3.3OLED

3.3.1硬件

3.3.2软件

OLED封装函数需要自己添加

3.4对射式红外传感器、旋转编码传感器

外部中断结构

3.4.1对射式红外

3.4.1.2接线、程序

封装函数

主函数

3.4.1.2调试、是否进入中断

点击进入调试模式

先在58行打断点,然后点2,全速运行

然后给传感器遮光,进入中断,如图,断点进入if语句

3.4.2旋转编码

3.4.2.1接线

3.4.2.2原理

机械式 EC11(最常用),内部结构:

  • 公共端 C(GND)
  • A 相触点 → 接 PB0
  • B 相触点 → 接 PB1
  • 内部有两个弹片,旋转时依次接通 / 断开 GND。

静止时(你设了上拉输入):

  • PB0(A)=高电平 1
  • PB1(B)=高电平 1

旋转时:

  • 弹片碰到 GND → 引脚变低(0)
  • 离开 → 弹回高(1)

旋转编码器不管正转、反转,两个中断(EXTI0 + EXTI1)都会触发!都会触发!都会触发!

  • 顺时针转一格:EXTI0 触发 → EXTI1 触发
  • 逆时针转一格:EXTI1 触发 → EXTI0 触发

两个中断都会进!

只是先后顺序不一样

3.4.2.3封装函数

函数运行原理:

1、旋转编码器→AB都产生下降沿→触发中断

  • 圆盘转动
  • A 弹片先碰到导电区 → PB0 变低(下降沿)
  • 再转一点点
  • B 弹片也碰到导电区 → PB1 变低(下降沿)

2、if语句判断旋转方向(根据GPIO0或GPIO1的高低电平)→进行count加减

#include "stm32f10x.h" // Device header uint16_t Encoder_Count; void Encoder_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 ; //GPIOB 0&1 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStruct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStructure); } uint16_t Encoder_Get(void) { int16_t Temp; Temp = Encoder_Count; Encoder_Count = 0; return Temp; //return Encoder_Count; } //只要编码器旋转,EXTI0、EXTI1 都会进入。 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) == SET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) // 判断方向 { Encoder_Count --; } EXTI_ClearITPendingBit(EXTI_Line0); } } void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) == SET) // { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) // 判断方向 { Encoder_Count ++; } EXTI_ClearITPendingBit(EXTI_Line1); } }

这里面temp ,每转一次,temp变成1,主程序中,把每个1都加起来

3.4.2.4主函数

OLED显示count数

3.4.2.5 tips
uint16_t Encoder_Get(void) { int16_t Temp; // 定义临时变量 Temp = Encoder_Count; // 把当前计数值存起来 Encoder_Count = 0; // 清零,准备下一次计数 return Temp; // 返回刚刚存的值 }
  • 你刚想读取Encoder_Count
  • 突然中断触发了
  • 中断里把Encoder_Count++
  • 你原来的值就变了!

这叫 **“数据被打断”**,会导致数字乱跳。

************************旋钮按下功能(按下num清零)*****************************

第一步:接线

编码器模块上方两个引脚(VCC、GND)是按键:

  • VCC → 接单片机 PA2(随便选一个 GPIO,我选 PA2 举例)
  • GND → 接单片机 GND

第二步:代码

只加了按键初始化 + 按键检测 + 清零功能其他完全不动!

1. 新增头文件 & 全局变量

#include "stm32f10x.h" #include "OLED.h" #include "Encoder.h" // 新增:按键引脚定义 #define KEY_Pin GPIO_Pin_2 #define KEY_GPIO_Port GPIOA

2. 按键初始化函数(加到 Encoder_Init 里面)

void Encoder_Init(void) { // 原来的编码器初始化代码…… // …… // --------------------- 新增:按键初始化 --------------------- GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = KEY_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(KEY_GPIO_Port, &GPIO_InitStructure); }

3. 主函数 while (1) 里加按键检测

int main(void) { OLED_Init(); Encoder_Init(); OLED_ShowString(1, 1, "Num:"); int16_t Num = 0; // 你的计数 while(1) { // --------------- 旋转计数(你原来的代码) --------------- Num += Encoder_Get(); // ------------------- 新增:按键按下清零 ------------------- if(GPIO_ReadInputDataBit(KEY_GPIO_Port, KEY_Pin) == 0) { Delay_ms(20); // 消抖 while(GPIO_ReadInputDataBit(KEY_GPIO_Port, KEY_Pin) == 0); Delay_ms(20); Num = 0; // 清零! } // 显示 OLED_ShowSignedNum(1,5,Num,5); } }

3.5 定时器中断、定时器外部时钟

定时器类型

3.5.1定时器中断

函数及其对应硬件

封装函数

1.RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 开启内部时钟 TIM2

2.TIM_InternalClockConfig(TIM2); //选择内部时钟(系统默认内部时钟)

//配置时基单元

3.TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; // PSC 预分频器

  • 作用:把 72MHz 时钟放慢 7200 倍
  • 结果:CNT 计数器 每秒跳 10000 下

4.TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; // ARR 自动重装器

  • 作用:CNT 数到 10000 就归零,触发中断
  • 结果:每秒刚好满一次 → 1 秒中断一次

5.TIM_GetCounter(TIM2) // → CNT(硬件) 能显示CNT计数器

6.TIM_Cmd(TIM2, ENABLE); //运行控制 启动定时器

7.TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //定时器2 更新中断

当CNT到达10000,执行此行代码,进行中断

中断控制函数模板(库函数自带,可以直接全局调用)

主函数

3.5.2定时器外部时钟

接线

封装,该为外部时钟控制,对射红外对PA0口进行输出

改一下计时时间

为什么【外部红外计数】PSC 必须 = 0?

因为:外部时钟不是 72MHz,是【你遮挡红外的动作】!

如果 PSC = 1(不分频是 0,分频 1 就是 2 分频)

  • 来 1 个脉冲 → 不算

  • 来 2 个脉冲 → CNT 才 +1

  • 你遮挡 2 次,只记 1 次 → 数据少一半!错了!


如果 PSC = 9(10 分频)

  • 来 10 个脉冲

  • CNT 才 +1

  • 你挡 10 次才算 1 次 → 完全不能用

3.6 PWM驱动LED呼吸灯、PWM驱动舵机、PWM驱动直流电机

原理结构图

CK_PSC默认72MHz

3.6.1 LED呼吸灯

接线

封装初始化

波形图

安捷伦DSO-X 2012A 使用说明在另一个文章

频率1KHz,占空比为50%的波形

常规端口

TIM2_CH1 默认对应 GPIO PA0 引脚(当需要用头一个引脚的不同默认功能,可以重定义)

一定要打开定时器PWM输出功能(把定时器中断功能关闭,要不然CNT到达设置的数,就会进入中断,然后卡死)

端口映射

TIM2_CH1默认PA0,映射到PA15,但PA15默认JTDI调试端口,需要特殊设置

GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将PA15、PB3、PB4变成普通GPIO口

1.开启AFIO时钟

2.映射配置

3.选择修改对应GPIO口

主函数

for循环控制CCR,从而改变占空比大小

void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);//此函数只改变CCR,并不直接改变占空比
}
根据调节CCR,灯的占空比从1到100到1

3.6.2 舵机

硬件

舵机要求频率50Hz、高电平时间是0.5ms~2.5ms(周期2ms)

封装

PWM封装

只改了通道,上一个LED用的OC1,这个用的OC2

舵机封装

主函数

底层逻辑,每次按键,都会改变CCR的值

3.6.3 直流电机

硬件

A0 为PWM输出端 A4 A5为电机极性设置端 电机驱动H桥需要外接5V供电

设置特定频率,消除人耳电机噪音

设置电机驱动模块的in 1 in 2 设置极性(电机正反转)

控制电机速度

3.7 输入捕获模式测频率、PWMI模式测占空比

原理

定时器输入和输出共用4个通道,原理都是对CCR的改变。

输出PWM波,就是不断改变CCR

输入检测,就是不断检测波形的高低电平,从而改变CCR来检测

32只能测高低电平,不能测正弦波(需要搭建外设)

测频法需要计次数目高一些。防止N太小,有误差

测周法要求计次数目低一些

测周法数学推理:

N / fc = T (测得的一个周期)1 / T 为测得频率。

fc 自己设置 72MHz / PSC (1MHz 标准频率,每 1us+1,72MHz是 1/72us +1 )

通过计算中介频率,来判断目标频率(目标频率可以根据经验,从而选择测频还是测周)是高频还是低频,从而选择测频法还是测周法

3.7.1输入捕获模式测频率 测周

原理

IC函数对应

原理:从第一个上升沿开始计数,到第二个上升沿结束,并且把CNT赋值到CCR,然后就能计算频率(具体计算步骤在下)N计次就是CNT 的数值,他等于CCR 的值。

信号发生器

代码解读

1.GPIO配置

// GPIOA配置 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 ; // GPIOA 6 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStruct);

配置输入捕获单元1

2.滤波器 →边沿检测→ 数据选择器(黄色块)→ 分频器

// 配置PWM IC 输入捕获单元 TIM_ICInitStructure.TIM_Channel = TIM_Channel_1 ; //选择通道 TIM_ICInitStructure.TIM_ICFilter = 0xF; //滤波器 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //边缘检测 极性选择 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 数据选择器 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //分频器 TIM_ICInit(TIM3, &TIM_ICInitStructure);

3.从模式配置

配置TRGI触发源为 TI1FP1

//触发源选择 TRGI的触发源为TI1FP1 TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);

从模式为Reset

//选择从模式 TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

关于计算问题
//这是定时器最大能数到多少 希望CNT 能数很大,不要中途溢出 //最多能测量 65535 微秒的周期,足够测量 0.065 秒 以内的信号,也就是 频率 > 15Hz 都能测 TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR 自动重装器 //把 72MHz 时钟 → 变成 1MHz CNT 每 1 微秒 + 1 TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC 预分频器 // uint32_t IC_GetFreq(void) { return 1000000 / TIM_GetCapture1(TIM3); //计算 }

ARR设置最大值,是防止计数溢出重置

PSC分频是因为时钟72MHz是 1/72us +1,有可能一个周期没测完,就达到ARR最大值重置了

分频成1MHz是 1us +1,基本不会超过ARR。最后利用公式1000000 / TIM_GetCapture1(TIM3);

也就是fc / N 。N计次就是CNT 的数值,他等于CCR 的值。

N / fc = T (测得的一个周期)1 / T 为测得频率。

fc 自己设置 72MHz / PSC (1MHz 标准频率,每 1us+1,72MHz是 1/72us +1 )

3.7.2 PWMI模式测占空比

原理

就是多加了一个TI1FP2,来负责获取高电平的计数,上升沿TI1FP1,TI1FP2都开始计数。当出现下降沿,TI1FP2结束计数,但不给CNT赋0,等到出现第二个上升沿TI1FP1给Reset赋值0。

主要看TI1FP2配置

3.8 编码器接口测速

利用定时器对编码器计数,可以有效节约资源,避免3.4.2小节,利用中断进行计数。

原理

代码用了TIM_EncoderMode_TI12四倍频模式:

EC11 旋转编码器是有「阻尼卡位」的,一格内部藏了 4 个信号边沿;

✅ 每触发 1 个边沿 → CNT ±1

✅ 4 个边沿凑齐 → 才是完整 1 格 → CNT ±4

函数

也就是说我的编码器就是外部时钟,转一下CNT就上升。如果是内部时钟72MHz,1秒跳72M次。时钟跳的次数和时钟频率有关

之前写过秒级定时器Timer.h,把获取速度的函数用定时器中断(硬件)获取,这样减轻CPU(主函数)压力,保证OLED能实时显示speed。

四、实战案例(2)

4.1ADC

4.1.1单通道

4.1.2多通道
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 23:06:46

网络原理 网络编程

一.网络初识链接: https://pan.baidu.com/s/1WspOI-oXB-WwE03z3BMU7Q 提取码: tyke二.网络编程1.网络编程基础⽹络资源:就是在⽹络中可以获取的各种数据资源。⽽所有的⽹络资源,都是通过⽹络编程来进⾏数据传输的。网络编程:指⽹络上的主机通…

作者头像 李华
网站建设 2026/4/25 23:03:05

儿童怎么掏耳朵?怎么给小孩掏耳屎?儿童掏耳朵神器推荐2026

儿童耳道娇嫩狭窄,鼓膜厚度仅为成人的一半左右。用传统棉签或金属挖耳勺给孩子掏耳朵,不仅容易划伤外耳道皮肤,更可能把耳垢推向深处形成栓塞,甚至戳伤鼓膜引发感染。面对这样的困境,可视掏耳勺通过微型摄像头将耳道内…

作者头像 李华
网站建设 2026/4/25 23:02:51

航海小知识 | AIS是什么?每艘船自带的“身份证+广播器” | VDES技术

嘿,我是小蓝。「航海小知识」是一个帮你把航海专业术语翻译成人话的轻科普栏目。3分钟搞懂一个航海小知识。今天是第2期,聊聊每艘船都有的“电子身份证”——AIS。 01什么是AIS? AIS,全称Automatic Identification System&#xf…

作者头像 李华
网站建设 2026/4/25 22:59:20

GetQzonehistory:一键永久保存QQ空间说说的终极免费方案

GetQzonehistory:一键永久保存QQ空间说说的终极免费方案 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾担心QQ空间里那些珍贵的青春记忆会随着时间流逝而消失&…

作者头像 李华