STM32应用开发实践教程:能小车循迹状态获取的应用开发

2023-10-30 20:40

本文主要是介绍STM32应用开发实践教程:能小车循迹状态获取的应用开发,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

3.1.1 任务分析
本任务要求设计一个应用程序,以实现周期性地获取智能小车循迹的状态。任务要求使用反
射式红外光电传感器电路板作为智能小车的循迹模块,实现智能小车的巡线前进功能。反射式红
外光电传感器和循迹电路板如图 3-1-1 所示。

反射式光电传感器的光源有多种,常用的有可见光、红外光和激光,本任务选取红外光源。
单个反射式红外光电传感器自带一个红外光源和一个光接收装置,光源发出的光经待测物体反射
后被光接收装置(光敏元件)接收,再经过信号调理电路处理即可获得所需信息,一般为数字信
号“1”或“0”。利用传感器的这个特性可以检测地面明暗程度和颜色的变化,也可以探测有无
接近的物体。
根据上述反射式红外光电传感器的工作原理来分析此任务,并在智能小车底盘前部安装反射
式红外光电传感器电路板(循迹电路板),板上装有 8 个反射式红外光电传感器。智能小车循迹
的具体工作原理如下:
 红外发射管发射光线到路面,光线遇到白底后被反射,接收管收到反射光,经过信号调
理电路处理后输出高电平;
 光线遇到黑底后被吸收,接收管没有收到反射光,经过信号调理电路处理后输出低电平;
 循迹电路板共输出 8 路数字信号到智能小车的主控板,其中数字信号“1”表示白色路面,
数字信号“0”表示黑色路面;
 智能小车主控板将接收到的 8 路数字信号作为车身当前状态的判别依据,并进一步控制电
机转动以使车身归位。测试场地路面有黑、白两色,其中黑色路面为循
迹线,宽 30mm,智能小车可循此线前进。测试场地示意如图 3-1-2 所示。


综上所述,如果要求智能小车以一定的速度沿着 黑色跑道前进,则微控制器需要以较短的周期频繁地获取循迹电路板上的 8 路数字信号,进而才
能通过及时判断车身状态以调整电机的转向。智能小车的该要求可通过定时器的基本定时功能来
实现,因此本任务涉及的知识点有:
 STM32F4 系列微控制器定时器的基本功能特性;
 STM32F4 系列微控制器定时器的基本定时功能的编程配置方法。


3.1.2 知识链接
1.STM32F4 系列微控制器定时器概述
STM32F4 系列微控制器共有 14 个定时器,编号为 TIM1~TIM14,其中包括 2 个高级控制定
时器、10 个通用定时器和 2 个基本定时器。
上述 3 种类型的定时器中,基本定时器功能最少,只有基本的定时功能和驱动数模转换器
(Digital to Analog Converter,DAC)的功能,不具备外部通道。通用定时器和高级控制定时器
的功能较强,如具有独立的外部通道,可用于输入捕获、输出比较、脉宽调制(Pulse Width
Modulation,PWM)信号输出等,支持正交编码器与霍尔传感器等电路。表 3-1-1 对各定时器
的功能特性进行了总结概括,查看此表时要注意区分 3 种类型定时器的功能差异

 

 表 3-1-1 仅列出了各类定时器存在差异的指标,参数相同的指标并未列出。

另外,定时器时钟(TIMxCLK)频率与 PCLKx 的关系如下。
如果 APB 预分频器(RCC_CFGR 中的 PPRE1、PPRE2)的分频系数配置为 1,则 TIMxCLK=
PCLKx;否则,定时器时钟频率将被设置为与定时器相连的 APB 域的频率的两倍。定时器时钟
频率与 PCLKx 的关系如图 3-1-3 中蓝色阴影和红色方框部分所示。

 

