前言

其實是今天請假在家,因為好不容易預約到可以打武漢肺癌的疫苗囉。在家等待的過程有點無聊,所以坐在電腦桌前玩起 ADXL345 的這個三軸加速度計囉!照著之前這篇部落格的經驗,很快的就完成 Flash FORTH 的版本。測試了一下,運作非常良好,所以做個小小的記錄囉。

 

ADXL345 三軸加速度計

這是顆非常不錯由 Analog Device 所開發的低功耗三軸加速度計,其特點如下

  • 體積小巧纖薄,非常適合行動裝備應用
  • 高達 +/-16 g 的加速度高分辨率 (13位) 測量,數字採用 1 word (2 bytes) 帶正負符號的整數
  • 提供 X, Y, Z 三軸加速度 Offset 補償功能,可以直接內部進行數值補償校正
  • 高解析度, 4 mg/LSB ,利用重力能夠量測 0.25度 的傾角變化。可以用在量測運動或衝擊導致的動態加速度,也可用在傾斜檢測應用中量測靜態重力加速度
  • 支援 SPI 3線或4線通訊,或是 I2C通訊
  • 內建活動/非活動檢測,可直接檢測是否加速度計從靜止被移動,無需外部MCU的協助
  • 內建自由落體檢測,可直接檢測是否加速度計從空中摔落,無需外部MCU的協助
  • 內建可定義的單擊/雙擊動作檢測,可監測使用者的動作,無需外部MCU的協助
  • 內建 32級 FIFO 緩衝暫存區,減少外部MCU來不及處理資料時的負擔
  • 睡眠模式,偵測到活動時才開啟系統,實現進一步省電的目的。低功耗模式,透過降低取樣率來進一步省電。

 

我買到的是已經弄好的模組囉,照片

A30B4815-BC5D-4CC6-B5BA-D59BB4AF71D6.jpeg

 

硬體接線

最近對 I2C 學到一個小技巧。大家都知道, I2C 的兩條通訊線 SDA/SCL 一定要上拉電阻來連接的,否則不會動。

但是其實也未必,當網路上只有一台 Master 跟一台 Slave 的時候,是不需要上拉電阻的。只要將雙方的 SDA 對連, SCL對連,這樣就搞定囉。

所以 Ardunio Uno A4 (SDA) 接上 ADXL345 的 SDA, A5 (SCL) 接上 ADXL345 的 SCL, VCC 接上 5V, GND 接地,這樣就搞定啦。

ADXL345 Uno.JPG

 

 

暫存器解說

ADXL345 的規格書在這裡

簡單簡介

暫存器 0x2D - POWER_CTL

7B66CF4F-ED14-4E25-A92B-CF79611D3FA4.jpeg

這個用來控制 ADXL345 的模式選擇

我們的應用這裡很單純的,就是 D3 設成 1 ,讓系統維持在 Measure 模式隨時量測所有的加速度。 所以就是 0x8 的資料。

暫存器 0x31 - DATA_FORMAT

0344F160-76CE-4D96-BAC2-D9D3A7D302B4.jpeg

這個暫存器用來控制加速度計數字輸出的格式,

這裡重要的是

D3: FULL_RES 設成1的話,會用最大解析度 4mg/LSB 來量測,然後 Scale 會手動固定由 D1.D0 的 Range 來決定。

D2: Justify, 設成0的話為 Right-Justify,經過測試,此時正負號會正常在 High Byte 的 MSB。

D1.D0 為手動 Range 選擇,

00 - +/-2g 的 Range   這裡希望最高解析度來偵測最小的震動,所以選用 +/-2g 最小的偵測範圍。

01 - +/-4g 的 Range

10 - +/-8g 的 Range

11 - +/-16g 的 Range

所以總結,這個暫存器需要設成 0x8 的資料。

 

暫存器 0x2E - INT_ENABLE

B4A94A26-9F08-47C5-8237-FD6DCCDA2BDF.jpeg

各種中斷訊號的致能設定,

而這裡沒有要使用任何的硬體中斷訊號來觸發,而是單純使用 I2C 來溝通。所以這個暫存器需要設成 0x0 的資料。

 

暫存器 0x2C - BW_RATE

