LINUX.ORG.RU

PID регулятор

 ,


1

2

Накидал простенький ПИД-регулятор для того чтобы поддерживать заданную скорость робота с двумя ведущими колесами. Формулы брал тут. Он вроде бы и работает, но я не могу его никак подобрать ему коэффициенты, постоянно то с места сдвинуться не может, то едет рывками, то разгоняется за несколько секунд до максимальных оборотов. Гляньте пожалуйста, мб я где нибудь глупую ошибку допустил в коде.

// main.c
#include "asuro.h"
#include "def.h"
#include "pid.h"

extern uint16_t left_speed = 16;
extern uint16_t right_speed = 16;
extern uint8_t pid_enable = 1;

int main(void)
{
    Init();
    
    EncoderInit();
    EncoderStart();
    
    // main loop
	while(1) {
		process_pid();
	}
    
    return 0;
}
// def.h
#ifndef DEF_H
#define DEF_H

// PID regulator coefficients
#define kP 		5
#define kI		0
#define kD		0

// PID regulator settings
#define PID_INTERVAL 		100
#define MOTOR_MAX_SPEED 	255
#define MOTOR_MIN_SPEED 	0

#endif // DEF_H
// pid.h
#ifndef PID_H
#define PID_H

#include "asuro.h"
#include "def.h"

uint16_t left_speed = 0;
uint16_t right_speed = 0;

uint8_t pid_enable = 0;
static uint32_t prev_pid_time = 0;

static uint16_t left_prev_error = 1;
static uint16_t right_prev_error = 1;
static float left_prev_i = 1;
static float right_prev_i = 1;

void process_pid(void);
static uint8_t pid_compute(int current, int must_be, uint16_t *prev_error, float *prev_i);

#endif	// PID_H
// pid.c
#include "pid.h"

void process_pid(void)
{
	if(!pid_enable) return;
	
	if(Gettime() > prev_pid_time){
		if(Gettime() - prev_pid_time >= PID_INTERVAL){
			uint8_t l_pwm = pid_compute(encoder[LEFT], left_speed, &left_prev_error, &left_prev_i);
			uint8_t r_pwm = pid_compute(encoder[RIGHT], right_speed, &right_prev_error, &right_prev_i);
			MotorSpeed(l_pwm, r_pwm);
			encoder[LEFT] = 0;
			encoder[RIGHT] = 0;
			prev_pid_time = Gettime();
		}
	} else {
		if(0xFF - prev_pid_time + Gettime() + 1 >= PID_INTERVAL){
			uint8_t l_pwm = pid_compute(encoder[LEFT], left_speed, &left_prev_error, &left_prev_i);
			uint8_t r_pwm = pid_compute(encoder[RIGHT], right_speed, &right_prev_error, &right_prev_i);
			MotorSpeed(l_pwm, r_pwm);
			encoder[LEFT] = 0;
			encoder[RIGHT] = 0;
			prev_pid_time = Gettime();
		}
	}
}

static uint8_t pid_compute(int current, int must_be, uint16_t *prev_error, float *prev_i)
{
	float p, i, d;
	uint8_t output;
	int16_t error = must_be - current;
	
	p = kP * error;
	
	i = *prev_i + (kI * error);
	
	d = kD * (current - *prev_error);
	
	output = p + i + d;
	
	if(output > MOTOR_MAX_SPEED) output = MOTOR_MAX_SPEED;
	if(output < MOTOR_MIN_SPEED) output = MOTOR_MIN_SPEED;
	
	*prev_i = i;
	*prev_error = error;
	
	return output;
}
★★★★
Ответ на: комментарий от anonymous

да вроде тут нету ошибки. здесь я просто учел переполнение таймера, когда он дотикает до 0xFF и снова начнет с нуля.

WRG ★★★★
() автор топика
Ответ на: комментарий от anonymous

unsigned long Gettime (void) - время в мс с момента запуска микроконтроллера

uint32_t prev_pid_time - это время в мс с момента запуска микроконтроллера, когда производился предыдущий запуск pid_compute()

#define PID_INTERVAL 200 - это время в мс, которое должно пройти после последнего запуска pid_compute(), чтобы запустить pid_compute(), иными словами это интервал времени, через который вызывается функция pid_compute()

WRG ★★★★
() автор топика
Ответ на: комментарий от WRG

допустим максимальное значение таймера это 255. последний раз pid_compute() вызывалась в 230мс, т.е. prev_pid_time=230. в следующий раз надо вызвать через 60мс, т.е. в 290мс на таймере, но таймер может только до 255. в нужный момент на таймере будет 35 а не 290 т.к. он переполнится и начнет с нуля.

WRG ★★★★
() автор топика
Ответ на: комментарий от anonymous

Короче, понял я. 0xFF это 255, а не то, что я хотел. я как то даже внимания на это не обращал. Надо заюзать UINT32_MAX? Посыпаю голову пеплом.

WRG ★★★★
() автор топика
Последнее исправление: WRG (всего исправлений: 1)
Ответ на: комментарий от WRG

если диапазон значений 0-255, то лучше использовать приведенную разницу:

((uint8_t)(Gettime() - prev_pid_time)) >= PID_INTERVAL
http://pastebin.com/tz74CQVv

unsigned long Gettime (void)

uint32_t prev_pid_time

лучше бы придерживаться одного стиля: unsigned long или uint32_t.

anonymous
()
Ответ на: комментарий от WRG

Посыпаю голову пеплом.

Да похоже мне надо. Ошибка то не там была. Ошибка вот тут:

if(Gettime() > prev_pid_time)
В этой строчке отличие от предложенного варианта. Там, куда указал ранее, было только следствие. При равенстве Gettime() и prev_pid_time получалось значение разницы времени 256, что больше PID_INTERVAL.

anonymous
()
Ответ на: комментарий от anonymous

Только у тебя вместо интегратора получается экспоненциальный фильтр, который не убирает статическую ошибку. А биений никаких не должно быть при правильно выбранном K_i.

prischeyadro ★★★☆☆
()
Ответ на: комментарий от prischeyadro

тогда назови цифру коэффицента для конкретно этого случая, оставляя весь код и остальные коэффициенты неизменными

anonymous
()
Ответ на: комментарий от prischeyadro

при правильно выбранных K_p, K_i и K_d может имелось ввиду?

anonymous
()
Ответ на: комментарий от anonymous

лучше бы придерживаться одного стиля: unsigned long или uint32_t

библиотеку не я писал, а тот, кто робота разрабатывал, а я привык уже к uint32_t, ибо так вот сразу понятно сколько байт.

WRG ★★★★
() автор топика
Ответ на: комментарий от anonymous

после всех правок регулятор наконец-то стал работать нормально, вчера удалось его настроить. спасибо всем, кто принимал участие.

WRG ★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.