Джойстик для денди на stm32


• О проекте
• Обратная связь
• Полезные ссылки
• Полезные программы
• Друзья сайта


Последние комментарии

Алексей: Управление энкодером на 400 шагов
Нет. Тоесть на пр...

Алексей: Тестовый пуск GSM модуля SIM800L
Если завис наглух...




           

Библиотека для AVR





AXLIB Генератор





Помощь сайту


   
				

Джойстик для денди на stm32

	
	
	

Дата: 12 Октября 2016. Автор: Алексей

	
	
Насмотрелся я тут в ютубе как народ играет на старых, добрых приставках из моего детства, а именно на Dendy. И меня так торкнула ностальгия, что я решил тоже поиграть. Встал вопрос. Купить приставку, коих сейчас огромное количество. Но что делать с играми. Нужно покупать кучу картриджей. Можно конечно заказать многоигровку у Кластера, но это опять покупать. Короче я остановился на эмуляторе для ПК. К тому же в любом случае нужен выход видео изображения, а монитор под это подходит как раз кстати. Скачал эмулятор, ромы игр, запустил и поиграв с пол часа ощутил себя тапером немого кино. Ну невозможно играть в Dendyвские игры с клавиатурой. Короче решил я изобразить джойстик для Dendy. Черт возьми, я же собрал себе 3D принтер, нужно его эксплуатировать))) А если честно, то меня на эту идею подбил Кластер со своим видео "Пока все играют 2". Ближе к концу он подключил джойстик к компу по USB, а потом для приставки собрал свой джойстик. Зы, посмотрев я решил скрестить эти два девайса))) А именно сделать свой джойстик для ПК через USB. И так поехали.

Для этого нам понадобится МК. У меня с USB есть куча STM32, а именно возьмем народный STM32F103C8T6. Так, Кластер использовал библиотеку LUFA с готовым функционалом джойстика. А я не хочу быть копирастом и нужно изобрести что-нибудь другое. Из HID устройств, коими и является джойстик, остаются еще мышь и клавиатура. Ну мышь соответственно не подойдет, а вот USB клавиатура как раз. Просто нужно прикрутить к каждой кнопке джойстика свою клавишу, а в эмуляторе прописать их в настройках. Первое с чего нужно начать, так это узнать как работает USB клавиатура. Роем интернет. Час, два, три.... Вашуж мать!!! Про PS/2 протокол написано столько, что можно энциклопедию по ней написать, а вот про USB молчек. Но все таки я нарыл. Официальная документация на Device Class Definition for Human Interface Devices (HID). Правда на басурманском но ничего. Я растолкую что к чему.

Протокол работы USB клавиатуры.

Передача данных происходит пакетно, то есть сама клавиатура ничего передать не может по причине того что она является девайсом. Девайс не может начать передавать данные сам, он лишь подготавливает их заранее, а когда хост(ПК) его об этом попросит, то девайс эти данные отдает. Из чего состоит пакет для передачи хосту. В пакете присутствует 8 байт. Первый байт отвечает за кнопки Shift, Alt, Ctrl, причем как за левые так и за правые. Второй байт передает что-то для клавиатуры OEM. Я честно не знаю что это и в доке пишут что для простой клавиатуры в этом байте должен быть всегда 0. Оставшиеся 6 байт передают код шести нажатых клавиш. Да, да, можно одновременно передавать 6 символов одновременно(Правда не все клавиатуры так делают, а лишь передают последний зажатый символ). Для того чтобы передать код нажатой клавиши, нужно записать заранее ее в один из шести байт и ждать пока хост не спросит. Как только спросил, передать этот пакет. Далее необходимо посмотреть, не нажата ли клавиша снова и если нет, то обязательно сказать об этом хосту. Для этого нужно в пакете исключить присутствие данного кода отпущенной клавиши. Если этого не сделать, то хост будет постоянно получать код нажатой клавиши и будет эффект удержания клавиши клавиатуры.

