之前用MAX7219点阵做过跑马灯显示,但其实对MAX7219的显示原理并不清楚,大概知道是SPI的协议。这次通过AndroidThings来彻底搞明白怎么显示的。首先看一下MAX7219点阵的原理图:

因为MAX7219是用来控制数码管,所以在原理图上可以看的DIG和SEG,这本来对应数码管的位和段,但在点阵模块中对应行和列,这样我们就把引脚和每一个点阵中的led对应起来了。

然后再来研究一下MAX7219数据传输。MAX7219每一次数据传输是16位,其中高八位表示寄存器地址,低八位表示数据,而高八位的地址中只有低4位才有意义,因为MAX7219只有15个寄存器用4位就可以表示了。

再回到原理图上,每一个DIG对应于一个寄存器,而这个寄存器中的数据就和SEG一一对应,也就是说8x8点阵中的一行对应一个寄存器地址,寄存器中的数据中的每一位对应这一行中的SEG值。另外还需要注意MAX7219可以设置译码方式,这主要是为了简化数码管的现实,设置BCD译码之后可以直接用真实的数字来表示数码管的显示,而不用去组合各个SEG的亮灭。但是在控制点阵时就用不上这个功能了。在非译码模式下,寄存器数据与各个SEG的对应关系如下图所示:

而每一行的寄存器地址是从0开始的,DIG0也就是第一行的寄存器地址是0,依次类推第八行对应的寄存器地址是1。这样就基本能够理解点阵的显示原理了。

比如说我们要让第一行第一个led点亮,那就向寄存器地址0写入10000000b,如果想要第一行全亮,那就写入11111111b。以数字0为例,想要在8x8点阵上显示,各行的亮灭情况如下所示:

其中红色的数字就是我们需要依次向0,1,2寄存器地址写入的数据。这样整个显示过程就很清晰了吧。

再回到数据传输上,16位数据中的寄存器概念并不是SPI协议本身,SPI每次传输8位数据。而MAX7219是按16位数据为一组数据对待,所以MAX7219需要知道数据起始,这时就需要CS来配合了,当CS为低电平时表示传输数据,为高电平时表示数据传输结束,否则传输的数据会被丢弃。

在Things中使用SpiDevice的write函数就可以写入数据,然后配合GPIO控制CS的高低电平,以上图中第一行数据为例,代码大概如下:

1
2
3
csGPIO.setValue(false);
spi.write({0x00, 0x7e}, 2);
csGPIO.setValue(true);

其实掌握了以上这些,然后对SPI协议也比较了解的话,就会发现网上的许多代码是通过GPIO来模拟SPI通信来驱动Max7219点阵的。现在我们来尝试直接用Things的SpiDevice来驱动Max7219。

首先,获取SpiDevice,这个跟之前的获取GPIO和UART类似,使用PeripheralManagerService的openSpiDevice函数即可,这里我们用的是SPI0.0,引脚如下所示:

将BCM10接DIN,BCM11接CLK,BCM25接CS,GND接Ground,VCC接3.3V。

初始化SPI及CS GPIO的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private boolean initSPI () {
boolean success = false;

try {
PeripheralManagerService manager = new PeripheralManagerService();
List<String> deviceList = manager.getSpiBusList();
if (deviceList.isEmpty()) {
Log.i(TAG, "No UART port available on this device.");
} else {
Log.i(TAG, "List of available devices: " + deviceList);
}

if (mGpioCS == null) {
mGpioCS = manager.openGpio("BCM25");
mGpioCS.setDirection(Gpio.DIRECTION_OUT_INITIALLY_HIGH);
}

if (mDevice == null) {
mDevice = manager.openSpiDevice("SPI0.0");
mDevice.setMode(SpiDevice.MODE2);
mDevice.setFrequency(8000000); // 8MHz
mDevice.setBitsPerWord(8); // 8 BPW
mDevice.setBitJustification(false); // MSB first

initMax7219();
}

success = true;
} catch (IOException e) {
Log.w(TAG, "Unable to access SPI device", e);
} finally {
return success;
}
}

在Max7219的Datasheet中提到CLK的最大频率是10MHz,这里我们设置为8MHz。

然后我们封装一个向Max7219发送一个字节的函数:

1
2
3
4
private void writeMax7219Byte(byte data) throws IOException {
byte[]out = {data};
mDevice.write(out, 1);
}

一般来说网上看到这部分的代码是通过GPIO来模拟的,我以前在NanoPi上用GPIO模拟SPI的C代码如下:

1
2
3
4
5
6
7
void WriteMax7219Byte(uchar data) {
for (int i = 7; i >= 0; i-- ) {
digitalWrite(pinCLK, LOW);
digitalWrite(pinDin, bitRead(data, i));
digitalWrite(pinCLK, HIGH);
}
}

比较来看用SPI代码简单了很多。但是直接GPIO模拟SPI还是比较易用。

然后封装一下Max7219的一次16字节的addr+data数据

1
2
3
4
5
6
private void writeMax7219(byte addr, byte data) throws IOException {
mGpioCS.setValue(false);
writeMax7219Byte(addr);
writeMax7219Byte(data);
mGpioCS.setValue(true);
}

注意CS的操作,传输数据开始前需要置为低电平,结束后需要置为高电平,这两步非常重要。

最后初始化一下Max7219

1
2
3
4
5
6
7
private void initMax7219() throws IOException {
writeMax7219((byte)0x09, (byte)0x00); //译码方式:不译码
writeMax7219((byte)0x0a, (byte)0x03); //亮度
writeMax7219((byte)0x0b, (byte)0x07); //扫描界限;8个数码管显示
writeMax7219((byte)0x0c, (byte)0x01); //掉电模式:0,普通模式:1
writeMax7219((byte)0x0f, (byte)0x00); //显示测试:1;测试结束,正常显示:0
}

这样就完成了,接下来就是之际通过writeMax7219来显示想要显示的数据就可以了。

注意:在测试的时候发现打开SPI的时候会出错,但是再运行一次就好了,通过adb shell发现,默认/dev下是没有spidev0.0和spidev0.1,打开失败之后就会出现这两个设备,可能是驱动没有常驻,还没有去深究。在使用示例代码的时候,可能需要多点两次按钮才能成功。

来一张效果图:

完整代码下载链接: https://pan.baidu.com/s/1smPf09R 密码: 7bgv