/* * This file is part of RPIO-PWM. * * Copyright * * Copyright (C) 2020 Xinkai Wang * * License * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details at * * * Documentation * * https://github.com/xinkaiwang/rpio-pwm * * dma.c, based on the excellent servod.c by Richard Hirst, provides flexible * PWM via DMA for the Raspberry Pi, supporting a resolution of up to 1us, * all 15 DMA channels, multiple GPIOs per channel, timing by PWM (default) * or PCM, a Python wrapper, and more. * * Feedback is much appreciated. */ #include "dma.hpp" #include #include #include #include #include "mailbox.h" #define DMY 255 // Used to represent an invalid P1 pin, or unmapped servo #define NUM_P1PINS 40 #define NUM_P5PINS 8 #define MAX_MEMORY_USAGE \ (16 * 1024 * 1024) /* Somewhat arbitrary limit of 16MB */ #define DEFAULT_CYCLE_TIME_US 20000 #define DEFAULT_STEP_TIME_US 10 #define DEFAULT_SERVO_MIN_US 500 #define DEFAULT_SERVO_MAX_US 2500 #define DEVFILE "/dev/servoblaster" #define CFGFILE "/dev/servoblaster-cfg" #define PAGE_SIZE 4096 #define PAGE_SHIFT 12 #define DMA_CHAN_SIZE 0x100 #define DMA_CHAN_MIN 0 #define DMA_CHAN_MAX 14 #define DMA_CHAN_DEFAULT 14 #define DMA_CHAN_PI4 7 #define DMA_BASE_OFFSET 0x00007000 #define DMA_LEN DMA_CHAN_SIZE *(DMA_CHAN_MAX + 1) #define PWM_BASE_OFFSET 0x0020C000 #define PWM_LEN 0x28 #define CLK_BASE_OFFSET 0x00101000 #define CLK_LEN 0xA8 #define GPIO_BASE_OFFSET 0x00200000 #define GPIO_LEN 0x100 #define PCM_BASE_OFFSET 0x00203000 #define PCM_LEN 0x24 #define DMA_VIRT_BASE(hw) ((hw).periph_virt_base + DMA_BASE_OFFSET) #define PWM_VIRT_BASE(hw) ((hw).periph_virt_base + PWM_BASE_OFFSET) #define CLK_VIRT_BASE(hw) ((hw).periph_virt_base + CLK_BASE_OFFSET) #define GPIO_VIRT_BASE(hw) ((hw).periph_virt_base + GPIO_BASE_OFFSET) #define PCM_VIRT_BASE(hw) ((hw).periph_virt_base + PCM_BASE_OFFSET) #define PWM_PHYS_BASE(hw) ((hw).periph_phys_base + PWM_BASE_OFFSET) #define PCM_PHYS_BASE(hw) ((hw).periph_phys_base + PCM_BASE_OFFSET) #define GPIO_PHYS_BASE(hw) ((hw).periph_phys_base + GPIO_BASE_OFFSET) #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 (1U << 31) #define DMA_INT (1 << 2) #define DMA_CS (0x00 / 4) #define DMA_CONBLK_AD (0x04 / 4) #define DMA_SOURCE_AD (0x0c / 4) #define DMA_DEBUG (0x20 / 4) #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) #define GPIO_MODE_IN 0 #define GPIO_MODE_OUT 1 #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 (1U << 31) #define PWMDMAC_THRSHLD ((15 << 8) | (15 << 0)) #define PCM_CS_A (0x00 / 4) #define PCM_FIFO_A (0x04 / 4) #define PCM_MODE_A (0x08 / 4) #define PCM_RXC_A (0x0c / 4) #define PCM_TXC_A (0x10 / 4) #define PCM_DREQ_A (0x14 / 4) #define PCM_INTEN_A (0x18 / 4) #define PCM_INT_STC_A (0x1c / 4) #define PCM_GRAY (0x20 / 4) #define PCMCLK_CNTL 38 #define PCMCLK_DIV 39 #define PLLDFREQ_MHZ_DEFAULT 500 #define PLLDFREQ_MHZ_PI4 750 // #define DELAY_VIA_PWM 0 // #define DELAY_VIA_PCM 1 #define ROUNDUP(val, blksz) (((val) + ((blksz)-1)) & ~(blksz - 1)) #define BUS_TO_PHYS(x) ((x) & ~0xC0000000) using namespace wpp; namespace { // DmaHardware hardware{}; // L362 static struct { int handle; /* From mbox_open() */ uint32_t size; /* Required size */ unsigned mem_ref; /* From mem_alloc() */ unsigned bus_addr; /* From mem_lock() */ uint8_t *virt_addr; /* From mapmem() */ } mbox; void set_servo(DmaChannel &ch, int servo, int width); // L375 static void udelay(int us) { struct timespec ts = {0, us * 1000}; nanosleep(&ts, NULL); } // L384 // void terminateChannel(DmaChannel &ch) { // if (ch.dma_reg && mbox.virt_addr) { // for (int i = 0; i < maxServoCount; i++) { // if (ch.pins[i]) { // set_servo(ch, i, 0); // } // } // udelay(ch.cycle_time_us); // ch.dma_reg[DMA_CS] = DMA_RESET; // udelay(10); // } // // if (restore_gpio_modes) { // // for (i = 0; i < MAX_SERVOS; i++) { // // if (servo2gpio[i] != DMY) // // gpio_set_mode(servo2gpio[i], gpiomode[i]); // // } // // } // } void terminate(int dummy) { if (DmaHardware::GetInstance().current_log_level >= LogLevel::Info) { printf("terminate() %d\n", dummy); } for (auto &it : DmaHardware::GetInstance().channels) { if (auto locked = it.lock()) { locked->DeactivateChannel(); } } if (mbox.virt_addr != NULL) { unmapmem(mbox.virt_addr, mbox.size); mem_unlock(mbox.handle, mbox.mem_ref); mem_free(mbox.handle, mbox.mem_ref); if (mbox.handle >= 0) mbox_close(mbox.handle); } exit(1); } // L480 static uint32_t gpio_get_mode(DmaChannel &ch, uint32_t gpio) { uint32_t fsel = ch.gpio_reg[GPIO_FSEL0 + gpio / 10]; return (fsel >> ((gpio % 10) * 3)) & 7; } // L488 static void gpio_set_mode(DmaChannel &ch, uint32_t gpio, uint32_t mode) { uint32_t fsel = ch.gpio_reg[GPIO_FSEL0 + gpio / 10]; fsel &= ~(7 << ((gpio % 10) * 3)); fsel |= mode << ((gpio % 10) * 3); ch.gpio_reg[GPIO_FSEL0 + gpio / 10] = fsel; } // L498 static void gpio_set(DmaChannel &ch, int gpio, int level) { if (level) ch.gpio_reg[GPIO_SET0] = 1 << gpio; else ch.gpio_reg[GPIO_CLR0] = 1 << gpio; } // L506 static uint32_t mem_virt_to_phys(void *virt) { uint32_t offset = (uint8_t *)virt - mbox.virt_addr; return mbox.bus_addr + offset; } // L515 static void *map_peripheral(uint32_t base, uint32_t len) { int fd = open("/dev/mem", O_RDWR | O_SYNC); void *vaddr; if (fd < 0) fatal("rpio-pwm: Failed to open /dev/mem: %m\n"); 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); close(fd); return vaddr; } /* Carefully add or remove bits from the turnoff_mask such that regardless * of where the DMA controller is in its cycle, and whether we are increasing * or decreasing the pulse width, the generated pulse will only ever be the * old width or the new width. If we don't take such care then there could be * a cycle with some pulse width between the two requested ones. That doesn't * really matter for servos, but when driving LEDs some odd intensity for one * cycle can be noticeable. It may be that the servo output has been turned * off via the inactivity timer, which is handled by always setting the turnon * mask appropriately at the end of this function. */ // L556 void set_servo(DmaChannel &ch, int servo, int newWidth) { volatile uint32_t *dp; int i; uint32_t mask = 1 << ch.pins[servo]->gpioPinNum; int oldWidth = ch.pins[servo]->servowidth; if (ch.hw.current_log_level >= LogLevel::Debug) { printf("set_servo oldWidth=%d new=%d\n", oldWidth, newWidth); } if (newWidth > oldWidth) { dp = ch.turnoff_mask + ch.servostart[servo] + newWidth; if (dp >= ch.turnoff_mask + ch.num_samples) dp -= ch.num_samples; for (i = newWidth; i > oldWidth; i--) { dp--; if (dp < ch.turnoff_mask) dp = ch.turnoff_mask + ch.num_samples - 1; // printf("%5d, clearing at %p\n", dp - ctl->turnoff, dp); *dp &= ~mask; } } else if (newWidth < oldWidth) { dp = ch.turnoff_mask + ch.servostart[servo] + newWidth; if (dp >= ch.turnoff_mask + ch.num_samples) dp -= ch.num_samples; for (i = newWidth; i < oldWidth; i++) { // printf("%5d, setting at %p\n", dp - ctl->turnoff, dp); *dp++ |= mask; if (dp >= ch.turnoff_mask + ch.num_samples) dp = ch.turnoff_mask; } } ch.pins[servo]->servowidth = newWidth; if (newWidth == 0) { ch.turnon_mask[servo] = 0; } else { ch.turnon_mask[servo] = mask; } } void set_mask_all(DmaChannel &ch, int servo) { volatile uint32_t *dp; uint32_t mask = 1 << ch.pins[servo]->gpioPinNum; for (dp = ch.turnoff_mask; dp < ch.turnoff_mask + ch.num_samples;) { *dp++ |= mask; } } void clear_mask_all(DmaChannel &ch, int servo) { volatile uint32_t *dp; uint32_t mask = 1 << ch.pins[servo]->gpioPinNum; for (dp = ch.turnoff_mask; dp < ch.turnoff_mask + ch.num_samples;) { *dp++ &= ~mask; } } // L596 // static void setup_sighandlers(void) { // int i; // // Catch all signals possible - it is vital we kill the DMA engine // // on process exit! // for (i = 0; i < 64; i++) { // struct sigaction sa; // memset(&sa, 0, sizeof(sa)); // sa.sa_handler = terminate; // sigaction(i, &sa, NULL); // } // } // L612 void init_ctrl_data(DmaHardware &hw, DmaChannel &ch) { dma_cb_t *cbp = ch.cb_base; uint32_t phys_fifo_addr, cbinfo; uint32_t phys_gpclr0; uint32_t phys_gpset0; int curstart = 0; uint32_t maskall = 0; if (ch.invert) { phys_gpclr0 = GPIO_PHYS_BASE(hw) + 0x1c; phys_gpset0 = GPIO_PHYS_BASE(hw) + 0x28; } else { phys_gpclr0 = GPIO_PHYS_BASE(hw) + 0x28; phys_gpset0 = GPIO_PHYS_BASE(hw) + 0x1c; } if (ch.delay_hw == DelayHardware::DELAY_VIA_PWM) { phys_fifo_addr = PWM_PHYS_BASE(hw) + 0x18; cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5); } else { phys_fifo_addr = PCM_PHYS_BASE(hw) + 0x04; cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(2); } memset(ch.turnon_mask, 0, maxServoCount * sizeof(*(ch.turnon_mask))); // for (servo = 0 ; servo < MAX_SERVOS; servo++) { // servowidth[servo] = 0; // if (servo2gpio[servo] != DMY) { // numservos++; // maskall |= 1 << servo2gpio[servo]; // } // } for (int i = 0; i < ch.num_samples; i++) ch.turnoff_mask[i] = maskall; for (uint32_t servo = 0; servo < ch.pins.size(); servo++) { ch.servostart[servo] = curstart; curstart += ch.num_samples / ch.pins.size(); } for (int i = 0, servo = 0; i < ch.num_samples; i++) { cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP; cbp->src = mem_virt_to_phys(ch.turnoff_mask + i); cbp->dst = phys_gpclr0; cbp->length = 4; cbp->stride = 0; cbp->next = mem_virt_to_phys(cbp + 1); cbp++; if (servo < maxServoCount && i == ch.servostart[servo]) { cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP; cbp->src = mem_virt_to_phys(ch.turnon_mask + servo); cbp->dst = phys_gpset0; cbp->length = 4; cbp->stride = 0; cbp->next = mem_virt_to_phys(cbp + 1); cbp++; servo++; } // Delay cbp->info = cbinfo; cbp->src = mem_virt_to_phys(ch.turnoff_mask); // Any data will do cbp->dst = phys_fifo_addr; cbp->length = 4; cbp->stride = 0; cbp->next = mem_virt_to_phys(cbp + 1); cbp++; } cbp--; cbp->next = mem_virt_to_phys(ch.cb_base); } // L695 void init_hardware(DmaHardware &hw, DmaChannel &ch) { if (ch.delay_hw == DelayHardware::DELAY_VIA_PWM) { // Initialise PWM ch.pwm_reg[PWM_CTL] = 0; udelay(10); ch.clk_reg[PWMCLK_CNTL] = 0x5A000006; // Source=PLLD (500MHz or 750MHz on Pi4) udelay(100); ch.clk_reg[PWMCLK_DIV] = 0x5A000000 | (hw.plldfreq_mhz << 12); // set pwm div to give 1MHz udelay(100); ch.clk_reg[PWMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable udelay(100); ch.pwm_reg[PWM_RNG1] = ch.step_time_us; udelay(10); ch.pwm_reg[PWM_DMAC] = PWMDMAC_ENAB | PWMDMAC_THRSHLD; udelay(10); ch.pwm_reg[PWM_CTL] = PWMCTL_CLRF; udelay(10); ch.pwm_reg[PWM_CTL] = PWMCTL_USEF1 | PWMCTL_PWEN1; udelay(10); } else { // Initialise PCM ch.pcm_reg[PCM_CS_A] = 1; // Disable Rx+Tx, Enable PCM block udelay(100); ch.clk_reg[PCMCLK_CNTL] = 0x5A000006; // Source=PLLD (500MHz or 750MHz on Pi4) udelay(100); ch.clk_reg[PCMCLK_DIV] = 0x5A000000 | (hw.plldfreq_mhz << 12); // Set pcm div to give 1MHz udelay(100); ch.clk_reg[PCMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable udelay(100); ch.pcm_reg[PCM_TXC_A] = 0U << 31 | 1 << 30 | 0 << 20 | 0 << 16; // 1 channel, 8 bits udelay(100); ch.pcm_reg[PCM_MODE_A] = (ch.step_time_us - 1) << 10; udelay(100); ch.pcm_reg[PCM_CS_A] |= 1 << 4 | 1 << 3; // Clear FIFOs udelay(100); ch.pcm_reg[PCM_DREQ_A] = 64 << 24 | 64 << 8; // DMA Req when one slot is free? udelay(100); ch.pcm_reg[PCM_CS_A] |= 1 << 9; // Enable DMA udelay(100); } // Initialise the DMA ch.dma_reg[DMA_CS] = DMA_RESET; udelay(10); ch.dma_reg[DMA_CS] = DMA_INT | DMA_END; ch.dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ch.cb_base); ch.dma_reg[DMA_DEBUG] = 7; // clear debug error flags ch.dma_reg[DMA_CS] = 0x10880001; // go, mid priority, wait for outstanding writes if (ch.delay_hw == DelayHardware::DELAY_VIA_PCM) { ch.pcm_reg[PCM_CS_A] |= 1 << 2; // Enable Tx } } /* Determining the board revision is a lot more complicated than it should be * (see comments in wiringPi for details). We will just look at the last two * digits of the Revision string and treat '00' and '01' as errors, '02' and * '03' as rev 1, and any other hex value as rev 2. 'Pi1 and Pi2 are * differentiated by the Hardware being BCM2708 or BCM2709. * * NOTE: These days we should just use bcm_host_get_model_type(). */ // L945 void get_model_and_revision(DmaHardware &hw) { char buf[128], revstr[128], modelstr[128]; char *ptr, *end, *res; int board_revision; FILE *fp; revstr[0] = modelstr[0] = '\0'; fp = fopen("/proc/cpuinfo", "r"); if (!fp) fatal("Unable to open /proc/cpuinfo: %m\n"); while ((res = fgets(buf, 128, fp))) { if (!strncasecmp("hardware", buf, 8)) memcpy(modelstr, buf, 128); else if (!strncasecmp(buf, "revision", 8)) memcpy(revstr, buf, 128); } fclose(fp); if (modelstr[0] == '\0') fatal("rpio-pwm: No 'Hardware' record in /proc/cpuinfo\n"); if (revstr[0] == '\0') fatal("rpio-pwm: No 'Revision' record in /proc/cpuinfo\n"); if (strstr(modelstr, "BCM2708")) hw.board_model = 1; else if (strstr(modelstr, "BCM2709") || strstr(modelstr, "BCM2835") || strstr(modelstr, "BCM2711")) hw.board_model = 2; else fatal("rpio-pwm: Cannot parse the hardware name string\n"); /* Revisions documented at http://elinux.org/RPi_HardwareHistory */ ptr = revstr + strlen(revstr) - 3; board_revision = strtol(ptr, &end, 16); if (end != ptr + 2) fatal("rpio-pwm: Failed to parse Revision string\n"); if (board_revision < 1) fatal("rpio-pwm: Invalid board Revision\n"); else if (board_revision < 4) hw.gpio_cfg = 1; else if (board_revision < 16) hw.gpio_cfg = 2; else hw.gpio_cfg = 3; if (bcm_host_is_model_pi4() || strstr(modelstr, "BCM2711") ) { hw.plldfreq_mhz = PLLDFREQ_MHZ_PI4; hw.host_is_model_pi4 = true; // hw.dma_chan = DMA_CHAN_PI4; } else { hw.plldfreq_mhz = PLLDFREQ_MHZ_DEFAULT; hw.host_is_model_pi4 = false; // hw.dma_chan = DMA_CHAN_DEFAULT; } hw.periph_virt_base = bcm_host_get_peripheral_address(); hw.dram_phys_base = bcm_host_get_sdram_address(); hw.periph_phys_base = 0x7e000000; /* * See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface * * 1: MEM_FLAG_DISCARDABLE = 1 << 0 // can be resized to 0 at any time. Use * for cached data * MEM_FLAG_NORMAL = 0 << 2 // normal allocating alias. Don't * use from ARM 4: MEM_FLAG_DIRECT = 1 << 2 // 0xC alias uncached 8: * MEM_FLAG_COHERENT = 2 << 2 // 0x8 alias. Non-allocating in L2 but coherent * MEM_FLAG_L1_NONALLOCATING = // Allocating in L2 * (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) * 16: MEM_FLAG_ZERO = 1 << 4 // initialise buffer to all zeros * 32: MEM_FLAG_NO_INIT = 1 << 5 // don't initialise (default is * initialise to all ones 64: MEM_FLAG_HINT_PERMALOCK = 1 << 6 // * Likely to be locked for long periods of time * */ if (hw.board_model == 1) { hw.mem_flag = 0x0c; /* MEM_FLAG_DIRECT | MEM_FLAG_COHERENT */ } else { hw.mem_flag = 0x04; /* MEM_FLAG_DIRECT */ } } // L1184 main() bool setup_hardware(DmaHardware &hw) { get_model_and_revision(hw); // init_idle_timers(hw); // setup_sighandlers(); return true; } } // anonymous namespace namespace wpp { // L416 void fatal(const char *fmt, ...) { printf("fatal() %s\n", fmt); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); terminate(0); } //************************** DmaHardware **************************** // static DmaHardware &DmaHardware::GetInstance() { static DmaHardware hardware{}; static bool inited = false; if (!inited) { inited = setup_hardware(hardware); } return hardware; } //************************** DmaChannel **************************** // static std::shared_ptr DmaChannel::CreateInstance(const DmaChannelConfig &config) { auto &hw = DmaHardware::GetInstance(); auto ch = std::make_shared(hw, config); ch->Init(); DmaHardware::GetInstance().channels.push_back(ch); return ch; } DmaChannel::DmaChannel(DmaHardware &hw, const DmaChannelConfig &config) : hw{hw}, delay_hw{config.delay_hw}, chNum{config.chNum}, cycle_time_us{config.cycleTimeUs}, step_time_us{config.stepTimeUs}, invert{config.invert} { if (step_time_us < 2 || step_time_us > 1000) { fatal("Invalid step-size specified"); } if (cycle_time_us < 1000 || cycle_time_us > 1000000) { fatal("Invalid cycle-time specified"); } if (cycle_time_us % step_time_us) { fatal("cycle-time is not a multiple of step-size"); } if (cycle_time_us / step_time_us < 100) { fatal("cycle-time must be at least 100 * step-size"); } } void DmaChannel::Init() { num_samples = cycle_time_us / step_time_us; num_cbs = num_samples * 2 + maxServoCount; num_pages = (num_cbs * sizeof(dma_cb_t) + num_samples * 4 + maxServoCount * 4 + PAGE_SIZE - 1) >> PAGE_SHIFT; auto &ch = *this; ch.dma_reg = static_cast(map_peripheral(DMA_VIRT_BASE(hw), DMA_LEN)); ch.dma_reg += ch.chNum * DMA_CHAN_SIZE / sizeof(uint32_t); ch.pwm_reg = static_cast(map_peripheral(PWM_VIRT_BASE(hw), PWM_LEN)); ch.pcm_reg = static_cast(map_peripheral(PCM_VIRT_BASE(hw), PCM_LEN)); ch.clk_reg = static_cast(map_peripheral(CLK_VIRT_BASE(hw), CLK_LEN)); ch.gpio_reg = static_cast(map_peripheral(GPIO_VIRT_BASE(hw), GPIO_LEN)); /* Use the mailbox interface to the VC to ask for physical memory */ // Use the mailbox interface to request memory from the VideoCore // We specifiy (-1) for the handle rather than calling mbox_open() // so multiple users can share the resource. mbox.handle = -1; // mbox_open(); mbox.size = ch.num_pages * 4096; mbox.mem_ref = mem_alloc(mbox.handle, mbox.size, 4096, hw.mem_flag); if (mbox.mem_ref == 0) { fatal("Failed to alloc memory from VideoCore\n"); } mbox.bus_addr = mem_lock(mbox.handle, mbox.mem_ref); if (mbox.bus_addr == ~0U) { mem_free(mbox.handle, mbox.size); fatal("Failed to lock memory\n"); } mbox.virt_addr = static_cast(mapmem(BUS_TO_PHYS(mbox.bus_addr), mbox.size)); ch.turnoff_mask = (uint32_t *)mbox.virt_addr; ch.turnon_mask = (uint32_t *)(mbox.virt_addr + ch.num_samples * sizeof(uint32_t)); ch.cb_base = (dma_cb_t *)(mbox.virt_addr + ROUNDUP(ch.num_samples + maxServoCount, 8) * sizeof(uint32_t)); init_ctrl_data(hw, ch); init_hardware(hw, ch); ch.isActive = true; if (hw.current_log_level >= LogLevel::Info) { printf("DmaChannel::Init() ch=%d, cycle_time_us=%d, step_time_us=%d, " "num_samples=%d\n", chNum, cycle_time_us, step_time_us, num_samples); } } void DmaChannel::DeactivateChannel() { auto &ch = *this; if (!ch.IsActive()) return; if (hw.current_log_level >= LogLevel::Info) { printf("DmaChannel::DeactivateChannel() ch=%d\n", ch.chNum); } ch.isActive = false; for (int i = 0; i < maxServoCount; i++) { if (ch.pins[i]) { ch.pins[i]->DeactivatePin(); } } udelay(ch.cycle_time_us); ch.dma_reg[DMA_CS] = DMA_RESET; udelay(10); } std::shared_ptr DmaChannel::CreatePin(const DmaPwmPinConfig &pinConfig) { auto pin = std::make_shared(this->shared_from_this(), pinConfig); // find next available slot in pins int servo = 0; while (servo < maxServoCount && pins[servo] != nullptr) { servo++; } if (servo == maxServoCount) { fatal("run out of availabel slots"); } pins[servo] = pin; pin->Init(servo); set_mask_all(*this, servo); set_servo(*this, servo, pinConfig.widthInSteps); return pin; } //************************** PwmPin **************************** PwmPin::PwmPin(std::shared_ptr dmaChannel, const DmaPwmPinConfig &pinConfig) : ch{dmaChannel}, gpioPinNum{pinConfig.gpioPinNum}, restoreOnExit{pinConfig.restoreOnExit} { // } void PwmPin::Init(int slotIndex) { this->slotIndex = slotIndex; this->gpiomode = gpio_get_mode(*ch, gpioPinNum); gpio_set(*ch, gpioPinNum, ch->invert ? 1 : 0); gpio_set_mode(*ch, gpioPinNum, GPIO_MODE_OUT); } void PwmPin::SetByWidth(const int width) { if (width < 0 || width > ch->num_samples) { fatal("width out of range"); } set_servo(*ch, slotIndex, width); } void PwmPin::SetByPercentage(const float pct) { if (slotIndex < 0) { fatal("already deactivated?"); } if (pct < 0.0f || pct > 100.0f) { fatal("pct out of range"); } int newWidth = static_cast(pct * ch->num_samples / 100.0f); set_servo(*ch, slotIndex, newWidth); } void PwmPin::SetByActiveTimeUs(const int timeInUs) { if (slotIndex < 0) { fatal("already deactivated?"); } if (timeInUs < 0 || timeInUs > ch->cycle_time_us) { fatal("timeInUs out of range"); } int newWidth = timeInUs / ch->step_time_us; set_servo(*ch, slotIndex, newWidth); } void PwmPin::DeactivatePin() { if (slotIndex < 0) { // already deactivated? return; } // to avoid destruct before exit scope std::shared_ptr self = shared_from_this(); set_servo(*ch, slotIndex, 0 /*newWidth*/); clear_mask_all(*ch, slotIndex); ch->pins[slotIndex] = nullptr; slotIndex = -1; } //************************** TestDma **************************** void TestDma() { // setup_hardware(hardware); // auto ch = DmaChannel::CreateInstance(14, 20000,10); std::cout << "TestDma"; } } // namespace wpp