【STM32 + HAL庫】倒立擺

一天時間終於做完了這顆倒立擺,能夠實現持久站立,但是抗干擾能力很弱。

先上兩張未經排版的圖(好吧,不能上傳GIF)

截止本文寫完,該擺最長站立了17分鐘。

【硬件部分】

1、倒立擺機械部分由老師提供,由同步輪+同步帶帶動滑臺在光桿上運動;角度反饋部分爲安裝在倒立擺下端的高精度電位器

2、電機爲帶編碼器的減速直流電機,由TB6612芯片驅動

3、主控板爲stm32f103vet6開發板

4、電機使用學生電源提供10V電壓驅動,單片機由電腦USB供電。

【使用HAL庫構建硬件層代碼】

 

1、RCC時鐘樹配置

在CubeMX中,使用外部晶振提供時鐘源,通過鎖相環倍頻到72MHz提供給外設,ADC模塊的時鐘不能太高,否則會影響轉換精度。詳細配置如圖

 

2、TIM8編碼器模式配置

使用stm32f103高級定時器TIM8的編碼器模式,測量電機的轉速。

在模式選擇窗口,選擇通道串聯模式,由兩個TIM輸入通道接入正交編碼器的A線和B線。

詳細配置窗口中,選擇向上計數,不允許自動重裝載。編碼器模式下,選擇在通道1和通道2的上升沿計數。理論上而言,這樣會導致計數值是實際值的4倍(但是實際程序中不是)

編碼器數值由軟件直接讀取和重置,不需要重裝載,也不需要中斷。

注意將GPIO腳設置爲上拉輸入,部分編碼器沒有接上拉電阻,需要單片機內部提供。

 

3、ADC1測量電位器阻值

配置ADC1的一通道對電位器阻值進行測量,從而獲取倒立擺的傾角。

如圖是選擇Channel1獨立測量、連續測量模式,因爲只有一個通道測量,所以掃描測量模式關閉。AD轉換同樣不需要中斷,在軟件中直接觸發一次轉換,之後讀取轉換值即可。

4、TIM1輸出PWM

在模式配置中,選擇時鐘源爲內部時鐘源72MHz,選擇通道一爲PWM輸出模式

在配置選項中,我們需要產生頻率爲1000Hz,佔空比可變的PWM。可以選擇預分頻係數爲72-1,預分頻之後得到1MHz時鐘信號;重裝載寄存器爲1000-1,得到1000Hz的PWM。選擇PWM Mode 1,當計數器值小於某個值時,輸出高電平,否則輸出低電平(Mode 2)相反。同時配置輸出GPIO爲推輓輸出模式。

【串級PID算法】

部分內容來自個人理解,如果不當,懇請批評指正。

顯然這是一個串級PID算法。

第一級PID輸入爲倒立擺的實際角度和目標角度,輸出電機的目標轉速。

實際上輸入爲ADC轉化的電位器兩端的電壓值,目標值爲電位器在平衡位置時ADC轉換的電壓值,輸出爲目標轉速。

第一級PID主要由比例和微分控制,由於下面還有一層PID,所以積分環節要麼選擇誤差過零時將積分項清零,要麼選擇較小的積分限幅。考慮到倒立擺對平衡性要求較高,誤差過零時將積分項清零的方法將使第一級PID的輸出在平衡位置發生跳變,所以選擇積分限幅,限制幅值較小,但積分參數較大。

第二級PID輸入爲電機的目標轉速和實際轉速,輸出電機的PWM。

第二級PID主要由PID三項控制構成,原本決定不用I控制,但是在實際調試過程中,由於電機存在一定的死區,在平衡位置附近PWM輸出很小,電機不轉,嚴重影響控制品質;可以按固定的PWM值進行死區補償,但是這樣較爲死板,考慮到倒立擺的擺動是一個相對慢的過程,所以使用積分項。同樣需要積分限幅。

 

【調試過程】

一開始PID調了好久都沒有效果,冷靜下來才發現方法不對。後來的調試過程如下:

1、先給第一級PID賦較大的P,I和D參數爲0

2、利用Arduino的串口繪圖器,將目標值與實際值繪圖,觀察變化趨勢,調節第二級參數

3、利用Arduino的串口繪圖器,將目標值與實際值繪圖,觀察變化趨勢,調節第一級參數

 

調試過程中的小技巧:

設置當倒立擺角度大於一定角度之後,電機目標速度置0,避免了頻繁開關電源。

把倒立擺固定好,如果桌面不平且倒立擺在桌面上移動,那麼每次調試時平衡位置對應的電位器的值不同,需要頻繁嘗試。

 

【主要代碼】

pid.h

#ifndef __pid_H_
#define __pid_H_
#include "main.h"
#include "pid.h"

#define TAR_ANGLE 1425    

typedef struct{
		float k_pro;	//±ÈÀý²ÎÊý	
		float k_int;	//»ý·Ö²ÎÊý
		float k_dif;	//΢·Ö²ÎÊý
		long max_int;	//»ý·ÖÏÞ·ù
		long i;	//»ý·Ö
		int tar_value;	//Ä¿±êÖµ
		int true_value; //Õæʵֵ
		int pre_error;	//ÉÏ´ÎÎó²î
		int output_limit_min;
		int output_limit_max;
		int output;
} pid;

