Эмулятор клавиатуры или мыши ps/2.

dtvims
Site Admin
Сообщения: 115
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Эмулятор клавиатуры или мыши ps/2.

Сообщение dtvims » Чт сен 06, 2012 12:33 am

Появилась у меня задача сделать некоторое устройство, управляемое с одного компа, подключенное к другому как мышь или клавиатура. Попалась мне на глаза отладочная плата микроконтроллера arduino. В целом это готовое решение по работе с микроконтроллерами ATmel. Почитав про нее подробнее, я понял, что это то что мне надо. Для обучения работы с микроконтроллерами Arduino в самый раз. Многие скажут, что для моей задачи есть варианты и лучше, и со штатной поддержкой USB, но для начинающих лучше начинать с чего-то простого и хорошо раскрученного.
Итак я нашел кучу описаний как можно проэмулировать и клавиатуру и мышь на arduino благодаря библиотеке ps2dev найденную на сайте ардуинки http://arduino.cc/playground/componentLib/Ps2mouse. Там описание почти никакое, поэтому необходимо обратиться за помощью к бывалым:

http://robocraft.ru/blog/communication/100.html - наилучшие примеры по работе с ардуино, но в данном случае не совсем то, но по теме.
http://www.computer-engineering.org/ps2protocol/ - описание протокола на английском языке, там же много полезного по теме.
http://marsohod.org/index.php/ourblog/11-blog/56-ps2 - описание протокола переведенное на русский.


На этом описание заканчивается, а проблемы нет!
1. Проблема аккуратности подключения провода с разъемом ps/2. Можно перепутать провода - это понятно. Можно получить плохой контакт, т.е. вроде он есть, но... Когда я просто взял провод от старой мышки и подключил его согласно схеме к ардуинке у меня от компа приходили постоянно reset и resend. Выходит, что arduino получает команды хоста, а отправить ответ правильно не может или не всегда отправляет. Проблема оказалась в плохом контакте, видать искрило. Кстати, я так и не понял нужно ли от порта ps/2 подключать к контролеру "Vcc +5", если контроллер питаетс от usb другого компа? Земля, разумеется общая, поэтому подключение обязательно, а vcc я оставил в итоге в воздухе, походу от нее скорее проблему будут, если конечно устройство не должно питаться от ps/2.
2. Данные вроде пошли верно, но устройство зависает, хотя инициализацию проходит. Стал разбираться. В описании протокола нашел следующее: "Контроллер материнской платы может сигнализировать устройству о невозможности приема опустив сигнал Clock в логический ноль. На практике этого по моему тоже никто не делает". Проверка путем подключения ардуинки к другому компу доказала мое предположение - тут все завелось сразу. Так в чем же дело? Демонстрационный скетч, который я использовал, выводил информацию чрез com-порт на ноутбук, которым я заодно и перепрошивал ардуинку. Одним из выводимых параметров был текст на каком Pin`е был опущен сигнал, когда программа начинает прием данных от хоста, а затем только принятый байт команды от хоста. Собственно, во время инициализации все шло хорошо, т.к. хост постоянно что-то запрашивал у устройства, а затем хост "Должен" поднимать clock и data, как бы говоря, что просто слушает порт. Но в поднятом состоянии оба pin`а находились всего пару секунд. Затем опускался clock и программа переходила в режим приема данных от хоста и все... Выходит это и есть тот самый случай, когда контроллер хоста запрещает передавать даные. Т.е. один мой комп постоянно слушает порт, а другой переиодически хочет отдохнуть - непорядок!
Смотрим код библиотеки ps2dev (C:\arduino-1.0.1\libraries\ps2dev\ps2dev.cpp), а главное команду read():

Код: Выделить всё

  //wait for data line to go low
  while (digitalRead(_ps2data) == HIGH) {

  }
  //wait for clock line to go high
  while (digitalRead(_ps2clk) == LOW) {

  }


Первая команда ожидает когда data будет опущен на землю, но поскольку хост и не собирался передавать данные, то это не происходит до тех пор, когда хосту действительно не понадобиться передать данные (например, при перезагрузке компа). Получается, что это и есть точка, где подвисает программа. Согласно протоколу, должен быть сперва опущен clock, затем data, затем, они оба поднимаются и ожидается подтверждение приема. Данный код подразумевает, что передача данных должна обязательно начаться, а вот нет. Нам надо, если data так и не опущен, а clock все-равно подняли - отменить прием данных.
Патчим бибилиотеку до:

Код: Выделить всё

//wait for data line to go low
  while (digitalRead(_ps2data) == HIGH) {
   if(digitalRead(_ps2clk) == HIGH){
      *value=0;
      return 0;
   }
  }
  //wait for clock line to go high
  while (digitalRead(_ps2clk) == LOW) {

  }

