적응형 알고리즘과 증분형 PID 알고리즘을 기반으로 한 헬리콥터 시뮬레이션 시스템


적응형 알고리즘과 증분형 PID 알고리즘을 기반으로 한 헬리콥터 시뮬레이션 시스템

제어 시스템 하드웨어

시뮬레이션된 헬리콥터 수직 리프트 제어 시스템은 주로 C8051F020 마이크로 컨트롤러, 버튼 및 디스플레이 모듈, 헬리콥터 수직 리프트 시뮬레이션 개체로 구성됩니다.

마이크로컨트롤러 시스템

디스플레이 기능은 LCD와 디지털 튜브에 의해 구현됩니다.LCD 화면과 디지털 튜브는 디스플레이 제어 메인 메뉴 인터페이스, 홀 전압의 현재 값 및 변화 곡선을 각각 구현합니다.버튼은 LCD 디스플레이를 전환하는 데 사용됩니다. 내용, PID 매개변수 설정, 홀 전압 설정 값 및 기타 기능 증가 및 감소.

스크린샷 2022-05-30 23.19.17

센서 시스템 소개

SS49E 선형 홀 센서는 작은 크기와 다양한 용도의 특성을 가지고 있습니다. SS49E는 영구자석이나 전자석을 사용하여 작동할 수 있으며, 전원전압은 선형 출력을 제어하고 자기장 강도에 따라 선형 변화를 만들 수 있습니다. SS49E는 내부에 저잡음 출력 회로를 통합하여 외부 필터가 필요하지 않습니다. 이 장치에는 온도 안정성과 정확성을 높이기 위해 박막 저항기가 통합되어 있습니다. SS49E의 동작전압은 4.5V~6V이다.

헬리콥터 시뮬레이션 시스템 소개

헬리콥터 수직 리프트 시뮬레이션 객체 시스템의 개략도는 그림 2-2에 표시되어 있으며 실제 객체는 그림 2-3에 표시되며 인터페이스 설명은 표 2-1에 표시됩니다.

스크린샷 2022-05-30 23.22.03

시스템 모듈 소개

ADC 샘플링 시스템

ADC 샘플링 시스템은 전체 프로젝트에서 가장 중요한 부분이기 때문에 여기서는 Stm32F103과 C8051F020 마이크로 컨트롤러의 차이점을 통해 제어에서 더욱 발전된 시스템의 역할을 보여주고 싶습니다.

STM32

ADC 구현 아이디어:

먼저 STM32F103 시리즈에 포함된 3개의 고정밀 ADC를 이해해야 합니다.이미지-20220119132911923

분명히 여기에서 f adc 작동 주파수는 14MHZ이고 시스템 클럭은 72MHZ임을 알 수 있습니다 . 분명히 분할 계수를 6으로 설정해야 합니다. 나중에 ADC를 처리할 때 CPU가 더 많은 작업을 수행하기를 원하기 때문에 당연히 폴링을 사용해서는 안 됩니다. 타이머 인터럽트가 ADC 샘플링을 트리거하고 DMA가 ADC 레지스터에서 코드를 지속적으로 이동하기를 바라기 때문입니다. 지정된 배열에.


CUBEMX 구성
ADC1 및 ADC3 구성
그래픽 초기화

이미지-20220119171906385

ADC3의 구성을 예로 들어 보겠습니다(ADC3의 채널 8은 PF10에 해당).

  • ADC 구성에서 타이머 8에 의해 트리거되도록 외부 트리거 제공 소스, 샘플링 시간을 7.5Cycles, 연속 변환 모드를 ENABLE로 수정해야 합니다.
  • DMA 구성에서 Data Width를 Half World로 변경 Mode를 Loop로 변경 DMA 전송이 멈추지 않도록 하는 것이 매우 중요함 작성자는 처음에 NORMAL로 기본 설정했는데 DEBug에서 발견함 매번 한 번만 전송이 완료되면 Peripheral에서 Memory로 방향이 변경됩니다.
타이머 타이머 인터페이스
이미지-20220119172725216

이는 0.x초마다 정확한 샘플링을 달성하는 데 필수적입니다. DMA 기술만 사용하면 ADC가 계속해서 높은 주파수로 샘플링하기 때문입니다. 저자는 특히 실제로는 이 속도가 너무 빠르고 제어할 수 없다는 것을 발견했습니다. , 우리는 또한 3개의 ADC 사이의 관계를 조정해야 합니다. 3개의 ADC가 모두 동시에 샘플링한다면 문제는 분명히 아주 잘 해결될 수 있습니다.

  • 주파수 분할 계수가 1999로 변경되고 트리거 이벤트 선택이 자동 업데이트로 변경되며 중단될 때마다 다시 카운트를 시작합니다.

ADC2 구성
그래픽 초기화
이미지-20220119191932844

ADC2에는 DMA 시스템이 없기 때문에 인터럽트를 발생시키기 위해서는 별도의 Timer3를 사용해야 하는데 여기서는 약간의 트릭을 사용한다. TIMER3에 의해 트리거되도록 ADC2를 직접 설정하면 매우 이상한 현상이 발생하고 쓰기가 중단되지 않습니다. 소프트웨어 인터럽트 트리거링을 직접 사용한 다음 두 개의 인터럽트 콜백 함수를 작성하여 판단하는 것이 더 낫습니다. 그 이유는 HAL 라이브러리를 호출한 후 프로그래머가 부분적으로 기본 제어를 상실하기 때문입니다.

  • 연속 변환 모드: 비활성화로 변경
  • Samping Time (DMA가 없기 때문에 빠른 대응 능력을 잃게 되므로 Samping Time을 직접 늘리는 것이 좋습니다.) : 41.5 Cycles
