LinuxCNC-RIO - RealtimeIO for LinuxCNC based on FPGA (ICE40 / ECP5)

More
19 Jun 2025 11:34 #330497 by DMNZ
wow, great answer thank you. i was close in my thoughts, since there is no cpu and in rio.c you compile riocomp i thought that there must be something like fixed data frame structure adjusted for each config, but you made it very clear.
do you do anything about noise? like CRC (checksum) maybe for each frame in case it gets corrupted?

Please Log in or Create an account to join the conversation.

More
19 Jun 2025 17:42 #330523 by meister
no checksum for SPI, it's only for short distance. only the UDP and RS485/RS422 connections have checksum

Please Log in or Create an account to join the conversation.

More
20 Jun 2025 06:22 #330550 by meister
Here is an output from the generator using the Tangbob configuration as an example:

github.com/multigcs/riocore/tree/dev/doc/example-output/Tangbob

and here is a kind of documentation of the generated gateware:

www.multixmedia.org/Tangbob-Gateware/

Please Log in or Create an account to join the conversation.

More
20 Jun 2025 07:12 - 20 Jun 2025 07:12 #330552 by meister
here i can also show the structure of the frames or the rio.v:

this is the 'top' verilog module, the main module:

github.com/multigcs/riocore/blob/dev/doc...ngbob/Gateware/rio.v

there is a block in which the RX frames are split into individual 'output signals':
    // PC -> FPGA (330 + FILL)
    // assign header_rx = {rx_data[327:320], rx_data[335:328], rx_data[343:336], rx_data[351:344]};
    assign VAROUT128_MODBUS0_TXDATA = {rx_data[199:192], rx_data[207:200], rx_data[215:208], rx_data[223:216], rx_data[231:224], rx_data[239:232], rx_data[247:240], rx_data[255:248], rx_data[263:256], rx_data[271:264], rx_data[279:272], rx_data[287:280], rx_data[295:288], rx_data[303:296], rx_data[311:304], rx_data[319:312]};
    assign VAROUT32_PWMOUT0_DTY = {rx_data[167:160], rx_data[175:168], rx_data[183:176], rx_data[191:184]};
    assign VAROUT32_STEPDIR0_VELOCITY = {rx_data[135:128], rx_data[143:136], rx_data[151:144], rx_data[159:152]};
    assign VAROUT32_STEPDIR1_VELOCITY = {rx_data[103:96], rx_data[111:104], rx_data[119:112], rx_data[127:120]};
    assign VAROUT32_STEPDIR2_VELOCITY = {rx_data[71:64], rx_data[79:72], rx_data[87:80], rx_data[95:88]};
    assign VAROUT32_STEPDIR3_VELOCITY = {rx_data[39:32], rx_data[47:40], rx_data[55:48], rx_data[63:56]};
    assign VAROUT1_WLED0_0_GREEN = {rx_data[31]};
    assign VAROUT1_WLED0_0_BLUE = {rx_data[30]};
    assign VAROUT1_WLED0_0_RED = {rx_data[29]};
    assign VAROUT1_BITOUT0_BIT = {rx_data[28]};
    assign VAROUT1_BITOUT1_BIT = {rx_data[27]};
    assign VAROUT1_PWMOUT0_ENABLE = {rx_data[26]};
    assign VAROUT1_STEPDIR0_ENABLE = {rx_data[25]};
    assign VAROUT1_STEPDIR1_ENABLE = {rx_data[24]};
    assign VAROUT1_STEPDIR2_ENABLE = {rx_data[23]};
    assign VAROUT1_STEPDIR3_ENABLE = {rx_data[22]};
    // assign FILL = rx_data[21:0];

