Proteus 8.15 Professional 多种传感器仿真测试实验

Created: June 2, 2023 10:20 PM
Created by: Luser
Type: MCU

实验环境:

  • Proteus 8.15 Professional
  • Keil MDK V5.38.0.0

工程: 点击下载

实验电路图:

01

1. 工程配置

时钟配置:

07

1.1 OLED

在Cube MX中将PA4、PA5设置为GPIO_OUTPUT模式,修改User_Label为OLED_SCL和OLED_SDA:

02

1.2 HCSR04(超声波)

引脚设置和OLED的一样,先设置模式再修改User Label:

04

1.3 GP2D12(红外线)

红外线传感器采用ADC采样读取电压值再转换为距离值的方式,开启ADC1_CH1:

05

1.4 L298驱动电机正反转

设置两路PWM波对电机进行正反转控制:

06

设置PA8对蜂鸣器进行控制,PA11和PA12对LED进行控制。

1.5 按键

设置按键中断:

08

1.6 串口

使用串口一:

09

1.7 定时器

TIM2用于输入捕获,TIM3用于定时控制:

10
11

生成工程,将.c.h文件移进工程文件夹中后在Keil中将一系列的.c文件添加进工程中:

03

2. 代码编写

2.1 导入头文件

/* USER CODE BEGIN Includes */
#include "sys.h"
#include "delay.h"
#include "iic.h"
#include "oled.h"
#include "bmp.h"
#include "gp2d12.h"
#include "hc_sr04.h"
#include "ds18b20.h"

// 输入捕获模式选择
#define IC_MODE HAL_GPIO_ReadPin(IC_MODE_GPIO_Port, IC_MODE_Pin)

/* USER CODE END Includes */

2.2 开机初始化

  /* USER CODE BEGIN 2 */
  delay_init(72);
  OLED_Init();
  BMP_Init();
  HC_SR04_Init(1);
  //开启定时器三计时
  HAL_TIM_Base_Start_IT(&htim3);
  //开启PWM波
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
  __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, PWM1);
  __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, PWM2);
  //输入捕获
	long long temp = 0;
  if(!IC_MODE){
    HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);  //开启TIM3通道1的捕获中断
    __HAL_TIM_ENABLE_IT(&htim2,TIM_IT_UPDATE);  //开启TIM3的更新中断
    OLED_ShowString(72, 4, "no", 16);
  }

  // DS18B20部分一直无法检测到
  // float ttemp = 0;
  // while (DS18B20_Init())
  // {
  //   Printf("DS18B20 checked failed!!!\r\n");
  //   HAL_Delay(100 - 1);
  // }
  // Printf("DS18B20 checked success!!!\r\n");
	
  // 开机蜂鸣器响三下,LED1闪三下
  // BUZ_3();

  Printf("Experiment Start!\r\n");
  Printf("Lucky Lu\r\n");
  Printf("2023\\06\\02\r\n");
  OLED_ShowString(0, 0, "BMP_Temp:", 16);
  OLED_ShowString(0, 2, "BMP_hPa:", 16);
  OLED_ShowString(0, 4, "Distance:", 16);
  OLED_ShowString(0, 6, "GP_Distance:", 16);
  /* USER CODE END 2 */