Теперь разрабатываем алгоритм. Пока хост занимается своими делами, нам нужно посмотреть на все кнопки джойстика и записать в пакет коды нажатых кнопок. Перед началом записи обязательно нужно обнулить пакет. После сборки данных о кнопках и записи всего этого добра, передаем эти данные хосту по его просьбе. А теперь самое интересное. Хост не может опрашивать клавиатуру как приведение, он должен это дело проворачивать в определенное время. А если быть точнее, то через какой-то оговоренный промежуток времени. Вот тут и начинаются шаманства с USB. Разбираться доскональна как работает USB я не горел желанием. Будет время почитаю, а вот чтобы срубиться с HID устройством мне пришлось полистать Агурова и почитать несколько статей и форумов. Здесь я расскажу лишь о том как я это понял и если что не так ляпну знающих попрошу меня поправить. И так USB.

На пальцах. С учетом того что шина является универсальной, то она понятия не имеет кто к ней подключился. Есть только физический уровень, который говорит хосту что подрубилось устройство. С учетом того что только хост может рулить передачей данных, то ему после подключения нового девайса не терпится узнать кто это. Для этого хост спрашивает у девайса данные о себе. Все запросы хоста к девайсу называются дескрипторы. Дескриптор это обычный массив с определенными данными в своих ячейках. Разбирать все дескрипторы я не буду, это нудно и если кому интересно почитайте Агурова. Здесь рассмотрим только самое важное. После обнаружения девайса хост просит ему представится, для этого девайс кидает хосту дескриптор устройства. Хост читает и начинает вникать кто это. Затем хост просит рассказать о конфиге девайса, на что девайс передает ему дескриптор конфигурации. Хост получив его определяет кто же это и подгружает необходимый драйвер. А затем определившись с девайсом, хост просит дескриптор репорта. Так как у нас HID устройство, а именно клавиатура, то дескриптор репорта один. А теперь давайте начнем программить. Открываем STM32CubeMX и...


Включаем USB устройства, выбираем Custom Human Interfase Device. Подрубаем внешний кварц и настраиваем нужное количество ножек на вход для кнопок. Я выбирал ножки таким образом, чтобы было легче разводить плату. Не забываем настроить ноги таким образом, чтобы они были подтянуты внутреннем резистором к земле. Мне влом было паять 10 подтягивающих резисторов)))


Теперь нам нужно настроить тактирование таким образом, чтобы шина на которой висит USB тактировалась на частоте 48МГц.


Все. Сохраняемся и генерим проект для IAR.

Умная программа сгенерит нам код, который нужно будет по минимуму допилить. Начнем с дескрипторов девайса. Расскажем кто он и что он. Для этого открываем файл usbd_customhid.c.


И находим строчку вида

/* USB CUSTOM_HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t
SBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =

За ней будет куча текста. Боятся не надо, нам нужна будет всего лишь одна строка. Находим строку вида:

0x00,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/

И меняем 0х00 на 0х01, таким образом сказав что мы будем клавиатурой.

Затем нам нужно заполнить дескриптор репорта. Для этого топаем в файл usbd_custom_hid_if.c



и находим строку вида:

/** @defgroup USBD_AUDIO_IF_Private_Variables
 * @{
 */
__ALIGN_BEGIN static uint8_t
CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =

Там будет всего пару байт. Удаляем все и заменяем вот на это.

    0x05, 0x01,          // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,          // USAGE (Keyboard)
    0xa1, 0x01,          // COLLECTION (Application)
    0x05, 0x07,          //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,          //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,          //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,          //   LOGICAL_MINIMUM (0)
    0x25, 0x01,          //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,          //   REPORT_SIZE (1)
    0x95, 0x08,          //   REPORT_COUNT (8)
    0x81, 0x02,          //   INPUT (Data,Var,Abs)
    0x95, 0x01,          //   REPORT_COUNT (1)
    0x75, 0x08,          //   REPORT_SIZE (8)
    0x81, 0x03,          //   INPUT (Cnst,Var,Abs)
    0x95, 0x05,          //   REPORT_COUNT (5)
    0x75, 0x01,          //   REPORT_SIZE (1)
    0x05, 0x08,          //   USAGE_PAGE (LEDs)
    0x19, 0x01,          //   USAGE_MINIMUM (Num Lock)
    0x29, 0x05,          //   USAGE_MAXIMUM (Kana)
    0x91, 0x02,          //   OUTPUT (Data,Var,Abs)
    0x95, 0x01,          //   REPORT_COUNT (1)
    0x75, 0x03,          //   REPORT_SIZE (3)
    0x91, 0x03,          //   OUTPUT (Cnst,Var,Abs)
    0x95, 0x06,          //   REPORT_COUNT (6)
    0x75, 0x08,          //   REPORT_SIZE (8)
    0x15, 0x00,          //   LOGICAL_MINIMUM (0)
    0x25, 0x65,          //   LOGICAL_MAXIMUM (101)
    0x05, 0x07,          //   USAGE_PAGE (Keyboard)
    0x19, 0x00,          //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x65,          //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,          //   INPUT (Data,Ary,Abs)
    0xc0                 // END_COLLECTION

