Compare commits

..

76 Commits

Author SHA1 Message Date
sago35 008dd7dfe6 atsamd51 atsame5x: change to allow specifying the ADC Bus number 2021-05-10 11:14:19 +09:00
Ayke van Laethem 9f5066aa6f runtime: use the tasks scheduler instead of coroutines
This results in smaller and likely more efficient code. It does require
some architecture specific code for each architecture, but I've kept the
amount of code as small as possible.
2021-05-09 17:40:13 +02:00
Ayke van Laethem 3b24fedf92 compiler: use wasm for tests
The next commit will change the implementation of func values on Linux
as a result of switching to a task-based scheduler. To keep the
compiler/testdata/func.go test working as expected, switch to
WebAssembly tests.
2021-05-09 17:40:13 +02:00
Ayke van Laethem 8cd2a462b9 runtime: remove the asyncScheduler constant
There is no reason to specialize this per chip as it is only ever used
for JavaScript. Not only that, it is causing confusion and is yet
another quirk to learn when porting the runtime to a new
microcontroller.
2021-05-08 23:08:12 +02:00
Ayke van Laethem 25b045d0a7 runtime: improve timers on nrf, and samd chips
This commit improves the timers on various microcontrollers to better
deal with counter wraparound. The result is a reduction in RAM size of
around 12 bytes and a small effect (sometimes positive, sometimes
negative) on flash consumption. But perhaps more importantly: getting
the current time is now interrupt-safe (it previously could result in a
race condition) and the timer will now be correct when the timer isn't
retrieved for a long duration. Before this commit, a call to `time.Now`
more than 8 minutes after the previous call could result in an incorrect
time.

For more details, see:
https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
2021-05-08 20:59:40 +02:00
Ayke van Laethem 78acbdf0d9 main: match go test output
This commit makes the output of `tinygo test` similar to that of `go
test`. It changes the following things in the process:

  * Running multiple tests in a single command is now possible. They
    aren't paralellized yet.
  * Packages with no test files won't crash TinyGo, instead it logs it
    in the same way the Go toolchain does.
2021-05-06 20:04:16 +02:00
Federico G. Schwindt 617e2791ef Add -llvm-features parameter
With this is possible to enable e.g., SIMD in WASM using -llvm-features
+simd128.  Multiple features can be specified separated by comma,
e.g., -llvm-features +simd128,+tail-call

With help from @deadprogram and @aykevl.
2021-05-06 18:07:14 +02:00
Kenneth Bell a0908ff55b compiler: openocd commands in tinygo command line 2021-05-06 15:09:41 +02:00
Ayke van Laethem 2f1f8fb075 machine: move PinMode to central location
It is always implemented exactly the same way (as an uint8) so there is
no reason to implement it in each target separately.

This also makes it easier to add some documentation to it.
2021-05-06 13:59:12 +02:00
sago35 6f61b83ad5 atsamd21: remove special handling for SPI-24mhz 2021-05-06 10:32:24 +02:00
Dan Kegel 8dfefb46d1 wasi: do not crash if argc is 0
Instead, leave args at its default value (which provides a fake argv[0] as it has for a long time).

linux and mac do not seem affected.

Fixes #1862 (tinygo apps after v0.17.0-113-g7b761fa crash if run without argv[0])
2021-05-05 19:16:28 +02:00
Ayke van Laethem 959442dc82 unix: use conservative GC by default
This commit does two things:

 1. It makes it possible to grow the heap on Linux and MacOS by
    allocating 1GB of virtual memory on startup and then slowly using it
    as necessary, when running out of available heap space.
 2. It switches the default GC to be the conservative GC (previously
    extalloc). This is good for consistency with other platforms that
    all use this same GC.

This makes the extalloc GC unused by default.
2021-05-05 17:20:15 +02:00
Ayke van Laethem c1aa152a63 unix: avoid possible heap allocation with -opt=0
This heap allocation would normally be optimized away, but with -opt=0
perhaps not. This is a problem if the conservative GC is used, because
the conservative GC needs to be initialized before use.
2021-05-05 17:20:15 +02:00
sago35 dd3d8a363a qtpy: fix i2c setting 2021-05-05 10:12:08 +02:00
sago35 bb509ec91d atsamd51, atsamd21: fix ADC.Get() value at 8bit and 10bit 2021-05-05 06:51:09 +02:00
Ayke van Laethem 944f022060 interp: support extractvalue/insertvalue with multiple operands
TinyGo doesn't emit these instructions, but they can occur as a result
of optimizations.
2021-05-04 21:21:56 +02:00
Ayke van Laethem cd517a30af transform: split interface and reflect lowering
These two passes are related, but can definitely work independently.
Which is what this change does: it splits the two passes. This should
make it easier to change these two new passes in the future.

This change now also enables slightly better testing by testing these
two passes independently. In particular, the reflect lowering pass got
some actual tests: it was barely unit-tested before.

I have verified that this doesn't really change code size, at least not
on the microbit target. Two tests do change, but in a very minor way
(and in opposite direction).
2021-05-03 20:10:49 +02:00
Olivier Fauchon 52d8655eec Patch Cleanup 2021-05-03 18:16:46 +02:00
Olivier Fauchon f5786941e5 Fix bad I2C0/I2C1 declaration 2021-05-03 18:16:46 +02:00
Ayke van Laethem 1f73941c43 ci: bump Xcode version to use macOS 10.14
The CircleCI macOS builds are failing, probably due to the old macOS
version that's used. This version (10.13 High Sierra) isn't supported
anymore on Homebrew so it seems best to me to simply bump the version.

I picked Xcode 11.1.0 because 10.3.0 is somehow triggering an error
while trying to install QEMU (the Python install fails).

Because of this newer Xcode version, I had to add an extra flag
(-isysroot) to the default command line for MacOS. The reason is that
this newer Xcode version no longer stores header files in /usr/local, an
SDK must be specified manually. With this change, the default SDK is
used.
2021-05-02 23:55:10 +02:00
Raqbit abeab51d00 Add Arduino Nano w/ New Bootloader target
Since 2018, Arduino Nanos and clones are sold with a new bootloader, which
requires programming at 115200 baud instead of the 57600 baud required
by the old one.
2021-05-01 17:09:46 +02:00
sago35 9ef75f17bf atsamd51, atsame5x: unify samd51 and same5x 2021-04-29 09:20:44 +02:00
Ayke van Laethem c3992bd77b compiler: improve position information
In many cases, position information is not stored in Go SSA instructions
because they don't exit directly in the source code. This includes
implicit type conversions, implicit returns at the end of a function,
the creation of a (hidden) slice when calling a variadic function, and
many other cases. I'm not sure where this information is supposed to
come from, but this patch takes the value (usually) from the value the
instruction refers to. This seems to work well for these implicit
conversions.

I've also added a few extra tests to the heap-to-stack transform pass,
of which one requires this improved position information.
2021-04-26 16:15:57 +02:00
Ayke van Laethem f79e66ac2e cortexm: disable FPU on Cortex-M4
On some boards the FPU is already enabled on startup, probably as part
of the bootloader. On other chips it was enabled as part of the runtime
startup code. In all these cases, enabling the FPU is currently
unsupported: the automatic stack sizing of goroutines assumes that the
processor won't need to reserve space for FPU registers. Enabling the
FPU therefore can lead to a stack overflow.

This commit either removes the code that enables the FPU, or simply
disables it in startup code. A future change should fully enable the FPU
so that operations on float32 can be performed by the FPU instead of in
software, greatly speeding up such code.
2021-04-24 18:41:40 +02:00
Ayke van Laethem 4eac212695 gen-device: add extra constants and rename them to be Go style
- Add some extra fields: FPUPresent, CPU and NVICPrioBits which may
    come in handy at a later time (and are easy to add).
  - Rename DEVICE to Device, to match Go style.

This is in preparation to the next commit, which requires the FPUPresent
flag.
2021-04-24 18:41:40 +02:00
deadprogram b661882391 machine/usbcdc: remove remaining heap allocations for USB CDC implementations
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-04-23 23:37:24 +02:00
Ayke van Laethem 321488dcfe machine: avoid heap allocations in USB code
This commit replaces most heap allocations in USB related code with
stack allocations. This is important for several reasons:

  - It avoids running the GC unnecessarily.
  - It reduces code size by 400-464 bytes.
  - USB code might be called from interrupt handlers. The heap may be in
    an inconsistent state when that happens if main thread code also
    performs heap allocations.

The last one is by far the most important one: not doing heap
allocations in interrupts is critical for correctness. But the code size
reduction alone should be worth it.

There are two heap allocations in USB related code left: in the function
receiveUSBControlPacket (SAMD21 and SAMD51). This heap allocation must
also be removed because it runs in an interrupt, but I've left that for
a future change.
2021-04-23 23:37:24 +02:00
Ayke van Laethem 80caf2dab2 copiler: add function attributes to some runtime calls
This allows better escape analysis even without being able to see the
entire program. This makes the stack allocation test case more complete
but probably won't have much of an effect outside of that (as the
compiler is able to infer these attributes in the whole-program
functionattrs pass).
2021-04-22 19:53:42 +02:00
Ayke van Laethem c466465c32 main: add -print-allocs flag that lets you print all heap allocations
This flag, if set, is a regexp for function names. If there are heap
allocations in the matching function names, these heap allocations will
be printed with an explanation why the heap allocation exists (and why
the object can't be stack allocated).
2021-04-22 19:53:42 +02:00
Ayke van Laethem 404b65941a transform: move tests to transform_test package
This allows for adding more advanced tests, for example tests that use
the compiler package so that test sources can be written in Go instead
of LLVM IR.
2021-04-22 19:53:42 +02:00
Kenneth Bell 25f3adb47e stm32: support SPI on L4 series 2021-04-21 21:09:41 +02:00
sago35 b9043b649d atsamd51: fix PWM support in atsamd51p20
This change is related to the following commit
72acda22b0
2021-04-21 15:02:47 +02:00
Ayke van Laethem 7b761fac78 runtime: implement command line arguments in hosted environments
Implement command line arguments for Linux, MacOS and WASI.
2021-04-21 10:32:09 +02:00
Ayke van Laethem c47cdfa66f runtime: implement environment variables for Linux 2021-04-21 10:32:09 +02:00
Ayke van Laethem 768a15c1dd interp: remove map support
The interp package is in many cases able to execute map functions in the
runtime directly. This is probably slower than adding special support
for them in the interp package and also doesn't cover all cases (most
importantly, map keys that contain pointers) but removing this code also
removes a large amount of code that needs to be maintained and is
susceptible to hard-to-find bugs.

As a side effect, this resulted in different output of the
testdata/map.go test because the test relied on the existing iteration
order of TinyGo maps. I've updated the test to not rely on this test,
making the output compatible with what the Go toolchain would output.
2021-04-21 07:37:22 +02:00
Ayke van Laethem c1c3be1aa7 interp: fix phi instruction
I've discovered a bug in the implementation of the PHI instruction in
the interp package. This commit fixes the bug.

I've found this issue while investigating an issue with maps after
running interp per package.
2021-04-21 07:37:22 +02:00
sago35 b2e72c96f4 ci: improve llvm-build cache 2021-04-19 17:24:52 +02:00
Ayke van Laethem f706219996 builder: hard code Clang compiler
At the moment, all targets use the Clang compiler to compile C and
assembly files. There is no good reason to make this configurable
anymore and in fact it will make future changes more complicated (and
thus more likely to have bugs). Therefore, I've removed support for
setting the compiler.

Note that the same is not true for the linker. While it makes sense to
standardize on the Clang compiler (because if Clang doesn't support a
target, TinyGo is unlikely to support it either), linkers will remain
configurable for the foreseeable future. One example is Xtensa, which is
supported by the Xtensa LLVM fork but doesn't have support in ld.lld
yet.

I've also fixed a bug in compileAndCacheCFile: it wasn't using the right
CFlags for caching purposes. This could lead to using stale caches. This
commit fixes that too.
2021-04-19 13:14:33 +02:00
sago35 6152a661e8 ci: improve llvm-source cache 2021-04-19 00:55:42 +02:00
Dan Kegel f1a5743f77 Make fmt-check happy again 2021-04-17 00:01:37 +02:00
sago35 9f52fe4e4a atsame51: add initial support for feather-m4-can 2021-04-16 17:49:46 +02:00
developer cb886a35c9 PWM Support for atmega1280
Add arduino mega 1280 PWM test
2021-04-16 17:47:31 +02:00
Tobias Theel 5707022951 add goroot for snap installs 2021-04-15 17:34:21 +02:00
sago35 bd212cc000 atsame54: add initial support for atsame54-xpro 2021-04-15 15:43:37 +02:00
Ayke van Laethem 6dd5666ed1 wasm: use WASI ABI for exit function
This improves compatibility between the regular browser target
(-target=wasm) and the WASI target (-target=wasi). Specifically, it
allows running WASI tests like this:

    tinygo test -target=wasi encoding/base32
2021-04-15 08:45:08 +02:00
Ayke van Laethem f145663464 cortexm: add __isr_vector symbol
This doesn't change the firmware, but it does make the disassembly of
the ELF files. Before:

    Disassembly of section .text:

    00000000 <(machine.UART).Write-0x100>:
           0:       20001000        .word   0x20001000
           4:       000009db        .word   0x000009db
           8:       00000f05        .word   0x00000f05
           c:       00000f0b        .word   0x00000f0b
          10:       00000f05        .word   0x00000f05

After:

    Disassembly of section .text:

    00000000 <__isr_vector>:
           0:       20001000        .word   0x20001000
           4:       000009db        .word   0x000009db
           8:       00000f05        .word   0x00000f05
           c:       00000f0b        .word   0x00000f0b
          10:       00000f05        .word   0x00000f05

The difference is that the disassembler will now use a proper symbol name
instead of using the closest by symbol (in this case, (machine.UART).Write).
This makes the disassembly easier to read.
2021-04-15 07:38:52 +02:00
Ayke van Laethem 0d66475e10 nrf52833: add PWM support
This chip wasn't included in the PR for PWM support. Adding this support
is very easy, luckily.
2021-04-14 23:42:02 +02:00
Ayke van Laethem 22b143eba8 microbit-v2: add support for S113 SoftDevice
This currently doesn't work with `tinygo flash` yet (even with
`-programmer=openocd`), you can use pyocd instead. For example, from the
Bluetooth package:

    tinygo build -o test.hex -target=microbit-v2-s113v7 ./examples/advertisement/
    pyocd flash --target=nrf52 test.hex

I intend to add support for pyocd to work around this issue, so that a simple
`tinygo flash` suffices.
2021-04-14 22:55:52 +02:00
Ayke van Laethem 3fe13a72bd microbit: remove LED constant
There doesn't appear to be a user-controllable LED outside of the LED
matrix. In fact, the pin assigned for this was P13, which was connected
to the SPI SCK pin.
2021-04-14 17:40:32 +02:00
Ayke van Laethem d919905c96 all: clean up Cortex-M target files
In this commit I've moved all core-specific flags to files for that
specific core. This is a bit of a cleanup (less duplicated JSON) but
should also help in the future when core-specific changes are made, such
as core specific build tags or when the FPU finally gets supported in
TinyGo.

