激光投影POV(下)(2015-10-01)
文章字数限制,分成三篇:
程序源码
- 完整代码下载:点击下载附件
- 主程序展示(凑个字数哈)
1 // ---------------------------------------------------------------------------- 2 // povLaser.ino 3 // 4 // Created 2015-08-26 5 // By seesea <seesea2517#gmail#com> 6 // 7 // 激光POV 8 // 9 // - 由于需要用到外部中断,又想要端口整体操作,所以用了 mega2560 10 // - A0 - A7 为 PORTK 端口,接到八个激光二极管正极,激光二极管负极接地(原用 pin22 - pin29 PORTA 端口,但焊接电路不方便就改 PORTK 了) 11 // - pin21 为外部中断 2,作为 POV 起点的光敏电阻检测引脚 12 // - pin20 为外部中断 3,作为 POV 终点的光敏电阻检测引脚 13 // - 光敏电阻一端接 VCC,另一端通过 10K 的可变电阻接到 GND,相接处引出接到 pin20 和 pin21 14 // 通过调整可变电阻达到调整检测灵敏度的目的,以便在不同的亮度的环境下使用 15 // - pin3 输出 PWM 通过 PNP 三极管控制电机转动,通过调整 PWM 值控制电机以合适速度转动 16 // - 电机控制一个镜子转动,镜子反射激光,达到偏转光线的作用 17 // 18 // 原打算在电机轴上使用光栅和红外对管来检测起止位置,后来发现镜子的转动造成光线 2 倍于镜子的转动角偏转,不好用 19 // 所以给发射部分增加一个激光管做为光线位置指示,用光敏电阻来检测就完全吻合了 20 // 21 // ---------------------------------------------------------------------------- 22 23 #include "asciiToDot.h" 24 #include <avr/io.h> 25 26 #define PORT_LASER PORTK // 控制八个激光二极管的端口寄存器 27 #define DDR_LASER DDRK // 控制八个激光二极管的端口方向寄存器 28 29 const char interruptNumStartSensor = 2; // 开始显示的光敏电阻的中断号 30 const char interruptNumStopSensor = 3; // 结束显示的光敏电阻的中断号 31 volatile unsigned long ti = 0; // 中断函数中用于记录时间的变量 32 volatile unsigned long microDelay = 0; // 扫描时激光二极管的点亮延时,在中断函数中更新值 33 volatile bool flagDisplayOn = false; // 是否显示的标志,当开始光敏电阻检测到信号的时候置 true,结束光敏电阻检测到信号时置 false 34 35 const unsigned char pinPwmMotor = 3; // 电机 pwm 控制引脚号 36 const unsigned char pwmMotor = 150; // 电机 pwm 值 37 38 #define MAX_BUF_SIZE 50 // 串口接收最大字符数 39 char bufStr[MAX_BUF_SIZE] = "seesea"; // 默认显示字符 40 byte *bufDot = stringToDot(bufStr); // 点阵缓冲区 41 int colNum = strlen(bufStr) * asciiCharDotWidth; // 点阵缓冲区大小 42 43 // 开始显示的中断处理函数 44 void ISR_displayOn() 45 { 46 ti = millis(); 47 flagDisplayOn = true; 48 } 49 50 // 结束显示的中断处理函数 51 void ISR_displayOff() 52 { 53 microDelay = 1000 * (millis() - ti) / colNum; 54 flagDisplayOn = false; 55 } 56 57 void setup() 58 { 59 DDR_LASER = 0xFF; 60 61 pinMode(pinPwmMotor, OUTPUT); 62 analogWrite(pinPwmMotor, pwmMotor); 63 64 attachInterrupt(interruptNumStartSensor, ISR_displayOn, RISING); 65 attachInterrupt(interruptNumStopSensor, ISR_displayOff, RISING); 66 interrupts(); 67 68 Serial.begin(9600); 69 } 70 71 void loop() 72 { 73 int i = 0; 74 75 if (! flagDisplayOn) 76 return; 77 78 for (i = 0; i < colNum; ++i) 79 { 80 PORT_LASER = bufDot[i]; 81 delayMicroseconds(microDelay); 82 } 83 } 84 85 // 串口中断 86 // 接收串口输入的字符串,更新显示缓冲区 87 // 输入以 \n 为结束符,结束输入后更新点阵显示缓冲区从而更新显示内容 88 // 注意:一次输入可能发生多次串口中断 89 void serialEvent() 90 { 91 static int i = 0; 92 while (Serial.available()) 93 { 94 bufStr[i] = (char)Serial.read(); 95 96 if (bufStr[i] == '\n') 97 { 98 bufStr[i] = '\0'; 99 delete [] bufDot; 100 bufDot = stringToDot(bufStr); 101 colNum = strlen(bufStr) * asciiCharDotWidth; 102 i = 0; 103 break; 104 } 105 106 if (i >= MAX_BUF_SIZE) 107 continue; 108 109 ++i; 110 } 111 }
软件字库的制作
- 没有字库芯片,咱自力更生做软件字库
- 制作步骤如下:
- 整理所需要的字符:整理了从空格到波浪号这一批常用ascii字符,它们的ascii码连续,方便编写程序
- 选择等宽字体,并且缩到最小显示效果也不错的字体,选择了“Courier New”字体,做成 8 个像素高的图片,可以发现每一个字符都是 7 个像素宽,这样就方便程序计算每一个字符的偏移量了
其中有一些字符的位置需要手工调整:- g j p q y 上移两个像素
- 所有的符号全体上移两个像素
- j {} 还有微调(高度缩短了一个像素)
- 使用字模软件取模后,做成一个数组即可
- 编写函数根据输入的字符取得点阵信息
根据每个字符宽 7 个像素,即在数组中占 7 个元素,首位是空格,ascii 值为 32,从而程序很容易计算任何一个字符的点阵数据在数组中的位置为: (字符的 ascii 值 - 空格的 ascii 值) * 7,连续 7 个元素 - 附上像素图(请准备好放大镜哦:D)