Откуда я это взял... Есть вот такая замечательная программа.


У которой есть в загашнике дескрипторы для самых распространенных устройств. Теперь после заполнения дескриптора, нам нужно в дефайне указать что теперь наш дескриптор состоит не из 2 байт, а из 63. Для этого переходим в файл usbd_conf.h


И в строке

#define USBD_CUSTOM_HID_REPORT_DESC_SIZE     2

Двойку меняем на 63.

Все. Теперь мы можем общаться с хостом как клавиатура. Если скомпилить этот проект и загрузить в МК, то компьютер определит наш девайс как HID устройство, а именно как клавиатура.


Я специально PID задал как E2E4 чтобы легче было ее найти.)))

Топаем в main. В самом начале подключим файл с функционалом для общения с хостом.


/* USER CODE BEGIN Includes */
#include "usbd_customhid.h"
/* USER CODE END Includes */


Затем ниже напишем.


// Псевдонимы пинов кнопок
#define BUTTON_UP             HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define BUTTON_DOWN       HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)
#define BUTTON_LEFT          HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
#define BUTTON_RIGHT       HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)
#define BUTTON_SELEC       HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)
#define BUTTON_START       HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)
#define BUTTON_A                HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2)
#define BUTTON_B                HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)
#define BUTTON_TURBO_A   HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10)
#define BUTTON_TURBO_B   HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11)

#define BUTTON_ON         1
#define BUTTON_OFF        0  

// Коды клавиш
uint8_t button_code[10] = {
  
  0x27,         // 0
  0x1E,         // 1
  0x1F,         // 2
  0x20,         // 3
  0x21,         // 4
  0x22,         // 5
  0x23,         // 6
  0x24,         // 7
  0x25,         // 8
  0x26          // 9
  
};

// Пакет для передачи хосту
uint8_t code[8] = {0};


Что здесь написано. Для начала создаем псевдонимы функций чтения состояния каждой кнопки. Это для удобства, ведь понять BUTTON_UP проще чем HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3), согласитесь.
Теперь куда бы мы не записали псевдоним, на этом месте будет функция чтения бита порта, а именно возвращенной ей значение 1 или 0. Затем создаем псевдоним нажатой или не нажатой кнопки. Это нужно будет для функции которая будет собирать пакет перед отправкой. Далее идет массив байт с кодами кнопок. Нужно усечь, что клавиатура не возвращает ASCII код символа, она возвращает код нажатой клавиши и ей пофиг что на данной клавише нацарапано, хоть китайский иероглиф. Последним идет массив из 8 байт. Это как раз пакет для передачи хосту. На этом немного обострим внимание. Пакет хосту передается от первого элемента массива к последнему, поэтому первый элемент будет нести в себе шифты, альты и всю муру доп кнопок, а конкретно у нас нули, второй элемент всегда ноль, а с третьего до восьмого наши кнопки.

Далее идут две строки для работы с функциями USB.


/* USER CODE BEGIN 0 */
extern USBD_HandleTypeDef hUsbDeviceFS;
USBD_StatusTypeDef answer; 
/* USER CODE END 0 */


Первая нужна для передачи указателя на структуру USB Device handle, а вторая нужна для получения состояния прочитанного пакета. Далее инициализируем функцию для записи или удаления нажатой кнопки.


// Функция подготовки пакета для отправки
void build_pocket(uint8_t but_code, uint8_t status);


В самом низу после функции main пишем тело нашей функции.


// Функция подготовки пакета для отправки
void build_pocket(uint8_t but_code, uint8_t status)
{
  uint8_t i = 0;
  
  // Сбросить клавишу даже если она нажата
  for(i = 2; i < 8; i++)
  {
    if(code[i] == but_code) code[i] = 0x00;
  }
  
  // Записать код клавиши если она нажата.
  if(status == BUTTON_ON)
  {
    for(i = 2; i < 8; i++)
    {
      if(code[i] == 0x00)
      {
        code[i] = but_code;
        return;
      }
    }
  }
}


