ev3dev / ev3dev

ev3dev meta - bug tracking, wiki and releases
http://www.ev3dev.org
GNU General Public License v2.0
631 stars 85 forks source link

Motor synchronisation for constant speed ratio #853

Open ndward opened 7 years ago

ndward commented 7 years ago

This message will make the case that EV3dev badly needs a function that synchronises two motor motions such that the two motors have a constant and correct speed ratio, thereby also causing the stopping of the motors to be synchronised. This should not be confused with the synchronised starting of motor movements which was the subject of issue 454. Also, motor regulation should not be confused with the synchronisation of two motors.

Most EV3-compatible languages (EV3-G, RobotC, EV3 Basic, LeJos…) have a function or multiple functions that offer motor synchronisation and it works very well. The motors turn with the requested speed ratio, turn through the correct angles and stop simultaneously. But because EV3dev/EV3 Python does not have such a feature it is, I believe, impossible or very difficult to achieve these results (correct speed ratio, correct angles, simultaneous stop). That's because individually-controlled EV3 motors usually do not turn with the requested speed. This was discussed in depth in EV3 Python issue 220 https://github.com/rhempel/ev3dev-lang-python/issues/220 . Summarising that issue, EV3 motors tend to turn slower than requested for high values of speed_sp (assuming starting from a standstill, in which case the inertia of the motor stops the motor from having the requested speed at the beginning of this motion). For low values of speed_sp the EV3 motors tend to turn faster than requested. This is to do with the difficulty of measuring slow speeds accurately over very small time intervals, though that does not fully explain why there is a systematic tendency for the motors to turn faster than requested. These discrepancies can be very significant. A table in issue 220 shows that for a requested speed_sp value of 50 the actual speed can be more than 20% greater than requested. These discrepancies are due to the properties of the motors themselves and are not EV3dev specific so I would expect other languages to have the same discrepancies but in other languages it would be less of a problem because motor synchronisation allows for the motors to move together with the correct speed ratio through the correct angles.

Rover robots are a classic application of the EV3 and it's vital to be able to make the rover follow a curved path accurately. Other languages can do this thanks to motor synchronisation but I believe EV3dev cannot. It would be banal to configure the move tank block in EV3-G, for example, to make motorB turn with speed 80 for 5 rotations while motorC turns at speed 20, stopping at the same time as motorB . Also, the block would usually be configured to make the program pause until the motors have completed their motions. In EV3 Python the equivalent code might look like this:

#!/usr/bin/env python3
from ev3dev.ev3 import *
mB = LargeMotor('outB')
mC = LargeMotor('outC')
mB.run_to_rel_pos(position_sp=1800, speed_sp=800, stop_action='hold')
mC.run_forever(speed_sp=200, stop_action='hold')
mB.wait_while('running')
mC.stop()

This code stops the slower motor once the faster motor has finished its movement, which is the behaviour described by Lego for the move tank block in the official documentation. Modifying the code slightly we can see how much each motor rotated:

#!/usr/bin/env python3
from ev3dev.ev3 import *
mB = LargeMotor('outB')
mC = LargeMotor('outC')
mB.position=0
mC.position=0
mB.run_to_rel_pos(position_sp=1800, speed_sp=800, stop_action='hold')
mC.run_forever(speed_sp=200, stop_action='hold')
mB.wait_while('running')
mC.stop()
print(mB.position)
print(mC.position)

Since motor C is set to turn at a quarter of the speed of motor B and both motors will turn for the same time we are implicitly requesting that motor C turns through a quarter of the angle turned through by motor B, i.e. 450°. In fact my tests show that motor C (starting from rest and with almost no load) turns through about 488°, or about 8% more than requested. This discrepancy is consistent with the observations raised in issue 220, that for small values of speed_sp the motor tends to turn faster than requested. Unlike the EV3 Python script above, the EV3-G move tank block turns the motors through the CORRECT angles because the motors are SYNCHRONISED to maintain the correct speed ratio. Therefore EV3 Python seems to be unable to match the precision of EV3-G in this case due to the impossibility of using synchronised motor movements.

You may be wondering whether a better result could be obtained with this code:

#!/usr/bin/env python3
from ev3dev.ev3 import *
mB = LargeMotor('outB')
mC = LargeMotor('outC')
mB.run_to_rel_pos(position_sp=1800, speed_sp=800, stop_action='hold')
mC.run_to_rel_pos(position_sp=450, speed_sp=200, stop_action='hold')
mB.wait_while('running')
mC.wait_while('running')

This code will indeed give correct results for the angles turned by each motor but there is still a problem: the slower motor turns faster than requested and the faster motor turns slower than requested (in accordance with issue 220) and therefore the slower motor completes its motion slightly before the faster motor (if you cannot see this effect easily then change the angles in the above code to 18000 and 4500). Therefore the movement happens in two stages: first the two motors are turning but the rover robot is turning less sharply than requested, then motor C stops turning and motor B continues turning for a moment while the rover turns more sharply than requested. This complex and undesired behaviour could be avoided if the motors could be synchronised to maintain a constant and correct speed ratio.

There is another good reason why it would be great to have a synchronised motor function in EV3dev/EV3 Python: it would allow for more concise code. Instead of needing two lines to launch the two motors only one would be needed. And a keyword argument could be included to make the program wait (or not) for the motors to complete their motion before continuing, further shortening the code. Note that the motion described in this message can be achieved in just one line in EV3 Basic : Motor.MoveSync("BC", 80, 20, 1800, "True") (This means: move motors B and C synchronously with speed B=80, speed C=20, with the faster motor turning through 1800° and the brake applied when the motion completes.)

But we have seen that achieving roughly the same (with less precision!) requires eight lines in EV3 Python. Python has a reputation for being relatively concise so it is disturbing to see that it takes eight times as many lines to achieve the same task compared to EV3 Basic (see my site ev3basic.com). I understand that it is rather difficult to write code for the synchronisation of motors but the developer of EV3 Basic, Reinhard Grafl, was able to write such a function working alone so I am confident that the EV3dev team can manage the same!

jabrena commented 7 years ago

Related issue: https://github.com/ev3dev/ev3dev/issues/454

MichaelBuss commented 7 years ago

As a newcomer I can definitely confirm that this would be a great, great feature that would 🚀 my progress. Is it possible to try It out already?

dlech commented 7 years ago

As far as I know, no one is working on this.

jabrena commented 7 years ago

In the case of the Java development, I will try in the next 2 months.

Next month, I will start testing a Differential Pilot without Motor Synchronisation. After testing, I will see the urgency. In LeJOS, the feature was added with EV3 but in RCX & NXT, we didn't use in the past. http://www.lejos.org/ev3/docs/lejos/hardware/motor/BaseRegulatedMotor.html https://lejosnews.wordpress.com/2014/10/02/motor-synchronization-problems-part-1/ https://lejosnews.wordpress.com/2014/10/06/motor-synchronization-problems-part-2/

Previous Motor Support: http://www.lejos.org/nxt/nxj/api/lejos/robotics/RegulatedMotor.html http://www.lejos.org/rcx/api/josx/platform/rcx/Motor.html

Juan Antonio

ndward commented 6 years ago

Interestingly, Reinhard Grafl, the developer of EV3 Basic (see EV3basic.com) has just added three new motor commands, all with synchronisation, to that language. But he points out that 'EV3 basic relies on the Lego firmware to do the hard work' while 'the EV3dev project team … is creating a completely custom firmware for the EV3 brick, so they need to re-invent all the really superb motor control functionality that is part of the original Lego firmware. Especially the synchronized movements are working astonishingly well. So if the EV3dev guys have not included it yet, they probably never will...

tcwan commented 6 years ago

Sorry to drag an old issue out of the closet, but I'd like to understand the scope of the problem. Is this something that has to be done in the kernel driver, or is it solvable using user mode library code?

Granted that controlling two motors using the Linux sysfs interface won't be very accurate due to the inherent file I/O overheads.

In addition, I found a description of dual motor synchronization algorithms, in Chapter 7.8 pg. 249 of "Mobile Robots: Inspiration to Implementation, 2nd Edition, AK Peters/CRC Press, 1998. ISBN-13: 978-1568810973 by Joseph L. Jones, Anita M. Flynn, Bruce A. Seiger". Is this typically what is implemented? (The text of the book is available on the Internet, not sure if it is out of print)