타이머 타이머 인터페이스

이미지-20220119192014751이미지-20220119192231707

  • TIMER3의 초기 설정은 TIMER8과 동일합니다.
인터럽트 초기화

이미지-20220119204400931

  • 인터럽트 초기화에서는 (선점 우선순위, 하위 포트)에 값 (0, 0)만 할당하면 됩니다.

  • ADC 샘플링이 진행 중이기 때문에 ADC에 너무 높은 우선 순위를 부여할 필요는 없습니다. 직렬 포트의 우선 순위에 주의해야 합니다. 직렬 포트가 데이터를 다시 보낼 때 WIFI 직렬 포트 우선 순위를 보냅니다 >> 디버그 우선 순위를 보냅니다. >> ADC 개인적으로 직렬 포트를 방해하는 것은 미친 행동이라고 생각합니다. 이 오류는 한때 오후 내내 저를 괴롭혔고 디버깅을 해도 결과를 얻을 수조차 없었습니다.


코드 소스코드(관심없으시면 건너뛰셔도 됩니다)
ADC.c - ADC.h

코드의 본체는 CUBEMX로 구성되어 있으며, 명시된 부분을 이해하고 수정이 필요하며, 특히 수정된 부분에 대해서는 추후에 자세한 설명을 드렸습니다.

(ADC1 ADC3) (ADC2)에는 각각 두 가지 구성 방법이 있습니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    adc.c
  * @brief   This file provides code for the configuration
  *          of the ADC instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "adc.h"

/* USER CODE BEGIN 0 */
__IO uint16_t ADC_ConvertedValue1;
__IO uint16_t ADC_ConvertedValue2;
__IO uint16_t ADC_ConvertedValue3;
#include "tim.h"
/* USER CODE END 0 */

ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;
ADC_HandleTypeDef hadc3;
DMA_HandleTypeDef hdma_adc1;
DMA_HandleTypeDef hdma_adc3;

/* ADC1 init function */
void MX_ADC1_Init(void)
{
    
    

  /* USER CODE BEGIN ADC1_Init 0 */
	__HAL_RCC_DMA1_CLK_ENABLE();
  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {
    
    0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */
  /** Common config
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /** Enable or disable the remapping of ADC1_ETRGREG:
  * ADC1 External Event regular conversion is connected to TIM8 TRG0
  */
  __HAL_AFIO_REMAP_ADC1_ETRGREG_ENABLE();
  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */
	//HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_ConvertedValue1, 1);
  /* USER CODE END ADC1_Init 2 */
}
/* ADC2 init function */
void MX_ADC2_Init(void)
{
    
    

  /* USER CODE BEGIN ADC2_Init 0 */
  /* USER CODE END ADC2_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {
    
    0};

  /* USER CODE BEGIN ADC2_Init 1 */

  /* USER CODE END ADC2_Init 1 */
  /** Common config
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc2.Init.ContinuousConvMode = DISABLE;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_41CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */

  /* USER CODE END ADC2_Init 2 */

}
/* ADC3 init function */
void MX_ADC3_Init(void)
{
    
    

  /* USER CODE BEGIN ADC3_Init 0 */
	__HAL_RCC_DMA2_CLK_ENABLE();
  /* USER CODE END ADC3_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {
    
    0};

  /* USER CODE BEGIN ADC3_Init 1 */

  /* USER CODE END ADC3_Init 1 */
  /** Common config
  */
  hadc3.Instance = ADC3;
  hadc3.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc3.Init.ContinuousConvMode = ENABLE;
  hadc3.Init.DiscontinuousConvMode = DISABLE;
  hadc3.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO;
  hadc3.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc3.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc3) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_8;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /* USER CODE BEGIN ADC3_Init 2 */
	//HAL_ADC_Start_DMA(&hadc3, (uint32_t*)&ADC_ConvertedValue3, 1);
  /* USER CODE END ADC3_Init 2 */

}

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
    
    

  GPIO_InitTypeDef GPIO_InitStruct = {
    
    0};
  if(adcHandle->Instance==ADC1)
  {
    
    
  /* USER CODE BEGIN ADC1_MspInit 0 */

  /* USER CODE END ADC1_MspInit 0 */
    /* ADC1 clock enable */
    __HAL_RCC_ADC1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**ADC1 GPIO Configuration
    PA4     ------> ADC1_IN4
    */
    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA1_Channel1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_MEDIUM;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
    
    
      Error_Handler();
    }

    __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);

    /* ADC1 interrupt Init */
    HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
  /* USER CODE BEGIN ADC1_MspInit 1 */

  /* USER CODE END ADC1_MspInit 1 */
  }
  else if(adcHandle->Instance==ADC2)
  {
    
    
  /* USER CODE BEGIN ADC2_MspInit 0 */

  /* USER CODE END ADC2_MspInit 0 */
    /* ADC2 clock enable */
    __HAL_RCC_ADC2_CLK_ENABLE();

    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**ADC2 GPIO Configuration
    PC2     ------> ADC2_IN12
    PA2     ------> ADC2_IN2
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* ADC2 interrupt Init */
    HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
  /* USER CODE BEGIN ADC2_MspInit 1 */

  /* USER CODE END ADC2_MspInit 1 */
  }
  else if(adcHandle->Instance==ADC3)
  {
    
    
  /* USER CODE BEGIN ADC3_MspInit 0 */

  /* USER CODE END ADC3_MspInit 0 */
    /* ADC3 clock enable */
    __HAL_RCC_ADC3_CLK_ENABLE();

    __HAL_RCC_GPIOF_CLK_ENABLE();
    /**ADC3 GPIO Configuration
    PF10     ------> ADC3_IN8
    */
    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    /* ADC3 DMA Init */
    /* ADC3 Init */
    hdma_adc3.Instance = DMA2_Channel5;
    hdma_adc3.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc3.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc3.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc3.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc3.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc3.Init.Mode = DMA_CIRCULAR;
    hdma_adc3.Init.Priority = DMA_PRIORITY_MEDIUM;
    if (HAL_DMA_Init(&hdma_adc3) != HAL_OK)
    {
    
    
      Error_Handler();
    }

    __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc3);

  /* USER CODE BEGIN ADC3_MspInit 1 */

  /* USER CODE END ADC3_MspInit 1 */
  }
}

