PCA9685是一個 I²C 協定控制的16通道PWM輸出IC,這顆IC原本被設計應用在RGBA彩色背光等LED亮度控制上,因其頻率的可編程範圍為24~1526Hz,含蓋了伺服馬達控制的50Hz,聰明的創客們便用它來擴充伺服的控制數量。

這篇文章主要以"VHDL語言撰寫控制PCA9685的程式碼"介紹為主,首先將全部程式碼顯示於下,接著再來看各程式碼的作用

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity PCA9685 is
port( CLK       : in std_logic;
      Start     : in std_logic;
      Addr      : in std_logic_vector(5 downto 0);
      RegAddr   : in std_logic_vector(7 downto 0);
      RegData   : in std_logic_vector(7 downto 0);
      SDA       : inout std_logic;
      SCL       : out std_logic;
      NoACK     : buffer std_logic;
      Complete  : buffer std_logic);
end PCA9685;

architecture ARCH of PCA9685 is
  constant indexStartCondition : integer := 0;
  constant indexActive : integer := 10;
  constant indexStopCondition : integer := 20;
    
  signal index : integer;
  signal delayCnt : integer;
  signal bitN : integer;
  signal exIndex : integer;
  signal exAddr : std_logic_vector(7 downto 0);
    
begin
  process(Start, CLK, index)
  begin
    if Start = '0' then
      SDA <= '1';
      SCL <= '1';
      NoACK <= '0';
      Complete <= '0';
      
      index <= 0;
      delayCnt <= 0;
      bitN <= 7;
      exIndex <= 0;
      exAddr <= '1' & Addr & '0';
      
    elsif CLK'event and CLK = '1' then
      if Complete = '0' then
        case index is
          when (indexStartCondition + 0) =>
            if delayCnt >= 29 then
              delayCnt <= 0;
              index <= index + 1;
            else
              delayCnt <= delayCnt + 1;
            end if;
          when (indexStartCondition + 1) =>
            SDA <= '0';
            index <= index + 1;
          when (indexStartCondition + 2) =>
            if delayCnt >= 14 then
              delayCnt <= 0;
              index <= index + 1;
            else
              delayCnt <= delayCnt + 1;
            end if;
          when (indexStartCondition + 3) =>
            index <= indexActive;
          --==============================================================================
          
          when (indexActive + 0) =>
            SCL <= '0';
            index <= index + 1;
          when (indexActive + 1) =>
            if bitN < 0 then
              SDA <= 'Z';
            elsif exIndex = 0 then
              SDA <= exAddr(bitN);
            elsif exIndex = 1 then
              SDA <= RegAddr(bitN);
            else
              SDA <= RegData(bitN);
            end if;
            index <= index + 1;
          when (indexActive + 2) =>
            if delayCnt >= 29 then
              delayCnt <= 0;
              index <= index + 1;
            else
              delayCnt <= delayCnt + 1;
            end if;
          when (indexActive + 3) =>
            SCL <= '1';
            index <= index + 1;
            if bitN < 0 then
              NoACK <= SDA;
            end if;
          when (indexActive + 4) =>
            if delayCnt >= 29 then
              delayCnt <= 0;
              index <= index + 1;
            else
              delayCnt <= delayCnt + 1;
            end if;
          when (indexActive + 5) =>
            if bitN = -1 then
              bitN <= 7;
              index <= index + 1;
            else
              bitN <= bitN - 1;
              index <= (indexActive + 0);
            end if;
          when (indexActive + 6) =>
            if exIndex = 2 then
              exIndex <= 0;
              index <= indexStopCondition;
            else
              exIndex <= exIndex + 1;
              index <= (indexActive + 0);
            end if;
          --==============================================================================
          
          when (indexStopCondition + 0) =>
            SCL <= '0';
            SDA <= '0';
            index <= index + 1;
          when (indexStopCondition + 1) =>
            if delayCnt >= 29 then
              delayCnt <= 0;
              index <= index + 1;
            else
              delayCnt <= delayCnt + 1;
            end if;
          when (indexStopCondition + 2) =>
            SCL <= '1';
            index <= index + 1;
          when (indexStopCondition + 3) =>
            if delayCnt >= 14 then
              delayCnt <= 0;
              index <= index + 1;
            else
              delayCnt <= delayCnt + 1;
            end if;
          when (indexStopCondition + 4) =>
            SDA <= '1';
            Complete <= '1';
          --==============================================================================
          
          when others => null;
        end case;
      end if;
    end if;
  end process;
end ARCH;

 

接下來分別看程式碼中各部份的作用

 

2021E1.png

這邊是定意一些輸入及輸出腳位,其中只有SDA及SCL會與 PCA9685 連接,其它的腳位則在FPGA內部與別的模組連接(當然亦可連接到外部腳位,但可能要改成串列傳輸會比較省腳位及配線)

 

2021E2.png

宣告一些定值及變數

 

2021E3.png

當未動作時,給予控制腳位及變數預設值

 

2021E4.png

在每次傳輸前,要有一個起手式,就是讓SCL保持在高電位,再讓SDA由高電位轉為低電位

 

2021E5_.png

開始傳輸資料啦~ 這邊有一個重點是每傳輸8個bit後,Slave會有一個ACK,我們必須讓SCL產生第9個脈衝來承接這個ACK,若沒有ACK代表沒有將資料正確傳送到Slave

 

2021E6.png

要收尾了,這個動作在告知Slave傳輸結束。做法是讓SCL保持在高電位,再讓SDA由低電位轉為高電位

 

以上是最陽春的作法,每次僅改變一個暫存器的內容。PCA9685有一個 Auto-Increment 的功能,可以一連串的寫入暫存器,效率會大幅提升,有興趣可以自行參閱手冊

關於I²C通訊協定的詳細規則,請參閱 UM10204 手冊

 

下一篇再來說明如何應用

使用VHDL語言實現 I²C協定,用來控制 PCA9685 (PCA9685應用篇)

arrow
arrow
    創作者介紹
    創作者 肯特王 的頭像
    肯特王

    肯特王的簿

    肯特王 發表在 痞客邦 留言(0) 人氣()