一、介绍二、新建工程2.1添加文件新建工程需要Start文件中一共六个启动文件地址C:\Users\亲爱的玩家\Desktop\STM32F10x_StdPeriph_Lib_V3.5.0其他描述文件地址C:\Users\亲爱的玩家\Desktop\STM32F10x_StdPeriph_Lib_V3.5.0\Librarieslibrary库文件uesr文件最终如图。红色为必要文件蓝色是封装库2.2必要步骤点击魔法棒点击C/C。第一个框填写一模一样第二个添加路径头文件路径烧录器类型点击setting选择port点击flashdownload烧录之后自动运行配置完成2.3硬件连接如图形成闭合回路2.4.keilkill如下工具可以删除编译数据减少内存三、实战案例13.1LED闪烁、LED流水灯、蜂鸣器3.1.1接线图代码3.1.2接线图代码红框为GPIOA的二进制开口3.1.3接线代码3.2按键控制、光敏传感器控制蜂鸣器3.2.1按键控制接线图代码主函数和封装建立封装函数一定建立 .C 和 .H 两个3.2.1.1 LED3.2.1.2 Key3.2.1.3 Buzzer3.2.1.4 Light Sensor3.2.2光敏传感器控制蜂鸣器3.3OLED3.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最常用内部结构公共端 CGNDA 相触点 → 接 PB0B 相触点 → 接 PB1内部有两个弹片旋转时依次接通 / 断开 GND。静止时你设了上拉输入PB0A高电平 1PB1B高电平 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 01 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 tipsuint16_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 GPIOA2. 按键初始化函数加到 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); // 开启内部时钟 TIM22.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 910 分频来 10 个脉冲CNT 才 1你挡 10 次才算 1 次 → 完全不能用3.6 PWM驱动LED呼吸灯、PWM驱动舵机、PWM驱动直流电机原理结构图CK_PSC默认72MHz3.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到13.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 标准频率每 1us172MHz是 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);配置输入捕获单元12.滤波器 →边沿检测→ 数据选择器黄色块→ 分频器// 配置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 标准频率每 1us172MHz是 1/72us 1 3.7.2 PWMI模式测占空比原理就是多加了一个TI1FP2来负责获取高电平的计数上升沿TI1FP1TI1FP2都开始计数。当出现下降沿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就上升。如果是内部时钟72MHz1秒跳72M次。时钟跳的次数和时钟频率有关之前写过秒级定时器Timer.h把获取速度的函数用定时器中断硬件获取这样减轻CPU主函数压力保证OLED能实时显示speed。四、实战案例24.1ADC4.1.1单通道4.1.2多通道