用于光敏聚合物LCD 3D打印机的DIY固件。第三部分





在前两部分中,我讨论了如何制作GUI,开始控制步进电机以及如何处理USB闪存驱动器上的文件。



今天,我将介绍打印过程,已打印图层的输出到高亮屏幕以及其余(不是很重要)的东西:



4.将图层的图像输出到高亮显示。

5.每件事,例如控制灯光和风扇,加载和保存设置等。

6.舒适和便利的附加功能。





4.



4.1 -



没有专用外设的微控制器如何通过MIPI接口以每秒7400万像素(2560x1440分辨率,每秒20帧)的速度刷新高分辨率矩阵上的图像?答案:使用连接了16MB SDRAMFPGA和两个MIPI接口芯片-SSD2828。之所以要使用两个微电路,是因为显示器在逻辑上分为两半,每个半部分都由自己的独立通道提供服务,因此可以得到两个显示器。



用于显示的图像存储在4个SDRAM库之一中,FPGA芯片负责服务SDRAM并将图像从其中输出到SSD2828。 FPGA为SSD2828和驱动器生成垂直和水平同步信号

通过24线(8R 8G 8B)连续像素流进入每个SSD2828。帧速率大约为20 Hz。



FPGA通过串行接口(SPI)连接到微控制器,微控制器可以通过该接口传输图像。它以数据包的形式传输,每个数据包包含一行图像(行沿显示器的短边计数-1440像素)。除此数据外,数据包还包含SDRAM存储区号,行号和校验和-CRC16。 FPGA接收此数据包,检查校验和,如果一切正常,则将数据保存到适当的SDRAM区域。如果CRC不匹配,则FPGA在也连接到微控制器的其引脚之一上设置信号,据此微控制器了解到数据未正常到达,并可以重复发送。为了获得完整的图像,微控制器必须将2560个此类数据包发送到FPGA。



数据包内部的图像数据以位格式表示:1-像素点亮,0-像素暗。 completely,这完全排除了组织印刷层边缘的灰度模糊的可能性-抗锯齿。为了组织这种模糊方式,有必要重写FPGA的配置(固件),我还没有准备好。我从事FPGA的时间太长且时间不长,我几乎必须重新掌握一切。



除了数据包之外,微控制器还可以发送控制命令,以指示从哪个SDRAM库读取数据以进行显示并打开/关闭图像输出。



SSD2828芯片也通过SPI连接到微控制器。为了在打开寄存器时配置它们的寄存器,将它们转移到睡眠或活动模式,这是必需的。

微控制器和FPGA / SSD2828之间还有多条线路-每个微电路的复位信号和有源芯片选择信号(芯片选择)。



总的来说,我认为这种工作方案远非最佳。例如,通过并行的外部存储器接口将FPGA连接到微控制器会更加合乎逻辑,数据的传输速度将比通过SPI(频率限制为20 MHz)快得多(当频率上升时,FPGA将停止正常接收数据)。另外,复位信号不会路由到物理复位FPGA输入,而是作为常规逻辑信号,也就是说,FPGA不会对其执行硬件复位。这也玩了一个残酷的笑话,将在下面讨论。



我通过了解制造商的源代码发现了所有这些。我按原样从其源代码转移了使用FPGA的功能,但我仍不完全了解其工作原理。幸运的是,中国人已经对他们的代码进行了足够多的注释(中文),以便能够轻松地将其找出来。



4.2从打印文件读取图层



好的,我们或多或少已经弄清楚了完成图像的输出,现在我将告诉您一些有关如何从准备打印的文件中提取这些图像的信息。 .pws,.photons,.photon,.cbddlp文件本质上是一堆图层图像。据我所知,这种格式来自中国的Chitu公司,该公司提出了用这种电路(微控制器-FPGA-SDRAM-SSD2828)制作电路板的想法。假设您要打印一个高度为30毫米,每层0.05毫米厚的模型。切片器程序将此模型切成指定厚度的图层,并为每个图层形成其图像。



因此,可获得分辨率为1440x2560的30 / 0.05 = 600张图像。这些图像打包到一个输出文件中,并在其中输入带有所有参数的标题,并且该文件已经发送到打印机。层图像的深度为1位,并且由RLE算法一次压缩一个字节,其中最高有效位指示颜色值,而七个最低有效位指示重复次数。此方法使您可以将图层图像从460 KB压缩到大约30-50。打印机读取压缩层,对其进行解压缩,然后将其逐行发送至FPGA。