6230246C-D113-41F5-9AB2-7D0A7ABA9818.jpeg

 

重點是 D3.D2.D1.D0 這四個位元,這四個位元控制了加速度計其值取樣的速度。

在正常模式下,其控制的取樣頻率如下表, D3.D2.D1.D0 = Rate Code

A9E38A90-D14B-4641-9D0C-E4CB39AC85A0.jpeg

這裡因為只是測試,所以先選擇 ADXL345 的預設值 Rate Code = %1010 = 0x0A 為 100Hz ,每秒100次的資料輸出標準設定。

 
98EAD29D-5B98-4594-BA30-0D938A6B694F.jpeg
 

接下來不用說,就是加速度 X, Y, Z 的資料暫存器了

0x32 X-Axis Data 0 (Low Byte)

0x33 X-Axis Data 1 (High Byte)

0x34 Y-Axis Data 0 (Low Byte)

0x35 Y-Axis Data 1 (High Byte)

0x36 Z-Axis Data 0 (Low Byte)

0x37 Z-Axis Data 1 (High Byte)

因為這幾個暫存器的位址是連續的,所以這裡為了減少資料傳輸的負擔,在 I2C 的傳輸時採用一次傳輸多 bytes 的 I2C協定。

所以傳送從 0x32 開始,Master 要求 Slave 依序傳送 6 bytes 過來,所有的資料就進來囉。

 

最後是

0x1E OFSX    X-axis Offset

0x1F OFSY    Y-axis Offset

0x20 OFSZ    Z-axis Offset

這三個是加速度計 Offset 補償值的暫存器。是個 8位元有正負號的數值,可正負補償 +/-127 個單位,單位視 Range 而決定。

當是 +/-2g的 Range 時,其單位為 15.6 mg/LSB   (0x7F = +2g)

 

 

FORTH 程式碼解說

程式的前半段是 Arduino 的 I2C 控制程式碼,關於 I2C 的程式碼的解說,請參照這篇我的部落格文章,這裡就不贅述囉。

FORTH 的特色,寫完的程式碼可以變成自己的函式庫,這樣再開發新的東西的時候就不用重新來過。這段程式碼已經驗證跟實戰很多次了,在工作上,利用 FORTH 交談性的開發過程。例如,我只要用 scanner 這個字,就可以立刻掃描所有的 I2C裝置,馬上可以驗證 I2C Slave 的程式碼有無錯誤。實戰驗證過,用來 debug 真的非常方便。

這裡,我把原來的 I2C 程式碼繼續延伸如下。在實際的經驗中,大部分 I2C Slave 裝置的溝通方式離不開對它們自己的暫存器做讀或寫的動作。訊息格式大概都是這樣的

 

1. 給定暫存器位址,讀取單一內容,訊息格式

<Start| <slaveID> |Addr-WR| <register-addr> |Tx|  |Restart| <slaveID> |Addr-RD| |Rx-NACK| |Stop>

讓我們把它廣義化成程式碼 regRead ( reg-addr -- dada),這樣以後就可以直接使用囉。

先定義一個變數存 slaveid,所以要使用前記得要初始化這個變數

variable slaveid         \ i2c address

剩下其實看圖說故事,照 I2C 的訊息格式寫程式碼,這樣就完成囉。 |Addr-WR| 因為是對 Slave 裝置做點名的動作,可能這個裝置是不存在的。所以後面用 ?Response 做個回應檢查,沒有回應的話它會執行 abort,跳出程式執行。

: regRead ( reg -- data)
    <Start|
   slaveid @ |Addr-WR|  ?Response
             |Tx|       ( reg)
             |Restart| 
   slaveid @ |Addr-RD| 
             |Rx-NACK| 
    |Stop>
;

 

2. 給定暫存器位址,寫入單一內容,訊息格式

<Start| <slaveID> |Addr-WR| <register-addr> |Tx| <data> |Tx| |Stop>

一樣,看圖說故事,程式碼如下。其實就是在 I2C上連送兩筆資料,第一筆是暫存器位址,第二筆是要給這個暫存器的資料。

