commit d0c615ed04ac0c6b1dc4a1decadfaf69a44b5dcf Author: Stuart Longland Date: Sat Jan 3 21:43:30 2015 +1000 Initial check-in of Bicycle Horn/Bell firmware diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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 2 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/audio/bell.py b/audio/bell.py new file mode 100644 index 0000000..97035ab --- /dev/null +++ b/audio/bell.py @@ -0,0 +1,55 @@ +#!/usr/bin/python + +import math +import wave +import struct +import textwrap + +# Generate a bell sound +F_sample = 6400 # 6.4kHz sample rate +F_bell = 2063 # Fundamental frequency +F_bell_mod = 15.625 # Modulated amplitude +F_reverb = 3.77 # Reverberation frequency +A_reverb = 0.07 # Reverberation amplitude +duration = 1.0 # Duration +decay = 1.0 # Decay + +samples = [] +bell = lambda t : ((1.0+math.cos(math.pi*t/duration))/2) * \ + (decay ** t) * \ + (1.0 - (A_reverb * math.cos(2*math.pi*F_reverb*t)) - (A_reverb/2)) * \ + (math.cos(2*math.pi*F_bell*t) * \ + math.sin(2*math.pi*F_bell_mod*t)) +for n in range(0, int(duration*F_sample)): + t = float(n)/float(F_sample) + samples.append(int(127*bell(t)) + 127) + assert samples[-1] >= 0, '%s <= 0' % samples[-1] + assert samples[-1] <= 255, '%s > 255' % samples[-1] + +num_samples = len(samples) + +# C output +with file('bellsnd.c','w') as f: + f.write('#include "bellsnd.h"\n') + f.write('const uint8_t bell[%s] PROGMEM = {\n' % num_samples) + f.write('\n'.join(textwrap.wrap(', '.join([str(s) for s in samples]), + initial_indent='\t', subsequent_indent='\t', + expand_tabs=False))) + f.write('\n};\n') +with file('bellsnd.h','w') as f: + f.write('#include \n') + f.write('#include \n') + f.write('#define BELL_RATE (%s)\n' % F_sample) + f.write('#define BELL_SZ (%s)\n' % num_samples) + f.write('const uint8_t bell[%s] PROGMEM;\n' % num_samples) +# For the sake of analysis, we'll dump wav audio too +f = wave.open('bellsnd.wav','w') +f.setnchannels(1) +f.setsampwidth(1) +f.setframerate(F_sample) +f.setnframes(num_samples) +f.setcomptype('NONE','not compressed') +f.writeframes(''.join([ + struct.pack('B', s) for s in samples +])) +f.close() diff --git a/audio/horn.py b/audio/horn.py new file mode 100644 index 0000000..13e5054 --- /dev/null +++ b/audio/horn.py @@ -0,0 +1,73 @@ +#!/usr/bin/python + +import math +import wave +import struct +import textwrap + +# Generate a horn sound +F_sample = 6400 # 6.4kHz sample rate +# Horn frequencies and amplitudes +A_horn = 0.8 +FA_horn = [ + (485, 10**(-20.4/20)), + (618, 10**(-15.4/20)), + (762, 10**(-7.3/20)), + (967, 10**(-13.4/20)), + (1147, 10**(-15.0/20)), +] +T_attack = 0.029 # Rise time, seconds +T_hold = 1.000 # Hold time, seconds +T_decay = 0.300 # Decay time, seconds +N_samples = int(math.ceil((T_attack+T_hold+T_decay) * F_sample)) + +T_hold_end = T_attack + T_hold +duration = T_hold_end + T_decay +def amplitude(t): + if t < T_attack: + return (1.0 + math.sin((t*math.pi)/(2*T_attack)))/2.0 + elif t < T_hold_end: + return 1.0 + else: + t -= T_hold_end + return (1.0 + math.cos((math.pi*t)/T_decay))/2.0 +horn = lambda t : amplitude(t) * sum([ \ + a * A_horn * math.sin(2*math.pi*f*t)\ + for f, a in FA_horn \ +]) +samples = [] +for n in range(0, int(duration*F_sample)): + t = float(n)/float(F_sample) + samples.append(int(127*horn(t)) + 127) + assert samples[-1] >= 0, '%s <= 0' % samples[-1] + assert samples[-1] <= 255, '%s > 255' % samples[-1] + +num_samples = len(samples) + +# C output +with file('hornsnd.c','w') as f: + f.write('#include "hornsnd.h"\n') + f.write('const uint8_t horn[%s] PROGMEM = {\n' % num_samples) + f.write('\n'.join(textwrap.wrap(', '.join([str(s) for s in samples]), + initial_indent='\t', subsequent_indent='\t', + expand_tabs=False))) + f.write('\n};\n') +with file('hornsnd.h','w') as f: + f.write('#include \n') + f.write('#include \n') + f.write('#define HORN_RATE (%s)\n' % F_sample) + f.write('#define HORN_LOOP_OFFSET (%s)\n' % int(T_attack*F_sample)) + f.write('#define HORN_LOOP_SZ (%s)\n' % int(T_hold_end*F_sample)) + f.write('#define HORN_SZ (%s)\n' % num_samples) + f.write('const uint8_t horn[%s] PROGMEM;\n' % num_samples) +# For the sake of analysis, we'll dump wav audio too +f = wave.open('hornsnd.wav','w') +f.setnchannels(1) +f.setsampwidth(1) +f.setframerate(F_sample) +f.setnframes(num_samples) +f.setcomptype('NONE','not compressed') +f.writeframes(''.join([ + struct.pack('B', s) for s in samples +])) +f.close() diff --git a/firmware/Makefile b/firmware/Makefile new file mode 100644 index 0000000..3d4af50 --- /dev/null +++ b/firmware/Makefile @@ -0,0 +1,46 @@ +# Bicycle Bell/Horn firmware +# Copyright (C) 2015 Stuart Longland +# +# This program 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 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +MCU=atmega32u4 +PTYPE=stk500v2 +PDEV=/dev/ttyACM0 +.PHONY: %.pgm all + +all: bell.pgm + +%.pgm: %.hex + avrdude -p $(MCU) -c $(PTYPE) -P $(PDEV) -e \ + -U flash:w:$^ + +%.elf: %.o + avr-gcc -mmcu=$(MCU) -o $@ $^ + +%.o: %.c + avr-gcc -mmcu=$(MCU) -Os -o $@ -c $< + +%.hex: %.elf + avr-objcopy -j .text -j .data -O ihex $< $@ + +bellsnd.c bellsnd.h: ../audio/bell.py + cd ../audio ; python bell.py + cp ../audio/bellsnd.[ch] . +hornsnd.c hornsnd.h: ../audio/horn.py + cd ../audio ; python horn.py + cp ../audio/hornsnd.[ch] . + +bell.elf: bell.o bellsnd.o hornsnd.o +bell.o: bellsnd.h hornsnd.h diff --git a/firmware/bell.c b/firmware/bell.c new file mode 100644 index 0000000..a450f26 --- /dev/null +++ b/firmware/bell.c @@ -0,0 +1,497 @@ +/* + * Bicycle Bell/Horn firmware + * Copyright (C) 2015 Stuart Longland + * + * This program 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 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define F_CPU 16000000UL +#include +#include +#include +#include +#include +#include + +#include "bellsnd.h" +#include "hornsnd.h" + +/* + * Connections: + * - Port B Pin 4: External Source (active low) + * - Port B Pin 5: Green LED + * - Port B Pin 6: Blue LED + * - Port B Pin 7: Sound output (PWM) + * - Port C Pin 7: Red LED + * - Port D Pin 6: Power On (active low) + * - Port D Pin 7: Mode + * - Port E Pin 6: Bell (active low) + * + * Test points: + * - Port C Pin 6: PWM Next Sample + * - Port D Pin 4: Main loop + * - Port D Pin 3: PWM Buffer Wait + * - Port D Pin 2: PWM Buffer Switch + * - Port D Pin 1: Bell down + * - Port D Pin 0: Bell release + * - Port F Pin 7: Bell state (main loop) + */ + +#define MODE_SW (PIND & (1 << DDB7)) +#define BELL_SW (!(PINE & (1 << DDB6))) +#define EXT_SW (!(PINB & (1 << DDB4))) +#define PWR_ON_PORT PORTD +#define PWR_ON_PIN (1 << DDB6) +#define RED_LED_PORT PORTC +#define GREEN_LED_PORT PORTB +#define BLUE_LED_PORT PORTB +#define RED_LED_PIN (1 << DDB7) +#define GREEN_LED_PIN (1 << DDB5) +#define BLUE_LED_PIN (1 << DDB6) + +/* + * Timer configuration. Freetronics picked GPIO B7 as their output for the + * sound, which means we're stuck with either using timers 0 (8-bit) or 1 + * (16-bit). I could pick a different pin, but sod it, I've already wired up + * the board so there's no going back now. + * + * Timers 0 and 1 are fed off the system clock, so we're stuck with a maximum + * of 16MHz as the input frequency. (If we had Timer4 available to us, then + * we've got 64MHz.) Timer 1 *can* do 16-bits, but at that resolution, it'll + * give us a 244Hz carrier, which is utterly useless for audio. So we'll + * suffer with 8-bits, which gives us a 62.5kHz carrier. + * + * Timer 0 is probably our easiest choice for this. Timer 1 can also do 8-bit + * but why complicate things with 16-bit registers? Our output will be OC0A. + * We will feed the samples into OCR0A. + */ + +#define T0_COMA (2) /* Output A: Clear on match */ +#define T0_COMB (0) /* Output B: Not used */ +#define T0_WGM (3) /* Fast PWM mode */ +#define T0_CS (1) /* 16MHz clock (no prescale) */ +#define T0_FCA (0) /* Do not force A */ +#define T0_FCB (0) /* Do not force B */ +#define TCCR0A_VAL \ + ( (T0_COMA << 6) \ + | (T0_COMB << 4) \ + | (T0_WGM & 0x3) ) +#define TCCR0B_VAL \ + ( (T0_FCA << 7) \ + | (T0_FCB << 6) \ + | ((T0_WGM & 0x04) << 1) \ + | T0_CS) +/* + * Timer 3 is well suited to give us a stable sample rate clock at sample + * rates up to 32kHz. Multiples of 8kHz should be smack on (according to the + * crystal), and multiples of 11025Hz should be less than 1% off. 48kHz will + * be off, but you'd be insane to expect DVD-quality audio out of the PWM + * output of an 8-bit micro. + * + * We set OCR3A according to the equation: + * OCR3A = (F_CPU / f_sample) - 1 + * + * The interrupt vector name is TIMER3_COMPA. + */ +#define T3_COMA (0) /* Output A: Not used */ +#define T3_COMB (0) /* Output B: Not used */ +#define T3_COMC (0) /* Output C: Not used */ +#define T3_WGM (4) /* Mode: CTC */ +#define T3_CS (1) /* 16MHz clock (no prescale) */ +#define T3_FCA (0) /* Do not force A */ +#define T3_FCB (0) /* Do not force B */ +#define T3_FCC (0) /* Do not force C */ +#define T3_ICNC (0) /* Don't care */ +#define T3_ICES (0) /* Don't care */ +#define TCCR3A_VAL \ + ( (T3_COMA << 6) \ + | (T3_COMB << 4) \ + | (T3_COMC << 2) \ + | (T3_WGM & 0x3) ) +#define TCCR3B_VAL \ + ( (T3_ICNC << 7) \ + | (T3_ICES << 6) \ + | ((T3_WGM & 0xc) << 1) \ + | T3_CS) +#define OCR3A_VAL(freq) \ + ((F_CPU / freq) - 1) +#define TIMSK3_VAL (1 << 1) + +/* + * The following is our output sample buffers, two buffers that get rotated + * around (double-buffering) to ensure we're not writing to the one we're + * reading from, buffer selector and the buffer pointer. + */ +#define BUFFER_SZ 256 +#define BUFFER_NUM 2 +static volatile uint8_t pwm_buffer[BUFFER_NUM][BUFFER_SZ]; +static volatile uint8_t buffer_ready = 0; +static volatile uint8_t buffer_sel = 0; +static volatile uint8_t buffer_ptr = 0; +static volatile uint8_t buffer_wait = 0; +static volatile uint8_t pwm_on = 0; + +/* + * That leaves us timer 1 for a tick counter, which we can tune as needed. + * Best rate would be about 10Hz to give us 100msec time slices that we can + * give us a nice tick counter. The primary aim of this will be to delay the + * system power-down after the bell or external source signals are + * de-asserted. + * + * We set OCR1A according to the equation: + * OCR1A = (F_CPU / (1024*f_sample)) - 1 + * + * This can be hard-coded since it will not be changing at runtime. The + * interrupt vector name is TIMER1_COMPA. + */ +#define T1_COMA (0) /* Output A: Not used */ +#define T1_COMB (0) /* Output B: Not used */ +#define T1_COMC (0) /* Output C: Not used */ +#define T1_WGM (4) /* Mode: CTC */ +#define T1_CS (5) /* 15.625kHz clock (16MHz / 1024) */ +#define T1_FCA (0) /* Do not force A */ +#define T1_FCB (0) /* Do not force B */ +#define T1_FCC (0) /* Do not force C */ +#define T1_ICNC (0) /* Don't care */ +#define T1_ICES (0) /* Don't care */ +#define T1_FREQ (10) /* 10Hz */ +#define TCCR1A_VAL \ + ( (T1_COMA << 6) \ + | (T1_COMB << 4) \ + | (T1_COMC << 2) \ + | (T1_WGM & 0x3) ) +#define TCCR1B_VAL \ + ( (T1_ICNC << 7) \ + | (T1_ICES << 6) \ + | ((T1_WGM & 0xc) << 1) \ + | T1_CS) +#define OCR1A_VAL \ + ((F_CPU / (1024*T1_FREQ)) - 1) +#define TIMSK1_VAL (1 << 1) + +/* This is our global tick counter variable */ +static volatile uint8_t system_tick = 0; + +/* LED state information */ +static volatile uint8_t led_colour = 0; + +/* Audio set-up routine, buffer 0 better be ready! */ +void start_audio(uint16_t sample_rate) { + /* Stop interrupts momentarily */ + if (pwm_on) { + /* We already have PWM running??? */ + RED_LED_PORT |= RED_LED_PIN; + GREEN_LED_PORT &= ~GREEN_LED_PIN; + BLUE_LED_PORT &= ~BLUE_LED_PIN; + while(1); + } + cli(); + /* Set up buffer pointers */ + buffer_sel = 1; + buffer_ptr = BUFFER_SZ-1; + /* Set up timer 0 */ + OCR0A = UINT8_MAX/2; + TCCR0A = TCCR0A_VAL; + TCCR0B = TCCR0B_VAL; + /* Set up timer 3 */ + OCR3A = OCR3A_VAL(sample_rate); + TCCR3A = TCCR3A_VAL; + TCCR3B = TCCR3B_VAL; + TIMSK3 = TIMSK3_VAL; + /* Resume interrupts */ + sei(); + /* Wait for audio interrupt to tick */ + //led_colour = 0xc; + while(!pwm_on); +} + +/* Audio tear-down routine */ +void stop_audio() { + /* Stop interrupts momentarily */ + cli(); + //led_colour = 0xc; + /* Re-set buffer pointers and PWM state */ + buffer_sel = 0; + buffer_ptr = 0; + buffer_ready = 0; + buffer_wait = 0; + pwm_on = 0; + /* Silence output */ + OCR0A = UINT8_MAX/2; + /* Stop timer 3 */ + TIMSK3 = 0; + TCCR3A = 0; + TCCR3B = 0; + OCR3A = 0; + + /* Stop timer 0 */ + TCCR0A = 0; + TCCR0B = 0; + /* Clear buffers */ + memset(pwm_buffer, 0, sizeof(pwm_buffer)); + /* Resume interrupts */ + sei(); +} + +/* Write audio to the output buffer */ +uint16_t write_audio(const uint8_t* audio, uint16_t offset, + uint16_t len, uint8_t is_ram, uint8_t loop) { + /* Wait until the interrupt handler switches buffers */ + while(buffer_ready); + + /* Pick the buffer not being read */ + uint8_t buf_num = buffer_sel ? 0 : 1; + uint16_t buf_rem = BUFFER_SZ; + uint16_t buf_ptr = 0; + uint16_t in_rem = len - offset; + volatile uint8_t* out = pwm_buffer[buf_num]; + const uint8_t* in = &audio[offset]; + + while(buf_rem && in_rem) { + if (is_ram) + *out = *in; + else + *out = pgm_read_byte(in); + out++; + buf_rem--; + in++; + in_rem--; + if (loop && (!in_rem)) { + in_rem = len; + in = audio; + } + } + /* Mark the buffer as ready */ + buffer_ready = 1; + /* Return where we got to */ + return len - in_rem; +} + +/* The loop point in the bell effect */ +#define BELL_LOOP_SZ (2054) + +/* Bell states */ +#define BELL_IDLE 0 +#define BELL_DOWN 1 +#define BELL_RELEASE 2 +#define BELL_STOP 3 +static volatile uint8_t bell_state = BELL_IDLE; +static volatile uint8_t bell_released = 0; +static volatile uint16_t bell_ptr = 0; +static uint8_t bell_mode = 0; +static const uint8_t* bell_snd = NULL; +static uint16_t bell_loop_sz = 0; +static uint16_t bell_sz = 0; +static uint8_t bell_loop = 0; + +/* What do we do when the bell is idle? */ +void bell_idle(void) { + if (BELL_SW) { + /* + * Someone has pressed the bell button, enter the "down" + * state, load the initial buffer then start the audio. + */ + bell_state = BELL_DOWN; + bell_mode = MODE_SW; + if (bell_mode) { + bell_snd = horn; + bell_loop_sz = HORN_LOOP_SZ; + bell_sz = HORN_SZ - HORN_LOOP_OFFSET; + } else { + bell_snd = bell; + bell_loop_sz = BELL_LOOP_SZ; + bell_sz = BELL_SZ; + } + bell_ptr = write_audio( + bell_snd, 0, bell_loop_sz, 0, 1); + start_audio(BELL_RATE); + } +} + +/* What do we do while the button is held? */ +void bell_down(void) { + if (BELL_SW) { + /* The button is still down, is there room? */ + if (!buffer_ready) { + /* There is, put some more dinging noises in */ + if (bell_mode && !bell_loop) { + /* + * We've played the initial part, now for + * the rest. + */ + bell_snd = &horn[HORN_LOOP_OFFSET]; + bell_loop_sz = HORN_LOOP_SZ - HORN_LOOP_OFFSET; + bell_ptr -= HORN_LOOP_OFFSET; + bell_sz -= HORN_LOOP_OFFSET; + bell_loop = 1; + } + bell_ptr = write_audio( + bell_snd, bell_ptr, bell_loop_sz, + 0, 1); + } + } else { + /* Button just released? Or switch bounce */ + bell_state = BELL_RELEASE; + bell_released = system_tick; + } +} + +/* What do we do when the button is released? */ +void bell_release(void) { + if (BELL_SW) { + /* The button is bouncing */ + bell_state = BELL_DOWN; + bell_down(); + } else if ((system_tick - bell_released) > 2) { + /* I'll call this released. */ + bell_state = BELL_STOP; + } else if (!buffer_ready) { + /* Keep making the dinging noises in the meantime */ + bell_ptr = write_audio( + bell_snd, bell_ptr, bell_loop_sz, + 0, 1); + } +} + +/* Waiting for the final ding */ +void bell_stop(void) { + if (bell_ptr < bell_sz) { + if (!buffer_ready) { + /* One more ding since there's room */ + bell_ptr = write_audio( + bell_snd, bell_ptr, bell_sz, 0, 0); + } + } else { + /* We're done, wait for the buffer to finish */ + while(!buffer_wait); + stop_audio(); + bell_state = BELL_IDLE; + } +} + +/* Our main loop */ +int main(void) { + /* Our last-activity time */ + uint8_t last_act = 0; + + /* Ensure interrupts are off */ + cli(); + + /* Ensure audio is not running */ + pwm_on = 0; + stop_audio(); + PORTB |= (1 << DDB0); + + /* Set up inputs */ + PORTE |= (1 << 6); + PORTD |= (1 << 6); + + /* Set up outputs */ + DDRB |= (1 << DDB5)|(1 << DDB6)|(1 << DDB7)|(1 << DDB0); + DDRC |= (1 << DDB7)|(1 << DDB6); + DDRD |= (1 << DDB6)|(1 << DDB5)| + (1 << DDB3)|(1 << DDB2)|(1 << DDB1)|(1 << DDB0); + + /* Hold the power on */ + PWR_ON_PORT |= PWR_ON_PIN; + + /* Set up Timer 1 */ + OCR1A = OCR1A_VAL; + TIMSK1 = TIMSK1_VAL; + TCCR1A = TCCR1A_VAL; + TCCR1B = TCCR1B_VAL; + + /* Enable interrupts */ + sei(); + while(1) { + if (bell_state == BELL_IDLE) { + led_colour = 1; + bell_idle(); + } else if (bell_state == BELL_DOWN) { + led_colour = 3; + bell_down(); + PORTD ^= (1 << DDB1); + } else if (bell_state == BELL_RELEASE) { + led_colour = 2; + bell_release(); + PORTD ^= (1 << DDB0); + } else { + led_colour = 5; + bell_stop(); + } + + /* Turn power off if self-powered */ + if (pwm_on) + PWR_ON_PORT |= PWR_ON_PIN; + else + PWR_ON_PORT &= ~PWR_ON_PIN; + } +} + +ISR(TIMER1_COMPA_vect) { + /* + * Executed every tick to time things like shutdown delay and + * to blink the LEDs. + */ + uint8_t c = led_colour; + system_tick++; + if ((c & 8) && (!(system_tick & 0x02))) + c = 0; + + if (c & 1) + RED_LED_PORT |= RED_LED_PIN; + else + RED_LED_PORT &= ~RED_LED_PIN; + + if (c & 2) + GREEN_LED_PORT |= GREEN_LED_PIN; + else + GREEN_LED_PORT &= ~GREEN_LED_PIN; + + if (c & 4) + BLUE_LED_PORT |= BLUE_LED_PIN; + else + BLUE_LED_PORT &= ~BLUE_LED_PIN; +} + +ISR(TIMER3_COMPA_vect) { + /* + * Executed to pull data from the buffer and stuff it into + * the PWM output. We begin by reading the sample at the current + * buffer location and writing that to PWM. + */ + OCR0A = pwm_buffer[buffer_sel][buffer_ptr]; + /* Is this the end of the buffer? */ + if (buffer_ptr < (BUFFER_SZ-1)) { + /* No, move on */ + buffer_ptr++; + //led_colour = 0x4; + PORTC ^= (1 << DDB6); + /* It is, is the other buffer ready? */ + } else if (buffer_ready) { + /* Swap */ + buffer_sel = buffer_sel ? 0 : 1; + buffer_ptr = 0; + buffer_ready = 0; + buffer_wait = 0; + PORTD ^= (1 << DDB2); + /* We're waiting on a buffer */ + } else if (!buffer_wait) { + buffer_wait = 1; + //led_colour = 0x9; + PORTD ^= (1 << DDB3); + } + pwm_on = 1; +}