Skip to content

Commit

Permalink
Rewrite build system
Browse files Browse the repository at this point in the history
The new build system is much more flexible and designed with porting to other
devices in mind. The multiple device support is just a stub to be completed
when the first porting happens, but almost everything is there.
See README.md for more details.
  • Loading branch information
ReservedField committed Aug 23, 2016
1 parent b67bcc4 commit a8762ee
Show file tree
Hide file tree
Showing 12 changed files with 2,257 additions and 395 deletions.
379 changes: 205 additions & 174 deletions Makefile

Large diffs are not rendered by default.

283 changes: 234 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ brew install gcc-arm-none-eabi

On Windows, first install the [precompiled ARM toolchain](https://launchpad.net/gcc-arm-embedded).
Choose an installation path without spaces to avoid problems with the build process.
If you already have a working Windows `make`, along with utilities such as `awk`, `grep`, `sed`,
`tr`, `git`, etc. you can go ahead, but make sure those binaries are in your `PATH`. Otherwise,
install [Cygwin](https://www.cygwin.com/) and add the following packages on top of the base install:
If you already have a working Windows `make` and `git` along with standard GNU utilities you can go
ahead, but make sure those binaries are in your `PATH`. Otherwise, install [Cygwin](https://www.cygwin.com/)
and add the following packages on top of the base install:
```
make
git
Expand Down Expand Up @@ -101,8 +101,8 @@ Installation
from Nuvoton and copy the `Library` folder inside `evic-sdk/nuvoton-sdk`, as to have
`evic-sdk/nuvoton-sdk/Library`.

3. Point the `EVICSDK` environment variable to the `evic-sdk` folder. This should do (assuming your
current directory is evic-sdk):
3. Point the `EVICSDK` environment variable to the `evic-sdk` folder. For example, if you're using
bash and are in the SDK directory:
```
echo "export EVICSDK=$(pwd)" >> $HOME/.bashrc
```
Expand All @@ -113,45 +113,25 @@ Installation
make
```

At this point, the SDK should be fully set up. You can also generate Doxygen documentation with:
```
make docs
```
To clean up the build (for example if you want to do a full rebuild), use the standard:
```
make clean
```

Building your first APROM
--------------------------

The `helloworld` example should be the first thing you try compiling and flashing,
to check that everything is working correctly.
Building is as easy as:
to check that everything is working correctly. Build it like:
```
cd example/helloworld
make
```
To clean you can use `make clean`, as usual.
If the build succeeds, you should now have a `bin/helloworld.bin` file ready to flash.
If the build succeeds, you should now have a `bin/rel/DEVICE/helloworld.bin` file ready to flash.
This file is encrypted and compatible with the official updater.
You can also generate a unencrypted binary:
```
make helloworld_unencrypted.bin
```

Flashing
--------

You can flash the output binary using the official updater. For development,
using `python-evic` is quicker and simpler.
I suggest to backup your dataflash before flashing, in case anything goes south:
using `python-evic` is quicker and simpler:
```
evic-usb dump-dataflash -o data.bin
```
Now, flash:
```
evic-usb upload bin/helloworld.bin
evic-usb upload bin/rel/DEVICE/helloworld.bin
```
If everything went well you should see the "Hello, World." message.

Expand All @@ -163,14 +143,216 @@ over USB is more convenient (as long as the APROM doesn't require significant po
it doesn't fire the atomizer). Similiarly, holding the left button during powerup will force
the system to boot from APROM.

If `python-evic` fails and the eVic won't flash back to a functioning state, don't panic.
Find a Windows/Mac machine (or virtualize one), boot the eVic to LDROM and flash an original
firmware using the official Joyetech updater. It has always worked for me.
Unless you're messing with the LDROM, this is practically unbrickable - you can always boot to
LDROM and restore. Actually, APROM update is always done from LDROM: the official APROM reboots
to LDROM and flashing happens from there.

The build system
----------------

The build system, used both by the SDK and APROMs, works around *specifiers*. A specifier is a
string in the form `device-flavor`, where `device` is a supported device and `flavor` is a build
flavor.

**Supported devices:**

- `evic`: Joyetech eVic VTC Mini
- `all`: all of the above

**Build flavors:**

- `dbg`: debug build
- `rel`: release build
- `all`: all of the above

See below for the differences between debug and release builds. The `all-all` specifier is also
aliased to `all`. There are two classes of make targets:

- `specifier`: performs a build for the specified device and flavor
- `clean-specifier`: cleans the build for the specified device and flavor

Some examples (if more than one command is given, they are alternatives):
```
# Build everything
make all
make all-all
# Clean everything
make clean-all
make clean-all-all
# Build for eVic VTC Mini, debug flavor
make evic-dbg
# Build for eVic VTC Mini, all flavors
make evic-all
# Clean for eVic VTC Mini, release flavor
make clean-evic-rel
# Build for all devices, release flavor
make all-rel
# Clean for all devices, debug flavor
make clean-all-dbg
# Mix & match
make all-rel evic-dbg
```

#### Default targets

While the specifier system lets you specify exactly what you want, it can feel too verbose for
day-to-day development. For this reason, the build system accepts two environment variables:

- `EVICSDK_MAKE_DEFAULT_DEVICE`: default device(s)
- `EVICSDK_MAKE_DEFAULT_FLAVOR`: default flavor(s)

Those can be set to a single device/flavor or to a list of them (for example, default flavors
`dbg rel` and `all` are the same). You can omit the device, the flavor or both from any specifier
and they will be filled in from those variables. Unset or empty variables default to `all`. The
empty target is also aliased to `def`. Some examples:
```
# Build for default device(s) and flavor(s)
make
make def
# Clean for default device(s) and flavor(s)
make clean
# Build for default device(s), debug flavor
make dbg
# Build for eVic VTC Mini, default flavor(s)
make evic
# Clean for default devices(s), release flavor
make clean-rel
```
If you're writing scripts around the build system don't assume the user's defaults and use the
full specifiers (unless you actually want to use the user's defaults).

#### Object files

Objects files for a specific device and flavor live in the `obj/FLAVOR/DEVICE` directory. The
source tree is replicated in there, to avoid conflicts between files with the same name. If
you wanted to compile the file `src/foo/bar.c` for an eVic VTC Mini, debug flavor, you could
issue `make obj/dbg/evic/src/foo/bar.o`.

Unless you're messing with the LDROM, this is practically unbrickable - you can always boot
to LDROM and restore. Actually, APROM update is always done from LDROM - the official firmware
doesn't even contain flash writing routines, it only provides access to the dataflash and the
actual APROM upload happens in LDROM after a reset.
#### SDK builds

SDK output files live in the `lib/FLAVOR/DEVICE` directory. Debug builds enable debug info
generation from the compiler and a fault handler that will display crash and register info, which
can be decoded using the script in `tools/fault-decode`.

You can generate Doxygen documentation (`doc` directory) for the SDK using `make docs`. To clean it
use `make clean-docs`.

#### APROM builds

APROM output files live in the `bin/FLAVOR/DEVICE` directory. The standard Makefile for APROMs
follows this scheme:
```
TARGET = helloworld
OBJS = main.o
include $(EVICSDK)/make/Base.mk
```
`TARGET` is the base name for outputs. `OBJS` is a list of objects to be built. Notice that, even
though objects live in `obj/FLAVOR/DEVICE`, the object list is written as if they were in the same
directory as the sources. For example, `src/foo/bar.c` would appear as `src/foo/bar.o` in the list.
This is done so that you can let the build system worry about devices and flavors (and to preserve
compatibility with Makefiles written for the old build system). Since the source tree is replicated
for the objects, **always use relative paths without `..` components** (you can't have sources at
a higher level than the Makefile). Finally, `make/Base.mk` has to be included (**after** setting
those variables). See the top of `make/Base.mk` for more variables you can use.

The main output is `bin/FLAVOR/DEVICE/TARGET.bin`, which is an encrypted binary compatible with the
official updater. Debug builds enable debug info generation from the compiler and generate some
additional outputs:

- `bin/dbg/DEVICE/TARGET.elf`: ELF output from compilation
- `bin/dbg/DEVICE/TARGET_dec.bin`: unencrypted binary
- `bin/dbg/DEVICE/TARGET.map`: linker map

Also, they are built against the debug SDK, so the fault handler is enabled. All the outputs except
the linker map are targets (so you could `make bin/dbg/DEVICE/TARGET.elf`, for example).

Note that APROMs for a certain device and flavor combination are built against the SDK for that
same device and flavor, so you'll need to have that SDK built for it to succeed. If you see linker
errors about missing `evicsdk-crt0.o`, `-levicsdk` or `-lnuvosdk` chances are you don't have the
needed SDK built.

#### Parallel make

Parallel make (`-j` option) is supported and works as you would expect. However, problems can arise
when you mix build and clean targets. For example, a default rebuild can be performed with
`make clean && make`. For single-threaded make this could be shortened to `make clean def`, because
targets will be made one at a time from left to right. With parallel make you will need to use the
full form because the two targets would likely be made concurrently, breaking the build. Of course,
mixing multiple build targets or multiple clean targets poses no issues.

#### Clang support

Clang is supported: just build with `CC=clang make`. At the moment, the support still depends on
arm-none-eabi GCC for some standard headers and libraries. It will also generate a fair amount of
warnings for SDK builds. **On Cygwin Clang is assumed to be compiled for Cygwin** and not for
Windows (e.g. downloaded via the Cygwin package manager).

#### C++ support

C++ is supported, just name your sources with `.cpp` extensions. The C++ standard library is not
supported. Exception handling and RTTI are disabled. It's more like C with classes than C++. You'll
need to have `arm-none-eabi-g++` installed to compile C++ code (even if you use Clang - we still
need GCC's headers).

#### Legacy clean

If you're migrating from the old build system to the new one you'll probably want to get rid of
the old cruft. Aside from running `make clean` with an old SDK, you can do it manually:

- For the SDK, remove the `lib` directory and all `.o` files (recursively).
- For APROMs, remove the `bin` directory and all `.o` files (recursively).

#### Misc

The following environment/make variables are available:

- `EVICSDK_MAKE_DEBUG`: set to non-empty to enable extra debug output from the build system.
Useful when investigating or reporting bugs.
- `EVICSDK_FPU_DISABLE`: set to non-empty to disable FPU support and lazy stacking. Needs a full
rebuild to avoid mixing FPU and no-FPU objects. Can be useful when debugging very rare, tricky
FP bugs. Do *not* use this unless you fully understand what it means (no, it won't make you
binaries smaller or faster, even if you don't use the FPU).

Reporting bugs
--------------

I invite you to report bugs using the Issues page. Please confirm the bug against the latest SDK
commit and include all information needed to reproduce the bug, like:

- OS, make/compiler/binutils/newlib versions;
- Head commit and branch of the SDK you're using;
- Device and flavor (does it happen in release builds? Debug? Both?);
- Code (if applicable), preferably reduced to a Minimal, Complete and Verifiable (MCV) example;
- Things you tried and didn't work;
- For crashes, the crash dump generated by a debug build;
- For build system bugs, the debug output (i.e. `EVICSDK_MAKE_DEBUG=1 make ...`). Try minimizing
it: if `make evic-dbg` is enough to make it happen, don't send output for `make all`.

Of course if you've already identified the bug in the SDK code you don't need to include as much
information. Be sensible and everyone involved will be happy.

Pull requests
-------------

Pull requests are welcome. A few rules:

- Respect the coding and documentation style of the SDK and refer to the tips & tricks.
- PRs must merge cleanly against the branch they target (preferably rebased on top of the latest
head for that branch).
- No useless commits. Multiple related commits in the same PR are okay, but no merge/revert commits
and no "Add foo" "Fix foo" "Fix foo again" stuff. Familiarize yourself with `git rebase [-i]` and
use it.
- Respect the commit message style (look at the history). The title should tell what the *effect*
of a commit is, not how it does it. Titles should be imperative: "Add foo", not "Adds foo". For
non-trivial commits you're welcome to add further technical details in the commit body. Stick to
80 columns per row.
- If you're fixing a bug [reference it](https://help.github.com/articles/closing-issues-via-commit-messages/)
in the title. If you're fixing a bug that hasn't been reported yet, create an issue first and
reference it in your commit title (you don't need to wait for an answer on the issue - go ahead
with the PR).

It is understood that big or complex changes often won't be perfect, no need to worry about it.
Again, just use common sense and everything will work out.

USB debugging
-------------
Expand All @@ -185,22 +367,25 @@ An example on how to use the port is given in `example/usbdebug`. You can commun
using your favorite serial port terminal. All the line coding parameters (baud rate, parity,
stop bits, data bits) are ignored, so you don't need to worry about them.

Coding guidelines
-----------------
Tips & tricks
-------------

While the SDK does a fairly good job of abstracting the low-level details, you still need to
remember that you're coding on an embedded platform. A few tips that might be useful:

- You should declare all variables shared between your main code and callbacks/interrupt
handlers as `volatile`.
- Try to minimize dynamic memory allocation: all memory not used by data or stack is assigned
to heap, but RAM is only 32kB.
- Declare constant data (such as lookup tables) as `const`: the compiler will place it in
the ROM, reducing RAM usage.
remember that you're coding on an embedded platform. A few tips that might be helpful:

- You should declare variables shared between threads or with callbacks/interrupts as `volatile`.
- Resources are limited: be frugal. Write efficient code.
- Minimize dynamic memory allocation: all memory not used by data or stack is assigned to heap,
but RAM is only 32kB and nobody likes a failed `malloc`.
- Declare constant data (such as lookup tables) as `const`: the compiler will place it in ROM,
reducing RAM usage.
- Prefer `siprintf` over `sprintf`, as it produces much smaller binaries by stripping out the
floating point printing routines. Of course `siprintf` doesn't support floating point numbers,
so if you need to print them and cannot use a fixed-point representation you'll have to live
with the increased binary size.
- C++ is supported, just name your C++ files with `.cpp` extensions. The C++ standard library is
NOT (yet?) supported. It's really bulky and it hardly fits in the ROM/RAM space we have.
Exception handling and RTTI are disabled.
- When using floating point variables, prefer `float` over `double`, because `float` has hardware
support. Using `double`s will pull in some floating point emulation code, making your binaries
larger.
- When using standard floating point functions, prefer the ones with the `f` suffix: hardware
support for `float`s means faster and smaller binaries. If you use `double` functions, they'll
pull in tons of floating point emulation code.
10 changes: 0 additions & 10 deletions linker/fpu.ld

This file was deleted.

23 changes: 23 additions & 0 deletions linker/common.ld → linker/linker.ld
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
/*
* This file is part of eVic SDK.
*
* eVic SDK is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* eVic SDK is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with eVic SDK. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2015-2016 ReservedField
*/

/* Library configurations */
INPUT(evicsdk-crt0.o)
GROUP(-lgcc -lc -lm -lnuvosdk -levicsdk)

/* Memory regions */
MEMORY {
ROM (rx) : ORIGIN = 0x00000000, LENGTH = 128K
Expand Down
10 changes: 0 additions & 10 deletions linker/nofpu.ld

This file was deleted.

Loading

0 comments on commit a8762ee

Please sign in to comment.