roboforum.ru

Технический форум по робототехнике.

контроллер не успевает считать импульсы

контроллер не успевает считать импульсы

hadok » 22 июл 2012, 12:42

Собрал плату для управления двигателем постоянного тока на mega16 , кварц 8 МГц. Контроллер управляет транзистором КП723Г с помощью аппаратного ШИМа. ОС организована на индуктивном датчике (как в системе ABS), который направлен на диск с выступами (или с отверстиями). Датчик http://www.megak.ru/Data/2/_M12/73_Z_.htm PNP - замыыкающий.
Управление скоростью ведётся по ПИД-закону. Информация о текущей скорости передаётся по RS232, по нему же планирую передавать задание.

Всё работает исправно, поддерживает скорость, достаточно быстро выходит на уставку. Проблема в том, что контроллер успевает посчитатьь только 30 импульсов за 0,25 секунды (120 имп за секунду). 4 раза в секунду текущая скорость сравнивается с заданием и вносится поправка в выходной сигнал. Частота работы датчика 600 Гц, частота оптрона (стоит между датчиком и входом прерывания)- килогерцы , физически обратная связь отработать успевает.
Гляньте мой код, может свежим взглядом, кто увидит возможную причину запаздывания.
Код: Выделить всёРазвернуть
                                                #include <mega16.h> 
//#include <m8_128.h> //   http://avr123.nm.ru/m8_128.h
#include <delay.h>
#include <stdio.h> 
#include <io.h>




int pwmPD4, pwmPD5, s, sumint;
int  count, time,  setpoint, n; 
  float E, E_pred, Y, Sum, XP, Tau_I, Tau_D, D, I;
unsigned char delta    ;
int pwm = 0; // Величина ШИМ начальная PWM в единицах от 1 до 1023     



  interrupt [TIM0_OVF] void TIM0_OVF_isr(void)  //обработа прерывания от таймера
{
         
//TCNT1H = 0x00;  // обнулить счет таймера
//TCNT1L = 0x00;     

  PORTC.0=0; // включаем реле к2
 
  PORTC.1=1;   
  PORTC.6=1;      //включаем средний сегмент
  PORTA.0^=1;     //инвертируем выход порта 
  TCNT0=s;        // таймер считает от этого значения до 255 
  SREG=1;         //разрешение прерываний
  GIFR=0x01 ;     //сброс флага переполнения таймера --- 0   
  time++;         //инкремент количества переполнений таймера

     if (time>=10)                          //   40 переполнений = 1 секунде
      {
     printf("imp %u %c\r\n",count,'');     
     // printf("TIME %u %c\r\n",time,'--');
     printf("pwm %u %c\r\n",pwmPD5,''); 
     printf("sum %u %c\r\n",sumint,'');
      PORTA.1^=1;                           // мигаем каждый такт
   
         // Т А У //
         
         XP=2;        // коэффициент пропорциональности
         Tau_I=0.01;     //постоянная интегрирования
         Tau_D=2;     //постоянная дифференцирования
         
              E=setpoint-count;         //вычисляем текущее рассогласование (разницу между заданием и текужим значением)
              Sum=Sum+E; 
               if (Sum<0) {Sum=2;}      //увеличиваем сумму ошибок
              D=Tau_D*(E-E_pred)/0.25;  //звено дифференцирования
              I=(Sum*0.25)/Tau_I;       //звено интегрирования
              Y=(1/XP)*(E+D+I)  ;       //итоговая формула
              if (Y<1) {Y=1;}  ;
               
                             
              if (Y>=pwm) { PORTC.2=1; PORTC.3=0;  } else
              if (Y<pwm) { PORTC.2=0; PORTC.3=1;  }
              pwm=Y;
               sumint=Sum;
              E_pred=E;   
                         
             
             
        // if (count<setpoint) {pwm=pwm+10*(setpoint-count); PORTC.2=0; PORTC.3=1; } else
        // if (count>setpoint) {pwm=pwm-10*(count-setpoint); PORTC.2=1; PORTC.3=0;}
         
     count=0; //обнулить количество импульсов от прерывания
     time=0;  // обнулить кол-во прерываний от таймера                                           
         
      } 
     
   
}       

               
  interrupt [EXT_INT0] void ext_int0_isr(void)     // обработка прерывания int0 при срабатывании датчика по возрастающему фронту
{   
   
    PORTA.5^=1;
    count++;
    n++;              //увеличение кол-ва импульсов-прерываний
    if (n>5) { PORTC.7=1; }       
    /*pwmPD4+=20;        //увеличение широты импульса для ШИМ сигнала на ножке PD4
    if (pwmPD4 > 1025) //если ШИМ уже более 100 %
    { pwmPD4 = 0;};    // обнулить величину ШИМ  */
               
       
    SREG=1;
}