: regWrite ( data reg --)
    <Start|   
    slaveid @ |Addr-WR|  ?Response
              |Tx|       ( reg)
              |Tx|       ( data)
    |Stop>
;

 

3. 給定起始暫存器位址,依序讀取接續每一筆暫存器資料。訊息格式如下,

<Start| <slaveID> |Addr-WR| <register-addr> |Tx|  |Restart| <slaveID> |Addr-RD| |Rx-ACK| |Rx-ACK| ... |Rx-ACK| |Rx-NACK| |Stop>

其實就是第一種例子,只是多了個 for-loop 用 |Rx-Ack| 來連續讀取資料,依序放到堆疊上

: regsRead ( reg n -- d1 d2 ... dn)
   <Start|
  slaveid @ |Addr-WR| ?Response
      swap  |Tx|             ( reg)
            |Restart|  
  slaveid @ |Addr-RD|
   1-
   for 
     |Rx-ACK|
   next
   |Rx-NACK| 
   |Stop>
;     

 

4. 給定起始暫存器位址,依序接續每一筆暫存器,寫入特定資料。訊息格式如下,

<Start| <slaveID> |Addr-WR| <register-addr> |Tx| <data1> |Tx| ... <dataN> |Tx| |Stop>

其實就是第二種例子,只是多了個 for-loop 來連續將堆疊上的資料,依序傳出給 slave 寫入暫存器中

: regsRead ( reg n -- d1 d2 ... dn)
   <Start|
  slaveid @ |Addr-WR| ?Response
      swap  |Tx|             ( reg)
            |Restart|  
  slaveid @ |Addr-RD|
   1-
   for 
     |Rx-ACK|
   next
   |Rx-NACK| 
   |Stop>
;     

 

我們的 ADXL345 是完全符合上面的四種情況的,所以直接可以拿 regRead, regsRead, regWrite, regsWrite 來使用。

 

剩下的,沒有難度

起始一下一些變數

: adxl345   
   $53 slaveid !    \ I2C slave address
   45 scaleX !      \ factor for mg/LSB ~ (3.5, 4.3)
   45 scaleY !      \ factor for mg/LSB ~ (3.5, 4.3)
   45 scaleZ !      \ factor for mg/LSB ~ (3.5, 4.3)
;

slaveid 透過 scanner 掃描後是 0x53 ,存入 slaveid 提供 i2c 使用

scaleX, scaleY, scaleZ 是三軸加速度計的 ADC 單位,每個 LSB 約是 3.9 mg 的解析度 (在 2g 的度規下),規格書是說保證介於 3.5 跟 4.3 之間。

實測,在沒校正 offset 的情況下,以 Z軸的重力加速度 1g 的情況下來計算,我手上的這顆約在 4.5 mg/LSB 左右,所以先採用 45來測試。ADXL345 傳來的是 ADC 單位,乘上這個比例常數後才會是 mg 的重力加速度單位。

 

起始一下 ADXL345

: init345
   adxl345   i2cInit
   $8 $2d ( POWER_CTL)   regWrite  \ measure mode
   $8 $31 ( DATA_FORMAT) regWrite  \ full resolution, right-justify, 2g
   $0 $2e ( INT_ENABLE)  regWrite  \ interrupt disable
   $a $2c ( BW_RATE)     regWrite  \ normal power 100Hz Sampling
   $0 $1e ( OFSX)        regWrite  \ offset for X
   $0 $1f ( OFSY)        regWrite  \ offset for Y
   $0 $20 ( OFSZ)        regWrite  \ offset for Z
;

設好 i2c slaveid,起始一下i2c的硬體。透過I2C傳輸設定一下 ADXL345的 量測模式,資料格式為全解析度,右齊,2g 的度規。關掉中斷模式,正常每秒100次的取樣模式。 X, Y, Z 加速度計的校正補償設為零。

 

跟 ADXL345 要資料囉

: accRead ( -- X Y Z)
   $32 6 regsRead
   8 lshift or  scaleZ @ *  >r
   8 lshift or  scaleY @ *  >r
   8 lshift or  scaleX @ *  r> r>
;

0x32 是加速度X Low Byte 的位址,後面六個 byte依序為 X-Low, X-High, Y-Low, Y-High, Z-Low, Z-High。所以透過 regsRead 一次搞定把全部6個三軸加速度計的所有值的弄到手。