编者使用的 STM32F4 开发板上的微控制器型号为 STM32F407ZGT6,在任 务 1.3 的学习中,
我们配置 HCLK 为 168 MHz,PCLK1 为 42 MHz,PCLK2 为 84 MHz,即 APB1 和 APB2 的分
频系数分别为 4 和 2。根据上述关系可知,定时器时钟频率 TIMxCLK = 2 × PCLKx。以 TIM13
为例,若其连接外设总线 APB1 = 42 MHz,那么 TIMxCLK = 2 × 42 MHz= 84 MHz。
对于STM32F42xxx 和STM32F43xxx 系列微控制器而言,根据《STM32F4xx 中文参考手册》,
定时器时钟预分频器由 RCC 专用时钟配置寄存器(RCC_DCKCFGR)的“TIMPRE”位段进行
配置(STM32F40xxx 系列微控制器无此寄存器),该寄存器各位的定义如图 3-1-4 所示。

 从图 3-1-4 中可以看到,RCC 专用时钟配置寄存器只有第 24 位“TIMPRE”有效,它被称
为“定时器时钟预分频器选择位”。该位默认值为“0”,TIMxCLK 频率的配置情况如前所述。
当该位被配置为“1”时,如果 APB 预分频器(RCC_CFGR 中的 PPRE1、PPRE2)的分
频系数配置为 1、2 或 4,则 TIMxCLK = HCLK;否则,定时器时钟频率将被设置为与定时器相
连的 APB 域的频率的 4 倍:TIMxCLK = 4 x PCLKx。以 TIM13 为例,APB1 外设总线的分频系
数为 4,因此 TIMxCLK =HCLK= 168 MHz。
2.基本定时器的功能框图解析
根据 3.1.1 节的任务分析结果可知,本任务需要用到定时器的基本定时功能,因此选择
STM32F4 系列微控制器的基本定时器即可。通过基本定时器的功能框图来学习其各部分的功能
特性,其功能框图如图 3-1-5 所示。

从图 3-1-5 中可以看到,基本定时器包含 3 部分:时钟源、控制器模块和时基单元。
自动重载寄存器和预分频器(PSC)的长方形框带有阴影,代表这两个寄存器带有影子寄
存器。
自动重载寄存器左侧有一个“事件”标志,表示在更新事件发生时,使用预装载值更新自动
重载影子寄存器。其右侧的“事件”标志与“中断和 DMA 输出”标志表示当计数器寄存器值与
自动重载寄存器值相等时,将发生事件、中断和 DMA 输出。
下面对基本定时器的各部分组成分别进行介绍。
(1)时钟源
功能框图中非常明确地表示了基本定时器的时钟源只能来自内部时钟(CK_INT),即 RCC
的 TIMxCLK。关于 TIMxCLK 频率的具体计算方法已在前述内容中阐述了,此处不再赘述。
通用定时器与高级控制定时器的时钟源除了可来自内部时钟,还可来自外部时钟或者其他定
时器等。
(2)控制器模块
基本定时器的控制器模块用于控制定时器的复位、使能与计数,或者用于触发 DAC 的转换
使能等。
(3)时基单元
基本定时器的时基单元由一个 16 位递增计数器(图 3-1-5 中的 CNT 计数器)及其相关的
自动重载寄存器组成,计数器的时钟通过预分频器进行分频。计数器寄存器、预分频器寄存器和
自动重载寄存器可通过软件进行读写。即使在计数器运行时也可对它们执行读写操作。
时基单元包括以下 3 部分。

① 计数器寄存器
计数器寄存器(TIMx_CNT)中存储了定时器当前的计数值。
② 预分频器寄存器
从图 3-1-5 可以看到,预分频器的输入为 CK_PSC(等于 CK_INT),经分频后,输出为
CK_CNT,分频系数由 16 位预分频器寄存器(TIMx_PSC)中的值决定,介于 1~65536。CK_CNT
的时钟频率与 CK_PSC 的时钟频率关系如下。
f CK_CNT =  f CK_PSC  / (TIMx_PSC + 1)
计数器由 CK_CNT 提供时钟,预分频器寄存器由于有缓冲,因此可被实时更改,但新的分
频系数将在下一个更新事件发生时被采用。图 3-1-6 展示了预分频器的分频系数由 1 变为 2 时
的计数器时序图。

 