Теперь, если Хост решит отдохнуть (а зачем?), процедура чтения вернет "0х00". А может это хост и хотел передать? Интересно, что если в общем теле loop(), в конце, поставить delay(1), то таких пустых команд от хоста будет несколько, а вот если поставить delay(10), то будет отработана только одна пустая команда, а остальные пропущены. Собственно, все-равно, если хост опустил clock, то устройство не должно передавать данные согласно протоколу. Далее у нас будет несколько секунд, когда хост будет покорно слушать устройство. Если не нужна повышенная скорость реакции, то можно тут более не беспокоиться.
Собственно сам скетч для проверки/отладки эмуляции:

Код: Выделить всё

#include "ps2dev.h"

// Orange = 2
// Blue = 3
// Red = 5V (3 in)
// Black = GND (4 in)
// EXT Power, USB for COM only

PS2dev keyboard(3,2); // PS2dev object (2:data, 3:clock)
int enabled = 0; // pseudo variable for state of “keyboard”
boolean serialConnected = false;
int incomingByte = 0;
int tempint=0;

void ack() {
  //acknowledge commands
  while(keyboard.write(0xFA));
}

int kbdCmd(int command) {
  unsigned char val;
  switch (command) {
  case 0xFF: //reset
    ack();
    //the while loop lets us wait for the host to be ready
    while(keyboard.write(0xAA)!=0);
    break;
  case 0xFE: //resend
    ack();
    break;
  case 0xF6: //set defaults
    //enter stream mode
    ack();
    break;
  case 0xF5: //disable data reporting
    //FM
    enabled = 0;
    ack();
    break;
  case 0xF4: //enable data reporting
    //FM
    enabled = 1;
    ack();
    break;
  case 0xF3: //set typematic rate
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  case 0xF2: //get device id
    ack();
    keyboard.write(0xAB);
    keyboard.write(0x83);
    break;
  case 0xF0: //set scan code set
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  case 0xEE: //echo
    //ack();
    keyboard.write(0xEE);
    break;
  case 0xED: //set/reset LEDs
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  }
}

void connectHost() {
  while (Serial.available() <= 0) {
    Serial.print("A"); // send a capital A
    delay(300);
  }
}

void setup() {
  pinMode(13, OUTPUT);
  //establish serial connection with host
  Serial.begin(9600);
  // establish ps/2 connection with target
  while(keyboard.write(0xAA)!=0){
    digitalWrite(13, HIGH);
    delay(500);
    digitalWrite(13, LOW);
    delay(500);
  }
  delay(100);

  connectHost();
  Serial.println("\nSerial Host Connected");
  Serial.flush();
}

int SerialreadHEX(){
  byte c = Serial.read();
  if(c>-'0' && c<='9'){
    return c - '0';
  }
  else if (c>='a' && c<='f'){
    return c - 'a'+10;
  }
  else if (c>='a' && c<='f'){
    return c - 'a'+10;
  }
  else if (c>='A' && c<='F'){
    return c - 'A'+10;
  }
  else {
    return -1;
  }
}

void loop() {
  unsigned char c;
  if( (digitalRead(3)==LOW) || (digitalRead(2) == LOW)) {
    if(digitalRead(3)==LOW){
      Serial.println("pin 3 is LOW");
    }
    else {
      Serial.println("pin 2 is LOW");
    }
    while(keyboard.read(&c));
    kbdCmd(c);
    Serial.print("Target: 0x");
    Serial.println(c, HEX);
  }
  else {//if host device wants to send a command:
    //echo ASCII code from terminal and write to ps/2
    if(Serial.available() > 0) {
      //incomingByte = Serial.read();
      incomingByte=SerialreadHEX();
      delay(5);
      if(incomingByte>-1 && Serial.available() > 0) {
        tempint=SerialreadHEX();
        if (tempint>-1){
          incomingByte=(incomingByte*16)+tempint;
        }
      }
      keyboard.write(incomingByte);
      Serial.print("ost: 0x");
      Serial.print(incomingByte, HEX);
      Serial.print(" ");
      Serial.print(incomingByte);
      Serial.print(" ");
      Serial.println(incomingByte, BIN);
    }
  }
  delay(10);
}


