/* * Copyright (C) 2016 Intel Corporation. All rights reserved. * * This file is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This file 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "RCOutput_AeroIO.h" #include #include #include #include using namespace Linux; // Device name in @SPIDeviceDriver#_device #define DEVICE_NAME "aeroio" // Number of channels #define PWM_CHAN_COUNT 16 // Set all channels #define ALL_CHAN_MASK ((1 << PWM_CHAN_COUNT) - 1) // Default PWM frequency #define DEFAULT_FREQ 400 // Default PWM duty cycle #define DEFAULT_DUTY 0 // Set or Clear MSb of BYTE #define WADDRESS(x) ((x) | 0x8000) #define RADDRESS(x) ((x) & 0x7FFF) // Variables to perform ongoing tests #define READ_PREFIX 0x80 #define WRITE_PREFIX 0x40 /** * The data_array uses 3 elements to perform the data transaction. * The first element is a data byte that provides to FPGA's hardware * the transaction type that will be realized inside the SPI module. * Where: * * ╔═════════╦═════════╦══════════╦══════════╦══════════╦══════════╦══════════╦═══════════╗ * ║ MSB ║ ║ ║ ║ ║ ║ ║ LSB ║ * ╠═════════╬═════════╬══════════╬══════════╬══════════╬══════════╬══════════╬═══════════╣ * ║ wr_addr ║ rd_addr ║ reserved ║ reserved ║ reserved ║ reserved ║ reserved ║ reserved ║ * ╚═════════╩═════════╩══════════╩══════════╩══════════╩══════════╩══════════╩═══════════╝ * * ╔═══════════╦═════════╦═════════╗ * ║ Register ║ wr_addr ║ rd_addr ║ * ╠═══════════╬═════════╬═════════╣ * ║ write ║ 0 ║ X ║ * ╠═══════════╬═════════╬═════════╣ * ║ read ║ X ║ 0 ║ * ╠═══════════╬═════════╬═════════╣ * ║ status ║ 1 ║ 1 ║ * ╚═══════════╩═════════╩═════════╝ * * So, to perform a write transaction in the SPI module it's necessary to send. E.g: * 0b 01xx xxxx * And to a read transaction.. * 0b 10xx xxxx * * The PWM frequency is always even and the duty cycle percentage odd. E.g: * pwm_01: Address 0x0000 frequency * : Address 0x0001 duty cycle * pwm_02: Address 0x0002 frequency * . * . * . * * Eg of allowed values: * // PWM channel in 100Hz * uint16_t freq = 100; * * // duty cycle in (1823/65535) that's 2.78% of 100Hz: * // the signal will hold high until 278 usec * uint16_t duty = 1823; */ extern const AP_HAL::HAL& hal; RCOutput_AeroIO::RCOutput_AeroIO() : _freq_buffer(NEW_NOTHROW uint16_t[PWM_CHAN_COUNT]) , _duty_buffer(NEW_NOTHROW uint16_t[PWM_CHAN_COUNT]) { } RCOutput_AeroIO::~RCOutput_AeroIO() { delete[] _freq_buffer; delete[] _duty_buffer; } void RCOutput_AeroIO::init() { _spi = std::move(hal.spi->get_device(DEVICE_NAME)); if (!_spi) { AP_HAL::panic("Could not initialize AeroIO"); } // Reset all channels to default value cork(); set_freq(ALL_CHAN_MASK, DEFAULT_FREQ); for (uint8_t i = 0; i < PWM_CHAN_COUNT; i++) { write(i, DEFAULT_DUTY); } push(); } void RCOutput_AeroIO::set_freq(uint32_t chmask, uint16_t freq_hz) { _pending_freq_write_mask |= chmask; for (uint8_t i = 0; i < PWM_CHAN_COUNT; i++) { if ((chmask >> i) & 0x01) { _freq_buffer[i] = freq_hz; } } if (!_corking) { _corking = true; push(); } } uint16_t RCOutput_AeroIO::get_freq(uint8_t ch) { if (ch >= PWM_CHAN_COUNT) { return 0; } return _freq_buffer[ch]; } void RCOutput_AeroIO::enable_ch(uint8_t ch) { if (ch >= PWM_CHAN_COUNT) { return; } _pending_duty_write_mask |= (1U << ch); _corking = true; push(); } void RCOutput_AeroIO::disable_ch(uint8_t ch) { if (ch >= PWM_CHAN_COUNT) { return; } _duty_buffer[ch] = 0; _pending_duty_write_mask |= (1U << ch); _corking = true; push(); } void RCOutput_AeroIO::write(uint8_t ch, uint16_t period_us) { _pending_duty_write_mask |= (1U << ch); _duty_buffer[ch] = period_us; if (!_corking) { _corking = true; push(); } } void RCOutput_AeroIO::cork() { _corking = true; } void RCOutput_AeroIO::push() { if (!_corking) { return; } _corking = false; for (uint8_t i = 0; i < PWM_CHAN_COUNT; i++) { if ((_pending_freq_write_mask >> i) & 0x01) { _hw_write(2 * i + 1, _freq_buffer[i]); } } for (uint8_t i = 0; i < PWM_CHAN_COUNT; i++) { if ((_pending_duty_write_mask >> i) & 0x01) { _hw_write(2 * i, _usec_to_hw(_freq_buffer[i], _duty_buffer[i])); } } _pending_freq_write_mask = _pending_duty_write_mask = 0; } uint16_t RCOutput_AeroIO::read(uint8_t ch) { if (ch >= PWM_CHAN_COUNT) { return 0; } #ifndef AEROIO_DEBUG return _duty_buffer[ch]; #else return _hw_to_usec(_freq_buffer[ch], _hw_read(2 * ch)); #endif } void RCOutput_AeroIO::read(uint16_t *period_us, uint8_t len) { for (uint8_t i = 0; i < len; i++) { period_us[i] = read(i); } } bool RCOutput_AeroIO::_hw_write(uint16_t address, uint16_t data) { struct PACKED { uint8_t prefix; be16_t addr; be16_t val; } tx; address = WADDRESS(address); tx.prefix = WRITE_PREFIX; tx.addr = htobe16(address); tx.val = htobe16(data); return _spi->transfer((uint8_t *)&tx, sizeof(tx), nullptr, 0); } uint16_t RCOutput_AeroIO::_hw_read(uint16_t address) { struct PACKED { uint8_t prefix; be16_t addr; } tx; struct PACKED { uint8_t ignored[2]; be16_t val; } rx; address = RADDRESS(address); // Write in the SPI buffer the address value tx.prefix = WRITE_PREFIX; tx.addr = htobe16(address); if (!_spi->transfer((uint8_t *)&tx, sizeof(tx), nullptr, 0)) { return 0; } /* * Read the SPI buffer, sending only the prefix as tx * The hardware will fill in 32 bits after the request */ tx.prefix = READ_PREFIX; if (!_spi->transfer((uint8_t *)&tx, 1, (uint8_t *)&rx, sizeof(rx))) { return 0; } return be16toh(rx.val); } uint16_t RCOutput_AeroIO::_usec_to_hw(uint16_t freq, uint16_t usec) { float f = freq; float u = usec; return 0xFFFF * u * f / AP_USEC_PER_SEC; } uint16_t RCOutput_AeroIO::_hw_to_usec(uint16_t freq, uint16_t hw_val) { float p = hw_val; float f = freq; return p * AP_USEC_PER_SEC / (0xFFFF * f); }