|
注:下文中提到的数字,皆为整数。
束坐标(Column,简写为c):是在3D8系统中,结合硬件系统定义的坐标值,其值c = y*8 + x。由此可
得c 的值域为[0, 63];并且我们还能计算出 x = c % 8, y = c / 8。(熟悉编程的朋友都会知道,这里的“%”为取余运算,“/”为整除运算)。
数据值(Data),即定义每一束(Column)上的显示状态。因为会有多点同时亮起的情况出现,且z的范围为[0, 7],所以一个字节(byte)的8个位(bit),恰好可用于定义这个状态。如果要求某束只有z=0点(第0层,即最底层)亮起,则可将Data值设置为0x01(0000 0001 B)(后缀B,表示该数为二进制数)。若要求某束只有z=6点(第6层,从上往下第2层)亮起,则Data = 0x40(0100 0000 B);若要求z=0, z=6点同时亮起,则Data = 0x41(0100 0001 B)。
驱动流程:
1. 主板上电以后,再给上位机上电。保证主板提前进入监听状态。
2. 在上位机程序中,可以通过上述三种中指令的一种或多种组合,实现对一屏画面的赋值。
3. 赋值结束后,主板内部的数据缓存会保存这些值,并不断对LED进行扫描显示。上位机可以通过延时函数,控制两帧画面之间的间隔。
4. 循环步骤2、3,实现动画效果。
系统重置 系统重置在目前的通讯协议下显得尤为重要,因为指令有长有短,同时主板并不会去判断连接是否中断。所以当一条指令没有发送完整时,若出现通讯中断,主板会继续等待剩余指令发送完成。而如果此时重新建立连接,主板将不会把接收到的第1个字节作为开始码进行判断。这就导致通讯无法正常进行,此时需要重启主板,上位机重新开始发送数据,方可让通讯同步。 简而言之,在断开上位机与主板的连接以后,重新连接时,应重启主板,而后上位机再开始发送数据。 若使用控制棒,控制棒也将使用主板电源,所以此时若重启主板,主板和控制棒会同时重新启动。因为控制棒内部包含启动延时程序,所以使得在其发送数据之前,主板已经提前进入了监听状态。
更新手记: V1.3 (2011-08-11):修正了单束赋值命令中,束坐标可能超出范围的bug。 V1.4 (2011-08-13):修正了发送代码过长、错位而导致开始码错误进而系统不响应的Bug,若开始码非0xf0/0xf1/0xf2,主板将继续等待接收开始码。
底层驱动模块- ////////////////////////////////////////////////////////////////////////////////
- // 3D8 串口通讯底层驱动(程序模板)
- // 基于上位机 12C5A60S2+22.1184M 外部晶振,若使用其它上位机,请在充分理解下列程序以后进行修改。
- // aGuegu / 官微宏 2011-08-02 weihong.guan@gmail.com
- ////////////////////////////////////////////////////////////////////////////////
- #include <STC12C5A60S2.H>
- #include <intrins.h>
- #include <stdlib.h>
- typedef unsigned char uint8;
- typedef unsigned int uint16;
- #define LAYER_COUNT 8
- #define COLUMN_COUNT 64
- // 延时函数
- void delay(uint16 a)
- {
- while( a-- )
- _nop_();
- }
- // 串口初始化:波特率 115200(使用频率为 22.1184M 的外部晶振驱动)
- void UART_Init (void)
- {
- TMOD = 0x20; SCON = 0x50;
- TH1 = 0xff; TL1 = 0xff; // 设置波特率为 115200
- PCON = 0x80; TR1 = 1;
- }
- // 串口发送数据函数
- void UART_Send (uint8 cData)
- {
- SBUF = cData;
- while(!TI); TI = 0;
- }
- // 根据 x,y 的值计算 column 值
- uint8 funGetColoumn(uint8 x, uint8 y)
- {
- return (8*y+x);
- }
- // 全局赋值函数
- void funPrintUniqueValue2Cube(uint8 cData)
- {
- UART_Send (0xf0); // 开始码内置于函数中发送
- UART_Send (cData);
- }
- // 单束赋值函数
- void funPrintColumn(uint8 cColumn, uint8 cData)
- {
- UART_Send(0xf1); UART_Send(cColumn); UART_Send(cData);
- }
- void funPrintCube(uint8 *p)
- {
- uint8 i; UART_Send(0xf2);
- for (i=0; i < COLUMN_COUNT; i++) UART_Send(p[i]);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // 以上函数作为固定驱动程序模块,一般不用修改
- ////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////
- 动画程序段:此处插入打包以后的动画程序
- ////////////////////////////////////////////////////////////////////////////////
- // 主函数
- void main(void)
- {
- delay(-1); //其实这里的-1 等效于 65535,也就是这个延时函数所能提供的最大延时间隔
- delay(-1); //
- UART_Init(); // 初始化串口
- while(1)
- {
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
复制代码 驱动例程
例程1 ——全屏闪动
// 将模板中主函数改为如下所示- void main(void)
- {
- delay(-1);
- delay(-1);
- UART_Init(); // 初始化串口
- while(1)
- {
- funPrintUniqueValue2Cube(0xff); // 开启所有显示
- delay(-1);
- // 显示时长,可以通过减小参数值,或增加 delay()运行的次数,改变闪烁间隔
- funPrintUniqueValue2Cube(0x00); // 关闭所有显示
- }
- }
复制代码 小贴士:
单帧显示时长由延时函数 delay(uint16); 控制,参数为(-1)的话,间隔时间比较长,如果变为 0x20,
甚至更小,甚至干脆不要延时。则会因为显示太快而导致肉眼察觉不出变化,反而是看到全屏都
被点亮。这个时候并不是主板运行不正常,而是扫描太快,导致肉眼看不出变化。
控制帧速(单帧显示时长的倒数),除了用延时函数的方法,当然还可以用定时器中断的方法,有 兴趣的朋友不妨试一下。
例程 2 ——三面扫描
在动画程序段插入如下函数:- // z 方向,逐面扫描
- void funScanByZ(void)
- {
- uint8 z; funPrintUniqueValue2Cube(0x00); for (z=0; z< LAYER_COUNT; z++)
- {
- funPrintUniqueValue2Cube(0x01 << z);
- delay(-1);
- }
- }
- // x 方向,逐面扫描
- void funScanByX(void)
- {
- uint8 x,y; funPrintUniqueValue2Cube(0x00); for (x=0; x< LAYER_COUNT; x++)
- {
- funPrintUniqueValue2Cube(0x00); // 若屏蔽此行,则函数变为逐面点亮。
- // 因为单束赋值并不会对别的束的值发生影响,所以在每一帧显示开始时,对全屏进行清屏操作。
- for (y=0; y<LAYER_COUNT; y++)
- funPrintColumn(funGetColoumn(x,y), 0xff);
- delay(-1);
- }
- }
- // y 方向,逐面扫描
- void funScanByY(void)
- {
- uint8 x,y;
- funPrintUniqueValue2Cube(0x00);
- for (y=0; y< LAYER_COUNT; y++)
- {
- funPrintUniqueValue2Cube(0x00); // 若屏蔽此行,则函数变为逐面点亮。
- // 因为单束赋值并不会对别的束的值发生影响,所以在每一帧显示开始时,对全屏进行清屏操作。
- for (x=0; x<LAYER_COUNT; x++)
- funPrintColumn(funGetColoumn(x,y), 0xff);
- delay(-1);
- }
- }
复制代码 将主函数更新为- void main(void)
- {
- delay(-1);
- delay(-1);
- UART_Init(); // 初始化串口
- while(1)
- {
- // 依次运行三个方向的扫描程序
- funScanByX(); funScanByY(); funScanByZ();
- }
- }
复制代码 小贴士:
在这里已经就利用了建立子函数的方法,将动画进行“打包”,使得主函数简洁明了。
在主函数的 while(1)循环内,依然可以使用 for,while 循环语句,来决定动画如何进行循环。
目前这几个动画函数都没有参数,有兴趣的朋友不妨试试看能否把内部 delay()函数的参数作 为整个函数的参数,这样当外部调用的时候,就可以直接控制帧速了。
例程3 ——上升流
在动画程序段插入如下函数:- void funDemoRise(uint8 *pCube)
- {
- uint8 i, x, y, j;
- j = 0x80; // j 作为计数器,计算总共上升了多少次
- while(j--)
- {
- // 上升
- for(i=0; i<COLUMN_COUNT; i++)
- {
- pCube[i] <<= 1;
- }
- // 底面重新有 3 个新的点亮起
- for(i=0; i<3; i++)
- {
- x = rand() % 8; y = rand() % 8; pCube[funGetColumn(x,y)] |= 0x01;
- }
- funPrintCube(pCube);
- delay(0x100);
- }
- }
复制代码 主函数改为:- void main(void)
- {
- uint8 pCube[COLUMN_COUNT];
- delay(-1); delay(-1);
- UART_Init(); // 初始化串口
- srand(9); // 给个随机种子
- while(1)
- {
- funDemoRise(pCube);
- }
- }
复制代码 小贴士:
这里只用到批量赋值命令,这也是我推荐大家使用的命令。其基本流程就是,在上位机内部建立 一个数组,通过各种运算,改变这个数组里面的值,以达到理想的效果,在运算完成以后,使用
批量赋值命令,将整体输出到 3D8 主板上。虽然这样传输的数据量最多,需要 65 个字节,但是我 们的高波特率传输已经使得,肉眼察觉不到这个时间,而且这设计动画效果过程中,这样的思路 最简单。
这里用到了随机函数,随机函数是最简单的让动画看起不那么千篇一律的方法,充分利用它吧。
Rand() 的返回值范围为[0,255],是用取余运算%8,可将其范围缩小到[0,7]。
位运算,在单片机程序中,位运算是经常用到的,这里用到了或运算|和左移位算<<。还有&、>>
等,好好复习一下,未来都会用得到。
指针,这里我使用了指针,传统的单片机程序可能更习惯于使用全局变量(当然,这个变量也是 个数组),但是这么做会增加冗余度,牺牲系统的稳定性,消耗大量的系统资源。其实完全可以避 免,把 C
语言的教材翻出来,看看指针怎么用吧。
指针运算中,最怕的就是指着指着,指到别的地方去,万一做了什么修改会导致程序的崩溃、跑 飞。所以我在宏定义中加入了 LAYER_COUNT 和
COLUMN_COUNT,对数组下标的上限进行了明确的 定义。而且在指针中尽量使用 p[c]这样的形式来调用,比较直观。(当然,高手的话,就更喜欢用 p++之类更加晦涩的语句了)。
关于计数器 j,其实我不太喜欢这个东西,但这也是最简单的,控制动画整体播放长度的方法。如 果只是单一的效果,不要这个循环也罢。但是如果有多个动画滚动显示,就需要这样的跳出机制。
有兴趣的朋友试试看,使用按键等其它办法来实现它。
欢迎大家参与交流
3D8 光立方官方交流1群 QQ 群:165068863(满)
3D8 光立方官方交流2群 QQ 群:153176062
淘宝店链接 :http://syyyd.taobao.com
新版3D8S 串口调试程序:
3D8S 8x8x8 LED 光立方--- 写3D8程序调试工具篇
|
|