"废物利用"也抄袭——“完全”DIY"绘图仪"<二、下位机程序设计>

        就不说怎么组装了吧,一把辛酸泪。说程序,因为这有两把辛酸泪……一把给下位机的C代码一把为了VB.NET的图像处理……不过就上上一篇说的,它们可以正确运行了,并且今天克服了Arduino上电过程中步进电机没事瞎转悠的困难。

        其实上位机和下位机的功能界定非常清晰:上位机解释图片为指令,下位机解释上位机指令为硬件动作——就俩步进和一个激光器。当然,如果有读卡器模块,完全可以把命令写成文件实现脱机打印。总体框架就是这样,那么下位机要实现的具体功能有哪些呢?

1、串口通讯:接收指令和发送请求。既然是通讯,校验是少不了的,我写了一点CRC8校验。

2、控制步进电机:这方面的文章很多,够学一会的。我修改了Stepper库,当然只是用它的大部分框架,这个框架么……哎

3、控制激光器:激光器这里调节亮度的时候使用了PWM,正好手头有若干L298N…………

4、X,Y轴限位:用外部中断来控制,需要注意的是,我用的Uno么有那么多中断口可以挥霍,所以全部的微动开关都是连接在一起的。我是并联的,所以未按下时应该时断开的;如果串联,那么未按下时应该是闭合的。

5、软复位功能:可以用软件控制Arduino重启,方法也搜了一些,有些看着高大上的却然并卵。所以用的看门狗。

大体就是这样吧,下面看一下部分代码:

void setup() {
	Serial.begin(115200);
	
	AboveStepper.setSpeed(aSpeed);			//设置上步进电机每分钟转数
	BelowStepper.setSpeed(bSpeed);			//设置下步进电机每分钟转数
	

	AboveStepper.SetEnabled(true);			//初始化完成完成其他初始化之后再开启步进电机
	BelowStepper.SetEnabled(true);

	attachInterrupt(InterruptIntID, Interrupt, CHANGE);	//高电平
	DoxGoto0();
	DoyGoto0();

	while (!Serial) {}
	Serial.println(r_Ready);
}

一、初始化函数:这个函数在板子重启后被运行一次。

a、首先初始化串口,需要注意的是,这个波特率在你的板子所支持的范围内,越高越好——速度差异很大的。在这种频繁收发数据的应用中,9600明显感觉非常慢。

b、设置步进电机的转速,然后开启步进电机。

c、附加外部中断,利用微动开关使x,y轴归零。需要注意的是,如果你的板子加电时有扰动,那么应该在附加外部中断之前使x,y轴倒退一定的安全距离。

d、等待串口就绪,发送准备就绪信号。

二、外部中断函数

void Interrupt() {
	if (digitalRead(InterruptIntPin) == HIGH) {
		CurState = 0;
	}
	else {
		if (CurState == 0) {			//发生不应有的中断
			CurState = -1;
			AboveStepper.steps_left = 0l;		//清理各个电机剩余步数
			BelowStepper.steps_left = 0l;
			digitalWrite(LaserPin, 0);			//关闭激光器
		}
		else if (CurState == c_xGoto0) {
			CurState = -c_xGoto0;
		}
		else if (CurState == c_yGoto0) {
			CurState = -c_yGoto0;
		}
		else if (CurState == c_lzGoto0) {
			CurState = -c_lzGoto0;
		}
		else if (CurState == c_rzGoto0) {
			CurState = -c_rzGoto0;
		}

	}
}

  这个函数也非常清晰,当微动闭合时,证明某一个开关被触动,如果是程序控制的,那么更改当前状态以便退出正在运行的循环;如果是意外中断,那么关闭相应的硬件避免损坏。这个函数应该尽可能短,它在极为有限的时间内就应调用完成,所以一般采用全局变量进行控制,这里就是使用CurState。

三、运行时的“循环”函数——Loop

        这个函数并不是一次运行的,它是被系统不断的反复调用。我的代码如下:

void loop() {
	if (CurState == 0 || CurState == State_Stop) {			//非中断状态
		if (Serial.available()>=msgBuffSize) {
			msgLen = Serial.readBytes(msgBuff, msgBuffSize);		//读取消息
			if (msgBuff[msgBuffSize - 1] == cal_crc_table(msgBuff)) {
				CommandParsing(msgBuff);							//处理消息
				if (CurState != State_Stop) {
					RequestData();											//请求数据
				}
			}else{
				RerequestData();
			}
		} 
	}
}

  这里添加了暂停的功能,所以看起来可能有点乱。首先在正常状态或暂停状态下,尝试读取串口获取指令,当获取到数据后,进行Crc8验证,若未通过则重新申请数据;否则对命令进行解释并执行,随后当不处于暂停状态时再次申请指令。

        命令解释器就不详细说了,无非是一个大的分支结构。这里简要说一下这个AxiDraw用的双电机结构是怎么移动x,y轴的,其实很简单,你装起来之后用手转转就知道了。两个电机不同时针方向运行控制一轴,两个电机同方向运行控制另一轴。我的是这样的(Y+,Y-代表Y轴正方向和负方向上的电机):

a、Y+顺时针Y-逆时针→X轴向负方向运行

b、Y+顺时针Y-顺时针→Y轴向负方向运行

所以代码是这样的:

void DoxMove(long dBeat) {
	int dir, step;
	if (dBeat < 0) {
		dir = -1;
		step = -dBeat;
	}else{
		dir = 1;
		step = dBeat;
	}
	for (int i = 0; i < step; i++) {
		AboveStepper.step(dir);
		BelowStepper.step(-dir);
	}
}

void DoyMove(long dBeat) {
	int dir, step;
	if (dBeat < 0) {
		dir = -1;
		step = -dBeat;
	}else{
		dir = 1;
		step = dBeat;
	}
	for (int i = 0; i < step; i++) {
		AboveStepper.step(dir);
		BelowStepper.step(dir);
	}
}

void Do13Move(long dBeat) {
	AboveStepper.step(dBeat);
}

void Do24Move(long dBeat) {
	BelowStepper.step(dBeat);
}

  当然,完全可以不用For循环。但是走斜线的时候感官上好像“绕远”,看着有点矬。然后是激光器控制,直接用PWM就可以了。最后,是软重启,用看门狗最通用,很稳定,无接线:

#include <avr/wdt.h>


void Soft_ReStart(){        
	do{                           
		wdt_enable(WDTO_15MS);		//开启看门狗计时器,然后不喂狗……就重启了。
	for (;;){ }                       
	} while (0);
}

  就是这……

原文地址:https://www.cnblogs.com/zcsor/p/9058032.html