FPGA驱动AD转换芯片MCP3202_VHDL语言

之前介绍过使用STM32的AD采样功能,采集外部电压。这次介绍AD转换芯片MCP3202,是美国MICROCHIP公司推出的一款双通道、12位分辨率的AD采样芯片。外部提供了SPI接口(支持00模式和11模式),可以实现数据的快速读取。芯片原理图如下:

虽然提供了SPI接口,但读取数据时,会发现并不是标准的SPI接口。如果使用的是MCU,参照这个时序图:

如果使用的是FPGA,参照这个时序图:

本文只说FPGA方面,所以看第二个图(上图)。从图中可知,当片选(CS)拉低以后,CLK提供了17个时钟,是的,不是8个,不是16个,是17个,这就是为什么说它不是标准的SPI接口。

拉低后,第一个时钟内,DIN提供高电平,告诉芯片开始转换;

第二、第三个时钟,配置高低电平,选择相应的通道(通道0、通道1)、模式(单端、差分)。

第四个时钟,配置数据是高位在前(MSB)还是低位在前(LSB)。

第五个时钟,空的,啥都不用管。

后面12个时钟,DIN上面的电平就无所谓了,只看DOUT上面的电平,它代表AD采样的数据。

搞明白它的工作方式,就可以开始写代码了。

首先写一段代码,提供18个时钟,及相应的CS信号。

为什么是18个?不是17个吗?

因为每次转换完成之后,CS要拉高持续一段时间,这个时间刚好可以用一个CLK来抵消。因此时钟及CS在这里完成。

--串行数据所需时钟的各个状态
type SclkState is (SclkS0 , SclkS1);		--SclkS0:初始化  SclkS1建立17个脉冲
signal SclkSt	:	SclkState := SclkS0;					--设置当前状态为SclkS0

--AD工作状态
type AdState is (AdS0 , AdS1 , AdS2 , AdS3 , AdS4 , AdS5 , AdS6 , AdS7 , AdS8);				--AdS0:启动转换  S1:等待转换  S2:接收数据  S3:锁存数据
signal	AdSt	:	AdState := AdS0;						--设置当前状态为AdS0

signal SclkTmp		:	std_logic := '0';								--串行时钟临时信号
signal CsTmp		:	std_logic := '1';								--片选临时信号
signal	Ad12		:	std_logic_vector (11 downto 0);					--Ad转换值


begin

--SclkTmp 状态机 建立读串行数据时钟18个方波脉冲 
	process (Clk)
	variable Cnt		:	std_logic_vector (9 downto 0) := (others => '0');	--读数据时钟半波计数器
	variable SclkCnt	:	std_logic_vector (4 downto 0) := "10010";			--读数据时钟数量计数器
	constant SclkPw		:	std_logic_vector (9 downto 0) := "1111101000"; 			--串行时钟半周期计数值(必须>=2);计1000个Clk时钟、10us,频率50khz
	
	begin
		if (Clk 'event and Clk = '1') then
			case SclkSt is
				when SclkS0 =>
					Cnt := (others => '0');	--计数器清零
					SclkCnt := "10010";		--数量计数器初始值为18
					SclkTmp <= '0';			--串行时钟变低
					CsTmp <= '0';				--片选拉低
					SclkSt <= SclkS1;
				when SclkS1 =>					--产生18个串行时钟方波
					if (Cnt < SclkPw - 1) then		--小于半波宽度
						Cnt := Cnt + 1;
					else
						Cnt := (others => '0');	--产生前17个串行时钟
						if (Sclkcnt > 2) then	--前16个时钟
							SclkTmp <= not (SclkTmp);
							if (SclkTmp = '1')then
								SclkCnt := SclkCnt - 1;
							end if;
						elsif (Sclkcnt = 2) then	--第17个时钟
							SclkTmp <= not (SclkTmp);
							if (SclkTmp = '1')then
								SclkCnt := SclkCnt - 1;
								CsTmp <= '1';				--片选拉高
							end if;
						else					--第18个串行时钟,维持CS高电平
							if (SclkTmp = '1')then
								SclkSt <= SclkS0;
							end if;
							SclkTmp <= not (SclkTmp);						
						end if;
					end if;	
			end case;
		end if;
	end process;
	AdSclk <= SclkTmp;							--将串行时钟送到AdSclk引脚
	AdCs <= CsTmp;

接下来写一段程序,按照时钟的上升沿、下降沿,去配置DIN、读取DOUT即可。

	--AD 状态机
	process (Clk)
	variable Sts	:	std_logic_vector (1 downto 0);
	variable Cnt	:	std_logic_vector (4 downto 0);
	variable AdData	:	std_logic_vector (11 downto 0);				--AD串行数据
	constant RcW	:	std_logic_vector (1 downto 0) := "11";		--启动转换Rc低电平宽度。占3个时钟、60ns	
	
	begin
		if (Clk 'event and Clk = '1') then
			case AdSt is
				when AdS0 =>					--开始
					Sts := "00";
					Cnt := (others => '0');
					AdSt <= AdS1;						
				when AdS1 =>					--等待CS下降沿
					Sts := Sts (0) & CsTmp;
					if (Sts = "10") then
						--Cnt := (others => '0');
						AdDi <= '1';			--启动转换  START
						AdSt <= AdS2;			--下个CLK
					end if;
				when AdS2 =>
					Sts := Sts (0) & SclkTmp;
					if (Sts = "10") then
						AdDi <= '1';			--选择通道0  SGL
						AdSt <= AdS3;			--下个CLK
					end if;
				when AdS3 =>
					Sts := Sts (0) & SclkTmp;
					if (Sts = "10") then
						AdDi <= '0';			--选择通道0  ODD
						AdSt <= AdS4;			--下个CLK
					end if;
				when AdS4 =>
					Sts := Sts (0) & SclkTmp;
					if (Sts = "10") then
						AdDi <= '1';			--高位在前  MSB
						AdSt <= AdS5;			--下个CLK
					end if;
				when AdS5 =>
					Sts := Sts (0) & SclkTmp;
					if (Sts = "10") then
						AdDi <= '1';			--空位     
						AdSt <= AdS6;			--下个CLK
					end if;
				when AdS6 =>
					Sts := Sts (0) & SclkTmp;
					if (Sts = "10") then     
						AdSt <= AdS7;			--下个CLK
					end if;
				when AdS7 =>					--接收数据(串行时钟上升沿收取)
					Sts := Sts (0) & SclkTmp;
					if (Sts = "01") then
						AdData := AdData (10 downto 0)& AdDo;
						if (Cnt < "1011") then
							Cnt := Cnt + 1;
						else
							AdSt <= AdS8;
						end if;
					end if;
				when AdS8 =>					--锁存数据
					Ad12 <= AdData;
					AdSt <= AdS0;
			end case;
		end  if;
	end process;
	AdQ <= Ad12;

外部时钟频率100MHZ,内部降频到了50KHZ,代码已验证。可以直接拿去用,也可以在此基础上做一定的修改。

打完收工!

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据