2.3 while循环

  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // ttemp = DS18B20_Get_Temp();
    // if(ttemp)
    //   Printf("%d\r\n",ttemp);
		
	// 读取BMP
    BMP_ReadCalibrationData();
    BMP_UncompemstatedToTrue();
	// 读取红外线测距值
    float gp_distance = get_distance();
	// 显示
    OLED_ShowNumber(72, 0, bmp180.Temp / 10, 2, 16);
    OLED_ShowChar(88, 0, '.', 16);
    OLED_ShowNumber(96, 0, bmp180.Temp, 1, 16);
    OLED_ShowNumber(64, 2, bmp180.p / 100, 4, 16);
    OLED_ShowChar(96, 2, '.', 16);
    OLED_ShowNumber(104, 2, bmp180.p, 2, 16);
    OLED_ShowNumber(96, 6, gp_distance, 2, 16);
	// 串口输入中断
    HAL_UART_Receive_IT(&huart1, UART1RxBuffer, 1);
	//红外线测距小于等于20则蜂鸣器响且LED1亮
    if (gp_distance <= 20){
      BUZ_SET;LED1_SET;
    }else{
      BUZ_RESET;LED1_RESET;
    }
	// 读取PB4的值判断TIM2的输入捕获使用方式
	// 0:捕获LED2高电平时间
	// 1:进行超声波测距
    if(IC_MODE){
      float distance = Get_Distance();
      OLED_ShowNumber(72, 4, distance, 2, 16);
    }else if(TIM2CH1_CAP_STA & 0X80){   //完成1次高电平捕获
      temp = TIM2CH1_CAP_STA & 0X3F;
      temp *= 0xffff;        //溢出总时间
      temp += TIM2CH1_CAP_VAL;  //总的高电平时间
      Printf("High level duration: %ld ms\r\n",(int)(temp & 0xFFFFFFFF)/1000);
      TIM2CH1_CAP_STA = 0;    //准备下一次捕获
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

2.4 串口中断、按键中断、输入捕获

/* USER CODE BEGIN 0 */
// 串口
uint8_t UART1RxBuffer[1];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1)
  {
    HAL_UART_Transmit(huart, UART1RxBuffer, 1, 100);
  }
  HAL_UART_Receive_IT(huart, UART1RxBuffer, 1);
}
// 按键中断
int PWM1=500,PWM2=0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
  switch(GPIO_Pin){
    case GPIO_PIN_0:PWM1=500;PWM2=0;break;//正转模式
    case GPIO_PIN_1:PWM1=0;PWM2=500;break;//反转模式
    case GPIO_PIN_2://加速
      PWM2==0?PWM1+=100:(PWM2+=100);
      if(PWM1>=1000)PWM1=1000;
      if(PWM2>=1000)PWM2=1000;
      break;
    case GPIO_PIN_3://减速
      PWM2==0?PWM1-=100:(PWM2-=100);
      if(PWM1<=0)PWM1=0;
      if(PWM2<=0)PWM2=0;
      break;
    case IC_MODE_Pin:
      HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);  //开启TIM2通道1的捕获中断
      __HAL_TIM_ENABLE_IT(&htim2,TIM_IT_UPDATE);  //开启TIM2的更新中断
      OLED_ShowString(72, 4, "no", 16);
      break;
  }
  __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, PWM1);
  __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, PWM2);
}

// 输入捕获
/* TIM2CH1_CAP_STA 各数据位说明
** bit7   捕获完成标志
** bit6   捕获到高电平标志
** bit5~0 捕获高电平后定时器溢出的次数*/
uint8_t TIM2CH1_CAP_STA = 0;
uint16_t TIM2CH1_CAP_VAL;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim == &htim2)
  {
    if((TIM2CH1_CAP_STA & 0X80) == 0){  //还未成功捕获
      if(TIM2CH1_CAP_STA & 0X40){   //已经捕获到高电平
        if((TIM2CH1_CAP_STA & 0X3F) == 0X3F){ //高电平时间太长了
          TIM2CH1_CAP_STA |= 0X80;      //标记为完成一次捕获
          TIM2CH1_CAP_VAL = 0XFFFF;     //计数器值
        }
        else
          TIM2CH1_CAP_STA++;    //溢出次数+1      
      } 
    }
  }
  if (htim == &htim3)
  {
    LED2_TOG;
  }
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
  if (htim == &htim2){
    if((TIM2CH1_CAP_STA & 0X80) == 0){  //还未成功捕获
      if(TIM2CH1_CAP_STA & 0X40){   //捕获到上升沿后条件为真
        TIM2CH1_CAP_STA |= 0X80;  //标记为完成一次高电平捕获
        TIM2CH1_CAP_VAL = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);  //设置上升沿捕获
        TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1);  //清除原来的设置  
        TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);  //设置上升沿捕获
      }
      else{
        TIM2CH1_CAP_STA = 0;
        TIM2CH1_CAP_VAL = 0;
        TIM2CH1_CAP_STA |= 0X40;  //标记捕获到上升沿
        __HAL_TIM_DISABLE(&htim2);  //关闭定时器
        __HAL_TIM_SET_COUNTER(&htim2,0);   //计数器值清零
        TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1);  //清除原来的设置      
        TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING); //设置下降沿捕获
        __HAL_TIM_ENABLE(&htim2); //使能定时器 
      } 
    }
  }
} 
/* USER CODE END 0 */

3. 函数说明

3.1 Printf

实验中使用的串口输出函数Printf()定义在usart.c中,定义如下:

/* USER CODE BEGIN 1 */

char uart_buffer[UART_BUFFER_SIZE];
int uart_buffer_pos = 0;

