3D打印機(jī)Marlin固件串口功能解析和程序移植
原版Marlin固件硬件平臺(tái)基于arduino,采用C++類對(duì)串口操作函數(shù)函數(shù)進(jìn)行了封裝,代碼注釋中介紹了這些函數(shù)的功能。MarlinSerial.h文件中類的定義,此處的類只保留的框架結(jié)構(gòu),留存的這些函數(shù)基本上是要一直到STM32平臺(tái)要實(shí)現(xiàn)的函數(shù)。
class MarlinSerial //: public Stream
{
public:
MarlinSerial();
void begin(long); //串口初始化設(shè)置,配置串口波特率
void end(); //禁止串口傳輸函數(shù)
int peek(void); //讀串口緩存中下一字節(jié)的數(shù)據(jù)(字符型),但不從內(nèi)部緩存中刪除該數(shù)據(jù)。
int read(void); //讀取串口數(shù)據(jù),一次讀一個(gè)字符,讀完后刪除已讀數(shù)據(jù)
void flush(void); //等待輸出數(shù)據(jù)傳送完畢
int available(void);//返回的是緩沖區(qū)準(zhǔn)確的可讀字節(jié)數(shù)
void checkRx(void)
};
extern MarlinSerial MSerial; //外部聲明,實(shí)例化一個(gè)串口對(duì)象MSerial
MarlinSerial.cpp文件中定義了具體函數(shù)的實(shí)現(xiàn)方式,通過實(shí)例化的對(duì)象便可以操作這些串口函數(shù) 。
循環(huán)隊(duì)列簡介
該串口操作函數(shù)用到了數(shù)據(jù)結(jié)構(gòu)中循環(huán)隊(duì)列的算法,下面先介紹一下循環(huán)隊(duì)列:
//定義隊(duì)列 #define MaxSize 50 //定義隊(duì)列中元素的最大個(gè)數(shù) typedef struct
{
int data[MaxSize]; //存放隊(duì)列元素
int front, rear; //隊(duì)頭指針和隊(duì)尾指針
}SqQueue
把存儲(chǔ)隊(duì)列元素的表從邏輯上看成一個(gè)環(huán),稱為循環(huán)隊(duì)列。當(dāng)隊(duì)首指針Q.font = MaxSize-1后再前進(jìn)一個(gè)位置就會(huì)自動(dòng)到0,這就可以利用除法取余運(yùn)算來實(shí)現(xiàn)。
具體循環(huán)隊(duì)列的實(shí)現(xiàn)請(qǐng)參考數(shù)據(jù)結(jié)構(gòu) 循環(huán)隊(duì)列部分。(后面整理這一部分)
為什么要在串口接收部分創(chuàng)建環(huán)形緩沖區(qū)?
(引用)串口數(shù)據(jù)處理機(jī)制是數(shù)據(jù)接收并原樣回發(fā)的機(jī)制是:成功接收到一個(gè)數(shù)據(jù),觸發(fā)進(jìn)入中斷, 在中斷函數(shù)中將數(shù)據(jù)讀取出來,然后立即處理。這一種數(shù)據(jù)處理機(jī)制是“非緩沖中斷方式”,雖然這種數(shù) 據(jù)處理方式不消耗時(shí)間,但是這種數(shù)據(jù)處理方式嚴(yán)重的缺點(diǎn)是:數(shù)據(jù)無緩沖區(qū),如果先前接收的的 數(shù)據(jù)如果尚未發(fā)送完成(處理完成),然后串口又接收到新的數(shù)據(jù),新接收的數(shù)據(jù)就會(huì)把尚未處理 的數(shù)據(jù)覆蓋,從而導(dǎo)致“數(shù)據(jù)丟包”。串口接收部分創(chuàng)建環(huán)形緩沖區(qū)便可以很好的避免因收發(fā)速度不 一致產(chǎn)生的數(shù)據(jù)丟包。
串口緩沖區(qū)的實(shí)現(xiàn)
接下來具體分析下Marlin串口緩沖區(qū)的實(shí)現(xiàn)(下面分析的代碼為移植到STM32上的實(shí)現(xiàn)代碼,原理一致。):
.h頭文件
#define RX_BUFFER_SIZE 128 //定義串口緩沖區(qū)的大小 //定義環(huán)形緩沖區(qū)結(jié)構(gòu)體
typerdef struct
{
unsigned char buffer[RX_BUFFER_SIZE]; //存放接收到的字符
int head; //隊(duì)頭指針
int tail; //隊(duì)尾指針
}ring_buffer;
注意:這里的頭和尾的定義恰與循環(huán)隊(duì)列里面的頭和尾定義相反,在理解上將head當(dāng)作rear,將tail當(dāng)作front即可
.c文件
ring_buffer rx_buffer = { { 0 }, 0, 0 }; //定義結(jié)構(gòu)體類型的接收緩沖區(qū)并初始化
void store_char(unsigned char c) //將接收到的數(shù)據(jù)存入緩沖區(qū)
{
int i = (unsigned int)(rx_buffer.head + 1) % RX_BUFFER_SIZE;
//如果我們應(yīng)該存儲(chǔ)的接收到的字符的位置剛好在尾端的前面
//(意味著頭部將要進(jìn)入尾端的當(dāng)前位置),這樣將會(huì)溢出緩沖區(qū),
//因此我們不該存入這個(gè)字符或使這個(gè)頭前進(jìn) if (i != rx_buffer.tail) //緩沖區(qū)沒有存滿
{
rx_buffer.buffer[rx_buffer.head] = c;
rx_buffer.head = i;
}
}
unsigned int MSerial_available(void) //返回串口緩存區(qū)中數(shù)據(jù)的個(gè)數(shù)
{ 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) //按存入順序逐個(gè)讀取緩沖區(qū)的數(shù)據(jù)
{
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) //等待串口數(shù)據(jù)傳送完畢
{
// RX
//不要顛倒這個(gè)否則可能會(huì)有一些問題,如果接收中斷發(fā)生在讀
//取rx_buffer_head之后但在寫入rx_buffer_tail之前
//之前的rx_buffer_head值可能被寫到rx_buffer_tail
//使它呈現(xiàn)緩沖區(qū)是滿的而非空的狀態(tài)*/
rx_buffer.head = rx_buffer.tail;
}
后面還有有什么不太理解,可以檢索“循環(huán)隊(duì)列” 、“串口環(huán)形緩沖區(qū)”等關(guān)鍵字來增進(jìn)理解。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!