Изначально код был написан неким автором (прошу прощения, потерял где взял) и доработан немного мной для более удобной отладки. Сперва устройство будет ждать инициализации com-порта, т.е. будет на него слать "а", до тех пор пока небудт получен ответ от порта в виде любого байта. Как-только в буфере контроллера окажется хоть один байт, наш эмулятор заработает. Включем испытуемый комп. Далее в "мониторе порта" мы видим как комп инициализирует клаву. Винда тоже будет ее инициализировать когда загрузится. Теперь мы можем тоже слать данные.
http://www.computer-engineering.org/ps2keyboard/scancodes2.html - тут мы видим коды клавишь. Разумеется они никак не соответсвуют кодировкам. Поэтому я сделал прием по два байта с com-порта, представляющих собой HEX код клавиши, которая будет отправлена хосту. Например вводим "1СА01С", будет проинтерпритировано как нажатие клавиши "a" (0х1C), и отпускание ее (0xF0, 0x1C). В открытом блокноте (если конечно его открыли), можно увидеть появившийся символ "a".

Вариантов использования море! Главное наконец заработало.
Далее добавлю скетч для тестирования мышки.

dtvims
Site Admin
Сообщения: 115
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Эмулятор мыши ps/2 на arduino НЕ РАБОТАЕТ!!! РЕШЕНО!!!

Сообщение dtvims » Пн сен 10, 2012 3:12 pm

После всех мучений проблем с клавиатурой, с мышой проблем возникнуть не должно! Так думал я, загружая скетч примера эмуляции мыши. Но он не заработал ни на одном из имеющихся у меня компов.
Я стал проверять, может мои дороботки в библиотеке, что-то нарушили? Я брал доделки из скетча про клавиатуру и переносил в скетч мыши и получал что-то непонятное, хотя там все работало, а тут...
В итоге решил сделать снифер ps/2 порта и посмотреть как общается реальная мыша с компом - она работала без проблем на всех компах. Я разобрал мышку и подтянул от туда провода: clock (pin 3), data (pin 2) и gnd. Я их тупо отвел на контроллер. Поскольку gnd общий, а мне надо только слушать, то поидее никаких сложных схем подключения не требуется.
Первый скетч показал, что вывод стразу лога протокола ps/2 на com-порт - неработоспособен, поскольку передача через com-порт слишком долгая по времени, а между тактами clock проходит от 20 микросекунд и мне нельзя попустить ни одно изменение. Также, если все ходы просто писать в память, то памяти ATmega32 просто не хватит для этого.
В итоге получился такой скетч (кому интересно):

Код: Выделить всё

int stclk=0;
int stdat=0;
int i=0;
unsigned long tm[150];
int arclk[150];
int ardat[150];

void gohi(int pin)
{
  pinMode(pin, INPUT);
  digitalWrite(pin, HIGH);
}

void golo(int pin)
{
  pinMode(pin, OUTPUT);
  digitalWrite(pin, LOW);
}

void setup() {
  Serial.begin(9600);
  Serial.println("\nSerial Host Connected");
  Serial.flush();
  gohi(2);
  gohi(3);
}

void loop() {
  int vl3;
  int vl2;
 
  vl2=digitalRead(2);
  vl3=digitalRead(3);
  if((stclk!=vl3 || stdat!=vl2)&& i<150){

    tm[i]=micros();
    stclk=vl3;
    stdat=vl2;
    arclk[i]=vl3;
    ardat[i]=vl2;

    i++;
  }else{
    if(micros()-tm[i-1]>10000 && i>20){
      for(int j=0;j<i;j++){
        Serial.print(tm[j]);
        Serial.print(";");
        Serial.print(arclk[j]);
        Serial.print(";");
        Serial.print(ardat[j]);
        Serial.println(";");
      }
      i=0;
    }
  }
}


Его основной недостаток, что если во время передачи логов на com-порт хост или устройство начнет передавать данные, то я не услышу (т.е. в логи это не попадет).
Так и вышло. Я получал? что хост отправил FF, мышь ответила FA, а дальше тишина, т.е. команд AA и 00, следующих далее от мыши я не получал. Как реально отвечает мыша я проверял по вот такому примеру http://robocraft.ru/blog/arduino/101.html. Однако я увидел там как работает реально протокол. Я увидел, что между командой хоста и ответом устройства проходит чуть более 100 микросекунд. А когда я продолжил эксперименты с эмулятором мыши, хост постоянно повторял одни и те же команды по много раз. Я сделал вывод, что хост ждет ответа несколько сотен микросекунд и если его не получает, то шлет запрос повторно. А вывод отладочной информации на com-порт занимает более 1 милисекунды - видимо тут один из косяков.
Что сделано:
1. В ps2dev в функциях read и write в конце скорректирована/добавлена задержка в 100 микросекунд. Я сделал расчет, что несколько микросекунд программа будет обрабатывать команду и запускать на выполнение команды ответа, то как раз получится задержка в 100 микросекунд с чуть-чутем.
2. Убрал из скетча все затормаживающие элементы между приемом/отправкой связанных данных, а оставил только уж по окончанию как факт о случившемся.
3. Поскольку в процессе отладки скетча, после первых двух доработок, хост продолжал повторно слать одни и те же команды, я проверил по документации http://www.computer-engineering.org/ps2mouse/ как именно должны отрабатываться устройством эти команды, т.е. добавил обработку команд, на которые в примере было ответом только подтверждение о приеме команды.
Вот что получилось:


Код: Выделить всё

#include "ps2dev.h"

PS2dev mouse(3,2); // 2 data 3clock

char buttons[3] = {0,0,0};

int delta_x = 0;
int delta_y = 0;
//we start off not enabled
int enabled =0;
int incomingByte=0;
int tempint=0;
int sr=20;
int rsl=0;
int oldcmd;

//ack a host command
void ack() {
  while(mouse.write(0xFA)!=0);
}

void write_packet() {
  char overflowx =0;
  char overflowy =0;
  char data[3];
  int x,y;
 
  if (delta_x > 255) {
    overflowx =1;
    x=255;
  }
  if (delta_x < -255) {
    overflowx = 1;
    x=-255;
  } 
  if (delta_y > 255) {
    overflowy =1;
    y=255;
  }
  if (delta_y < -255) {
    overflowy = 1;
    y=-255;
  }
 
  data[0] = ((overflowy & 1) << 7) |
    ( (overflowx & 1) << 6) |
    ( (((delta_y &0x100)>>8) & 1) << 5) |
    ( ( ((delta_x &0x100)>>8)& 1) << 4) |
    ( ( 1) << 3) |
    ( ( buttons[1] & 1) << 2) |
    ( ( buttons[2] & 1) << 1) |
    ( ( buttons[0] & 1) << 0) ;
   
  data[1] = delta_x & 0xff;
  data[2] = delta_y & 0xff;
 
 
  while(mouse.write(data[0])!=0);
  while(mouse.write(data[1])!=0); 
  while(mouse.write(data[2])!=0);
  //while(mouse.write(0x00)!=0); // Scroll
  printserial(data[0]);
  printserial(data[1]);
  printserial(data[2]);
 

  delta_x = 0;
  delta_y = 0;
}

void printserial(int c){
    Serial.print("Device: 0x");
    Serial.println(c, HEX);
}
void printhost(int c){
    Serial.print("Host: 0x");
    Serial.println(c, HEX);
}

int mousecommand(int command) {
  unsigned char val;

  //This implements enough mouse commands to get by, most of them are
  //just acked without really doing anything

  switch (command) {
  case 0xFF: //reset
    ack();
    //the while loop lets us wait for the host to be ready

    while(mouse.write(0xAA)!=0); 
    while(mouse.write(0x00)!=0);
 
    break;
  case 0xFE: //resend
    //ack();
    mousecommand(oldcmd); // рекурсивный повтор предыдущей команды - возможен сбой при частых ошибках.
    break;
  case 0xF6: //set defaults
    //enter stream mode   
    ack();
    break;
  case 0xF5:  //disable data reporting
    //FM
    ack();
    break;
  case 0xF4: //enable data reporting
    //FM
    enabled = 1;
    ack();
    break;
  case 0xF3: //set sample rate
    ack();
    mouse.read(&val); // for now drop the new rate on the floor
    ack();
    sr=val; // надо запомнить, что от нас хочет хост
    printhost(val);
    break;
  case 0xF2: //get device id
    ack();
    while(mouse.write(0x00)!=0);
    break;
  case 0xF0: //set remote mode
    ack(); 
    break;
  case 0xEE: //set wrap mode
    ack();
    break;
  case 0xEC: //reset wrap mode
    ack();
    break;
  case 0xEB: //read data
    ack();
    write_packet();
    break;
  case 0xEA: //set stream mode
    ack();
    break;
  case 0xE9: //status request
    ack();
    while(mouse.write(0xE6)!=0); // Даем инфу о настройках мыши
    while(mouse.write(rsl)!=0);  // и если потребуется запомненные от хоста параметры,
    while(mouse.write(sr)!=0);   // если тот проверить захочет
    //      send_status();
    break;
  case 0xE8: //set resolution
    ack();
    mouse.read(&val);
    ack();
    rsl=val; // Запоминаем параметры
    printhost(val);
    break;
  case 0xE7: //set scaling 2:1
    ack();
    break;
  case 0xE6: //set scaling 1:1
    ack();
    break;
  case 0x0: // Такой команды нет, но есть такая заглушка, ответа не требуется
    break;
  default: // Вообще на любую команду нужен ответ
    ack();
  }
  if(command!=0xfe){ // Если не ошибка, то запоминаем последнюю команду
    oldcmd=command;
  }
}

int xcenter ;
int ycenter;