void Printf(const char *format, ...)
{
  char buffer[100];
  va_list args;
  va_start(args, format);
  vsnprintf(buffer, sizeof(buffer), format, args);
  HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY);
  va_end(args);
}

其在usart.h中被调用:

/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void Printf(const char *format, ...);

3.2 get_distance(GP2D12)

该函数位于gp2d12.c中,其调用的ADC相关函数位于adc.c中:

// gp2d12.h
#ifndef __GP2D12_H
#define __GP2D12_H

float vol2dis(float voltage);
float get_distance(void);

#endif	


// gp2d12.c
#include "gp2d12.h"
#include "adc.h"
#include  "math.h"//使用pow函数

float vol2dis(float voltage) { return 28.69643063188021 * pow(voltage, -1.153681035551901) - 0.15368103480916; }

float get_distance(){
    uint16_t adcx;
    adcx = Get_Adc_Average(ADC_CHANNEL_1, 20);
    float voltage = (float)adcx * (5.0 / 4096);
    float dp_distance = vol2dis(voltage);
}
//adc.h
/* USER CODE BEGIN Prototypes */
uint16_t Get_Adc(uint32_t ch);          
uint16_t Get_Adc_Average(uint32_t ch,uint8_t times);
/* USER CODE END Prototypes */

//adc.c
/* USER CODE BEGIN 1 */

uint16_t Get_Adc(uint32_t ch)   
{
    ADC_ChannelConfTypeDef ADC1_ChanConf;
    
    ADC1_ChanConf.Channel=ch;                                  
    ADC1_ChanConf.Rank=1;                                       
    ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_239CYCLES_5;        
    // ADC1_ChanConf.Offset=0;                 
    HAL_ADC_ConfigChannel(&hadc1,&ADC1_ChanConf);        
  
    HAL_ADC_Start(&hadc1);                              
  
    HAL_ADC_PollForConversion(&hadc1,10);                
 
  return (uint16_t)HAL_ADC_GetValue(&hadc1);          
}
uint16_t Get_Adc_Average(uint32_t ch,uint8_t times)
{
  uint32_t temp_val=0;
  uint8_t t;
  for(t=0;t<times;t++)
  {
    temp_val+=Get_Adc(ch);
    HAL_Delay(5);
  }
  return temp_val/times;
} 
/* USER CODE END 1 */

3.2.1 vol2dis

vol2dis函数的作用是将ADC获取到的电压值转换为距离值,是根据Proteus仿真采集到的数据使用线性函数拟合而来,具体的python程序如下:

from pylab import *
from scipy import optimize
mpl.rcParams['font.sans-serif'] = ['SimHei']

#需要拟合的函数
def f_1(x, A, B, C):
    return A * pow(x,C) + B

x_axis_data = [2.35, 2.17, 2.02, 1.89, 1.78, 1.68, 1.59, 1.51, 1.44, 1.38, 1.33, 1.27, 1.22, 1.18, 1.14, 1.10,
               0.67, 0.66, 0.64, 0.63, 0.62, 0.61, 0.60, 0.59, 0.58, 0.57, 0.56, 
                0.41, 0.41, 0.42, 0.42, 0.43, 0.43, 0.43, 0.44, 0.45, 0.45, 0.46]
y_axis_data = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0,
                45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0,
                  80.0, 79.0, 78.0, 77.0, 76.0, 75.0, 74.0, 73.0, 72.0, 71.0, 70.0]

plt.figure('test')
plt.title('GP2D12输出电压与距离关系')
plt.scatter(x_axis_data, y_axis_data, 10, "red")

A1, B1, C1 = optimize.curve_fit(f_1, x_axis_data, y_axis_data)[0]
x1 = np.arange(0, 100, 0.01)#建立一串0~100的一维数据,步长为0.01
y1 = A1 * pow(x1,C1) + B1
print("A="+str(A1))
print("B="+str(B1))
print("C="+str(C1))
plt.plot(x1, y1, 6, "blue")

for x, y in zip(x_axis_data, y_axis_data):
    plt.text(x, y+0.3, '%.0f' % y, ha='center', va='bottom', fontsize=10.5)


plt.xlim(0, 3)
plt.ylim(0, 80)
plt.xlabel('电压')
plt.ylabel('距离')

# plt.savefig('拟合直线.jpg')  # 保存该图片
plt.show()

OutPut:

y1 = A1 * pow(x1,C1) + B1
A=28.69643063188021
B=-0.7567939194670654
C=-1.15368103480916
12