制造商这样做如下:



  1. — 1, 1, 0. , (1440), .
  2. , 1440 (180 ).
  3. FPGA .


这是中国人使用的三步法。事实证明,这样做是为了使该层的图像可以缩小的形式显示在界面显示器上,向用户显示正在打印的内容。该图像仅由字节数组形成。尽管阻止从解码位立即形成它的原因尚不清楚。还不清楚什么原因阻止了在同一周期内形成位图传输到FPGA。



现在,我使用了相同的方法,尽管进行了优化。为了阐明什么是优化,我需要再说明一点。显示行的数据不是有效载荷的固定数组。由于在短边连接了两个显示控制器,因此中间有一些额外的“非工作”像素,每个显示控制器的边缘都有24个“非工作”像素。因此,图像的一行的实际传输数据包括3个部分:上半部分的数据(第一控制器),中间的“非工作” 48像素,下半部分的数据(第二控制器)。



因此,中文在循环内形成字节数组时,检查是否已到达前一半的末尾,如果未到达,则该值由* p指针写入,否则通过指针*(p + 48)。对1440个值中的每一个进行此检查,甚至对其中一半的指针进行修改,显然都不会提高循环速度。我将此循环分为两个单独的循环-在第一个循环中,数组的前半部分已填充,在此循环后,指针增加了48,第二个循环从数组的后半部分开始。在原始版本中,该层的读取和显示时间为1.9秒,仅此修改便将读取和输出时间减少到1.2秒。



另一个变化涉及到向FPGA的数据传输。在原始源中,它是通过DMA发生的,但是在通过DMA开始传输之后,该函数等待其完成,直到此后才开始解码并形成新的图像行。我删除了这种期望,以便在传输前一行中的数据时生成下一行。这样又将时间减少了0.3秒,每层减少了0.9秒。这是在不进行优化的情况下进行编译的时间,如果您进行完全优化的编译,则时间将减少到约0.53秒,这已经可以接受了。在这0.53秒中,计算CRC16大约需要0.22秒,从字节数组形成位图之前大约需要0.19秒。但是,将所有线路完全转移到FPGA大约需要0.4秒,因此,很可能无需执行任何操作-这里的一切都取决于FPGA允许的最大SPI频率的限制。



如果我可以自己编写FPGA配置,则可以给它进行RLE解压缩,这可以将层的输出速度提高一个数量级,但是如何完成呢?



是的,我要写的是与此有关的一个事实,即FPGA不会通过硬件根据来自微控制器的复位信号进行复位。因此,当我已经学会了如何显示图层图像时,我自己完成了打印过程,遇到了一个难以理解的错误-从5到10,一旦完成,便开始使用完全照明的显示器进行打印。我在调试器中看到正确读取了层,数据根据需要发送到FPGA,FPGA确认了CRC的正确性。也就是说,一切正常,而不是绘制图层,而是完全白色的显示。显然,应该归咎于FPGA或SSD2828。我再次仔细检查了SSD2828的初始化-一切都很好,它们中的所有寄存器都被初始化为所需的值,这可以在从它们的控制读取值中看到。然后,我已经用示波器接触到板子。我发现发生此类故障时,FPGA不会将任何数据写入SDRAM。 WE信号,允许写作,站在非活动级别的现场。如果没有一位朋友建议我在复位之前尝试给FPGA一个显式命令以关闭图像输出,我可能会为此困扰很长时间,因此建议在复位时确保没有从FPGA到SDRAM的调用。我尝试了,它起作用了!这个错误再也没有显示出来。最后,我们得出的结论是,FPGA内部的SDRAM控制器的IP内核未正确实现,在所有情况下SDRAM控制器的复位和初始化都不会正常发生。如果此时正在访问SDRAM中的数据,则某些操作会阻止正确的重置。像这样…建议谁在复位之前尝试一下,以便给FPGA一个明确的命令以关闭图像输出,这样在复位时就可以保证没有从FPGA到SDRAM的调用。我尝试了,它起作用了!这个错误再也没有显示出来。最后,我们得出的结论是,FPGA内部的SDRAM控制器的IP内核未正确实现,在所有情况下SDRAM控制器的复位和初始化都不会正常发生。如果此时访问SDRAM中的数据,则会影响正确的重置。像这样…建议谁在复位之前尝试一下,以便给FPGA一个明确的命令以关闭图像输出,这样在复位时就可以保证没有从FPGA到SDRAM的调用。我尝试了,它起作用了!这个错误再也没有显示出来。最后,我们得出的结论是,FPGA内部的SDRAM控制器的IP内核未正确实现,在所有情况下SDRAM控制器的复位和初始化都不会正常发生。如果此时访问SDRAM中的数据,则会影响正确的重置。像这样…FPGA内部SDRAM控制器的IP内核未正确实现,因此重置和初始化SDRAM控制器在所有情况下均无法正常工作。如果此时访问SDRAM中的数据,则会影响正确的重置。像这样…FPGA内部SDRAM控制器的IP内核未正确实现,因此重置和初始化SDRAM控制器在所有情况下均无法正常工作。如果此时访问SDRAM中的数据,则会影响正确的重置。像这样…



