圣源电子制作

 找回密码
 立即注册
查看: 8724|回复: 1
打印 上一主题 下一主题

旧版本 3D8 串口数据通讯协议介绍v1.4

[复制链接]
跳转到指定楼层
楼主
发表于 2012-8-31 12:11:54 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式



注:下文中提到的数字,皆为整数。
 束坐标(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,主板将继续等待接收开始码。

底层驱动模块
  1. ////////////////////////////////////////////////////////////////////////////////
  2. // 3D8  串口通讯底层驱动(程序模板)
  3. //  基于上位机 12C5A60S2+22.1184M 外部晶振,若使用其它上位机,请在充分理解下列程序以后进行修改。
  4. // aGuegu /  官微宏 2011-08-02 weihong.guan@gmail.com
  5. ////////////////////////////////////////////////////////////////////////////////
  6. #include <STC12C5A60S2.H>
  7. #include <intrins.h>
  8. #include <stdlib.h>
  9. typedef unsigned char uint8;
  10. typedef unsigned int uint16;
  11. #define LAYER_COUNT 8
  12. #define COLUMN_COUNT 64
  13. //  延时函数
  14. void delay(uint16 a)
  15. {
  16. while( a-- )
  17. _nop_();
  18. }
  19. //  串口初始化:波特率 115200(使用频率为 22.1184M 的外部晶振驱动)
  20. void UART_Init (void)
  21. {
  22. TMOD = 0x20; SCON = 0x50;
  23. TH1 = 0xff; TL1 = 0xff; //  设置波特率为 115200
  24. PCON = 0x80; TR1 = 1;
  25. }
  26. //  串口发送数据函数
  27. void UART_Send (uint8 cData)
  28. {
  29. SBUF = cData;
  30. while(!TI); TI = 0;
  31. }
  32. //  根据 x,y 的值计算 column 值
  33. uint8 funGetColoumn(uint8 x, uint8 y)
  34. {
  35. return (8*y+x);
  36. }
  37. //  全局赋值函数
  38. void funPrintUniqueValue2Cube(uint8 cData)
  39. {
  40. UART_Send (0xf0); //  开始码内置于函数中发送
  41. UART_Send (cData);
  42. }
  43. //  单束赋值函数
  44. void funPrintColumn(uint8 cColumn, uint8 cData)
  45. {
  46. UART_Send(0xf1); UART_Send(cColumn); UART_Send(cData);
  47. }
  48. void funPrintCube(uint8 *p)
  49. {
  50. uint8 i; UART_Send(0xf2);
  51. for (i=0; i < COLUMN_COUNT; i++) UART_Send(p[i]);
  52. }
  53. ////////////////////////////////////////////////////////////////////////////////
  54. //  以上函数作为固定驱动程序模块,一般不用修改
  55. ////////////////////////////////////////////////////////////////////////////////
  56. ////////////////////////////////////////////////////////////////////////////////
  57. 动画程序段:此处插入打包以后的动画程序
  58. ////////////////////////////////////////////////////////////////////////////////
  59. //  主函数
  60. void main(void)
  61. {
  62. delay(-1); //其实这里的-1 等效于 65535,也就是这个延时函数所能提供的最大延时间隔
  63. delay(-1); //
  64. UART_Init(); //  初始化串口
  65. while(1)
  66. {
  67. }
  68. }
  69. ////////////////////////////////////////////////////////////////////////////////
复制代码
驱动例程
 例程1 ——全屏闪动
// 将模板中主函数改为如下所示
  1. void main(void)
  2. {
  3. delay(-1);
  4. delay(-1);
  5. UART_Init(); //  初始化串口
  6. while(1)
  7. {
  8. funPrintUniqueValue2Cube(0xff);    //  开启所有显示
  9. delay(-1);
  10. //  显示时长,可以通过减小参数值,或增加 delay()运行的次数,改变闪烁间隔
  11. funPrintUniqueValue2Cube(0x00);    //  关闭所有显示
  12. }
  13. }
复制代码
小贴士:
    单帧显示时长由延时函数 delay(uint16);  控制,参数为(-1)的话,间隔时间比较长,如果变为 0x20,
甚至更小,甚至干脆不要延时。则会因为显示太快而导致肉眼察觉不出变化,反而是看到全屏都
被点亮。这个时候并不是主板运行不正常,而是扫描太快,导致肉眼看不出变化。
    控制帧速(单帧显示时长的倒数),除了用延时函数的方法,当然还可以用定时器中断的方法,有 兴趣的朋友不妨试一下。

例程 2 ——三面扫描
在动画程序段插入如下函数:
  1. // z 方向,逐面扫描
  2. void funScanByZ(void)
  3. {
  4. uint8 z; funPrintUniqueValue2Cube(0x00); for (z=0; z< LAYER_COUNT; z++)
  5. {
  6. funPrintUniqueValue2Cube(0x01 << z);
  7. delay(-1);
  8. }
  9. }
  10. // x 方向,逐面扫描
  11. void funScanByX(void)
  12. {
  13. uint8 x,y; funPrintUniqueValue2Cube(0x00); for (x=0; x< LAYER_COUNT; x++)
  14. {
  15. funPrintUniqueValue2Cube(0x00);                  //  若屏蔽此行,则函数变为逐面点亮。
  16. //  因为单束赋值并不会对别的束的值发生影响,所以在每一帧显示开始时,对全屏进行清屏操作。
  17. for (y=0; y<LAYER_COUNT; y++)
  18. funPrintColumn(funGetColoumn(x,y), 0xff);
  19. delay(-1);
  20. }
  21. }
  22. // y 方向,逐面扫描
  23. void funScanByY(void)
  24. {
  25. uint8 x,y;
  26. funPrintUniqueValue2Cube(0x00);
  27. for (y=0; y< LAYER_COUNT; y++)
  28. {
  29. funPrintUniqueValue2Cube(0x00);                  //  若屏蔽此行,则函数变为逐面点亮。
  30. //  因为单束赋值并不会对别的束的值发生影响,所以在每一帧显示开始时,对全屏进行清屏操作。
  31. for (x=0; x<LAYER_COUNT; x++)
  32. funPrintColumn(funGetColoumn(x,y), 0xff);
  33. delay(-1);
  34. }
  35. }
复制代码
将主函数更新为
  1. void main(void)
  2. {
  3. delay(-1);
  4. delay(-1);
  5. UART_Init(); //  初始化串口
  6. while(1)
  7. {
  8. //  依次运行三个方向的扫描程序
  9. funScanByX(); funScanByY(); funScanByZ();
  10. }
  11. }
复制代码
小贴士:
    在这里已经就利用了建立子函数的方法,将动画进行“打包”,使得主函数简洁明了。
    在主函数的 while(1)循环内,依然可以使用 for,while 循环语句,来决定动画如何进行循环。
    目前这几个动画函数都没有参数,有兴趣的朋友不妨试试看能否把内部 delay()函数的参数作 为整个函数的参数,这样当外部调用的时候,就可以直接控制帧速了。

例程3 ——上升流
在动画程序段插入如下函数:
  1. void funDemoRise(uint8 *pCube)
  2. {
  3. uint8 i, x, y, j;
  4. j = 0x80;  // j 作为计数器,计算总共上升了多少次
  5. while(j--)
  6. {
  7. //  上升
  8. for(i=0; i<COLUMN_COUNT; i++)
  9. {
  10. pCube[i] <<= 1;
  11. }
  12. //  底面重新有 3 个新的点亮起
  13. for(i=0; i<3; i++)
  14. {
  15. x = rand() % 8; y = rand() % 8; pCube[funGetColumn(x,y)] |= 0x01;
  16. }
  17. funPrintCube(pCube);
  18. delay(0x100);
  19. }
  20. }
复制代码
主函数改为:
  1. void main(void)
  2. {
  3. uint8 pCube[COLUMN_COUNT];
  4. delay(-1); delay(-1);
  5. UART_Init(); //  初始化串口
  6. srand(9); //  给个随机种子
  7. while(1)
  8. {
  9. funDemoRise(pCube);
  10. }
  11. }
复制代码
小贴士:
 这里只用到批量赋值命令,这也是我推荐大家使用的命令。其基本流程就是,在上位机内部建立 一个数组,通过各种运算,改变这个数组里面的值,以达到理想的效果,在运算完成以后,使用
批量赋值命令,将整体输出到 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程序调试工具篇


回复

使用道具 举报

沙发
发表于 2013-3-15 21:11:34 | 只看该作者
好东西学习了!!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|联系我们|闽公网安备 35012102000020号|闽ICP备11020110号-1|圣源电子

GMT+8, 2024-11-22 00:51 , Processed in 0.045338 second(s), 21 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表