การใช้งานไอซีตัวต้านทานปรับค่าได้แบบดิจิทัลสำหรับ FPGA#

Keywords: Digital Logic Design, VHDL, Intel / Altera FPGA, Quartus Prime, 8-bit Digital Potentiometer, MCP41010, SPI Bus

ความรู้และทักษะพื้นฐานที่เกี่ยวข้อง

  • อิเล็กทรอนิกส์ (Electronics): การสร้างวงจรแบ่งแรงดัน (Voltage Divider) ด้วยไอซีตัวต้านทานปรับค่าได้ (Trimpot) การแปลงสัญญาณดิจิทัลเป็นแอนะล็อก (DAC) การสื่อสารด้วยบัส (SPI) การเชื่อมต่อ I/O รวมถึงการศึกษาเอกสาร Datasheet ของไอซี MCP41010
  • การออกแบบวงจรลอจิก (Logic Design): การออกแบบวงจรดิจิทัล และการใช้งานชิป Cyclone IV FPGA
  • การวัด (Measurement): การใช้ Signal Tap Logic Analyzer การตั้งค่า Trigger Conditions และการวัดสัญญาณด้วยออสซิลโลสโคป
  • การเขียนโค้ดและการใช้ซอฟต์แวร์ (Software/Coding): การเขียนโค้ดด้วย VHDL และการใช้งาน Intel Quartus Prime (Lite Edition)

การเชื่อมต่อกับไอซีประเภท Digital Potentiometer#

MCP41010 เป็นตัวอย่างของไอซีประเภทตัวต้านทานปรับค่าได้แบบดิจิทัล (Digital Potentiometer) และสามารถนำมาสร้างเป็นวงจรแบ่งแรงดัน (Voltage Divider) ที่ปรับเปลี่ยนระดับแรงดันไฟฟ้า และใช้เป็นสัญญาณเอาต์พุตแบบแอนะล็อกได้

ในการปรับค่าความต้านทานของไอซี MCP41010 จะต้องตั้งค่าไบต์คำสั่ง หรือ Command Byte โดยให้มีค่าบิต C1C0 = "01" เพื่อเข้าสู่โหมดการเขียน และ P1P0 = "01" เพื่อใช้กับตัวต้านทานวงจรแรก ดังนั้นจะได้ไบต์คำสั่งเป็น 0x11 (hex) และถัดจากไบต์คำสั่ง จึงเป็นข้อมูลหนึ่งไบต์ (Data Byte) เพื่อกำหนดค่าความต้านทาน โดยเลือกได้ 256 ระดับ (0..255) การส่งไบต์คำสั่งและไบต์ข้อมูลรวม 16 บิต จะต้องใช้วิธีเลื่อนบิตข้อมูลตามรูปแบบของบัส SPI

รูป: ไบต์คำสั่ง Command Byte สำหรับ MCP41010

รูป: การส่งไบต์คำสั่งและไบต์ข้อมูล 16 บิต สำหรับบัส SPI

 


ตัวอย่างโค้ด VHDL สำหรับทดลองใช้ไอซี MCP41010 ร่วมกับบอร์ด FPGA#

โค้ดตัวอย่างถัดไป ใช้สำหรับวงจรดิจิทัลที่มีชื่อว่า mcp41010 ทำหน้าที่รับข้อมูล 8 บิต เข้ามาเป็นอินพุต DATA เมื่อสัญญาณอินพุต START เป็นลอจิก 1 จะมีการเก็บค่าลงในรีจิสเตอร์ภายใน แล้วจึงมีการส่งข้อมูลออก โดยการเลื่อนบิตทีละบิต ตามการทำงานของวงจร FSM (Finite State Machine) มีขาสัญญาณเอาต์พุตสำหรับบัส SPI ได้แก่ CSN (Chip Select), SCLK (Serial Clock) และ SDATA (Serial Data) ในระหว่างการส่งข้อมูลบิต สัญญาณเอาต์พุต BUSY จะเป็น 1 แล้วเปลี่ยนเป็น 0 เมื่อจบการส่งข้อมูล

วงจรนี้ทำงานด้วยสัญญาณอินพุต Clock ความถี่ 50MHz (มีคาบเวลา 20ns) และมีการกำหนดค่าคงที่ SPI_CLK_DIV ให้เท่ากับ 5 ในโค้ดตัวอย่าง สำหรับการทำงานของวงจรที่ทำหน้าที่เป็น SPI Master เพื่อสื่อสารข้อมูลกับไอซี MCP41010 (เป็น SPI Slave)

