├── LICENSE ├── README.md ├── firmware ├── SimpleRingBuffer.cpp ├── SimpleRingBuffer.h ├── SparkIntervalTimer.cpp ├── SparkIntervalTimer.h ├── build.sh ├── main.ino └── walkieTalkies.bin ├── hardware ├── README.md └── photon_walkie_talkies.fzz └── photos ├── IMG_2229.JPG ├── IMG_2230.JPG └── IMG_2231.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | walkie_talkies 2 | ==== 3 | 4 | Send and receive sound between Particle Photons, using microphones and speakers from Adafruit! Easy and fun, 1 day build or faster. 5 | 6 | 7 | 8 | Hardware 9 | ==== 10 | 11 | Checkout the hardware folder for a bill of materials and a fritzing diagram 12 | 13 | 14 | Software 15 | ==== 16 | 17 | Checkout the firmware folder for a simple app that will broadcast audio as basic UDP packets to your Wi-Fi network. The 18 | code also listens for packets from other similar devices and automatically plays the audio it receives! 19 | 20 | 21 | Thanks! 22 | 23 | Enjoy! 24 | David 25 | -------------------------------------------------------------------------------- /firmware/SimpleRingBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "application.h" 2 | #include "SimpleRingBuffer.h" 3 | 4 | SimpleRingBuffer::SimpleRingBuffer() { 5 | 6 | } 7 | 8 | void SimpleRingBuffer::init(unsigned int size) { 9 | cur_idx = 0; //pos 10 | cur_len = 0; //len 11 | 12 | _data = (uint8_t*)malloc(size * sizeof(uint8_t)); 13 | data_size = size; //cap 14 | } 15 | 16 | bool SimpleRingBuffer::put(uint8_t value) { 17 | if (cur_len < data_size) { 18 | // get our index, wrap on buffer size 19 | _data[(cur_idx + cur_len)%data_size] = value; 20 | cur_len++; 21 | return true; 22 | } 23 | return false; 24 | } 25 | 26 | uint8_t SimpleRingBuffer::get() { 27 | // lets return 0 if we don't have anything 28 | uint8_t val = 0; 29 | 30 | 31 | if (cur_len > 0) { 32 | 33 | // grab the value from the current index 34 | val = _data[cur_idx]; 35 | 36 | // move to next, wrap as necessary, shrink length 37 | cur_idx = (cur_idx + 1) % data_size; 38 | cur_len--; 39 | } 40 | return val; 41 | } 42 | 43 | unsigned int SimpleRingBuffer::getSize() { 44 | return cur_len; 45 | } 46 | 47 | unsigned int SimpleRingBuffer::getCapacity() { 48 | return data_size; 49 | } 50 | 51 | void SimpleRingBuffer::clear() { 52 | cur_idx = 0; 53 | cur_len = 0; 54 | } 55 | 56 | void SimpleRingBuffer::destroy() { 57 | free(_data); 58 | _data = NULL; 59 | } 60 | -------------------------------------------------------------------------------- /firmware/SimpleRingBuffer.h: -------------------------------------------------------------------------------- 1 | #include "application.h" 2 | 3 | #ifndef SIMPLERINGBUFFER_H 4 | #define SIMPLERINGBUFFER_H 5 | 6 | 7 | class SimpleRingBuffer { 8 | protected: 9 | uint8_t* _data; 10 | unsigned int data_size; 11 | unsigned int cur_idx; 12 | unsigned int cur_len; 13 | 14 | public: 15 | SimpleRingBuffer(); 16 | 17 | void init(unsigned int size); 18 | 19 | bool put(uint8_t value); 20 | uint8_t get(); 21 | 22 | unsigned int getSize(); 23 | unsigned int getCapacity(); 24 | 25 | void clear(); 26 | void destroy(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /firmware/SparkIntervalTimer.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Paul Kourany, based on work by Dianel Gilbert 2 | 3 | UPDATED Sept 3, 2015 - Added support for Particle Photon 4 | 5 | Copyright (c) 2013 Daniel Gilbert, loglow@gmail.com 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in the 9 | Software without restriction, including without limitation the rights to use, copy, 10 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 11 | and to permit persons to whom the Software is furnished to do so, subject to the 12 | following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 18 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 19 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 23 | 24 | #include "SparkIntervalTimer.h" 25 | 26 | 27 | // ------------------------------------------------------------ 28 | // static class variables need to be reiterated here before use 29 | // ------------------------------------------------------------ 30 | bool IntervalTimer::SIT_used[]; 31 | IntervalTimer::ISRcallback IntervalTimer::SIT_CALLBACK[]; 32 | 33 | // ------------------------------------------------------------ 34 | // Define interval timer ISR hooks for three available timers 35 | // TIM2...TIM7 with callbacks to user code. 36 | // ------------------------------------------------------------ 37 | #if defined(STM32F10X_MD) || !defined(PLATFORM_ID) //Core 38 | void Wiring_TIM2_Interrupt_Handler_override() 39 | { 40 | if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) 41 | { 42 | TIM_ClearITPendingBit(TIM2, TIM_IT_Update); 43 | IntervalTimer::SIT_CALLBACK[0](); 44 | } 45 | } 46 | 47 | void Wiring_TIM3_Interrupt_Handler_override() 48 | { 49 | if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) 50 | { 51 | TIM_ClearITPendingBit(TIM3, TIM_IT_Update); 52 | IntervalTimer::SIT_CALLBACK[1](); 53 | } 54 | } 55 | 56 | void Wiring_TIM4_Interrupt_Handler_override() 57 | { 58 | if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) 59 | { 60 | TIM_ClearITPendingBit(TIM4, TIM_IT_Update); 61 | IntervalTimer::SIT_CALLBACK[2](); 62 | } 63 | } 64 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 65 | void Wiring_TIM3_Interrupt_Handler_override() 66 | { 67 | if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) 68 | { 69 | TIM_ClearITPendingBit(TIM3, TIM_IT_Update); 70 | IntervalTimer::SIT_CALLBACK[0](); 71 | } 72 | } 73 | 74 | void Wiring_TIM4_Interrupt_Handler_override() 75 | { 76 | if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) 77 | { 78 | TIM_ClearITPendingBit(TIM4, TIM_IT_Update); 79 | IntervalTimer::SIT_CALLBACK[1](); 80 | } 81 | } 82 | 83 | void Wiring_TIM5_Interrupt_Handler_override() 84 | { 85 | if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) 86 | { 87 | TIM_ClearITPendingBit(TIM5, TIM_IT_Update); 88 | IntervalTimer::SIT_CALLBACK[2](); 89 | } 90 | } 91 | 92 | void Wiring_TIM6_Interrupt_Handler_override() 93 | { 94 | if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) 95 | { 96 | TIM_ClearITPendingBit(TIM6, TIM_IT_Update); 97 | IntervalTimer::SIT_CALLBACK[3](); 98 | } 99 | } 100 | 101 | void Wiring_TIM7_Interrupt_Handler_override() 102 | { 103 | if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET) 104 | { 105 | TIM_ClearITPendingBit(TIM7, TIM_IT_Update); 106 | IntervalTimer::SIT_CALLBACK[4](); 107 | } 108 | } 109 | #else 110 | #error "*** PARTICLE device not supported by this library. PLATFORM should be Core or Photon ***" 111 | #endif 112 | 113 | // ------------------------------------------------------------ 114 | // this function inits and starts the timer, using the specified 115 | // function as a callback and the period provided. must be passed 116 | // the name of a function taking no arguments and returning void. 117 | // make sure this function can complete within the time allowed. 118 | // attempts to allocate a timer using available resources, 119 | // returning true on success or false in case of failure. 120 | // Period units is defined by scale, where scale = uSec or hmSec 121 | // and = 1-65535 microsecond (uSec) 122 | // or 1-65535 0.5ms increments (hmSec) 123 | // ------------------------------------------------------------ 124 | bool IntervalTimer::beginCycles(void (*isrCallback)(), intPeriod Period, bool scale, TIMid id) { 125 | 126 | // if this interval timer is already running, stop and deallocate it 127 | if (status == TIMER_SIT) { 128 | stop_SIT(); 129 | status = TIMER_OFF; 130 | } 131 | // store callback pointer 132 | myISRcallback = isrCallback; 133 | 134 | if (id > NUM_SIT) { // Allocate specified timer (id=0 to 2/4) or auto-allocate from pool (id=255) 135 | // attempt to allocate this timer 136 | if (allocate_SIT(Period, scale, id)) status = TIMER_SIT; //255 means allocate from pool 137 | else status = TIMER_OFF; 138 | } 139 | else { 140 | // attempt to allocate this timer 141 | if (allocate_SIT(Period, scale, AUTO)) status = TIMER_SIT; //255 means allocate from pool 142 | else status = TIMER_OFF; 143 | } 144 | 145 | // check for success and return 146 | if (status != TIMER_OFF) return true; 147 | return false; 148 | 149 | } 150 | 151 | 152 | // ------------------------------------------------------------ 153 | // enables the SIT clock if not already enabled, then checks to 154 | // see if any SITs are available for use. if one is available, 155 | // it's initialized and started with the specified value, and 156 | // the function returns true, otherwise it returns false 157 | // ------------------------------------------------------------ 158 | bool IntervalTimer::allocate_SIT(intPeriod Period, bool scale, TIMid id) { 159 | 160 | if (id < NUM_SIT) { // Allocate specified timer (id=TIMER3/4/5) or auto-allocate from pool (id=AUTO) 161 | if (!SIT_used[id]) { 162 | start_SIT(Period, scale); 163 | SIT_used[id] = true; 164 | return true; 165 | } 166 | } 167 | else { 168 | // Auto allocate - check for an available SIT, and if so, start it 169 | for (uint8_t tid = 0; tid < NUM_SIT; tid++) { 170 | if (!SIT_used[tid]) { 171 | SIT_id = tid; 172 | start_SIT(Period, scale); 173 | SIT_used[tid] = true; 174 | return true; 175 | } 176 | } 177 | } 178 | 179 | // Specified or no auto-allocate SIT available 180 | return false; 181 | } 182 | 183 | 184 | 185 | // ------------------------------------------------------------ 186 | // configuters a SIT's TIMER registers, etc and enables 187 | // interrupts, effectively starting the timer upon completion 188 | // ------------------------------------------------------------ 189 | void IntervalTimer::start_SIT(intPeriod Period, bool scale) { 190 | 191 | TIM_TimeBaseInitTypeDef timerInitStructure; 192 | NVIC_InitTypeDef nvicStructure; 193 | intPeriod prescaler; 194 | TIM_TypeDef* TIMx; 195 | 196 | //use SIT_id to identify TIM# 197 | switch (SIT_id) { 198 | #if defined(STM32F10X_MD) || !defined(PLATFORM_ID) //Core 199 | case 0: // TIM2 200 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 201 | nvicStructure.NVIC_IRQChannel = TIM2_IRQn; 202 | TIMx = TIM2; 203 | break; 204 | case 1: // TIM3 205 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 206 | nvicStructure.NVIC_IRQChannel = TIM3_IRQn; 207 | TIMx = TIM3; 208 | break; 209 | case 2: // TIM4 210 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); 211 | nvicStructure.NVIC_IRQChannel = TIM4_IRQn; 212 | TIMx = TIM4; 213 | break; 214 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 215 | case 0: // TIM3 216 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 217 | nvicStructure.NVIC_IRQChannel = TIM3_IRQn; 218 | TIMx = TIM3; 219 | break; 220 | case 1: // TIM4 221 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); 222 | nvicStructure.NVIC_IRQChannel = TIM4_IRQn; 223 | TIMx = TIM4; 224 | break; 225 | case 2: // TIM5 226 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); 227 | nvicStructure.NVIC_IRQChannel = TIM5_IRQn; 228 | TIMx = TIM5; 229 | break; 230 | case 3: // TIM6 231 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); 232 | nvicStructure.NVIC_IRQChannel = TIM6_DAC_IRQn; 233 | TIMx = TIM6; 234 | break; 235 | case 4: // TIM7 236 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE); 237 | nvicStructure.NVIC_IRQChannel = TIM7_IRQn; 238 | TIMx = TIM7; 239 | break; 240 | #endif 241 | } 242 | 243 | // Initialize Timer 244 | switch (scale) { 245 | case uSec: 246 | prescaler = SIT_PRESCALERu; // Set prescaler for 1MHz clock, 1us period 247 | break; 248 | case hmSec: 249 | prescaler = SIT_PRESCALERm; // Set prescaler for 2Hz clock, .5ms period 250 | break; 251 | default: 252 | prescaler = SIT_PRESCALERu; 253 | scale = uSec; // Default to microseconds 254 | break; 255 | } 256 | 257 | timerInitStructure.TIM_Prescaler = prescaler; 258 | timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up; 259 | timerInitStructure.TIM_Period = Period; 260 | timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 261 | timerInitStructure.TIM_RepetitionCounter = 0; 262 | 263 | TIM_TimeBaseInit(TIMx, &timerInitStructure); 264 | TIM_Cmd(TIMx, ENABLE); 265 | TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE); 266 | 267 | // point to the correct SIT ISR 268 | SIT_CALLBACK[SIT_id] = myISRcallback; 269 | 270 | //Enable Timer Interrupt 271 | nvicStructure.NVIC_IRQChannelPreemptionPriority = 10; 272 | nvicStructure.NVIC_IRQChannelSubPriority = 1; 273 | nvicStructure.NVIC_IRQChannelCmd = ENABLE; 274 | NVIC_Init(&nvicStructure); 275 | } 276 | 277 | 278 | // ------------------------------------------------------------ 279 | // stop the timer if it's currently running, using its status 280 | // to determine what hardware resources the timer may be using 281 | // ------------------------------------------------------------ 282 | void IntervalTimer::end() { 283 | if (status == TIMER_SIT) stop_SIT(); 284 | status = TIMER_OFF; 285 | } 286 | 287 | 288 | // ------------------------------------------------------------ 289 | // stops an active SIT by disabling its interrupt and TIMER 290 | // and freeing up its state for future use. 291 | // ------------------------------------------------------------ 292 | void IntervalTimer::stop_SIT() { 293 | 294 | NVIC_InitTypeDef nvicStructure; 295 | TIM_TypeDef* TIMx; 296 | 297 | 298 | //use SIT_id to identify TIM# 299 | switch (SIT_id) { 300 | #if defined(STM32F10X_MD) || !defined(PLATFORM_ID) //Core 301 | case 0: // TIM2 302 | nvicStructure.NVIC_IRQChannel = TIM2_IRQn; 303 | TIMx = TIM2; 304 | break; 305 | case 1: // TIM3 306 | nvicStructure.NVIC_IRQChannel = TIM3_IRQn; 307 | TIMx = TIM3; 308 | break; 309 | case 2: // TIM4 310 | nvicStructure.NVIC_IRQChannel = TIM4_IRQn; 311 | TIMx = TIM4; 312 | break; 313 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 314 | case 0: // TIM3 315 | nvicStructure.NVIC_IRQChannel = TIM3_IRQn; 316 | TIMx = TIM3; 317 | break; 318 | case 1: // TIM4 319 | nvicStructure.NVIC_IRQChannel = TIM4_IRQn; 320 | TIMx = TIM4; 321 | break; 322 | case 2: // TIM5 323 | nvicStructure.NVIC_IRQChannel = TIM5_IRQn; 324 | TIMx = TIM5; 325 | break; 326 | case 3: // TIM6 327 | nvicStructure.NVIC_IRQChannel = TIM6_DAC_IRQn; 328 | TIMx = TIM6; 329 | break; 330 | case 4: // TIM7 331 | nvicStructure.NVIC_IRQChannel = TIM7_IRQn; 332 | TIMx = TIM7; 333 | break; 334 | #endif 335 | } 336 | // disable counter 337 | TIM_Cmd(TIMx, DISABLE); 338 | 339 | // disable interrupt 340 | nvicStructure.NVIC_IRQChannelCmd = DISABLE; 341 | NVIC_Init(&nvicStructure); 342 | 343 | // disable timer peripheral 344 | TIM_DeInit(TIMx); 345 | 346 | // free SIT for future use 347 | SIT_used[SIT_id] = false; 348 | } 349 | 350 | 351 | // ------------------------------------------------------------ 352 | // Enables or disables an active SIT's interrupt without 353 | // removing the SIT. 354 | // ------------------------------------------------------------ 355 | void IntervalTimer::interrupt_SIT(action ACT) 356 | { 357 | NVIC_InitTypeDef nvicStructure; 358 | TIM_TypeDef* TIMx; 359 | 360 | //use SIT_id to identify TIM# 361 | switch (SIT_id) { 362 | #if defined(STM32F10X_MD) || !defined(PLATFORM_ID) //Core 363 | case 0: // TIM2 364 | nvicStructure.NVIC_IRQChannel = TIM2_IRQn; 365 | TIMx = TIM2; 366 | break; 367 | case 1: // TIM3 368 | nvicStructure.NVIC_IRQChannel = TIM3_IRQn; 369 | TIMx = TIM3; 370 | break; 371 | case 2: // TIM4 372 | nvicStructure.NVIC_IRQChannel = TIM4_IRQn; 373 | TIMx = TIM4; 374 | break; 375 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 376 | case 0: // TIM3 377 | nvicStructure.NVIC_IRQChannel = TIM3_IRQn; 378 | TIMx = TIM3; 379 | break; 380 | case 1: // TIM4 381 | nvicStructure.NVIC_IRQChannel = TIM4_IRQn; 382 | TIMx = TIM4; 383 | break; 384 | case 2: // TIM5 385 | nvicStructure.NVIC_IRQChannel = TIM5_IRQn; 386 | TIMx = TIM5; 387 | break; 388 | case 3: // TIM6 389 | nvicStructure.NVIC_IRQChannel = TIM6_DAC_IRQn; 390 | TIMx = TIM6; 391 | break; 392 | case 4: // TIM7 393 | nvicStructure.NVIC_IRQChannel = TIM7_IRQn; 394 | TIMx = TIM7; 395 | break; 396 | #endif 397 | } 398 | 399 | switch (ACT) { 400 | case INT_ENABLE: 401 | //Enable Timer Interrupt 402 | nvicStructure.NVIC_IRQChannelPreemptionPriority = 0; 403 | nvicStructure.NVIC_IRQChannelSubPriority = 1; 404 | nvicStructure.NVIC_IRQChannelCmd = ENABLE; 405 | NVIC_Init(&nvicStructure); 406 | break; 407 | case INT_DISABLE: 408 | // disable interrupt 409 | nvicStructure.NVIC_IRQChannelCmd = DISABLE; 410 | NVIC_Init(&nvicStructure); 411 | break; 412 | default: 413 | //Do nothing 414 | break; 415 | } 416 | } 417 | 418 | 419 | // ------------------------------------------------------------ 420 | // Set new period for the SIT without 421 | // removing the SIT. 422 | // ------------------------------------------------------------ 423 | void IntervalTimer::resetPeriod_SIT(intPeriod newPeriod, bool scale) 424 | { 425 | //TIM_TimeBaseInitTypeDef timerInitStructure; 426 | TIM_TypeDef* TIMx; 427 | intPeriod prescaler; 428 | 429 | //use SIT_id to identify TIM# 430 | switch (SIT_id) { 431 | #if defined(STM32F10X_MD) || !defined(PLATFORM_ID) //Core 432 | case 0: // TIM2 433 | TIMx = TIM2; 434 | break; 435 | case 1: // TIM3 436 | TIMx = TIM3; 437 | break; 438 | case 2: // TIM4 439 | TIMx = TIM4; 440 | break; 441 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 442 | case 0: // TIM3 443 | TIMx = TIM3; 444 | break; 445 | case 1: // TIM4 446 | TIMx = TIM4; 447 | break; 448 | case 2: // TIM5 449 | TIMx = TIM5; 450 | break; 451 | case 3: // TIM6 452 | TIMx = TIM6; 453 | break; 454 | case 4: // TIM7 455 | TIMx = TIM7; 456 | break; 457 | #endif 458 | } 459 | 460 | switch (scale) { 461 | case uSec: 462 | prescaler = SIT_PRESCALERu; // Set prescaler for 1MHz clock, 1us period 463 | break; 464 | case hmSec: 465 | prescaler = SIT_PRESCALERm; // Set prescaler for 2Hz clock, .5ms period 466 | break; 467 | default: 468 | scale = uSec; // Default to microseconds 469 | prescaler = SIT_PRESCALERu; 470 | break; 471 | } 472 | 473 | TIMx->ARR = newPeriod; 474 | TIMx->PSC = prescaler; 475 | TIMx->EGR = TIM_PSCReloadMode_Immediate; 476 | TIM_ClearITPendingBit(TIMx, TIM_IT_Update); 477 | } 478 | 479 | // ------------------------------------------------------------ 480 | // Returns -1 if timer not allocated or sid number: 481 | // 0 = TMR2, 1 = TMR3, 2 = TMR4 482 | // ------------------------------------------------------------ 483 | int8_t IntervalTimer::isAllocated_SIT(void) 484 | { 485 | if (status == TIMER_SIT) 486 | return -1; 487 | else 488 | return SIT_id; 489 | } 490 | -------------------------------------------------------------------------------- /firmware/SparkIntervalTimer.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Paul Kourany, based on work by Dianel Gilbert 2 | 3 | UPDATED Sept 3, 2015 - Added support for Particle Photon 4 | 5 | Copyright (c) 2013 Daniel Gilbert, loglow@gmail.com 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in the 9 | Software without restriction, including without limitation the rights to use, copy, 10 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 11 | and to permit persons to whom the Software is furnished to do so, subject to the 12 | following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 18 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 19 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 23 | 24 | 25 | #ifndef __INTERVALTIMER_H__ 26 | #define __INTERVALTIMER_H__ 27 | 28 | #include "application.h" 29 | 30 | 31 | #if defined(STM32F10X_MD) || !defined(PLATFORM_ID) //Core 32 | #define SYSCORECLOCK 72000000UL 33 | 34 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 35 | #define SYSCORECLOCK 60000000UL // Timer clock tree uses core clock / 2 36 | #else 37 | #error "*** PARTICLE device not supported by this library. PLATFORM should be Core or Photon ***" 38 | #endif 39 | 40 | enum {uSec, hmSec}; // microseconds or half-milliseconds 41 | enum action {INT_DISABLE, INT_ENABLE}; 42 | 43 | #ifdef __cplusplus 44 | extern "C" { 45 | #endif 46 | 47 | #if !defined(PLATFORM_ID) //Core v0.3.4 48 | #warning "CORE" 49 | extern void (*Wiring_TIM2_Interrupt_Handler)(void); 50 | extern void (*Wiring_TIM3_Interrupt_Handler)(void); 51 | extern void (*Wiring_TIM4_Interrupt_Handler)(void); 52 | 53 | extern void Wiring_TIM2_Interrupt_Handler_override(void); 54 | extern void Wiring_TIM3_Interrupt_Handler_override(void); 55 | extern void Wiring_TIM4_Interrupt_Handler_override(void); 56 | 57 | enum TIMid {TIMER2, TIMER3, TIMER4, AUTO=255}; 58 | typedef uint16_t intPeriod; 59 | #elif defined(STM32F10X_MD) //Core 60 | #warning "CORE NEW" 61 | extern void Wiring_TIM2_Interrupt_Handler_override(void); 62 | extern void Wiring_TIM3_Interrupt_Handler_override(void); 63 | extern void Wiring_TIM4_Interrupt_Handler_override(void); 64 | 65 | enum TIMid {TIMER2, TIMER3, TIMER4, AUTO=255}; 66 | typedef uint16_t intPeriod; 67 | #elif defined(STM32F2XX) //Photon 68 | extern void Wiring_TIM3_Interrupt_Handler_override(void); 69 | extern void Wiring_TIM4_Interrupt_Handler_override(void); 70 | extern void Wiring_TIM5_Interrupt_Handler_override(void); 71 | extern void Wiring_TIM6_Interrupt_Handler_override(void); 72 | extern void Wiring_TIM7_Interrupt_Handler_override(void); 73 | 74 | enum TIMid {TIMER3, TIMER4, TIMER5, TIMER6, TIMER7, AUTO=255}; 75 | typedef uint32_t intPeriod; 76 | #endif 77 | 78 | class IntervalTimer { 79 | private: 80 | typedef void (*ISRcallback)(); 81 | enum {TIMER_OFF, TIMER_SIT}; 82 | #if defined(STM32F10X_MD) || !defined(PLATFORM_ID) //Core 83 | static const uint8_t NUM_SIT = 3; 84 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 85 | static const uint8_t NUM_SIT = 5; 86 | #endif 87 | 88 | bool sysIntSetupDone = false; 89 | 90 | // Timer ClockDivision = DIV4 91 | const uint16_t SIT_PRESCALERu = (uint16_t)(SYSCORECLOCK / 1000000UL) - 1; //To get TIM counter clock = 1MHz 92 | const uint16_t SIT_PRESCALERm = (uint16_t)(SYSCORECLOCK / 2000UL) - 1; //To get TIM counter clock = 2KHz 93 | const uint16_t MAX_PERIOD = UINT16_MAX; // 1-65535 us 94 | 95 | static bool SIT_used[NUM_SIT]; 96 | bool allocate_SIT(intPeriod Period, bool scale, TIMid id); 97 | void start_SIT(intPeriod Period, bool scale); 98 | void stop_SIT(); 99 | bool status; 100 | uint8_t SIT_id; 101 | ISRcallback myISRcallback; 102 | 103 | bool beginCycles(void (*isrCallback)(), intPeriod Period, bool scale, TIMid id); 104 | 105 | public: 106 | IntervalTimer() { 107 | status = TIMER_OFF; 108 | 109 | for (int i=0; i < NUM_SIT; i++) //Set all SIT slots to unused 110 | SIT_used[i] = false; 111 | 112 | // Attach timer interrupt handlers 113 | #if !defined(PLATFORM_ID) //Core v0.3.4 114 | Wiring_TIM2_Interrupt_Handler = Wiring_TIM2_Interrupt_Handler_override; 115 | Wiring_TIM3_Interrupt_Handler = Wiring_TIM3_Interrupt_Handler_override; 116 | Wiring_TIM4_Interrupt_Handler = Wiring_TIM4_Interrupt_Handler_override; 117 | 118 | #elif defined(STM32F10X_MD) //Core 119 | if (!sysIntSetupDone) { 120 | sysIntSetupDone = true; 121 | if (!attachSystemInterrupt(SysInterrupt_TIM2, Wiring_TIM2_Interrupt_Handler_override)) ; //error 122 | if (!attachSystemInterrupt(SysInterrupt_TIM3, Wiring_TIM3_Interrupt_Handler_override)) ; //error 123 | if (!attachSystemInterrupt(SysInterrupt_TIM4, Wiring_TIM4_Interrupt_Handler_override)) ; //error 124 | } 125 | #elif defined(STM32F2XX) && defined(PLATFORM_ID) //Photon 126 | if (!sysIntSetupDone) { 127 | sysIntSetupDone = true; 128 | if (!attachSystemInterrupt(SysInterrupt_TIM3_Update, Wiring_TIM3_Interrupt_Handler_override)) ; //error 129 | if (!attachSystemInterrupt(SysInterrupt_TIM4_Update, Wiring_TIM4_Interrupt_Handler_override)) ; //error 130 | if (!attachSystemInterrupt(SysInterrupt_TIM5_Update, Wiring_TIM5_Interrupt_Handler_override)) ; //error 131 | if (!attachSystemInterrupt(SysInterrupt_TIM6_Update, Wiring_TIM6_Interrupt_Handler_override)); //error 132 | if (!attachSystemInterrupt(SysInterrupt_TIM7_Update, Wiring_TIM7_Interrupt_Handler_override)); //error 133 | } 134 | #endif 135 | 136 | } 137 | 138 | ~IntervalTimer() { end(); } 139 | 140 | bool begin(void (*isrCallback)(), intPeriod Period, bool scale) { 141 | if (Period < 10 || Period > MAX_PERIOD) 142 | return false; 143 | return beginCycles(isrCallback, Period, scale, AUTO); 144 | } 145 | 146 | bool begin(void (*isrCallback)(), intPeriod Period, bool scale, TIMid id) { 147 | if (Period < 10 || Period > MAX_PERIOD) 148 | return false; 149 | return beginCycles(isrCallback, Period, scale, id); 150 | } 151 | 152 | void end(); 153 | void interrupt_SIT(action ACT); 154 | void resetPeriod_SIT(intPeriod newPeriod, bool scale); 155 | int8_t isAllocated_SIT(void); 156 | 157 | static ISRcallback SIT_CALLBACK[NUM_SIT]; 158 | }; 159 | 160 | 161 | #ifdef __cplusplus 162 | } 163 | #endif 164 | 165 | #endif 166 | -------------------------------------------------------------------------------- /firmware/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | particle compile photon . --saveTo walkieTalkies.bin 4 | -------------------------------------------------------------------------------- /firmware/main.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "SparkIntervalTimer.h" 4 | #include "SimpleRingBuffer.h" 5 | 6 | //WiFi.selectAntenna(ANT_EXTERNAL); 7 | 8 | #define MICROPHONE_PIN DAC1 9 | #define SPEAKER_PIN DAC2 10 | #define BUTTON_PIN A0 11 | #define BROADCAST_PORT 3443 12 | #define UDP_BROADCAST_PORT 3444 13 | #define AUDIO_BUFFER_MAX 8192 14 | 15 | //#define SERIAL_DEBUG_ON true 16 | 17 | #define AUDIO_TIMING_VAL 125 /* 8,000 hz */ 18 | //#define AUDIO_TIMING_VAL 62 /* 16,000 hz */ 19 | //#define AUDIO_TIMING_VAL 50 /* 20,000 hz */ 20 | 21 | UDP Udp; 22 | IPAddress broadcastAddress(255,255,255,255); 23 | 24 | int audioStartIdx = 0, audioEndIdx = 0; 25 | int rxBufferLen = 0, rxBufferIdx = 0; 26 | 27 | //uint16_t audioBuffer[AUDIO_BUFFER_MAX]; 28 | uint8_t txBuffer[AUDIO_BUFFER_MAX]; 29 | //uint8_t rxBuffer[AUDIO_BUFFER_MAX]; 30 | 31 | 32 | SimpleRingBuffer audio_buffer; 33 | SimpleRingBuffer recv_buffer; 34 | 35 | 36 | // IntervalTimer readMicTimer; 37 | // IntervalTimer sendAudioTimer; 38 | 39 | // version without timers 40 | unsigned long lastRead = micros(); 41 | unsigned long lastSend = millis(); 42 | char myIpAddress[24]; 43 | 44 | TCPClient audioClient; 45 | TCPClient checkClient; 46 | TCPServer audioServer = TCPServer(BROADCAST_PORT); 47 | 48 | IntervalTimer readMicTimer; 49 | //int led_state = 0; 50 | float _volumeRatio = 0.25; 51 | int _sendBufferLength = 0; 52 | unsigned int lastPublished = 0; 53 | 54 | 55 | void setup() { 56 | #if SERIAL_DEBUG_ON 57 | Serial.begin(115200); 58 | #endif 59 | 60 | pinMode(MICROPHONE_PIN, INPUT); 61 | pinMode(SPEAKER_PIN, OUTPUT); 62 | pinMode(BUTTON_PIN, INPUT_PULLDOWN); 63 | pinMode(D7, OUTPUT); 64 | 65 | Particle.function("setVolume", onSetVolume); 66 | 67 | Particle.variable("ipAddress", myIpAddress, STRING); 68 | IPAddress myIp = WiFi.localIP(); 69 | sprintf(myIpAddress, "%d.%d.%d.%d", myIp[0], myIp[1], myIp[2], myIp[3]); 70 | 71 | recv_buffer.init(AUDIO_BUFFER_MAX); 72 | audio_buffer.init(AUDIO_BUFFER_MAX); 73 | 74 | Udp.setBuffer(1024); 75 | Udp.begin(UDP_BROADCAST_PORT); 76 | 77 | 78 | // Udp.beginPacket(broadcastAddress, UDP_BROADCAST_PORT); 79 | // Udp.write(rxBuffer, 10); 80 | // Udp.endPacket(); 81 | 82 | 83 | // 1/16,000th of a second is ~62 mcsec 84 | //readMicTimer.begin(readMic, 62, uSec); 85 | 86 | 87 | // // send a chunk of audio every 1/2 second 88 | // sendAudioTimer.begin(sendAudio, 1000, hmSec); 89 | 90 | audioServer.begin(); 91 | 92 | lastRead = micros(); 93 | } 94 | 95 | bool _isRecording = false; 96 | void startRecording() { 97 | 98 | if (!_isRecording) { 99 | // 1/8000th of a second is 125 microseconds 100 | readMicTimer.begin(readMic, AUDIO_TIMING_VAL, uSec); 101 | } 102 | 103 | _isRecording = true; 104 | } 105 | 106 | void stopRecording() { 107 | if (_isRecording) { 108 | readMicTimer.end(); 109 | } 110 | _isRecording = false; 111 | } 112 | 113 | void loop() { 114 | // checkClient = audioServer.available(); 115 | // if (checkClient.connected()) { 116 | // audioClient = checkClient; 117 | // } 118 | 119 | //listen for 100ms, taking a sample every 125us, 120 | //and then send that chunk over the network. 121 | //listenAndSend(100); 122 | 123 | if (digitalRead(BUTTON_PIN) == HIGH) { 124 | digitalWrite(D7, HIGH); 125 | startRecording(); 126 | sendEvery(100); 127 | } 128 | else { 129 | digitalWrite(D7, LOW); 130 | stopRecording(); 131 | } 132 | 133 | readAndPlay(); 134 | //led_state = !led_state; 135 | } 136 | 137 | 138 | int onSetVolume(String cmd) { 139 | _volumeRatio = cmd.toFloat() / 100; 140 | } 141 | 142 | void readAndPlay() { 143 | 144 | 145 | while (Udp.parsePacket() > 0) { 146 | while (Udp.available() > 0) { 147 | recv_buffer.put(Udp.read()); 148 | } 149 | if (recv_buffer.getSize() == 0) { 150 | analogWrite(SPEAKER_PIN, 0); 151 | } 152 | } 153 | 154 | 155 | 156 | 157 | 158 | // #if SERIAL_DEBUG_ON 159 | // Serial.println("received " + String(count)); 160 | // #endif 161 | 162 | // //read as much as we can off the buffer. 163 | // rxBufferLen = Udp.read(rxBuffer, AUDIO_BUFFER_MAX); 164 | // rxBufferIdx = 0; 165 | 166 | playRxAudio(); 167 | } 168 | 169 | void playRxAudio() { 170 | unsigned long lastWrite = micros(); 171 | unsigned long now, diff; 172 | int value; 173 | 174 | //toggleLED(); 175 | 176 | //noInterrupts(); 177 | 178 | //while (rxBufferIdx < rxBufferLen) { 179 | while (recv_buffer.getSize() > 0) { 180 | 181 | // --- 182 | //map it back from 1 byte to 2 bytes 183 | //map(value, fromLow, fromHigh, toLow, toHigh); 184 | //value = map(rxBuffer[rxBufferIdx++], 0, 255, 0, 4095); 185 | 186 | //play audio 187 | value = recv_buffer.get(); 188 | value = map(value, 0, 255, 0, 4095); 189 | value = value * _volumeRatio; 190 | 191 | now = micros(); 192 | diff = (now - lastWrite); 193 | if (diff < AUDIO_TIMING_VAL) { 194 | delayMicroseconds(AUDIO_TIMING_VAL - diff); 195 | } 196 | 197 | //analogWrite(SPEAKER_PIN, rxBuffer[rxBufferIdx++]); 198 | analogWrite(SPEAKER_PIN, value); 199 | lastWrite = micros(); 200 | } 201 | 202 | //interrupts(); 203 | 204 | //toggleLED(); 205 | } 206 | 207 | 208 | void listenAndSend(int delay) { 209 | unsigned long startedListening = millis(); 210 | 211 | while ((millis() - startedListening) < delay) { 212 | unsigned long time = micros(); 213 | 214 | if (lastRead > time) { 215 | // time wrapped? 216 | //lets just skip a beat for now, whatever. 217 | lastRead = time; 218 | } 219 | 220 | //125 microseconds is 1/8000th of a second 221 | if ((time - lastRead) > 125) { 222 | lastRead = time; 223 | readMic(); 224 | } 225 | } 226 | sendAudio(); 227 | } 228 | 229 | void sendEvery(int delay) { 230 | 231 | // #if SERIAL_DEBUG_ON 232 | // Serial.println("sendEvery"); 233 | // #endif 234 | 235 | // if it's been longer than 100ms since our last broadcast, then broadcast. 236 | if ((millis() - lastSend) >= delay) { 237 | 238 | sendAudio(); 239 | 240 | lastSend = millis(); 241 | } 242 | } 243 | 244 | // Callback for Timer 1 245 | void readMic(void) { 246 | //read audio 247 | uint16_t value = analogRead(MICROPHONE_PIN); 248 | value = map(value, 0, 4095, 0, 255); 249 | audio_buffer.put(value); 250 | 251 | //old 252 | // if (audioEndIdx >= AUDIO_BUFFER_MAX) { 253 | // audioEndIdx = 0; 254 | // } 255 | // audioBuffer[audioEndIdx++] = value; 256 | 257 | 258 | // //play audio 259 | // value = map(recv_buffer.get(), 0, 255, 0, 4095); 260 | // if (value >= 0) { 261 | // analogWrite(SPEAKER_PIN, value); 262 | // } 263 | 264 | 265 | // //play audio 266 | // if (rxBufferIdx < rxBufferLen) { 267 | // 268 | //// uint8_t lsb = rxBuffer[rxBufferIdx]; 269 | //// uint8_t msb = rxBuffer[rxBufferIdx+1]; 270 | //// rxBufferIdx +=2; 271 | //// uint16_t value = ((msb << 8) | (lsb & 0xFF)); 272 | //// value = (value / 65536.0) * 4095.0; 273 | //// analogWrite(SPEAKER_PIN, value); 274 | // 275 | // //tcpBuffer[tcpIdx] = map(val, 0, 4095, 0, 255); 276 | // analogWrite(SPEAKER_PIN, rxBuffer[rxBufferIdx++]); 277 | // } 278 | //digitalWrite(D7, (led_state) ? HIGH : LOW); 279 | 280 | // if (rxBufferIdx < rxBufferLen) { 281 | // int value = map(rxBuffer[rxBufferIdx++], 0, 255, 0, 4095); 282 | // analogWrite(SPEAKER_PIN, value); 283 | // } 284 | } 285 | 286 | void copyAudio(uint8_t *bufferPtr) { 287 | 288 | int c = 0; 289 | while ((audio_buffer.getSize() > 0) && (c < AUDIO_BUFFER_MAX)) { 290 | bufferPtr[c++] = audio_buffer.get(); 291 | } 292 | _sendBufferLength = c - 1; 293 | 294 | 295 | 296 | // //if end is after start, read from start->end 297 | // //if end is before start, then we wrapped, read from start->max, 0->end 298 | // 299 | // int endSnapshotIdx = audioEndIdx; 300 | // bool wrapped = endSnapshotIdx < audioStartIdx; 301 | // int endIdx = (wrapped) ? AUDIO_BUFFER_MAX : endSnapshotIdx; 302 | // 303 | // 304 | // for(int i=audioStartIdx;i= 1024) { 364 | socket.write(tcpBuffer, tcpIdx); 365 | tcpIdx = 0; 366 | } 367 | 368 | tcpBuffer[tcpIdx] = val & 0xff; 369 | tcpBuffer[tcpIdx+1] = (val >> 8); 370 | tcpIdx += 2; 371 | } 372 | 373 | // any leftovers? 374 | if (tcpIdx > 0) { 375 | socket.write(tcpBuffer, tcpIdx); 376 | } 377 | } 378 | 379 | void write_UDP(uint8_t *buffer) { 380 | int stopIndex=_sendBufferLength; 381 | // uint16_t val = 0; 382 | 383 | // int tcpIdx = 0; 384 | // uint8_t tcpBuffer[1024]; 385 | 386 | 387 | 388 | 389 | // while (( buffer[stopIndex++] < 4096 ) && (stopIndex < AUDIO_BUFFER_MAX)) { 390 | // ; 391 | // } 392 | #if SERIAL_DEBUG_ON 393 | Serial.println("SENDING " + String(stopIndex)); 394 | #endif 395 | Udp.sendPacket(buffer, stopIndex, broadcastAddress, UDP_BROADCAST_PORT); 396 | 397 | //Udp.beginPacket(broadcastAddress, UDP_BROADCAST_PORT); 398 | 399 | // while( (val = buffer[i++]) < 4096 ) { 400 | // if ((tcpIdx+1) >= 1024) { 401 | // 402 | // //works 403 | //// Udp.beginPacket(broadcastAddress, UDP_BROADCAST_PORT); 404 | //// Udp.write(tcpBuffer, tcpIdx); 405 | //// Udp.endPacket(); 406 | // 407 | // //Doesn't work 408 | // Udp.sendPacket(tcpBuffer, tcpIdx, broadcastAddress, UDP_BROADCAST_PORT); 409 | // 410 | // 411 | // #if SERIAL_DEBUG_ON 412 | // Serial.println("SENT " + String(tcpIdx)); 413 | // #endif 414 | // //delay(5); 415 | // 416 | // 417 | // tcpIdx = 0; 418 | // toggleLED(); 419 | // } 420 | // 421 | // //map(value, fromLow, fromHigh, toLow, toHigh); 422 | // tcpBuffer[tcpIdx] = val; //map(val, 0, 4095, 0, 255); 423 | // tcpIdx++; 424 | // 425 | //// tcpBuffer[tcpIdx] = val & 0xff; 426 | //// tcpBuffer[tcpIdx+1] = (val >> 8); 427 | //// tcpIdx += 2; 428 | // } 429 | // 430 | // // any leftovers? 431 | // if (tcpIdx > 0) { 432 | // //works 433 | //// Udp.beginPacket(broadcastAddress, UDP_BROADCAST_PORT); 434 | //// Udp.write(tcpBuffer, tcpIdx); 435 | //// Udp.endPacket(); 436 | // 437 | // //doesn't work 438 | // Udp.sendPacket(tcpBuffer, tcpIdx, broadcastAddress, UDP_BROADCAST_PORT); 439 | // 440 | // #if SERIAL_DEBUG_ON 441 | // Serial.println("SENT " + String(tcpIdx)); 442 | // #endif 443 | // } 444 | 445 | //toggleLED(); 446 | } 447 | 448 | bool ledState = false; 449 | void toggleLED() { 450 | ledState = !ledState; 451 | digitalWrite(D7, (ledState) ? HIGH : LOW); 452 | } -------------------------------------------------------------------------------- /firmware/walkieTalkies.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmiddlecamp/walkie_talkies/c59f2538193c263ed09a255ec8fafe06ab2d521a/firmware/walkieTalkies.bin -------------------------------------------------------------------------------- /hardware/README.md: -------------------------------------------------------------------------------- 1 | Bill Of Materials: 2 | === 3 | 4 | - Particle Photon ($19) 5 | - https://www.adafruit.com/products/2721 6 | - powerful little low power microcontroller, super easy to make a connected thing 7 | 8 | - Small speaker ($2) 9 | - https://www.adafruit.com/products/1314 10 | - a 3w 4ohm speaker should be plenty loud for this! We'll use a small amp to power it 11 | 12 | - Small Speaker Amp ($4) (optional) 13 | - https://www.adafruit.com/products/2130 14 | - so you can be loud! 15 | 16 | - Microphone ($8) 17 | - https://www.adafruit.com/products/1713 18 | - I like this microphone because it adjusts the gain so you can use it from far away or close up easily! 19 | 20 | - Small button (optional) 21 | - https://www.adafruit.com/products/367 22 | - push to talk! fits in a breadboard! 23 | 24 | - Breadboard! ($5) 25 | - https://www.adafruit.com/products/64 26 | - You might have a bunch of these laying around like me, if not they're super handy! 27 | 28 | Subtotal 29 | === 30 | 31 | Depending on what you have laying around, retail you can build a pair of these for less than $70. Not bad for a 32 | high quality, fully customizable toy / tool that works anywhere your Wi-Fi works. And with some small software 33 | changes, can work anywhere in the world... So not bad for a simple VOIP phone with zero subscription costs! -------------------------------------------------------------------------------- /hardware/photon_walkie_talkies.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmiddlecamp/walkie_talkies/c59f2538193c263ed09a255ec8fafe06ab2d521a/hardware/photon_walkie_talkies.fzz -------------------------------------------------------------------------------- /photos/IMG_2229.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmiddlecamp/walkie_talkies/c59f2538193c263ed09a255ec8fafe06ab2d521a/photos/IMG_2229.JPG -------------------------------------------------------------------------------- /photos/IMG_2230.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmiddlecamp/walkie_talkies/c59f2538193c263ed09a255ec8fafe06ab2d521a/photos/IMG_2230.JPG -------------------------------------------------------------------------------- /photos/IMG_2231.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmiddlecamp/walkie_talkies/c59f2538193c263ed09a255ec8fafe06ab2d521a/photos/IMG_2231.jpg --------------------------------------------------------------------------------