4.3文件打印过程中的用户界面



用户选择文件并开始打印后,将出现以下屏幕:







这是此类光敏聚合物打印机的相当标准的屏幕。



屏幕的最大区域被当前曝光层的图片占据。

此图片的显示与背光同步-打开背光时,显示图片,关闭背光时,将删除图片。图像形成为用于UV显示-沿着图像的短边。我没有急于沿着这幅图片的行偏移指针,但是在显示它之前,我给显示控制器一个命令来更改正在倒入数据的输出方向,即 该图片的区域在其侧面被证明是“转向”的。



以下是有关打印进度的信息-打印的经过和估计的时间,当前图层和图层总数,进度条及其右边的百分比。我也想在层数之后添加当前高度(以毫米为单位)。



右边是暂停,设置和中断按钮。当您按下固件中的暂停时,将设置暂停标志,并且进一步的行为取决于当前打印机的状态。如果平台因下一层故障而关闭,或者固件层已经开始曝光,则固件将完成曝光,只有在此之后,平台才会升高到暂停高度(在设置中进行了设置),它将等待用户单击“继续”按钮:







首先以文件参数中指定的速度升高平台以进行暂停,然后在相同参数中指定的高度之后升高速度。



当打印中断时,将出现一个窗口确认该操作,只有确认后,打印才会停止,平台将上升到最大轴高。提升速度以及暂停过程中的提升速度是可变的-首先缓慢地将层从胶片上分离下来,然后再增加到最大。



设置按钮尚不起作用,但是当您单击它时,用户将进入带有可更改打印参数的屏幕-层曝光时间,高度和提升速度等。现在,我正在完成它。还有一种想法可以将更改的参数保存回打印文件。



5.每件事,例如控制灯光和风扇,加载和保存设置等。



该评估板具有3个大功率MOSFET输出-一个用于照明的UV LED,两个用于风扇(例如,冷却照明二极管和冷却显示器)。这里没有什么有趣的-微控制器的输出连接到这些晶体管的栅极,并且控制它们就像使LED闪烁一样简单。为了提高曝光时间的准确性,可以通过设置操作时间的功能在主循环中将其打开:



UVLED_TimerOn(l_info.light_time * 1000);

void		UVLED_TimerOn(uint32_t time)
{
	uvled_timer = time;
	UVLED_On();
}


当背光计数器达到零时,它将从计时器的毫秒中断中关闭:



...
	if (uvled_timer && uvled_timer != TIMER_DISABLE)
	{
		uvled_timer--;
		if (uvled_timer == 0)
			UVLED_Off();
	}
...


5.1设置,从文件加载并保存到EEPROM



设置存储在板载EEPROM中24c16。在这里,与将资源存储在大型闪存中相比,一切都很简单-对于每种类型的存储数据,EEPROM内部的地址偏移都是硬编码的。它总共存储三个块:Z轴设置,常规系统设置(语言,声音等)和主要打印机组件的时间计数器-照明,显示和风扇。