В качестве аргументов функция получает код клавиши и состояние нажата или нет.
Помните #define BUTTON_ON 1

Следующий шаг это опрос клавиш и обработка их состояния. Делаем это все в бесконечном цикле while(1){}.


 // Проверка состояния стрелки наверх
    if(BUTTON_UP)
    {
      build_pocket(button_code[0], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[0], BUTTON_OFF);
    }
    
    // Проверка состояния стрелки вниз
    if(BUTTON_DOWN)
    {
      build_pocket(button_code[1], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[1], BUTTON_OFF);
    }
    
    // Проверка состояния стрелки влево
    if(BUTTON_LEFT)
    {
      build_pocket(button_code[2], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[2], BUTTON_OFF);
    }
    
    // Проверка состояния стрелки вправо
    if(BUTTON_RIGHT)
    {
      build_pocket(button_code[3], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[3], BUTTON_OFF);
    }
    
    // Проверка состояния кнопки select
    if(BUTTON_SELEC)
    {
      build_pocket(button_code[4], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[4], BUTTON_OFF);
    }
    
    // Проверка состояния кнопки start
    if(BUTTON_START)
    {
      build_pocket(button_code[5], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[5], BUTTON_OFF);
    }
    
    // Проверка состояния кнопки A
    if(BUTTON_A)
    {
      build_pocket(button_code[6], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[6], BUTTON_OFF);
    }
    
    // Проверка состояния кнопки B
    if(BUTTON_B)
    {
      build_pocket(button_code[7], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[7], BUTTON_OFF);
    }
    
    // Проверка состояния кнопки turbo A
    if(BUTTON_TURBO_A)
    {
      build_pocket(button_code[8], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[8], BUTTON_OFF);
    }
    
    // Проверка состояния кнопки turbo B
    if(BUTTON_TURBO_B)
    {
      build_pocket(button_code[9], BUTTON_ON);
    }
    else
    {
      build_pocket(button_code[9], BUTTON_OFF);
    }
    
    // Отправить пакет хосту
    answer = (USBD_StatusTypeDef)USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, code, 8);
    
    // Ждем пока хост не забрал пакет
    while(answer != USBD_OK);


Здесь мы по очереди проверяем нажата ли клавиша или нет и в зависимости от ее состояния записываем в пакет для передачи. Проверив все кнопки отправляем пакет хосту. Но вот здесь немного по подробнее. Наш МК работает на большой скорости и проверку состояния клавиш и сбор пакета для отправки он делает очень быстро, а с учетм того что период опроса у нас в дескрипторе прописан 20мс, то мы успеем поменять данные о кнопках между запросами. Чтобы такого не случилось функция USBD_CUSTOM_HID_SendReport() может нам вернуть одно из трех значений, а именно Все хорошо, Я занята и Все упало. Из этого следует что можно просто в бесконечном цикле ждать ответа Все хорошо, а потом заново проверять кнопки. Так конечно делать не правильно, в данном цикле нужно еще проверять на Все упало или на отсутсвие занятости, но на практике за все время передача данных не разу не упала. Так что все это заливаем в МК и радуемся.

Вот принципиальная схема.


По крупнее
А это трассировка.


Страшно))) Это размеры для проектирования 3D модели корпуса))) А так на плате всего 6 перемычек и нижняя сторона сплошная земля.


И в живую.


Далее проект корпуса)))


Печатаем.


Собираем.


И напоследок архив с проектами IAR и DipTrace
JW Player goes here



vuser133    13.10.16 00:27

Отличная статья!

p.s. Акслиб под 328мегу пилится? :)

Алексей    13.10.16 07:33

Да. С некоторыми ограничениями. Я сейчас над ними работаю.

ФилиппКиркор    17.11.16 11:19

Отличная статья. А Cube только под IAR умеет код генерить?

Алексей    17.11.16 11:29

Нет, там перед генерацией можно выбрать кеил и еще какие-то.

александр    06.12.16 13:50