void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
    
    

  if(adcHandle->Instance==ADC1)
  {
    
    
  /* USER CODE BEGIN ADC1_MspDeInit 0 */

  /* USER CODE END ADC1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_ADC1_CLK_DISABLE();

    /**ADC1 GPIO Configuration
    PA4     ------> ADC1_IN4
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_4);

    /* ADC1 DMA DeInit */
    HAL_DMA_DeInit(adcHandle->DMA_Handle);

    /* ADC1 interrupt Deinit */
  /* USER CODE BEGIN ADC1:ADC1_2_IRQn disable */
    /**
    * Uncomment the line below to disable the "ADC1_2_IRQn" interrupt
    * Be aware, disabling shared interrupt may affect other IPs
    */
    /* HAL_NVIC_DisableIRQ(ADC1_2_IRQn); */
  /* USER CODE END ADC1:ADC1_2_IRQn disable */

  /* USER CODE BEGIN ADC1_MspDeInit 1 */

  /* USER CODE END ADC1_MspDeInit 1 */
  }
  else if(adcHandle->Instance==ADC2)
  {
    
    
  /* USER CODE BEGIN ADC2_MspDeInit 0 */

  /* USER CODE END ADC2_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_ADC2_CLK_DISABLE();

    /**ADC2 GPIO Configuration
    PC2     ------> ADC2_IN12
    PA2     ------> ADC2_IN2
    */
    HAL_GPIO_DeInit(GPIOC, GPIO_PIN_2);

    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_2);

    /* ADC2 interrupt Deinit */
  /* USER CODE BEGIN ADC2:ADC1_2_IRQn disable */
    /**
    * Uncomment the line below to disable the "ADC1_2_IRQn" interrupt
    * Be aware, disabling shared interrupt may affect other IPs
    */
    /* HAL_NVIC_DisableIRQ(ADC1_2_IRQn); */
  /* USER CODE END ADC2:ADC1_2_IRQn disable */

  /* USER CODE BEGIN ADC2_MspDeInit 1 */

  /* USER CODE END ADC2_MspDeInit 1 */
  }
  else if(adcHandle->Instance==ADC3)
  {
    
    
  /* USER CODE BEGIN ADC3_MspDeInit 0 */

  /* USER CODE END ADC3_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_ADC3_CLK_DISABLE();

    /**ADC3 GPIO Configuration
    PF10     ------> ADC3_IN8
    */
    HAL_GPIO_DeInit(GPIOF, GPIO_PIN_10);

    /* ADC3 DMA DeInit */
    HAL_DMA_DeInit(adcHandle->DMA_Handle);
  /* USER CODE BEGIN ADC3_MspDeInit 1 */

  /* USER CODE END ADC3_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时器中断回调
{
    
    
    HAL_ADC_Start_IT(&hadc2); 
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle){
    
    
		HAL_ADC_Stop_IT(&hadc2);    
    HAL_TIM_Base_Stop_IT(&htim3);
		ADC_ConvertedValue2=HAL_ADC_GetValue(&hadc2);
    HAL_TIM_Base_Start_IT(&htim3); 
}

ADC3 인터럽트 기능 설명

여기서 주의할 점은 인터럽트에서 시계를 껐다가 처리한 후에 다시 켜야 한다는 것입니다. 얼마나 오랫동안 처리될지 아무도 모르기 때문에 좋은 습관입니다. 꺼지지 않으면 다음 인터럽트가 발생합니다. 프로그램 예외가 발생할 수 있습니다. 블러디 레슨

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时器中断回调
{
    
    
    HAL_ADC_Start_IT(&hadc2); //定时器中断里面开启ADC中断转换,1ms开启一次采集    
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle){
    
    
		HAL_ADC_Stop_IT(&hadc2);    //关闭adc2
    HAL_TIM_Base_Stop_IT(&htim3);	//关闭时钟Timer2
		ADC_ConvertedValue2=HAL_ADC_GetValue(&hadc2);//传递值
    HAL_TIM_Base_Start_IT(&htim3);  //打开时钟Timer2
}
ADC1,2 초기화 지침

DMA 전송을 사용하려면 첫 번째 문장에서 DMA 클록을 활성화해야 하지만 HAL 라이브러리에서 생성된 DMA_init를 사용하지 않으므로 특정 타이밍 논리 혼란이 있습니다.

void MX_ADC1_Init(void)
{
    
    

  /* USER CODE BEGIN ADC1_Init 0 */
	__HAL_RCC_DMA1_CLK_ENABLE();
  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {
    
    0};
  ...
  }
  
  
  void MX_ADC3_Init(void)
{
    
    

  /* USER CODE BEGIN ADC3_Init 0 */
	__HAL_RCC_DMA2_CLK_ENABLE();
  /* USER CODE END ADC3_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {
    
    0};

  /* USER CODE BEGIN ADC3_Init 1 */
  ...}
  