存储的块结构包含当前固件版本和原始校验和-只是该块中所有字节的值的16位总和。从EPROM读取设置时,将检查CRC,如果它与实际的不对应,则为该块的参数分配默认值,计算新的CRC并将该块保存在EPROM中,而不是旧的CRC。如果读取的块与当前版本不匹配,则应将其更新为当前版本,并将其以新形式而不是旧形式保存。这尚未实现,但将来会进行以正确更新固件。



可以通过界面更改某些设置,但是大多数只能通过加载配置文件来更改。在这里,我没有改变自己的习惯,而是为此类文件编写了自己的解析器。



这种文件的结构是标准的:参数名称+等号+参数值。一行-一个参数。行首以及等号与名称和值之间的空格和制表符将被忽略。空行和以井号字符“#”开头的行也将被忽略,该字符定义带注释的行。参数和节的名称中字母的大小写无关紧要。



除参数外,该文件还包含其名称括在方括号中的节。在遇到遇到的节名之后,解析器期望只有属于该节的参数才能继续前进,直到遇到另一个节名。老实说,我不知道为什么要介绍这些部分。当我这样做时,我有一些与他们相关的想法,但是现在我不记得了。



为了缩短读取的参数名称与预定义名称的比较,首先解析读取名称的第一个字母,然后仅比较以该字母开头的那些名称。



配置文件内容
# Stepper motor Z axis settings
[ZMotor]

	#    .
	#  : 0  1.  : 1.
	#         .
	invert_dir = 1

	#       .
	#  : -1  1.  : -1.
	#     -1,     
	#    ,   .   1
	#      .
	home_direction = -1

	#   Z    .  ,  
	#    0,   -   .
	home_pos = 0.0

	#         .
	#  :     -32000.0  32000.0.
	#  : -3.0
	#        . 
	#     ,    .
	min_pos = -3.0

	#         .
	#  :     -32000.0  32000.0.
	#  : 180.0
	#        . 
	#     ,    .
	max_pos = 180.0

	#   .
	#  : 0  1.  : 1.
	#         ,  
	#  1,   -  0.
	min_endstop_inverting = 1

	#   .
	#  : 0  1.  : 1.
	#         ,  
	#  1,   -  0.
	max_endstop_inverting = 1

	#     1   .
	steps_per_mm = 1600

	#  ,       
	# , /.  : 6.0.
	homing_feedrate_fast = 6.0

	#  ,       
	# , /.  : 1.0.
	homing_feedrate_slow = 1.0

	#     , /2.
	acceleration = 0.7

	#      , /.
	feedrate = 5.0

	#       (   ,
	#      ..), /2.
	travel_acceleration = 25.0

	#       (   ,
	#      ..), /.    30  
	#          ,   
	# 5 /.
	travel_feedrate = 25.0

	#       , .
	current_vref = 800.0

	#          , .
	current_hold_vref = 300.0

	#      ,    
	#    .   .  0  
	#    .
	hold_time = 30.0

	#      ,    
	# .   .       
	#   hold_time.  0   .
	#  ,       .
	off_time = 10.0



# General settings
[General]

	#      (0.001 )   
	#      .
	#  :  0  15000.  : 700 (0.7 ).
	buzzer_msg_duration = 700

	#      (0.001 )  
	#     ,   .
	#  :  0  15000.  : 70 (0.07 ).
	buzzer_touch_duration = 70

	#       180 .
	#            .
	#  : 0  1.  : 0.
	rotate_display = 0

	#           ,   .
	#    LCD-.      -   
	#  .
	#  :  0  15000.  : 10.  0   .
	screensaver_time = 10




在文件列表中选择此类文件(扩展名为.acfg)时,固件将询问用户是否要从该文件下载并应用设置,并在确认后开始解析该文件。







如果发现错误,将显示一条消息,指示错误类型和行号。处理以下错误:



  • 未知的分区名称
  • 参数名称未知
  • 无效的参数值-例如,当试图为数字参数分配文本值时


如果有人感兴趣-这是解析器的三个主要功能的完整清单
void			_cfg_GetParamName(char *src, char *dest, uint16_t maxlen)
{
	if (src == NULL || dest == NULL)
		return;
	
	char *string = src;
	// skip spaces
	while (*string != 0 && maxlen > 0 && (*string == ' ' || *string == '\t' || *string == '\r'))
	{
		string++;
		maxlen--;
	}
	// until first space symbol
	while (maxlen > 0 && *string != 0 && *string != ' ' && *string != '\t' && *string != '\r' && *string != '\n' && *string != '=')
	{
		*dest = *string;
		dest++;
		string++;
		maxlen--;
	}
	
	if (maxlen == 0)
		dest--;
	
	*dest = 0;
	return;
}
//==============================================================================