Автору статьи громадный респект.Но для этого случая есть более простое решение.Вскрываем клавиатуру USB,достаем плату и через текстовый редактор с контактных площадок,методом научного тыка отзваниваем интересующие нас
клавиши.Их количество определяем сами,можно отзвонить все клавиши клавиатуры.Затем припаиваем нужное нам количество проводв к плате,на фотке эти места обозначены красными точками(условно)и выводим их на коннектор
нужной нам емкости который монтируем на тыльной части клавиатуры,место там позволяет.В свое время я ставил там
коннектор от LPT(фотки не сохранились)К полученному порту можно подключить джойстик от денди,естественно переделанный,оставляем только кнопки и менем шнур.Еще варианты:загнать плату в корпус другого джойстика или
использовать вариант платы автора,но без электроники.

александр    06.12.16 13:56

my-files.ru/8y2hia
Прошу прощения первый раз ссылка не прошла.

александр    06.12.16 14:36

P.S.а для особо ленивых есть общеизвестный вариант с Xpadder.Эмулируем работу кнопок/осей любого USB джойстика в работу клавиш клавиатуры.

Алексей    06.12.16 18:25

Здесь была поставлена задача не джойстик собрать, а разобраться с USB у STM. Джойстик это побочный продукт. Я разобрался с работой порта, а ребенок поиграл в игры из моего детства.

александр    07.12.16 20:44

Здравствуйие Алексей.Я не критикую Ваш проект,он конечно супер,просто недопонял,что Вы его делали с
целью опыта реализации USB(что с моей точки зрения очень ценно) и предложил альтернативные варианты.
К слову:у Вас нет какой нибудь информации о реализации USB HID Joystik на STM?Я пытался реализовать этот
девайс на XMega,что то не не выходит,я из начинающих,видимо не мой уровень.

Алексей    07.12.16 21:14

Для STM32 есть CubeMX которая содержит библиотеку с поддержкой USB. Остается только подправить под себя дескрипторы. Для Атмела есть классная библиотека от LUFA. На ее базе тоже очень просто создать USB устройство. Но если вы новичок, то я больше чем уверен что это будет вам на данный момент не по зубам. Я прошел долгий путь в постижении программирования МК и могу сказать что у любого новичка начинают гореть глаза видя как кто-то с легкостью что-то делает и он начинает считать что это просто и прям сам все сейчас сможет сделать. Но стоит приложить усилие как появляется разочарование. Происходит это потому что нельзя просто взять и сделать что-то без подготовки. Это как понаблюдав за легкими движениями рук мебельщика, схватить стамески и с надеждой что тут все просто попытаться повторить какой-нибудь сервант. Все должно постигается потихоньку, от простого к сложному. Сначала нужно научиться уверенно мигать светодиодом. Да, да и это не шутка. Мигание светодиодом учит правильно настраивать порты ввода/вывода и их использовать. Это как азбука. А дальше как снежный ком знания накатывать в голове. И как только ком достигнет размера что можно понять принцип работы USB, то сразу все необходимые кусочки знаний встанут на свои места и становится довольно таки легко разобраться с поставленной задачей.

александр    07.12.16 22:32

Спасибо за консультацию,в ASF нашел Joystick HID Device
Demo(Class Driver APIs)/(Low LevelAPIs)-XMega Architecture,
но это цифровые джойстики другого класса,в Lufa-Lufa USB driver и Lufa Joystick Board driver,но с ними не смог разобраться.Еще вопрос:CubeMX идет с ограничением кода,взломанные версии есть?Cветодиодом пару раз мигал,понравилось,вообще программирование,по моему мнению,
очень красивый процесс.

Алексей    07.12.16 23:37

CubeMX полностью бесплатна и лежит на офсайте st.com. Библиотека HAL открыта и после генерации проекта можно по ней гулять свободно.

александр    08.12.16 13:51

Спасибо за информацию,попробую поработать с CubeMX.

E.J.SanYo    28.06.17 15:33

Поковырялся в исходниках middleware, оказалось что USBD_CUSTOM_HID_SendReport всегда возвращает USBD_OK. А если USB занят и т.д. - тупо пропускает внутри себя отправку репорта. Выходит, что смысла проверять что функция возвращает - вообще нет? Интересно, это баг или фича? Библиотеки последние, 1.8.0.

Алексей    29.06.17 12:19

А это знает только бог STMа)))




Чтобы вставить ссылку используйте форму вида[url]http://www.адрес.ru[/url][text]текст ссылки[/text]
Чтобы вставить код используйте форму вида[code]код[/code]

Имя:   





  








Вверх


Рейтинг@Mail.ru Яндекс.Метрика