Some notable specific changes:

  - I've removed floating point flags from the Teensy 3.6 target. The
    reason is that the FPU is not yet supported in TinyGo (in goroutine
    stack switching for example) and floating point numbers would only
    be supported by C files, not Go files (because the LLVM FPU feature
    flags aren't used). This would create an ABI mismatch across CGo.
  - I've added the "cpu":"cortex-m7" to the cortex-m7.json file to match
    the configuration for the Teensy 4.0. This implies a change to the
    nucleo-f722ze (because now it has its CPU field set). Somehow that
    reduces the code size, so it looks like a good change.

I don't believe any of these changes should have any practical
consequences.

One issue I've found is in the Cortex-M33 target: it uses armv7m, which
is incorrect: it should be armv8m. But the chip is backwards compatible
so this should mostly work. Switching to armv8m led to a compilation
failure because PRIMASK isn't defined, this may be an actual bug.
2021-04-14 09:17:54 +02:00
Ayke van Laethem 96b1b76483 all: use -Qunused-arguments only for assembly files
The -Qunused-arguments flag disables the warning where some flags are
not relevant to a compilation. This commonly happens when compiling
assembly files (.s or .S files) because some flags are specific to C and
not relevant to assembly.
Because practically all baremetal targets need some form of assembly,
this flag is added to most CFlags. This creates a lot of noise. And it
is also added for compiling C code where it might hide bugs (by hiding
the fact a flag is actually unused).

This commit adds the flag to all assembly compilations and removes them
from all target JSON files.
2021-04-14 09:17:54 +02:00
sago35 f234df7a50 cmsis-svd: add svd file for the atsame5x 2021-04-14 06:39:36 +02:00
Agurato e6d5c26df5 Fix RGBA color interpretation for GameBoyAdvance 2021-04-13 18:51:14 +02:00
Kenneth Bell ae59e7703e stm32: make SPI CLK fast to fix data issue
See "STM32F40x and STM32F41x Errata sheet" - SPI CLK port must be 'fast' or 'very fast' to avoid data corruption on last bit (at the APB clocks we configure).
2021-04-13 07:38:30 +02:00
Ayke van Laethem e587b1d1b4 reflect: implement New function
This is very important for some use cases, for example for Vecty.
2021-04-12 14:49:26 +02:00
Ayke van Laethem 57271d7eaa compiler: decouple func lowering from interface type codes
There is no good reason for func values to refer to interface type
codes. The only thing they need is a stable identifier for function
signatures, which is easily created as a new kind of globals. Decoupling
makes it easier to change interface related code.
2021-04-12 12:07:42 +02:00
Ayke van Laethem 8383552552 compiler: add func tests
This is basically just a golden test for the "switch" style of func
lowering. The next commit will make changes to this lowering, which will
be visible in the test output.
2021-04-12 12:07:42 +02:00
developer aa8e12c464 Arduino Mega 1280 support
Fix ldflags

Update targets/arduino-mega1280.json

Co-authored-by: Ayke <aykevanlaethem@gmail.com>

Update atmega1280.json

Update Makefile
2021-04-12 11:03:13 +02:00
Ayke van Laethem e7a05b6e74 transform: do not lower zero-sized alloc to alloca
The LLVM CoroFrame pass appears to be tripping over this zero-sized
alloca. Therefore, do what the runtime would do: return a pointer to
runtime.zeroSizedAlloc. Or just don't deal with this case. But don't
emit a zero sized alloca to avoid this LLVM bug.

More information: https://bugs.llvm.org/show_bug.cgi?id=49916
2021-04-12 08:11:28 +02:00
Ayke van Laethem 2fd8f103ab transform: fix func lowering assertion failure
The func-lowering pass has started to fail in the dev branch, probably
as a result of replacing the ConstPropagation pass with the IPSCCP pass.
This commit makes the code a bit more robust and should be able to
handle all possible cases (not just ptrtoint).
2021-04-12 08:11:28 +02:00
Ayke van Laethem 33f76d1c2e main: implement -ldflags="-X ..."
This commit implements replacing some global variables with a different
value, if the global variable has no initializer. For example, if you
have:

    package main

    var version string

you can replace the value with -ldflags="-X main.version=0.2".

Right now it only works for uninitialized globals. The Go tooling also
supports initialized globals (var version = "<undefined>") but that is a
bit hard to combine with how initialized globals are currently
implemented.

The current implementation still allows caching package IR files while
making sure the values don't end up in the build cache. This means
compiling a program multiple times with different values will use the
cached package each time, inserting the string value only late in the
build process.

Fixes #1045
2021-04-09 18:33:48 +02:00
Ayke van Laethem ea8f7ba1f9 main: add tests for less common build configurations
Don't run the entire test suite for these options, as that would quickly
explode the testing time (making it less likely people actually run it).
Instead, run just one test for each configuration that should check for
most issues.
2021-04-09 18:33:48 +02:00
Ayke van Laethem 0ffe5ac2fa main: clean up tests
- Explicitly list all test cases. This makes it possible to store
    tests in testdata/ that aren't tested on all platforms.
  - Clean up filesystem and env test, by running them in a subtest and
    deduplicating some code and removing the additionalArgs parameter.
2021-04-09 18:33:48 +02:00
Ayke van Laethem 7bac93aab3 main: remove -cflags and -ldflags flags
These are unnecessary now that they are supported in CGo.
2021-04-09 18:33:48 +02:00
Ayke van Laethem b61751e429 compiler: check for errors
Some errors were generated but never returned or never checked in the
test function. That's a problem. Therefore this commit fixes this
oversight (by me).
2021-04-09 14:05:44 +02:00
Ayke van Laethem 25dac32a88 transform: use IPSCCP pass instead of the constant propagation pass
The constant propagation pass is removed in LLVM 12, so this pass needs
to be replaced anyway. The direct replacement would be the SCCP (sparse
conditional constant propagation) pass, but perhaps a better replacement
is the IPSCCP pass, which is an interprocedural version of the SCCP
pass and propagates constants across function calls if possible.

This is not always a code size reduction, but it appears to reduce code
size in a majority of cases. It certainly reduces code size in almost
all WebAssembly tests I did.
2021-04-08 12:31:26 +02:00
Ayke van Laethem 56cf69a66b builder: run function passes per package
This should result in a small compile time reduction for incremental
builds, somewhere around 5-9%.

This commit, while small, required many previous commits to not regress
binary size. Right now binary size is basically identical with very few
changes in size (the only baremetal program that changed in size did so
with a 4 byte increase).

This commit is one extra step towards doing as much work as possible in
the parallel and cached package build step, out of the serial LTO phase.
Later improvements in this area have this change as a prerequisite.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 04d12bf2ba interp: add support for switch statement
A switch statement is not normally emitted by the compiler package, but
LLVM function passes may convert a series of if/else pairs to a switch
statement. A future change will run function passes in the package
compile phase, so the interp package (which is also run after all
modules are merged together) will need to deal with these new switch
statements.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 0b7957d612 compiler: optimize string literals and globals
This commit optimizes string literals and globals by setting the
appropriate alignment and using a nil pointer in zero-length strings.

  - Setting the alignment for string values has a surprisingly large
    effect, up to around 2% in binary size. I suspect that LLVM will
    pick some default alignment for larger byte arrays if no alignment
    has been specified and forcing an alignment of 1 will pack all
    strings closer together.
  - Using nil for zero-length strings also has a positive effect, but
    I'm not sure why. Perhaps it makes some optimizations more trivial.
  - Always setting the alignment on globals improves code size slightly,
    probably for the same reasons setting the alignment of string
    literals improves code size. The effect is much smaller, however.

This commit might have an effect on performance, but if it does this
should be tested separately and such a large win in binary size should
definitely not be ignored for small embedded systems.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 61243f6c57 transform: don't rely on struct name of runtime.typecodeID
Sometimes, LLVM may rename named structs when merging modules.
Therefore, we can't rely on typecodeID structs to retain their struct
names.

This commit changes the interface lowering pass to not rely on these
names. The interp package does however still rely on this name, but I
hope to fix that in the future.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 49ec3eb58e builder: add optsize attribute while building the package
This simplifies future changes. While the move itself is very simple, it
required some other changes to a few transforms that create new
functions to add the optsize attribute manually. It also required
abstracting away the optimization level flags (based on the -opt flag)
so that it can easily be retrieved from the config object.

This commit does not impact binary size on baremetal and WebAssembly.
I've seen a few tests on linux/amd64 grow slightly in size, but I'm not
too worried about those.
2021-04-08 11:40:59 +02:00
sago35 fa6c1b69ce build: improve error messages in getDefaultPort(), support for multiple ports 2021-04-08 10:32:20 +02:00
deadprogram 1e9a41dc94 modules: add latest go-llvm because seems like older SHA is missing?
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-04-07 18:10:43 +02:00
Kenneth Bell a30671751f stm32: add nucleo-l031k6 support
Adds i2c for all L0 series

UART, Blinky (LED) and i2c tested
2021-04-07 17:20:19 +02:00
Ayke van Laethem c246978dd7 ci: limit test runs of assert-test-linux to two jobs
Instead of the regular build, it's the `make test` line that fails due
to OOM. This is because testing means that a lot of test binaries need
to be built while the regular build only needs to link one binary.

This improves https://github.com/tinygo-org/tinygo/pull/1774 and should
hopefully actually fix the OOM errors.
2021-04-07 08:08:40 +02:00
Ayke van Laethem 72acda22b0 machine: refactor PWM support
This commit refactors PWM support in the machine package to be more
flexible. The new API can be used to produce tones at a specific
frequency and control servos in a portable way, by abstracting over
counter widths and prescalers.
2021-04-06 20:36:10 +02:00
248 changed files with 7192 additions and 2589 deletions
+42 -23
View File
@@ -68,14 +68,17 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-11-v1
- llvm-source-11-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-11-v1
key: llvm-source-11-v2
paths:
- llvm-project
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
build-wasi-libc:
steps:
- restore_cache:
@@ -154,12 +157,15 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-11-linux-v2-assert
- llvm-build-11-linux-v3-assert
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
sudo apt-get install cmake ninja-build
# hack ninja to use less jobs
@@ -167,20 +173,22 @@ commands:
chmod +x /go/bin/ninja
# build!
make ASSERT=1 llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-linux-v2-assert
key: llvm-build-11-linux-v3-assert
paths:
llvm-build
- run: |
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
# 2CPU/4GB executor).
GOFLAGS="-p=2" make ASSERT=1
- run: make ASSERT=1
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
environment:
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
# 2CPU/4GB executor).
GOFLAGS: -p=2
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
@@ -217,12 +225,15 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-11-linux-v2-noassert
- llvm-build-11-linux-v3-noassert
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
sudo apt-get install cmake ninja-build
# hack ninja to use less jobs
@@ -230,9 +241,10 @@ commands:
chmod +x /go/bin/ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-linux-v2-noassert
key: llvm-build-11-linux-v3-noassert
paths:
llvm-build
- build-wasi-libc
@@ -282,43 +294,50 @@ commands:
variant: "macos"
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v3-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-11-macos-v1
- llvm-source-11-macos-v3
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-11-macos-v1
key: llvm-source-11-macos-v3
paths:
- llvm-project
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
- restore_cache:
keys:
- llvm-build-11-macos-v2
- llvm-build-11-macos-v4
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-macos-v2
key: llvm-build-11-macos-v4
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v3
- wasi-libc-sysroot-macos-v4
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v3
key: wasi-libc-sysroot-macos-v4
paths:
- lib/wasi-libc/sysroot
- run:
@@ -340,7 +359,7 @@ commands:
tinygo version
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
@@ -382,7 +401,7 @@ jobs:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
xcode: "11.1.0" # macOS 10.14
steps:
- build-macos
+37 -22
View File
@@ -152,9 +152,9 @@ gen-device-stm32: build/gen-device-svd
# Get LLVM sources.
$(LLVM_PROJECTDIR)/README.md:
$(LLVM_PROJECTDIR)/llvm:
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/README.md
llvm-source: $(LLVM_PROJECTDIR)/llvm
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
@@ -182,25 +182,28 @@ tinygo:
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
TEST_PACKAGES = \
container/heap \
container/list \
container/ring \
crypto/des \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/hex \
hash/adler32 \
hash/fnv \
hash/crc64 \
math \
math/cmplx \
text/scanner \
unicode/utf8 \
# Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no
# implied -v flag).
# TODO: parallelize, and only show failing tests (no implied -v flag).
.PHONY: tinygo-test
tinygo-test:
$(TINYGO) test container/heap
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test crypto/des
$(TINYGO) test encoding/ascii85
$(TINYGO) test encoding/base32
$(TINYGO) test encoding/hex
$(TINYGO) test hash/adler32
$(TINYGO) test hash/fnv
$(TINYGO) test hash/crc64
$(TINYGO) test math
$(TINYGO) test math/cmplx
$(TINYGO) test text/scanner
$(TINYGO) test unicode/utf8
$(TINYGO) test $(TEST_PACKAGES)
.PHONY: smoketest
smoketest:
@@ -226,8 +229,6 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@@ -256,6 +257,10 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@@ -288,6 +293,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) test.gba
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
@@ -334,6 +341,10 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
@@ -341,8 +352,6 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
@$(MD5SUM) test.hex
ifneq ($(STM32), 0)
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex
@@ -354,6 +363,8 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
@@ -374,6 +385,10 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
+3 -1
View File
@@ -43,13 +43,14 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 55 microcontroller boards are currently supported:
The following 57 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
@@ -77,6 +78,7 @@ The following 55 microcontroller boards are currently supported:
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
* [Nintendo Switch](https://www.nintendo.com/switch/)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
+164 -35
View File
@@ -39,6 +39,11 @@ type BuildResult struct {
// The directory of the main package. This is useful for testing as the test
// binary must be run in the directory of the tested package.
MainDir string
// ImportPath is the import path of the main package. This is useful for
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
}
// packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -52,14 +57,17 @@ type BuildResult struct {
// key, avoiding the need for recompiling all dependencies when only the
// implementation of an imported package changes.
type packageAction struct {
ImportPath string
CompilerVersion int // compiler.Version
InterpVersion int // interp.Version
LLVMVersion string
Config *compiler.Config
CFlags []string
FileHashes map[string]string // hash of every file that's part of the package
Imports map[string]string // map from imported package to action ID hash
ImportPath string
CompilerVersion int // compiler.Version
InterpVersion int // interp.Version
LLVMVersion string
Config *compiler.Config
CFlags []string
FileHashes map[string]string // hash of every file that's part of the package
Imports map[string]string // map from imported package to action ID hash
OptLevel int // LLVM optimization level (0-3)
SizeLevel int // LLVM optimization for size level (0-2)
UndefinedGlobals []string // globals that are left as external globals (no initializer)
}
// Build performs a single package to executable Go build. It takes in a package
@@ -91,6 +99,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
DefaultStackSize: config.Target.DefaultStackSize,
NeedsStackObjects: config.NeedsStackObjects(),
Debug: config.Debug(),
LLVMFeatures: config.LLVMFeatures(),
}
// Load the target machine, which is the LLVM object that contains all
@@ -127,20 +136,30 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
var packageJobs []*compileJob
packageBitcodePaths := make(map[string]string)
packageActionIDs := make(map[string]string)
optLevel, sizeLevel, _ := config.OptLevels()
for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string
for name := range config.Options.GlobalValues[pkg.Pkg.Path()] {
undefinedGlobals = append(undefinedGlobals, name)
}
sort.Strings(undefinedGlobals)
// Create a cache key: a hash from the action ID below that contains all
// the parameters for the build.
actionID := packageAction{
ImportPath: pkg.ImportPath,
CompilerVersion: compiler.Version,
InterpVersion: interp.Version,
LLVMVersion: llvm.Version,
Config: compilerConfig,
CFlags: pkg.CFlags,
FileHashes: make(map[string]string, len(pkg.FileHashes)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
ImportPath: pkg.ImportPath,
CompilerVersion: compiler.Version,
InterpVersion: interp.Version,
LLVMVersion: llvm.Version,
Config: compilerConfig,
CFlags: pkg.CFlags,
FileHashes: make(map[string]string, len(pkg.FileHashes)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals,
}
for filePath, hash := range pkg.FileHashes {
actionID.FileHashes[filePath] = hex.EncodeToString(hash)
@@ -191,6 +210,25 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after compiling package " + pkg.ImportPath)
}
// Erase all globals that are part of the undefinedGlobals list.
// This list comes from the -ldflags="-X pkg.foo=val" option.
// Instead of setting the value directly in the AST (which would
// mean the value, which may be a secret, is stored in the build
// cache), the global itself is left external (undefined) and is
// only set at the end of the compilation.
for _, name := range undefinedGlobals {
globalName := pkg.Pkg.Path() + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() {
return errors.New("global not found: " + globalName)
}
name := global.Name()
newGlobal := llvm.AddGlobal(mod, global.Type().ElementType(), name+".tmp")
global.ReplaceAllUsesWith(newGlobal)
global.EraseFromParentAsGlobal()
newGlobal.SetName(name)
}
// Try to interpret package initializers at compile time.
// It may only be possible to do this partially, in which case
// it is completed after all IR files are linked.
@@ -206,6 +244,38 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after interpreting " + pkgInit.Name())
}
if sizeLevel >= 2 {
// Set the "optsize" attribute to make slightly smaller
// binaries at the cost of some performance.
kind := llvm.AttributeKindID("optsize")
attr := mod.Context().CreateEnumAttribute(kind, 0)
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
fn.AddFunctionAttr(attr)
}
}
// Run function passes for each function in the module.
// These passes are intended to be run on each function right
// after they're created to reduce IR size (and maybe also for
// cache locality to improve performance), but for now they're
// run here for each function in turn. Maybe this can be
// improved in the future.
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
}
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Serialize the LLVM module as a bitcode file.
// Write to a temporary path that is renamed to the destination
// file to avoid race conditions with other TinyGo invocatiosn
@@ -430,7 +500,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
job := &compileJob{
description: "compile extra file " + path,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config)
result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config.Options.PrintCommands)
job.result = result
return err
},
@@ -449,7 +519,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config)
result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config.Options.PrintCommands)
job.result = result
return err
},
@@ -573,8 +643,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: lprogram.MainPkg().Dir,
Binary: tmppath,
MainDir: lprogram.MainPkg().Dir,
ImportPath: lprogram.MainPkg().ImportPath,
})
}
@@ -603,6 +674,12 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil {
return err
}
// Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To
// keep functions interoperable, pass int64 types as pointers to
@@ -617,21 +694,8 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
var errs []error
switch config.Options.Opt {
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
case "1":
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
case "s":
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
case "z":
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
default:
return errors.New("unknown optimization level: -opt=" + config.Options.Opt)
}
optLevel, sizeLevel, inlinerThreshold := config.OptLevels()
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold)
if len(errs) > 0 {
return newMultiError(errs)
}
@@ -653,6 +717,71 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
return nil
}
// setGlobalValues sets the global values from the -ldflags="-X ..." compiler
// option in the given module. An error may be returned if the global is not of
// the expected type.
func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error {
var pkgPaths []string
for pkgPath := range globals {
pkgPaths = append(pkgPaths, pkgPath)
}
sort.Strings(pkgPaths)
for _, pkgPath := range pkgPaths {
pkg := globals[pkgPath]
var names []string
for name := range pkg {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
value := pkg[name]
globalName := pkgPath + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() || !global.Initializer().IsNil() {
// The global either does not exist (optimized away?) or has
// some value, in which case it has already been initialized at
// package init time.
continue
}
// A strin is a {ptr, len} pair. We need these types to build the
// initializer.
initializerType := global.Type().ElementType()
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" {
return fmt.Errorf("%s: not a string", globalName)
}
elementTypes := initializerType.StructElementTypes()
if len(elementTypes) != 2 {
return fmt.Errorf("%s: not a string", globalName)
}
// Create a buffer for the string contents.
bufInitializer := mod.Context().ConstString(value, false)
buf := llvm.AddGlobal(mod, bufInitializer.Type(), ".string")
buf.SetInitializer(bufInitializer)
buf.SetAlignment(1)
buf.SetUnnamedAddr(true)
buf.SetLinkage(llvm.PrivateLinkage)
// Create the string value, which is a {ptr, len} pair.
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
ptr := llvm.ConstGEP(buf, []llvm.Value{zero, zero})
if ptr.Type() != elementTypes[0] {
return fmt.Errorf("%s: not a string", globalName)
}
length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{
ptr,
length,
})
// Set the initializer. No initializer should be set at this point.
global.SetInitializer(initializer)
}
}
return nil
}
// functionStackSizes keeps stack size information about a single function
// (usually a goroutine).
type functionStackSize struct {
+12 -8
View File
@@ -17,7 +17,6 @@ import (
"strings"
"unicode"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
)
@@ -57,7 +56,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, config *compileopts.Config) (string, error) {
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands bool) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
@@ -68,14 +67,12 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, config *compi
buf, err := json.Marshal(struct {
Path string
Hash string
Compiler string
Flags []string
LLVMVersion string
}{
Path: abspath,
Hash: fileHash,
Compiler: config.Target.Compiler,
Flags: config.CFlags(),
Flags: cflags,
LLVMVersion: llvm.Version,
})
if err != nil {
@@ -124,10 +121,17 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, config *compi
flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if config.Options.PrintCommands {
fmt.Printf("%s %s\n", config.Target.Compiler, strings.Join(flags, " "))
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
// If this is an assembly file (.s or .S, lowercase or uppercase), then
// we'll need to add -Qunused-arguments because many parameters are
// relevant to C, not assembly. And with -Werror, having meaningless
// flags (for the assembler) is a compiler error.
flags = append(flags, "-Qunused-arguments")
}
err = runCCompiler(config.Target.Compiler, flags...)
if printCommands {
fmt.Printf("clang %s\n", strings.Join(flags, " "))
}
err = runCCompiler(flags...)
if err != nil {
return "", &commandError{"failed to build", abspath, err}
}
+10
View File
@@ -17,10 +17,18 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil {
return nil, err
}
if options.OpenOCDCommands != nil {
// Override the OpenOCDCommands from the target spec if specified on
// the command-line
spec.OpenOCDCommands = options.OpenOCDCommands
}
goroot := goenv.Get("GOROOT")
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
@@ -28,7 +36,9 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if major != 1 || minor < 13 || minor > 16 {
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
Options: options,
Target: spec,
+1 -1
View File
@@ -136,7 +136,7 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
var compileArgs []string
compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath)
err := runCCompiler("clang", compileArgs...)
err := runCCompiler(compileArgs...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
+4 -13
View File
@@ -9,8 +9,8 @@ import (
)
// runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(command string, flags ...string) error {
if hasBuiltinTools && command == "clang" {
func runCCompiler(flags ...string) error {
if hasBuiltinTools {
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
@@ -23,17 +23,8 @@ func runCCompiler(command string, flags ...string) error {
return cmd.Run()
}
// Running some other compiler. Maybe it has been defined in the
// commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
// Compile this with an external invocation of the Clang compiler.
return execCommand(commands["clang"], flags...)
}
// link invokes a linker with the given name and flags.
+28 -8
View File
@@ -80,12 +80,7 @@ func (c *Config) GC() string {
if c.Target.GC != "" {
return c.Target.GC
}
for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
}
}
return "extalloc"
return "conservative"
}
// NeedsStackObjects returns true if the compiler should insert stack objects
@@ -118,6 +113,27 @@ func (c *Config) Scheduler() string {
return "coroutines"
}
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
switch c.Options.Opt {
case "none", "0":
return 0, 0, 0 // -O0
case "1":
return 1, 0, 0 // -O1
case "2":
return 2, 0, 225 // -O2
case "s":
return 2, 1, 225 // -Os
case "z":
return 2, 2, 5 // -Oz, default
default:
// This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check.
panic("unknown optimization level: -opt=" + c.Options.Opt)
}
}
// FuncImplementation picks an appropriate func value implementation for the
// target.
func (c *Config) FuncImplementation() string {
@@ -163,7 +179,7 @@ func (c *Config) AutomaticStackSize() bool {
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
cflags := append([]string{}, c.Options.CFlags...)
var cflags []string
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
}
@@ -184,7 +200,7 @@ func (c *Config) CFlags() []string {
func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags.
ldflags := append([]string{}, c.Options.LDFlags...)
var ldflags []string
for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
}
@@ -321,6 +337,10 @@ func (c *Config) WasmAbi() string {
return c.Target.WasmAbi
}
func (c *Config) LLVMFeatures() string {
return c.Options.LLVMFeatures
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
+28 -18
View File
@@ -2,6 +2,7 @@ package compileopts
import (
"fmt"
"regexp"
"strings"
)
@@ -10,29 +11,32 @@ var (
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
)
// Options contains extra options to give to the compiler. These options are
// usually passed from the command line.
type Options struct {
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands bool
Debug bool
PrintSizes string
PrintStacks bool
CFlags []string
LDFlags []string
Tags string
WasmAbi string
TestConfig TestConfig
Programmer string
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands bool
Debug bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool
Tags string
WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
TestConfig TestConfig
Programmer string
OpenOCDCommands []string
LLVMFeatures string
}
// Verify performs a validation on the given options, raising an error if options are not valid.
@@ -73,6 +77,12 @@ func (o *Options) Verify() error {
}
}
if o.Opt != "" {
if !isInArray(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
}
}
return nil
}
+12 -10
View File
@@ -31,7 +31,6 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Compiler string `json:"compiler"`
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
@@ -240,23 +239,26 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// No target spec available. Use the default one, useful on most systems
// with a regular OS.
spec := TargetSpec{
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"},
PortReset: "false",
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Scheduler: "tasks",
Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB
CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"},
PortReset: "false",
}
if goos == "darwin" {
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if goarch != "wasm" {
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
}
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
+72 -12
View File
@@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number.
const Version = 6 // last change: fix issue 1304
const Version = 9 // last change: implement reflect.New()
func init() {
llvm.InitializeAllTargets()
@@ -59,6 +59,7 @@ type Config struct {
DefaultStackSize uint64
NeedsStackObjects bool
Debug bool // Whether to emit debug information in the LLVM module.
LLVMFeatures string
}
// compilerContext contains function-independent data that should still be
@@ -185,7 +186,12 @@ func NewTargetMachine(config *Config) (llvm.TargetMachine, error) {
if err != nil {
return llvm.TargetMachine{}, err
}
features := strings.Join(config.Features, ",")
feat := config.Features
if len(config.LLVMFeatures) > 0 {
feat = append(feat, config.LLVMFeatures)
}
features := strings.Join(feat, ",")
var codeModel llvm.CodeModel
var relocationModel llvm.RelocMode
@@ -298,7 +304,7 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
c.dibuilder.Finalize()
}
return c.mod, nil
return c.mod, c.diagnostics
}
// getLLVMRuntimeType obtains a named type from the runtime package and returns
@@ -964,11 +970,59 @@ func (b *builder) createFunction() {
}
}
// posser is an interface that's implemented by both ssa.Value and
// ssa.Instruction. It is implemented by everything that has a Pos() method,
// which is all that getPos() needs.
type posser interface {
Pos() token.Pos
}
// getPos returns position information for a ssa.Value or ssa.Instruction.
//
// Not all instructions have position information, especially when they're
// implicit (such as implicit casts or implicit returns at the end of a
// function). In these cases, it makes sense to try a bit harder to guess what
// the position really should be.
func getPos(val posser) token.Pos {
pos := val.Pos()
if pos != token.NoPos {
// Easy: position is known.
return pos
}
// No position information is known.
switch val := val.(type) {
case *ssa.MakeInterface:
return getPos(val.X)
case *ssa.Return:
syntax := val.Parent().Syntax()
if syntax != nil {
// non-synthetic
return syntax.End()
}
return token.NoPos
case *ssa.FieldAddr:
return getPos(val.X)
case *ssa.IndexAddr:
return getPos(val.X)
case *ssa.Slice:
return getPos(val.X)
case *ssa.Store:
return getPos(val.Addr)
case *ssa.Extract:
return getPos(val.Tuple)
default:
// This is reachable, for example with *ssa.Const, *ssa.If, and
// *ssa.Jump. They might be implemented in some way in the future.
return token.NoPos
}
}
// createInstruction builds the LLVM IR equivalent instructions for the
// particular Go SSA instruction.
func (b *builder) createInstruction(instr ssa.Instruction) {
if b.Debug {
pos := b.program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(getPos(instr))
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
@@ -2258,14 +2312,20 @@ func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value {
} else if typ.Info()&types.IsString != 0 {
str := constant.StringVal(expr.Value)
strLen := llvm.ConstInt(b.uintptrType, uint64(len(str)), false)
objname := prefix + "$string"
global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname)
global.SetInitializer(b.ctx.ConstString(str, false))
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
strPtr := b.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
var strPtr llvm.Value
if str != "" {
objname := prefix + "$string"
global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname)
global.SetInitializer(b.ctx.ConstString(str, false))
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetAlignment(1)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
strPtr = b.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
} else {
strPtr = llvm.ConstNull(b.i8ptrType)
}
strObj := llvm.ConstNamedStruct(b.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
return strObj
} else if typ.Kind() == types.UnsafePointer {
+8 -2
View File
@@ -32,7 +32,7 @@ func TestCompiler(t *testing.T) {
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
}
target, err := compileopts.LoadTarget("i686--linux")
target, err := compileopts.LoadTarget("wasm")
if err != nil {
t.Fatal("failed to load target:", err)
}
@@ -62,6 +62,7 @@ func TestCompiler(t *testing.T) {
"string.go",
"float.go",
"interface.go",
"func.go",
}
for _, testCase := range tests {
@@ -84,11 +85,16 @@ func TestCompiler(t *testing.T) {
mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Log("error:", err)
t.Error(err)
}
return
}
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil {
t.Error(err)
}
// Optimize IR a little.
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
+15 -3
View File
@@ -25,14 +25,13 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case "switch":
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
c.getFuncSignatureID(sig),
})
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
@@ -50,6 +49,19 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
return funcValue
}
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -71,7 +83,7 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
funcPtr = b.CreateExtractValue(funcValue, 1, "")
case "switch":
llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getTypeCode(sig)
sigGlobal := b.getFuncSignatureID(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
default:
+3
View File
@@ -34,6 +34,9 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
} else {
// The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized.
if b.DefaultStackSize == 0 {
b.addError(pos, "default stack size for goroutines is not set")
}
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
}
case "coroutines":
+20 -16
View File
@@ -46,6 +46,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
var references llvm.Value
var length int64
var methodSet llvm.Value
var ptrTo llvm.Value
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
@@ -69,22 +70,25 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ)
}
if !references.IsNil() || length != 0 || !methodSet.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
if _, ok := typ.Underlying().(*types.Pointer); !ok {
ptrTo = c.getTypeCode(types.NewPointer(typ))
}
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true)
}
return global
@@ -341,7 +345,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else {
globalName := "reflect/types.type:" + getTypeCodeName(expr.AssertedType) + "$id"
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
+18 -5
View File
@@ -139,6 +139,17 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
}
case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't
// be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.sliceCopy":
// Copying a slice won't capture any of the parameters.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer
@@ -355,16 +366,18 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
// Set alignment from the //go:align comment.
var alignInBits uint32
if info.align < 0 || info.align&(info.align-1) != 0 {
alignment := c.targetData.ABITypeAlignment(llvmType)
if info.align > alignment {
alignment = info.align
}
if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else {
// Set the alignment only when it is a power of two.
alignInBits = uint32(info.align) ^ uint32(info.align-1)
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
alignInBits = uint32(alignment) ^ uint32(alignment-1)
llvmGlobal.SetAlignment(alignment)
}
if c.Debug && !info.extern {
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'basic.go'
source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'float.go'
source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
+12
View File
@@ -0,0 +1,12 @@
package main
func foo(callback func(int)) {
callback(3)
}
func bar() {
foo(someFunc)
}
func someFunc(int) {
}
+47
View File
@@ -0,0 +1,47 @@
; ModuleID = 'func.go'
source_filename = "func.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
%1 = icmp eq i32 %0, 0
br i1 %1, label %fpcall.throw, label %fpcall.next
fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(i8* undef, i8* null)
unreachable
fpcall.next: ; preds = %entry
%2 = inttoptr i32 %0 to void (i32, i8*, i8*)*
call void %2(i32 3, i8* %callback.context, i8* undef)
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
declare void @runtime.nilPanic(i8*, i8*)
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @main.foo(i8* undef, i32 ptrtoint (%runtime.funcValueWithSignature* @"main.someFunc$withSignature" to i32), i8* undef, i8* undef)
ret void
}
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
+13 -12
View File
@@ -1,25 +1,26 @@
; ModuleID = 'interface.go'
source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID zeroinitializer
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" }
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }
@"func Error() string" = external constant i8
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }
@"func String() string" = external constant i8
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"]
@"reflect/types.type:basic:int$id" = external constant i8
@"reflect/types.typeid:basic:int" = external constant i8
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -51,7 +52,7 @@ entry:
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.type:basic:int$id", i8* undef, i8* null)
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null)
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'pointer.go'
source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
+4 -4
View File
@@ -1,7 +1,7 @@
; ModuleID = 'slice.go'
source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -60,7 +60,7 @@ entry:
ret { i32*, i32, i32 } %7
}
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8*, i32, i32, i32, i32, i8*, i8*)
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8* nocapture readonly, i32, i32, i32, i32, i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
@@ -85,4 +85,4 @@ entry:
ret i32 %copy.n
}
declare i32 @runtime.sliceCopy(i8*, i8*, i32, i32, i32, i8*, i8*)
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
+8
View File
@@ -1,5 +1,13 @@
package main
func someString() string {
return "foo"
}
func zeroLengthString() string {
return ""
}
func stringLen(s string) int {
return len(s)
}
+16 -2
View File
@@ -1,7 +1,11 @@
; ModuleID = 'string.go'
source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime._string = type { i8*, i32 }
@"main.someString$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -10,6 +14,16 @@ entry:
ret void
}
define hidden %runtime._string @main.someString(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._string { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main.someString$string", i32 0, i32 0), i32 3 }
}
define hidden %runtime._string @main.zeroLengthString(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._string zeroinitializer
}
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %s.len
+1 -1
View File
@@ -12,5 +12,5 @@ require (
go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
)
+2
View File
@@ -59,3 +59,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c h1:vn9IPshzYmzZis10UEVrsIBRv9FpykADw6M3/tHHROg=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+3 -2
View File
@@ -171,8 +171,9 @@ func getGoroot() string {
switch runtime.GOOS {
case "linux":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
"/snap/go/current/", // installed using snap
}
case "darwin":
candidates = []string{
+17 -1
View File
@@ -25,6 +25,7 @@ type function struct {
// basicBlock represents a LLVM basic block and contains a slice of
// instructions. The last instruction must be a terminator instruction.
type basicBlock struct {
phiNodes []instruction
instructions []instruction
}
@@ -135,6 +136,15 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
default:
panic("unknown number of operands")
}
case llvm.Switch:
// A switch is an array of (value, label) pairs, of which the
// first one indicates the to-switch value and the default
// label.
numOperands := llvmInst.OperandsCount()
for i := 0; i < numOperands; i += 2 {
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
}
case llvm.PHI:
inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount()
@@ -337,7 +347,13 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
// This error is handled when actually trying to interpret this
// instruction (to not trigger on code that won't be executed).
}
bb.instructions = append(bb.instructions, inst)
if inst.opcode == llvm.PHI {
// PHI nodes need to be treated specially, see the comment in
// interpreter.go for an explanation.
bb.phiNodes = append(bb.phiNodes, inst)
} else {
bb.instructions = append(bb.instructions, inst)
}
}
}
return fn
+1 -1
View File
@@ -13,9 +13,9 @@ import (
func TestInterp(t *testing.T) {
for _, name := range []string{
"basic",
"phi",
"slice-copy",
"consteval",
"map",
"interface",
} {
name := name // make tc local to this closure
+84 -92
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"math"
"os"
"strconv"
"strings"
"time"
@@ -33,6 +34,50 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
lastBB := -1 // last basic block is undefined, only defined after a branch
var operands []value
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
if instIndex == 0 {
// This is the start of a new basic block.
// There may be PHI nodes that need to be resolved. Resolve all PHI
// nodes before continuing with regular instructions.
// PHI nodes need to be treated specially because they can have a
// mutual dependency:
// for.loop:
// %a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
// %b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
// If these PHI nodes are processed like a regular instruction, %a
// and %b are both 3 on the second iteration of the loop because %b
// loads the value of %a from the second iteration, while it should
// load the value from the previous iteration. The correct behavior
// is that these two values swap each others place on each
// iteration.
var phiValues []value
var phiIndices []int
for _, inst := range bb.phiNodes {
var result value
for i := 0; i < len(inst.operands); i += 2 {
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
incoming := inst.operands[i+1]
if local, ok := incoming.(localValue); ok {
result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break
}
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
}
if result == nil {
panic("could not find PHI input")
}
phiValues = append(phiValues, result)
phiIndices = append(phiIndices, inst.localIndex)
}
for i, value := range phiValues {
locals[phiIndices[i]] = value
}
}
inst := bb.instructions[instIndex]
operands = operands[:0]
isRuntimeInst := false
@@ -103,27 +148,24 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
default:
panic("unknown operands length")
}
break // continue with next block
case llvm.PHI:
var result value
for i := 0; i < len(inst.operands); i += 2 {
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
incoming := inst.operands[i+1]
if local, ok := incoming.(localValue); ok {
result = locals[fn.locals[local.value]]
} else {
result = incoming
}
case llvm.Switch:
// Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...]
value := operands[0].Uint()
targetLabel := operands[1].Uint() // default label
// Do a lazy switch by iterating over all cases.
for i := 2; i < len(operands); i += 2 {
if value == operands[i].Uint() {
targetLabel = operands[i+1].Uint()
break
}
}
lastBB = currentBB
currentBB = int(targetLabel)
bb = fn.blocks[currentBB]
instIndex = -1 // start at 0 the next cycle
if r.debug {
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
fmt.Fprintln(os.Stderr, indent+"switch", operands, "->", currentBB)
}
if result == nil {
panic("could not find PHI input")
}
locals[inst.localIndex] = result
case llvm.Select:
// Select is much like a ternary operator: it picks a result from
// the second and third operand based on the boolean first operand.
@@ -155,7 +197,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// which case this call won't even get to this point but will
// already be emitted in initAll.
continue
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "os.runtime_args":
// These functions should be run at runtime. Specifically:
// * Print and panic functions are best emitted directly without
// interpreting them, otherwise we get a ton of putchar (etc.)
@@ -163,6 +205,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// * runtime.hashmapGet tries to access the map value directly.
// This is not possible as the map value is treated as a special
// kind of object in this package.
// * os.runtime_args reads globals that are initialized outside
// the view of the interp package so it always needs to be run
// at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
@@ -328,7 +373,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, err)
}
actualType := actualTypePtrToInt.Operand(0)
if actualType.Name()+"$id" == assertedType.Name() {
if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") {
locals[inst.localIndex] = literalValue{uint8(1)}
} else {
locals[inst.localIndex] = literalValue{uint8(0)}
@@ -415,74 +460,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name()))
}
locals[inst.localIndex] = r.getValue(method)
case callFn.name == "runtime.hashmapMake":
// Create a new map.
hashmapPointerType := inst.llvmInst.Type()
keySize := uint32(operands[1].Uint())
valueSize := uint32(operands[2].Uint())
m := newMapValue(r, hashmapPointerType, keySize, valueSize)
alloc := object{
llvmType: hashmapPointerType,
globalName: r.pkgName + "$map",
buffer: m,
size: m.len(r),
}
index := len(r.objects)
r.objects = append(r.objects, alloc)
// Create a pointer to this map. Maps are reference types, so
// are implemented as pointers.
ptr := newPointerValue(r, index, 0)
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapMake:", keySize, valueSize, "->", ptr)
}
locals[inst.localIndex] = ptr
case callFn.name == "runtime.hashmapBinarySet":
// Do a mapassign operation with a binary key (that is, without
// a string key).
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
keyPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
valuePtr, err := operands[3].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putBinary(&mem, keyPtr, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
case callFn.name == "runtime.hashmapStringSet":
// Do a mapassign operation with a string key.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
stringPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
stringLen := operands[3].Uint()
valuePtr, err := operands[4].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putString(&mem, stringPtr, stringLen, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
default:
if len(callFn.blocks) == 0 {
// Call to a function declaration without a definition
@@ -954,16 +931,31 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
case llvm.ExtractValue:
indices := inst.llvmInst.Indices()
if len(indices) != 1 {
panic("expected exactly one index")
// Note: the Go LLVM API doesn't support multiple indices, so simulate
// this operation with some extra extractvalue instructions. Hopefully
// this is optimized to a single instruction.
agg := operands[0]
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg")
}
result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
result = r.builder.CreateExtractValue(agg, int(indices[len(indices)-1]), inst.name)
case llvm.InsertValue:
indices := inst.llvmInst.Indices()
if len(indices) != 1 {
panic("expected exactly one index")
// Similar to extractvalue, we're working around a limitation in the Go
// LLVM API here by splitting the insertvalue into multiple instructions
// if there is more than one operand.
agg := operands[0]
aggregates := []llvm.Value{agg}
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg"+strconv.Itoa(i))
aggregates = append(aggregates, agg)
}
result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
result = operands[1]
for i := len(indices) - 1; i >= 0; i-- {
agg := aggregates[i]
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
}
case llvm.Add:
result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
case llvm.Sub:
-282
View File
@@ -640,288 +640,6 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
return gep, nil
}
// mapValue implements a Go map which is created at compile time and stored as a
// global variable.
// The value itself is only used as part of an object (object.buffer). Maps are
// reference types aka pointers, so it can only be used as a pointerValue, not
// directly.
type mapValue struct {
r *runner
pkgName string
size uint32 // byte size of runtime.hashmap
hashmap llvm.Value
keyIsString bool
keys []interface{} // either rawValue (for binary key) or mapStringKey (for string key)
values []rawValue
keySize uint32
valueSize uint32
}
type mapStringKey struct {
buf pointerValue
size uint64
data []uint64
}
func newMapValue(r *runner, hashmapPointerType llvm.Type, keySize, valueSize uint32) *mapValue {
size := uint32(r.targetData.TypeAllocSize(hashmapPointerType.ElementType()))
return &mapValue{
r: r,
pkgName: r.pkgName,
size: size,
keySize: keySize,
valueSize: valueSize,
}
}
func (v *mapValue) len(r *runner) uint32 {
return v.size
}
func (v *mapValue) clone() value {
// Return a copy of mapValue.
clone := *v
clone.keys = append([]interface{}{}, clone.keys...)
clone.values = append([]rawValue{}, clone.values...)
return &clone
}
func (v *mapValue) asPointer(r *runner) (pointerValue, error) {
panic("interp: mapValue.asPointer")
}
func (v *mapValue) asRawValue(r *runner) rawValue {
panic("interp: mapValue.asRawValue")
}
func (v *mapValue) Uint() uint64 {
panic("interp: mapValue.Uint")
}
func (v *mapValue) Int() int64 {
panic("interp: mapValue.Int")
}
// Temporary struct to collect data before turning this into a hashmap bucket
// LLVM value.
type mapBucket struct {
m *mapValue
tophash [8]uint8
keys []rawValue // can have up to 8 keys
values []rawValue // can have up to 8 values, len(keys) == len(values)
}
// create returns a (pointer to a) buffer structurally equivalent to
// runtime.hashmapBucket.
func (b *mapBucket) create(ctx llvm.Context, nextBucket llvm.Value, mem *memoryView) llvm.Value {
// Create tophash array.
int8Type := ctx.Int8Type()
tophashValues := make([]llvm.Value, 8)
for i := range tophashValues {
tophashValues[i] = llvm.ConstInt(int8Type, uint64(b.tophash[i]), false)
}
tophash := llvm.ConstArray(int8Type, tophashValues)
// Create next pointer (if not set).
if nextBucket.IsNil() {
nextBucket = llvm.ConstNull(llvm.PointerType(int8Type, 0))
}
// Create data for keys.
var keyValues []llvm.Value
for _, key := range b.keys {
keyValue, err := key.rawLLVMValue(mem)
if err != nil {
panic(err)
}
keyValues = append(keyValues, keyValue)
}
if len(b.keys) < 8 {
keyValues = append(keyValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.keySize)*(8-len(b.keys)))))
}
keyValue := ctx.ConstStruct(keyValues, false)
if checks && uint32(b.m.r.targetData.TypeAllocSize(keyValue.Type())) != b.m.keySize*8 {
panic("key size invalid")
}
// Create data for values.
var valueValues []llvm.Value
for _, value := range b.values {
v, err := value.rawLLVMValue(mem)
if err != nil {
panic(err)
}
valueValues = append(valueValues, v)
}
if len(b.values) < 8 {
valueValues = append(valueValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.valueSize)*(8-len(b.values)))))
}
valueValue := ctx.ConstStruct(valueValues, false)
if checks && uint32(b.m.r.targetData.TypeAllocSize(valueValue.Type())) != b.m.valueSize*8 {
panic("value size invalid")
}
// Create the bucket.
bucketInitializer := ctx.ConstStruct([]llvm.Value{
tophash,
nextBucket,
keyValue,
valueValue,
}, false)
bucket := llvm.AddGlobal(b.m.r.mod, bucketInitializer.Type(), b.m.pkgName+"$mapbucket")
bucket.SetInitializer(bucketInitializer)
bucket.SetLinkage(llvm.InternalLinkage)
bucket.SetUnnamedAddr(true)
return bucket
}
func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) (llvm.Value, error) {
if !v.hashmap.IsNil() {
return v.hashmap, nil
}
// Create a slice of buckets with all the keys and values in the hashmap.
var buckets []*mapBucket
var bucket *mapBucket
for i, key := range v.keys {
var data []uint64
var keyValue rawValue
switch key := key.(type) {
case mapStringKey:
data = key.data
keyValue = newRawValue(v.keySize)
// runtime._string is {ptr, length}
for i := uint32(0); i < v.keySize/2; i++ {
keyValue.buf[i] = key.buf.pointer
}
copy(keyValue.buf[v.keySize/2:], literalValue{key.size}.asRawValue(v.r).buf)
case rawValue:
if key.hasPointer() {
return llvm.Value{}, errors.New("interp: todo: map key with pointer")
}
data = key.buf
keyValue = key
default:
return llvm.Value{}, errors.New("interp: unknown map key type")
}
buf := make([]byte, len(data))
for i, p := range data {
buf[i] = byte(p)
}
hash := v.hash(buf)
if i%8 == 0 {
bucket = &mapBucket{m: v}
buckets = append(buckets, bucket)
}
bucket.tophash[i%8] = v.topHash(hash)
bucket.keys = append(bucket.keys, keyValue)
bucket.values = append(bucket.values, v.values[i])
}
// Convert these buckets into LLVM global variables.
ctx := v.r.mod.Context()
var nextBucket llvm.Value
for i := len(buckets) - 1; i >= 0; i-- {
bucket = buckets[i]
bucketValue := bucket.create(ctx, nextBucket, mem)
nextBucket = bucketValue
}
firstBucket := nextBucket
if firstBucket.IsNil() {
firstBucket = llvm.ConstNull(mem.r.i8ptrType)
} else {
firstBucket = llvm.ConstBitCast(firstBucket, mem.r.i8ptrType)
}
// Create the hashmap itself, pointing to these buckets.
hashmapPointerType := llvm.PointerType(hashmapType, 0)
hashmap := llvm.ConstNamedStruct(hashmapType, []llvm.Value{
llvm.ConstPointerNull(hashmapPointerType), // next
firstBucket, // buckets
llvm.ConstInt(hashmapType.StructElementTypes()[2], uint64(len(v.keys)), false), // count
llvm.ConstInt(ctx.Int8Type(), uint64(v.keySize), false), // keySize
llvm.ConstInt(ctx.Int8Type(), uint64(v.valueSize), false), // valueSize
llvm.ConstInt(ctx.Int8Type(), 0, false), // bucketBits
})
v.hashmap = hashmap
return v.hashmap, nil
}
// putString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *mapValue) putString(mem *memoryView, stringBuf pointerValue, stringLen uint64, valuePtr pointerValue) error {
if !v.hashmap.IsNil() {
return errMapAlreadyCreated
}
value := mem.load(valuePtr, v.valueSize)
stringValue := mem.load(stringBuf, uint32(stringLen)).asRawValue(v.r)
if stringValue.hasPointer() {
panic("interp: string contains pointer")
}
// TODO: avoid duplicate keys
v.keys = append(v.keys, mapStringKey{stringBuf, stringLen, stringValue.buf})
v.values = append(v.values, value.asRawValue(v.r))
v.keyIsString = true
return nil
}
// putBinary does a map assign operation for binary data (e.g. [3]int etc). The
// key must not contain pointer values.
func (v *mapValue) putBinary(mem *memoryView, keyPtr, valuePtr pointerValue) error {
if !v.hashmap.IsNil() {
return errMapAlreadyCreated
}
key := mem.load(keyPtr, v.keySize)
value := mem.load(valuePtr, v.valueSize)
// Sanity checks.
if v.keySize != key.len(mem.r) || v.valueSize != value.len(mem.r) {
// This is a bug (not unhandled input), so panic.
panic("interp: key or value size mismatch")
}
if v.keyIsString {
panic("cannot put binary keys in string map")
}
// TODO: avoid duplicate keys
v.keys = append(v.keys, key.asRawValue(v.r))
v.values = append(v.values, value.asRawValue(v.r))
return nil
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
func (v *mapValue) hash(data []byte) uint32 {
var result uint32 = 2166136261 // FNV offset basis
for _, c := range data {
result ^= uint32(c)
result *= 16777619 // FNV prime
}
return result
}
// Get the topmost 8 bits of the hash, without using a special value (like 0).
func (v *mapValue) topHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
if tophash < 1 {
// 0 means empty slot, so make it bigger.
tophash++
}
return tophash
}
func (v *mapValue) String() string {
return "<map keySize=" + strconv.Itoa(int(v.keySize)) + " valueSize=" + strconv.Itoa(int(v.valueSize)) + ">"
}
// rawValue is a raw memory buffer that can store either pointers or regular
// data. This is the fallback data for everything that isn't clearly a
// literalValue or pointerValue.
+35
View File
@@ -8,6 +8,7 @@ target triple = "x86_64--linux"
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0
@main.exposedValue2 = global i16 0
@main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr
@@ -65,6 +66,19 @@ entry:
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
; Test switch statement.
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
%switch2 = call i64 @testSwitch(i64 9) ; 9 returns the default value -1
call void @runtime.printint64(i64 %switch1)
call void @runtime.printint64(i64 %switch2)
; Test extractvalue/insertvalue with multiple operands.
%agg = call {i8, i32, {float, {i64, i16}}} @nestedStruct()
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
call void @runtime.printint64(i64 %elt)
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
ret void
}
@@ -87,3 +101,24 @@ entry:
store i16 8, i16* @main.exposedValue2
ret void
}
define i64 @testSwitch(i64 %val) {
entry:
; Test switch statement.
switch i64 %val, label %otherwise [ i64 0, label %zero
i64 1, label %one
i64 2, label %two ]
zero:
ret i64 5
one:
ret i64 6
two:
ret i64 7
otherwise:
ret i64 -1
}
declare {i8, i32, {float, {i64, i16}}} @nestedStruct()
+37
View File
@@ -7,6 +7,7 @@ target triple = "x86_64--linux"
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0
@main.exposedValue2 = local_unnamed_addr global i16 0
@main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr
@@ -25,6 +26,19 @@ entry:
store i16 5, i16* @main.exposedValue1
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
call void @runtime.printint64(i64 6)
call void @runtime.printint64(i64 -1)
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
%elt.agg = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%elt.agg1 = extractvalue { float, { i64, i16 } } %elt.agg, 1
%elt = extractvalue { i64, i16 } %elt.agg1, 0
call void @runtime.printint64(i64 %elt)
%agg2.agg0 = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%agg2.agg1 = extractvalue { float, { i64, i16 } } %agg2.agg0, 1
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue
ret void
}
@@ -44,3 +58,26 @@ entry:
store i16 8, i16* @main.exposedValue2
ret void
}
define i64 @testSwitch(i64 %val) local_unnamed_addr {
entry:
switch i64 %val, label %otherwise [
i64 0, label %zero
i64 1, label %one
i64 2, label %two
]
zero: ; preds = %entry
ret i64 5
one: ; preds = %entry
ret i64 6
two: ; preds = %entry
ret i64 7
otherwise: ; preds = %entry
ret i64 -1
}
declare { i8, i32, { float, { i64, i16 } } } @nestedStruct() local_unnamed_addr
+2 -2
View File
@@ -6,7 +6,7 @@ target triple = "x86_64--linux"
@main.v1 = global i1 0
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:named:main.foo$id" = external constant i8
@"reflect/types.typeid:named:main.foo" = external constant i8
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@@ -21,7 +21,7 @@ entry:
define internal void @main.init() unnamed_addr {
entry:
; Test type asserts.
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.type:named:main.foo$id", i8* undef, i8* null)
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
store i1 %typecode, i1* @main.v1
ret void
}
-74
View File
@@ -1,74 +0,0 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime._string = type { i8*, i32 }
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
@main.m = global %runtime.hashmap* null
@main.binaryMap = global %runtime.hashmap* null
@main.stringMap = global %runtime.hashmap* null
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle)
declare void @llvm.lifetime.end.p0i8(i64, i8*)
declare void @llvm.lifetime.start.p0i8(i64, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init(i8* undef, i8* null)
ret void
}
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
; Test that hashmap optimizations generally work (even with lifetimes).
%hashmap.key = alloca i8
%hashmap.value = alloca %runtime._string
%0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null)
%hashmap.value.bitcast = bitcast %runtime._string* %hashmap.value to i8*
call void @llvm.lifetime.start.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string* %hashmap.value
call void @llvm.lifetime.start.p0i8(i64 1, i8* %hashmap.key)
store i8 1, i8* %hashmap.key
call void @runtime.hashmapBinarySet(%runtime.hashmap* %0, i8* %hashmap.key, i8* %hashmap.value.bitcast, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key)
call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime.hashmap* %0, %runtime.hashmap** @main.m
; Other tests, that can be done in a separate function.
call void @main.testNonConstantBinarySet()
call void @main.testNonConstantStringSet()
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with binary keys).
define internal void @main.testNonConstantBinarySet() {
%hashmap.key = alloca i8
%hashmap.value = alloca i8
; Create hashmap from global.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
; Do the binary set to the newly loaded map.
store i8 1, i8* %hashmap.key
store i8 2, i8* %hashmap.value
call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with string keys).
define internal void @main.testNonConstantStringSet() {
%hashmap.value = alloca i8
; Create hashmap from global.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
; Do the string set to the newly loaded map.
store i8 2, i8* %hashmap.value
call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
-20
View File
@@ -1,20 +0,0 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
@main.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.1"
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.3"
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
@"main$map" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] zeroinitializer } }
@"main$map.1" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.2", i32 0, i32 0, i32 0), i32 1, i8 1, i8 1, i8 0 }
@"main$mapbucket.2" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
@"main$map.3" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.4", i32 0, i32 0, i32 0), i32 1, i8 8, i8 1, i8 0 }
@"main$mapbucket.4" = internal unnamed_addr global { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"x\00\00\00\00\00\00\00", i8* null, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
define void @runtime.initAll() unnamed_addr {
entry:
ret void
}
+31
View File
@@ -0,0 +1,31 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.phiNodesResultA = global i8 0
@main.phiNodesResultB = global i8 0
define void @runtime.initAll() {
call void @main.init()
ret void
}
; PHI nodes always use the value from the previous block, even in a loop. This
; means that the loop below should swap the values %a and %b on each iteration.
; Previously there was a bug which resulted in %b getting the value 3 on the
; second iteration while it should have gotten 1 (from the first iteration of
; %for.loop).
define internal void @main.init() {
entry:
br label %for.loop
for.loop:
%a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
%b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
%icmp = icmp eq i8 %a, 3
br i1 %icmp, label %for.done, label %for.loop
for.done:
store i8 %a, i8* @main.phiNodesResultA
store i8 %b, i8* @main.phiNodesResultB
ret void
}
+9
View File
@@ -0,0 +1,9 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.phiNodesResultA = local_unnamed_addr global i8 3
@main.phiNodesResultB = local_unnamed_addr global i8 1
define void @runtime.initAll() local_unnamed_addr {
ret void
}
+10
View File
@@ -23,3 +23,13 @@ type Error struct {
func (e Error) Error() string {
return e.Err.Error()
}
// Error returned when loading a *Program for a test binary but no test files
// are present.
type NoTestFilesError struct {
ImportPath string
}
func (e NoTestFilesError) Error() string {
return "no test files"
}
+6
View File
@@ -210,6 +210,12 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
p.Packages[pkg.ImportPath] = pkg
}
if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") {
// Trying to compile a test binary but there are no test files in this
// package.
return p, NoTestFilesError{p.sorted[len(p.sorted)-1].ImportPath}
}
return p, nil
}
+235 -104
View File
@@ -13,11 +13,13 @@ import (
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync/atomic"
"time"
"github.com/google/shlex"
"github.com/mattn/go-colorable"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts"
@@ -28,6 +30,7 @@ import (
"tinygo.org/x/go-llvm"
"go.bug.st/serial"
"go.bug.st/serial/enumerator"
)
var (
@@ -133,15 +136,17 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
})
}
// Test runs the tests in the given package.
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) error {
// Test runs the tests in the given package. Returns whether the test passed and
// possibly an error if the test failed to run.
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) (bool, error) {
options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options)
if err != nil {
return err
return false, err
}
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
var passed bool
err = builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
if testCompileOnly || outpath != "" {
// Write test binary to the specified file name.
if outpath == "" {
@@ -155,48 +160,78 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
// Do not run the test.
return nil
}
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := executeCommand(config.Options, result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run()
if err != nil {
// Propagate the exit code
if err, ok := err.(*exec.ExitError); ok {
os.Exit(err.ExitCode())
}
return &commandError{"failed to run compiled binary", result.Binary, err}
}
return nil
// Run the test.
start := time.Now()
var err error
passed, err = runPackageTest(config, result)
if err != nil {
return err
}
duration := time.Since(start)
// Print the result.
importPath := strings.TrimSuffix(result.ImportPath, ".test")
if passed {
fmt.Printf("ok \t%s\t%.3fs\n", importPath, duration.Seconds())
} else {
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
// Workaround for QEMU which always exits with an error.
return &commandError{"failed to run emulator with", result.Binary, err}
}
fmt.Printf("FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
}
return nil
})
if err, ok := err.(loader.NoTestFilesError); ok {
fmt.Printf("? \t%s\t[no test files]\n", err.ImportPath)
// Pretend the test passed - it at least didn't fail.
return true, nil
}
return passed, err
}
// runPackageTest runs a test binary that was previously built. The return
// values are whether the test passed and any errors encountered while trying to
// run the binary.
func runPackageTest(config *compileopts.Config, result builder.BuildResult) (bool, error) {
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := executeCommand(config.Options, result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
// Binary exited with a non-zero exit code, which means the test
// failed.
return false, nil
}
testOutput := string(buf.Bytes())
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
// Test passed.
return nil
} else {
// Test failed, either by ending with the word "FAIL" or with a
// panic of some sort.
os.Exit(1)
return nil // unreachable
return false, &commandError{"failed to run compiled binary", result.Binary, err}
}
return true, nil
} else {
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
// Workaround for QEMU which always exits with an error.
return false, &commandError{"failed to run emulator with", result.Binary, err}
}
}
})
testOutput := string(buf.Bytes())
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
// Test passed.
return true, nil
} else {
// Test failed, either by ending with the word "FAIL" or with a
// panic of some sort.
return false, nil
}
}
}
// Flash builds and flashes the built binary to the given serial port.
@@ -240,15 +275,12 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
// do we need port reset to put MCU into bootloader mode?
if config.Target.PortReset == "true" && flashMethod != "openocd" {
if port == "" {
var err error
port, err = getDefaultPort()
if err != nil {
return err
}
port, err := getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' }))
if err != nil {
return err
}
err := touchSerialPortAt1200bps(port)
err = touchSerialPortAt1200bps(port)
if err != nil {
return &commandError{"failed to reset port", result.Binary, err}
}
@@ -264,9 +296,9 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.ReplaceAll(flashCmd, fileToken, result.Binary)
if port == "" && strings.Contains(flashCmd, "{port}") {
if strings.Contains(flashCmd, "{port}") {
var err error
port, err = getDefaultPort()
port, err = getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' }))
if err != nil {
return err
}
@@ -665,41 +697,66 @@ func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, e
}
// getDefaultPort returns the default serial port depending on the operating system.
func getDefaultPort() (port string, err error) {
var portPath string
func getDefaultPort(portCandidates []string) (port string, err error) {
if len(portCandidates) == 1 {
return portCandidates[0], nil
}
var ports []string
switch runtime.GOOS {
case "darwin":
portPath = "/dev/cu.usb*"
case "linux":
portPath = "/dev/ttyACM*"
case "freebsd":
portPath = "/dev/cuaU*"
case "windows":
ports, err := serial.GetPortsList()
ports, err = filepath.Glob("/dev/cuaU*")
case "darwin", "linux", "windows":
var portsList []*enumerator.PortDetails
portsList, err = enumerator.GetDetailedPortsList()
if err != nil {
return "", err
}
if len(ports) == 0 {
return "", errors.New("no serial ports available")
} else if len(ports) > 1 {
return "", errors.New("multiple serial ports available - use -port flag")
for _, p := range portsList {
ports = append(ports, p.Name)
}
return ports[0], nil
if ports == nil || len(ports) == 0 {
// fallback
switch runtime.GOOS {
case "darwin":
ports, err = filepath.Glob("/dev/cu.usb*")
case "linux":
ports, err = filepath.Glob("/dev/ttyACM*")
case "windows":
ports, err = serial.GetPortsList()
}
}
default:
return "", errors.New("unable to search for a default USB device to be flashed on this OS")
}
d, err := filepath.Glob(portPath)
if err != nil {
return "", err
}
if d == nil {
} else if ports == nil {
return "", errors.New("unable to locate a serial port")
} else if len(ports) == 0 {
return "", errors.New("no serial ports available")
}
return d[0], nil
if len(portCandidates) == 0 {
if len(ports) == 1 {
return ports[0], nil
} else {
return "", errors.New("multiple serial ports available - use -port flag, available ports are " + strings.Join(ports, ", "))
}
}
for _, ps := range portCandidates {
for _, p := range ports {
if p == ps {
return p, nil
}
}
}
return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", "))
}
func usage() {
@@ -812,6 +869,52 @@ func handleCompilerError(err error) {
}
}
// This is a special type for the -X flag to parse the pkgpath.Var=stringVal
// format. It has to be a special type to allow multiple variables to be defined
// this way.
type globalValuesFlag map[string]map[string]string
func (m globalValuesFlag) String() string {
return "pkgpath.Var=value"
}
func (m globalValuesFlag) Set(value string) error {
equalsIndex := strings.IndexByte(value, '=')
if equalsIndex < 0 {
return errors.New("expected format pkgpath.Var=value")
}
pathAndName := value[:equalsIndex]
pointIndex := strings.LastIndexByte(pathAndName, '.')
if pointIndex < 0 {
return errors.New("expected format pkgpath.Var=value")
}
path := pathAndName[:pointIndex]
name := pathAndName[pointIndex+1:]
stringValue := value[equalsIndex+1:]
if m[path] == nil {
m[path] = make(map[string]string)
}
m[path][name] = stringValue
return nil
}
// parseGoLinkFlag parses the -ldflags parameter. Its primary purpose right now
// is the -X flag, for setting the value of global string variables.
func parseGoLinkFlag(flagsString string) (map[string]map[string]string, error) {
set := flag.NewFlagSet("link", flag.ExitOnError)
globalVarValues := make(globalValuesFlag)
set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.")
flags, err := shlex.Split(flagsString)
if err != nil {
return nil, err
}
err = set.Parse(flags)
if err != nil {
return nil, err
}
return map[string]map[string]string(globalVarValues), nil
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
@@ -831,14 +934,16 @@ func main() {
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)")
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
printCommands := flag.Bool("x", false, "Print commands")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
port := flag.String("port", "", "flash port")
port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)")
programmer := flag.String("programmer", "", "which hardware programmer to use")
cFlags := flag.String("cflags", "", "additional cflags for compiler")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags")
wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic")
llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable")
var flagJSON, flagDeps *bool
if command == "help" || command == "list" {
@@ -867,35 +972,51 @@ func main() {
}
flag.CommandLine.Parse(os.Args[2:])
globalVarValues, err := parseGoLinkFlag(*ldflags)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var printAllocs *regexp.Regexp
if *printAllocsString != "" {
printAllocs, err = regexp.Compile(*printAllocsString)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
var ocdCommands []string
if *ocdCommandsString != "" {
ocdCommands = strings.Split(*ocdCommandsString, ",")
}
options := &compileopts.Options{
Target: *target,
Opt: *opt,
GC: *gc,
PanicStrategy: *panicStrategy,
Scheduler: *scheduler,
PrintIR: *printIR,
DumpSSA: *dumpSSA,
VerifyIR: *verifyIR,
Debug: !*nodebug,
PrintSizes: *printSize,
PrintStacks: *printStacks,
PrintCommands: *printCommands,
Tags: *tags,
WasmAbi: *wasmAbi,
Programmer: *programmer,
}
if *cFlags != "" {
options.CFlags = strings.Split(*cFlags, " ")
}
if *ldFlags != "" {
options.LDFlags = strings.Split(*ldFlags, " ")
Target: *target,
Opt: *opt,
GC: *gc,
PanicStrategy: *panicStrategy,
Scheduler: *scheduler,
PrintIR: *printIR,
DumpSSA: *dumpSSA,
VerifyIR: *verifyIR,
Debug: !*nodebug,
PrintSizes: *printSize,
PrintStacks: *printStacks,
PrintAllocs: printAllocs,
PrintCommands: *printCommands,
Tags: *tags,
GlobalValues: globalVarValues,
WasmAbi: *wasmAbi,
Programmer: *programmer,
OpenOCDCommands: ocdCommands,
LLVMFeatures: *llvmFeatures,
}
os.Setenv("CC", "clang -target="+*target)
err := options.Verify()
err = options.Verify()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
usage()
@@ -983,16 +1104,26 @@ func main() {
err := Run(pkgName, options)
handleCompilerError(err)
case "test":
pkgName := "."
if flag.NArg() == 1 {
pkgName = filepath.ToSlash(flag.Arg(0))
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
usage()
var pkgNames []string
for i := 0; i < flag.NArg(); i++ {
pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i)))
}
if len(pkgNames) == 0 {
pkgNames = []string{"."}
}
allTestsPassed := true
for _, pkgName := range pkgNames {
// TODO: parallelize building the test binaries
passed, err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
handleCompilerError(err)
if !passed {
allTestsPassed = false
}
}
if !allTestsPassed {
fmt.Println("FAIL")
os.Exit(1)
}
err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
handleCompilerError(err)
case "targets":
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
entries, err := ioutil.ReadDir(dir)
+125 -55
View File
@@ -13,8 +13,8 @@ import (
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"testing"
"time"
@@ -28,41 +28,43 @@ const TESTDATA = "testdata"
var testTarget = flag.String("target", "", "override test target")
func TestCompiler(t *testing.T) {
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
if err != nil {
t.Fatal("could not read test files:", err)
tests := []string{
"alias.go",
"atomic.go",
"binop.go",
"calls.go",
"cgo/",
"channel.go",
"coroutines.go",
"float.go",
"gc.go",
"init.go",
"init_multi.go",
"interface.go",
"json.go",
"map.go",
"math.go",
"print.go",
"reflect.go",
"slice.go",
"sort.go",
"stdlib.go",
"string.go",
"structs.go",
"zeroalloc.go",
}
dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
if err != nil {
t.Fatal("could not read test packages:", err)
}
if len(matches) == 0 || len(dirMatches) == 0 {
t.Fatal("no test files found")
}
for _, m := range dirMatches {
matches = append(matches, filepath.Dir(m)+string(filepath.Separator))
}
sort.Strings(matches)
if *testTarget != "" {
// This makes it possible to run one specific test (instead of all),
// which is especially useful to quickly check whether some changes
// affect a particular target architecture.
runPlatTests(*testTarget, matches, t)
runPlatTests(*testTarget, tests, t)
return
}
if runtime.GOOS != "windows" {
t.Run("Host", func(t *testing.T) {
runPlatTests("", matches, t)
if runtime.GOOS == "darwin" {
runTest("testdata/libc/filesystem.go", "", t,
nil, nil)
runTest("testdata/libc/env.go", "", t,
[]string{"ENV1=VALUE1", "ENV2=VALUE2"}, nil)
}
runPlatTests("", tests, t)
})
}
@@ -71,26 +73,26 @@ func TestCompiler(t *testing.T) {
}
t.Run("EmulatedCortexM3", func(t *testing.T) {
runPlatTests("cortex-m-qemu", matches, t)
runPlatTests("cortex-m-qemu", tests, t)
})
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
// Note: running only on Windows and macOS because Linux (as of 2020)
// usually has an outdated QEMU version that doesn't support RISC-V yet.
t.Run("EmulatedRISCV", func(t *testing.T) {
runPlatTests("riscv-qemu", matches, t)
runPlatTests("riscv-qemu", tests, t)
})
}
if runtime.GOOS == "linux" {
t.Run("X86Linux", func(t *testing.T) {
runPlatTests("i386--linux-gnu", matches, t)
runPlatTests("i386--linux-gnu", tests, t)
})
t.Run("ARMLinux", func(t *testing.T) {
runPlatTests("arm--linux-gnueabihf", matches, t)
runPlatTests("arm--linux-gnueabihf", tests, t)
})
t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", matches, t)
runPlatTests("aarch64--linux-gnu", tests, t)
})
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
if err != nil {
@@ -103,35 +105,110 @@ func TestCompiler(t *testing.T) {
// below that are also not supported but still seem to pass, so
// include them in the tests for now.
t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", matches, t)
runPlatTests("wasm", tests, t)
})
}
t.Run("WASI", func(t *testing.T) {
runPlatTests("wasi", matches, t)
runTest("testdata/libc/env.go", "wasi", t,
[]string{"--env", "ENV1=VALUE1", "--env", "ENV2=VALUE2"}, nil)
runTest("testdata/libc/filesystem.go", "wasi", t, nil, []string{"--dir=."})
runPlatTests("wasi", tests, t)
})
}
// Test a few build options.
t.Run("build-options", func(t *testing.T) {
if runtime.GOOS == "windows" {
// These tests assume a host that is supported by TinyGo.
t.Skip("can't test build options on Windows")
}
t.Parallel()
// Test with few optimizations enabled (no inlining, etc).
t.Run("opt=1", func(t *testing.T) {
t.Parallel()
runTestWithConfig("stdlib.go", "", t, &compileopts.Options{
Opt: "1",
}, nil, nil)
})
// Test with only the bare minimum of optimizations enabled.
// TODO: fix this for stdlib.go, which currently fails.
t.Run("opt=0", func(t *testing.T) {
t.Parallel()
runTestWithConfig("print.go", "", t, &compileopts.Options{
Opt: "0",
}, nil, nil)
})
t.Run("ldflags", func(t *testing.T) {
t.Parallel()
runTestWithConfig("ldflags.go", "", t, &compileopts.Options{
Opt: "z",
GlobalValues: map[string]map[string]string{
"main": {
"someGlobal": "foobar",
},
},
}, nil, nil)
})
})
}
func runPlatTests(target string, matches []string, t *testing.T) {
func runPlatTests(target string, tests []string, t *testing.T) {
t.Parallel()
for _, path := range matches {
path := path // redefine to avoid race condition
t.Run(filepath.Base(path), func(t *testing.T) {
for _, name := range tests {
name := name // redefine to avoid race condition
t.Run(name, func(t *testing.T) {
t.Parallel()
runTest(path, target, t, nil, nil)
runTest(name, target, t, nil, nil)
})
}
if target == "wasi" || target == "" {
t.Run("filesystem.go", func(t *testing.T) {
t.Parallel()
runTest("filesystem.go", target, t, nil, nil)
})
t.Run("env.go", func(t *testing.T) {
t.Parallel()
runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
})
}
}
func runTest(path, target string, t *testing.T, environmentVars []string, additionalArgs []string) {
// Due to some problems with LLD, we cannot run links in parallel, or in parallel with compiles.
// Therefore, we put a lock around builds and run everything else in parallel.
var buildLock sync.Mutex
// runBuild is a thread-safe wrapper around Build.
func runBuild(src, out string, opts *compileopts.Options) error {
buildLock.Lock()
defer buildLock.Unlock()
return Build(src, out, opts)
}
func runTest(name, target string, t *testing.T, cmdArgs, environmentVars []string) {
options := &compileopts.Options{
Target: target,
Opt: "z",
PrintIR: false,
DumpSSA: false,
VerifyIR: true,
Debug: true,
PrintSizes: "",
WasmAbi: "",
}
runTestWithConfig(name, target, t, options, cmdArgs, environmentVars)
}
func runTestWithConfig(name, target string, t *testing.T, options *compileopts.Options, cmdArgs, environmentVars []string) {
// Get the expected output for this test.
// Note: not using filepath.Join as it strips the path separator at the end
// of the path.
path := TESTDATA + "/" + name
// Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt"
if path[len(path)-1] == os.PathSeparator {
if path[len(path)-1] == '/' {
txtpath = path + "out.txt"
}
expected, err := ioutil.ReadFile(txtpath)
@@ -152,19 +229,8 @@ func runTest(path, target string, t *testing.T, environmentVars []string, additi
}()
// Build the test binary.
config := &compileopts.Options{
Target: target,
Opt: "z",
PrintIR: false,
DumpSSA: false,
VerifyIR: true,
Debug: true,
PrintSizes: "",
WasmAbi: "",
}
binary := filepath.Join(tmpdir, "test")
err = Build("./"+path, binary, config)
err = runBuild("./"+path, binary, options)
if err != nil {
printCompilerError(t.Log, err)
t.Fail()
@@ -178,6 +244,7 @@ func runTest(path, target string, t *testing.T, environmentVars []string, additi
if target == "" {
cmd = exec.Command(binary)
cmd.Env = append(cmd.Env, environmentVars...)
cmd.Args = append(cmd.Args, cmdArgs...)
} else {
spec, err := compileopts.LoadTarget(target)
if err != nil {
@@ -187,13 +254,16 @@ func runTest(path, target string, t *testing.T, environmentVars []string, additi
cmd = exec.Command(binary)
} else {
args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], append(args, additionalArgs...)...)
cmd = exec.Command(spec.Emulator[0], args...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
// Allow reading from the current directory.
cmd.Args = append(cmd.Args, "--dir=.")
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
cmd.Args = append(cmd.Args, cmdArgs...)
} else {
cmd.Env = append(cmd.Env, environmentVars...)
}
+15 -6
View File
@@ -26,12 +26,21 @@ type SCB_Type struct {
SHPR2 volatile.Register32 // 0xD1C: System Handler Priority Register 2
SHPR3 volatile.Register32 // 0xD20: System Handler Priority Register 3
// the following are only applicable for Cortex-M3/M33/M4/M7
SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register
CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register
HFSR volatile.Register32 // 0xD2C: HardFault Status Register
DFSR volatile.Register32 // 0xD30: Debug Fault Status Register
MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register
BFAR volatile.Register32 // 0xD38: BusFault Address Register
SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register
CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register
HFSR volatile.Register32 // 0xD2C: HardFault Status Register
DFSR volatile.Register32 // 0xD30: Debug Fault Status Register
MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register
BFAR volatile.Register32 // 0xD38: BusFault Address Register
AFSR volatile.Register32 // 0xD3C: Auxiliary Fault Status Register
PFR [2]volatile.Register32 // 0xD40: Processor Feature Register
DFR volatile.Register32 // 0xD48: Debug Feature Register
ADR volatile.Register32 // 0xD4C: Auxiliary Feature Register
MMFR [4]volatile.Register32 // 0xD50: Memory Model Feature Register
ISAR [5]volatile.Register32 // 0xD60: Instruction Set Attributes Register
_ [5]uint32 // reserved
CPACR volatile.Register32 // 0xD88: Coprocessor Access Control Register
}
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
+71
View File
@@ -0,0 +1,71 @@
// Hand created file. DO NOT DELETE.
// atsamd51x bitfield definitions that are not auto-generated by gen-device-svd.go
// +build sam,atsame5x
// These are the supported pchctrl function numberings on the atsamd51x
// See http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf
// table 14-9
package sam
const (
PCHCTRL_GCLK_OSCCTRL_DFLL48 = 0 // DFLL48 input clock source
PCHCTRL_GCLK_OSCCTRL_FDPLL0 = 1 // Reference clock for FDPLL0
PCHCTRL_GCLK_OSCCTRL_FDPLL1 = 2 // Reference clock for FDPLL1
PCHCTRL_GCLK_OSCCTRL_FDPLL0_32K = 3 // FDPLL0 = 3 // 32KHz clock for internal lock timer
PCHCTRL_GCLK_OSCCTRL_FDPLL1_32K = 3 // FDPLL1 = 3 // 32KHz clock for internal lock timer
PCHCTRL_GCLK_SDHC0_SLOW = 3 // SDHC0 = 3 // Slow
PCHCTRL_GCLK_SDHC1_SLOW = 3 // SDHC1 = 3 // Slow
PCHCTRL_GCLK_SERCOMX_SLOW = 3 // GCLK_SERCOM[0..7]_SLOW = 3
PCHCTRL_GCLK_EIC = 4
PCHCTRL_GCLK_FREQM_MSR = 5 // FREQM Measure
PCHCTRL_GCLK_FREQM_REF = 6 // FREQM Reference
PCHCTRL_GCLK_SERCOM0_CORE = 7 // SERCOM0 Core
PCHCTRL_GCLK_SERCOM1_CORE = 8 // SERCOM1 Core
PCHCTRL_GCLK_TC0 = 9
PCHCTRL_GCLK_TC1 = 9 // TC0, TC1
PCHCTRL_GCLK_USB = 10 // USB
PCHCTRL_GCLK_EVSYS0 = 11
PCHCTRL_GCLK_EVSYS1 = 12
PCHCTRL_GCLK_EVSYS2 = 13
PCHCTRL_GCLK_EVSYS3 = 14
PCHCTRL_GCLK_EVSYS4 = 15
PCHCTRL_GCLK_EVSYS5 = 16
PCHCTRL_GCLK_EVSYS6 = 17
PCHCTRL_GCLK_EVSYS7 = 18
PCHCTRL_GCLK_EVSYS8 = 19
PCHCTRL_GCLK_EVSYS9 = 20
PCHCTRL_GCLK_EVSYS10 = 21
PCHCTRL_GCLK_EVSYS11 = 22
PCHCTRL_GCLK_SERCOM2_CORE = 23 // SERCOM2 Core
PCHCTRL_GCLK_SERCOM3_CORE = 24 // SERCOM3 Core
PCHCTRL_GCLK_TCC0 = 25
PCHCTRL_GCLK_TCC1 = 25 // TCC0, TCC1
PCHCTRL_GCLK_TC2 = 26
PCHCTRL_GCLK_TC3 = 26 // TC2, TC3
PCHCTRL_GCLK_CAN0 = 27 // CAN0
PCHCTRL_GCLK_CAN1 = 28 // CAN1
PCHCTRL_GCLK_TCC2 = 29
PCHCTRL_GCLK_TCC3 = 29 // TCC2, TCC3
PCHCTRL_GCLK_TC4 = 30
PCHCTRL_GCLK_TC5 = 30 // TC4, TC5
PCHCTRL_GCLK_PDEC = 31 // PDEC
PCHCTRL_GCLK_AC = 32 // AC
PCHCTRL_GCLK_CCL = 33 // CCL
PCHCTRL_GCLK_SERCOM4_CORE = 34 // SERCOM4 Core
PCHCTRL_GCLK_SERCOM5_CORE = 35 // SERCOM5 Core
PCHCTRL_GCLK_SERCOM6_CORE = 36 // SERCOM6 Core
PCHCTRL_GCLK_SERCOM7_CORE = 37 // SERCOM7 Core
PCHCTRL_GCLK_TCC4 = 38 // TCC4
PCHCTRL_GCLK_TC6 = 39
PCHCTRL_GCLK_TC7 = 39 // TC6, TC7
PCHCTRL_GCLK_ADC0 = 40 // ADC0
PCHCTRL_GCLK_ADC1 = 41 // ADC1
PCHCTRL_GCLK_DAC = 42 // DAC
PCHCTRL_GCLK_I2S0 = 43
PCHCTRL_GCLK_I2S1 = 44
PCHCTRL_GCLK_SDHC0 = 45 // SDHC0
PCHCTRL_GCLK_SDHC1 = 46 // SDHC1
PCHCTRL_GCLK_CM4_TRACE = 47 // CM4 Trace
)
+1 -1
View File
@@ -14,7 +14,7 @@ func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
sensor := machine.ADC{machine.ADC2}
sensor := machine.ADC{Pin: machine.ADC2}
sensor.Configure(machine.ADCConfig{})
for {
+12
View File
@@ -0,0 +1,12 @@
// +build arduino_mega1280
package main
import "machine"
var (
// Configuration on an Arduino Uno.
pwm = machine.Timer3
pinA = machine.PH3 // pin 6 on the Mega
pinB = machine.PH4 // pin 7 on the Mega
)
+12
View File
@@ -0,0 +1,12 @@
// +build arduino
package main
import "machine"
var (
// Configuration on an Arduino Uno.
pwm = machine.Timer2
pinA = machine.PB3 // pin 11 on the Uno
pinB = machine.PD3 // pin 3 on the Uno
)
+11
View File
@@ -0,0 +1,11 @@
// +build feather_m4
package main
import "machine"
var (
pwm = machine.TCC0
pinA = machine.D12
pinB = machine.D13
)
+11
View File
@@ -0,0 +1,11 @@
// +build itsybitsy_m0
package main
import "machine"
var (
pwm = machine.TCC0
pinA = machine.D3
pinB = machine.D4
)
+11
View File
@@ -0,0 +1,11 @@
// +build itsybitsy_m4
package main
import "machine"
var (
pwm = machine.TCC0
pinA = machine.D12
pinB = machine.D13
)
+57 -47
View File
@@ -1,64 +1,74 @@
package main
// This example demonstrates some features of the PWM support.
import (
"machine"
"time"
)
// This example assumes that an RGB LED is connected to pins 3, 5 and 6 on an Arduino.
// Change the values below to use different pins.
const (
redPin = machine.D4
greenPin = machine.D5
bluePin = machine.D6
)
// cycleColor is just a placeholder until math/rand or some equivalent is working.
func cycleColor(color uint8) uint8 {
if color < 10 {
return color + 1
} else if color < 200 {
return color + 10
} else {
return 0
}
}
const delayBetweenPeriods = time.Second * 5
func main() {
machine.InitPWM()
// Delay a bit on startup to easily catch the first messages.
time.Sleep(time.Second * 2)
red := machine.PWM{redPin}
err := red.Configure()
checkError(err, "failed to configure red pin")
// Configure the PWM with the given period.
err := pwm.Configure(machine.PWMConfig{
Period: 16384e3, // 16.384ms
})
if err != nil {
println("failed to configure PWM")
return
}
green := machine.PWM{greenPin}
err = green.Configure()
checkError(err, "failed to configure green pin")
// The top value is the highest value that can be passed to PWMChannel.Set.
// It is usually an even number.
println("top:", pwm.Top())
blue := machine.PWM{bluePin}
err = blue.Configure()
checkError(err, "failed to configure blue pin")
// Configure the two channels we'll use as outputs.
channelA, err := pwm.Channel(pinA)
if err != nil {
println("failed to configure channel A")
return
}
channelB, err := pwm.Channel(pinB)
if err != nil {
println("failed to configure channel B")
return
}
var rc uint8
var gc uint8 = 20
var bc uint8 = 30
// Invert one of the channels to demonstrate output polarity.
pwm.SetInverting(channelB, true)
// Test out various frequencies below, including some edge cases.
println("running at 0% duty cycle")
pwm.Set(channelA, 0)
pwm.Set(channelB, 0)
time.Sleep(delayBetweenPeriods)
println("running at 1")
pwm.Set(channelA, 1)
pwm.Set(channelB, 1)
time.Sleep(delayBetweenPeriods)
println("running at 25% duty cycle")
pwm.Set(channelA, pwm.Top()/4)
pwm.Set(channelB, pwm.Top()/4)
time.Sleep(delayBetweenPeriods)
println("running at top-1")
pwm.Set(channelA, pwm.Top()-1)
pwm.Set(channelB, pwm.Top()-1)
time.Sleep(delayBetweenPeriods)
println("running at 100% duty cycle")
pwm.Set(channelA, pwm.Top())
pwm.Set(channelB, pwm.Top())
time.Sleep(delayBetweenPeriods)
for {
rc = cycleColor(rc)
gc = cycleColor(gc)
bc = cycleColor(bc)
red.Set(uint16(rc) << 8)
green.Set(uint16(gc) << 8)
blue.Set(uint16(bc) << 8)
time.Sleep(time.Millisecond * 500)
}
}
func checkError(err error, msg string) {
if err != nil {
print(msg, ": ", err.Error())
println()
time.Sleep(time.Second)
}
}
+58
View File
@@ -0,0 +1,58 @@
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
.cfi_startproc
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, EBX contain the pc of the to-be-started function and
// ESI contain the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Indicate to the unwinder that there is nothing to unwind, this is the
// root frame. It avoids bogus extra frames in GDB.
.cfi_undefined eip
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
pushl %esi
// Branch to the "goroutine start" function.
calll *%ebx
// Rebalance the stack (to undo the above push).
addl $4, %esp
// After return, exit this goroutine. This is a tail call.
jmp tinygo_pause
.cfi_endproc
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
movl 4(%esp), %eax // newStack uintptr
movl 8(%esp), %ecx // oldStack *uintptr
// More information on the calling convention:
// https://wiki.osdev.org/System_V_ABI#i386
// Save all callee-saved registers:
pushl %ebp
pushl %edi
pushl %esi
pushl %ebx
// Save the current stack pointer in oldStack.
movl %esp, (%ecx)
// Switch to the new stack pointer.
movl %eax, %esp
// Load saved register from the new stack.
popl %ebx
popl %esi
popl %edi
popl %ebp
// Return into the new task, as if tinygo_swapTask was a regular call.
ret
+59
View File
@@ -0,0 +1,59 @@
// +build scheduler.tasks,386
package task
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_386.S that relies on the exact
// layout of this struct.
type calleeSavedRegs struct {
ebx uintptr
esi uintptr
edi uintptr
ebp uintptr
pc uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in
// src/internal/task/task_stack_386.S). This assembly code calls a function
// (passed in EBX) with a single argument (passed in ESI). After the
// function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in EBX.
// This function is a compiler-generated wrapper which loads arguments out
// of a struct pointer. See createGoroutineStartWrapper (defined in
// compiler/goroutine.go) for more information.
r.ebx = fn
// Pass the pointer to the arguments struct in ESI.
r.esi = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
+74
View File
@@ -0,0 +1,74 @@
#ifdef __MACH__ // Darwin
.global _tinygo_startTask
_tinygo_startTask:
#else // Linux etc
.section .text.tinygo_startTask
.global tinygo_startTask
tinygo_startTask:
#endif
.cfi_startproc
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r12 contain the pc of the to-be-started function and
// r13 contain the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Indicate to the unwinder that there is nothing to unwind, this is the
// root frame. It avoids bogus extra frames in GDB like here:
// #10 0x00000000004277b6 in <goroutine wrapper> () at [...]
// #11 0x00000000004278f3 in tinygo_startTask () at [...]
// #12 0x0000000000002030 in ?? ()
// #13 0x0000000000000071 in ?? ()
.cfi_undefined rip
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
movq %r13, %rdi
// Branch to the "goroutine start" function.
callq *%r12
// After return, exit this goroutine. This is a tail call.
#ifdef __MACH__
jmp _tinygo_pause
#else
jmp tinygo_pause
#endif
.cfi_endproc
#ifdef __MACH__ // Darwin
.global _tinygo_swapTask
_tinygo_swapTask:
#else // Linux etc
.global tinygo_swapTask
.section .text.tinygo_swapTask
tinygo_swapTask:
#endif
// This function gets the following parameters:
// %rdi = newStack uintptr
// %rsi = oldStack *uintptr
// Save all callee-saved registers:
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbp
pushq %rbx
// Save the current stack pointer in oldStack.
movq %rsp, (%rsi)
// Switch to the new stack pointer.
movq %rdi, %rsp
// Load saved register from the new stack.
popq %rbx
popq %rbp
popq %r12
popq %r13
popq %r14
popq %r15
// Return into the new task, as if tinygo_swapTask was a regular call.
ret
+61
View File
@@ -0,0 +1,61 @@
// +build scheduler.tasks,amd64
package task
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_amd64.S that relies on the exact
// layout of this struct.
type calleeSavedRegs struct {
rbx uintptr
rbp uintptr
r12 uintptr
r13 uintptr
r14 uintptr
r15 uintptr
pc uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in
// src/internal/task/task_stack_amd64.S). This assembly code calls a
// function (passed in r12) with a single argument (passed in r13). After
// the function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in r12.
// This function is a compiler-generated wrapper which loads arguments out
// of a struct pointer. See createGoroutineStartWrapper (defined in
// compiler/goroutine.go) for more information.
r.r12 = fn
// Pass the pointer to the arguments struct in r13.
r.r13 = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
+51
View File
@@ -0,0 +1,51 @@
// Only generate .debug_frame, don't generate .eh_frame.
.cfi_sections .debug_frame
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
.cfi_startproc
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r4 contains the pc of the to-be-started function and r5
// contains the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Indicate to the unwinder that there is nothing to unwind, this is the
// root frame. It avoids the following (bogus) error message in GDB:
// Backtrace stopped: previous frame identical to this frame (corrupt stack?)
.cfi_undefined lr
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
mov r0, r5
// Branch to the "goroutine start" function. By using blx instead of bx,
// we'll return here instead of tail calling.
blx r4
// After return, exit this goroutine. This is a tail call.
bl tinygo_pause
.cfi_endproc
.size tinygo_startTask, .-tinygo_startTask
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
// r0 = newStack uintptr
// r1 = oldStack *uintptr
// Save all callee-saved registers:
push {r4-r11, lr}
// Save the current stack pointer in oldStack.
str sp, [r1]
// Switch to the new stack pointer.
mov sp, r0
// Load state from new task and branch to the previous position in the
// program.
pop {r4-r11, pc}
+61
View File
@@ -0,0 +1,61 @@
// +build scheduler.tasks,arm,!cortexm,!avr,!xtensa
package task
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_arm.S that relies on the exact
// layout of this struct.
type calleeSavedRegs struct {
r4 uintptr
r5 uintptr
r6 uintptr
r7 uintptr
r8 uintptr
r9 uintptr
r10 uintptr
r11 uintptr
pc uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm.S).
// This assembly code calls a function (passed in r4) with a single argument
// (passed in r5). After the function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in r4.
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
r.r4 = fn
// Pass the pointer to the arguments struct in r5.
r.r5 = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
+59
View File
@@ -0,0 +1,59 @@
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
.cfi_startproc
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, x19 contains the pc of the to-be-started function and
// x20 contains the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Indicate to the unwinder that there is nothing to unwind, this is the
// root frame. It avoids the following (bogus) error message in GDB:
// Backtrace stopped: previous frame identical to this frame (corrupt stack?)
.cfi_undefined lr
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
mov x0, x20
// Branch to the "goroutine start" function. By using blx instead of bx,
// we'll return here instead of tail calling.
blr x19
// After return, exit this goroutine. This is a tail call.
b tinygo_pause
.cfi_endproc
.size tinygo_startTask, .-tinygo_startTask
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
// x0 = newStack uintptr
// x1 = oldStack *uintptr
// Save all callee-saved registers:
stp x19, x20, [sp, #-96]!
stp x21, x22, [sp, #16]
stp x23, x24, [sp, #32]
stp x25, x26, [sp, #48]
stp x27, x28, [sp, #64]
stp x29, x30, [sp, #80]
// Save the current stack pointer in oldStack.
mov x8, sp
str x8, [x1]
// Switch to the new stack pointer.
mov sp, x0
// Restore stack state and return.
ldp x29, x30, [sp, #80]
ldp x27, x28, [sp, #64]
ldp x25, x26, [sp, #48]
ldp x23, x24, [sp, #32]
ldp x21, x22, [sp, #16]
ldp x19, x20, [sp], #96
ret
+64
View File
@@ -0,0 +1,64 @@
// +build scheduler.tasks,arm64
package task
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_arm64.S that relies on the exact
// layout of this struct.
type calleeSavedRegs struct {
x19 uintptr
x20 uintptr
x21 uintptr
x22 uintptr
x23 uintptr
x24 uintptr
x25 uintptr
x26 uintptr
x27 uintptr
x28 uintptr
x29 uintptr
pc uintptr // aka x30 aka LR
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm64.S).
// This assembly code calls a function (passed in x19) with a single argument
// (passed in x20). After the function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in x19.
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
r.x19 = fn
// Pass the pointer to the arguments struct in x20.
r.x20 = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
+6
View File
@@ -2,6 +2,12 @@
package task
// Note that this is almost the same as task_stack_arm.go, but it uses the MSP
// register to store the system stack pointer instead of a global variable. The
// big advantage of this is that interrupts always execute with MSP (and not
// PSP, which is used for goroutines) so that goroutines do not need extra stack
// space for interrupts.
import (
"device/arm"
"unsafe"
+7
View File
@@ -9,4 +9,11 @@ type ADCConfig struct {
Reference uint32 // analog reference voltage (AREF) in millivolts
Resolution uint32 // number of bits for a single conversion (e.g., 8, 10, 12)
Samples uint32 // number of samples for a single conversion (e.g., 4, 8, 16, 32)
Bus uint8 // bus Number of ADC
}
const (
AdcBusAuto = 0
AdcBus0 = 1
AdcBus1 = 2
)
+104
View File
@@ -0,0 +1,104 @@
// +build arduino_mega1280
package machine
// Return the current CPU frequency in hertz.
func CPUFrequency() uint32 {
return 16000000
}
const (
AREF Pin = NoPin
LED Pin = PB7
A0 Pin = PF0
A1 Pin = PF1
A2 Pin = PF2
A3 Pin = PF3
A4 Pin = PF4
A5 Pin = PF5
A6 Pin = PF6
A7 Pin = PF7
A8 Pin = PK0
A9 Pin = PK1
A10 Pin = PK2
A11 Pin = PK3
A12 Pin = PK4
A13 Pin = PK5
A14 Pin = PK6
A15 Pin = PK7
// Analog Input
ADC0 Pin = PF0
ADC1 Pin = PF1
ADC2 Pin = PF2
ADC3 Pin = PF3
ADC4 Pin = PF4
ADC5 Pin = PF5
ADC6 Pin = PF6
ADC7 Pin = PF7
ADC8 Pin = PK0
ADC9 Pin = PK1
ADC10 Pin = PK2
ADC11 Pin = PK3
ADC12 Pin = PK4
ADC13 Pin = PK5
ADC14 Pin = PK6
ADC15 Pin = PK7
// Digital pins
D0 Pin = PE0
D1 Pin = PE1
D2 Pin = PE4
D3 Pin = PE5
D4 Pin = PG5
D5 Pin = PE3
D6 Pin = PH3
D7 Pin = PH4
D8 Pin = PH5
D9 Pin = PH6
D10 Pin = PB4
D11 Pin = PB5
D12 Pin = PB6
D13 Pin = PB7
D14 Pin = PJ1
D15 Pin = PJ0
D16 Pin = PH1
D17 Pin = PH0
D18 Pin = PD3
D19 Pin = PD2
D20 Pin = PD1
D21 Pin = PD0
D22 Pin = PA0
D23 Pin = PA1
D24 Pin = PA2
D25 Pin = PA3
D26 Pin = PA4
D27 Pin = PA5
D28 Pin = PA6
D29 Pin = PA7
D30 Pin = PC7
D31 Pin = PC6
D32 Pin = PC5
D33 Pin = PC4
D34 Pin = PC3
D35 Pin = PC2
D36 Pin = PC1
D37 Pin = PC0
D38 Pin = PD7
D39 Pin = PG2
D40 Pin = PG1
D41 Pin = PG0
D42 Pin = PL7
D43 Pin = PL6
D44 Pin = PL5
D45 Pin = PL4
D46 Pin = PL3
D47 Pin = PL2
D48 Pin = PL1
D49 Pin = PL0
D50 Pin = PB3
D51 Pin = PB2
D52 Pin = PB1
D53 Pin = PB0
)
+330
View File
@@ -0,0 +1,330 @@
// +build atsame54_xpro
package machine
import (
"device/sam"
"runtime/interrupt"
)
// Definition for compatibility, but not used
const RESET_MAGIC_VALUE = 0x00000000
const (
LED = PC18
BUTTON = PB31
)
const (
// https://ww1.microchip.com/downloads/en/DeviceDoc/70005321A.pdf
// Extension Header EXT1
EXT1_PIN3_ADC_P = PB04
EXT1_PIN4_ADC_N = PB05
EXT1_PIN5_GPIO1 = PA06
EXT1_PIN6_GPIO2 = PA07
EXT1_PIN7_PWM_P = PB08
EXT1_PIN8_PWM_N = PB09
EXT1_PIN9_IRQ = PB07
EXT1_PIN9_GPIO = PB07
EXT1_PIN10_SPI_SS_B = PA27
EXT1_PIN10_GPIO = PA27
EXT1_PIN11_TWI_SDA = PA22
EXT1_PIN12_TWI_SCL = PA23
EXT1_PIN13_UART_RX = PA05
EXT1_PIN14_UART_TX = PA04
EXT1_PIN15_SPI_SS_A = PB28
EXT1_PIN16_SPI_SDO = PB27
EXT1_PIN17_SPI_SDI = PB29
EXT1_PIN18_SPI_SCK = PB26
// Extension Header EXT2
EXT2_PIN3_ADC_P = PB00
EXT2_PIN4_ADC_N = PA03
EXT2_PIN5_GPIO1 = PB01
EXT2_PIN6_GPIO2 = PB06
EXT2_PIN7_PWM_P = PB14
EXT2_PIN8_PWM_N = PB15
EXT2_PIN9_IRQ = PD00
EXT2_PIN9_GPIO = PD00
EXT2_PIN10_SPI_SS_B = PB02
EXT2_PIN10_GPIO = PB02
EXT2_PIN11_TWI_SDA = PD08
EXT2_PIN12_TWI_SCL = PD09
EXT2_PIN13_UART_RX = PB17
EXT2_PIN14_UART_TX = PB16
EXT2_PIN15_SPI_SS_A = PC06
EXT2_PIN16_SPI_SDO = PC04
EXT2_PIN17_SPI_SDI = PC07
EXT2_PIN18_SPI_SCK = PC05
// Extension Header EXT3
EXT3_PIN3_ADC_P = PC02
EXT3_PIN4_ADC_N = PC03
EXT3_PIN5_GPIO1 = PC01
EXT3_PIN6_GPIO2 = PC10
EXT3_PIN7_PWM_P = PD10
EXT3_PIN8_PWM_N = PD11
EXT3_PIN9_IRQ = PC30
EXT3_PIN9_GPIO = PC30
EXT3_PIN10_SPI_SS_B = PC31
EXT3_PIN10_GPIO = PC31
EXT3_PIN11_TWI_SDA = PD08
EXT3_PIN12_TWI_SCL = PD09
EXT3_PIN13_UART_RX = PC23
EXT3_PIN14_UART_TX = PC22
EXT3_PIN15_SPI_SS_A = PC14
EXT3_PIN16_SPI_SDO = PC04
EXT3_PIN17_SPI_SDI = PC07
EXT3_PIN18_SPI_SCK = PC05
// SD_CARD
SD_CARD_MCDA0 = PB18
SD_CARD_MCDA1 = PB19
SD_CARD_MCDA2 = PB20
SD_CARD_MCDA3 = PB21
SD_CARD_MCCK = PA21
SD_CARD_MCCDA = PA20
SD_CARD_DETECT = PD20
SD_CARD_PROTECT = PD21
// I2C
I2C_SDA = PD08
I2C_SCL = PD09
// CAN
CAN0_TX = PA22
CAN0_RX = PA23
CAN1_STANDBY = PC13
CAN1_TX = PB12
CAN1_RX = PB13
CAN_STANDBY = CAN1_STANDBY
CAN_TX = CAN1_TX
CAN_RX = CAN1_RX
// PDEC
PDEC_PHASE_A = PC16
PDEC_PHASE_B = PC17
PDEC_INDEX = PC18
// PCC
PCC_I2C_SDA = PD08
PCC_I2C_SCL = PD09
PCC_VSYNC_DEN1 = PA12
PCC_HSYNC_DEN2 = PA13
PCC_CLK = PA14
PCC_XCLK = PA15
PCC_DATA00 = PA16
PCC_DATA01 = PA17
PCC_DATA02 = PA18
PCC_DATA03 = PA19
PCC_DATA04 = PA20
PCC_DATA05 = PA21
PCC_DATA06 = PA22
PCC_DATA07 = PA23
PCC_DATA08 = PB14
PCC_DATA09 = PB15
PCC_RESET = PC12
PCC_PWDN = PC11
// Ethernet
ETHERNET_TXCK = PA14
ETHERNET_TXEN = PA17
ETHERNET_TX0 = PA18
ETHERNET_TX1 = PA19
ETHERNET_RXER = PA15
ETHERNET_RX0 = PA13
ETHERNET_RX1 = PA12
ETHERNET_RXDV = PC20
ETHERNET_MDIO = PC12
ETHERNET_MDC = PC11
ETHERNET_INT = PD12
ETHERNET_RESET = PC21
PIN_QT_BUTTON = PA16
PIN_BTN0 = PB31
PIN_ETH_LED = PC15
PIN_LED0 = PC18
PIN_ADC_DAC = PA02
PIN_VBUS_DETECT = PC00
PIN_USB_ID = PC19
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART pins
const (
// Extension Header EXT1
UART_TX_PIN = PA04 // TX : SERCOM0/PAD[0]
UART_RX_PIN = PA05 // RX : SERCOM0/PAD[1]
// Extension Header EXT2
UART2_TX_PIN = PB16 // TX : SERCOM5/PAD[0]
UART2_RX_PIN = PB17 // RX : SERCOM5/PAD[1]
// Extension Header EXT3
UART3_TX_PIN = PC22 // TX : SERCOM1/PAD[0]
UART3_RX_PIN = PC23 // RX : SERCOM1/PAD[1]
// Virtual COM Port
UART4_TX_PIN = PB25 // TX : SERCOM2/PAD[0]
UART4_RX_PIN = PB24 // RX : SERCOM2/PAD[1]
)
// I2C pins
const (
// Extension Header EXT1
SDA0_PIN = PA22 // SDA: SERCOM3/PAD[0]
SCL0_PIN = PA23 // SCL: SERCOM3/PAD[1]
// Extension Header EXT2
SDA1_PIN = PD08 // SDA: SERCOM7/PAD[0]
SCL1_PIN = PD09 // SCL: SERCOM7/PAD[1]
// Extension Header EXT3
SDA2_PIN = PD08 // SDA: SERCOM7/PAD[0]
SCL2_PIN = PD09 // SCL: SERCOM7/PAD[1]
// Data Gateway Interface
SDA_DGI_PIN = PD08 // SDA: SERCOM7/PAD[0]
SCL_DGI_PIN = PD09 // SCL: SERCOM7/PAD[1]
SDA_PIN = SDA0_PIN
SCL_PIN = SCL0_PIN
)
// SPI pins
const (
// Extension Header EXT1
SPI0_SCK_PIN = PB26 // SCK: SERCOM4/PAD[1]
SPI0_SDO_PIN = PB27 // SDO: SERCOM4/PAD[0]
SPI0_SDI_PIN = PB29 // SDI: SERCOM4/PAD[3]
SPI0_SS_PIN = PB28 // SS : SERCOM4/PAD[2]
// Extension Header EXT2
SPI1_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1]
SPI1_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0]
SPI1_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3]
SPI1_SS_PIN = PC06 // SS : SERCOM6/PAD[2]
// Extension Header EXT3
SPI2_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1]
SPI2_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0]
SPI2_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3]
SPI2_SS_PIN = PC14 // SS : GPIO
// Data Gateway Interface
SPI_DGI_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1]
SPI_DGI_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0]
SPI_DGI_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3]
SPI_DGI_SS_PIN = PD01 // SS : GPIO
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "SAM E54 Xplained Pro"
usb_STRING_MANUFACTURER = "Atmel"
)
var (
usb_VID uint16 = 0x03EB
usb_PID uint16 = 0x2404
)
// UART on the SAM E54 Xplained Pro
var (
// Extension Header EXT1
UART1 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
}
// Extension Header EXT2
UART2 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM5_USART_INT,
SERCOM: 5,
}
// Extension Header EXT3
UART3 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM1_USART_INT,
SERCOM: 1,
}
// EDBG Virtual COM Port
UART4 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM2_USART_INT,
SERCOM: 2,
}
)
func init() {
UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART1.handleInterrupt)
UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, UART2.handleInterrupt)
UART3.Interrupt = interrupt.New(sam.IRQ_SERCOM1_2, UART3.handleInterrupt)
UART4.Interrupt = interrupt.New(sam.IRQ_SERCOM2_2, UART4.handleInterrupt)
}
// I2C on the SAM E54 Xplained Pro
var (
// Extension Header EXT1
I2C0 = I2C{
Bus: sam.SERCOM3_I2CM,
SERCOM: 3,
}
// Extension Header EXT2
I2C1 = I2C{
Bus: sam.SERCOM7_I2CM,
SERCOM: 7,
}
// Extension Header EXT3
I2C2 = I2C{
Bus: sam.SERCOM7_I2CM,
SERCOM: 7,
}
// Data Gateway Interface
I2C3 = I2C{
Bus: sam.SERCOM7_I2CM,
SERCOM: 7,
}
)
// SPI on the SAM E54 Xplained Pro
var (
// Extension Header EXT1
SPI0 = SPI{
Bus: sam.SERCOM4_SPIM,
SERCOM: 4,
}
// Extension Header EXT2
SPI1 = SPI{
Bus: sam.SERCOM6_SPIM,
SERCOM: 6,
}
// Extension Header EXT3
SPI2 = SPI{
Bus: sam.SERCOM6_SPIM,
SERCOM: 6,
}
// Data Gateway Interface
SPI3 = SPI{
Bus: sam.SERCOM6_SPIM,
SERCOM: 6,
}
)
+2 -2
View File
@@ -25,8 +25,8 @@ const (
// Analog Pins
const (
A0 = PA02 // PWM available, also ADC/AIN[0]
A1 = PA05 // ADC/AIN[5]
A0 = PA02 // ADC/AIN[0]
A1 = PA05 // PWM available, also ADC/AIN[5]
A2 = PA06 // PWM available, also ADC/AIN[6]
A3 = PA07 // PWM available, also ADC/AIN[7]
A4 = PB03 // PORTB
+142
View File
@@ -0,0 +1,142 @@
// +build feather_m4_can
package machine
import (
"device/sam"
"runtime/interrupt"
)
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PB17 // UART0 RX/PWM available
D1 = PB16 // UART0 TX/PWM available
D4 = PA14 // PWM available
D5 = PA16 // PWM available
D6 = PA18 // PWM available
D7 = PB03 // neopixel power
D8 = PB02 // built-in neopixel
D9 = PA19 // PWM available
D10 = PA20 // can be used for PWM or UART1 TX
D11 = PA21 // can be used for PWM or UART1 RX
D12 = PA22 // PWM available
D13 = PA23 // PWM available
D21 = PA13 // PWM available
D22 = PA12 // PWM available
D23 = PB22 // PWM available
D24 = PB23 // PWM available
D25 = PA17 // PWM available
)
// Analog pins
const (
A0 = PA02 // ADC/AIN[0]
A1 = PA05 // ADC/AIN[2]
A2 = PB08 // ADC/AIN[3]
A3 = PB09 // ADC/AIN[4]
A4 = PA04 // ADC/AIN[5]
A5 = PA06 // ADC/AIN[10]
)
const (
LED = D13
NEOPIXELS = D8
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
const (
UART_TX_PIN = D1
UART_RX_PIN = D0
)
const (
UART2_TX_PIN = A4
UART2_RX_PIN = A5
)
// I2C pins
const (
SDA_PIN = D22 // SDA: SERCOM2/PAD[0]
SCL_PIN = D21 // SCL: SERCOM2/PAD[1]
)
// SPI pins
const (
SPI0_SCK_PIN = D25 // SCK: SERCOM1/PAD[1]
SPI0_SDO_PIN = D24 // SDO: SERCOM1/PAD[3]
SPI0_SDI_PIN = D23 // SDI: SERCOM1/PAD[2]
)
// CAN pins
const (
CAN0_TX = PA22
CAN0_RX = PA23
CAN1_STANDBY = PB12
CAN1_TX = PB14
CAN1_RX = PB15
BOOST_EN = PB13 // power control of CAN1's TCAN1051HGV (H: enable)
CAN_STANDBY = CAN1_STANDBY
CAN_S = CAN1_STANDBY
CAN_TX = CAN1_TX
CAN_RX = CAN1_RX
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "Adafruit Feather M4 CAN"
usb_STRING_MANUFACTURER = "Adafruit"
)
var (
usb_VID uint16 = 0x239A
usb_PID uint16 = 0x80CD
)
var (
UART1 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM5_USART_INT,
SERCOM: 5,
}
UART2 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
}
)
func init() {
UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, UART1.handleInterrupt)
UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt)
// turn on neopixel
D7.Configure(PinConfig{Mode: PinOutput})
D7.High()
}
// I2C on the Feather M4.
var (
I2C0 = &I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
)
// SPI on the Feather M4.
var (
SPI0 = SPI{
Bus: sam.SERCOM1_SPIM,
SERCOM: 1,
}
)
+13 -2
View File
@@ -41,13 +41,17 @@ const (
// LORA RFM95 Radio
RFM95_DIO0_PIN = PC13
//TinyGo UART is MCU LPUSART1
// TinyGo UART is MCU LPUSART1
UART_RX_PIN = PA13
UART_TX_PIN = PA14
//TinyGo UART1 is MCU USART1
// TinyGo UART1 is MCU USART1
UART1_RX_PIN = PB6
UART1_TX_PIN = PB7
// MPU9250 Nine-Axis (Gyro + Accelerometer + Compass)
I2C0_SCL_PIN = PA9
I2C0_SDA_PIN = PA10
)
var (
@@ -68,6 +72,13 @@ var (
RxAltFuncSelector: 0,
}
// MPU9250 Nine-Axis (Gyro + Accelerometer + Compass)
I2C1 = &I2C{
Bus: stm32.I2C1,
AltFuncSelector: 6,
}
I2C0 = I2C1
// SPI
SPI0 = SPI{
Bus: stm32.SPI1,
-5
View File
@@ -5,11 +5,6 @@ package machine
// The micro:bit does not have a 32kHz crystal on board.
const HasLowFrequencyCrystal = false
const (
LED = P13
LED1 = LED
)
// Buttons on the micro:bit v2 (A and B)
const (
BUTTON Pin = BUTTONA
+92
View File
@@ -0,0 +1,92 @@
// +build nucleol031k6
package machine
import (
"device/stm32"
"runtime/interrupt"
)
const (
LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN
LED_GREEN = PB3
)
const (
// Arduino Pins
A0 = PA0 // ADC_IN0
A1 = PA1 // ADC_IN1
A2 = PA3 // ADC_IN3
A3 = PA4 // ADC_IN4
A4 = PA5 // ADC_IN5 || I2C1_SDA
A5 = PA6 // ADC_IN6 || I2C1_SCL
A6 = PA7 // ADC_IN7
A7 = PA2 // ADC_IN2
D0 = PA10 // USART1_TX
D1 = PA9 // USART1_RX
D2 = PA12
D3 = PB0 // TIM2_CH3
D4 = PB7
D5 = PB6 // TIM16_CH1N
D6 = PB1 // TIM14_CH1
D9 = PA8 // TIM1_CH1
D10 = PA11 // SPI_CS || TIM1_CH4
D11 = PB5 // SPI1_MOSI || TIM3_CH2
D12 = PB4 // SPI1_MISO
D13 = PB3 // SPI1_SCK
)
const (
// UART pins
// PA2 and PA15 are connected to the ST-Link Virtual Com Port (VCP)
UART_TX_PIN = PA2
UART_RX_PIN = PA15
// SPI
SPI1_SCK_PIN = PB3
SPI1_SDI_PIN = PB5
SPI1_SDO_PIN = PB4
SPI0_SCK_PIN = SPI1_SCK_PIN
SPI0_SDI_PIN = SPI1_SDI_PIN
SPI0_SDO_PIN = SPI1_SDO_PIN
// I2C pins
// PB6 and PB7 are mapped to CN4 pin 7 and CN4 pin 8 respectively with the
// default solder bridge settings
I2C0_SCL_PIN = PB7
I2C0_SDA_PIN = PB6
I2C0_ALT_FUNC = 1
)
var (
// USART2 is the hardware serial port connected to the onboard ST-LINK
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
// Both UART0 and UART1 refer to USART2.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
TxAltFuncSelector: 4,
RxAltFuncSelector: 4,
}
UART1 = &UART0
// I2C1 is documented, alias to I2C0 as well
I2C1 = &I2C{
Bus: stm32.I2C1,
AltFuncSelector: 1,
}
I2C0 = I2C1
// SPI
SPI0 = SPI{
Bus: stm32.SPI1,
AltFuncSelector: 0,
}
SPI1 = &SPI0
)
func init() {
UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt)
}
+44 -6
View File
@@ -13,20 +13,53 @@ const (
LED_GREEN = PB3
)
// UART pins
const (
// Arduino Pins
A0 = PA0
A1 = PA1
A2 = PA3
A3 = PA4
A4 = PA5
A5 = PA6
A6 = PA7
A7 = PA2
D0 = PA10
D1 = PA9
D2 = PA12
D3 = PB0
D4 = PB7
D5 = PB6
D6 = PB1
D7 = PC14
D8 = PC15
D9 = PA8
D10 = PA11
D11 = PB5
D12 = PB4
D13 = PB3
)
const (
// UART pins
// PA2 and PA15 are connected to the ST-Link Virtual Com Port (VCP)
UART_TX_PIN = PA2
UART_RX_PIN = PA15
)
// I2C pins
const (
// I2C pins
// With default solder bridge settings:
// PB6 / Arduino D5 / CN3 Pin 8 is SCL
// PB7 / Arduino D4 / CN3 Pin 7 is SDA
I2C0_SCL_PIN = PB6
I2C0_SDA_PIN = PB7
// SPI pins
SPI1_SCK_PIN = PB3
SPI1_SDI_PIN = PB5
SPI1_SDO_PIN = PB4
SPI0_SCK_PIN = SPI1_SCK_PIN
SPI0_SDI_PIN = SPI1_SDI_PIN
SPI0_SDO_PIN = SPI1_SDO_PIN
)
var (
@@ -40,15 +73,20 @@ var (
RxAltFuncSelector: 3,
}
UART1 = &UART0
)
var (
// I2C1 is documented, alias to I2C0 as well
I2C1 = &I2C{
Bus: stm32.I2C1,
AltFuncSelector: 4,
}
I2C0 = I2C1
// SPI1 is documented, alias to SPI0 as well
SPI1 = &SPI{
Bus: stm32.SPI1,
AltFuncSelector: 5,
}
SPI0 = SPI1
)
func init() {
+2 -2
View File
@@ -94,8 +94,8 @@ const (
// I2C on the QT Py M0.
var (
I2C0 = &I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
Bus: sam.SERCOM1_I2CM,
SERCOM: 1,
}
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build atmega nrf sam stm32,!stm32l0 fe310 k210
// +build atmega nrf sam stm32 fe310 k210
package machine
+6 -4
View File
@@ -10,6 +10,11 @@ var (
ErrNoPinChangeChannel = errors.New("machine: no channel available for pin interrupt")
)
// PinMode sets the direction and pull mode of the pin. For example, PinOutput
// sets the pin as an output and PinInputPullup sets the pin as an input with a
// pull-up.
type PinMode uint8
type PinConfig struct {
Mode PinMode
}
@@ -37,10 +42,7 @@ func (p Pin) Low() {
p.Set(false)
}
type PWM struct {
Pin Pin
}
type ADC struct {
Pin Pin
Bus uint8
}
+931
View File
@@ -0,0 +1,931 @@
// +build avr,atmega1280
package machine
import (
"device/avr"
"runtime/interrupt"
"runtime/volatile"
)
const irq_USART0_RX = avr.IRQ_USART0_RX
const (
portA Pin = iota * 8
portB
portC
portD
portE
portF
portG
portH
portJ
portK
portL
)
const (
PA0 = portA + 0
PA1 = portA + 1
PA2 = portA + 2
PA3 = portA + 3
PA4 = portA + 4
PA5 = portA + 5
PA6 = portA + 6
PA7 = portA + 7
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
PD0 = portD + 0
PD1 = portD + 1
PD2 = portD + 2
PD3 = portD + 3
PD7 = portD + 7
PE0 = portE + 0
PE1 = portE + 1
PE3 = portE + 3
PE4 = portE + 4
PE5 = portE + 5
PE6 = portE + 6
PF0 = portF + 0
PF1 = portF + 1
PF2 = portF + 2
PF3 = portF + 3
PF4 = portF + 4
PF5 = portF + 5
PF6 = portF + 6
PF7 = portF + 7
PG0 = portG + 0
PG1 = portG + 1
PG2 = portG + 2
PG5 = portG + 5
PH0 = portH + 0
PH1 = portH + 1
PH3 = portH + 3
PH4 = portH + 4
PH5 = portH + 5
PH6 = portH + 6
PJ0 = portJ + 0
PJ1 = portJ + 1
PK0 = portK + 0
PK1 = portK + 1
PK2 = portK + 2
PK3 = portK + 3
PK4 = portH + 4
PK5 = portH + 5
PK6 = portH + 6
PK7 = portH + 7
PL0 = portL + 0
PL1 = portL + 1
PL2 = portL + 2
PL3 = portL + 3
PL4 = portL + 4
PL5 = portL + 5
PL6 = portL + 6
PL7 = portL + 7
)
// getPortMask returns the PORTx register and mask for the pin.
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
switch {
case p >= PA0 && p <= PA7:
return avr.PORTA, 1 << uint8(p-portA)
case p >= PB0 && p <= PB7:
return avr.PORTB, 1 << uint8(p-portB)
case p >= PC0 && p <= PC7:
return avr.PORTC, 1 << uint8(p-portC)
case p >= PD0 && p <= PD7:
return avr.PORTD, 1 << uint8(p-portD)
case p >= PE0 && p <= PE6:
return avr.PORTE, 1 << uint8(p-portE)
case p >= PF0 && p <= PF7:
return avr.PORTF, 1 << uint8(p-portF)
case p >= PG0 && p <= PG5:
return avr.PORTG, 1 << uint8(p-portG)
case p >= PH0 && p <= PH6:
return avr.PORTH, 1 << uint8(p-portH)
case p >= PJ0 && p <= PJ1:
return avr.PORTJ, 1 << uint8(p-portJ)
case p >= PK0 && p <= PK7:
return avr.PORTK, 1 << uint8(p-portK)
case p >= PL0 && p <= PL7:
return avr.PORTL, 1 << uint8(p-portL)
default:
return avr.PORTA, 255
}
}
// PWM is one PWM peripheral, which consists of a counter and two output
// channels (that can be connected to two fixed pins). You can set the frequency
// using SetPeriod, but only for all the channels in this PWM peripheral at
// once.
type PWM struct {
num uint8
}
var (
Timer0 = PWM{0} // 8 bit timer for PB7 and PG5
Timer1 = PWM{1} // 16 bit timer for PB5 and PB6
Timer2 = PWM{2} // 8 bit timer for PB4 and PH6
Timer3 = PWM{3} // 16 bit timer for PE3, PE4 and PE5
Timer4 = PWM{4} // 16 bit timer for PH3, PH4 and PH5
Timer5 = PWM{5} // 16 bit timer for PL3, PL4 and PL5
)
// Configure enables and configures this PWM.
//
// For the two 8 bit timers, there is only a limited number of periods
// available, namely the CPU frequency divided by 256 and again divided by 1, 8,
// 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs,
// 128µs, 1024µs, 4096µs, or 16384µs.
func (pwm PWM) Configure(config PWMConfig) error {
switch pwm.num {
case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2)
// Calculate the timer prescaler.
// While we could configure a flexible top, that would sacrifice one of
// the PWM output compare registers and thus a PWM channel. I've chosen
// to instead limit this timer to a fixed number of frequencies.
var prescaler uint8
switch config.Period {
case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()):
prescaler = 1
case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()):
prescaler = 2
case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()):
prescaler = 3
case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()):
prescaler = 4
case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()):
prescaler = 5
default:
return ErrPWMPeriodTooLong
}
if pwm.num == 0 {
avr.TCCR0B.Set(prescaler)
// Set the PWM mode to fast PWM (mode = 3).
avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01)
} else {
avr.TCCR2B.Set(prescaler)
// Set the PWM mode to fast PWM (mode = 3).
avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21)
}
case 1, 3, 4, 5:
// The top value is the number of PWM ticks a PWM period takes. It is
// initially picked assuming an unlimited counter top and no PWM
// prescaler.
var top uint64
if config.Period == 0 {
// Use a top appropriate for LEDs. Picking a relatively low period
// here (0xff) for consistency with the other timers.
top = 0xff
} else {
// The formula below calculates the following formula, optimized:
// top = period * (CPUFrequency() / 1e9)
// By dividing the CPU frequency first (an operation that is easily
// optimized away) the period has less chance of overflowing.
top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000
}
// The ideal PWM period may be larger than would fit in the PWM counter,
// which is 16 bits (see maxTop). Therefore, try to make the PWM clock
// speed lower with a prescaler to make the top value fit the maximum
// top value.
const maxTop = 0x10000
var prescalingTop uint8
switch {
case top <= maxTop:
prescalingTop = 3<<3 | 1 // no prescaling
case top/8 <= maxTop:
prescalingTop = 3<<3 | 2 // divide by 8
top /= 8
case top/64 <= maxTop:
prescalingTop = 3<<3 | 3 // divide by 64
top /= 64
case top/256 <= maxTop:
prescalingTop = 3<<3 | 4 // divide by 256
top /= 256
case top/1024 <= maxTop:
prescalingTop = 3<<3 | 5 // divide by 1024
top /= 1024
default:
return ErrPWMPeriodTooLong
}
// A top of 0x10000 is at 100% duty cycle. Subtract one because the
// counter counts from 0, not 1 (avoiding an off-by-one).
top -= 1
switch pwm.num {
case 1:
avr.TCCR1A.Set(avr.TCCR1A_WGM11)
avr.TCCR1B.Set(prescalingTop)
avr.ICR1H.Set(uint8(top >> 8))
avr.ICR1L.Set(uint8(top))
case 3:
avr.TCCR3A.Set(avr.TCCR3A_WGM31)
avr.TCCR3B.Set(prescalingTop)
avr.ICR3H.Set(uint8(top >> 8))
avr.ICR3L.Set(uint8(top))
case 4:
avr.TCCR4A.Set(avr.TCCR4A_WGM41)
avr.TCCR4B.Set(prescalingTop)
avr.ICR4H.Set(uint8(top >> 8))
avr.ICR4L.Set(uint8(top))
case 5:
avr.TCCR5A.Set(avr.TCCR5A_WGM51)
avr.TCCR5B.Set(prescalingTop)
avr.ICR5H.Set(uint8(top >> 8))
avr.ICR5L.Set(uint8(top))
}
}
return nil
}
// SetPeriod updates the period of this PWM peripheral.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the PWM peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (pwm PWM) SetPeriod(period uint64) error {
if pwm.num == 0 || pwm.num == 2 {
return ErrPWMPeriodTooLong // TODO better error message
}
// The top value is the number of PWM ticks a PWM period takes. It is
// initially picked assuming an unlimited counter top and no PWM
// prescaler.
var top uint64
if period == 0 {
// Use a top appropriate for LEDs. Picking a relatively low period
// here (0xff) for consistency with the other timers.
top = 0xff
} else {
// The formula below calculates the following formula, optimized:
// top = period * (CPUFrequency() / 1e9)
// By dividing the CPU frequency first (an operation that is easily
// optimized away) the period has less chance of overflowing.
top = period * (uint64(CPUFrequency()) / 1000000) / 1000
}
var prescaler uint8
switch pwm.num {
case 1:
prescaler = avr.TCCR1B.Get() & 0x7
case 3:
prescaler = avr.TCCR3B.Get() & 0x7
case 4:
prescaler = avr.TCCR4B.Get() & 0x7
case 5:
prescaler = avr.TCCR5B.Get() & 0x7
}
switch prescaler {
case 1:
top /= 1
case 2:
top /= 8
case 3:
top /= 64
case 4:
top /= 256
case 5:
top /= 1024
}
// A top of 0x10000 is at 100% duty cycle. Subtract one because the counter
// counts from 0, not 1 (avoiding an off-by-one).
top -= 1
if top > 0xffff {
return ErrPWMPeriodTooLong
}
switch pwm.num {
case 1:
// Warning: this change is not atomic!
avr.ICR1H.Set(uint8(top >> 8))
avr.ICR1L.Set(uint8(top))
// ... and because of that, set the counter back to zero to avoid most of
// the effects of this non-atomicity.
avr.TCNT1H.Set(0)
avr.TCNT1L.Set(0)
case 3:
// Warning: this change is not atomic!
avr.ICR3H.Set(uint8(top >> 8))
avr.ICR3L.Set(uint8(top))
// ... and because of that, set the counter back to zero to avoid most of
// the effects of this non-atomicity.
avr.TCNT3H.Set(0)
avr.TCNT3L.Set(0)
case 4:
// Warning: this change is not atomic!
avr.ICR4H.Set(uint8(top >> 8))
avr.ICR4L.Set(uint8(top))
// ... and because of that, set the counter back to zero to avoid most of
// the effects of this non-atomicity.
avr.TCNT4H.Set(0)
avr.TCNT4L.Set(0)
case 5:
// Warning: this change is not atomic!
avr.ICR5H.Set(uint8(top >> 8))
avr.ICR5L.Set(uint8(top))
// ... and because of that, set the counter back to zero to avoid most of
// the effects of this non-atomicity.
avr.TCNT5H.Set(0)
avr.TCNT5L.Set(0)
}
return nil
}
// Top returns the current counter top, for use in duty cycle calculation. It
// will only change with a call to Configure or SetPeriod, otherwise it is
// constant.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to Set
// (see Set documentation for more information).
func (pwm PWM) Top() uint32 {
switch pwm.num {
case 1:
// Timer 1 has a configurable top value.
low := avr.ICR1L.Get()
high := avr.ICR1H.Get()
return uint32(high)<<8 | uint32(low) + 1
case 3:
// Timer 3 has a configurable top value.
low := avr.ICR3L.Get()
high := avr.ICR3H.Get()
return uint32(high)<<8 | uint32(low) + 1
case 4:
// Timer 4 has a configurable top value.
low := avr.ICR4L.Get()
high := avr.ICR4H.Get()
return uint32(high)<<8 | uint32(low) + 1
case 5:
// Timer 5 has a configurable top value.
low := avr.ICR5L.Get()
high := avr.ICR5H.Get()
return uint32(high)<<8 | uint32(low) + 1
}
// Other timers go from 0 to 0xff (0x100 or 256 in total).
return 256
}
// Counter returns the current counter value of the timer in this PWM
// peripheral. It may be useful for debugging.
func (pwm PWM) Counter() uint32 {
switch pwm.num {
case 0:
return uint32(avr.TCNT0.Get())
case 1:
mask := interrupt.Disable()
low := avr.TCNT1L.Get()
high := avr.TCNT1H.Get()
interrupt.Restore(mask)
return uint32(high)<<8 | uint32(low)
case 2:
return uint32(avr.TCNT2.Get())
case 3:
mask := interrupt.Disable()
low := avr.TCNT3L.Get()
high := avr.TCNT3H.Get()
interrupt.Restore(mask)
return uint32(high)<<8 | uint32(low)
case 4:
mask := interrupt.Disable()
low := avr.TCNT4L.Get()
high := avr.TCNT4H.Get()
interrupt.Restore(mask)
return uint32(high)<<8 | uint32(low)
case 5:
mask := interrupt.Disable()
low := avr.TCNT5L.Get()
high := avr.TCNT5H.Get()
interrupt.Restore(mask)
return uint32(high)<<8 | uint32(low)
}
// Unknown PWM.
return 0
}
// Period returns the used PWM period in nanoseconds. It might deviate slightly
// from the configured period due to rounding.
func (pwm PWM) Period() uint64 {
var prescaler uint8
switch pwm.num {
case 0:
prescaler = avr.TCCR0B.Get() & 0x7
case 1:
prescaler = avr.TCCR1B.Get() & 0x7
case 2:
prescaler = avr.TCCR2B.Get() & 0x7
case 3:
prescaler = avr.TCCR3B.Get() & 0x7
case 4:
prescaler = avr.TCCR4B.Get() & 0x7
case 5:
prescaler = avr.TCCR5B.Get() & 0x7
}
top := uint64(pwm.Top())
switch prescaler {
case 1: // prescaler 1
return 1 * top * 1000 / uint64(CPUFrequency()/1e6)
case 2: // prescaler 8
return 8 * top * 1000 / uint64(CPUFrequency()/1e6)
case 3: // prescaler 64
return 64 * top * 1000 / uint64(CPUFrequency()/1e6)
case 4: // prescaler 256
return 256 * top * 1000 / uint64(CPUFrequency()/1e6)
case 5: // prescaler 1024
return 1024 * top * 1000 / uint64(CPUFrequency()/1e6)
default: // unknown clock source
return 0
}
}
// Channel returns a PWM channel for the given pin.
func (pwm PWM) Channel(pin Pin) (uint8, error) {
pin.Configure(PinConfig{Mode: PinOutput})
pin.Low()
switch pwm.num {
case 0:
switch pin {
case PB7: // channel A
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
return 0, nil
case PG5: // channel B
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
return 1, nil
}
case 1:
switch pin {
case PB5: // channel A
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
return 0, nil
case PB6: // channel B
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
return 1, nil
}
case 2:
switch pin {
case PB4: // channel A
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
return 0, nil
case PH6: // channel B
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
return 1, nil
}
case 3:
switch pin {
case PE3: // channel A
avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1)
return 0, nil
case PE4: //channel B
avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1)
return 1, nil
case PE5: //channel C
avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1)
return 2, nil
}
case 4:
switch pin {
case PH3: // channel A
avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1)
return 0, nil
case PH4: //channel B
avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1)
return 1, nil
case PH5: //channel C
avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1)
return 2, nil
}
case 5:
switch pin {
case PL3: // channel A
avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1)
return 0, nil
case PL4: //channel B
avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1)
return 1, nil
case PL5: //channel C
avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1)
return 2, nil
}
}
return 0, ErrInvalidOutputPin
}
// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
//
// Note: the invert state may not be applied on the AVR until the next call to
// ch.Set().
func (pwm PWM) SetInverting(channel uint8, inverting bool) {
switch pwm.num {
case 0:
switch channel {
case 0: // channel A, PB7
if inverting {
avr.PORTB.SetBits(1 << 7) // PB7 high
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0)
} else {
avr.PORTB.ClearBits(1 << 7) // PB7 low
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0)
}
case 1: // channel B, PG5
if inverting {
avr.PORTG.SetBits(1 << 5) // PG5 high
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0)
} else {
avr.PORTG.ClearBits(1 << 5) // PG5 low
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0)
}
}
case 1:
// Note: the COM1A0/COM1B0 bit is not set with the configuration below.
// It will be set the following call to Set(), however.
switch channel {
case 0: // channel A, PB5
if inverting {
avr.PORTB.SetBits(1 << 5) // PB5 high
} else {
avr.PORTB.ClearBits(1 << 5) // PB5 low
}
case 1: // channel B, PB6
if inverting {
avr.PORTB.SetBits(1 << 6) // PB6 high
} else {
avr.PORTB.ClearBits(1 << 6) // PB6 low
}
}
case 2:
switch channel {
case 0: // channel A, PB4
if inverting {
avr.PORTB.SetBits(1 << 4) // PB4 high
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0)
} else {
avr.PORTB.ClearBits(1 << 4) // PB4 low
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0)
}
case 1: // channel B, PH6
if inverting {
avr.PORTH.SetBits(1 << 6) // PH6 high
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0)
} else {
avr.PORTH.ClearBits(1 << 6) // PH6 low
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0)
}
}
case 3:
// Note: the COM3A0/COM3B0 bit is not set with the configuration below.
// It will be set the following call to Set(), however.
switch channel {
case 0: // channel A, PE3
if inverting {
avr.PORTE.SetBits(1 << 3) // PE3 high
} else {
avr.PORTE.ClearBits(1 << 3) // PE3 low
}
case 1: // channel B, PE4
if inverting {
avr.PORTE.SetBits(1 << 4) // PE4 high
} else {
avr.PORTE.ClearBits(1 << 4) // PE4 low
}
case 2: // channel C, PE5
if inverting {
avr.PORTE.SetBits(1 << 5) // PE4 high
} else {
avr.PORTE.ClearBits(1 << 5) // PE4 low
}
}
case 4:
// Note: the COM3A0/COM3B0 bit is not set with the configuration below.
// It will be set the following call to Set(), however.
switch channel {
case 0: // channel A, PH3
if inverting {
avr.PORTH.SetBits(1 << 3) // PH3 high
} else {
avr.PORTH.ClearBits(1 << 3) // PH3 low
}
case 1: // channel B, PH4
if inverting {
avr.PORTH.SetBits(1 << 4) // PH4 high
} else {
avr.PORTH.ClearBits(1 << 4) // PH4 low
}
case 2: // channel C, PH5
if inverting {
avr.PORTH.SetBits(1 << 5) // PH4 high
} else {
avr.PORTH.ClearBits(1 << 5) // PH4 low
}
}
case 5:
// Note: the COM3A0/COM3B0 bit is not set with the configuration below.
// It will be set the following call to Set(), however.
switch channel {
case 0: // channel A, PL3
if inverting {
avr.PORTL.SetBits(1 << 3) // PL3 high
} else {
avr.PORTL.ClearBits(1 << 3) // PL3 low
}
case 1: // channel B, PL4
if inverting {
avr.PORTL.SetBits(1 << 4) // PL4 high
} else {
avr.PORTL.ClearBits(1 << 4) // PL4 low
}
case 2: // channel C, PH5
if inverting {
avr.PORTL.SetBits(1 << 5) // PL4 high
} else {
avr.PORTL.ClearBits(1 << 5) // PL4 low
}
}
}
}
// Set updates the channel value. This is used to control the channel duty
// cycle, in other words the fraction of time the channel output is high (or low
// when inverted). For example, to set it to a 25% duty cycle, use:
//
// pwm.Set(channel, pwm.Top() / 4)
//
// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel,
// pwm.Top()) will set the output to high, assuming the output isn't inverted.
func (pwm PWM) Set(channel uint8, value uint32) {
switch pwm.num {
case 0:
value := uint16(value)
switch channel {
case 0: // channel A
if value == 0 {
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1)
} else {
avr.OCR0A.Set(uint8(value - 1))
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
}
case 1: // channel B
if value == 0 {
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1)
} else {
avr.OCR0B.Set(uint8(value) - 1)
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
}
}
case 1:
mask := interrupt.Disable()
switch channel {
case 0: // channel A, PB5
if value == 0 {
avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR1AH.Set(uint8(value >> 8))
avr.OCR1AL.Set(uint8(value))
if avr.PORTB.HasBits(1 << 5) { // is PB1 high?
// Yes, set the inverting bit.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0)
} else {
// No, output is non-inverting.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
}
}
case 1: // channel B, PB6
if value == 0 {
avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR1BH.Set(uint8(value >> 8))
avr.OCR1BL.Set(uint8(value))
if avr.PORTB.HasBits(1 << 6) { // is PB6 high?
// Yes, set the inverting bit.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0)
} else {
// No, output is non-inverting.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
}
}
}
interrupt.Restore(mask)
case 2:
value := uint16(value)
switch channel {
case 0: // channel A
if value == 0 {
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1)
} else {
avr.OCR2A.Set(uint8(value - 1))
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
}
case 1: // channel B
if value == 0 {
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1)
} else {
avr.OCR2B.Set(uint8(value - 1))
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
}
}
case 3:
mask := interrupt.Disable()
switch channel {
case 0: // channel A, PE3
if value == 0 {
avr.TCCR3A.ClearBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR3AH.Set(uint8(value >> 8))
avr.OCR3AL.Set(uint8(value))
if avr.PORTE.HasBits(1 << 3) { // is PE3 high?
// Yes, set the inverting bit.
avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0)
} else {
// No, output is non-inverting.
avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1)
}
}
case 1: // channel B, PE4
if value == 0 {
avr.TCCR3A.ClearBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR3BH.Set(uint8(value >> 8))
avr.OCR3BL.Set(uint8(value))
if avr.PORTE.HasBits(1 << 4) { // is PE4 high?
// Yes, set the inverting bit.
avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0)
} else {
// No, output is non-inverting.
avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1)
}
}
case 2: // channel C, PE5
if value == 0 {
avr.TCCR3A.ClearBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR3CH.Set(uint8(value >> 8))
avr.OCR3CL.Set(uint8(value))
if avr.PORTE.HasBits(1 << 5) { // is PE5 high?
// Yes, set the inverting bit.
avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0)
} else {
// No, output is non-inverting.
avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1)
}
}
}
interrupt.Restore(mask)
case 4:
mask := interrupt.Disable()
switch channel {
case 0: // channel A, PH3
if value == 0 {
avr.TCCR4A.ClearBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR4AH.Set(uint8(value >> 8))
avr.OCR4AL.Set(uint8(value))
if avr.PORTH.HasBits(1 << 3) { // is PH3 high?
// Yes, set the inverting bit.
avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0)
} else {
// No, output is non-inverting.
avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1)
}
}
case 1: // channel B, PH4
if value == 0 {
avr.TCCR4A.ClearBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR4BH.Set(uint8(value >> 8))
avr.OCR4BL.Set(uint8(value))
if avr.PORTH.HasBits(1 << 4) { // is PH4 high?
// Yes, set the inverting bit.
avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0)
} else {
// No, output is non-inverting.
avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1)
}
}
case 2: // channel C, PH5
if value == 0 {
avr.TCCR4A.ClearBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR4CH.Set(uint8(value >> 8))
avr.OCR4CL.Set(uint8(value))
if avr.PORTH.HasBits(1 << 5) { // is PH5 high?
// Yes, set the inverting bit.
avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0)
} else {
// No, output is non-inverting.
avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1)
}
}
}
interrupt.Restore(mask)
case 5:
mask := interrupt.Disable()
switch channel {
case 0: // channel A, PL3
if value == 0 {
avr.TCCR5A.ClearBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR5AH.Set(uint8(value >> 8))
avr.OCR5AL.Set(uint8(value))
if avr.PORTL.HasBits(1 << 3) { // is PL3 high?
// Yes, set the inverting bit.
avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0)
} else {
// No, output is non-inverting.
avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1)
}
}
case 1: // channel B, PL4
if value == 0 {
avr.TCCR5A.ClearBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR5BH.Set(uint8(value >> 8))
avr.OCR5BL.Set(uint8(value))
if avr.PORTL.HasBits(1 << 4) { // is PL4 high?
// Yes, set the inverting bit.
avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0)
} else {
// No, output is non-inverting.
avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1)
}
}
case 2: // channel C, PL5
if value == 0 {
avr.TCCR5A.ClearBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR5CH.Set(uint8(value >> 8))
avr.OCR5CL.Set(uint8(value))
if avr.PORTL.HasBits(1 << 5) { // is PL5 high?
// Yes, set the inverting bit.
avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0)
} else {
// No, output is non-inverting.
avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1)
}
}
}
interrupt.Restore(mask)
}
}
// SPI configuration
var SPI0 = SPI{
spcr: avr.SPCR,
spdr: avr.SPDR,
spsr: avr.SPSR,
sck: PB1,
sdo: PB2,
sdi: PB3,
cs: PB0}
+421 -59
View File
@@ -4,6 +4,7 @@ package machine
import (
"device/avr"
"runtime/interrupt"
"runtime/volatile"
)
@@ -21,71 +22,432 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) {
}
}
// InitPWM initializes the registers needed for PWM.
func InitPWM() {
// use waveform generation
avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)
// set timer 0 prescale factor to 64
avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00)
// set timer 1 prescale factor to 64
avr.TCCR1B.SetBits(avr.TCCR1B_CS11)
// put timer 1 in 8-bit phase correct pwm mode
avr.TCCR1A.SetBits(avr.TCCR1A_WGM10)
// set timer 2 prescale factor to 64
avr.TCCR2B.SetBits(avr.TCCR2B_CS22)
// configure timer 2 for phase correct pwm (8-bit)
avr.TCCR2A.SetBits(avr.TCCR2A_WGM20)
// PWM is one PWM peripheral, which consists of a counter and two output
// channels (that can be connected to two fixed pins). You can set the frequency
// using SetPeriod, but only for all the channels in this PWM peripheral at
// once.
type PWM struct {
num uint8
}
// Configure configures a PWM pin for output.
func (pwm PWM) Configure() error {
switch pwm.Pin / 8 {
case 0: // port B
avr.DDRB.SetBits(1 << uint8(pwm.Pin))
case 2: // port D
avr.DDRD.SetBits(1 << uint8(pwm.Pin-16))
var (
Timer0 = PWM{0} // 8 bit timer for PD5 and PD6
Timer1 = PWM{1} // 16 bit timer for PB1 and PB2
Timer2 = PWM{2} // 8 bit timer for PB3 and PD3
)
// Configure enables and configures this PWM.
//
// For the two 8 bit timers, there is only a limited number of periods
// available, namely the CPU frequency divided by 256 and again divided by 1, 8,
// 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs,
// 128µs, 1024µs, 4096µs, or 16384µs.
func (pwm PWM) Configure(config PWMConfig) error {
switch pwm.num {
case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2)
// Calculate the timer prescaler.
// While we could configure a flexible top, that would sacrifice one of
// the PWM output compare registers and thus a PWM channel. I've chosen
// to instead limit this timer to a fixed number of frequencies.
var prescaler uint8
switch config.Period {
case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()):
prescaler = 1
case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()):
prescaler = 2
case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()):
prescaler = 3
case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()):
prescaler = 4
case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()):
prescaler = 5
default:
return ErrPWMPeriodTooLong
}
if pwm.num == 0 {
avr.TCCR0B.Set(prescaler)
// Set the PWM mode to fast PWM (mode = 3).
avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01)
} else {
avr.TCCR2B.Set(prescaler)
// Set the PWM mode to fast PWM (mode = 3).
avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21)
}
case 1: // Timer/counter 1
// The top value is the number of PWM ticks a PWM period takes. It is
// initially picked assuming an unlimited counter top and no PWM
// prescaler.
var top uint64
if config.Period == 0 {
// Use a top appropriate for LEDs. Picking a relatively low period
// here (0xff) for consistency with the other timers.
top = 0xff
} else {
// The formula below calculates the following formula, optimized:
// top = period * (CPUFrequency() / 1e9)
// By dividing the CPU frequency first (an operation that is easily
// optimized away) the period has less chance of overflowing.
top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000
}
avr.TCCR1A.Set(avr.TCCR1A_WGM11)
// The ideal PWM period may be larger than would fit in the PWM counter,
// which is 16 bits (see maxTop). Therefore, try to make the PWM clock
// speed lower with a prescaler to make the top value fit the maximum
// top value.
const maxTop = 0x10000
switch {
case top <= maxTop:
avr.TCCR1B.Set(3<<3 | 1) // no prescaling
case top/8 <= maxTop:
avr.TCCR1B.Set(3<<3 | 2) // divide by 8
top /= 8
case top/64 <= maxTop:
avr.TCCR1B.Set(3<<3 | 3) // divide by 64
top /= 64
case top/256 <= maxTop:
avr.TCCR1B.Set(3<<3 | 4) // divide by 256
top /= 256
case top/1024 <= maxTop:
avr.TCCR1B.Set(3<<3 | 5) // divide by 1024
top /= 1024
default:
return ErrPWMPeriodTooLong
}
// A top of 0x10000 is at 100% duty cycle. Subtract one because the
// counter counts from 0, not 1 (avoiding an off-by-one).
top -= 1
avr.ICR1H.Set(uint8(top >> 8))
avr.ICR1L.Set(uint8(top))
}
return nil
}
// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a
// 8-bit value ranging from 0 to 255.
func (pwm PWM) Set(value uint16) {
value8 := uint8(value >> 8)
switch pwm.Pin {
case PD3:
// connect pwm to pin on timer 2, channel B
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
avr.OCR2B.Set(value8) // set pwm duty
case PD5:
// connect pwm to pin on timer 0, channel B
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
avr.OCR0B.Set(value8) // set pwm duty
case PD6:
// connect pwm to pin on timer 0, channel A
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
avr.OCR0A.Set(value8) // set pwm duty
case PB1:
// connect pwm to pin on timer 1, channel A
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
// this is a 16-bit value, but we only currently allow the low order bits to be set
avr.OCR1AL.Set(value8) // set pwm duty
case PB2:
// connect pwm to pin on timer 1, channel B
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
// this is a 16-bit value, but we only currently allow the low order bits to be set
avr.OCR1BL.Set(value8) // set pwm duty
case PB3:
// connect pwm to pin on timer 2, channel A
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
avr.OCR2A.Set(value8) // set pwm duty
default:
panic("Invalid PWM pin")
// SetPeriod updates the period of this PWM peripheral.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the PWM peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (pwm PWM) SetPeriod(period uint64) error {
if pwm.num != 1 {
return ErrPWMPeriodTooLong // TODO better error message
}
// The top value is the number of PWM ticks a PWM period takes. It is
// initially picked assuming an unlimited counter top and no PWM
// prescaler.
var top uint64
if period == 0 {
// Use a top appropriate for LEDs. Picking a relatively low period
// here (0xff) for consistency with the other timers.
top = 0xff
} else {
// The formula below calculates the following formula, optimized:
// top = period * (CPUFrequency() / 1e9)
// By dividing the CPU frequency first (an operation that is easily
// optimized away) the period has less chance of overflowing.
top = period * (uint64(CPUFrequency()) / 1000000) / 1000
}
prescaler := avr.TCCR1B.Get() & 0x7
switch prescaler {
case 1:
top /= 1
case 2:
top /= 8
case 3:
top /= 64
case 4:
top /= 256
case 5:
top /= 1024
}
// A top of 0x10000 is at 100% duty cycle. Subtract one because the counter
// counts from 0, not 1 (avoiding an off-by-one).
top -= 1
if top > 0xffff {
return ErrPWMPeriodTooLong
}
// Warning: this change is not atomic!
avr.ICR1H.Set(uint8(top >> 8))
avr.ICR1L.Set(uint8(top))
// ... and because of that, set the counter back to zero to avoid most of
// the effects of this non-atomicity.
avr.TCNT1H.Set(0)
avr.TCNT1L.Set(0)
return nil
}
// Top returns the current counter top, for use in duty cycle calculation. It
// will only change with a call to Configure or SetPeriod, otherwise it is
// constant.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to Set
// (see Set documentation for more information).
func (pwm PWM) Top() uint32 {
if pwm.num == 1 {
// Timer 1 has a configurable top value.
low := avr.ICR1L.Get()
high := avr.ICR1H.Get()
return uint32(high)<<8 | uint32(low) + 1
}
// Other timers go from 0 to 0xff (0x100 or 256 in total).
return 256
}
// Counter returns the current counter value of the timer in this PWM
// peripheral. It may be useful for debugging.
func (pwm PWM) Counter() uint32 {
switch pwm.num {
case 0:
return uint32(avr.TCNT0.Get())
case 1:
mask := interrupt.Disable()
low := avr.TCNT1L.Get()
high := avr.TCNT1H.Get()
interrupt.Restore(mask)
return uint32(high)<<8 | uint32(low)
case 2:
return uint32(avr.TCNT2.Get())
}
// Unknown PWM.
return 0
}
// Period returns the used PWM period in nanoseconds. It might deviate slightly
// from the configured period due to rounding.
func (pwm PWM) Period() uint64 {
var prescaler uint8
switch pwm.num {
case 0:
prescaler = avr.TCCR0B.Get() & 0x7
case 1:
prescaler = avr.TCCR1B.Get() & 0x7
case 2:
prescaler = avr.TCCR2B.Get() & 0x7
}
top := uint64(pwm.Top())
switch prescaler {
case 1: // prescaler 1
return 1 * top * 1000 / uint64(CPUFrequency()/1e6)
case 2: // prescaler 8
return 8 * top * 1000 / uint64(CPUFrequency()/1e6)
case 3: // prescaler 64
return 64 * top * 1000 / uint64(CPUFrequency()/1e6)
case 4: // prescaler 256
return 256 * top * 1000 / uint64(CPUFrequency()/1e6)
case 5: // prescaler 1024
return 1024 * top * 1000 / uint64(CPUFrequency()/1e6)
default: // unknown clock source
return 0
}
}
// Channel returns a PWM channel for the given pin.
func (pwm PWM) Channel(pin Pin) (uint8, error) {
pin.Configure(PinConfig{Mode: PinOutput})
pin.Low()
switch pwm.num {
case 0:
switch pin {
case PD6: // channel A
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
return 0, nil
case PD5: // channel B
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
return 1, nil
}
case 1:
switch pin {
case PB1: // channel A
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
return 0, nil
case PB2: // channel B
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
return 1, nil
}
case 2:
switch pin {
case PB3: // channel A
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
return 0, nil
case PD3: // channel B
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
return 1, nil
}
}
return 0, ErrInvalidOutputPin
}
// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
//
// Note: the invert state may not be applied on the AVR until the next call to
// ch.Set().
func (pwm PWM) SetInverting(channel uint8, inverting bool) {
switch pwm.num {
case 0:
switch channel {
case 0: // channel A
if inverting {
avr.PORTB.SetBits(1 << 6) // PB6 high
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0)
} else {
avr.PORTB.ClearBits(1 << 6) // PB6 low
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0)
}
case 1: // channel B
if inverting {
avr.PORTB.SetBits(1 << 5) // PB5 high
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0)
} else {
avr.PORTB.ClearBits(1 << 5) // PB5 low
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0)
}
}
case 1:
// Note: the COM1A0/COM1B0 bit is not set with the configuration below.
// It will be set the following call to Set(), however.
switch channel {
case 0: // channel A, PB1
if inverting {
avr.PORTB.SetBits(1 << 1) // PB1 high
} else {
avr.PORTB.ClearBits(1 << 1) // PB1 low
}
case 1: // channel B, PB2
if inverting {
avr.PORTB.SetBits(1 << 2) // PB2 high
} else {
avr.PORTB.ClearBits(1 << 2) // PB2 low
}
}
case 2:
switch channel {
case 0: // channel A
if inverting {
avr.PORTB.SetBits(1 << 3) // PB3 high
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0)
} else {
avr.PORTB.ClearBits(1 << 3) // PB3 low
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0)
}
case 1: // channel B
if inverting {
avr.PORTD.SetBits(1 << 3) // PD3 high
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0)
} else {
avr.PORTD.ClearBits(1 << 3) // PD3 low
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0)
}
}
}
}
// Set updates the channel value. This is used to control the channel duty
// cycle, in other words the fraction of time the channel output is high (or low
// when inverted). For example, to set it to a 25% duty cycle, use:
//
// pwm.Set(channel, pwm.Top() / 4)
//
// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel,
// pwm.Top()) will set the output to high, assuming the output isn't inverted.
func (pwm PWM) Set(channel uint8, value uint32) {
switch pwm.num {
case 0:
value := uint16(value)
switch channel {
case 0: // channel A
if value == 0 {
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1)
} else {
avr.OCR0A.Set(uint8(value - 1))
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
}
case 1: // channel B
if value == 0 {
avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1)
} else {
avr.OCR0B.Set(uint8(value) - 1)
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
}
}
case 1:
mask := interrupt.Disable()
switch channel {
case 0: // channel A, PB1
if value == 0 {
avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR1AH.Set(uint8(value >> 8))
avr.OCR1AL.Set(uint8(value))
if avr.PORTB.HasBits(1 << 1) { // is PB1 high?
// Yes, set the inverting bit.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0)
} else {
// No, output is non-inverting.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
}
}
case 1: // channel B, PB2
if value == 0 {
avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0)
} else {
value := uint16(value) - 1 // yes, this is safe (it relies on underflow)
avr.OCR1BH.Set(uint8(value >> 8))
avr.OCR1BL.Set(uint8(value))
if avr.PORTB.HasBits(1 << 2) { // is PB2 high?
// Yes, set the inverting bit.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0)
} else {
// No, output is non-inverting.
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
}
}
}
interrupt.Restore(mask)
case 2:
value := uint16(value)
switch channel {
case 0: // channel A
if value == 0 {
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1)
} else {
avr.OCR2A.Set(uint8(value - 1))
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
}
case 1: // channel B
if value == 0 {
avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1)
} else {
avr.OCR2B.Set(uint8(value - 1))
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
}
}
}
}
+383 -258
View File
@@ -8,7 +8,6 @@
package machine
import (
"device"
"device/arm"
"device/sam"
"errors"
@@ -17,8 +16,6 @@ import (
"unsafe"
)
type PinMode uint8
const (
PinAnalog PinMode = 1
PinSERCOM PinMode = 2
@@ -31,8 +28,8 @@ const (
PinInput PinMode = 9
PinInputPullup PinMode = 10
PinOutput PinMode = 11
PinPWM PinMode = PinTimer
PinPWMAlt PinMode = PinTimerAlt
PinTCC PinMode = PinTimer
PinTCCAlt PinMode = PinTimerAlt
PinInputPulldown PinMode = 12
)
@@ -433,7 +430,18 @@ func (a ADC) Get() uint16 {
sam.ADC.CTRLA.ClearBits(sam.ADC_CTRLA_ENABLE)
waitADCSync()
return uint16(val) << 4 // scales from 12 to 16-bit result
// scales to 16-bit result
switch (sam.ADC.CTRLB.Get() & sam.ADC_CTRLB_RESSEL_Msk) >> sam.ADC_CTRLB_RESSEL_Pos {
case sam.ADC_CTRLB_RESSEL_8BIT:
val = val << 8
case sam.ADC_CTRLB_RESSEL_10BIT:
val = val << 6
case sam.ADC_CTRLB_RESSEL_16BIT:
val = val << 4
case sam.ADC_CTRLB_RESSEL_12BIT:
val = val << 4
}
return val
}
func (a ADC) getADCChannel() uint8 {
@@ -1290,43 +1298,21 @@ var (
// spi.Tx(nil, rx)
//
func (spi SPI) Tx(w, r []byte) error {
if spi.Bus.BAUD.Get() == 0x00 {
// When the SPI Freq is 24MHz, special processing is performed to improve the speed.
switch {
case w == nil:
// read only, so write zero and read a result.
spi.rx(r)
case r == nil:
// write only
spi.tx(w)
switch {
case w == nil:
// read only, so write zero and read a result.
spi.rx(r)
case r == nil:
// write only
spi.tx24mhz(w)
default:
// write/read
if len(w) != len(r) {
return ErrTxInvalidSliceSize
}
spi.txrx24mhz(w, r)
default:
// write/read
if len(w) != len(r) {
return ErrTxInvalidSliceSize
}
} else {
switch {
case w == nil:
// read only, so write zero and read a result.
spi.rx(r)
case r == nil:
// write only
spi.tx(w)
default:
// write/read
if len(w) != len(r) {
return ErrTxInvalidSliceSize
}
spi.txrx(w, r)
}
spi.txrx(w, r)
}
return nil
@@ -1379,243 +1365,373 @@ func (spi SPI) txrx(tx, rx []byte) {
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
}
// tx24mhz is a special tx/rx function for CPU Clock 48 Mhz and SPI Freq 24 Mhz
func (spi SPI) tx24mhz(tx []byte) {
spi.Bus.DATA.Set(uint32(tx[0]))
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
// TCC is one timer/counter peripheral, which consists of a counter and multiple
// output channels (that can be connected to actual pins). You can set the
// frequency using SetPeriod, but only for all the channels in this TCC
// peripheral at once.
type TCC sam.TCC_Type
for i := 1; i < len(tx); i++ {
spi.Bus.DATA.Set(uint32(tx[i]))
device.Asm("nop")
device.Asm("nop")
spi.Bus.DATA.Get()
}
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
spi.Bus.DATA.Get()
// The SAM D21 has three TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
)
//go:inline
func (tcc *TCC) timer() *sam.TCC_Type {
return (*sam.TCC_Type)(tcc)
}
// txrx24mhz is a special tx/rx function for CPU Clock 48 Mhz and SPI Freq 24 Mhz
func (spi SPI) txrx24mhz(tx, rx []byte) {
spi.Bus.DATA.Set(uint32(tx[0]))
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
device.Asm("nop")
for i := 1; i < len(rx); i++ {
spi.Bus.DATA.Set(uint32(tx[i]))
device.Asm("nop")
device.Asm("nop")
rx[i-1] = byte(spi.Bus.DATA.Get())
}
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) {
}
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
}
// PWM
const period = 0xFFFF
// InitPWM initializes the PWM interface.
func InitPWM() {
// turn on timer clocks used for PWM
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC0_ | sam.PM_APBCMASK_TCC1_ | sam.PM_APBCMASK_TCC2_)
// Use GCLK0 for TCC0/TCC1
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
// Configure enables and configures this TCC.
func (tcc *TCC) Configure(config PWMConfig) error {
// Enable the clock source for this timer.
switch tcc.timer() {
case sam.TCC0:
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC0_)
// Use GCLK0 for TCC0/TCC1
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
}
case sam.TCC1:
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC1_)
// Use GCLK0 for TCC0/TCC1
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
}
case sam.TCC2:
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC2_)
// Use GCLK0 for TCC2/TC3
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
}
}
// Use GCLK0 for TCC2/TC3
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
}
}
// Configure configures a PWM pin for output.
func (pwm PWM) Configure() error {
// figure out which TCCX timer for this pin
timer := pwm.getTimer()
if timer == nil {
return ErrInvalidOutputPin
}
// disable timer
timer.CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
}
// Disable timer (if it was enabled). This is necessary because
// tcc.setPeriod may want to change the prescaler bits in CTRLA, which is
// only allowed when the TCC is disabled.
tcc.timer().CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
// Use "Normal PWM" (single-slope PWM)
timer.WAVE.SetBits(sam.TCC_WAVE_WAVEGEN_NPWM)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_WAVE) {
tcc.timer().WAVE.Set(sam.TCC_WAVE_WAVEGEN_NPWM)
// Wait for synchronization of all changed registers.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
// Set the period (the number to count to (TOP) before resetting timer)
//TCC0->PER.reg = period;
timer.PER.Set(period)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_PER) {
// Set the period and prescaler.
err := tcc.setPeriod(config.Period, true)
// Enable the timer.
tcc.timer().CTRLA.SetBits(sam.TCC_CTRLA_ENABLE)
// Wait for synchronization of all changed registers.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
// Set pin as output
sam.PORT.DIRSET0.Set(1 << uint8(pwm.Pin))
// Set pin to low
sam.PORT.OUTCLR0.Set(1 << uint8(pwm.Pin))
// Return any error that might have occured in the tcc.setPeriod call.
return err
}
// Enable the port multiplexer for pin
pwm.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
// Connect TCCX timer to pin.
// we normally use the F channel aka ALT
pwmConfig := PinPWMAlt
// in the case of PA6 or PA7 we have to use E channel
if pwm.Pin == 6 || pwm.Pin == 7 {
pwmConfig = PinPWM
// SetPeriod updates the period of this TCC peripheral.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the TCC peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (tcc *TCC) SetPeriod(period uint64) error {
err := tcc.setPeriod(period, false)
if err == nil {
if tcc.Counter() >= tcc.Top() {
// When setting the timer to a shorter period, there is a chance
// that it passes the counter value and thus goes all the way to MAX
// before wrapping back to zero.
// To avoid this, reset the counter back to 0.
tcc.timer().COUNT.Set(0)
}
}
return err
}
if pwm.Pin&1 > 0 {
// odd pin, so save the even pins
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
pwm.setPMux(val | uint8(pwmConfig<<sam.PORT_PMUX0_PMUXO_Pos))
// setPeriod sets the period of this TCC, possibly updating the prescaler as
// well. The prescaler can only modified when the TCC is disabled, that is, in
// the Configure function.
func (tcc *TCC) setPeriod(period uint64, updatePrescaler bool) error {
var top uint64
if period == 0 {
// Make sure the TOP value is at 0xffff (enough for a 16-bit timer).
top = 0xffff
} else {
// even pin, so save the odd pins
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
pwm.setPMux(val | uint8(pwmConfig<<sam.PORT_PMUX0_PMUXE_Pos))
// The formula below calculates the following formula, optimized:
// period * (48e6 / 1e9)
// This assumes that the chip is running at the (default) 48MHz speed.
top = period * 6 / 125
}
maxTop := uint64(0xffffff)
if tcc.timer() == sam.TCC2 {
// TCC2 is a 16-bit timer, not a 24-bit timer.
maxTop = 0xffff
}
if updatePrescaler {
// This function was called during Configure(), with the timer disabled.
// Note that updating the prescaler can only happen while the peripheral
// is disabled.
var prescaler uint32
switch {
case top <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV1
case top/2 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV2
top = top / 2
case top/4 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV4
top = top / 4
case top/8 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV8
top = top / 8
case top/16 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV16
top = top / 16
case top/64 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV64
top = top / 64
case top/256 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV256
top = top / 256
case top/1024 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV1024
top = top / 1024
default:
return ErrPWMPeriodTooLong
}
tcc.timer().CTRLA.Set((tcc.timer().CTRLA.Get() &^ sam.TCC_CTRLA_PRESCALER_Msk) | (prescaler << sam.TCC_CTRLA_PRESCALER_Pos))
} else {
// Do not update the prescaler, but use the already-configured
// prescaler. This is the normal SetPeriod case, where the prescaler
// must not be changed.
prescaler := (tcc.timer().CTRLA.Get() & sam.TCC_CTRLA_PRESCALER_Msk) >> sam.TCC_CTRLA_PRESCALER_Pos
switch prescaler {
case sam.TCC_CTRLA_PRESCALER_DIV1:
top /= 1 // no-op
case sam.TCC_CTRLA_PRESCALER_DIV2:
top /= 2
case sam.TCC_CTRLA_PRESCALER_DIV4:
top /= 4
case sam.TCC_CTRLA_PRESCALER_DIV8:
top /= 8
case sam.TCC_CTRLA_PRESCALER_DIV16:
top /= 16
case sam.TCC_CTRLA_PRESCALER_DIV64:
top /= 64
case sam.TCC_CTRLA_PRESCALER_DIV256:
top /= 256
case sam.TCC_CTRLA_PRESCALER_DIV1024:
top /= 1024
default:
// unreachable
}
if top > maxTop {
return ErrPWMPeriodTooLong
}
}
// Set the period (the counter top).
tcc.timer().PER.Set(uint32(top) - 1)
// Wait for synchronization of CTRLA.PRESCALER and PER registers.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
return nil
}
// Set turns on the duty cycle for a PWM pin using the provided value.
func (pwm PWM) Set(value uint16) {
// figure out which TCCX timer for this pin
timer := pwm.getTimer()
if timer == nil {
// The Configure call above cannot have succeeded, so simply ignore this
// error.
return
// Top returns the current counter top, for use in duty cycle calculation. It
// will only change with a call to Configure or SetPeriod, otherwise it is
// constant.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to Set
// (see Set documentation for more information).
func (tcc *TCC) Top() uint32 {
return tcc.timer().PER.Get() + 1
}
// Counter returns the current counter value of the timer in this TCC
// peripheral. It may be useful for debugging.
func (tcc *TCC) Counter() uint32 {
tcc.timer().CTRLBSET.Set(sam.TCC_CTRLBSET_CMD_READSYNC << sam.TCC_CTRLBSET_CMD_Pos)
for tcc.timer().SYNCBUSY.Get() != 0 {
}
return tcc.timer().COUNT.Get()
}
// Some constans to make pinTimerMapping below easier to read.
const (
pinTCC0 = 1
pinTCC1 = 2
pinTCC2 = 3
pinTimerCh0 = 0 << 3
pinTimerCh2 = 1 << 3
pinTCC0Ch0 = pinTCC0 | pinTimerCh0
pinTCC0Ch2 = pinTCC0 | pinTimerCh2
pinTCC1Ch0 = pinTCC1 | pinTimerCh0
pinTCC1Ch2 = pinTCC1 | pinTimerCh2
pinTCC2Ch0 = pinTCC2 | pinTimerCh0
)
// Mapping from pin number to TCC peripheral and channel using a special
// encoding. Note that only TCC0-TCC2 are included, not TC3 and up.
// Every byte is split in two nibbles where the low nibble describes PinTCC and
// the high nibble describes PinTCCAlt. Within a nibble, there is one bit that
// indicates Ch0/Ch1 or Ch2/Ch3, and three other bits that contain the TCC
// peripheral number plus one (to distinguish between TCC0Ch0 and 0).
//
// The encoding can be so compact because all pins are configured in pairs, so
// if you know PA00 you can infer the configuration of PA01. And only channel 0
// or 2 need to be included (taking up just one bit), because channel 0 and 2
// are only ever used on odd pins and channel 1 and 3 on even pins, again using
// the pin pair pattern to reduce the amount of information needed to be stored.
//
// Datasheet: https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf
var pinTimerMapping = [...]uint8{
// page 21
PA00 / 2: pinTCC2Ch0 | 0,
PA04 / 2: pinTCC0Ch0 | 0,
PA06 / 2: pinTCC1Ch0 | 0,
PA08 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4,
PA10 / 2: pinTCC1Ch0 | pinTCC0Ch2<<4,
// page 22
PB10 / 2: 0 | pinTCC0Ch0<<4,
PB12 / 2: 0 | pinTCC0Ch2<<4,
PA12 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4,
PA14 / 2: 0 | pinTCC0Ch0<<4,
PA16 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4,
PA18 / 2: 0 | pinTCC0Ch2<<4,
PB16 / 2: 0 | pinTCC0Ch0<<4,
PA20 / 2: 0 | pinTCC0Ch2<<4,
PA22 / 2: 0 | pinTCC0Ch0<<4,
PA24 / 2: 0 | pinTCC1Ch2<<4,
// page 23
PA30 / 2: 0 | pinTCC1Ch0<<4,
PB30 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4,
}
// findPinPadMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel
// number for a given timer and pin. A zero PinMode is returned if no mapping
// could be found.
func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) {
mapping := pinTimerMapping[pin/2]
// evenChannel below indicates the channel 0 or 2, for the even part of the
// pin pair. The next pin will also have the next channel (1 or 3).
if mapping&0x07 == timer+1 {
// PWM output is on peripheral function E.
evenChannel := ((mapping >> 3) & 1) * 2
return PinTCC, evenChannel + uint8(pin&1)
}
if (mapping&0x70)>>4 == timer+1 {
// PWM output is on peripheral function F.
evenChannel := ((mapping >> 7) & 1) * 2
return PinTCCAlt, evenChannel + uint8(pin&1)
}
return 0, 0
}
// Channel returns a PWM channel for the given pin. Note that one channel may be
// shared between multiple pins, and so will have the same duty cycle. If this
// is not desirable, look for a different TCC peripheral or consider using a
// different pin.
func (tcc *TCC) Channel(pin Pin) (uint8, error) {
var pinMode PinMode
var channel uint8
switch tcc.timer() {
case sam.TCC0:
pinMode, channel = findPinTimerMapping(0, pin)
case sam.TCC1:
pinMode, channel = findPinTimerMapping(1, pin)
case sam.TCC2:
pinMode, channel = findPinTimerMapping(2, pin)
}
// disable output
timer.CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
if pinMode == 0 {
// No pin could be found.
return 0, ErrInvalidOutputPin
}
// Enable the port multiplexer for pin
pin.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
if pin&1 > 0 {
// odd pin, so save the even pins
val := pin.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
pin.setPMux(val | uint8(pinMode<<sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := pin.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
pin.setPMux(val | uint8(pinMode<<sam.PORT_PMUX0_PMUXE_Pos))
}
return channel, nil
}
// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
func (tcc *TCC) SetInverting(channel uint8, inverting bool) {
if inverting {
tcc.timer().WAVE.SetBits(1 << (sam.TCC_WAVE_POL0_Pos + channel))
} else {
tcc.timer().WAVE.ClearBits(1 << (sam.TCC_WAVE_POL0_Pos + channel))
}
// Wait for synchronization of the WAVE register.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
}
// Set updates the channel value. This is used to control the channel duty
// cycle, in other words the fraction of time the channel output is high (or low
// when inverted). For example, to set it to a 25% duty cycle, use:
//
// tcc.Set(channel, tcc.Top() / 4)
//
// tcc.Set(channel, 0) will set the output to low and tcc.Set(channel,
// tcc.Top()) will set the output to high, assuming the output isn't inverted.
func (tcc *TCC) Set(channel uint8, value uint32) {
// Set PWM signal to output duty cycle
pwm.setChannel(timer, uint32(value))
// Wait for synchronization on all channels
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0 |
sam.TCC_SYNCBUSY_CC1 |
sam.TCC_SYNCBUSY_CC2 |
sam.TCC_SYNCBUSY_CC3) {
}
// enable
timer.CTRLA.SetBits(sam.TCC_CTRLA_ENABLE)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
}
}
// getPMux returns the value for the correct PMUX register for this pin.
func (pwm PWM) getPMux() uint8 {
return pwm.Pin.getPMux()
}
// setPMux sets the value for the correct PMUX register for this pin.
func (pwm PWM) setPMux(val uint8) {
pwm.Pin.setPMux(val)
}
// getPinCfg returns the value for the correct PINCFG register for this pin.
func (pwm PWM) getPinCfg() uint8 {
return pwm.Pin.getPinCfg()
}
// setPinCfg sets the value for the correct PINCFG register for this pin.
func (pwm PWM) setPinCfg(val uint8) {
pwm.Pin.setPinCfg(val)
}
// getTimer returns the timer to be used for PWM on this pin
func (pwm PWM) getTimer() *sam.TCC_Type {
switch pwm.Pin {
case 6:
return sam.TCC1
case 7:
return sam.TCC1
case 8:
return sam.TCC1
case 9:
return sam.TCC1
case 14:
return sam.TCC0
case 15:
return sam.TCC0
case 16:
return sam.TCC0
case 17:
return sam.TCC0
case 18:
return sam.TCC0
case 19:
return sam.TCC0
case 20:
return sam.TCC0
case 21:
return sam.TCC0
switch channel {
case 0:
tcc.timer().CC0.Set(value)
case 1:
tcc.timer().CC1.Set(value)
case 2:
tcc.timer().CC2.Set(value)
case 3:
tcc.timer().CC3.Set(value)
default:
return nil // not supported on this pin
// invalid PWM channel, ignore.
}
}
// setChannel sets the value for the correct channel for PWM on this pin
func (pwm PWM) setChannel(timer *sam.TCC_Type, val uint32) {
switch pwm.Pin {
case 6:
timer.CC0.Set(val)
case 7:
timer.CC1.Set(val)
case 8:
timer.CC0.Set(val)
case 9:
timer.CC1.Set(val)
case 14:
timer.CC0.Set(val)
case 15:
timer.CC1.Set(val)
case 16:
timer.CC2.Set(val)
case 17:
timer.CC3.Set(val)
case 18:
timer.CC2.Set(val)
case 19:
timer.CC3.Set(val)
case 20:
timer.CC2.Set(val)
case 21:
timer.CC3.Set(val)
default:
return // not supported on this pin
// Wait for synchronization on all channels (or anything in this peripheral,
// really).
for tcc.timer().SYNCBUSY.Get() != 0 {
}
}
@@ -2126,7 +2242,7 @@ func handleStandardSetup(setup usbSetup) bool {
func cdcSetup(setup usbSetup) bool {
if setup.bmRequestType == usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE {
if setup.bRequest == usb_CDC_GET_LINE_CODING {
b := make([]byte, 7)
var b [cdcLineInfoSize]byte
b[0] = byte(usbLineInfo.dwDTERate)
b[1] = byte(usbLineInfo.dwDTERate >> 8)
b[2] = byte(usbLineInfo.dwDTERate >> 16)
@@ -2135,14 +2251,18 @@ func cdcSetup(setup usbSetup) bool {
b[5] = byte(usbLineInfo.bParityType)
b[6] = byte(usbLineInfo.bDataBits)
sendUSBPacket(0, b)
sendUSBPacket(0, b[:])
return true
}
}
if setup.bmRequestType == usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE {
if setup.bRequest == usb_CDC_SET_LINE_CODING {
b := receiveUSBControlPacket()
b, err := receiveUSBControlPacket()
if err != nil {
return false
}
usbLineInfo.dwDTERate = uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
usbLineInfo.bCharFormat = b[4]
usbLineInfo.bParityType = b[5]
@@ -2189,7 +2309,9 @@ func sendUSBPacket(ep uint32, data []byte) {
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(uint32((len(data) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos))
}
func receiveUSBControlPacket() []byte {
func receiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
var b [cdcLineInfoSize]byte
// address
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
@@ -2204,7 +2326,7 @@ func receiveUSBControlPacket() []byte {
for (getEPSTATUS(0) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
timeout--
if timeout == 0 {
return []byte{}
return b, errUSBCDCReadTimeout
}
}
@@ -2213,7 +2335,7 @@ func receiveUSBControlPacket() []byte {
for (getEPINTFLAG(0) & sam.USB_DEVICE_EPINTFLAG_TRCPT0) == 0 {
timeout--
if timeout == 0 {
return []byte{}
return b, errUSBCDCReadTimeout
}
}
@@ -2221,10 +2343,13 @@ func receiveUSBControlPacket() []byte {
bytesread := uint32((usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.Get() >>
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask)
data := make([]byte, bytesread)
copy(data, udd_ep_out_cache_buffer[0][:])
if bytesread != cdcLineInfoSize {
return b, errUSBCDCBytesRead
}
return data
copy(b[:7], udd_ep_out_cache_buffer[0][:7])
return b, nil
}
func handleEndpoint(ep uint32) {
+393 -240
View File
@@ -1,4 +1,4 @@
// +build sam,atsamd51
// +build sam,atsamd51 sam,atsame5x
// Peripheral abstraction layer for the atsamd51.
//
@@ -20,8 +20,6 @@ func CPUFrequency() uint32 {
return 120000000
}
type PinMode uint8
const (
PinAnalog PinMode = 1
PinSERCOM PinMode = 2
@@ -40,10 +38,13 @@ const (
PinInput PinMode = 15
PinInputPullup PinMode = 16
PinOutput PinMode = 17
PinPWME PinMode = PinTimer
PinPWMF PinMode = PinTimerAlt
PinPWMG PinMode = PinTCCPDEC
PinTCCE PinMode = PinTimer
PinTCCF PinMode = PinTimerAlt
PinTCCG PinMode = PinTCCPDEC
PinInputPulldown PinMode = 18
PinCAN PinMode = 19
PinCAN0 PinMode = PinSDHC
PinCAN1 PinMode = PinCom
)
type PinChange uint8
@@ -627,6 +628,18 @@ func (p Pin) Configure(config PinConfig) {
}
// enable port config
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
case PinSDHC:
if p&1 > 0 {
// odd pin, so save the even pins
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
}
// enable port config
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN)
}
}
@@ -800,6 +813,18 @@ func (a ADC) Configure(config ADCConfig) {
}
a.Pin.Configure(PinConfig{Mode: PinAnalog})
switch config.Bus {
case AdcBusAuto:
bus := a.getADCBus()
if bus == sam.ADC0 {
a.Bus = 0
} else {
a.Bus = 1
}
default:
a.Bus = config.Bus
}
}
// Get returns the current value of a ADC pin, in the range 0..0xffff.
@@ -848,10 +873,27 @@ func (a ADC) Get() uint16 {
for bus.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_ENABLE) {
}
return uint16(val) << 4 // scales from 12 to 16-bit result
// scales to 16-bit result
switch (bus.CTRLB.Get() & sam.ADC_CTRLB_RESSEL_Msk) >> sam.ADC_CTRLB_RESSEL_Pos {
case sam.ADC_CTRLB_RESSEL_8BIT:
val = val << 8
case sam.ADC_CTRLB_RESSEL_10BIT:
val = val << 6
case sam.ADC_CTRLB_RESSEL_16BIT:
val = val << 4
case sam.ADC_CTRLB_RESSEL_12BIT:
val = val << 4
}
return val
}
func (a ADC) getADCBus() *sam.ADC_Type {
if a.Bus == AdcBus0 {
return sam.ADC0
} else if a.Bus == AdcBus1 {
return sam.ADC1
}
if (a.Pin >= PB04 && a.Pin <= PB07) || (a.Pin >= PC00) {
return sam.ADC1
}
@@ -863,9 +905,17 @@ func (a ADC) getADCChannel() uint8 {
case PA02:
return 0
case PB08:
return 2
if a.Bus == AdcBus1 {
return 0
} else {
return 2
}
case PB09:
return 3
if a.Bus == AdcBus1 {
return 1
} else {
return 3
}
case PA04:
return 4
case PA05:
@@ -1570,256 +1620,350 @@ const (
QSPI_DATA3 = PA11
)
// PWM
const period = 0xFFFF
// TCC is one timer peripheral, which consists of a counter and multiple output
// channels (that can be connected to actual pins). You can set the frequency
// using SetPeriod, but only for all the channels in this timer peripheral at
// once.
type TCC sam.TCC_Type
// Configure configures a PWM pin for output.
func (pwm PWM) Configure() error {
// Set pin as output
sam.PORT.GROUP[0].DIRSET.Set(1 << uint8(pwm.Pin))
// Set pin to low
sam.PORT.GROUP[0].OUTCLR.Set(1 << uint8(pwm.Pin))
//go:inline
func (tcc *TCC) timer() *sam.TCC_Type {
return (*sam.TCC_Type)(tcc)
}
// Enable the port multiplexer for pin
pwm.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN)
// Configure enables and configures this TCC.
func (tcc *TCC) Configure(config PWMConfig) error {
// Enable the TCC clock to be able to use the TCC.
tcc.configureClock()
// Connect timer/mux to pin.
pwmConfig := pwm.getMux()
if pwm.Pin&1 > 0 {
// odd pin, so save the even pins
val := pwm.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
pwm.setPMux(val | uint8(pwmConfig<<sam.PORT_GROUP_PMUX_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := pwm.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
pwm.setPMux(val | uint8(pwmConfig<<sam.PORT_GROUP_PMUX_PMUXE_Pos))
}
// figure out which TCCX timer for this pin
timer := pwm.getTimer()
if timer == nil {
return ErrInvalidOutputPin
}
// disable timer
timer.CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
}
// Set prescaler to 1/256
// TCCx->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV256 | TCC_CTRLA_PRESCSYNC_GCLK;
timer.CTRLA.SetBits(sam.TCC_CTRLA_PRESCALER_DIV256 | sam.TCC_CTRLA_PRESCSYNC_GCLK)
// Disable timer (if it was enabled). This is necessary because
// tcc.setPeriod may want to change the prescaler bits in CTRLA, which is
// only allowed when the TCC is disabled.
tcc.timer().CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
// Use "Normal PWM" (single-slope PWM)
timer.WAVE.SetBits(sam.TCC_WAVE_WAVEGEN_NPWM)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_WAVE) {
tcc.timer().WAVE.Set(sam.TCC_WAVE_WAVEGEN_NPWM)
// Wait for synchronization of all changed registers.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
// while (TCCx->SYNCBUSY.bit.CC0 || TCCx->SYNCBUSY.bit.CC1);
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) ||
timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) {
// Set the period and prescaler.
err := tcc.setPeriod(config.Period, true)
// Enable the timer.
tcc.timer().CTRLA.SetBits(sam.TCC_CTRLA_ENABLE)
// Wait for synchronization of all changed registers.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
// Set the initial value
// TCCx->CC[tcChannel].reg = (uint32_t) value;
pwm.setChannel(timer, 0)
// Return any error that might have occured in the tcc.setPeriod call.
return err
}
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) ||
timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) {
// SetPeriod updates the period of this TCC peripheral.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the TCC peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (tcc *TCC) SetPeriod(period uint64) error {
return tcc.setPeriod(period, false)
}
// setPeriod sets the period of this TCC, possibly updating the prescaler as
// well. The prescaler can only modified when the TCC is disabled, that is, in
// the Configure function.
func (tcc *TCC) setPeriod(period uint64, updatePrescaler bool) error {
var top uint64
if period == 0 {
// Make sure the TOP value is at 0xffff (enough for a 16-bit timer).
top = 0xffff
} else {
// The formula below calculates the following formula, optimized:
// period * (120e6 / 1e9)
// This assumes that the chip is running from generic clock generator 0
// at 120MHz.
top = period * 3 / 25
}
// Set the period (the number to count to (TOP) before resetting timer)
//TCC0->PER.reg = period;
timer.PER.Set(period)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_PER) {
maxTop := uint64(0xffff)
if tcc.timer() == sam.TCC0 || tcc.timer() == sam.TCC1 {
// Only TCC0 and TCC1 are 24-bit timers, the rest are 16-bit.
maxTop = 0xffffff
}
// enable timer
timer.CTRLA.SetBits(sam.TCC_CTRLA_ENABLE)
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
if updatePrescaler {
// This function was called during Configure(), with the timer disabled.
// Note that updating the prescaler can only happen while the peripheral
// is disabled.
var prescaler uint32
switch {
case top <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV1
case top/2 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV2
top = top / 2
case top/4 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV4
top = top / 4
case top/8 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV8
top = top / 8
case top/16 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV16
top = top / 16
case top/64 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV64
top = top / 64
case top/256 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV256
top = top / 256
case top/1024 <= maxTop:
prescaler = sam.TCC_CTRLA_PRESCALER_DIV1024
top = top / 1024
default:
return ErrPWMPeriodTooLong
}
tcc.timer().CTRLA.Set((tcc.timer().CTRLA.Get() &^ sam.TCC_CTRLA_PRESCALER_Msk) | (prescaler << sam.TCC_CTRLA_PRESCALER_Pos))
} else {
// Do not update the prescaler, but use the already-configured
// prescaler. This is the normal SetPeriod case, where the prescaler
// must not be changed.
prescaler := (tcc.timer().CTRLA.Get() & sam.TCC_CTRLA_PRESCALER_Msk) >> sam.TCC_CTRLA_PRESCALER_Pos
switch prescaler {
case sam.TCC_CTRLA_PRESCALER_DIV1:
top /= 1 // no-op
case sam.TCC_CTRLA_PRESCALER_DIV2:
top /= 2
case sam.TCC_CTRLA_PRESCALER_DIV4:
top /= 4
case sam.TCC_CTRLA_PRESCALER_DIV8:
top /= 8
case sam.TCC_CTRLA_PRESCALER_DIV16:
top /= 16
case sam.TCC_CTRLA_PRESCALER_DIV64:
top /= 64
case sam.TCC_CTRLA_PRESCALER_DIV256:
top /= 256
case sam.TCC_CTRLA_PRESCALER_DIV1024:
top /= 1024
default:
// unreachable
}
if top > maxTop {
return ErrPWMPeriodTooLong
}
}
// Set the period (the counter top).
tcc.timer().PER.Set(uint32(top) - 1)
// Wait for synchronization of CTRLA.PRESCALER and PER registers.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
return nil
}
// Set turns on the duty cycle for a PWM pin using the provided value.
func (pwm PWM) Set(value uint16) {
// figure out which TCCX timer for this pin
timer := pwm.getTimer()
if timer == nil {
// The Configure call above cannot have succeeded, so simply ignore this
// error.
return
// Top returns the current counter top, for use in duty cycle calculation. It
// will only change with a call to Configure or SetPeriod, otherwise it is
// constant.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to
// tcc.Set (see tcc.Set for more information).
func (tcc *TCC) Top() uint32 {
return tcc.timer().PER.Get() + 1
}
// Counter returns the current counter value of the timer in this TCC
// peripheral. It may be useful for debugging.
func (tcc *TCC) Counter() uint32 {
tcc.timer().CTRLBSET.Set(sam.TCC_CTRLBSET_CMD_READSYNC << sam.TCC_CTRLBSET_CMD_Pos)
for tcc.timer().SYNCBUSY.Get() != 0 {
}
return tcc.timer().COUNT.Get()
}
// Constants that encode a TCC number and WO number together in a single byte.
const (
pinTCC0 = 1 << 4 // keep the value 0 usable as "no value"
pinTCC1 = 2 << 4
pinTCC2 = 3 << 4
pinTCC3 = 4 << 4
pinTCC4 = 5 << 4
pinTCC0_0 = pinTCC0 | 0
pinTCC0_1 = pinTCC0 | 1
pinTCC0_2 = pinTCC0 | 2
pinTCC0_3 = pinTCC0 | 3
pinTCC0_4 = pinTCC0 | 4
pinTCC0_5 = pinTCC0 | 5
pinTCC0_6 = pinTCC0 | 6
pinTCC1_0 = pinTCC1 | 0
pinTCC1_2 = pinTCC1 | 2
pinTCC1_4 = pinTCC1 | 4
pinTCC1_6 = pinTCC1 | 6
pinTCC2_0 = pinTCC2 | 0
pinTCC2_2 = pinTCC2 | 2
pinTCC3_0 = pinTCC3 | 0
pinTCC4_0 = pinTCC4 | 0
)
// This is a copy of columns F and G (the TCC columns) of table 6-1 in the
// datasheet:
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507E.pdf
// For example, "TCC0/WO[2]" is converted to pinTCC0_2.
// Only the even pin numbers are stored here. The odd pin numbers are left out,
// because their PWM output can be determined from the even number: just add one
// to the wave output (WO) number.
var pinTimerMapping = [...]struct{ F, G uint8 }{
// page 33
PC04 / 2: {pinTCC0_0, 0},
PA08 / 2: {pinTCC0_0, pinTCC1_4},
PA10 / 2: {pinTCC0_2, pinTCC1_6},
PB10 / 2: {pinTCC0_4, pinTCC1_0},
PB12 / 2: {pinTCC3_0, pinTCC0_0},
PB14 / 2: {pinTCC4_0, pinTCC0_2},
PD08 / 2: {pinTCC0_1, 0},
PD10 / 2: {pinTCC0_3, 0},
PD12 / 2: {pinTCC0_5, 0},
PC10 / 2: {pinTCC0_0, pinTCC1_4},
// page 34
PC12 / 2: {pinTCC0_2, pinTCC1_6},
PC14 / 2: {pinTCC0_4, pinTCC1_0},
PA12 / 2: {pinTCC0_6, pinTCC1_2},
PA14 / 2: {pinTCC2_0, pinTCC1_2},
PA16 / 2: {pinTCC1_0, pinTCC0_4},
PA18 / 2: {pinTCC1_2, pinTCC0_6},
PC16 / 2: {pinTCC0_0, 0},
PC18 / 2: {pinTCC0_2, 0},
PC20 / 2: {pinTCC0_4, 0},
PC22 / 2: {pinTCC0_6, 0},
PD20 / 2: {pinTCC1_0, 0},
PB16 / 2: {pinTCC3_0, pinTCC0_4},
PB18 / 2: {pinTCC1_0, 0},
// page 35
PB20 / 2: {pinTCC1_2, 0},
PA20 / 2: {pinTCC1_4, pinTCC0_0},
PA22 / 2: {pinTCC1_6, pinTCC0_2},
PA24 / 2: {pinTCC2_2, 0},
PB26 / 2: {pinTCC1_2, 0},
PB28 / 2: {pinTCC1_4, 0},
PA30 / 2: {pinTCC2_0, 0},
// page 36
PB30 / 2: {pinTCC4_0, pinTCC0_6},
PB02 / 2: {pinTCC2_2, 0},
}
// findPinPadMapping returns the pin mode (PinTCCF or PinTCCG) and the channel
// number for a given timer and pin. A zero PinMode is returned if no mapping
// could be found.
func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) {
if int(pin/2) >= len(pinTimerMapping) {
return 0, 0 // invalid pin number
}
// Wait for synchronization
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CTRLB) {
}
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) ||
timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) {
mapping := pinTimerMapping[pin/2]
// Check for column F in the datasheet.
if mapping.F>>4-1 == timer {
return PinTCCF, mapping.F&0x0f + uint8(pin)&1
}
// TCCx->CCBUF[tcChannel].reg = (uint32_t) value;
pwm.setChannelBuffer(timer, uint32(value))
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) ||
timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) {
// Check for column G in the datasheet.
if mapping.G>>4-1 == timer {
return PinTCCG, mapping.G&0x0f + uint8(pin)&1
}
// TCCx->CTRLBCLR.bit.LUPD = 1;
timer.CTRLBCLR.SetBits(sam.TCC_CTRLBCLR_LUPD)
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CTRLB) {
// Nothing found.
return 0, 0
}
// Channel returns a PWM channel for the given pin. Note that one channel may be
// shared between multiple pins, and so will have the same duty cycle. If this
// is not desirable, look for a different TCC or consider using a different pin.
func (tcc *TCC) Channel(pin Pin) (uint8, error) {
pinMode, woOutput := findPinTimerMapping(tcc.timerNum(), pin)
if pinMode == 0 {
// No pin could be found.
return 0, ErrInvalidOutputPin
}
// Convert from waveform output to channel, assuming WEXCTRL.OTMX equals 0.
// See table 49-4 "Output Matrix Channel Pin Routing Configuration" on page
// 1829 of the datasheet.
// The number of channels varies by TCC instance, hence the need to switch
// over them. For TCC2-4 the number of channels is equal to the number of
// waveform outputs, so the WO number maps directly to the channel number.
// For TCC0 and TCC1 this is not the case so they will need some special
// handling.
channel := woOutput
switch tcc.timer() {
case sam.TCC0:
channel = woOutput % 6
case sam.TCC1:
channel = woOutput % 4
}
// Enable the port multiplexer for pin
pin.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN)
// Connect timer/mux to pin.
if pin&1 > 0 {
// odd pin, so save the even pins
val := pin.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
pin.setPMux(val | uint8(pinMode<<sam.PORT_GROUP_PMUX_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := pin.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
pin.setPMux(val | uint8(pinMode<<sam.PORT_GROUP_PMUX_PMUXE_Pos))
}
return channel, nil
}
// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
func (tcc *TCC) SetInverting(channel uint8, inverting bool) {
if inverting {
tcc.timer().WAVE.SetBits(1 << (sam.TCC_WAVE_POL0_Pos + channel))
} else {
tcc.timer().WAVE.ClearBits(1 << (sam.TCC_WAVE_POL0_Pos + channel))
}
// Wait for synchronization of the WAVE register.
for tcc.timer().SYNCBUSY.Get() != 0 {
}
}
// getPMux returns the value for the correct PMUX register for this pin.
func (pwm PWM) getPMux() uint8 {
return pwm.Pin.getPMux()
}
// setPMux sets the value for the correct PMUX register for this pin.
func (pwm PWM) setPMux(val uint8) {
pwm.Pin.setPMux(val)
}
// getPinCfg returns the value for the correct PINCFG register for this pin.
func (pwm PWM) getPinCfg() uint8 {
return pwm.Pin.getPinCfg()
}
// setPinCfg sets the value for the correct PINCFG register for this pin.
func (pwm PWM) setPinCfg(val uint8) {
pwm.Pin.setPinCfg(val)
}
// setChannel sets the value for the correct channel for PWM on this pin.
func (pwm PWM) setChannel(timer *sam.TCC_Type, val uint32) {
switch pwm.Pin {
case PA14:
timer.CC[0].Set(val)
case PA15:
timer.CC[1].Set(val)
case PA16:
timer.CC[0].Set(val)
case PA17:
timer.CC[1].Set(val)
case PA18:
timer.CC[2].Set(val)
case PA19:
timer.CC[3].Set(val)
case PA20:
timer.CC[0].Set(val)
case PA21:
timer.CC[1].Set(val)
case PA22:
timer.CC[2].Set(val)
case PA23:
timer.CC[3].Set(val)
case PB12:
timer.CC[0].Set(val)
case PB13:
timer.CC[1].Set(val)
case PB14:
timer.CC[0].Set(val)
case PB15:
timer.CC[1].Set(val)
case PB16:
timer.CC[4].Set(val)
case PB17:
timer.CC[5].Set(val)
case PB31:
timer.CC[1].Set(val)
default:
return // not supported on this pin
}
}
// setChannelBuffer sets the value for the correct channel buffer for PWM on this pin
func (pwm PWM) setChannelBuffer(timer *sam.TCC_Type, val uint32) {
switch pwm.Pin {
case PA14:
timer.CCBUF[0].Set(val)
case PA15:
timer.CCBUF[1].Set(val)
case PA16:
timer.CCBUF[0].Set(val)
case PA17:
timer.CCBUF[1].Set(val)
case PA18:
timer.CCBUF[2].Set(val)
case PA19:
timer.CCBUF[3].Set(val)
case PA20:
timer.CCBUF[0].Set(val)
case PA21:
timer.CCBUF[1].Set(val)
case PA22:
timer.CCBUF[2].Set(val)
case PA23:
timer.CCBUF[3].Set(val)
case PB12:
timer.CCBUF[0].Set(val)
case PB13:
timer.CCBUF[1].Set(val)
case PB14:
timer.CCBUF[0].Set(val)
case PB15:
timer.CCBUF[1].Set(val)
case PB16:
timer.CCBUF[4].Set(val)
case PB17:
timer.CCBUF[5].Set(val)
case PB31:
timer.CCBUF[1].Set(val)
default:
return // not supported on this pin
}
}
// getMux returns the pin mode mux to be used for PWM on this pin.
func (pwm PWM) getMux() PinMode {
switch pwm.Pin {
case PA14:
return PinPWMF
case PA15:
return PinPWMF
case PA16:
return PinPWMF
case PA17:
return PinPWMF
case PA18:
return PinPWMF
case PA19:
return PinPWMF
case PA20:
return PinPWMG
case PA21:
return PinPWMG
case PA22:
return PinPWMG
case PA23:
return PinPWMG
case PB12:
return PinPWMF
case PB13:
return PinPWMF
case PB14:
return PinPWMF
case PB15:
return PinPWMF
case PB16:
return PinPWMG
case PB17:
return PinPWMG
case PB31:
return PinPWMF
default:
return 0 // not supported on this pin
// Set updates the channel value. This is used to control the channel duty
// cycle, in other words the fraction of time the channel output is high (or low
// when inverted). For example, to set it to a 25% duty cycle, use:
//
// tcc.Set(channel, tcc.Top() / 4)
//
// tcc.Set(channel, 0) will set the output to low and tcc.Set(channel,
// tcc.Top()) will set the output to high, assuming the output isn't inverted.
func (tcc *TCC) Set(channel uint8, value uint32) {
// Update CCBUF, which provides double buffering. The update is applied on
// the next cycle.
tcc.timer().CCBUF[channel].Set(value)
for tcc.timer().SYNCBUSY.Get() != 0 {
}
}
@@ -2332,7 +2476,7 @@ func handleStandardSetup(setup usbSetup) bool {
func cdcSetup(setup usbSetup) bool {
if setup.bmRequestType == usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE {
if setup.bRequest == usb_CDC_GET_LINE_CODING {
b := make([]byte, 7)
var b [cdcLineInfoSize]byte
b[0] = byte(usbLineInfo.dwDTERate)
b[1] = byte(usbLineInfo.dwDTERate >> 8)
b[2] = byte(usbLineInfo.dwDTERate >> 16)
@@ -2341,14 +2485,18 @@ func cdcSetup(setup usbSetup) bool {
b[5] = byte(usbLineInfo.bParityType)
b[6] = byte(usbLineInfo.bDataBits)
sendUSBPacket(0, b)
sendUSBPacket(0, b[:])
return true
}
}
if setup.bmRequestType == usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE {
if setup.bRequest == usb_CDC_SET_LINE_CODING {
b := receiveUSBControlPacket()
b, err := receiveUSBControlPacket()
if err != nil {
return false
}
usbLineInfo.dwDTERate = uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
usbLineInfo.bCharFormat = b[4]
usbLineInfo.bParityType = b[5]
@@ -2395,7 +2543,9 @@ func sendUSBPacket(ep uint32, data []byte) {
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(uint32((len(data) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos))
}
func receiveUSBControlPacket() []byte {
func receiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
var b [cdcLineInfoSize]byte
// address
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
@@ -2410,7 +2560,7 @@ func receiveUSBControlPacket() []byte {
for (getEPSTATUS(0) & sam.USB_DEVICE_ENDPOINT_EPSTATUS_BK0RDY) == 0 {
timeout--
if timeout == 0 {
return []byte{}
return b, errUSBCDCReadTimeout
}
}
@@ -2419,7 +2569,7 @@ func receiveUSBControlPacket() []byte {
for (getEPINTFLAG(0) & sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT1) == 0 {
timeout--
if timeout == 0 {
return []byte{}
return b, errUSBCDCReadTimeout
}
}
@@ -2427,10 +2577,13 @@ func receiveUSBControlPacket() []byte {
bytesread := uint32((usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.Get() >>
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask)
data := make([]byte, bytesread)
copy(data, udd_ep_out_cache_buffer[0][:])
if bytesread != cdcLineInfoSize {
return b, errUSBCDCBytesRead
}
return data
copy(b[:7], udd_ep_out_cache_buffer[0][:7])
return b, nil
}
func handleEndpoint(ep uint32) {
+31 -37
View File
@@ -11,43 +11,37 @@ import "device/sam"
const HSRAM_SIZE = 0x00030000
// InitPWM initializes the PWM interface.
func InitPWM() {
// turn on timer clocks used for PWM
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_ | sam.MCLK_APBBMASK_TCC1_)
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
// This chip has three TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
)
//use clock generator 0
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
// getTimer returns the timer to be used for PWM on this pin
func (pwm PWM) getTimer() *sam.TCC_Type {
switch pwm.Pin {
case PA14:
return sam.TCC2
case PA15:
return sam.TCC2
case PA16:
return sam.TCC1
case PA17:
return sam.TCC1
case PA18:
return sam.TCC1
case PA19:
return sam.TCC1
case PA20:
return sam.TCC0
case PA21:
return sam.TCC0
case PA22:
return sam.TCC0
case PA23:
return sam.TCC0
default:
return nil // not supported on this pin
func (tcc *TCC) configureClock() {
// Turn on timer clocks used for TCC and use generic clock generator 0.
switch tcc.timer() {
case sam.TCC0:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC1:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC2:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
}
}
func (tcc *TCC) timerNum() uint8 {
switch tcc.timer() {
case sam.TCC0:
return 0
case sam.TCC1:
return 1
case sam.TCC2:
return 2
default:
return 0x0f // should not happen
}
}
+43 -54
View File
@@ -11,60 +11,49 @@ import "device/sam"
const HSRAM_SIZE = 0x00030000
// InitPWM initializes the PWM interface.
func InitPWM() {
// turn on timer clocks used for PWM
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_ | sam.MCLK_APBBMASK_TCC1_)
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_ | sam.MCLK_APBCMASK_TCC3_)
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
// This chip has five TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
TCC3 = (*TCC)(sam.TCC3)
TCC4 = (*TCC)(sam.TCC4)
)
//use clock generator 0
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
// getTimer returns the timer to be used for PWM on this pin
func (pwm PWM) getTimer() *sam.TCC_Type {
switch pwm.Pin {
case PA14:
return sam.TCC2
case PA15:
return sam.TCC2
case PA16:
return sam.TCC1
case PA17:
return sam.TCC1
case PA18:
return sam.TCC1
case PA19:
return sam.TCC1
case PA20:
return sam.TCC0
case PA21:
return sam.TCC0
case PA22:
return sam.TCC0
case PA23:
return sam.TCC0
case PB12:
return sam.TCC3
case PB13:
return sam.TCC3
case PB14:
return sam.TCC4
case PB15:
return sam.TCC4
case PB16:
return sam.TCC0
case PB17:
return sam.TCC0
case PB31:
return sam.TCC4
default:
return nil // not supported on this pin
func (tcc *TCC) configureClock() {
// Turn on timer clocks used for the TCC and use generic clock generator 0.
switch tcc.timer() {
case sam.TCC0:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC1:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC2:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC3:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC4:
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
}
}
func (tcc *TCC) timerNum() uint8 {
switch tcc.timer() {
case sam.TCC0:
return 0
case sam.TCC1:
return 1
case sam.TCC2:
return 2
case sam.TCC3:
return 3
case sam.TCC4:
return 4
default:
return 0x0f // should not happen
}
}
+43 -54
View File
@@ -11,60 +11,49 @@ import "device/sam"
const HSRAM_SIZE = 0x00040000
// InitPWM initializes the PWM interface.
func InitPWM() {
// turn on timer clocks used for PWM
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_ | sam.MCLK_APBBMASK_TCC1_)
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_ | sam.MCLK_APBCMASK_TCC3_)
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
// This chip has five TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
TCC3 = (*TCC)(sam.TCC3)
TCC4 = (*TCC)(sam.TCC4)
)
//use clock generator 0
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
// getTimer returns the timer to be used for PWM on this pin
func (pwm PWM) getTimer() *sam.TCC_Type {
switch pwm.Pin {
case PA14:
return sam.TCC2
case PA15:
return sam.TCC2
case PA16:
return sam.TCC1
case PA17:
return sam.TCC1
case PA18:
return sam.TCC1
case PA19:
return sam.TCC1
case PA20:
return sam.TCC0
case PA21:
return sam.TCC0
case PA22:
return sam.TCC0
case PA23:
return sam.TCC0
case PB12:
return sam.TCC3
case PB13:
return sam.TCC3
case PB14:
return sam.TCC4
case PB15:
return sam.TCC4
case PB16:
return sam.TCC0
case PB17:
return sam.TCC0
case PB31:
return sam.TCC4
default:
return nil // not supported on this pin
func (tcc *TCC) configureClock() {
// Turn on timer clocks used for TCC and use generic clock generator 0.
switch tcc.timer() {
case sam.TCC0:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC1:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC2:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC3:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC4:
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
}
}
func (tcc *TCC) timerNum() uint8 {
switch tcc.timer() {
case sam.TCC0:
return 0
case sam.TCC1:
return 1
case sam.TCC2:
return 2
case sam.TCC3:
return 3
case sam.TCC4:
return 4
default:
return 0x0f // should not happen
}
}
+43 -54
View File
@@ -11,60 +11,49 @@ import "device/sam"
const HSRAM_SIZE = 0x00030000
// InitPWM initializes the PWM interface.
func InitPWM() {
// turn on timer clocks used for PWM
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_ | sam.MCLK_APBBMASK_TCC1_)
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_ | sam.MCLK_APBCMASK_TCC3_)
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
// This chip has five TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
TCC3 = (*TCC)(sam.TCC3)
TCC4 = (*TCC)(sam.TCC4)
)
//use clock generator 0
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
// getTimer returns the timer to be used for PWM on this pin
func (pwm PWM) getTimer() *sam.TCC_Type {
switch pwm.Pin {
case PA14:
return sam.TCC2
case PA15:
return sam.TCC2
case PA16:
return sam.TCC1
case PA17:
return sam.TCC1
case PA18:
return sam.TCC1
case PA19:
return sam.TCC1
case PA20:
return sam.TCC0
case PA21:
return sam.TCC0
case PA22:
return sam.TCC0
case PA23:
return sam.TCC0
case PB12:
return sam.TCC3
case PB13:
return sam.TCC3
case PB14:
return sam.TCC4
case PB15:
return sam.TCC4
case PB16:
return sam.TCC0
case PB17:
return sam.TCC0
case PB31:
return sam.TCC4
default:
return nil // not supported on this pin
func (tcc *TCC) configureClock() {
// Turn on timer clocks used for TCC and use generic clock generator 0.
switch tcc.timer() {
case sam.TCC0:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC1:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC2:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC3:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC4:
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
}
}
func (tcc *TCC) timerNum() uint8 {
switch tcc.timer() {
case sam.TCC0:
return 0
case sam.TCC1:
return 1
case sam.TCC2:
return 2
case sam.TCC3:
return 3
case sam.TCC4:
return 4
default:
return 0x0f // should not happen
}
}
+43 -54
View File
@@ -11,60 +11,49 @@ import "device/sam"
const HSRAM_SIZE = 0x00040000
// InitPWM initializes the PWM interface.
func InitPWM() {
// turn on timer clocks used for PWM
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_ | sam.MCLK_APBBMASK_TCC1_)
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_ | sam.MCLK_APBCMASK_TCC3_)
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
// This chip has five TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
TCC3 = (*TCC)(sam.TCC3)
TCC4 = (*TCC)(sam.TCC4)
)
//use clock generator 0
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
sam.GCLK_PCHCTRL_CHEN)
}
// getTimer returns the timer to be used for PWM on this pin
func (pwm PWM) getTimer() *sam.TCC_Type {
switch pwm.Pin {
case PC18:
return sam.TCC0
case PC19:
return sam.TCC0
case PC20:
return sam.TCC0
case PC21:
return sam.TCC0
case PD20:
return sam.TCC1
case PD21:
return sam.TCC1
case PB18:
return sam.TCC1
case PB12:
return sam.TCC3
case PB13:
return sam.TCC3
case PA15:
return sam.TCC2
case PC17:
return sam.TCC0
case PC16:
return sam.TCC0
case PA14:
return sam.TCC2
case PB15:
return sam.TCC4
case PB14:
return sam.TCC4
case PB20:
return sam.TCC1
case PB21:
return sam.TCC1
default:
return nil // not supported on this pin
func (tcc *TCC) configureClock() {
// Turn on timer clocks used for TCC and use generic clock generator 0.
switch tcc.timer() {
case sam.TCC0:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC1:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC2:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC3:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC4:
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
}
}
func (tcc *TCC) timerNum() uint8 {
switch tcc.timer() {
case sam.TCC0:
return 0
case sam.TCC1:
return 1
case sam.TCC2:
return 2
case sam.TCC3:
return 3
case sam.TCC4:
return 4
default:
return 0x0f // should not happen
}
}
+59
View File
@@ -0,0 +1,59 @@
// +build sam,atsame51,atsame51j19
// Peripheral abstraction layer for the atsame51.
//
// Datasheet:
// http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf
//
package machine
import "device/sam"
const HSRAM_SIZE = 0x00030000
// This chip has five TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
TCC3 = (*TCC)(sam.TCC3)
TCC4 = (*TCC)(sam.TCC4)
)
func (tcc *TCC) configureClock() {
// Turn on timer clocks used for the TCC and use generic clock generator 0.
switch tcc.timer() {
case sam.TCC0:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC1:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC2:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC3:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC4:
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
}
}
func (tcc *TCC) timerNum() uint8 {
switch tcc.timer() {
case sam.TCC0:
return 0
case sam.TCC1:
return 1
case sam.TCC2:
return 2
case sam.TCC3:
return 3
case sam.TCC4:
return 4
default:
return 0x0f // should not happen
}
}
+59
View File
@@ -0,0 +1,59 @@
// +build sam,atsame5x,atsame54p20
// Peripheral abstraction layer for the atsame54.
//
// Datasheet:
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
//
package machine
import "device/sam"
const HSRAM_SIZE = 0x00040000
// This chip has five TCC peripherals, which have PWM as one feature.
var (
TCC0 = (*TCC)(sam.TCC0)
TCC1 = (*TCC)(sam.TCC1)
TCC2 = (*TCC)(sam.TCC2)
TCC3 = (*TCC)(sam.TCC3)
TCC4 = (*TCC)(sam.TCC4)
)
func (tcc *TCC) configureClock() {
// Turn on timer clocks used for TCC and use generic clock generator 0.
switch tcc.timer() {
case sam.TCC0:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC1:
sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC2:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC3:
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
case sam.TCC4:
sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
}
}
func (tcc *TCC) timerNum() uint8 {
switch tcc.timer() {
case sam.TCC0:
return 0
case sam.TCC1:
return 1
case sam.TCC2:
return 2
case sam.TCC3:
return 3
case sam.TCC4:
return 4
default:
return 0x0f // should not happen
}
}
-2
View File
@@ -8,8 +8,6 @@ import (
"unsafe"
)
type PinMode uint8
const (
PinInput PinMode = iota
PinInputPullup
-2
View File
@@ -21,8 +21,6 @@ var (
ErrInvalidSPIBus = errors.New("machine: invalid SPI bus")
)
type PinMode uint8
const (
PinOutput PinMode = iota
PinInput
-2
View File
@@ -11,8 +11,6 @@ func CPUFrequency() uint32 {
return 80000000 // 80MHz
}
type PinMode uint8
const (
PinOutput PinMode = iota
PinInput
-2
View File
@@ -12,8 +12,6 @@ func CPUFrequency() uint32 {
return 16000000
}
type PinMode uint8
const (
PinInput PinMode = iota
PinOutput
+1 -3
View File
@@ -30,8 +30,6 @@ const (
// Make it easier to directly write to I/O RAM.
var ioram = (*[0x400]volatile.Register8)(unsafe.Pointer(uintptr(0x04000000)))
type PinMode uint8
// Set has not been implemented.
func (p Pin) Set(value bool) {
// do nothing
@@ -54,7 +52,7 @@ func (d FramebufDisplay) Size() (x, y int16) {
}
func (d FramebufDisplay) SetPixel(x, y int16, c color.RGBA) {
d.port[y][x].Set(uint16(c.R)&0x1f | uint16(c.G)&0x1f<<5 | uint16(c.B)&0x1f<<10)
d.port[y][x].Set((uint16(c.R) >> 3) | ((uint16(c.G) >> 3) << 5) | ((uint16(c.B) >> 3) << 10))
}
func (d FramebufDisplay) Display() error {
-20
View File
@@ -10,8 +10,6 @@ var (
UART0 = UART{0}
)
type PinMode uint8
const (
PinInput PinMode = iota
PinOutput
@@ -84,24 +82,6 @@ func (adc ADC) Get() uint16 {
//export __tinygo_adc_read
func adcRead(pin Pin) uint16
// InitPWM enables support for PWM peripherals.
func InitPWM() {
// Nothing to do here.
}
// Configure configures a PWM pin for output.
func (pwm PWM) Configure() error {
return nil
}
// Set turns on the duty cycle for a PWM pin using the provided value.
func (pwm PWM) Set(value uint16) {
pwmSet(pwm.Pin, value)
}
//export __tinygo_pwm_set
func pwmSet(pin Pin, value uint16)
// I2C is a generic implementation of the Inter-IC communication protocol.
type I2C struct {
Bus uint8
-1
View File
@@ -14,7 +14,6 @@ func CPUFrequency() uint32 {
return 390000000
}
type PinMode uint8
type fpioaPullMode uint8
type PinChange uint8
-2
View File
@@ -15,8 +15,6 @@ func CPUFrequency() uint32 {
return 600000000
}
type PinMode uint8
const (
// GPIO
PinInput PinMode = iota
-2
View File
@@ -13,8 +13,6 @@ var (
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
)
type PinMode uint8
const (
PinInput PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos)
PinInputPullup PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos)
+3 -3
View File
@@ -63,7 +63,7 @@ func (i2c *I2C) setPins(scl, sda Pin) {
// PWM
var (
pwmChannelPins = [3]uint32{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}
pwms = [3]*nrf.PWM_Type{nrf.PWM0, nrf.PWM1, nrf.PWM2}
pwmChannelSequence [3]uint16
PWM0 = &PWM{PWM: nrf.PWM0}
PWM1 = &PWM{PWM: nrf.PWM1}
PWM2 = &PWM{PWM: nrf.PWM2}
)
+4 -3
View File
@@ -83,7 +83,8 @@ func (i2c *I2C) setPins(scl, sda Pin) {
// PWM
var (
pwmChannelPins = [4]uint32{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}
pwms = [4]*nrf.PWM_Type{nrf.PWM0, nrf.PWM1, nrf.PWM2, nrf.PWM3}
pwmChannelSequence [4]uint16
PWM0 = &PWM{PWM: nrf.PWM0}
PWM1 = &PWM{PWM: nrf.PWM1}
PWM2 = &PWM{PWM: nrf.PWM2}
PWM3 = &PWM{PWM: nrf.PWM3}
)
+4 -3
View File
@@ -79,7 +79,8 @@ func (i2c *I2C) setPins(scl, sda Pin) {
// PWM
var (
pwmChannelPins = [4]uint32{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}
pwms = [4]*nrf.PWM_Type{nrf.PWM0, nrf.PWM1, nrf.PWM2, nrf.PWM3}
pwmChannelSequence [4]uint16
PWM0 = &PWM{PWM: nrf.PWM0}
PWM1 = &PWM{PWM: nrf.PWM1}
PWM2 = &PWM{PWM: nrf.PWM2}
PWM3 = &PWM{PWM: nrf.PWM3}
)
+2 -2
View File
@@ -445,7 +445,7 @@ func handleStandardSetup(setup usbSetup) bool {
func cdcSetup(setup usbSetup) bool {
if setup.bmRequestType == usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE {
if setup.bRequest == usb_CDC_GET_LINE_CODING {
b := make([]byte, 7)
var b [cdcLineInfoSize]byte
b[0] = byte(usbLineInfo.dwDTERate)
b[1] = byte(usbLineInfo.dwDTERate >> 8)
b[2] = byte(usbLineInfo.dwDTERate >> 16)
@@ -454,7 +454,7 @@ func cdcSetup(setup usbSetup) bool {
b[5] = byte(usbLineInfo.bParityType)
b[6] = byte(usbLineInfo.bDataBits)
sendUSBPacket(0, b)
sendUSBPacket(0, b[:])
return true
}
}

Some files were not shown because too many files have changed in this diff Show More