void			_cfg_GetParamValue(char *src, PARAM_VALUE *val)
{
	val->type = PARAMVAL_NONE;
	val->float_val = 0;
	val->int_val = 0;
	val->uint_val = 0;
	val->char_val = (char*)"";
	
	if (src == NULL)
		return;
	if (val == NULL)
		return;
	
	char *string = src;
	// search '='
	while (*string > 0 && *string != '=')
		string++;
	if (*string == 0)
		return;
	
	// skip '='
	string++;
	// skip spaces
	while (*string != 0 && (*string == ' ' || *string == '\t' || *string == '\r'))
		string++;
	if (*string == 0)
		return;

	// check param if it numeric
	if ((*string > 47 && *string < 58) || *string == '.' || (*string == '-' && (*(string+1) > 47 && *(string+1) < 58) || *(string+1) == '.'))
	{
		val->type = PARAMVAL_NUMERIC;
		val->float_val = (float)atof(string);
		val->int_val = atoi(string);
		val->uint_val = strtoul(string, NULL, 10);
	}
	else
	{
		val->type = PARAMVAL_STRING;
		val->char_val = string;
	}
	
	return;
}
//==============================================================================




void			CFG_LoadFromFile(void *par1, void *par2)
{
	sprintf(msg, LANG_GetString(LSTR_MSG_CFGFILE_LOADING), cfgCFileName);
	TGUI_MessageBoxWait(LANG_GetString(LSTR_WAIT), msg);

	UTF8ToUnicode_Str(cfgTFileName, cfgCFileName, sizeof(cfgTFileName)/2);
	if (f_open(&ufile, cfgTFileName, FA_OPEN_EXISTING | FA_READ) != FR_OK)
	{
		if (tguiActiveScreen == (TG_SCREEN*)&tguiMsgBox)
			tguiActiveScreen = (TG_SCREEN*)((TG_MSGBOX*)tguiActiveScreen)->prevscreen;
		TGUI_MessageBoxOk(LANG_GetString(LSTR_ERROR), LANG_GetString(LSTR_MSG_FILE_OPEN_ERROR));
		BUZZ_TimerOn(cfgConfig.buzzer_msg);
		return;
	}

	uint16_t		cnt = 0;
	uint32_t		readed = 0, totalreaded = 0;
	char			*string = msg;
	char			lexem[128];
	PARAM_VALUE		pval;
	CFGREAD_STATE	rdstate = CFGR_GENERAL;
	int16_t			numstr = 0;
	
	while (1)
	{
		// read one string
		cnt = 0;
		readed = 0;
		string = msg;
		while (cnt < sizeof(msg))
		{
			if (f_read(&ufile, string, 1, &readed) != FR_OK || readed == 0 || *string == '\n')
			{
				*string = 0;
				break;
			}
			cnt++;
			string++;
			totalreaded += readed;
		}
		if (cnt == sizeof(msg))
		{
			string--;
			*string = 0;
		}
		numstr++;
		string = msg;
		
		// trim spaces/tabs at begin and end
		strtrim(string);
		
		// if string is empty
		if (*string == 0)
		{
			// if end of file
			if (readed == 0)
				break;
			else
				continue;
		}
		
		// skip comments
		if (*string == '#')
			continue;
		
		// upper all letters
		strupper_utf(string);
		
		// get parameter name
		_cfg_GetParamName(string, lexem, sizeof(lexem));
		
		// check if here section name
		if (*lexem == '[')
		{
			if (strcmp(lexem, (char*)"[ZMOTOR]") == 0)
			{
				rdstate = CFGR_ZMOTOR;
				continue;
			}
			else if (strcmp(lexem, (char*)"[GENERAL]") == 0)
			{
				rdstate = CFGR_GENERAL;
				continue;
			}
			else
			{
				rdstate = CFGR_ERROR;
				string = LANG_GetString(LSTR_MSG_UNKNOWN_SECTNAME_IN_CFG);
				sprintf(msg, string, numstr);
				break;
			}
		}
		
		// get parameter value
		_cfg_GetParamValue(string, &pval);
		if (pval.type == PARAMVAL_NONE)
		{
			rdstate = CFGR_ERROR;
			string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
			sprintf(msg, string, numstr);
			break;
		}
		
		// check and setup parameter
		switch (rdstate)
		{
			case CFGR_ZMOTOR:
				rdstate = CFGR_ERROR;
				if (*lexem == 'A')
				{
					if (strcmp(lexem, (char*)"ACCELERATION") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.float_val < 0.1)
							pval.float_val = 0.1;
						cfgzMotor.acceleration = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'C')
				{
					if (strcmp(lexem, (char*)"CURRENT_HOLD_VREF") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val < 100)
							pval.uint_val = 100;
						if (pval.uint_val > 1000)
							pval.uint_val = 1000;
						cfgzMotor.current_hold_vref = pval.uint_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"CURRENT_VREF") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val < 100)
							pval.uint_val = 100;
						if (pval.uint_val > 1000)
							pval.uint_val = 1000;
						cfgzMotor.current_vref = pval.uint_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'F')
				{
					if (strcmp(lexem, (char*)"FEEDRATE") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.float_val < 0.1)
							pval.float_val = 0.1;
						if (pval.float_val > 40)
							pval.float_val = 40;
						cfgzMotor.feedrate = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'H')
				{
					if (strcmp(lexem, (char*)"HOLD_TIME") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val == 0)
							pval.uint_val = TIMER_DISABLE;
						else if (pval.uint_val > 100000)
							pval.uint_val = 100000;
						cfgzMotor.hold_time = pval.uint_val * 1000;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"HOME_DIRECTION") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.int_val != -1.0 && pval.int_val != 1.0)
							pval.int_val = -1;
						cfgzMotor.home_dir = pval.int_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"HOME_POS") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						cfgzMotor.home_pos = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"HOMING_FEEDRATE_FAST") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.float_val < 0.1)
							pval.float_val = 0.1;
						if (pval.float_val > 40)
							pval.float_val = 40;
						cfgzMotor.homing_feedrate_fast = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"HOMING_FEEDRATE_SLOW") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.float_val < 0.1)
							pval.float_val = 0.1;
						if (pval.float_val > 40)
							pval.float_val = 40;
						cfgzMotor.homing_feedrate_slow = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'I')
				{
					if (strcmp(lexem, (char*)"INVERT_DIR") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.int_val < 0 || pval.int_val > 1)
							pval.int_val = 1;
						cfgzMotor.invert_dir = pval.int_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'M')
				{
					if (strcmp(lexem, (char*)"MAX_ENDSTOP_INVERTING") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.int_val < 0 || pval.int_val > 1)
							pval.int_val = 1;
						cfgzMotor.max_endstop_inverting = pval.int_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"MAX_POS") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						cfgzMotor.max_pos = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"MIN_ENDSTOP_INVERTING") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.int_val < 0 || pval.int_val > 1)
							pval.int_val = 1;
						cfgzMotor.min_endstop_inverting = pval.int_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"MIN_POS") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						cfgzMotor.min_pos = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'O')
				{
					if (strcmp(lexem, (char*)"OFF_TIME") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val > 100000)
							pval.uint_val = 100000;
						else if (pval.uint_val < cfgzMotor.hold_time)
							pval.uint_val = cfgzMotor.hold_time + 1000;
						else if (pval.uint_val == 0)
							pval.uint_val = TIMER_DISABLE;
						cfgzMotor.off_time = pval.int_val * 60000;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'S')
				{
					if (strcmp(lexem, (char*)"STEPS_PER_MM") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val < 1)
							pval.uint_val = 1;
						if (pval.uint_val > 200000)
							pval.uint_val = 200000;
						cfgzMotor.steps_per_mm = pval.uint_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				} else
				if (*lexem == 'T')
				{
					if (strcmp(lexem, (char*)"TRAVEL_ACCELERATION") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.float_val < 0.1)
							pval.float_val = 0.1;
						cfgzMotor.travel_acceleration = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
					if (strcmp(lexem, (char*)"TRAVEL_FEEDRATE") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.float_val < 0.1)
							pval.float_val = 0.1;
						cfgzMotor.travel_feedrate = pval.float_val;
						rdstate = CFGR_ZMOTOR;
						break;
					}
				}

				string = LANG_GetString(LSTR_MSG_UNKNOWN_PARAMNAME_IN_CFG);
				sprintf(msg, string, numstr);
				break;

			case CFGR_GENERAL:
				rdstate = CFGR_ERROR;
				if (*lexem == 'B')
				{
					if (strcmp(lexem, (char*)"BUZZER_MSG_DURATION") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val > 15000)
							pval.uint_val = 15000;
						cfgConfig.buzzer_msg = pval.uint_val;
						rdstate = CFGR_GENERAL;
						break;
					}
					if (strcmp(lexem, (char*)"BUZZER_TOUCH_DURATION") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val > 15000)
							pval.uint_val = 15000;
						cfgConfig.buzzer_touch = pval.uint_val;
						rdstate = CFGR_GENERAL;
						break;
					}
				} else
				if (*lexem == 'R')
				{
					if (strcmp(lexem, (char*)"ROTATE_DISPLAY") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val > 0)
						{
							cfgConfig.display_rotate = 1;
							LCD_WriteCmd(0x0036);
							LCD_WriteRAM(0x0078);
						}
						else
						{
							cfgConfig.display_rotate = 0;
							LCD_WriteCmd(0x0036);
							LCD_WriteRAM(0x00B8);
						}
						rdstate = CFGR_GENERAL;
						break;
					}
				} else
				if (*lexem == 'S')
				{
					if (strcmp(lexem, (char*)"SCREENSAVER_TIME") == 0)
					{
						if (pval.type != PARAMVAL_NUMERIC)
						{
							string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
							sprintf(msg, string, numstr);
							break;
						}
						if (pval.uint_val > 15000)
							cfgConfig.screensaver_time = 15000 * 60000;
						else if (pval.uint_val == 0)
							pval.uint_val = TIMER_DISABLE;
						else
							cfgConfig.screensaver_time = pval.uint_val * 60000;
						rdstate = CFGR_GENERAL;
						break;
					}
				}

				string = LANG_GetString(LSTR_MSG_UNKNOWN_PARAMNAME_IN_CFG);
				sprintf(msg, string, numstr);
				break;

		}
		
		if (rdstate == CFGR_ERROR)
			break;
		
		
	}
	f_close(&ufile);
	
	
	if (tguiActiveScreen == (TG_SCREEN*)&tguiMsgBox)
	{
		tguiActiveScreen = (TG_SCREEN*)((TG_MSGBOX*)tguiActiveScreen)->prevscreen;
	}

	if (rdstate == CFGR_ERROR)
	{
		TGUI_MessageBoxOk(LANG_GetString(LSTR_ERROR), msg);
		BUZZ_TimerOn(cfgConfig.buzzer_msg);
	}
	else
	{
		CFG_SaveMotor();
		CFG_SaveConfig();
		TGUI_MessageBoxOk(LANG_GetString(LSTR_COMPLETED), LANG_GetString(LSTR_MSG_CFGFILE_LOADED));
	}
}
//==============================================================================