and a block in which the ‘input signals’ are assembled to the TX frame:
    // FPGA -> PC (349 + FILL)
    assign tx_data = {
        header_tx[7:0], header_tx[15:8], header_tx[23:16], header_tx[31:24],
        timestamp[7:0], timestamp[15:8], timestamp[23:16], timestamp[31:24],
        MULTIPLEXED_INPUT_VALUE[7:0], MULTIPLEXED_INPUT_VALUE[15:8],
        MULTIPLEXED_INPUT_ID[7:0],
        VARIN128_MODBUS0_RXDATA[7:0], VARIN128_MODBUS0_RXDATA[15:8], VARIN128_MODBUS0_RXDATA[23:16], VARIN128_MODBUS0_RXDATA[31:24], VARIN128_MODBUS0_RXDATA[39:32], VARIN128_MODBUS0_RXDATA[47:40], VARIN128_MODBUS0_RXDATA[55:48], VARIN128_MODBUS0_RXDATA[63:56], VARIN128_MODBUS0_RXDATA[71:64], VARIN128_MODBUS0_RXDATA[79:72], VARIN128_MODBUS0_RXDATA[87:80], VARIN128_MODBUS0_RXDATA[95:88], VARIN128_MODBUS0_RXDATA[103:96], VARIN128_MODBUS0_RXDATA[111:104], VARIN128_MODBUS0_RXDATA[119:112], VARIN128_MODBUS0_RXDATA[127:120],
        VARIN32_STEPDIR0_POSITION[7:0], VARIN32_STEPDIR0_POSITION[15:8], VARIN32_STEPDIR0_POSITION[23:16], VARIN32_STEPDIR0_POSITION[31:24],
        VARIN32_STEPDIR1_POSITION[7:0], VARIN32_STEPDIR1_POSITION[15:8], VARIN32_STEPDIR1_POSITION[23:16], VARIN32_STEPDIR1_POSITION[31:24],
        VARIN32_STEPDIR2_POSITION[7:0], VARIN32_STEPDIR2_POSITION[15:8], VARIN32_STEPDIR2_POSITION[23:16], VARIN32_STEPDIR2_POSITION[31:24],
        VARIN32_STEPDIR3_POSITION[7:0], VARIN32_STEPDIR3_POSITION[15:8], VARIN32_STEPDIR3_POSITION[23:16], VARIN32_STEPDIR3_POSITION[31:24],
        VARIN1_BITIN0_BIT,
        VARIN1_BITIN1_BIT,
        VARIN1_BITIN2_BIT,
        VARIN1_BITIN3_BIT,
        VARIN1_BITIN4_BIT,
        3'd0
    };
    

in the case of the Tangbob, the two frames ‘rx_data’ and ‘tx_data’ are passed to the w5500 module, which then takes care of the data exchange with the host computer:
    w5500 #(
        .MAC_ADDR({8'hAA, 8'hAF, 8'hFA, 8'hCC, 8'hE3, 8'h1C}),
        .IP_ADDR({8'd192, 8'd168, 8'd10, 8'd194}),
        .NET_MASK({8'd255, 8'd255, 8'd255, 8'd0}),
        .GW_ADDR({8'd192, 8'd168, 8'd10, 8'd1}),
        .PORT(2390),
        .BUFFER_SIZE(BUFFER_SIZE),
        .MSGID(32'h74697277),
        .DIVIDER(0)
    ) w55000 (
        .clk(sysclk),
        .mosi(PINOUT_W55000_MOSI_RAW),
        .miso(PININ_W55000_MISO),
        .sclk(PINOUT_W55000_SCLK_RAW),
        .sel(PINOUT_W55000_SEL_RAW),
        .intr(1'd0),
        .rx_data(rx_data),
        .tx_data(tx_data),
        .sync(INTERFACE_SYNC)
    );

Now let's take a look at the signals from one of the Stepdir module instances:

the input/output signals have the following naming scheme: VAR[DIRECTION][SIZE]_[MODULE_INSTANCE]_[SIGNALNAME]

* VAROUT32_STEPDIR0_VELOCITY : a 32 bit output value for the specified velocity (steprate)
* VAROUT1_STEPDIR0_ENABLE : a 1 bit output value to activate the stepper (enable signal)
* VARIN32_STEPDIR0_POSITION : a 32 bit input value to return the current position (step counter)

    stepdir #(
        .PULSE_LEN(108),
        .DIR_DELAY(18)
    ) stepdir0 (
        .clk(sysclk),
        .step(PINOUT_STEPDIR0_STEP_RAW),
        .dir(PINOUT_STEPDIR0_DIR_RAW),
        .velocity(VAROUT32_STEPDIR0_VELOCITY),
        .enable(VAROUT1_STEPDIR0_ENABLE & ~ERROR),
        .position(VARIN32_STEPDIR0_POSITION)
    );


