1.前言
onewire(單總線) 是DALLAS公司推出的外圍串行擴(kuò)展總線技術(shù)總線,顧名思義,它是采用一根信號線進(jìn)行通信,既傳輸時(shí)鐘信號又傳輸數(shù)據(jù),而且能夠進(jìn)行雙向通信,具有節(jié)省I/O口線、資源結(jié)構(gòu)簡單、成本低廉、便于總線擴(kuò)展和維護(hù)等諸多優(yōu)點(diǎn)。
常用到單總線的器件,一般是溫度傳感器、EEPROM、唯一序列號芯片等,如DS18B20、DS2431。
在使用單總線時(shí),往往很少CPU會提供硬件單總線,幾乎都是根據(jù)單總線標(biāo)準(zhǔn)的時(shí)序圖,通過普通IO翻轉(zhuǎn)模擬實(shí)現(xiàn)單總線。而在模式實(shí)現(xiàn)時(shí)序圖的過程中,需要根據(jù)CPU時(shí)鐘頻率等條件進(jìn)行時(shí)序時(shí)間計(jì)算,如果更換CPU后,需要重新計(jì)算時(shí)序時(shí)間,如果時(shí)序代碼和器件外設(shè)控制代碼集成在一起,則代碼改動比較大。
或者同一CPU需要模擬多根單總線時(shí),傳統(tǒng)的“復(fù)制”方式使得程序顯得累贅,還增加ROM占用空間。因此,可以利用“函數(shù)指針”的方式,將時(shí)序部分抽象出來,達(dá)到“復(fù)用”代碼的效果,減少重復(fù)代碼編寫。
2.onewire 抽象
2.1 onewire 結(jié)構(gòu)體
onewire結(jié)構(gòu)體主要是對與CPU底層相關(guān)的操作抽象分離,調(diào)用時(shí)只需將該結(jié)構(gòu)體地址(指針)作為函數(shù)入口參數(shù),通過該指針實(shí)現(xiàn)對底層函數(shù)的回調(diào)。該結(jié)構(gòu)體我們命名為“struct ops_onewire_dev”,其原型如下:
struct ops_onewire_dev { void (*set_sdo)(int8_t state); uint8_t (*get_sdo)(void); void (*delayus)(uint32_t us); };其中: 1)set_sdo:IO輸出1bit,包括時(shí)鐘和數(shù)據(jù)。 2)get_sdo:IO輸入1bit,包括時(shí)鐘和數(shù)據(jù)。 3)delayus:時(shí)序延時(shí)函數(shù),根據(jù)CPU頻率進(jìn)行計(jì)算。 回調(diào)函數(shù)相關(guān)文章:C語言技巧之回調(diào)函數(shù)
2.2 onewire 對外接口
extern uint8_t ops_onewire_reset(struct ops_onewire_dev *onewire); extern int ops_onewire_read(struct ops_onewire_dev *onewire,void *buff,int size); externintops_onewire_write(structops_onewire_dev*onewire,void*buff,intsize);1)分別為復(fù)位函數(shù)、讀函數(shù)、寫函數(shù)。
2)入口首參數(shù)為“struct ops_onewire_dev”結(jié)構(gòu)體指針,此部分就是硬件層相關(guān),需要后期初始化的.
3)其余入口參數(shù)易于理解,讀/寫緩存及數(shù)據(jù)大小。
2.3 onewire 抽象接口實(shí)現(xiàn)
分別實(shí)現(xiàn)上述三者函數(shù)接口。
2.3.1 復(fù)位函數(shù)
復(fù)位函數(shù),在單總線初始化外設(shè)器件時(shí)需要用到,用于判斷總線與器件是否通信上,類似“握手”的動作。如圖,為DS18B20的復(fù)位時(shí)序圖,以下與單總線相關(guān)的時(shí)序圖,都是以DS18B20為例,因?yàn)榇诵酒瑸閱慰偩€應(yīng)用的經(jīng)典。 根據(jù)時(shí)序圖,實(shí)現(xiàn)復(fù)位函數(shù)。
/** * @brief 單總線復(fù)位時(shí)序 * @param onewire 總線結(jié)構(gòu)體指針 * @retval 成功返回0 */ uint8_t ops_onewire_reset(struct ops_onewire_dev *onewire) { uint8_t ret = 0; onewire->set_sdo(1); onewire->delayus(50); onewire->set_sdo(0); onewire->delayus(500); onewire->set_sdo(1); onewire->delayus(40); ret = onewire->get_sdo(); onewire->delayus(500); onewire->set_sdo(1); return ret; }
2.3.2 讀函數(shù)
讀函數(shù)即以該函數(shù),通過單總線從外設(shè)上讀取數(shù)據(jù),至于代碼的實(shí)現(xiàn),完全是時(shí)序圖的實(shí)現(xiàn),無特殊難點(diǎn)。先實(shí)現(xiàn)單字節(jié)讀函數(shù),再通過調(diào)用單字節(jié)讀函數(shù)實(shí)現(xiàn)多字節(jié)讀函數(shù)。
/** * @brief 單總線讀取一字節(jié)數(shù)據(jù) * @param onewire 總線結(jié)構(gòu)體指針 * @retval 返回讀取的數(shù)據(jù) */ static char ops_onewire_read_byte(struct ops_onewire_dev *onewire) { char data = 0; uint8_t i; for(i=8;i>0;i--) { data >>= 1; onewire->set_sdo(0); onewire->delayus(5); onewire->set_sdo(1); onewire->delayus(5); if(onewire->get_sdo()) data |= 0x80; else data &= 0x7f; onewire->delayus(65); onewire->set_sdo(1); } return data; } /** * @brief 讀取多字節(jié) * @param onewire 總線結(jié)構(gòu)體指針 * @param buff 存放數(shù)據(jù)緩存 * @param size 數(shù)據(jù)大小 * @retval 返回讀取到的數(shù)據(jù)大小 */ int ops_onewire_read(struct ops_onewire_dev *onewire,void *buff,int size) { int i; char *p = (char*)buff; for(i=0;i
2.3.3 寫函數(shù)
寫函數(shù)與讀函數(shù)同理,即以該函數(shù),通過單總線往外設(shè)寫入數(shù)據(jù),至于代碼的實(shí)現(xiàn),完全是時(shí)序圖的實(shí)現(xiàn),無特殊難點(diǎn)。先實(shí)現(xiàn)單字節(jié)寫函數(shù),再通過調(diào)用單字節(jié)寫函數(shù)實(shí)現(xiàn)多字節(jié)寫函數(shù)。
/** * @brief 單總線寫一字節(jié) * @param onewire 總線結(jié)構(gòu)體指針 * @param data 待寫數(shù)據(jù) * @retval 返回讀取的數(shù)據(jù) */ static int ops_onewire_write_byte(struct ops_onewire_dev *onewire,char data) { uint8_t i; for(i=8;i>0;i--) { onewire->set_sdo(0); onewire->delayus(5); if(data&0x01) onewire->set_sdo(1); else onewire->set_sdo(0); onewire->delayus(65); onewire->set_sdo(1); onewire->delayus(2); data >>= 1; } return 0; } /** * @brief 寫多字節(jié) * @param onewire 總線結(jié)構(gòu)體指針 * @param buff 代寫數(shù)據(jù)地址 * @param size 數(shù)據(jù)大小 * @retval 寫入數(shù)據(jù)大小 */ int ops_onewire_write(struct ops_onewire_dev *onewire,void *buff,int size) { int i; char *p = (char*)buff; for(i=0;i至此,onewire(單總線)抽象化完成,此部分代碼與硬件層分離,亦可單獨(dú)作為一個(gè)模塊,移植到不同平臺CPU時(shí),也幾乎無需改動。剩下部分工作則是實(shí)現(xiàn)“struct ops_onewire_dev”中的函數(shù)指針原型,即可使用一根單總線。
3.onewire 抽象應(yīng)用
以STM32F1為例,實(shí)現(xiàn)上述抽象接口。
3.1 “struct ops_onewire_dev” 實(shí)現(xiàn)
此部分即是與硬件相關(guān)部分,不同CPU平臺改動該部分即可,如從51單片機(jī)移植到STM32上。下面涉及到的IO宏,是對應(yīng)IO的宏定義,如“ONEWIRE1_PORT”、“ONEWIRE1_PIN”,實(shí)際使用的是PC13 IO口。
3.1.1 IO輸出
static void gpio_set_sdo(int8_t state) { if (state) GPIO_SetBits(ONEWIRE1_PORT,ONEWIRE1_PIN); else GPIO_ResetBits(ONEWIRE1_PORT,ONEWIRE1_PIN); }
3.1.2 IO輸入
static uint8_t gpio_get_sdo(void) { return (GPIO_ReadInputDataBit(ONEWIRE1_PORT,ONEWIRE1_PIN)); }
3.1.3 延時(shí)函數(shù)
static void gpio_delayus(uint32_t us) { #if 1 /* 不用系統(tǒng)延時(shí)時(shí),開啟 */ volatile int32_t i; for (; us > 0; us--) { i = 30; //mini 17 while(i--); } #else delayus(us); #endif }
3.2onewire 總線初始化
3.2.1 onewire 抽象相關(guān)
第一步:定義一個(gè)“struct ops_onewire_dev”結(jié)構(gòu)體類型變量(全局)——onewire1_dev。
struct ops_onewire_dev onewire1_dev;第二步:實(shí)例化“onewire1_dev”中的函數(shù)指針。onewire1_dev.get_sdo=gpio_get_sdo; onewire1_dev.set_sdo=gpio_set_sdo; onewire1_dev.delayus=gpio_delayus; 第三步:使用時(shí),通過傳入“onewire1_dev”地址(指針)即可。
3.2.2 onewire 基礎(chǔ)相關(guān)
初始基礎(chǔ)部分,與使用的CPU硬件相關(guān),如時(shí)鐘、IO方向等。
/** * @brief 初始化單總線 * @param none * @retval none */ void stm32f1xx_onewire1_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(ONEWIRE1_RCC,ENABLE); GPIO_InitStructure.GPIO_Pin = ONEWIRE1_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(ONEWIRE1_PORT, &GPIO_InitStructure); ONEWIRE1_PORT->BSRR = ONEWIRE1_PIN; /* device init */ onewire1_dev.get_sdo = gpio_get_sdo; onewire1_dev.set_sdo = gpio_set_sdo; onewire1_dev.delayus = gpio_delayus; }
4.onewire 使用
經(jīng)過前面的步驟后,我們已經(jīng)通過IO口翻轉(zhuǎn),模擬實(shí)現(xiàn)了一根單總線——“onewire1_dev”,以DS18B20為例,調(diào)用第一部分中三者接口,實(shí)現(xiàn)對DS18B20的操作。
4.1 DS18B20操作
對于DS18B20,不陌生,即是溫度傳感器,不多贅述,使用的功能主要是作為溫度檢測,另外還有其內(nèi)部的唯一序列號會作為同一總線上掛多個(gè)DS18B20時(shí)的“地址”識別。 亦可把DS18B20的唯一序列號作為模塊、產(chǎn)品、通信總線等的唯一標(biāo)識使用。因此,代碼也是主要實(shí)現(xiàn)這兩個(gè)功能。
#include "onewire_hw.h" #include "ds18b20.h" static uint8_t ds18b20_start(void) { char reg; ops_onewire_reset(&onewire1_dev); reg = 0xcc; /* 跳過ROM */ ops_onewire_write(&onewire1_dev,®,1); reg = 0x44; /* 溫度轉(zhuǎn)換指令 */ ops_onewire_write(&onewire1_dev,®,1); return 0; } /** * @brief 讀取溫度 * @param none * @retval 溫度值,浮點(diǎn)型 */ float ds18b20_readtemp(void) { uint8_t tl,th,sign; uint16_t reg_temp; char reg; float temp; ds18b20_start(); ops_onewire_reset(&onewire1_dev); reg = 0xcc; ops_onewire_write(&onewire1_dev,®,1); /* 跳過ROM */ reg = 0xbe; ops_onewire_write(&onewire1_dev,®,1); /* 讀取RAM */ ops_onewire_read(&onewire1_dev,&tl,1); /* 低8位數(shù)據(jù) */ ops_onewire_read(&onewire1_dev,&th,1); /* 高8位數(shù)據(jù) */ if(th > 7) {/* - */ th = ~th; tl = ~tl + 1; sign = 0; } else {/* + */ sign = 1; } reg_temp = (th<<8) | tl; temp = reg_temp * 0.0625f; if(sign) { return temp; } else { return -temp; } } /** * @brief 讀唯一序列號 * @param rom 返回序列號緩存 * @retval none */ void ds18b20_readrom(char *rom) { uint8_t i; char reg; ops_onewire_reset(&onewire1_dev); reg = 0x33; ops_onewire_write(&onewire1_dev,®,1); for (i = 0;i < 8;i++) { ops_onewire_read(&onewire1_dev,&rom[i],1); } }至此,完成單總線的抽象分層使用。
審核編輯:劉清
-
溫度傳感器
+關(guān)注
關(guān)注
48文章
2940瀏覽量
156078 -
STM32
+關(guān)注
關(guān)注
2270文章
10900瀏覽量
356046 -
EEPROM
+關(guān)注
關(guān)注
9文章
1020瀏覽量
81608 -
DS2431
+關(guān)注
關(guān)注
0文章
3瀏覽量
8406 -
OneWire
+關(guān)注
關(guān)注
0文章
2瀏覽量
846
原文標(biāo)題:STM32基于onewire單總線的數(shù)據(jù)抽象實(shí)例
文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論