目錄
專用名詞
一. 溫度傳感器介紹
1.2 DS18B20結構
二. DS18B20工作流程
2.1 三步驟
2.2 初始化序列
2.3 ROM指令
2.4 功能ROM指令
2.5 讀寫時序
三 代碼設計
3.1 狀态跳轉——工作流程
3.2 狀态跳轉條件
3.3 代碼
四 上闆驗證
五 總結
專用名詞
CONFIGURATION REGISTER 配置寄存器 scrathpad memory 緩沖存儲器、高速暫存存儲器、暫時存儲器
一. 溫度傳感器介紹
DS18B20 預設溫度資料是 12bit(補碼),最高位為符号位(1:負溫,0:正溫),低 4bit為小數位,低 11bit 轉化為十進制後乘以 0.0625 即為實際溫度值。
DS18B20 是一款常用的數字式溫度傳感器,輸出數字信号來表示溫度值,具有體積小,精度高等特點。DS18B20 的溫度測量範圍為-55℃~+125℃;精度為±0.5℃;輸出溫度值位寬可程式設計(9—12bit);單總線接口允許多個裝置挂在同一總線,可以用于部署分布式溫度采集系統;可配置的溫度轉換時間,最大溫度轉換時間為 750ms。
DS18B20的傳輸序列:
1. 初始化 2. ROM指令 3. 功能指令 當需要讀取 DS18B20 的溫度轉換結果時,ROM 指令發送完後,需要回到步驟 1,再次發送初始化序列,然後發送功能指令。
1.2 DS18B20結構
高速緩存器的結構框圖如下:
高速緩存器一共有九個八位寄存器,位元組0和位元組1是存儲溫度的,從低位元組LSB到高位元組MSB,這兩個位元組不可以更改,隻能讀出。認配置溫度資料為12位,其中最高位為符号位,即溫度值共11位,最低四位為小數位。FPGA在讀取溫度資料時,一次會讀2位元組共16位,讀完後将低11位的二進制數轉化為十進制數後再乘以0.0625得到所測的實際溫度值。另外還需要判斷溫度的正負,前5個數字為符号位,這5位同時變化,我們隻需要判斷其中任何一位就可以了。前5位為1時,讀取的溫度為負值,則測到的數值需要取反加1再乘以0.0625才可得到實際溫度值。前5位為0時,讀取的溫度為正值,隻要将測得的數值乘以0.0625即可得到實際溫值。高溫出發值 TH 和低溫觸發值 TL 都是要滿足一定條件才會響應,需要自己設定。
由上圖可知DS18B20的高速緩存器共有9個8位寄存器,其中溫度資料低位(LSB)對應位元組 位址0,溫度資料高位(MSB)對應位元組位址1,以此類推,配置寄存器的位元組位址為4。溫度資料存放的格式如下圖:
DS18B20在出廠時預設配置溫度資料為12位,其中最高位為符号位,即溫度值共11位,最低四位為小數位。FPGA在讀取溫度資料時,一次會讀2位元組共16位,讀完後将低11位的二進制 數轉化為十進制數後再乘以0.0625得到所測的實際溫度值。
另外還需要判斷溫度的正負,前5 個數字為符号位,這5位同時變化,我們隻需要判斷其中任何一位就可以了。前5位為1時,讀 取的溫度為負值,則測到的數值需要取反加1再乘以0.0625才可得到實際溫度值。前5位為0時, 讀取的溫度為正值,隻要将測得的數值乘以0.0625即可得到實際溫度值。
高速緩存器中第四個位元組即為配置寄存器,使用者通過改變 R1 和 R0 的值來配置 DS18B20 的分辨率,上電預設為 R1=1 以及 R0=1(12 位分辨率)。需要注意的是轉換時間與分辨率時間是有關系的。另外寄存器中最高位和低 5 位作 為内部使用而保留使用,不可被寫入。轉換時間與位數關系如下表所示:
二. 單總線協定
2.1 單總線傳輸原理
由于DS18B20溫度傳感器采用單總線的方式進行通信,是以我們先簡略的介紹一下單總線
通信的原理。
單總線傳輸的定義:顧名思義,即主機和從機用一根總線進行通信,是一種半雙工的通信
方式,單線=時鐘線+資料線+控制線(+電源線)。理想狀況下一條總線上的從器件數量幾乎不
受數量限制。
單總線優劣勢:
單總線技術具有線路簡單,硬體開銷少,成本低廉,便于總線擴充和維護等優點。但由于隻有一根總線,驅動能力一般較差,不能接過多的從器件,實際使用中,一般最多隻能接8個從器件;抗幹擾能力較差,一般隻能在中短距離的低速傳輸中使用;軟體設計複雜,事物往往有兩面性,硬體部分的簡單往往需要軟體在複雜度上做出犧牲。
資料傳輸:
單總線通信協定為確定資料的完整性,定義了如下幾種單線信号類型:複位脈沖、存在脈
沖、寫0、寫1、讀0和讀1。所有這些信号,除存在脈沖外,都是由總線控制器發出的。
單總線協定中主機和從機(DS18B20)間的任何通訊都需要以初始化序列開始
2.2 資料傳輸三步驟
1. 初始化序列 2. rom指令 3. 功能指令
2.3 初始化序列
即初始化序列分為三個步驟:
1.主機發送複位脈沖 至少480us
2.上拉電阻将總線拉高 15~60us
3.DS18B20發送存在脈沖應答主機 60~240us
初始化—複位和存在脈沖
單總線上的所有事件都必須以初始化為開始。初始化序列由總線上的主裝置發出的複位脈沖以及緊跟着從裝置回應的存在脈沖構成。該存在脈沖是讓總線主裝置知道 DS18B20 在總線上并準備好運作
與 DS18B20 所有的通信都是由初始化開始的,初始化由主裝置發出的複位脈沖及 DS18B20 響應的存在脈沖組成。如下圖 所示。當 DS18B20 響應複位信号的存在脈沖 後,則其向主裝置表明其在該總線上,并且已經做好了執行指令的準備。 在初始化狀态,總線上的主裝置通過拉低 1-Wire 總線最少 480us 來表示發送複位脈 沖。發送完之後,主裝置要釋放總線進入接收模式。當總線釋放後,上拉電阻将 1- Wire 總線拉至高電平。當 DS18B20 檢測到該上升沿信号後,其等待 15us 至 60us 後将總線 拉低 60us 至 240us 來實作發送一個存在脈沖。
2.4 ROM指令
用于對 64 位的 ROM 操作;
F0H:搜尋ROM 當系統上電初始化後,主裝置可識别該總線上所有的從裝置的 ROM 編碼,這樣就可以使得主裝置确定總線上的從裝置的類型以及數量。 33H:讀 ROM 該指令允許主裝置讀取 DS18B20 的 64 位 ROM 編碼,隻有在總線上隻有一個 DS18B20 時才能使用這個指令。如果總線上存在多個從裝置,發送此指令,則當所有從設 備都會回應時,将會引起資料沖突。 55H:比對 ROM 該比對 ROM 指令之後接着發出 64 位 ROM 編碼,使主裝置在多點總線上定位一隻特定的 DS18B20。隻有和 64 位 ROM 序列完全比對的 DS18B20 才會做出響應。總線上的其 他從裝置都将等待下一個複位脈沖。此指令在總線上有單個或多個器件時都可以使用。 CCH:跳過 ROM 這條指令可以不用提供 64 位 ROM 編碼就進行下一步操作,在單點總線(一個 DS18B20 傳感器)情況下可以節省時間。如果總線上不止一個從裝置,在跳過 ROM 指令 之後跟着發一條讀指令,則所有從裝置将會同時執行溫度轉換,總線上就會發生資料沖突。 ECH:報警搜尋 該指令的操作與跳過 ROM 指令基本相同,但是不同的是隻有溫度高于 TH 或低于 TL (達到報警條件)的從裝置才會響應。隻要不掉電,警報狀态将一直保持,直到溫度不在警報範圍内為止。 如果系統中隻有一個 DS18B20,就不需要讀取 ROM 與比對 ROM 操作,可以直接發送跳過 ROM(CCH)指令,然後就可以開始測量溫度。
2.5 功能ROM指令
用于主機向DS18B20 的暫存器寫/讀資料,發溫度轉換以及讀取供電模式;
44H:溫度轉換 此指令為初始化單次溫度轉換,溫度轉換完後,轉換的溫度資料會寄存在高速緩存器 的 byte0(溫度資料低八位)和 byte1(溫度資料高八位)中,之後 DS18B20 恢複到低功耗 的閑置狀态。如果總線在該指令後發出讀時隙,若 DS18B20 正在進行溫度轉換則會響應 “0”,若完成了溫度轉換則響應“1”。如果是用的“寄生電源”供電模式,則在指令發 出後應立即強制拉高總線,拉高時間應大于時序要求。 4EH:寫暫存寄存器 該指令使得主裝置向高速緩存器寫入 3 個位元組的資料。第一個位元組寫入高速緩存器的 byte2 中(TH 寄存器),第二個位元組的資料寫入 byte3 中(TL 寄存器),第三個位元組的資料寫入 byte4 中(配置寄存器)。所有的資料都是由低位到高位的順序寫入。複位可随時中斷寫入。 48H:複制 RAM 該指令是将高速緩存器中的 TH(byte2)、TL(byte3)以及配置寄存器(byte4)裡的 值拷貝到非易失性的存儲器 EEPROM 裡。如果總線控制器在這條指令之後跟着發出讀時 隙,而 DS18B20 又正在忙于把暫存器拷貝到 EEPROM 存儲器,DS18B20 就會輸出一個 “0”,如果拷貝結束的話,DS18B20 則輸出“1”。如果裝置采用“寄生電源”供電模 式,則在該指令發送後,必須立即強制拉高總線至少 10ms。 BEH:讀暫存寄存器 從 byte0(溫度低八位)開始一直讀到 byte8(CRC 校驗),每個位元組的資料從低位開始傳送。若是不想讀取這麼多資料則在讀取資料時随時可以通過主機發送複位脈沖來停止讀取。 B8H:重調 EEPROM 該指令将溫度報警觸發值(TH 和 TL)及配置寄存器的資料從 EEPROM 中召回至高速 緩存器中。這個操作會在上電後自動執行一次,是以在上電期間暫存器中一直會存在有效 的資料。若在召回指令之後啟動讀時隙,若 DS18B20 正在進行召回 EEPROM 則會響應 “0”,若召回完成則響應“1”。 B4H:讀供電方式 該指令可以讀取總線上的 DS18B20 是否是由“寄生電源”供電。在讀取資料時序中 “0”表示“寄生電源供”模式供電,“1”表示外部電源供電。 暫存器位元組 0 和位元組 1 是隻讀的,用于存儲傳感器測量的溫度值
2.6 讀寫時序
寫時隙:
主裝置通過寫時隙将指令寫入 DS18B20 中,寫時隙有兩種情況:寫“1”和寫“0”時 隙。主裝置通過寫 1 時隙來向 DS18B20 中寫入邏輯 1,通過寫 0 時隙來向 DS18B20 中寫入 邏輯 0。當主裝置将總線從高電平拉至低電平時,啟動寫時隙,所有的寫時隙持續時間最 少為 60us,每個寫時隙間的恢複時間最少為 1us。 當總線(DQ)拉低後,DS18B20 在 15us 至 60us 之間對總線進行采樣,如果采的 DQ 為高電平則發生寫 1,如果為低電平則發生寫 0,如下圖所示(圖中的總線控制器即為主裝置)。 如果要産生寫 1 時隙,必須先将總線拉至邏輯低電平然後釋放總線,允許總線在寫 隙開始後 15us 内上拉至高電平。若要産生寫 0 時隙,必須将總線拉至邏輯低電平并保持不 變最少 60us。
讀時隙:
當我們發送完讀取供電模式[B4h]或讀高速緩存器[BEh]指令時,必須及時地生成讀時隙,隻有在讀時隙 DS18B20 才能向主裝置傳送資料。每個讀時隙最小必須有 60us 的持續 時間以及每個讀時隙間至少要有 1us 的恢複時間。當主裝置将總線從高電平拉至低電平超 過 1us,啟動讀時隙,如下圖所示。當啟動讀時隙後,DS18B20 将會向主裝置發送“0”或者“1”。DS18B20 通過将總線 拉高來發送 1,将總線拉低來發送 0 。當讀時隙完成後,DQ 引腳将通過上拉電阻将總線拉高至高電平的閑置狀态。從 DS18B20 中輸出的資料在啟動讀時隙後的 15us 内有效,是以,主裝置在讀時隙開始後的 15us 内必須釋放總線,并且對總線進行采樣。
三 代碼設計
3.1 狀态跳轉——工作流程
DS18B20的典型溫度讀取過程為:初始化➔發跳過ROM指令(CCH)➔發開始轉換指令(44H)➔延時➔初始化➔發送跳過ROM指令(CCH)➔發讀存儲器指令(BEH)➔連續讀出兩個位元組資料(即溫度)➔結束或開始下一循環。
主狀态機
就是用來表示溫度傳感器的工作流程的,首先是初始化
即初始化序列分為三個步驟: 1.主機發送複位脈沖 至少480us 2.上拉電阻将總線拉高即 釋放總線 15~60us 3.DS18B20發送存在脈沖應答主機 60~240us
初始化完成之後,主機就可以向從機讀寫資料。主機向從機發送跳過ROM指令,跳過ROM指令對單總線上僅有一個從機的情況下可以節省很多時間,不用提供64位ROM編碼就可以進行下一步操作。
條件:
當從機接收到應答,且計數器記到60us進行采樣,slave_ack == 1'b0 方可發送rom指令 即從機接收應答到發送rom指令是,跳過ROm指令發出 M_ACK2M_ROMS
本次實驗包含三個指令:
CMD_ROMS = 8'hCC,//跳過ROM指令 CMD_CONT = 8'h44,//溫度轉化 CMD_RTMP = 8'hBE;//讀暫存器
發送完跳過ROM指令之後進入發送功能指令
包括溫度轉換和讀取溫度暫存器指令,需要我們标記區分,是以需要一個flag标志,當m_wait2m_rest階段,flag為1,當m_rtmp2m_idle階段,則flag為0
若m_roms2m_cont,表示轉換溫度指令,當跳過rom指令寫入完成,即當從狀态機完成資料寫入并且從狀态機處于S_DONE,且标志flag為0,将rom指令寫入之後,進入溫度轉換
溫度轉換也需要向暫存器寫入資料,即溫度最開始的LSB到MSB兩個位元組的資料,CMD_CONT = 8'h44指令寫入後,即當從狀态機完成資料寫入并且從狀态機處于S_DONE,進入等待狀态
等待狀态即等待溫度轉換完成,需要等待750ms,之後直接進入idle狀态或者主機發送複位脈沖狀态,之後再次進行初始化序列,完成後發送rom指令,再進行讀取溫度寄存器指令
若m_roms2m_rcmd,表示讀溫度暫存器指令,即當從狀态機完成資料寫入并且從狀态機處于S_DONE,當跳過rom指令寫完後且flag為1,進入讀溫度指令。
讀溫度指令寫需要進行寫資料操作,當CMD_RTMP = 8'hBE寫入之後,即當從狀态機完成資料寫入并且從狀态機處于S_DONE,進入讀取溫度值,讀溫度值則進行讀操作,讀取溫度完成後進入idle狀态
從狀态機:
主機想從機進行寫資料(寫操作),或從機向主機發送資料(讀操作)
從機開啟條件:
assign s_idle2s_low = s_state_c == S_IDLE && (m_state_c == M_ROMS || m_state_c == M_CONT || m_state_c == M_RCMD || m_state_c == M_RTMP);
即當主機處于M_ROMS|| M_CONT || M_RCMD || M_RTMP時,從機開啟進入總線拉低狀态,s_low
assign s_low2s_send = s_state_c == S_LOW && (m_state_c == M_ROMS || m_state_c == M_CONT || m_state_c == M_RCMD) && end_cnt1;//拉低2us
即當主機處于M_ROMS|| M_CONT || M_RCMD ,從機近些寫操作,向從機寫入八比特資料,
寫資料是一比特一比特的資料寫入,寫入一個比特需要60us,進入釋放總線狀态,S_RELE,一個位元組沒有寫完則繼續進行寫入,釋放總線 3us (至少1us),即s_rele2s_low,然後主機拉低總線至少1us,進行寫“0”或寫“1”,再進入釋放總線狀态,直到一個位元組資料寫完,從機從s_rele2s_done,釋放總線 3us (至少1us)。
讀溫度暫存器:當主機處于M_RTMP,從機進行讀操作,主機拉低總線至少1us,進行讀資料,讀1bit需要60us,直到2位元組資料讀完(低位元組資料LSB到高位元組MSB)一共16位。
計數器
計數器主要記主狀态機狀态轉移時間,和從狀态機的讀寫時間,以及資料位數
設定時間參數:
parameter TIME_1US = 50, //基本時間1us //主狀态機延時 TIME_RST = 500, //複位脈沖 500us TIME_REL = 20, //主機釋放總線 20us TIME_PRE = 200, //主機接收存在脈沖 200us TIME_WAIT = 750000, //主機發完溫度轉換指令 等待750ms //從狀态機的延時 TIME_LOW = 2, //主機拉低總線 2us TIME_RW = 60, //主機讀、寫1bit 60us TIME_REC = 3; //主機讀寫完1bit釋放總線 3us包括 rele_low,rele_done
bit計數器主要用于記寫入一個位元組資料或讀出兩個位元組資料
assign add_cnt_bit = s_state_c == S_RELE && end_cnt1; // 從機處于RELE狀态,時隙恢複時間,表明1 bit 資料傳輸完成 assign end_cnt_bit = add_cnt_bit && cnt_bit == ((m_state_c == M_RTMP)?16-1:8-1);
slave_ack:采樣傳感器的存在脈沖
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin slave_ack <= 1'b1; end //接收應答狀态 計數器計到60us 進行采樣 else if(m_state_c == M_RACK && cnt0 == 60 && end_cnt_1us)begin slave_ack <= dq_din; end end
輸出信号
//dq_out_en always @(posedge clk or negedge rst_n)begin if(!rst_n)begin dq_out_en <= 0; end else if(m_idle2m_rest | s_idle2s_low | s_rele2s_low | m_wait2m_rest)begin dq_out_en <= 1'b1; //輸出 dq_out end else if(m_rest2m_rele | s_send2s_rele | s_low2s_samp)begin dq_out_en <= 1'b0; //不輸出 dq_out end end //dq_out always @(posedge clk or negedge rst_n)begin if(!rst_n)begin dq_out <= 0; end else if(m_idle2m_rest | s_idle2s_low | s_rele2s_low | m_wait2m_rest)begin dq_out <= 1'b0; end else if(s_low2s_send)begin dq_out <= wr_data[cnt_bit]; end end
溫度采集轉換并輸出:
//orign_data 溫度采集 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin orign_data <= 0; end else if(s_state_c == S_SAMP && cnt1 == 12 && end_cnt_1us)begin //溫度采集,在時隙起始後15us内采樣 orign_data[cnt_bit] <= dq_din; end end //temp_data 溫度判斷 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin temp_data <= 0; end else if(s_state_c == S_SAMP && cnt_bit == 15 && s_samp2s_rele)begin if(orign_data[15]) //判斷正負溫度,決定是否需要做補碼-原碼轉化 temp_data <= ~orign_data[10:0] + 1'b1; //負溫 則取反加1 else temp_data <= orign_data[10:0]; //正溫 end end /* 實際的溫度值為 temp_data * 0.0625; 為了保留4位小數精度,将實際溫度值放大了10000倍, 即 temp_data * 625; */ assign temp_data_w = temp_data * 625; //組合邏輯計算十進制溫度 //temp_out always @(posedge clk or negedge rst_n)begin if(!rst_n)begin temp_out <= 0; end else if(m_state_c == M_RTMP && s_rele2s_done)begin temp_out <= temp_data_w; end end //temp_out_vld 訓示溫度值輸出有效 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin temp_out_vld <= 0; end else begin temp_out_vld <= m_state_c == M_RTMP && s_rele2s_done; end end
狀态跳轉條件
主狀态機:
assign m_idle2m_rest = m_state_c == M_IDLE && (1'b1); //進入m_idle狀态,下一個時鐘周期上升沿将進入複位狀态
assign m_rest2m_rele = m_state_c == M_REST && (end_cnt0);
assign m_rele2m_rack = m_state_c == M_RELE && (end_cnt0);
assign m_rack2m_roms = m_state_c == M_RACK && (end_cnt0 && slave_ack == 1'b0);
assign m_roms2m_cont = m_state_c == M_ROMS && (s_state_c == S_DONE && flag == 1'b0);
assign m_roms2m_rcmd = m_state_c == M_ROMS && (s_state_c == S_DONE && flag == 1'b1);
assign m_cont2m_wait = m_state_c == M_CONT && (s_state_c == S_DONE);
assign m_wait2m_rest = m_state_c == M_WAIT && (end_cnt0);
assign m_rcmd2m_rtmp = m_state_c == M_RCMD && (s_state_c == S_DONE);
assign m_rtmp2m_idle = m_state_c == M_RTMP && (s_state_c == S_DONE);
從狀态機:
assign s_idle2s_low = s_state_c == S_IDLE && (m_state_c == M_ROMS ||
m_state_c == M_CONT || m_state_c == M_RCMD || m_state_c == M_RTMP);
assign s_low2s_send = s_state_c == S_LOW && (m_state_c == M_ROMS ||
m_state_c == M_CONT || m_state_c == M_RCMD) && end_cnt1;
assign s_low2s_samp = s_state_c == S_LOW && (m_state_c == M_RTMP && end_cnt1);
assign s_send2s_rele = s_state_c == S_SEND && (end_cnt1);
assign s_samp2s_rele = s_state_c == S_SAMP && (end_cnt1);
assign s_rele2s_low = s_state_c == S_RELE && (end_cnt1 && end_cnt_bit == 1'b0);
assign s_rele2s_done = s_state_c == S_RELE && (end_cnt1 && end_cnt_bit == 1'b1);
3.2 代碼
3.2 狀态跳轉條件
主狀态機狀态跳轉條件:
assign m_idle2m_rest = m_state_c == M_IDLE && (1'b1); //進入m_idle狀态,下一個時鐘周期上升沿将進入複位狀态 assign m_rest2m_rele = m_state_c == M_REST && (end_cnt0); assign m_rele2m_rack = m_state_c == M_RELE && (end_cnt0); assign m_rack2m_roms = m_state_c == M_RACK && (end_cnt0 && slave_ack == 1'b0); assign m_roms2m_cont = m_state_c == M_ROMS && (s_state_c == S_DONE && flag == 1'b0); assign m_roms2m_rcmd = m_state_c == M_ROMS && (s_state_c == S_DONE && flag == 1'b1); assign m_cont2m_wait = m_state_c == M_CONT && (s_state_c == S_DONE); assign m_wait2m_rest = m_state_c == M_WAIT && (end_cnt0); assign m_rcmd2m_rtmp = m_state_c == M_RCMD && (s_state_c == S_DONE); assign m_rtmp2m_idle = m_state_c == M_RTMP && (s_state_c == S_DONE);
從狀态機:
assign s_idle2s_low = s_state_c == S_IDLE && (m_state_c == M_ROMS || m_state_c == M_CONT || m_state_c == M_RCMD || m_state_c == M_RTMP); assign s_low2s_send = s_state_c == S_LOW && (m_state_c == M_ROMS || m_state_c == M_CONT || m_state_c == M_RCMD) && end_cnt1; assign s_low2s_samp = s_state_c == S_LOW && (m_state_c == M_RTMP && end_cnt1); assign s_send2s_rele = s_state_c == S_SEND && (end_cnt1); assign s_samp2s_rele = s_state_c == S_SAMP && (end_cnt1); assign s_rele2s_low = s_state_c == S_RELE && (end_cnt1 && end_cnt_bit == 1'b0); assign s_rele2s_done = s_state_c == S_RELE && (end_cnt1 && end_cnt_bit == 1'b1);
3.3 代碼
ds18_driver:
module ds18_driver(
input clk ,
input rst_n ,
input dq_din ,
output reg dq_out ,
output reg dq_out_en ,
output reg temp_sign ,//溫度值符号位 0:正 1:負溫
output reg [23:0] temp_out ,
output reg temp_out_vld
);
//狀态機參數
localparam M_IDLE = 9'b0_0000_0001,//空閑狀态
M_REST = 9'b0_0000_0010,//複位
M_RELE = 9'b0_0000_0100,//釋放總線 -- ds18b20等待
M_RACK = 9'b0_0000_1000,//接收應答 -- 主機接收存在脈沖
M_ROMS = 9'b0_0001_0000,//rom指令 -- 跳過rom指令
M_CONT = 9'b0_0010_0000,//轉化
M_WAIT = 9'b0_0100_0000,//等待 -- 12bit分辨率下的溫度轉化時間
M_RCMD = 9'b0_1000_0000,//讀指令 -- 讀暫存器指令
M_RTMP = 9'b1_0000_0000;//讀溫度 -- 産生讀時隙 -- 接收2位元組帶符号位的補碼溫度值
localparam S_IDLE = 6'b00_0001,
S_LOW = 6'b00_0010,//拉低總線 -- 時隙的開始
S_SEND = 6'b00_0100,//發送 -- 15us内
S_SAMP = 6'b00_1000,//采樣 -- 在15us内
S_RELE = 6'b01_0000,//釋放 -- 時隙的恢複時間
S_DONE = 6'b10_0000;
parameter TIME_1US = 50, //基本時間1us
//主狀态機延時
TIME_RST = 500, //複位脈沖 500us
TIME_REL = 20, //主機釋放總線 20us
TIME_PRE = 200, //主機接收存在脈沖 200us
TIME_WAIT = 750000, //主機發完溫度轉換指令 等待750ms
//從狀态機的延時
TIME_LOW = 2, //主機拉低總線 2us
TIME_RW = 60, //主機讀、寫1bit 60us
TIME_REC = 3; //主機讀寫完1bit釋放總線 3us
localparam CMD_ROMS = 8'hCC,//跳過ROM指令
CMD_CONT = 8'h44,//溫度轉化
CMD_RTMP = 8'hBE;//讀暫存器
//信号定義
reg [8:0] m_state_c ;//主狀态機
reg [8:0] m_state_n ;
reg [5:0] s_state_c ;//從狀态機
reg [5:0] s_state_n ;
reg [5:0] cnt_1us ;//1us計數器
wire add_cnt_1us ;
wire end_cnt_1us ;
reg [19:0] cnt0 ;//複位脈沖、釋放、存在脈沖、等待750ms
wire add_cnt0 ;
wire end_cnt0 ;
reg [19:0] X ;
reg [5:0] cnt1 ;//計數從狀态機每個狀态多少us
wire add_cnt1 ;
wire end_cnt1 ;
reg [5:0] Y ;
reg [4:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg slave_ack ;//接收存在脈沖
reg flag ;//0:發溫度轉換指令 1:發溫度讀取指令
reg [7:0] wr_data ;
reg [15:0] orign_data ;//采樣溫度值寄存器
reg [10:0] temp_data ;
wire [23:0] temp_data_w ;//組合邏輯計算實際溫度值 十進制
wire m_idle2m_rest ;
wire m_rest2m_rele ;
wire m_rele2m_rack ;
wire m_rack2m_roms ;
wire m_roms2m_cont ;
wire m_roms2m_rcmd ;
wire m_cont2m_wait ;
wire m_wait2m_rest ;
wire m_rcmd2m_rtmp ;
wire m_rtmp2m_idle ;
wire s_idle2s_low ;
wire s_low2s_send ;
wire s_low2s_samp ;
wire s_send2s_rele ;
wire s_samp2s_rele ;
wire s_rele2s_low ;
wire s_rele2s_done ;
//狀态機設計
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
m_state_c <= M_IDLE;
end
else begin
m_state_c <= m_state_n;
end
end
always @(*)begin
case(m_state_c)
M_IDLE:begin
if(m_idle2m_rest)
m_state_n = M_REST;
else
m_state_n = m_state_c;
end
M_REST:begin
if(m_rest2m_rele)
m_state_n = M_RELE;
else
m_state_n = m_state_c;
end
M_RELE:begin
if(m_rele2m_rack)
m_state_n = M_RACK;
else
m_state_n = m_state_c;
end
M_RACK:begin
if(m_rack2m_roms)
m_state_n = M_ROMS;
else
m_state_n = m_state_c;
end
M_ROMS:begin
if(m_roms2m_cont)
m_state_n = M_CONT;
else if(m_roms2m_rcmd)
m_state_n = M_RCMD;
else
m_state_n = m_state_c;
end
M_CONT:begin
if(m_cont2m_wait)
m_state_n = M_WAIT;
else
m_state_n = m_state_c;
end
M_WAIT:begin
if(m_wait2m_rest)
m_state_n = M_REST;
else
m_state_n = m_state_c;
end
M_RCMD:begin
if(m_rcmd2m_rtmp)
m_state_n = M_RTMP;
else
m_state_n = m_state_c;
end
M_RTMP:begin
if(m_rtmp2m_idle)
m_state_n = M_IDLE;
else
m_state_n = m_state_c;
end
default:m_state_n = M_IDLE;
endcase
end
assign m_idle2m_rest = m_state_c == M_IDLE && (1'b1); //進入m_idle狀态,下一個時鐘周期上升沿将進入複位狀态
assign m_rest2m_rele = m_state_c == M_REST && (end_cnt0);
assign m_rele2m_rack = m_state_c == M_RELE && (end_cnt0);
assign m_rack2m_roms = m_state_c == M_RACK && (end_cnt0 && slave_ack == 1'b0);
assign m_roms2m_cont = m_state_c == M_ROMS && (s_state_c == S_DONE && flag == 1'b0);
assign m_roms2m_rcmd = m_state_c == M_ROMS && (s_state_c == S_DONE && flag == 1'b1);
assign m_cont2m_wait = m_state_c == M_CONT && (s_state_c == S_DONE);
assign m_wait2m_rest = m_state_c == M_WAIT && (end_cnt0);
assign m_rcmd2m_rtmp = m_state_c == M_RCMD && (s_state_c == S_DONE);
assign m_rtmp2m_idle = m_state_c == M_RTMP && (s_state_c == S_DONE);
//從狀态機
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
s_state_c <= S_IDLE;
end
else begin
s_state_c <= s_state_n;
end
end
always @(*)begin
case(s_state_c)
S_IDLE:begin
if(s_idle2s_low)
s_state_n = S_LOW;
else
s_state_n = s_state_c;
end
S_LOW :begin
if(s_low2s_send)
s_state_n = S_SEND;
else if(s_low2s_samp)
s_state_n = S_SAMP;
else
s_state_n = s_state_c;
end
S_SEND:begin
if(s_send2s_rele)
s_state_n = S_RELE;
else
s_state_n = s_state_c;
end
S_SAMP:begin
if(s_samp2s_rele)
s_state_n = S_RELE;
else
s_state_n = s_state_c;
end
S_RELE:begin
if(s_rele2s_low)
s_state_n = S_LOW;
else if(s_rele2s_done)
s_state_n = S_DONE;
else
s_state_n = s_state_c;
end
S_DONE:begin //進入s_done狀态,下一個時鐘周期上升沿将回到初始狀态
s_state_n = S_IDLE;
end
default:s_state_n = S_IDLE;
endcase
end
assign s_idle2s_low = s_state_c == S_IDLE && (m_state_c == M_ROMS ||
m_state_c == M_CONT || m_state_c == M_RCMD || m_state_c == M_RTMP);
assign s_low2s_send = s_state_c == S_LOW && (m_state_c == M_ROMS ||
m_state_c == M_CONT || m_state_c == M_RCMD) && end_cnt1;
assign s_low2s_samp = s_state_c == S_LOW && (m_state_c == M_RTMP && end_cnt1);
assign s_send2s_rele = s_state_c == S_SEND && (end_cnt1);
assign s_samp2s_rele = s_state_c == S_SAMP && (end_cnt1);
assign s_rele2s_low = s_state_c == S_RELE && (end_cnt1 && end_cnt_bit == 1'b0);
assign s_rele2s_done = s_state_c == S_RELE && (end_cnt1 && end_cnt_bit == 1'b1);
//計數器
always @(posedge clk or negedge rst_n)begin //1us計數
if(!rst_n)begin
cnt_1us <= 0;
end
else if(add_cnt_1us)begin
if(end_cnt_1us)begin
cnt_1us <= 0;
end
else begin
cnt_1us <= cnt_1us + 1;
end
end
end
assign add_cnt_1us = m_state_c != M_IDLE; //非IDLE狀态持續計數
assign end_cnt_1us = add_cnt_1us && cnt_1us == TIME_1US-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)begin
cnt0 <= 0;
end
else begin
cnt0 <= cnt0 + 1;
end
end
end
assign add_cnt0 = (m_state_c == M_REST || m_state_c == M_RELE || m_state_c == M_RACK || m_state_c == M_WAIT) && end_cnt_1us;
assign end_cnt0 = add_cnt0 && cnt0 == X-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
X <= 0;
end
else if(m_state_c == M_REST)begin // 複位:500us (480us)
X <= TIME_RST;
end
else if(m_state_c == M_RELE)begin // 釋放總線:20us (15-60us 内)
X <= TIME_REL;
end
else if(m_state_c == M_RACK)begin // 接收應答:200us (60-240us)
X <= TIME_PRE;
end
else if(m_state_c == M_WAIT)begin // 等待:750ms (等待轉換完成)
X <= TIME_WAIT;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)begin
cnt1 <= 0;
end
else begin
cnt1 <= cnt1 + 1;
end
end
end
assign add_cnt1 = (s_state_c == S_LOW || s_state_c == S_SEND ||
s_state_c == S_SAMP || s_state_c == S_RELE) && end_cnt_1us;
assign end_cnt1 = add_cnt1 && cnt1 == Y-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
Y <= 0;
end
else if(s_state_c == S_LOW)begin
Y <= TIME_LOW; // 主機拉低總線 2us (大于1us)
end
else if(s_state_c == S_SEND || s_state_c == S_SAMP)begin
Y <= TIME_RW; // 主機讀寫1bit 60us ()
end
else begin
Y <= TIME_REC; // 主機讀寫完1bit釋放總線 3us (至少1us)
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = s_state_c == S_RELE && end_cnt1; // 從機處于RELE狀态,時隙恢複時間,表明1 bit 資料傳輸完成
assign end_cnt_bit = add_cnt_bit && cnt_bit == ((m_state_c == M_RTMP)?16-1:8-1);
//slave_ack 采樣傳感器的存在脈沖
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
slave_ack <= 1'b1;
end //接收應答狀态 計數器計到60us 進行采樣
else if(m_state_c == M_RACK && cnt0 == 60 && end_cnt_1us)begin
slave_ack <= dq_din;
end
end
always @(posedge clk or negedge rst_n)begin //指令發送标志 (區分溫度轉換和溫度讀取指令)
if(!rst_n)begin
flag <= 0;
end
else if(m_wait2m_rest)begin
flag <= 1'b1;
end
else if(m_rtmp2m_idle)begin
flag <= 1'b0;
end
end
//輸出信号
//dq_out_en
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dq_out_en <= 0;
end
else if(m_idle2m_rest | s_idle2s_low | s_rele2s_low | m_wait2m_rest)begin
dq_out_en <= 1'b1; //輸出 dq_out
end
else if(m_rest2m_rele | s_send2s_rele | s_low2s_samp)begin
dq_out_en <= 1'b0; //不輸出 dq_out
end
end
//dq_out
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dq_out <= 0;
end
else if(m_idle2m_rest | s_idle2s_low | s_rele2s_low | m_wait2m_rest)begin
dq_out <= 1'b0;
end
else if(s_low2s_send)begin
dq_out <= wr_data[cnt_bit];
end
end
//wr_data 指令
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_data <= 0;
end
else if(m_rack2m_roms)begin
wr_data <= CMD_ROMS;
end
else if(m_roms2m_cont)begin
wr_data <= CMD_CONT;
end
else if(m_roms2m_rcmd)begin
wr_data <= CMD_RTMP;
end
end
//orign_data 溫度采集
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
orign_data <= 0;
end
else if(s_state_c == S_SAMP && cnt1 == 12 && end_cnt_1us)begin //溫度采集,在時隙起始後15us内采樣
orign_data[cnt_bit] <= dq_din;
end
end
//temp_data 溫度判斷
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
temp_data <= 0;
end
else if(s_state_c == S_SAMP && cnt_bit == 15 && s_samp2s_rele)begin
if(orign_data[15]) //判斷正負溫度,決定是否需要做補碼-原碼轉化
temp_data <= ~orign_data[10:0] + 1'b1; //負溫 則取反加1
else
temp_data <= orign_data[10:0]; //正溫
end
end
/*
實際的溫度值為 temp_data * 0.0625;
為了保留4位小數精度,将實際溫度值放大了10000倍,
即 temp_data * 625;
*/
assign temp_data_w = temp_data * 625;
//temp_out
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
temp_out <= 0;
end
else if(m_state_c == M_RTMP && s_rele2s_done)begin
temp_out <= temp_data_w;
end
end
//temp_out_vld 訓示溫度值輸出有效
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
temp_out_vld <= 0;
end
else begin
temp_out_vld <= m_state_c == M_RTMP && s_rele2s_done;
end
end
//temp_sign (輸出至數位管顯示時使用)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
temp_sign <= 0;
end
else if(s_state_c == S_SAMP && cnt_bit == 15 && s_samp2s_rele)begin
temp_sign <= orign_data[15];
end
end
endmodule
control:
module control(
input clk ,
input rst_n ,
input din_sign ,
input [23:0] din ,//輸入24位十進制溫度值(*10000)
input din_vld ,
output wire dout_sign ,
output wire[23:0] dout ,//輸出每位數位管對應顯示的值
output reg dout_vld
);
//中間信号定義
reg [23:0] din_r ;
reg din_vld_r0 ;
reg din_vld_r1 ;
wire [7:0] tmp_int_w ;//整數部分
reg [7:0] tmp_int_r ;
wire [3:0] tmp_int_w2 ;
wire [3:0] tmp_int_w1 ;
wire [3:0] tmp_int_w0 ;
reg [3:0] tmp_int_r2 ;
reg [3:0] tmp_int_r1 ;
reg [3:0] tmp_int_r0 ;
wire [15:0] tmp_dot_w ;//小數部分
reg [15:0] tmp_dot_r ;
wire [3:0] tmp_dot_w3 ;
wire [3:0] tmp_dot_w0 ;
wire [3:0] tmp_dot_w1 ;
wire [3:0] tmp_dot_w2 ;
reg [3:0] tmp_dot_r3 ;
reg [3:0] tmp_dot_r0 ;
reg [3:0] tmp_dot_r1 ;
reg [3:0] tmp_dot_r2 ;
//din_r
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
din_r <= 0;
end
else if(din_vld)begin
din_r <= din;
end
end
//din_vld_r0 din_vld_r1
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
din_vld_r0 <= 0;
din_vld_r1 <= 0;
end
else begin
din_vld_r0 <= din_vld;
din_vld_r1 <= din_vld_r0;
end
end
assign tmp_int_w = din_r/10000;//拆分整數部分及小數部分
assign tmp_dot_w = din_r%10000;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tmp_int_r <= 0;
end
else if(din_vld_r0)begin
tmp_int_r <= tmp_int_w;
tmp_dot_r <= tmp_dot_w;
end
end
assign tmp_int_w2 = tmp_int_r/100;//百位
assign tmp_int_w1 = tmp_int_r/10%10 ;//十位
assign tmp_int_w0 = tmp_int_r%10;//個位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tmp_int_r2 <= 0;
tmp_int_r1 <= 0;
tmp_int_r0 <= 0;
end
else if(din_vld_r1)begin
tmp_int_r2 <= tmp_int_w2;
tmp_int_r1 <= tmp_int_w1;
tmp_int_r0 <= tmp_int_w0;
end
end
assign tmp_dot_w0 = tmp_dot_r/1000;
assign tmp_dot_w1 = tmp_dot_r/100%10;
assign tmp_dot_w2 = tmp_dot_r/10%10;
assign tmp_dot_w3 = tmp_dot_r%10;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tmp_dot_r0 <= 0;
tmp_dot_r1 <= 0;
tmp_dot_r2 <= 0;
tmp_dot_r3 <= 0;
end
else if(din_vld_r1)begin
tmp_dot_r0 <= tmp_dot_w0;
tmp_dot_r1 <= tmp_dot_w1;
tmp_dot_r2 <= tmp_dot_w2;
tmp_dot_r3 <= tmp_dot_w3;
end
end
assign dout = {tmp_int_r1,tmp_int_r0,tmp_dot_r0,tmp_dot_r1,tmp_dot_r2,tmp_dot_r3};
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_vld <= 1'b0;
end
else begin
dout_vld <= din_vld_r1;
end
end
assign dout_sign = din_sign;
endmodule
seg_driver:
module seg_driver(
input clk ,//時鐘信号
input rst_n ,//複位信号
input din_sign,
input [23:0] din ,
input din_vld ,
output reg [5:0] sel ,
output reg [7:0] dig
);
//參數定義
parameter TIME_1MS = 25_000,
ZERO = 7'b100_0000,
ONE = 7'b111_1001,
TWO = 7'b010_0100,
THREE = 7'b011_0000,
FOUR = 7'b001_1001,
FIVE = 7'b001_0010,
SIX = 7'b000_0010,
SEVEN = 7'b111_1000,
EIGHT = 7'b000_0000,
NINE = 7'b001_0000,
P = 7'b000_1111,
N = 7'b011_1111;
//信号定義
reg [19:0] cnt_1ms;//掃描頻率計數器
wire add_cnt_1ms;
wire end_cnt_1ms;
reg [3 :0] disp_num;
reg dot;
//數位管掃描頻率計數
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1ms <= 0;
end
else if(add_cnt_1ms) begin
if(end_cnt_1ms)begin
cnt_1ms <= 0;
end
else begin
cnt_1ms <= cnt_1ms + 1;
end
end
end
assign add_cnt_1ms = 1'b1;
assign end_cnt_1ms = add_cnt_1ms && cnt_1ms == TIME_1MS - 1;
//seg_sel 數位管片選信号
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sel <= 6'b111110;
end
else if(end_cnt_1ms) begin
sel <= {sel[4:0],sel[5]};
end
end
//譯碼
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
disp_num <= 0;
end
else begin
case(sel)
6'b011111:begin disp_num <= din_sign?4'ha:4'hb;dot <= 1; end
6'b101111:begin disp_num <= din[23:20];dot <= 1;end
6'b110111:begin disp_num <= din[19:16];dot <= 0;end
6'b111011:begin disp_num <= din[15:12];dot <= 1;end
6'b111101:begin disp_num <= din[11:8] ;dot <= 1;end
6'b111110:begin disp_num <= din[7 :4] ;dot <= 1;end
default :begin disp_num <= 4'hF ;end
endcase
end
end
//segment 段選譯碼
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dig <= 8'hff;
end
else begin//顯示小數點
case(disp_num)
4'h0:dig <= {dot,ZERO } ;
4'h1:dig <= {dot,ONE } ;
4'h2:dig <= {dot,TWO } ;
4'h3:dig <= {dot,THREE} ;
4'h4:dig <= {dot,FOUR } ;
4'h5:dig <= {dot,FIVE } ;
4'h6:dig <= {dot,SIX } ;
4'h7:dig <= {dot,SEVEN} ;
4'h8:dig <= {dot,EIGHT} ;
4'h9:dig <= {dot,NINE } ;
4'ha:dig <= {dot,N } ;
4'hb:dig <= {dot,P } ;
default:dig <= 8'hff ;
endcase
end
end
endmodule
temp_top
module temp_top (
input clk ,
input rst_n ,
inout dq ,
output [5:0] sel ,
output [7:0] seg
);
//信号定義
wire dq_in ;
wire dq_out ;
wire dq_out_en ;
wire temp_sign ;
wire [23:0] temp_out ;
wire temp_out_vld;
wire dout_sign ;
wire [23:0] dout ;
wire dout_vld ;
assign dq = dq_out_en?dq_out:1'bz;
assign dq_in = dq;
//子產品例化
ds18_driver u_ds18_driver(
.clk (clk ),
.rst_n (rst_n ),
.dq_din (dq_in ),//dq總線DS18B20輸出
.dq_out (dq_out ),//dq總線FPGA輸出,DS18B20輸入
.dq_out_en (dq_out_en ),//dq總線輸出使能控制信号
.temp_sign (temp_sign ),//溫度的正負
.temp_out (temp_out ),//輸出十進制溫度
.temp_out_vld (temp_out_vld ) //溫度采集資料有效
);
control u_control(
.clk (clk ),
.rst_n (rst_n ),
.din_sign (temp_sign ),
.din (temp_out ),
.din_vld (temp_out_vld ),
.dout_sign (dout_sign ),
.dout (dout ),//輸出溫度值數位管對應位置的bcd碼
.dout_vld (dout_vld )
);
seg_driver seg_driver(
.clk (clk ),
.rst_n (rst_n ),
.din_sign (temp_sign ),
.din (dout ),
.din_vld (dout_vld ),
.sel (sel ),
.dig (seg )
);
endmodule //temp_top
四 上闆驗證
五 總結
多調試,多動手,畫好時序圖,畫好邏輯圖,注意狀态轉移條件。