AVRでPS2コントローラーを使ってみた

ロボコンを見ているとPSのコントローラとか使ってるのをよく見かけます。
私もPS2コンでロボットを動かしたくなったのでやってみました。

使用した部品は以下の通りです。
・ATmega328Pー20PU (AVRマイコン)
・DUALSHOCK2 (PS2コントローラ)
・FT232RL (マイコンの書き込みに使います)
・ELECOM ゲームパッドコンバータ USB接続 プレステ/プレステ2コントローラ対応 2ポート JC-PS102UBK (コネクタが欲しいのでこれから剥がします)
・1kΩ抵抗 (プルアップ抵抗として使います)
・3.3V三端子レギュレータ

まずPS2コントローラの簡単な仕様説明
コントローラはSPI通信の規格に従います。
コントローラのピン配置は下図のようになっています。

1 DAT SPIのMISOに対応
2 CMD SPIのMOSIに対応
3 7V
4 GND
5 3.3V IC電源
6 SEL SPIのSSに対応
7 SCK SPIのCLKに対応
8 NC
9 ACK 使いません

9番ピンのACKは特に確認する必要はないので接続しなくても問題ありません。

SPI通信の設定はマイコンのデータシートを読んでいきましょう(英語つらい.....
PS2コントローラはSlaveなので、マイコンはMaster側で設定していきます。
いろいろごちゃごちゃ書いてありますが、SPI通信の規格についてはここでは割愛させていただきます。
(気が向いたら弄ってるレジスタについてちょこっと補足書くかも…)
いじるレジスタ
SPCRレジスタと、SPSRレジスタ、あとはDDRレジスタの初期化くらいです。

SPI_Master_init

void SPI_Master_init(void)
{
	SPCR = _BV(SPE) | _BV(DORD) | _BV(MSTR);
	SPCR |= _BV(CPOL) | _BV(CPHA);
			//SPIE	=0	SPI interrupt not Enable	
			//SPI	=1	SPI Enable
			//DORD	=1	Data Order
			//MSTR	=1	Master Select
			//CPOL	=1	Leading Edge:Falling , Trailing Edge:Rising
			//CPHA	=1	Leading Edge:Setup , Trailing Edge:Sample
	DDRB = 0b00101100;
			//bit5=1	PB5(SCK):OUTPUT
			//bit4=0	PB4(MISO):INPUT
			//bit3=1	PB3(MOSI):OUTPUT
			//bit2=1	PB2(~SS):OUTPUT
	PORTB |= _BV(PB4);
			//set MISO pull-up
}

これでSPIの初期設定は終わり。
受信は使わないので、送信部分だけ書きます。

SPI_transmit

void transmit(char tdata)
{
	SPDR = tdata;
	while(!(SPSR & (1<<SPIF)));
}

あとはこれを使ってSPI通信をします。
SPIの通信は、dualshock2に合わせて送信します。
通信規格はこちらのサイトを参考にさせていただきました↓↓
https://applause.elfmimi.jp/dualshock/millar/NT/dualshock_2.txt
ここも関数にしておきましょう。

dualshock2_SPI

void dualshock2_SPI()
{
	//~SSをHIGH
	PORTB &= ~_BV(PB2);

	//01
	SPI_transmit(0x01);
	_delay_us(10);

	//02
	SPI_transmit(0x42);
	_delay_us(10);

	//03
	SPI_transmit(0x00);
	_delay_us(10);

	//04-
	uint8_t i = 0;
	for( i=0; i<=5; i++)
	{
		SPI_transmit(0x00);
		_delay_us(10);
		pad_dat[i] = SPDR;
	}

	//~SSをLOW
	PORTB |= _BV(PB2);
	_delay_us(500);
}

これらの関数を書いたうえで、プログラムを書きます。

PS2_controller.c

#define F_CPU (1E6)
#include <avr/io.h>
#include <util/delay.h>

void SPI_Master_init(void);
void SPI_transmit(char tdata);
void dualshock2_SPI();

int main(void)
{
	SPI_Master_init();
	volatile char pad_dat[6] = {0,0,0,0,0,0};
		//pad_dat[0] = pad1
		//pad_dat[1] = pad2
		//pad_dat[2] = analogRightLR
		//pad_dat[3] = analogRightUD
		//pad_dat[4] = analogLeftLR
		//pad_dat[5] = analogLeftUD
	while(1)
	{
		dualshock2_SPI();

		//ここに処理を書いていきます。

		//sample
		//○が押されているとき。
		if(~pad_dat[1] & 0b00100000 == 0b00100000)
		{
			//L1が押された時の処理
		}
		//上が押されているとき。
		if(~pad_dat[0] & 0b00010000 == 0b00010000)
		{
			//L1が押された時の処理
		}

		
	}
}

プログラム中のsampleのように、フィルタをかませてやります。
このとき、受信データはすべてビット反転してるので、否定してからフィルタをかけます。
こんな感じでがりがり書いていけばなんとか使えます。
pad_dat[3]からpad_dat[6]についてはアナログパッドに対応してます。
コントローラ側のアナログボタンを点灯させた状態にすれば、8bitのアナログ入力が取得できます。

これでなんとか使えるものになりそうです。
PS2コントローラを使えるようにするというよりは、SPI通信の練習みたいな感じでした…。

有線はこれでいいとして、無線も使ってみたいのでそのうちPS3コントローラにも手を出してみようかなと…。