void main(void)
{     
count=0; 
time=0;
setpoint=15;   //задание скорости ////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declare your local variables here

// Input/Output Ports initialization
PORTA=0xFF;
DDRA=0xFF;



PORTB=0x00; 
DDRB=0x00;

PORTC=0x00;
DDRC=0xFF;

// Port D initialization
PORTD=0xFF;
DDRD=0x30; // 0011 0000   PD5(OC1A) PD4(OC1B) - PWM Timer1 OUT   

  //Настройка таймера 0 таймер считает с периодом 24,96 мс  за 40 переполнений получится 1 сек

s=0x3D;     //  таймер считает от этого (61) значения до 255   то есть 194 такта   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
TCCR0=0x05;      //частота таймера в 1024 раз меньше тактовой   
TCNT0=s;    //  таймер считает от этого значения до 255
OCR0=0x00;
   //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   
   
// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: Off
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud rate: 9600
UCSRA=0x00;
UCSRB=0x08;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x33;;
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
   
   
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 8000,000 kHz
// Mode: Fast PWM top=03FFh
// OC1A output: Non-Inv.
// OC1B output: Non-Inv.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=0b10100011;
TCCR1B=0b00001011;    // делитель 64

TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;

// +++++++++++++++++++++++++++++++++++++++++++++++
// установить величину ШИМ на PD5  ===  0
OCR1AH=0x00;
OCR1AL=0x00;   
   
// +++++++++++++++++++++++++++++++++++++++++++++++
// установить величину ШИМ на PD4  ===  0
OCR1BH=0x00;
OCR1BL=0x00;     

// External Interrupt(s) initialization
// INT0: On
// INT1: Off
// INT2: Off
GICR=0x40;      //включено прерывание  Int0
MCUCR=0x03;     //по нарастающему фронту на ножке PD2
MCUCSR=0x00;   
GIFR=0x00;
TIMSK=0x01;     //прерывание при переполнении таймера 0
               

ACSR=0x80;
SFIOR=0x00;


// Global enable interrupts
#asm("sei")         ;

// +++++++++++++++++++++++++++++++++++++++++++++++
// установить ШИМ 25% на PD4
//OCR1BH = 0x00;  // PWM(PD4) OCR1B / 10.23  (%)
//OCR1BL = 0xFF;  // PWM(PD4)  255 / 10.23 = 24.9 (%)
// +++++++++++++++++++++++++++++++++++++++++++++++       

// +++++++++++++++++++++++++++++++++++++++++++++++
// установить ШИМ 25% на PD5
//OCR1AH = 0x00;  // PWM(PD5) OCR1A / 10.23  (%)
//OCR1AL = 0xFF;  // PWM(PD5)  255 / 10.23 = 24.9 (%)
// +++++++++++++++++++++++++++++++++++++++++++++++       




while (1){

      // Place your code here         
     
     
if (pwm > 1025) { //если ШИМ уже более 100 %
       pwm = 1022;  // обнулить величину ШИМ
               };
               
               
////////////////////////////////////////новое значение ШИМ сигнала 

pwmPD5=pwm;


/* printf("PWM %u %c\n",pwm,'%');
// вывели новое значение ШИМ в %     */



//  +++++++++++++++++++++++++++++++++++++
// pwm_val - это число от 0 до 1023
// PWM(PD5) = OCR1A / 10.23  (%)
OCR1AH = (char)(pwmPD5>>8);
OCR1AL = (char)pwmPD5;   

//OCR1BH = (char)(pwmPD4>>8);
//OCR1BL = (char)pwmPD4;     

// pwm += 10; //увеличим ШИМ на 10%

delay_ms(1); // пауза   

      };  // закрывающая скобка для while(1)
     
}


Вложения
IMG_20120215_084508.jpg
IMG_20120215_140527.jpg
плата.jpg

Re: контроллер не успевает считать импульсы

legion » 22 июл 2012, 13:50

А pwmPD4 что делает?

Почему OCR1A обновляется в основном цикле программы, а не в прерывании сразу после вычисления нового pwm?

GIFR=0x01 ; //сброс флага переполнения таймера --- 0
Флаг переполнения таймера - это TIFR. Да и зачем его сбрасывать? Он же только что сам сбросился при входе в прерывание.

