EMC2 running on Raspberry Pi?

More
13 years 6 months ago #33149 by mungkie
If you can copy the code below to a textfile on your rpi (e.g. dma_stepbuf.c ) then at the command prompt in the directory where the file is type 'gcc dma_stepbuf.c'

If there are any errors either you or I have made a mistake somewhere so post the errors here I will try to solve them.

If the code compiles without errors connect the DSO to pins 22,23,24 of the P1 gpio port on the rpi, then type 'sudo ./a.out'

The code should printout a list of the bit patterns on gpio pins 22/23/24 and let the DMA loop for 30 seconds then change the bit patterns.

What I would really like to know from the test is if there is any delay at the begining middle or end of the 200 sample buffer when the DMA loops back to the start of the buffer or changes the buffer position flag.

If the step generation looks fairly constant on the DSO then I will try and finish off the driver for linuxcnc (should only take an evenings work unless I have missed something).
Code:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <stdint.h> #include <signal.h> #include <time.h> #include <sys/types.h> #include <fcntl.h> #include <sys/mman.h> int setup(int pw_incr_us); void shutdown(void); int init_channel(int channel, int subcycle_time_us); int print_channel(int channel); #define SUBCYCLE_TIME_US_DEFAULT 4000 // Subcycle minimum. We kept seeing no signals and strange behavior of the RPi #define SUBCYCLE_TIME_US_MIN 2000 // Default pulse-width-increment-granularity #define PULSE_WIDTH_INCREMENT_GRANULARITY_US_DEFAULT 20 // 15 DMA channels are usable on the RPi (0..14) #define DMA_CHANNELS 15 // Standard page sizes #define PAGE_SIZE 4096 #define PAGE_SHIFT 12 // Memory Addresses #define DMA_BASE 0x20007000 #define DMA_CHANNEL_INC 0x100 #define DMA_LEN 0x24 #define PWM_BASE 0x2020C000 #define PWM_LEN 0x28 #define CLK_BASE 0x20101000 #define CLK_LEN 0xA8 #define GPIO_BASE 0x20200000 #define GPIO_LEN 0x100 // Datasheet p. 51: #define DMA_NO_WIDE_BURSTS (1<<26) #define DMA_WAIT_RESP (1<<3) #define DMA_D_DREQ (1<<6) #define DMA_PER_MAP(x) ((x)<<16) #define DMA_END (1<<1) #define DMA_RESET (1<<31) #define DMA_INT (1<<2) // Each DMA channel has 3 writeable registers: #define DMA_CS (0x00/4) #define DMA_CONBLK_AD (0x04/4) #define DMA_DEBUG (0x20/4) // GPIO Memory Addresses #define GPIO_FSEL0 (0x00/4) #define GPIO_SET0 (0x1c/4) #define GPIO_CLR0 (0x28/4) #define GPIO_LEV0 (0x34/4) #define GPIO_PULLEN (0x94/4) #define GPIO_PULLCLK (0x98/4) // GPIO Modes (IN=0, OUT=1) #define GPIO_MODE_IN 0 #define GPIO_MODE_OUT 1 // PWM Memory Addresses #define PWM_CTL (0x00/4) #define PWM_DMAC (0x08/4) #define PWM_RNG1 (0x10/4) #define PWM_FIFO (0x18/4) #define PWMCLK_CNTL 40 #define PWMCLK_DIV 41 #define PWMCTL_MODE1 (1<<1) #define PWMCTL_PWEN1 (1<<0) #define PWMCTL_CLRF (1<<6) #define PWMCTL_USEF1 (1<<5) #define PWMDMAC_ENAB (1<<31) #define PWMDMAC_THRSHLD ((15<<8) | (15<<0)) // DMA Control Block Data Structure (p40): 8 words (256 bits) typedef struct { uint32_t info; // TI: transfer information uint32_t src; // SOURCE_AD uint32_t dst; // DEST_AD uint32_t length; // TXFR_LEN: transfer length uint32_t stride; // 2D stride mode uint32_t next; // NEXTCONBK uint32_t pad[2]; // _reserved_ } dma_cb_t; // Memory mapping typedef struct { uint8_t *virtaddr; uint32_t physaddr; } page_map_t; // Main control structure per channel struct channel { uint8_t *virtbase; uint32_t *sample; dma_cb_t *cb; page_map_t *page_map; volatile uint32_t *dma_reg; // Set by user uint32_t subcycle_time_us; uint32_t num_flags; // Set by system uint32_t num_samples; uint32_t num_cbs; uint32_t num_pages; // Used only for control purposes uint32_t width_max; }; // One control structure per channel static struct channel channels[DMA_CHANNELS]; // Pulse width increment granularity static uint16_t pulse_width_incr_us = -1; static uint8_t _is_setup = 0; static int gpio_setup = 0; // bitfield for setup gpios (setup = out/low) // Common registers static volatile uint32_t *pwm_reg; static volatile uint32_t *pcm_reg; static volatile uint32_t *clk_reg; static volatile uint32_t *gpio_reg; #define log_debug printf // Sets a GPIO to either GPIO_MODE_IN(=0) or GPIO_MODE_OUT(=1) static void gpio_set_mode(uint32_t pin, uint32_t mode) { uint32_t fsel = gpio_reg[GPIO_FSEL0 + pin/10]; fsel &= ~(7 << ((pin % 10) * 3)); fsel |= mode << ((pin % 10) * 3); gpio_reg[GPIO_FSEL0 + pin/10] = fsel; } static void udelay(int us) { struct timespec ts = { 0, us * 1000 }; nanosleep(&ts, NULL); } // Shutdown -- its important to reset the DMA before quitting void shutdown(void) { int i; for (i = 0; i < DMA_CHANNELS; i++) { if (channels[i].dma_reg && channels[i].virtbase) { log_debug("shutting down dma channel %d\n", i); udelay(channels[i].subcycle_time_us); channels[i].dma_reg[DMA_CS] = DMA_RESET; udelay(10); } } } // Terminate is triggered by signals static void terminate(void) { shutdown(); exit(0); } #define fatal(fmt...) { printf(fmt); shutdown(); exit(-1); } // Catch all signals possible - it is vital we kill the DMA engine on process exit! static void setup_sighandlers(void) { int i; for (i = 0; i < 64; i++) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = (void *) terminate; sigaction(i, &sa, NULL); } } // Memory mapping static uint32_t mem_virt_to_phys(int channel, void *virt) { uint32_t offset = (uint8_t *)virt - channels[channel].virtbase; return channels[channel].page_map[offset >> PAGE_SHIFT].physaddr + (offset % PAGE_SIZE); } // Peripherals memory mapping static void * map_peripheral(uint32_t base, uint32_t len) { int fd = open("/dev/mem", O_RDWR); void * vaddr; if (fd < 0) { fatal("rpio-pwm: Failed to open /dev/mem: %m\n"); return NULL; } vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base); if (vaddr == MAP_FAILED) { fatal("rpio-pwm: Failed to map peripheral at 0x%08x: %m\n", base); return NULL; } close(fd); return vaddr; } // Returns a pointer to the control block of this channel in DMA memory uint8_t* get_cb(int channel) { return channels[channel].virtbase + (sizeof(uint32_t)) * channels[channel].num_samples + (sizeof(uint32_t)) * channels[channel].num_flags ; } // Get a channel's pagemap static int make_pagemap(int channel) { int i, fd, memfd, pid; char pagemap_fn[64]; channels[channel].page_map = malloc(channels[channel].num_pages * sizeof(*channels[channel].page_map)); if (channels[channel].page_map == 0) fatal("rpio-pwm: Failed to malloc page_map: %m\n"); memfd = open("/dev/mem", O_RDWR); if (memfd < 0) fatal("rpio-pwm: Failed to open /dev/mem: %m\n"); pid = getpid(); sprintf(pagemap_fn, "/proc/%d/pagemap", pid); fd = open(pagemap_fn, O_RDONLY); if (fd < 0) fatal("rpio-pwm: Failed to open %s: %m\n", pagemap_fn); if (lseek(fd, (uint32_t)channels[channel].virtbase >> 9, SEEK_SET) != (uint32_t)channels[channel].virtbase >> 9) { fatal("rpio-pwm: Failed to seek on %s: %m\n", pagemap_fn); } for (i = 0; i < channels[channel].num_pages; i++) { uint64_t pfn; channels[channel].page_map[i].virtaddr = channels[channel].virtbase + i * PAGE_SIZE; // Following line forces page to be allocated channels[channel].page_map[i].virtaddr[0] = 0; if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn)) fatal("rpio-pwm: Failed to read %s: %m\n", pagemap_fn); if (((pfn >> 55) & 0x1bf) != 0x10c) fatal("rpio-pwm: Page %d not present (pfn 0x%016llx)\n", i, pfn); channels[channel].page_map[i].physaddr = (uint32_t)pfn << PAGE_SHIFT | 0x40000000; } close(fd); close(memfd); return 0; } static int init_virtbase(int channel) { channels[channel].virtbase = mmap(NULL, channels[channel].num_pages * PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED, -1, 0); if (channels[channel].virtbase == MAP_FAILED) fatal("rpio-pwm: Failed to mmap physical pages: %m\n"); if ((unsigned long)channels[channel].virtbase & (PAGE_SIZE-1)) fatal("rpio-pwm: Virtual address is not page aligned\n"); return 0; } // Initialize control block for this channel static int init_ctrl_data(int channel) { dma_cb_t *cbp = (dma_cb_t *) get_cb(channel); uint32_t *sample = (uint32_t *) channels[channel].virtbase; uint32_t phys_gpset0 = 0x7e200000 + 0x1c; uint32_t phys_fifo_addr; uint32_t phys_gpclr0 = 0x7e200000 + 0x28; int i; channels[channel].dma_reg = map_peripheral(DMA_BASE, DMA_LEN) + (DMA_CHANNEL_INC * channel); if (channels[channel].dma_reg == NULL) return -1; phys_fifo_addr = (PWM_BASE | 0x7e000000) + 0x18; // Reset complete per-sample gpio mask to 0 memset(sample, 0, sizeof(channels[channel].num_samples * sizeof(uint32_t))); #define Setcbbp() cbp->length = 4; cbp->stride = 0; cbp->next = mem_virt_to_phys(channel, cbp + 1); cbp++; #define FLAG_BUF 0 #if FLAG_BUF // add a control block at start to flag writing this half of buffer cbp->info = DMA_NO_WIDE_BURSTS; cbp->src = mem_virt_to_phys(channel, sample + channels[channel].num_samples+1); cbp->dst = mem_virt_to_phys(channel, sample + channels[channel].num_samples); Setcbbp(); cbp->info = DMA_NO_WIDE_BURSTS; cbp->src = mem_virt_to_phys(channel, sample + channels[channel].num_samples+1); cbp->dst = mem_virt_to_phys(channel, sample + channels[channel].num_samples); Setcbbp(); #endif for (i = 0; i < channels[channel].num_samples; i++) { cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP; cbp->src = mem_virt_to_phys(channel, sample + i); cbp->dst = phys_gpclr0; Setcbbp(); // Delay cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5); cbp->src = mem_virt_to_phys(channel, sample); // Any data will do cbp->dst = phys_fifo_addr; Setcbbp(); #if FLAG_BUF // add an extra flag control block to show writing top half of buffer if(i == channels[channel].num_samples/2 ) { cbp->info = DMA_NO_WIDE_BURSTS; cbp->src = mem_virt_to_phys(channel, sample + channels[channel].num_samples+2); cbp->dst = mem_virt_to_phys(channel, sample + channels[channel].num_samples); Setcbbp(); cbp->info = DMA_NO_WIDE_BURSTS; cbp->src = mem_virt_to_phys(channel, sample + channels[channel].num_samples+2); cbp->dst = mem_virt_to_phys(channel, sample + channels[channel].num_samples); Setcbbp(); } #endif cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP; cbp->src = mem_virt_to_phys(channel, sample + i); // src contains mask of which gpios need change at this sample cbp->dst = phys_gpset0; //phys_gpclr0; // set each sample to clear set gpios by default Setcbbp(); // Delay cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5); cbp->src = mem_virt_to_phys(channel, sample); // Any data will do cbp->dst = phys_fifo_addr; Setcbbp(); } // The last control block links back to the first (= endless loop) cbp--; cbp->next = mem_virt_to_phys(channel, get_cb(channel)); // Initialize the DMA channel 0 (p46, 47) channels[channel].dma_reg[DMA_CS] = DMA_RESET; // DMA channel reset udelay(10); channels[channel].dma_reg[DMA_CS] = DMA_INT | DMA_END; // Interrupt status & DMA end flag channels[channel].dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(channel, get_cb(channel)); // initial CB channels[channel].dma_reg[DMA_DEBUG] = 7; // clear debug error flags channels[channel].dma_reg[DMA_CS] = 0x10880001; // go, mid priority, wait for outstanding writes return 0; } // Initialize PWM or PCM hardware once for all channels (10MHz) static void init_hardware(void) { // Initialise PWM pwm_reg[PWM_CTL] = 0; udelay(10); clk_reg[PWMCLK_CNTL] = 0x5A000006; // Source=PLLD (500MHz) udelay(100); clk_reg[PWMCLK_DIV] = 0x5A000000 | (50<<12); // set pwm div to 50, giving 10MHz udelay(100); clk_reg[PWMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable udelay(100); pwm_reg[PWM_RNG1] = pulse_width_incr_us * 10; udelay(10); pwm_reg[PWM_DMAC] = PWMDMAC_ENAB | PWMDMAC_THRSHLD; udelay(10); pwm_reg[PWM_CTL] = PWMCTL_CLRF; udelay(10); pwm_reg[PWM_CTL] = PWMCTL_USEF1 | PWMCTL_PWEN1; udelay(10); } int init_channel(int channel, int subcycle_time_us) { log_debug("Initializing channel %d...\n", channel); if (_is_setup == 0) fatal("Error: you need to call `setup(..)` before initializing channels\n"); if (channel > DMA_CHANNELS-1) fatal("Error: maximum channel is %d (requested channel %d)\n", DMA_CHANNELS-1, channel); // Setup Data channels[channel].num_flags = 8; channels[channel].subcycle_time_us = subcycle_time_us; channels[channel].num_samples = channels[channel].subcycle_time_us / pulse_width_incr_us; channels[channel].width_max = channels[channel].num_samples - 1; channels[channel].num_cbs = (channels[channel].num_samples * 4) + 4; channels[channel].num_pages = ((channels[channel].num_cbs * 32 + channels[channel].num_samples * 4 + channels[channel].num_flags * 4 + \ PAGE_SIZE - 1) >> PAGE_SHIFT); // Initialize channel if (init_virtbase(channel) == -1) return -1; if (make_pagemap(channel) == -1) return -1; if (init_ctrl_data(channel) == -1) return -1; return 0; } int print_channel(int channel) { if (channel > DMA_CHANNELS - 1) fatal("Error: you tried to print channel %d, but max channel is %d\n", channel, DMA_CHANNELS-1); log_debug("Subcycle time: %dus\n", channels[channel].subcycle_time_us); log_debug("PW Increments: %dus\n", pulse_width_incr_us); log_debug("Num samples: %d\n", channels[channel].num_samples); log_debug("Num CBS: %d\n", channels[channel].num_cbs); log_debug("Num pages: %d\n", channels[channel].num_pages); return 0; } int setup(int pw_incr_us) { pulse_width_incr_us = pw_incr_us; if (_is_setup == 1) fatal("Error: setup(..) has already been called before\n"); log_debug("PW increments: %dus\n", pulse_width_incr_us); setup_sighandlers(); pwm_reg = map_peripheral(PWM_BASE, PWM_LEN); clk_reg = map_peripheral(CLK_BASE, CLK_LEN); gpio_reg = map_peripheral(GPIO_BASE, GPIO_LEN); if (pwm_reg == NULL || clk_reg == NULL || gpio_reg == NULL) return -1; init_hardware(); _is_setup = 1; return 0; } int freq(int channel, int gpio, int width) { int i; uint32_t phys_gpclr0 = 0x7e200000 + 0x28; uint32_t phys_gpset0 = 0x7e200000 + 0x1c; uint32_t *dp = (uint32_t *) channels[channel].virtbase; // this is for the buffer sync position flags... *(dp + channels[channel].num_samples+1) = 1; *(dp + channels[channel].num_samples+2) = 0; log_debug("freq: channel=%d, gpio=%d, width=%d\n", channel, gpio, width); if ((gpio_setup & 1<<gpio) == 0){ log_debug("init_gpio %d\n", gpio); gpio_reg[GPIO_CLR0] = 1 << gpio; gpio_set_mode(gpio, GPIO_MODE_OUT); gpio_setup |= 1 << gpio; } { int j=0, oon=1; for (i = 0; i < channels[channel].num_samples ; i++) { if(j== width){ j=0; oon= !oon; } if(oon){ *(dp + i) &= ~(1 << gpio); } // set 0 else{ *(dp + i) |= 1 << gpio; } // set 1 j++; } } return 0; } // prints out bit patterns sent to gpio 22/23/24 int print_buf(int channel) { int i; uint32_t *dp = (uint32_t *) channels[channel].virtbase; log_debug("buffer: \n"); for (i = 0; i < channels[channel].num_samples ; i++) { log_debug("%d%d%d\n", *(dp + i) & (1 << 22)? 0:1 , *(dp + i) & (1 << 23)? 0:1, *(dp + i) & (1 << 24)? 0:1); } log_debug("\n"); return 0; } int main(int argc, char **argv) { setup(PULSE_WIDTH_INCREMENT_GRANULARITY_US_DEFAULT); int gpio[3]={22,23,24}, period[4]={50,10,5,2}; int channel = 0,i=0; int subcycle_time_us = SUBCYCLE_TIME_US_DEFAULT; init_channel(channel, subcycle_time_us); print_channel(channel); freq( channel, gpio[0], 50); freq( channel, gpio[1], 5); freq( channel, gpio[2], 10); print_buf(channel); sleep(30); freq( channel, gpio[0], 1); freq( channel, gpio[1], 20); freq( channel, gpio[2], 2); print_buf(channel); sleep(30); shutdown(); exit(0); }

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

