]> git.droids-corp.org - red-alert.git/commitdiff
add embedded code
authorOlivier Matz <zer0@droids-corp.org>
Wed, 28 Aug 2019 21:51:31 +0000 (23:51 +0200)
committerOlivier Matz <zer0@droids-corp.org>
Wed, 28 Aug 2019 21:51:31 +0000 (23:51 +0200)
43 files changed:
Makefile [new file with mode: 0644]
mk/ar-rules.mk [new file with mode: 0644]
mk/ar-vars.mk [new file with mode: 0644]
mk/arch-avr.mk [new file with mode: 0644]
mk/clean-rules.mk [new file with mode: 0644]
mk/clean-vars.mk [new file with mode: 0644]
mk/copy-rules.mk [new file with mode: 0644]
mk/copy-vars.mk [new file with mode: 0644]
mk/exe-rules.mk [new file with mode: 0644]
mk/exe-vars.mk [new file with mode: 0644]
mk/obj-rules.mk [new file with mode: 0644]
mk/obj-vars.mk [new file with mode: 0644]
mk/objcopy-rules.mk [new file with mode: 0644]
mk/objcopy-vars.mk [new file with mode: 0644]
mk/post.mk [new file with mode: 0644]
mk/pre.mk [new file with mode: 0644]
mk/shlib-rules.mk [new file with mode: 0644]
mk/shlib-vars.mk [new file with mode: 0644]
mk/slink-rules.mk [new file with mode: 0644]
mk/slink-vars.mk [new file with mode: 0644]
mk/subdir-rules.mk [new file with mode: 0644]
mk/subdir-vars.mk [new file with mode: 0644]
mk/tools.mk [new file with mode: 0644]
mk/vars.mk [new file with mode: 0644]
src/buzzer.c [new file with mode: 0644]
src/buzzer.h [new file with mode: 0644]
src/callout.c [new file with mode: 0644]
src/callout.h [new file with mode: 0644]
src/cirbuf.c [new file with mode: 0644]
src/cirbuf.h [new file with mode: 0644]
src/commands.c [new file with mode: 0644]
src/commands.h [new file with mode: 0644]
src/errno.h [new file with mode: 0644]
src/irq.h [new file with mode: 0644]
src/led.c [new file with mode: 0644]
src/led.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/main.h [new file with mode: 0644]
src/queue.h [new file with mode: 0644]
src/rtc.c [new file with mode: 0644]
src/rtc.h [new file with mode: 0644]
src/uart.c [new file with mode: 0644]
src/uart.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..d37d4f5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,84 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+#
+
+ROOTDIR := $(abspath .)
+ARCH := avr
+CROSS := avr-
+
+include $(ROOTDIR)/mk/pre.mk
+
+MCU = atmega328p
+F_CPU = 8000000UL
+
+#AVRDUDE_OPTS = -P /dev/ttyUSB0 -c arduino -b 57600
+#AVRDUDE_OPTS = -P usb -c dragon_isp -B 100 -v -v -v -v -i 100
+#AVRDUDE_OPTS = -P usb -c avrispmkii -B 100 -v -v -v -v -i 100
+#AVRDUDE_OPTS = -P usb -c avrispmkii
+AVRDUDE_OPTS = -P usb -c usbasp-clone -B 100 -i 100
+
+O ?= $(CURDIR)/build
+PROG = $(O)/red-alert
+
+CFLAGS := -g -Werror -Isrc
+CFLAGS += -Wall -W
+CFLAGS += -Os
+CFLAGS += -ffunction-sections
+CFLAGS += -DF_CPU=$(F_CPU)
+CFLAGS += -mmcu=$(MCU)
+
+LDFLAGS := -Wl,--gc-sections
+LDFLAGS += -mmcu=$(MCU)
+LDFLAGS += -lm
+
+exe-y-$(PROG) += src/cirbuf.c
+exe-y-$(PROG) += src/uart.c
+exe-y-$(PROG) += src/callout.c
+exe-y-$(PROG) += src/led.c
+exe-y-$(PROG) += src/rtc.c
+exe-y-$(PROG) += src/commands.c
+exe-y-$(PROG) += src/buzzer.c
+exe-y-$(PROG) += src/main.c
+
+ldflags-$(PROG) += -Wl,-Map=$(PROG).map,--cref
+
+objcopy-hex-y-$(PROG).hex := $(PROG)
+objcopy-bin-y-$(PROG).bin := $(PROG)
+
+BOOTLOADER = $(O)/bootloader
+exe-y-$(BOOTLOADER) += bootloader/bootloader.c
+
+# 0x7800 bytes == 0x3c00 words -> BOOTSZ1=0, BOOTSZ0=1
+ldflags-$(BOOTLOADER) += -Wl,--section-start=.text=7800
+ldflags-$(BOOTLOADER) += -Wl,-Map=$(BOOTLOADER).map,--cref
+
+objcopy-hex-y-$(BOOTLOADER).hex := $(BOOTLOADER)
+objcopy-bin-y-$(BOOTLOADER).bin := $(BOOTLOADER)
+
+
+.PHONY: all
+all:
+       $(CROSS)size $(PROG)
+       $(CROSS)size $(BOOTLOADER)
+
+include $(ROOTDIR)/mk/post.mk
+
+all: $(all-targets)
+
+.PHONY: clean
+clean: __clean
+
+.PHONY: program
+program: all
+#      avrdude -e -p $(MCU) $(AVRDUDE_OPTS) -U flash:w:$(PROG):e
+       avrdude -e -p $(MCU) $(AVRDUDE_OPTS) -U flash:w:$(BOOTLOADER):e
+
+.PHONY: reset
+reset:
+       avrdude -p $(MCU) $(AVRDUDE_OPTS)
+
+# defaults + CKDIV8=1, BOOTSZ0=1, BOOTRST=0
+.PHONY: fuse
+fuse:
+       avrdude -p $(MCU) $(AVRDUDE_OPTS) -U lfuse:w:0xE2:m -U hfuse:w:0xDA:m -U efuse:w:0xFF:m
diff --git a/mk/ar-rules.mk b/mk/ar-rules.mk
new file mode 100644 (file)
index 0000000..4ad30cb
--- /dev/null
@@ -0,0 +1,55 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# dump some infos if debug is enabled
+ifeq ($(D),1)
+$(call disp_list,------ all-ar,$(all-ar))
+$(foreach ar,$(all-ar),\
+       $(info,out-$(ar): $(out-$(ar))) \
+       $(call disp_list,pre-$(ar),$(pre-$(ar))) \
+)
+endif
+
+# include dependencies and commands files if they exist
+$(foreach ar,$(all-ar),\
+       $(eval -include $(call depfile,$(ar))) \
+       $(eval -include $(call cmdfile,$(ar))) \
+)
+
+# remove duplicates
+filtered-all-ar := $(sort $(all-ar))
+
+# link several objects files into one shared object
+$(filtered-all-ar): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call ar_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call ar_cmd,$(pre-$(@)),$@),$?),\
+               $(call ar_print_cmd,$(pre-$(@)),$@) && \
+               $(call ar_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call ar_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
diff --git a/mk/ar-vars.mk b/mk/ar-vars.mk
new file mode 100644 (file)
index 0000000..7d2fab7
--- /dev/null
@@ -0,0 +1,75 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# ar-y-$(ar) is provided by the user
+#   $(ar) is the path of the static library, and the variable contains
+#   the list of sources. Several ar-y-$(ar) can be present.
+
+# list all ar builds requested by user
+all-ar := $(patsubst ar-y-%,%,$(filter ar-y-%,$(.VARIABLES)))
+
+# add them to the list of targets
+all-targets += $(all-ar)
+
+# for each ar, create the following variables:
+#   out-$(ar) = output path of the arcutable
+#   pre-$(ar) = list of prerequisites for this arcutable
+# Some source files need intermediate objects, we define these variables
+# for them too, and add them in a list: $(all-iobj).
+# Last, we add the generated files in $(all-clean-file).
+$(foreach ar,$(all-ar),\
+       $(eval out-$(ar) := $(dir $(ar))) \
+       $(eval pre-$(ar) := ) \
+       $(foreach src,$(ar-y-$(ar)), \
+               $(if $(call is_cc_source,$(src)), \
+                       $(eval iobj := $(call src2iobj,$(src),$(out-$(ar)))) \
+                       $(eval pre-$(iobj) := $(src)) \
+                       $(eval all-iobj += $(iobj)) \
+                       $(eval all-clean-file += $(iobj)) \
+                       $(eval pre-$(ar) += $(iobj)) \
+               , \
+               $(if $(call is_obj_source,$(src)),\
+                       $(eval pre-$(ar) += $(src)) \
+               , \
+               $(error "unsupported source format: $(src)"))) \
+       )\
+       $(eval all-clean-file += $(ar)) \
+)
+
+# link several *.o files into a static libary
+#   $1: sources (*.o)
+#   $2: dst (xyz.a)
+ar_cmd = ar crsD $(2) $(1)
+
+# print line used to ar object files
+ifeq ($(V),1)
+ar_print_cmd = echo $(call protect_quote,$(call ar_cmd,$1,$2))
+else
+ar_print_cmd = echo "  AR $(2)"
+endif
+
+all-clean-file += $(all-ar)
diff --git a/mk/arch-avr.mk b/mk/arch-avr.mk
new file mode 100644 (file)
index 0000000..3c04e58
--- /dev/null
@@ -0,0 +1,34 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+ARCH_CFLAGS += -Wall -W
+ARCH_CFLAGS += -Os
+ARCH_CFLAGS += -ffunction-sections
+
+ARCH_LDFLAGS += -Wl,--gc-sections
+
+ARCH_CROSS := avr-
diff --git a/mk/clean-rules.mk b/mk/clean-rules.mk
new file mode 100644 (file)
index 0000000..5e6bd49
--- /dev/null
@@ -0,0 +1,33 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+.PHONY: __clean
+__clean: $(all-clean-target) FORCE
+       @$(call clean_print_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \
+               $(call cmdfile,$(all-clean-file))) && \
+       $(call clean_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \
+               $(call cmdfile,$(all-clean-file)))
diff --git a/mk/clean-vars.mk b/mk/clean-vars.mk
new file mode 100644 (file)
index 0000000..53f732e
--- /dev/null
@@ -0,0 +1,37 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# remove files
+#  $1: files
+clean_cmd = rm -rf $(1)
+
+# print line used to clean files
+ifeq ($(V),1)
+clean_print_cmd = echo $(call protect_quote,$(call clean_cmd,$1))
+else
+clean_print_cmd = echo "  CLEAN $(CURDIR)"
+endif
diff --git a/mk/copy-rules.mk b/mk/copy-rules.mk
new file mode 100644 (file)
index 0000000..6602a9e
--- /dev/null
@@ -0,0 +1,55 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# dump some infos if debug is enabled
+ifeq ($(D),1)
+$(call disp_list,------ all-copy,$(all-copy))
+$(foreach copy,$(all-copy),\
+       $(info,out-$(copy): $(out-$(copy))) \
+       $(call disp_list,pre-$(copy),$(pre-$(copy))) \
+)
+endif
+
+# include dependencies and commands files if they exist
+$(foreach copy,$(all-copy),\
+       $(eval -include $(call depfile,$(copy))) \
+       $(eval -include $(call cmdfile,$(copy))) \
+)
+
+# remove duplicates
+filtered-all-copy := $(sort $(all-copy))
+
+# convert format of executable
+$(filtered-all-copy): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call copy_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call copy_cmd,$(pre-$(@)),$@),$?),\
+               $(call copy_print_cmd,$(pre-$(@)),$@) && \
+               $(call copy_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call copy_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
diff --git a/mk/copy-vars.mk b/mk/copy-vars.mk
new file mode 100644 (file)
index 0000000..677274e
--- /dev/null
@@ -0,0 +1,74 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# copy a file
+# copy-y-$(copy) is provided by the user
+#   $(copy) is the path of the directory containing the destination
+#   files, and the variable contains the path of the files to copy. Several
+#   copy-y-$(copy) can be present.
+
+# list all path requested by user
+_all-copy := $(patsubst copy-y-%,%,$(filter copy-y-%,$(.VARIABLES)))
+all-copy :=
+
+# for each copy, create the following variables:
+#   out-$(copy) = output path of the executable
+#   pre-$(copy) = list of prerequisites for this executable
+# We also add the files in $(all-copy).
+$(foreach copy,$(_all-copy),\
+       $(if $(notdir $(copy)), \
+               $(if $(call compare,$(words $(copy-y-$(copy))),1), \
+                       $(error "only one source file is allowed in copy-y-$(copy)")) \
+               $(eval dst := $(dir $(copy))$(notdir $(copy-y-$(copy)))) \
+               $(eval out-$(copy) := $(dir $(copy))) \
+               $(eval pre-$(copy) := $(copy-y-$(copy))) \
+               $(eval all-copy += $(dst)) \
+       , \
+               $(foreach src,$(copy-y-$(copy)),\
+                       $(eval dst := $(copy)$(notdir $(src))) \
+                       $(eval out-$(copy) := $(copy)) \
+                       $(eval pre-$(dst) := $(src)) \
+                       $(eval all-copy += $(dst)) \
+               ) \
+       ) \
+)
+
+# add them to the list of targets and clean
+all-targets += $(all-copy)
+all-clean-file += $(all-copy)
+
+# convert format of executable from elf to ihex
+#   $1: source executable (elf)
+#   $2: destination file
+copy_cmd = $(CP) $(1) $(2)
+
+# print line used to convert executable format
+ifeq ($(V),1)
+copy_print_cmd = echo $(call protect_quote,$(call copy_cmd,$1,$2))
+else
+copy_print_cmd = echo "  COPY $(2)"
+endif
diff --git a/mk/exe-rules.mk b/mk/exe-rules.mk
new file mode 100644 (file)
index 0000000..a1a5c42
--- /dev/null
@@ -0,0 +1,55 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# dump some infos if debug is enabled
+ifeq ($(D),1)
+$(call disp_list,------ all-exe,$(all-exe))
+$(foreach exe,$(all-exe),\
+       $(info,out-$(exe): $(out-$(exe))) \
+       $(call disp_list,pre-$(exe),$(pre-$(exe))) \
+)
+endif
+
+# include dependencies and commands files if they exist
+$(foreach exe,$(all-exe),\
+       $(eval -include $(call depfile,$(exe))) \
+       $(eval -include $(call cmdfile,$(exe))) \
+)
+
+# remove duplicates
+filtered-all-exe := $(sort $(all-exe))
+
+# link several objects files into one executable
+$(filtered-all-exe): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call link_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call link_cmd,$(pre-$(@)),$@),$?),\
+               $(call link_print_cmd,$(pre-$(@)),$@) && \
+               $(call link_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call link_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
diff --git a/mk/exe-vars.mk b/mk/exe-vars.mk
new file mode 100644 (file)
index 0000000..f980be4
--- /dev/null
@@ -0,0 +1,79 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# exe-y-$(exe) is provided by the user
+#   $(exe) is the path of the binary, and the variable contains
+#   the list of sources. Several exe-y-$(exe) can be present.
+
+# list all exe builds requested by user
+all-exe := $(patsubst exe-y-%,%,$(filter exe-y-%,$(.VARIABLES)))
+
+# add them to the list of targets
+all-targets += $(all-exe)
+
+# for each exe, create the following variables:
+#   out-$(exe) = output path of the executable
+#   pre-$(exe) = list of prerequisites for this executable
+# Some source files need intermediate objects, we define these variables
+# for them too, and add them in a list: $(all-iobj).
+# Last, we add the generated files in $(all-clean-file).
+$(foreach exe,$(all-exe),\
+       $(eval out-$(exe) := $(dir $(exe))) \
+       $(eval pre-$(exe) := ) \
+       $(foreach src,$(exe-y-$(exe)), \
+               $(if $(call is_cc_source,$(src)), \
+                       $(eval iobj := $(call src2iobj,$(src),$(out-$(exe)))) \
+                       $(eval pre-$(iobj) := $(src)) \
+                       $(eval all-iobj += $(iobj)) \
+                       $(eval all-clean-file += $(iobj)) \
+                       $(eval pre-$(exe) += $(iobj)) \
+               , \
+               $(if $(call is_obj_source,$(src)),\
+                       $(eval pre-$(exe) += $(src)) \
+               , \
+               $(if $(call is_alib_source,$(src)),\
+                       $(eval pre-$(exe) += $(src)) \
+               , \
+               $(error "unsupported source format: $(src)")))) \
+       )\
+       $(eval all-clean-file += $(exe)) \
+)
+
+# link several *.o files into a exeary
+#   $1: sources (*.o) (*.a)
+#   $2: dst (xyz.o too)
+link_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -o $(2) $(filter %.o,$(1)) \
+        $(filter %.a,$(1)) $(LDLIBS) $(ldlibs-$(2))
+
+# print line used to link object files
+ifeq ($(V),1)
+link_print_cmd = echo $(call protect_quote,$(call link_cmd,$1,$2))
+else
+link_print_cmd = echo "  EXE $(2)"
+endif
+
+all-clean-file += $(all-exe)
diff --git a/mk/obj-rules.mk b/mk/obj-rules.mk
new file mode 100644 (file)
index 0000000..af208e4
--- /dev/null
@@ -0,0 +1,83 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# dump some infos if debug is enabled
+ifeq ($(D),1)
+$(call disp_list,------ all-obj,$(all-obj))
+$(foreach obj,$(all-obj),\
+       $(info,out-$(obj): $(out-$(obj))) \
+       $(call disp_list,pre-$(obj),$(pre-$(obj))) \
+)
+$(call disp_list,------ all-iobj,$(all-iobj))
+$(foreach iobj,$(all-iobj),\
+       $(call disp_list,pre-$(iobj),$(pre-$(iobj))) \
+)
+endif
+
+# if a generated file has the same name than a user target,
+# generate an error
+conflicts := $(filter $(all-iobj),$(all-targets))
+$(if $(conflicts), \
+       $(error Intermediate file has the same names than user targets:\
+               $(conflicts)))
+
+# include dependencies and commands files if they exist
+$(foreach obj,$(all-obj),\
+       $(eval -include $(call depfile,$(obj))) \
+       $(eval -include $(call cmdfile,$(obj))) \
+)
+$(foreach iobj,$(all-iobj),\
+       $(eval -include $(call depfile,$(iobj))) \
+       $(eval -include $(call cmdfile,$(iobj))) \
+)
+
+# remove duplicates
+filtered-all-iobj := $(sort $(all-iobj))
+
+# convert source files to intermediate object file
+$(filtered-all-iobj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,$(call compile_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call compile_cmd,$(pre-$(@)),$@),$?),\
+               $(call compile_print_cmd,$(pre-$(@)),$@) && \
+               $(call compile_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call compile_cmd,$(pre-$(@)),$@),$@) && \
+               $(call obj-fixdep,$@))
+
+# remove duplicates
+filtered-all-obj := $(sort $(all-obj))
+
+# combine several objects files to one
+$(filtered-all-obj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call combine_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call combine_cmd,$(pre-$(@)),$@),$?),\
+               $(call combine_print_cmd,$(pre-$(@)),$@) && \
+               $(call combine_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call combine_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
diff --git a/mk/obj-vars.mk b/mk/obj-vars.mk
new file mode 100644 (file)
index 0000000..8bd9101
--- /dev/null
@@ -0,0 +1,124 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# obj-y-$(obj) is provided by the user
+#  $(obj) is the path of the object, and the variable contains
+#  the list of sources. Several obj-y-$(obj) can be present.
+
+# list all object builds requested by user
+all-obj := $(patsubst obj-y-%,%,$(filter obj-y-%,$(.VARIABLES)))
+
+# add them to the list of targets
+all-targets += $(all-obj)
+
+# convert source path to intermediate object path, and filter
+# objects from sources
+#  $1: list of source paths
+#  $2: output directory (including trailing slash)
+#  return: list of intermediate object paths
+src2iobj = $(addprefix $(filter-out ./,$(2)),$(notdir $(strip \
+       $(patsubst %.c,%.o,\
+       $(patsubst %.s,%.o,\
+       $(filter-out %.o,$(1)))))))
+
+# return the file if it matches a extension that is built with cc
+#   $1: source file
+is_cc_source = $(filter %.c %.s %S,$(1))
+
+# return the file if it's already an object file: in this case no
+# intermediate object is needed
+#   $1: source file
+is_obj_source = $(filter %.o,$(1))
+
+# return the file if it's a static library
+#   $1: source file
+is_alib_source = $(filter %.a,$(1))
+
+# for each obj, create the following variables:
+#   out-$(obj) = output path of the object
+#   pre-$(obj) = list of prerequisites for this object
+# Some source files need intermediate objects, we define these variables
+# for them too, and add them in a list: $(all-iobj).
+# Last, we add the generated files in $(all-clean-file).
+$(foreach obj,$(all-obj),\
+       $(eval out-$(obj) := $(dir $(obj))) \
+       $(eval pre-$(obj) := ) \
+       $(foreach src,$(obj-y-$(obj)), \
+               $(if $(call is_cc_source,$(src)), \
+                       $(eval iobj := $(call src2iobj,$(src),$(out-$(obj)))) \
+                       $(eval pre-$(iobj) := $(src)) \
+                       $(eval all-iobj += $(iobj)) \
+                       $(eval all-clean-file += $(iobj)) \
+                       $(eval pre-$(obj) += $(iobj)) \
+               , \
+               $(if $(call is_obj_source,$(src)),\
+                       $(eval pre-$(obj) += $(src)) \
+               , \
+               $(error "unsupported source format: $(src)"))) \
+       )\
+       $(eval all-clean-file += $(obj)) \
+)
+
+# fix the format of .o.d.tmp (generated by gcc) to a .o.d that defines
+# dependencies as makefile variables
+#  $1: object file (.o)
+obj-fixdep = if [ -f $(call file2tmpdep,$(1)) ]; then\
+               echo -n "dep-$(1) = " > $(call depfile,$(1)) && \
+               sed 's,^[^ ][^:]*: ,,' $(call file2tmpdep,$(1)) >> $(call depfile,$(1)) && \
+               rm -f $(call file2tmpdep,$(1)); \
+       else \
+               $(call create_empty_depfile,$(1)); \
+       fi
+
+# compile a file
+#  $1: sources
+#  $2: dst
+compile_cmd = $(CC) -Wp,-MD,$(call file2tmpdep,$(2)) \
+       $(CPPFLAGS) $(cppflags-$(2)) \
+       $(CFLAGS) $(cflags-$(2)) \
+       -c -o $2 $1
+
+# print line used to compile a file
+ifeq ($(V),1)
+compile_print_cmd = echo $(call protect_quote,$(call compile_cmd,$1,$2))
+else
+compile_print_cmd = echo "  CC $(2)"
+endif
+
+# combine several *.o files into one
+#   $1: sources (*.o)
+#   $2: dst (xyz.o too)
+combine_cmd = $(LD) -r $(1) -o $(2)
+
+# print line used to combine object files
+ifeq ($(V),1)
+combine_print_cmd = echo $(call protect_quote,$(call combine_cmd,$1,$2))
+else
+combine_print_cmd = echo "  LD $(2)"
+endif
+
+all-clean-file += $(all-obj)
diff --git a/mk/objcopy-rules.mk b/mk/objcopy-rules.mk
new file mode 100644 (file)
index 0000000..86a7cb6
--- /dev/null
@@ -0,0 +1,74 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# dump some infos if debug is enabled
+ifeq ($(D),1)
+$(call disp_list,------ all-objcopy-hex,$(all-objcopy-hex))
+$(foreach objcopy,$(all-objcopy-hex),\
+       $(info,out-$(objcopy): $(out-$(objcopy))) \
+       $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \
+)
+$(call disp_list,------ all-objcopy-bin,$(all-objcopy-bin))
+$(foreach objcopy,$(all-objcopy-bin),\
+       $(info,out-$(objcopy): $(out-$(objcopy))) \
+       $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \
+)
+endif
+
+# include dependencies and commands files if they exist
+$(foreach objcopy,$(all-objcopy-hex) $(all-objcopy-bin),\
+       $(eval -include $(call depfile,$(objcopy))) \
+       $(eval -include $(call cmdfile,$(objcopy))) \
+)
+
+# remove duplicates
+filtered-all-objcopy-hex := $(sort $(all-objcopy-hex))
+
+# convert format of executable
+$(filtered-all-objcopy-hex): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call objcopy_hex_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$?),\
+               $(call objcopy_print_cmd,$(pre-$(@)),$@) && \
+               $(call objcopy_hex_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
+
+# remove duplicates
+filtered-all-objcopy-bin := $(sort $(all-objcopy-bin))
+
+# convert format of executable
+$(filtered-all-objcopy-bin): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call objcopy_bin_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$?),\
+               $(call objcopy_print_cmd,$(pre-$(@)),$@) && \
+               $(call objcopy_bin_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
diff --git a/mk/objcopy-vars.mk b/mk/objcopy-vars.mk
new file mode 100644 (file)
index 0000000..d515d02
--- /dev/null
@@ -0,0 +1,89 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# objcopy changes the format of a binary
+# objcopy-hex-y-$(objcopy), objcopy-bin-y-$(objcopy) are provided by the user
+#   $(objcopy) is the path of the binary, and the variable contains
+#   the path to the elf. Several objcopy-y-$(objcopy) can be present.
+
+# list all executable builds requested by user
+all-objcopy-hex := $(patsubst objcopy-hex-y-%,%,$(filter objcopy-hex-y-%,$(.VARIABLES)))
+all-objcopy-bin := $(patsubst objcopy-bin-y-%,%,$(filter objcopy-bin-y-%,$(.VARIABLES)))
+
+# add them to the list of targets
+all-targets += $(all-objcopy-hex) $(all-objcopy-bin)
+
+# for each objcopy, create the following variables:
+#   out-$(objcopy) = output path of the executable
+#   pre-$(objcopy) = list of prerequisites for this executable
+# We also add the generated files in $(all-clean-file).
+$(foreach objcopy,$(all-objcopy-hex),\
+       $(if $(call compare,$(words $(objcopy-hex-y-$(objcopy))),1),\
+               $(error "only one source file is allowed in objcopy-hex-y-$(objcopy)")) \
+       $(eval out-$(objcopy) := $(dir $(objcopy))) \
+       $(eval pre-$(objcopy) := $(objcopy-hex-y-$(objcopy))) \
+       $(eval all-clean-file += $(objcopy)) \
+)
+
+# for each objcopy, create the following variables:
+#   out-$(objcopy) = output path of the executable
+#   pre-$(objcopy) = list of prerequisites for this executable
+# We also add the generated files in $(all-clean-file).
+$(foreach objcopy,$(all-objcopy-bin),\
+       $(if $(call compare,$(words $(objcopy-bin-y-$(objcopy))),1),\
+               $(error "only one source file is allowed in objcopy-bin-y-$(objcopy)")) \
+       $(eval out-$(objcopy) := $(dir $(objcopy))) \
+       $(eval pre-$(objcopy) := $(objcopy-bin-y-$(objcopy))) \
+       $(eval all-clean-file += $(objcopy)) \
+)
+
+# convert format of executable from elf to ihex
+#   $1: source executable (elf)
+#   $2: destination file
+objcopy_hex_cmd = $(OBJCOPY) -O ihex $(1) $(2)
+
+# print line used to convert executable format
+ifeq ($(V),1)
+objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_hex_cmd,$1,$2))
+else
+objcopy_print_cmd = echo "  OBJCOPY $(2)"
+endif
+
+# convert format of executable from elf to binary
+#   $1: source executable (elf)
+#   $2: destination file
+objcopy_bin_cmd = $(OBJCOPY) -O binary $(1) $(2)
+
+# print line used to convert executable format
+ifeq ($(V),1)
+objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_bin_cmd,$1,$2))
+else
+objcopy_print_cmd = echo "  OBJCOPY $(2)"
+endif
+
+# XXX dup ?
+all-clean-file += $(all-objcopy-hex) $(all-objcopy-bin)
diff --git a/mk/post.mk b/mk/post.mk
new file mode 100644 (file)
index 0000000..2707162
--- /dev/null
@@ -0,0 +1,108 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# ---- variables that must be defined:
+#
+#  ROOTDIR: path to project root directory
+#
+# ---- variable that can be defined anywhere
+#
+#  CROSS: prefix of the toolchain
+#  CP, LN, GAWK, GREP: coreutils tools
+#  CC, CPP, AR, LD, OBJCOPY, OBJDUMP, STRIP: compilers/binutils
+#
+# ---- variable that can be defined by Makefile:
+#
+#  obj-y-$(path)
+#  exe-y-$(path)
+#  ar-y-$(path)
+#  shlib-y-$(path)
+#  copy-y-$(path)
+#  slink-y-$(path)
+#  objcopy-y-$(path)
+#  subdir-y
+#
+#  CPPFLAGS, CFLAGS, LDFLAGS, LDLIBS: global flags
+#  cflags-$(path), cppflags-$(path), ldflags-$(path), ldlibs-$(path): per
+#    file flags
+#  mkflags-$(path): flags for subdirectories
+#
+# ---- variables that can be defined on the command line:
+#
+#  EXTRA_CPPFLAGS, EXTRA_CFLAGS, EXTRA_LDFLAGS, EXTRA_LDLIBS: global
+#    extra flags
+#  extra-cflags-$(path), extra-cppflags-$(path): per object extra flags
+
+ifeq ($(ROOTDIR),)
+$(error ROOTDIR environment variable is not defined)
+endif
+
+# list of targets asked by user
+all-targets :=
+# list of files generated
+all-clean-file :=
+
+# usual internal variables:
+#   out-$(file) = output path of a generated file
+#   pre-$(file) = list of files needed to generate $(file)
+#   all-type = list of targets for this type
+
+include $(ROOTDIR)/mk/obj-vars.mk
+include $(ROOTDIR)/mk/exe-vars.mk
+include $(ROOTDIR)/mk/ar-vars.mk
+include $(ROOTDIR)/mk/shlib-vars.mk
+include $(ROOTDIR)/mk/copy-vars.mk
+include $(ROOTDIR)/mk/slink-vars.mk
+include $(ROOTDIR)/mk/objcopy-vars.mk
+include $(ROOTDIR)/mk/subdir-vars.mk
+# must stay at the end
+include $(ROOTDIR)/mk/clean-vars.mk
+
+# dump the list of targets
+ifeq ($(D),1)
+$(call disp_list,------ all-targets,$(all-targets))
+endif
+
+# first rule (default)
+.PHONY: __all
+__all: $(all-targets)
+
+# the includes below require second expansion
+.SECONDEXPANSION:
+
+include $(ROOTDIR)/mk/obj-rules.mk
+include $(ROOTDIR)/mk/exe-rules.mk
+include $(ROOTDIR)/mk/ar-rules.mk
+include $(ROOTDIR)/mk/shlib-rules.mk
+include $(ROOTDIR)/mk/copy-rules.mk
+include $(ROOTDIR)/mk/slink-rules.mk
+include $(ROOTDIR)/mk/objcopy-rules.mk
+include $(ROOTDIR)/mk/subdir-rules.mk
+include $(ROOTDIR)/mk/clean-rules.mk
+
+.PHONY: FORCE
+FORCE:
diff --git a/mk/pre.mk b/mk/pre.mk
new file mode 100644 (file)
index 0000000..8280cc8
--- /dev/null
+++ b/mk/pre.mk
@@ -0,0 +1,48 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# ---- variables that must be defined:
+#
+#  ROOTDIR: path to project root dir
+#  ARCH: architecture (ex: avr, stm32, ...)
+#
+
+ifeq ($(ROOTDIR),)
+$(error ROOTDIR environment variable is not defined)
+endif
+
+ifeq ($(ARCH),)
+$(error ARCH environment variable is not defined)
+endif
+
+MAKEFLAGS += --no-print-directory
+
+include $(ROOTDIR)/mk/tools.mk
+
+include $(ROOTDIR)/mk/arch-$(ARCH).mk
+
+include $(ROOTDIR)/mk/vars.mk
diff --git a/mk/shlib-rules.mk b/mk/shlib-rules.mk
new file mode 100644 (file)
index 0000000..1a131cc
--- /dev/null
@@ -0,0 +1,55 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# dump some infos if debug is enabled
+ifeq ($(D),1)
+$(call disp_list,------ all-shlib,$(all-shlib))
+$(foreach shlib,$(all-shlib),\
+       $(info,out-$(shlib): $(out-$(shlib))) \
+       $(call disp_list,pre-$(shlib),$(pre-$(shlib))) \
+)
+endif
+
+# include dependencies and commands files if they exist
+$(foreach shlib,$(all-shlib),\
+       $(eval -include $(call depfile,$(shlib))) \
+       $(eval -include $(call cmdfile,$(shlib))) \
+)
+
+# remove duplicates
+filtered-all-shlib := $(sort $(all-shlib))
+
+# link several objects files into one shared object
+$(filtered-all-shlib): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call shlib_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call shlib_cmd,$(pre-$(@)),$@),$?),\
+               $(call shlib_print_cmd,$(pre-$(@)),$@) && \
+               $(call shlib_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call shlib_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
diff --git a/mk/shlib-vars.mk b/mk/shlib-vars.mk
new file mode 100644 (file)
index 0000000..7906510
--- /dev/null
@@ -0,0 +1,76 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# shlib-y-$(shlib) is provided by the user
+#   $(shlib) is the path of the shared library, and the variable
+#   contains the list of sources. Several shlib-y-$(shlib) can be
+#   present.
+
+# list all shlib builds requested by user
+all-shlib := $(patsubst shlib-y-%,%,$(filter shlib-y-%,$(.VARIABLES)))
+
+# add them to the list of targets
+all-targets += $(all-shlib)
+
+# for each shlib, create the following variables:
+#   out-$(shlib) = output path of the shlibcutable
+#   pre-$(shlib) = list of prerequisites for this shlibcutable
+# Some source files need intermediate objects, we define these variables
+# for them too, and add them in a list: $(all-iobj).
+# Last, we add the generated files in $(all-clean-file).
+$(foreach shlib,$(all-shlib),\
+       $(eval out-$(shlib) := $(dir $(shlib))) \
+       $(eval pre-$(shlib) := ) \
+       $(foreach src,$(shlib-y-$(shlib)), \
+               $(if $(call is_cc_source,$(src)), \
+                       $(eval iobj := $(call src2iobj,$(src),$(out-$(shlib)))) \
+                       $(eval pre-$(iobj) := $(src)) \
+                       $(eval all-iobj += $(iobj)) \
+                       $(eval all-clean-file += $(iobj)) \
+                       $(eval pre-$(shlib) += $(iobj)) \
+               , \
+               $(if $(call is_obj_source,$(src)),\
+                       $(eval pre-$(shlib) += $(src)) \
+               , \
+               $(error "unsupported source format: $(src)"))) \
+       )\
+       $(eval all-clean-file += $(shlib)) \
+)
+
+# link several *.o files into a shared libary
+#   $1: sources (*.o)
+#   $2: dst (xyz.so)
+shlib_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -shared -o $(2) $(1)
+
+# print line used to shlib object files
+ifeq ($(V),1)
+shlib_print_cmd = echo $(call protect_quote,$(call shlib_cmd,$1,$2))
+else
+shlib_print_cmd = echo "  SHLIB $(2)"
+endif
+
+all-clean-file += $(all-shlib)
diff --git a/mk/slink-rules.mk b/mk/slink-rules.mk
new file mode 100644 (file)
index 0000000..bea3aad
--- /dev/null
@@ -0,0 +1,55 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# dump some infos if debug is enabled
+ifeq ($(D),1)
+$(call disp_list,------ all-slink,$(all-slink))
+$(foreach slink,$(all-slink),\
+       $(info,out-$(slink): $(out-$(slink))) \
+       $(call disp_list,pre-$(slink),$(pre-$(slink))) \
+)
+endif
+
+# include dependencies and commands files if they exist
+$(foreach slink,$(all-slink),\
+       $(eval -include $(call depfile,$(slink))) \
+       $(eval -include $(call cmdfile,$(slink))) \
+)
+
+# remove duplicates
+filtered-all-slink := $(sort $(all-slink))
+
+# convert format of executable
+$(filtered-all-slink): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
+       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
+       @$(call display_deps,$(pre-$(@)),$@,\
+               $(call slink_cmd,$(pre-$(@)),$@),$?)
+       @$(if $(call check_deps,$@,$(call slink_cmd,$(pre-$(@)),$@),$?),\
+               $(call slink_print_cmd,$(pre-$(@)),$@) && \
+               $(call slink_cmd,$(pre-$(@)),$@) && \
+               $(call save_cmd,$(call slink_cmd,$(pre-$(@)),$@),$@) && \
+               $(call create_empty_depfile,$@))
diff --git a/mk/slink-vars.mk b/mk/slink-vars.mk
new file mode 100644 (file)
index 0000000..428cb25
--- /dev/null
@@ -0,0 +1,74 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# create a symbolic link of a file
+# slink-y-$(slink) is provided by the user
+#   $(slink) is the path of the directory containing the destination
+#   files, and the variable contains the path of the files to linked. Several
+#   slink-y-$(slink) can be present.
+
+# list all path requested by user
+_all-slink := $(patsubst slink-y-%,%,$(filter slink-y-%,$(.VARIABLES)))
+all-slink :=
+
+# for each slink, create the following variables:
+#   out-$(slink) = output path of the executable
+#   pre-$(slink) = list of prerequisites for this executable
+# We also add the files in $(all-slink).
+$(foreach slink,$(_all-slink),\
+       $(if $(notdir $(slink)), \
+               $(if $(call compare,$(words $(slink-y-$(slink))),1), \
+                       $(error "only one source file is allowed in slink-y-$(slink)")) \
+               $(eval dst := $(dir $(slink))$(notdir $(slink-y-$(slink)))) \
+               $(eval out-$(slink) := $(dir $(slink))) \
+               $(eval pre-$(slink) := $(slink-y-$(slink))) \
+               $(eval all-slink += $(dst)) \
+       , \
+               $(foreach src,$(slink-y-$(slink)),\
+                       $(eval dst := $(slink)$(notdir $(src))) \
+                       $(eval out-$(slink) := $(slink)) \
+                       $(eval pre-$(dst) := $(src)) \
+                       $(eval all-slink += $(dst)) \
+               ) \
+       ) \
+)
+
+# add them to the list of targets and clean
+all-targets += $(all-slink)
+all-clean-file += $(all-slink)
+
+# convert format of executable from elf to ihex
+#   $1: source executable (elf)
+#   $2: destination file
+slink_cmd = $(LN) -nsf $(abspath $(1)) $(2)
+
+# print line used to convert executable format
+ifeq ($(V),1)
+slink_print_cmd = echo $(call protect_quote,$(call slink_cmd,$1,$2))
+else
+slink_print_cmd = echo "  SLINK $(2)"
+endif
diff --git a/mk/subdir-rules.mk b/mk/subdir-rules.mk
new file mode 100644 (file)
index 0000000..aae82db
--- /dev/null
@@ -0,0 +1,30 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+.PHONY: $(subdir-y)
+$(subdir-y): FORCE
+       $(Q)$(MAKE) -C $(@) $(mkflags-$(@)) $(MAKECMDGOALS)
diff --git a/mk/subdir-vars.mk b/mk/subdir-vars.mk
new file mode 100644 (file)
index 0000000..14e4ee1
--- /dev/null
@@ -0,0 +1,33 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# subdir-y is provided by the user
+#  it contains the list of directory to build
+
+# add them to the list of targets
+all-targets += $(subdir-y)
+all-clean-target += $(subdir-y)
diff --git a/mk/tools.mk b/mk/tools.mk
new file mode 100644 (file)
index 0000000..484262b
--- /dev/null
@@ -0,0 +1,159 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+empty:=
+space:= $(empty) $(empty)
+indent:= $(space)$(space)
+
+# define a newline char, useful for debugging with $(info)
+define newline
+
+
+endef
+
+# $(prefix shell commands with $(Q) to silent them, except if V=1
+Q=@
+ifeq ("$(V)-$(origin V)", "1-command line")
+Q=
+endif
+
+# set variable $1 to $2 if the variable has an implicit value or
+# is not defined
+#  $1 variable name
+#  $2 new variable content
+set_default = $(if \
+       $(call not,$(or \
+               $(compare $(origin $(1)),default), \
+               $(compare $(origin $(1)),undefined) \
+       )),\
+       $(eval $(1) = $(2)) \
+)
+
+# display a list
+#  $1 title
+#  $2 list
+disp_list = $(info $(1)$(newline)\
+       $(addsuffix $(newline),$(addprefix $(space),$(2))))
+
+# add a dot in front of the file name
+#  $1 list of paths
+#  return: full paths with files prefixed by a dot
+dotfile = $(strip $(foreach f,$(1),\
+       $(join $(dir $f),.$(notdir $f))))
+
+# convert source/obj files into dot-dep filename
+#  $1 list of paths
+#  return: full paths with files prefixed by a dot and suffixed with .d
+depfile = $(strip $(call dotfile,$(addsuffix .d,$(1))))
+
+# convert source/obj files into dot-dep filename
+#  $1 list of paths
+#  return: full paths with files prefixed by a dot and suffixed with .d.tmp
+file2tmpdep = $(strip $(call dotfile,$(addsuffix .d.tmp,$(1))))
+
+# convert source/obj files into dot-cmd filename
+#  $1 list of paths
+#  return: full paths with files prefixed by a dot and suffixed with .cmd
+cmdfile = $(strip $(call dotfile,$(addsuffix .cmd,$(1))))
+
+# add a \ before each quote
+protect_quote = $(subst ','\'',$(1))
+#'# editor syntax highlight fix
+
+# return an non-empty string if $1 is empty, and vice versa
+#  $1 a string
+not = $(if $1,,true)
+
+# return 1 if parameter is a non-empty string, else 0
+boolean = $(if $1,1,0)
+
+# return an empty string if string are equal
+compare = $(strip $(subst $(1),,$(2)) $(subst $(2),,$(1)))
+
+# return a non-empty string if a file does not exist
+#  $1: file
+file_missing = $(call compare,$(wildcard $1),$1)
+
+# return a non-empty string if cmdline changed
+#  $1: file to be built
+#  $2: the command to build it
+cmdline_changed = $(call compare,$(strip $(cmd-$(1))),$(strip $(2)))
+
+# return an non-empty string if the .d file does not exist
+#  $1: the dep file (.d)
+depfile_missing = $(call compare,$(wildcard $(1)),$(1))
+
+# return a non-empty string if, according to dep-xyz variable, a file
+# needed to build $1 does not exist. In this case we need to rebuild
+# the file and the .d file.
+#  $1: file to be built
+dep-missing = $(call compare,$(wildcard $(dep-$(1))),$(dep-$(1)))
+
+# return an empty string if no prereq is newer than target
+#  $1: list of prerequisites newer than target ($?)
+dep-newer = $(strip $(filter-out FORCE,$(1)))
+
+# display why a file should be re-built
+#  $1: source files
+#  $2: dst file
+#  $3: build command
+#  $4: all prerequisites newer than target ($?)
+ifeq ($(D),1)
+display_deps = \
+       echo -n "$1 -> $2 " ; \
+       echo -n "file_missing=$(call boolean,$(call file_missing,$(2))) " ; \
+       echo -n "cmdline_changed=$(call boolean,$(call cmdline_changed,$(2),$(3))) " ; \
+       echo -n "depfile_missing=$(call boolean,$(call depfile_missing,$(call depfile,$(2)))) " ; \
+       echo -n "dep-missing=$(call boolean,$(call dep-missing,$(2))) " ; \
+       echo "dep-newer=$(call boolean,$(call dep-newer,$(4)))"
+else
+display_deps=
+endif
+
+# return an empty string if a file should be rebuilt
+#  $1: dst file
+#  $2: build command
+#  $3: all prerequisites newer than target ($?)
+check_deps = \
+       $(or $(call file_missing,$(1)),\
+       $(call cmdline_changed,$(1),$(2)),\
+       $(call depfile_missing,$(call depfile,$(1))),\
+       $(call dep-missing,$(1)),\
+       $(call dep-newer,$(3)))
+
+# create a depfile (.d) with no additional deps
+#  $1: object file (.o)
+create_empty_depfile = echo "dep-$(1) =" > $(call depfile,$(1))
+
+# save a command in a file
+#  $1: command to build the file
+#  $2: name of the file
+save_cmd = echo "cmd-$(2) = $(call protect_quote,$(1))" > $(call cmdfile,$(2))
+
+# remove the FORCE target from the list of all prerequisites $+
+#  no arguments, use $+
+prereq = $(filter-out FORCE,$(+))
diff --git a/mk/vars.mk b/mk/vars.mk
new file mode 100644 (file)
index 0000000..c07764e
--- /dev/null
@@ -0,0 +1,49 @@
+#
+# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the University of California, Berkeley nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+CROSS ?= $(ARCH_CROSS)
+
+# core tools
+CP ?= cp
+LN ?= ln
+GAWK ?= gawk
+GREP ?= grep
+# compiler and binutils, set_default overrides mk implicit value
+# but not command line or standard variables
+$(call set_default,CC,$(CROSS)gcc)
+$(call set_default,CPP,$(CROSS)cpp)
+$(call set_default,AR,$(CROSS)ar)
+$(call set_default,LD,$(CROSS)ld)
+$(call set_default,OBJCOPY,$(CROSS)objcopy)
+$(call set_default,OBJDUMP,$(CROSS)objdump)
+$(call set_default,STRIP,$(CROSS)strip)
+HOSTCC ?= cc
+
+CFLAGS += $(EXTRA_CFLAGS)
+CPPFLAGS += $(EXTRA_CPPFLAGS)
+LDFLAGS += $(EXTRA_LDFLAGS)
+LDLIBS += $(EXTRA_LDLIBS)
diff --git a/src/buzzer.c b/src/buzzer.c
new file mode 100644 (file)
index 0000000..1844e85
--- /dev/null
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <avr/io.h>
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "callout.h"
+#include "main.h"
+#include "buzzer.h"
+
+#define BUZZER_RESOLUTION_MS 8
+
+struct buzzer_state {
+       uint16_t ms; /* current time in ms */
+       uint16_t duration_ms;
+
+       uint16_t vibrato_ms; /* time in ms, modulo vibrato period */
+       uint16_t vibrato_period_ms;
+       uint8_t type; /* saw, sin, cos, ... */
+
+       float p1; /* start tone period */
+       float k;  /* coef to scale to end tone period */
+};
+
+static struct buzzer_state buzzer_state;
+static struct callout buzzer_timer;
+
+/* reminder: period_us is approximately between 100 and 10000.
+ * period_us -> freq
+ * 100 us    ->  10000Hz
+ * 1000 us   ->  1000Hz
+ * 10000 us  ->  100Hz
+ */
+static void buzzer_set(uint16_t period_us)
+{
+       BUILD_BUG_ON(F_CPU != 8000000UL);
+       TCCR1B |= (1 << CS10); /* count at F_CPU */
+
+       /* duty cycle is 50%, but we could control it in the future */
+       OCR1B = period_us * 2;
+       OCR1A = period_us * 4;
+}
+
+void buzzer_stop(void)
+{
+       /* just stop timer that controls the PWM */
+       TCCR1B &= ~(1 << CS10);
+}
+
+static void buzzer_cb(struct callout_mgr *cm,
+               struct callout *tim, void *arg)
+{
+       struct buzzer_state *st = &buzzer_state;
+       float t, x, period_us;
+
+       (void)arg;
+
+       st->ms += BUZZER_RESOLUTION_MS;
+       if (st->duration_ms != 0 && st->ms >= st->duration_ms) {
+               buzzer_stop();
+               return;
+       }
+
+       if (st->type == VIBRATO_TYPE_NONE)
+               goto reschedule;
+
+       st->vibrato_ms += BUZZER_RESOLUTION_MS;
+       if (st->vibrato_ms >= st->vibrato_period_ms)
+               st->vibrato_ms -= st->vibrato_period_ms;
+
+       /* t is the time, normalized to be between 0 and 1 */
+       t = (float)st->vibrato_ms / (float)st->vibrato_period_ms;
+
+       /* x determines the normalized vibrato level, between 0 and 1 */
+       switch (st->type) {
+       case VIBRATO_TYPE_SIN:
+               x = (sin(2 * M_PI * t) + 1) / 2.;
+               break;
+       case VIBRATO_TYPE_COS:
+               x = (-cos(2 * M_PI * t) + 1) / 2.;
+               break;
+       case VIBRATO_TYPE_TRIANGLE:
+               if (t < 0.5)
+                       x = t * 2;
+               else
+                       x = 2 - (t * 2);
+               break;
+       case VIBRATO_TYPE_SQUARE:
+               if (t < 0.5)
+                       x = 0;
+               else
+                       x = 1;
+               break;
+       case VIBRATO_TYPE_SAW:
+       default:
+               x = t;
+               break;
+       }
+
+       /* scale x between period1 and period2 */
+       period_us = st->p1 * exp(st->k * x);
+       buzzer_set(period_us);
+
+reschedule:
+       callout_reschedule(cm, tim, BUZZER_RESOLUTION_MS);
+}
+
+void buzzer_init(void)
+{
+       DDRB |= (1 << 2); /* buzzer pin as output */
+
+       /* Fast PWM mode: OCR1A for TOP, OCR1B for half period */
+       TCCR1A = (1 << WGM11) | (1 << WGM10) | (1 << COM1B1);
+       TCCR1B = (1 << WGM13) | (1 << WGM12);
+
+       callout_init(&buzzer_timer, buzzer_cb, NULL, 128);
+}
+
+void buzzer(uint16_t period_us, uint16_t duration_ms)
+{
+       callout_stop(&callout_mgr, &buzzer_timer);
+       buzzer_stop();
+
+       if (period_us == 0)
+               return;
+
+       memset(&buzzer_state, 0, sizeof(buzzer_state));
+       buzzer_state.type = VIBRATO_TYPE_NONE;
+       buzzer_state.duration_ms = duration_ms;
+       buzzer_set(period_us);
+
+       callout_schedule(&callout_mgr, &buzzer_timer, 0);
+}
+
+void buzzer_vibrato(uint16_t period1_us, uint16_t period2_us,
+               uint16_t duration_ms, uint16_t vibrato_period_ms,
+               uint8_t vibrato_type)
+{
+       callout_stop(&callout_mgr, &buzzer_timer);
+       buzzer_stop();
+
+       if (vibrato_period_ms == 0 || period1_us == 0 || period2_us == 0)
+               return;
+
+       memset(&buzzer_state, 0, sizeof(buzzer_state));
+       buzzer_state.duration_ms = duration_ms;
+       buzzer_state.vibrato_period_ms = vibrato_period_ms;
+       buzzer_state.type = vibrato_type;
+       buzzer_state.p1 = (float)period1_us;
+       buzzer_state.k = log((float)period2_us / (float)period1_us);
+
+       callout_schedule(&callout_mgr, &buzzer_timer, 0);
+}
diff --git a/src/buzzer.h b/src/buzzer.h
new file mode 100644 (file)
index 0000000..cf1b52a
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef BUZZER_H_
+#define BUZZER_H_
+
+/* initialize timer1 for buzzer */
+void buzzer_init(void);
+
+/* stop buzzer */
+void buzzer_stop(void);
+
+/* buzz with specified period during specified time
+ * period_us is between 100 and 10000, duration_ms max is 32767 */
+void buzzer(uint16_t period_us, uint16_t duration_ms);
+
+#define VIBRATO_TYPE_NONE     0
+#define VIBRATO_TYPE_SAW      1
+#define VIBRATO_TYPE_TRIANGLE 2
+#define VIBRATO_TYPE_SIN      3
+#define VIBRATO_TYPE_COS      4
+#define VIBRATO_TYPE_SQUARE   5
+
+/* buzz with a vibrato effect */
+void buzzer_vibrato(uint16_t period1_us, uint16_t period2_us,
+               uint16_t duration_ms, uint16_t vibrato_period_ms,
+               uint8_t vibrato_type);
+
+#endif
diff --git a/src/callout.c b/src/callout.c
new file mode 100644 (file)
index 0000000..9f56fbe
--- /dev/null
@@ -0,0 +1,359 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2014-2019, Olivier MATZ <zer0@droids-corp.org>
+ * Copyright 2010 Intel Corporation
+ * (Inspired by Intel DPDK rte_timer library)
+ */
+
+#include <avr/pgmspace.h>
+
+#include <queue.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <irq.h>
+#include <callout.h>
+
+/* allow to browse a list while modifying the current element */
+#define _LIST_FOREACH_SAFE(cur, next, head, field)                     \
+       for ((cur) = LIST_FIRST((head)),                                \
+                    (next) = ((cur) ? LIST_NEXT((cur), field) : NULL); \
+            (cur);                                                     \
+            (cur) = (next),                                            \
+                    (next) = ((cur) ? LIST_NEXT((cur), field) : NULL))
+
+#ifdef CALLOUT_STATS
+/* called with irq locked */
+#define CALLOUT_STAT_ADD(cm, field, x) do {    \
+       cm->stats.field += x;                   \
+       } while(0)
+#else
+#define CALLOUT_STAT_ADD(cm, field, x) do { } while(0)
+#endif
+
+#ifdef CALLOUT_DEBUG
+#define callout_dprintf(fmt, ...) \
+       printf_P(PSTR("%s(): " fmt), __FUNCTION__, __VA_ARGS__)
+#else
+#define callout_dprintf(...) do { } while (0)
+#endif
+
+/* Initialize a callout manager */
+void
+callout_mgr_init(struct callout_mgr *cm,
+       callout_get_time_t *get_time)
+{
+       memset(cm, 0, sizeof(*cm));
+       cm->get_time = get_time;
+       LIST_INIT(&cm->sched_list);
+}
+
+/* Initialize the timer handle tim for use */
+void
+callout_init(struct callout *tim, callout_cb_t f, void *arg,
+       uint8_t priority)
+{
+       memset(tim, 0, sizeof(*tim));
+       tim->f = f;
+       tim->arg = arg;
+       tim->priority = priority;
+}
+
+/*
+ * Add a timer in the scheduled list (timer must not already be in a list). The
+ * timers are sorted in the list according the expire time (the closer timers
+ * first).
+ *
+ * called with irq locked
+ */
+static void
+callout_add_in_sched_list(struct callout_mgr *cm, struct callout *tim)
+{
+       struct callout *t, *prev_t;
+
+       callout_dprintf("cm=%p tim=%p\r\n", cm, tim);
+       tim->state = CALLOUT_STATE_SCHEDULED;
+
+       /* list is empty */
+       if (LIST_EMPTY(&cm->sched_list)) {
+               LIST_INSERT_HEAD(&cm->sched_list, tim, next);
+               return;
+       }
+
+       /* 'tim' expires before first entry */
+       t = LIST_FIRST(&cm->sched_list);
+       if ((int16_t)(tim->expire - t->expire) <= 0) {
+               LIST_INSERT_HEAD(&cm->sched_list, tim, next);
+               return;
+       }
+
+       /* find an element that will expire after 'tim' */
+       LIST_FOREACH(t, &cm->sched_list, next) {
+               if ((int16_t)(tim->expire - t->expire) <= 0) {
+                       LIST_INSERT_BEFORE(t, tim, next);
+                       return;
+               }
+               prev_t = t;
+       }
+
+       /* not found, insert at the end of the list */
+       LIST_INSERT_AFTER(prev_t, tim, next);
+}
+
+/*
+ * Add a timer in the local expired list (timer must not already be in a
+ * list). The timers are sorted in the list according to the priority (high
+ * priority first).
+ *
+ * called with irq locked
+ */
+static void
+callout_add_in_expired_list(struct callout_mgr *cm,
+       struct callout_list *expired_list, struct callout *tim)
+{
+       struct callout *t, *prev_t;
+
+       (void)cm; /* avoid warning if debug is disabled */
+
+       callout_dprintf("cm=%p tim=%p\r\n", cm, tim);
+       tim->state = CALLOUT_STATE_EXPIRED;
+
+       /* list is empty */
+       if (LIST_EMPTY(expired_list)) {
+               LIST_INSERT_HEAD(expired_list, tim, next);
+               return;
+       }
+
+       /* 'tim' has a higher prio */
+       t = LIST_FIRST(expired_list);
+       if (tim->priority >= t->priority) {
+               LIST_INSERT_HEAD(expired_list, tim, next);
+               return;
+       }
+
+       /* find an element that will expire after 'tim' */
+       LIST_FOREACH(t, expired_list, next) {
+               if (tim->priority >= t->priority) {
+                       LIST_INSERT_BEFORE(t, tim, next);
+                       return;
+               }
+               prev_t = t;
+       }
+
+       /* not found, insert at the end of the list */
+       LIST_INSERT_AFTER(prev_t, tim, next);
+}
+
+/*
+ * del from list (timer must be in a list)
+ */
+static void
+callout_del(struct callout_mgr *cm, struct callout *tim)
+{
+       (void)cm; /* avoid warning if debug is disabled */
+       callout_dprintf("cm=%p tim=%p\r\n", cm, tim);
+       LIST_REMOVE(tim, next);
+}
+
+/* Reset and start the timer associated with the timer handle tim */
+static int
+__callout_schedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t expire)
+{
+       irqflags_t flags;
+
+       callout_dprintf("cm=%p tim=%p expire=%d\r\n",
+                         cm, tim, expire);
+
+       flags = irq_lock_save();
+       CALLOUT_STAT_ADD(cm, schedule, 1);
+
+       /* remove it from list */
+       if (tim->state != CALLOUT_STATE_STOPPED) {
+               /* stats */
+               if (tim->state == CALLOUT_STATE_SCHEDULED)
+                       CALLOUT_STAT_ADD(cm, cur_scheduled, -1);
+               else if (tim->state == CALLOUT_STATE_EXPIRED)
+                       CALLOUT_STAT_ADD(cm, cur_expired, -1);
+               if (tim->state == CALLOUT_STATE_RUNNING)
+                       CALLOUT_STAT_ADD(cm, cur_running, -1);
+
+               callout_del(cm, tim);
+       }
+
+       tim->expire = expire;
+       CALLOUT_STAT_ADD(cm, cur_scheduled, 1);
+       callout_add_in_sched_list(cm, tim);
+       irq_unlock_restore(flags);
+
+       return 0;
+}
+
+/* Reset and start the timer associated with the timer handle tim */
+int
+callout_schedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t ticks)
+{
+       return __callout_schedule(cm, tim, cm->get_time() + ticks);
+}
+
+/* Reset and start the timer associated with the timer handle tim */
+int
+callout_reschedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t ticks)
+{
+       return __callout_schedule(cm, tim, tim->expire + ticks);
+}
+
+/* Stop the timer associated with the timer handle tim */
+void
+callout_stop(struct callout_mgr *cm, struct callout *tim)
+{
+       irqflags_t flags;
+
+       callout_dprintf("cm=%p tim=%p\r\n", cm, tim);
+
+       flags = irq_lock_save();
+       if (tim->state != CALLOUT_STATE_STOPPED) {
+
+               /* stats */
+               if (tim->state == CALLOUT_STATE_SCHEDULED)
+                       CALLOUT_STAT_ADD(cm, cur_scheduled, -1);
+               else if (tim->state == CALLOUT_STATE_EXPIRED)
+                       CALLOUT_STAT_ADD(cm, cur_expired, -1);
+               if (tim->state == CALLOUT_STATE_RUNNING)
+                       CALLOUT_STAT_ADD(cm, cur_running, -1);
+               CALLOUT_STAT_ADD(cm, stop, 1);
+
+               /* remove it from list */
+               callout_del(cm, tim);
+               tim->state = CALLOUT_STATE_STOPPED;
+       }
+       irq_unlock_restore(flags);
+}
+
+/* must be called periodically, run all timer that expired */
+void callout_manage(struct callout_mgr *cm)
+{
+       struct callout_list expired_list;
+       struct callout_list reschedule_list;
+       struct callout *tim, *tim_next;
+       uint16_t cur_time;
+       uint8_t old_prio;
+       int16_t diff;
+
+       CALLOUT_STAT_ADD(cm, manage, 1);
+       callout_dprintf("cm=%p\r\n", cm);
+
+       /* maximize the number of self-recursions */
+       if (cm->nb_recursion >= CALLOUT_MAX_RECURSION) {
+               CALLOUT_STAT_ADD(cm, max_recursion, 1);
+               return;
+       }
+
+       irq_lock();
+       cm->nb_recursion++;
+       LIST_INIT(&expired_list);
+       LIST_INIT(&reschedule_list);
+       cur_time = cm->get_time();
+       old_prio = cm->cur_priority;
+
+       /* move all expired timers in a local list */
+       _LIST_FOREACH_SAFE(tim, tim_next, &cm->sched_list, next) {
+
+               diff = cur_time - tim->expire;
+
+               /* check the expiration time (tasks are sorted) */
+               if (diff < 0)
+                       break;
+
+               callout_dprintf("cm=%p diff=%d\r\n", cm, diff);
+
+               /* check the priority, if it's too low, inc stats */
+               if (tim->priority <= cm->cur_priority) {
+                       if (diff < 16484)
+                               CALLOUT_STAT_ADD(cm, delayed, 1);
+                       else {
+                               /* reschedule to avoid an overflow */
+                               CALLOUT_STAT_ADD(cm, hard_delayed, 1);
+                               LIST_REMOVE(tim, next);
+                               tim->expire = cur_time;
+                               LIST_INSERT_HEAD(&reschedule_list, tim, next);
+                       }
+                       continue;
+               }
+
+               LIST_REMOVE(tim, next);
+               callout_add_in_expired_list(cm, &expired_list, tim);
+               CALLOUT_STAT_ADD(cm, cur_scheduled, -1);
+               CALLOUT_STAT_ADD(cm, cur_expired, 1);
+       }
+
+       /* reschedule hard_delayed timers, this does not happen usually */
+       while (!LIST_EMPTY(&reschedule_list)) {
+               tim = LIST_FIRST(&reschedule_list);
+               LIST_REMOVE(tim, next);
+               callout_add_in_sched_list(cm, tim);
+       }
+
+       /* for each timer of 'expired' list, execute callback */
+       while (!LIST_EMPTY(&expired_list)) {
+               tim = LIST_FIRST(&expired_list);
+               LIST_REMOVE(tim, next);
+
+               /* execute callback function */
+               CALLOUT_STAT_ADD(cm, cur_expired, -1);
+               CALLOUT_STAT_ADD(cm, cur_running, 1);
+               tim->state = CALLOUT_STATE_RUNNING;
+               cm->cur_priority = tim->priority;
+               irq_unlock();
+               tim->f(cm, tim, tim->arg);
+               irq_lock();
+       }
+
+       cm->cur_priority = old_prio;
+       cm->nb_recursion--;
+       irq_unlock();
+}
+
+/* set the current priority level */
+uint8_t callout_mgr_set_prio(struct callout_mgr *cm, uint8_t new_prio)
+{
+       uint8_t old_prio;
+
+       old_prio = cm->cur_priority;
+       if (new_prio <= old_prio)
+               return old_prio;
+
+       cm->cur_priority = new_prio;
+       return old_prio;
+}
+
+/* restore the current priority level */
+void callout_mgr_restore_prio(struct callout_mgr *cm, uint8_t old_prio)
+{
+       cm->cur_priority = old_prio;
+}
+
+/* dump statistics about timers */
+void callout_dump_stats(struct callout_mgr *cm)
+{
+#ifdef CALLOUT_STATS
+       printf_P(PSTR("Timer statistics:\r\n"));
+       printf_P(PSTR("  schedule = %"PRIu32"\r\n"), cm->stats.schedule);
+       printf_P(PSTR("  stop = %"PRIu32"\r\n"), cm->stats.stop);
+       printf_P(PSTR("  manage = %"PRIu32"\r\n"), cm->stats.manage);
+       printf_P(PSTR("  max_recursion = %"PRIu32"\r\n"),
+               cm->stats.max_recursion);
+       printf_P(PSTR("  delayed = %"PRIu32"\r\n"), cm->stats.delayed);
+       printf_P(PSTR("  hard_delayed = %"PRIu32"\r\n"),
+               cm->stats.hard_delayed);
+
+       printf_P(PSTR("  cur_scheduled = %u\r\n"), cm->stats.cur_scheduled);
+       printf_P(PSTR("  cur_expired = %u\r\n"), cm->stats.cur_expired);
+       printf_P(PSTR("  cur_running = %u\r\n"), cm->stats.cur_running);
+#else
+       printf_P(PSTR("No timer statistics, CALLOUT_STATS is disabled\r\n"));
+#endif
+}
diff --git a/src/callout.h b/src/callout.h
new file mode 100644 (file)
index 0000000..de0eadc
--- /dev/null
@@ -0,0 +1,304 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2014-2015, Olivier MATZ <zer0@droids-corp.org>
+ * Copyright 2010 Intel Corporation
+ * (Inspired by Intel DPDK rte_timer library)
+ */
+
+#ifndef CALLOUT_H_
+#define CALLOUT_H_
+
+#include <queue.h>
+
+#define CALLOUT_STATS
+/* #define CALLOUT_DEBUG */
+
+/**
+ * This module provides a timer service. The manager function
+ * callout_manage() can be called from an interrupt or from a
+ * standard function (usually a main-loop). In the latter case, no
+ * preemption is possible.
+ *
+ * Each timer has a priority: the timers with higher priorities are
+ * scheduled before the others. This feature is mostly useful when the
+ * manager is called from an interrupt. Indeed, the callback function of
+ * a timer with a high priority cannot be preempted by a timer with a
+ * lower priority.
+ *
+ * The module locks interrupts when doing critical operations, ensuring that
+ * critical data are accessed atomically.
+ *
+ * State of timers:
+ * - stopped: initial state after callout_init()
+ * - scheduled: after a call to callout_schedule(), the timer is in the
+ *   scheduled list of the callout manager
+ * - expired: after a call to callout_manage(), if the expire time of a
+ *   timer is reached, it is moved in a local list and its state is
+ *   changed to "expired".
+ * - before starting the callback, the timer goes in state "running".
+ *
+ * Once running, the associated timer is not touched anymore by
+ * callout_manage(). As a result, the timer MUST be either reloaded
+ * or stopped (and potentially freed).
+ */
+
+/**
+ * Maximum number of nested preemptions.
+ */
+#define CALLOUT_MAX_RECURSION 5
+
+#ifdef CALLOUT_STATS
+/**
+ * The structure that stores the timer statistics, mostly useful for debug
+ * purposes.
+ */
+struct callout_debug_stats {
+       uint32_t schedule;      /**< nb of calls to callout_(re)schedule() */
+       uint32_t stop;          /**< nb of calls to callout_stop() */
+       uint32_t manage;        /**< nb of calls to callout_manage() */
+       uint32_t max_recursion; /** manage() skipped due to max recursion */
+       uint32_t delayed;       /** task delayed a bit due to low prio */
+       uint32_t hard_delayed;  /** task recheduled later due to low priority */
+
+       uint8_t cur_scheduled;  /**< current number of scheduled timers */
+       uint8_t cur_expired;    /**< current number of expired timers */
+       uint8_t cur_running;    /**< current number of running timers */
+};
+#endif
+
+struct callout;
+struct callout_mgr;
+
+/**
+ * The type of a callout callback function.
+ */
+typedef void (callout_cb_t)(struct callout_mgr *cm,
+       struct callout *tim, void *arg);
+
+/**
+ * A callout structure, storing all data associated to a timer.
+ */
+struct callout {
+       LIST_ENTRY(callout) next; /**< next/prev in list */
+
+#define CALLOUT_STATE_STOPPED    0 /**< not scheduled */
+#define CALLOUT_STATE_SCHEDULED  1 /**< in the scheduled list */
+#define CALLOUT_STATE_EXPIRED    2 /**< expired, will be executed soon */
+#define CALLOUT_STATE_RUNNING    3 /**< being executed */
+       uint8_t state;             /**< stopped, scheduled, expired */
+       uint8_t priority;          /**< the priority of the timer */
+       uint16_t expire;           /**< time when timer should expire */
+
+       callout_cb_t *f;       /**< callback function pointer */
+       void *arg;                 /**< argument given to the cb function. */
+};
+
+/* define the callout list */
+LIST_HEAD(callout_list, callout);
+
+/* static initializer for a timer structure */
+#define CALLOUT_INITIALIZER { }
+
+/**
+ * Type of the function used by a callout manager to get a time reference
+ */
+typedef uint16_t (callout_get_time_t)(void);
+
+/**
+ * An instance of callout manager. It is possible to have several
+ * managers. A callout is attached to one manager at a time.
+ */
+struct callout_mgr {
+       callout_get_time_t *get_time; /**< func to get the time reference */
+       uint16_t prev_time;   /**< time of previous call */
+       uint8_t cur_priority; /** priority of running event */
+       uint8_t nb_recursion; /** number of recursion */
+       struct callout_list sched_list; /**< list of scheduled timers */
+
+#ifdef CALLOUT_STATS
+       struct callout_debug_stats stats; /**< stats */
+#endif
+};
+
+/**
+ * Initialize a callout manager
+ *
+ * The callout manager must be initialized before callout_add() or
+ * callout_manage() can be called.
+ *
+ * @param cm
+ *   Pointer to the uninitialized callout manager structure.
+ * @param get_time
+ *   Pointer to a function that returns a time reference (unsigned 16 bits).
+ */
+void callout_mgr_init(struct callout_mgr *cm,
+       callout_get_time_t *get_time);
+
+/**
+ * Initialize a callout structure and set callback function
+ *
+ * Before doing any operation on the callout structure, it has to be
+ * initialized with this function. It is possible to reinitialize a
+ * timer that has been previously scheduled, but it must be stopped.
+ *
+ * @param tim
+ *   The timer to initialize.
+ * @param priority
+ *   The priority of the callout (high value means higher priority)
+ * @param f
+ *   The callback function of the timer.
+ * @param arg
+ *   The user argument of the callback function.
+ */
+void callout_init(struct callout *tim, callout_cb_t f, void *arg,
+       uint8_t priority);
+
+/**
+ * Schedule a callout
+ *
+ * The callout_schedule() function adds the timer in the scheduled
+ * list. After the specified amount of ticks are elapsed, the callback
+ * function of the timer previously given to callout_init() will be
+ * invoked with its argument.
+ *
+ * The given "tick" value is relative to the current time, and is 16 bits
+ * wide. As it internally uses signed 16 bits comparison, the max value for
+ * ticks is 32767.
+ *
+ * @param cm
+ *   The callout manager where the timer should be scheduled
+ * @param tim
+ *   The timer handle
+ * @param ticks
+ *   The number of ticks before the callback function is called, relative to now
+ *   (the reference is given by the get_time() function of the callout manager).
+ * @return
+ *   0 on success, negative on error
+ */
+int callout_schedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t ticks);
+
+/**
+ * Reschedule a callout
+ *
+ * This function does exactly the same than callout_schedule()
+ * except that the given time "ticks" is not relative to the current
+ * time but to the "expire" field of the timer.
+ *
+ * Using this function is advised to avoid drift if you want to have periodic
+ * timers.
+ *
+ * This function should preferably be called from the callback function of
+ * the timer. Indeed, if the "expire" field should be a known value or it
+ * can result in an undefined behavior
+ *
+ * The given "tick" value is relative to the "expire" field of the
+ * timer, and is 16 bits wide. As it internally uses signed 16 bits
+ * comparison, the max value for ticks is 32767.
+ *
+ * @param cm
+ *   The callout manager where the timer should be scheduled
+ * @param tim
+ *   The timer handle
+ * @param ticks
+ *   The number of ticks before the callback function is called, relative to
+ *   the "expire" value of the timer
+ * @return
+ *   0 on success, negative on error
+ */
+int callout_reschedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t ticks);
+
+/**
+ * Stop a timer.
+ *
+ * The callout_stop() function stops a timer associated with the
+ * timer handle tim.
+ *
+ * If the timer is scheduled or expired, it is removed from the list:
+ * the callback function won't be invoked. If the timer is stopped or
+ * running the function does nothing.
+ *
+ * If a timer structure is dynamically allocated, invoking
+ * callout_stop() is needed before freeing the structure, even if
+ * the freeing occurs in a callback. Indeed, this function can be called
+ * safely from a timer callback. If it succeeds, the timer is not
+ * referenced anymore by the callout manager.
+ *
+ * @param cm
+ *   The callout manager where the timer is or was scheduled
+ * @param tim
+ *   The timer
+ * @return
+ *   0 on success, negative on error
+ */
+void callout_stop(struct callout_mgr *cm, struct callout *tim);
+
+/**
+ * Return the state of a timer
+ *
+ * @param tim
+ *   The timer
+ * @return
+ *   - CALLOUT_STATE_STOPPED: the timer is stopped
+ *   - CALLOUT_STATE_SCHEDULED: the timer is scheduled
+ *   - CALLOUT_STATE_EXPIRED: the timer was moved in a local list before
+ *     execution
+ */
+static inline uint8_t callout_state(struct callout *tim)
+{
+       return tim->state;
+}
+
+/**
+ * Manage the timer list and execute callback functions.
+ *
+ * This function must be called periodically, either from a loop of from
+ * an interrupt. It browses the list of scheduled timers and runs all
+ * timers that are expired.
+ *
+ * This function must be called at least every 16384 reference ticks of
+ * cm->get_time(), but calling it more often is recommanded to avoid
+ * delaying task abusively.
+ *
+ * The function must be called with IRQ allowed.
+ */
+void callout_manage(struct callout_mgr *cm);
+
+/**
+ * Dump statistics about timers.
+ */
+void callout_dump_stats(struct callout_mgr *cm);
+
+/**
+ * Set the current priority level
+ *
+ * Prevent callout with a priority lower than "new_prio" to be executed.
+ * If the current priority of the callout manager is already lower higher
+ * than "new_prio", the function won't change the running priority.
+ *
+ * The returned value should be stored by the caller and restored with
+ * callout_mgr_restore_prio(), preferably in the same function.
+ *
+ * @param cm
+ *   The callout manager
+ * @param new_prio
+ *   The new running priority
+ *
+ * @return
+ *   The value of the running priority before the call og this function
+ */
+uint8_t callout_mgr_set_prio(struct callout_mgr *cm, uint8_t new_prio);
+
+/**
+ * Restore the current priority level
+ *
+ * Used after a call to callout_mgr_set_prio().
+ *
+ * @param cm
+ *   The callout manager
+ * @param old_prio
+ *   The old running priority
+ */
+void callout_mgr_restore_prio(struct callout_mgr *cm, uint8_t old_prio);
+
+#endif /* CALLOUT_H_ */
diff --git a/src/cirbuf.c b/src/cirbuf.c
new file mode 100644 (file)
index 0000000..0ab9465
--- /dev/null
@@ -0,0 +1,374 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2009-2015, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <cirbuf.h>
+
+/* init a circular buffer */
+void
+cirbuf_init(struct cirbuf *cbuf, char *buf, unsigned start,
+       unsigned maxlen)
+{
+       cbuf->maxlen = maxlen;
+       cbuf->len = 0;
+       cbuf->start = start;
+       cbuf->buf = buf;
+}
+
+/* return the index of the tail (contains an empty element) */
+static unsigned
+cirbuf_get_end(const struct cirbuf *cbuf)
+{
+       unsigned end;
+
+       end = cbuf->start + cbuf->len;
+       if (end >= cbuf->maxlen)
+               end -= cbuf->maxlen;
+
+       return end;
+}
+
+/* multiple add at head */
+int
+cirbuf_add_buf_head(struct cirbuf *cbuf, const char *buf, unsigned n)
+{
+       unsigned remain = n;
+       int copy_start;
+       unsigned copy_len;
+
+       if (remain == 0 || remain > cirbuf_get_freelen(cbuf))
+               return -EINVAL;
+
+       copy_start = cbuf->start - remain;
+       if (copy_start < 0)
+               copy_start += cbuf->maxlen;
+       cbuf->start = copy_start;
+       cbuf->len += remain;
+       copy_len = remain;
+       if ((unsigned)copy_start + copy_len >= cbuf->maxlen)
+               copy_len = cbuf->maxlen - copy_start;
+       if (copy_len > 0) {
+               memcpy(cbuf->buf + copy_start, buf, copy_len);
+               buf += copy_len;
+               remain -= copy_len;
+       }
+       if (remain == 0)
+               return n;
+
+       copy_len = remain;
+       memcpy(cbuf->buf, buf, copy_len);
+
+       return n;
+}
+
+/* multiple add at tail */
+int
+cirbuf_add_buf_tail(struct cirbuf *cbuf, const char *buf, unsigned n)
+{
+       unsigned remain = n;
+       unsigned copy_start, copy_len;
+
+       if (remain == 0 || remain > cirbuf_get_freelen(cbuf))
+               return -EINVAL;
+
+       copy_start = cirbuf_get_end(cbuf);
+       cbuf->len += remain;
+       copy_len = remain;
+       if (copy_start + copy_len >= cbuf->maxlen)
+               copy_len = cbuf->maxlen - copy_start;
+       if (copy_len > 0) {
+               memcpy(cbuf->buf + copy_start, buf, copy_len);
+               buf += copy_len;
+               remain -= copy_len;
+       }
+       if (remain == 0)
+               return n;
+
+       copy_len = remain;
+       memcpy(cbuf->buf, buf, copy_len);
+
+       return n;
+}
+
+/* single add at head */
+static inline void
+__cirbuf_add_head(struct cirbuf *cbuf, char c)
+{
+       unsigned start = cbuf->start;
+
+       if (start == 0)
+               start = cbuf->maxlen - 1;
+       else
+               start = start - 1;
+       cbuf->buf[start] = c;
+       cbuf->start = start;
+       cbuf->len++;
+}
+
+/* single add at head, checking if full first */
+int
+cirbuf_add_head_safe(struct cirbuf *cbuf, char c)
+{
+       if (!cirbuf_is_full(cbuf)) {
+               __cirbuf_add_head(cbuf, c);
+               return 0;
+       }
+       return -EINVAL;
+}
+
+/* single add at head */
+void
+cirbuf_add_head(struct cirbuf *cbuf, char c)
+{
+       __cirbuf_add_head(cbuf, c);
+}
+
+/* single add at tail */
+static inline void
+__cirbuf_add_tail(struct cirbuf *cbuf, char c)
+{
+       unsigned end = cirbuf_get_end(cbuf);
+
+       cbuf->buf[end] = c;
+       cbuf->len++;
+}
+
+/* single add at tail, checking if full first */
+int
+cirbuf_add_tail_safe(struct cirbuf *cbuf, char c)
+{
+       if (!cirbuf_is_full(cbuf)) {
+               __cirbuf_add_tail(cbuf, c);
+               return 0;
+       }
+       return -EINVAL;
+}
+
+/* single add at tail */
+void
+cirbuf_add_tail(struct cirbuf *cbuf, char c)
+{
+       __cirbuf_add_tail(cbuf, c);
+}
+
+/* multiple delete at head */
+int
+cirbuf_del_buf_head(struct cirbuf *cbuf, unsigned size)
+{
+       if (size == 0 || size > cirbuf_get_len(cbuf))
+               return -EINVAL;
+
+       cbuf->len -= size;
+       if (cirbuf_is_empty(cbuf)) {
+               cbuf->start += size - 1;
+               cbuf->start %= cbuf->maxlen;
+       }
+       else {
+               cbuf->start += size;
+               cbuf->start %= cbuf->maxlen;
+       }
+       return 0;
+}
+
+/* multiple delete at tail */
+int
+cirbuf_del_buf_tail(struct cirbuf *cbuf, unsigned size)
+{
+       if (size == 0 || size > cirbuf_get_len(cbuf))
+               return -EINVAL;
+
+       cbuf->len -= size;
+       return 0;
+}
+
+/* single del at head */
+static inline void
+__cirbuf_del_head(struct cirbuf *cbuf)
+{
+       cbuf->len --;
+       if (!cirbuf_is_empty(cbuf)) {
+               cbuf->start ++;
+               cbuf->start %= cbuf->maxlen;
+       }
+}
+
+/* single del at head, checking if empty first */
+int
+cirbuf_del_head_safe(struct cirbuf *cbuf)
+{
+       if (cbuf && !cirbuf_is_empty(cbuf)) {
+               __cirbuf_del_head(cbuf);
+               return 0;
+       }
+       return -EINVAL;
+}
+
+/* single del at head */
+void
+cirbuf_del_head(struct cirbuf *cbuf)
+{
+       __cirbuf_del_head(cbuf);
+}
+
+/* single del at tail */
+static inline void
+__cirbuf_del_tail(struct cirbuf *cbuf)
+{
+       cbuf->len--;
+}
+
+/* single del at tail, checking if empty first */
+int
+cirbuf_del_tail_safe(struct cirbuf *cbuf)
+{
+       if (cbuf && !cirbuf_is_empty(cbuf)) {
+               __cirbuf_del_tail(cbuf);
+               return 0;
+       }
+       return -EINVAL;
+}
+
+/* single del at tail */
+void
+cirbuf_del_tail(struct cirbuf *cbuf)
+{
+       __cirbuf_del_tail(cbuf);
+}
+
+/* convert to buffer */
+int
+cirbuf_get_buf_head(const struct cirbuf *cbuf, char *buf, unsigned n)
+{
+       unsigned remain = n;
+       unsigned cirbuf_len, copy_start, copy_len;
+
+       cirbuf_len = cirbuf_get_len(cbuf);
+       if (remain >= cirbuf_len)
+               remain = cirbuf_len;
+
+       if (remain == 0)
+               return 0;
+
+       copy_start = cbuf->start;
+       copy_len = remain;
+       if (copy_start + copy_len >= cbuf->maxlen)
+               copy_len = cbuf->maxlen - copy_start;
+       if (copy_len > 0) {
+               memcpy(buf, cbuf->buf + copy_start, copy_len);
+               buf += copy_len;
+               remain -= copy_len;
+       }
+       if (remain == 0)
+               return n;
+
+       copy_len = remain;
+       memcpy(buf, cbuf->buf, copy_len);
+
+       return n;
+}
+
+/* convert to buffer */
+int
+cirbuf_get_buf_tail(const struct cirbuf *cbuf, char *buf, unsigned n)
+{
+       unsigned remain = n;
+       int copy_start;
+       unsigned cirbuf_len, copy_len;
+
+       cirbuf_len = cirbuf_get_len(cbuf);
+       if (remain >= cirbuf_len)
+               remain = cirbuf_len;
+
+       if (remain == 0)
+               return 0;
+
+       copy_start = cirbuf_get_end(cbuf) - remain;
+       if (copy_start < 0)
+               copy_start += cbuf->maxlen;
+       copy_len = remain;
+       if ((unsigned)copy_start + copy_len >= cbuf->maxlen)
+               copy_len = cbuf->maxlen - copy_start;
+       if (copy_len > 0) {
+               memcpy(buf, cbuf->buf + copy_start, copy_len);
+               buf += copy_len;
+               remain -= copy_len;
+       }
+       if (remain == 0)
+               return n;
+
+       copy_len = remain;
+       memcpy(buf, cbuf->buf, copy_len);
+
+       return n;
+}
+
+/* get head */
+char
+cirbuf_get_head(const struct cirbuf *cbuf)
+{
+       return cbuf->buf[cbuf->start];
+}
+
+/* get tail */
+char
+cirbuf_get_tail(const struct cirbuf *cbuf)
+{
+       unsigned end;
+
+       /* should not happen */
+       if (cbuf->len == 0)
+               return 0;
+
+       end = cbuf->start + cbuf->len - 1;
+       if (end >= cbuf->maxlen)
+               end -= cbuf->maxlen;
+       return cbuf->buf[end];
+}
+
+static void
+__cirbuf_shift(struct cirbuf *cbuf, unsigned n)
+{
+       char tmp, tmp2;
+       unsigned start, cur, min;
+
+       start = 0;
+       cur = 0;
+       tmp = cbuf->buf[0];
+       min = cbuf->maxlen;
+
+       while (1) {
+               cur = cur + n;
+               if (cur >= cbuf->maxlen)
+                       cur -= cbuf->maxlen;
+               tmp2 = cbuf->buf[cur];
+               cbuf->buf[cur] = tmp;
+               tmp = tmp2;
+               if (cur == start) {
+                       if ((cur + 1) == min)
+                               break;
+                       cur++;
+                       tmp = cbuf->buf[cur];
+                       start = cur;
+               } else if (cur < min) {
+                       min = cur;
+               }
+       }
+
+       cbuf->start += n;
+       if (cbuf->start >= cbuf->maxlen)
+               cbuf->start -= cbuf->maxlen;
+}
+
+void cirbuf_align_left(struct cirbuf *cbuf)
+{
+       __cirbuf_shift(cbuf, cbuf->maxlen - cbuf->start);
+}
+
+void cirbuf_align_right(struct cirbuf *cbuf)
+{
+       unsigned end = cirbuf_get_end(cbuf);
+       __cirbuf_shift(cbuf, cbuf->maxlen - end);
+}
diff --git a/src/cirbuf.h b/src/cirbuf.h
new file mode 100644 (file)
index 0000000..ab05ee5
--- /dev/null
@@ -0,0 +1,355 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2009-2015, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef CIRBUF_H_
+#define CIRBUF_H_
+
+#include <stdio.h>
+
+/**
+ * A circular buffer.
+ */
+struct cirbuf {
+       unsigned maxlen; /**< Total length of the fifo (number of elements). */
+       unsigned start;  /**< Index of the first element. */
+       unsigned len;    /**< Current len of fifo. */
+       char *buf;       /**< Pointer to the data buffer. */
+};
+
+/**
+ * Initialize a circular buffer.
+ *
+ * @param cbuf
+ *   A pointer to an uninitialized circular buffer structure.
+ * @param buf
+ *   The buffer used to store the data.
+ * @param start
+ *   The index of head at initialization.
+ * @param maxlen
+ *   The size of the buffer.
+ */
+void cirbuf_init(struct cirbuf *cbuf, char *buf, unsigned start,
+       unsigned maxlen);
+
+/**
+ * Check if the circular buffer is full.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   1 if the circular buffer is full, else 0.
+ */
+static inline int cirbuf_is_full(const struct cirbuf *cbuf)
+{
+       return cbuf->len == cbuf->maxlen;
+}
+
+/**
+ * Check if the circular buffer is empty.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   1 if the circular buffer is empty, else 0.
+ */
+static inline int cirbuf_is_empty(const struct cirbuf *cbuf)
+{
+       return cbuf->len == 0;
+}
+
+/**
+ * Get the length of data in the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   The current length of data in the circular buffer.
+ */
+static inline unsigned cirbuf_get_len(const struct cirbuf *cbuf)
+{
+       return cbuf->len;
+}
+
+/**
+ * Get the size of the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   Return the maximum size of the circular buffer (used + free elements)
+ */
+static inline unsigned cirbuf_get_maxlen(const struct cirbuf *cbuf)
+{
+       return cbuf->maxlen;
+}
+
+/**
+ * Get the lenght of free space in the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   Return the length of free space.
+ */
+static inline unsigned cirbuf_get_freelen(const struct cirbuf *cbuf)
+{
+       return cbuf->maxlen - cbuf->len;
+}
+
+/**
+ * Iterator for a circular buffer
+ *
+ *   cirbuf: struct cirbuf pointer
+ *   i: an integer internally used in the macro
+ *   elt: char that takes the value for each iteration
+ */
+#define CIRBUF_FOREACH(cirbuf, i, elt)                         \
+       for (i = 0, elt = (cirbuf)->buf[(cirbuf)->start];               \
+            i < ((cirbuf)->len);                                       \
+            i ++,  elt = (cirbuf)->buf[((cirbuf)->start + i) %         \
+                    ((cirbuf)->maxlen)])
+
+/**
+ * Add a character at the head of the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param c
+ *   The character to add.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_add_head_safe(struct cirbuf *cbuf, char c);
+
+/**
+ * Add a character at the head of the circular buffer.
+ *
+ * The function does not check that there is enough free space
+ * in the buffer, so it has to be done by the caller. If it's
+ * not the case, undefined behavior will occur.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param c
+ *   The character to add.
+ */
+void cirbuf_add_head(struct cirbuf *cbuf, char c);
+
+/**
+ * Add a character at the tail of the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param c
+ *   The character to add.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_add_tail_safe(struct cirbuf *cbuf, char c);
+
+/**
+ * Add a character at the tail of the circular buffer.
+ *
+ * The function does not check that there is enough free space
+ * in the buffer, so it has to be done by the caller. If it's
+ * not the case, undefined behavior will occur.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param c
+ *   The character to add.
+ */
+void cirbuf_add_tail(struct cirbuf *cbuf, char c);
+
+/**
+ * Remove a char at the head of the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_del_head_safe(struct cirbuf *cbuf);
+
+/**
+ * Remove a char at the head of the circular buffer.
+ *
+ * The function does not check that there is enough elements
+ * in the buffer, so it has to be done by the caller. If it's
+ * not the case, undefined behavior will occur.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ */
+void cirbuf_del_head(struct cirbuf *cbuf);
+
+/**
+ * Remove a char at the tail of the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_del_tail_safe(struct cirbuf *cbuf);
+
+/**
+ * Remove a char at the tail of the circular buffer.
+ *
+ * The function does not check that there is enough elements
+ * in the buffer, so it has to be done by the caller. If it's
+ * not the case, undefined behavior will occur.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ */
+void cirbuf_del_tail(struct cirbuf *cbuf);
+
+/**
+ * Return the element at the tail of the circular buffer.
+ *
+ * The circular buffer must not be empty or an undefined character
+ * will be returned.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   The character at the tail of the circular buffer.
+ */
+char cirbuf_get_head(const struct cirbuf *cbuf);
+
+/**
+ * Return the element at the tail of the circular buffer.
+ *
+ * The circular buffer must not be empty or an undefined character
+ * will be returned.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @return
+ *   The character at the tail of the circular buffer.
+ */
+char cirbuf_get_tail(const struct cirbuf *cbuf);
+
+/**
+ * Add a buffer at the head of the circular buffer.
+ *
+ * Add 'n' bytes of buffer pointed by 'buf' ad the head of th
+ * circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param buf
+ *   The pointer to the buffer.
+ * @param n
+ *   Number of bytes to add.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_add_buf_head(struct cirbuf *cbuf, const char *buf,
+       unsigned n);
+
+/**
+ * Add a buffer at the tail of the circular buffer.
+ *
+ * Add 'n' bytes of buffer pointed by 'buf' ad the tail of th
+ * circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param buf
+ *   The pointer to the buffer.
+ * @param n
+ *   Number of bytes to add.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_add_buf_tail(struct cirbuf *cbuf, const char *buf,
+       unsigned n);
+
+/**
+ * Remove chars at the head of the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param n
+ *   Number of bytes to remove.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_del_buf_head(struct cirbuf *cbuf, unsigned n);
+
+/**
+ * Remove chars at the tail of the circular buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param n
+ *   Number of bytes to remove.
+ * @return
+ *   Return 0 on success, or a negative value on error.
+ */
+int cirbuf_del_buf_tail(struct cirbuf *cbuf, unsigned n);
+
+/**
+ * Copy multiple bytes from the head of the circular buffer.
+ *
+ * Copy a maximum of 'n' characters from the head of the circular buffer
+ * into a flat buffer pointed by 'buf'. If the circular buffer is
+ * smaller than n, less bytes are copied.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param buf
+ *   The pointer to the buffer.
+ * @param n
+ *   Maximum number of bytes to copy.
+ * @return
+ *   Return the number of copied chars.
+ */
+int cirbuf_get_buf_head(const struct cirbuf *cbuf, char *buf,
+       unsigned n);
+
+/**
+ * Copy multiple bytes from the tail of the circular buffer.
+ *
+ * Copy a maximum of 'n' characters from the tail of the circular buffer
+ * into a flat buffer pointed by 'buf'. If the circular buffer is
+ * smaller than n, less bytes are copied.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ * @param buf
+ *   The pointer to the buffer.
+ * @param n
+ *   Maximum number of bytes to copy.
+ * @return
+ *   Return the number of copied chars.
+ */
+int cirbuf_get_buf_tail(const struct cirbuf *cbuf, char *buf,
+       unsigned n);
+
+/**
+ * Set the start of the data to the index 0 of the internal buffer.
+ *
+ * After a call to this function, it is possible for the caller to
+ * use cbuf->buf as a linear (read-only) buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ */
+void cirbuf_align_left(struct cirbuf *cbuf);
+
+/**
+ * Set the end of the data to the last index of the internal buffer.
+ *
+ * After a call to this function, it is possible for the caller to
+ * use cbuf->buf as a linear (read-only) buffer.
+ *
+ * @param cbuf
+ *   The circular buffer pointer.
+ */
+void cirbuf_align_right(struct cirbuf *cbuf);
+
+#endif /* CIRBUF_H_ */
diff --git a/src/commands.c b/src/commands.c
new file mode 100644 (file)
index 0000000..54bd7a3
--- /dev/null
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <avr/wdt.h>
+#include <avr/pgmspace.h>
+
+#include "irq.h"
+#include "uart.h"
+#include "led.h"
+#include "buzzer.h"
+#include "main.h"
+#include "commands.h"
+
+#define ntohs __builtin_bswap16
+#define htons __builtin_bswap16
+
+#define CMD_LED_DEBUG 0x00
+struct cmd_led_debug {
+       uint8_t on;
+};
+
+#define CMD_BUZZER 0x01
+struct cmd_buzzer {
+       uint16_t period_us;
+       uint16_t duration_ms;
+};
+
+#define CMD_LED_GYRO_ALL 0x02
+struct cmd_led_gyro_all {
+       uint16_t intensity;
+       uint16_t blink_on_ms;
+       uint16_t blink_off_ms;
+       uint16_t stop_ms;
+};
+
+#define CMD_LED_GYRO_RAY 0x03
+struct cmd_led_gyro_ray {
+       uint16_t angle;
+       uint16_t intensity;
+       uint16_t width;
+       int16_t rot_speed;
+       uint16_t stop_ms;
+};
+
+#define CMD_LOG_DEBUG 0x04
+struct cmd_log_debug {
+       uint8_t mask;
+};
+
+#define CMD_RESET 0x05
+
+#define CMD_BUZZER_VIBRATO 0x06
+struct cmd_buzzer_vibrato {
+       uint16_t period1_us;
+       uint16_t period2_us;
+       uint16_t duration_ms;
+       uint16_t vibrato_period_ms;
+       uint8_t vibrato_type;
+};
+
+#define CMD_MAX 0x07
+
+#define CMD_INVALID 0xFF
+
+struct cmd_buf {
+       uint8_t cmd;
+       uint8_t len;
+       union {
+               struct cmd_led_debug led_debug;
+               struct cmd_buzzer buzzer;
+               struct cmd_led_gyro_all led_gyro_all;
+               struct cmd_led_gyro_ray led_gyro_ray;
+               struct cmd_log_debug log_debug;
+               struct cmd_buzzer_vibrato buzzer_vibrato;
+       };
+       uint8_t csum;
+};
+
+/* current command buffer */
+static struct cmd_buf cmd_buf;
+static uint8_t cmd_len;
+static uint8_t cmd_csum;
+
+static void clear_cmd(void)
+{
+       cmd_len = 0;
+       cmd_csum = 0;
+}
+
+static uint8_t csum_add(uint8_t x, uint8_t y)
+{
+       uint16_t res = (uint16_t)x + (uint16_t)y;
+       res = (res >> 8) + (res & 0xff);
+       return res;
+}
+
+static void cmd_led_debug(void)
+{
+       if (cmd_buf.len != sizeof(struct cmd_led_debug) + 3)
+               return;
+       if (cmd_buf.led_debug.on)
+               led_debug_on();
+       else
+               led_debug_off();
+}
+
+static void cmd_buzzer(void)
+{
+       if (cmd_buf.len != sizeof(struct cmd_buzzer) + 3)
+               return;
+       buzzer(ntohs(cmd_buf.buzzer.period_us),
+               ntohs(cmd_buf.buzzer.duration_ms));
+}
+
+static void cmd_led_gyro_all(void)
+{
+       if (cmd_buf.len != sizeof(struct cmd_led_gyro_all) + 3)
+               return;
+       led_gyro_all(ntohs(cmd_buf.led_gyro_all.intensity),
+               ntohs(cmd_buf.led_gyro_all.blink_on_ms),
+               ntohs(cmd_buf.led_gyro_all.blink_off_ms),
+               ntohs(cmd_buf.led_gyro_all.stop_ms));
+}
+
+static void cmd_led_gyro_ray(void)
+{
+       if (cmd_buf.len != sizeof(struct cmd_led_gyro_ray) + 3)
+               return;
+       led_gyro_ray(
+               ntohs(cmd_buf.led_gyro_ray.angle),
+               ntohs(cmd_buf.led_gyro_ray.intensity),
+               ntohs(cmd_buf.led_gyro_ray.width),
+               ntohs(cmd_buf.led_gyro_ray.rot_speed),
+               ntohs(cmd_buf.led_gyro_ray.stop_ms));
+}
+
+static void cmd_log_debug(void)
+{
+       if (cmd_buf.len != sizeof(struct cmd_log_debug) + 3)
+               return;
+       log_debug = 1;
+}
+
+static void cmd_reset(void)
+{
+       wdt_enable(WDTO_15MS);
+       while(1);
+}
+
+static void cmd_buzzer_vibrato(void)
+{
+       if (cmd_buf.len != sizeof(struct cmd_buzzer_vibrato) + 3)
+               return;
+       buzzer_vibrato(ntohs(cmd_buf.buzzer_vibrato.period1_us),
+               ntohs(cmd_buf.buzzer_vibrato.period2_us),
+               ntohs(cmd_buf.buzzer_vibrato.duration_ms),
+               ntohs(cmd_buf.buzzer_vibrato.vibrato_period_ms),
+               cmd_buf.buzzer_vibrato.vibrato_type);
+}
+
+static void cmd_exec(void)
+{
+       if (cmd_csum != 0xff) {
+               clear_cmd();
+               return;
+       }
+
+       if (log_debug)
+               printf_P(PSTR("command %d received\n"), cmd_buf.cmd);
+
+       switch (cmd_buf.cmd) {
+       case CMD_LED_DEBUG: cmd_led_debug(); break;
+       case CMD_BUZZER: cmd_buzzer(); break;
+       case CMD_LED_GYRO_ALL: cmd_led_gyro_all(); break;
+       case CMD_LED_GYRO_RAY: cmd_led_gyro_ray(); break;
+       case CMD_LOG_DEBUG: cmd_log_debug(); break;
+       case CMD_RESET: cmd_reset(); break;
+       case CMD_BUZZER_VIBRATO: cmd_buzzer_vibrato(); break;
+       default: break;
+       }
+
+}
+
+void cmd_input(uint8_t c)
+{
+       if (cmd_len >= sizeof(cmd_buf))
+               clear_cmd();
+
+       if (cmd_len == 0) {
+               if (c >= CMD_MAX || c == CMD_INVALID) {
+                       clear_cmd();
+                       return;
+               }
+       } else if (cmd_len == 1) {
+               if (c < 3 || c > sizeof(cmd_buf)) {
+                       clear_cmd();
+                       return;
+               }
+       }
+
+       ((uint8_t *)&cmd_buf)[cmd_len] = c;
+       cmd_len++;
+       cmd_csum = csum_add(cmd_csum, c);
+
+       if (cmd_len >= 3 && cmd_len == cmd_buf.len) {
+               cmd_exec();
+               clear_cmd();
+       }
+}
diff --git a/src/commands.h b/src/commands.h
new file mode 100644 (file)
index 0000000..b4d8b38
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef _COMMANDS_H
+#define _COMMANDS_H
+
+#include <stdint.h>
+
+void cmd_input(uint8_t c);
+
+#endif
diff --git a/src/errno.h b/src/errno.h
new file mode 100644 (file)
index 0000000..5d77341
--- /dev/null
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 1982, 1986, 1989, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ */
+
+#ifndef ERRNO_H_
+#define ERRNO_H_
+
+#define        EPERM           1               /* Operation not permitted */
+#define        ENOENT          2               /* No such file or directory */
+#define        ESRCH           3               /* No such process */
+#define        EINTR           4               /* Interrupted system call */
+#define        EIO             5               /* Input/output error */
+#define        ENXIO           6               /* Device not configured */
+#define        E2BIG           7               /* Argument list too long */
+#define        ENOEXEC         8               /* Exec format error */
+#define        EBADF           9               /* Bad file descriptor */
+#define        ECHILD          10              /* No child processes */
+#define        EDEADLK         11              /* Resource deadlock avoided */
+                                       /* 11 was EAGAIN */
+#define        ENOMEM          12              /* Cannot allocate memory */
+#define        EACCES          13              /* Permission denied */
+#define        EFAULT          14              /* Bad address */
+#define        ENOTBLK         15              /* Block device required */
+#define        EBUSY           16              /* Device busy */
+#define        EEXIST          17              /* File exists */
+#define        EXDEV           18              /* Cross-device link */
+#define        ENODEV          19              /* Operation not supported by device */
+#define        ENOTDIR         20              /* Not a directory */
+#define        EISDIR          21              /* Is a directory */
+#define        EINVAL          22              /* Invalid argument */
+#define        ENFILE          23              /* Too many open files in system */
+#define        EMFILE          24              /* Too many open files */
+#define        ENOTTY          25              /* Inappropriate ioctl for device */
+#define        ETXTBSY         26              /* Text file busy */
+#define        EFBIG           27              /* File too large */
+#define        ENOSPC          28              /* No space left on device */
+#define        ESPIPE          29              /* Illegal seek */
+#define        EROFS           30              /* Read-only file system */
+#define        EMLINK          31              /* Too many links */
+#define        EPIPE           32              /* Broken pipe */
+
+/* math software */
+#define        EDOM            33              /* Numerical argument out of domain */
+#define        ERANGE          34              /* Result too large or too small */
+
+/* non-blocking and interrupt i/o */
+#define        EAGAIN          35              /* Resource temporarily unavailable */
+#define        EWOULDBLOCK     EAGAIN          /* Operation would block */
+#define        EINPROGRESS     36              /* Operation now in progress */
+#define        EALREADY        37              /* Operation already in progress */
+
+/* ipc/network software -- argument errors */
+#define        ENOTSOCK        38              /* Socket operation on non-socket */
+#define        EDESTADDRREQ    39              /* Destination address required */
+#define        EMSGSIZE        40              /* Message too long */
+#define        EPROTOTYPE      41              /* Protocol wrong type for socket */
+#define        ENOPROTOOPT     42              /* Protocol option not available */
+#define        EPROTONOSUPPORT 43              /* Protocol not supported */
+#define        ESOCKTNOSUPPORT 44              /* Socket type not supported */
+#define        EOPNOTSUPP      45              /* Operation not supported */
+#define        EPFNOSUPPORT    46              /* Protocol family not supported */
+#define        EAFNOSUPPORT    47              /* Address family not supported by protocol family */
+#define        EADDRINUSE      48              /* Address already in use */
+#define        EADDRNOTAVAIL   49              /* Can't assign requested address */
+
+/* ipc/network software -- operational errors */
+#define        ENETDOWN        50              /* Network is down */
+#define        ENETUNREACH     51              /* Network is unreachable */
+#define        ENETRESET       52              /* Network dropped connection on reset */
+#define        ECONNABORTED    53              /* Software caused connection abort */
+#define        ECONNRESET      54              /* Connection reset by peer */
+#define        ENOBUFS         55              /* No buffer space available */
+#define        EISCONN         56              /* Socket is already connected */
+#define        ENOTCONN        57              /* Socket is not connected */
+#define        ESHUTDOWN       58              /* Can't send after socket shutdown */
+#define        ETOOMANYREFS    59              /* Too many references: can't splice */
+#define        ETIMEDOUT       60              /* Operation timed out */
+#define        ECONNREFUSED    61              /* Connection refused */
+
+#define        ELOOP           62              /* Too many levels of symbolic links */
+#define        ENAMETOOLONG    63              /* File name too long */
+
+/* should be rearranged */
+#define        EHOSTDOWN       64              /* Host is down */
+#define        EHOSTUNREACH    65              /* No route to host */
+#define        ENOTEMPTY       66              /* Directory not empty */
+
+/* quotas & mush */
+#define        EPROCLIM        67              /* Too many processes */
+#define        EUSERS          68              /* Too many users */
+#define        EDQUOT          69              /* Disc quota exceeded */
+
+/* Network File System */
+#define        ESTALE          70              /* Stale NFS file handle */
+#define        EREMOTE         71              /* Too many levels of remote in path */
+#define        EBADRPC         72              /* RPC struct is bad */
+#define        ERPCMISMATCH    73              /* RPC version wrong */
+#define        EPROGUNAVAIL    74              /* RPC prog. not avail */
+#define        EPROGMISMATCH   75              /* Program version wrong */
+#define        EPROCUNAVAIL    76              /* Bad procedure for program */
+
+#define        ENOLCK          77              /* No locks available */
+#define        ENOSYS          78              /* Function not implemented */
+
+#define        EFTYPE          79              /* Inappropriate file type or format */
+#define        EAUTH           80              /* Authentication error */
+#define        ENEEDAUTH       81              /* Need authenticator */
+
+/* SystemV IPC */
+#define        EIDRM           82              /* Identifier removed */
+#define        ENOMSG          83              /* No message of desired type */
+#define        EOVERFLOW       84              /* Value too large to be stored in data type */
+
+/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */
+#define        EILSEQ          85              /* Illegal byte sequence */
+
+/* From IEEE Std 1003.1-2001 */
+/* Base, Realtime, Threads or Thread Priority Scheduling option errors */
+#define ENOTSUP                86              /* Not supported */
+
+/* Realtime option errors */
+#define ECANCELED      87              /* Operation canceled */
+
+/* Realtime, XSI STREAMS option errors */
+#define EBADMSG                88              /* Bad or Corrupt message */
+
+/* XSI STREAMS option errors  */
+#define ENODATA                89              /* No message available */
+#define ENOSR          90              /* No STREAM resources */
+#define ENOSTR         91              /* Not a STREAM */
+#define ETIME          92              /* STREAM ioctl timeout */
+
+/* File system extended attribute errors */
+#define        ENOATTR         93              /* Attribute not found */
+
+/* Realtime, XSI STREAMS option errors */
+#define        EMULTIHOP       94              /* Multihop attempted */
+#define        ENOLINK         95              /* Link has been severed */
+#define        EPROTO          96              /* Protocol error */
+
+#define        ELAST           96              /* Must equal largest errno */
+
+#include_next <errno.h>
+
+#endif /* !ERRNO_H_ */
diff --git a/src/irq.h b/src/irq.h
new file mode 100644 (file)
index 0000000..72b2944
--- /dev/null
+++ b/src/irq.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef IRQ_H_
+#define IRQ_H_
+
+#include <avr/interrupt.h>
+
+typedef uint8_t irqflags_t;
+
+static inline void irq_lock(void)
+{
+       cli();
+}
+
+static inline void irq_unlock(void)
+{
+       sei();
+}
+
+static inline irqflags_t irq_lock_save(void)
+{
+       irqflags_t flags;
+
+       flags = SREG;
+       cli();
+       return flags;
+}
+
+static inline void irq_unlock_restore(irqflags_t flags)
+{
+       SREG = flags;
+}
+
+static inline int irq_locked(void)
+{
+       return !(bit_is_set(SREG,7));
+}
+
+#endif /* IRQ_H_ */
diff --git a/src/led.c b/src/led.c
new file mode 100644 (file)
index 0000000..85a79d5
--- /dev/null
+++ b/src/led.c
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <avr/io.h>
+#include <util/delay.h>
+#include <avr/pgmspace.h>
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "main.h"
+#include "callout.h"
+#include "led.h"
+
+#define GYRO_UPDATE_MS 10
+#define GYRO_N_LEDS 12
+
+#define GYRO_TYPE_STATIC  0
+#define GYRO_TYPE_BLINK   1
+#define GYRO_TYPE_ROTATE  2
+
+#define LED1642_PORT PORTD
+#define LED1642_PIN PIND
+#define LED1642_DDR DDRD
+#define SDI_BIT 6
+#define SDO_BIT 2
+#define LE_BIT 3
+#define CLK_BIT 4
+#define PWCLK_BIT 5
+
+#define LED1642_CMD_DATA_SWITCH 1
+#define LED1642_CMD_DATA_LATCH 3
+#define LED1642_CMD_GLOBAL_LATCH 5
+#define LED1642_CMD_WRITE_CONFIG 7
+#define LED1642_CMD_READ_CONFIG 8
+
+
+#define LED1642_CFG_CURRENT 0 /* 6 bits */
+#define LED1642_CFG_CURRENT_RANGE 6
+#define LED1642_CFG_ERROR_DETECT 7
+#define LED1642_CFG_SHORTED_LEDS 8 /* 2 bits */
+#define LED1642_CFG_AUTO_OFF 10
+#define LED1642_CFG_TURN_ON_TIME 11 /* 2 bits */
+#define LED1642_CFG_SDO_DELAY 13
+#define LED1642_CFG_OUT_DELAY 14
+#define LED1642_CFG_PWM12 15
+
+struct gyro_state {
+       uint8_t type; /* none, blink, rotate */
+       uint16_t ms;
+       uint16_t stop_ms;
+
+       union {
+               struct {
+                       uint16_t period_ms;
+                       uint16_t intensity;
+                       uint16_t on_ms;
+                       uint16_t off_ms;
+               } blink;
+               struct {
+                       uint16_t angle;
+                       uint16_t intensity;
+                       uint16_t width;
+                       int16_t speed;
+               } rot;
+       };
+};
+
+/* LEDs status. Intensity is stored within 12 bits. */
+static uint16_t gyro_leds[GYRO_N_LEDS];
+static struct gyro_state gyro_state;
+static struct callout gyro_timer;
+
+void __led_gyro_ray(uint16_t angle, uint16_t intensity, uint16_t width)
+{
+#define LED_WIDTH ((uint16_t)(65536 / GYRO_N_LEDS))
+#define LED_HALF ((uint16_t)(65536 / (GYRO_N_LEDS * 2)))
+
+       uint32_t ray_half;
+       uint16_t led_angle;
+       int16_t diff;
+       uint8_t i;
+
+       ray_half = width / 2;
+
+       for (i = 0; i < GYRO_N_LEDS; i++) {
+               gyro_leds[i] = 0;
+
+               led_angle = (uint16_t)(((uint32_t)i * 65536) / GYRO_N_LEDS);
+
+               /* get angle diff */
+               diff = led_angle - angle; /* modulo 16 bits */
+               if (diff == -32768)
+                       diff = 32767;
+               else if (diff < 0)
+                       diff = -diff;
+
+               /* calculate led intensity */
+               if ((uint16_t)diff >= ray_half + LED_HALF)
+                       gyro_leds[i] = 0;
+               else if (diff <= (int32_t)(ray_half - LED_HALF))
+                       gyro_leds[i] = intensity;
+               else
+                       gyro_leds[i] = ((int32_t)(ray_half + LED_HALF - diff) *
+                                       intensity) / LED_WIDTH;
+       }
+}
+
+static void
+led1642_write(uint8_t cmd, uint16_t data)
+{
+       uint8_t i;
+
+       LED1642_PORT = 0;
+       for (i = 0; i < 16; i++, data <<= 1) {
+               if (data & 0x8000)
+                       LED1642_PORT |= (1 << SDI_BIT);
+               else
+                       LED1642_PORT &= ~(1 << SDI_BIT);
+               if (i + cmd >= 16)
+                       LED1642_PORT |= (1 << LE_BIT);
+
+               LED1642_PORT |= (1 << CLK_BIT);
+               LED1642_PORT &= ~(1 << CLK_BIT);
+       }
+       LED1642_PORT = 0;
+}
+
+static uint16_t
+led1642_read(uint8_t cmd)
+{
+       uint8_t i;
+       uint16_t data = 0;
+
+       led1642_write(cmd, 0);
+       for (i = 0; i < 16; i++) {
+               LED1642_PORT |= (1 << CLK_BIT);
+               LED1642_PORT &= ~(1 << CLK_BIT);
+               data <<= 1;
+               if (LED1642_PIN & (1 << SDO_BIT))
+                       data |= 1;
+       }
+       LED1642_PORT = 0;
+       return data;
+}
+
+/* send gyro_leds[] to led1642 */
+static void
+gyro_update_leds(void)
+{
+       uint16_t data, cmd;
+       uint8_t i;
+
+       for (i = 0; i < 16; i++) {
+               if (i >= 4)
+                       data = (gyro_leds[i - 4]) >> 4;
+               else
+                       data = 0;
+               if (i != 15)
+                       cmd = LED1642_CMD_DATA_LATCH;
+               else
+                       cmd = LED1642_CMD_GLOBAL_LATCH;
+
+               led1642_write(cmd, data);
+       }
+}
+
+void led_gyro_off(void)
+{
+       callout_stop(&callout_mgr, &gyro_timer);
+       memset(gyro_leds, 0, sizeof(gyro_leds));
+       gyro_update_leds();
+}
+
+static void gyro_update_cb(struct callout_mgr *cm,
+                       struct callout *tim, void *arg)
+{
+       uint16_t intensity;
+       uint8_t i, update = 0;
+
+       (void)arg;
+
+       gyro_state.ms += GYRO_UPDATE_MS;
+       if (gyro_state.ms >= gyro_state.stop_ms) {
+               led_gyro_off();
+               return;
+       }
+
+       switch (gyro_state.type) {
+       case GYRO_TYPE_STATIC:
+               break;
+       case GYRO_TYPE_BLINK:
+               gyro_state.blink.period_ms += GYRO_UPDATE_MS;
+               if (gyro_state.blink.period_ms > gyro_state.blink.on_ms +
+                               gyro_state.blink.off_ms) {
+                       intensity = gyro_state.blink.intensity;
+                       gyro_state.blink.period_ms -= gyro_state.blink.on_ms +
+                               gyro_state.blink.off_ms;
+                       update = 1;
+               } else if (gyro_state.blink.period_ms > gyro_state.blink.on_ms) {
+                       intensity = 0;
+                       update = 1;
+               }
+               if (update) {
+                       for (i = 0; i < GYRO_N_LEDS; i++)
+                               gyro_leds[i] = intensity;
+               }
+               break;
+       case GYRO_TYPE_ROTATE:
+               gyro_state.rot.angle += gyro_state.rot.speed;
+               __led_gyro_ray(gyro_state.rot.angle, gyro_state.rot.intensity,
+                       gyro_state.rot.width);
+               update = 1;
+               break;
+       default:
+               break;
+       }
+
+       if (update)
+               gyro_update_leds();
+
+       callout_reschedule(cm, tim, GYRO_UPDATE_MS);
+}
+
+void led_debug_init(void)
+{
+       DDRB |= (1 << 6); /* debug led as output */
+}
+
+void led_debug_on(void)
+{
+       PORTB |= (1 << 6);
+}
+
+void led_debug_off(void)
+{
+       PORTB &= (~(1 << 6));
+}
+
+void led_debug_toggle(void)
+{
+       PORTB ^= (1 << 6);
+}
+
+void led_gyro_all(uint16_t intensity, uint16_t blink_on_ms,
+       uint16_t blink_off_ms, uint16_t stop_ms)
+{
+       uint8_t i;
+
+       callout_stop(&callout_mgr, &gyro_timer);
+       printf_P(PSTR("%u %u %u %u\n"),
+               intensity, blink_on_ms, blink_off_ms, stop_ms);
+       for (i = 0; i < GYRO_N_LEDS; i++)
+               gyro_leds[i] = intensity;
+       gyro_update_leds();
+
+       if (!blink_on_ms || !blink_off_ms) {
+               gyro_state.type = GYRO_TYPE_STATIC;
+               return;
+       }
+
+       gyro_state.type = GYRO_TYPE_BLINK;
+       gyro_state.ms = 0;
+       gyro_state.stop_ms = stop_ms;
+       gyro_state.blink.on_ms = blink_on_ms;
+       gyro_state.blink.off_ms = blink_off_ms;
+       gyro_state.blink.intensity = intensity;
+       gyro_state.blink.period_ms = 0;
+       callout_schedule(&callout_mgr, &gyro_timer, GYRO_UPDATE_MS);
+}
+
+void led_gyro_ray(uint16_t angle, uint16_t intensity, uint16_t width,
+       int16_t rot_speed, uint16_t stop_ms)
+{
+       callout_stop(&callout_mgr, &gyro_timer);
+
+       printf_P(PSTR("led_gyro_ray %u %u %u %u %u\n"),
+               angle, intensity, width, rot_speed, stop_ms);
+       __led_gyro_ray(angle, intensity, width);
+       gyro_update_leds();
+
+       gyro_state.ms = 0;
+       gyro_state.stop_ms = stop_ms;
+       if (rot_speed == 0) {
+               gyro_state.type = GYRO_TYPE_STATIC;
+       } else {
+               gyro_state.type = GYRO_TYPE_ROTATE;
+               gyro_state.rot.angle = angle;
+               gyro_state.rot.speed = rot_speed;
+               gyro_state.rot.intensity = intensity;
+               gyro_state.rot.width = width;
+       }
+       callout_schedule(&callout_mgr, &gyro_timer, GYRO_UPDATE_MS);
+}
+
+void
+led1642_init(void)
+{
+       /* 0 = 9.8mA, 31 = 39.3mA (with current range = 1)*/
+       const uint16_t current = 0;
+       const uint16_t config = (
+               (current << LED1642_CFG_CURRENT) |
+               (1U << LED1642_CFG_CURRENT_RANGE) |
+               (1U << LED1642_CFG_OUT_DELAY) |
+               (1U << LED1642_CFG_PWM12));
+       uint16_t data;
+
+       LED1642_DDR |= (1 << SDI_BIT) | (1 << LE_BIT) | (1 << CLK_BIT) |
+               (1 << PWCLK_BIT);
+
+       /* Fast PWM, reset timer at OCRA, set OC0B at bottom, clear
+        * OC0B at OCR0B */
+       TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
+       TCCR0B = (1 << WGM02) | (1 << CS00);   /* div = 1 */
+       OCR0B = 2;
+       OCR0A = 4;
+
+       LED1642_PORT = 0;
+
+       led1642_write(LED1642_CMD_DATA_SWITCH, 0);
+
+       led1642_write(LED1642_CMD_WRITE_CONFIG, config);
+       data = led1642_read(LED1642_CMD_READ_CONFIG);
+       if (data != config)
+               printf_P(PSTR("data (0x%x) != config (0x%x)\n"),
+                       data, config);
+
+       led1642_write(LED1642_CMD_DATA_SWITCH, 0xffff);
+
+       _delay_ms(1000);
+
+       led_gyro_off();
+
+       /* timer that updates gyro LEDs */
+       callout_init(&gyro_timer, gyro_update_cb, NULL, 128);
+}
diff --git a/src/led.h b/src/led.h
new file mode 100644 (file)
index 0000000..764a902
--- /dev/null
+++ b/src/led.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef _LED_H
+#define _LED_H
+
+void led_debug_init(void);
+void led1642_init(void);
+
+void led_debug_on(void);
+void led_debug_off(void);
+void led_debug_toggle(void);
+
+/**
+ * Control all leds at the same time with specified intensity.
+ *
+ * @param intensity
+ *   Led intensity between 0 and 4095
+ * @param blink_on_ms
+ *   Time during which leds are on when blinking. If 0, don't blink.
+ * @param blink_off_ms
+ *   Time during which leds are off when blinking. If 0, don't blink.
+ *   Note that on + off times must be < 65536.
+ * @param stop_ms
+ *   Time after which all the leds are switched off. If 0, never stop.
+ */
+void led_gyro_all(uint16_t intensity, uint16_t blink_on_ms,
+       uint16_t blink_off_ms, uint16_t stop_ms);
+
+/**
+ * Switch on the leds at specified intensity in one direction.
+ *
+ * @param angle
+ *   Between 0 and 65535.
+ * @param intensity
+ *   Led intensity, between 0 and 4095.
+ * @param width
+ *   The width represents the angle of the enabled zone, between 0
+ *   and 65535 (one led = 6553).
+ * @param rot_speed
+ *   The rotation speed is angle per 10ms (so 655 is ~1 round/sec).
+ * @param stop_ms
+ *   Time after which all the leds are switched off. If 0, never stop.
+ */
+void led_gyro_ray(uint16_t angle, uint16_t intensity, uint16_t width,
+       int16_t rot_speed, uint16_t stop_ms);
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..0012835
--- /dev/null
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <avr/io.h>
+#include <util/delay.h>
+#include <avr/wdt.h>
+#include <avr/pgmspace.h>
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include "irq.h"
+#include "uart.h"
+#include "callout.h"
+#include "commands.h"
+#include "led.h"
+#include "rtc.h"
+#include "buzzer.h"
+
+struct callout_mgr callout_mgr;
+uint8_t log_debug = 0;
+
+/* disable watchdog early at init */
+__attribute__((naked)) __attribute__((section(".init3")))
+void wdt_init(void)
+{
+    MCUSR = 0;
+    wdt_disable();
+}
+
+/* called every ms to manage callouts */
+static void callout_cb(void)
+{
+       callout_manage(&callout_mgr);
+}
+
+int main(void)
+{
+       uint8_t i;
+       char c;
+
+       /* LEDs as output pin */
+       led_debug_init();
+
+       /* toggle the pin */
+       for (i = 0; i < 3; i++) {
+               led_debug_on();
+               _delay_ms(100);
+               led_debug_off();
+               _delay_ms(100);
+       }
+
+       /* uart and stdout initialization */
+       uart_init();
+
+       /* timer interrupt and callout manager */
+       callout_mgr_init(&callout_mgr, rtc_get_ms_u16);
+       rtc_init(&callout_cb);
+
+       /* LEDs as output pin */
+       led1642_init();
+
+       /* timer 1 and buzzer */
+       buzzer_init();
+
+       /* enable interrupts globally */
+       irq_unlock();
+
+       //buzzer(100, 100);
+       //buzzer_vibrato(800, 600, 10000, 800, VIBRATO_TYPE_TRIANGLE);
+
+       printf_P(PSTR("ready\r\n"));
+
+       /* mainloop */
+       while (1) {
+               if (uart_recv(&c) == 0)
+                       cmd_input(c);
+       }
+
+       return 0;
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644 (file)
index 0000000..4ffb195
--- /dev/null
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef MAIN_H_
+#define MAIN_H_
+
+#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
+
+extern struct callout_mgr callout_mgr;
+extern uint8_t log_debug;
+
+#endif
diff --git a/src/queue.h b/src/queue.h
new file mode 100644 (file)
index 0000000..00dab04
--- /dev/null
@@ -0,0 +1,844 @@
+/*
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)queue.h     8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef        _SYS_QUEUE_H_
+#define        _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The
+ * elements are singly linked for minimum space and pointer manipulation
+ * overhead at the expense of O(n) removal for arbitrary elements. New
+ * elements can be added to the list after an existing element or at the
+ * head of the list.  Elements being removed from the head of the list
+ * should use the explicit macro for this purpose for optimum
+ * efficiency. A singly-linked list may only be traversed in the forward
+ * direction.  Singly-linked lists are ideal for applications with large
+ * datasets and few or no removals or for implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+/*
+ * Include the definition of NULL only on NetBSD because sys/null.h
+ * is not available elsewhere.  This conditional makes the header
+ * portable and it can simply be dropped verbatim into any system.
+ * The caveat is that on other systems some other header
+ * must provide NULL before the macros can be used.
+ */
+#ifdef __NetBSD__
+#include <sys/null.h>
+#endif
+
+#if defined(QUEUEDEBUG)
+# if defined(_KERNEL)
+#  define QUEUEDEBUG_ABORT(...) panic(__VA_ARGS__)
+# else
+#  include <err.h>
+#  define QUEUEDEBUG_ABORT(...) err(1, __VA_ARGS__)
+# endif
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define        SLIST_HEAD(name, type)                                          \
+struct name {                                                          \
+       struct type *slh_first; /* first element */                     \
+}
+
+#define        SLIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define        SLIST_ENTRY(type)                                               \
+struct {                                                               \
+       struct type *sle_next;  /* next element */                      \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define        SLIST_FIRST(head)       ((head)->slh_first)
+#define        SLIST_END(head)         NULL
+#define        SLIST_EMPTY(head)       ((head)->slh_first == NULL)
+#define        SLIST_NEXT(elm, field)  ((elm)->field.sle_next)
+
+#define        SLIST_FOREACH(var, head, field)                                 \
+       for((var) = (head)->slh_first;                                  \
+           (var) != SLIST_END(head);                                   \
+           (var) = (var)->field.sle_next)
+
+#define        SLIST_FOREACH_SAFE(var, head, field, tvar)                      \
+       for ((var) = SLIST_FIRST((head));                               \
+           (var) != SLIST_END(head) &&                                 \
+           ((tvar) = SLIST_NEXT((var), field), 1);                     \
+           (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define        SLIST_INIT(head) do {                                           \
+       (head)->slh_first = SLIST_END(head);                            \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \
+       (elm)->field.sle_next = (slistelm)->field.sle_next;             \
+       (slistelm)->field.sle_next = (elm);                             \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_INSERT_HEAD(head, elm, field) do {                        \
+       (elm)->field.sle_next = (head)->slh_first;                      \
+       (head)->slh_first = (elm);                                      \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_REMOVE_AFTER(slistelm, field) do {                        \
+       (slistelm)->field.sle_next =                                    \
+           SLIST_NEXT(SLIST_NEXT((slistelm), field), field);           \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_REMOVE_HEAD(head, field) do {                             \
+       (head)->slh_first = (head)->slh_first->field.sle_next;          \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_REMOVE(head, elm, type, field) do {                       \
+       if ((head)->slh_first == (elm)) {                               \
+               SLIST_REMOVE_HEAD((head), field);                       \
+       }                                                               \
+       else {                                                          \
+               struct type *curelm = (head)->slh_first;                \
+               while(curelm->field.sle_next != (elm))                  \
+                       curelm = curelm->field.sle_next;                \
+               curelm->field.sle_next =                                \
+                   curelm->field.sle_next->field.sle_next;             \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+
+/*
+ * List definitions.
+ */
+#define        LIST_HEAD(name, type)                                           \
+struct name {                                                          \
+       struct type *lh_first;  /* first element */                     \
+}
+
+#define        LIST_HEAD_INITIALIZER(head)                                     \
+       { NULL }
+
+#define        LIST_ENTRY(type)                                                \
+struct {                                                               \
+       struct type *le_next;   /* next element */                      \
+       struct type **le_prev;  /* address of previous next element */  \
+}
+
+/*
+ * List access methods.
+ */
+#define        LIST_FIRST(head)                ((head)->lh_first)
+#define        LIST_END(head)                  NULL
+#define        LIST_EMPTY(head)                ((head)->lh_first == LIST_END(head))
+#define        LIST_NEXT(elm, field)           ((elm)->field.le_next)
+
+#define        LIST_FOREACH(var, head, field)                                  \
+       for ((var) = ((head)->lh_first);                                \
+           (var) != LIST_END(head);                                    \
+           (var) = ((var)->field.le_next))
+
+#define        LIST_FOREACH_SAFE(var, head, field, tvar)                       \
+       for ((var) = LIST_FIRST((head));                                \
+           (var) != LIST_END(head) &&                                  \
+           ((tvar) = LIST_NEXT((var), field), 1);                      \
+           (var) = (tvar))
+
+#define        LIST_MOVE(head1, head2) do {                                    \
+       LIST_INIT((head2));                                             \
+       if (!LIST_EMPTY((head1))) {                                     \
+               (head2)->lh_first = (head1)->lh_first;                  \
+               LIST_INIT((head1));                                     \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+/*
+ * List functions.
+ */
+#if defined(QUEUEDEBUG)
+#define        QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field)                   \
+       if ((head)->lh_first &&                                         \
+           (head)->lh_first->field.le_prev != &(head)->lh_first)       \
+               QUEUEDEBUG_ABORT("LIST_INSERT_HEAD %p %s:%d", (head),   \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_LIST_OP(elm, field)                                  \
+       if ((elm)->field.le_next &&                                     \
+           (elm)->field.le_next->field.le_prev !=                      \
+           &(elm)->field.le_next)                                      \
+               QUEUEDEBUG_ABORT("LIST_* forw %p %s:%d", (elm),         \
+                   __FILE__, __LINE__);                                \
+       if (*(elm)->field.le_prev != (elm))                             \
+               QUEUEDEBUG_ABORT("LIST_* back %p %s:%d", (elm),         \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_LIST_POSTREMOVE(elm, field)                          \
+       (elm)->field.le_next = (void *)1L;                              \
+       (elm)->field.le_prev = (void *)1L;
+#else
+#define        QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field)
+#define        QUEUEDEBUG_LIST_OP(elm, field)
+#define        QUEUEDEBUG_LIST_POSTREMOVE(elm, field)
+#endif
+
+#define        LIST_INIT(head) do {                                            \
+       (head)->lh_first = LIST_END(head);                              \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_INSERT_AFTER(listelm, elm, field) do {                     \
+       QUEUEDEBUG_LIST_OP((listelm), field)                            \
+       if (((elm)->field.le_next = (listelm)->field.le_next) !=        \
+           LIST_END(head))                                             \
+               (listelm)->field.le_next->field.le_prev =               \
+                   &(elm)->field.le_next;                              \
+       (listelm)->field.le_next = (elm);                               \
+       (elm)->field.le_prev = &(listelm)->field.le_next;               \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_INSERT_BEFORE(listelm, elm, field) do {                    \
+       QUEUEDEBUG_LIST_OP((listelm), field)                            \
+       (elm)->field.le_prev = (listelm)->field.le_prev;                \
+       (elm)->field.le_next = (listelm);                               \
+       *(listelm)->field.le_prev = (elm);                              \
+       (listelm)->field.le_prev = &(elm)->field.le_next;               \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_INSERT_HEAD(head, elm, field) do {                         \
+       QUEUEDEBUG_LIST_INSERT_HEAD((head), (elm), field)               \
+       if (((elm)->field.le_next = (head)->lh_first) != LIST_END(head))\
+               (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+       (head)->lh_first = (elm);                                       \
+       (elm)->field.le_prev = &(head)->lh_first;                       \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_REMOVE(elm, field) do {                                    \
+       QUEUEDEBUG_LIST_OP((elm), field)                                \
+       if ((elm)->field.le_next != NULL)                               \
+               (elm)->field.le_next->field.le_prev =                   \
+                   (elm)->field.le_prev;                               \
+       *(elm)->field.le_prev = (elm)->field.le_next;                   \
+       QUEUEDEBUG_LIST_POSTREMOVE((elm), field)                        \
+} while (/*CONSTCOND*/0)
+
+#define LIST_REPLACE(elm, elm2, field) do {                            \
+       if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)     \
+               (elm2)->field.le_next->field.le_prev =                  \
+                   &(elm2)->field.le_next;                             \
+       (elm2)->field.le_prev = (elm)->field.le_prev;                   \
+       *(elm2)->field.le_prev = (elm2);                                \
+       QUEUEDEBUG_LIST_POSTREMOVE((elm), field)                        \
+} while (/*CONSTCOND*/0)
+
+/*
+ * Simple queue definitions.
+ */
+#define        SIMPLEQ_HEAD(name, type)                                        \
+struct name {                                                          \
+       struct type *sqh_first; /* first element */                     \
+       struct type **sqh_last; /* addr of last next element */         \
+}
+
+#define        SIMPLEQ_HEAD_INITIALIZER(head)                                  \
+       { NULL, &(head).sqh_first }
+
+#define        SIMPLEQ_ENTRY(type)                                             \
+struct {                                                               \
+       struct type *sqe_next;  /* next element */                      \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define        SIMPLEQ_FIRST(head)             ((head)->sqh_first)
+#define        SIMPLEQ_END(head)               NULL
+#define        SIMPLEQ_EMPTY(head)             ((head)->sqh_first == SIMPLEQ_END(head))
+#define        SIMPLEQ_NEXT(elm, field)        ((elm)->field.sqe_next)
+
+#define        SIMPLEQ_FOREACH(var, head, field)                               \
+       for ((var) = ((head)->sqh_first);                               \
+           (var) != SIMPLEQ_END(head);                                 \
+           (var) = ((var)->field.sqe_next))
+
+#define        SIMPLEQ_FOREACH_SAFE(var, head, field, next)                    \
+       for ((var) = ((head)->sqh_first);                               \
+           (var) != SIMPLEQ_END(head) &&                               \
+           ((next = ((var)->field.sqe_next)), 1);                      \
+           (var) = (next))
+
+/*
+ * Simple queue functions.
+ */
+#define        SIMPLEQ_INIT(head) do {                                         \
+       (head)->sqh_first = NULL;                                       \
+       (head)->sqh_last = &(head)->sqh_first;                          \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_INSERT_HEAD(head, elm, field) do {                      \
+       if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)        \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (head)->sqh_first = (elm);                                      \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_INSERT_TAIL(head, elm, field) do {                      \
+       (elm)->field.sqe_next = NULL;                                   \
+       *(head)->sqh_last = (elm);                                      \
+       (head)->sqh_last = &(elm)->field.sqe_next;                      \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {            \
+       if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (listelm)->field.sqe_next = (elm);                              \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_REMOVE_HEAD(head, field) do {                           \
+       if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+               (head)->sqh_last = &(head)->sqh_first;                  \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do {                    \
+       if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+           == NULL)                                                    \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_REMOVE(head, elm, type, field) do {                     \
+       if ((head)->sqh_first == (elm)) {                               \
+               SIMPLEQ_REMOVE_HEAD((head), field);                     \
+       } else {                                                        \
+               struct type *curelm = (head)->sqh_first;                \
+               while (curelm->field.sqe_next != (elm))                 \
+                       curelm = curelm->field.sqe_next;                \
+               if ((curelm->field.sqe_next =                           \
+                       curelm->field.sqe_next->field.sqe_next) == NULL) \
+                           (head)->sqh_last = &(curelm)->field.sqe_next; \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_CONCAT(head1, head2) do {                               \
+       if (!SIMPLEQ_EMPTY((head2))) {                                  \
+               *(head1)->sqh_last = (head2)->sqh_first;                \
+               (head1)->sqh_last = (head2)->sqh_last;          \
+               SIMPLEQ_INIT((head2));                                  \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_LAST(head, type, field)                                 \
+       (SIMPLEQ_EMPTY((head)) ?                                                \
+               NULL :                                                  \
+               ((struct type *)(void *)                                \
+               ((char *)((head)->sqh_last) - offsetof(struct type, field))))
+
+/*
+ * Tail queue definitions.
+ */
+#define        _TAILQ_HEAD(name, type, qual)                                   \
+struct name {                                                          \
+       qual type *tqh_first;           /* first element */             \
+       qual type *qual *tqh_last;      /* addr of last next element */ \
+}
+#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
+
+#define        TAILQ_HEAD_INITIALIZER(head)                                    \
+       { TAILQ_END(head), &(head).tqh_first }
+
+#define        _TAILQ_ENTRY(type, qual)                                        \
+struct {                                                               \
+       qual type *tqe_next;            /* next element */              \
+       qual type *qual *tqe_prev;      /* address of previous next element */\
+}
+#define TAILQ_ENTRY(type)      _TAILQ_ENTRY(struct type,)
+
+/*
+ * Tail queue access methods.
+ */
+#define        TAILQ_FIRST(head)               ((head)->tqh_first)
+#define        TAILQ_END(head)                 (NULL)
+#define        TAILQ_NEXT(elm, field)          ((elm)->field.tqe_next)
+#define        TAILQ_LAST(head, headname) \
+       (*(((struct headname *)((head)->tqh_last))->tqh_last))
+#define        TAILQ_PREV(elm, headname, field) \
+       (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define        TAILQ_EMPTY(head)               (TAILQ_FIRST(head) == TAILQ_END(head))
+
+
+#define        TAILQ_FOREACH(var, head, field)                                 \
+       for ((var) = ((head)->tqh_first);                               \
+           (var) != TAILQ_END(head);                                   \
+           (var) = ((var)->field.tqe_next))
+
+#define        TAILQ_FOREACH_SAFE(var, head, field, next)                      \
+       for ((var) = ((head)->tqh_first);                               \
+           (var) != TAILQ_END(head) &&                                 \
+           ((next) = TAILQ_NEXT(var, field), 1); (var) = (next))
+
+#define        TAILQ_FOREACH_REVERSE(var, head, headname, field)               \
+       for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\
+           (var) != TAILQ_END(head);                                   \
+           (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))
+
+#define        TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev)    \
+       for ((var) = TAILQ_LAST((head), headname);                      \
+           (var) != TAILQ_END(head) &&                                 \
+           ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev))
+
+/*
+ * Tail queue functions.
+ */
+#if defined(QUEUEDEBUG)
+#define        QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field)                  \
+       if ((head)->tqh_first &&                                        \
+           (head)->tqh_first->field.tqe_prev != &(head)->tqh_first)    \
+               QUEUEDEBUG_ABORT("TAILQ_INSERT_HEAD %p %s:%d", (head),  \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field)                  \
+       if (*(head)->tqh_last != NULL)                                  \
+               QUEUEDEBUG_ABORT("TAILQ_INSERT_TAIL %p %s:%d", (head),  \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_OP(elm, field)                                 \
+       if ((elm)->field.tqe_next &&                                    \
+           (elm)->field.tqe_next->field.tqe_prev !=                    \
+           &(elm)->field.tqe_next)                                     \
+               QUEUEDEBUG_ABORT("TAILQ_* forw %p %s:%d", (elm),        \
+                   __FILE__, __LINE__);                                \
+       if (*(elm)->field.tqe_prev != (elm))                            \
+               QUEUEDEBUG_ABORT("TAILQ_* back %p %s:%d", (elm),        \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field)                    \
+       if ((elm)->field.tqe_next == NULL &&                            \
+           (head)->tqh_last != &(elm)->field.tqe_next)                 \
+               QUEUEDEBUG_ABORT("TAILQ_PREREMOVE head %p elm %p %s:%d",\
+                   (head), (elm), __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field)                         \
+       (elm)->field.tqe_next = (void *)1L;                             \
+       (elm)->field.tqe_prev = (void *)1L;
+#else
+#define        QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field)
+#define        QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field)
+#define        QUEUEDEBUG_TAILQ_OP(elm, field)
+#define        QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field)
+#define        QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field)
+#endif
+
+#define        TAILQ_INIT(head) do {                                           \
+       (head)->tqh_first = TAILQ_END(head);                            \
+       (head)->tqh_last = &(head)->tqh_first;                          \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_HEAD(head, elm, field) do {                        \
+       QUEUEDEBUG_TAILQ_INSERT_HEAD((head), (elm), field)              \
+       if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\
+               (head)->tqh_first->field.tqe_prev =                     \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (head)->tqh_first = (elm);                                      \
+       (elm)->field.tqe_prev = &(head)->tqh_first;                     \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_TAIL(head, elm, field) do {                        \
+       QUEUEDEBUG_TAILQ_INSERT_TAIL((head), (elm), field)              \
+       (elm)->field.tqe_next = TAILQ_END(head);                        \
+       (elm)->field.tqe_prev = (head)->tqh_last;                       \
+       *(head)->tqh_last = (elm);                                      \
+       (head)->tqh_last = &(elm)->field.tqe_next;                      \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_AFTER(head, listelm, elm, field) do {              \
+       QUEUEDEBUG_TAILQ_OP((listelm), field)                           \
+       if (((elm)->field.tqe_next = (listelm)->field.tqe_next) !=      \
+           TAILQ_END(head))                                            \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (listelm)->field.tqe_next = (elm);                              \
+       (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
+       QUEUEDEBUG_TAILQ_OP((listelm), field)                           \
+       (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
+       (elm)->field.tqe_next = (listelm);                              \
+       *(listelm)->field.tqe_prev = (elm);                             \
+       (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_REMOVE(head, elm, field) do {                             \
+       QUEUEDEBUG_TAILQ_PREREMOVE((head), (elm), field)                \
+       QUEUEDEBUG_TAILQ_OP((elm), field)                               \
+       if (((elm)->field.tqe_next) != TAILQ_END(head))                 \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   (elm)->field.tqe_prev;                              \
+       else                                                            \
+               (head)->tqh_last = (elm)->field.tqe_prev;               \
+       *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
+       QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field);                      \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do {                     \
+        if (((elm2)->field.tqe_next = (elm)->field.tqe_next) !=        \
+           TAILQ_END(head))                                            \
+                (elm2)->field.tqe_next->field.tqe_prev =               \
+                    &(elm2)->field.tqe_next;                           \
+        else                                                           \
+                (head)->tqh_last = &(elm2)->field.tqe_next;            \
+        (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                        \
+        *(elm2)->field.tqe_prev = (elm2);                              \
+       QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field);                      \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_CONCAT(head1, head2, field) do {                          \
+       if (!TAILQ_EMPTY(head2)) {                                      \
+               *(head1)->tqh_last = (head2)->tqh_first;                \
+               (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+               (head1)->tqh_last = (head2)->tqh_last;                  \
+               TAILQ_INIT((head2));                                    \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define        STAILQ_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *stqh_first;        /* first element */             \
+       struct type **stqh_last;        /* addr of last next element */ \
+}
+
+#define        STAILQ_HEAD_INITIALIZER(head)                                   \
+       { NULL, &(head).stqh_first }
+
+#define        STAILQ_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *stqe_next; /* next element */                      \
+}
+
+/*
+ * Singly-linked Tail queue access methods.
+ */
+#define        STAILQ_FIRST(head)      ((head)->stqh_first)
+#define        STAILQ_END(head)        NULL
+#define        STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+#define        STAILQ_EMPTY(head)      (STAILQ_FIRST(head) == STAILQ_END(head))
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define        STAILQ_INIT(head) do {                                          \
+       (head)->stqh_first = NULL;                                      \
+       (head)->stqh_last = &(head)->stqh_first;                                \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_INSERT_HEAD(head, elm, field) do {                       \
+       if (((elm)->field.stqe_next = (head)->stqh_first) == NULL)      \
+               (head)->stqh_last = &(elm)->field.stqe_next;            \
+       (head)->stqh_first = (elm);                                     \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_INSERT_TAIL(head, elm, field) do {                       \
+       (elm)->field.stqe_next = NULL;                                  \
+       *(head)->stqh_last = (elm);                                     \
+       (head)->stqh_last = &(elm)->field.stqe_next;                    \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \
+       if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\
+               (head)->stqh_last = &(elm)->field.stqe_next;            \
+       (listelm)->field.stqe_next = (elm);                             \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_REMOVE_HEAD(head, field) do {                            \
+       if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \
+               (head)->stqh_last = &(head)->stqh_first;                        \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_REMOVE(head, elm, type, field) do {                      \
+       if ((head)->stqh_first == (elm)) {                              \
+               STAILQ_REMOVE_HEAD((head), field);                      \
+       } else {                                                        \
+               struct type *curelm = (head)->stqh_first;               \
+               while (curelm->field.stqe_next != (elm))                        \
+                       curelm = curelm->field.stqe_next;               \
+               if ((curelm->field.stqe_next =                          \
+                       curelm->field.stqe_next->field.stqe_next) == NULL) \
+                           (head)->stqh_last = &(curelm)->field.stqe_next; \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_FOREACH(var, head, field)                                \
+       for ((var) = ((head)->stqh_first);                              \
+               (var);                                                  \
+               (var) = ((var)->field.stqe_next))
+
+#define        STAILQ_FOREACH_SAFE(var, head, field, tvar)                     \
+       for ((var) = STAILQ_FIRST((head));                              \
+           (var) && ((tvar) = STAILQ_NEXT((var), field), 1);           \
+           (var) = (tvar))
+
+#define        STAILQ_CONCAT(head1, head2) do {                                \
+       if (!STAILQ_EMPTY((head2))) {                                   \
+               *(head1)->stqh_last = (head2)->stqh_first;              \
+               (head1)->stqh_last = (head2)->stqh_last;                \
+               STAILQ_INIT((head2));                                   \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_LAST(head, type, field)                                  \
+       (STAILQ_EMPTY((head)) ?                                         \
+               NULL :                                                  \
+               ((struct type *)(void *)                                \
+               ((char *)((head)->stqh_last) - offsetof(struct type, field))))
+
+
+#ifndef _KERNEL
+/*
+ * Circular queue definitions. Do not use. We still keep the macros
+ * for compatibility but because of pointer aliasing issues their use
+ * is discouraged!
+ */
+
+/*
+ * __launder_type():  We use this ugly hack to work around the the compiler
+ * noticing that two types may not alias each other and elide tests in code.
+ * We hit this in the CIRCLEQ macros when comparing 'struct name *' and
+ * 'struct type *' (see CIRCLEQ_HEAD()).  Modern compilers (such as GCC
+ * 4.8) declare these comparisons as always false, causing the code to
+ * not run as designed.
+ *
+ * This hack is only to be used for comparisons and thus can be fully const.
+ * Do not use for assignment.
+ *
+ * If we ever choose to change the ABI of the CIRCLEQ macros, we could fix
+ * this by changing the head/tail sentinal values, but see the note above
+ * this one.
+ */
+static __inline const void * __launder_type(const void *);
+static __inline const void *
+__launder_type(const void *__x)
+{
+       __asm __volatile("" : "+r" (__x));
+       return __x;
+}
+
+#if defined(QUEUEDEBUG)
+#define QUEUEDEBUG_CIRCLEQ_HEAD(head, field)                           \
+       if ((head)->cqh_first != CIRCLEQ_ENDC(head) &&                  \
+           (head)->cqh_first->field.cqe_prev != CIRCLEQ_ENDC(head))    \
+               QUEUEDEBUG_ABORT("CIRCLEQ head forw %p %s:%d", (head),  \
+                     __FILE__, __LINE__);                              \
+       if ((head)->cqh_last != CIRCLEQ_ENDC(head) &&                   \
+           (head)->cqh_last->field.cqe_next != CIRCLEQ_ENDC(head))     \
+               QUEUEDEBUG_ABORT("CIRCLEQ head back %p %s:%d", (head),  \
+                     __FILE__, __LINE__);
+#define QUEUEDEBUG_CIRCLEQ_ELM(head, elm, field)                       \
+       if ((elm)->field.cqe_next == CIRCLEQ_ENDC(head)) {              \
+               if ((head)->cqh_last != (elm))                          \
+                       QUEUEDEBUG_ABORT("CIRCLEQ elm last %p %s:%d",   \
+                           (elm), __FILE__, __LINE__);                 \
+       } else {                                                        \
+               if ((elm)->field.cqe_next->field.cqe_prev != (elm))     \
+                       QUEUEDEBUG_ABORT("CIRCLEQ elm forw %p %s:%d",   \
+                           (elm), __FILE__, __LINE__);                 \
+       }                                                               \
+       if ((elm)->field.cqe_prev == CIRCLEQ_ENDC(head)) {              \
+               if ((head)->cqh_first != (elm))                         \
+                       QUEUEDEBUG_ABORT("CIRCLEQ elm first %p %s:%d",  \
+                           (elm), __FILE__, __LINE__);                 \
+       } else {                                                        \
+               if ((elm)->field.cqe_prev->field.cqe_next != (elm))     \
+                       QUEUEDEBUG_ABORT("CIRCLEQ elm prev %p %s:%d",   \
+                           (elm), __FILE__, __LINE__);                 \
+       }
+#define QUEUEDEBUG_CIRCLEQ_POSTREMOVE(elm, field)                      \
+       (elm)->field.cqe_next = (void *)1L;                             \
+       (elm)->field.cqe_prev = (void *)1L;
+#else
+#define QUEUEDEBUG_CIRCLEQ_HEAD(head, field)
+#define QUEUEDEBUG_CIRCLEQ_ELM(head, elm, field)
+#define QUEUEDEBUG_CIRCLEQ_POSTREMOVE(elm, field)
+#endif
+
+#define        CIRCLEQ_HEAD(name, type)                                        \
+struct name {                                                          \
+       struct type *cqh_first;         /* first element */             \
+       struct type *cqh_last;          /* last element */              \
+}
+
+#define        CIRCLEQ_HEAD_INITIALIZER(head)                                  \
+       { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+
+#define        CIRCLEQ_ENTRY(type)                                             \
+struct {                                                               \
+       struct type *cqe_next;          /* next element */              \
+       struct type *cqe_prev;          /* previous element */          \
+}
+
+/*
+ * Circular queue functions.
+ */
+#define        CIRCLEQ_INIT(head) do {                                         \
+       (head)->cqh_first = CIRCLEQ_END(head);                          \
+       (head)->cqh_last = CIRCLEQ_END(head);                           \
+} while (/*CONSTCOND*/0)
+
+#define        CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {            \
+       QUEUEDEBUG_CIRCLEQ_HEAD((head), field)                          \
+       QUEUEDEBUG_CIRCLEQ_ELM((head), (listelm), field)                \
+       (elm)->field.cqe_next = (listelm)->field.cqe_next;              \
+       (elm)->field.cqe_prev = (listelm);                              \
+       if ((listelm)->field.cqe_next == CIRCLEQ_ENDC(head))            \
+               (head)->cqh_last = (elm);                               \
+       else                                                            \
+               (listelm)->field.cqe_next->field.cqe_prev = (elm);      \
+       (listelm)->field.cqe_next = (elm);                              \
+} while (/*CONSTCOND*/0)
+
+#define        CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {           \
+       QUEUEDEBUG_CIRCLEQ_HEAD((head), field)                          \
+       QUEUEDEBUG_CIRCLEQ_ELM((head), (listelm), field)                \
+       (elm)->field.cqe_next = (listelm);                              \
+       (elm)->field.cqe_prev = (listelm)->field.cqe_prev;              \
+       if ((listelm)->field.cqe_prev == CIRCLEQ_ENDC(head))            \
+               (head)->cqh_first = (elm);                              \
+       else                                                            \
+               (listelm)->field.cqe_prev->field.cqe_next = (elm);      \
+       (listelm)->field.cqe_prev = (elm);                              \
+} while (/*CONSTCOND*/0)
+
+#define        CIRCLEQ_INSERT_HEAD(head, elm, field) do {                      \
+       QUEUEDEBUG_CIRCLEQ_HEAD((head), field)                          \
+       (elm)->field.cqe_next = (head)->cqh_first;                      \
+       (elm)->field.cqe_prev = CIRCLEQ_END(head);                      \
+       if ((head)->cqh_last == CIRCLEQ_ENDC(head))                     \
+               (head)->cqh_last = (elm);                               \
+       else                                                            \
+               (head)->cqh_first->field.cqe_prev = (elm);              \
+       (head)->cqh_first = (elm);                                      \
+} while (/*CONSTCOND*/0)
+
+#define        CIRCLEQ_INSERT_TAIL(head, elm, field) do {                      \
+       QUEUEDEBUG_CIRCLEQ_HEAD((head), field)                          \
+       (elm)->field.cqe_next = CIRCLEQ_END(head);                      \
+       (elm)->field.cqe_prev = (head)->cqh_last;                       \
+       if ((head)->cqh_first == CIRCLEQ_ENDC(head))                    \
+               (head)->cqh_first = (elm);                              \
+       else                                                            \
+               (head)->cqh_last->field.cqe_next = (elm);               \
+       (head)->cqh_last = (elm);                                       \
+} while (/*CONSTCOND*/0)
+
+#define        CIRCLEQ_REMOVE(head, elm, field) do {                           \
+       QUEUEDEBUG_CIRCLEQ_HEAD((head), field)                          \
+       QUEUEDEBUG_CIRCLEQ_ELM((head), (elm), field)                    \
+       if ((elm)->field.cqe_next == CIRCLEQ_ENDC(head))                \
+               (head)->cqh_last = (elm)->field.cqe_prev;               \
+       else                                                            \
+               (elm)->field.cqe_next->field.cqe_prev =                 \
+                   (elm)->field.cqe_prev;                              \
+       if ((elm)->field.cqe_prev == CIRCLEQ_ENDC(head))                \
+               (head)->cqh_first = (elm)->field.cqe_next;              \
+       else                                                            \
+               (elm)->field.cqe_prev->field.cqe_next =                 \
+                   (elm)->field.cqe_next;                              \
+       QUEUEDEBUG_CIRCLEQ_POSTREMOVE((elm), field)                     \
+} while (/*CONSTCOND*/0)
+
+#define        CIRCLEQ_FOREACH(var, head, field)                               \
+       for ((var) = ((head)->cqh_first);                               \
+               (var) != CIRCLEQ_ENDC(head);                            \
+               (var) = ((var)->field.cqe_next))
+
+#define        CIRCLEQ_FOREACH_REVERSE(var, head, field)                       \
+       for ((var) = ((head)->cqh_last);                                \
+               (var) != CIRCLEQ_ENDC(head);                            \
+               (var) = ((var)->field.cqe_prev))
+
+/*
+ * Circular queue access methods.
+ */
+#define        CIRCLEQ_FIRST(head)             ((head)->cqh_first)
+#define        CIRCLEQ_LAST(head)              ((head)->cqh_last)
+/* For comparisons */
+#define        CIRCLEQ_ENDC(head)              (__launder_type(head))
+/* For assignments */
+#define        CIRCLEQ_END(head)               ((void *)(head))
+#define        CIRCLEQ_NEXT(elm, field)        ((elm)->field.cqe_next)
+#define        CIRCLEQ_PREV(elm, field)        ((elm)->field.cqe_prev)
+#define        CIRCLEQ_EMPTY(head)                                             \
+    (CIRCLEQ_FIRST(head) == CIRCLEQ_ENDC(head))
+
+#define CIRCLEQ_LOOP_NEXT(head, elm, field)                            \
+       (((elm)->field.cqe_next == CIRCLEQ_ENDC(head))                  \
+           ? ((head)->cqh_first)                                       \
+           : (elm->field.cqe_next))
+#define CIRCLEQ_LOOP_PREV(head, elm, field)                            \
+       (((elm)->field.cqe_prev == CIRCLEQ_ENDC(head))                  \
+           ? ((head)->cqh_last)                                        \
+           : (elm->field.cqe_prev))
+#endif /* !_KERNEL */
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/src/rtc.c b/src/rtc.c
new file mode 100644 (file)
index 0000000..dd72a9f
--- /dev/null
+++ b/src/rtc.c
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <avr/io.h>
+
+#include <stddef.h>
+
+#include "irq.h"
+#include "main.h"
+#include "callout.h"
+
+static volatile uint32_t global_ms;
+static void (*rtc_cb)(void);
+
+void rtc_init(void (*cb)(void))
+{
+       /* timer2 interrupt */
+       BUILD_BUG_ON(F_CPU != 8000000UL);
+       TCCR2B = (1 << CS21);   /* div = 8 */
+       TIMSK2 |= (1 << TOIE2); /* enable intr */
+       rtc_cb = cb;
+}
+
+/* interrupt every 256us */
+SIGNAL(TIMER2_OVF_vect)
+{
+       static uint16_t i = 0;
+
+       i++;
+       if ((i & 0x3) != 0)
+               return;
+
+       global_ms++;
+       if (rtc_cb != NULL)
+               rtc_cb();
+}
+
+/* return the absolute time in ms */
+uint32_t rtc_get_ms_u32(void)
+{
+       irqflags_t flags;
+       uint32_t ms;
+
+       flags = irq_lock_save();
+       ms = global_ms;
+       irq_unlock_restore(flags);
+       return ms;
+}
+
+uint16_t rtc_get_ms_u16(void)
+{
+       return (uint16_t)rtc_get_ms_u32();
+}
diff --git a/src/rtc.h b/src/rtc.h
new file mode 100644 (file)
index 0000000..ef9dbd2
--- /dev/null
+++ b/src/rtc.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef _RTC_H
+#define _RTC_H
+
+/* Rtc initialization. The cb is executed every ms. */
+void rtc_init(void (*cb)(void));
+
+/* return the absolute time in ms */
+uint16_t rtc_get_ms_u16(void);
+
+/* return the absolute time in ms */
+uint32_t rtc_get_ms_u32(void);
+
+#endif
diff --git a/src/uart.c b/src/uart.c
new file mode 100644 (file)
index 0000000..86c15ef
--- /dev/null
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <irq.h>
+
+#include "cirbuf.h"
+#include "main.h"
+#include "uart.h"
+
+#define UART_RXQ_LEN 16
+struct cirbuf uart_rxq;
+static char uart_buf[UART_RXQ_LEN];
+
+SIGNAL(USART_RX_vect)
+{
+       char c = UDR0;
+       cirbuf_add_tail_safe(&uart_rxq, c);
+}
+
+void uart_send(char c)
+{
+       while (!(UCSR0A & (1 << UDRE0)))
+               ;
+       UDR0 = c;
+}
+
+/* used by printf */
+static int stdout_send(char c, FILE *f)
+{
+       (void)f;
+       uart_send(c);
+       return 0;
+}
+
+int8_t uart_recv(char *c)
+{
+       int8_t ret = -1;
+       irqflags_t flags;
+
+       flags = irq_lock_save();
+       if (!cirbuf_is_empty(&uart_rxq)) {
+               *c = cirbuf_get_head(&uart_rxq);
+               cirbuf_del_head(&uart_rxq);
+               ret = 0;
+       }
+       irq_unlock_restore(flags);
+
+       return ret;
+}
+
+void uart_init(void)
+{
+       cirbuf_init(&uart_rxq, uart_buf, 0, UART_RXQ_LEN);
+
+       /* 57600 bauds @ 8Mhz */
+       BUILD_BUG_ON(F_CPU != 8000000UL);
+       UBRR0H = 0;
+       UBRR0L = 16;
+       UCSR0A = (1 << U2X0);
+       UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
+       UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
+
+       fdevopen(stdout_send, NULL);
+}
diff --git a/src/uart.h b/src/uart.h
new file mode 100644 (file)
index 0000000..c4039f4
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef UART_H_
+#define UART_H_
+
+#include <stdio.h>
+
+#include "cirbuf.h"
+
+extern struct cirbuf uart_rxq;
+
+/* Initialise uart hardware. */
+void uart_init(void);
+
+/* Synchronous uart send. */
+void uart_send(char c);
+
+/* Receive a character from the rxq.
+ * Return 0 on success, -1 if queue is empty. */
+int8_t uart_recv(char *c);
+
+#endif