roboforum.ru

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


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

Программирование микроконтроллеров AVR, PIC, ARM.
Разработка и изготовление печатных плат для модулей.

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

Сообщение 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
Аватара пользователя
hadok
 
Сообщения: 166
Зарегистрирован: 06 июн 2005, 02:27
Откуда: Минск, Лида

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

Сообщение legion » 22 июл 2012, 13:50

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

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

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

Еще меня смущают SREG=1 в прерываниях. Не знаю как в C, но в асме такая штука в прерывании может закосячить любой условный переход, который происходит при разрешенном прерывании. SREG=1 в конце обработчика EXT_INT0 еще более загадочен, т.к. сразу после него идет выход из прерывания, т.е., по идее, RETI, который сам разрешает прерывания. Или C-шный компилятор всегда использует RET? О_о
legion
 
Сообщения: 736
Зарегистрирован: 24 апр 2010, 14:47
Откуда: Уфа
прог. языки: avr asm

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

Сообщение uni » 24 июл 2012, 07:05

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

Второе, если нет осциллографа или вообще какого-то измерительного оборудования, то нужно пользоваться тем, что есть - Proteus'ом. Я не знаю может ли CodeVision создавать объектные файлы (лучше отлаживать там с исходниками), но в крайнем случае туда можно забить простой hex. Вместо того, чтобы слать куда-то числа, нужно во время начала работы прерывания выдавать на какой-то свободный вывод высокий уровень, а по окончании сбрасывать его в низкий. У Proteus'а есть виртуальный осциллограф. Там можно замерить полученные таким элементарным образом временные интервалы, при этом такие отладочные вставки никак не влияют на основной код в отличие от работы с USART.
Россия навсегда!
Аватара пользователя
uni
 
Сообщения: 23
Зарегистрирован: 11 фев 2007, 21:13
Откуда: Екатеринбург
Skype: viacheslavmezentsev
прог. языки: VB6, C++, OPascal, C#, Java, Win32Asm, ...
ФИО: Мезенцев Вячеслав Николаевич

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

Сообщение legion » 24 июл 2012, 07:26

printf(), sprintf() тормозные какие-то чтоли? Так-то байт-второй в УАРТ отправить вроде не грех, он же аппаратный. Да и более длинные сообщения при наличии буфера должны на прерываниях уходить почти не затрагивая основную работу.
legion
 
Сообщения: 736
Зарегистрирован: 24 апр 2010, 14:47
Откуда: Уфа
прог. языки: avr asm

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()'ами своих аргументов и работу с памятью. Страшно представить что там в мозгах у контроллера происходит при таком раскладе.
Россия навсегда!
Аватара пользователя
uni
 
Сообщения: 23
Зарегистрирован: 11 фев 2007, 21:13
Откуда: Екатеринбург
Skype: viacheslavmezentsev
прог. языки: VB6, C++, OPascal, C#, Java, Win32Asm, ...
ФИО: Мезенцев Вячеслав Николаевич

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

Сообщение legion » 24 июл 2012, 09:13

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

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

А вообще да, я бы вырезал все лишнее, отладку по УАРТ бы оставил, но слал бы только пару байт с подсчитанным за четверть секунды количеством импульсов. И покрутил энкодер вручную. Надо локализовать баг - в железе он, или в программе.
legion
 
Сообщения: 736
Зарегистрирован: 24 апр 2010, 14:47
Откуда: Уфа
прог. языки: avr asm

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

Сообщение uni » 24 июл 2012, 10:02

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

П.С. Вариант номер 3. Нужно останавливать таймер на всё время работы ISR по переполнению, убрав источник тактирования в самом начале ISR. Это не совсем хороший приём, так как его можно применять только в исключительных случаях, компенсируя задержку (но её нужно высчитывать для этого).
Аватара пользователя
uni
 
Сообщения: 23
Зарегистрирован: 11 фев 2007, 21:13
Откуда: Екатеринбург
Skype: viacheslavmezentsev
прог. языки: VB6, C++, OPascal, C#, Java, Win32Asm, ...
ФИО: Мезенцев Вячеслав Николаевич

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

Сообщение legion » 24 июл 2012, 10:54

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

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

Компилятор С сохраняет SREG автоматом в обработчиках прерываний? Или просто автору кода везет и его код работает достаточно долго, прежде чем запоротый условный переход обрушит логику?
Последний раз редактировалось legion 24 июл 2012, 11:03, всего редактировалось 1 раз.
legion
 
Сообщения: 736
Зарегистрирован: 24 апр 2010, 14:47
Откуда: Уфа
прог. языки: avr asm

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

Сообщение uni » 24 июл 2012, 11:02

Есть настройка скорости последовательного порта и каким бы "аппаратным" ни был USART, пока 3 printf()'а не отработают, ISR переполнения таймера не закончится. См. мою рекомендацию: вариант 3.
Россия навсегда!
Аватара пользователя
uni
 
Сообщения: 23
Зарегистрирован: 11 фев 2007, 21:13
Откуда: Екатеринбург
Skype: viacheslavmezentsev
прог. языки: VB6, C++, OPascal, C#, Java, Win32Asm, ...
ФИО: Мезенцев Вячеслав Николаевич

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

Сообщение legion » 24 июл 2012, 11:17

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

Вместо варианта 3, если бы мне непременно надо было отправлять эти 30 символов, я бы в прерывании махал флагом, а в основном цикле программы по взмаху заводил бы printf. Ну и скорость USART поднял бы.
legion
 
Сообщения: 736
Зарегистрирован: 24 апр 2010, 14:47
Откуда: Уфа
прог. языки: avr asm

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

Сообщение uni » 24 июл 2012, 11:29

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

Все переменные, время жизни которых определено фигурными скобками функции или оператора, находятся в стеке (для контроллеров с малым количеством ОЗУ тем более). А вообще, это зависит от реализации этих функций. В IAR'е, к примеру, можно выбрать из трёх вариантов реализаций в настройках проекта.
Россия навсегда!
Аватара пользователя
uni
 
Сообщения: 23
Зарегистрирован: 11 фев 2007, 21:13
Откуда: Екатеринбург
Skype: viacheslavmezentsev
прог. языки: VB6, C++, OPascal, C#, Java, Win32Asm, ...
ФИО: Мезенцев Вячеслав Николаевич

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:
Аватара пользователя
hadok
 
Сообщения: 166
Зарегистрирован: 06 июн 2005, 02:27
Откуда: Минск, Лида

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

Сообщение Madf » 02 май 2013, 10:20

неправильная конфигурация I/O
так же надо не забывать о том, что на вывод информации тоже требуется время и вовремя отправки (в том виде, что есть сейчас) происходит пропуск входных данных
т.е. у вас сейчас синхронный процесс (скорость входа = скорости выхода (как бы))
если хотите получить не пропускание входного сигнала, то нужно делать асинхронную передачу (на прерывании и кэше (если без кэша, то выходные данные будут не все отправлять, пропускаться будут))
Madf
 
Сообщения: 3298
Зарегистрирован: 03 янв 2012, 12:55
Откуда: Москва
прог. языки: VB6, BASCOM, ASM...


Вернуться в Микроконтроллеры

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 13