Еще меня смущают SREG=1 в прерываниях. Не знаю как в C, но в асме такая штука в прерывании может закосячить любой условный переход, который происходит при разрешенном прерывании. SREG=1 в конце обработчика EXT_INT0 еще более загадочен, т.к. сразу после него идет выход из прерывания, т.е., по идее, RETI, который сам разрешает прерывания. Или C-шный компилятор всегда использует RET? О_о

Re: контроллер не успевает считать импульсы

uni » 24 июл 2012, 07:05

Не должно быть никакого отладочного вывода в обработчиках прерываний. Т.е. функции printf(), sprintf() и прочие им подобные ни в коем случае не должны находиться в ISR для быстротекущих процессов, где частота их срабатывания несколько сотен Гц и выше. Вообще не должно быть такой привычки вставлять просмотр переменных через USART таким образом. Это первое.

Второе, если нет осциллографа или вообще какого-то измерительного оборудования, то нужно пользоваться тем, что есть - Proteus'ом. Я не знаю может ли CodeVision создавать объектные файлы (лучше отлаживать там с исходниками), но в крайнем случае туда можно забить простой hex. Вместо того, чтобы слать куда-то числа, нужно во время начала работы прерывания выдавать на какой-то свободный вывод высокий уровень, а по окончании сбрасывать его в низкий. У Proteus'а есть виртуальный осциллограф. Там можно замерить полученные таким элементарным образом временные интервалы, при этом такие отладочные вставки никак не влияют на основной код в отличие от работы с USART.

Re: контроллер не успевает считать импульсы

legion » 24 июл 2012, 07:26

printf(), sprintf() тормозные какие-то чтоли? Так-то байт-второй в УАРТ отправить вроде не грех, он же аппаратный. Да и более длинные сообщения при наличии буфера должны на прерываниях уходить почти не затрагивая основную работу.

Re: контроллер не успевает считать импульсы

uni » 24 июл 2012, 08:42

Меня тоже смутил загадочный SREG. Разрешение вложенных прерываний может привести к неожиданным результатам. Я не специалист по CodeVision AVR, но у меня большие сомнения, что так можно писать. Если собираются разрешать вложенные прерывания, то SREG в самом начале обработчика сохраняют во временный регистр, а в конце обработчика возвращают это значение, чтобы то место, которое мы прервали нормально продолжило свою работу. Компилятор обычно сам это делает и нет никакого смысла в том, чтобы манипулировать целым регистром. Достаточно написать sei(); или так SREG.7=1;, чтобы разрешить прерывания.

SREG=1; - это бессмысленная операция в конце ISR. Компилятор восстановит значение, которое было до вызова обработчика.

Дело ещё и в том, что перед передачей байтов по USART, нужно сначала выделить память под буфер, посчитать что вставить в строчку для каждого параметра, а потом уже пересылать.

Можно прикинуть снизу время для передачи лога: передача одного бита 1/9600 ~= 0.1 мс. Пусть на байт уходит 10 бит. Тогда только на передачу уйдёт минимум 30 символов * 10 бит * 0,1 мс = 30 мс. А таймер, как написано, прерывается с частотой 24,96 мс. А я ещё не учёл время на обработку printf()'ами своих аргументов и работу с памятью. Страшно представить что там в мозгах у контроллера происходит при таком раскладе.

Re: контроллер не успевает считать импульсы

legion » 24 июл 2012, 09:13

Вывод идет только в каждом десятом переполнении, т.е. каждые 250 мс.

Вообще интересно, конечно, как компилятор такие команды вывода интерпретирует. Он использует прерывание по флагу UDRE? А если программист сам UDRE использует, как тогда?

А вообще да, я бы вырезал все лишнее, отладку по УАРТ бы оставил, но слал бы только пару байт с подсчитанным за четверть секунды количеством импульсов. И покрутил энкодер вручную. Надо локализовать баг - в железе он, или в программе.

Re: контроллер не успевает считать импульсы

uni » 24 июл 2012, 10:02

Да, этого я не заметил, про 10 отсчёт переполнения. Ну, допустим, случился 10 отсчёт и 30 мс ISR пытается что-то вывести в USART, при этом разрешены вложенные прерывания и идут странные манипуляции с регистрами, отвечающими за их настройку. Через 25 мс опять формируется прерывание от переполнения таймера, а он ещё не закончил вывод (!). Что будет? Тут есть два варианта: прерывание будет пропущено (счётчик не будет правильно инициализирован и т.д.), либо по каким-то причинам ISR сработает... Вот если будет второй случай, то поведение программы труднопредсказуемо.

П.С. Вариант номер 3. Нужно останавливать таймер на всё время работы ISR по переполнению, убрав источник тактирования в самом начале ISR. Это не совсем хороший приём, так как его можно применять только в исключительных случаях, компенсируя задержку (но её нужно высчитывать для этого).

