0 前言
🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。
为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是
🚩 基于PID控制的智能平衡车设计与实现
🥇学长这里给一个题目综合评分(每项满分5分)
- 难度系数:4分
- 工作量:4分
- 创新点:3分
1 简介
在一些简单的电子电路设计中,无需使用到高性能且体积庞大的台式示波器。因此,本项目为体积小巧的USB虚拟示波器。在设计过程中,在考虑了成本问题的基础上,尽量实现标准示波器的各项功能,充分发挥芯片性能。参数和特点如下:
- 采样率2MSa/S
- 测量范围±10V
- 交流耦合和直流耦合模式
- 输入阻抗约为1MΩ/100pF
- 最大100倍程控增益
- 电压测量精度2%
- 支持一键自动功能
- 支持FFT模式
- 在快速模式下屏幕刷新率可达20FPS
2 主要器件
- STM32G030主控芯片
- XC6902N331稳压芯片
- 1117线性稳压芯片
- NCS20072运放芯片
- CD4053模拟开关
- CH340串口转USB芯片
3 实现效果
4 设计原理
4.1 硬件部分
总体说明
硬件主要包括将测量信号转换至单片机AD可以采集的模拟信号转换部分、单片机电路USB串口数字电路部分和±3.3V电源部分。
信号转换电路
信号首先经过680KΩ和220KΩ电阻电容构成的衰减电路,使得±10V的信号转化至运放可以处理的±2.5V范围,后一级电压跟随器使得示波器的输入阻抗为900KΩ/90pF。
由单刀双掷开关控制是否经过RC高通滤波器,以实现交直流耦合切换。此处使用继电器是一个更好的方案,但模拟开关CD4053的价格显然更为合适。
两个同相比例放大器构成类似于级联的形式,由模拟开关控制是否进行放大。第一级放大倍数为5倍,第二级放大倍数接近于20倍。从而可以实现放大倍数为1倍、5倍、20倍、100倍的不同增益。相比较由一个运放构成的改变反馈电阻的程控放大电路,该电路对于频率较高的小信号有更好的放大能力。
后续经过电压抬升电路,将正负电压信号转换成单片机AD可以采集的信号,最终的信号将叠加在1.5V直流量上。比较器的正比较端也接入1.5V直流信号,比较器输出端即为同频率的脉冲信号。加入比较器的主要作用是获取波形的频率值,以更快的实现一键自动功能。
此外,图中所用的LM358通用运放在实际调试中改成了性能更好的NCS20072运放。可能由于LM358的压摆率太低,导致在频率超过10KHz时就会出现奇怪的失真波形。
主要原理图
4.2 软件部分
软件包括STM32单片机部分和LabVIEW上位机部分。单片机主要实现串口接收指令、AD采集、波形数据转换和串口发送波形数据的功能。采用STM32Cube预先配置各项参数,在Keil中进一步编程。
单片机参数配置
配置ADC触发方式为定时器触发,这样可以更精准的控制采样间隔。由DMA控制器将波形数据保存至数组,并设置DMA为非循环模式。
配置串口波特率为256000(没有采用常用的115200,因为只有在该波特率下才能够实现20FPS的刷新率)。开启接收中断。由DMA控制串口发送数据,这样在发送串口数据时,不会影响主程序的运行,从而可以提高刷新速度。
在频率测量中,配置定时器为输入捕获模式,将定时器从模式设置为复位,触发源为TI1FP1。由DMA控制器将每次脉冲信号来临时,将定时器的计数值保存至数组。另外,比较器的输出端含有高频脉冲噪声,需要配置定时器输入通道滤波器。
单片机波形采集程序
串口波形数据格式为< Time00640+Fre00100+Amp0+DC+波形数据点>的形式。其中AD触发定时器的计数值Time,最大65535;输入捕获定时器的计数值Fre,最大65535;程控放大器的放大倍数Amp,最大值3;耦合方式用DC和AC表示。
波形采集过程如下。
上位机程序
上位机的主要工作是解析来自串口的字符串,处理波形、计算相关参数并显示。在单片机中不进行浮点运算,所有的波形数据均为AD采集的原始数据,为整数形式。而在上位机中,需要将这些整数转换成电压值。很显然,单片机AD值和输入电压值之间理论上成完全的线性关系,在上位机中需要跟据此线性关系求得实际电压值。在本作品中,有着4种程控增益,也就对应着4个函数关系。
5 部分核心代码
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "wave.h"
#include "msg.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 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_DMA_Init();
MX_ADC1_Init();
MX_TIM1_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
my_UartInit(&UsartData);
my_WaveInit(&WaveData);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
my_Wave(&WaveData);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
RCC_OscInitStruct.PLL.PLLN = 8;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_ADC;
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1;
PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM14 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM14) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics ***** END OF FILE ****/