I'm trying here is to let the real-time units of the BeagleBone generate the signals for a stepper motor. In this post: control two output pins from PRU and Linux |
The PRU will have to drive two pins when driving the stepper motor.
One for the direction of rotation. The other for the step pulses.
I'll try here to send the PRU a number of steps, and a flag to give the direction.
The PRU should then drive the DIR pin (high or low based on the parameter) and pulse the STEP pin the requested number of times.
I'm selecting PRU GPIO pins 29 and 31 - because I have a cape that has LEDs on these pins that make it easy to see the effect.
But any PRU accessible GPIO pins can be used.
MUX Outputs for PRU
There are several ways you can define the pin mux. I'm using the Linux driver to do that.
These are the two pins I'm using:
- PR1_PRU0_GPO0: P9_31 (blue LED, symbolically represents the DIR signal)
- PR1_PRU0_GPO1: P9_29 (green LED, represents the STEP signal)
image source: modified from seeed wiki for the BB green
These Linux commands reserve them as PRU controlled output pins:
config-pin P9_31 pruout config-pin P9_29 pruout
The PRU can now address them and drive them high and low.
When finished at the end of the program, best assign them back to their default muxing. Else the PRU driver complains after reboot.
config-pin P9_31 default config-pin P9_29 default
Very likely there's a better way to do this (overlay?). If you know how, put it in the comments please.
Simple PRU Program to Drive the Pins
My code is based on TI's example program LAB 6: Blinking LEDs with RPMsg from Linux User Space.
The TI copyright is taken over. I don't claim any additional rights for my changes. They are free for any purpose.
// Based on LAB 6: Blinking LEDs with RPMsg from Linux User Space. // http://processors.wiki.ti.com/index.php/PRU_Training:_Hands-on_Labs#LAB_6:_Blinking_LEDs_with_RPMsg_from_Linux_User_Space /* * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ .... */ #include <stdint.h> #include <stdio.h> #include <ctype.h> #include <pru_cfg.h> #include <pru_intc.h> #include <rsc_types.h> #include <pru_rpmsg.h> #include "resource_table_0.h"
Two PRU registers are used for the two pins and interrupts from + to Linux
volatile register uint32_t __R30; volatile register uint32_t __R31; /* Host-0 Interrupt sets bit 30 in register R31 */ #define HOST_INT ((uint32_t) 1 << 30) /* The PRU-ICSS system events used for RPMsg are defined in the Linux device tree * PRU0 uses system event 16 (To ARM) and 17 (From ARM) * PRU1 uses system event 18 (To ARM) and 19 (From ARM) */ #define TO_ARM_HOST 16 #define FROM_ARM_HOST 17 #define DIR 0x0 #define STEP 0x1
Common parts to exchange info with Linux. Literally from TI's example:
/* * Using the name 'rpmsg-pru' will probe the rpmsg_pru driver found * at linux-x.y.z/drivers/rpmsg/rpmsg_pru.c */ #define CHAN_NAME "rpmsg-pru" #define CHAN_DESC "Channel 30" #define CHAN_PORT 30 /* * Used to make sure the Linux drivers are ready for RPMsg communication * Found at linux-x.y.z/include/uapi/linux/virtio_config.h */ #define VIRTIO_CONFIG_S_DRIVER_OK 4 uint8_t payload[RPMSG_MESSAGE_SIZE]; /* * main.c */ void main(void) { struct pru_rpmsg_transport transport; uint16_t src, dst, len; volatile uint8_t *status; __R30 = 0x0; /* Allow OCP master port access by the PRU so the PRU can read external memories */ CT_CFG.SYSCFG_bit.STANDBY_INIT = 0; /* Clear the status of the PRU-ICSS system event that the ARM will use to 'kick' us */ CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST; /* Make sure the Linux drivers are ready for RPMsg communication */ status = &resourceTable.rpmsg_vdev.status; while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK)); /* Initialize the RPMsg transport structure */ pru_rpmsg_init(&transport, &resourceTable.rpmsg_vring0, &resourceTable.rpmsg_vring1, TO_ARM_HOST, FROM_ARM_HOST); /* Create the RPMsg channel between the PRU and ARM user space using the transport structure. */ while (pru_rpmsg_channel(RPMSG_NS_CREATE, &transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS); while (1) { /* Check bit 30 of register R31 to see if the ARM has kicked us */ if (__R31 & HOST_INT) { /* Clear the event status */ CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;
Now my part. I expect a string of characters from Linux.
Position 1 is the direction ('0' or '1').
All others are a number of steps, and should all be digits.
Example:
1458: Set the DIR pin high, pulse 458 times.
06: Set the DIR pin low, pulse 6 times.
First we check the direction and drive the pin accordingly:
/* Receive all available messages, multiple messages can be sent per kick */ while (pru_rpmsg_receive(&transport, &src, &dst, payload, &len) == PRU_RPMSG_SUCCESS) { if (len < 2) { // at least directory and 1 bit of counts break; } // fist position is direction. If '0', then clear DIR bit, else set dir bit if (payload[0] == '0') { __R30 &= ~(1UL << DIR); } else { __R30 |= 1UL << DIR; }
Then calculate the number of pulses.
I parse each character in the argument and give it the right weight by using this formula (the first, most siginificant digit is at index 1):
digit * 10 pow (total length - 2 - position)
// get the number of pulses int i; uint32_t pulses = 0U; for (i = 1; i < len; i++) { if (isdigit(payload[i])) { pulses += ((payload[i] - '0') * opt_int_pow(len - 2 - i)); } }
And then toggle the step line twice for each pulse, because a pulse has two edges)
// toggle STEP twice the pulse count (a pulse is two toggles) for (i = 0; i < pulses * 2; i++) { __R30 ^= 1UL << STEP; // can be optimised if I constant the right operator __delay_cycles(600000); // Intrinsic method delay } } } } }
Because the standard pow() function does not fit in the PRU's memory, I borrowed one from stackowerflow:
static int opt_int_pow(int n) { int r = 1; const int x = 10; while (n) { if (n & 1) { r *= x; n--; } else { r *= x * x; n -= 2; } } return r; }
Compile, then copy the executable to the BB. I moved it to /home/debian/bin
In Linux, execute these commands to drive the DIR high and run 20 pulses:
sudo -i config-pin P9_31 pruout config-pin P9_29 pruout cp /home/debian/bin/bb_PRU_STEPPER.out /lib/firmware/bb_PRU_STEPPER.out echo 'bb_PRU_STEPPER.out' > /sys/class/remoteproc/remoteproc1/firmware echo 'start' > /sys/class/remoteproc/remoteproc1/state echo "120" > /dev/rpmsg_pru30 echo 'stop' > /sys/class/remoteproc/remoteproc1/state config-pin P9_31 default config-pin P9_29 default exit
It works!
Code Composer Studio project attached.
You need to download TI's LABs, they contain some dependencies we need.
In the project's properties, adapt the variable PRU_SOFTWARE_SUPPORT_PACKAGE to the home location where you saved the LABs.
Top Comments