Raspberry pi通过i2c 与FPGA通信

底端硬件必须用到FPGA,但是树莓并没有引出存储器总线(如TI的gpmc总线),只好用i2c将就代替,

经测试,速度从100kbps~2Mbps都可以通信,打算使用固定的2Mbps用到以后的产品上。


通信模式为cpu--> i2c --> FPGA --> 7bit 地址,16 bit 数据宽度Local bus。


树莓有个i2ctool,可以用来检测外部i2c设备

sudo apt-get install i2ctool

sudo i2cdetect -y 0

0表示A版,1表示B版,我的是古老的A版,so~~ -y 0

Raspberry pi通过i2c 与FPGA通信_第1张图片


这货只是发送第一节地址,然后侦测外部设备有没在ACK位拉低,若有,将这个地址标记为有外部设备,即上上图是数字而不是“--”。

拿根线把sda拉到GND,他也认为有响应有设备,当然这只是骗骗自己。




注意!树莓A版在编译bcm2835的c库时,要把

// #define I2C_V1

的注释打开

#define I2C_V1
因为最新的bcm2835的c库是给B版用的,所以默认的把I2C define I2C_V2

装好c库http://www.airspayce.com/mikem/bcm2835/  ,就可以开始写通信的代码了


贴上FPGA的i2c slave 代码

-- ============================================================
-- File Name	:	i2c_sla.vhd
-- Author		:	
-- Build Date	:
-- Version		:	V 1.3
-- ChangeLog	:
--			v1.1	fixed the rising/falling edge detect of sdi
--
--			v1.2	make some comment,make it suit 7-bit-addressing
--					only
--			v1.3	fixed a THEORY edge detect problem!!
-- ============================================================
-- ************************************************************
-- Description	:	i2c slave module , 7-bit-addressing & 10-bit
--					-addressing suitable , the data width is 2
--				 	byte
-- 					this module WONT auto addr-add-one on the 
--					continuesly write or read
--
--					ATTENTION!!!this suit 7bit-addr only!!!
-- ************************************************************

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

entity i2c_sla is
port
(
	clk		: in	std_logic;		-- scl's freq * 8 or more
	rst		: in	std_logic;
	
	scl		: in	std_logic;
	sdi		: in	std_logic;
	sdo		: out	std_logic;
	sdo_rls	: out	std_logic;
	
	wrn		: out	std_logic;
	oen		: out	std_logic;
	ain		: out	std_logic_vector(9 downto 0);
	din		: out	std_logic_vector(15 downto 0);
	dout	: in	std_logic_vector(15 downto 0);
	
	led		: out	std_logic
);
end i2c_sla;

architecture rtl of i2c_sla is
type rxstate_type is(idle,ctrl_byte,sla_ack1,sla_addr,sla_ack2,sla_read,sla_write);
type sla_rd_type is(prepare,shift,ack);
type sla_wr_type is(shift,ack);
type d_type is record
	rxstate			: rxstate_type;
	sla_rd_state	: sla_rd_type;
	sla_wr_state	: sla_wr_type;
	scl				: std_logic_vector(1 downto 0);
	sdi				: std_logic_vector(1 downto 0);
	wrn				: std_logic;
	oen				: std_logic;
	rxcnt			: integer range 0 to 8;
	txcnt			: integer range 0 to 8;
	rxbuf			: std_logic_vector(7 downto 0);
	ctrl_reg		: std_logic_vector(7 downto 0);
	sdo_release		: std_logic;
	sdo				: std_logic;
	sla_addr		: std_logic_vector(9 downto 0);
	txbuf			: std_logic_vector(15 downto 0);
	data_reg		: std_logic_vector(15 downto 0);
	din				: std_logic_vector(15 downto 0);
	ack_det			: std_logic;
	led				: std_logic;
end record;
signal d,q	: d_type;

constant RD				: std_logic	:= '1';
constant WR				: std_logic	:= '0';