Re: контроллер не успевает считать импульсы

legion » 24 июл 2012, 10:54

Все зависит от того, как в конечном счете реализован вывод в УАРТ. Если там буфер и отстрел байтов по UDRE, то проблем никаких, программа уйдет дальше, буфер отстрелится автоматом, почти не потребляя процессорного времени. Если же команда вывода затыкает код до конца передачи, тогда да, будет не очень. Хотя и тогда прерывание по переполнению не пропустится - когда мк выйдет из первого, будет стоять флаг на второе и мк сразу на него уйдет. Правда, в это время можно напропускать внешних прерываний.

Upd: Хотя нет, код же разрешает вложенные прерывания. Тогда во втором случае второе прерывание может вклиниться в обработку первого, нагадить в SREG и свалить.

Компилятор С сохраняет SREG автоматом в обработчиках прерываний? Или просто автору кода везет и его код работает достаточно долго, прежде чем запоротый условный переход обрушит логику?
Последний раз редактировалось legion 24 июл 2012, 11:03, всего редактировалось 1 раз.

Re: контроллер не успевает считать импульсы

uni » 24 июл 2012, 11:02

Есть настройка скорости последовательного порта и каким бы "аппаратным" ни был USART, пока 3 printf()'а не отработают, ISR переполнения таймера не закончится. См. мою рекомендацию: вариант 3.

Re: контроллер не успевает считать импульсы

legion » 24 июл 2012, 11:17

Да, посмотрел по-диагонали инфу по printf, похоже буфер она не использует ни в каких компиляторах. Строка забивается в стек и не спеша оттуда раскуривается.

Вместо варианта 3, если бы мне непременно надо было отправлять эти 30 символов, я бы в прерывании махал флагом, а в основном цикле программы по взмаху заводил бы printf. Ну и скорость USART поднял бы.

Re: контроллер не успевает считать импульсы

uni » 24 июл 2012, 11:29

Да, лучше делать так, но и там есть свои подводные камни (volatile и т.д.). Поэтому я и пишу в обоих тут темах, что в ISR не нужно привыкать использовать такие тяжёлые функции. Лучше освойте Proteus и махайте свободными выводами контроллера как флагами, а результат наблюдайте на осциллограмме, заодно узнаете сколько по времени занимает каждая функция, команда вплоть до такта.

Все переменные, время жизни которых определено фигурными скобками функции или оператора, находятся в стеке (для контроллеров с малым количеством ОЗУ тем более). А вообще, это зависит от реализации этих функций. В IAR'е, к примеру, можно выбрать из трёх вариантов реализаций в настройках проекта.

Re: контроллер не успевает считать импульсы

hadok » 01 май 2013, 21:53

Убил несколько дней на этот кусок кода, ну не понимаю я , в чём тут проблема. Мозг уже кипит :P .

Код: Выделить всёРазвернуть
while (1){

  if (Flag_usart == 1) {               //
     PORTC.4^=1;                       //
     printf("i%u %c\r\n",count,'');    //
     printf("T%u %c\r\n",time,'');     // вывод информации в порт компьютера     -->
     printf("p%u %c\r\n",pwmPD5,'');   //
     printf("s%u %c\r\n",sumint,'');   //
     Flag_usart=0;                     //
};                                     //



while (getchar()== 255) {              //
//printf("s1 %u %c\r\n",getchar(),'');   //
//printf("s2 %u %c\r\n",getchar(),'');   //
setpoint=getchar();                    // получение команды из порта компьютера   <--
//printf("s3 %u %c\r\n",setpoint,'');    //
  PORTC.4^=1;                          //
} ;      }                       

Проблема в том, что работает либо приём либо передача. То есть в том виде, как написан код, после установки flag_usart в единицу по идее должен отработать кусок кода "вывод информации в порт". Но единица появляется, в usart с компа отправлений не было, а вот код пропускается, даже breakpoint игнорирует. Ну что тут может быть не так? :O:

Re: контроллер не успевает считать импульсы

Madf » 02 май 2013, 10:20

неправильная конфигурация I/O
так же надо не забывать о том, что на вывод информации тоже требуется время и вовремя отправки (в том виде, что есть сейчас) происходит пропуск входных данных
т.е. у вас сейчас синхронный процесс (скорость входа = скорости выхода (как бы))
если хотите получить не пропускание входного сигнала, то нужно делать асинхронную передачу (на прерывании и кэше (если без кэша, то выходные данные будут не все отправлять, пропускаться будут))


Rambler\'s Top100 Mail.ru counter