一、脉冲模块
(1)资源介绍
🔅原理图
蓝桥杯物联网竞赛实训平台提供了一个拓展接口 CN2 ,所有拓展模块均可直接安装在 Lora终端上使用;
图1 拓展接口
脉冲模块电路原理图如下所示:
图2 脉冲模块电路原理图
通过两张电路图连接可知,引脚资源配置情况为:
PULSE | MCU |
---|---|
PR1 | PB1 |
PULS | PB0 |
LED1 | PB7 |
LED2 | PB6 |
[表1 引脚资源配置情况] |
🔅采集原理
脉冲信号由脉冲芯片产生,通过PR3滑动变阻器控制,不同阻值下的脉冲频率不同。
通过公式可得,脉冲频率 = 脉冲周期的倒数,那么我们可以将问题转换为求取脉冲周期。
PULS 连接在PB0 引脚,该引脚为定时器3的通道3,我们可以使用定时器的输入捕获模式(Input Capture direct mode)。捕获脉冲的上升沿,通过计算相邻两个上升沿之间的差值,即可得到该脉冲的周期。示意图如下所示:
图3 脉冲示意图
⚠️注意: ++由于定时器周期不为1Hz,所以这里需要用定时器时钟频率除以脉冲周期++;
定时器的计数寄存器为16位,范围是0~65535,溢出后清零重新计数。在计算脉冲周期时会遇到三种情况,如图4所示:
图4 计数不同情况
对于情况3,如果两次获取的值相等,需要考虑是否为定时器计数异常等情况,所以一般判为error;
🟠️伪代码如下:
cpp
if(第一个上升沿)
{
cnt1 = 获取定时器cnt值;
}
else if(第二个上升沿)
{
cnt2 = 获取定时器cnt值;
/* 判断情况 */
if(cnt2 > cnt1)
脉冲周期 = cnt2 - cnt1;
else if(cnt2 < cnt1)
脉冲周期 = 0xFFFF - cnt1 + 1 + cnt2;
else
error报错;
uwFreq = 定时器时钟频率 / 脉冲周期;
}
PR1为AD采集,参考之前的文章即可:
(2)STM32CubeMX 软件配置
🔅"工程建立、时钟树配置、Debug 串行线配置、代码生成配置" 在下文中有讲解,这里不再赘述❗️
1️⃣点击引脚 PB0 → 选择 TIM3_CH3 ;(此时引脚呈黄色,因为TIM 还未配置)
2️⃣点击 "Timers " → 点击"TIM3 " → 将 "Channel3 " 栏修改为 "Input Capture direct mode ",即将 PB0 引脚配置为TIM3 通道3的输入捕获模式;(此时引脚呈绿色,可以正常工作)
⚠️**注意:**下方参数表中:
- 计数周期为65535;
- 通道3输入捕获极性为++上升沿++或++下降沿++;
图5 引脚配置
3️⃣配置PB1 引脚为AD 采集引脚;(具体参考AD采集一文)
4️⃣初始化 OLED ;(具体参考OLED一文)
5️⃣生成代码即可;
(3)代码编写
🟢️main 函数
cpp
/* USER CODE BEGIN PV */
uint8_t puc_oled[17]; // oled显示缓存区
uint16_t pui_adc; // adc采集值
uint8_t uc_ic_state; // 输入捕获状态,区分第一次和第二次上升沿
uint32_t uwValue1; // 第一次上升沿的计数值
uint32_t uwValue2; // 第二次上升沿的计数值
uint32_t uwDiff; // 脉冲周期
uint32_t uwFreq; // 脉冲频率
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) // 判断是否为通道3中断
{
if(uc_ic_state == 0) // 第一次上升沿
{
uwValue1 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_3); // 获取计数值
uc_ic_state = 1; // 修改状态变量
}
else if(uc_ic_state == 1) // 第二次上升沿
{
uwValue2 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_3); // 获取计数值
/* 判断情况 */
if(uwValue2 > uwValue1)
uwDiff = uwValue2 - uwValue1;
else if(uwValue2 < uwValue1)
uwDiff = 0xFFFF - uwValue1 + 1 + uwValue2;
else
Error_Handler();
uwFreq = HAL_RCC_GetPCLK1Freq() / uwDiff; // 计算脉冲频率
uc_ic_state = 0; // 修改状态变量
}
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC_Init();
MX_TIM3_Init();
MX_I2C3_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_3); // 开启定时器捕获中断
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* ADC采集 */
HAL_ADC_Start(&hadc);
if(HAL_ADC_PollForConversion(&hadc, 10) == HAL_OK)
pui_adc = HAL_ADC_GetValue(&hadc);
/* OLED显示 */
sprintf((char*)puc_oled, "TIM:%u ", uwFreq);
OLED_ShowString(0, puc_oled);
sprintf((char*)puc_oled, "ADC:%.2f ", pui_adc * 3.3 / 4095);
OLED_ShowString(2, puc_oled);
HAL_Delay(200);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
(4)实验现象
- 旋转PR1按钮,能够实时采集AD值;
- 旋转PR3按钮,能够实时采集频率值;
二、脉冲模块接口函数封装
🟡️定时器输入捕获中断函数
cpp
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) // 判断是否为通道3中断
{
if(uc_ic_state == 0) // 第一次上升沿
{
uwValue1 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_3); // 获取计数值
uc_ic_state = 1; // 修改状态变量
}
else if(uc_ic_state == 1) // 第二次上升沿
{
uwValue2 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_3); // 获取计数值
/* 判断情况 */
if(uwValue2 > uwValue1)
uwDiff = uwValue2 - uwValue1;
else if(uwValue2 < uwValue1)
uwDiff = 0xFFFF - uwValue1 + 1 + uwValue2;
else
Error_Handler();
uwFreq = HAL_RCC_GetPCLK1Freq() / uwDiff; // 计算脉冲频率
uc_ic_state = 0; // 修改状态变量
}
}
}
三、踩坑日记
(1)中断使能
🔅中断使能应该使用函数HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_3);否则中断无法进入;
(2)引脚配置
🔅引脚模式配置为**++带上拉电阻++**(目前没发现不配置有什么问题);
🔅速度配置为Very High;
(3)ADC校准
🔅ADC模块注意:初始化函数里面需要进行++ADC校准++,否则无法测量准确的值‼️