시험 결과
ADC1
이미지-20220119214943265
ADC2
이미지-20220119214959132
ADC3
이미지-20220119215016871
하드웨어 설명(ADC1,2,3 공통)
ADC 하드웨어 연결

ADC 샘플링은 0~3.3v 사이의 전압만 수집할 수 있으므로 하드웨어 설계 시 센서의 전압 분배를 고려해야 하며, 5V를 3.3.v로 분배하려면 2개의 칩 저항기를 사용하는 것이 좋습니다. ADC의 입력 저항 RAIN _

이미지-20220119215036440

이 임베디드 시스템은 Ts = 55.5를 채택합니다.

51ADC

DA 변환

DAC를 출력하는 방법에는 여러 가지가 있습니다. 타이머 2 오버플로를 통해 DAC 출력을 업데이트하도록 선택한 경우 C8051F02 마이크로 컨트롤러의 DAC0은 12비트 정밀 디지털-아날로그 변환기이므로 16비트일 때 다른 데이터가 생성됩니다. 저장하는 데 사용됩니다. DAC0을 설정할 때 초기화 부분에서 DAC0의 업데이트 모드와 정렬 모드를 먼저 설정한 후 DAC0의 기준 전압(보통 내부 기준 전압)을 선택한 후 데이터 레지스터를 0으로 클리어하여 초기화를 완료한다. DAC0의 출력 제어를 위해 타이머 2 인터럽트가 오버플로되면 출력될 전압에 해당하는 값이 상위 및 하위 데이터 레지스터로 전송됩니다.

광고 전환

ADC0은 12비트 정밀 아날로그-디지털 변환기로, DAC0과 마찬가지로 타이밍을 제어하기 위한 타이머도 필요합니다. 차이점은 ADC0은 변환을 시작하기 위해 타이머 오버플로의 인터럽트에 의존하지 않지만 타이머가 오버플로될 때 인터럽트를 트리거하지 않고 변환이 완료되면 자체 인터럽트를 트리거하여 마이크로프로세서가 데이터를 읽을 수 있다는 것입니다. 타이머 3의 초기화는 다른 타이머의 초기화와 약간 다르며 타이머 오버플로 인터럽트가 필요하지 않으므로 초기화 중에는 타이머 인터럽트를 끄고 타이밍을 즉시 시작해야 합니다.
(1)ADC0 초기화

ADC0의 초기 설정은 DAC0의 초기 설정보다 조금 더 복잡합니다. 타이머 3 오버플로 시작, 데이터 정렬 형식 및 기준 전압을 설정하는 것 외에도 ADC0의 샘플링 채널, ADC0의 SAR 클록 주파수, ADC0의 이득을 설정하고 ADC0 끝에 대한 인터럽트 요청을 활성화해야 합니다. 변환. 이러한 매개변수는 필요에 따라 수정할 수 있습니다. ADC0의 변환 결과를 읽을 때 인터럽트 핸들러에서 먼저 인터럽트를 수동 클리어로 트리거한 다음 버퍼링 아이디어를 사용하여 ADC0의 각 채널 값을 버퍼로 읽어들이고 지속적으로 수행해야 합니다. 채널을 변경하면 다른 채널의 값을 읽는 것부터 시작할 수 있어 타이밍이 프로그램에 미치는 영향을 피할 수 있습니다. ADC0의 샘플링 주파수가 프로그램에서 사용하는 주파수보다 크면 버퍼를 계속해서 덮어쓰므로 최신 샘플링 값을 사용하는 데이터가 보장됩니다.ADC0의 기능이 낭비되더라도 문제가 발생하지는 않습니다. 프로그램. 그러나 ADC0의 샘플링 주파수가 샘플링된 값을 사용하는 시스템의 주파수보다 낮은 경우 시스템은 동일한 샘플링된 값을 반복해서 읽는 것을 방지하기 위해 기다려야 하며 다음과 같은 방법을 사용할 수 있습니다. 버퍼를 불가능한 값(예: 0xffff)으로 설정한 경우, 읽은 값이 비정상인 한 메인 프로그램은 정상적인 값을 얻을 때까지 기다립니다.

(2) ADC0 데이터 처리
ADC0에서 얻은 데이터를 처리할 때는 ADC0에서 얻은 12비트 데이터를 10진수 데이터로 변환해야 합니다. 동시에, 물리적 환경의 영향으로 인해 ADC0에서 읽은 값은 그다지 정확하지 않으며 선형성조차 보장할 수 없습니다. 계산 시 ADC0의 샘플링 값을 수정해야 합니다. 수정 방법은 ADC0이 선형이라고 가정하는 것입니다. ADC0의 입력 단자는 각각 1V 및 5V 전압에 연결되어 있으며, ADC0의 해당 값을 구하고 정규화 처리를 수행합니다. 입력 전압을 더 정확하게 읽으십시오. 예를 들어, 시뮬레이션된 헬리콥터 수직 리프트 제어 시스템에서 ADC0의 입력 전압은 외부 증폭기 회로에 따라 적절하게 감소하고 12비트 이진수로 변환되어 ADC0의 데이터 레지스터로 전송되어야 합니다.

