roboforum.ru

Технический форум по робототехнике.
Текущее время: 18 фев 2025, 18:06

Часовой пояс: UTC + 4 часа




Начать новую тему Ответить на тему  [ Сообщений: 13 ] 
Автор Сообщение
 Заголовок сообщения: контроллер не успевает считать импульсы
СообщениеДобавлено: 22 июл 2012, 12:42 
Не в сети
Аватара пользователя

Зарегистрирован: 06 июн 2005, 02:27
Сообщения: 166
Откуда: Минск, Лида
Собрал плату для управления двигателем постоянного тока на 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_084508.jpg [ 554.08 КиБ | Просмотров: 4829 ]
IMG_20120215_140527.jpg
IMG_20120215_140527.jpg [ 785.61 КиБ | Просмотров: 4816 ]
плата.jpg
плата.jpg [ 711.11 КиБ | Просмотров: 4820 ]
Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 22 июл 2012, 13:50 
Не в сети

Зарегистрирован: 24 апр 2010, 14:47
Сообщения: 736
Откуда: Уфа
прог. языки: avr asm
А pwmPD4 что делает?

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

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

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 07:05 
Не в сети
Аватара пользователя

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

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

_________________
Россия навсегда!


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 07:26 
Не в сети

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 08:42 
Не в сети
Аватара пользователя

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

Зарегистрирован: 24 апр 2010, 14:47
Сообщения: 736
Откуда: Уфа
прог. языки: avr asm
Вывод идет только в каждом десятом переполнении, т.е. каждые 250 мс.

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

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 10:02 
Не в сети
Аватара пользователя

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

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 10:54 
Не в сети

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

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

Компилятор С сохраняет SREG автоматом в обработчиках прерываний? Или просто автору кода везет и его код работает достаточно долго, прежде чем запоротый условный переход обрушит логику?


Последний раз редактировалось legion 24 июл 2012, 11:03, всего редактировалось 1 раз.

Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 11:02 
Не в сети
Аватара пользователя

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

_________________
Россия навсегда!


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 11:17 
Не в сети

Зарегистрирован: 24 апр 2010, 14:47
Сообщения: 736
Откуда: Уфа
прог. языки: avr asm
Да, посмотрел по-диагонали инфу по printf, похоже буфер она не использует ни в каких компиляторах. Строка забивается в стек и не спеша оттуда раскуривается.

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 24 июл 2012, 11:29 
Не в сети
Аватара пользователя

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

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

_________________
Россия навсегда!


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: контроллер не успевает считать импульсы
СообщениеДобавлено: 01 май 2013, 21:53 
Не в сети
Аватара пользователя

Зарегистрирован: 06 июн 2005, 02:27
Сообщения: 166
Откуда: Минск, Лида
Убил несколько дней на этот кусок кода, ну не понимаю я , в чём тут проблема. Мозг уже кипит :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: контроллер не успевает считать импульсы
СообщениеДобавлено: 02 май 2013, 10:20 
Не в сети

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


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 13 ] 

Часовой пояс: UTC + 4 часа


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

Сейчас этот форум просматривают: Google [Bot] и гости: 0


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Русская поддержка phpBB
phpBB SEO