the pin signals have the following naming scheme: PIN[DIRECTION]_[MODULE_INSTANCE]_[PINNAME]

however, they can also have suffixes, such as. _RAW / _INVERTED / _DEBOUNCED / ...
this is due to the modifiers pipeline that can be configured for each pin.
Last edit: 20 Jun 2025 07:12 by meister.

Please Log in or Create an account to join the conversation.

More
21 Jun 2025 08:35 #330625 by DMNZ
ok. i think i got it. you create a bus with width of the data frame, clock the data in/out via SPI, and map each peripheral to a certain bits in the bus to use as input/output control registers. very elegant, simple and lightweight, the only inconvenience it needs driver rebuild for peripherals changes.

compared to mesa which uses memory map with peripherals mapped to addresses in the memory and need cpu parsing to read write addresses. but this allows dynamic configuration of the driver.
The following user(s) said Thank You: meister

Please Log in or Create an account to join the conversation.

More
21 Jun 2025 15:42 #330638 by PCW
compared to mesa which uses memory map with peripherals mapped to addresses in the memory and need cpu parsing to read write addresses. but this allows dynamic configuration of the driver.

I should note that a CPU is not required with HostMot2 and no interface CPU is  used in PCI, SPI or EPP
interfaced configurations. Interface CPUs are only used with  Ethernet, USB, and serial interfaced
configurations to support the more complex wire protocol.

 
The following user(s) said Thank You: tommylight, meister

Please Log in or Create an account to join the conversation.

More
22 Jun 2025 00:48 - 22 Jun 2025 01:30 #330667 by DMNZ
yes. sorry. i did not mean to compare anything in a bad way. i only meant Ethernet. in fact, RIO also needs CPU for ethernet, it just placed outside of fpga in UDP2SPI Bridge with STM32.
i hope i got the difference in data frame at least, RIO has fixed static data frame while Mesa has dynamic data frame configured by reading IDROM at the driver initialization.
Last edit: 22 Jun 2025 01:30 by DMNZ.

Please Log in or Create an account to join the conversation.

More
22 Jun 2025 12:15 #330683 by meister
small correction , udp2spi is outdated, and the W5500 is controlled directly by the FPGA

Please Log in or Create an account to join the conversation.

More
22 Jun 2025 14:00 #330691 by epineh
I'm glad you guys know what you're talking about, most days I'm happy if I tie my shoelaces correctly 

Any hoo, I was wondering about shift registers, in that should I use hardware means to clear all outputs? I haven't had a lot to do with them but I understand that the output shift registers can have undefined states at startup and possibly turn on randomly. For the most part probably not an issue but if you are using them for things that shouldn't turn on at startup, tool release, turret clamp, Z Axis brake, spindle gearbox and so on. My friend (Babinda01 on here) has mentioned using a simple resistor/cap combination connected to the shift register clear pin to reset the output registers at power up, which sounds like a good idea instead of burning up a pin from the FPGA.

Also just checking that I can assign ModBus pins for output for a second FPGA and also additional ModBus pins for, well normal Modbus things? My understanding is the ModBus to the second FPGA is just for doing that, and not for adding traditional ModBus devices, so I should design a second "standard" Modbus connection.

Cheers.
Russell.

Please Log in or Create an account to join the conversation.

More
22 Jun 2025 16:15 #330707 by meister
in general, FPGAs and microcontrollers also have undefined outputs at startup, so there should be a pullup or donw resistor on each one (all are floating by default).

in the case of the shift register, i'm not sure right now :(
they are always set to output and i thought that they are always set to 0 at first.

you can use as many of the same plugins next to each other as you want and as much space as you have :)

for modbus (frame IO), it should always be on the master FPGA,
I'm not sure if it works properly on a slave.
For encoders with index I have to check again, because there is an inout pin.

the uart plugin for connecting several FPGAs doesn't have much to do with modbus, it can simply use RS485, so that's not a problem either.
The following user(s) said Thank You: epineh

Please Log in or Create an account to join the conversation.

Time to create page: 0.202 seconds
Powered by Kunena Forum