High-byte 在後面,所以 lshift 左移8位元後 or 跟 Low-byte 合併,就是完整的加速度資料了。

因為是 ADC 的數值,所以需要乘上 scaleX, scaleY, scaleZ 後才是重力加速度 g 的單位。就這樣, X, Y, Z 的加速度 get!

 

再來是列印囉,

測試後 Z 軸的加速度是 226,乘上 scaleZ = 45, 216*45 = 10170 ,這個數值等於 1.0170 g 的重力加速度,所以要列印四位小數且需要正負號。來看看怎麼印啦,

: .acc ( acc ---)
   dup s>d dabs <# # # # # [char] . hold #s rot sign #> type
;

先數字 dup 複製一下留個底,因為後面要利用它來看正負號。

然後 s>d 帶符號的單整數轉成雙整數,因為 <# # ... #s #> 這些都是為雙整數數字來設計的。

然後,很重要的因為 <# # #s #> 是針對不帶符號的雙整數來運作的,當數字有帶符號的話,當其是負數的時候會算錯。所以一定要 dabs 取個絕對值後才可以繼續轉換。

四位小數,就是 <# # # # # [char] . hold #s #> ,但是且慢,正負號還沒加上去呀! rot 可以把我們剛剛刻意留下來有符號的單整數翻進來, sign 則會根據堆疊上的單整數的符號,假如是負的,補上 "-"字串。

所以就這樣,搞定四位小數,有帶正負號數字的列印。

 

最後就是主程式,串連全部

: main
   init345
   begin
     accRead
     rot  ." X=" .acc  ." , "
     swap ." Y=" .acc  ." , "
          ." Z=" .acc  cr
   again
;

起始 345 跟 i2c 後, 無窮迴圈不斷透過 i2c 讀取 X, Y, Z 的加速度後列印。

 

實際測試影片

運作非常完美,預計程式再改一下,可以利用重力來做水平儀的應用,或是地震儀應該也不錯。

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Frank Lin(@ohiyooo)分享的貼文

 

 

原始程式列表

 

\  ADXL 345 Test
\   2021.7.19  Frank Lin
\


marker --i2c--

decimal

\
\  ARduino I2C 
\


\ Registers

$b8 constant TWBR
$b9 constant TWSR
$bb constant TWDR
$bc constant TWCR


\ Bits in the Control Register 

%10000000 constant mTWINT   \ Interrupt Flag
%01000000 constant mTWEA    \ Enable Acknowledge
%00100000 constant mTWSTA   \ Start Condition
%00010000 constant mTWSTO   \ Stop Condition
%00001000 constant mTWWC    \ Write Collition
%00000100 constant mTWEN    \ Enable
%00000001 constant mTWIE    \ Interrupt Enable

variable ACK?