int xsum = 0;
int ysum = 0;

void setup() {
  unsigned char val;

  Serial.begin(9600);
 
  Serial.println("\nSerial Host Connected");
  Serial.flush();
 
  // При старте ничего отправлять хосту не требуется, только если тот попросит.
  // Более того, если отправить вдруг (после инициализации мыши хостом) AA и 00, 
  // то хост опустит clock и на этом все закончится - требуется перезагрузка хоста.
}


int SerialreadHEX(){
  byte c = Serial.read();
  if(c>-'0' && c<='9'){
    return c - '0';
  }
  else if (c>='a' && c<='f'){
    return c - 'a'+10;
  }
  else if (c>='a' && c<='f'){
    return c - 'a'+10;
  }
  else if (c>='A' && c<='F'){
    return c - 'A'+10;
  }
  else {
    return -1;
  }
}


void loop() {
  unsigned char  c;
  if( (digitalRead(3)==LOW) || (digitalRead(2) == LOW)) {
    while(mouse.read(&c)) ;   
    mousecommand(c);
    printhost(c);
  }else{
    if(Serial.available() > 0) {
      incomingByte=SerialreadHEX();
      delay(5);
      if(incomingByte>-1 && Serial.available() > 0) {
        tempint=SerialreadHEX();
        if (tempint>-1){
          incomingByte=(incomingByte*16)+tempint;
        }
      }
      while(mouse.write(incomingByte)!=0);
      Serial.print("ost: 0x");
      Serial.print(incomingByte, HEX);
      Serial.print(" ");
      Serial.print(incomingByte);
      Serial.print(" ");
      Serial.println(incomingByte, BIN);
    }
  }
  if (enabled) {
    // move the mouse diagonally
    delta_x = 1;
    delta_y = 1;
    write_packet();
  }

}


О чудо - данный скетч заработал!

Модифицированный ps2dev.cpp:

Код: Выделить всё

/*
 * ps2dev.cpp - an interface library for ps2 host. 
 * limitations:
 *      we do not handle parity errors.
 *      The timing constants are hard coded from the spec. Data rate is
 *         not impressive.
 *      probably lots of room for optimization.
 */

#include "Arduino.h"
#include "ps2dev.h"


//since for the device side we are going to be in charge of the clock,
//the two defines below are how long each _phase_ of the clock cycle is
#define CLKFULL 40
// we make changes in the middle of a phase, this how long from the
// start of phase to the when we drive the data line
#define CLKHALF 20

/*
 * the clock and data pins can be wired directly to the clk and data pins
 * of the PS2 connector.  No external parts are needed.
 */
PS2dev::PS2dev(int clk, int data)
{
  _ps2clk = clk;
  _ps2data = data;
  gohi(_ps2clk);
  gohi(_ps2data);
}

/*
 * according to some code I saw, these functions will
 * correctly set the clock and data pins for
 * various conditions.  It's done this way so you don't need
 * pullup resistors.
 */
void
PS2dev::gohi(int pin)
{
  pinMode(pin, INPUT);
  digitalWrite(pin, HIGH);
}

void
PS2dev::golo(int pin)
{
  pinMode(pin, OUTPUT);
  digitalWrite(pin, LOW);
}

int PS2dev::write(unsigned char data)
{
  unsigned char i;
  unsigned char parity = 1;

  //   Serial.print("sending ");
  //Serial.println(data,HEX);

 /* if (digitalRead(_ps2clk) == HIGH && digitalRead(_ps2data) == HIGH) {
   delayMicroseconds(50);
  }*/
   
  if (digitalRead(_ps2clk) == LOW) {
    return -1;
  }

  if (digitalRead(_ps2data) == LOW) {
    return -2;
  }


  golo(_ps2data);
  delayMicroseconds(CLKHALF);
  // device sends on falling clock
  golo(_ps2clk);   // start bit
  delayMicroseconds(CLKFULL);
  gohi(_ps2clk);
  delayMicroseconds(CLKHALF);

  for (i=0; i < 8; i++)
    {
      if (data & 0x01)
   {
     gohi(_ps2data);
   } else {
   golo(_ps2data);
      }
      delayMicroseconds(CLKHALF);
      golo(_ps2clk);   
      delayMicroseconds(CLKFULL);
      gohi(_ps2clk);
      delayMicroseconds(CLKHALF);

      parity = parity ^ (data & 0x01);
      data = data >> 1;
    }
  // parity bit
  if (parity)
    {
      gohi(_ps2data);
    } else {
    golo(_ps2data);
  }
  delayMicroseconds(CLKHALF);
  golo(_ps2clk);   
  delayMicroseconds(CLKFULL);
  gohi(_ps2clk);
  delayMicroseconds(CLKHALF);

  // stop bit
  gohi(_ps2data);
  delayMicroseconds(CLKHALF);
  golo(_ps2clk);   
  delayMicroseconds(CLKFULL);
  gohi(_ps2clk);
  delayMicroseconds(CLKHALF);

  delayMicroseconds(100);
  return 0;
}


