Implementation of a Phase Accumulator in a CPLD - Page 1
Forum section: electronics / general

author: CM800
author_rank: Frequent Contributor
timestamp: October 14, 2016, 02:27:57 pm
content: I'm attempting to implement a phase accumulator in a CPLD,I have done some reading into Phase accumulators as I was recommended to by a forum member, right now I'm trying to implement it and I'm a bit stuck....I have managed to reduce down the maths to be done in the CPLD to:Fout * 85.91065, giving me the phase accumulator increment value.I have two questions right now from here:What is the best way for me to implement this multiplication in the CPLD (can you do it in one clock cycle, I havn't went beyond addition yet)How is the best way to check the phase accumulator has overflowed. Do I just need to check the last bit / 33rd bit?Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity Tests isPort ( CLK: in STD_LOGIC;LED_0: out STD_LOGIC;LED_1: out STD_LOGIC;PULSE_OUT: out STD_LOGIC;SW_0: inSTD_LOGIC;SW_1: inSTD_LOGIC);end Tests;architecture Behavioral of Tests issignal pulse: STD_LOGIC:= '0';signal phaseAcc: unsigned(31 downto 0) := (others => '0');signal increment: unsigned(31 downto 0) := (others => '0');signal outputFreq: unsigned(31 downto 0) := (others => '0');beginPULSE_OUT <= pulse;counter: process(CLK)beginif rising_edge(clk) thenincrement = outputFreq * 85.91065 -- Not sure how best to do this?if phaseAcc thenpulse <= not pulse;phaseAcc <= (others => '0');end if;phaseAcc <= phaseAcc + increment;end if;end process;end Behavioral;The goal is to be able to instruct the drive what frequency to output a pulsetrain at to drive a stepper motor.

author: suicidaleggroll
author_rank: Super Contributor
timestamp: October 14, 2016, 03:11:29 pm
content: You should seriously reconsider the use of a float in programmable logic. It's possible, but not recommended. What is "85.91065"? What does it represent? Is there a better way of handling this with simple integers (or better yet, bit shifting)?

author: rstofer
author_rank: Super Contributor
timestamp: October 14, 2016, 03:12:38 pm
content: You have the right idea but calculating the increment is done elsewhere and certainly not in a clocked process. FPGAs have 1 cycle multipliers, I can't imagine a CPLD having such a thing.Your higher level control will probably calculate the increment and feed it to a register in the CPLD. You will push the value in just before commanding a speed change. High speed SPI is a nice way to do this. Besides, you will probably be pushing 3 values, one for each axis.There may be a lookup table involved with calculating the increment with some kind of interpolation. ANYTHING to avoid dealing with floating point on a chip that doesn't have an FPU.Ordinarily, Mach3 issues the pulses because it knows very well how to do phase accumulation to control speed.Some part of your project needs to figure out how many steps to take in each axis and come up with a way to get all those steps synchronized so that the endpoint is reached simultaneously. This is where the 3 different increments comes in.I don't know that a 32 bit phase accumulator is long enough. You need to work through the math for very low speeds and see how it comes out. Suppose you needed to take just one step in Y while traversing the entire X range. You would want that step dead center in the X motion.I'm pretty sure these types of projects have already been done. Aren't any available via Google?

