原版Marlin固件硬件平台基于arduino,采用C++类对串口操作函数函数进行了封装,代码注释中介绍了这些函数的功能。MarlinSerial.h
文件中类的定义,此处的类只保留的框架结构,留存的这些函数基本上是要一直到STM32平台要实现的函数。
class MarlinSerial //: public Stream
{
public:
MarlinSerial();
void begin(long); //串口初始化设置,配置串口波特率
void end(); //禁止串口传输函数
int peek(void); //读串口缓存中下一字节的数据(字符型),但不从内部缓存中删除该数据。
int read(void); //读取串口数据,一次读一个字符,读完后删除已读数据
void flush(void); //等待输出数据传送完毕
int available(void);//返回的是缓冲区准确的可读字节数
void checkRx(void)
};
extern MarlinSerial MSerial; //外部声明,实例化一个串口对象MSerial
MarlinSerial.cpp
文件中定义了具体函数的实现方式,通过实例化的对象便可以操作这些串口函数 。
该串口操作函数用到了数据结构中循环队列的算法,下面先介绍一下循环队列:
//定义队列
#define MaxSize 50 //定义队列中元素的最大个数
typedef struct
{
int data[MaxSize]; //存放队列元素
int front, rear; //队头指针和队尾指针
}SqQueue
把存储队列元素的表从逻辑上看成一个环,称为循环队列。当队首指针Q.font = MaxSize-1后再前进一个位置就会自动到0,这就可以利用除法取余运算来实现。
具体循环队列的实现请参考数据结构 循环队列部分。(后面整理这一部分)
(引用)串口数据处理机制是数据接收并原样回发的机制是:成功接收到一个数据,触发进入中断, 在中断函数中将数据读取出来,然后立即处理。这一种数据处理机制是“非缓冲中断方式”,虽然这种数 据处理方式不消耗时间,但是这种数据处理方式严重的缺点是:数据无缓冲区,如果先前接收的的 数据如果尚未发送完成(处理完成),然后串口又接收到新的数据,新接收的数据就会把尚未处理 的数据覆盖,从而导致“数据丢包”。串口接收部分创建环形缓冲区便可以很好的避免因收发速度不 一致产生的数据丢包。
接下来具体分析下Marlin串口缓冲区的实现(下面分析的代码为移植到STM32上的实现代码,原理一致。):
.h头文件
#define RX_BUFFER_SIZE 128 //定义串口缓冲区的大小
//定义环形缓冲区结构体
typerdef struct
{
unsigned char buffer[RX_BUFFER_SIZE]; //存放接收到的字符
int head; //队头指针
int tail; //队尾指针
}ring_buffer;
注意:这里的头和尾的定义恰与循环队列里面的头和尾定义相反,在理解上将head当作rear,将tail当作front即可
.c文件
ring_buffer rx_buffer = { { 0 }, 0, 0 }; //定义结构体类型的接收缓冲区并初始化
void store_char(unsigned char c) //将接收到的数据存入缓冲区
{
int i = (unsigned int)(rx_buffer.head + 1) % RX_BUFFER_SIZE;
//如果我们应该存储的接收到的字符的位置刚好在尾端的前面
//(意味着头部将要进入尾端的当前位置),这样将会溢出缓冲区,
//因此我们不该存入这个字符或使这个头前进
if (i != rx_buffer.tail) //缓冲区没有存满
{
rx_buffer.buffer[rx_buffer.head] = c;
rx_buffer.head = i;
}
}
unsigned int MSerial_available(void) //返回串口缓存区中数据的个数
{
return (unsigned int)(RX_BUFFER_SIZE + rx_buffer.head
- rx_buffer.tail) % RX_BUFFER_SIZE;
}
uint8_t MSerial_peek(void)
{
if (rx_buffer.head == rx_buffer.tail)
{
return 0;
}
else
{
return rx_buffer.buffer[rx_buffer.tail];
}
}
uint8_t Mserial_read(void) //按存入顺序逐个读取缓冲区的数据
{
uint8_t c;
/*如果头不是在尾的前面,将收不到任何字符*/
if (rx_buffer.head == rx_buffer.tail)
{
return 0;
}
else
{
c = rx_buffer.buffer[rx_buffer.tail];
rx_buffer.tail = (unsigned int)(rx_buffer.tail + 1) % RX_BUFFER_SIZE;
return c;
}
}
void MSerial_flush(void) //等待串口数据传送完毕
{
// RX
//不要颠倒这个否则可能会有一些问题,如果接收中断发生在读
//取rx_buffer_head之后但在写入rx_buffer_tail之前
//之前的rx_buffer_head值可能被写到rx_buffer_tail
//使它呈现缓冲区是满的而非空的状态*/
rx_buffer.head = rx_buffer.tail;
}
后面还有有什么不太理解,可以检索“循环队列” 、“串口环形缓冲区”等关键字来增进理解。