암호
void Do(void) {
    
    
    if ((timer1_value & 0x0007) == 0x0001) {
    
         
        if (channel == 1) 	 {
    
    
            // 从 ADC0 AIN1 取得 10 位 16 进制数 vadc
            vadc = ADC_ValueReturn(channel);
            // 将 vadc 转化为 10 进制数进行计算
            vadc_dec = (unsigned long int)vadc * (unsigned long int)vref / 4096;
						PID_contrl(&tmp_pid,vadc_dec);
						vdac_dec=vadc_dec+tmp_pid.result;
						// 将 10 进制数 vdac_dec 转化为 16 进制数
            vdac = (unsigned long int)vdac_dec * 4096 / (unsigned long int)vref;
            // 从 ADC0 输出 10 位 16 进制数 vdac
            DAC0_Output(vdac);
					//调节目标霍尔电压值
					if(KEY_FLAG==1){
    
    
						tmp_pid.setpoint-=100; //用于控制目标电压
						KEY_FLAG=0;}
					else{
    
    
						if(KEY_FLAG==2){
    
    
						tmp_pid.setpoint+=100; //用于控制目标电压
						KEY_FLAG=0;}
          }
      }
		}
	}

제어 알고리즘

적응 제어 알고리즘
적응제어 알고리즘의 원리

적응과정은 끊임없이 목표에 접근하는 과정이다. 그것이 따르는 경로는 적응형 알고리즘이라는 수학적 모델로 표현됩니다. 일반적으로 Gradient 기반 알고리즘이 사용되며, 그 중 최소 평균 제곱 오차 알고리즘이 특히 일반적으로 사용됩니다. 적응형 알고리즘은 하드웨어(처리 회로) 또는 소프트웨어(프로그램 제어)의 두 가지 방법으로 구현될 수 있습니다. 전자는 알고리즘의 수학적 모델을 기반으로 회로를 설계하는 것이고, 후자는 알고리즘의 수학적 모델을 프로그램으로 컴파일하여 컴퓨터로 구현하는 것이다. 알고리즘에는 여러 종류가 있으며, 처리 시스템의 성능 품질과 타당성을 결정하므로 그 선택이 매우 중요합니다.

적응제어 알고리즘 코드
void FuzzyPID();	//初始化PID参数
float FuzzyPIDcontroller(float e_max, float e_min, float ec_max, float ec_min, float kp_max, float kp_min, float error, float error_c, float ki_max, float ki_min,float kd_max, float kd_min,float error_pre, float error_ppre);  //模糊PID控制实现函数
float Quantization(float maximum, float minimum, float x);	//误差 error 和误差变化 error_c 映射到论域中的函数
void Get_grad_membership(float error, float error_c);	计算输入e与de/dt隶属度
void GetSumGrad();// 获取输出增量 △kp、△ki、△kd 的总隶属度
void GetOUT();  // 计算输出增量 △kp、△ki、△kd 对应论域值
float Inverse_quantization(float maximum, float minimum, float qvalues);  //去模糊化


const int  num_area = 8; //划分区域个数
float e_membership_values[7] = {
    
    -3,-2,-1,0,1,2,3}; //输入e的隶属值
float ec_membership_values[7] = {
    
     -3,-2,-1,0,1,2,3 };//输入de/dt的隶属值
float kp_menbership_values[7] = {
    
     -3,-2,-1,0,1,2,3 };//输出增量kp的隶属值
float ki_menbership_values[7] = {
    
     -3,-2,-1,0,1,2,3 }; //输出增量ki的隶属值
float kd_menbership_values[7] = {
    
     -3,-2,-1,0,1,2,3 };  //输出增量kd的隶属值

float kp;                       //PID参数kp
float ki;                       //PID参数ki
float kd;                       //PID参数kd
float qdetail_kp;               //增量kp对应论域中的值
float qdetail_ki;               //增量ki对应论域中的值
float qdetail_kd;               //增量kd对应论域中的值
float detail_kp;                //输出增量kp
float detail_ki;                //输出增量ki
float detail_kd;                //输出增量kd
float qerror;                    //输入e对应论域中的值
float qerror_c;                  //输入de/dt对应论域中的值             
float e_gradmembership[2];      //输入e的隶属度
float ec_gradmembership[2];     //输入de/dt的隶属度
int e_grad_index[2];            //输入e隶属度在规则表的索引
int ec_grad_index[2];           //输入de/dt隶属度在规则表的索引
float KpgradSums[7] = {
    
     0,0,0,0,0,0,0 };   //输出增量kp总的隶属度
float KigradSums[7] = {
    
     0,0,0,0,0,0,0 };   //输出增量ki总的隶属度
float KdgradSums[7] = {
    
     0,0,0,0,0,0,0 };   //输出增量kd总的隶属度

float e_max = 150;      //误差最大值
float e_min = -150;    //误差最小值
float ec_max = 300;     //误差变化最大值
float ec_min = -300;    //误差变化最小值
float kp_max = 50;       //比例系数 kp 上限值
float kp_min = -50;    //比例系数 kp 下限值
float ki_max = 0.1;     //积分系数 ki 上限值
float ki_min = -0.1;    //积分系数 ki 下限值
float kd_max = 0.01;    //微分系数 kd 上限值
float kd_min = -0.01;   //微分系数 kd 下限值
float error;        //误差值
float error_c;      //误差变化值
float error_pre = 0;    //上一次误差值
float error_ppre = 0;   //上上次误差值


int  Kp_rule_list[7][7] = {
    
     {
    
    PB,PB,PM,PM,PS,ZO,ZO},        //kp规则表
			       {
    
    PB,PB,PM,PS,PS,ZO,NS},
			       {
    
    PM,PM,PM,PS,ZO,NS,NS},
			       {
    
    PM,PM,PS,ZO,NS,NM,NM},
			       {
    
    PS,PS,ZO,NS,NS,NM,NM},
			       {
    
    PS,ZO,NS,NM,NM,NM,NB},
			       {
    
    ZO,ZO,NM,NM,NM,NB,NB} };