int PS2dev::read(unsigned char * value)
{
  unsigned char data = 0x00;
  unsigned char i;
  unsigned char bit = 0x01;
 
  unsigned char parity = 1;
 
  //wait for data line to go low
  while (digitalRead(_ps2data) == HIGH) {
   if(digitalRead(_ps2clk) == HIGH){
      *value=0;
      return 0;
   }
  }
  //wait for clock line to go high
  while (digitalRead(_ps2clk) == LOW) {

  }

 
  delayMicroseconds(CLKHALF);
  golo(_ps2clk);
  delayMicroseconds(CLKFULL);
  gohi(_ps2clk);
  delayMicroseconds(CLKHALF);

  for (i=0; i < 8; i++)
    {
      if (digitalRead(_ps2data) == HIGH)
   {
     data = data | bit;
   } else {
      }


      bit = bit << 1;
     
      delayMicroseconds(CLKHALF);
      golo(_ps2clk);   
      delayMicroseconds(CLKFULL);
      gohi(_ps2clk);
      delayMicroseconds(CLKHALF);
     
      parity = parity ^ (data & 0x01);
    }
  // we do the delay at the end of the loop, so at this point we have
  // already done the delay for the parity bit

  // stop bit
  delayMicroseconds(CLKHALF);
  golo(_ps2clk);   
  delayMicroseconds(CLKFULL);
  gohi(_ps2clk);
  delayMicroseconds(CLKHALF);
 

  delayMicroseconds(CLKHALF);
  golo(_ps2data);
  golo(_ps2clk);   
  delayMicroseconds(CLKFULL);
  gohi(_ps2clk);
  delayMicroseconds(CLKHALF);
  gohi(_ps2data);

  delayMicroseconds(100);
  *value = data;
 
  return 0;
}


Мне говорили, что USB сложнее, но по USB полно примеров, а вот по ps/2 очень мало. Видимо это следствие того, что ps/2 стал вытеснятся usb. Однако, если еще не так давно с материнских плат стали исчезать порты ps/2, то на последних они стали возвращаться. Зачем? Видимо из-за использования свичей для переключения между системными блоками, в которых использоваться может что-то одно и если свитч на ps/2-ные мышь и клаву, то через эти порты они и должны подключаться на каждом системнике. Мышь и клава давно научились поддерживать одновременно оба интерфейса, а вот свичи нет. Т.о. ps/2 - это, хоть и устаревший интерфейс, но жизнестойкий!

Вариант использования:
На различных предприятих очень жесткие правила политики безопасности, могут быть ограничены права пользователя в системе, может быть заблокировано использование внешних устройств (например USB флешек и т.п.) и не отключаемый скринсейвер, а может и лог присутствия на рабочем месте. А ведь даже для работы бывает необходимо что-то включить, а скринсейвер сбивает работу. Или необходимо симулировать свое присутствие на рабочем месте. Была такая история, что кому-то необходимо было обойти скринсейвер, для этого прицепили провод мыши к cd-rom`у и написали простенькую прогу для открывания закрывания привода cd-rom. На ПО может не хватить прав, чтобы обойти такую защиту. Механические системы вроде цепляния к проводу мышки к cd-rom`у не надежны. А Ps/2 обычно ни чем не защищен, поэтому нам ничто не мешает нацепить на него эмулятор мышки.

dtvims
Site Admin
Сообщения: 115
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Re: Эмулятор клавиатуры или мыши ps/2.

Сообщение dtvims » Вт сен 11, 2012 9:22 am

В добавлении к протоколу PS/2.
Нашел некие обсуждения на форумах:
1. Работа мышки перебивает работу клавиатуры.
2. При передачи от устройства к хосту присутствует сигнал ack от хоста, что данные получены.

1. Мы в реальных условиях такого ни разу не замечали. Разумеется, что если передавать данные не глядя, то можно попасть на режим, когда хост запрещает передачу данных опуская clock на землю и тогда данные не дойдут.
Судя по рассуждениям форумчан, не смотря на независимость работы портов мыши и клавиатуры, они не могут обрабатываться независимо и поэтому, во время работы одного из них на втором хост опускает clock. Таким образом, если проверять свободу на линии, то потерь передачи данных не должно возникнуть.
2. Согласно описанию протокола сигнал ack присутствует только при передаче от хоста к устройству. Обратно такого нет. Это же подтверждает исследование снифером. Однако возможна ситуация, что сразу после передачи данных хост может опускать clock на землю, просто ради того чтобы устройство прекратило временно передачу. Данный сигнал очевидно нельзя принимать за ack.

