├── .config ├── LICENSE ├── Makefile ├── README ├── include ├── comm │ ├── spi.h │ └── usart.h ├── mem │ └── memory.h ├── sys │ ├── kernel.h │ ├── mutex.h │ ├── request.h │ ├── stack.h │ ├── system.h │ ├── tasks.h │ └── watchdog.h ├── tmr │ ├── software_timer.h │ └── timer.h └── util │ └── queue.h ├── src ├── comm │ ├── spi.c │ └── usart.c ├── main.c ├── mem │ └── memory.c ├── sys │ ├── ' │ ├── housekeeper_prelude.S │ ├── housekeeping.S │ ├── kernel.c │ ├── request.c │ ├── system.c │ ├── tasks.c │ └── watchdog.c ├── tmr │ ├── software_timer.c │ ├── timer.c │ └── timer_isr.S └── util │ └── queue.c └── usr ├── include ├── memory_request.h └── user.h └── src ├── memory_request.c └── user.c /.config: -------------------------------------------------------------------------------- 1 | TARGET=avr 2 | ARCH=ATMEGA328P 3 | ADUDE = avrdude 4 | OCOPY = avr-objcopy 5 | CFLAGS := -mmcu=atmega328p -DF_CPU=16000000UL -fno-stack-protector 6 | CFLAGS += -fshort-enums -fno-pie -Os -ggdb 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := avr 2 | CC := $(TARGET)-gcc 3 | INCLUDE := include/ 4 | USER_INCLUDE := usr/include/ 5 | PROJDIRS := mem usr sys tmr net comm 6 | OCOPY := avr-objcopy 7 | INSTALLER := avrdude 8 | GLOBAL_DEFINES := -DUSE_EEPROM_MEMORY=0 -DDEBUG=1 -DUSE_SOFTWARE_TIMER=1 9 | 10 | CFLAGS = -mmcu=atmega328p -I$(INCLUDE) -I$(USER_INCLUDE) $(GLOBAL_DEFINES) -Os 11 | 12 | SOURCEFILES := $(shell find . -name "*.c") 13 | SOURCEFILES += $(shell find . -name "*.S") 14 | 15 | OBJS := $(SOURCEFILES) 16 | 17 | %.o: %.c 18 | $(CC) $(CFLAGS) -c -o $@ $< $(CFLAGS) 19 | 20 | %.o: %.S 21 | $(CC) $(CFLAGS) -c -o $@ $< $(CFLAGS) 22 | 23 | kernel: $(OBJS) 24 | $(CC) -o $@ $^ $(CFLAGS) 25 | $(OCOPY) -O ihex -R .eeprom kernel kernel.hex 26 | 27 | 28 | install: kernel 29 | $(INSTALLER) -F -V -c arduino -p ATMEGA328P -P /dev/ttyACM0 -U flash:w:kernel.hex 30 | 31 | .PHONY: clean 32 | 33 | clean: 34 | rm kernel kernel.hex 35 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is a simple kernel for the arduino uno. It is designed to be pre-emptive. 2 | It features a round robin scheduler, and I am in the process of implementing a 3 | blocking sleep that works even when accounting for time taken by other tasks 4 | and context switching. 5 | 6 | The only note is that the round robin scheduler does not necessarily run a task 7 | that has just been woken up. Instead, it will set the task as runnable and then 8 | go through the process of selecting the next task (whatever is next in the list). 9 | For that reason, there may be some delay between when the task is woken up and 10 | when the task is actually run (although, this will be a matter of milliseconds). 11 | 12 | I have successfully implemented an API that can be used in order to easily access 13 | the eeprom. Both the low level utilities (eeprom_write) as well as the high level 14 | utility (memory_write) can be used. Reading and writing are both supported. It 15 | will be easy to add support for writing to flash with memory_read/write, 16 | however, I intend to put that away for now. 17 | 18 | For the EEPROM and flash utilities, the user will be able to use an API 19 | (detailed in memory_request.h) that will mainly consist of filling an 20 | array of structs and setting number of members. From there, reading 21 | or writing to the EEPROM becomes as easy as providing a task index and 22 | a space for the user. If the user wishes to use the low level utilities 23 | and do it themselves, this can also be done. However, it is advised that 24 | they set the pointers to NULL and the req_count variables to 0 in the 25 | memory_request.c file. 26 | 27 | One other thing that should be mentioned is that a number of the data 28 | structures that are used throughout the kernel have been moved into 29 | the data section (they are declared and defined in user.h/c). The 30 | user will mainly be required to set #defines in user.h. However, 31 | they will also be required to set up the function pointers in 32 | user.c 33 | 34 | Here are some things that I plan to add: 35 | 36 | Mutexes 37 | Making the kernel easier to port to other architectures 38 | -------------------------------------------------------------------------------- /include/comm/spi.h: -------------------------------------------------------------------------------- 1 | #ifndef SPI_H 2 | #define SPI_H 3 | #include 4 | #include 5 | 6 | #define SPI_SCK PB5 7 | #define SPI_MISO PB4 8 | #define SPI_MOSI PB3 9 | #define SPI_SS PB2 10 | 11 | #define SPI_SS_LOW() PORTB &= ~(1 << SPI_SS) 12 | #define SPI_SS_HIGH() PORTB |= (1 << SPI_SS) 13 | 14 | void spi_init(); 15 | 16 | uint8_t do_spi(uint8_t byte); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/comm/usart.h: -------------------------------------------------------------------------------- 1 | #ifndef USART_H 2 | #define USART_H 3 | #include 4 | #include 5 | #include "sys/mutex.h" 6 | 7 | #define USART_DATA_EMPTY (1 << 5) 8 | #define TRANSMITTER_ENABLE (1 << 3) 9 | #define TRANSMIT_8_BITS ((1 << 2) | (1 << 1)) 10 | 11 | #define STRLEN(X) ((sizeof(X)/sizeof(X[0]))-1) 12 | 13 | #define println_c(U8_DATA) println(U8_DATA, STRLEN(U8_DATA)) 14 | 15 | /* 16 | * For now, we're going to keep it simple and have a simple usart transmitter 17 | * that transmits at 9600 8N1. Right now, it's there to debug the kernel. 18 | */ 19 | void usart_init(); 20 | 21 | void usart_transmit(uint8_t data); 22 | 23 | void print(uint8_t *data, uint16_t len); 24 | 25 | void println(uint8_t *data, uint16_t len); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/mem/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_H 2 | #define MEMORY_H 3 | #include 4 | #include "sys/tasks.h" 5 | #include "memory_request.h" 6 | 7 | #define EEPROM_PGSIZE 4 8 | #define EEPROM_PGCOUNT 250 9 | #define FLASH_WORD_PGSIZE 64 10 | #define FLASH_WORD_PGCOUNT 256 11 | 12 | 13 | /* The reason why I use this macro is because 14 | * it may save on memory in the event that 15 | * the user does not desire to use any of the 16 | * memory io functions. 17 | * 18 | * Given the power of GCC, it is likely that 19 | * it will disinclude any memory io functions that 20 | * are not used anyway. However, this maybe this will 21 | * add a little safeguard, just in case (or maybe its 22 | * inefficient) 23 | */ 24 | 25 | #ifdef MEMORY_REQUESTED 26 | 27 | enum memory_space { 28 | eeprom = 1, 29 | flash = 2 30 | }; 31 | 32 | static inline unsigned char eeprom_read_byte(void *addr) { 33 | 34 | // Wait for completion of previous write 35 | while (EECR & (1 << EEPE)); 36 | 37 | // Set up address register 38 | EEAR = (uint16_t) addr; 39 | 40 | /* This might be an area where a read can 41 | * fail if an interrupt is generated. I 42 | * don't really know, to be honest. 43 | */ 44 | asm("cli\n\t"); 45 | // Start eeprom read by 46 | EECR |= (1 << EERE); 47 | asm("sei\n\t"); 48 | 49 | // Return data from data register 50 | return EEDR; 51 | } 52 | 53 | static inline void eeprom_read(void *addr, 54 | uint16_t begin, 55 | uint16_t end, 56 | unsigned char *buffer) { 57 | 58 | /* Read the total number of requested bytes from eeprom 59 | * into buffer 60 | */ 61 | for (int i = begin; i < end; ++i) { 62 | buffer[i] = eeprom_read_byte((void *) 63 | ((unsigned int) addr + i)); 64 | } 65 | } 66 | 67 | static inline void eeprom_write_byte(void *addr, 68 | unsigned char data) { 69 | 70 | // This came from the data sheet 71 | // Wait for completion of previous write 72 | while (EECR & (1 << EEPE)); 73 | 74 | // Write new EEPROM address to EEAR 75 | EEAR = (uint16_t) addr; 76 | 77 | // Write new EEPROM data to EEDR 78 | EEDR = data; 79 | 80 | /* During this portion, it is important to clear interrupts in order to 81 | * ensure write success 82 | */ 83 | asm("cli\n\t"); 84 | // Write a logical one to the EEMPE bit while writing a zero to EEPE in EECR 85 | EECR |= (1 << EEMPE); 86 | EECR |= (1 << EEPE); 87 | asm("sei\n\t"); 88 | 89 | return; 90 | } 91 | 92 | static inline void eeprom_write(void *addr, 93 | uint16_t begin, 94 | uint16_t end, 95 | unsigned char *data) { 96 | 97 | // Write each byte to eeprom 98 | for (int i = begin; i < end; ++i) { 99 | eeprom_write_byte((void *)((unsigned int) addr + i), data[i]); 100 | } 101 | 102 | return; 103 | } 104 | 105 | /* The idea is that the programmer will fill a 106 | * datastructure that will contain the task's id (an index 107 | * for the set of tasks), as well as the desired 108 | * size for eeprom (0 if none) and the desired size 109 | * for the flash. From here, the kernel is able to 110 | * infer where the task's location in memory is. 111 | * 112 | * If the there is an error, the kernel will return 113 | * 0 (false). Eventually, it would be useful for me 114 | * to include error codes in these functions. 115 | */ 116 | 117 | 118 | /* Memory write will write the buffer to the task's 119 | * space. 120 | * 121 | * The begin and end is used in order to determine where within the buffer 122 | * to begin and end. This can be used to get a slice of data within the 123 | * buffer if not all is needed. 124 | * 125 | * 1 indicates success 126 | * 0 indicates error 127 | */ 128 | int memory_write(int task_id, 129 | enum memory_space zone, 130 | uint16_t begin, 131 | uint16_t end, 132 | unsigned char *buffer); 133 | 134 | /* read contents of task's memory space 135 | * into the buffer. This will mean either 136 | * the entire memory space or the number of 137 | * bytes, whichever is smallest. 138 | * 139 | * 1 indicates success 140 | * 0 indicates error 141 | */ 142 | int memory_read(int task_id, 143 | enum memory_space zone, 144 | uint16_t begin, 145 | uint16_t end, 146 | unsigned char *buffer); 147 | 148 | #endif 149 | #endif 150 | -------------------------------------------------------------------------------- /include/sys/kernel.h: -------------------------------------------------------------------------------- 1 | #ifndef KERNEL_H 2 | #define KERNEL_H 3 | #include "sys/tasks.h" 4 | 5 | #define KERNEL_STACK_SIZE 0x20 6 | 7 | extern struct task kernel_task; 8 | 9 | void kernel_task_funct(); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/sys/mutex.h: -------------------------------------------------------------------------------- 1 | #ifndef MUTEX_H 2 | #define MUTEX_H 3 | #include 4 | #include 5 | 6 | struct mutex { 7 | uint8_t held; 8 | }; 9 | 10 | static inline void hold_mutex(struct mutex *m) { 11 | while (m->held); 12 | 13 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 14 | m->held = 1; 15 | } 16 | } 17 | 18 | static inline void release_mutex(struct mutex *m) { 19 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 20 | m->held = 0; 21 | } 22 | } 23 | 24 | static inline void hold_mutex_or_return(struct mutex *m) { 25 | if (m->held) { 26 | return; 27 | } 28 | 29 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 30 | m->held = 0; 31 | } 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /include/sys/request.h: -------------------------------------------------------------------------------- 1 | #ifndef REQUEST_H 2 | #define REQUEST_H 3 | #include 4 | #include 5 | #include "tasks.h" 6 | 7 | 8 | enum active_req_status { 9 | inactive = 0, 10 | active = 1 11 | }; 12 | 13 | struct request_entry { 14 | uint32_t value; 15 | }; 16 | 17 | extern struct task tasks[]; 18 | extern struct request_entry requests []; 19 | extern int request_array[]; 20 | 21 | #include "system.h" 22 | 23 | /* Add the task to the next spot in the array */ 24 | void add_req_entry(uint8_t task_ndx, uint32_t value); 25 | 26 | 27 | /* Shift struct values through the first inactive index. 28 | * This way, index 0 has the values of index 1 and so on. 29 | */ 30 | void check_req_top(); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /include/sys/stack.h: -------------------------------------------------------------------------------- 1 | #ifndef STACK_H 2 | #define STACK_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* A big allocated stack. This is portioned out as needed for the various 9 | * tasks. 10 | */ 11 | struct stack { 12 | void *stack_space; 13 | size_t stack_num; 14 | size_t stack_size; 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /include/sys/system.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEM_H 2 | #define SYSTEM_H 3 | #include 4 | #include "request.h" 5 | #include "user.h" 6 | 7 | struct sys_clock { 8 | volatile uint32_t time; 9 | }; 10 | 11 | extern volatile struct sys_clock system_time; 12 | 13 | static inline void task_yield() { 14 | asm( 15 | "call housekeeper_prelude\n\t" 16 | ); 17 | } 18 | 19 | void kernel_task_funct(); 20 | 21 | void task_sleep(uint8_t task_ndx, uint32_t time); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/sys/tasks.h: -------------------------------------------------------------------------------- 1 | #ifndef TASKS_H 2 | #define TASKS_H 3 | #include 4 | #include 5 | #include "stack.h" 6 | #include 7 | #include 8 | 9 | #define ADDR_LO(LABEL) LABEL & 0xff 10 | #define ADDR_HI(LABEL) (LABEL & 0xff00) >> 8 11 | 12 | /* Right now there are only two 13 | * task states. The only possible 14 | * reason why this would be necessary 15 | * is if the programmer had a task 16 | * that could end, and wanted to use 17 | * the stack space of that task later. 18 | */ 19 | 20 | typedef enum task_state { 21 | runnable = 0, 22 | complete = 1, 23 | waiting = 2 24 | } task_state; 25 | 26 | struct context { 27 | void *sp_start; 28 | void *sp; 29 | uint8_t r0; 30 | /* r1 should always contain zero. Must be saved during ISR prologue. Must be zeroed out during 31 | * the ISR routine. Must be restored during ISR epilogue 32 | */ 33 | uint8_t r1; 34 | uint8_t r2; 35 | uint8_t r3; 36 | uint8_t r4; 37 | uint8_t r5; 38 | uint8_t r6; 39 | uint8_t r7; 40 | uint8_t r8; 41 | uint8_t r9; 42 | uint8_t r10; 43 | uint8_t r11; 44 | uint8_t r12; 45 | uint8_t r13; 46 | uint8_t r14; 47 | uint8_t r15; 48 | uint8_t r16; 49 | uint8_t r17; 50 | uint8_t r18; 51 | uint8_t r19; 52 | uint8_t r20; 53 | uint8_t r21; 54 | uint8_t r22; 55 | uint8_t r23; 56 | uint8_t r24; 57 | uint8_t r25; 58 | uint8_t r26; 59 | uint8_t r27; 60 | uint8_t r28; 61 | uint8_t r29; 62 | uint8_t r30; 63 | uint8_t r31; 64 | }; 65 | 66 | // This is a linked list for the task structs 67 | 68 | struct task { 69 | struct context c; 70 | struct task *next; 71 | task_state state; 72 | void (*task_funct)(); 73 | }; 74 | 75 | extern struct task tasks[]; 76 | 77 | void make_task(struct task *curr); 78 | 79 | /* Assign a stack for each task struct. If there are more 80 | * tasks than stacks, then it will exit with error. 81 | */ 82 | 83 | int set_task_stacks(struct task *t, size_t task_num, 84 | struct stack *s, size_t stack_num); 85 | 86 | /* Mark the task as complete. Since the taska aren't 87 | * actually suppposed to exit, this tells the scheduler 88 | * not to choose that task, since it's dead. 89 | */ 90 | void end_task(struct task *t); 91 | 92 | // run task 93 | void do_task(struct task *t); 94 | 95 | extern struct task *curr; 96 | 97 | /* Iterate through the set of tasks to find one that is 98 | * runnable. If there is no runnable task found, then 99 | * return false. If a runnable task is found, then save it 100 | * in curr, and then return true. 101 | */ 102 | unsigned int get_next_task(); 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /include/sys/watchdog.h: -------------------------------------------------------------------------------- 1 | #ifndef WATCHDOG_H 2 | #define WATCHDOG_H 3 | #include "comm/usart.h" 4 | #include 5 | #include 6 | 7 | #define WATCHDOG_SYSTEM_RESET (1 << 3) 8 | #define WDT_TIMEOUT_8 ((1 << 5) | (1 << 0)) 9 | #define WDT_INTERRUPT_ENABLE (1 << 6) 10 | #define WDT_RESET_ENABLE (1 << 3) 11 | #define WDT_CHANGE_ENABLE (1 << 4) 12 | 13 | static inline void watchdog_reset() { 14 | asm("WDR\n\t"); 15 | } 16 | 17 | int watchdog_reset_detected(); 18 | 19 | void init_watchdog(); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/tmr/software_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef SOFTWARE_TIMER_H 2 | #define SOFTWARE_TIMER_H 3 | #include 4 | #include "util/queue.h" 5 | #include "sys/tasks.h" 6 | 7 | #if USE_SOFTWARE_TIMER == 1 8 | 9 | enum timer_state 10 | { 11 | timer_stopped = 0, 12 | timer_started = 1 13 | }; 14 | 15 | enum timer_type 16 | { 17 | sw_timer_one_shot, 18 | sw_timer_periodic 19 | }; 20 | 21 | struct software_timer 22 | { 23 | uint8_t id; 24 | uint32_t period; 25 | uint32_t counter; 26 | enum timer_type t; 27 | enum timer_state state; 28 | void (*callback)(); 29 | }__attribute__((packed)); 30 | 31 | extern volatile struct task software_timer_task; 32 | 33 | void init_software_timers(); 34 | 35 | void software_timer_start(struct software_timer *tmr); 36 | 37 | void software_timer_stop(struct software_timer *tmr); 38 | 39 | #endif 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /include/tmr/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H 2 | #define TIMER_H 3 | #include "comm/usart.h" 4 | #include "tmr/software_timer.h" 5 | 6 | #if DEBUG == 1 7 | static inline void print_sys_timer_start() { 8 | println("In system timer ISR", 9 | STRLEN("In system timer ISR")); 10 | } 11 | 12 | static inline void print_sys_timer_end() { 13 | println("Out of system timer ISR", 14 | STRLEN("Out of system timer ISR")); 15 | } 16 | 17 | #endif 18 | 19 | void init_timers(); 20 | 21 | void set_sys_timer(); 22 | 23 | #if USE_SOFTWARE_TIMER == 1 24 | 25 | void set_usr_timer(); 26 | 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/util/queue.h: -------------------------------------------------------------------------------- 1 | #ifndef QUEUE_H 2 | #define QUEUE_H 3 | #include 4 | #include 5 | 6 | struct queue 7 | { 8 | uint8_t size; 9 | uint8_t capacity; 10 | uint8_t head; 11 | uint8_t tail; 12 | void *items[]; 13 | }__attribute__((packed)); 14 | 15 | int enqueue(struct queue *q, void *itm); 16 | 17 | void *dequeue(struct queue *q); 18 | 19 | int queue_is_empty(struct queue *q); 20 | 21 | int queue_is_full(struct queue *q); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/comm/spi.c: -------------------------------------------------------------------------------- 1 | #include "comm/spi.h" 2 | 3 | /*! 4 | * \brief Initialize the SPI peripheral. 5 | * 6 | * This sets master, and also sets the interrupt enable. In addition, the 7 | * the mode is set to 0. 8 | */ 9 | void spi_init() { 10 | uint8_t clear; 11 | DDRB = (1 << SPI_MOSI) | (1 << SPI_SCK) | (1 << SPI_SS); 12 | 13 | SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0); 14 | 15 | SPI_SS_LOW(); 16 | 17 | SPI_SS_HIGH(); 18 | 19 | clear = SPSR; 20 | clear = SPDR; 21 | } 22 | 23 | /*! 24 | * \brief Send one byte over SPI. 25 | * 26 | * do_spi is the only available function because often the exact procedure 27 | * for reading or writing multiple bytes can vary between devices. 28 | * 29 | * \param byte 30 | * The byte to send. 31 | */ 32 | uint8_t do_spi(uint8_t byte) { 33 | SPDR = byte; 34 | 35 | while (!(SPSR & (1 << SPIF))); 36 | 37 | return SPDR; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/comm/usart.c: -------------------------------------------------------------------------------- 1 | #include "comm/usart.h" 2 | 3 | struct mutex usart_mutex = { 4 | .held = 0 5 | }; 6 | 7 | /* 8 | * Asynchronous USART, no parity, 1 stop bit, 8 9 | * data bits. 10 | * 11 | * We set the baud rate to 9600. 12 | */ 13 | void usart_init() { 14 | uint16_t baudrate = 103; 15 | UBRR0H = (baudrate >> 8); 16 | UBRR0L = (baudrate); 17 | UCSR0B = TRANSMITTER_ENABLE; 18 | UCSR0C = TRANSMIT_8_BITS; 19 | } 20 | 21 | void usart_transmit(uint8_t data) { 22 | while (!(UCSR0A & USART_DATA_EMPTY)); 23 | 24 | UDR0 = data; 25 | } 26 | 27 | void print(uint8_t *data, uint16_t len) { 28 | uint16_t i; 29 | 30 | hold_mutex_or_return(&usart_mutex); 31 | 32 | for (i = 0; i < len; ++i) { 33 | usart_transmit(data[i]); 34 | } 35 | 36 | release_mutex(&usart_mutex); 37 | } 38 | 39 | void println(uint8_t *data, uint16_t len) { 40 | uint16_t i; 41 | 42 | hold_mutex_or_return(&usart_mutex); 43 | 44 | for (i = 0; i < len; ++i) { 45 | usart_transmit(data[i]); 46 | } 47 | 48 | usart_transmit((uint8_t) '\n'); 49 | usart_transmit((uint8_t) '\r'); 50 | 51 | release_mutex(&usart_mutex); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sys/tasks.h" 3 | #include "tmr/timer.h" 4 | #include "sys/system.h" 5 | #include "user.h" 6 | #include "sys/stack.h" 7 | #include "tmr/software_timer.h" 8 | #include "comm/usart.h" 9 | #include "sys/watchdog.h" 10 | #include "sys/kernel.h" 11 | 12 | // This is mainly declared in case we wish to use flash 13 | extern uint16_t _etext; 14 | extern struct stack s; 15 | extern struct task tasks[TASK_COUNT]; 16 | 17 | /* 18 | * This is a pointer to the current task. Initially, it is set to the kernel 19 | * task (k). 20 | */ 21 | volatile struct task *current_task = &kernel_task; 22 | 23 | /*! 24 | * \brief Initialize the kernel and start the schedule. 25 | * Main begins by initializing tasks. After that, it sets their stacks. 26 | * Following that, the timers are initialized. After that, control is 27 | * given to the scheduler. 28 | */ 29 | 30 | ISR(BADISR_vect) { 31 | println("Default ISR called!", STRLEN("Default ISR called!")); 32 | while (1); 33 | } 34 | 35 | int main() { 36 | int result = 0; 37 | usart_init(); 38 | 39 | #if DEBUG == 1 40 | println("USART Initialized", STRLEN("USART Initialized")); 41 | #endif 42 | // Create linked list 43 | for (uint8_t i = 0; i < TASK_COUNT; ++i) { 44 | make_task(&tasks[i]); 45 | } 46 | 47 | // The last task will have kernel as the next task 48 | tasks[TASK_COUNT - 1].next = &kernel_task; 49 | 50 | // Set stack pointers for each task 51 | result = set_task_stacks(&tasks[0], TASK_COUNT, &s, s.stack_num); 52 | 53 | // If there was an issue with this, exit with error 54 | if (!result) { 55 | goto error; 56 | } 57 | 58 | 59 | // Set the timer 60 | 61 | #if DEBUG == 1 62 | println("Timer initialization started", 63 | STRLEN("Timer initialization started")); 64 | #endif 65 | 66 | init_timers(); 67 | 68 | #if DEBUG == 1 69 | println("Timer initialization stopped", 70 | STRLEN("Timer initialization stopped")); 71 | #endif 72 | 73 | /* This will jump to the house keeper. The house keeper will 74 | * find a new task and do some house keeping stuff 75 | */ 76 | 77 | #if DEBUG == 1 78 | println("Scheduler starting. Best of luck!", 79 | STRLEN("Scheduler starting. Best of luck!")); 80 | #endif 81 | task_yield(); 82 | 83 | /* This should never be reached */ 84 | error: 85 | 86 | #if DEBUG == 1 87 | println("Entered the error state. Something went wrong.", 88 | STRLEN("Entered the error state. Something went wrong.")); 89 | #endif 90 | while (1); 91 | } 92 | -------------------------------------------------------------------------------- /src/mem/memory.c: -------------------------------------------------------------------------------- 1 | #include "mem/memory.h" 2 | 3 | #if USE_EEPROM_MEMORY == 1 4 | 5 | #define ITERATE_ZONE_STRUCT(ZONE) \ 6 | for (int i = 0; i < ZONE ## _req_count; ++i) \ 7 | 8 | /*! 9 | * \brief Write a buffer to the memory zone. 10 | * 11 | * The idea of the memory zone is to make it convenient to write to 12 | * different memory devices easily. I may end up changing this, however. 13 | * 14 | * \param task_id 15 | * The id of the task that "owns" that part of memory. 16 | * 17 | * \param begin 18 | * Offset into the memory space owned by the task. 19 | * 20 | * \param end 21 | * End of the offset into the memory space owned by the task. 22 | * 23 | * \param buffer 24 | * The buffer to write into memory. 25 | * 26 | * \ret 27 | * 1 for success or 0 for error. 28 | */ 29 | int memory_write(int task_id, 30 | enum memory_space zone, 31 | uint16_t begin, 32 | uint16_t end, 33 | unsigned char *buffer) { 34 | 35 | /* If both pointers are NULL or both request counts 36 | * are 0, then we'll exit because the programmer 37 | * did not fill out their paperwork. 38 | */ 39 | if ((eeprom_ptr == NULL && flash_ptr == NULL) || 40 | (eeprom_req_count == 0 && flash_req_count == 0)) { 41 | return 0; 42 | } 43 | 44 | uint16_t address = 0; 45 | uint16_t size = 0; 46 | 47 | /* Page size is 4 bytes for eeprom */ 48 | if (zone == eeprom) { 49 | /* I'm going to assume that the user is not going 50 | * to try to access memory space for a task not 51 | * represented in the data structure. 52 | */ 53 | ITERATE_ZONE_STRUCT(eeprom) { 54 | /* Iterate until we get to our task id or the end */ 55 | if (eeprom_ptr[i].task_ndx == task_id) { 56 | size = eeprom_ptr[i].desired_pages * 4; 57 | break; 58 | } 59 | /* add desired_pages * 4 to get byte width address */ 60 | address += eeprom_ptr[i].desired_pages * 4; 61 | } 62 | 63 | if (end - begin > size) { 64 | return 0; 65 | } 66 | 67 | eeprom_write((void *) address, begin, end, buffer); 68 | } 69 | 70 | 71 | // If we got to this point we were successful 72 | return 1; 73 | } 74 | 75 | /*! 76 | * \brief Read from memory into a buffer. 77 | * 78 | * \param task_id 79 | * The id of the task that "owns" the memory space to be accessed. 80 | * 81 | * \param begin 82 | * The offset into the memory space owned by the task. 83 | * 84 | * \param end 85 | * The end index of the memory owned by the task to be accessed. 86 | * 87 | * \param buffer 88 | * The buffer into which read memory should be stored. 89 | * 90 | * \ret 91 | * 0 for error and 1 for success. 92 | */ 93 | int memory_read(int task_id, 94 | enum memory_space zone, 95 | uint16_t begin, 96 | uint16_t end, 97 | unsigned char *buffer) { 98 | 99 | if (eeprom_ptr == NULL && flash_ptr == NULL || 100 | (eeprom_req_count == 0 && flash_req_count == 0)) { 101 | return 0; 102 | } 103 | 104 | uint16_t address = 0; 105 | uint16_t size = 0; 106 | 107 | if (zone == eeprom) { 108 | ITERATE_ZONE_STRUCT(eeprom) { 109 | if (eeprom_ptr[i].task_ndx == task_id) { 110 | size = eeprom_ptr[i].desired_pages * 4; 111 | break; 112 | } 113 | 114 | address += eeprom_ptr[i].desired_pages * 4; 115 | } 116 | 117 | if (end - begin > size) { 118 | return 0; 119 | } 120 | 121 | eeprom_read((void *) address, begin, end, buffer); 122 | } 123 | 124 | return 1; 125 | } 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /src/sys/': -------------------------------------------------------------------------------- 1 | #ifndef REQUEST_H 2 | #define REQUEST_H 3 | #include 4 | #include 5 | #include "tasks.h" 6 | 7 | 8 | enum active_req_status { 9 | inactive = 0, 10 | active = 1 11 | }; 12 | 13 | struct request_entry { 14 | uint32_t value; 15 | }; 16 | 17 | extern struct task tasks[]; 18 | /* 19 | * The request entry indicates a time that a given task is meant to wake up. 20 | * Each task is given a certain index into this array. 21 | */ 22 | extern struct request_entry requests []; 23 | 24 | /* 25 | * Given the amount of time it takes to shift 32-bit values, a second array of 26 | * unsigned chars is used. These hold an index into the array of requests, 27 | * at which point the wake up time is set. 28 | */ 29 | extern uint8_t request_array[]; 30 | 31 | #include "system.h" 32 | 33 | /* Add the task to the next spot in the array */ 34 | void add_req_entry(uint8_t task_ndx, uint32_t value); 35 | 36 | 37 | /* Shift struct values through the first inactive index. 38 | * This way, index 0 has the values of index 1 and so on. 39 | */ 40 | void awaken_tasks(); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/sys/housekeeper_prelude.S: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | .section .text 4 | .global housekeeper_prelude 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/sys/housekeeping.S: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | __zero_reg__ = 1 4 | __tmp_reg__ = 0 5 | 6 | .section .bss 7 | .global in_housekeeping 8 | .global task_unavailable 9 | 10 | mod_four_count: 11 | .zero 1 12 | 13 | reg_saver: 14 | .zero 1 15 | 16 | task_unavailable: 17 | .zero 1 18 | 19 | in_housekeeping: 20 | .zero 1 21 | 22 | tmp_stack_ptr: 23 | .zero 2 24 | 25 | .section .text 26 | .global TIMER2_COMPA_vect 27 | 28 | // The reason why I don't push SREG onto the stack is that the timer isr 29 | // will mostly return to the running task. In that case, shelf it and then 30 | // bring it back. As for pushing and then popping registers, the reason why 31 | // I do that is because the housekeeper can be called independent of the 32 | // timer ISR. For that reason, I don't make assumptions on what registers 33 | // have been saved in the housekeeper. 34 | TIMER2_COMPA_vect: 35 | push r31 36 | push r30 37 | push r29 38 | push r28 39 | push r27 40 | in r27,_SFR_IO_ADDR(SREG) 41 | // Use this as the zero register 42 | push r1 43 | clr r1 44 | // Load system time 45 | lds r28,system_time 46 | lds r29,system_time+1 47 | lds r30,system_time+2 48 | lds r31,system_time+3 49 | // Add one to 28,29 50 | adiw r28,1 51 | // If overflow, then add one to 26 52 | adc r30,__zero_reg__ 53 | // If overflow, then add one to 27 54 | adc r31,__zero_reg__ 55 | // Store system time 56 | sts system_time,r28 57 | sts system_time+1,r29 58 | sts system_time+2,r30 59 | sts system_time+3,r31 60 | // Check to see system time % 4 == 0 61 | lds r28,mod_four_count 62 | cpi r28,3 63 | // If not, increment and store 64 | // Otherwise, reset 65 | breq reset_mod_four_count 66 | inc r28 67 | sts mod_four_count,r28 68 | // Go through process of restoring registers and exiting 69 | cleanup: 70 | out _SFR_IO_ADDR(SREG),r27 71 | pop r1 72 | pop r27 73 | pop r28 74 | pop r29 75 | pop r30 76 | pop r31 77 | reti 78 | // Otherwise, reset counter and then set address of do_housekeeping 79 | reset_mod_four_count: 80 | sts mod_four_count,__zero_reg__ 81 | // Restore the remaining registers 82 | out _SFR_IO_ADDR(SREG),r27 83 | pop r1 84 | pop r27 85 | pop r28 86 | pop r29 87 | pop r30 88 | // Except use r31 to set return address 89 | pop r31 90 | sts reg_saver,r31 91 | ldi r31,lo8(gs(housekeeper_prelude)) 92 | push r31 93 | ldi r31,hi8(gs(housekeeper_prelude)) 94 | push r31 95 | lds r31,reg_saver 96 | reti 97 | housekeeper_prelude: 98 | push r31 99 | push r30 100 | push r29 101 | push r28 102 | in r29,_SFR_IO_ADDR(SREG) 103 | // Load current and check to see if it is house keeping's task 104 | lds r30,in_housekeeping 105 | cpi r30,0 106 | breq set_housekeeping 107 | // If house keeping's task, then return 108 | out _SFR_IO_ADDR(SREG),r29 109 | pop r28 110 | pop r29 111 | pop r30 112 | pop r31 113 | ret 114 | set_housekeeping: 115 | // Push the status register onto the stack 116 | push r29 117 | // Set return address to do_housekeeping 118 | ldi r30,lo8(gs(do_housekeeping)) 119 | ldi r31,hi8(gs(do_housekeeping)) 120 | push r30 121 | push r31 122 | ret 123 | do_housekeeping: 124 | // Clear interrupts in order to set a flag up to indicate 125 | // That we are in house keeping. Re-enable interrupts after 126 | // that. 127 | cli 128 | ori r30,0xFF 129 | sts in_housekeeping,r30 130 | sei 131 | save_context: 132 | lds r28,curr 133 | lds r29,curr+1 134 | std Y+4,r0 135 | std Y+5,r1 136 | clr r1 137 | // Store sreg 138 | pop r0 139 | std Y+6,r2 140 | std Y+7,r3 141 | std Y+8,r4 142 | std Y+9,r5 143 | std Y+10,r6 144 | std Y+11,r7 145 | std Y+12,r8 146 | std Y+13,r9 147 | std Y+14,r10 148 | std Y+15,r11 149 | std Y+16,r12 150 | std Y+17,r13 151 | std Y+18,r14 152 | std Y+19,r15 153 | std Y+20,r16 154 | std Y+21,r17 155 | std Y+22,r18 156 | std Y+23,r19 157 | std Y+24,r20 158 | std Y+25,r21 159 | std Y+26,r22 160 | std Y+27,r23 161 | std Y+28,r24 162 | std Y+29,r25 163 | std Y+30,r26 164 | std Y+31,r27 165 | // Store r28 166 | pop r24 167 | std Y+32,r24 168 | // Store r29 169 | pop r24 170 | std Y+33,r24 171 | // Store r30 172 | pop r24 173 | std Y+34,r24 174 | // Store r31 175 | pop r24 176 | std Y+35,r24 177 | // Put SREG back on stack 178 | push r0 179 | // Save the stack pointer 180 | in r0,_SFR_IO_ADDR(SPL) 181 | std Y+2,r0 182 | in r0,_SFR_IO_ADDR(SPH) 183 | std Y+3,r0 184 | // Go through and check the list to see if item can be removed. 185 | // Also go through and find new task. Repeat until task can be 186 | // found 187 | check_wait_list: 188 | call check_req_top 189 | find_task: 190 | // If get_next_task return 0, then check the request top again. 191 | // Otherwise, load task 192 | call get_next_task 193 | or r24,r25 194 | breq check_wait_list 195 | load_next_task: 196 | lds r28,curr 197 | lds r29,curr+1 198 | // Load stack pointer for new context 199 | ldd r0,Y+2 200 | out _SFR_IO_ADDR(SPL),r0 201 | ldd r0,Y+3 202 | out _SFR_IO_ADDR(SPH),r0 203 | // Restore SREG. Flags are not changed by these instructions 204 | pop r0 205 | out _SFR_IO_ADDR(SREG),r0 206 | // begin restoring GPR 207 | ldd r0,Y+4 208 | ldd r1,Y+5 209 | ldd r2,Y+6 210 | ldd r3,Y+7 211 | ldd r4,Y+8 212 | ldd r5,Y+9 213 | ldd r6,Y+10 214 | ldd r7,Y+11 215 | ldd r8,Y+12 216 | ldd r9,Y+13 217 | ldd r10,Y+14 218 | ldd r11,Y+15 219 | ldd r12,Y+16 220 | ldd r13,Y+17 221 | ldd r14,Y+18 222 | ldd r15,Y+19 223 | ldd r16,Y+20 224 | ldd r17,Y+21 225 | ldd r18,Y+22 226 | ldd r19,Y+23 227 | ldd r20,Y+24 228 | ldd r21,Y+25 229 | ldd r22,Y+26 230 | ldd r23,Y+27 231 | ldd r24,Y+28 232 | ldd r25,Y+29 233 | ldd r26,Y+30 234 | ldd r27,Y+31 235 | // Clobber Z registers so that we can restore Y 236 | ldd r30,Y+34 237 | ldd r31,Y+35 238 | push r30 239 | push r31 240 | ldd r30,Y+32 241 | ldd r31,Y+33 242 | movw r28,r30 243 | pop r31 244 | // Set flag down to indicate we are not in house keeping routine 245 | cli 246 | ldi r30,0 247 | sts in_housekeeping,r30 248 | pop r30 249 | sei 250 | ret 251 | -------------------------------------------------------------------------------- /src/sys/kernel.c: -------------------------------------------------------------------------------- 1 | #include "sys/kernel.h" 2 | #include "sys/system.h" 3 | 4 | uint8_t kernel_stack[KERNEL_STACK_SIZE] = {0}; 5 | 6 | struct task kernel_task = { 7 | .c = 8 | { 9 | .sp_start = &kernel_stack[KERNEL_STACK_SIZE - 1], 10 | .sp = &kernel_stack[0], 11 | .r0 = 0, 12 | .r1 = 0, 13 | .r2 = 0, 14 | .r3 = 0, 15 | .r4 = 0, 16 | .r5 = 0, 17 | .r6 = 0, 18 | .r7 = 0, 19 | .r8 = 0, 20 | .r9 = 0, 21 | .r10 = 0, 22 | .r11 = 0, 23 | .r12 = 0, 24 | .r13 = 0, 25 | .r14 = 0, 26 | .r15 = 0, 27 | .r16 = 0, 28 | .r17 = 0, 29 | .r18 = 0, 30 | .r19 = 0, 31 | .r20 = 0, 32 | .r21 = 0, 33 | .r22 = 0, 34 | .r23 = 0, 35 | .r24 = 0, 36 | .r25 = 0, 37 | .r26 = 0, 38 | .r27 = 0, 39 | .r28 = 0, 40 | .r29 = 0, 41 | .r30 = 0, 42 | .r31 = 0 43 | }, 44 | .state = runnable, 45 | .task_funct = kernel_task_funct, 46 | .next = &tasks[0] 47 | }; 48 | 49 | /* 50 | * Currently, the kernel task doesn't do anything. 51 | */ 52 | void kernel_task_funct() { 53 | // Awaken all sleeping_tasks that are due to wake up 54 | awaken_tasks(); 55 | // Set the sleep time for when the next task wakes up 56 | task_sleep(TASK_COUNT, requests[request_array[0]].value); 57 | } 58 | -------------------------------------------------------------------------------- /src/sys/request.c: -------------------------------------------------------------------------------- 1 | #include "sys/request.h" 2 | 3 | struct request_entry requests[]; 4 | 5 | uint8_t request_array[]; 6 | 7 | /*! 8 | * \brief Add a task to the list of sleeping tasks. 9 | * 10 | * The request data structure is effectively an ordered queue. 11 | * 12 | * \param task_ndx 13 | * The index of the task that will be sleeping. 14 | * 15 | * \param value 16 | * The length of time that the task will sleep. 17 | */ 18 | void add_req_entry(uint8_t task_ndx, uint32_t value) { 19 | uint8_t task_tmp = 0; 20 | 21 | 22 | /* Go through the array of requests and determine 23 | * where the value of one is greater than the new 24 | * value. This means that the new sleeper will 25 | * wake up before the sleeper in the array element 26 | */ 27 | for (uint8_t i = 0; i < REQUEST_MAX; ++i) { 28 | task_tmp = request_array[i]; 29 | 30 | /* If there's an empty spot, go ahead and add */ 31 | 32 | if (request_array[i] == -1) { 33 | request_array[i] = task_ndx; 34 | } 35 | /* If a later sleeper is found, then shift all of the 36 | * elements of the array to the right by 1. This will 37 | * make room for the new sleeper between the earlier 38 | * wake up time and later wake up time. 39 | */ 40 | else if (requests[task_tmp].value > value) { 41 | for (uint8_t j = REQUEST_MAX - 1; j > i; --j) { 42 | request_array[j] = request_array[j-1]; 43 | } 44 | 45 | } 46 | 47 | /* This is to avoid repeating two lines of code */ 48 | else { 49 | continue; 50 | } 51 | 52 | /* Add new sleeper */ 53 | requests[task_ndx].value = value; 54 | request_array[i] = task_ndx; 55 | break; 56 | 57 | } 58 | } 59 | 60 | /*! 61 | * \brief Determine if there are tasks to be woken. 62 | * 63 | * This function checks to first element to see if there's something to be 64 | * awoken. If a task can be awoken, then any others will also be awoken. 65 | */ 66 | void awaken_tasks() { 67 | 68 | 69 | /* Unblock all tasks need to be woken up */ 70 | 71 | while (wakers_found) { 72 | task_ndx = request_array[0]; 73 | 74 | /* If the task can be woken up, then go through and 75 | * shift all of the indices by one. If a -1 is found 76 | * before that point, then exit. 77 | * If we got to the end, but there were no -1's found, 78 | * then set the last index to -1 79 | */ 80 | if (system_time.time >= requests[task_ndx].value) { 81 | 82 | #if USE_SOFTWARE_TIMER == 1 83 | if (task_ndx == SOFTWARE_TIMER_NDX) { 84 | software_timer_task.state = runnable; 85 | } 86 | else { 87 | tasks[task_ndx].state = runnable; 88 | } 89 | 90 | #else 91 | /* Wake the task back up. Ideally, this should 92 | * be done somewhere else, but right now it's 93 | * good. 94 | */ 95 | tasks[task_ndx].state = runnable; 96 | #endif 97 | 98 | for (uint8_t i = 0; i < REQUEST_MAX - 1; ++i) { 99 | if (request_array[i] != -1) 100 | request_array[i] = request_array[i+1]; 101 | else { 102 | request_array[i-1] = -1; 103 | break; 104 | } 105 | } 106 | 107 | request_array[REQUEST_MAX-1] = -1; 108 | } 109 | 110 | else { 111 | wakers_found = 0; 112 | } 113 | 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/sys/system.c: -------------------------------------------------------------------------------- 1 | #include "sys/system.h" 2 | 3 | volatile struct sys_clock system_time = { .time = 0 }; 4 | 5 | /*! 6 | * \brief Put a task to sleep for a certain period of time. 7 | * The sleep time is added onto the system time to give a certain wake up 8 | * time. This can probably be optimized, and make more stable. 9 | * 10 | * \param task_ndx 11 | * The index of the task 12 | * 13 | * \param time 14 | * The total length of time that the task will sleep. 15 | */ 16 | void task_sleep(uint8_t task_ndx, uint32_t time) { 17 | /* Make an entry for the current task */ 18 | add_req_entry(task_ndx, system_time.time+time); 19 | 20 | curr->state = waiting; 21 | 22 | task_yield(); 23 | } 24 | -------------------------------------------------------------------------------- /src/sys/tasks.c: -------------------------------------------------------------------------------- 1 | #include "sys/tasks.h" 2 | #include "tmr/software_timer.h" 3 | #include "sys/kernel.h" 4 | 5 | /*! 6 | * \brief Initialize a task. 7 | * 8 | * This consists of setting up the argument registers for "do_task" and 9 | * then setting the task as runnable. 10 | * 11 | * \param current 12 | * The task struct currently being initialized. 13 | */ 14 | void make_task(struct task *current) { 15 | 16 | // 26 and 25 contain curr as argument for do_task 17 | uint16_t addr = (uint16_t) current; 18 | current->c.r24 = ADDR_LO(addr); 19 | current->c.r25 = ADDR_HI(addr); 20 | current->state = runnable; 21 | } 22 | 23 | /*! 24 | * \brief Set up the stack for a given task. 25 | * 26 | * The complexity is that the address of do_task needs to be added to the 27 | * stack. The reason is that the housekeeper will then pop the address of 28 | * do_task from the task's stack when the task is selected. 29 | * 30 | * \param t 31 | * The task for which the stack needs to be set up. 32 | * 33 | * \param task_num 34 | * The total number of tasks. 35 | * 36 | * \param s 37 | * The stack that the tasks will use. 38 | * 39 | * \param stack_num 40 | * The total number of stacks to be parceled. 41 | * 42 | * \ret 43 | * 0 if there are more stacks than tasks, 1 if otherwise. 44 | * 45 | */ 46 | int set_task_stacks(struct task *t, size_t task_num, 47 | struct stack *s, size_t stack_num) { 48 | // Exit error if there are more tasks than stacks 49 | if (stack_num < task_num) 50 | return 0; 51 | 52 | struct task *current = t; 53 | /* In order to simplify the lines, we're going to use these holders 54 | * In addition, a void * is two bytes. For that reason we are dereferencing 55 | * with a uint16_t. 56 | */ 57 | void *sspace = s->stack_space; 58 | size_t ssize = s->stack_size; 59 | 60 | for (size_t i = 0, ndx = 0; i < stack_num; ++i, ++ndx) { 61 | if (!current) { 62 | break; 63 | } 64 | /* Store the address of stack at index sp_start. 65 | * Keep in mind that the stacks grow downward, 66 | * so we're going to the end of the stack region 67 | * rather than the beginning. 68 | */ 69 | current->c.sp_start = sspace+(ssize*ndx)+ssize-1; 70 | current->c.sp = current->c.sp_start; 71 | 72 | /* Write the address of do_task in the stack. It will act as 73 | * the return address when the ISR is finished. 74 | * In addition, we are going to store a preliminary status 75 | * register 76 | */ 77 | uint16_t addr = (uint16_t) do_task; 78 | uint8_t lower = ADDR_LO(addr); 79 | uint8_t upper = ADDR_HI(addr); 80 | 81 | *(uint8_t *) current->c.sp-- = lower; 82 | *(uint8_t *) current->c.sp-- = upper; 83 | *(uint8_t *) current->c.sp-- = 0; 84 | current = current->next; 85 | } 86 | 87 | #if USE_SOFTWARE_TIMER == 1 88 | software_timer_task.c.r24 = ADDR_LO((uint16_t) &software_timer_task); 89 | software_timer_task.c.r25 = ADDR_HI((uint16_t) &software_timer_task); 90 | #endif 91 | 92 | kernel_task.c.r24 = ADDR_LO((uint16_t) &kernel_task); 93 | kernel_task.c.r25 = ADDR_HI((uint16_t) &kernel_task); 94 | 95 | return 1; 96 | } 97 | 98 | /*! 99 | * \brief End a task. 100 | * 101 | * This consists of setting a task state to complete. This will cause the 102 | * task to be ignored in the linked list. 103 | * 104 | * \param t 105 | * The task to be ended. 106 | */ 107 | void end_task(struct task *t) { 108 | t->state = complete; 109 | return; 110 | } 111 | 112 | /*! 113 | * \brief Run the task's callback. 114 | * 115 | * \param t 116 | * The task 117 | */ 118 | void do_task(struct task *t) { 119 | 120 | t->task_funct(); 121 | end_task(t); 122 | // Infinite loop because we're not supposed to return 123 | while(1); 124 | } 125 | 126 | 127 | /*! 128 | * \brief Find the next task to run. 129 | * 130 | * \ret 131 | * This always returns 1 if a runnable task was found, 0 otherwise. 132 | */ 133 | unsigned int get_next_task() { 134 | struct task *tmp = curr; 135 | struct task *t = curr->next; 136 | 137 | while (tmp != t) { 138 | if (t->state == runnable) { 139 | curr = t; 140 | return 1; 141 | } 142 | 143 | t = t->next; 144 | } 145 | 146 | return 0; 147 | } 148 | 149 | -------------------------------------------------------------------------------- /src/sys/watchdog.c: -------------------------------------------------------------------------------- 1 | #include "sys/watchdog.h" 2 | 3 | ISR(WDT_vect) { 4 | #if DEBUG == 1 5 | println("Watchdog reset detected.", 6 | STRLEN("Watchdog reset detected.")); 7 | #endif 8 | } 9 | 10 | int watchdog_reset_detected() { 11 | return (MCUSR & (WATCHDOG_SYSTEM_RESET) == 1); 12 | } 13 | 14 | void init_watchdog() { 15 | // Set the prescaler and set to interrupt/reset mode 16 | cli(); 17 | watchdog_reset(); 18 | WDTCSR |= WDT_CHANGE_ENABLE | WDT_RESET_ENABLE; 19 | WDTCSR |= (WDT_RESET_ENABLE | WDT_TIMEOUT_8); 20 | WDTCSR |= WDT_INTERRUPT_ENABLE; 21 | sei(); 22 | } 23 | -------------------------------------------------------------------------------- /src/tmr/software_timer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "user.h" 3 | #include "tmr/software_timer.h" 4 | #include "comm/usart.h" 5 | #include "sys/tasks.h" 6 | #include "sys/request.h" 7 | #if USE_SOFTWARE_TIMER == 1 8 | 9 | #define SOFTWARE_TIMER_STACK_SIZE 32 10 | 11 | void software_timer_task_funct(); 12 | 13 | extern struct software_timer tmr_arr[]; 14 | 15 | struct queue tmr_callback_queue = { 16 | .capacity = SOFTWARE_TIMER_COUNT, 17 | .size = 0, 18 | .head = 0, 19 | .tail = 0, 20 | .items = { [0 ... SOFTWARE_TIMER_COUNT - 1] = 0 } 21 | }; 22 | 23 | uint8_t software_timer_stack[SOFTWARE_TIMER_STACK_SIZE] = {0}; 24 | 25 | volatile struct task software_timer_task = { 26 | .c = { 27 | .sp_start = 28 | &software_timer_stack[SOFTWARE_TIMER_STACK_SIZE - 1], 29 | .sp = &software_timer_stack[0], 30 | .r0 = 0, 31 | .r1 = 0, 32 | .r2 = 0, 33 | .r3 = 0, 34 | .r4 = 0, 35 | .r5 = 0, 36 | .r6 = 0, 37 | .r7 = 0, 38 | .r8 = 0, 39 | .r9 = 0, 40 | .r10 = 0, 41 | .r11 = 0, 42 | .r12 = 0, 43 | .r13 = 0, 44 | .r14 = 0, 45 | .r15 = 0, 46 | .r16 = 0, 47 | .r17 = 0, 48 | .r18 = 0, 49 | .r19 = 0, 50 | .r20 = 0, 51 | .r21 = 0, 52 | .r22 = 0, 53 | .r23 = 0, 54 | .r24 = 0, 55 | .r25 = 0, 56 | .r26 = 0, 57 | .r27 = 0, 58 | .r28 = 0, 59 | .r29 = 0, 60 | .r30 = 0, 61 | .r31 = 0 62 | }, 63 | 64 | .next = NULL, 65 | .state = runnable, 66 | .task_funct = software_timer_task_funct 67 | }; 68 | 69 | uint32_t software_timer_lowest_value = 0xFFFFFFFF; 70 | 71 | /* 72 | * Run through each timer. If the timer has expired, add the callback to the 73 | * queue. In addition, stop the one shot timers. For the periodic timers, add 74 | * the periodic value to the counter. 75 | * 76 | * Also, find the new lowest value and then sleep for that amount of time. 77 | */ 78 | void software_timer_task_funct() { 79 | 80 | void (*callback)(); 81 | uint8_t i; 82 | uint8_t software_timer_set = 0; 83 | 84 | software_timer_lowest_value = 0xFFFFFFFF; 85 | 86 | for (i = 0; i < SOFTWARE_TIMER_COUNT; ++i) { 87 | if (tmr_arr[i].state == timer_stopped) { 88 | continue; 89 | } 90 | 91 | if (tmr_arr[i].counter <= system_time.time) { 92 | enqueue(&tmr_callback_queue, tmr_arr[i].callback); 93 | 94 | if (tmr_arr.t == sw_timer_one_shot) { 95 | software_timer.state = timer_stopped; 96 | } 97 | 98 | else { 99 | software_timer.counter += software_timer.period; 100 | } 101 | } 102 | 103 | /* 104 | * This is a guard to protect against rollover. 105 | */ 106 | if (tmr_arr[i].counter <= software_timer_lowest_value && 107 | tmr_arr[i].counter > system_timer.time) { 108 | software_timer_set = 1; 109 | software_timer_lowest_value = tmr_arr[i].counter; 110 | } 111 | 112 | } 113 | 114 | callback = dequeue(&tmr_callback_queue); 115 | 116 | while (callback != NULL) { 117 | callback(); 118 | callback = dequeue(&tmr_callback_queue); 119 | } 120 | 121 | if (software_timer_set) { 122 | add_request_entry(SOFTWARE_TIMER_TASK_NDX, 123 | software_timer_task.state = waiting; 124 | } 125 | 126 | else { 127 | software_timer_task.state = complete; 128 | } 129 | } 130 | 131 | /* 132 | * Initialize the timers by putting the stopped timers in the back and the 133 | * started timers in the front. For each started timer, set the counter to the 134 | * period. 135 | */ 136 | void init_software_timers() { 137 | uint8_t i; 138 | uint8_t software_timer_set = 0; 139 | 140 | for (i = 0; i < SOFTWARE_TIMER_COUNT; ++i) { 141 | if (tmr_arr[i].state != timer_stopped) { 142 | tmr_arr[i].counter = tmr_arr[i].period; 143 | 144 | /* 145 | * This is a guard to protect against rollover. 146 | */ 147 | if (tmr_arr[i].counter <= software_timer_lowest_value && 148 | tmr_arr[i].counter >= system_time.time) { 149 | software_timer_set = 1; 150 | software_timer_lowest_value = 151 | tmr_arr[i].counter; 152 | } 153 | } 154 | } 155 | 156 | /* 157 | * The software timer task will basically wait until the next timer is 158 | * to run, provided that there are software timers that are waiting. 159 | */ 160 | 161 | if (software_timer_set) { 162 | add_request_entry(SOFTWARE_TIMER_TASK_NDX, 163 | software_timer_lowest_value); 164 | software_timer_task.state = waiting; 165 | } 166 | 167 | else { 168 | software_timer_task.state = complete; 169 | } 170 | } 171 | 172 | /* 173 | * Run through the list of timers until one is found that is stopped. 174 | * 175 | * After that, look for the timer that is to be started. If the first stopped 176 | * timer and the timer that is to be started are the same, then simply start 177 | * the timer. Otherwise, do a swap. 178 | */ 179 | void software_timer_start(uint8_t tmr_id) { 180 | uint8_t software_timer_set = 0; 181 | 182 | tmr_arr[tmr_id].counter = tmr_arr[tmr_id].period + system_timer.time; 183 | 184 | tmr_arr[tmr_id].state = timer_started; 185 | 186 | if (tmr_arr[tmr_id].counter <= software_timer_lowest_value && 187 | tmr_arr[tmr_id].counter >= system_time.time) { 188 | 189 | software_timer_lowest_value = tmr_arr[tmr_id].counter; 190 | software_timer_set = 1; 191 | } 192 | 193 | if (software_timer_set) { 194 | add_request_entry(SOFTWARE_TIMER_TASK_NDDX, 195 | software_timer_lowest_value); 196 | 197 | software_timer_task.state = waiting; 198 | } 199 | } 200 | 201 | /* 202 | * Find the position of the timer to stop. After that, determine whether or not 203 | * we reached the end of the software timers. If we found a timer, then i 204 | * should be less than the total number of software timers. If so, then we 205 | * memmove the remaining timers, and then throw the software timer being 206 | * stopped in the back. 207 | */ 208 | void software_timer_stop(uint8_t tmr_id) { 209 | uint8_t i; 210 | uint8_t software_timer_set = 0; 211 | 212 | software_timer_lowest_value = 0xFFFFFFFF; 213 | 214 | tmr_arr[tmr_id].state = timer_stopped; 215 | 216 | /* 217 | * Find the lowest timer counter value for and set the internal lowest 218 | * timer to that value, provided that there is at least one started 219 | * timer. Remove it as a sleeping task if there are none. 220 | */ 221 | for (i = 0; i < SOFTWARE_TIMER_COUNT; ++i) { 222 | if (tmr_arr[i].state == timer_stopped) { 223 | continue; 224 | } 225 | 226 | if (tmr_arr[i].counter <= software_timer_lowest_value && 227 | tmr_arr[i].counter <= system_timer.system_time; 228 | software_timer_lowest_value = tmr_arr[i].counter; 229 | software_timer_set = 1; 230 | } 231 | } 232 | 233 | if (software_timer_set) { 234 | add_req_entry(SOFTWARE_TIMER_TASK_NDX, 235 | software_timer_lowest_value); 236 | 237 | software_timer_task.state = waiting; 238 | } 239 | 240 | else { 241 | software_timer_task.state = complete; 242 | } 243 | } 244 | 245 | #endif 246 | -------------------------------------------------------------------------------- /src/tmr/timer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "tmr/timer.h" 4 | 5 | void set_sys_timer() { 6 | 7 | cli(); 8 | TCNT2 = 0; 9 | 10 | OCR2A = 249; 11 | 12 | TCCR2A = (1 << WGM21); 13 | TCCR2B = (1 << CS22); 14 | TIMSK2 = (1 << OCIE2A); 15 | /* Clear the interrupt flags because somehow that gets set when 16 | * setting the mask register. 17 | */ 18 | TIFR2 = 0; 19 | 20 | #if DEBUG == 1 21 | println("Preparing to sei - set_sys_timer", 22 | STRLEN("Preparing to sei - set_sys_timer")); 23 | #endif 24 | sei(); 25 | 26 | #if DEBUG == 1 27 | println("sei complete - set_sys_timer", 28 | STRLEN("sei complete - set_sys_timer")); 29 | #endif 30 | 31 | } 32 | 33 | void init_timers() { 34 | #if USE_SOFTWARE_TIMER == 1 35 | init_software_timers(); 36 | set_usr_timer(); 37 | #endif 38 | 39 | #if DEBUG == 1 40 | println("Setting system timer.", 41 | STRLEN("Setting system timer.")); 42 | #endif 43 | 44 | set_sys_timer(); 45 | 46 | #if DEBUG == 1 47 | println("System timer set.", 48 | STRLEN("System timer set.")); 49 | #endif 50 | } 51 | 52 | #if USE_SOFTWARE_TIMER == 1 53 | 54 | void set_usr_timer() { 55 | 56 | cli(); 57 | 58 | TCNT0 = 0; 59 | 60 | OCR0A = 50; 61 | 62 | TCCR0A = (1 << WGM01); 63 | TCCR0B = ((1 << CS11) | (1 << CS10)); 64 | TIMSK0 = (1 << OCIE0A); 65 | TIFR0 = 0; 66 | sei(); 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/tmr/timer_isr.S: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | __zero_reg__ = 1 4 | 5 | .global TIMER2_COMPA_vect 6 | 7 | 8 | // The reason why I don't push SREG onto the stack is that the timer isr 9 | // will mostly return to the running task. In that case, shelf it and then 10 | // bring it back. As for pushing and then popping registers, the reason why 11 | // I do that is because the housekeeper can be called independent of the 12 | // timer ISR. For that reason, I don't make assumptions on what registers 13 | // have been saved in the housekeeper. 14 | TIMER2_COMPA_vect: 15 | push r31 16 | push r30 17 | push r29 18 | push r28 19 | push r27 20 | in r27,_SFR_IO_ADDR(SREG) 21 | // Use this as the zero register 22 | push r1 23 | clr r1 24 | // Load system time 25 | lds r28,system_time 26 | lds r29,system_time+1 27 | lds r30,system_time+2 28 | lds r31,system_time+3 29 | // Add one to 28,29 30 | adiw r28,1 31 | // If overflow, then add one to 26 32 | adc r30,__zero_reg__ 33 | // If overflow, then add one to 27 34 | adc r31,__zero_reg__ 35 | // Store system time 36 | sts system_time,r28 37 | sts system_time+1,r29 38 | sts system_time+2,r30 39 | sts system_time+3,r31 40 | // Check to see system time % 4 == 0 41 | lds r28,mod_four_count 42 | cpi r28,3 43 | // If not, increment and store 44 | breq reset_mod_four_count 45 | inc r28 46 | sts mod_four_count,r28 47 | // Go through process of restoring registers and exiting 48 | cleanup: 49 | out _SFR_IO_ADDR(SREG),r27 50 | pop r1 51 | pop r27 52 | pop r28 53 | pop r29 54 | pop r30 55 | pop r31 56 | reti 57 | // Otherwise, reset counter and then set address of do_housekeeping 58 | reset_mod_four_count: 59 | sts mod_four_count,__zero_reg__ 60 | // Restore the remaining registers 61 | out _SFR_IO_ADDR(SREG),r27 62 | pop r1 63 | pop r27 64 | pop r28 65 | pop r29 66 | pop r30 67 | // Except use r31 to set return address 68 | pop r31 69 | sts reg_saver,r31 70 | ldi r31,lo8(gs(housekeeper_prelude)) 71 | push r31 72 | ldi r31,hi8(gs(housekeeper_prelude)) 73 | push r31 74 | lds r31,reg_saver 75 | reti 76 | 77 | mod_four_count: 78 | .zero 1 79 | 80 | reg_saver: 81 | .zero 1 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/util/queue.c: -------------------------------------------------------------------------------- 1 | #include "util/queue.h" 2 | 3 | /* 4 | * \brief Add an item into a queue. 5 | * 6 | * \param q 7 | * The queue into which the item will be added. 8 | * 9 | * \param item 10 | * The item to be added into the queue. 11 | * 12 | * \ret 13 | * 0 for error and 1 for success. 14 | */ 15 | int enqueue(struct queue *q, void *itm) { 16 | uint8_t tail = q->tail; 17 | uint8_t size = q->size; 18 | uint8_t capacity = q->capacity; 19 | 20 | if (capacity <= size) 21 | { 22 | return 0; 23 | } 24 | 25 | q->items[tail] = itm; 26 | q->size++; 27 | q->tail = ++(q->tail) % capacity; 28 | 29 | return 1; 30 | } 31 | 32 | /* 33 | * \brief Remove an item from a queue. 34 | * 35 | * \param q 36 | * The queue from which the item will be removed. 37 | * 38 | * \ret 39 | * NULL if the queue is empty, or the item (which is a pointer). 40 | */ 41 | void *dequeue(struct queue *q) { 42 | uint8_t size = q->size; 43 | void *ret; 44 | 45 | if (size == 0) { 46 | return NULL; 47 | } 48 | 49 | ret = q->items[q->head]; 50 | q->head = ++q->head % q->capacity; 51 | q->size--; 52 | 53 | return ret; 54 | } 55 | 56 | int queue_is_empty(struct queue *q) { 57 | return (q->size == 0); 58 | } 59 | 60 | int queue_is_full(struct queue *q) { 61 | return (q->capacity <= q->size); 62 | } 63 | -------------------------------------------------------------------------------- /usr/include/memory_request.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_REQUEST_H 2 | #define MEMORY_REQUEST_H 3 | #include 4 | #include 5 | 6 | #if USE_EEPROM_MEMORY == 1 7 | 8 | struct memory_request { 9 | uint8_t task_ndx; 10 | uint8_t memory_space; 11 | uint16_t desired_pages; 12 | }; 13 | 14 | extern struct memory_request *eeprom_ptr; 15 | extern struct memory_request *flash_ptr; 16 | extern uint8_t eeprom_req_count; 17 | extern uint8_t flash_req_count; 18 | 19 | #endif 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /usr/include/user.h: -------------------------------------------------------------------------------- 1 | #ifndef USER_H 2 | #define USER_H 3 | #include 4 | #include 5 | #include 6 | #include "sys/tasks.h" 7 | #include "sys/stack.h" 8 | #include "sys/request.h" 9 | #include "tmr/software_timer.h" 10 | #include "comm/usart.h" 11 | #include "sys/watchdog.h" 12 | 13 | // These are macros that relate to serial communicate, if desired 14 | // Remove this macro in order to remove the code associated with usart 15 | // Define CPU frequency for UBR calculation 16 | #ifndef F_OSC 17 | #define F_OSC 16UL 18 | #endif 19 | 20 | /* The purpose of this is to define in the data and bss section a series 21 | * of variables that have currently been stored in stack. This will allow 22 | * the user more flexibility (although will require better understanding of 23 | * the kernel 24 | * 25 | * The data structures are declared here and then defined in the data structure 26 | */ 27 | // This concerns the total number of tasks 28 | #define TASK_COUNT 2 29 | // This concerns the total number of software timers 30 | 31 | // The size of a given task's stack 32 | #define STACK_SIZE 64 33 | // Total number of requests allowed. 34 | // Currently, this must be equal to the number of tasks 35 | #define REQUEST_MAX TASK_COUNT 36 | 37 | #if USE_SOFTWARE_TIMER == 1 38 | #define SOFTWARE_TIMER_COUNT 2 39 | #endif 40 | 41 | extern struct task tasks[TASK_COUNT]; 42 | 43 | #if USE_SOFTWARE_TIMER == 1 44 | extern struct software_timer tmr_arr[]; 45 | #endif 46 | 47 | extern struct stack s; 48 | 49 | extern struct request_entry requests[REQUEST_MAX]; 50 | 51 | extern int request_array[REQUEST_MAX]; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /usr/src/memory_request.c: -------------------------------------------------------------------------------- 1 | #include "memory_request.h" 2 | 3 | #if USE_EEPROM_MEMORY == 1 4 | 5 | #define EEPROM 1 6 | 7 | struct memory_request mem_reqs[4] = { { 8 | .task_ndx = 0, 9 | /* For some reason, entering 10 | * 'eeprom' is leading to an error 11 | */ 12 | .memory_space = EEPROM, 13 | .desired_pages = 2 14 | }, 15 | 16 | { 17 | .task_ndx = 1, 18 | .memory_space = EEPROM, 19 | .desired_pages = 2 20 | }, 21 | 22 | { 23 | .task_ndx = 2, 24 | .memory_space = EEPROM, 25 | .desired_pages = 3 26 | }, 27 | 28 | { 29 | .task_ndx = 3, 30 | .memory_space = EEPROM, 31 | .desired_pages = 2 32 | } 33 | }; 34 | 35 | struct memory_request *eeprom_ptr = &mem_reqs[0]; 36 | struct memory_request *flash_ptr = NULL; 37 | 38 | uint8_t eeprom_req_count = 4; 39 | uint8_t flash_req_count = 0; 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /usr/src/user.c: -------------------------------------------------------------------------------- 1 | #include "user.h" 2 | 3 | // User must define task funct array here 4 | 5 | #define NOP() asm("nop\n\t") 6 | 7 | void main_task() { 8 | 9 | DDRB |= (1 << 0); 10 | DDRB |= (1 << 1); 11 | DDRB |= (1 << 2); 12 | 13 | PORTB |= (1 << 2); 14 | 15 | task_sleep(0, 1000); 16 | 17 | software_timer_start(&tmr_arr[1]); 18 | 19 | while (1) { 20 | 21 | PORTB &= ~(1 << 2); 22 | 23 | task_sleep(0,250); 24 | 25 | PORTB |= (1 << 2); 26 | 27 | task_sleep(0,250); 28 | } 29 | }; 30 | 31 | void second_task() { 32 | while (1) { 33 | task_sleep(1, 250); 34 | } 35 | } 36 | 37 | void third_task() { 38 | while (1) { 39 | task_sleep(2, 500); 40 | } 41 | } 42 | 43 | struct task tasks[TASK_COUNT] = { 44 | [0] = { 45 | .c = {0}, 46 | .next = &tasks[1], 47 | .state = runnable, 48 | .task_funct = main_task 49 | }, 50 | 51 | [1] = { 52 | .c = {0}, 53 | .next = &tasks[0], 54 | .state = runnable, 55 | .task_funct = second_task 56 | },/* 57 | 58 | [2] = { 59 | .c = {0}, 60 | .next = &tasks[0], 61 | .state = runnable, 62 | .task_funct = third_task 63 | }*/ 64 | }; 65 | 66 | uint8_t stack_space[TASK_COUNT*STACK_SIZE] = {0}; 67 | 68 | struct stack s = { 69 | .stack_space = &stack_space[0], 70 | .stack_num = TASK_COUNT, 71 | .stack_size = STACK_SIZE 72 | }; 73 | 74 | struct request_entry requests[REQUEST_MAX] = {0}; 75 | 76 | int request_array[REQUEST_MAX] = { [0 ... REQUEST_MAX-1] = -1}; 77 | 78 | #if USE_SOFTWARE_TIMER == 1 79 | 80 | enum led_state { 81 | LED_ON, 82 | LED_OFF 83 | }; 84 | 85 | uint8_t led1_state = LED_OFF; 86 | 87 | uint8_t led2_state = LED_ON; 88 | 89 | void switch_led1() { 90 | if (led1_state == LED_OFF) { 91 | PORTB |= (1 << 0); 92 | led1_state = LED_ON; 93 | } 94 | 95 | else { 96 | PORTB &= ~(1 << 0); 97 | led1_state = LED_OFF; 98 | } 99 | } 100 | 101 | void switch_led2() { 102 | if (led2_state == LED_OFF) { 103 | PORTB |= (1 << 1); 104 | led2_state = LED_ON; 105 | } 106 | 107 | else { 108 | PORTB &= ~(1 << 1); 109 | led2_state = LED_OFF; 110 | } 111 | } 112 | 113 | struct software_timer tmr_arr[SOFTWARE_TIMER_COUNT] = { 114 | 115 | [0] = { 116 | .id = 0, 117 | .period = 500, 118 | .counter = 0, 119 | .state = timer_started, 120 | .t = sw_timer_periodic, 121 | .callback = switch_led1 122 | }, 123 | 124 | [1] = { 125 | .id = 1, 126 | .period = 500, 127 | .counter = 0, 128 | .state = timer_stopped, 129 | .t = sw_timer_periodic, 130 | .callback = switch_led2 131 | } 132 | }; 133 | 134 | #endif 135 | 136 | 137 | --------------------------------------------------------------------------------