成功解析文件后,新设置将立即应用并保存到EPROM。



仅当文件被打印或中断时,打印机组件的运行时间计数器才在EPROM中更新。



6.舒适便利的附加功能



6.1带日历的时钟



好吧,只是为了做到这一点。为什么要浪费良品-微控制器内置的自主实时时钟,可以在关闭电源的情况下使用锂电池供电,并且消耗很少,因此根据计算,CR2032应该足够使用几年了。此外,制造商甚至在板上提供了这款手表所需的32 kHz石英。剩下的只是将电池座粘在板上,并将其上的接线焊接到公共负极和微控制器的特殊端子上,这是我在家做的。



时间,日期和月份显示在主屏幕的左上方:







相同的实时时钟用于计算组件的打印时间和运行时间。并且它们也用在屏幕保护程序中,如下所述。



6.2在打印过程中锁定屏幕以防意外点击



这是在熟人的要求下完成的。好吧,为什么不这样做,在某些情况下可能会有用。长按(约2.5秒)在打印屏幕标题上可以启用和禁用锁定。激活锁定后,右上角会显示一个红色锁定。在打印结束时,该锁会自动释放。



6.3在保持模式下降低电动机电流,停机电动机空闲



旨在减少打印机内部积聚的热量。在配置的非移动时间后,可以降低电流使电机进入保持模式。顺便说一下,此功能在TB6560型“成人”步进电机驱动器中很普遍。此外,在设置中,您可以设置一个时间,在此时间之后,如果不移动,则电动机将完全断电。但是,这也将导致以下事实:如果执行了轴归零,则将变得无效。可以在相同设置中完全禁用这两个功能。