begin
	

	comb:process(q,rst,scl,sdi,dout)
	variable w				: d_type;
	variable scl_rising		: std_logic;
	variable scl_falling	: std_logic;
	begin
		w := q;
		
		w.scl := q.scl(0) & scl;
		w.sdi := q.sdi(0) & sdi;
		
		scl_rising := (q.scl(0))and(not q.scl(1));
		scl_falling := (q.scl(1))and(not q.scl(0));
		
		w.wrn := '1';
		w.oen := '1';
		
		case q.rxstate is
			when idle =>
				w.rxcnt := 0;
				w.txcnt := 0;
				w.sdo := '1';
				w.sdo_release := '0';
				w.led := '1';	--**************************for debug
			when ctrl_byte =>
				w.led := '0';	--****************************for debug
				if scl_rising = '1' then	--scl rising edge
					w.rxbuf := q.rxbuf(6 downto 0) & q.sdi(0);
					w.rxcnt := q.rxcnt + 1;
					if w.rxcnt = 8 then
						w.rxcnt := 0;
						w.ctrl_reg := w.rxbuf;
						w.rxstate := sla_ack1;
					end if;
				end if;
			when sla_ack1 =>
				if scl_falling = '1' then	--scl falling edge
					w.sdo_release := '1';
					w.sdo := '0';
					if q.ctrl_reg(0) = RD then		--read
						w.rxstate := sla_read;
						w.oen := '0';
			--			if q.ctrl_reg(7 downto 3) /= "11110" then			-- 7-bit-addressing,save addr
							w.sla_addr := "000" & q.ctrl_reg(7 downto 1);	-- if it is 10-bit-addressing,addr has already save
			--			end if;												-- when last time write process done
					else							--write
						if q.sdo_release = '1' then
							w.sdo := '1';
							w.sdo_release := '0';
							w.sla_addr := "000" & q.ctrl_reg(7 downto 1);
			--				if q.ctrl_reg(7 downto 3) = "11110" then	--10-bit-addressing,go to the second addr handle process
			--					w.rxstate := sla_addr;
			--				else							--7-bit-addressing,go to write process directly
								w.rxstate := sla_write;
			--				end if;
						end if;
					end if;
				end if;
			when sla_addr =>
				if scl_rising = '1' then	--scl rising edge
					w.rxbuf := q.rxbuf(6 downto 0) & q.sdi(0);
					w.rxcnt := q.rxcnt + 1;
					if w.rxcnt = 8 then
						w.rxcnt := 0;
						w.sla_addr := q.ctrl_reg(2 downto 1) & w.rxbuf;
						w.rxstate := sla_ack2;
					end if;
				end if;
			when sla_ack2 =>
				if scl_falling = '1' then	--scl falling edge
					w.sdo_release := '1';
					w.sdo := '0';
					if q.sdo_release = '1' then		--slave has already acknowledge
						w.sdo := '1';
						w.sdo_release := '0';
						w.rxstate := sla_write;
					end if;
				end if;
			when sla_read =>
				case q.sla_rd_state is
					when prepare =>
						w.txbuf := dout;
						w.sla_rd_state := shift;
					when shift =>			-- in the read state , it will got the stop bit
						if scl_falling = '1' then	--scl falling edge
							w.sdo_release := '1';
							w.sdo := q.txbuf(7);
							w.txbuf(7 downto 0) := q.txbuf(6 downto 0) & q.txbuf(7);
							w.txcnt := q.txcnt + 1;
							if w.txcnt = 8 then
								w.txcnt := 0;
								w.sla_rd_state := ack;
							end if;
						end if;
					when ack =>
						if scl_falling = '1' then
							w.sdo_release := '0';
							w.ack_det := '1';
						end if;
						if q.ack_det = '1' then
							if scl_rising = '1' then	--scl rising edge
								w.sla_rd_state := shift;
								w.ack_det := '0';
								if q.sdi(0) = '0' then				--master acknowledge
									w.txbuf := q.txbuf(7 downto 0) & q.txbuf(15 downto 8);
								else						--master did not acknowledge
									w.rxstate := idle;
									w.sla_rd_state := prepare;
								end if;
							end if;
						end if;
				end case;
			when sla_write =>
				case q.sla_wr_state is
					when shift =>
						if scl_rising = '1' then	--scl rising edge
							w.rxbuf := q.rxbuf(6 downto 0) & q.sdi(0);
							w.rxcnt := q.rxcnt + 1;
							if w.rxcnt = 8 then
								w.rxcnt := 0;
								w.data_reg := w.rxbuf & q.data_reg(15 downto 8);
								w.sla_wr_state := ack;
							end if;
						end if;
						if (((q.sdi(0))and(not q.sdi(1)))and((q.scl(0))and(q.scl(1)))) = '1' then	--sdi rising edge and scl is high
							w.wrn := '0';
							w.din := q.data_reg;
						end if;
					when ack =>
						if scl_falling = '1' then	--scl falling edge
							w.sdo_release := '1';
							w.sdo := '0';
							if q.sdo_release = '1' then		--slave has already acknowledge
								w.sdo := '1';
								w.sdo_release := '0';
								w.sla_wr_state := shift;
							end if;
						end if;
				end case;
		end case;
		--=========================== stop bit process
		if (((q.sdi(0))and(not q.sdi(1)))and((q.scl(0))and(q.scl(1)))) = '1' then	--sdi rising edge and scl is high
			w.rxstate := idle;							--it means the i2c come to the stop bit
			w.sla_rd_state := prepare;
			w.sla_wr_state := shift;
		end if;
		--========================== start/restart process
		if (((q.sdi(1))and(not q.sdi(0)))and((q.scl(0))and(q.scl(1)))) = '1' then	--sdi falling edge and scl is high
			w.rxstate := ctrl_byte;			--i2c got a start/restart bit
			w.rxcnt := 0;
			w.txcnt := 0;
		end if;
		--=======================
		if rst = '1' then
			w.rxstate		:= idle;
			w.sla_rd_state	:= prepare;
			w.sla_wr_state	:= shift;
			w.scl			:= "11";
			w.sdi			:= "11";
			w.sdo			:= '1';
			w.sdo_release	:= '0';
			w.wrn			:= '1';
			w.oen			:= '1';
			w.sla_addr		:= (others => '0');
			w.din			:= (others => '0');
			w.rxcnt			:= 0;
			w.txcnt			:= 0;
			w.rxbuf			:= (others => '0');
			w.txbuf			:= (others => '0');
			w.ctrl_reg		:= (others => '0');
			w.data_reg		:= (others => '0');
			w.ack_det		:= '0';
			w.led			:= '1';
		end if;
		
		sdo		<= q.sdo;
		sdo_rls	<= q.sdo_release;
		oen		<= q.oen;
		wrn		<= q.wrn;
		ain		<= q.sla_addr;
		din		<= q.din;
		led		<= q.led;
		d		<= w;
	end process;

	regs:process(clk)
	begin
		if rising_edge(clk) then
			q <= d;
		end if;
	end process;
