From 5925d146bc0457ad0955a939e50eff4c5fe131f8 Mon Sep 17 00:00:00 2001 From: px4dev Date: Sun, 21 Oct 2012 17:51:21 -0700 Subject: Move the tone_alarm driver out of the NuttX configs area and add it as an stm32-specific driver in the PX4 apps space. Add a new tone_alarm command that can be used to start/stop alarm tones from the shell. --- apps/drivers/stm32/tone_alarm/tone_alarm.cpp | 632 +++++++++++++++++++++++++++ 1 file changed, 632 insertions(+) create mode 100644 apps/drivers/stm32/tone_alarm/tone_alarm.cpp (limited to 'apps/drivers/stm32/tone_alarm/tone_alarm.cpp') diff --git a/apps/drivers/stm32/tone_alarm/tone_alarm.cpp b/apps/drivers/stm32/tone_alarm/tone_alarm.cpp new file mode 100644 index 000000000..5a0467197 --- /dev/null +++ b/apps/drivers/stm32/tone_alarm/tone_alarm.cpp @@ -0,0 +1,632 @@ +/**************************************************************************** + * + * Copyright (C) 2012 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * Driver for the PX4 audio alarm port, /dev/tone_alarm. + * + * The tone_alarm driver supports a set of predefined "alarm" + * patterns and one user-supplied pattern. Patterns are ordered by + * priority, with a higher-priority pattern interrupting any + * lower-priority pattern that might be playing. + * + * The TONE_SET_ALARM ioctl can be used to select a predefined + * alarm pattern, from 1 - . Selecting pattern zero silences + * the alarm. + * + * To supply a custom pattern, write an array of 1 - tone_note + * structures to /dev/tone_alarm. The custom pattern has a priority + * of zero. + * + * Patterns will normally play once and then silence (if a pattern + * was overridden it will not resume). A pattern may be made to + * repeat by inserting a note with the duration set to + * DURATION_REPEAT. This pattern will loop until either a + * higher-priority pattern is started or pattern zero is requested + * via the ioctl. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#ifndef CONFIG_HRT_TIMER +# error CONFIG_TONE_ALARM requires CONFIG_HRT_TIMER +#endif + +/* Tone alarm configuration */ +#if TONE_ALARM_TIMER == 2 +# define TONE_ALARM_BASE STM32_TIM2_BASE +# define TONE_ALARM_CLOCK STM32_APB1_TIM2_CLKIN +# define TONE_ALARM_CLOCK_ENABLE RCC_APB1ENR_TIM2EN +# ifdef CONFIG_STM32_TIM2 +# error Must not set CONFIG_STM32_TIM2 when TONE_ALARM_TIMER is 2 +# endif +#elif TONE_ALARM_TIMER == 3 +# define TONE_ALARM_BASE STM32_TIM3_BASE +# define TONE_ALARM_CLOCK STM32_APB1_TIM3_CLKIN +# define TONE_ALARM_CLOCK_ENABLE RCC_APB1ENR_TIM3EN +# ifdef CONFIG_STM32_TIM3 +# error Must not set CONFIG_STM32_TIM3 when TONE_ALARM_TIMER is 3 +# endif +#elif TONE_ALARM_TIMER == 4 +# define TONE_ALARM_BASE STM32_TIM4_BASE +# define TONE_ALARM_CLOCK STM32_APB1_TIM4_CLKIN +# define TONE_ALARM_CLOCK_ENABLE RCC_APB1ENR_TIM4EN +# ifdef CONFIG_STM32_TIM4 +# error Must not set CONFIG_STM32_TIM4 when TONE_ALARM_TIMER is 4 +# endif +#elif TONE_ALARM_TIMER == 5 +# define TONE_ALARM_BASE STM32_TIM5_BASE +# define TONE_ALARM_CLOCK STM32_APB1_TIM5_CLKIN +# define TONE_ALARM_CLOCK_ENABLE RCC_APB1ENR_TIM5EN +# ifdef CONFIG_STM32_TIM5 +# error Must not set CONFIG_STM32_TIM5 when TONE_ALARM_TIMER is 5 +# endif +#elif TONE_ALARM_TIMER == 9 +# define TONE_ALARM_BASE STM32_TIM9_BASE +# define TONE_ALARM_CLOCK STM32_APB1_TIM9_CLKIN +# define TONE_ALARM_CLOCK_ENABLE RCC_APB1ENR_TIM9EN +# ifdef CONFIG_STM32_TIM9 +# error Must not set CONFIG_STM32_TIM9 when TONE_ALARM_TIMER is 9 +# endif +#elif TONE_ALARM_TIMER == 10 +# define TONE_ALARM_BASE STM32_TIM10_BASE +# define TONE_ALARM_CLOCK STM32_APB1_TIM10_CLKIN +# define TONE_ALARM_CLOCK_ENABLE RCC_APB1ENR_TIM10EN +# ifdef CONFIG_STM32_TIM10 +# error Must not set CONFIG_STM32_TIM10 when TONE_ALARM_TIMER is 10 +# endif +#elif TONE_ALARM_TIMER == 11 +# define TONE_ALARM_BASE STM32_TIM11_BASE +# define TONE_ALARM_CLOCK STM32_APB1_TIM11_CLKIN +# define TONE_ALARM_CLOCK_ENABLE RCC_APB1ENR_TIM11EN +# ifdef CONFIG_STM32_TIM11 +# error Must not set CONFIG_STM32_TIM11 when TONE_ALARM_TIMER is 11 +# endif +#else +# error Must set TONE_ALARM_TIMER to a generic timer in order to use this driver. +#endif + +#if TONE_ALARM_CHANNEL == 1 +# define TONE_CCMR1 (3 << 4) +# define TONE_CCMR2 0 +# define TONE_CCER (1 << 0) +# define TONE_rCCR rCCR1 +#elif TONE_ALARM_CHANNEL == 2 +# define TONE_CCMR1 (3 << 12) +# define TONE_CCMR2 0 +# define TONE_CCER (1 << 4) +# define TONE_rCCR rCCR2 +#elif TONE_ALARM_CHANNEL == 3 +# define TONE_CCMR1 0 +# define TONE_CCMR2 (3 << 4) +# define TONE_CCER (1 << 8) +# define TONE_rCCR rCCR3 +#elif TONE_ALARM_CHANNEL == 4 +# define TONE_CCMR1 0 +# define TONE_CCMR2 (3 << 12) +# define TONE_CCER (1 << 12) +# define TONE_rCCR rCCR4 +#else +# error Must set TONE_ALARM_CHANNEL to a value between 1 and 4 to use this driver. +#endif + + +/* + * Timer register accessors + */ +#define REG(_reg) (*(volatile uint32_t *)(TONE_ALARM_BASE + _reg)) + +#define rCR1 REG(STM32_GTIM_CR1_OFFSET) +#define rCR2 REG(STM32_GTIM_CR2_OFFSET) +#define rSMCR REG(STM32_GTIM_SMCR_OFFSET) +#define rDIER REG(STM32_GTIM_DIER_OFFSET) +#define rSR REG(STM32_GTIM_SR_OFFSET) +#define rEGR REG(STM32_GTIM_EGR_OFFSET) +#define rCCMR1 REG(STM32_GTIM_CCMR1_OFFSET) +#define rCCMR2 REG(STM32_GTIM_CCMR2_OFFSET) +#define rCCER REG(STM32_GTIM_CCER_OFFSET) +#define rCNT REG(STM32_GTIM_CNT_OFFSET) +#define rPSC REG(STM32_GTIM_PSC_OFFSET) +#define rARR REG(STM32_GTIM_ARR_OFFSET) +#define rCCR1 REG(STM32_GTIM_CCR1_OFFSET) +#define rCCR2 REG(STM32_GTIM_CCR2_OFFSET) +#define rCCR3 REG(STM32_GTIM_CCR3_OFFSET) +#define rCCR4 REG(STM32_GTIM_CCR4_OFFSET) +#define rDCR REG(STM32_GTIM_DCR_OFFSET) +#define rDMAR REG(STM32_GTIM_DMAR_OFFSET) + +class ToneAlarm : public device::CDev +{ +public: + ToneAlarm(); + ~ToneAlarm(); + + virtual int init(); + + virtual int ioctl(file *filp, int cmd, unsigned long arg); + virtual int write(file *filp, const char *buffer, size_t len); + +private: + static const unsigned _max_pattern = 6; + static const unsigned _pattern_none = _max_pattern + 1; + static const unsigned _pattern_user = 0; + static const unsigned _max_pattern_len = 40; + static const unsigned _note_max = TONE_NOTE_MAX; + + static const tone_note _patterns[_max_pattern][_max_pattern_len]; + static const uint16_t _notes[_note_max]; + + unsigned _current_pattern; + unsigned _next_note; + + hrt_call _note_end; + tone_note _user_pattern[_max_pattern_len]; + + static void next_trampoline(void *arg); + void next(); + bool check(tone_note *pattern); + void stop(); +}; + + +/* predefined patterns for alarms 1-_max_pattern */ +const tone_note ToneAlarm::_patterns[_max_pattern][_max_pattern_len] = { + { + {TONE_NOTE_A7, 12}, + {TONE_NOTE_D8, 12}, + {TONE_NOTE_C8, 12}, + {TONE_NOTE_A7, 12}, + {TONE_NOTE_D8, 12}, + {TONE_NOTE_C8, 12}, + {TONE_NOTE_D8, 4}, + {TONE_NOTE_C8, 4}, + {TONE_NOTE_D8, 4}, + {TONE_NOTE_C8, 4}, + {TONE_NOTE_D8, 4}, + {TONE_NOTE_C8, 4}, + }, + {{TONE_NOTE_B6, 100}, {TONE_NOTE_B6, DURATION_REPEAT}}, + {{TONE_NOTE_C7, 100}}, + {{TONE_NOTE_D7, 100}}, + {{TONE_NOTE_E7, 100}}, + { //This is tetris ;) + {TONE_NOTE_C6, 40}, + {TONE_NOTE_G5, 20}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_A5S, 40}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_G5, 20}, + {TONE_NOTE_F5, 40}, + {TONE_NOTE_F5, 20}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_C6, 40}, + {TONE_NOTE_A5S, 20}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_G5, 60}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_A5S, 40}, + {TONE_NOTE_C6, 40}, + {TONE_NOTE_G5S, 40}, + {TONE_NOTE_F5, 40}, + {TONE_NOTE_F5, 60}, + {TONE_NOTE_A5S, 40}, + {TONE_NOTE_C6S, 20}, + {TONE_NOTE_F6, 40}, + {TONE_NOTE_D6S, 20}, + {TONE_NOTE_C6S, 20}, + {TONE_NOTE_C6, 60}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_C6, 40}, + {TONE_NOTE_A5S, 20}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_G5, 40}, + {TONE_NOTE_G5, 20}, + {TONE_NOTE_G5S, 20}, + {TONE_NOTE_A5S, 40}, + {TONE_NOTE_C6, 40}, + {TONE_NOTE_G5S, 40}, + {TONE_NOTE_F5, 40}, + {TONE_NOTE_F5, 60}, + } +}; + +const uint16_t ToneAlarm::_notes[_note_max] = { + 63707, /* E4 */ + 60132, /* F4 */ + 56758, /* F#4/Gb4 */ + 53571, /* G4 */ + 50565, /* G#4/Ab4 */ + 47727, /* A4 */ + 45048, /* A#4/Bb4 */ + 42520, /* B4 */ + 40133, /* C5 */ + 37880, /* C#5/Db5 */ + 35755, /* D5 */ + 33748, /* D#5/Eb5 */ + 31853, /* E5 */ + 30066, /* F5 */ + 28378, /* F#5/Gb5 */ + 26786, /* G5 */ + 25282, /* G#5/Ab5 */ + 23863, /* A5 */ + 22524, /* A#5/Bb5 */ + 21260, /* B5 */ + 20066, /* C6 */ + 18940, /* C#6/Db6 */ + 17877, /* D6 */ + 16874, /* D#6/Eb6 */ + 15927, /* E6 */ + 15033, /* F6 */ + 14189, /* F#6/Gb6 */ + 13393, /* G6 */ + 12641, /* G#6/Ab6 */ + 11931, /* A6 */ + 11262, /* A#6/Bb6 */ + 10630, /* B6 */ + 10033, /* C7 */ + 9470, /* C#7/Db7 */ + 8938, /* D7 */ + 8437, /* D#7/Eb7 */ + 7963, /* E7 */ + 7516, /* F7 */ + 7094, /* F#7/Gb7 */ + 6696, /* G7 */ + 6320, /* G#7/Ab7 */ + 5965, /* A7 */ + 5631, /* A#7/Bb7 */ + 5315, /* B7 */ + 5016, /* C8 */ + 4735, /* C#8/Db8 */ + 4469, /* D8 */ + 4218 /* D#8/Eb8 */ +}; + +/* + * Driver 'main' command. + */ +extern "C" __EXPORT int tone_alarm_main(int argc, char *argv[]); + + +ToneAlarm::ToneAlarm() : + CDev("tone_alarm", "/dev/tone_alarm"), + _current_pattern(_pattern_none), + _next_note(0) +{ + // enable debug() calls + //_debug_enabled = true; +} + +ToneAlarm::~ToneAlarm() +{ +} + +int +ToneAlarm::init() +{ + int ret; + + ret = CDev::init(); + if (ret != OK) + return ret; + + /* configure the GPIO to the idle state */ + stm32_configgpio(GPIO_TONE_ALARM); + + /* clock/power on our timer */ + modifyreg32(STM32_RCC_APB1ENR, 0, TONE_ALARM_CLOCK_ENABLE); + + /* initialise the timer */ + rCR1 = 0; + rCR2 = 0; + rSMCR = 0; + rDIER = 0; + rCCER &= TONE_CCER; /* unlock CCMR* registers */ + rCCMR1 = TONE_CCMR1; + rCCMR2 = TONE_CCMR2; + rCCER = TONE_CCER; + rDCR = 0; + + /* toggle the CC output each time the count passes 1 */ + TONE_rCCR = 1; + + /* + * Configure the timebase to free-run at half max frequency. + * XXX this should be more flexible in order to get a better + * frequency range, but for the F4 with the APB1 timers based + * at 42MHz, this gets us down to ~320Hz or so. + */ + rPSC = 1; + + /* make sure the timer is running */ + rCR1 = GTIM_CR1_CEN; + + debug("ready"); + return OK; +} + +int +ToneAlarm::ioctl(file *filp, int cmd, unsigned long arg) +{ + int result = 0; + unsigned new_pattern = arg; + + debug("ioctl %i %u", cmd, new_pattern); + +// irqstate_t flags = irqsave(); + + /* decide whether to increase the alarm level to cmd or leave it alone */ + switch (cmd) { + case TONE_SET_ALARM: + debug("TONE_SET_ALARM %u", arg); + if (new_pattern == 0) { + /* cancel any current alarm */ + _current_pattern = _pattern_none; + next(); + + } else if (new_pattern > _max_pattern) { + + /* not a legal alarm value */ + result = -ERANGE; + + } else if ((_current_pattern == _pattern_none) || (new_pattern > _current_pattern)) { + + /* higher priority than the current alarm */ + _current_pattern = new_pattern; + _next_note = 0; + + /* and start playing it */ + next(); + } else { + /* current pattern is higher priority than the new pattern, ignore */ + } + break; + default: + result = -ENOTTY; + break; + } + +// irqrestore(flags); + + /* give it to the superclass if we didn't like it */ + if (result == -ENOTTY) + result = CDev::ioctl(filp, cmd, arg); + + return result; +} + +int +ToneAlarm::write(file *filp, const char *buffer, size_t len) +{ + irqstate_t flags; + + /* sanity-check the size of the write */ + if (len > (_max_pattern_len * sizeof(tone_note))) + return -EFBIG; + if ((len % sizeof(tone_note)) || (len == 0)) + return -EIO; + if (!check((tone_note *)buffer)) + return -EIO; + + flags = irqsave(); + + /* if we aren't playing an alarm tone */ + if (_current_pattern <= _pattern_user) { + + /* reset the tone state to play the new user pattern */ + _current_pattern = _pattern_user; + _next_note = 0; + + /* copy in the new pattern */ + memset(_user_pattern, 0, sizeof(_user_pattern)); + memcpy(_user_pattern, buffer, len); + + /* and start it */ + debug("starting user pattern"); + next(); + } + irqrestore(flags); + + return len; +} + +void +ToneAlarm::next_trampoline(void *arg) +{ + ToneAlarm *ta = (ToneAlarm *)arg; + + ta->next(); +} + +void +ToneAlarm::next(void) +{ + const tone_note *np; + + /* stop the current note */ + rCCER &= ~TONE_CCER; + + /* if we are no longer playing a pattern, we have nothing else to do here */ + if (_current_pattern == _pattern_none) { + stop(); + return; + } + + ASSERT(_next_note < _note_max); + + /* find the note to play */ + if (_current_pattern == _pattern_user) { + np = &_user_pattern[_next_note]; + } else { + np = &_patterns[_current_pattern - 1][_next_note]; + } + + /* work out which note is next */ + _next_note++; + if (_next_note >= _note_max) { + /* hit the end of the pattern, stop */ + _current_pattern = _pattern_none; + } else if (np[1].duration == DURATION_END) { + /* hit the end of the pattern, stop */ + _current_pattern = _pattern_none; + } else if (np[1].duration == DURATION_REPEAT) { + /* next note is a repeat, rewind in preparation */ + _next_note = 0; + } + + /* set the timer to play the note, if required */ + if (np->pitch <= TONE_NOTE_SILENCE) { + + /* set reload based on the pitch */ + rARR = _notes[np->pitch]; + + /* force an update, reloads the counter and all registers */ + rEGR = GTIM_EGR_UG; + + /* enable the output */ + rCCER |= TONE_CCER; + } + + /* arrange a callback when the note/rest is done */ + hrt_call_after(&_note_end, (hrt_abstime)10000 * np->duration, (hrt_callout)next_trampoline, this); +} + +bool +ToneAlarm::check(tone_note *pattern) +{ + for (unsigned i = 0; i < _note_max; i++) { + + /* first note must not be repeat or end */ + if ((i == 0) && + ((pattern[i].duration == DURATION_END) || (pattern[i].duration == DURATION_REPEAT))) + return false; + if (pattern[i].duration == DURATION_END) + break; + + /* pitch must be legal */ + if (pattern[i].pitch >= _note_max) + return false; + } + return true; +} + +void +ToneAlarm::stop() +{ + /* stop the current note */ + rCCER &= ~TONE_CCER; + + /* + * Make sure the GPIO is not driving the speaker. + * + * XXX this presumes PX4FMU and the onboard speaker driver FET. + */ + stm32_gpiowrite(GPIO_TONE_ALARM, 0); +} + +/** + * Local functions in support of the shell command. + */ +namespace +{ + +ToneAlarm *g_dev; + +int +play_pattern(unsigned pattern) +{ + int fd, ret; + + fd = open("/dev/tone_alarm", 0); + if (fd < 0) + err(1, "/dev/tone_alarm"); + + warnx("playing pattern %u", pattern); + ret = ioctl(fd, TONE_SET_ALARM, pattern); + if (ret != 0) + err(1, "TONE_SET_ALARM"); + exit(0); +} + +} // namespace + +int +tone_alarm_main(int argc, char *argv[]) +{ + unsigned pattern; + + /* start the driver lazily */ + if (g_dev == nullptr) { + g_dev = new ToneAlarm; + + if (g_dev == nullptr) + errx(1, "couldn't allocate the ToneAlarm driver"); + if (g_dev->init() != OK) { + delete g_dev; + errx(1, "ToneAlarm init failed"); + } + } + + if ((argc > 1) && !strcmp(argv[1], "start")) + play_pattern(1); + if ((argc > 1) && !strcmp(argv[1], "stop")) + play_pattern(0); + if ((pattern = strtol(argv[1], nullptr, 10)) != 0) + play_pattern(pattern); + + errx(1, "unrecognised command, try 'start', 'stop' or an alarm number"); +} -- cgit v1.2.3