More
13 years 6 months ago #33150 by andypugh
I will try to get to this tonight, but getting my motorcycle working again is a slightly higher priority.

One thing that did occur to me is that if you have a conventional PC running LinuxCNC then you can halscope reading the p-port in the base thread as a DSO. You need the step length on the Pi to be longer than the base period of the PC, though.

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

More
13 years 6 months ago #33168 by mungkie

One thing that did occur to me is that if you have a conventional PC running LinuxCNC then you can halscope reading the p-port in the base thread as a DSO. You need the step length on the Pi to be longer than the base period of the PC, though.


Unfortunately I don't have a linuxcnc system that runs a base thread above 10khz I never really bothered trying to tune my linuxcnc setups as the stepper drives cannot run faster.

But I had a quick search and found an interesting link that may also be relevant to linuxcnc rpi gpio DMA drivers:

www.raspberrypi.org/phpBB3/viewtopic.php...b2d8a0cf16e&start=50

Seems the 'panalyzer' system runs on the rpi and can sample gpio at around ~20MHz, again using DMA but also I think some interupt masking maybe involved, and I think it looks like a system clock is used for better timing rather than the pwm (I am always unsure what other parts of the system maybe using clocks and interupts that may cause conflicts).

I am wondering if other parts of the linux kernel system maybe trying to use the DMA or causing interupts that effect the DMA, or maybe the scope is just faulty?