author: CM800
author_rank: Frequent Contributor
timestamp: October 14, 2016, 03:21:24 pm
content: I've done some more work on this code and got a basic phase accumulator working:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity Tests isPort ( CLK: in STD_LOGIC;LED_0: out STD_LOGIC;LED_1: out STD_LOGIC;PULSE_OUT: out STD_LOGIC;SW_0: inSTD_LOGIC;SW_1: inSTD_LOGIC);end Tests;architecture Behavioral of Tests issignal pulse: STD_LOGIC:= '0';signal phaseAcc: unsigned(32 downto 0) := (others => '0');signal increment: unsigned(31 downto 0) := (others => '0');signal outputFreq: unsigned(31 downto 0) := (others => '0');beginPULSE_OUT <= pulse;counter: process(CLK)beginif rising_edge(clk) thenif(SW_0 = '1') thenphaseAcc <= phaseAcc + to_unsigned(1099656, 32);elsephaseAcc <= phaseAcc + to_unsigned(549828, 32);end if;if phaseAcc(32) = '1' thenpulse <= not pulse;phaseAcc <= (others => '0');end if;end if;end process;end Behavioral;Right now, I'm trying to figure out how best to do the multiplication, or if I should do it before the acceleration in the MCU?I suppose we could avoid the floating point by multiplying it up to whole integers.the 85.91065 is a number derrived from the clock frequency (50MHz) over the phase accumulator length (2^32) giving me the resolution of each bit. in seconds, then recipricated.Fout = (increment / 2^n)*Fclockrearranges toincrement = Fout / (Fclock/2^n)Substitute in Fclock = 50,000,000 & n = 32increment = Fout / 0.01164increment = (1/0.01164) * Foutincrement = 85.91065 * FoutQuote from: rstofer on October 14, 2016, 03:12:38 pmI'm just working on a single axis motion controller as my HNC project, I'd do a 3 axis one but 1 axis is more advanced of a project then half the HNC class combined... (think arduino alarm systems...)You suggested about putting the increment in before a speed change... I'm a bit stuck on this.I'm still trying to work out the implementation of how my MCU will control the CPLD. It seems other projects get the controller to tell the CPLD or FPGA an acceleration or jerk value and monitors the velocity / position accordingly.My end goal is to have a unit that I can give it max velocity and acceleration, then tell it to go to a position or at a speed and it accelerates and decelerates accordingly.The maths seem fine for low speeds, if I want to go at 0.25rpm and the motor is 200 counts per rev then the increment would be 71.59.In vertially all applications, drives run at 128,000 steps per revolution or so.The resolution of the 32 bit accumulator is 0.01164Hz if my maths is correct (following Wiki's equation)

author: Kalvin
author_rank: Super Contributor
timestamp: October 14, 2016, 04:39:45 pm
content: Typically one doesn't reset the phase accumulator when it overflows as the phase accumulator typically implements modulo arithmetic. The pulse output can be taken directly from the MSB of the phase accumulator:Code:PULSE = 0PHASEACC = 0PHASEINC = calculated phase increment needed for the wanted output frequency.at each rising edge do:PHASEACC = PHASEACC + PHASEINC # Modulo-arthmetics so the overflow is actually desiredPULSE = most significant bit of PHASEACCYou can change the output frequency by setting different values to PHASEINC. You can increase or decrease the frequency resolution by using more or fewer bits in the PHASEACC. For example, using a 50MHz clock and 32-bit PHASEACC you will achieve ouput resolution of 50MHz / 2^32 = 0.01Hz. With a 24-bit PHASEACC you can achieve 50Mhz / 2^24 = 3Hz resolution.Here is the wikipedia article:https://en.wikipedia.org/wiki/Numerically_controlled_oscillator

author: CM800
author_rank: Frequent Contributor
timestamp: October 14, 2016, 05:52:54 pm
content: Quote from: Kalvin on October 14, 2016, 04:39:45 pmYes, that's the article I got the most clues from in implementing this.Thanks for pointing out the lack of a need to clear the PhaseACC... I missed that. I'm going to implement that nowFinally got home so I'm setting up gear with my scope on an output too.EDIT:I'm getting a very nice and clean 10kHz and 20kHz square wave....however if I remove the code that clears the accumulator, I get this:

author: rstofer
author_rank: Super Contributor
timestamp: October 14, 2016, 07:40:25 pm
content: Those traces look like you have gated a clock or something with the logic output.Consider a phase accumulator that counts to 10.First clock : add 2 -> count is 2Second clock : add 2 -> count is 4Third clock : add 2 -> count is 6Fourth clock : add 2 -> count is 8Fifth clock : add 2 -> count is 0 and overflowNext clock : add 2 -> count is 2and so on - it comes out even and divides by 5, exactlyNow try 4First clock : add 4 -> count is 4Second clock : add 4 -> count is 8Third clock : add 4 -> count is 2 and overflowNext clock : add 4 -> count is 6Next clock : add 4 -> count is 0 and overflowWith such a low number of bits, there is some noticeable jitter (10 / 4 doesn't come out even) but the bottom line is we just never clear the accumulator unless we decide to change the speed. Even then, I'd have to think about it.Try your hand with adding 5. It will work out even and device the clocks by 2.2*n doesn't get involved as long as n is long enough to get the resolution and low frequencies.It's just add, add, add ... and watch for overflows to trigger the next step.We want a LOT of adds before even the faster output frequencies which is why we want a high clock frequency and a long phase accumulator.

author: CM800
author_rank: Frequent Contributor
timestamp: October 14, 2016, 08:59:05 pm
content: Quote from: rstofer on October 14, 2016, 07:40:25 pmPer your suggestion I tried this:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity Tests isPort ( CLK: in STD_LOGIC;LED_0: out STD_LOGIC;LED_1: out STD_LOGIC;PULSE_OUT: out STD_LOGIC;SW_0: inSTD_LOGIC;SW_1: inSTD_LOGIC);end Tests;architecture Behavioral of Tests issignal pulse: STD_LOGIC:= '0';signal phaseAcc: unsigned(64 downto 0) := (others => '0');signal increment: unsigned(63 downto 0) := (others => '0');signal outputFreq: unsigned(31 downto 0) := (others => '0');beginPULSE_OUT <= pulse;counter: process(CLK)beginif rising_edge(clk) thenif(SW_0 = '1') thenphaseAcc <= phaseAcc + to_unsigned(189632529100000000, 64);elsephaseAcc <= phaseAcc + to_unsigned(73786976290000000, 64);end if;if phaseAcc(64) = '1' thenpulse <= not pulse;--phaseAcc <= (others => '0');end if;end if;end process;end Behavioral;So it has a 64bit accumulator (65th bit is overflow detection, I think I'm doing it right yeah?)I turned of the phase accumulate clearing code.I recalculated the settings for 200kHz @ 2^64 accumulator length... and lo behold, I get a 12.5MHz messy triangle wave (likely just the terrible signal cables I'm using)When I press my button to switch to another frequency, nothing changes. Maybe I've made a mistake in the code there, or I'm doing something wrong to your suggestion?EDIT:Good news! I've fixed the issue and managed to remove the 'clear accumulator' code.Turns out it was the extra bit I was using to detect the overflow wasn't needed, I was looking at it wrong, the below code works perfectly:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity Tests isPort ( CLK: in STD_LOGIC;LED_0: out STD_LOGIC;LED_1: out STD_LOGIC;PULSE_OUT: out STD_LOGIC;SW_0: inSTD_LOGIC;SW_1: inSTD_LOGIC);end Tests;architecture Behavioral of Tests issignal pulse: STD_LOGIC:= '0';signal phaseAcc: unsigned(31 downto 0) := (others => '0');signal increment: unsigned(31 downto 0) := (others => '0');signal outputFreq: unsigned(31 downto 0) := (others => '0');beginPULSE_OUT <= phaseAcc(31);counter: process(CLK)beginif rising_edge(clk) thenif(SW_0 = '1') thenphaseAcc <= phaseAcc + to_unsigned(2216495, 32);elsephaseAcc <= phaseAcc + to_unsigned(48883160, 32);end if;end if;end process;end Behavioral;

author: rstofer
author_rank: Super Contributor
timestamp: October 14, 2016, 10:19:57 pm
content: Now you have the idea! Simple code is way better than complicated code.Eventually, you will pull the increment code outside the clocked process. Maybe you will have a register that receives the new value and holds it. Then some part of the process will clock the received value into the actual increment register. This all needs to be timed out so that changing speed is bumpless. In fact, you may want to interpolate between the existing increment and the new increment in several (dozens) of steps to control acceleration. Or maybe something up stream deals with that by sending a lot of 'new' increments.Anyway, you're on the right track.

author: CM800
author_rank: Frequent Contributor
timestamp: October 14, 2016, 10:33:03 pm
content: Quote from: rstofer on October 14, 2016, 10:19:57 pmGreat!I intend to do some experimenting on that front.Could you suggest how I would go about organizing my files, creating 'modules' / components. I've found examples in Verilog but not VHDL, I want to try organize my code.For example, I want to make a phase-accumulator component, an SPI interface component, an encoder componentand so on... and how to bring them all together on the topfile.I haven't found much in googling to try organize code efficiently and allow for portability too.

author: rstofer
author_rank: Super Contributor
timestamp: October 15, 2016, 12:40:26 am
content: Your components can be in separate files and simply instantiated into your other filesHere is the instantiation of the Coldstart gadget as it appears in my top level file. These instantiations are placed at the bottom of my source file.Code:Inst_Coldstart: entity work.Coldstart PORT MAP(Reset=> Reset,Clk=> Clk,ColdstartReq=> ColdstartReq,ColdstartAck=> ColdstartAck,ColdstartAddr=> ColdstartAddr,ColdstartData=> ColdstartData,ColdstartWr=> ColdstartWr,ColdstartHold=> ColdstartHold);The coldstart code itself is in another file (Coldstart.vhd) and looks like:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.STD_LOGIC_ARITH.ALL;use IEEE.STD_LOGIC_UNSIGNED.ALL;use work.IBM1130_PACK.ALL;-- Uncomment the following library declaration if instantiating-- any Xilinx primitives in this code.--library UNISIM;--use UNISIM.VComponents.all;entity Coldstart isPort ( Reset: inSTD_LOGIC;Clk: inSTD_LOGIC;ColdstartReq: outSTD_LOGIC;ColdstartAck: inSTD_LOGIC;ColdstartAddr: outSTD_LOGIC_VECTOR (15 downto 0);ColdstartData: outSTD_LOGIC_VECTOR (15 downto 0);ColdstartWr: inoutSTD_LOGIC;ColdstartHold: outSTD_LOGIC);end Coldstart;architecture Behavioral of Coldstart issignal AddressCounter: std_logic_vector(15 downto 0);type STATE_TYPE is (START, REQ, ACK, DONE);signal State, NextState : STATE_TYPE;type ADDR_COUNTER_CMD is (CMD_NOP, CMD_INC);signal AddressCounterControl: ADDR_COUNTER_CMD;beginColdstartAddr<= AddressCounter;ColdstartWr<= '1';ColdstartHold<= not AddressCounter(7);-- we're done when AddressCounter == 128process(Clk, Reset, NextState)beginif Reset = '1' thenState<= START;elsif rising_edge(Clk) thenState <= NextState;end if;end process;process(Clk, Reset, AddressCounterControl, AddressCounter)beginif Reset = '1' thenAddressCounter <= (others => '0');elsif rising_edge(Clk) thencase AddressCounterControl iswhen CMD_NOP=> null;when CMD_INC=> AddressCounter <= AddressCounter + 1;when others=> null;end case;end if;end process;process(State, AddressCounter)beginColdstartReq<= '0';AddressCounterControl<= CMD_NOP;case State iswhen START=> NextState <= REQ;when REQ=> ColdstartReq <= '1';NextState <= ACK;when ACK=> ColdstartReq <= '1';if ColdstartAck = '1' thenAddressCounterControl <= CMD_INC;if AddressCounter(7 downto 0) = x"7F" thenNextState <= DONE;elseNextState <= START;end if;elseNextState <= ACK;end if;when DONE=> NextState <= DONE;when others=> NextState <= START;end case;end process;Inst_ColdstartROM: entity work.ColdstartROM PORT MAP(Addr => AddressCounter(6 downto 0),Data => ColdstartData);end Behavioral;If you look, you will see where there is an instantiation of the ColdstartROM inside the Coldstart.vhd file.The file ColdstartROM.vhd looks like this (snipped for brevity)Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.STD_LOGIC_ARITH.ALL;use IEEE.STD_LOGIC_UNSIGNED.ALL;library UNISIM;use UNISIM.VComponents.all;entity ColdstartROM is port (Addr : in STD_LOGIC_VECTOR ( 6 downto 0);Data : out STD_LOGIC_VECTOR (15 downto 0));end ColdstartROM;architecture Behavioral of ColdstartROM isbeginROM128X1_inst_F : ROM128X1generic map (INIT => X"0000000000008000D1FD77C93FF4097D")port map (O => Data(15),A0 => Addr(0),A1 => Addr(1),A2 => Addr(2),A3 => Addr(3),A4 => Addr(4),A5 => Addr(5),A6 => Addr(6));<snipped>ROM128X1_inst_0 : ROM128X1generic map (INIT => X"0000000000001255A6B1E64D00008378")port map (O => Data(0),A0 => Addr(0),A1 => Addr(1),A2 => Addr(2),A3 => Addr(3),A4 => Addr(4),A5 => Addr(5),A6 => Addr(6));end Behavioral;Note that all of my instantiations specify that the files are in 'work'. In other words, they are in the project tree and, in my case, are all in the only source directory.The problem with most tutorials is that they don't hit the core issues. They nibble away at one detail or another without ever building a multi-file project that is totally complete. It's a 'learn-as-you-go' kind of deal.So, this is the way I learned to do it! I'm sure there are others.BTW, investigate .PACK files if you want to store all your parameters and constants in the 'package' file. I did that for my CPU project and I'm not convinced it was a good idea. It does help when more than one source file needs access to the constants but I included constants that are only used in one file and I shouldn't have done that.Here's how you add a .PACK file:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.STD_LOGIC_ARITH.ALL;use IEEE.STD_LOGIC_UNSIGNED.ALL;use work.IBM1130_PACK.ALL;Here's what the .PACK file looks like:Code:library IEEE;use IEEE.STD_LOGIC_1164.all;package IBM1130_PACK istype AFR_CtrlType is (AFR_NOP, AFR_LOAD, AFR_COMP);type ACC_CtrlType is (ACC_NOP, ACC_LOAD, ACC_AND, ACC_OR, ACC_EOR,ACC_SHIFT_LEFT, ACC_SHIFT_RIGHT, ACC_XCHG);type A_BUS_CtrlType is (A_BUS_NOP, A_BUS_SAR, A_BUS_SBR, A_BUS_ACC,A_BUS_EXT, A_BUS_TAR, A_BUS_IAR, A_BUS_AFR,A_BUS_RamData, A_BUS_TAG, A_BUS_DISP,<snip>-- register addresses for UARTconstant RXR: std_logic_vector(2 downto 0) := "000";constant TXR: std_logic_vector(2 downto 0) := "000";constant IER: std_logic_vector(2 downto 0) := "001";constant LCR: std_logic_vector(2 downto 0) := "011";constant MCR: std_logic_vector(2 downto 0) := "100";constant LSR: std_logic_vector(2 downto 0) := "101";end IBM1130_PACK;

author: CM800
author_rank: Frequent Contributor
timestamp: October 15, 2016, 01:34:47 pm
content: Quote from: rstofer on October 15, 2016, 12:40:26 amthanks for that!Let's give it a go adding an SPI module to control it from my arduino Due.

author: CM800
author_rank: Frequent Contributor
timestamp: October 17, 2016, 03:53:40 pm
content: I think I've fell in love with CPLDs, wow they are great for waveform stuff, you can do some stuff that would take ages to work out on an MCU (let alone getting it to work reliably)really quickly on a CPLD:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity Tests isPort ( CLK: in STD_LOGIC;LED_0: out STD_LOGIC;LED_1: out STD_LOGIC;PULSE_OUT: out STD_LOGIC;PULSE_OUT2: out STD_LOGIC;PULSE_OUT3: out STD_LOGIC;SW_0: inSTD_LOGIC;SW_1: inSTD_LOGIC);end Tests;architecture Behavioral of Tests issignal pulse: STD_LOGIC:= '0';signal direction: STD_LOGIC:= '0';signal phaseAcc: unsigned(31 downto 0) := (others => '0');signal phaseAcc2: unsigned(31 downto 0) := to_unsigned(2863311531,32);signal phaseAcc3: unsigned(31 downto 0) := to_unsigned(1431655765,32);signal increment: unsigned(31 downto 0) := (others => '0');signal outputFreq: unsigned(31 downto 0) := (others => '0');beginPULSE_OUT <= phaseAcc(31);PULSE_OUT2 <= phaseAcc2(31);PULSE_OUT3 <= phaseAcc3(31);counter: process(CLK)beginif rising_edge(clk) thenif(SW_0 = '1') thenphaseAcc <= phaseAcc + to_unsigned(4295, 32);else--phaseAcc <= phaseAcc + to_unsigned(20615843, 32);end if;phaseAcc2 <= phaseAcc2 + to_unsigned(4295, 32);phaseAcc3 <= phaseAcc3 + to_unsigned(4295, 32);end if;end process;end Behavioral;

author: rstofer
author_rank: Super Contributor
timestamp: October 17, 2016, 06:01:14 pm
content: CPLDs or, even better, FPGAs are as close to magic as it gets!

author: CM800
author_rank: Frequent Contributor
timestamp: October 17, 2016, 07:37:52 pm
content: Quote from: rstofer on October 17, 2016, 06:01:14 pmI figure I'll ask here, unless you think it's worth opening up another thread?What would be the best way of implementing SPI on such a unit, while allowing me to have some form of register addressing system?I need to access a number of registers (haven't worked them all out yet) from SPI and assign / read values from them from 32 bits to 64 bits

author: hamster_nz
author_rank: Super Contributor
timestamp: October 17, 2016, 10:02:00 pm
content: SPI is a relatively simple protocol, esp if you don't want to implement all of the features.Although it is in Verilog,http://fpga4fun.com/SPI.htmlshould give you ideas.Mike

author: rstofer
author_rank: Super Contributor
timestamp: October 17, 2016, 10:10:14 pm
content: Just write a FSA to implement the slave end of SPI. You start when CS' goes low and you have your choice of the 4 different configurations of CPOL and CPHA:https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_BusBasically, the master will send one 'block' (whether that is a byte, short or 32 bit int is up to you) which will indicate whether to read or write and which register address. Immediately after that, the master will send 'blocks' of data. If it is a read operation, you need to transfer the current contents of some register. If it is a write operation, you need to transfer the incoming block to some register. You might consider allowing the 'address' to increment (and wrap around) following each block. That way the master could send a write command to address 0, say, and then write consecutive blocks to all of the registers in one long transaction.I would just have one incoming shift register and use the address from the command to direct the final shifted value to the proper register after it is totally received. Same story with shifting the output.You can generate time delays by having the command and address bits be less than a block. When you receive the command, it will be in the first bits received followed by the address bits followed by fill bits. During the fill bits you can get things organized.

author: rstofer
author_rank: Super Contributor
timestamp: October 17, 2016, 10:13:30 pm
content: Remember that the shift clock (SCK) and the MISO, MOSI and CS' signals are crossing a clock domain. Typically, these are synchronized by passing them through 2 serial D flops clocked by the CPLD clock. Research 'crossing clock domains'.

author: Someone
author_rank: Super Contributor
timestamp: October 18, 2016, 05:23:21 am
content: Quote from: CM800 on October 14, 2016, 03:21:24 pmIt possibly time to reassess your scope, since the fundamental skills/knowledge are still missing for you to start such a project. An Arduino sketched alarm is not such a silly thing to implement and contains many real world problems they will need to solve, sounds like a better scope than your ambitious project.

author: CM800
author_rank: Frequent Contributor
timestamp: October 18, 2016, 09:49:44 am
content: Quote from: Someone on October 18, 2016, 05:23:21 amI disagree with you, strongly.I've done Arduino alarm system before, I have pretty much done everything there is to be done with the alarm system concept in the first year (Computer linked, access logging, multiple POA etc.) while the rest of the class were learning the Arduinos (I have a few years on them for this)The HNC course has two projects infact the other one is an alarm, (One project is your personal project, the other is a group project) I chose the group project to be an alarm as it's something I can take an advisory / backseat on and help my classmate learn and do the programming, else I'd finish it in an evening.I chosen this as I would learn a good few things. I'd already had some experiance with CPLDs and drag-drop logic circuits in them, I'm now more familier with VHDL, I'm only asking some of these questions because there is no point re-inventing the wheel (I'd rather go the right direction once then the wrong direction 3 times before finding the best method of implementation.)I admit I may be taking a little offence to your post, I'd say I know what I'm capable of and I've set myself a reachable goal.

author: rstofer
author_rank: Super Contributor
timestamp: October 18, 2016, 03:09:05 pm
content: I think writing the SPI slave will be a good deal more difficult than implementing a master. There are example projects on OpenCores.org. Here's one:http://opencores.org/project,spi_master_slaveMost developers shy away from code at OpenCores and probably with good reason. Nevertheless, it is a start. There are several other projects.The screenshot of the waveforms with shifted phase is interesting. Now change the frequencies individually while keeping the phase shift and post the screen shots. I haven't gotten around to working with a phase accumulator so I am interest in how it is working out.

author: CM800
author_rank: Frequent Contributor
timestamp: October 18, 2016, 03:27:48 pm
content: Quote from: rstofer on October 18, 2016, 03:09:05 pmThat would be quite interesting to play with actually..The phase can simply be calculated by taking the number of bits 2^n and diving it, e.g. 120 degrees would be 1/3*(2^n)I'd imagine to change the frequency while keeping the phase would require you to reset the phase start position at each change.. or do some form of funky maths there... I'll have a little look into it. If anything it could be neat to add a PWM stage to the phases and allow frequency control, could make a nice little 3 phase BLDC driver.

author: rstofer
author_rank: Super Contributor
timestamp: October 18, 2016, 03:54:27 pm
content: I was't thinking about PWM in this context. Given what you have, once you change the frequency of an accumulator, all phase information gets washed away sooner or later. Since the original topic was frequency synthesis, I was more interested in that aspect than in phase shift. If you had a variable increment based on some other logic/counter, you could create an FM signal of some kind. In fact, if you just incremented the increment, you could watch the frequency resolution on the scope's frequency counter. Just something to play with while you consider the SPI slave.PWM could certainly be done but more typically, frequency isn't varied, just pulse width. I'm pretty sure simple on and off counters will work well given a sufficiently high clock rate.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 18, 2016, 06:07:45 pm
content: Quote from: CM800 on October 18, 2016, 03:27:48 pmResetting the phase is the worst thing you can do..... see the little runt pulse:Code:| Runt pulse going from 8 to 6V due to phase accumulator reset000011110000111100001000111000111000111000111Far better to either:Hold on to the 'step' value until the end of the cycle (which can cause a delay going from very slow to very fastCode:| Change happens here but is delayedV until long/slow cycle is complete000000000011111111110000000000111111111101010101010101But this can cause a latchup if you load "0" into your phase-to-add-per-cycle register.Or just start running with the new value and keep the current phase value:Code:| Change happens here and takesV effect immediately00000000001111111111000000000101010101010101

author: CM800
author_rank: Frequent Contributor
timestamp: October 18, 2016, 09:41:51 pm
content: This evening I spent some time reorganizing my code and getting more familiar with actual VHDL rather then Digital Logic tech.Debounce.vhdCode:library IEEE;use IEEE.STD_LOGIC_1164.ALL;entity Debounce isPort (CLK : in STD_LOGIC;x : in STD_LOGIC;DBx : out STD_LOGIC);end Debounce;architecture Behavioral of Debounce istype State_Type is (S0, S1);signal State : State_Type := S0;signal DPB, SPB : STD_LOGIC;signal DReg : STD_LOGIC_VECTOR (7 downto 0);beginprocess (CLK, x)variable SDC : integer;constant Delay : integer := 50000;beginif CLK'Event and CLK = '1' then-- Double latch input signalDPB <= SPB;SPB <= x;case State iswhen S0 =>DReg <= DReg(6 downto 0) & DPB;SDC := Delay;State <= S1;when S1 =>SDC := SDC - 1;if SDC = 0 thenState <= S0;end if;when others =>State <= S0;end case;if DReg = X"FF" thenDBx <= '1';elsif DReg = X"00" thenDBx <= '0';end if;end if;end process;end Behavioral;Test.vhd - Top-Level EntityCode:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity Tests isPort ( CLK: in STD_LOGIC;SW_0: inSTD_LOGIC;SW_1: inSTD_LOGIC;LED_0: out STD_LOGIC;LED_1: out STD_LOGIC;PULSE_OUT: out STD_LOGIC);end Tests;architecture Behavioral of Tests is-- COMPONENTScomponent DebouncePort (CLK : in STD_LOGIC;x : in STD_LOGIC;DBx : out STD_LOGIC);end component;-- SIGNALSsignal SW_0D: STD_LOGIC:= '0';signal led_toggle: STD_LOGIC:= '0';begin-- ASSIGNMENTSLED_0 <= led_toggle;-- COMPONENT INSTANCESu1: Debounceport map(CLK => CLK,x => SW_0,DBx => SW_0D);-- MAINcounter: process(CLK, SW_0D)beginif rising_edge(SW_0D) thenled_toggle <= not led_toggle;end if;end process;end Behavioral;Just a simple toggle code with a push button input. What would you say about this in terms of space efficiency and how the code is organized?Looking for a little feedback, I figure other's thoughts couldn't hurt on what I've done to help improve my progress.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 18, 2016, 11:10:01 pm
content: Am I correct in the design for the debouncer was:a) Sample the input signal once every 50,000 clock cyclesb) Set output when you have sampled eight '1's in a row.c) If the output is set, unset it soon as you sample a '0' on the input.If that was what you wanted, that is pretty much what you got!A noisy / bouncy / intermittent switch should still work, but could cause double-bounce if noise happens after 400,000 cycles (which could be < 10ms at 50MHz) . - but better than it not working at all!400,000 cycles from pushing the button till it does something is pretty long, depending on your clock speed.I don't much care for using 'integer' types - I would use a more primative UNSIGNED type, and the two-state state machine - it is overthinking it a bit.A small niggle - you do not synchronise the async input signal, so there is a minuscule chance of this misbehaving if Dreg(0) goes metastable.The bit I don't like is that you are using the output of the debounce as the clock signal within the top level.You should use the same clock for this as the one you are using to sample the input (keeping it all in the same clock domain) - something like:Code:clk_proc: process(clk)beginif rising_edge(clk) thenif SW_0D = '1' and old_value = '0' thenled_toggle <= not led_toggle;end ifold_value <= SW_0D;end if;end process;What you are doing is just fine if the led_toggle signal is only used external to the CPLD, but if you were using it within the same design to control stuff you would have to resynchronise it back into the main clock domain.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 19, 2016, 12:37:49 am
content: Here is a ''near enough' debounce solution, that samples every 2^16 cycles - every time that 'counter' rolls over.It has to sample eight '1's in a row before the output goes high, and the output goes low when the first '0' is sampled.Code:...signal synced_button : std_logic := '0';signal counter : unsigned(15 downto 0) := (others =>'1');signal samples : std_logic_vector(7 downto 0);begin-- The MSB of the 'samples' shift register is the outputdebounced_button <= samples(samples'high);process(clk)beginif rising_edge(clk) then-- sample every 2^16 cyclesif counter = 0 thenif synced_btn = '1' thensamples <= sample(samples'high-1 downto 0) & '1';elsesamples <= (others => '0');end if;end if;counter <= counter+1;synced_button <= button;end if;end process;If you wanted to be picky you could use the carry out logic, and save the logic that implements compare against zero, but that gets ugly.

author: rstofer
author_rank: Super Contributor
timestamp: October 19, 2016, 12:42:56 am
content: Here's a variant on the debounce idea. Basically, it counts clocks just like all the rest EXCEPT that if it detects a bounce, it resets the count to 0Apparently I can't attach a .vhd file so it is now .txt

author: CM800
author_rank: Frequent Contributor
timestamp: October 19, 2016, 11:48:56 am
content: Quote from: rstofer on October 18, 2016, 03:09:05 pmI decided to have a play around with changing the frequency of two accumulators in phase. Naturally the phase angle between them changes. (if you update them both at the same time, there is no difference. It's a function of time between the updates as to how much the phase changes between them.Even if you try play catchup by changing the other frequency to match the original changed one you won't restore it.I'm going to play around with making a method to keep the phase the same with the frequency update.http://puu.sh/rOcCu/d987408251.bmphttp://puu.sh/rOcD0/fb56297b00.bmphttp://puu.sh/rOcDj/283166a14d.bmphttp://puu.sh/rOcDx/db1efb44c0.bmpPlaying Catchup:http://puu.sh/rOcDS/46d8507092.bmpPressing them both together: (notice the phase measurement)http://puu.sh/rOcGy/cd629eceb0.bmphttp://puu.sh/rOcH0/e3a9e58c88.bmpThen a slight stagger:http://puu.sh/rOcHJ/7b50b8a162.bmp

author: Kalvin
author_rank: Super Contributor
timestamp: October 19, 2016, 12:09:55 pm
content: Quote from: hamster_nz on October 19, 2016, 12:37:49 amAnother solution would be modify the code that it will shift the samples into an eight bit shift register and output "1" when all eight samples are "1", output "0" when the all eight samples are "0", otherwise the output will hold its state.

author: CM800
author_rank: Frequent Contributor
timestamp: October 19, 2016, 12:46:17 pm
content: Quote from: hamster_nz on October 18, 2016, 11:10:01 pmOw! that's something I hadn't considered at all, I havn't been thinking much about clock domains, it's something I'm a little scared of still, trying to keep track of it all. I'll take that into consideration.I was using said switch to add to the accumulator, think that could have caused any issues? (I didn't see any overly wackey behavior at least.Since I am operating the addition of the accumulator on the rising edge, would I want to change the increment on the falling edge to prevent any strange occorences?

author: rstofer
author_rank: Super Contributor
timestamp: October 19, 2016, 01:45:57 pm
content: You will eventually want to receive all the new values over SPI and then update the registers at the same time and that time will be somehow related to the value in the phase accumulator or at least the number of steps required.In the end, frequency isn't what we're measuring, it's steps. Frequency gets us there at a particular feed rate, including ramps, but it's steps that get us to the proper position before we make the next move.Which brings up acceleration or ramps. You should probably do that internal to the phase accumulator. You may decide there is a certain acceleration slope you would like to use but it gets more hairy when there are multiple axes. Side issue: how odd that the word axes is the plural of ax, axe and axis. It's the only word in the English language that is the plural of 3 words.You could also investigate S curve acceleration. Perhaps Google CNC Interpolation.

author: CM800
author_rank: Frequent Contributor
timestamp: October 19, 2016, 02:58:31 pm
content: Quote from: rstofer on October 19, 2016, 01:45:57 pmIf you agree that it's a wise idea, I will have the MCU pass an acceleration value to the CPLD, which will in turn add it to the incrementer every TxTx seems to be where my problems lie, Ideally Tx would be the same as the accumulator increment clock, however I have concerns about how the MCU will keep track of everything and update the unit.else it won't slow down in time if the acceleration isn't changed at just the right times. (down to 20ns)The only way I can think of fixing this is to have 2 registers, one that I put the new value in, the other is a timestamp at which time or position it should be updated....or have the CPLD control -everything- motion control, meaning you'd input the type of motion you want (Velocity control, Acceleration control, Position input) then set the parameters and press fire.I could see this needing a much larger CPLD, even an FPGA to do such a thing, it may be too over complicated for a single axis motion controller? It also brings up questions of how to handle the user wanting to suddenly change the target speed or max position while it is moving.S-curve is just adding MORE complexity, I'd like to investigate that, but I'd rather look into it after I've got a 'first-gen' finished.

author: rstofer
author_rank: Super Contributor
timestamp: October 19, 2016, 03:38:48 pm
content: I haven't spent a lot of time on the CNC thing and I only vaguely remember details of the early NC machines (circa '69). I use Mach3 to send pulses (I'm just guessing) over TCP/IP to a SmoothStepper. Or, it could be sending entire motion packets. I don't really know what SmoothStepper does other than emulate a bunch of parallel ports. The SmoothStepper is based on an FPGA.There is an Arduino project to implement a CNC controller capable of interpreting G codes. It's basically the entire package for a multi-axis machine. In addition to studying up on CNC interpolation, the project code might be worth studying:http://blog.protoneer.co.nz/arduino-cnc-shield/Part of the G-code is feedrate and if they truly implement G-code, circular interpolation will be included in addition to simple linear interpolation.The phase accumulator increment is supposed to work such that all 3 (or 4 or 5) axes reach their endpoint at the same time. So, somehow, there is a ratio between the increments such that the machine can cut ramps and circles. The only part of that interpolation I remember is that our controllers used a mercury delay line for memory and the Bendix Dynapath controller had swing-out gates with card racks. Lots and lots of card racks. I think they got 2 flops per PCB. All transistor logic...And, yes, this is probably a job for an FPGA. But maybe not just yet and for a demo project of a single axis, the CPLD may be entirely adequate.

author: CM800
author_rank: Frequent Contributor
timestamp: October 19, 2016, 04:09:15 pm
content: Quote from: rstofer on October 19, 2016, 03:38:48 pmreading into the GRBL (Gerbal?) controller it seems they run pre-computed movements from a 'step segment buffer', using a bresenham algorithem (still reading into how they are doing it)QuotePrecomputed velocity movements. If only I had a few spare axis laying about, I'd see if I could use the CPLD as a direct standin for the IRQ instead. That could be interesting.It seems to be a far-from ideal solution, hence why all the gerbal controlled cnc units are quite noisy buggers.FYI The code is here:Code:ISR(TIMER1_COMPA_vect){// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISRif (busy) { return; } // The busy-flag is used to avoid reentering this interrupt// Set the direction pins a couple of nanoseconds before we step the steppersDIRECTION_PORT = (DIRECTION_PORT & ~DIRECTION_MASK) | (st.dir_outbits & DIRECTION_MASK);// Then pulse the stepping pins#ifdef STEP_PULSE_DELAYst.step_bits = (STEP_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.#else // Normal operationSTEP_PORT = (STEP_PORT & ~STEP_MASK) | st.step_outbits;#endif// Enable step pulse reset timer so that The Stepper Port Reset Interrupt can reset the signal after// exactly settings.pulse_microseconds microseconds, independent of the main Timer1 prescaler.TCNT0 = st.step_pulse_time; // Reload Timer0 counterTCCR0B = (1<<CS01); // Begin Timer0. Full speed, 1/8 prescalerbusy = true;sei(); // Re-enable interrupts to allow Stepper Port Reset Interrupt to fire on-time.// NOTE: The remaining code in this ISR will finish before returning to main program.// If there is no step segment, attempt to pop one from the stepper bufferif (st.exec_segment == NULL) {// Anything in the buffer? If so, load and initialize next step segment.if (segment_buffer_head != segment_buffer_tail) {// Initialize new step segment and load number of steps to executest.exec_segment = &segment_buffer;#ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING// With AMASS is disabled, set timer prescaler for segments with slow step frequencies (< 250Hz).TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (st.exec_segment->prescaler<<CS10);#endif// Initialize step segment timing per step and load number of steps to execute.OCR1A = st.exec_segment->cycles_per_tick;st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow.// If the new segment starts a new planner block, initialize stepper variables and counters.// NOTE: When the segment data index changes, this indicates a new planner block.if ( st.exec_block_index != st.exec_segment->st_block_index ) {st.exec_block_index = st.exec_segment->st_block_index;st.exec_block = &st_block_buffer[st.exec_block_index];// Initialize Bresenham line and distance countersst.counter_x = st.counter_y = st.counter_z = (st.exec_block->step_event_count >> 1);}st.dir_outbits = st.exec_block->direction_bits ^ dir_port_invert_mask;#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING// With AMASS enabled, adjust Bresenham axis increment counters according to AMASS level.st.steps = st.exec_block->steps >> st.exec_segment->amass_level;st.steps = st.exec_block->steps >> st.exec_segment->amass_level;st.steps = st.exec_block->steps >> st.exec_segment->amass_level;#endif} else {// Segment buffer empty. Shutdown.st_go_idle();bit_true_atomic(sys_rt_exec_state,EXEC_CYCLE_STOP); // Flag main program for cycle endreturn; // Nothing to do but exit.}}

author: rstofer
author_rank: Super Contributor
timestamp: October 19, 2016, 06:10:35 pm
content: The idea of motion segments is where this has been heading from the beginning. Phase accumulation for frequency synthesis is very interesting but frequency is a side issue for CNC. The axes could all have different acceleration slopes (set by the user in Mach3) and yet they all manage to output pulses in the exact instant as part of the 3 dimensional motion. A single G1 command can move all 5 axes (if the machine has 5 axes) and expect everything to work out.So, now that dealing with motion segments is laid out, maybe your idea of implementing it in a CPLD is the way to go. Then let the Arduino (I would prefer ARM) do the number crunching to build up the motion segments.

author: CM800
author_rank: Frequent Contributor
timestamp: October 19, 2016, 06:49:20 pm
content: Quote from: rstofer on October 19, 2016, 06:10:35 pmI have been thinking about this all the way home on my moped... nearly hit a car when I realized this direction of following grbl won't help memaking motion segment packets is all good for CNC as you don't do any interruptions while it's operating. I guess I never specified this in full detail when we began discussing, Here is a project summery:QuoteThe unit needs to be a real time motion controller that is coupled with a PLC like implementation on the MCU, possibly using an arduino as a co-processor for the user to program in entirety. The user at any time could change the destination position, set it to go at a specific velocity etc and the motion needs to follow as so. This is where I'm focusing on.

author: rstofer
author_rank: Super Contributor
timestamp: October 19, 2016, 08:01:20 pm
content: Quote from: CM800 on October 19, 2016, 06:49:20 pmThe user at any time could change the destinationisn't supported by physics. It simply isn't possible to be running full speed in some direction and make a sudden left turn. The previous motion command would need to be complete before another command could be accepted. Sure, it is possible to abort a previous command but some deceleration is still going to be required. Unless mass is VERY low.We had this very problem in the early days of NC where the programmers weren't thinking clearly about deceleration into a corner. They were trying to make a right turn with a gantry weighing tens of tons with hundreds of cutting horsepower. Of course there was overshoot! And a lot of equipment damage...Following error, that's the trick! There was a lag between commanded position and actual position. That difference was measured and used to control the hydraulic servo valves, PID like. This automatically takes care of acceleration and deceleration. I have no idea how to use that concept with stepper motors.

author: CM800
author_rank: Frequent Contributor
timestamp: October 19, 2016, 08:16:18 pm
content: Quote from: rstofer on October 19, 2016, 08:01:20 pmYou took that too literally, this is already a system implemented on all motion controllers.You set an acceleration and deceleration for your motion controllerif you command a velocity, it accelerates up to said velocity, then if you command a new one, it decelerates down to the velocity.If you set the controller to go to position A in one direction, then tell it to go to position B in the other direction, it will decelerate and accelerate in the opposite direction.Sorry if I'm not stating it clearly enough, I forget not everyone is in the same field as me (I'm primarily a motion control engineer)Stepper motors operate on a simple 'you input a pulse, I move one pulse' if you put too many pulses in without acceleration, it stalls. Quite simple really.I'll be adding in closed loop later, but for now that won't be needed (I'd implement the closed loop in the MCU anyway, I don't fancy doing PI loops in a CPLD / FPGA yet.)The actual project I'm working on for my HNC will be used for automation primarily, it's not for CNC machining(only one axis)An example might be that a user has an endstop on a bandsaw with a stepper motor, the stepper motor moves it in and out so that he can enter the distance it wants to be at.The user would program my project to home to a known distance on power up and follow commands from a RS422 port for example on where to go.Or maybe it's taking advantage of the analog input, following the position to the potentiometer, the position controlling, for example, a gantry crane arm:The user has programmed the drive to scale the 0-1023 / 0-4096 counts to the full distance (possibly with a filter) so the potentiometer is a scale version of the gantry,the gantry will then follow along and accelerate at a programmed speed verified to be safe for the unit.These are the kind of applications the company that pays for my HNC course comes across, we currently distribute products for this, but they are often over-complicated / over featured for what is needed, this is where my unit fits in. (We have controllers suitable for 64axis linear motor arrays for nano-motion / automation, but nothing for more run-of-the-mill applications.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 19, 2016, 09:00:44 pm
content: It is always interesting to see how following the 'obvious' solution in this project (generating pulses at a given frequency) is leading to lots of interesting discussion of why the obvious solution is the wrong one.If I was attempting this, I would have five 'ideal' curves - acceleration, change to constant speed, constant speed, change to constant deceleration, deceleration, each being precisely mapped out (so for example you how long both time and distance it takes to decelerate from 1m/s to stopped).The planning software would conceptually join these three curves (offsetting the middle three stages in the Y axis ), aiming to minimizing the length of the constant speed segment but still keeping it below the maximum speed. You will end up with a consistent end-to-end path, where the acceleration ramps up and then down smoothly, a wee bit of constant speed if needed, and the deceleration ramps up and down as it comes to rest. This path can then be integrated to give the desired position at a given time, which is the profile for your motion.This gives the profile for the move (based in turns of the encoder or whatever) and your PID controller can then just aim to have that many turns at that point in time.

author: CM800
author_rank: Frequent Contributor
timestamp: October 19, 2016, 09:35:33 pm
content: Quote from: hamster_nz on October 19, 2016, 09:00:44 pmIt is indeed quite interesting. The big thing is, may people have went about it different ways with different results it seems.Some of the ultra-high-end motion controllers seem to do it the way I've discussed, using a processor to do on the fly calculations and control the frequency either open loop, imagining ideal world, or closed loop where it's also running a loop with position feedback.Other controllers are implemented on a pulse-by-pulse basis, it calculates where it is, where it needs to be and decides if to do a step or not (very processor intensive)I think it may be best just to 'have a go' at it. I think it'd be worth trying a solution I've read about a few times first, where the controller has influence over the acceleration and has a 'view' on time, velocity and position.An example process would be:User Instructs it to go to position 'X' with a Maximum Velocity, Acc/Decceleration.VProcessor checks on current position & velocityProcessor calculates the times and accelerations then inserts them into PLD registers.VProcessor instructs the PLD to 'go', resetting the time to 0 and 'activating' the registers.VUser changes the position again so it suddenly needs to go the other direction to Position 'G'VCycle begins again.I think this makes the most sense and seems realistic in terms of how an open loop motion controller is implemented in practice.Also, a note with stepper motors for your interest, often you may start at a non-zero velocity, 'jumping' over some parts of the acceleration because of resonant bands.Quotehttps://www.pmdcorp.com/resources/articles/23-------------------------------------------------------------Closed loop.In a closed loop system with encoder feedback, I'd suggest that the same system is implemented, but the unit (probibly the MCU or a co-processor) monitors the feedback position and the output (ideal) position, looking at the difference, then passing that through a PI loop and into the Acceleration (through some kind of 'top up' register that is added at the same time as the phase-accumulator increment)This is just contemplation for future tests to do.

author: Someone
author_rank: Super Contributor
timestamp: October 19, 2016, 11:42:10 pm
content: Quote from: CM800 on October 18, 2016, 09:49:44 amOffence intended, you're looking down on other peoples projects (which are probably well scoped) while at the same time relying heavily on the generous assistance you're getting here to make progress on your project. Even a trivial project can be done to excellence and assessment should be able to separate those apart, alarms are not a one night project and something you've already done completely the sky's the limit for robust code and fault tolerance.You're jumping right into hardware when the usual design approach for programmable logic is to start with high level design, then move into simulation and sanity checking of resource usage. A fixed point motion controller is a substantial project, I'm guessing you've bitten off much more than you can complete based on your failure to grasp the fundamentals, but its your funeral when time for delivery comes.

author: CM800
author_rank: Frequent Contributor
timestamp: October 20, 2016, 07:14:43 am
content: Quote from: Someone on October 19, 2016, 11:42:10 pmIgnoring the documentation, you could write up a full arduino based alarm system in one evening. If I really felt I had to prove it to you, I would do it too, however as I am doing an alarm project on the side with a classmate for the other part of the course, I think he'd be a bit irritated if I did all the work overnight, leaving nothing for him to do / learn. (Really clever with analog electronics, no experience with digital / MCUs, he wants to take the programming role to learn more.)I admit I am getting assistance on here (no shit that's what a forum is for) I wouldn't say I'm relying on it heavily, I'm very grateful to Hamster and rstofer for their input so far. The hand in date is in a years time, to put it in perspective, my initial specification is due in next week. So I've got plenty of time. And y'know what? Even if I need to stretch it to two years development, That's also fine, I could even get more marks for the writeup if I take it onto the foundation degree to finish the project.I have nobody else to bat ideas at and see if they stick or fall, at an HNC level I can't find anyone else to provide suggestions, discuss logic implementation or learn from, I'd consider a failure of education. At HNC level, the most we will be doing is Karnaugh maps, the same thing I did in GCSE level... GCSE, A-Level, BTEC and HNC, the only difference is the amount of writeup and the latter three go over inductor / capacitor theory (and around 3 lessons of programming) You can't rely on the education system these days to teach you at a pace you are comfortable with (certainly faster then right now...) so I've been taking it into my own hands. The only point CPLDs are even mentioned is where we used them to 'prototype logic gate circuits' just because it's quicker then doing it on prototype board.Quote from: Someone on October 19, 2016, 11:42:10 pmI know I'm jumping right into the hardware. That's for good reason, It's so I can learn quicker and pick up more tricks of the trade with PLDs, when it comes to the finial implementation I intend to draw out the whole design and re-implement it in clean solid code then test it fully for predictability before sizing up the CPLD / FPGA for the board.At the end of the day, yes sure, it may be a challenge, but I feel I will get more out of this then redoing any project I've done before and I'll learn A LOT more on the journey. Look at it this way, 3 weeks in and I've already got enough familiarity with VHDL to write it without copying, most of my time is being spent learning the logic theory. The only place I'm going to learn half this stuff is here and online, the only reason I'm in the HNC course is just to get the next part of paper to move on to 'level up' to full university to get better instruction on all this... and even then I don't know if I will be able to get there, tuition is insanely expensive in UK these days compared to what it used to.At the end of the day, there's only one thing I can do and that is prove you wrong, so that is what I will do.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 20, 2016, 09:33:22 am
content: Just because I've been thinking about x^2 in a different context (signal acquisition in GPS) and how if you add the series of odd numbers (1,3,5,7.9) you get the squares (1,4,9,16,25...) - this ties into constant acceleration d=a/2*t^2 - yeah, I'm that lameIt sounds like the idea is to drive a stepper motor in an open loop system (just counting pulses to keep track of absolute position)If you chose your time scale appropriately you can really simplify things, to the point it can all be calculated with minimal hardware.For each time step you chose the first of five applicable options that doesn't cause the distance required to stop to exceed the distance left to travel- Accelerate by one step per unit of time towards the target, if it doesn't exceed the max speed- Stay at the current speed- Decelerate by one step per unit of time.- Do nothing (because you are stopped)And when you change speed along the way you calculate the distance left to stop on the fly - if accelerating, add the speed over that this period to the time to stop, if decelerating then subtract the speed over that time period from the time to stop.The logic then becomes very, very simple - and now you have a phase accumulator you can generate an integer number of pulses over a known unit of time - the control for acceleration is changing how often the phase accumulator roles over, and all the maths stays simple, easy integer.

author: CM800
author_rank: Frequent Contributor
timestamp: October 20, 2016, 09:56:29 am
content: Quote from: hamster_nz on October 20, 2016, 09:33:22 amInteresting.... Admittedly I hadn't noticed that before. I haven't had nearly as much time to play with numbers and applications as I'd like.That's an interesting way of looking at it, I see where your coming from. It certainly warrents an investigation.Unfortunately I left my dev kits at home last night so I can't do much work on this today.My next plan is to trial what I posted further up and document it. I also need to do some actual paperwork for the course

author: rstofer
author_rank: Super Contributor
timestamp: October 20, 2016, 03:51:50 pm
content: Quote from: hamster_nz on October 20, 2016, 09:33:22 amIn effect, implement the ramp output given in the sketch above leaving the acceleration ramp slope but varying the height and width. Some moves won't ever get to the plateau in speed.Here is an interesting paper on interpolation for an FPGA:http://www.iaeme.com/MasterAdmin/UploadFolder/DESIGN%20AND%20IMPLEMENTATION%20OF%203%20AXIS%20LINEAR%20INTERPOLATION%20CONTROLLER%20IN%20FPGA%20FOR%20CNC%20MACHINES%20AND%20ROBOTICS/DESIGN%20AND%20IMPLEMENTATION%20OF%203%20AXIS%20LINEAR%20INTERPOLATION%20CONTROLLER%20IN%20FPGA%20FOR%20CNC%20MACHINES%20AND%20ROBOTICS.pdf

author: CM800
author_rank: Frequent Contributor
timestamp: October 20, 2016, 05:13:29 pm
content: Quote from: rstofer on October 20, 2016, 03:51:50 pmCheers for the paper, it's an interesting read so far, just going through it now, it holds some rather useful information in it!

author: rstofer
author_rank: Super Contributor
timestamp: October 21, 2016, 11:13:13 pm
content: That paper is pretty spot-on. I wonder if the Zynq 7000 wouldn't be a perfect platform for this kind of thing. There is an ARM core to do all the user interface and computation and enough of an FPGA to implement the motion control. That the core runs Linux is probably not required but, well, everything else that is embedded seems to.http://store.digilentinc.com/zybo-zynq-7000-arm-fpga-soc-trainer-board/

author: CM800
author_rank: Frequent Contributor
timestamp: October 22, 2016, 08:21:48 am
content: Quote from: rstofer on October 21, 2016, 11:13:13 pmIf this was soley a project for my own entertainment I would love to go for a Zynq, however I'm hoping after the HNC is finished to have a project that can be turned into a product with the company in the future, this results in me hoping to keep the BOM as low as possible, ideally under £10 for 1k off I'm currently thinking a MAX 10 FPGA or MAX V CPLD with some generic ARM chip. It could be interesting looking at microsemi's SoCs..