dtvims
Site Admin
Сообщения: 115
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Клавиатура и Мышь одновременно на одном контроллере.

Сообщение dtvims » Пн сен 24, 2012 3:50 pm

Клавиатура и Мышь одновременно на одном контроллере.
Стоит заметить, что на предложенной библиотеке не сделать адекватно работающие одновременно сразу 2 устройства ps/2. Не смотря на то, что я раннее писал, что хосты на материнской плате ПК вещают в разное время, т.е. конфликтовать не должны. Оказалось, что хост для мыши начинает создавать запрос, опускает на землю сперва clock (отработано заглушкой выше), а затем опускает data, что согласно протоколу является началом передачи данных. Но реально данные начинают передаваться только когда хост отпустит data. Даже если реально данных никаких хост не отправит, согласно протоколу, устройство данный сигнал воспримет как FF. Самое интересно, что на FF обязательно последует ответ, а если ответ не будет получен хостом, то он тихо воспримет это как полное отсутсвие устройства. Одним словом, замкнуты круг. Можно, наверное поставить еще одну заглужку... НО! Для нормальной инициализации клавы или особенно мышки, важно отвечать ровно как хост запросит информацию. Далее я выяснил, что все задержки между приемом и передачей данных устройством должны быть минимальны и снова, НО! Оказалось, что хост делает запрос, а затем держит clock опущенным некоторое время (где-то до 1 милисекунды, притом что протокол ps/2 делает перерывы между итерациями 20-40 микросекунд). Т.о. получается, что нам надо срочно передавать данные, да еще и ждать, когда можно будет передавать, а во время ожидания может пройти запрос и на другое устройство, а ведь может и пользователь со своей командой встрять...
Как правильно решить данную проблему?
Опытные программисты, скажут, что надо вешаться на прерывания таймера и обрабатывать запросы в несколько потоков.
Как настроить таймер - это вообще отдельная тема, вроде и документации много, а как сделать то что я хочу не совсем ясно. Тут у меня возникли непонятки почему при одних и техже настройках таймер в одном случае вызывает прерывание через 40мкс, а вдругом 20мкс (как мне и надо было).
В общем я добился прерываний раз в 20мкс. это необходимо как минимальное время бездействия по потоколу (например, мы поднимем clock, ждем 20мкс, задаем data, ждем 20мкс и опускаем clock).
Далее опишу в общих словах алгоритм, что происходит внутри прерывания раз в 20 микросекунд:
1. В самых первых строках я через порты устанавлию/снимаю сигнал с clock и data, просто снимаю состояние и сохраняю в памяти, а затем сразу ставлю значения, которые до этого уже были расчитаны за ранее. Если расчеты делать перед установкой значений, сразу после срабатывания прерывания, то не возможно будет соблюсти точность сигнала с паузами в ровно 20микросекунд.
2. Для реализации протокола я создал, наверное, несколько конечных автоматов (или один). 1-й автомат меняет основные состояния устройства. Проверка хоста, хочет ли он что нам сказать? Прием данных от хоста. Передача данных хосту. И наконец служебный контроллер для определения, нужен ли ответ хосту дать или принять от хоста настройки. Т.е. есть 4 состояния.
2.1 Проверка на передачу данных от хоста осуществляется простыми проверками состояний сигналных контактов, см п.1. Тут также посчет времени, а сколько у нас держался clock? и т.п.
2.2. Если хост проинициировал передачу данных, то отправляемся в следующее состояние "прием".
2.3. "Прием" (абсолютно аналогично "передача") осуществляется итерационно (2-й автомат). Сперва ожидание когда хост отпустит data. Затем начинаем расчитывать, что при будетследующей итерации (вспоним, что в п.1 устанавливатся состояния контактов согласно за ранее расчитанным позициям). Нужно начать генерить сигнал синхронизации clock. И далее в нужный момент начинаем снимать стояние data (устанавливать, если передаем). Каждое новое состояние - новая итерация по протоколу (как-то так).
2.4. Поле приема отправлем на служебный контроллер.
2.5. Контроллер смотрит что была за команда принята, а в нем забиты действия вроде "отправить ACK, принять байт, отправить ACK".
2.6. Передача данных (3-й автомат). Аналогично приему расчитывает за ранее следующую итерацию. После пердачи, снова на контроллер.
2.7. Если контроллер отследил, что запрос хоста отработан верно, т.е. все необходимые ответы отправлены и настройки получены, то переводит в состояние по п.2.1.
3. Если никаких данных от хоста принимать не следует, то можно заняться обработкой пользовательских запросов. После проверок состояний п.2.1. вставляем пользовательский контроллер, который считывает данные из буфера на передачу и отправит данные на хост с помощью п.2.3.