: i2cInit ( -- )     \ Set frequency to 100kHz 
  %11 TWSR mclr      \ prescale = 1
  [ Fcy #100 / #16 - 2/ ] literal TWBR c!
  mTWEN TWCR mset
;

: DoneWait ( -- )  \ Wait for operation to complete 
  begin TWCR c@ mTWINT and until
;

: <Start| ( -- )   \ Send start condition
  [ mTWINT mTWEN or mTWSTA or ] literal  TWCR c!
  DoneWait
;


: |Restart| ( -- ) \ Send repeated start 
  <Start|     
;


: |Stop> ( -- )   \ Send stop condition
  [ mTWINT mTWEN or mTWSTO or ] literal  TWCR c!
;

 

: |Tx|  ( c--)   \ send 1 byte to bus
  DoneWait
  
  TWDR c!
  [ mTWINT mTWEN or ] literal  TWCR c!
  DoneWait
  
  TWSR c@ $f8 and $18 xor 0=   \ SLA + W
  if  true ACK? !   exit then  \ true if ACK
  
  TWSR c@ $f8 and $28 xor 0=   \ data byte
  if  true ACK? !   exit then  \ true if ACK
  
  TWSR c@ $f8 and $40 xor 0=   \ SLA + R
  if  true ACK? !   exit then  \ true if ACK
  
  false ACK? !    \ false if NACK
;  


: |Rx-ACK| ( -- c)
  [ mTWINT mTWEN or mTWEA or ] literal TWCR c! 
  DoneWait  TWDR c@
;  


: |Rx-NACK| ( -- c)
  [ mTWINT mTWEN or ] literal TWCR c! 
  DoneWait  TWDR c@
;


: |Addr-WR| ( 7-bit-addr --)   \ ask slave for writing
  1 lshift 1 invert and
  |Tx|
;


: |Addr-RD| ( 7-bit-addr --)   \ ask slve for reading
  1 lshift 1 or 
  |Tx|
;


: ?Response  ( --)  \ ACK check
   ACK? @  abort" I2C: No response from Slave!"  
;


: i2cPing? ( 7-bit-addr -- f )  \ ping i2c slave
   <Start| |Addr-RD| 
   ACK? @ 
   if |Rx-NACK| drop true  
   else              false   then
;


: scanner ( --)  \ scan all i2c slave
  base @ hex i2cInit
  cr #5 spaces $10 for r@ 2 u.r next
  $80 
  for r@ $0f and $f =
      if cr r@ $f0 and #2 u.r [char] : emit space
      then
      r@ $7 $78 within
      if  r@ i2cPing?
          if    r@ #2 u.r
          else ." -- "   then
      else  #3 spaces  then
  next
  cr base !
;
  

\
\ I2C extension
\

variable slaveid         \ i2c address


: regWrite ( data reg --)
    <Start|   
    slaveid @ |Addr-WR|  ?Response
              |Tx|       ( reg)
              |Tx|       ( data)
    |Stop>
;


: regRead ( reg -- data)
    <Start|
   slaveid @ |Addr-WR|  ?Response
             |Tx|       ( reg)
             |Restart| 
   slaveid @ |Addr-RD| 
             |Rx-NACK| 
    |Stop>
;

 

: regsWrite ( d1 d2 d3 ... dn reg n --)
   <Start|
   slaveid @ |Addr-WR| ?Response 
       swap  |Tx|  ( reg)
       for
             |Tx|  ( data)
       next
     |Stop>
;


: regsRead ( reg n -- d1 d2 ... dn)
   <Start|
  slaveid @ |Addr-WR| ?Response
      swap  |Tx|             ( reg)
            |Restart|  
  slaveid @ |Addr-RD|
   1-
   for 
     |Rx-ACK|
   next
   |Rx-NACK| 
   |Stop>
;     

 

marker --adxl345--


\
\  ADXL345 control
\


variable scaleX
variable scaleY
variable scaleZ


: adxl345   
   $53 slaveid !    \ I2C slave address
   45 scaleX !      \ factor for mg/LSB ~ (3.5, 4.3)
   45 scaleY !      \ factor for mg/LSB ~ (3.5, 4.3)
   45 scaleZ !      \ factor for mg/LSB ~ (3.5, 4.3)
;


: init345
   adxl345   i2cInit
   $8 $2d ( POWER_CTL)   regWrite  \ measure mode
   $8 $31 ( DATA_FORMAT) regWrite  \ full resolution, right-justify, 2g
   $0 $2e ( INT_ENABLE)  regWrite  \ interrupt disable
   $a $2c ( BW_RATE)     regWrite  \ normal power 100Hz Sampling
   $0 $1e ( OFSX)        regWrite  \ offset for X
   $0 $1f ( OFSY)        regWrite  \ offset for Y
   $0 $20 ( OFSZ)        regWrite  \ offset for Z
;

: accRead ( -- X Y Z)
   $32 6 regsRead
   8 lshift or  scaleZ @ *  >r
   8 lshift or  scaleY @ *  >r
   8 lshift or  scaleX @ *  r> r>
;


: .acc ( acc ---)
   dup s>d dabs <# # # # # [char] . hold #s rot sign #> type
;

: main
   init345
   begin
     accRead
     rot  ." X=" .acc  ." , "
     swap ." Y=" .acc  ." , "
          ." Z=" .acc  cr
   again
;
   

arrow
arrow

    ohiyooo2 發表在 痞客邦 留言(1) 人氣()