void pid_speed_init();
void pid_set_tarspeed(uint32_t tar);
void pid_speed_cal();
void pid_angle_cal();

void pid_angle_init();



#endif /* __pid_H_ */

pid.c

#include "main.h"
#include "pid.h"

#define PID_SPEED_K_PRO 85.0
#define PID_SPEED_K_INT 2.0
#define PID_SPEED_K_DIF -30.0

#define PID_ANGLE_K_PRO 0.60
#define PID_ANGLE_K_INT 0.70
#define PID_ANGLE_K_DIF -0.105



pid pid_speed;
pid pid_angle;

void pid_speed_init(){
	pid_speed.i = 0;
	pid_speed.k_dif = PID_SPEED_K_DIF;
	pid_speed.k_int = PID_SPEED_K_INT;
	pid_speed.k_pro = PID_SPEED_K_PRO;
	pid_speed.max_int = 180;
	pid_speed.tar_value = 0;
	pid_speed.true_value = 0;
	pid_speed.pre_error = 0;
	
	pid_speed.output_limit_min = -999;
	pid_speed.output_limit_max = 999;
	pid_speed.output = 0;
	
}
void pid_angle_init(){
	pid_angle.i = 0;
	pid_angle.k_dif = PID_ANGLE_K_DIF;
	pid_angle.k_int = PID_ANGLE_K_INT;
	pid_angle.k_pro = PID_ANGLE_K_PRO;
	pid_angle.max_int = 60;
	
	pid_angle.tar_value = TAR_ANGLE;
	
	pid_angle.true_value = 0;
	pid_angle.pre_error = 0;
	
	pid_angle.output_limit_min = -65535;
	pid_angle.output_limit_max = 65535;
	pid_angle.output = 0;
	
}

void pid_set_tarspeed(uint32_t tar){
	pid_speed.tar_value = tar;
	
}
extern uint32_t adc_raw;
void pid_angle_cal(){
	int error = pid_angle.tar_value - pid_angle.true_value;
	
	if(pid_angle.true_value < TAR_ANGLE - 250 || pid_angle.true_value > TAR_ANGLE + 250);
	else pid_angle.i += error;
	
	if (pid_angle.i > pid_angle.max_int) pid_angle.i = pid_angle.max_int;
	if (pid_angle.i < -pid_angle.max_int) pid_angle.i = -pid_angle.max_int;
		
	//printf("T:%d\n", pid_speed.tar_value);
	//printf("E:%d\n", error);
	int delta_error = error - pid_angle.pre_error;
	pid_angle.pre_error = error;
	
	pid_angle.output = pid_angle.k_pro * error + pid_angle.k_int * pid_angle.i + pid_angle.k_dif * delta_error;
	printf("P:%d I:%d D:%d\n", (int)(pid_angle.k_pro * error), (int)(pid_angle.k_int * pid_angle.i), (int)(pid_angle.k_dif * delta_error));
	if (pid_angle.output > pid_angle.output_limit_max) pid_angle.output = pid_angle.output_limit_max;
	else if (pid_angle.output < pid_angle.output_limit_min) pid_angle.output = pid_angle.output_limit_min;
	//printf("AO:%d\n", pid_angle.output);
}



void pid_speed_cal(){
	int error = pid_speed.tar_value - pid_speed.true_value;
	
	if(pid_angle.true_value < TAR_ANGLE - 250 || pid_angle.true_value > TAR_ANGLE + 250);
	else pid_speed.i += error;
	
	if (pid_speed.i > pid_speed.max_int) pid_speed.i = pid_speed.max_int;
	else if (pid_speed.i < -pid_speed.max_int) pid_speed.i = -pid_speed.max_int;
	
	

	int delta_error = error - pid_speed.pre_error;
	pid_speed.pre_error = error;
	
	pid_speed.output = pid_speed.k_pro * error + pid_speed.k_int * pid_speed.i + pid_speed.k_dif * delta_error;
	

	
	//printf("P:%d ", (int)(pid_speed.k_pro * error));
	//printf("P:%d\n",(int)(pid_speed.k_int * pid_speed.i));
	if (pid_speed.output > pid_speed.output_limit_max) pid_speed.output = pid_speed.output_limit_max;
	else if (pid_speed.output < pid_speed.output_limit_min) pid_speed.output = pid_speed.output_limit_min;
	
	
}

主代碼main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include "pid.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 */
int a = 0;
uint8_t aTxStartMessages[] = "\r\n******UART commucition using IT******\r\nPlease enter 10 characters:\r\n";
uint8_t aRxBuffer[1];
extern pid pid_speed;
extern pid pid_angle;
	
/* 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 */
int fputc(int c, FILE *stream)    //??fputc??
{

    HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);   
    return 1;
}