Все пункты реализованы в одном прерывании таймера одновременно. Сперва считываются/устанавливаются состояния на пинах мыши и клавиатуры, затем для них расчитывается, что делать дальше.

На этот раз обойдусь без исходников, скажу только, что алгоритм работает. Его можно доработать в плане настройки пирема параметров от хоста, т.к. там еще много мелких тонкостей осталось. Таже есть возможность ошибок буфера данных, если пользователь будет пытаться передавать команды мыши во время инициализации мыши, но это мало вероятных случай, хотя возможен (ошибка может возникнуть из-за того, что пакет данных от мыши состоит из 3-х байтов и, если хост их будет принимать начиная со второго, то получит что-то иное нежели хотел пользователь.

Если есть желание получить воспользоваться моим кодом, то пишите на мой ящик на mail.ru, пользователь dtvims (вот такой квест: догадаться какой ящик). Если есть достойна цель и сохраните мое авторство, то милости просим.

UPD: Ввиду популярности вопроса про PS/2, решил все-таки выложить исходный код своей реализации. Не помню последняя ли это версия, но вроде она. Есть куча споров: может ли эмулировать Arduino PS/2 мышку и клавиатуру, тем более одновременно? ОТВЕТ: Может!
Если есть вопросы, не стесняйтесь, задавайте!
Вложения
ps2_mouse_kbd_int.zip
(2.7 КБ) 368 скачиваний

dtvims
Site Admin
Сообщения: 115
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Вариант использования.

Сообщение dtvims » Пн сен 24, 2012 4:12 pm

Собственно, есть такие устройства: IP KVM switch - устройство позволяющее по сети управлять любым компом еще до загрузки ОС. Такой свитч подключается как клавиатура, мышка и монитор к системному блоку, а пользователь с произвольного компьютера получает через сеть доступ к данному системному блоку. Минимально - это видеть картинку на мониторе и управлять клавой и мышой, а как максимум еще и управлять питанием. Но стоит такое устройство от 15 000р. У китайцев подобной техники не видел, а значит цены никто не демпенгует.
МК, вроде ATMega способен управлять клавой и мышой, а также питанием. Процесорного времени хватит и еще на чуть-чуть. Самое простое что может прийти в голову - это взять 2 servo-машинки, прицепить к ним web-камеру, которую необходимо поставить перед монитором. Тут только одна проблема: кто будет снимать видео с web-камеры.
Помимо web-камеры, есть еже преобразователь с VGA на S-video стоимостью около 5$ (качества для отображения БИОСА и предварительной загрузки ОС более чем достаточно, далее можно подключиться используя ресурсы ОС). А оцифровать s-video можно простым адаптером на USB за 18$ (Благодаря китайской промышленности).
Недостаток такого решения, что для управления нужен еще один комп, к которому надо подключить USB устройства. Но старый комп для этого найти не проблема. Всегда есть что завалялось и ни кому не надо, т.к. устарело, нам-то много ли надо? Добавим сюда еще WAKE ON LAN и данный системник включится, только когда понадобиться и не будет тратить драгоценное электричество. Вообще тут идей еще много. Можно вместо системника использовать старый роутер, но тут тоже есть свои камни предкновения, т.к. по винду набросать ПО для управления всей этой системой гораздо легче, да и не каждый роутер потянет нагрузку обработки видео.
Мне осталось почти только собрать полный прототип комплекса. Но я еще хочу сделать и USB эмулятор мыши и клавы для полного счастья...

vanyaacido
Сообщения: 9
Зарегистрирован: Вс дек 14, 2014 9:36 pm
Откуда: Россия
Контактная информация:

Эмулятор клавиатуры или мыши ps/2

Сообщение vanyaacido » Пн янв 19, 2015 10:08 pm

Друзья Помогите найти эмулятор корейской клавиатуры. Чтобы в нем можно было мышкой выбирать буквы, а затем готовые фразы копировать в буфер и вставлять, например, в Word.

MoskKsusBiz
Сообщения: 10
Зарегистрирован: Пн окт 19, 2015 10:43 am

Эмулятор клавиатуры или мыши ps/2

Сообщение MoskKsusBiz » Ср фев 03, 2016 7:45 am

Ну, это перебор. Написать код опроса клавиатуры - неужто так сложно? Если сложно, тогда зачем вообще микроконтроллеры? Чтоб навешивать вот такую кучу внешних деталей вместо того, чтобы обойтись без этого?


Вернуться в «Микроконтроллеры и автоматизация»

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

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