Difference between revisions of "FPGA - Rotary Encoder"
(5 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
− | + | Here's public domain code for a Spartan 3E, to interface with multiple rotary encoders. It's currently tested up to 8. It uses Block RAM to keep track of a Rotary ID and Value for the rotary (between 0-127). It outputs the rotary values via UART on 57600 baud. Based off of the Papilio and also HamsterNZ's introductory PDF on FPGAs. | |
I have different versions of this. I've had some issues with UART partial garbage output on a Spartan 3E 500, whereas the Papilio's 250 did not have such issues. I'll start with the one that's compatible with the Papilio board (including the pinout / constraints.ucf file). | I have different versions of this. I've had some issues with UART partial garbage output on a Spartan 3E 500, whereas the Papilio's 250 did not have such issues. I'll start with the one that's compatible with the Papilio board (including the pinout / constraints.ucf file). | ||
Line 5: | Line 5: | ||
There is Rotary_Top.vhd, which is the main code, then there is rotary.vhd. Finally you will need to initialize the Block Ram, and also optionally include a init hex file. | There is Rotary_Top.vhd, which is the main code, then there is rotary.vhd. Finally you will need to initialize the Block Ram, and also optionally include a init hex file. | ||
+ | '''Caution: Prototype code! Expect extra notes / comments.''' | ||
==Rotary_Top.vhd== | ==Rotary_Top.vhd== | ||
Line 1,028: | Line 1,029: | ||
==Init hex File== | ==Init hex File== | ||
− | You will also need an init Hex file to load in the Block RAM, in order to make it easy to see where the bits start and stop | + | You will also need an init Hex file to load in the Block RAM, in order to make it easy to see where the bits start and stop. The radix is 16 (so hex) and each two integers is one byte (e.g. 0x00, 0x01, would be the first two). This is what the UART outputs, without any data added. So you see 00, then 01. The 01 is an index, the 00 is the value of the rotary encoder (being 7 bits, so 0 - 127), and so on. This way you get the UART from FPGA to the micro with an index. |
It should look something like: | It should look something like: | ||
<pre> | <pre> | ||
Line 1,057: | Line 1,058: | ||
0017, | 0017, | ||
0018, | 0018, | ||
+ | 0019, | ||
+ | 001A, | ||
+ | 001B, | ||
+ | 001C, | ||
+ | 001D, | ||
+ | 001E, | ||
+ | 001F, | ||
+ | 0020, | ||
+ | 0021, | ||
+ | 0022, | ||
+ | 0023, | ||
+ | 0024, | ||
+ | 0025, | ||
+ | 0026, | ||
+ | 0027, | ||
+ | 0028, | ||
+ | 0029, | ||
+ | 002A, | ||
+ | 002B, | ||
+ | 002C, | ||
+ | 002D, | ||
+ | 002E, | ||
+ | 002F, | ||
+ | 0030, | ||
+ | 0031, | ||
+ | 0032, | ||
+ | 0033, | ||
+ | 0034, | ||
+ | 0035, | ||
+ | 0036, | ||
+ | 0037, | ||
+ | 0038, | ||
+ | 0039, | ||
+ | 003A, | ||
+ | 003B, | ||
+ | 003C, | ||
+ | 003D, | ||
+ | 003E, | ||
+ | 003F, | ||
+ | 0040, | ||
+ | 0041, | ||
+ | 0042, | ||
+ | 0043, | ||
+ | 0044, | ||
+ | 0045, | ||
+ | 0046, | ||
+ | 0047, | ||
+ | 0048, | ||
+ | 0049, | ||
+ | 004A, | ||
+ | 004B, | ||
+ | 004C, | ||
+ | 004D, | ||
+ | 004E, | ||
+ | 004F, | ||
+ | 0050, | ||
+ | 0051, | ||
+ | 0052, | ||
+ | 0053, | ||
+ | 0054, | ||
+ | 0055, | ||
+ | 0056, | ||
+ | 0057, | ||
+ | 0058, | ||
+ | 0059, | ||
+ | 005A, | ||
+ | 005B, | ||
+ | 005C, | ||
+ | 005D, | ||
+ | 005E, | ||
+ | 005F, | ||
+ | 0060, | ||
+ | 0061, | ||
+ | 0062, | ||
+ | 0063, | ||
+ | 0064, | ||
+ | 0065, | ||
+ | 7A7A, | ||
</pre> | </pre> | ||
==Resources== | ==Resources== | ||
− | * [http://papilio.cc/ Papilio FPGA] While the Papilio is obsolete now, and you should probably start with a newer FPGA, it's still | + | * [http://papilio.cc/ Papilio FPGA] While the Papilio is obsolete now, and you should probably start with a newer FPGA, it's still not a bad place to start. |
* [http://hamsterworks.co.nz HamsterWorks] | * [http://hamsterworks.co.nz HamsterWorks] | ||
+ | * http://web.archive.org/web/20190803104358/http://hamsterworks.co.nz/mediawiki/index.php/Main_Page | ||
* [https://www.eevblog.com/forum/projects/rotary-encoders-new-polling-and-pin-interrupt-servicing-routines/ EEVblog Forum] The algorithm for the Rotary was from here. | * [https://www.eevblog.com/forum/projects/rotary-encoders-new-polling-and-pin-interrupt-servicing-routines/ EEVblog Forum] The algorithm for the Rotary was from here. | ||
+ | * [http://dangerousprototypes.com/docs/CPLD:_Complex_programmable_logic_devices#CPLD_development_tutorials] Some basic vhdl/verilog/schematic entry examples for ISE. A good place to start, although I like HamsterNZ's pdf better. Still, some good info here on setup, and initialization. Good on these gentlemen for their use of CC-0 / Public Domain. | ||
{{Electronics}} | {{Electronics}} |
Latest revision as of 22:58, 19 April 2024
Here's public domain code for a Spartan 3E, to interface with multiple rotary encoders. It's currently tested up to 8. It uses Block RAM to keep track of a Rotary ID and Value for the rotary (between 0-127). It outputs the rotary values via UART on 57600 baud. Based off of the Papilio and also HamsterNZ's introductory PDF on FPGAs.
I have different versions of this. I've had some issues with UART partial garbage output on a Spartan 3E 500, whereas the Papilio's 250 did not have such issues. I'll start with the one that's compatible with the Papilio board (including the pinout / constraints.ucf file).
There is Rotary_Top.vhd, which is the main code, then there is rotary.vhd. Finally you will need to initialize the Block Ram, and also optionally include a init hex file.
Caution: Prototype code! Expect extra notes / comments.
Rotary_Top.vhd
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; use IEEE.NUMERIC_STD.ALL; -- TODO --DONE test reading one address in RAM. make sure it is right. --DONE make sure output on UART is correct. Also bits are --DONE in right order too. DONE. --DONE (used counter, not loop) setup a read on 0-99 addresses of RAM . use loop. --DONE have it output each memory address in uart 3 or 4 times before --going to the next. --DONE EASY have it loop through the ram 24/7 EASY --DONE: set default values of ram to be letters abcabcabcabc --RAM -- idea -- have top level manage all ram and output -- have output only if cw or ccw -- if outputs, have that rotaryID(or memory address) -- be the chosen one to write to. --UART -- possible solution #1 -- if a memory address is updated -- then output uart of new memory address only -- every rotary change, causes -- new ram write/ read, and uart output -- on as needed basis -- PROBLEMS: having trouble getting just one uart byte out -- noisy, and timing is off possibly (check timing) -- possible solution #2 -- loop through all memory constantly -- output all values constantly -- any changes will be reflected in micro -- hopefully fast enough? (should be)(57600 is p fast) -- any rotaries that update will write to ram -- problems: if 100 rotaries updating at once, -- could not be fast enough? maybe? maybe not. maybe it -- is fast enough to read io, write ram. -- write RAM would be separate from read ram / UART here -- but need to adjust write enable flag as needed... maybe -- two port RAM is better? constantly write on port A -- constantly read from port b. no need to adjust write enable -- flag... -- Yes, you can use dual port RAM. one port always wea high -- other port always wea low. and they should only interfere -- if read is during a write. however, -- depending how fast I read the data, I may be able to get -- around htis. i could also average the values (say last 3 -- values) from the serial for a given byte, and call that -- the true value. this way avoid any memory errors. -- TODO -- Need to writeup basic documentation and clean these at the -- very end. -- look up what a memory latch is -- output serial as BCD or ascii table bits -- i.e. instead of using straight hex (one byte each) for -- a number, convert it to its ascii equivalent (not as fast) -- (takes more bytes) -- EDIT: just outputting raw 8 bits entity rotary_top is --must map all 100 rotaries here GENERIC( RAMADDR : STD_LOGIC_VECTOR(7 downto 0) := x"FF"; ROTARYID : integer := 1 ); PORT( ROT : in STD_LOGIC_VECTOR(1 downto 0); -- semi colons ROT2 : in STD_LOGIC_VECTOR(1 downto 0); -- here ROT3 : in STD_LOGIC_VECTOR(1 downto 0); ROT4 : in STD_LOGIC_VECTOR(1 downto 0); ROT5 : in STD_LOGIC_VECTOR(1 downto 0); ROT6 : in STD_LOGIC_VECTOR(1 downto 0); ROT7 : in STD_LOGIC_VECTOR(1 downto 0); ROT8 : in STD_LOGIC_VECTOR(1 downto 0); LED : out STD_LOGIC_VECTOR(7 downto 0); -- commas in --LEDWATCH : out STD_LOGIC_VECTOR(7 downto 0); --instance clk : in STD_LOGIC; --DATAOUTUART : out STD_LOGIC; TX : out STD_LOGIC_VECTOR(0 downto 0) --RX : STD_LOGIC ); end rotary_top; architecture Behavioral of rotary_top is COMPONENT rotary GENERIC( RAMADDR : STD_LOGIC_VECTOR(7 downto 0) := x"FF"; ROTARYID : STD_LOGIC_VECTOR(7 downto 0) := x"00" ); PORT( ROT : IN std_logic_vector(1 downto 0); clk : IN std_logic; LED : OUT std_logic_vector(3 downto 0); -- LEDWATCH : OUT std_logic_vector(7 downto 0); DATAOUT : OUT STD_LOGIC_VECTOR(15 downto 0) ); END COMPONENT; COMPONENT DUALRAM PORT ( clka : IN STD_LOGIC; ena : IN STD_LOGIC; wea : IN STD_LOGIC_VECTOR(0 DOWNTO 0); addra : IN STD_LOGIC_VECTOR(7 DOWNTO 0); dina : IN STD_LOGIC_VECTOR(15 DOWNTO 0); douta : OUT STD_LOGIC_VECTOR(15 DOWNTO 0); clkb : IN STD_LOGIC; enb : IN STD_LOGIC; web : IN STD_LOGIC_VECTOR(0 DOWNTO 0); addrb : IN STD_LOGIC_VECTOR(7 DOWNTO 0); dinb : IN STD_LOGIC_VECTOR(15 DOWNTO 0); doutb : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ); END COMPONENT; signal resultsigtest : STD_LOGIC_VECTOR(7 downto 0) ; signal RAMADDR_CURRENT : STD_LOGIC_VECTOR(7 downto 0) := "00000000"; -- RAMADDR_0 is start byte (zz (LOWER CASE)) for pic to know when array starts constant RAMADDR_1 : STD_LOGIC_VECTOR(7 downto 0) := "00000001"; constant ROTARYID_1 : STD_LOGIC_VECTOR(7 downto 0) := "00000001"; constant RAMADDR_2 : STD_LOGIC_VECTOR(7 downto 0) := "00000010"; constant ROTARYID_2 : STD_LOGIC_VECTOR(7 downto 0) := "00000010"; --signal RAMADDR_CURRENT2 : STD_LOGIC_VECTOR(7 downto 0) := "00001000"; constant RAMADDR_3 : STD_LOGIC_VECTOR(7 downto 0) := "00000011"; constant ROTARYID_3 : STD_LOGIC_VECTOR(7 downto 0) := "00000011"; constant RAMADDR_4 : STD_LOGIC_VECTOR(7 downto 0) := "00000100"; constant ROTARYID_4 : STD_LOGIC_VECTOR(7 downto 0) := "00000100"; constant RAMADDR_5 : STD_LOGIC_VECTOR(7 downto 0) := "00000101"; constant ROTARYID_5 : STD_LOGIC_VECTOR(7 downto 0) := "00000101"; constant RAMADDR_6 : STD_LOGIC_VECTOR(7 downto 0) := "00000110"; constant ROTARYID_6 : STD_LOGIC_VECTOR(7 downto 0) := "00000110"; constant RAMADDR_7 : STD_LOGIC_VECTOR(7 downto 0) := "00000111"; constant ROTARYID_7 : STD_LOGIC_VECTOR(7 downto 0) := "00000111"; constant RAMADDR_8 : STD_LOGIC_VECTOR(7 downto 0) := "00001000"; constant ROTARYID_8 : STD_LOGIC_VECTOR(7 downto 0) := "00001000"; -- these must be here or error. must translate output to -- sig, then to output. can't directly go -- from output to output signal ROT1OUT : STD_LOGIC_VECTOR(15 downto 0); signal ROT2OUT : STD_LOGIC_VECTOR(15 downto 0); --must be set to zero (or unequal to rot1out) at start signal ROT1PREV : STD_LOGIC_VECTOR(15 downto 0) := (others => '0'); type t_ROTOUT is array (1 to 100) of std_logic_vector(15 downto 0); signal r_ROTO : t_ROTOUT := (others => (others => '0')); signal wea : STD_LOGIC_VECTOR(0 DOWNTO 0) := "0"; signal addra : STD_LOGIC_VECTOR(7 DOWNTO 0) := (others => '0'); signal dina : STD_LOGIC_VECTOR(15 DOWNTO 0) := (others => '0'); signal douta : STD_LOGIC_VECTOR(15 DOWNTO 0) := (others => '0'); signal ena : STD_LOGIC := '0'; signal DINAPROXY : STD_LOGIC_VECTOR(15 DOWNTO 0) := (others => '0'); signal web : STD_LOGIC_VECTOR(0 DOWNTO 0) := "0"; signal addrb : STD_LOGIC_VECTOR(7 DOWNTO 0) := (others => '0'); signal dinb : STD_LOGIC_VECTOR(15 DOWNTO 0) := (others => '0'); signal doutb : STD_LOGIC_VECTOR(15 DOWNTO 0) := (others => '0'); signal enb : STD_LOGIC := '0'; signal ADDRAPROXY : STD_LOGIC_VECTOR(7 DOWNTO 0) := "00000001"; signal ADDRBPROXY : STD_LOGIC_VECTOR(7 DOWNTO 0) := (others => '0'); signal ADDR_RD_COUNTER : STD_LOGIC_VECTOR(7 DOWNTO 0) := (others => '0'); signal WRITE_COUNTER1 : STD_LOGIC_VECTOR(12 downto 0) := (others => '0'); signal WRITE_COUNTER2 : STD_LOGIC_VECTOR(12 downto 0) := (others => '0'); signal COUNTER : STD_LOGIC_VECTOR(29 downto 0) := (others => '0'); signal READOUT : STD_LOGIC_VECTOR(15 downto 0) := (others => '0'); --"1111111010110100"; --default data to test uart signal BUSY : STD_LOGIC := '0'; signal UART_COUNTER : STD_LOGIC_VECTOR(12 downto 0) := (others => '0'); signal UART_COUNTER2 : STD_LOGIC_VECTOR(12 downto 0) := (others => '0'); signal UART_COUNTER3 : STD_LOGIC_VECTOR(12 downto 0) := (others => '0'); signal RAM_COUNTER : STD_LOGIC_VECTOR(29 downto 0) := (others => '0'); --signal UART_COUNTER_SPACER : STD_LOGIC_VECTOR(12 downto 0) := (others => '0'); --signal UART_COUNTER4 : STD_LOGIC_VECTOR(12 downto 0) := (others => '0'); --signal UART_OFFON : STD_LOGIC := '0'; ---variable ROT_INCREASE : integer range 0 to 255 := 1; signal ROT_INCREASE : STD_LOGIC_VECTOR(7 downto 0) := x"01"; --signal UARTOUT : STD_LOGIC_VECTOR(15 downto 0) := "1111111010110100"; --default data to test uart without reading rotary --signal UARTOUT : STD_LOGIC_VECTOR(29 downto 0) := "111110101101001111111010110100"; --Y then Z -- WORKS signal UARTOUT : STD_LOGIC_VECTOR(199 downto 0) := (others => '1'); --debugging uart --signal UARTOUT : STD_LOGIC_VECTOR(29 downto 0) := "101010101010101010101010101010"; -- must map all 100 instances of rotary module here --TODO -- input a value for rotary (i.e. rotary1= 1, as 8 bit value) -- output that value somewhere else, and put it into uart -- output the value for rotary plus or minus into -- a specific 8 bit register. output into uart when called. -- After uart reads, erase these banks. -- OR, need to find how to write to RAM. Then I need to -- programatically write the rotary to a specific ram register -- then read the registers begin Inst_DUALRAM: DUALRAM PORT MAP ( clka => clk, ena => ena, wea => wea, addra => addra, dina => dina, douta => douta, clkb => clk, enb => enb, web => web, addrb => addrb, dinb => dinb, doutb => doutb ); Inst_ROTARY_1: rotary GENERIC MAP( RAMADDR => RAMADDR_1, ROTARYID => ROTARYID_1 ) PORT MAP( ROT => ROT, LED => open, clk => clk, DATAOUT => r_ROTO(1) ); Inst_ROTARY_2: rotary GENERIC MAP( RAMADDR => RAMADDR_2, ROTARYID => ROTARYID_2 ) PORT MAP( ROT => ROT2, LED => open, clk => clk, DATAOUT => r_ROTO(2) ); Inst_ROTARY_3: rotary GENERIC MAP( RAMADDR => RAMADDR_3, ROTARYID => ROTARYID_3 ) PORT MAP( ROT => ROT3, LED => open, clk => clk, DATAOUT => r_ROTO(3) ); Inst_ROTARY_4: rotary GENERIC MAP( RAMADDR => RAMADDR_4, ROTARYID => ROTARYID_4 ) PORT MAP( ROT => ROT4, LED => open, clk => clk, DATAOUT => r_ROTO(4) ); Inst_ROTARY_5: rotary GENERIC MAP( RAMADDR => RAMADDR_5, ROTARYID => ROTARYID_5 ) PORT MAP( ROT => ROT5, LED => open, clk => clk, DATAOUT => r_ROTO(5) ); Inst_ROTARY_6: rotary GENERIC MAP( RAMADDR => RAMADDR_6, ROTARYID => ROTARYID_6 ) PORT MAP( ROT => ROT6, LED => open, clk => clk, DATAOUT => r_ROTO(6) ); Inst_ROTARY_7: rotary GENERIC MAP( RAMADDR => RAMADDR_7, ROTARYID => ROTARYID_7 ) PORT MAP( ROT => ROT7, LED => open, clk => clk, DATAOUT => r_ROTO(7) ); Inst_ROTARY_8: rotary GENERIC MAP( RAMADDR => RAMADDR_8, ROTARYID => ROTARYID_8 ) PORT MAP( ROT => ROT8, LED => open, clk => clk, DATAOUT => r_ROTO(8) ); -- LEDWATCH <=READOUT(7 downto 0); --works -- this means i'm passing rotary values succesfully through -- to ROT1OUT and the rotary ID as well. --LEDWATCH <= READOUT(7 downto 0); --put everything in a process because why not. --try this out of the clock for starters... --RAMWRITE: process(r_ROTO(1)) ----variable ROT_INCREASE : integer range 0 to 255 := 1; --begin -- -- DINAPROXY <= ROT1OUT; -- ADDRAPROXY <= RAMADDR_1; -- ---- --if rising_edge(clk) then this breaks things ---- ROT_INCREASE <= x"01"; ---- --RAMADDR_CURRENT <= std_logic_vector(to_unsigned(ROT_INCREASE, RAMADDR_CURRENT'length)); ---- RAMADDR_CURRENT <= ROT_INCREASE; ---- --write to ram ---- dina <= r_ROTO(to_integer(unsigned(ROT_INCREASE))); ---- ADDRAPROXY <= RAMADDR_CURRENT; ---- --ROT_INCREASE := ROT_INCREASE + 1; ---- --end if; -- -- ---- ROT_INCREASE <= x"00"; ---- if ROT_INCREASE = x"00" then ---- ROT_INCREASE <= x"01"; ---- RAMADDR_CURRENT <= ROT_INCREASE; ---- dina <= r_ROTO(to_integer(unsigned(ROT_INCREASE))); ---- ADDRAPROXY <= RAMADDR_CURRENT; ---- elsif ROT_INCREASE = x"01" then ---- ROT_INCREASE <= x"02"; ---- RAMADDR_CURRENT <= ROT_INCREASE; ---- dina <= r_ROTO(to_integer(unsigned(ROT_INCREASE))); ---- ADDRAPROXY <= RAMADDR_CURRENT; ---- elsif ROT_INCREASE = x"02" then ---- ROT_INCREASE <= x"00"; ---- end if; ---- ---- ROT_INCREASE <= x"00"; ---- if ROT_INCREASE = x"00" then ---- ROT_INCREASE <= x"01"; ---- RAMADDR_CURRENT <= RAMADDR_1; ---- dina <= r_ROTO(1); ---- ADDRAPROXY <= RAMADDR_CURRENT; ------ elsif ROT_INCREASE = x"01" then ------ ROT_INCREASE <= x"02"; ------ RAMADDR_CURRENT <= RAMADDR_2; ------ dina <= r_ROTO(2); ------ ADDRAPROXY <= RAMADDR_CURRENT; ---- elsif ROT_INCREASE = x"01" then ---- ROT_INCREASE <= x"00"; ---- end if; ---- -- -- --ROT_INCREASE := ROT_INCREASE + 1; -- ---- for x in 1 to 50 loop ---- ROT_INCREASE := 2; ---- RAMADDR_CURRENT <= std_logic_vector(to_unsigned(ROT_INCREASE, RAMADDR_CURRENT'length)); ---- --write to ram ---- dina <= r_ROTO(ROT_INCREASE); ---- ADDRAPROXY <= RAMADDR_CURRENT; ---- --ROT_INCREASE := ROT_INCREASE + 1; ---- end loop; -- -- -- --end if; -- -- --maybe just add rot1out to a queue? or variable -- -- put it in its own process instead of clk? -- -- -- -- is this if statement needed here? I don't think so. -- -- I think the process(ROT1OUT) assumes it will run -- -- should anything change on ROT1OUT. or i could do an if with a explicit change detected i think -- --if ROT1PREV /= ROT1OUT and BUSY = '0' then --put in fms -- --if BUSY = '0' then --if some aren't detected, try removing busy... -- --BUSY <= '1'; -- -- --for loop: upon a change in anything, loop -- --through all values, and write to block ram -- --using variables -- -- -- --ADDRBPROXY <= RAMADDR_CURRENT; -- ---- ROT1PREV <= doutb; -- this needs to be somewhere else ---- READOUT <= doutb; -- RAM reads are all done at once -- -- in main read loop -- --BUSY <= '0'; -- --end if; -- --end process; -- --ALL RAM writes must be in same processper mikes electiceev forum --RAMWRITE2: process(r_ROTO(2)) --begin -- DINAPROXY <= ROT2OUT; -- ADDRAPROXY <= RAMADDR_2; --end process; rotarytop: process (clk) begin if rising_edge(clk) then --if rot1out goes to dinaproxy (which is not connected --to anything) then nothing happens. I htink it was over --writing the zs --this works if WRITE_COUNTER1 < 100000 then ADDRAPROXY <= x"8C"; WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; elsif WRITE_COUNTER1 < 8000000 then dina <= r_ROTO(to_integer(unsigned(ROT_INCREASE))); ADDRAPROXY <= ROT_INCREASE; WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; elsif WRITE_COUNTER1 = 10000000 then ROT_INCREASE <= ROT_INCREASE + 1; WRITE_COUNTER1 <= (others => '0'); else WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; end if; if ROT_INCREASE > x"09" then ROT_INCREASE <= x"01"; end if; -- if ROT_INCREASE = x"00" then -- ROT_INCREASE <= x"01"; -- end if; --NOT NEEDED. Be careful NOT to set ROT_INCREASE -- TO ZERO ON STARTUP. SET TO x"01". --this works -- if WRITE_COUNTER2 < 2 then -- WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; -- ADDRAPROXY <= x"8C"; -- ROT_INCREASE <= x"01"; -- elsif WRITE_COUNTER2 < 10 then -- dina <= r_ROTO(to_integer(unsigned(ROT_INCREASE))); -- ADDRAPROXY <= ROT_INCREASE; -- WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; -- --use elsif here to keep writes exclusive -- elsif WRITE_COUNTER2 < 12 then -- ADDRAPROXY <= x"8C"; -- ROT_INCREASE <= x"02"; -- WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; -- elsif WRITE_COUNTER2 < 25 then -- dina <= r_ROTO(to_integer(unsigned(ROT_INCREASE))); -- ADDRAPROXY <= ROT_INCREASE; -- WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; -- elsif WRITE_COUNTER2 < 30 then -- ADDRAPROXY <= x"8C"; -- WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; -- else -- WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; -- end if; -- -- if WRITE_COUNTER1 > 1000000 then -- WRITE_COUNTER2 <= WRITE_COUNTER2 + 1; -- WRITE_COUNTER1 <= (others => '0'); -- end if; -- if WRITE_COUNTER2 > 30 then -- WRITE_COUNTER2 <= (others => '0'); -- end if; -- if WRITE_COUNTER2 = 10 then -- --write to ram -- dina <= ROT1OUT; -- ADDRAPROXY <= RAMADDR_1; -- --RAMADDR_CURRENT <= RAMADDR_CURRENT + 1; -- end if; -- -- if WRITE_COUNTER1 = 500 then -- -- WRITE_COUNTER2 <= WRITE_COUNTER2 + 1; -- else -- WRITE_COUNTER1 <= WRITE_COUNTER1 + 1; -- end if; -- ALWAYS -- ena <= '1'; -- ram enable high always enb <= '1'; wea <= "1"; -- write port, never reads web <= "0"; -- read port, never writes addra <= ADDRAPROXY; --addrb <= ADDRBPROXY; TX(0) <= UARTOUT(0); -- DEBUG -- --LEDWATCH <= READOUT(7 downto 0); --check values are as expected --LEDWATCH <= ROT1PREV(7 downto 0); --rot1prev WORKING --LEDWATCH <= ROT1OUT(7 downto 0); --rot1out WORKING only 8 --LEDWATCH <= doutb(7 downto 0); --LEDWATCH(7 downto 4) <= READOUT(3 downto 0); -- LED(0) <= r_ROTO(1)(0); --green -- LED(1) <= r_ROTO(2)(0); --red -- LED(2) <= ADDRAPROXY(0); -- LED(3) <= WEA(0); -- LED(4) <= ROT_INCREASE(0); --green -- LED(5) <= ROT_INCREASE(1); --red -- LED(6) <= ROT_INCREASE(2); -- LED(7) <= ROT_INCREASE(3); --LED(0) <= r_ROTO(1)(0); --green --seems to be skipping on two sometimes --LED <= r_ROTO(2)(7 downto 0); LED <= r_ROTO(3)(7 downto 0); --case state is -- idle state -- -- when state_start => -- ROT1PREV starts as zero, state_RAM as soon as ROT1OUT change ---- if ROT1PREV /= ROT1OUT and BUSY = '0' and UARTOFF = '0' then -- state <= state_RAM; -- else -- state <= state_start; -- end if; ------------- RAM WRITE & READ ------------------ -- if rot1out has a change? -- put it in its own process instead of clk? -- if ROT1PREV /= ROT1OUT and BUSY = '0' then --put in fms -- -- BUSY <= '1'; -- -- dina <= ROT1OUT; -- addra <= RAMADDR_1; -- -- RAMADDR_CURRENT <= RAMADDR_1; -- addrb <= RAMADDR_1; -- -- ROT1PREV <= doutb; -- --in here -- -- then write new data output to uart -- -- put loops in to keep it to appropriate baud -- -- use a for loop (of say 1000 times) -- -- and a counter that counts up, then down -- -- as delay with an outer for loop -- -- that shifts bits out once every loop (nested loops) -- -- -- BUSY <= '0'; -- end if; -- RAM READ -- -- This will be separate process / module -- that will read constantly from all memory addresses -- and output the results to UART pin. need to time correctly. -- if RAM_COUNTER = 1000000 then -- ---- addrb <= RAMADDR_CURRENT; ---- READOUT <= doutb; -- -- -- "0000 0000 0000 0000 0000 0000 0000 00" -- -- xxxxxx8 5 1sxx xxxx x8 5 1s -- -- ---- UARTOUT(0) <= '0'; ---- UARTOUT(8 downto 1) <= READOUT(7 downto 0); ---- UARTOUT(15 downto 9) <= (others => '1'); --spacing to allow settling ---- UARTOUT(16) <= '0'; ---- UARTOUT(24 downto 17) <= READOUT(15 downto 8); ---- UARTOUT(29 downto 25) <= (others => '1'); -- RAM_COUNTER <= (others => '0'); -- else -- RAM_COUNTER <= RAM_COUNTER + 1; -- -- end if; -- BEWARE -- When using jumper leads with uart -- you can't go over like a few inches. -- or signal fails. BEWARE -- UART -- -- SHIFTING -- -- We always shift whatever is in the UART register out -- When we are idling, we fill UART register with 1's (idle high -- is normal). Every so often (further down), we read an address -- and output the value into UART register IF uart is on. -- Otherwise nothing happens. -- I think timing is wrong on this. need a scope. --if OUTPUTHIGH = '1' then --this broke things --if UART_COUNTER = 3333 then --9600 --if UART_COUNTER = 278 then -- 115200 --maybe too fast. maybe not. --if UART_OFFON = '0' then --this spaces out groups of bytes. not what I'm intending --uart offon didn't work. if UART_COUNTER = 555 then -- 57600 -- pretty good. --shift bit over every 3332 cycles or 9600 bits per second UARTOUT <= UARTOUT(0) & UARTOUT(199 downto 1); UART_COUNTER <= (others => '0'); UART_COUNTER3 <= UART_COUNTER3 + 1; --UART_COUNTER4 <= UART_COUNTER4 + 1; --with uartoffon else UART_COUNTER <= UART_COUNTER + 1; end if; --end if; --with uart offon -- --shift one sequence, then stop -- if UART_COUNTER4 = 1 then -- UART_OFFON <= '1'; -- end if; -- -- --idle high while uart_offon is high -- if UART_OFFON = '1' then -- UARTOUT <= (others => '1'); -- end if; -- for 9600 baud, or 3333 cycles -- --approach 1 -- if UART_COUNTER2 = 99990 then -- addrb <= RAMADDR_CURRENT; -- READOUT <= doutb; -- UARTOUT(0) <= '0'; -- UARTOUT(8 downto 1) <= READOUT(7 downto 0); -- UARTOUT(15 downto 9) <= (others => '1'); --spacing to allow settling -- UARTOUT(16) <= '0'; -- UARTOUT(24 downto 17) <= READOUT(15 downto 8); -- UARTOUT(29 downto 25) <= (others => '1'); -- UART_COUNTER2 <= (others => '0'); -- UART_COUNTER <= (others => '0'); -- else -- UART_COUNTER2 <= UART_COUNTER2 + 1; -- end if; RAM READ -- -- APPROACH 1 -- read all memory addresses, at the speed of if uart counter3 -- equals 90. -- for i in 0 to 99 loop -- addrb <= i; -- READOUT <= doutb; -- end loop; -- APPROACH 2 -- use counter -- if uartcOunter3=90 then -- addrb <= ADDRCOUNTER (starts at zero) -- READOUT <= doutb -- ADDRCOUNTER <= ADDRCOUNTER + 1; -- if ADDRCOUNTER = 100 then -- ADDRCOUNTER <= (others to 0) -- end if -- end if; --approach 2 for ram read --read ram for every three txout if UART_COUNTER3 = 199 then --after one sequence --if UART_OFFON = '0' then --only do if on addrb <= ADDR_RD_COUNTER; READOUT <= doutb; ADDR_RD_COUNTER <= ADDR_RD_COUNTER + 1; -- send two bits. one id, one value --UARTOUT(0) <= '0'; --UARTOUT(8 downto 1) <= READOUT(7 downto 0); --UARTOUT(15 downto 9) <= (others => '1'); --spacing to allow settling --UARTOUT(16) <= '0'; --UARTOUT(24 downto 17) <= READOUT(15 downto 8); --UARTOUT(29 downto 25) <= (others => '1'); -- send RotaryID and Count UARTOUT(3 downto 0) <= (others => '1'); --idle when high UARTOUT(4) <= '0'; --start bit UARTOUT(12 downto 5) <= READOUT(15 downto 8); -- ROTARYID first UARTOUT(102 downto 13) <= (others => '1'); --idle when high UARTOUT(103) <= '0'; --start bit UARTOUT(111 downto 104) <= READOUT(7 downto 0); -- ROTARYCOUNT second UARTOUT(199 downto 112) <= (others => '1'); --idle when high UART_COUNTER3 <= (others => '0'); --UART_COUNTER4 <= UART_COUNTER4 + 1; if ADDR_RD_COUNTER = 100 then ADDR_RD_COUNTER <= (others => '0'); end if; --end if; --end uartoffon end if; -- COUNTER <= COUNTER +1; -- if (COUNTER= 100000) then -- COUNTER <= (others => '0'); -- UART_OFFON <= '0'; -- UART_COUNTER4 <= (others => '0'); -- end if; -- if (COUNTER(12) = '1') then -- UART_COUNTER_SPACER <= UART_COUNTER_SPACER +1; -- end if; -- if (UART_COUNTER_SPACER = 4) then -- UART_COUNTER_SPACER <= (others => '0'); -- end if; end if; -- rising clk end end process;
Rotary.vhd
-------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; -- todo: IO of rotary needs communication -- need to store values in signals -- need to output signals with distinct ID (generic passed -- holds the ID number) -- one byte output, with 1 bits for left/right (either 1 or 0) -- 7 bits for rotary ID, one bit for stop (i.e. normal uart) --the idea will be. each rotary has a unique ID, and a unique --signal (ROTSIG1, ROTSIG2) in each instance --which will output direction bit and id bits. --there will be a uart to send out bits. Or preferably --some way to read registers (see hamsterbook) beyond uart. --there will be a busy flag, which all rotary instances share --and when a byte is being sent, other rotary instances wait --until it is cleared. after each sends a byte, it clears --it. each rotary stores the direction / id in a signal and --after outputting, it erases the direction. there will be --another flag to determine if data needs to be sent or not. --if dataoutputneededflag is 0, then no data is sent. --signals are specified in top level module and mapped in --instance. you can map the ports in the instance to signals. -- Outputs ROTARYCOUNT (7 to 0) -- and ROTARYID (15 to 8) -- to DATAOUT entity rotary is GENERIC( RAMADDR : STD_LOGIC_VECTOR(7 downto 0) := "11001100"; ROTARYID : STD_LOGIC_VECTOR(7 downto 0) := "01100110" ); PORT ( ROT : in STD_LOGIC_VECTOR(1 downto 0); LED : out STD_LOGIC_VECTOR(3 downto 0); --LEDWATCH : out STD_LOGIC_VECTOR(7 downto 0); clk : in STD_LOGIC; DATAOUT : out STD_LOGIC_VECTOR(15 downto 0) ); end rotary; architecture Behavioral of rotary is -- logic_vector takes double quotes for single num -- std_logic takes single for single num signal current : STD_LOGIC_VECTOR(1 downto 0); signal previous : STD_LOGIC_VECTOR(1 downto 0); signal target : STD_LOGIC_VECTOR(1 downto 0) := "00"; signal truetarget : STD_LOGIC_VECTOR(1 downto 0) := "00"; signal xortest : STD_LOGIC_VECTOR(1 downto 0); signal counter : STD_LOGIC_VECTOR(29 downto 0) := (others => '0'); signal shiftreg : STD_LOGIC_VECTOR(9 downto 0) := "1010110100"; signal counter2 : STD_LOGIC_VECTOR(29 downto 0) := (others => '0'); signal ROTARYCOUNT : STD_LOGIC_VECTOR(6 downto 0) := "0000000"; signal counter3 : STD_LOGIC_VECTOR(31 downto 0) := (others => '0'); signal ROTARYMEMWRITE : STD_LOGIC_VECTOR(15 downto 0) := x"0101"; signal counter4 : STD_LOGIC_VECTOR(29 downto 0) := (others => '0'); signal counter5 : STD_LOGIC_VECTOR(7 downto 0) := (others => '0'); signal writeenable : STD_LOGIC_VECTOR(0 downto 0) := (others => '0'); signal memoutput : STD_LOGIC_VECTOR(7 downto 0) := (others => '0'); constant CCWPIN : STD_LOGIC_VECTOR(1 downto 0) := "10"; constant CWPIN : STD_LOGIC_VECTOR(1 downto 0) := "01"; constant PINS : STD_LOGIC_VECTOR(1 downto 0) := "11"; begin -- hamster page 103 section 20.1 what is rs232 rotarywatch: process (clk) begin if rising_edge(clk) then DATAOUT(6 downto 0) <= ROTARYCOUNT; DATAOUT(7) <= '0'; DATAOUT(15 downto 8) <= ROTARYID; --LED(1) <= douta(0); --red --LED(0) <= wea(0); -- green --ena <= '1'; --if counter(8) = '1' then -- wea <= "1" ; -- this works. but lower down make sure it has -- enough time. EDIT: seems its easier to leave it up -- and pull it down for reads than vice versa. -- THIS WORKS -- note how i had to leave wea to write high occasionally -- otherwise it will allow enough time to read --end if; -- use ledwatch to check addra (CHECKEDOU T) -- then check dina (CHECKED OUT) -- then check wea (NOT CHANGING FAST ENOUGH) -- then check douta etc etc make sure it all lines up -- douta is not going high, last thing I did last night. --LEDWATCH(6 downto 0) <= douta(6 downto 0); --LEDWATCH(6) <= ena; --LEDWATCH(6) <= douta(0); --LEDWATCH(7) <= wea(0); -- how to write to mem? -- make write enable high -- specify address -- output to data in? -- need to read xapp463 which is the data sheet and -- copy the pins --LEDWATCH(7 downto 0) <= counter3(31 downto 24); --LEDWATCH(7 downto 6) <= previous; --LEDWATCH(5 downto 4) <= current; --LEDWATCH(3 downto 2) <= xortest; --LEDWATCH(1 downto 0) <= target; --LEDWATCH <= rotarycount; xortest <= current xor previous; --have it run constnatly -- so we don't have to worry when debugging whether target -- equals current if counter(0) = '1' then current(0) <= ROT(0); current(1) <= ROT(1); if current = target then if current = truetarget then --wea <= "1"; --this doesn't work. not enough time see above for better spot for wea if xortest = CCWPIN then --LED(2) <= '1'; ROTARYCOUNT <= ROTARYCOUNT-1; --dina <= rotarycount; end if; if xortest = CWPIN then --LED(3) <= '1'; -- leds are NOT reliable -- for any fast signals -- as these missed many ROTARYCOUNT <= ROTARYCOUNT+1; -- if write enabled -- address? --wea <= "1" ; --dina <= rotarycount; end if; --LED(2) <= '1'; --debug (yellow ch1) --wea <= "0"; enable this to only read when change end if; target <= PINS XOR target; --invert if equal to target (not truetarget) end if; previous <= current; --wea <= "0"; enable this to read at all times end if; if counter = 64000000 then --runs every 0.5 or .4 second --previous <= current; --debug only counter <= (others => '0'); --target <= PINS XOR target; works else counter <= counter+1; counter2 <= counter2+1; end if; if counter2 = 500000000 then counter2 <= (others => '0'); end if; end if; end process; end Behavioral;
Constraints/Pin Mapping
For Papilio FPGA.
NET ROT(0) LOC="P16" | IOSTANDARD=LVTTL; NET ROT(1) LOC="P17" | IOSTANDARD=LVTTL; #NET ROT2(0) LOC="P12" | IOSTANDARD=LVTTL; #NET ROT2(1) LOC="P15" | IOSTANDARD=LVTTL; NET TX(0) LOC="P12" | IOSTANDARD=LVTTL; #NET RX(0) LOC="P15" | IOSTANDARD=LVTTL; NET LED(0) LOC="P2" | IOSTANDARD=LVTTL; NET LED(1) LOC="P3" | IOSTANDARD=LVTTL; NET LED(2) LOC="P4" | IOSTANDARD=LVTTL; NET LED(3) LOC="P5" | IOSTANDARD=LVTTL; NET LED(4) LOC="P98" | IOSTANDARD=LVTTL; NET LED(5) LOC="P95" | IOSTANDARD=LVTTL; NET LED(6) LOC="P94" | IOSTANDARD=LVTTL; NET LED(7) LOC="P92" | IOSTANDARD=LVTTL; # no tabs? spaces only? NET clk LOC="P89" | IOSTANDARD=LVCMOS25; #NET LEDWATCH(0) LOC="P58" | IOSTANDARD=LVTTL; #NET LEDWATCH(1) LOC="P54" | IOSTANDARD=LVTTL; #NET LEDWATCH(2) LOC="P41" | IOSTANDARD=LVTTL; #NET LEDWATCH(3) LOC="P36" | IOSTANDARD=LVTTL; #NET LEDWATCH(4) LOC="P34" | IOSTANDARD=LVTTL; #NET LEDWATCH(5) LOC="P32" | IOSTANDARD=LVTTL; #NET LEDWATCH(6) LOC="P25" | IOSTANDARD=LVTTL; #NET LEDWATCH(7) LOC="P22" | IOSTANDARD=LVTTL; #NET DATAOUTUART LOC="P91" | IOSTANDARD=LVTTL; NET ROT2(0) LOC="P22" | IOSTANDARD=LVTTL; NET ROT2(1) LOC="P18" | IOSTANDARD=LVTTL; NET ROT3(0) LOC="P25" | IOSTANDARD=LVTTL; NET ROT3(1) LOC="P23" | IOSTANDARD=LVTTL; NET ROT4(0) LOC="P32" | IOSTANDARD=LVTTL; NET ROT4(1) LOC="P26" | IOSTANDARD=LVTTL; NET ROT5(0) LOC="P34" | IOSTANDARD=LVTTL; NET ROT5(1) LOC="P33" | IOSTANDARD=LVTTL; NET ROT6(0) LOC="P36" | IOSTANDARD=LVTTL; NET ROT6(1) LOC="P35" | IOSTANDARD=LVTTL; NET ROT7(0) LOC="P41" | IOSTANDARD=LVTTL; NET ROT7(1) LOC="P40" | IOSTANDARD=LVTTL; NET ROT8(0) LOC="P54" | IOSTANDARD=LVTTL; NET ROT8(1) LOC="P53" | IOSTANDARD=LVTTL;
Block RAM Settings
true dual port ram common clock y 16 write width 160 write depth operating mode (write or read first) - no change yes, using enable pins and load init file, write rest 0's.
Init hex File
You will also need an init Hex file to load in the Block RAM, in order to make it easy to see where the bits start and stop. The radix is 16 (so hex) and each two integers is one byte (e.g. 0x00, 0x01, would be the first two). This is what the UART outputs, without any data added. So you see 00, then 01. The 01 is an index, the 00 is the value of the rotary encoder (being 7 bits, so 0 - 127), and so on. This way you get the UART from FPGA to the micro with an index. It should look something like:
memory_initialization_radix=16; memory_initialization_vector= 0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009, 000A, 000B, 000C, 000D, 000E, 000F, 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017, 0018, 0019, 001A, 001B, 001C, 001D, 001E, 001F, 0020, 0021, 0022, 0023, 0024, 0025, 0026, 0027, 0028, 0029, 002A, 002B, 002C, 002D, 002E, 002F, 0030, 0031, 0032, 0033, 0034, 0035, 0036, 0037, 0038, 0039, 003A, 003B, 003C, 003D, 003E, 003F, 0040, 0041, 0042, 0043, 0044, 0045, 0046, 0047, 0048, 0049, 004A, 004B, 004C, 004D, 004E, 004F, 0050, 0051, 0052, 0053, 0054, 0055, 0056, 0057, 0058, 0059, 005A, 005B, 005C, 005D, 005E, 005F, 0060, 0061, 0062, 0063, 0064, 0065, 7A7A,
Resources
- Papilio FPGA While the Papilio is obsolete now, and you should probably start with a newer FPGA, it's still not a bad place to start.
- HamsterWorks
- http://web.archive.org/web/20190803104358/http://hamsterworks.co.nz/mediawiki/index.php/Main_Page
- EEVblog Forum The algorithm for the Rotary was from here.
- [1] Some basic vhdl/verilog/schematic entry examples for ISE. A good place to start, although I like HamsterNZ's pdf better. Still, some good info here on setup, and initialization. Good on these gentlemen for their use of CC-0 / Public Domain.
|