From c32dc3bc37e536bd3fac3c5d9866d64a99b72e75 Mon Sep 17 00:00:00 2001 From: "Patrick J.P" Date: Thu, 2 Jun 2016 13:18:13 -0300 Subject: [PATCH] AP_HAL_Linux: Add AeroIO communication module Signed-off-by: Patrick J.P --- libraries/AP_HAL_Linux/RCOutput_AeroIO.cpp | 281 +++++++++++++++++++++ libraries/AP_HAL_Linux/RCOutput_AeroIO.h | 156 ++++++++++++ 2 files changed, 437 insertions(+) create mode 100644 libraries/AP_HAL_Linux/RCOutput_AeroIO.cpp create mode 100644 libraries/AP_HAL_Linux/RCOutput_AeroIO.h diff --git a/libraries/AP_HAL_Linux/RCOutput_AeroIO.cpp b/libraries/AP_HAL_Linux/RCOutput_AeroIO.cpp new file mode 100644 index 0000000000..e36ada977b --- /dev/null +++ b/libraries/AP_HAL_Linux/RCOutput_AeroIO.cpp @@ -0,0 +1,281 @@ +/// -- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -- +/* + * 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; + */ + +static const AP_HAL::HAL &hal = AP_HAL::get_HAL(); + +RCOutput_AeroIO::RCOutput_AeroIO() + : _freq_buffer(new uint16_t[PWM_CHAN_COUNT]) + , _duty_buffer(new 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) { + 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); + 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); + 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) { + push(); + } +} + +void RCOutput_AeroIO::cork() +{ + _corking = true; +} + +void RCOutput_AeroIO::push() +{ + _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 / 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 * USEC_PER_SEC / (0xFFFF * f); +} diff --git a/libraries/AP_HAL_Linux/RCOutput_AeroIO.h b/libraries/AP_HAL_Linux/RCOutput_AeroIO.h new file mode 100644 index 0000000000..1592591fa9 --- /dev/null +++ b/libraries/AP_HAL_Linux/RCOutput_AeroIO.h @@ -0,0 +1,156 @@ +/// -- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -- +/* + * 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 . + */ + +#pragma once + +#include "AP_HAL_Linux.h" + +namespace Linux { + +class RCOutput_AeroIO : public AP_HAL::RCOutput { +public: + RCOutput_AeroIO(); + ~RCOutput_AeroIO(); + + void init() override; + + /** + * Enable channel + */ + void enable_ch(uint8_t ch) override; + + /** + * Disable channel (0 of duty cycle) + */ + void disable_ch(uint8_t ch) override; + + /** + * Set all channels in the same frequency + * + * @chmask Bitmask + * @freq_hz Frequency + */ + void set_freq(uint32_t chmask, uint16_t freq_hz) override; + + /** + * Get frequency of channel + * + * @ch channel + * + * Return: frequency of this channel + */ + uint16_t get_freq(uint8_t ch) override; + + /** + * Set period in µs + * + * @ch channel + * @period_us time in µs + */ + void write(uint8_t ch, uint16_t period_us) override; + + void cork() override; + + void push() override; + + /** + * Get period of the duty cycle in µs + */ + uint16_t read(uint8_t ch) override; + + /** + * Set @period_us with the values in µs of each channel + * + * @period_us vector that will be filled with duty cycle periods of + * each channel + * @len size of period_us vector + */ + void read(uint16_t *period_us, uint8_t len) override; + +private: + /** + * Convert from µs to hw units, 16bits percentage of + * the frequency, where 0xFFFF is 1/Freq seconds in high + * + * @freq Frequency + * @usec Time in µseconds + * + * Return: conversion from µs in a specific frequency to 16bits + */ + static uint16_t _usec_to_hw(uint16_t freq, uint16_t usec); + + /** + * Convert from hw units, 16bits percentage of the frequency, to + * time in µs + * + * @freq Frequency + * @hw_val 16b percentage + */ + static uint16_t _hw_to_usec(uint16_t freq, uint16_t hw_val); + + /** + * Low-level spi write + * + * @address register address + * @data value to be written + * + * Return: true on success, false otherwise + */ + bool _hw_write(uint16_t address, uint16_t data); + + /** + * Low-level spi read + * + * @address register address + * + * Return: value read from @address + */ + uint16_t _hw_read(uint16_t address); + + AP_HAL::OwnPtr _spi; + + /** + * Frequency buffer of last written values + */ + uint16_t *_freq_buffer; + + /** + * Duty cycle buffer of last written values + */ + uint16_t *_duty_buffer; + + /** + * Save information about the channel that will be write in the + * next @RCOutput_AeroIO#push call. + * + * 0b...101 + * ││└── 1st channel (Pending operation) + * │└─── 2nd Channel (No pending operation) + * └──── 3rd Channel (Pending operation) + */ + uint32_t _pending_duty_write_mask = 0; + uint32_t _pending_freq_write_mask = 0; + + /** + * If true the push must be called to perform the writing, + * otherwise will be not necessary. + */ + bool _corking = false; +}; + +}