uint32_t adc_raw = 0;
uint16_t timer2_counter = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
		timer2_counter ++;
		if (timer2_counter == 50){
			
			timer2_counter = 0;
			
			pid_angle.true_value = adc_raw;
			//printf("%d %d\n", pid_angle.true_value, pid_angle.tar_value);
			pid_angle_cal();
			
			pid_set_tarspeed(pid_angle.output);
		}
		if (timer2_counter % 10 == 0){
			pid_speed.true_value = 30000 - __HAL_TIM_GET_COUNTER(&htim8);
			__HAL_TIM_SET_COUNTER(&htim8, 30000);
			pid_speed_cal();
			
			if(pid_angle.true_value < TAR_ANGLE - 250 || pid_angle.true_value > TAR_ANGLE + 250)
				pid_speed.output = 0;
			//printf("Speed:%d\n", pid_speed.true_value);
			if (pid_speed.output < 0){
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);
				__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, -pid_speed.output);
				
			}
			
			else if (pid_speed.output > 0){ 
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
				__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pid_speed.output);
			}
			else if (pid_speed.output == 0){ 
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
				__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
			}
		}
	
}
uint8_t msg[1];

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
		printf("%d\n", msg[0]);
		if(msg[0] == '1'){
			pid_angle.k_pro += 0.05;
			printf("Kp->:%.2f\n", pid_angle.k_pro);
		}
		else if (msg[0] == '2'){
			pid_angle.k_pro -= 0.05;
			printf("Kp->:%.2f\n", pid_angle.k_pro);
		}
		else if(msg[0] == '3'){
			pid_angle.k_int += 0.05;
			printf("Kd->:%.2f\n", pid_angle.k_int);
		}
		else if (msg[0] == '4'){
			pid_angle.k_int -= 0.05;
			printf("Kd->:%.2f\n", pid_angle.k_int);
		}
		
		else if(msg[0] == '5'){
			pid_angle.k_dif += 0.005;
			printf("Kp->:%.3f\n", pid_angle.k_dif);
		}
		else if (msg[0] == '6'){
			pid_angle.k_dif -= 0.005;
			printf("Kp->:%.3f\n", pid_angle.k_dif);
		}
		
		else if(msg[0] == '7'){
			pid_angle.k_pro += 0.05;
			printf("Kd->:%.2f\n", pid_angle.k_pro);
		}
		else if(msg[0] == '8'){
			pid_angle.k_pro -= 0.05;
			printf("Kd->:%.2f\n", pid_angle.k_pro);
		}
		else if(msg[0] == '9'){
			pid_angle.k_dif += 0.005;
			printf("Kd->:%.3f\n", pid_angle.k_dif);
		}
		else if(msg[0] == '0'){
			pid_angle.k_dif -= 0.005;
			printf("Kd->:%.3f\n", pid_angle.k_dif);
		}
		
		HAL_UART_Receive_IT(&huart1, (uint8_t *)&msg, 1);
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	

	uint8_t adc = 0;
	
  /* 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_TIM1_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  MX_TIM2_Init();
  MX_TIM8_Init();
  /* USER CODE BEGIN 2 */
	__HAL_TIM_SET_COUNTER(&htim8, 30000);
	HAL_TIM_Encoder_Start(&htim8,TIM_CHANNEL_ALL);
	pid_speed_init();
	pid_angle_init();
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&msg, 1);
	HAL_TIM_Base_Start_IT(&htim1); 
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	
	
	
	HAL_UART_Receive_IT(&huart1,(uint8_t*)aRxBuffer,1);
	HAL_UART_Transmit(&huart1,aTxStartMessages,sizeof(aTxStartMessages),100);
	HAL_TIM_Base_Start_IT(&htim2);
	
	
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
//		HAL_UART_Transmit(&huart1, (uint8_t *)&msg, 3, 1000);
//		HAL_Delay(500);
//		HAL_GPIO_WritePin(LED_Pin1_GPIO_Port, LED_Pin1_Pin, GPIO_PIN_SET);
//		HAL_Delay(500);
			HAL_ADC_Start(&hadc1);
      HAL_ADC_PollForConversion(&hadc1, 500);
			adc_raw = HAL_ADC_GetValue(&hadc1);
			//adc = adc_raw / 0xFFFFFF;
			//HAL_UART_Transmit(&huart1, &adc, 1, 100);
//		printf("Data: %d\n", adc_raw);
//		printf("End of while");
		
  }
  /* 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};

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV8;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @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 */

  /* 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,
     tex: 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****/

由於一遍一遍嘗試PID參數較爲麻煩,程序中使用串口接收電腦端的字符信息,進行PID參數的加減,較爲方便的完成了調試。

 

【進一步優化】

倒立擺能夠實現較爲持久的直立,但是抗干擾能力較弱,在弱干擾情況下會震盪然後摔倒。下一步考慮能否在由傾角較大返回平衡狀態的過程中,對PID控制器進行優化。

【下載鏈接】

鏈接:https://pan.baidu.com/s/1qwym3qKKoyAnxOy4WM-fpQ 
提取碼:drc5 

 

個人見解,如果不當,懇請各位朋友批評指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章