dlech commented 6 years ago

It is certainly possible to implement such an algorithm in userspace (using run-direct and duty_cycle_sp), but as you suspect, the sysfs overhead will likely cause problems.

While we would get better results from implementing this in the kernel, I'm learning over time that implementing such things in the kernel is not really the best way to do it. Ideally we would create a character device that allows access to the motors without the overhead of sysfs.

But, since all of the ev3dev libraries are basically just thin wrappers around sysfs, this would break all ev3dev programs ever written.

WasabiFan commented 6 years ago

But, since all of the ev3dev libraries are basically just thin wrappers around sysfs, this would break all ev3dev programs ever written.

What model are you imagining for the interface via a character device? Would it be so different from the existing model that the sysfs-style property interface couldn't internally be implemented in the various libraries to write commands to the stream whenever necessary?

tcwan commented 6 years ago

@dlech:

I'm not sure if a character device would be the best way to handle I/O commands, since character devices are byte-stream oriented. The driver would have to parse the input and possibly deal with incomplete command sequence strings.

Normally ioctl() would be the preferred and atomic way of handling I/O commands, but I'm not keen to go down the path of debugging kernel drivers either.

I've been messing with User IO (generic_uio) drivers and using mmap() to perform I/O operations from user space. That might be another way to address the problem by using mmap() and direct I/O register manipulation.

jabrena commented 6 years ago

If we create a new method on ev3dev with synchronize features, we could not break anything. Besides, in that level, more members could test the solution.

dlech commented 6 years ago

I'm not sure if a character device would be the best way to handle I/O commands, since character devices are byte-stream oriented

You still need a character device to do ioctl and mmap as you suggested. Also, character devices don't have to be text-based. You can read/write binary data.

What I am envisioning is using an ioctl to set the speed of the motor (or possibly other commands depending on the motor controller) and reading from the char device to get events like a change in position or status flags.

If one waw to use uio to poke hardware registers directly, one would still have to write a userspace daemon that essentially does exactly what the kernel driver does. But one will be at the disadvantage of not having interrupts in userspace and will have to constantly poll the hardware. We almost certainly won't be able to get correct position feedback this way because of missing gpio events.

Really, the ideal solution would be to implement a motor controller in one of the PRUs so that we have hard-realtime control of the motor. Or at a minimum, implement a quadrature encoder in the PRU.

dlech commented 6 years ago

What model are you imagining for the interface via a character device?

The kernel drivers shouldn't try to do things that the motor controller doesn't already know how to do. Trying to do things like ramping and whatnot in the kernel just haven't worked out. So, in the case of the EV3, basically we would just have the equivalent of the run-direct command and the position attribute.

If we implemented a more advanced motor controller in the PRU though, then the kernel interface would look a bit different (more like BrickPi, etc. that have an external motor controller).

tcwan commented 6 years ago

@dlech:

UIO does present a rudimentary interrupt handling capability via uio_pdrv_genirq.ko module, where you can select() on the uio device file to detect interrupts. It'll not be as efficient compared to a proper kernel driver.

The PRU approach will be specific to the EV3 (and other TI-based SBCs) though, since the other platforms don't have equivalent features?

I am probably crazy to even attempt to do this for Linux, though I'm aware that the PRU exists in the AM1808.

