
一、关于串口 串口通信是我们平时调试中经常使用到的一种调试方法,其连接简易,拔插相对方便,而且使用过程中上位机不需要安装驱动程序(除非使用USB转串口工具),因而在电调的调试过程,若能实时的通过串口向计算机发送电调状态信息,将会十分的有助于电调的开发工作。但是串口的缺点是每次只能发送一个字节的数据,对于需要的发送一串数据的情况,一般采用循环发送方式(如printf),浪费了处理器的时间,而且对于一些复杂的数据如float、int数据则还需转化成字符型才能进行发送。如何实现一个既能实现不占用太多处理器时间,而且可以发送一些复杂数据类型的串口变得十分必要。MK电调这方面做的很好值得我们学习一番。 二、MK电调调试串口功能实现 首先MK电调中定义了一个用于输出调试信息的数据类型str_DebugOut: struct str_DebugOut { unsigned char Digital[2]; unsigned int Analog[8]; // Debugwerte }; 接着定义了一个基于该类型的变量DebugOut。 主程序中调试输出部分程序片段如下,该段程序在while下循环的运行。 ……………… if(!UebertragungAbgeschlossen) SendUart(); else DatenUebertragung(); ……………… UebertragungAbgeschlossen为数据发送完成的标志。0表示一组报文发送未完成,1表示完成一组报文发送。 首先我们假定一组报文已经发送完成 则调用函数voidDatenUebertragung(void) ,进行数据编码和定时发送设定工作。 void DatenUebertragung(void) { if((CheckDelay(Debug_Timer) &&UebertragungAbgeschlossen)) { SendOutData('D',MeineSlaveAdresse,(unsignedchar *) &DebugOut,sizeof(DebugOut)); Debug_Timer = SetDelay(50); // Sendeintervall } } 而函数DatenUebertragung调用了SendOutData(unsigned charcmd,unsigned char modul, unsigned char *snd, unsigned char len)函数,该函数将调试信息进行数据的重新编码,并且加入的CRC校验位,进一步提高了数据的可靠性。 SendOutData函数的具体定义如下: voidSendOutData(unsigned char cmd,unsigned char modul, unsigned char *snd, unsignedchar len) { unsigned int pt = 0; unsigned char a,b,c; unsigned char ptr = 0; % |; i T8 H2 }, U% D SendeBuffer[pt++] = '#'; // Startzeichen SendeBuffer[pt++] = modul; // Adresse (a=0; b=1,...) SendeBuffer[pt++] = cmd; // Commando ; L4 E6 O5 q& u1 z8 \ while(len) { if(len) { a = snd[ptr++]; len--;} else a =0; if(len) { b = snd[ptr++]; len--;} else b =0; if(len) { c = snd[ptr++]; len--;} else c =0; SendeBuffer[pt++] = '=' + (a >> 2); SendeBuffer[pt++] = '=' + (((a & 0x03)<< 4) | ((b & 0xf0) >> 4)); SendeBuffer[pt++] = '=' + (((b & 0x0f)<< 2) | ((c & 0xc0) >> 6)); SendeBuffer[pt++] = '=' + ( c & 0x3f); } AddCRC(pt); } AddCRC函数如下: voidAddCRC(unsigned int wieviele) { unsigned int tmpCRC = 0,i; for(i = 0; i < wieviele;i++) { tmpCRC += SendeBuffer; } tmpCRC %= 4096; SendeBuffer[i++] = '=' + tmpCRC / 64; SendeBuffer[i++] = '=' + tmpCRC % 64; SendeBuffer[i++] = '\r'; UebertragungAbgeschlossen = 0; UDR = SendeBuffer[0]; } 上述函数中SendeBuffer为串口发送的缓存区,很明显发送的报文包头为'#',随后是modul,其次是cmd,后面跟着的是调试信息的重新编码数据,然后CRC校验码,最后是结束符'\r'。该部分的数据编码,则是MK电调串口通信的精华部分。 MK电调进行数据编码的一个重要目的就是要能够在接受数据端能够识别出报文起始'#'和结束符'\r',因此在报文的中间必须不能够出现与这两个字符相同的字符。以下列出使用到的几个关键ASCII码。 # — 35(十进制) 0x23(十六进制) \r — 13(十进制) 0x0D(十六进制) = — 61(十进制) 0x3D(十六进制) 从上述的ASCII码值,只要数据区的每个字节值>35或者<13时即可识别到报文的头和尾,很明显大于35的数据范围比较广,所以将数据编码到大于35的ASCII码的值比较方便。MK电调采用此种方法,将原来的数据由三个字节扩充为四个字节,并将各个字节加上‘=’(0x3D),从而使得扩充后的字节均大于‘#’。编码后的数据区数据将全部大于’#’、‘\r’,从而从接收得到的报文中可以检测出报文头和尾,是不是设计的非常巧妙啊,德国人果然很严谨啊!! 数据编码完成了,下面就是数据发送了,如果采用一股脑的发送方式(如printf()等),将会浪费处理器不少的时间,软件的实时性得不到保证,那怎么办年。首先设定一个定时输出的调试信息的定时器Debug_Timer,定时器时间到后则启动函数DatenUebertragung()进行数据编码,把数据准备好,然后在主程序中持续的查询数据有没有发送完成的标志UebertragungAbgeschlossen(前文提到了该标志),若发送未完成则进行发送运行函数SendUart,否则运行DatenUebertragung()进行数据编码, 进入SendUart函数也不一定就可以立马发送数据,因此进入该函数后,首先查询下串口发送标志,串口忙则不发送直接返回,串口闲的话就再发送一个字节了,全部数据包发送完成后将发送完成标志置位。 void SendUart(void) { static unsigned int ptr = 0; unsigned char tmp_tx; if(!(UCSRA & 0x40)) return; if(!UebertragungAbgeschlossen) { ptr++; // die[0] wurde schon gesendet tmp_tx = SendeBuffer[ptr]; if((tmp_tx == '\r') || (ptr == MAX_SENDE_BUFF)) { ptr = 0; UebertragungAbgeschlossen = 1; } USR |= (1<TXC); UDR = tmp_tx; } else ptr = 0; } 采用这种串口编码方式是不是既能保证不占用处理器的时间,又能够发送复杂的数据类型啊。 三、基于STM32的MK电调调试串口功能实现 分析完了MK电调后,考虑下如何在STM32 中实现吧,首先数据编码这块的可以直接采用MK电调的方法,而数据发送部分在STM32 中可以做的更简化些,使用串口发送DMA功能进行实现,将串口DMA的发送缓存器指向数据区,当编码完成后,使能串口发送DMA功能,不需要处理器干涉数据将进行发送。附件里提供MK电调的源码和采用STM32实现该功能的源代码。 ![]() |
谢谢顶帖!
谢谢,欢迎交流。
谢谢顶贴!!