前几天刚在某宝上买了个树莓派4b 4G内存版,附带了外壳,读卡器,16G TF卡,风扇,散热片,3.5寸电阻屏,几个RGB LED和一个8x8矩阵。我对点亮8x8矩阵这块兴趣较高,所以先拿来研究,由于是java出身,对java熟悉,而且听说过有个叫pi4j的东东,本着想做个自定义报时钟,需要音乐播放器,同时希望显示播放音乐的电平特效,本人之前用java实现过,所以首当其冲选用java。开始设想过用golang,但是golang除了服务外的其他方面太弱,python研究成本较高,因为我对它不是很熟悉。
言归正传,一开始在查阅pi4j时就遇到了一些阻力,因为有人说最新的pi4j v-1对树莓派4支持的不够友好,看了官网确实最新版仅支持3b+,pi4j v-2正在开发,为支持JDK11和其他一些新功能,当然4b也在其中,我也一直担心不兼容问题,不过经过测试,对于本例而言这些担心是多余的。之前看了很多博客,没有一篇是完整的从接线到代码,零零散散,我甚至不知道如何接线,这次把这些详细的都记下来,方便不时之需。
注:向硬件发送数据部分代码是借鉴了其他博客以及卖家给的python代码,照抄,具体含义不清楚,待研究,这篇博客的目的不是理解原理,而是实现目的。
说明:本篇仅介绍了基于GNIO最基础操作,8x8LED操作,可以在控制台上查看模拟效果
准备材料
- 树莓派4b
- 5根母对母杜邦线
- 一个MAX7219 LED 8x8矩阵(其他类型的比如74HC595啥的自行研究吧,不清楚)
准备软件
- JDK 8
- XShell(为Windows准备,Mac或Linux自带终端就够了)
- 一个可编辑java的工具(txt记事本/node++/vs code/netbeans/eclipse/idea随便)
首先使用XShell连接上树莓派(这个有屏无屏连接方式有很多,百度即可),推荐安装lrzsz命令,方便上传和下载,比linux自带的scp舒服,mac用lrzsz需要费点力气,或安装ftp服务,XManager可图形化管理。然后输入gpio readall命令,你会看到:
留下来备用,拿起你的LED矩阵,查看针脚,然后做对比。python有GPIO的setModel函数做映射切换,java的我没找到,默认使用的是wringPi编号,所以对比wPi这一列,给出对应关系(方便查找):
- [LED]CLK -> [NAME]SCLK ->[BCM]11 ->[WPI]14 -> [BOARD] 23
- [LED]DIN -> [NAME]MOSI -> [BCM]10 ->[WPI]12 -> [BOARD] 19
- [LED]CS -> [NAME]CE1 -> [BCM]7 -> [WPI]11 -> [BOARD] 26
- VCC 电源5v+
- GND或- 对应GND(地线或负极,图上为0v)
- 其他针脚不用管(如果有的话)
java里真正编程时候用到的也就是WPI这一列的编号了,BCM对应图中的BCM,BOARD对应图中的Physical(物理地址)。拿起树莓派,将针脚接口面对自己,USB/网卡接口朝下(或者说也是朝自己)看针脚,针脚和图中的编号一一对应,右边的第一,二个是电源供电出口,这里我的有一组接了散热风扇,占用了右侧的第二个和第三个口,只剩下第一个口可用,剩下的自行对照,一个一个的数哈,一定要对准了把杜邦线往上插就是了。
然后上代码,从其他博客上和卖家给的代码结合扒过来的代码,这部分代码入口仅提供了一个byte[8]的数组,我这里将数组的下标设定为y坐标,每个数组元素控制着x坐标,也就是说数组第一个元素控制第一行灯的亮与灭,以此类推。
import static com.pi4j.wiringpi.Gpio.*;
/**
* 脚编号使用wip
* 使用直接操作,未使用提供的细化api
* 全局仅一个对象,不可同时操作
* max7219 单色8x8
*
* CLK - > SCLK bcm:11 wpi:14 board:23
* DIN - > MOSI bcm:10 wpi:12 board:19
* CS -> CE1 bcm:7 wpi:11 board:26
* 树莓派4b
* 参阅com.shisan.pi.tools.PointToSPI,得到sendPoints方法参数
*/
public class Pi4b8x8 {
private static final int CS = 11,CLK = 14,DIN = 12;
private static final byte []NONE_POINT = new byte[]{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0};
private volatile static boolean hasInit = false;
/**
* 该byte数组len不大于8。
* 从0-7分别代表各个y坐标。
* @param xs 表示x坐标
*/
public static void sendPoints(byte []xs){
if(!hasInit){
synchronized (Pi4b8x8.class){
if(!hasInit){
init();
hasInit = true;
}
}
}
for (int i = 0; i < xs.length; i++) {
writeWord(i+1,xs[i]&0xff);
}
}
/**
* 使用前必须先init
*/
private static void init(){
wiringPiSetup();
pinMode(DIN,OUTPUT);
pinMode(CS,OUTPUT);
pinMode(CLK,OUTPUT);
writeWord(0x09,0x00);
writeWord(0x0a,0x03);
writeWord(0x0b,0x07);
//这个是shutdown命令了,如果需要关闭led设置为 writeWord(0x0c,0x00)
writeWord(0x0c,0x01);
//显示测试?应该是要不要区别意义不大吧
writeWord(0xff,0x00);
}
/**
* 使用完毕后clear(服务退出关闭led)。
*/
public static void clear(){
sendPoints(NONE_POINT);
}
private static void writeWord(int pin,int num){
digitalWrite(CS,HIGH);
digitalWrite(CS,LOW);
digitalWrite(CLK,LOW);
sendNum(pin);
sendNum(num);
digitalWrite(CS,HIGH);
}
private static void sendNum(int n){
int tmp;
for (int i = 0; i < 8; i++) {
tmp = n&0x80;
if(tmp > 0){
digitalWrite(DIN,HIGH);
}else{
digitalWrite(DIN,LOW);
}
n = n <<1;
digitalWrite(CLK,HIGH);
digitalWrite(CLK,LOW);
}
}
}
在人的世界观里,(x,y)这样的坐标看着更舒服,所以我又封装了一个转换类:
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author Shisan
* @version V1.0.0
* @Package com
* @ClassName: Test1
* @Description:
* @date 2019年11月22日 10:16
*/
public class PointToSPI {
public static void main(String[] args) throws Exception{
PointToSPI t = new PointToSPI();
List<Point> list = new ArrayList<>();
list.add(new Point(1,1));
list.add(new Point(1,2));
list.add(new Point(2,1));
list.add(new Point(4,5));
list.add(new Point(3,3));
list.add(new Point(0,7));
list.add(new Point(1,7));
list.add(new Point(5,5));
list.stream().forEach(e->t.add((byte)e.getX(),(byte)e.getY()));
t.remove((byte)1,(byte)1);
System.out.println(t.toString());
}
private byte[] result = new byte[8];
private byte[] result2;
/**
* 加入一个坐标点
* @param x
* @param y
*/
public void add(byte x,byte y){
if(x > 0x7 || y > 0x7){
return;
}
result2 = null;
result[y] |= 1 << 7 - x;
}
public void remove(byte x,byte y){
if(x > 0x7 || y > 0x7){
return;
}
result2 = null;
result[y] &= ~(1 << 7 - x);
}
public byte[] get(){
return result;
}
public byte[] get2(){
if(result2 == null){
result2 = new byte[result.length];
for (int i = 0; i < result.length; i++) {
result2[i] = (byte)~result[i];
}
}
return result2;
}
public void clear(){
for (int i = 0; i < result.length; i++) {
result[i] = 0;
}
}
@Override
public String toString(){
StringBuilder builder = new StringBuilder();
builder.append(" 0 1 2 3 4 5 6 7\n");
builder.append(" |-|-|-|-|-|-|-|-|\n");
for (int i = 0; i < result.length; i++) {
builder.append(i).append(":");
for (int j = 0; j < 8; j++) {
builder.append("|").append((1<<7-j&result[i])!=0?"@":" ");
}
builder.append("|\n");
}
builder.append(" |-|-|-|-|-|-|-|-|");
return builder.toString();
}
}
综上所述,代码可写为这样就能完成发送:
public static void main(String args[]){
PointToSPI p = new PointToSPI();
//点亮左上角第一个灯
p.add((byte)0,(byte)0);
//在控制台查看效果
System.out.println(p.toString());
//发送给LED矩阵显示
Pi4b8x8.sendPoints(p.get());
}
如果你追求效率的话,就不推荐将点拆分为x,y的坐标了,还是合并起来更好。一个8x8 LED矩阵由8个1字节组成的数组组成,在java中,如果想用一个变量就能表示那long类型就再合适不过了,随后我对这个做了升级,摒弃了PointToSPI类。
以下这个类自带了一些效果图
import java.lang.reflect.Field;
import java.util.Random;
public class DefaultFont8x8 {
/**
* 笑脸娃娃
*/
public static final long FACE_HAPPY = 4342214966986228284L;
public static final long FACE_UNHAPPY = 4342214966785688124L;
public static final long FACE_NORMAL = 4342214966382248508L;
public static final long FACE_SHRPRISE = 4342214966784901692L;
/**
* 符号
*/
public static final long SIGN_FALSE = -9132697443079273855L;
public static final long SIGN_TRUE = 283966729879552L;
public static final long SIGN_WARN = 1739555223478305279L;
public static final long SIGN_LOADING = 9097873883144667774L;
public static final long SIGN_LOADING_FILL = 9114788770027961982L;
public static final long SIGN_EMBARRASS = -35565062528073217L;
public static final long SIGN_LOVE = 7393082656524739608L;
public static final long SIGN_LOVE_FILL = 7421932185898073112L;
//?
public static final long SIGN_UNKONW = 2027184998608011272L;
//#
public static final long SIGN_PAND = 10271792857752576L;
/**
* 数字
*/
public static final long NUM_ZERO = 2027224744808555036L;
public static final long NUM_ONE = 583224982331983900L;
public static final long NUM_TWO = 2027184998608544318L;
public static final long NUM_THREE = 2027185032866701852L;
public static final long NUM_FOUR = 291630221331661828L;
public static final long NUM_FIVE = 9097342148642226748L;
public static final long NUM_SIX = 4342103893170340412L;
public static final long NUM_SEVEN = 9079822015070736400L;
public static final long NUM_EIGHT = 4342105817315689020L;
public static final long NUM_NINE = 4342105824827671100L;
/**
* 双位数字显示
*/
public static final long NUM_0_RIGHT = 145528082076599554L;
public static final long NUM_1_LEFT= 4665799858655871200L;
public static final long NUM_1_RIGHT = 145806245582995975L;
public static final long NUM_2_LEFT = 4656757337594306784L;
public static final long NUM_2_RIGHT = 145523666799822087L;
public static final long NUM_3_LEFT = 4656757474490097728L;
public static final long NUM_3_RIGHT = 145523671077815554L;
public static final long NUM_4_LEFT = 2333041218733219872L;
public static final long NUM_4_RIGHT = 145815076170826242L;
public static final long NUM_5_LEFT = -2269672649533677376L;
public static final long NUM_5_RIGHT = 505533482005496070L;
public static final long NUM_6_LEFT = 4656863579518050368L;
public static final long NUM_6_RIGHT = 145526986859939074L;
public static final long NUM_7_LEFT = -2296800349626793856L;
public static final long NUM_7_RIGHT = 504685741377586180L;
public static final long NUM_8_LEFT = 4656898214134325312L;
public static final long NUM_8_RIGHT = 145528069191697666L;
public static final long NUM_9_LEFT = 4656898349417406528L;
public static final long NUM_9_RIGHT = 145528073419293954L;
/**
* 大写字母
*/
public static final long UPPER_A = 582127782826426689L;
public static final long UPPER_B = 8953792110620983932L;
public static final long UPPER_C = 4342103635438617148L;
public static final long UPPER_D = 8953791861512880764L;
public static final long UPPER_E = 9097342149686476926L;
public static final long UPPER_F = 9097342149686476864L;
public static final long UPPER_G = 4485656002443756094L;
public static final long UPPER_H = 4774451665011098178L;
public static final long UPPER_I = 2019873263463172124L;
public static final long UPPER_J = 2019873263463180304L;
public static final long UPPER_K = 2460135483748131362L;
public static final long UPPER_L = 2314885530818453566L;
public static final long UPPER_M = -4511668628444591799L;
public static final long UPPER_N = -4367838175208782266L;
public static final long UPPER_O = 4342105843085492796L;
public static final long UPPER_P = 8953791862485827648L;
public static final long UPPER_Q = 4342105843085623869L;
public static final long UPPER_R = 8953791871008916034L;
public static final long UPPER_S = 4342103617214497340L;
public static final long UPPER_T = 9153575073218037768L;
public static final long UPPER_U = 4774451407313060412L;
public static final long UPPER_V = 4702077015947482120L;
public static final long UPPER_W = 4702156215280079892L;
public static final long UPPER_X = 4693335786401309249L;
public static final long UPPER_Y = 4693335786400516104L;
public static final long UPPER_Z = 9079824231409139838L;
/**
* 动态组合
*/
public static final long[] GIF_CIRCLE_LOADING = new long[]{
9123619555872702846L,
9119115964835266942L,
9116864165055136126L,
9115738265148424574L,
9115736066125170046L,
9115736057535237502L,
9115736057501687166L,
9115736057501564286L,
9115736057505757566L,
9115736058579497342L,
9115736333457400190L,
9115806702201569662L,
9124813901452116350L,
9129317500005745022L,
9131569024941523326L,
9132624556104188286L,
};
public static final long[] GIF_LINE_LOADING = new long[]{
35608723204439552L,
35608481746746880L,
35608361017900544L,
35608300653477376L,
35608542111170048L,
35608662840016384L
};
/**
* 获取SPI点
* @param points
* @return
*/
public static byte[] getPoints(long points){
byte ps[] = new byte[8];
for (int i = 0; i < ps.length; i++) {
ps[i] = (byte)(points >> (7 - i << 3) & 0xff);
}
return ps;
}
/**
* 获取所有坐标点
* @param points
* @return
*/
public static long[] getAllSingleOfPoints(long points){
byte num = getNumOfPoints(points);
long singleOfPoints[] = new long[num];
long p;
for (int i = 0,j =0; i < 64 && j<num; i++) {
if(((p = 1L << i & points)) != 0){
singleOfPoints[j] = p;
j ++;
}
}
return singleOfPoints;
}
public static long[] randomSinglePoints(long[] points){
int r = 0;
Random random = new Random();
long newPoints[] = new long[points.length];
System.arraycopy(points,0,newPoints,0,points.length);
for (int i = 0; i < newPoints.length; i++) {
r = random.nextInt(newPoints.length);
if(newPoints[i] != newPoints[r]){
newPoints[i] = newPoints[i] ^ newPoints[r];
newPoints[r] = newPoints[i] ^ newPoints[r];
newPoints[i] = newPoints[i] ^ newPoints[r];
}
}
return newPoints;
}
/**
* 获取点个数
* @param points
* @return
*/
private static byte getNumOfPoints(long points){
byte res = 0;
while(points != 0){
points = points & (points - 1);
res ++;
}
return res;
}
public static void main(String[] args) throws Exception {
//这里做演示,获取到一个byte[8]数组视为完成发送(伪发送)。
//可以传递给PointToSPI在控制台上查看效果
//发送12给LED
getPoints(NUM_1_LEFT|NUM_2_RIGHT);
//实现一个动态的碎片化到完整文字的特效
long[] all = randomSinglePoints(getAllSingleOfPoints(SIGN_FALSE));
long o = 0L;
//为了查看效果而引入的。
PointToSPI p = new PointToSPI();
//反射获取值
Field f = ReflectUtil.getFieldByName("result",p.getClass());
f.setAccessible(true);
for (int i = 0; i < all.length; i++) {
byte []v = getPoints(o |= all[i]);
f.set(p,v);
//打印查看效果
System.out.println(p.toString());
Thread.sleep(100);
}
f.setAccessible(false);
}
}
当然上面那么一大堆的东东不是我一个一个试验的,而是用javafx写了个简陋的8x8造字程序。
文字平移和滚动
基于上述的一个long代表一个图案,然后将这个图案向左向右平移(上下平移自行实现吧,我这里没这样的需求)。
/**
* @author Shisan
* @version V1.0.0
* @Package com.shisan.pi.clock.simple
* @ClassName: PointLineTranslationTools
* @Description:
* @date 2019年12月23日 15:49
*/
public class PointLineTranslationTools {
public static long toLeft(long point,byte px){
if(px > 7){
return 0;
}
point = point << px;
byte x;
for (int i = 0; i < 63; i+=8) {
x = 0;
for (; x < px ; x++) {
point &= ~(1L << i+x);
}
}
return point;
}
public static long toRight(long point,byte px){
if(px > 7){
return 0;
}
point = point >>> px;
byte x;
for (int i = 7; i < 63; i+=8) {
x = 0;
for(;x < px;x++){
point &= ~(1L << i-x);
}
}
return point;
}
/**
*
* @param front
* @param after
* @param offset 最大值7,最小值-7
* @param space 间隔,不高于127,不小于0
* @return
*/
public static long follow(long front,long after,byte offset,byte space){
if(space < 0){
space = 0;
}
if(offset < 0){
if(offset > -8 && front!=0){
front = toLeft(front,(byte)(~offset+1));
}else{
front = 0;
}
offset = (byte)(8+offset+space);
if(offset > 7 || after == 0){
after = 0;
}else{
after = toRight(after,offset);
}
}else{
after = 0;
if(offset > 8 || front == 0){
front = 0;
}else{
front = toRight(front,offset);
}
}
return front|after;
}
}
最后结合桢就可以实现平移动画,这里有一个现成的文字从右往左滚动特效:
import com.shisan.pi.tools.DefaultFont8x8;
import com.shisan.pi.tools.PointToSPI;
import com.shisan.pi.tools.ReflectUtil;
import java.lang.reflect.Field;
import java.util.LinkedList;
import static com.shisan.pi.clock.simple.PointLineTranslationTools.*;
/**
* @author Shisan
* @version V1.0.0
* @Package com.shisan.pi.clock.simple
* @ClassName: HLineAnimation
* @Description: 横向滚动从左到右
* @date 2019年12月23日 15:43
*/
public class HLineAnimation {
private LinkedList<TextTools.TextProperty> datas;
private volatile TextTools.TextProperty first;
private volatile TextTools.TextProperty next;
private volatile TextTools.TextProperty current;
private volatile int offset;
private volatile int space;
private volatile int loopTimes = 0;
/**
* 1 - 结束后等全部消失再第二次重复
* 0 - 自然重复
*/
private volatile int state;
public HLineAnimation(LinkedList<TextTools.TextProperty> datas){
this.datas = datas;
current = first = datas.poll();
datas.add(first);
next = datas.poll();
datas.add(next);
offset = 8;
space = next.getSpace();
}
public int getLoopTimes(){
return loopTimes;
}
public void setLastLoopState(int state){
this.state = state;
}
public int getLastLoopState(){
return this.state;
}
public long getNext(){
long p;
if(offset > -7){
if(offset<0 && state > 0 && next == first){
p = follow(current.getPoints(),0,(byte)offset,(byte)space);
}else{
p = follow(current.getPoints(),next.getPoints(),(byte)offset,(byte)space);
}
offset --;
}else{
offset = space + 1;
if(next == first){
loopTimes++;
if(state > 0){
offset = 8;
}
System.out.println("结束一遍");
}
current = next;
next = datas.poll();
datas.add(next);
space = next.getSpace();
p = follow(current.getPoints(),next.getPoints(),(byte)offset,(byte)space);
offset --;
}
return p;
}
public static void main(String[] args) throws Exception{
//TextTools是一个文字映射到Default8x8的map,这里可以自行来几个long类型的形状添加进去,这里实现了一个ABCDE5个字母来回滚动效果。
HLineAnimation h = new HLineAnimation(TextTools.textToPoints("ABCDE"));
h.setLastLoopState(1);
PointToSPI p = new PointToSPI();
Field f = ReflectUtil.getFieldByName("result", p.getClass());
while(true){
f.setAccessible(true);
f.set(p, DefaultFont8x8.getPoints(h.getNext()));
f.setAccessible(false);
System.out.println(p.toString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
为了代码的完整性,还是把TextTools贴出来吧:
import com.shisan.pi.tools.DefaultFont8x8;
import lombok.Data;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* @author Shisan
* @version V1.0.0
* @Package com.shisan.pi.clock.simple
* @ClassName: TextTools
* @Description:
* @date 2019年12月23日 15:06
*/
public class TextTools {
private static final Map<Character,Long> MAPPING = new HashMap<Character,Long>(){
{
put('0', DefaultFont8x8.NUM_ZERO);
put('1', DefaultFont8x8.NUM_ONE);
put('2', DefaultFont8x8.NUM_TWO);
put('3', DefaultFont8x8.NUM_THREE);
put('4', DefaultFont8x8.NUM_FOUR);
put('5', DefaultFont8x8.NUM_FIVE);
put('6', DefaultFont8x8.NUM_SIX);
put('7', DefaultFont8x8.NUM_SEVEN);
put('8', DefaultFont8x8.NUM_EIGHT);
put('9', DefaultFont8x8.NUM_NINE);
put('A', DefaultFont8x8.UPPER_A);
put('B', DefaultFont8x8.UPPER_B);
put('C', DefaultFont8x8.UPPER_C);
put('D', DefaultFont8x8.UPPER_D);
put('E', DefaultFont8x8.UPPER_E);
put('F', DefaultFont8x8.UPPER_F);
put('G', DefaultFont8x8.UPPER_G);
put('H', DefaultFont8x8.UPPER_H);
put('I', DefaultFont8x8.UPPER_I);
put('J', DefaultFont8x8.UPPER_J);
put('K', DefaultFont8x8.UPPER_K);
put('L', DefaultFont8x8.UPPER_L);
put('M', DefaultFont8x8.UPPER_M);
put('N', DefaultFont8x8.UPPER_N);
put('O', DefaultFont8x8.UPPER_O);
put('P', DefaultFont8x8.UPPER_P);
put('Q', DefaultFont8x8.UPPER_Q);
put('R', DefaultFont8x8.UPPER_R);
put('S', DefaultFont8x8.UPPER_S);
put('T', DefaultFont8x8.UPPER_T);
put('U', DefaultFont8x8.UPPER_U);
put('V', DefaultFont8x8.UPPER_V);
put('W', DefaultFont8x8.UPPER_W);
put('X', DefaultFont8x8.UPPER_X);
put('Y', DefaultFont8x8.UPPER_Y);
put('Z', DefaultFont8x8.UPPER_Z);
}
};
public static LinkedList<TextProperty> textToPoints(String text){
char strs[] = text.toUpperCase().toCharArray();
LinkedList<TextProperty> ps = new LinkedList<>();
TextProperty p = new TextProperty();
char c;
for (int i = 0; i < strs.length; i++) {
c = strs[i];
if(c == ' '){
p.setSpace((byte)(p.getSpace()+1));
}else{
p.setPoints(MAPPING.getOrDefault(c,DefaultFont8x8.SIGN_UNKONW));
ps.add(p);
p = new TextProperty();
}
}
return ps;
}
@Data
public static class TextProperty{
private long points;
private byte space;
}
public static void main(String[] args) {
// LinkedList<TextProperty> ps = textToPoints("a bc d 你");
// System.out.println(ps);
}
}
利用Swing和AWT包完成文字提取(java对文字操作我不是很熟,曲线救国)
话说,8x8的提取还是了解了解就好,毕竟像素太低了,提取出来的都不及自己画出来的好看。利用Graphics和BufferedImage类可以轻松实现:
BufferedImage bi = new BufferedImage(size,size,BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g2d = bi.createGraphics();
g2d.setPaint(new Color(0x000000));
g2d.fillRect(0,0,size,size);
//g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(new Color((Integer) getOwnProperty(KEY_COLOR)));
g2d.setFont(new Font((String) getOwnProperty(KEY_FONT_FAMILY), fontBold,fontSize));
g2d.drawString(String.valueOf(text),0, fontSize-1);
for(int i=0;i<bi.getWidth();i++){
for(int j=0;j<bi.getHeight();j++){
int rgb = bi.getRGB(i,j);
if((rgb&0x00ffffff)!=0){
//i为x坐标,j为y坐标。
}
}
}
基于上面的原理,不光是文字,图片也是可以提取的,如果想看效果,就买上N个8x8 led拼起来看吧,对于多个led拼装的代码尚未研究,我手头也没硬件暂不考虑。可以先看下在手机上展示的效果,很有意思,这是我做的一个验证码效果图:
sdn在实际使用中,如果你写了多个java应用一起点亮led是有问题的,我这里使用了netty做了一个统一的服务,每个需要点亮led的都去申请服务授权,每个申请者有都需要表明自己的优先级,优先级高的优先被展示,优先级低的将不再展示,直到优先级高的释放了自己的连接后才会展示优先级低的数据,就类似于桌面上一个窗口遮住了另一个窗口。
好了就写这么多了,把自己研究的核心的内容基本都贴上去了。