I'll need to study this a bit more, but currently I'd like information on:

  1. Build tools for PRU programming. I found a related article but I haven't read it yet.
  2. Some references on the algorithm to implement (I don't know control theory in detail)
dlech commented 6 years ago

Build tools for PRU programming. I found a related article but I haven't read it yet.

Most of the information about TI PRUs is for newer processors, like AM35xx (BeagleBone). It is very similar, so most of that info is probably applicable. TI has a C compiler for the newer PRUs. I'm not sure if it works for the older AM18xx processor though.

We actually already have a repo for PRU code. https://github.com/ev3dev/ev3dev-pru-firmware. We are using it for extra I2C adapters on BeagleBone. I was able to figure it out by reading the info in the TI wiki. There are some links in the readme for that repo.

The PRU approach will be specific to the EV3 (and other TI-based SBCs) though, since the other platforms don't have equivalent features?

As I already mentioned, BeagleBone has a very similar PRU, so it should work for that too. Everything else (BrickPi3 and PiStorms) have external microprocessors that do motor control already. The firmware for these is not open-source unfortunately.

Some references on the algorithm to implement (I don't know control theory in detail)

paging @laurensvalk

jabrena commented 6 years ago

Hi @dlech, if you need hand for testing or a bit od development, tell me.

tcwan commented 6 years ago

@dlech:

  1. Based on motor drive ports, I guess we should use outA and outB as the synchronized motor ports?

Ideally the control algorithm should not be constrained by 2 motors only, but let's worry about the basic functionality first.

  1. How do you get the tachometer readings? Which AM1808 peripheral is used?

    Will this be different for NXT and servo motors?

  2. I presume that only PRU1 is available since PRU0 is being used for UART3 and 4. Would that be similar for the Beaglebone?

  3. The part I'm most confused by currently is how to interface with the motor driver and obtain tachometer readings.

    • I presume that we would need a new Linux kernel driver to interface with the PRU, and communicate with the PRU via some form of memory mapped buffer.
    • the PRU only implements the control algorithm
    • commands to update the motor settings will be done by the kernel driver (this may have latency / jitter issues)
    • tachometer readings will be passed by the kernel driver to the PRU

Edited: From the description of the PRU, the RISC engine would have access to I/O pins directly controlled by itself, as well as the low level hardware module (e.g., EPWM0/1). However, I'm no sure if it is a good idea to mess with the PWM directly without going through the normal kernel driver path. In addition, I'd rather not worry about overdriving the motors if possible.

  1. I still have not managed to determine if the PRU C compiler is able to support the AM1808 PRU, but worst case we can extract the C compiler ASM output and sanitize it for the AM1808 if necessary.
dlech commented 6 years ago

Based on motor drive ports, I guess we should use outA and outB as the synchronized motor ports?

Actually, I would use B and C since that is what official LEGO models use for drive/steering motors.

How do you get the tachometer readings? Which AM1808 peripheral is used?

They are attached to GPIOs, so we have to watch for interrupts and use a state machine to figure out which direction we are turning. Wikipedia has some decent introductory info if you are not familiar with this: https://en.wikipedia.org/wiki/Rotary_encoder#Incremental_rotary_encoder

I presume that only PRU1 is available since PRU0 is being used for UART3 and 4. Would that be similar for the Beaglebone?

Yes. (on BeagleBone PRU0 is free)

The part I'm most confused by currently is how to interface with the motor driver and obtain tachometer readings.

Actually, I was hoping to do it the other way around. The PRU should monitor the GPIOs for the quadrature encoder and determine the motor position based on this. Then the kernel driver can just read the motor position from the PRU.

I presume that we would need a new Linux kernel driver to interface with the PRU, and communicate with the PRU via some form of memory mapped buffer.

It might be worthwhile to make a new kernel driver for testing, but we should be able to use the existing driver and just replace certain parts of it. There are shared memory addresses between the PRU and the CPU (i.e. 0x01C30000 to 0x01C301FF - comes from http://processors.wiki.ti.com/index.php/PRUSS_Memory_Map - will be different for BeagleBone)

the PRU only implements the control algorithm

And also the quadrature encoder (as mentioned above). In fact, I think it would be a good idea to just implement the quadrature encoder to begin with just to get the hang of how things work and then add a control algorithm later.

commands to update the motor settings will be done by the kernel driver (this may have latency / jitter issues)

Correct. However, if we can get the quadrature encoder in the PRU, then using the RT kernel patchset might be a possibility. It will reduce the jitter, but at the expense of making everything else run even slower than it already does on the EV3.

I still have not managed to determine if the PRU C compiler is able to support the AM1808 PRU, but worst case we can extract the C compiler ASM output and sanitize it for the AM1808 if necessary.

:+1:

dlech commented 6 years ago

If you are really serious about working on this, I can probably come up with a kernel driver fairly quickly for testing that just loads the PRU firmware and maps the shared memory.

tcwan commented 6 years ago

If you are really serious about working on this, I can probably come up with a kernel driver fairly quickly for testing that just loads the PRU firmware and maps the shared memory.

Well, I'm curious enough to give it a stab. Just monitoring the quadrature encoders sounds doable for now.

However, I'm not sure how to arbitrate access to the quadrature encoder GPIOs between this and the tacho motor driver. Would it be ok to just poll them, then I don't have to worry about interrupt handlers which are probably being used by the regular tacho motor driver currently?

Should I do this using the ev3dev-pru-firmware repo?

Finally what version of CSS should I use? The latest release is version 8.

dlech commented 6 years ago

Well, I'm curious enough to give it a stab. Just monitoring the quadrature encoders sounds doable for now.

Great. If you give me a few days, I'll try to clean up the PRU UART code and create a new driver template for loading your firmware in the other PRU.

However, I'm not sure how to arbitrate access to the quadrature encoder GPIOs between this and the tacho motor driver. Would it be ok to just poll them, then I don't have to worry about interrupt handlers which are probably being used by the regular tacho motor driver currently?

The PRUs have their own "interrupt controllers". I put this in quotation marks because "interrupts" don't actually trigger an interrupt handler like you may be accustomed to. So, you basically have to poll the GPIOs even if you do use "interrupts". And, you don't have to worry about affecting the ARM interrupt controller.

Given that and the fact that we are using these GPIOs as inputs, you don't have to worry about conflicting with the current motor driver.

Should I do this using the ev3dev-pru-firmware repo?

That would be the best place to submit PRU code.

Finally what version of CSS should I use? The latest release is version 8.

Latest version is probably the best. I haven't touched it in a while.

tcwan commented 6 years ago

Ok. I found a reference project which may be a good starting point.

There's a more advanced example but I can't seem to find the source files. They may be part of the Processor SDK installation.

Edit: As usual, silicon vendors have ridiculously convoluted IDE and SDK naming as well as releases. From what I can gather, I need the Processor SDK to get the PRU compilers, and the Processor SDK only works with CCS version 7.x currently.

They have Processor support for OMAP-L138, which is basically the AM1808 + DSP in a single package. I will look into using that package for now.

Edit 2: Sitara device support is only available in Linux / Ubuntu and Windows version of CCS. I think I don't actually need the Processor support packages which are for their own Linux distro or RTOS.

The PRU compiler is installed via CCS App Center (I think you can probably access everything through CCS App Center or the Resource Explorer). I'm going to use CCS version 8 since it is the currently maintained version.

The code for the PRU Industrial Drive package is not linked from the information page. Instead, it is here.

tcwan commented 6 years ago

I don't see any Issues tab for the ev3dev-pru-firmware project, so I'm dumping the info here for future reference:

LEGO Motor Ports Info

tcwan commented 6 years ago

quadrature-encoder-states

My first stab at creating a State machine to capture the behavior of the quadrature encoder. This uses 9 states, for clarity. It can be simplified down to 5 (or even 4 states) at the expense of more complex transitions.

Edit 2 : Updated with the reverse transition from CCW to CW states.

laurensvalk commented 6 years ago

Thanks for working on this @tcwan

To make this also valuable for speed measurements, it would be helpful to keep a FIFO array of hardware timer values corresponding to equidistant transitions.

Essentially adding a time stamp to each count +/- degree transition. Integer clock counts are sufficient. Is memory a concern here? I think keeping a FIFO array of about 100 transition times will be more than enough.

The actual speed computation is something interesting to think about. This does not have to be done by the PRUs. Having accurate transition time data is key in any case though, and the PRUs could do this nicely.

The same goes for controllers. When accurate timing is available from the PRU in shared memory, the kernel or the user program can compute control signals (PID, etc). And this would need to be done much less frequently compared to handling pin interrupts.


The current ev3dev speed computation does not work very well, which limits the usability of the run-to- pos/abs/time/forever commands.

To see this, run any motor at a constant physical speed (in run-direct mode at e.g. 60% duty cycle) with no load on the motor. Display the speed readings as you go along.

Not only do they vary quite a bit (even though the physical motor speed is constant), they are also quite far off from the actual speed, where the actual speed is obtained by running for e.g. 50 seconds and displaying (end_pos - start_pos)/50.

tcwan commented 6 years ago

quadrature-encoder-states-simplified

After looking at the original state transitions, I think it is easier just to simplify it to a 5-state version.

@laurensvalk, thanks for the info. I'll have to explore this issue more to understand how to best implement it.

dlech commented 6 years ago

To make this also valuable for speed measurements, it would be helpful to keep a FIFO array of hardware timer values corresponding to equidistant transitions.

We can use the top half of TIMER64P0 (register TIM34) for this timestamp. It is configured as a free running 32-bit counter already by the OS. And it always runs at 24MHz.

We can use the iio subsystem to expose the FIFO to userspace... but one thing at a time.

Is memory a concern here?

There is an 8K "ARM Local RAM" and a 128K "On Chip RAM" at our disposal (a small portion of each is already used). Thinking out loud: 4K would get us 500 events (4 bytes for timestamp + 4 bytes for position). If we double the precision (see #148), then assuming a max speed of 2000 deg/sec (actual max speed of medium motor will full batteries is something like 1600 deg/sec, so there is a pretty good margin for safety figured in here), we would get a max of 4000 events per second, which means we would have to drain the FIFO from userspace every 125ms in order to not miss any events.

This does not have to be done by the PRUs.

I'm very much in favor of doing a little as possible in the PRU to make it more flexible. So, if we can just get away with doing timestamps and not calculating speed, I'm all for it.

dlech commented 6 years ago

@tcwan, I've managed to get a blinking LED working with the PRU on the EV3.

You will need to build your own kernel from https://github.com/ev3dev/ev3-kernel. You will need swap the "disabled" and "okay" shown here to enable the new remoteproc driver. You may also find it helpful to enable the DEBUG_FS compile option.

Then, build the ev3-pru-test project from https://github.com/ev3dev/ev3dev-pru-firmware. Copy the ev3-pru-test.out file to /lib/firmware/ on the EV3. After a reboot, you should see the left green LED blink constantly.

laurensvalk commented 6 years ago

Thinking about timers some more, there is an easier way to get speed measurements. It's almost equally accurate, but the implementation is much closer to what we have now, making the process easier on the kernel side, compared to the approach I mentioned above.

I suppose the timer can also be used to trigger interrupts on the PRU? If so, we can fire one separate interrupt routine every X clock ticks to store the present encoder value of each motor in a FIFO array. Since the interrupt frequency is known, there is no need to store timer values. Also, the time stamps are the same for each motor.

Speed is then computed in the kernel by taking the most recent position, minus the position Y trigger instances ago, divided by the time between them (which is always constant). I use this approach for the Segway. That bit of code is here. But with samples stored by the PRU it would be much more accurate.

The parameter Y (window size) could be a user configurable parameter. A longer window gives you smoother speed values with a slight delay in them, and vice versa.

dlech commented 6 years ago

I suppose the timer can also be used to trigger interrupts on the PRU?

The PRU can't do interrupts in the conventional sense. (There is an "interrupt controller", but is just sets and clears flags and can't call an ISR). So, the only way to do something every X clock ticks is busy-waiting until we see the "interrupt".

The parameter Y (window size) could be a user configurable parameter.

Not a bad idea, but making stuff in the kernel and PRU "user configurable" has proved to be difficult to implement, so I would like to avoid this if there are other reasonable options.

laurensvalk commented 6 years ago

Thinking out loud: 4K would get us (...)

For speed computations, I think storing the last 2 bytes of the encoder position is more than enough because we only need the position differences. Even if we considered measurement windows of a second, you can hit up to 65536/360*60=11K RPM before running out of bits.

The position counter would still be stored in 4 bytes.

laurensvalk commented 6 years ago

The PRU can't do interrupts in the conventional sense.

Ah, in that case the first approach may still be necessary. I'll think about that.

The parameter Y (window size) could be a user configurable parameter.

Not a bad idea, but making stuff in the kernel and PRU "user configurable" has proved to be difficult to implement, so I would like to avoid this if there are other reasonable options.

In principle, Y would only be a parameter in the final computation (which does not take place on the PRU). It would not be much different than changing the currently used Kp/i/d parameters.

However, a sensible fixed value should suffice indeed.

laurensvalk commented 6 years ago

The PRU can't do interrupts in the conventional sense.

Ah, in that case the first approach may still be necessary. I'll think about that.

On second thought, if some sort of state machine is required to busy-wait for encoder pin pulse flags anyway, it's perhaps not really a stretch to add one short interrupt driven routine that stores the motor positions.

tcwan commented 6 years ago

@dlech:

You will need to build your own kernel from https://github.com/ev3dev/ev3-kernel. You will need swap the "disabled" and "okay" shown here to enable the new remoteproc driver. You may also find it helpful to enable the DEBUG_FS compile option.

Does this replace the in3 and in4 sensor driver? If so, then I'll be writing code to run on PRU0, am I correct?

dlech commented 6 years ago

The existing PRU UART driver basically hogs all of the PRU memory addresses, so you have to disable it in order to use the new PRU remoteproc driver. This is orthogonal to the input port drivers. UART sensors won't work on input ports 3 and 4, but that is the only side effect.

The current PRU remoteproc driver only loads firmware into PRU0, so use that one for now.

tcwan commented 6 years ago

@dlech:

I can't seem to find any information on how to build the ev3-kernel source. I presume that I should be using the ev3dev-stretch branch.

Do I build it using the stretch docker image?

dlech commented 6 years ago

https://github.com/ev3dev/ev3dev-buildscripts

tcwan commented 6 years ago

@dlech:

There is an 8K "ARM Local RAM" and a 128K "On Chip RAM" at our disposal (a small portion of each is already used)

Actually the 12K (sic) memory mapped RAM (shared with the ARM CPU) is only available on the AM335x CPU, so EV3 would not be able to take advantage of it. In addition, the EV3 only has 512 bytes of dedicated PRU "Local RAM"

It would still be nice to be able to do accurate motor control using the Beaglebone though.

dlech commented 6 years ago

The PRU has access to most on-chip peripherals, including to two RAMs I mentioned. They are both different from the 512B PRU local RAM.

tcwan commented 6 years ago

@dlech:

The PRU has access to most on-chip peripherals, including to two RAMs I mentioned. They are both different from the 512B PRU local RAM.

From the global memory map, there are 3 types of memory associated with the PRUs:

  1. Local Data RAM (512 bytes for EV3, 8K for Beaglebone)
  2. Shared main DRAM (0 for EV3, 12K for Beaglebone)
  3. Local Instruction RAM (4K for EV3, 8K for Beaglebone)

1 and 2 (where available) should be usable for data storage. I'm not sure if 3 can be used from within the PRU to store data since it looks like a Harvard architecture device where the Instruction Address space and Data Address space are separate.

dlech commented 6 years ago

Have a look at the AM1808 datasheet, there are additional SoC RAMs not listed in the PRU docs.

dlech commented 6 years ago

FWIW, the existing PRU UART driver uses the first 8K of the 128K RAM.

tcwan commented 6 years ago

@dlech:

Ok. I see the on-SoC DRAM listed. I'm not sure how to access it yet, based on the PRU Memory Map info in the PRUSS_Training_Slides.pdf.

The AM1808 Technical Reference (spruh82a) doesn't even talk about the PRU, it links to the TI Wiki.

dlech commented 6 years ago

The DDR RAM is yet another RAM, don't use it. It is owned by the Linux kernel. You can do something like this:


typedef struct {
    uint32_t timestamp;
    uint32_t position;
} event;

#define ON_CHIP_RAM ((volatile event *)(0x80000000))

...

    ON_CHIP_RAM[0].timestamp = 123456;
    ON_CHIP_RAM[0].position = 99;

See Table 3-2 in the "data sheet" (not the technical reference).

tcwan commented 6 years ago

@dlech: From the ev3dev-buildscripts README.md:

~/work/ev3dev-buildscripts $ cd ./build-area/linux-ev3dev-ev3-dist
~/work/ev3dev-buildscripts/build-area/linux-ev3dev-ev3-dist $ cp uImage <path-to-boot-partition>/uImage
~/work/ev3dev-buildscripts/build-area/linux-ev3dev-ev3-dist $ sudo cp -r lib/ <path-to-file-system-partition>

Then later:

# replace `/mnt/ev3dev-root` with your actual mount point
export EV3DEV_INSTALL_KERNEL=/mnt/ev3dev-root/boot/flash
export EV3DEV_INSTALL_MODULES=/mnt/ev3dev-root

/dev/sde1 contains the u-boot.bin first stage boot loader, which isn't the Linux root file system.

$ sudo mount /dev/sde2 /mnt/sdcard

$ ls /mnt/sdcard/boot
boot.scr                         dtbs
boot.scr.bak                     flash
config-4.14.49-ev3dev-2.0.2-ev3  initrd.img-4.14.49-ev3dev-2.0.2-ev3
dtb                              System.map-4.14.49-ev3dev-2.0.2-ev3
dtb-4.14.49-ev3dev-2.0.2-ev3     vmlinuz-4.14.49-ev3dev-2.0.2-ev3

Where should the uImage file go? I don't find any files in the /boot/flash directory. By right the kernel image is vmlinuz-*, but I probably need to rebuild initrd.img* as well, which isn't part of the instructions.

I think I should also replace the *dtb with the updated versions.

[Edited]

dlech commented 6 years ago

The readme is a bit out of date with recent changes to the bootloader in the stretch images. I think the easiest way to get everything in the right place is to create a debian package (the readme also explains how to do this).

BTW, pining me on IRC or Gitter is probably the best for these types of questions.

tcwan commented 6 years ago

@dlech:

I've checked in a dummy PRU driver in my repository, under the ev3-tacho-encoder branch. I've just adopted the existing software license from the TI PRU source files, not sure if you have any specific preference.

Basically the PRU driver alternatively blink the left and right LEDs. I'll be using the LEDs to debug the tacho count logic later.

We need to define the data structure for storing the encoder values. Are we storing timestamp, encoder count, or both? Do we need to store the whole 32-bits or only the lower 16-bits (we can store the starting value as 32-bit). I presume that we should have some kind of ring buffer logic for generating and consuming the values. Should we cater for multiple motors (more than 2) in the data structure if possible?

In addition, I'm thinking of using a lock-free ringbuffer but I don't have any specific implementation in mind. The URL was just the first hit Google returned.

laurensvalk commented 6 years ago

Of course you are welcome to choose your own scheme but I thought I'd sum up my proposal above for brevity:


I would store the current count variable in your diagram as a 32-bit variable.

And after every n_time_cycles (a constant integer) clock cycles, store the last 16 bits of the current count value, in an array count_history of length n_window. (If memory is no issue, you might as well store 32-bit arrays for consistency.)

No time stamps would be needed for this approach.

To get a FIFO-like behavior without shifting array data, you could keep a continuously incrementing index value (modulo its length) corresponding to the oldest value. Each time the clock is triggered, the new 16-bit value overwrites the oldest one.

The speed integer value (with units of count_diff per n_window*n_time_cycles/pru_freq seconds) is simply the newest position minus the oldest one before it gets purged. The kernel can later scale it to something like degrees per second with a constant scaling factor.

The kernel would need access to the 32 bit position counter , the array of 16-bit values, and the running index value. and the unscaled speed value.

Let me know if pseudocode of this would be helpful.


For four motors, this is four 32-bit count positions, and four 16-bit count_history arrays, each of a constant length n_window.

The counters get upgraded according to the state machine (one state machine for each motor)

All 4 arrays get updated once every n_time_cycles cycles (one update routine for all four motors). So one would have to keep and share only one index value.

Above I'm assuming that the kernel can read the shared memory without the PRU updating it in the mean time.

dlech commented 6 years ago

And after every n_time_cycles (a constant integer) clock cycles

How often do we need to do this to be useful? 1kHZ, 10kHz, 100kHz?

tcwan commented 6 years ago

@laurensvalk:

Thanks for the input. I think I understand what you're describing in terms of the data capture and storage.

I'd just like to ask whether 4 motors would be the maximum in terms of tachometer reading support. We would still only be able to display the state for two motors at any given time on the Left and Right LEDs, but there is no problem keeping track of multiple motor tacho counts.

dlech commented 6 years ago

I've just adopted the existing software license from the TI PRU source files, not sure if you have any specific preference.

That's what I have been doing too, rather than dealing with two different licenses.

Should we cater for multiple motors (more than 2) in the data structure if possible?

Yes, we want the same data from all 4 motors.