I am going to continue searching and wait on this for a while see what else turns up, there maybe easier or better ways to get gpio switched without effecting system performance.

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

More
13 years 6 months ago #33171 by andypugh
I am not seeing any glitches on my scope.
The following user(s) said Thank You: mungkie

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

More
13 years 6 months ago #33175 by mungkie
Okay if you sure there are no glitches then I suppose I should continue, and I almost feel hopeful that linuxcnc could soon work very nicely on the rpi (I am also pissed off as I just spent out £25 on pic devel parts to make an offboard controller).

I think should be possible to fix the update_pos function to give correct position so there are no joint follow errors.

Rough outline of maths, the step pattern buffer must be 4000us and the pulse width can be anything down to 1us but there could be large memory requirements as each step requires 168bytes (1us steps would be 4000*168bytes of DMA buffer)

There is probably something that I am not seeing and this will be another fail, but I am guessing I could have the driver finished for testing with an evenings coding so long as I don't get brain overload and start writing nonsense.

Just in case I am missing something I suppose I should ask if anyone knows of any hidden signals that are not listed in the .hal config that secretly connect into tasks that my effect functions in base-thread or servo-thread, I say as an example 'iocontrol.0.user-enable-out iocontrol.0.emc-enable-in' which are used in .hal file but 'iocontrol' do not seem to have an addf hook also same with 'axis'.

I am really a newbie to the linuxcnc internals so if anyone with low level knowledge can offer advice, especially if I am wrong that would be helpful, otherwise I suppose I will just hack on with what I am doing.

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

More
13 years 6 months ago - 13 years 6 months ago #33182 by ArcEye
Hi

Just in case I am missing something I suppose I should ask if anyone knows of any hidden signals that are not listed in the .hal config that secretly connect into tasks that my effect functions in base-thread or servo-thread, I say as an example 'iocontrol.0.user-enable-out iocontrol.0.emc-enable-in' which are used in .hal file but 'iocontrol' do not seem to have an addf hook also same with 'axis'.

The implementation of some of the startup stuff is hidden from the user config, even though it uses the .ini file.

Have a look at the linuxcnc script in /usr/bin

In section 4 it makes various halcmd calls, which amongst other things load iocontrol and halui based upon .ini file settings.
(they are both actually userspace modules)

You then need to look at the code for halcmd too, to get the two pass loading of all the hal modules listed in the HALFILE entries of the .ini file,

regards
Last edit: 13 years 6 months ago by ArcEye.

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

Time to create page: 0.232 seconds
Powered by Kunena Forum