ความถี่ของสัญญาณ SPI Clock (SCLK) จะได้เท่ากับ 50MHz / (2 * 5) หรือ 5MHz

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;

entity mcp41010 is
  port (
    CLK  : in std_logic; -- 50 MHz system clock
    NRST : in std_logic; -- Active-low asynchronous reset
    -------------------------------------------
    START : in std_logic;
    DATA  : in std_logic_vector(7 downto 0);
    BUSY  : buffer std_logic;
    -------------------------------------------
    CSN   : out std_logic; -- Chip-Select
    SCLK  : out std_logic; -- Serial Clock
    SDATA : out std_logic  -- Serial Data
    -------------------------------------------
  );
end mcp41010;

architecture behavioral of mcp41010 is

  constant SPI_CLK_DIV : integer := 5; -- 50MH/(2*5) => 5MHz SCLK freq.
  constant DATA_WIDTH  : integer := 16;

  type state_type is (ST_IDLE, ST_START, ST_SCK_H, ST_SCK_L, ST_END);
  signal state : state_type := ST_IDLE;

  signal bit_cnt   : integer range 0 to DATA_WIDTH := 0;
  signal shift_en  : std_logic := '0';
  signal shift_reg : std_logic_vector(DATA_WIDTH-1 downto 0);

begin

  process (CLK, NRST)
    variable count : integer range 0 to (SPI_CLK_DIV-1) := 0;
  begin
    if NRST = '0' then
      count := 0;
      shift_en <= '0';
    elsif rising_edge(CLK) then
      if count = (SPI_CLK_DIV - 1) then
        count := 0;
        shift_en <= '1';
      else
        count := count + 1;
        shift_en <= '0';
      end if;
    end if;
  end process;

  process (CLK, NRST)
  begin
    if NRST = '0' then
      SCLK    <= '0';
      SDATA   <= '0';
      BUSY    <= '0';
      bit_cnt <= 0;
      state   <= ST_IDLE;

    elsif rising_edge(CLK) then

      case state is
        when ST_IDLE =>
          bit_cnt <= 0;
          SCLK    <= '0';
          SDATA   <= '0';
          if START = '1' then
            state                  <= ST_SCK_L;
            shift_reg(15 downto 8) <= b"0001_0001";
            shift_reg(7 downto 0)  <= data;
            BUSY                   <= '1';
          end if;

        when ST_SCK_L =>
          if shift_en = '1' then
            SCLK  <= '0';
            SDATA <= shift_reg(shift_reg'left);
            if bit_cnt = DATA_WIDTH then
              state <= ST_END;
            else
              state <= ST_SCK_H;
            end if;
          end if;

        when ST_SCK_H =>
          if shift_en = '1' then
            SCLK      <= '1';
            shift_reg <= shift_reg(shift_reg'left-1 downto 0) & '0';
            bit_cnt   <= bit_cnt + 1;
            state     <= ST_SCK_L;
          end if;

        when ST_END =>
          if shift_en = '1' then
            SCLK  <= '0';
            BUSY  <= '0';
            state <= ST_IDLE;
          end if;

        when others =>
          state <= ST_IDLE;
      end case;
    end if;
  end process;

  CSN <= not BUSY;

end behavioral;

รูป: VHDL Entity mcp41010 และ de0nano_mcp41010_demo

 

วงจรถัดไป de0nano_mcp41010_demo ใช้สัญญาณอินพุต Clock ที่มีความถี่ 50MHz และมีการใช้งาน mcp41010 เป็นส่วนประกอบหลักในการสร้างสัญญาณเอาต์พุตแบบแอนะล็อก โดยมีสัญญาณควบคุม START เป็นลอจิก 1 พร้อมข้อมูล DATA ขนาด 8 บิต เมื่อสัญญาณ BUSY มีค่าเป็น 0 วงจรจะเริ่มขั้นตอนการส่งข้อมูลออกด้วยบัส SPI

การส่งข้อมูล 8 บิตนี้ จะเกิดขึ้นทุก ๆ 500 ไซเคิล หรือ รอบของสัญญาณ Clock (50MHz) และส่งให้ไอซี MCP41010 นำไปสร้างสัญญาณเอาต์พุต โดยที่มีอัตราการอัปเดตข้อมูล 50MHz / 500 = 100kHz หรือ 100,000 Samples/sec

ข้อมูล 8 บิต ที่ถูกสร้างขึ้นมาและนำมาใช้ตามลำดับ เป็นไปตามรูปคลื่นสัญญาณแบบสามเหลี่ยม (Triangular Wave) มีการนับขึ้น (Count up) จาก 0 ถึง 255 ทีละหนึ่ง และตามด้วยการนับลง (Count Down) จาก 255 ถึง 0 ในหนึ่งคาบ ดังนั้นคลื่นสามเหลี่ยมนี้ จะมีความถี่ 100kHz / 512 = 195.3Hz

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;

entity de0nano_mcp41010_demo is
  port (
    CLK   : in std_logic; -- 50 MHz system clock
    NRST  : in std_logic; -- Active-low asynchronous reset
    CSN   : out std_logic;
    SCLK  : out std_logic;
    SDATA : out std_logic
  );
end de0nano_mcp41010_demo;

architecture synth of de0nano_mcp41010_demo is

  component mcp41010
    port (
      CLK   : in std_logic;
      NRST  : in std_logic;
      START : in std_logic;
      DATA  : in std_logic_vector(7 downto 0);
      BUSY  : buffer std_logic;
      CSN   : out std_logic;
      SCLK  : out std_logic;
      SDATA : out std_logic
    );
  end component;

  type state_type is (ST_IDLE, ST_START);
  signal state : state_type := ST_IDLE;

  -- Signals to interface with mcp41010
  signal start : std_logic := '0';
  signal data  : std_logic_vector(7 downto 0) := (others => '0');
  signal busy  : std_logic;

  -- Internal signals for triangular wave generation
  signal updown  : std_logic := '1';
  signal counter : integer range 0 to 255 := 0;

  -- Internal constant and signal for tick counter
  constant COUNT_MAX : integer := (500) - 1;
  signal count       : integer range 0 to COUNT_MAX;
  signal tick        : std_logic;

begin

  -- Instantiate mcp41010 component
  mcp41010_inst : mcp41010
  port map
  (
    CLK   => CLK,
    NRST  => NRST,
    START => start,
    DATA  => data,
    BUSY  => busy,
    CSN   => csn,
    SCLK  => sclk,
    SDATA => sdata
  );

  process (CLK, NRST)
  begin
    if NRST = '0' then
      tick  <= '0';
      count <= 0;
    elsif rising_edge(CLK) then
      if count = COUNT_MAX then
        count <= 0;
        tick  <= '1';
      else
        count <= count + 1;
        tick  <= '0';
      end if;
    end if;
  end process;

  -- Triangular wave generator process
  process (CLK, NRST)
  begin
    if NRST = '0' then
      counter <= 0;
      updown  <= '1';
      start   <= '0';
      state   <= ST_IDLE;

    elsif rising_edge(CLK) then

      case STATE is
        when ST_IDLE =>
          start <= '0';
          if tick = '1' and busy = '0' then
            -- Set data and trigger start signal
            data  <= std_logic_vector(TO_UNSIGNED(counter, 8));
            start <= '1';
            state <= ST_START;
          end if;

        when ST_START =>
          if busy = '1' then
            start <= '0';
            -- Update counter for triangular waveform
            if updown = '1' then
              if counter = 255 then
                updown <= '0'; -- count down
              else
                counter <= counter + 1;
              end if;
            else
              if counter = 0 then
                updown <= '1'; -- count up
              else
                counter <= counter - 1;
              end if;
            end if;
            state <= ST_IDLE;
          end if;

        when others =>
          state <= ST_IDLE;
      end case;
    end if;
  end process;

end synth;

ไฟล์สำหรับการตั้งค่าการใช้งานขา FPGA I/O ของบอร์ด Terasic DE0-Nano มีดังนี้

# Pin & Location Assignments
# set_global_assignment -name FAMILY "Cyclone IV E"
# set_global_assignment -name DEVICE EP4CE22F17C6N 

# GPIO_117 PIN_K16 CSN
# GPIO_119 PIN_L15 MISO
# GPIO_121 PIN_P16 MOSI
# GPIO_123 PIN_N16 SCLK

set_location_assignment PIN_J15 -to NRST
set_location_assignment PIN_R8  -to CLK
set_location_assignment PIN_K16 -to CSN
set_location_assignment PIN_P16 -to SDATA
set_location_assignment PIN_N16 -to SCLK

set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to *

 


การคอมไพล์โค้ดด้วย Quartus Prime Lite Edition#

ขั้นตอนถัดไป เป็นการใช้ซอฟต์แวร์ Intel Quartus Prime Lite Edition v22.1 แปลงโค้ด VHDL ให้เป็นวงจรดิจิทัล เพื่อนำไปใช้กับบอร์ด FPGA (Cyclone IV)

รูป: ซอฟต์แวร์ Intel Quartus Prime Lite Edition v22.1

เมื่อทำขั้นตอน Compilation ได้เสร็จสมบูรณ์แล้ว ก็ทดลองกับบอร์ด FPGA และวัดสัญญาณเอาต์พุตที่ได้จากไอซี MCP41010 โดยใช้ออสซิลโลสโคปแบบดิจิทัล จากรูปตัวอย่าง จะเห็นได้ว่า มีลักษณะเป็นสัญญาณคาบรูปสามเหลี่ยม และมีความถี่ 195Hz

รูป: ตัวอย่างรูปคลื่นสัญญาณเอาต์พุต

รูป: สัญญาณจากออสซิลโลสโคป CH1: SCLK และ CH2: CSN

จากรูปสัญญาณที่วัดได้ด้วยออสซิลโลสโคป จะเห็นได้ว่า ความถี่ของสัญญาณ SCLK เท่ากับ 5MHz และระยะห่างระหว่างขอบขาลงของสัญญาณ CSN วัดได้ 10us

ถัดไปเป็นตัวอย่างการใช้ Signal Tap Logic Analyzer สำหรับวงจรดิจิทัล มีการเลือกสัญญาณดิจิทัลของวงจรมาบางส่วนเพื่อบันทึกค่าลอจิก และมีการตั้งค่าเงื่อนไขสำหรับทริกเกอร์ เป็นขอบขาลง (Falling Edge) ของสัญญาณ CSN เมื่อเงื่อนไขของทริกเกอร์เป็นจริง ก็จะมีการบันทึกข้อมูลตามจังหวะของสัญญาณ 50MHz Clock

จากรูปจะเห็นได้ว่า ในช่วงเวลาที่สัญญาณ CSN มีค่าลอจิกเป็น Low มีความกว้างเท่ากับ 170 ไซเคิล หรือ คิดเป็น (170 * 20ns) = 3,400 ns หรือ คิดเป็น 3.4us

รูป: สัญญาณดิจิทัลที่ได้จากการทำงานของ Signal Tap Logic Analyzer

รูป: ระยะเวลาในการเกิดขอบขาลงของสัญญาณ CSN ถัดกันสองครั้ง เท่ากับ 500 ไซเคิล (คิดเป็นคาบเวลา 10us หรือ ความถี่ 100kHz)

รูป: การต่อวงจรทดลองบนเบรดบอร์ดร่วมกับบอร์ด Terasic DE0-Nano FPGA

 


ตัวอย่างโค้ด VHDL สำหรับการสร้างสัญญาณคาบรูปคลื่นไซน์#

ถัดไปเป็นตัวอย่างการสร้างสัญญาณคาบตามรูปคลื่นไซน์ โดยการสร้างตาราง หรืออาร์เรย์เก็บข้อมูล หรือค่าคงที่ ขนาด 8 บิต เป็นเลขจำนวนเต็มแบบ Unsigned มีจำนวน 256 ตัวเลข ตัวเลขเหล่านี้ได้จากการคำนวณค่าของฟังก์ชันไซน์ในหนึ่งคาบ และมีการบวกค่า DC Offset เพื่อให้แอมพลิจูดเป็นบวก (ไม่เป็นลบ) และมีค่าอยู่ในช่วง 0..255

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;
use IEEE.MATH_REAL.all;

entity de0nano_mcp41010_demo is
  port (
    CLK   : in std_logic; -- 50 MHz system clock
    NRST  : in std_logic; -- Active-low asynchronous reset
    CSN   : out std_logic;
    SCLK  : out std_logic;
    SDATA : out std_logic
  );
end de0nano_mcp41010_demo;

architecture synth of de0nano_mcp41010_demo is

  component mcp41010
    port (
      CLK   : in std_logic;
      NRST  : in std_logic;
      START : in std_logic;
      DATA  : in std_logic_vector(7 downto 0);
      BUSY  : buffer std_logic;
      CSN   : out std_logic;
      SCLK  : out std_logic;
      SDATA : out std_logic
    );
  end component;

  type state_type is (ST_IDLE, ST_START);
  signal state : state_type := ST_IDLE;

  constant BW        : integer := 8; -- 8 bits for DAC output
  constant M         : integer := 8; -- log2(#samples)
  constant MAX_INDEX : integer := 2**M - 1;

  -- Signals to interface with mcp41010
  signal start : std_logic := '0';
  signal data  : std_logic_vector(BW downto 0) := (others => '0');
  signal busy  : std_logic;

  subtype sample_t is std_logic_vector((BW-1) downto 0);
  type sample_table_t is array(0 to MAX_INDEX) of sample_t;

  function init_table return sample_table_t is
    variable LUT : sample_table_t;
    variable x   : REAL;
  begin
    for i in 0 to MAX_INDEX loop
      -- sinusoidal waveform (with DC offset)
      x      := (1.0 + SIN(2.0*MATH_PI*real(i) / real(2**M)))/2.0;
      LUT(i) := std_logic_vector(to_unsigned(integer(x*real(2**BW-1)),BW));
    end loop;
    return LUT;
  end function;

  -- Signal to index through LUT
  signal lut_index : integer range 0 to (2**M - 1) := 0;
  signal lut_table : sample_table_t := init_table;

  constant COUNT_MAX : integer := (500) - 1;
  signal count       : integer range 0 to COUNT_MAX;
  signal tick        : std_logic;

begin

  -- Instantiate mcp41010 component
  mcp41010_inst : mcp41010
  port map (
    CLK   => CLK,
    NRST  => NRST,
    START => start,
    DATA  => data,
    BUSY  => busy,
    CSN   => CSN,
    SCLK  => SCLK,
    SDATA => SDATA
  );

  -- Process for tick generation
  process (CLK, NRST)
  begin
    if NRST = '0' then
      tick  <= '0';
      count <= 0;
    elsif rising_edge(CLK) then
      if count = COUNT_MAX then
        count <= 0;
        tick  <= '1';
      else
        count <= count + 1;
        tick  <= '0';
      end if;
    end if;
  end process;

  -- Sine wave generator process
  process (CLK, NRST)
  begin
    if NRST = '0' then
      lut_index <= 0;
      start     <= '0';
      state     <= ST_IDLE;

    elsif rising_edge(CLK) then

      case state is
        when ST_IDLE =>
          start <= '0';
          if tick = '1' and busy = '0' then
            -- Set data from LUT and trigger start signal
            data  <= lut_table(lut_index);
            start <= '1';
            state <= ST_START;
          end if;

        when ST_START =>
          if busy = '1' then
            start <= '0';
            -- Increment LUT index for next sine value
            if lut_index = MAX_INDEX then
              lut_index <= 0; -- reset to beginning of LUT
            else
              lut_index <= lut_index + 1;
            end if;
            state <= ST_IDLE;
          end if;

        when others =>
          state <= ST_IDLE;
      end case;
    end if;
  end process;

end synth;

รูป: สัญญาณเอาต์พุต (สัญญาณคาบรูปคลื่นไซน์) ซึ่งวัดความถี่ได้ประมาณ 391Hz

รูป: สเปกตรัมของคลื่นสัญญาณโดยใช้การประมวลผลด้วย FFT (Fast Fourier Transform) สำหรับสัญญาณเอาต์พุตในโหมด AC Coupling

 


กล่าวสรุป#

บทความนี้ได้นำเสนอตัวอย่างการออกแบบวงจรดิจิทัลเพื่อใช้กับบอร์ด FPGA โดยได้เลือกใช้บอร์ด DE0-Nano (Cyclone IV FPGA) และมีการเขียนข้อมูล 8 บิต ไปยังไอซี MCP41040 Digital Potentiometer ด้วยอัตราคงที่ และได้สัญญาณเอาต์พุตเป็นสัญญาณแอนะล็อก

บทความที่เกี่ยวข้อง


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Created: 2024-11-13 | Last Updated: 2024-11-14