int  Ki_rule_list[7][7] = {
    
     {
    
    NB,NB,NM,NM,NS,ZO,ZO},     //ki规则表
			      {
    
    NB,NB,NM,NS,NS,ZO,ZO},
			      {
    
    NB,NM,NS,NS,ZO,PS,PS},
			      {
    
    NM,NM,NS,ZO,PS,PM,PM},
			      {
    
    NM,NS,ZO,PS,PS,PM,PB},
			      {
    
    ZO,ZO,PS,PS,PM,PB,PB},
			      {
    
    ZO,ZO,PS,PM,PM,PB,PB} };

	int  Kd_rule_list[7][7] = {
    
     {
    
    PS,NS,NB,NB,NB,NM,PS},     //kd规则表
			       {
    
    PS,NS,NB,NM,NM,NS,ZO},
			       {
    
    ZO,NS,NM,NM,NS,NS,ZO},
			       {
    
    ZO,NS,NS,NS,NS,NS,ZO},
			       {
    
    ZO,ZO,ZO,ZO,ZO,ZO,ZO},
		                       {
    
    PB,NS,PS,PS,PS,PS,PB},
		                       {
    
    PB,PM,PM,PM,PS,PS,PB} };

void FuzzyPID()  //参数初始化
{
    
    
	kp = 0;
	ki = 0;
	kd = 0;
	qdetail_kp = 0;
	qdetail_ki = 0;
	qdetail_kd = 0;
}

//模糊PID控制实现函数
float FuzzyPIDcontroller(float e_max, float e_min, float ec_max, float ec_min, float kp_max, float kp_min, float error, float error_c,float ki_max,float ki_min,float kd_max,float kd_min,float error_pre,float error_ppre)
{
    
    
	float output;
	qerror = Quantization(e_max, e_min, error);	   //将 误差 error 映射到论域中
	qerror_c = Quantization(ec_max, ec_min, error_c);	  //将误差变化 error_c 映射到论域中
	Get_grad_membership(qerror, qerror_c);	//计算误差 error 和误差变化 error_c 的隶属度
	GetSumGrad();	//计算输出增量 △kp、△ki、△kd 的总隶属度
	GetOUT();		// 计算输出增量 △kp、△ki、△kd 对应论域值
	detail_kp = Inverse_quantization(kp_max, kp_min, qdetail_kp);    //去模糊化得到增量 △kp
	detail_ki = Inverse_quantization(ki_max, ki_min, qdetail_ki);    //去模糊化得到增量 △ki
	detail_kd = Inverse_quantization(kd_max, kd_min, qdetail_kd);    //去模糊化得到增量 △kd
	qdetail_kd = 0;
	qdetail_ki = 0;
	qdetail_kp = 0;
	kp = kp + detail_kp;    //得到最终的 kp 值
	ki = ki + detail_ki;    //得到最终的 ki 值
	kd = kd + detail_kd;    //得到最终的 kd 值
	if (kp < 0){
    
    
		kp = 0;}
	if (ki < 0){
    
    
		ki = 0;}
	if (kd < 0){
    
    
		kd = 0;}
	detail_kp = 0;
  detail_ki = 0;
  detail_kd = 0;
  output = kp*(error - error_pre) + ki * error + kd * (error - 2 * error_pre + error_ppre);   //计算最终的输出
	return output;
}

 
///区间映射函数
float Quantization(float maximum,float minimum,float x)
{
    
    
	float qvalues= 6.0 *(x-minimum)/(maximum - minimum)-3;
	return qvalues;
}
 
//输入e与de/dt隶属度计算函数
void Get_grad_membership(float error,float error_c)   
{
    
    
	int i;
	if (error > e_membership_values[0] && error < e_membership_values[6])
	{
    
    
		for ( i = 0; i < num_area - 2; i++)
		{
    
    
			if (error >= e_membership_values[i] && error <= e_membership_values[i + 1])
			{
    
    
				e_gradmembership[0] = -(error - e_membership_values[i + 1]) / (e_membership_values[i + 1] - e_membership_values[i]);
				e_gradmembership[1] = 1+(error - e_membership_values[i + 1]) / (e_membership_values[i + 1] - e_membership_values[i]);
				e_grad_index[0] = i;
				e_grad_index[1] = i + 1;
				break;
			}
		}
	}
	else
	{
    
    
		if (error <= e_membership_values[0])
		{
    
    
			e_gradmembership[0] = 1;
			e_gradmembership[1] = 0;
			e_grad_index[0] = 0;
			e_grad_index[1] = -1;
		}
		else if (error >= e_membership_values[6])
		{
    
    
			e_gradmembership[0] = 1;
			e_gradmembership[1] = 0;
			e_grad_index[0] = 6;
			e_grad_index[1] = -1;
		}
	}
 
	if (error_c > ec_membership_values[0] && error_c < ec_membership_values[6])
	{
    
    
		for ( i = 0; i < num_area - 2; i++)
		{
    
    
			if (error_c >= ec_membership_values[i] && error_c <= ec_membership_values[i + 1])
			{
    
    
				ec_gradmembership[0] = -(error_c - ec_membership_values[i + 1]) / (ec_membership_values[i + 1] - ec_membership_values[i]);
				ec_gradmembership[1] = 1 + (error_c - ec_membership_values[i + 1]) / (ec_membership_values[i + 1] - ec_membership_values[i]);
				ec_grad_index[0] = i;
				ec_grad_index[1] = i + 1;
				break;
			}
		}
	}
	else
	{
    
    
		if (error_c <= ec_membership_values[0])
		{
    
    
			ec_gradmembership[0] = 1;
			ec_gradmembership[1] = 0;
			ec_grad_index[0] = 0;
			ec_grad_index[1] = -1;
		}
		else if (error_c >= ec_membership_values[6])
		{
    
    
			ec_gradmembership[0] = 1;
			ec_gradmembership[1] = 0;
			ec_grad_index[0] = 6;
			ec_grad_index[1] = -1;
		}
	}
 
}
 
