GPIOだけでSPI信号を送信してみる
Send a software SPI signal(GPIO only)
前回では
Windows IoT Coreが用意してくれているSPIをやりとりするAPIを使ったプログラミングを紹介しました。電子パーツのお店で売っているLCDはI2CやSPI規格に則ったものが2,3千円くらいで売られています(表示できる内容や規模にもよります)が、ワゴンに無造作に入れられたような数百円で買える激安LCDは独自のシリアル通信を使っていたりします。今回はそういった信号のための学習として、GPIOだけによるSPI通信を試してみました。
配線図は前回とほとんど同じですが、Raspberry Pi側のピンをすべてSPIに対応していない箇所に移しています。データは書き込みのみで、読み取りは行わないので、この線は省略しています。
まずはどのようにしてSPI通信が行われているかを知る必要があります。MCP23S08のデータシートやウィキペディア英語版によると、
CSに
信号を流していない間に、総ビット分のオン・オフをSCKで持続的におこない、SCKがオン(またはオフ)になるタイミングでSI(SO)に信号があるかないかで、シリアルデータが決定されるようです。
ここでWindows特有の問題が発生します。Windows IoT CoreはUniversal Windows Appベースのため、待機状態にするには精度の低い「await Task.Delay()」しかプログラム命令がない上に、Arduinoでのdelay、Linuxでのusleepのようにミリ秒未満を指定することができません(
追記:先日発売された
トランジスタ技術2016年11月号の記事によると、Raspberry Pi自体がマイクロ秒のカウントに対応していないようです)。
こちらのブログによると、マイクロ秒単位での待機には、精度の高いStopwatchクラスのカウンターが一定数に達するまで無限ループでぶん回すという力技しか今のところ方法は無いようです。
以上の点を踏まえたプログラムコードがこちらです。つないだ各種ピンをGPIOとして宣言したうえで、信号を送るときはCSピンをオフにし、入力ピンのスイッチを切り替え、パルス信号を送信する手順を行っています。
spigpio.cs
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Devices.Gpio;
namespace SpiGpio
{
public sealed class StartupTask : IBackgroundTask
{
private BackgroundTaskDeferral deferral;
private const int GPIO_CS_PIN = 2; // External chip select input
private const int GPIO_SI_PIN = 3; // Serial data input
private const int GPIO_CSK_PIN = 4; // Serial clock input
private const int GPIO_SO_PIN = 17; // Serial data output
private GpioPin cskpin, sipin, sopin, cspin;
//Refer from : //uepon.hatenadiary.com/entry/2016/08/19/234838
private Stopwatch sw = new Stopwatch();
private const long WAIT_SEC = 10;
private void msDelay(long microSeconds)
{
long ticks = microSeconds * Stopwatch.Frequency / 1000000L;
sw.Restart();
while (sw.ElapsedTicks < ticks) ;
}
private GpioPin OpenPin(int pinno, bool input, bool high = false)
{
var pin = GpioController.GetDefault().OpenPin(pinno);
if (input == false) {
pin.SetDriveMode(GpioPinDriveMode.Output);
pin.Write((high == true) ? GpioPinValue.High : GpioPinValue.Low);
} else {
pin.SetDriveMode(GpioPinDriveMode.Input);
}
return pin;
}
private void SendByte(byte bits)
{
for (int i = 0; i < 8; i++) {
var pv = (((bits >> (7 - i)) & 0x1) != 0) ? GpioPinValue.High : GpioPinValue.Low;
sipin.Write(pv);
msDelay(WAIT_SEC / 2);
cskpin.Write(GpioPinValue.High);
msDelay(WAIT_SEC);
cskpin.Write(GpioPinValue.Low);
msDelay(WAIT_SEC / 2);
}
}
private void SendCommand(params byte[] bytes)
{
cspin.Write(GpioPinValue.Low);
msDelay(WAIT_SEC);
foreach(byte b in bytes) {
SendByte(b);
}
msDelay(WAIT_SEC);
cspin.Write(GpioPinValue.High);
}
private async void Start()
{
cskpin = OpenPin(GPIO_CSK_PIN, false);
sipin = OpenPin(GPIO_SI_PIN, false);
sopin = OpenPin(GPIO_SO_PIN, true);
cspin = OpenPin(GPIO_CS_PIN, false, true);
SendCommand(0x40, 0x00, 0x00);
Random r = new Random();
while (true) {
byte bits = 0x0;
for(int i = 0; i < 4; i++) {
if (r.Next(0, 2) == 1) bits |= (byte)(1 << i);
}
SendCommand(0x40, 0x09, bits);
await Task.Delay(500);
}
}
public void Run(IBackgroundTaskInstance taskInstance)
{
deferral = taskInstance.GetDeferral();
Start();
}
}
}
このプログラムで実行される内容は前回のLチカと全く同じなので、実行例は写真を掲載するだけにとどめておきます。
2016/10/07