6.4屏幕保护程序



就像手表一样-因为我可以。在设置中指定的时间之后没有按屏幕的情况下,屏幕将切换到数字桌面时钟的仿真模式:







除了时间以外,还将显示带有星期几的完整日期。固件通过按显示屏的任何部分退出此模式。考虑到数字很大,并且引擎关闭时的耗电量不到2瓦,具有这种屏幕保护程序的打印机很可能用作房间时钟:)在打印过程中,屏幕保护程序也会在指定的时间后出现,但有一个补充-打印进度在屏幕底部:







在设置中,您可以设置屏幕保护程序的响应时间或将其禁用。



6.5背光和显示检查







可以从“服务”菜单访问此屏幕,在检查背光二极管或紫外线显示时将很有用。在顶部,选择了三个图像之一,这些图像将显示在UV显示屏上-框架,整个显示屏的全部照明,矩形。底部有两个按钮,用于打开和关闭背光灯和显示屏。2分钟后,随附的指示灯将自动熄灭,通常此时间足以进行任何测试。退出此屏幕时,背光和显示屏都会自动关闭。



6.6设定







也可以从“工具”菜单访问此屏幕。这里的设置很少,说实话,我从来没有想过经常需要哪种设置,以至于将它们放在界面中,而不仅仅是在配置文件中是有意义的。这还将增加重置打印机组件的操作时间计数器的功能,嗯,我不知道了:)