// 获取输出增量kp,ki,kd的总隶属度
void GetSumGrad()
{
    
    
	int i;
	int j;

    // 初始化 Kp、Ki、Kd 总的隶属度值为 0
								
	for ( i = 0; i <= num_area - 1; i++)
	{
    
    
		KpgradSums[i] = 0;
		KigradSums[i] = 0;
        		KdgradSums[i] = 0;
	}

    	for ( i = 0; i < 2; i++)
   	 {
    
    
        		if (e_grad_index[i] == -1)
        		{
    
    
            			continue;
       		}
        		for ( j = 0; j < 2; j++)
       		{
    
    
            			if (ec_grad_index[j] != -1)
            			{
    
    
                			int indexKp = Kp_rule_list[e_grad_index[i]][ec_grad_index[j]] + 3;
               				int indexKi = Ki_rule_list[e_grad_index[i]][ec_grad_index[j]] + 3;
                			int indexKd = Kd_rule_list[e_grad_index[i]][ec_grad_index[j]] + 3;
                			KpgradSums[indexKp]= KpgradSums[indexKp] + (e_gradmembership[i] * ec_gradmembership[j]);
               				KigradSums[indexKi] = KigradSums[indexKi] + (e_gradmembership[i] * ec_gradmembership[j]);
                			KdgradSums[indexKd] = KdgradSums[indexKd] + (e_gradmembership[i] * ec_gradmembership[j]);
            			}
            			else
            			{
    
    
                			continue;
            			}
        		}
 	 }
}
 
// 计算输出增量kp,kd,ki对应论域值
void GetOUT()
{
    
    
	int i;
	for ( i = 0; i < num_area - 1; i++)
	{
    
    
		qdetail_kp += kp_menbership_values[i] * KpgradSums[i];
		qdetail_ki += ki_menbership_values[i] * KigradSums[i];
		qdetail_kd += kd_menbership_values[i] * KdgradSums[i];
	}
}
 
//反区间映射函数
float Inverse_quantization(float maximum, float minimum, float qvalues)
{
    
    
	float x = (maximum - minimum) *(qvalues + 3)/6 + minimum;
	return x;
}
적응형 제어 알고리즘의 단점

​ 실제 코드에서는 시스템이 최적화되었기 때문에 여전히 상당히 좁은 데드존 내에서만 이동할 수 있는 상황이 있는데, 이는 컴퓨팅 파워 문제로 갑자기 제어가 불가능해질 수도 있다는 뜻이다.

제어 알고리즘에 대한 추가 참고 사항
적응 제어 PID 구조 배열
typedef struct{
    
    
	int setpoint;	//设定值
	long sumerror;	//误差的总和		
	float P;			//p
	float I;			//I
	float D;	//D
	int lasterror;//当前误差
	int preverror;//前一次误差
	int result;//本次PID结果
}PID;
적응형 제어 PID는 출력 전압 코드를 계산합니다.
void PID_contrl(PID* ptr,int nowpoint){
    
    
	int tmp_error;
	int tmp_common;
	tmp_error = ptr->setpoint - nowpoint;	
	ptr->sumerror+=tmp_error;
	tmp_common=tmp_error-ptr->lasterror;
	ptr->result=FuzzyPIDcontroller(2000, -2000,500, -500, 50, -50, tmp_error, tmp_common, 0.1,-0.1,0.01, -0.01,ptr->lasterror, ptr->preverror);
	ptr->preverror=ptr->lasterror;
	ptr->lasterror = tmp_error;		
}
증분형 PID 제어 알고리즘

*: 이 알고리즘은 수락 당시의 코드이지만 전송된 코드는 아닙니다.

증분형 PID 알고리즘의 원리

증분식 PID 제어는 위치 PID 제어와 달리 현재 순간의 제어량과 이전 순간의 제어량의 차이를 만들어 새로운 제어량으로 사용하는 재귀적 알고리즘입니다.

증분형 PID 알고리즘용 코드

u [ n − 1 ] = K p { e [ n − 1 ] + TT i ∑ i = 0 n − 1 e [ i ] + T d T { e [ n − 1 ] − e [ n − 2 ] } } u[n-1]=K_{p}\left\{e[n-1]+\frac{T}{T_{i}} \sum_{i=0}^{n-1} e[i] +\frac{T_{d}}{T}\{e[n-1]-e[n-2]\}\right\}[ -1 ]=케이{ 전자 [ -1 ]+나는 = 0n 1전자 [ 나는 ]+{ 전자 [ -1 ]-전자 [ -2 ] } }

