Fuzzy Logic управление электродвигателем с помощью контроллера MSP430x14x

В данной статье рассматривается реализация алгоритма управления на базе нечеткой логики с помощью микроконтроллера MSP430F14x.
Поскольку все семейства MSP430 совместимы снизу-вверх, то программа без труда может быть адаптирована и для других моделей контроллеров MSP430.
Алгоритмы управления на основе нечеткой логики могут быть использованы для решения вопросов, которые трудно решить традиционными методами управления. В качестве примера, демонстрируется универсальная последовательная система управления частотой вращения двигателя .
Контроль скорости вращения серийных универсальных двигателей очень сложный, когда реализуется с использованием традиционных методов управления, так как требует очень сложную математическую модель. Использование нечеткой логики устраняет необходимость математического моделирования и позволяет легко реализовать подобные решения. Нечеткая логика предоставляет правила, которые определяют поведение системы с помощью описания словами вместо математических уравнений. Алгоритм состоит из трех этапов:
1. фаззификации
2. Нечеткие определение
3. дефаззификация
В следующих разделах этой главы кратко обсудим эти три процесса. Для получения более подробной информации,
пожалуйста, обратитесь к [1].
Это значение получается путем вычитания предыдущего значения ошибки из текущего значения ошибки:
В нашем примере, пять нечетких множеств устанавливают границы для ввода значений Error и dError:
4. PS: положительное малое
5. PM: положительное среднее
Функции принадлежности (рис 1) треугольной формы, а максимальное значение масштабируется до 400H вместо 1, которое содержится в других документах, описывающих нечеткое теорию. Таким образом, сложность расчета значительно снижается, поскольку операция умножения становится только одним сложением или вычитанием.
Рис. 1. Функция принадлежности
X1[ ] = [0h, 0h, 3D0h, 30h, 0h]
X2[ ] = [0h, 0h, 3F0h, 10h, 0h]
Рис. 2. Функция принадлежности Error (X1[ ])
Рис. 3. Функция принадлежности dError (X2[ ])
Fuzzy Логический вывод
Использование max-min отношений Заде , как показал опыт , дают точные результаты.
3. Сравните их с каждым элементом вектора X2 [ ]. Меньшее значение записывается при каждом сравнении.
4. Среди участников находится максимальное значение , полученное на шаге 3.
5. Это максимальное значение затем добавляется к выходному элементу вектора, который определяется правилами Логического вывода.
6. Берется ледующий элемент вектора X1 [ ] и процесс с 3 до 6 повторяется.
Табл.1 Правила нечеткой логики
dError (X2[ ]) | ||||||
NM | NS | ZE | PS | PM | ||
Error (X1[ ]) | NM | PM | PM | PM | PS | ZE |
NS | PM | PM | PS | ZE | NS | |
ZE | PM | PS | ZE | NS | NM | |
PS | PS | ZE | NS | NM | NM | |
PM | ZE | NS | NM | NM | NM |
В данном примере выходной вектор Y [ ] является: Y [ ] = [0 ч, 30h, 3D0h, 0h, 0h]
Центроидный метод расчета дефаззификации или метод среднего
(1)
Y [ i ] являются i-м членом выходного вектора
multifact [ i ] является коэффициентом выходной функции принадлежности .
Рис.4 Выходная функция принадлежности
Используя, например выходной вектор Y [ ] = [0h, 30h, 3D0h, 0h, 0h], можно рассчитать следующие выходные значения дефаззификации:
(2)
Рис.5 Выходная функция принадлежности помноженная с вектором Y[ ]
Описание устройства на микроконтроллере
Микроконтроллер MSP430F149 используется в данноммприложении для управления двигателем . Он может быть легко заменен любой другой MSP430 микроконтроллер с доступными двумя таймерами Timer_A и Timer_B . На рисунке 6 показана схема MSP430 с тактовой частотой 8 МГц для обеспечения источника тактовых импульсов с высоким разрешением, которое используется для генерации ШИМ-регулирования и измерения скорости.
Универсальный серийный электродвигатель приводится в действие с помощью ШИМ-сигнала, генерируемого с использованием блока сравнения/ захвата Timer_B, управляется в режиме сравнения. Выходной сигнал MSP430 подается на драйвер двигателя выходного каскада. В этом примере, каскад двигателя питается от выпрямленного 230-V напряжения питающей сети.
Для обеспечения обратной связи в нечетком контуре логического управления используется оптический датчик. Этот датчик состоит из передатчик и приемника света. С помощью щелевого диска, который прикреплен к валу двигателя, генерируются 24 импульсов для каждого оборота вала двигателя . Фактическая частота вращения двигателя получается путем измерения времени между двумя последовательными импульсами с захватом блоком сравнения/захвата Timer_A , работающего в режиме захвата.
Для минимизации погрешности измерения, что имеет место в высоковольтном двигателе, используется 8-ми канальный фильтр скользящего среднего.
Рис.6 Схема устройства
После настройки микроконтроллера MSP430 часы, периферийные устройства настроены в требуемое состояние. Timer_A используется для определения фактической скорости двигателя. Передний фронт входа CCI0B (вывод порта P2.2) используется для запуска события захвата 0-м блоком сравнения/захвата таймера Timer_A. В соответствии функции ISR, считывается и обрабатывается точная временная метка Hardware- генератора . После прохождения этого значения через 8-канальный фильтр среднего скользящего, вновь рассчитанное значение скорости сохраняется в глобальной переменной CurrentSpeed.
Блок захвата / сравнения 1 сконфигурирован в режим сравнения и используется для обеспечения механизма тайм-аута для измерения скорости. Это необходимо для того, что в случае, когда двигатель не стоит на месте, захвата событий не будет генерироваться, а программное обеспечение не будеть неправильно считать, что двигатель все еще работает. Тем не менее, с реализацией этого тайм-аута, глобальная переменная CurrentSpeed все еще обновляется и алгоритм управления будет продолжать работать. Обратите внимание, что Timer_A работает в непрерывном режиме, чтобы избежать этого, любая программная задержка влияет на измерения. Блок 2 захвата / сравнения таймера Timer_B настроен на режим сравнения и используется для формирования ШИМ-сигнала. Timer_B работает в повышающем режиме и управляется источником 8MHz часов.С помощью регистра периода, установленного в 3999, эффективная выходная частота ШИМ 8MHz / (3999 + 1) = 2 кГц. Эта установка дает прекрасную детализацию для регулировки мощности двигателя, что приводит к более плавному управлению. Обратите внимание, что для нашего примера, выбор блока захвата / сравнения выбор таймеров Timer_A и Timer_B произвольный. Для более детальной информации обратитесь к [3].
Затем модуль (WDT) таймер MSP430 сторожевого конфигурируется, как интервальный таймер. Связанная с ним ISR функция вызывается 244 раз в секунду и пробуждает процессор из режима пониженного энергопотребления LPM0. Этот механизм обеспечивает интервал времени между каждым циклом управления.
После того, как периферия сконфигурирована,вводится фактический контур управления. Сразу при входе,активизируется Режим 0 с низким энергопотреблением, тем самым выключая процессор и останавливая выполнения программы на время ожидания. Выход из спящего режима вызывается WDT ISR.
При пробуждении микроконтроллера, ошибки абсолютного и дифференциального контура управления'Error' и 'dError' рассчитываются на основе заданной скорости (переменной SetSpeed), текущей скорости (переменная CurrentSpeed) и предыдущего значения ошибки (Переменная LastError). Эти значения ошибок затем преобразуются в нечеткие векторы X1 [ ] и Х2 [ ] с помощью функции фаззификации (). После того, как применяются фаззификация и нечеткие правила вывода , нечеткий вывод вектор Y [ ] генерируется посредством вызова функции FuzzyInference (). Этот выходной вектор затем преобразуется обратно в одно выходное значение контура управления с помощью вызова дефаззификации () и добавляется к текущему циклу ШИМ. Таким образом, контур управления замыкается. Обратите внимание, что два определения PWM_Min и PWM_Max используются для ограничения дежурного цикла двигателя и может потребоваться корректировка в зависимости от приложения и нагрузки.
Для обсуждения используемых алгоритмов нечеткой логики, пожалуйста, обратитесь к разделу 1. На Рисунке 7, Рисунке 8 показан пример запуска двигателя с помощью испытательной установки, которая была использована для разработки и проверки описанного в данной статье алгоритма. Скорость двигателя устанавливается на 50 rpsec, на прилагаемых графиках показан запуск двигателя без и с механической нагрузкой.
Рис.7. Запуск двигателя без нагрузки
Рис.8 Запуск двигателя с нагрузкой
Программа на языке Си для среды разработки IAR
1 //------------------------------------------------------------------------------
2 // MSP430F14x Fuzzy Logic Motor Control Demo
3 //
4 // Описание: Программа управляет двигателем с помощью алгоритма нечеткой логики Fuzzy logic
5 // Фактическая частота вращения определяется измерением времени между переходами от
6 // оптического датчика
7 // Заданная скорость записывается в переменную SetSpeed.
8 //
9 // MSP430F14x
10 // +---------------+
11 // | |
12 // | P2.2|<--- Оптический энкодер, 24 импульса на оборот
13 // | |
14 // | P4.2|---> PWM широтно-импульсная модуляция, выходной каскад привода
15 // | |
16 // | XIN/XOUT|<--- 8MHz crystal генератор тактовой частоты микроконтроллера
17 // | |
18 // +---------------+
19 //
20 // Andreas Dannenberg
21 // Texas Instruments Inc.
22 // January 2005
23 // Built with IAR Embedded Workbench Version: 3.21A
24 //
25 // Courtesy of Dr. Odry Pйter, Divйki Szabolcs, Csasznyi Andor, Bъrбny Nбndor
26 //------------------------------------------------------------------------------
27 // THIS PROGRAM IS PROVIDED "AS IS". TI MAKES NO WARRANTIES OR
28 // REPRESENTATIONS, EITHER EXPRESS, IMPLIED OR STATUTORY,
29 // INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
30 // FOR A PARTICULAR PURPOSE, LACK OF VIRUSES, ACCURACY OR
31 // COMPLETENESS OF RESPONSES, RESULTS AND LACK OF NEGLIGENCE.
32 // TI DISCLAIMS ANY WARRANTY OF TITLE, QUIET ENJOYMENT, QUIET
33 // POSSESSION, AND NON-INFRINGEMENT OF ANY THIRD PARTY
34 // INTELLECTUAL PROPERTY RIGHTS WITH REGARD TO THE PROGRAM OR
35 // YOUR USE OF THE PROGRAM.
36 //
37 // IN NO EVENT SHALL TI BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
38 // CONSEQUENTIAL OR INDIRECT DAMAGES, HOWEVER CAUSED, ON ANY
39 // THEORY OF LIABILITY AND WHETHER OR NOT TI HAS BEEN ADVISED
40 // OF THE POSSIBILITY OF SUCH DAMAGES, ARISING IN ANY WAY OUT
41 // OF THIS AGREEMENT, THE PROGRAM, OR YOUR USE OF THE PROGRAM.
42 // EXCLUDED DAMAGES INCLUDE, BUT ARE NOT LIMITED TO, COST OF
43 // REMOVAL OR REINSTALLATION, COMPUTER TIME, LABOR COSTS, LOSS
44 // OF GOODWILL, LOSS OF PROFITS, LOSS OF SAVINGS, OR LOSS OF
45 // USE OR INTERRUPTION OF BUSINESS. IN NO EVENT WILL TI'S
46 // AGGREGATE LIABILITY UNDER THIS AGREEMENT OR ARISING OUT OF
47 // YOUR USE OF THE PROGRAM EXCEED FIVE HUNDRED DOLLARS
48 // (U.S.$500).
49 //
50 // Unless otherwise stated, the Program written and copyrighted
51 // by Texas Instruments is distributed as "freeware". You may,
52 // only under TI's copyright in the Program, use and modify the
53 // Program without any charge or restriction. You may
54 // distribute to third parties, provided that you transfer a
55 // copy of this license to the third party and the third party
56 // agrees to these terms by its first use of the Program. You
57 // must reproduce the copyright notice and any other legend of
58 // ownership on each copy or partial copy, of the Program.
59 //
60 // You acknowledge and agree that the Program contains
61 // copyrighted material, trade secrets and other TI proprietary
62 // information and is protected by copyright laws,
63 // international copyright treaties, and trade secret laws, as
64 // well as other intellectual property laws. To protect TI's
65 // rights in the Program, you agree not to decompile, reverse
66 // engineer, disassemble or otherwise translate any object code
67 // versions of the Program to a human-readable form. You agree
68 // that in no event will you alter, remove or destroy any
69 // copyright notice included in the Program. TI reserves all
70 // rights not specifically granted under this license. Except
71 // as specifically provided herein, nothing in this agreement
72 // shall be construed as conferring by implication, estoppel,
73 // or otherwise, upon you, any license or other right under any
74 // TI patents, copyrights or trade secrets.
75 //
76 // You may not use the Program in non-TI devices.
77 //------------------------------------------------------------------------------
78
79 #include "msp430x14x.h"
80
81 #define PWM_Period 3999 // 8MHz / 4000 = 2kHz
82 #define PWM_Max 1199 // Максимально используемый коэффициент заполнения
83 #define PWM_Min 149 // Min duty factor used
84
85 #define MIN_SPEED 10000 // Минимальная измеренная скорость
86
87 int Array[700]; // Debug буфер
88 int pArray = 0;
89
90 unsigned int SetSpeed = 1667; // 1667 equal 50rpsec
91 // SetSpeed = f_Timer / (RevPerSec * 24)
92 // with f_Timer = 2MHz
93
94 unsigned int LastTACCR; // Измерение скоростиt vars
95 unsigned int CurrentSpeed = MIN_SPEED;
96 unsigned long SpeedMemSum = 8 * (unsigned long)MIN_SPEED;
97 unsigned int pSpeedMem = 0;
98 unsigned int SpeedMem[8] =
99 {
100 MIN_SPEED, MIN_SPEED, MIN_SPEED, MIN_SPEED,
101 MIN_SPEED, MIN_SPEED, MIN_SPEED, MIN_SPEED
102 };
103
104 long Error; // Control algorithm vars
105 long dError;
106 long LastError;
107 long PWMValue;
108
109 int X1[5]; // Fuzzy нечеткие управляющие переменные
110 int X2[5];
111 int Y[5];
112 int Output;
113
114 #define NM 0 // negative medium
115 #define NS 1 // negative small
116 #define ZE 2 // zero equal
117 #define PS 3 // positive small
118 #define PM 4 // positive medium
119
120 const unsigned char InferenceTable[5][5] =
121 {
122 { PM, PM, PM, PS, ZE },
123 { PM, PM, PS, ZE, NS },
124 { PM, PS, ZE, NS, NM },
125 { PS, ZE, NS, NM, NM },
126 { ZE, NS, NM, NM, NM }
127 };
128
129 const signed char OutputFunc[5] =
130 {
131 -0x10, -0x08, 0, 0x08, 0x10
132 };
133
134 // Function prototypes
135 void InitSystem(void);
136 void Fuzzification(int Value, int *Data);
137 void FuzzyInference(int *Error, int *dError, int *Y);
138 int Defuzzification(int *Y);
139 __interrupt void WDT_ISR(void);
140 __interrupt void TimerA0_ISR(void);
141 __interrupt void TimerA1_ISR(void);
142
143 void main(void)
144 {
145 volatile unsigned int i;
146
147 InitSystem();
148
149 while (1)
150 {
151 __bis_SR_register(LPM0_bits); // Enter LPM, wait for start of
152 // next control cycle
153
154 // If Error > 0 then motor is to fast
155 // If Error < 0 then motor is to slow
156
157 LastError = Error;
158
159 __disable_interrupt(); // Protect following statements
160 Error = (long)SetSpeed - CurrentSpeed; // Calc absolute error
161
162 if (pArray < sizeof Array/sizeof Array[0]) // Store some values for debug
163 Array[pArray++] = CurrentSpeed;
164
165 __enable_interrupt();
166
167 Error <<= 3; // Multiply by 8
168
169 dError = Error - LastError; // Calc differential error
170 dError <<= 5; // Multiply by 32
171
172 // Ensure error is within Fuzzy boundaries
173 if (Error > 0xc00) // Motor too fast?
174 {
175 TBCCR2 = PWM_Min; // Set PWM to minimum
176 continue; // Skip over control algorithm
177 }
178
179 if (Error < -0xc00) // Motor too slow?
180 {
181 TBCCR2 = PWM_Max; // Set PWM to maximum
182 continue; // Skip over control algorithm
183 }
184
185 Fuzzification(Error, X1); // Transform absolute error
186 Fuzzification(dError, X2); // Transform differential error
187 FuzzyInference(X1, X2, Y); // Apply Fuzzy rule table
188 Output = Defuzzification(Y); // Obtain scalar result
189
190 PWMValue = TBCCR2 + Output;
191 if (PWMValue < PWM_Min) PWMValue = PWM_Min; // Limit output value
192 if (PWMValue > PWM_Max) PWMValue = PWM_Max;
193 TBCCR2 = PWMValue; // Assign new PWM duty cycle
194 }
195 }
196 //------------------------------------------------------------------------------
197 // Function converts the discrete input value 'Value' into the 5-element
198 // Fuzzy vector Data[ ].
199 //------------------------------------------------------------------------------
200 void Fuzzification(int Value, int *Data)
201 {
202 int i;
203
204 for (i = 0; i < 5; i++)
205 Data[i] = 0;
206
207 if (Value < -0x800)
208 Data[NM] = 0x400;
209 else if (Value < -0x400)
210 {
211 Data[NM] = 0x400 - (Value + 0x800);
212 Data[NS] = Value + 0x800;
213 }
214 else if (Value < 0)
215 {
216 Data[NS] = 0x400 - (Value + 0x400);
217 Data[ZE] = Value + 0x400;
218 }
219 else if (Value < 0x400)
220 {
221 Data[ZE] = 0x400 - Value;
222 Data[PS] = Value;
223 }
224 else if (Value < 0x800)
225 {
226 Data[PS] = 0x400 - (Value - 0x400);
227 Data[PM] = Value - 0x400;
228 }
229 else
230 Data[PM] = 0x400;
231 }
232 //------------------------------------------------------------------------------
233 // Function applies the Fuzzy control interference rule table InferenceTable[ ][ ]
234 // to the two input arrays X1[ ] and X2[ ] to generate the output vector Y[ ].
235 //------------------------------------------------------------------------------
236 void FuzzyInference(int *X1, int *X2, int *Y)
237 {
238 int min[5];
239 int max;
240 int maxpos;
241 int i, j;
242
243 for (i = 0; i < 5; i++) // Clear output vector Y[ ]
244 Y[i] = 0;
245
246 for (i = 0; i < 5; i++) // Loop through X1[ ]
247 {
248 for (j = 0; j < 5; j++) // Loop through X2[ ]
249 if (X1[i] < X2[j]) // Determine smaller value,
250 min[j] = X1[i]; // store into min[ ]
251 else
252 min[j] = X2[j];
253
254 max = min[0]; // Find maximum in min[ ]
255 maxpos = 0;
256 for (j = 1; j < 5; j++)
257 if (max < min[j])
258 {
259 max = min[j]; // Store maximum
260 maxpos = j; // Store position of maximum
261 }
262
263 if (max > Y[InferenceTable[i][maxpos]]) // Apply inference table
264 Y[InferenceTable[i][maxpos]] += max;
265
266 if (Y[InferenceTable[i][maxpos]] > 0x400) // Limit output vector elements
267 Y[InferenceTable[i][maxpos]] = 0x400;
268 }
269 }
270 //------------------------------------------------------------------------------
271 // Function transforms the Fuzzy vector Y[ ] to a discrete value using the
272 // center of gravity method.
273 //------------------------------------------------------------------------------
274 int Defuzzification(int *Y)
275 {
276 int i;
277 int ReturnVal = 0;
278 int SumY = 0;
279
280 for (i = 0; i < 5; i++)
281 {
282 SumY += Y[i];
283 ReturnVal += Y[i] * OutputFunc[i];
284 }
285
286 return ((long)ReturnVal << 2) / SumY; // Scale result by 4
287 }
288 //------------------------------------------------------------------------------
289 // MSP430-specific initialization of clock system and timer modules
290 //------------------------------------------------------------------------------
291 void InitSystem(void)
292 {
293 volatile unsigned int i;
294
295 WDTCTL = WDTPW + WDTHOLD; // Hold WDT
296
297 // Setup Clock System
298 BCSCTL1 |= XTS; // ACLK=LFXT1=HF XTAL
299
300 do {
301 IFG1 &= ~OFIFG; // Clear OSCFault flag
302 for (i = 0xFF; i > 0; i--); // Time for flag to set
303 } while (IFG1 & OFIFG); // OSCFault flag still set?
304
305 BCSCTL2 |= SELM_3; // MCLK=LFXT1 (safe)
306
307 // Setup Ports
308 P2SEL = 0x04; // Assign P2.2 to Timer_A.CCI0B
309 P4SEL = 0x04; // Assign P4.2 to Timer_B.OUT2
310 P4DIR = 0x04; // P4.2 output
311
312 // Setup Timer_A for speed measurement
313 TACCR1 = MIN_SPEED; // Set minimum speed that is read out
314 TACCTL0 = CM_1 + CCIS_1 + SCS + CAP + CCIE; // Capture rising edge of CCI0B, interrupt
315 TACCTL1 = CCIE; // Compare mode, interrupt on EQU1
316 TACTL = TASSEL_1 + ID_2 + MC_2; // Use ACLK/4=2MHz, start in continuos mode
317
318 // Setup Timer_B for PWM generation
319 TBCCR0 = PWM_Period; // PWM Period
320 TBCCTL2 = OUTMOD_7 + CLLD0; // Set OUT2 on EQU0, reset on EQU1,
321 // sync latch load with EQU0
322 TBCTL = TBSSEL_1 + MC_1; // Use ACLK, start in up-mode
323
324 // Setup WDT for periodic interrupt
325 WDTCTL = WDTPW + WDTTMSEL + WDTSSEL; // Intervall timer, 8MHz/32768=244Hz
326 IE1 |= WDTIE;
327
328 __enable_interrupt();
329 }
330 //------------------------------------------------------------------------------
331 // The Watchdog-timer ISR is used to periodically wake-up from low-power mode
332 // LPM0 to execute the Fuzzy control loop.
333 //------------------------------------------------------------------------------
334 #pragma vector = WDT_VECTOR
335 __interrupt void WDT_ISR(void)
336 {
337 __bic_SR_register_on_exit(LPM0_bits); // Wake up from LPM
338 }
339 //------------------------------------------------------------------------------
340 // The Timer_A CCR0 ISR is called on each TACCR0 capture event to obtain
341 // the time stamp of the input signal transition. An 8-tap moving average
342 // filter is used to minimize measurement error.
343 //------------------------------------------------------------------------------
344 #pragma vector = TIMERA0_VECTOR
345 __interrupt void TimerA0_ISR(void)
346 {
347 SpeedMemSum -= SpeedMem[pSpeedMem]; // Remove oldest value
348 SpeedMem[pSpeedMem] = (unsigned int)(TACCR0 - LastTACCR); // Replace with current
349 SpeedMemSum += SpeedMem[pSpeedMem++]; // Update running sum
350 CurrentSpeed = SpeedMemSum >> 3; // Calc speed by div 8
351 pSpeedMem &= 0x07; // Adjust circular pointer
352
353 LastTACCR = TACCR0;
354 TACCR1 = LastTACCR + MIN_SPEED; // Set timeout for minimum speed
355 } // to be read out
356 //------------------------------------------------------------------------------
357 // The Timer_A CCR1 ISR is called on TACCR1 compare events, which are generated
358 // when no input signal change is detected within 'MIN_SPEED' clock ticks after
359 // the last TACCR0 event. This provides a timeout function to update the speed
360 // variables in the case the motor gets halted.
361 //------------------------------------------------------------------------------
362 #pragma vector = TIMERA1_VECTOR
363 __interrupt void TimerA1_ISR(void)
364 {
365 switch (TAIV)
366 {
367 case 0x02 : // TACCR1 CCIFG
368 SpeedMemSum -= SpeedMem[pSpeedMem]; // Remove oldest value
369 SpeedMem[pSpeedMem] = (unsigned int)(TACCR1 - LastTACCR); // Replace with current
370 SpeedMemSum += SpeedMem[pSpeedMem++]; // Update running sum
371 CurrentSpeed = SpeedMemSum >> 3; // Calc speed by div 8
372 pSpeedMem &= 0x07; // Adjust circular pointer
373
374 LastTACCR = TACCR1;
375 TACCR1 = LastTACCR + MIN_SPEED; // Set timeout for minimum speed
376 break; // to be read out
377 }
378 }
379
Литература
Но портировать на другие модели действительно не сложно.
Интересно, а почему сейчас как-то подзабылась эта тема?
Или уже не актуально?
В 90-х как то все спохватились выпускать специализированные контроллеры,
а сейчас чего-то поутихла эта тема.