当然,您可以在单独打开的屏幕中设置时间和日期(因为有时钟):







您可以将平台提升高度设置为暂停,然后将其打开和关闭显示点击和消息的声音。更改设置时,新值仅在关闭电源之前才有效,并且不会保存到EPROM中。要保存它们,请在更改参数后按菜单中的保存按钮(带有软盘图标)。



在一个特殊的屏幕中输入数值:







在这里,我实现了我在其他打印机上所缺少的所有功能。



  1. “±”和“。”按钮 仅当已编辑的参数可以分别为负数或小数时才有效。
  2. 如果在进入此屏幕后首先按下任何数字按钮,则旧值将被相应的数字替换。如果按钮为“。”,它将被替换为“ 0”。也就是说,无需删除旧值,您可以立即开始输入新值。
  3. “”按钮,将当前值清零。



    按下“后退”按钮将不会应用新值。要应用它,您需要单击“确定”。


6.7最后-打印机信息屏幕







此屏幕可直接从主菜单访问。这里最重要的是固件/ FPGA版本和运行时间计数器。在底部,仍然有关于接口作者和GitHub上存储库地址的信息。界面的作者是未来的基础。如果仍然可以通过一个简单的文本文件配置界面,那么就有机会指定作者的姓名。



结束



这是我这个宠物项目的最后一部分。该项目可以生存和发展,尽管没有我所希望的快,但它已经相当高效。



我可能应该添加更多代码...但是我不认为我的代码中有什么值得夸耀的地方。我认为,描述它的工作原理和完成的工作更重要,并且代码在那里,都在GitHub上,只要有兴趣,我就可以完整地观看。我想是这样。



我期待您的问题和评论,并感谢您对这些文章的关注。



-第1部分: 1.用户界面。

-第2部分: 2.在USB闪存驱动器上使用文件系统。 3.步进电机控制平台移动。

-第3部分:4.将图层图像输出到背光显示器。5.每件事,例如控制灯光和风扇,加载和保存设置等。6.舒适和便利的附加功能。



链接



Aliexpress上的MKS DLP套件

GitHub上制造商的原始固件资源GitHub

上两个版本的板的制造商的方案资源GitHub上的

我的资源



All Articles