extern float Kp
extern float T
extern float Ti
extern float Td
void PID_contrl(PID* ptr,int nowpoint){
    
    
	int tmp_error;
	int tmp_common;
	tmp_error = ptr->setpoint - nowpoint;	
	tmp_common=tmp_error-ptr->lasterror;
	ptr->result=Kp*(ptr->lasterror+(T/Ti)*ptr->sumerror+Td/T*(ptr->preverror-ptr->lasterror);
  ptr->sumerror+=tmp_error;
	ptr->preverror=ptr->lasterror;
	ptr->lasterror = tmp_error;		
}
증분형 PID 알고리즘의 단점

증분 PID는 장치마다 완전히 다른 영향을 미치며 PID 매개변수를 수동으로 조정해야 하는데 이는 상당히 번거로운 작업입니다.

LED 디스플레이 모듈

这部分只需要修改实验1例程即可得到结果。
void my_LedDispNum(unsigned int num1,unsigned int num2,unsigned int num3)  
{
    
    
	unsigned char temp1[4];
	unsigned char temp2[4];
	unsigned char temp3[4];
	
	temp1[0] = num1%10;
	temp1[1] = num1%100/10;
	temp1[2] = num1%1000/100; 
	temp1[3] = num1/1000;
	
	temp2[0] = num2%10;
	temp2[1] = num2%100/10;
	temp2[2] = num2%1000/100; 
	temp2[3] = num2/1000;
	
	temp3[0] = num3%10;
	temp3[1] = num3%100/10;
	temp3[2] = num3%1000/100; 
	temp3[3] = num3/1000;

	select(4);display(temp1[0]); Delay1(500); P7 = 0xff;
	select(3);display(temp1[1]); Delay1(500); P7 = 0xff;
	select(2);display(temp1[2]); Delay1(500); P7 = 0xff;
	select(1);display(temp1[3]); P7 = P7 & ~0x80;if(temp1[3] == 0) P7 = 0xff; Delay1(500); P7 = 0xff; 

	select(8);display(temp2[0]); Delay1(500); P7 = 0xff;
	select(7);display(temp2[1]); Delay1(500); P7 = 0xff;
	select(6);display(temp2[2]); Delay1(500); P7 = 0xff;
	select(5);display(temp2[3]); P7 = P7 & ~0x80;Delay1(500); P7 = 0xff; // Delay(500);

	select(12);display(temp3[0]); Delay1(500); P7 = 0xff;
	select(11);display(temp3[1]); Delay1(500); P7 = 0xff;
	select(10);display(temp3[2]); Delay1(500); P7 = 0xff;
	select(9) ;display(temp3[3]); P7 = P7 & ~0x80;Delay1(500); P7 = 0xff; // Delay(500);
}

그리기 포인트 기능

这是我在网上找到的一段规范代码,就是用于将X依次画出
void LcdShowPoint(unsigned char x)
{
    
    
	unsigned char i;
	unsigned char col=x/16;
	unsigned char off=x%16;
	unsigned char row1=wave[x]/128;
	unsigned char datah1=0;
	unsigned char datal1=0;
	for(i=0;i<8;i++)
	{
    
    
		if(i<=off&&wave[col*16+i]/128==row1) datah1|=0x80>>i;
		if(i+8<=off&&wave[col*16+8+i]/128==row1) datal1|=0x80>>i;
  }
	WriteCommand(0x34);
	WriteCommand(0x80+31-row1);
	WriteCommand(0x80+col);
	WriteCommand(0x30);
	WriteData(datah1);
	WriteData(datal1);
	WriteCommand(0x32);
	WriteCommand(0x36);
}

프로그램 순서 분석

논리가 완전히 직선이므로 논리 블록 다이어그램은 여기서 더 이상 사용되지 않습니다.

초기화

클럭과 인터럽트는 초기화가 필요하며 여기에 언급된 내용이 있는데, 우리 코드를 공부하는 많은 학생들은 이 작업을 수행하는 방법을 모릅니다. IF의 의미를 이해해야 합니다.

int main(void) {
    
    
    int i=0,j=0;
		Device_Init();
	  PID_init(&tmp_pid);
}
void INT_Init(void) {
    
    
  IT1 = 0;
	//enable INT1
	EX1 = 1;
	//enable all interrupt
	EA = 1;  
	INT1_Type1;     // 设定 TCON 中断标志位 2,INT1 中断为边缘触发
  Enable_INT1;    // 设定 IE 标志位 2,允许 INT1 中断请求
}

while 루프 제어

while (1) 
{
    
    		
  my_LedDispNum(vadc_dec,tmp_pid.setpoint,vdac_dec);//打印点
	PID_display();//PID屏幕初始化
  vdac=0;
	DAC0_Output(vdac);
	KEY_FLAG=0;
	LcdInit();
  
	if(KEY_FLAG==1)
    	{
    
     //屏幕退出控制
				LcdClear(); 
				ImageShow(blank);
				KEY_FLAG=0;
 			 }
    while(KEY_FLAG!=3)
    		{
    
    
			my_LedDispNum(vadc_dec,tmp_pid.setpoint,vdac_dec);//打印
			Do();//实现控制,函数在上面adc部分已介绍
			i+=1;
			if(i%5==0)
      {
    
    		//每隔五个点打印一次
				wave[j]=vadc_dec;
				LcdShowPoint(j);
				i=0;
				j+=1;
				if(j==128)//屏幕到达边界
        {
    
    
					ImageShow(blank);
					j=0;
				}
			}
    	}}
    

실험 요약

실험 결과

증분형 PID를 사용하면 좋은 결과를 빠르게 얻을 수 있지만 적응형 알고리즘은 컴퓨팅 성능 부족으로 인해 원하는 기능을 달성할 수 없다는 점을 분명히 알 수 있습니다.

실험 검토

예전에 다이소 조종차에서 PID 알고리즘을 사용해 본 경험이 있는데, 이번 실험에서 헬리콥터 시뮬레이터를 처음 접했다는 점을 어렵지 않게 찾을 수 있습니다. 매우 흥미로웠습니다. 또한 버튼이 눌리는 문제도 겪었습니다. 이식 후에는 사용할 수 없었지만 결국 하나씩 극복해 나갔고, 이 과정에서 선생님의 도움 덕분에 이 문서는 프로젝트와 함께 gitee 와 내 개인 웹사이트에 게시되었습니다.

추천

출처blog.csdn.net/xrk00/article/details/125057168