Created: June 2, 2023 10:20 PM
Created by: Luser
Type: MCU
实验环境:
- Proteus 8.15 Professional
- Keil MDK V5.38.0.0
工程: 点击下载
实验电路图:
1. 工程配置
时钟配置:
1.1 OLED
在Cube MX中将PA4、PA5设置为GPIO_OUTPUT模式,修改User_Label为OLED_SCL和OLED_SDA:
1.2 HCSR04(超声波)
引脚设置和OLED的一样,先设置模式再修改User Label:
1.3 GP2D12(红外线)
红外线传感器采用ADC采样读取电压值再转换为距离值的方式,开启ADC1_CH1:
1.4 L298驱动电机正反转
设置两路PWM波对电机进行正反转控制:
设置PA8对蜂鸣器进行控制,PA11和PA12对LED进行控制。
1.5 按键
设置按键中断:
1.6 串口
使用串口一:
1.7 定时器
TIM2用于输入捕获,TIM3用于定时控制:
生成工程,将.c
和.h
文件移进工程文件夹中后在Keil中将一系列的.c
文件添加进工程中:
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