从图 3-1-6 中可以看到,计数器计数到“F8”时,在 TIMx_PSC 中写入了新值“1”(图
中蓝色阴影处,原值为“0”)。在此之前, f CK_CNT =  f CK_PSC  。但写入新值后,预分频器的分频系
数并没有马上变为 2,在更新事件发生时(图中橙色阴影处), f CK_CNT 的频率才变为 f CK_PSC 的 2
分频。
③ 自动重载寄存器
自动重载寄存器(TIMx_ARR)由两部分构成:预装载寄存器和影子寄存器。真正起作用
的是影子寄存器,它支持预装载,每次尝试对它执行读写操作时都会访问预装载寄存器。预
装载寄存器的内容既可以直接传输到影子寄存器,也可以在每次发生更新事件(UEV)的时
候传输到影子寄存器,这取决于 TIMx_CR1 中的自动重载预装载使能位(APRE),其工作特
性如下。
 当 APRE=0 时,TIMx_ARR 不进行缓冲,预装载寄存器的值直接传到影子寄存器中,如
图 3-1-7 所示。
 当 APRE=1 时,TIMx_ARR 预装载(进行缓冲),当发生更新事件后,预装载寄存器的
值才传输到影子寄存器中,如图 3-1-8 所示。

 

对时基单元的计数过程总结如下。
 计数器从 0 开始计数,当其值变为自动重载值(TIMx_ARR 的值)时,发生“计数器
上溢”。
 每次发生计数器上溢时会生成“更新事件(UEV)”,同时更新所有寄存器并将“更新中
断标志位”(TIMx_SR 中的 UIF 位)置 1。
 更新所有寄存器的动作,具体包括:
 使用预装载值(TIMx_PSC 的内容)重新装载预分频器的缓冲区;
 使用预装载值(TIMx_ARR 的内容)更新自动重载影子寄存器。 

计数器重新从 0 开始计数。
下面通过一张计数器工作时序示意图(如图 3-1-9 所示)来说明上述计数过程,在该示意
图中 TIMx_PSC 的值为 1(即 2 分频 ),TIMx_ARR 的值为 36。

.定时器基本初始化结构体介绍
STM32F4 标准外设库为定时器外设提供了 4 个初始化结构体,本任务由于只用到定时器中
断功能,因此只涉及定时器基本初始化结构体(TIM_TimeBaseInitTypeDef)。该结构体用于配
置定时器的基本工作参数(如预分频器分频系数、计数模式和定时器周期等),被
TIM_TimeBaseInit()函数调用。其原型定义如下所示:

typedef struct {
uint16_t TIM_Prescaler; // 预分频器
uint16_t TIM_CounterMode;  // 计数模式
uint32_t TIM_Period;  // 定时器周期
uint16_t TIM_ClockDivision; // 时钟分频
uint8_t TIM_RepetitionCounter; // 重复计数器
} TIM_TimeBaseInitTypeDef;

 对结构体各成员变量的作用介绍如下。
(1)TIM_Prescaler
定时器预分频器分频系数配置,它被用于配置预分频器寄存器的值,可配置的范围为
0~65535,对应 1~65536 分频。
(2)TIM_CounterMode
定时器计数方式配置,可配置的参数如下:
 向上计数(TIM_CounterMode_Up);
 向下计数(TIM_CounterMode_Down);
 中心对齐模式 1(TIM_CounterMode_CenterAligned1);
 中心对齐模式 2(TIM_CounterMode_CenterAligned2);
 中心对齐模式 3(TIM_CounterMode_CenterAligned3)。
对于基本定时器而言,只能使用向上计数的方式,因此无须配置该项,使用默认值即可。