end rtl;

vhdl中把其中几个注释打开,就可以用作10bit-addressing的i2c,但是树莓是7bit-addressing的,所以注释掉。


贴上C代码

#include   
#include 

int main()
{
	char wbuf[2] = {0x00,0x00};
	char addr = 0x00;
	uint8_t result;
	char rxbuf[2] = {0x00,0x00};
	uint16_t err_cnt = 0;
	
	if (!bcm2835_init()) return 1;
	bcm2835_i2c_begin();

	bcm2835_i2c_setClockDivider(125);
	while(1)
		{
			bcm2835_i2c_setSlaveAddress(addr);
			bcm2835_i2c_write(wbuf, 2);
			
			printf("write addr is 0x%x\n",addr);
			printf("write data is 0x%x%x\n\n",wbuf[1],wbuf[0]);
			
			bcm2835_delay(500);

			result = bcm2835_i2c_read(rxbuf, 2);

			printf("read data is 0x%x%x\n\n",rxbuf[1],rxbuf[0]);
			if(wbuf[1] != rxbuf[1]) err_cnt = err_cnt + 1;
			else if(wbuf[0] != rxbuf[0]) err_cnt = err_cnt + 1;
				
			printf("err cnt is : %d\n",err_cnt);
			
			printf("***************\n");
			bcm2835_delay(500);
			
			if(err_cnt >= 1)
			{ 
				break;
			}
			

			addr = addr + 1;
			wbuf[1] = wbuf[1] + 1;
			wbuf[0] = wbuf[0] + 1;
		}

	bcm2835_i2c_end();   
	bcm2835_close();
	return 0;
}
我的C语言太渣渣,测试代码可以将就用用,软件的还是交给软件工程师去写吧


gcc -o ts01 ts01.c -l bcm2835

编译后跑起,测试后是写进去的与读出来的是一致的,就只贴几个吧, 做测试的时候是要跑个几分钟没出错才行。

Raspberry pi通过i2c 与FPGA通信_第2张图片

Raspberry pi通过i2c 与FPGA通信_第3张图片




你可能感兴趣的:(raspberry_pi)