適応アルゴリズムとインクリメンタル PID アルゴリズムに基づくシミュレートされたヘリコプタ制御システム
記事ディレクトリ
制御システムハードウェア
シミュレートされたヘリコプター垂直揚力制御システムは、主に C8051F020 マイクロコントローラー、ボタンおよびディスプレイ モジュール、ヘリコプター垂直揚力シミュレーション オブジェクトで構成されています。
マイコンシステム
表示機能は、LCD とデジタル管によって実現されます。LCD 画面とデジタル管は、表示制御メイン メニュー インターフェイス、ホール電圧の現在値と変化曲線などを実現します。ボタンは LCD の切り替えに使用されます。表示内容、PIDパラメータの設定、ホール電圧設定値の増減などの機能を実行します。
センサーシステムの紹介
SS49E リニアホールセンサーは、小型で幅広い用途に使用できるという特徴を持っています。SS49Eは永久磁石または電磁石で動作し、電源電圧によりリニア出力を制御し、磁界強度に応じてリニアに変化させることができます。SS49E は内部に低ノイズ出力回路を統合しているため、外部フィルターが不要です。このデバイスには、温度の安定性と精度を向上させるために薄膜抵抗器が組み込まれています。SS49Eの動作電圧は4.5V~6Vです。
ヘリコプターシミュレーションシステムのご紹介
ヘリコプタ垂直揚力シミュレーション オブジェクト システムの概略図を図 2-2 に、実際のオブジェクトを図 2-3 に、インターフェースの説明を表 2-1 に示します。
システムモジュールの紹介
ADCサンプリングシステム
ADC サンプリング システムはプロジェクト全体の中で最も重要な部分であるため、ここでは Stm32F103 と C8051F020 マイクロコントローラーの違いを通じて、制御におけるより高度なシステムの役割を示したいと思います。
STM32
ADC 実装のアイデア:
まず、STM32F103 シリーズが持つ 3 つの高精度 ADC について理解する必要があります。
ここでの f adc動作周波数は 14MHZ、システム クロックは 72MHZ であることがわかり、明らかに分周率を 6 に設定する必要があります。後で ADC を処理するときに CPU にさらに多くの操作を実行させたいため、タイマー割り込みが ADC のサンプリングをトリガーし、DMA が ADC レジスタからコードを継続的に移動することを期待しているため、ポーリングは明らかに使用すべきではありません。指定された配列に。
CUBEMX 構成
ADC1とADC3の構成
グラフィックスの初期化
ADC3 の構成を例に挙げます (ADC3 のチャネル 8 は PF10 に対応します)。
- ADC 構成では、外部トリガー変換ソースをタイマー 8 によってトリガーされるように変更し、サンプリング時間を 7.5 サイクルに、連続変換モードを ENABLE に変更する必要があります。
- DMA 構成では、データ幅がハーフワールドに変更され、モードがループに変更されます。これは、DMA 転送が停止しないように非常に重要です。作成者は最初はデフォルトを NORMAL に設定していましたが、DEBug はそれがデフォルトであることがわかりました。転送は毎回 1 回だけで終了、ペリフェラルからメモリへ方向が変更されます
TIMEr タイマーインターフェイス
これは、0.x 秒ごとの正確なサンプリングを達成するために不可欠です。DMA テクノロジのみを使用すると、ADC は高周波数でサンプリングを継続するためです。特に実際には、この速度は速すぎて制御できないことがわかりました。 3 つの ADC 間の関係も調整する必要があります。3 つの ADC がすべて同時にサンプリングできれば、問題は明らかにうまく解決できます。
- 分周係数が 1999 に変更され、トリガーイベント選択が自動的に更新されるように変更され、割り込みが発生するたびに再びカウントが開始されます。
ADC2の構成
グラフィカルな初期化
ADC2 には DMA システムがないため、割り込みを生成するには別の Timer3 を使用する必要があります。ここで少しトリックを使用します。ADC2 を TIMER3 でトリガするように直接設定すると、非常に奇妙な現象が発生し、書き込みが中断されなくなります。ソフトウェア割り込みトリガを直接使用し、判断するために 2 つの割り込みコールバック関数を作成する方がよいのですが、その本質的な理由は、HAL ライブラリを呼び出した後、プログラマが基礎となる制御を部分的に失うためです。
- 連続変換モード: 無効に変更
- サンプリング時間 (DMA がないため、迅速な応答能力が失われるため、サンピング時間を直接増やすことをお勧めします): 41.5 サイクル
タイマー タイマー インターフェイス
- TIMER3の初期設定はTIMER8と同じです。
割り込みの初期化
-
割り込みの初期化では、(プリエンプション優先度、サブポート) に値 (0, 0) を割り当てるだけで済みます。
-
ADC のサンプリングが進行中であるため、ADC の優先順位をあまり高くする必要はありません。シリアル ポートの優先順位に注意を払う必要があります。シリアル ポートがデータを再度送信するときは、WIFI シリアル ポートの優先順位を送信 >> デバッグの優先順位を送信します。 >> ADC.個人的にシリアルポートに割り込むのは狂った行為だと思うのですが、一度このエラーで午後中ずっと悩まされ、デバッグしても結果が得られませんでした。
コードのソースコード (興味がない場合は読み飛ばしてください)
ADC.c と ADC.h
コード本体はCUBEMXで構成されており、指定された部分を理解して変更する必要があるため、特に変更部分については後ほど詳しくコメントさせていただきました。
(ADC1 ADC3) (ADC2) にはそれぞれ 2 つの設定方法があります
/* 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
ADC2
ADC3
ハードウェア説明(ADC1,2,3共通)
ADCハードウェア接続
ADC サンプリングでは 0 ~ 3.3v の電圧しか収集できないため、ハードウェア設計ではセンサーの分圧を考慮する必要があります。5V を 3.3.v に分圧するには 2 つのチップ抵抗を使用することをお勧めします。 ADC の入力抵抗 RAIN _
この組み込みシステムは T s =55.5を採用しています。
51ADC
DA変換
DAC を出力するには多くの方法がありますが、タイマー 2 のオーバーフローを通じて DAC 出力を更新することを選択した場合、C8051F02 マイクロコントローラーの DAC0 は 12 ビット精度のデジタル - アナログ コンバーターであるため、16 ビットの場合には異なるデータが生成されます。を使用して保存します。DAC0 を設定する場合、最初に初期化部分で DAC0 のアップデート モードとアライメント モードを設定し、次に DAC0 の基準電圧 (通常は内部基準電圧) を選択し、データ レジスタを 0 にクリアして初期化を完了します。DAC0 の出力制御では、タイマ 2 割り込みがオーバーフローすると、出力される電圧に対応する値が上位および下位データ レジスタに送信されます。
AD変換
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 は次のことができます。入力電圧をより正確に読み取ります。例えば、模擬ヘリコプタ垂直揚力制御システムでは、ADC0 の入力電圧を外部増幅回路に応じて適切に降圧し、12 ビットの 2 進数に変換して 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;}
}
}
}
}
制御アルゴリズム
適応制御アルゴリズム
適応制御アルゴリズムの原理
適応プロセスは、常に目標に近づくプロセスです。経路は、適応アルゴリズムと呼ばれる数学モデルによって表されます。通常は勾配ベースのアルゴリズムが使用され、その中でも最小平均二乗誤差アルゴリズムが特によく使用されます。適応アルゴリズムは、ハードウェア (処理回路) またはソフトウェア (プログラム制御) の 2 つの方法で実装できます。前者はアルゴリズムの数理モデルに基づいて回路を設計するのに対し、後者はアルゴリズムの数理モデルをプログラムにしてコンピュータを用いて実装します。アルゴリズムにはさまざまな種類があり、その選択は処理システムのパフォーマンス品質と実現可能性を決定するため、非常に重要です。
適応制御アルゴリズムのコード
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\}あなた[ n−1 ]=Kp{ e [ n−1 ]+T私はTi = 0∑n − 1e [ i ]+TTd{ e [ n−1 ]−e [ n−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 と私の個人ウェブサイトで公開することができました。