(3)TIM_Period
定时器周期配置,实际上本成员变量配置的是自动重载寄存器的值,事件生成时更新到影子
寄存器,可配置的范围为 0~65535。
(4)TIM_ClockDivision
时钟分频配置,它被用于配置定时器内部时钟频率与数字滤波器采样时钟频率的分频比,基
本定时器不具备输入捕获功能,可不用进行时钟分频配置。
(5)TIM_RepetitionCounter
重复计数器配置,高级控制定时器专用,基本定时器可不用进行该配置。
4.定时器中断功能的编程配置步骤
掌握了基本定时器的功能特性与初始化结构体的配置内容后,我们可进行完整的定时器中断
功能编程配置的学习。接下来我们将学习如何配置定时器 6,使之按时产生中断,并在中断服务
函数中完成相应的工作。具体的编程配置步骤如下。
(1)开启 TIM6 时钟
根据表 3-1-1 可知,TIM6 挂载在 APB1 上,因此需要调用 ABP1 的时钟使能函数以开启
TIM6 的时钟。具体代码如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);  // 使能 TIM6 时钟
(2)配置定时器的工作参数
首先配置“定时器基本初始化结构体(TIM_TimeBaseInitTypeDef)”的各成员变量,然后调
用 TIM_TimeBaseInit()函数以完成参数的初始化。
我们通过一个实例来学习具体的配置方法。如在实际应用中,要求每隔 1s 采集一次环境温
湿度信息,使用定时器 6 实现,该如何配置定时器?
在上述实例中,我们可先配置 CK_CNT 频率。TIM6 挂载在 APB1 上,定时器时钟源频率
(CK_INT = CK_PSC)为 42 MHz×2=84 MHz。可将 TIMx_PSC 配置为 8399,根据计算公式可得:
f CK_CNT = 84 MHz/ (8399 + 1) = 10000 Hz(周期为 100μs) 
配置 TIMx_ARR 的值为 9999,进而即可将定时器配置为每隔 1s 产生更新中断。间隔时间
的计算方法如下:

100 μs×(TIMx_ARR + 1) = 1000000 μs = 1s 
实现上述配置要求的代码块如下:

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);
TIM_TimeBaseStructure.TIM_Period = 10000-1;  // 配置 TIMx_ARR
TIM_TimeBaseStructure.TIM_Prescaler = 8400-1; // 配置 TIMx_PSC
TIM_TimeBaseInit(TIM6,&TIM_TimeBaseStructure);

另外可根据以下公式计算定时器的溢出时间( T out )。
T out (μs) = [(TIMx_ARR + 1)×(TIMx_PSC + 1)]÷ f CK_PSC (MHz) 
在上述公式中, T out 的单位为 μs, f CK_PSC 为定时器的工作频率,单位为 MHz。将实例中的各
个数字代入上式后可得:
1s = 1000000 μs =(10000×8400)÷84(MHz) 
(3)配置允许定时器产生更新中断
本任务要求在到达定时时间后,产生更新事件并将中断标志位置 1,因此需要配置允许 TIM6
产生更新中断。STM32F4 标准外设库使用 TIM_ITConfig()函数来进行定时器中断使能,它的函
数原型定义如下:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
第一个参数为定时器编号,取值为 TIM1~TIM14。
第二个参数用于指明要使能的定时器中断的类型,在本任务中使用更新中断,因此要配置为
“TIM_IT_Update”。
第三个参数指明使能(ENABLE)或失能(DISABLE)。
例如要使能 TIM6 的更新中断,具体代码如下:
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
(4)配置定时器中断优先级
与任务 2.2 中的“按键中断”优先级和任务 2.3 中的“串口接收中断”优先级配置类似,使
能定时器的“更新中断”后,也须对 NVIC 进行优先级配置。使用以下代码可配置 TIM6 的中断
优先级。 

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* TIM6 NVIC 配置 */
NVIC_InitStructure.NVIC_IRQChannel = TIM6_DAC_IRQHandler;  // 定时器 6 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; // 抢占优先级 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; // 子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

