The doppler is a Cortex M4F Microcontroller + FPGA development board. It comes in the same tiny form factor similar to a Teensy and is open source.
- 120Mhz ARM Cortex M4F MCU 512KB Flash (Microchip ATSAMD51G19A) with FPU
- FPGA 5000 LUT, 1MBit RAM, 6 DSP Cores,OSC, PLL (Lattice ICE40UP5K)
- Arduino IDE compatible
- Breadboard friendly (DIL48)
- Micro USB
- Power over USB or external via pin headers
- VCC 3.5V …. 5.5V
- All GPIO Pins are 3.3V
- 1 LED connected to SAMD51
- 4 x 4 LED Matrix (connected to FPGA)
- 2 User buttons (connected to FPGA)
- AREF Solder Jumper
- I2C (need external pullup), SPI, QSPI, USART Pins
- 2 DAC pins, 10 ADC pins
- Pinout here: https://github.com/dadamachines/doppler/blob/master/hardware/doppler-quickstart_print.pdf
- Full open source toolchain
- SWD programming pin headers
- Double press reset to enter the bootloader
- UF2 Bootloader with Firmwareupload via simple USB stick mode (Mass Storage Device)
We are very excited about FPGA’s but getting started with them is daunting especially because of the ecosystem of proprietary toolchains and expensive hardware. The doppler takes away most of these pains by providing all the tooling to get you up and running.
There are two chips on the board a SAMD51 ARM Microcontroller and an ICE40 FPGA. The Microcontroller is easily programmable with, for example, the beginner-friendly Arduino environment. It also needs to be used to upload the configuration as a bitstream to the FPGA. This guide goes in detail on how to set up both development environments for the SAMD51 and the ICE40.
Please find the instructions in our repository with the dadamachines board support packages for Arduino. https://github.com/dadamachines/arduino-board-index
1.1 Install Docker as binary:
1.2 If you are on macOS using homebrew you can do this easily by running:
brew cask install docker
-
Open the Docker Application. And give it the privileges it's asking for.
-
Download zip or git clone the doppler-FPGA-firmware:
git clone https://github.com/dadamachines/doppler-FPGA-firmware
- Go into the directory you just downloaded.
cd doppler-FPGA-firmware/
- Build the icestorm toolchain with Docker:
This will take a while... (you only need todo that once or when updating the toolchain)
docker build -t icestorm icestorm/
- Set the Mountpoint for Docker
export MOUNTPOINT=`pwd`
docker run -it -v $MOUNTPOINT:/PRJ icestorm bash
- Now we are in the container and can build our bitstream. For our doppler_simple_io example: The Makefile generate the header file from bitstream to include in the arduino sketch.
cd PRJ/doppler_simple_io/
make
ls -l
#include <ICEClass.h>
#include "[PATHTO]/top.bin.h" // set up the path to doppler_simple_io or custom firmware
ICEClass ice40; // subclass ICEClass to implement custom spi protocol
void setup() {
ice40.upload(top_bin,sizeof(top_bin)); // Upload BitStream Firmware to FPGA -> see variant.h
ice40.upload(); // Upload BitStream Firmware to FPGA -> see variant.h
ice40.initSPI(); // start SPI runtime Link to FPGA
}
void loop() {
static uint16_t x = 0;
ice40.sendSPI16(x++); delay(50);
}
Our language of choice for the FPGA is Verilog
We recommend this Youtube channel to learn Verilog
https://www.youtube.com/watch?v=-bIeiMmqaZE
/* DOPPLER-Board-Layout:
* ---------------- FPGA Pins ------------------
* DAC1 SCK MOSI DAC0 LedR LedG LedB CT1 CP0
* DIL Pin 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25
* |--O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O---|
* name | VIN 5V 3.3V A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 GND R2 R1 R0 F14 F13 F12 F11 F10 F9 |
* alt | VIN 5V 3.3V PA11 PA10 PA09 PA08 PA07 PA06 PA05 PA04 PB09 PB08 PA02 GND 41 40 39 38 37 36 35 34 32 |
* | ö ö ö ö |
* | ö ö ö ö |BTN:S1| |
* | USB DOPPLER: SamD51 <- SPI -> icE40 |BTN:RESET| ö ö ö ö |
* | ö ö ö ö |BTN:S2| |
* | |
* alt | GND PA13 PA12 PB11 PA14 PA15 PB10 PA31 PA30 RES PA19 PA20 PA21 PA22 3.3V 11 12 13 18 19 20 21 23 25 |
* name | GND 0 1 2 3 4 5 6 7 8 9 3.3V F0 F1 F2 F3 F4 F5 F6 F7 F8 |
* L--O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O----O---|
* DIL Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
* SCL SDA MISO SS SWD SWC RES CT0 CP0
* -- I2C-- --- SWD --- ----- Shared ----- ---------------- FPGA Pins ------------------
*/
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop routine runs over and over again forever:
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
void setup() {
// put your setup code here, to run once:
pinMode(PIN_DAC0,OUTPUT);
pinMode(PIN_DAC1,OUTPUT);
dacInit();
}
void loop() {
// put your main code here, to run repeatedly:
static uint16_t left = 0;
static uint16_t right = 0;
left+=256;
right-=256;
dacWrite(left,right);
}
see https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-wire how to handle sercoms
#include <Wire.h>
#include "wiring_private.h" // pinPeripheral() function
TwoWire Wire1(&sercom0, A7, A8); // create new Wire Port
void setup() {
// put your setup code here, to run once:
Wire1.begin(); // set pinPeripheral after this line!!!
pinPeripheral(A7, PIO_SERCOM); // assign SDA
pinPeripheral(A8, PIO_SERCOM); // assign SDC
Serial.begin(9600);
Serial.println("\nI2C Scanner");
}
void loop() {
// put your main code here, to run repeatedly:
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for(address = 1; address < 127; address++ ) {
// The i2c_scanner uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire1.beginTransmission(address);
error = Wire1.endTransmission();
Wire1.requestFrom(address,2);
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address<16)
Serial.print("0");
Serial.print(address,HEX);
Serial.println(" !");
nDevices++;
} else if (error==4) {
Serial.print("Unknow error at address 0x");
if (address<16)
Serial.print("0");
Serial.println(address,HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices found\n");
else
Serial.println("done, if find on each addr -> check pullups! \n");
delay(5000); // wait 5 seconds for next scan
}
Learn how to use SERCOMS on SAMD Microcontrollers https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-serial
// Create Serial2 instance
#include "wiring_private.h" // pinPeripheral() function
Uart Serial2 (&sercom0, A7, A8, SERCOM_RX_PAD_1, UART_TX_PAD_0);
void SERCOM0_0_Handler() { Serial2.IrqHandler(); }
void SERCOM0_1_Handler() { Serial2.IrqHandler(); }
void SERCOM0_2_Handler() { Serial2.IrqHandler(); }
void SERCOM0_3_Handler() { Serial2.IrqHandler(); }
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial2.begin(9600);
pinPeripheral(A7, PIO_SERCOM); // TX
pinPeripheral(A8, PIO_SERCOM); // RX
}
uint8_t i=0;
void loop() {
Serial.print(i);
Serial2.write(i++);
if (Serial2.available()) {
Serial.print(" -> 0x"); Serial.print(Serial2.read(), HEX);
}
Serial.println();
delay(500);
}
See https://github.com/dadamachines/doppler-FPGA-firmware to learn how to make a bitstream from Verilog.
#include <ICEClass.h>
ICEClass ice40;
uint16_t leds = 1;
void setup() {
// put your setup code here, to run once:
ice40.upload(); // Upload BitStream Firmware to FPGA -> see variant.h
delay(100);
// start SPI runtime Link to FPGA
ice40.initSPI();
}
void loop() {
// put your main code here, to run repeatedly:
ice40.sendSPI16(leds );
leds = leds << 1;
if(leds==0){
leds=1;
}
delay(100);
}
#include <ICEClass.h>
ICEClass ice40;
uint16_t hexmapFont[16] = { 0xF99F,0xF22F,0xF42F,0xF17F,0x1F99,0x7F8F,0xF9F8,0x111F,
0x7DBE,0x1F9F,0x9F9F,0xADAC,0xF88F,0xE99E,0xF8EF,0x8E8F };
void setup() { // put your setup code here, to run once:
ice40.upload(); // Upload BitStream Firmware to FPGA -> see variant.h
delay(100);
ice40.initSPI(); // start SPI runtime Link to FPGA
}
void loop() { // put your main code here, to run repeatedly:
for(int i = 0 ; i < 16 ; i++){
ice40.sendSPI16(hexmapFont[i] );
delay(800);
}
}
To report a bug, contribute, discuss usage, or simply request support, please create an issue here.
Ideation & Software: Sven Braun
Design & Hardware: Johannes Elias Lohbihler
Sales & Distribution: dadamachines
Production: Watterott electronic
Arduino & Arduino IDE: arduino.cc
SAMD51 Arduino Cores: Adafruit Industries
Icestorm: https://github.com/cliffordwolf/icestorm
Yosys: https://github.com/YosysHQ/yosys