(5)使能定时器
完成定时器的相关配置以后,须开启定时器才能使其开始工作。这里通过将控制寄存器
(TIMx_CR1)的“CEN”位段置 1 实现。STM32F4 标准外设库通过调用 TIM_Cmd()函数实现,
该函数的原型定义如下:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
使用实例如下:
TIM_Cmd(TIM6, ENABLE);
(6)编写定时器中断服务函数
定时器中断服务函数用于处理中断发生后的事件。定时器支持多种中断类型,但中断的入口
函数一般是统一的,如 TIM6 的中断服务函数为“TIM6_DAC_IRQHandler()”。因此在进入中断
服务函数后,首先应通过 TIMx_SR 判断中断类型,然后再执行相应的后续操作。STM32F4 标准
外设库提供了获取中断类型的函数,它的函数原型定义如下:
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
该函数的作用是:判断当前定时器发生了哪种类型的中断。具体使用实例如下:
if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET){};
上述语句用于判断当前 TIM6 是否发生了“更新中断(TIM_IT_Update)”。
另外,处理完中断之后,应将相应的中断标志位清除,即向 TIMx_SR 的相应位写入 0。
STM32F4 标准外设库提供了清除中断标志位的函数,函数原型定义如下:

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
该函数的作用是:清除定时器相应的中断标志位。具体使用实例如下:
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
上述语句用于清除 TIM6 的“更新中断标志位”。
下面给出定时器中断服务函数的一般框架,其中入口函数名需要根据实际定时器进行修改,
如定时器 6 的中断入口函数名为“TIM6_DAC_IRQHandler()”。

void TIMx_IRQHandler(void)
{
if(TIM_GetITStatus(TIMx,TIM_IT_Update) == SET) // 如果产生了更新中断
{
/* DoSomething */
}
TIM_ClearITPendingBit(TIMx,TIM_IT_Update); // 清除更新中断的标志位
}

3.1.3 任务实施
1.根据任务要求计算 TIMx_ARR 与 TIMx_PSC 值
3.1.1 节的任务要求智能小车以一定的速度沿着黑色跑道前进,因此微控制器需要以较短的
周期频繁地获取循迹电路板上 8 路光电传感器的实时状态。可将 TIM6 的定时中断时间配置为 10
ms,然后在中断服务函数中获取循迹电路板的状态数据,以作为应用程序控制电机的依据。可
配置定时器的工作参数为 TIMx_PSC =8399,TIMx_ARR = 99。
根据计算公式可得:
f CK_CNT = 84 MHz/ (8399 + 1) = 10000 Hz(周期为 100 μs) 
定时中断时间为:
100 μs×(TIMx_ARR + 1) = 10000 μs = 0.01s = 10 ms 
2.编写 TIM6 定时中断初始化程序
复制一份 task2.3_USART_WaterFlow_LED 工程,并将其重命名为“task3.1_Timer_Interrupt_
GetTrackData”。在“HARDWARE”文件夹下新建“TIMER”子文件夹,新建“timer6.c”和“timer6.h”
两个文件,将它们加入工程中,并配置头文件包含路径。
在“timer6.c”文件中输入以下代码:

#include "timer6.h"
/**
* @brief TIM6 定时中断功能初始化
* @param arr:  自动重装载值, psc:  定时器预分频值
* @retval None
*/
void TIM6_Int_Init(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);// 使能 TIM6 时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; // 自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 定时器预分频值
TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure); // 初始化 TIM6
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); // 清除更新中断请求位
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE); // 允许定时器 6 更新中断
TIM_Cmd(TIM6,ENABLE); // 使能定时器 6
NVIC_InitStructure.NVIC_IRQChannel = TIM6_DAC_IRQn;  // 定时器 6 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;  // 抢占优先级 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; // 子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}在“timer6.h”文件中输入以下代码:
#ifndef __TIMER6_H
#define __TIMER6_H
#include "sys.h"
void TIM6_Int_Init(uint16_t arr, uint16_t psc);
#endif

3.编写循迹电路板连接端口初始化程序
在“HARDWARE”文件夹下新建“TRACK”子文件夹,新建“track.c”和“track.h”两个
文件,将它们加入工程中,并配置头文件包含路径。
在“track.c”文件中输入以下代码:

#include "track.h"
#include "led.h"
#include "usart.h"
/*  存放循迹电路板上传来的 8 位二进制数 */
uint8_t trackData = 0;
uint16_t tCount = 0;
/**
* @brief 循迹电路板端口初始化
* @param None
* @retval None
*/
void TrackBoard_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);// 使能 GPIOF 时钟
/* PF0~PF7 端口初始化设置 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3 \
|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;  // 输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;  // 引脚速率 100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  // 上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);
}在“track.h”文件中输入以下代码:#ifndef __TRACK_H
#define __TRACK_H
#include "sys.h"void TrackBoard_Init(void); // 循迹电路板端口初始化#endif

4.编写 TIM6 的定时中断服务函数
TIM6 的定时中断服务函数理论上可放置在应用程序中的任意一个源代码文件中,但中断服
务函数中通常包括数据处理程序,因此从编程的便利性来说应将中断服务函数与数据处理程序放
置在同一个源代码文件中。本任务将其编写在“track.c”文件中,具体代码如下:

/**
* @brief TIM6 定时中断服务函数
* @param None
* @retval None
*/
void TIM6_DAC_IRQHandler(void)
{
if(TIM_GetITStatus(TIM6,TIM_IT_Update) == SET)// 若发生更新中断
{
tCount++;
/*  获取 GPIOF 16 bit 数据中的低 8 位 (bit0 ~ bit7) */
trackData = GPIO_ReadInputData(GPIOF) & 0xFF;
if(tCount >= 200)  // 每隔 2s 翻转 LED1 ,打印信息
{
LED1 = ~LED1;
printf("trackData is 0x%x\r\n",trackData);
tCount = 0;
}
}
TIM_ClearITPendingBit(TIM6,TIM_IT_Update);// 清除更新中断标志位
}

5.编写 main()函数
在“main.c”文件中输入以下代码:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer6.h"
#include "track.h"
int main(void)
{
delay_init(168); // 延时函数初始化
LED_Init(); //LED 端口初始化
USART1_Init(115200);  //USART1 初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*  配置 TIM6 定时中断时间为 10 ms */
TIM6_Int_Init(100-1, 8400-1);
TrackBoard_Init();
printf("System Started...\r\n");
while(1)
{
}
}

6.观察试验现象
本任务中 8 路循迹电路板数据的输出端口与 STM32F4 系列微控制器的 PF0~PF7 引脚相连,
为了更加方便地观察试验现象,已在 TIM6 的定时中断服务函数中将每隔 2s 获取的循迹电路板
数据通过 USART1 发送到上位机。应用程序编译无误后,下载至开发板运行,打开上位机的串
口调试助手,可观察到如图 3-1-10 所示的定时器中断试验现象。
由于循迹电路板端口初始化为输入模式,默认上拉,因此从图 3-1-10 中可以看到默认获取
到的 8 位数据全为“1”,即十六进制 0xff。使用杜邦线将 PF0 端口接地后,获取到的数据变为
0xfd,即最低位被清零。

 

这篇关于STM32应用开发实践教程:能小车循迹状态获取的应用开发的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/310465

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1565(状态压缩)

本人第一道ac的状态压缩dp,这题的数据非常水,很容易过 题意:在n*n的矩阵中选数字使得不存在任意两个数字相邻,求最大值 解题思路: 一、因为在1<<20中有很多状态是无效的,所以第一步是选择有效状态,存到cnt[]数组中 二、dp[i][j]表示到第i行的状态cnt[j]所能得到的最大值,状态转移方程dp[i][j] = max(dp[i][j],dp[i-1][k]) ,其中k满足c

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个