[ion] Initial add of the f730 folder

This commit is contained in:
Romain Goyet
2019-01-09 10:01:28 +01:00
parent bfb5826a1d
commit 927763bc31
132 changed files with 8048 additions and 0 deletions

51
ion/src/f730/Makefile Normal file
View File

@@ -0,0 +1,51 @@
include ion/src/device/boot/Makefile
include ion/src/device/bench/Makefile
include ion/src/device/usb/Makefile
ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))"
objs += $(addprefix ion/src/shared/, \
console_line.o \
crc32_padded.o\
events_modifier.o \
)
# If you need to profile execution, you can replace events_keyboard with
# events_replay.o and dummy/events_modifier.o
objs += $(addprefix ion/src/device/, \
backlight.o \
battery.o\
base64.o\
console.o \
device.o\
display.o\
events.o\
flash.o\
keyboard.o\
led.o\
power.o\
sd_card.o\
stack.o\
swd.o \
timing.o \
usb.o \
wakeup.o \
)
# When using the register.h C++ file in production mode, we expect the compiler
# to completely inline all bit manipulations. For some reason, if we build using
# the -Os optimization flag, GCC doesn't inline everything and and ends up
# emitting calls to aeabi_llsl for 64-bits registers. This is very sub-optimal
# so we're enforcing -O3 for this specific file.
ifneq ($(DEBUG),1)
ifneq ($(COMPILER),llvm)
ion/src/device/led.o: SFLAGS+=-O3
ion/src/device/console.o: SFLAGS+=-O3
ion/src/device/display.o: SFLAGS+=-O3
ion/src/device/swd.o: SFLAGS+=-O3
endif
endif
#objs += $(addprefix ion/src/device/keyboard/, keyboard.o)

View File

@@ -0,0 +1,85 @@
#include <ion.h>
#include "regs/regs.h"
#include "backlight.h"
/* This driver controls the RT9365 LED driver.
* This chip allows the brightness to be set to 16 different values. It starts
* at full brightness on power on. Applying a pulse on the EN pin will select
* the next value in decreasing order. Once it reaches the minimal value, the
* next pulse will loop back to full brightness. */
// Public Ion::Backlight methods
namespace Ion {
namespace Backlight {
void setBrightness(uint8_t b) {
Device::setLevel(b >> 4);
}
uint8_t brightness() {
return Device::level() << 4;
}
}
}
// Private Ion::Backlight::Device methods
namespace Ion {
namespace Backlight {
namespace Device {
static uint8_t sLevel;
void init() {
GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Output);
sLevel = 0xF;
resume();
}
void shutdown() {
GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Analog);
GPIOC.PUPDR()->setPull(6, GPIO::PUPDR::Pull::None);
}
void suspend() {
GPIOC.ODR()->set(6, false);
Timing::msleep(3); // Might not need to be blocking
}
void resume() {
GPIOC.ODR()->set(6, true);
Timing::usleep(50);
uint8_t level = sLevel;
sLevel = 0xF;
setLevel(level);
}
void setLevel(uint8_t level) {
// From sLevel = 12 to level 7 : 5 pulses
// From sLevel = 5 to level 9 : 12 pulses (5 to go to level 16, and 7 to 9)
if (sLevel < level) {
sendPulses(16 + sLevel - level);
} else {
sendPulses(sLevel - level);
}
sLevel = level;
}
uint8_t level() {
return sLevel;
}
void sendPulses(int n) {
for (int i=0; i<n; i++) {
GPIOC.ODR()->set(6, false);
Timing::usleep(20);
GPIOC.ODR()->set(6, true);
Timing::usleep(20);
}
}
}
}
}

28
ion/src/f730/backlight.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef ION_DEVICE_BACKLIGHT_H
#define ION_DEVICE_BACKLIGHT_H
#include <ion/backlight.h>
namespace Ion {
namespace Backlight {
namespace Device {
/* Pin | Role | Mode | Function
* -----+-------------------+-----------------------+----------
* PC6 | Backlight Enable | Output |
*/
void init();
void shutdown();
void suspend();
void resume();
void setLevel(uint8_t level);
uint8_t level();
void sendPulses(int n);
}
}
}
#endif

48
ion/src/f730/base64.cpp Normal file
View File

@@ -0,0 +1,48 @@
namespace Base64 {
static constexpr char encodeTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/',
};
constexpr char Padding = '=';
void encode(const unsigned char * input, unsigned int inputLength, char * output) {
unsigned int i, j;
for (i = j = 0; i < inputLength; i++) {
int s = i % 3; /* from 6/gcd(6, 8) */
switch (s) {
case 0:
output[j++] = encodeTable[(input[i] >> 2) & 0x3F];
continue;
case 1:
output[j++] = encodeTable[((input[i-1] & 0x3) << 4) + ((input[i] >> 4) & 0xF)];
continue;
case 2:
output[j++] = encodeTable[((input[i-1] & 0xF) << 2) + ((input[i] >> 6) & 0x3)];
output[j++] = encodeTable[input[i] & 0x3F];
}
}
/* move back */
i -= 1;
/* check the last and add padding */
if ((i % 3) == 0) {
output[j++] = encodeTable[(input[i] & 0x3) << 4];
output[j++] = Padding;
output[j++] = Padding;
} else if ((i % 3) == 1) {
output[j++] = encodeTable[(input[i] & 0xF) << 2];
output[j++] = Padding;
}
}
}

5
ion/src/f730/base64.h Normal file
View File

@@ -0,0 +1,5 @@
namespace Base64 {
void encode(const unsigned char * input, unsigned int inputLength, char * output);
}

88
ion/src/f730/battery.cpp Normal file
View File

@@ -0,0 +1,88 @@
#include <ion/battery.h>
#include "battery.h"
#include "regs/regs.h"
/* To measure the battery voltage, we're using the internal ADC. The ADC works
* by comparing the input voltage to a reference voltage. The only fixed voltage
* we have around is 2.8V, so that's the one we're using as a refrence. However,
* and ADC can only measure voltage that is lower than the reference voltage. So
* we need to use a voltage divider before sampling Vbat.
* To avoid draining the battery, we're using an high-impedence voltage divider,
* so we need to be careful when sampling the ADC. See AN2834 for more info. */
namespace Ion {
namespace Battery {
bool isCharging() {
return !Device::ChargingGPIO.IDR()->get(Device::ChargingPin);
}
Charge level() {
if (voltage() < 3.2f) {
return Charge::EMPTY;
}
if (voltage() < 3.5f) {
return Charge::LOW;
}
if (voltage() < 3.8f) {
return Charge::SOMEWHERE_INBETWEEN;
}
return Charge::FULL;
}
float voltage() {
ADC.CR2()->setSWSTART(true);
while (ADC.SR()->getEOC() != true) {
}
uint16_t value = ADC.DR()->get();
// The ADC is 12 bits by default
return Device::ADCDividerBridgeRatio*(Device::ADCReferenceVoltage * value)/0xFFF;
}
}
}
namespace Ion {
namespace Battery {
namespace Device {
void init() {
initGPIO();
/* The BAT_SNS pin is connected to Vbat through a divider bridge. It therefore
* has a voltage of Vbat/2. We'll measure this using ADC channel 0. */
ADCGPIO.MODER()->setMode(ADCPin, GPIO::MODER::Mode::Analog);
// Step 2 - Enable the ADC
RCC.APB2ENR()->setADC1EN(true);
ADC.CR2()->setADON(true);
// Configure the ADC channel
ADC.SQR1()->setL(0); // Always sample the same channel
ADC.SQR3()->setSQ1(ADCChannel);
ADC.SMPR()->setSamplingTime(ADCChannel, ADC::SMPR::SamplingTime::Cycles480); // Use the max sampling time
}
void initGPIO() {
/* Step 1 - Configure the GPIOs
* The BAT_CHRG pin is connected to the Li-Po charging IC. That pin uses an
* open-drain output. Open-drain output are either connected to ground or left
* floating. To interact with such an output, our input must therefore be
* pulled up. */
ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Input);
ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::Up);
}
void shutdown() {
ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Analog);
ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::None);
// Disable the ADC
ADC.CR2()->setADON(false);
RCC.APB2ENR()->setADC1EN(false);
}
}
}
}

35
ion/src/f730/battery.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef ION_DEVICE_BATTERY_H
#define ION_DEVICE_BATTERY_H
#include "regs/regs.h"
namespace Ion {
namespace Battery {
namespace Device {
/* Pin | Role | Mode | Function
* -----+-------------------+-----------------------+----------
* PA0 | BAT_CHRG | Input, pulled up | Low = charging, high = full
* PA1 | VBAT_SNS | Analog | ADC1_1
*/
void init();
void shutdown();
void initGPIO();
void initADC();
constexpr GPIO ChargingGPIO = GPIOA;
constexpr uint8_t ChargingPin = 0;
constexpr GPIO ADCGPIO = GPIOA;
constexpr uint8_t ADCPin = 1;
constexpr uint8_t ADCChannel = 1;
constexpr float ADCReferenceVoltage = 2.8f;
constexpr float ADCDividerBridgeRatio = 2.0f;
}
}
}
#endif

View File

@@ -0,0 +1,21 @@
objs += $(addprefix ion/src/device/bench/, \
bench.o \
command_handler.o \
command_list.o \
)
objs += $(addprefix ion/src/device/bench/command/, \
command.o \
adc.o \
backlight.o \
charge.o \
display.o \
exit.o \
keyboard.o \
led.o \
mcu_serial.o \
ping.o \
print.o \
suspend.o \
vblank.o \
)

View File

@@ -0,0 +1,46 @@
#include "bench.h"
#include <ion.h>
#include <kandinsky.h>
#include "command_list.h"
namespace Ion {
namespace Device {
namespace Bench {
constexpr CommandHandler handles[] = {
CommandHandler("ADC", Command::ADC),
CommandHandler("BACKLIGHT", Command::Backlight),
CommandHandler("CHARGE", Command::Charge),
CommandHandler("DISPLAY", Command::Display),
CommandHandler("EXIT", Command::Exit),
CommandHandler("KEYBOARD", Command::Keyboard),
CommandHandler("LED", Command::LED),
CommandHandler("MCU_SERIAL", Command::MCUSerial),
CommandHandler("PING", Command::Ping),
CommandHandler("PRINT", Command::Print),
CommandHandler("SUSPEND", Command::Suspend),
CommandHandler("VBLANK", Command::VBlank),
CommandHandler(nullptr, nullptr)
};
constexpr const CommandList sCommandList = CommandList(handles);
constexpr int kMaxCommandLength = 255;
void run() {
KDContext * ctx = KDIonContext::sharedContext();
ctx->fillRect(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColorWhite);
ctx->drawString("BENCH", KDPoint((320-50)/2, (240-18)/2));
char command[kMaxCommandLength];
while (true) {
Ion::Console::readLine(command, kMaxCommandLength);
const CommandHandler * ch = sCommandList.dispatch(command);
if (ch != nullptr && ch->function() == Command::Exit) {
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
#ifndef ION_DEVICE_BENCH_BENCH_H
#define ION_DEVICE_BENCH_BENCH_H
namespace Ion {
namespace Device {
namespace Bench {
void run();
}
}
}
#endif

View File

@@ -0,0 +1,27 @@
#include "command.h"
#include <ion.h>
#include <poincare/print_float.h>
#include <ion/src/device/led.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void ADC(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
float result = Ion::Battery::voltage();
constexpr int precision = 8;
constexpr int bufferSize = Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(precision);
char responseBuffer[bufferSize+4] = {'A', 'D', 'C', '='}; // ADC=
Poincare::PrintFloat::convertFloatToText<float>(result, responseBuffer+4, bufferSize, precision, Poincare::Preferences::PrintFloatMode::Decimal);
reply(responseBuffer);
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
#include "command.h"
#include <ion.h>
#include <ion/src/device/backlight.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void Backlight(const char * input) {
// Input must be of the form "0xAA" or "ON" or "OFF"
if (strcmp(input, sON) == 0) {
Ion::Backlight::Device::init();
reply(sOK);
return;
}
if (strcmp(input, sOFF) == 0) {
Ion::Backlight::Device::shutdown();
reply(sOK);
return;
}
if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || input[4] != NULL) {
reply(sSyntaxError);
return;
}
uint32_t brightness = hexNumber(input+2);
Ion::Backlight::setBrightness(brightness);
reply(sOK);
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
#include "command.h"
#include <ion.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void Charge(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
if (Ion::Battery::isCharging()) {
reply("CHARGE=ON");
} else {
reply("CHARGE=OFF");
}
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
#include "command.h"
#include <ion.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
const char * const sOK = "OK";
const char * const sKO = "KO";
const char * const sSyntaxError = "SYNTAX_ERROR";
const char * const sON = "ON";
const char * const sOFF = "OFF";
void reply(const char * s) {
Console::writeLine(s);
}
int8_t hexChar(char c) {
if (c >= '0' && c <= '9') {
return (c - '0');
}
if (c >= 'A' && c <= 'F') {
return (c - 'A') + 0xA;
}
return -1;
}
bool isHex(char c) {
return hexChar(c) >= 0;
}
uint32_t hexNumber(const char * s, int maxLength) {
uint32_t result = 0;
int index = 0;
while ((maxLength < 0 || index < maxLength) && s[index] != NULL) {
result = (result << 4) | hexChar(s[index]);
index++;
}
return result;
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
#ifndef ION_DEVICE_BENCH_COMMAND_COMMAND_H
#define ION_DEVICE_BENCH_COMMAND_COMMAND_H
#include <stdint.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
typedef void (*Function)(const char * input);
void ADC(const char * input);
void Backlight(const char * input);
void Charge(const char * input);
void Display(const char * input);
void Exit(const char * input);
void Keyboard(const char * input);
void LED(const char * input);
void MCUSerial(const char * input);
void Ping(const char * input);
void Print(const char * input);
void Suspend(const char * input);
void VBlank(const char * input);
extern const char * const sOK;
extern const char * const sKO;
extern const char * const sSyntaxError;
extern const char * const sON;
extern const char * const sOFF;
void reply(const char * s);
int8_t hexChar(char c);
bool isHex(char c);
uint32_t hexNumber(const char * s, int maxLength = -1);
}
}
}
}
#endif

View File

@@ -0,0 +1,74 @@
#include "command.h"
#include <ion.h>
#include <ion/src/device/display.h>
#include <poincare/integer.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
// Input must be of the form "0xAABBCC" or "ON" or "OFF"
void Display(const char * input) {
if (strcmp(input, sON) == 0) {
Ion::Display::Device::init();
reply(sOK);
return;
}
if (strcmp(input, sOFF) == 0) {
Ion::Display::Device::shutdown();
reply(sOK);
return;
}
if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) {
reply(sSyntaxError);
return;
}
/* We fill the screen with a color and return OK if we read that color back everywhere. */
KDColor c = KDColor::RGB24(hexNumber(input));
constexpr int stampHeight = 10;
constexpr int stampWidth = 10;
static_assert(Ion::Display::Width % stampWidth == 0, "Stamps must tesselate the display");
static_assert(Ion::Display::Height % stampHeight == 0, "Stamps must tesselate the display");
static_assert(stampHeight % 2 == 0 || stampWidth % 2 == 0, "Even number of XOR needed.");
KDColor stamp[stampWidth*stampHeight];
for (int i=0;i<stampWidth*stampHeight; i++) {
stamp[i] = c;
}
for (int i=0; i<Ion::Display::Width/stampWidth; i++) {
for (int j=0; j<Ion::Display::Height/stampHeight; j++) {
Ion::Display::pushRect(KDRect(i*stampWidth, j*stampHeight, stampWidth, stampHeight), stamp);
}
}
int numberOfInvalidPixels = 0;
for (int i=0; i<Ion::Display::Width/stampWidth; i++) {
for (int j=0; j<Ion::Display::Height/stampHeight; j++) {
for (int i=0;i<stampWidth*stampHeight; i++) {
stamp[i] = KDColorBlack;
}
Ion::Display::pullRect(KDRect(i*stampWidth, j*stampHeight, stampWidth, stampHeight), stamp);
for (int i=0;i<stampWidth*stampHeight; i++) {
if (stamp[i] != c) {
numberOfInvalidPixels++;
}
}
}
}
char response[16] = {'D', 'E', 'L', 'T', 'A', '='};
Poincare::Integer(numberOfInvalidPixels).serialize(response+6, sizeof(response)-6);
reply(response);
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
#include "command.h"
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void Exit(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
reply(sOK);
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
#include "command.h"
#include <ion.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void Keyboard(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
char result[9+Ion::Keyboard::NumberOfKeys+1] = { 'K', 'E', 'Y', 'B', 'O', 'A', 'R', 'D', '=' };
Ion::Keyboard::State state = Ion::Keyboard::scan();
for (uint8_t i=0; i<Ion::Keyboard::NumberOfKeys; i++) {
result[9+i] = state.keyDown((Ion::Keyboard::Key)i) ? '1' : '0';
}
result[9+Ion::Keyboard::NumberOfKeys] = 0;
reply(result);
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
#include "command.h"
#include <ion.h>
#include <ion/src/device/led.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
// Input must be of the form "0xAABBCC" or "ON" or "OFF"
void LED(const char * input) {
if (strcmp(input, sON) == 0) {
Ion::LED::Device::init();
Ion::Console::writeLine(sOK);
return;
}
if (strcmp(input, sOFF) == 0) {
Ion::LED::Device::shutdown();
Ion::Console::writeLine(sOK);
return;
}
if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) {
Ion::Console::writeLine(sSyntaxError);
return;
}
uint32_t hexColor = hexNumber(input+2);
KDColor ledColor = KDColor::RGB24(hexColor);
Ion::LED::setColor(ledColor);
Ion::Console::writeLine(sOK);
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
#include "command.h"
#include <ion.h>
#include "../../device.h"
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void MCUSerial(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
char response[11 + Ion::Device::SerialNumberLength + 1] = {'M', 'C', 'U', '_', 'S', 'E', 'R', 'I', 'A', 'L', '=', 0};
Ion::Device::copySerialNumber(response + 11);
reply(response);
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
#include "command.h"
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void Ping(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
reply("PONG");
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
#include "command.h"
#include <ion.h>
#include <ion/src/device/led.h>
#include <kandinsky.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
// Input must be of the form "XX,YY,STRING"
void Print(const char * input) {
if (input == nullptr || !isHex(input[0]) || !isHex(input[1]) || input[2] != ',' || !isHex(input[3]) || !isHex(input[4]) || input[5] != ',') {
reply(sKO);
return;
}
char x = hexNumber(input, 2);
char y = hexNumber(input+3, 2);
KDContext * ctx = KDIonContext::sharedContext();
ctx->drawString(input+6, KDPoint(x, y));
reply(sOK);
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
#include "command.h"
#include <ion.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void Suspend(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
reply(sOK);
Ion::Timing::msleep(100);
Ion::Power::suspend();
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
#include "command.h"
#include <ion.h>
#include <ion/src/device/display.h>
namespace Ion {
namespace Device {
namespace Bench {
namespace Command {
void VBlank(const char * input) {
if (input != nullptr) {
reply(sSyntaxError);
return;
}
for (int i=0; i<6; i++) {
Ion::Display::waitForVBlank();
}
reply(sOK);
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
#include "command_handler.h"
#include <string.h>
namespace Ion {
namespace Device {
namespace Bench {
bool CommandHandler::valid() const {
return (m_name != nullptr && m_function != nullptr);
}
bool CommandHandler::handle(const char * command) const {
if (matches(command)) {
size_t nameLength = strlen(m_name);
if (command[nameLength] == '=') {
m_function(command+nameLength+1); // Skip the "Equal character"
} else {
m_function(nullptr);
}
return true;
}
return false;
}
bool CommandHandler::matches(const char * command) const {
const char * c = command;
const char * n = m_name;
while (true) {
if (*n == NULL) {
if (*c == NULL || *c == '=') {
return true;
}
}
if (*c != *n) {
return false;
}
c++;
n++;
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
#ifndef ION_DEVICE_BENCH_COMMAND_HANDLER_H
#define ION_DEVICE_BENCH_COMMAND_HANDLER_H
#include "command/command.h"
namespace Ion {
namespace Device {
namespace Bench {
class CommandHandler {
public:
constexpr CommandHandler(const char * name, Command::Function function) :
m_name(name), m_function(function) {}
bool valid() const;
bool handle(const char * command) const;
Command::Function function() const { return m_function; }
private:
bool matches(const char * command) const;
const char * m_name;
Command::Function m_function;
};
}
}
}
#endif

View File

@@ -0,0 +1,22 @@
#include "command_list.h"
#include <ion.h>
namespace Ion {
namespace Device {
namespace Bench {
const CommandHandler * CommandList::dispatch(const char * command) const {
const CommandHandler * handler = m_handlers;
while (handler->valid()) {
if (handler->handle(command)) {
return handler;
}
handler++;
}
Console::writeLine("NOT_FOUND");
return nullptr;
}
}
}
}

View File

@@ -0,0 +1,22 @@
#ifndef ION_DEVICE_BENCH_COMMAND_LIST_H
#define ION_DEVICE_BENCH_COMMAND_LIST_H
#include "command_handler.h"
namespace Ion {
namespace Device {
namespace Bench {
class CommandList {
public:
constexpr CommandList(const CommandHandler * handlers) : m_handlers(handlers) {}
const CommandHandler * dispatch(const char * command) const;
private:
const CommandHandler * m_handlers;
};
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
objs += $(addprefix ion/src/device/boot/, isr.o rt0.o)
LDSCRIPT = ion/src/device/boot/flash.ld

112
ion/src/f730/boot/flash.ld Normal file
View File

@@ -0,0 +1,112 @@
/* Linker script
* The role of this script is to take all the object files built by the compiler
* and produce a single binary suitable for execution.
* Without an explicit linker script, the linker will produce a binary file that
* would not match some of our requirements (for example, we want the code to be
* written at a specific address (in Flash ROM) and the data at another. */
/* Let's instruct the linker about our memory layout.
* This will let us use shortcuts such as ">FLASH" to ask for a given section to
* be stored in Flash. */
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K
}
STACK_SIZE = 32K;
SECTIONS {
.isr_vector_table ORIGIN(FLASH) : {
/* When booting, the STM32F412 fetches the content of address 0x0, and
* extracts from it various key infos: the initial value of the PC register
* (program counter), the initial value of the stack pointer, and various
* entry points to interrupt service routines. This data is called the ISR
* vector table.
*
* Note that address 0x0 is always an alias. It points to the beginning of
* Flash, SRAM, or integrated bootloader depending on the boot mode chosen.
* (This mode is chosen by setting the BOOTn pins on the chip).
*
* We're generating the ISR vector table in code because it's very
* convenient: using function pointers, we can easily point to the service
* routine for each interrupt. */
KEEP(*(.isr_vector_table))
} >FLASH
.header : {
KEEP(*(.header))
} >FLASH
.text : {
. = ALIGN(4);
*(.text)
*(.text.*)
} >FLASH
.init_array : {
. = ALIGN(4);
_init_array_start = .;
KEEP (*(.init_array*))
_init_array_end = .;
} >FLASH
.rodata : {
. = ALIGN(4);
*(.rodata)
*(.rodata.*)
} >FLASH
.data : {
/* The data section is written to Flash but linked as if it were in RAM.
*
* This is required because its initial value matters (so it has to be in
* persistant memory in the first place), but it is a R/W area of memory
* so it will have to live in RAM upon execution (in linker lingo, that
* translates to the data section having a LMA in Flash and a VMA in RAM).
*
* This means we'll have to copy it from Flash to RAM on initialization.
* To do this, we'll need to know the source location of the data section
* (in Flash), the target location (in RAM), and the size of the section.
* That's why we're defining three symbols that we'll use in the initial-
* -ization routine. */
. = ALIGN(4);
_data_section_start_flash = LOADADDR(.data);
_data_section_start_ram = .;
*(.data)
*(.data.*)
_data_section_end_ram = .;
} >SRAM AT> FLASH
.bss : {
/* The bss section contains data for all uninitialized variables
* So like the .data section, it will go in RAM, but unlike the data section
* we don't care at all about an initial value.
*
* Before execution, crt0 will erase that section of memory though, so we'll
* need pointers to the beginning and end of this section. */
. = ALIGN(4);
_bss_section_start_ram = .;
*(.bss)
*(.bss.*)
/* The compiler may choose to allocate uninitialized global variables as
* COMMON blocks. This can be disabled with -fno-common if needed. */
*(COMMON)
_bss_section_end_ram = .;
} >SRAM
.heap : {
_heap_start = .;
/* Note: We don't increment "." here, we set it. */
. = (ORIGIN(SRAM) + LENGTH(SRAM) - STACK_SIZE);
_heap_end = .;
} >SRAM
.stack : {
. = ALIGN(8);
_stack_end = .;
. += (STACK_SIZE - 8);
. = ALIGN(8);
_stack_start = .;
} >SRAM
}

129
ion/src/f730/boot/isr.c Normal file
View File

@@ -0,0 +1,129 @@
#include "isr.h"
extern const void * _stack_start;
/* Interrupt Service Routines are void->void functions */
typedef void(*ISR)(void);
/* Notice: The Cortex-M4 expects all jumps to be made at an odd address when
* jumping to Thumb code. For example, if you want to execute Thumb code at
* address 0x100, you'll have to jump to 0x101. Luckily, this idiosyncrasy is
* properly handled by the C compiler that will generate proper addresses when
* using function pointers. */
#define INITIALISATION_VECTOR_SIZE 0x71
ISR InitialisationVector[INITIALISATION_VECTOR_SIZE]
__attribute__((section(".isr_vector_table")))
__attribute__((used))
= {
(ISR)&_stack_start, // Stack start
start, // Reset service routine,
0, // NMI service routine,
abort, // HardFault service routine,
0, // MemManage service routine,
0, // BusFault service routine,
0, // UsageFault service routine,
0, 0, 0, 0, // Reserved
0, // SVCall service routine,
0, // DebugMonitor service routine,
0, // Reserved
0, // PendSV service routine,
isr_systick, // SysTick service routine
0, // WWDG service routine
0, // PVD service routine
0, // TampStamp service routine
0, // RtcWakeup service routine
0, // Flash service routine
0, // RCC service routine
0, // EXTI0 service routine
0, // EXTI1 service routine
0, // EXTI2 service routine
0, // EXTI3 service routine
0, // EXTI4 service routine
0, // DMA1Stream0 service routine
0, // DMA1Stream1 service routine
0, // DMA1Stream2 service routine
0, // DMA1Stream3 service routine
0, // DMA1Stream4 service routine
0, // DMA1Stream5 service routine
0, // DMA1Stream6 service routine
0, // ADC1 global interrupt
0, // CAN1 TX interrupt
0, // CAN1 RX0 interrupt
0, // CAN1 RX1 interrupt
0, // CAN1 SCE interrupt
0, // EXTI Line[9:5] interrupts
0, // TIM1 Break interrupt and TIM9 global interrupt
0, // TIM1 update interrupt and TIM10 global interrupt
0, // TIM1 Trigger & Commutation interrupts and TIM11 global interrupt
0, // TIM1 Capture Compare interrupt
0, // TIM2 global interrupt
0, // TIM3 global interrupt
0, // TIM4 global interrupt
0, // I2C1 global event interrupt
0, // I2C1 global error interrupt
0, // I2C2 global event interrupt
0, // I2C2 global error interrupt
0, // SPI1 global interrupt
0, // SPI2 global interrupt
0, // USART1 global interrupt
0, // USART2 global interrupt
0, // USART3 global interrupt
0, // EXTI Line[15:10] interrupts
0, // EXTI Line 17 interrupt RTC Alarms (A and B) through EXTI line interrupt
0, // EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt
0, // TIM8 Break interrupt TIM12 global interrupt
0, // TIM8 Update interrupt TIM13 global interrupt
0, // TIM8 Trigger & Commutation interrupt TIM14 global interrupt
0, // TIM8 Cap/Com interrupt
0, // DMA1 global interrupt Channel 7
0, // FSMC global interrupt
0, // SDIO global interrupt
0, // TIM5 global interrupt
0, // SPI3 global interrupt
0, // ?
0, // ?
0, // TIM6 global interrupt
0, // TIM7 global interrupt
0, // DMA2 Stream0 global interrupt
0, // DMA2 Stream1 global interrupt
0, // DMA2 Stream2 global interrupt
0, // DMA2 Stream3 global interrupt
0, // DMA2 Stream4 global interrupt
0, // SD filter0 global interrupt
0, // SD filter1 global interrupt
0, // CAN2 TX interrupt
0, // BXCAN2 RX0 interrupt
0, // BXCAN2 RX1 interrupt
0, // CAN2 SCE interrupt
0, // USB On The Go FS global interrupt
0, // DMA2 Stream5 global interrupts
0, // DMA2 Stream6 global interrupt
0, // DMA2 Stream7 global interrupt
0, // USART6 global interrupt
0, // I2C3 event interrupt
0, // I2C3 error interrupt
0, // ?
0, // ?
0, // ?
0, // ?
0, // ?
0, // ?
0, // RNG global interrupt
0, // FPU global interrupt
0, // ?
0, // ?
0, // SPI4 global interrupt
0, // SPI5 global interrupt
0, // ?
0, // ?
0, // ?
0, // ?
0, // ?
0, // ?
0, // Quad-SPI global interrupt
0, // ?
0, // ?
0, // I2CFMP1 event interrupt
0 // I2CFMP1 error interrupt
};

16
ion/src/f730/boot/isr.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef ION_DEVICE_BOOT_ISR_H
#define ION_DEVICE_BOOT_ISR_H
#ifdef __cplusplus
extern "C" {
#endif
void start();
void abort();
void isr_systick();
#ifdef __cplusplus
}
#endif
#endif

97
ion/src/f730/boot/rt0.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "isr.h"
#include <stdint.h>
#include <string.h>
#include <ion.h>
#include "../device.h"
#include "../timing.h"
#include "../console.h"
typedef void (*cxx_constructor)();
extern "C" {
extern char _data_section_start_flash;
extern char _data_section_start_ram;
extern char _data_section_end_ram;
extern char _bss_section_start_ram;
extern char _bss_section_end_ram;
extern cxx_constructor _init_array_start;
extern cxx_constructor _init_array_end;
}
void abort() {
#if DEBUG
while (1) {
}
#else
Ion::Device::coreReset();
#endif
}
/* By default, the compiler is free to inline any function call he wants. If the
* compiler decides to inline some functions that make use of VFP registers, it
* will need to push VFP them onto the stack in calling function's prologue.
* Problem: in start()'s prologue, we would never had a chance to enable the FPU
* since this function is the first thing called after reset.
* We can safely assume that neither memcpy, memset, nor any Ion::Device::init*
* method will use floating-point numbers, but ion_main very well can.
* To make sure ion_main's potential usage of VFP registers doesn't bubble-up to
* start(), we isolate it in its very own non-inlined function call. */
static void __attribute__((noinline)) non_inlined_ion_main() {
return ion_main(0, nullptr);
}
void start() {
// This is where execution starts after reset.
// Many things are not initialized yet so the code here has to pay attention.
/* Copy data section to RAM
* The data section is R/W but its initialization value matters. It's stored
* in Flash, but linked as if it were in RAM. Now's our opportunity to copy
* it. Note that until then the data section (e.g. global variables) contains
* garbage values and should not be used. */
size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram);
memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength);
/* Zero-out the bss section in RAM
* Until we do, any uninitialized global variable will be unusable. */
size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram);
memset(&_bss_section_start_ram, 0, bssSectionLength);
/* Initialize the FPU as early as possible.
* For example, static C++ objects are very likely to manipulate float values */
Ion::Device::initFPU();
#if 0
Ion::Device::initMPU();
#endif
/* Call static C++ object constructors
* The C++ compiler creates an initialization function for each static object.
* The linker then stores the address of each of those functions consecutively
* between _init_array_start and _init_array_end. So to initialize all C++
* static objects we just have to iterate between theses two addresses and
* call the pointed function. */
#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0
#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS
for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) {
(*c)();
}
#else
/* In practice, static initialized objects are a terrible idea. Since the init
* order is not specified, most often than not this yields the dreaded static
* init order fiasco. How about bypassing the issue altogether? */
if (&_init_array_start != &_init_array_end) {
abort();
}
#endif
Ion::Device::init();
non_inlined_ion_main();
abort();
}
void __attribute__((interrupt)) isr_systick() {
Ion::Timing::Device::MillisElapsed++;
}

76
ion/src/f730/console.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include <ion.h>
#include "console.h"
/* This file implements a serial console.
* We use a 115200 8N1 serial port */
namespace Ion {
namespace Console {
char readChar() {
while (Device::UARTPort.SR()->getRXNE() == 0) {
}
return (char)Device::UARTPort.DR()->get();
}
void writeChar(char c) {
while (Device::UARTPort.SR()->getTXE() == 0) {
}
Device::UARTPort.DR()->set(c);
}
}
}
namespace Ion {
namespace Console {
namespace Device {
void init() {
RCC.APB1ENR()->setUSART3EN(true);
for(const GPIOPin & g : Pins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF7);
}
UARTPort.CR1()->setUE(true);
UARTPort.CR1()->setTE(true);
UARTPort.CR1()->setRE(true);
/* We need to set the baud rate of the UART port.
* This is set relative to the APB1 clock, which runs at 48 MHz.
*
* The baud rate is set by the following equation:
* BaudRate = fAPB1/(16*USARTDIV), where USARTDIV is a divider.
* In other words, USARDFIV = fAPB1/(16*BaudRate). All frequencies in Hz.
*
* In our case, fAPB1 = 48 MHz, so USARTDIV = 26.0416667
* DIV_MANTISSA = 26
* DIV_FRAC = 16*0.0416667 = 1
*/
UARTPort.BRR()->setDIV_MANTISSA(26);
UARTPort.BRR()->setDIV_FRAC(1);
}
void shutdown() {
for(const GPIOPin & g : Pins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
}
}
bool peerConnected() {
RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::Down);
RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::Input);
Timing::msleep(1);
bool result = RxPin.group().IDR()->get(RxPin.pin());
RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::None);
RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::AlternateFunction);
return result;
}
}
}
}

30
ion/src/f730/console.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef ION_DEVICE_CONSOLE_H
#define ION_DEVICE_CONSOLE_H
#include <ion/console.h>
#include "regs/regs.h"
namespace Ion {
namespace Console {
namespace Device {
/* Pin | Role | Mode
* -----+-------------------+--------------------
* PC11 | UART3 RX | Alternate Function
* PD8 | UART3 TX | Alternate Function
*/
void init();
void shutdown();
bool peerConnected();
constexpr USART UARTPort = USART(3);
constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11);
constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8);
constexpr static GPIOPin Pins[] = { RxPin, TxPin };
}
}
}
#endif

325
ion/src/f730/device.cpp Normal file
View File

@@ -0,0 +1,325 @@
#include "device.h"
#include "regs/regs.h"
extern "C" {
#include <assert.h>
}
#include <ion.h>
#include "led.h"
#include "display.h"
#include "keyboard.h"
#include "battery.h"
#include "sd_card.h"
#include "backlight.h"
#include "console.h"
#include "swd.h"
#include "usb.h"
#include "bench/bench.h"
#include "base64.h"
#define USE_SD_CARD 0
extern "C" {
extern const void * _stack_end;
}
// Public Ion methods
uint32_t Ion::crc32(const uint32_t * data, size_t length) {
bool initialCRCEngineState = RCC.AHB1ENR()->getCRCEN();
RCC.AHB1ENR()->setCRCEN(true);
CRC.CR()->setRESET(true);
const uint32_t * end = data + length;
while (data < end) {
CRC.DR()->set(*data++);
}
uint32_t result = CRC.DR()->get();
RCC.AHB1ENR()->setCRCEN(initialCRCEngineState);
return result;
}
uint32_t Ion::random() {
bool initialRNGEngineState = RCC.AHB2ENR()->getRNGEN();
RCC.AHB2ENR()->setRNGEN(true);
RNG.CR()->setRNGEN(true);
while (RNG.SR()->getDRDY() == 0) {
}
uint32_t result = RNG.DR()->get();
RNG.CR()->setRNGEN(false);
RCC.AHB2ENR()->setRNGEN(initialRNGEngineState);
return result;
}
void Ion::Device::copySerialNumber(char * buffer) {
const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10;
Base64::encode(rawUniqueID, 12, buffer);
buffer[SerialNumberLength] = 0;
}
const char * Ion::serialNumber() {
static char serialNumber[Device::SerialNumberLength + 1] = {0};
if (serialNumber[0] == 0) {
Device::copySerialNumber(serialNumber);
}
return serialNumber;
}
const char * Ion::fccId() {
return "2ALWP-N0100";
}
// Private Ion::Device methods
namespace Ion {
namespace Device {
void initFPU() {
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABDBFBJ.html
CM4.CPACR()->setAccess(10, CM4::CPACR::Access::Full);
CM4.CPACR()->setAccess(11, CM4::CPACR::Access::Full);
// FIXME: The pipeline should be flushed at this point
}
#if 0
void initMPU() {
/* Region 0 reprensents the last 128 bytes of the stack: accessing this
* memory means we are really likely to overflow the stack very soon. */
MPU.RNR()->setREGION(0x00);
MPU.RBAR()->setADDR(&_stack_end);
MPU.RASR()->setSIZE(MPU::RASR::RegionSize::Bytes128);
MPU.RASR()->setENABLE(true);
MPU.RASR()->setAP(0x000); // Forbid access
MPU.CTRL()->setPRIVDEFENA(true);
MPU.CTRL()->setENABLE(true);
}
#endif
void initSysTick() {
// CPU clock is 96 MHz, and systick clock source is divided by 8
// To get 1 ms systick overflow we need to reset it to
// 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0)
CM4.SYST_RVR()->setRELOAD(11999);
CM4.SYST_CVR()->setCURRENT(0);
CM4.SYST_CSR()->setCLKSOURCE(CM4::SYST_CSR::CLKSOURCE::AHB_DIV8);
CM4.SYST_CSR()->setTICKINT(true);
CM4.SYST_CSR()->setENABLE(true);
}
void shutdownSysTick() {
CM4.SYST_CSR()->setENABLE(false);
CM4.SYST_CSR()->setTICKINT(false);
}
void coreReset() {
// Perform a full core reset
CM4.AIRCR()->requestReset();
}
void jumpReset() {
uint32_t * stackPointerAddress = reinterpret_cast<uint32_t *>(0x08000000);
uint32_t * resetHandlerAddress = reinterpret_cast<uint32_t *>(0x08000004);
/* Jump to the reset service routine after having reset the stack pointer.
* Both addresses are fetched from the base of the Flash memory, just like a
* real reset would. These operations should be made at once, otherwise the C
* compiler might emit some instructions that modify the stack inbetween. */
asm volatile (
"msr MSP, %[stackPointer] ; bx %[resetHandler]"
: :
[stackPointer] "r" (*stackPointerAddress),
[resetHandler] "r" (*resetHandlerAddress)
);
}
void init() {
initClocks();
// Ensure right location of interrupt vectors
// The bootloader leaves its own after flashing
SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory);
CM4.VTOR()->setVTOR((void*) 0);
// Put all inputs as Analog Input, No pull-up nor pull-down
// Except for the SWD port (PB3, PA13, PA14)
GPIOA.MODER()->set(0xEBFFFFFF);
GPIOA.PUPDR()->set(0x24000000);
GPIOB.MODER()->set(0xFFFFFFBF);
GPIOB.PUPDR()->set(0x00000000);
for (int g=2; g<5; g++) {
GPIO(g).MODER()->set(0xFFFFFFFF); // All to "Analog"
GPIO(g).PUPDR()->set(0x00000000); // All to "None"
}
#if EPSILON_DEVICE_BENCH
bool consolePeerConnectedOnBoot = Ion::Console::Device::peerConnected();
#endif
initPeripherals();
#if EPSILON_DEVICE_BENCH
if (consolePeerConnectedOnBoot) {
Ion::Device::Bench::run();
}
#endif
}
void shutdown() {
shutdownPeripherals();
shutdownClocks();
}
void initPeripherals() {
Display::Device::init();
Backlight::Device::init();
Keyboard::Device::init();
LED::Device::init();
Battery::Device::init();
USB::Device::init();
#if USE_SD_CARD
SDCard::Device::init();
#endif
Console::Device::init();
SWD::Device::init();
initSysTick();
}
void shutdownPeripherals(bool keepLEDAwake) {
shutdownSysTick();
SWD::Device::shutdown();
Console::Device::shutdown();
#if USE_SD_CARD
SDCard::Device::shutdown();
#endif
USB::Device::shutdown();
Battery::Device::shutdown();
if (!keepLEDAwake) {
LED::Device::shutdown();
}
Keyboard::Device::shutdown();
Backlight::Device::shutdown();
Display::Device::shutdown();
}
void initClocks() {
/* System clock
* Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */
/* After reset the Flash runs as fast as the CPU. When we clock the CPU faster
* the flash memory cannot follow and therefore flash memory accesses need to
* wait a little bit.
* The spec tells us that at 2.8V and over 90MHz the flash expects 3 WS. */
FLASH.ACR()->setLATENCY(3);
/* Enable prefetching flash instructions */
/* Fetching instructions increases slightly the power consumption but the
* increase is negligible compared to the screen consumption. */
FLASH.ACR()->setPRFTEN(true);
/* Set flash instruction and data cache */
FLASH.ACR()->setDCEN(true);
FLASH.ACR()->setICEN(true);
/* After reset, the device is using the high-speed internal oscillator (HSI)
* as a clock source, which runs at a fixed 16 MHz frequency. The HSI is not
* accurate enough for reliable USB operation, so we need to use the external
* high-speed oscillator (HSE). */
// Enable the HSE and wait for it to be ready
RCC.CR()->setHSEON(true);
while(!RCC.CR()->getHSERDY()) {
}
/* Given the crystal used on our device, the HSE will oscillate at 25 MHz. By
* piping it through a phase-locked loop (PLL) we can derive other frequencies
* for use in different parts of the system. Combining the default PLL values
* with a PLLM of 25 and a PLLQ of 4 yields both a 96 MHz frequency for SYSCLK
* and the required 48 MHz USB clock. */
// Configure the PLL ratios and use HSE as a PLL input
RCC.PLLCFGR()->setPLLM(25);
RCC.PLLCFGR()->setPLLQ(4);
RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE);
// 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz
RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy2);
/* If you want to considerably slow down the whole machine uniformely, which
* can be very useful to diagnose performance issues, just uncomment the line
* below. Note that even booting takes a few seconds, so don't be surprised
* if the screen is black for a short while upon booting. */
// RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy128);
// Enable the PLL and wait for it to be ready
RCC.CR()->setPLLON(true);
while(!RCC.CR()->getPLLRDY()) {
}
// Use the PLL output as a SYSCLK source
RCC.CFGR()->setSW(RCC::CFGR::SW::PLL);
while (RCC.CFGR()->getSWS() != RCC::CFGR::SW::PLL) {
}
// Now that we don't need use it anymore, turn the HSI off
RCC.CR()->setHSION(false);
// Peripheral clocks
// AHB1 bus
// Our peripherals are using GPIO A, B, C, D and E.
// We're not using the CRC nor DMA engines.
class RCC::AHB1ENR ahb1enr(0); // Reset value
ahb1enr.setGPIOAEN(true);
ahb1enr.setGPIOBEN(true);
ahb1enr.setGPIOCEN(true);
ahb1enr.setGPIODEN(true);
ahb1enr.setGPIOEEN(true);
ahb1enr.setDMA2EN(true);
RCC.AHB1ENR()->set(ahb1enr);
// AHB2 bus
RCC.AHB2ENR()->setOTGFSEN(true);
// AHB3 bus
RCC.AHB3ENR()->setFSMCEN(true);
// APB1 bus
// We're using TIM3 for the LEDs
RCC.APB1ENR()->setTIM3EN(true);
RCC.APB1ENR()->setPWREN(true);
// APB2 bus
class RCC::APB2ENR apb2enr(0x00008000); // Reset value
apb2enr.setADC1EN(true);
apb2enr.setSYSCFGEN(true);
#if USE_SD_CARD
apb2enr.setSDIOEN(true);
#endif
RCC.APB2ENR()->set(apb2enr);
}
void shutdownClocks(bool keepLEDAwake) {
// APB2 bus
RCC.APB2ENR()->set(0x00008000); // Reset value
// APB1
class RCC::APB1ENR apb1enr(0x00000400); // Reset value
// AHB1 bus
class RCC::AHB1ENR ahb1enr(0); // Reset value
if (keepLEDAwake) {
apb1enr.setTIM3EN(true);
ahb1enr.setGPIOBEN(true);
ahb1enr.setGPIOCEN(true);
}
RCC.APB1ENR()->set(apb1enr);
RCC.AHB1ENR()->set(ahb1enr);
RCC.AHB3ENR()->setFSMCEN(false);
}
}
}

36
ion/src/f730/device.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef ION_DEVICE_H
#define ION_DEVICE_H
namespace Ion {
namespace Device {
void init();
void shutdown();
void initFPU();
#if 0
void initMPU();
#endif
void initSysTick();
void shutdownSysTick();
void coreReset();
void jumpReset();
void initPeripherals();
void shutdownPeripherals(bool keepLEDAwake = false);
void initClocks();
void shutdownClocks(bool keepLEDAwake = false);
/* The serial number is 96 bits long. That's equal to 16 digits in base 64. We
* expose a convenient "copySerialNumber" routine which can be called without
* using a static variable (and therefore without a .bss section). This is used
* in the RAM'ed DFU bootloader. */
constexpr static int SerialNumberLength = 16;
void copySerialNumber(char * buffer);
}
}
#endif

376
ion/src/f730/display.cpp Normal file
View File

@@ -0,0 +1,376 @@
#include <ion.h>
#include "display.h"
#include "regs/regs.h"
extern "C" {
#include <assert.h>
}
/* This driver interfaces with the ST7789V LCD controller.
* This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as
* needed. We use the STM32's FSMC to drive the bus between the ST7789V. Once
* configured, we only need to write in the address space of the MCU to actually
* send some data to the LCD controller. */
#define USE_DMA_FOR_PUSH_PIXELS 0
#define USE_DMA_FOR_PUSH_COLOR 0
#define USE_DMA (USE_DMA_FOR_PUSH_PIXELS|USE_DMA_FOR_PUSH_COLOR)
// Public Ion::Display methods
namespace Ion {
namespace Display {
void pushRect(KDRect r, const KDColor * pixels) {
#if USE_DMA
Device::waitForPendingDMAUploadCompletion();
#endif
Device::setDrawingArea(r, Device::Orientation::Landscape);
Device::pushPixels(pixels, r.width()*r.height());
}
void pushRectUniform(KDRect r, KDColor c) {
#if USE_DMA
Device::waitForPendingDMAUploadCompletion();
#endif
Device::setDrawingArea(r, Device::Orientation::Portrait);
Device::pushColor(c, r.width()*r.height());
}
void pullRect(KDRect r, KDColor * pixels) {
#if USE_DMA
Device::waitForPendingDMAUploadCompletion();
#endif
Device::setDrawingArea(r, Device::Orientation::Landscape);
Device::pullPixels(pixels, r.width()*r.height());
}
void waitForVBlank() {
// We want to return as soon as the TE line is transitionning from "DOWN" to "UP"
while (Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) {
// Loop while high, exit when low
// Wait for zero
}
while (!Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) {
// Loop while low, exit when high
}
}
}
}
// Private Ion::Display::Device methods
namespace Ion {
namespace Display {
namespace Device {
static inline void send_data(uint16_t d) {
*DataAddress = d;
}
static inline uint16_t receive_data() {
return *DataAddress;
}
template<typename... Args>
static inline void send_data(uint16_t d, Args... other) {
send_data(d);
send_data(other...);
}
static inline void send_command(Command c) {
*CommandAddress = c;
}
template<typename... Args>
static inline void send_command(Command c, Args... d) {
send_command(c);
send_data(d...);
}
void init() {
#if USE_DMA
initDMA();
#endif
initGPIO();
initFSMC();
initPanel();
}
void shutdown() {
shutdownPanel();
shutdownFSMC();
shutdownGPIO();
}
#if USE_DMA
void initDMA() {
// Only DMA2 can perform memory-to-memory transfers
//assert(DMAEngine == DMA2);
/* In memory-to-memory transfers, the "peripheral" is the source and the
* "memory" is the destination. In other words, memory is copied from address
* DMA_SxPAR to address DMA_SxM0AR. */
DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory);
DMAEngine.SM0AR(DMAStream)->set((uint32_t)DataAddress);
DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord);
DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord);
DMAEngine.SCR(DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4);
DMAEngine.SCR(DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4);
DMAEngine.SCR(DMAStream)->setMINC(false);
}
void waitForPendingDMAUploadCompletion() {
// Loop until DMA engine available
while (DMAEngine.SCR(DMAStream)->getEN()) {
}
}
static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16_t length) {
// Reset interruption markers
DMAEngine.LIFCR()->set(0xF7D0F7D);
DMAEngine.SNDTR(DMAStream)->set(length);
DMAEngine.SPAR(DMAStream)->set((uint32_t)src);
DMAEngine.SCR(DMAStream)->setPINC(incrementSrc);
DMAEngine.SCR(DMAStream)->setEN(true);
}
#endif
void initGPIO() {
// All the FSMC GPIO pins use the alternate function number 12
for(const GPIOPin & g : FSMCPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12);
}
// Turn on the power
PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Output);
PowerPin.group().ODR()->set(PowerPin.pin(), true);
// Turn on the reset pin
ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Output);
ResetPin.group().ODR()->set(ResetPin.pin(), true);
// Turn on the extended command pin
ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output);
ExtendedCommandPin.group().ODR()->set(ExtendedCommandPin.pin(), true);
// Turn on the Tearing Effect pin
TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Input);
TearingEffectPin.group().PUPDR()->setPull(TearingEffectPin.pin(), GPIO::PUPDR::Pull::None);
Timing::msleep(120);
}
void shutdownGPIO() {
// All the FSMC GPIO pins use the alternate function number 12
for(const GPIOPin & g : FSMCPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
}
ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Analog);
ResetPin.group().PUPDR()->setPull(ResetPin.pin(), GPIO::PUPDR::Pull::None);
PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Analog);
PowerPin.group().PUPDR()->setPull(PowerPin.pin(), GPIO::PUPDR::Pull::None);
ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog);
ExtendedCommandPin.group().PUPDR()->setPull(ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None);
TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog);
}
void initFSMC() {
/* Set up the FSMC control registers.
* We address the LCD panel as if it were an SRAM module, using a 16bits wide
* bus, non-multiplexed.
* The STM32 FSMC supports two kinds of memory access modes :
* - Base modes (1 and 2), which use the same timings for reads and writes
* - Extended modes (named A to D), which can be customized further.
* The LCD panel can be written to faster than it can be read from, therefore
* we want to use one of the extended modes. */
FSMC.BCR(FSMCMemoryBank)->setEXTMOD(true);
FSMC.BCR(FSMCMemoryBank)->setWREN(true);
FSMC.BCR(FSMCMemoryBank)->setMWID(FSMC::BCR::MWID::SIXTEEN_BITS);
FSMC.BCR(FSMCMemoryBank)->setMTYP(FSMC::BCR::MTYP::SRAM);
FSMC.BCR(FSMCMemoryBank)->setMUXEN(false);
FSMC.BCR(FSMCMemoryBank)->setMBKEN(true);
/* We now need to set the actual timings. First, the FSMC and LCD specs don't
* use the same names. Here's the mapping:
*
* FSMC | LCD
* -----+-----
* NOE | RDX
* NWE | WRX
* NE1 | CSX
* A16 | D/CX
* Dn | Dn
*
* We need to set the values of the BTR and BWTR which gives the timings for
* reading and writing. Note that the STM32 datasheet doesn't take into
* account the time needed to actually switch from one logic state to another,
* whereas the ST7789V one does, so we'll add T(R) and T(F) as needed.
* Last but not least, timings on the STM32 have to be expressed in terms of
* HCLK = 1/96MHz = 10.42ns.
* - We'll pick Mode A which corresponds to SRAM with OE toggling
* - ADDSET = T(AST) + T(F) = 0ns + 15ns = 2 HCLK
* - ADDHLD is unused in this mode, set to 0
* - DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK
* DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK
* - BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK
* BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK
*/
// Read timing from the LCD
FSMC.BTR(FSMCMemoryBank)->setADDSET(2);
FSMC.BTR(FSMCMemoryBank)->setADDHLD(0);
FSMC.BTR(FSMCMemoryBank)->setDATAST(36);
FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10);
FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A);
// Write timings for the LCD
FSMC.BWTR(FSMCMemoryBank)->setADDSET(2);
FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0);
FSMC.BWTR(FSMCMemoryBank)->setDATAST(3);
FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3);
FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A);
}
void shutdownFSMC() {
}
void initPanel() {
send_command(Command::Reset);
Timing::msleep(5);
send_command(Command::SleepOut);
Timing::msleep(5);
send_command(Command::PixelFormatSet, 0x05);
send_command(Command::TearingEffectLineOn, 0x00);
send_command(Command::FrameRateControl, 0x1E); // 40 Hz frame rate
send_command(Command::DisplayOn);
}
void shutdownPanel() {
send_command(Command::DisplayOff);
send_command(Command::SleepIn);
Timing::msleep(5);
}
void setDrawingArea(KDRect r, Orientation o) {
uint16_t x_start, x_end, y_start, y_end;
if (o == Orientation::Landscape) {
send_command(Command::MemoryAccessControl, 0xA0);
x_start = r.x();
x_end = r.x() + r.width() - 1;
y_start = r.y();
y_end = r.y() + r.height() - 1;
} else {
send_command(Command::MemoryAccessControl, 0x00);
x_start = r.y();
x_end = r.y() + r.height() - 1;
y_start = Ion::Display::Width - (r.x() + r.width());
y_end = Ion::Display::Width - r.x() - 1;
}
send_command(
Command::ColumnAddressSet,
(x_start >> 8),
(x_start & 0xFF),
(x_end >> 8),
(x_end & 0xFF)
);
send_command(
Command::PageAddressSet,
(y_start >> 8),
(y_start & 0xFF),
(y_end >> 8),
(y_end & 0xFF)
);
}
void pushPixels(const KDColor * pixels, size_t numberOfPixels) {
send_command(Command::MemoryWrite);
/* Theoretically, we should not be able to use DMA here. Indeed, we have no
* guarantee that the content at "pixels" will remain valid once we exit this
* function call. In practice, we might be able to use DMA here because most
* of the time we push pixels from static locations. */
#if USE_DMA_FOR_PUSH_PIXELS
startDMAUpload(pixels, true, numberOfPixels);
#else
while (numberOfPixels > 8) {
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
numberOfPixels -= 8;
}
while (numberOfPixels--) {
send_data(*pixels++);
}
#endif
}
void pushColor(KDColor color, size_t numberOfPixels) {
send_command(Command::MemoryWrite);
#if USE_DMA_FOR_PUSH_COLOR
/* The "color" variable lives on the stack. We cannot take its address because
* it will stop being valid as soon as we return. An easy workaround is to
* duplicate the content in a static variable, whose value is guaranteed to be
* kept until the next pushColor call. */
static KDColor staticColor;
staticColor = color;
startDMAUpload(&staticColor, false, (numberOfPixels > 64000 ? 64000 : numberOfPixels));
#else
while (numberOfPixels--) {
send_data(color);
}
#endif
}
void pullPixels(KDColor * pixels, size_t numberOfPixels) {
if (numberOfPixels == 0) {
return;
}
send_command(Command::PixelFormatSet, 0x06);
send_command(Command::MemoryRead);
receive_data(); // First read is dummy data, per datasheet
while (true) {
if (numberOfPixels == 0) {
break;
}
uint16_t one = receive_data();
uint16_t two = receive_data();
uint16_t firstPixel = (one & 0xF800) | (one & 0xFC) << 3 | (two & 0xF800) >> 11;
*pixels++ = KDColor::RGB16(firstPixel);
numberOfPixels--;
if (numberOfPixels == 0) {
break;
}
uint16_t three = receive_data();
uint16_t secondPixel = (two & 0xF8) << 8 | (three & 0xFC00) >> 5 | (three & 0xF8) >> 3;
*pixels++ = KDColor::RGB16(secondPixel);
numberOfPixels--;
}
send_command(Command::PixelFormatSet, 0x05);
}
}
}
}

110
ion/src/f730/display.h Normal file
View File

@@ -0,0 +1,110 @@
#ifndef ION_DEVICE_DISPLAY_H
#define ION_DEVICE_DISPLAY_H
#include <kandinsky/rect.h>
#include <kandinsky/color.h>
extern "C" {
#include <stddef.h>
}
#include "regs/regs.h"
namespace Ion {
namespace Display {
namespace Device {
/* Pin | Role | Mode | Function | Note
* -----+-------------------+-----------------------+----------|------
* PA2 | LCD D4 | Alternate Function 12 | FSMC_D4 |
* PA3 | LCD D5 | Alternate Function 12 | FSMC_D5 |
* PA4 | LCD D6 | Alternate Function 12 | FSMC_D6 |
* PB12 | LCD D13 | Alternate Function 12 | FSMC_D13 |
* PB14 | LCD power | Output | | LCD controller is powered directly from GPIO
* PD0 | LCD D2 | Alternate Function 12 | FSMC_D2 |
* PD1 | LCD D3 | Alternate Function 12 | FSMC_D3 |
* PD4 | LCD read signal | Alternate Function 12 | FSMC_NOE |
* PD5 | LCD write signal | Alternate Function 12 | FSMC_NWE
* PD7 | LCD chip select | Alternate Function 12 | FSMC_NE1 | Memory bank 1
* PD9 | LCD D14 | Alternate Function 12 | FSMC_D14 |
* PD10 | LCD D15 | Alternate Function 12 | FSMC_D15 |
* PD11 | LCD data/command | Alternate Function 12 | FSMC_A16 | Data/Command is address bit 16
* PD14 | LCD D0 | Alternate Function 12 | FSMC_D0 |
* PD15 | LCD D1 | Alternate Function 12 | FSMC_D1 |
* PE9 | LCD reset | Output | |
* PE10 | LCD D7 | Alternate Function 12 | FSMC_D7 |
* PE11 | LCD D8 | Alternate Function 12 | FSMC_D8 |
* PE12 | LCD D9 | Alternate Function 12 | FSMC_D9 |
* PE13 | LCD D10 | Alternate Function 12 | FSMC_D10 |
* PE14 | LCD D11 | Alternate Function 12 | FSMC_D11 |
* PE15 | LCD D12 | Alternate Function 12 | FSMC_D12 |
*/
void init();
void shutdown();
void initDMA();
void initGPIO();
void shutdownGPIO();
void initFSMC();
void shutdownFSMC();
void initPanel();
void shutdownPanel();
enum class Orientation {
Landscape = 0,
Portrait = 1
};
void setDrawingArea(KDRect r, Orientation o);
void waitForPendingDMAUploadCompletion();
void pushPixels(const KDColor * pixels, size_t numberOfPixels);
void pushColor(KDColor color, size_t numberOfPixels);
void pullPixels(KDColor * pixels, size_t numberOfPixels);
enum class Command : uint16_t {
Nop = 0x00,
Reset = 0x01,
SleepIn = 0x10,
SleepOut = 0x11,
DisplayOff = 0x28,
DisplayOn = 0x29,
ColumnAddressSet = 0x2A,
PageAddressSet = 0x2B,
MemoryWrite = 0x2C,
MemoryRead = 0x2E,
TearingEffectLineOn = 0x35,
MemoryAccessControl = 0x36,
PixelFormatSet = 0x3A,
FrameRateControl = 0xC6
};
constexpr static GPIOPin FSMCPins[] = {
GPIOPin(GPIOA, 2), GPIOPin(GPIOA, 3), GPIOPin(GPIOA, 4), GPIOPin(GPIOB, 12),
GPIOPin(GPIOB, 12), GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4),
GPIOPin(GPIOD, 5), GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10),
GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 10),
GPIOPin(GPIOE, 11), GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14),
GPIOPin(GPIOE, 15)
};
constexpr static GPIOPin PowerPin = GPIOPin(GPIOB, 14);
constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 9);
constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOB, 13);
constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 10);
constexpr static int FSMCMemoryBank = 1;
constexpr static int FSMCDataCommandAddressBit = 16;
constexpr static uint32_t FSMCBaseAddress = 0x60000000;
constexpr static uint32_t FSMCBankAddress = FSMCBaseAddress + (FSMCMemoryBank-1)*0x04000000;
constexpr static DMA DMAEngine = DMA2;
constexpr static int DMAStream = 0;
static volatile Command * const CommandAddress = (Command *)(FSMCBankAddress);
static volatile uint16_t * const DataAddress = (uint16_t *)(FSMCBankAddress | (1<<(FSMCDataCommandAddressBit+1)));
}
}
}
#endif

93
ion/src/f730/events.cpp Normal file
View File

@@ -0,0 +1,93 @@
#include <ion.h>
#include <assert.h>
namespace Ion {
namespace Events {
static bool sleepWithTimeout(int duration, int * timeout) {
if (*timeout >= duration) {
Timing::msleep(duration);
*timeout -= duration;
return false;
} else {
Timing::msleep(*timeout);
*timeout = 0;
return true;
}
}
Event sLastEvent = Events::None;
Keyboard::State sLastKeyboardState;
bool sLastUSBPlugged = false;
bool sLastUSBEnumerated = false;
bool sEventIsRepeating = 0;
constexpr int delayBeforeRepeat = 200;
constexpr int delayBetweenRepeat = 50;
bool canRepeatEvent(Event e) {
return (e == Events::Left || e == Events::Up || e == Events::Down || e == Events::Right || e == Events::Backspace);
}
Event getEvent(int * timeout) {
assert(*timeout > delayBeforeRepeat);
assert(*timeout > delayBetweenRepeat);
int time = 0;
uint64_t keysSeenUp = 0;
uint64_t keysSeenTransitionningFromUpToDown = 0;
while (true) {
// First, check if the USB plugged status has changed
bool usbPlugged = USB::isPlugged();
if (usbPlugged != sLastUSBPlugged) {
sLastUSBPlugged = usbPlugged;
return Events::USBPlug;
}
// Second, check if the USB device has been connected to an USB host
bool usbEnumerated = USB::isEnumerated();
if (usbEnumerated != sLastUSBEnumerated) {
sLastUSBEnumerated = usbEnumerated;
if (usbEnumerated) {
return Events::USBEnumeration;
}
}
Keyboard::State state = Keyboard::scan();
keysSeenUp |= ~state;
keysSeenTransitionningFromUpToDown = keysSeenUp & state;
if (keysSeenTransitionningFromUpToDown != 0) {
sEventIsRepeating = false;
/* The key that triggered the event corresponds to the first non-zero bit
* in "match". This is a rather simple logic operation for the which many
* processors have an instruction (ARM thumb uses CLZ).
* Unfortunately there's no way to express this in standard C, so we have
* to resort to using a builtin function. */
Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown));
Event event(key, isShiftActive(), isAlphaActive());
updateModifiersFromEvent(event);
sLastEvent = event;
sLastKeyboardState = state;
return event;
}
if (sleepWithTimeout(10, timeout)) {
// Timeout occured
return Events::None;
}
time += 10;
// At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero
// In other words, no new key has been pressed
if (canRepeatEvent(sLastEvent) && (state == sLastKeyboardState)) {
int delay = (sEventIsRepeating ? delayBetweenRepeat : delayBeforeRepeat);
if (time >= delay) {
sEventIsRepeating = true;
sLastKeyboardState = state;
return sLastEvent;
}
}
}
}
}
}

233
ion/src/f730/flash.cpp Normal file
View File

@@ -0,0 +1,233 @@
#include "flash.h"
#include <assert.h>
namespace Ion {
namespace Flash {
namespace Device {
static inline void wait() {
// Wait for pending Flash operations to complete
while (FLASH.SR()->getBSY()) {
}
}
static void open() {
// Unlock the Flash configuration register if needed
if (FLASH.CR()->getLOCK()) {
FLASH.KEYR()->set(0x45670123);
FLASH.KEYR()->set(0xCDEF89AB);
}
assert(FLASH.CR()->getLOCK() == false);
// Set the programming parallelism
FLASH.CR()->setPSIZE(MemoryAccessWidth);
}
static void close() {
// Lock the Flash configuration register
assert(!FLASH.CR()->getMER());
assert(!FLASH.CR()->getSER());
assert(!FLASH.CR()->getPG());
FLASH.CR()->setLOCK(true);
// Purge Data and instruction cache
if (FLASH.ACR()->getDCEN()) {
FLASH.ACR()->setDCEN(false);
FLASH.ACR()->setDCRST(true);
FLASH.ACR()->setDCRST(false);
FLASH.ACR()->setDCEN(true);
}
if (FLASH.ACR()->getICEN()) {
FLASH.ACR()->setICEN(false);
FLASH.ACR()->setICRST(true);
FLASH.ACR()->setICRST(false);
FLASH.ACR()->setICEN(true);
}
}
// Compile-time log2
static inline constexpr size_t clog2(size_t input) {
return (input == 1) ? 0 : clog2(input/2)+1;
}
// Align a pointer to a given type's boundaries
// Returns a value that is lower or equal to input
template <typename T>
static inline T * align(void * input) {
size_t k = clog2(sizeof(T));
return reinterpret_cast<T *>(reinterpret_cast<uintptr_t>(input) & ~((1<<k) - 1));
}
template <typename T>
static inline T eat(void * ptr) {
T * pointer = *reinterpret_cast<T **>(ptr);
T result = *pointer;
*reinterpret_cast<T **>(ptr) = pointer+1;
return result;
}
static inline ptrdiff_t byte_offset(void * p1, void * p2) {
return reinterpret_cast<uint8_t *>(p2) - reinterpret_cast<uint8_t *>(p1);
}
template <typename T>
static inline T min(T i, T j) {
return (i<j) ? i : j;
}
static void flash_memcpy(uint8_t * source, uint8_t * destination, size_t length) {
/* RM0402 3.5.4
* It is not allowed to program data to the Flash memory that would cross the
* 128-bit row boundary. In such a case, the write operation is not performed
* and a program alignment error flag (PGAERR) is set in the FLASH_SR
* register.
* The write access type (byte, half-word, word or double word) must
* correspond to the type of parallelism chosen (x8, x16, x32 or x64). If not,
* the write operation is not performed and a program parallelism error flag
* (PGPERR) is set in the FLASH_SR register. */
static_assert(
sizeof(MemoryAccessType) == 1 ||
sizeof(MemoryAccessType) == 2 ||
sizeof(MemoryAccessType) == 4 ||
sizeof(MemoryAccessType) == 8,
"Invalid MemoryAccessType");
/* So we may only perform memory writes with pointers of type MemoryAccessType
* and we must make sure to never cross 128 bit boundaries. This second
* requirement is satisfied iif the pointers are aligned on MemoryAccessType
* boundaries.
* Long story short: we want to perform writes to aligned(MemoryAccessType *).
*/
/* Step 1 - Copy a header if needed
* We start by copying a Header, whose size is MemoryAccessType, to bring us
* back on aligned tracks.
*
* _AlignedDst _DESTINATION
* | |
* --+--------+--------+--------+--------+--------+--------+--
* | || | | | || |
*---+--------+--------+--------+--------+--------+--------+--
* |<------------ Header ------------->|
* |-- HeaderDelta ->|
*/
MemoryAccessType * alignedDestination = align<MemoryAccessType>(destination);
ptrdiff_t headerDelta = byte_offset(alignedDestination, destination);
assert(headerDelta >= 0 && headerDelta < static_cast<ptrdiff_t>(sizeof(MemoryAccessType)));
if (headerDelta > 0) {
// At this point, alignedDestination < destination
// We'll then retrieve the current value at alignedDestination, fill it with
// bytes from source, and write it back at alignedDestination.
// First, retrieve the current value at alignedDestination
MemoryAccessType header = *alignedDestination;
// Then copy headerLength bytes from source and put them in the header
uint8_t * headerStart = reinterpret_cast<uint8_t *>(&header);
// Here's where source data shall start being copied in the header
uint8_t * headerDataStart = headerStart + headerDelta;
// And here's where it should end
uint8_t * headerDataEnd = min<uint8_t *>(
headerStart + sizeof(MemoryAccessType), // Either at the end of the header
headerDataStart + length // or whenever src runs out of data
);
for (uint8_t * h = headerDataStart; h<headerDataEnd; h++) {
*h = eat<uint8_t>(&source);
}
// Then eventually write the header back into the aligned destination
*alignedDestination++ = header;
}
/* Step 2 - Copy the bulk of the data
* At this point, we can use aligned MemoryAccessType pointers. */
MemoryAccessType * lastAlignedDestination = align<MemoryAccessType>(destination + length);
while (alignedDestination < lastAlignedDestination) {
*alignedDestination++ = eat<MemoryAccessType>(&source);
}
/* Step 3 - Copy a footer if needed
* Some unaligned data can be pending at the end. Let's take care of it like
* we did for the header.
*
* _alignedDst _Destination+length
* | |
* --+--------+--------+--------+--------+--------+--------+--
* | || | | | || |
*---+--------+--------+--------+--------+--------+--------+--
* |<------------ Footer ------------->|
* |- footerLength ->|
*/
ptrdiff_t footerLength = byte_offset(alignedDestination, destination + length);
assert(footerLength < static_cast<ptrdiff_t>(sizeof(MemoryAccessType)));
if (footerLength > 0) {
assert(alignedDestination == lastAlignedDestination);
// First, retrieve the current value at alignedDestination
MemoryAccessType footer = *alignedDestination;
/* Then copy footerLength bytes from source and put them at the beginning of
* the footer */
uint8_t * footerPointer = reinterpret_cast<uint8_t *>(&footer);
for (ptrdiff_t i=0; i<footerLength; i++) {
footerPointer[i] = eat<uint8_t>(&source);
}
// Then eventually write the footer back into the aligned destination
*alignedDestination = footer;
}
}
int SectorAtAddress(uint32_t address) {
uint32_t sectorAddresses[NumberOfSectors+1] = {
0x08000000, 0x08004000, 0x08008000, 0x0800C000,
0x08010000, 0x08020000, 0x08040000, 0x08060000,
0x08080000, 0x080A0000, 0x080C0000, 0x080E0000,
0x08100000
};
for (int i=0; i<NumberOfSectors; i++) {
if (address >= sectorAddresses[i] && address < sectorAddresses[i+1]) {
return i;
}
}
return -1;
}
void MassErase() {
open();
FLASH.CR()->setMER(true);
FLASH.CR()->setSTRT(true);
wait();
FLASH.CR()->setMER(false);
close();
}
void EraseSector(int i) {
assert(i >= 0 && i < NumberOfSectors);
open();
FLASH.CR()->setSNB(i);
FLASH.CR()->setSER(true);
FLASH.CR()->setSTRT(true);
wait();
FLASH.CR()->setSNB(0);
FLASH.CR()->setSER(false);
close();
}
void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) {
open();
FLASH.CR()->setPG(true);
flash_memcpy(source, destination, length);
wait();
FLASH.CR()->setPG(false);
close();
}
}
}
}

28
ion/src/f730/flash.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef ION_DEVICE_FLASH_H
#define ION_DEVICE_FLASH_H
#include <stddef.h>
#include "regs/flash.h"
namespace Ion {
namespace Flash {
namespace Device {
void MassErase();
constexpr int NumberOfSectors = 12;
int SectorAtAddress(uint32_t address);
void EraseSector(int i);
void WriteMemory(uint8_t * source, uint8_t * destination, size_t length);
/* The Device is powered by a 2.8V LDO. This allows us to perform writes to the
* Flash 32 bits at once. */
constexpr FLASH::CR::PSIZE MemoryAccessWidth = FLASH::CR::PSIZE::X32;
typedef uint32_t MemoryAccessType;
}
}
}
#endif

112
ion/src/f730/keyboard.cpp Normal file
View File

@@ -0,0 +1,112 @@
/* Keyboard initialization code
*
* The job of this code is to implement the "ion_key_state" function.
*
* The keyboard is a matrix that is laid out as follow:
*
* | PC0 | PC1 | PC2 | PC3 | PC4 | PC5 |
* -----+------+------+------+------+------+------+
* PE0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 |
* -----+------+------+------+------+------+------+
* PE1 | K_B1 | K_B2 | | | | |
* -----+------+------+------+------+------+------+
* PE2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 |
* -----+------+------+------+------+------+------+
* PE3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 |
* -----+------+------+------+------+------+------+
* PE4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 |
* -----+------+------+------+------+------+------+
* PE5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | |
* -----+------+------+------+------+------+------+
* PE6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | |
* -----+------+------+------+------+------+------+
* PE7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | |
* -----+------+------+------+------+------+------+
* PE8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | |
* -----+------+------+------+------+------+------|
*
* We decide to drive the rows (PE0-8) and read the columns (PC0-5).
*
* To avoid short-circuits, the pins E0-E8 will not be standard outputs but
* only open-drain. Open drain means the pin is either driven low or left
* floating.
* When a user presses multiple keys, a connection between two rows can happen.
* If we don't use open drain outputs, this situation could trigger a short
* circuit between an output driving high and another driving low.
*
* If the outputs are open-drain, this means that the input must be pulled up.
* So if the input reads "1", this means the key is in fact *not* pressed, and
* if it reads "0" it means that there's a short to an open-drain output. Which
* means the corresponding key is pressed.
*/
#include "keyboard.h"
// Public Ion::Keyboard methods
namespace Ion {
namespace Keyboard {
State scan() {
uint64_t state = 0;
for (uint8_t i=0; i<Device::numberOfRows; i++) {
Device::activateRow(Device::numberOfRows-1-i);
// TODO: Assert pin numbers are sequentials and dynamically find 8 and 0
uint8_t columns = Device::ColumnGPIO.IDR()->getBitRange(5,0);
/* The key is down if the input is brought low by the output. In other
* words, we want to return true if the input is low (false). So we need to
* append 6 bits of (not columns) to state. */
state = (state << 6) | (~columns & 0x3F);
}
/* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not
* defined. Therefore we want to make sure those bits are forced to zero in
* whatever value we return. */
state = state & 0x1F7DF7FFFFF0FF;
return State(state);
}
}
}
// Private Ion::Keyboard::Device methods
namespace Ion {
namespace Keyboard {
namespace Device {
void init() {
for (uint8_t i=0; i<numberOfRows; i++) {
uint8_t pin = RowPins[i];
RowGPIO.MODER()->setMode(pin, GPIO::MODER::Mode::Output);
RowGPIO.OTYPER()->setType(pin, GPIO::OTYPER::Type::OpenDrain);
}
for (uint8_t i=0; i<numberOfColumns; i++) {
uint8_t pin = ColumnPins[i];
ColumnGPIO.MODER()->setMode(pin, GPIO::MODER::Mode::Input);
ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::Up);
}
}
void shutdown() {
for (uint8_t i=0; i<numberOfRows; i++) {
uint8_t pin = RowPins[i];
RowGPIO.MODER()->setMode(pin, GPIO::MODER::Mode::Analog);
RowGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None);
}
for (uint8_t i=0; i<numberOfColumns; i++) {
uint8_t pin = ColumnPins[i];
ColumnGPIO.MODER()->setMode(pin, GPIO::MODER::Mode::Analog);
ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None);
}
}
}
}
}

70
ion/src/f730/keyboard.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef ION_DEVICE_KEYBOARD_H
#define ION_DEVICE_KEYBOARD_H
#include <ion/keyboard.h>
#include <ion.h>
#include "regs/regs.h"
namespace Ion {
namespace Keyboard {
namespace Device {
/* Pin | Role | Mode
* -----+-------------------+--------------------
* PC0 | Keyboard column 1 | Input, pulled up
* PC1 | Keyboard column 2 | Input, pulled up
* PC2 | Keyboard column 3 | Input, pulled up
* PC3 | Keyboard column 4 | Input, pulled up
* PC4 | Keyboard column 5 | Input, pulled up
* PC5 | Keyboard column 6 | Input, pulled up
* PE0 | Keyboard row A | Output, open drain
* PE1 | Keyboard row B | Output, open drain
* PE2 | Keyboard row C | Output, open drain
* PE3 | Keyboard row D | Output, open drain
* PE4 | Keyboard row E | Output, open drain
* PE5 | Keyboard row F | Output, open drain
* PE6 | Keyboard row G | Output, open drain
* PE7 | Keyboard row H | Output, open drain
* PE8 | Keyboard row I | Output, open drain
*/
void init();
void shutdown();
constexpr GPIO RowGPIO = GPIOE;
constexpr uint8_t numberOfRows = 9;
constexpr uint8_t RowPins[numberOfRows] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
constexpr GPIO ColumnGPIO = GPIOC;
constexpr uint8_t numberOfColumns = 6;
constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5};
inline uint8_t rowForKey(Key key) {
return (int)key/numberOfColumns;
}
inline uint8_t columnForKey(Key key) {
return (int)key%numberOfColumns;
}
inline void activateRow(uint8_t row) {
/* In open-drain mode, a 0 in the register drives the pin low, and a 1 lets
* the pin floating (Hi-Z). So we want to set the current row to zero and all
* the others to 1. */
uint16_t rowState = ~(1<<row);
// TODO: Assert pin numbers are sequentials and dynamically find 9 and 0
Device::RowGPIO.ODR()->setBitRange(9, 0, rowState);
// TODO: 100 us seems to work, but wasn't really calculated
Timing::usleep(100);
}
inline bool columnIsActive(uint8_t column) {
return !(Device::ColumnGPIO.IDR()->getBitRange(column,column));
}
}
}
}
#endif

133
ion/src/f730/led.cpp Normal file
View File

@@ -0,0 +1,133 @@
#include <ion/led.h>
#include <ion/display.h>
#include "device.h"
#include "led.h"
#include "regs/regs.h"
// Public Ion::LED methods
static KDColor sLedColor = KDColorBlack;
KDColor Ion::LED::getColor() {
return sLedColor;
}
void Ion::LED::setColor(KDColor c) {
sLedColor = c;
/* Active all RGB colors */
TIM3.CCMR()->setOC2M(TIM<Register16>::CCMR::OCM::PWM1);
TIM3.CCMR()->setOC4M(TIM<Register16>::CCMR::OCM::PWM1);
TIM3.CCMR()->setOC3M(TIM<Register16>::CCMR::OCM::PWM1);
/* Set the PWM duty cycles to display the right color */
constexpr float maxColorValue = (float)((1 << 8) -1);
Device::setPeriodAndDutyCycles(Device::Mode::PWM, c.red()/maxColorValue, c.green()/maxColorValue, c.blue()/maxColorValue);
}
void Ion::LED::setBlinking(uint16_t period, float dutyCycle) {
/* We want to use the PWM at a slow rate to display a seeable blink.
* Consequently, we do not use PWM to display the right color anymore but to
* blink. We cannot use the PWM to display the exact color so we 'project the
* color on 3 bits' : all colors have 2 states - active or not. */
TIM3.CCMR()->setOC2M(sLedColor.red() > 0 ? TIM<Register16>::CCMR::OCM::PWM1 : TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC4M(sLedColor.green() > 0 ? TIM<Register16>::CCMR::OCM::PWM1 : TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM<Register16>::CCMR::OCM::PWM1 : TIM<Register16>::CCMR::OCM::ForceInactive);
Device::setPeriodAndDutyCycles(Device::Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period);
}
// Private Ion::Device::LED methods
namespace Ion {
namespace LED {
namespace Device {
void init() {
initGPIO();
initTimer();
}
void shutdown() {
shutdownTimer();
shutdownGPIO();
}
void initGPIO() {
/* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer,
* which is an alternate function. More precisely, we will use AF2, which maps
* PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */
for(const GPIOPin & g : RGBPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2);
}
}
void shutdownGPIO() {
for(const GPIOPin & g : RGBPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
}
}
void initTimer() {
// Output preload enable for channels 2-4
TIM3.CCMR()->setOC2PE(true);
TIM3.CCMR()->setOC3PE(true);
TIM3.CCMR()->setOC4PE(true);
// Auto-reload preload enable
TIM3.CR1()->setARPE(true);
// Enable Capture/Compare for channel 2 to 4
TIM3.CCER()->setCC2E(true);
TIM3.CCER()->setCC3E(true);
TIM3.CCER()->setCC4E(true);
TIM3.BDTR()->setMOE(true);
TIM3.CR1()->setCEN(true);
}
void shutdownTimer() {
TIM3.CCMR()->setOC2M(TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC4M(TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC3M(TIM<Register16>::CCMR::OCM::ForceInactive);
}
/* Pulse width modulation mode allows you to generate a signal with a
* frequency determined by the value of the TIMx_ARR register and a duty cycle
* determined by the value of the TIMx_CCRx register. */
void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period) {
switch (mode) {
case Mode::PWM:
/* Let's set the prescaler to 1. Increasing the prescaler would slow down
* the modulation, which can be useful when debugging or when we want an
* actual blinking. */
TIM3.PSC()->set(1);
TIM3.ARR()->set(PWMPeriod);
period = PWMPeriod;
break;
case Mode::Blink:
int systemClockFreq = 96;
/* We still want to do PWM, but at a rate slow enough to blink. Ideally,
* we want to pre-scale the period to be able to set it in milliseconds;
* however, as the prescaler is cap by 2^16-1, we divide it by a factor
* and correct the period consequently. */
int factor = 2;
// TODO: explain the 2 ?
TIM3.PSC()->set(systemClockFreq*1000/factor);
period *= factor;
TIM3.ARR()->set(period);
break;
}
TIM3.CCR2()->set(dutyCycleRed*period);
TIM3.CCR3()->set(dutyCycleBlue*period);
TIM3.CCR4()->set(dutyCycleGreen*period);
}
}
}
}

48
ion/src/f730/led.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef ION_DEVICE_LED_H
#define ION_DEVICE_LED_H
#include "regs/regs.h"
namespace Ion {
namespace LED {
namespace Device {
/* Pin | Role | Mode | Function
* -----+-------------------+-----------------------+----------
* PB0 | LED blue | Alternate Function 2 | TIM3_CH3
* PB1 | LED green | Alternate Function 2 | TIM3_CH4
* PC7 | LED red | Alternate Function 2 | TIM3_CH2
*/
enum class Mode {
PWM,
Blink
};
enum class Color {
Red,
Green,
Blue
};
void init();
void shutdown();
void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period = 0);
void initGPIO();
void shutdownGPIO();
void initTimer();
void shutdownTimer();
constexpr static GPIOPin RGBPins[] = {
GPIOPin(GPIOC, 7), GPIOPin(GPIOB, 1), GPIOPin(GPIOB, 0)
};
constexpr uint16_t PWMPeriod = 40000;
}
}
}
#endif

15
ion/src/f730/log.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include <ion/log.h>
#include "regs/itm.h"
// We're printing using SWO.
// This is achieved by writing to the ITM register, which is sent through the
// Cortex Debug bus
void ion_log_string(const char * message) {
char character = 0;
while ((character = *message++) != 0) {
if (ITM.TER()->get(0)) {
ITM.STIM(0)->set(character);
}
}
}

69
ion/src/f730/power.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include <ion.h>
#include "battery.h"
#include "device.h"
#include "display.h"
#include "keyboard.h"
#include "led.h"
#include "usb.h"
#include "wakeup.h"
#include "regs/regs.h"
void Ion::Power::suspend(bool checkIfPowerKeyReleased) {
bool isLEDActive = Ion::LED::getColor() != KDColorBlack;
if (checkIfPowerKeyReleased) {
/* Wait until power is released to avoid restarting just after suspending */
bool isPowerDown = true;
while (isPowerDown) {
Keyboard::State scan = Keyboard::scan();
isPowerDown = scan.keyDown(Keyboard::Key::B2);
}
}
Device::shutdownPeripherals(isLEDActive);
PWR.CR()->setLPDS(true); // Turn the regulator off. Takes longer to wake up.
PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up.
CM4.SCR()->setSLEEPDEEP(!isLEDActive);
while (1) {
#if EPSILON_LED_WHILE_CHARGING
/* Update LEDS
* if the standby mode was stopped due to a "stop charging" event, we wait
* a while to be sure that the plug state of the USB is up-to-date. */
msleep(200);
//Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging());
#endif
WakeUp::Device::onPowerKeyDown();
WakeUp::Device::onUSBPlugging();
#if EPSILON_LED_WHILE_CHARGING
WakeUp::Device::onChargingEvent();
#endif
Device::shutdownClocks(isLEDActive);
/* To enter sleep, we need to issue a WFE instruction, which waits for the
* event flag to be set and then clears it. However, the event flag might
* already be on. So the safest way to make sure we actually wait for a new
* event is to force the event flag to on (SEV instruction), use a first WFE
* to clear it, and then a second WFE to wait for a _new_ event. */
asm("sev");
asm("wfe");
asm("nop");
asm("wfe");
Device::initClocks();
Keyboard::Device::init();
Keyboard::State scan = Keyboard::scan();
Keyboard::Device::shutdown();
Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::Key::B2);
if (scan == OnlyPowerKeyDown || USB::isPlugged()) {
// Wake up
break;
}
}
Device::initClocks();
Device::initPeripherals();
}

73
ion/src/f730/regs/adc.h Normal file
View File

@@ -0,0 +1,73 @@
#ifndef REGS_ADC_H
#define REGS_ADC_H
#include "register.h"
class ADC {
public:
class SR : Register32 {
public:
REGS_BOOL_FIELD(EOC, 1);
};
class CR1 : Register32 {
public:
};
class CR2 : Register32 {
public:
REGS_BOOL_FIELD(ADON, 0);
REGS_BOOL_FIELD(SWSTART, 30);
};
class SMPR : Register64 {
/* The SMPR register doesn't exist per-se in the documentation. It is the
* consolidation of SMPR1 and SMPR2 which are two 32-bit registers. */
public:
enum class SamplingTime {
Cycles3 = 0,
Cycles15 = 1,
Cycles28 = 2,
Cycles56 = 3,
Cycles84 = 4,
Cycles112 = 5,
Cycles144 = 6,
Cycles480 = 7
};
SamplingTime getSamplingTime(int channel) {
return (SamplingTime)getBitRange(3*channel+2, 3*channel);
}
void setSamplingTime(int channel, SamplingTime t) volatile {
setBitRange(3*channel+2, 3*channel, (uint64_t)t);
}
};
class SQR1 : Register32 {
public:
REGS_FIELD(L, uint8_t, 23, 20);
};
class SQR3 : Register32 {
public:
REGS_FIELD(SQ1, uint8_t, 4, 0);
};
class DR : public Register16 {
};
constexpr ADC() {};
REGS_REGISTER_AT(SR, 0x0);
REGS_REGISTER_AT(CR2, 0x08);
REGS_REGISTER_AT(SMPR, 0x0C);
REGS_REGISTER_AT(SQR1, 0x2C);
REGS_REGISTER_AT(SQR3, 0x34);
REGS_REGISTER_AT(DR, 0x4C);
private:
constexpr uint32_t Base() const {
return 0x40012000;
};
};
constexpr ADC ADC;
#endif

81
ion/src/f730/regs/cm4.h Normal file
View File

@@ -0,0 +1,81 @@
#ifndef REGS_CM4_H
#define REGS_CM4_H
#include "register.h"
class CM4 {
public:
// Vector table offset register
// http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf
class VTOR : Register32 {
public:
void setVTOR(void *address) volatile { setBitRange(29, 9, (uint32_t)address); }
};
// Coprocessor Access Control Register
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BEHBJHIG.html
class CPACR : public Register32 {
public:
enum class Access {
Denied = 0,
PrivilegedOnly = 1,
Full = 3
};
void setAccess(int index, Access a) volatile { setBitRange(2*index+1, 2*index, (uint32_t)a); }
};
// Application Interrupt and Reset Control Register
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihehdge.html
class AIRCR : public Register32 {
public:
void requestReset() volatile {
set(0x5FA<<16 |(1<<2));
}
};
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Cihhjgdh.html
class SCR : public Register32 {
public:
REGS_BOOL_FIELD(SLEEPDEEP, 2);
};
class SYST_CSR : public Register32 {
public:
enum class CLKSOURCE : uint8_t {
AHB_DIV8 = 0,
AHB = 1
};
REGS_BOOL_FIELD(COUNTFLAG, 16);
REGS_TYPE_FIELD(CLKSOURCE, 2, 2);
REGS_BOOL_FIELD(TICKINT, 1);
REGS_BOOL_FIELD(ENABLE, 0);
};
class SYST_RVR : public Register32 {
public:
REGS_FIELD(RELOAD, uint32_t, 23, 0);
};
class SYST_CVR : public Register32 {
public:
REGS_FIELD(CURRENT, uint32_t, 23, 0);
};
constexpr CM4() {};
REGS_REGISTER_AT(SYST_CSR, 0x10);
REGS_REGISTER_AT(SYST_RVR, 0x14);
REGS_REGISTER_AT(SYST_CVR, 0x18);
REGS_REGISTER_AT(VTOR, 0xD08);
REGS_REGISTER_AT(AIRCR, 0xD0C);
REGS_REGISTER_AT(SCR, 0xD10);
REGS_REGISTER_AT(CPACR, 0xD88);
private:
constexpr uint32_t Base() const {
return 0xE000E000;
}
};
constexpr CM4 CM4;
#endif

27
ion/src/f730/regs/crc.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef REGS_CRC_H
#define REGS_CRC_H
#include "register.h"
class CRC {
public:
class DR : public Register32 {
};
class CR : Register32 {
public:
REGS_BOOL_FIELD(RESET, 0);
};
constexpr CRC() {};
REGS_REGISTER_AT(DR, 0x00);
REGS_REGISTER_AT(CR, 0x08);
private:
constexpr uint32_t Base() const {
return 0x40023000;
};
};
constexpr CRC CRC;
#endif

63
ion/src/f730/regs/dma.h Normal file
View File

@@ -0,0 +1,63 @@
#ifndef REGS_DMA_H
#define REGS_DMA_H
#include "register.h"
class DMA {
public:
class LIFCR : public Register32 {
};
class SCR : Register32 {
public:
enum class Burst : uint8_t {
Single = 0,
Incremental4 = 1,
Incremental8 = 2,
Incremental16 = 3
};
enum class DataSize : uint8_t {
Byte = 0,
HalfWord = 1,
Word = 2
};
enum class Direction : uint8_t {
PeripheralToMemory = 0,
MemoryToPeripheral = 1,
MemoryToMemory = 2
};
REGS_FIELD(CHSEL, uint8_t, 27, 25);
REGS_FIELD(MBURST, Burst, 24, 23);
REGS_FIELD(PBURST, Burst, 22, 21);
REGS_FIELD(MSIZE, DataSize, 14, 13);
REGS_FIELD(PSIZE, DataSize, 12, 11);
REGS_BOOL_FIELD(MINC, 10);
REGS_BOOL_FIELD(PINC, 9);
REGS_BOOL_FIELD(CIRC, 8);
REGS_FIELD(DIR, Direction, 7, 6);
REGS_BOOL_FIELD(EN, 0);
};
class SNDTR : public Register32 {
};
class SPAR : public Register32 {
};
class SM0AR : public Register32 {
};
constexpr DMA(int i) : m_index(i) {}
//constexpr operator int() const { return m_index; }
REGS_REGISTER_AT(LIFCR, 0x08);
volatile SCR * SCR(int i ) const { return (class SCR *)(Base() + 0x10 + 0x18*i); };
volatile SNDTR * SNDTR(int i ) const { return (class SNDTR *)(Base() + 0x14 + 0x18*i); };
volatile SPAR * SPAR(int i ) const { return (class SPAR *)(Base() + 0x18 + 0x18*i); };
volatile SM0AR * SM0AR(int i ) const { return (class SM0AR *)(Base() + 0x1C + 0x18*i); };
private:
constexpr uint32_t Base() const {
return 0x40026000 + 0x400*m_index;
};
int m_index;
};
constexpr DMA DMA1(0);
constexpr DMA DMA2(1);
#endif

34
ion/src/f730/regs/exti.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef REGS_EXTI_H
#define REGS_EXTI_H
#include "register.h"
class EXTI {
public:
class MaskRegister : Register32 {
public:
bool get(int index) { return (bool)getBitRange(index, index); }
void set(int index, bool state) volatile { setBitRange(index, index, state); }
};
class IMR : public MaskRegister { };
class EMR : public MaskRegister { };
class RTSR : public MaskRegister { };
class FTSR : public MaskRegister { };
class PR : public MaskRegister { };
constexpr EXTI() {};
REGS_REGISTER_AT(IMR, 0x00);
REGS_REGISTER_AT(EMR, 0x04);
REGS_REGISTER_AT(RTSR, 0x08);
REGS_REGISTER_AT(FTSR, 0x0C);
REGS_REGISTER_AT(PR, 0x14);
private:
constexpr uint32_t Base() const {
return 0x40013C00;
}
};
constexpr EXTI EXTI;
#endif

56
ion/src/f730/regs/flash.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef REGS_FLASH_H
#define REGS_FLASH_H
#include "register.h"
class FLASH {
public:
class ACR : public Register32 {
public:
REGS_FIELD(LATENCY, uint8_t, 3, 0);
REGS_BOOL_FIELD(PRFTEN, 8);
REGS_BOOL_FIELD(ICEN, 9);
REGS_BOOL_FIELD(DCEN, 10);
REGS_BOOL_FIELD(ICRST, 11);
REGS_BOOL_FIELD(DCRST, 12);
};
class KEYR : public Register32 {
};
class CR : public Register32 {
public:
enum class PSIZE : uint8_t {
X8 = 0,
X16 = 1,
X32 = 2,
X64 = 3
};
REGS_BOOL_FIELD(PG, 0);
REGS_BOOL_FIELD(SER, 1);
REGS_BOOL_FIELD(MER, 2);
REGS_FIELD(SNB, uint8_t, 6, 3);
REGS_TYPE_FIELD(PSIZE, 9, 8);
REGS_BOOL_FIELD(STRT, 16);
REGS_BOOL_FIELD(LOCK, 31);
};
class SR : public Register32 {
public:
REGS_BOOL_FIELD(BSY, 16);
};
constexpr FLASH() {};
REGS_REGISTER_AT(ACR, 0x00);
REGS_REGISTER_AT(KEYR, 0x04);
REGS_REGISTER_AT(SR, 0x0C);
REGS_REGISTER_AT(CR, 0x10);
private:
constexpr uint32_t Base() const {
return 0x40023C00;
}
};
constexpr FLASH FLASH;
#endif

79
ion/src/f730/regs/fsmc.h Normal file
View File

@@ -0,0 +1,79 @@
#ifndef REGS_FSMC_H
#define REGS_FSMC_H
#include "register.h"
class FSMC {
public:
class BCR : Register32 {
public:
enum class MTYP : uint8_t {
SRAM = 0,
PSRAM = 1,
NOR = 2
};
enum class MWID : uint8_t {
EIGHT_BITS = 0,
SIXTEEN_BITS = 1
};
REGS_BOOL_FIELD(MBKEN, 0);
REGS_BOOL_FIELD(MUXEN, 1);
REGS_TYPE_FIELD(MTYP, 3, 2);
REGS_TYPE_FIELD(MWID, 5, 4);
REGS_BOOL_FIELD(WREN, 12);
REGS_BOOL_FIELD(EXTMOD, 14);
};
class BTR : Register32 {
public:
enum class ACCMOD : uint8_t {
A = 0,
B = 1,
C = 2,
D = 3
};
REGS_FIELD(ADDSET, uint8_t, 3, 0);
REGS_FIELD(ADDHLD, uint8_t, 7, 4);
REGS_FIELD(DATAST, uint8_t, 15, 8);
REGS_FIELD(BUSTURN, uint8_t, 19, 16);
REGS_FIELD(CLKDIV, uint8_t, 23, 20);
REGS_FIELD(DATLAT, uint8_t, 27, 24);
REGS_TYPE_FIELD(ACCMOD, 29, 28);
};
class BWTR : Register32 {
public:
enum class ACCMOD : uint8_t {
A = 0,
B = 1,
C = 2,
D = 3
};
REGS_FIELD(ADDSET, uint8_t, 3, 0);
REGS_FIELD(ADDHLD, uint8_t, 7, 4);
REGS_FIELD(DATAST, uint8_t, 15, 8);
REGS_FIELD(BUSTURN, uint8_t, 19, 16);
REGS_FIELD(CLKDIV, uint8_t, 23, 20);
REGS_FIELD(DATLAT, uint8_t, 27, 24);
REGS_TYPE_FIELD(ACCMOD, 29, 28);
};
constexpr FSMC() {}
volatile BCR * BCR(int index) const {
return (class BCR *)(Base() + 8*(index-1));
}
volatile BTR * BTR(int index) const {
return (class BTR *)(Base() + 4 + 8*(index-1));
}
volatile BWTR * BWTR(int index) const {
return (class BWTR *)(Base() + 0x104 + 8*(index-1));
}
private:
constexpr uint32_t Base() const {
return 0xA0000000;
};
};
constexpr FSMC FSMC;
#endif

104
ion/src/f730/regs/gpio.h Normal file
View File

@@ -0,0 +1,104 @@
#ifndef REGS_GPIO_H
#define REGS_GPIO_H
#include "register.h"
class GPIO {
public:
class MODER : public Register32 {
public:
enum class Mode {
Input = 0,
Output = 1,
AlternateFunction = 2,
Analog = 3
};
Mode getMode(int index) { return (Mode)getBitRange(2*index+1, 2*index); }
void setMode(int index, Mode mode) volatile { setBitRange(2*index+1, 2*index, (uint32_t)mode); }
};
class OTYPER : Register32 {
public:
enum class Type {
PushPull = 0,
OpenDrain = 1
};
Type getType(int index) { return (Type)getBitRange(index, index); }
void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); }
};
class PUPDR : public Register32 {
public:
enum class Pull {
None = 0,
Up = 1,
Down = 2,
Reserved = 3
};
Pull getPull(int index) { return (Pull)getBitRange(2*index+1, 2*index); }
void setPull(int index, Pull pull) volatile { setBitRange(2*index+1, 2*index, (uint32_t)pull); }
};
class IDR : public Register32 {
public:
bool get(int index) volatile { return (bool)getBitRange(index, index); }
};
class ODR : public Register32 {
public:
bool get(int index) volatile { return (bool)getBitRange(index, index); }
void set(int index, bool state) volatile { setBitRange(index, index, state); }
};
class AFR : Register64 {
/* The AFR register doesn't exist per-se in the documentation. It is the
* consolidation of AFRL and AFRH which are two 32 bits registers. */
public:
enum class AlternateFunction {
AF0 = 0, AF1 = 1, AF2 = 2, AF3 = 3,
AF4 = 4, AF5 = 5, AF6 = 6, AF7 = 7,
AF8 = 8, AF9 = 9, AF10 = 10, AF11 = 11,
AF12 = 12, AF13 = 13, AF14 = 14, AF15 = 15
};
AlternateFunction getAlternateFunction(int index) {
return (AlternateFunction)getBitRange(4*index+3, 4*index);
}
void setAlternateFunction(int index, AlternateFunction af) volatile {
setBitRange(4*index+3, 4*index, (uint64_t)af);
}
};
constexpr GPIO(int i) : m_index(i) {}
constexpr operator int() const { return m_index; }
REGS_REGISTER_AT(MODER, 0x00);
REGS_REGISTER_AT(OTYPER, 0x04);
REGS_REGISTER_AT(PUPDR, 0x0C);
REGS_REGISTER_AT(IDR, 0x10);
REGS_REGISTER_AT(ODR, 0x14);
REGS_REGISTER_AT(AFR, 0x20);
private:
constexpr uint32_t Base() const {
return 0x40020000 + 0x400*m_index;
};
int m_index;
};
constexpr GPIO GPIOA(0);
constexpr GPIO GPIOB(1);
constexpr GPIO GPIOC(2);
constexpr GPIO GPIOD(3);
constexpr GPIO GPIOE(4);
constexpr GPIO GPIOF(5);
constexpr GPIO GPIOG(6);
constexpr GPIO GPIOH(7);
class GPIOPin {
public:
constexpr GPIOPin(GPIO group, uint8_t pin) : m_data(((group&0xF) << 4) | (pin&0xF)) {}
GPIO group() const { return GPIO(m_data>>4); }
uint8_t pin() const { return m_data & 0xF; }
private:
uint8_t m_data;
};
#endif

32
ion/src/f730/regs/itm.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef REGS_ITM_H
#define REGS_ITM_H
#include "register.h"
// See ARM Cortex M4 TRM
class ITM {
public:
class STIM : public Register8 {
};
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0314h/Chdbicac.html
class TER : Register32 {
public:
bool get(int index) volatile { return (bool)getBitRange(index, index); }
};
constexpr ITM() {};
volatile STIM * STIM(int i) const {
return (class STIM *)(Base() + 4*i);
};
REGS_REGISTER_AT(TER, 0xE00);
private:
constexpr uint32_t Base() const {
return 0xE0000000;
}
};
constexpr ITM ITM;
#endif

71
ion/src/f730/regs/mpu.h Normal file
View File

@@ -0,0 +1,71 @@
#ifndef REGS_MPU_H
#define REGS_MPU_H
#include "register.h"
class MPU {
public:
class TYPER : Register32 {
public:
REGS_FIELD_R(IREGION, uint8_t, 23, 16);
REGS_FIELD_R(DREGION, uint8_t, 15, 8);
REGS_BOOL_FIELD_R(SEPARATE, 0);
};
class CTRL : Register32 {
public:
REGS_BOOL_FIELD(PRIVDEFENA, 2);
REGS_BOOL_FIELD(HFNMIENA, 1);
REGS_BOOL_FIELD(ENABLE, 0);
};
class RNR : Register32 {
public:
REGS_FIELD(REGION, uint8_t, 7, 0);
};
class RBAR : Register32 {
public:
void setADDR(void * address) volatile { assert(((uint32_t)address & 0b11111) == 0); setBitRange(31, 5, (uint32_t)address >> 5); }
REGS_BOOL_FIELD(VALID, 4);
REGS_FIELD(REGION, uint8_t, 3, 0);
};
class RASR : Register32 {
public:
REGS_BOOL_FIELD(XN, 28);
REGS_FIELD(AP, uint8_t, 26, 24);
REGS_FIELD(TEX, uint8_t, 21, 19);
REGS_BOOL_FIELD(S, 18);
REGS_BOOL_FIELD(C, 17);
REGS_BOOL_FIELD(B, 16);
REGS_FIELD(SRD, uint8_t, 15, 8);
enum class RegionSize : uint8_t {
Bytes32 = 0b00100,
Bytes64 = 0b00101,
Bytes128 = 0b00110,
KyloBytes1 = 0b01001,
MegaBytes1 = 0b10011,
GigaBytes1 = 0b11101,
GigaBytes4 = 0b11111
};
REGS_FIELD(SIZE, RegionSize, 5, 1);
REGS_BOOL_FIELD(ENABLE, 0);
};
constexpr MPU() {};
REGS_REGISTER_AT(TYPER, 0x0);
REGS_REGISTER_AT(CTRL, 0x04);
REGS_REGISTER_AT(RNR, 0x08);
REGS_REGISTER_AT(RBAR, 0x0C);
REGS_REGISTER_AT(RASR, 0x10);
private:
constexpr uint32_t Base() const {
return 0xE000ED90;
};
};
constexpr MPU MPU;
#endif

37
ion/src/f730/regs/nvic.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef REGS_NVIC_H
#define REGS_NVIC_H
#include "register.h"
// http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf
class NVIC {
public:
class MaskRegister : Register32 {
public:
bool get(int index) { return (bool)getBitRange(index, index); }
void set(int index, bool state) volatile { setBitRange(index, index, state); }
};
class NVIC_ISER0 : public MaskRegister { };
class NVIC_ISER1 : public MaskRegister { };
class NVIC_ISER2 : public MaskRegister { };
class NVIC_ICER0 : public MaskRegister { };
class NVIC_ICER1 : public MaskRegister { };
class NVIC_ICER2 : public MaskRegister { };
constexpr NVIC() {};
REGS_REGISTER_AT(NVIC_ISER0, 0x00);
REGS_REGISTER_AT(NVIC_ISER1, 0x04);
REGS_REGISTER_AT(NVIC_ISER2, 0x08);
REGS_REGISTER_AT(NVIC_ICER0, 0x80);
REGS_REGISTER_AT(NVIC_ICER1, 0x84);
REGS_REGISTER_AT(NVIC_ICER2, 0x88);
private:
constexpr uint32_t Base() const {
return 0xE000E100;
}
};
constexpr NVIC NVIC;
#endif

195
ion/src/f730/regs/otg.h Normal file
View File

@@ -0,0 +1,195 @@
#ifndef REGS_OTG_H
#define REGS_OTG_H
#include "register.h"
class OTG {
public:
class GAHBCFG : public Register32 {
public:
REGS_BOOL_FIELD(GINTMSK, 0);
};
class GUSBCFG : public Register32 {
public:
REGS_BOOL_FIELD(PHYSEL, 6);
REGS_FIELD(TRDT, uint8_t, 13, 10);
REGS_BOOL_FIELD(FDMOD, 30);
};
class GRSTCTL : public Register32 {
public:
REGS_BOOL_FIELD(CSRST, 0);
REGS_BOOL_FIELD(RXFFLSH, 4);
REGS_BOOL_FIELD(TXFFLSH, 5);
REGS_FIELD(TXFNUM, uint8_t, 10, 6);
REGS_BOOL_FIELD(AHBIDL, 31);
};
class GINTSTS : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD(MMIS, 1);
REGS_BOOL_FIELD(SOF, 3);
REGS_BOOL_FIELD(RXFLVL, 4);
REGS_BOOL_FIELD(USBSUSP, 11);
REGS_BOOL_FIELD(USBRST, 12);
REGS_BOOL_FIELD(ENUMDNE, 13);
REGS_BOOL_FIELD(IEPINT, 18);
REGS_BOOL_FIELD(WKUPINT, 31);
};
class GINTMSK : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD(RXFLVLM, 4);
REGS_BOOL_FIELD(USBSUSPM, 11);
REGS_BOOL_FIELD(USBRST, 12);
REGS_BOOL_FIELD(ENUMDNEM, 13);
REGS_BOOL_FIELD(IEPINT, 18);
REGS_BOOL_FIELD(WUIM, 31);
};
class GRXSTSP : public Register32 {
public:
using Register32::Register32;
enum class PKTSTS {
GlobalOutNAK = 1,
OutReceived = 2,
OutTransferCompleted = 3, // After each Out Transaction
SetupTransactionCompleted = 4, // Supposed to be after each SETUP transaction
SetupReceived = 6
};
REGS_FIELD(EPNUM, uint8_t, 3, 0);
REGS_FIELD(BCNT, uint16_t, 14, 4);
PKTSTS getPKTSTS() volatile { return (PKTSTS)getBitRange(20, 17); }
};
class GRXFSIZ : public Register32 {
public:
REGS_FIELD(RXFD, uint16_t, 15, 0);
};
class DIEPTXF0 : public Register32 {
public:
REGS_FIELD(TX0FSA, uint16_t, 15, 0);
REGS_FIELD(TX0FD, uint16_t, 31, 16);
};
class GCCFG : public Register32 {
public:
REGS_BOOL_FIELD(PWRDWN, 16);
REGS_BOOL_FIELD(VBDEN, 21);
};
class DCFG : public Register32 {
public:
enum class DSPD {
FullSpeed = 3,
};
void setDSPD(DSPD s) volatile { setBitRange(1, 0, (uint8_t)s); }
REGS_FIELD(DAD, uint8_t, 10, 4);
};
class DCTL : public Register32 {
public:
REGS_BOOL_FIELD(SDIS, 1);
};
class DIEPMSK : public Register32 {
public:
REGS_BOOL_FIELD(XFRCM, 0);
};
class DAINTMSK : public Register32 {
public:
REGS_FIELD(IEPM, uint16_t, 15, 0);
REGS_FIELD(OEPM, uint16_t, 31, 16);
};
class DIEPCTL0 : public Register32 {
public:
enum class MPSIZ {
Size64 = 0,
Size32 = 1,
Size16 = 2,
Size8 = 3
};
using Register32::Register32;
void setMPSIZ(MPSIZ s) volatile { setBitRange(1, 0, (uint8_t)s); }
REGS_BOOL_FIELD(STALL, 21);
REGS_FIELD(TXFNUM, uint8_t, 25, 22);
REGS_BOOL_FIELD(CNAK, 26);
REGS_BOOL_FIELD(SNAK, 27);
REGS_BOOL_FIELD(EPENA, 31);
};
class DOEPCTL0 : public Register32 {
public:
REGS_BOOL_FIELD(CNAK, 26);
REGS_BOOL_FIELD(SNAK, 27);
REGS_BOOL_FIELD(EPENA, 31);
};
class DIEPINT : public Register32 {
public:
REGS_BOOL_FIELD(XFRC, 0);
REGS_BOOL_FIELD(INEPNE, 6);
};
class DIEPTSIZ0 : public Register32 {
public:
using Register32::Register32;
REGS_FIELD(XFRSIZ, uint8_t, 6, 0);
REGS_FIELD(PKTCNT, uint8_t, 20, 19);
};
class DOEPTSIZ0 : public Register32 {
public:
using Register32::Register32;
REGS_FIELD(XFRSIZ, uint8_t, 6, 0);
REGS_BOOL_FIELD(PKTCNT, 19);
REGS_FIELD(STUPCNT, uint8_t, 30, 29);
};
class PCGCCTL : public Register32 {
public:
REGS_BOOL_FIELD(STPPCLK, 0);
REGS_BOOL_FIELD(GATEHCLK, 1);
};
class DFIFO0 : public Register32 {
};
constexpr OTG() {};
REGS_REGISTER_AT(GAHBCFG, 0x008);
REGS_REGISTER_AT(GUSBCFG, 0x00C);
REGS_REGISTER_AT(GRSTCTL, 0x010);
REGS_REGISTER_AT(GINTSTS, 0x014);
REGS_REGISTER_AT(GINTMSK, 0x018);
REGS_REGISTER_AT(GRXSTSP, 0x020);
REGS_REGISTER_AT(GRXFSIZ, 0x024);
REGS_REGISTER_AT(DIEPTXF0, 0x28);
REGS_REGISTER_AT(GCCFG, 0x038);
REGS_REGISTER_AT(DCFG, 0x800);
REGS_REGISTER_AT(DCTL, 0x804);
REGS_REGISTER_AT(DIEPMSK, 0x810);
REGS_REGISTER_AT(DAINTMSK, 0x81C);
REGS_REGISTER_AT(DIEPCTL0, 0x900);
REGS_REGISTER_AT(DIEPTSIZ0, 0x910);
REGS_REGISTER_AT(DOEPCTL0, 0xB00);
REGS_REGISTER_AT(DOEPTSIZ0, 0xB10);
REGS_REGISTER_AT(PCGCCTL, 0xE00);
REGS_REGISTER_AT(DFIFO0, 0x1000);
constexpr volatile DIEPINT * DIEPINT(int i) const {
return (class DIEPINT *)(Base() + 0x908 + i*0x20);
}
private:
constexpr uint32_t Base() const {
return 0x50000000;
}
};
constexpr OTG OTG;
#endif

25
ion/src/f730/regs/pwr.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef REGS_PWR_H
#define REGS_PWR_H
#include "register.h"
class PWR {
public:
class CR : Register32 {
public:
REGS_BOOL_FIELD(LPDS, 0);
REGS_BOOL_FIELD(PPDS, 1);
REGS_BOOL_FIELD(FPDS, 9);
};
constexpr PWR() {};
REGS_REGISTER_AT(CR, 0x00);
private:
constexpr uint32_t Base() const {
return 0x40007000;
};
};
constexpr PWR PWR;
#endif

149
ion/src/f730/regs/rcc.h Normal file
View File

@@ -0,0 +1,149 @@
#ifndef REGS_RCC_H
#define REGS_RCC_H
#include "register.h"
class RCC {
public:
class CR : public Register32 {
public:
REGS_BOOL_FIELD(HSION, 0);
REGS_BOOL_FIELD(HSEON, 16);
REGS_BOOL_FIELD_R(HSERDY, 17);
REGS_BOOL_FIELD(PLLON, 24);
REGS_BOOL_FIELD(PLLRDY, 25);
};
class PLLCFGR : public Register32 {
public:
REGS_FIELD(PLLM, uint8_t, 5, 0);
REGS_FIELD(PLLN, uint16_t, 14, 6);
REGS_FIELD(PLLP, uint8_t, 17, 16);
enum class PLLSRC {
HSI = 0,
HSE = 1
};
void setPLLSRC(PLLSRC s) volatile { setBitRange(22, 22, (uint8_t)s); }
REGS_FIELD(PLLQ, uint8_t, 27, 24);
REGS_FIELD(PLLR, uint8_t, 30, 28);
};
class CFGR : public Register32 {
public:
enum class SW {
HSI = 0,
HSE = 1,
PLL = 2
};
void setSW(SW s) volatile { setBitRange(1, 0, (uint8_t)s); }
SW getSWS() volatile { return (SW)getBitRange(3,2); }
enum class AHBPrescaler {
SysClk = 0,
SysClkDividedBy2 = 8,
SysClkDividedBy4 = 9,
SysClkDividedBy8 = 10,
SysClkDividedBy16 = 11,
SysClkDividedBy64 = 12,
SysClkDividedBy128 = 13,
SysClkDividedBy256 = 14,
SysClkDividedBy512 = 15
};
void setHPRE(AHBPrescaler p) volatile { setBitRange(7, 4, (uint32_t)p); }
enum class APBPrescaler{
AHB = 0,
AHBDividedBy2 = 4,
AHBDividedBy4 = 5,
AHBDividedBy8 = 6,
AHBDividedBy16 = 7
};
void setPPRE1(APBPrescaler r) volatile { setBitRange(12, 10, (uint32_t)r); }
};
class AHB1ENR : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD(GPIOAEN, 0);
REGS_BOOL_FIELD(GPIOBEN, 1);
REGS_BOOL_FIELD(GPIOCEN, 2);
REGS_BOOL_FIELD(GPIODEN, 3);
REGS_BOOL_FIELD(GPIOEEN, 4);
REGS_BOOL_FIELD(GPIOFEN, 5);
REGS_BOOL_FIELD(GPIOGEN, 6);
REGS_BOOL_FIELD(GPIOHEN, 7);
REGS_BOOL_FIELD(CRCEN, 12);
REGS_BOOL_FIELD(DMA1EN, 21);
REGS_BOOL_FIELD(DMA2EN, 22);
};
class AHB2ENR : Register32 {
public:
REGS_BOOL_FIELD(RNGEN, 6);
REGS_BOOL_FIELD(OTGFSEN, 7);
};
class AHB3ENR : Register32 {
public:
REGS_BOOL_FIELD(FSMCEN, 0);
REGS_BOOL_FIELD(QSPIEN, 1);
};
class APB1ENR : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD(TIM3EN, 1);
REGS_BOOL_FIELD(SPI3EN, 15);
REGS_BOOL_FIELD(USART3EN, 18);
REGS_BOOL_FIELD(PWREN, 28);
};
class APB2ENR : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD(TIM1EN, 0);
REGS_BOOL_FIELD(USART1EN, 4);
REGS_BOOL_FIELD(ADC1EN, 8);
REGS_BOOL_FIELD(SDIOEN, 11);
REGS_BOOL_FIELD(SPI1EN, 12);
REGS_BOOL_FIELD(SYSCFGEN, 14);
};
class AHB1LPENR : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD(GPIOBLPEN, 1);
REGS_BOOL_FIELD(GPIOCLPEN, 2);
};
class APB1LPENR : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD(TIM3LPEN, 1);
};
class DCKCFGR2 : Register32 {
public:
REGS_BOOL_FIELD(CK48MSEL, 27);
REGS_BOOL_FIELD(CKSDIOSEL, 28);
};
constexpr RCC() {};
REGS_REGISTER_AT(CR, 0x00);
REGS_REGISTER_AT(PLLCFGR, 0x04);
REGS_REGISTER_AT(CFGR, 0x08);
REGS_REGISTER_AT(AHB1ENR, 0x30);
REGS_REGISTER_AT(AHB2ENR, 0x34);
REGS_REGISTER_AT(AHB3ENR, 0x38);
REGS_REGISTER_AT(APB1ENR, 0x40);
REGS_REGISTER_AT(APB2ENR, 0x44);
REGS_REGISTER_AT(AHB1LPENR, 0x50);
REGS_REGISTER_AT(APB1LPENR, 0x60);
REGS_REGISTER_AT(DCKCFGR2, 0x94);
private:
constexpr uint32_t Base() const {
return 0x40023800;
}
};
constexpr RCC RCC;
#endif

View File

@@ -0,0 +1,61 @@
#ifndef REGS_REGISTER_H
#define REGS_REGISTER_H
#include <stdint.h>
#include <assert.h>
template <typename T>
class Register {
public:
Register() = delete;
Register(T v) : m_value(v) {}
void set(Register<T> value) volatile {
m_value = value.m_value;
}
void set(T value) volatile {
m_value = value;
}
T get() volatile {
return m_value;
}
void setBitRange(uint8_t high, uint8_t low, T value) volatile {
m_value = bit_range_set_value(high, low, m_value, value);
}
T getBitRange(uint8_t high, uint8_t low) volatile {
/* "Shift behavior is undefined if the right operand is negative, or greater
* than or equal to the length in bits of the promoted left operand" according
* to C++ spec. */
assert(low < 8*sizeof(T));
return (m_value & bit_range_mask(high,low)) >> low;
}
protected:
static constexpr T bit_range_mask(uint8_t high, uint8_t low) {
// Same comment as for getBitRange: we should assert (high-low+1) < 8*sizeof(T)
return ((((T)1)<<(high-low+1))-1)<<low;
}
static constexpr T bit_range_value(T value, uint8_t high, uint8_t low) {
// Same comment as for getBitRange: we should assert low < 8*sizeof(T))
return (value<<low) & bit_range_mask(high,low);
}
static constexpr T bit_range_set_value(uint8_t high, uint8_t low, T originalValue, T targetValue) {
return (originalValue & ~bit_range_mask(high,low))|bit_range_value(targetValue, high, low);
}
private:
T m_value;
};
typedef Register<uint8_t> Register8;
typedef Register<uint16_t> Register16;
typedef Register<uint32_t> Register32;
typedef Register<uint64_t> Register64;
#define REGS_FIELD_R(name,type,high,low) type get##name() volatile { return (type)getBitRange(high,low); };
#define REGS_FIELD_W(name,type,high,low) void set##name(type v) volatile { static_assert(sizeof(type) <= 4, "Invalid size"); setBitRange(high, low, static_cast<uint32_t>(v)); };
#define REGS_FIELD(name,type,high,low) REGS_FIELD_R(name,type,high,low); REGS_FIELD_W(name,type,high,low);
#define REGS_TYPE_FIELD(name,high,low) REGS_FIELD(name,name,high,low)
#define REGS_BOOL_FIELD(name,bit) REGS_FIELD(name,bool,bit,bit)
#define REGS_BOOL_FIELD_R(name,bit) REGS_FIELD_R(name,bool,bit,bit)
#define REGS_BOOL_FIELD_W(name,bit) REGS_FIELD_W(name,bool,bit,bit)
#define REGS_REGISTER_AT(name, offset) constexpr volatile name * name() const { return (class name *)(Base() + offset); };
#endif

25
ion/src/f730/regs/regs.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef REGS_REGS_H
#define REGS_REGS_H
#include "adc.h"
#include "cm4.h"
#include "crc.h"
#include "dma.h"
#include "exti.h"
#include "flash.h"
#include "fsmc.h"
#include "gpio.h"
#include "itm.h"
#include "mpu.h"
#include "nvic.h"
#include "pwr.h"
#include "rcc.h"
#include "rng.h"
#include "otg.h"
#include "sdio.h"
#include "spi.h"
#include "syscfg.h"
#include "tim.h"
#include "usart.h"
#endif

33
ion/src/f730/regs/rng.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef REGS_RNG_H
#define REGS_RNG_H
#include "register.h"
class RNG {
public:
class CR : Register32 {
public:
REGS_BOOL_FIELD(RNGEN, 2);
};
class SR : Register32 {
public:
REGS_BOOL_FIELD(DRDY, 0);
};
class DR : public Register32 {
};
constexpr RNG() {};
REGS_REGISTER_AT(CR, 0x00);
REGS_REGISTER_AT(SR, 0x04);
REGS_REGISTER_AT(DR, 0x08);
private:
constexpr uint32_t Base() const {
return 0x50060800;
}
};
constexpr RNG RNG;
#endif

112
ion/src/f730/regs/sdio.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef REGS_SDIO_H
#define REGS_SDIO_H
#include "register.h"
class SDIO {
public:
class POWER : Register32 {
public:
enum class PWRCTRL : uint8_t {
Off = 0,
On = 3
};
REGS_TYPE_FIELD(PWRCTRL, 1, 0);
};
class CLKCR : Register32 {
public:
enum class WIDBUS : uint8_t {
Default = 0,
FourBits = 1,
EightBits = 2
};
REGS_FIELD(CLKDIV, uint8_t, 7, 0);
REGS_BOOL_FIELD(CLKEN, 8);
REGS_BOOL_FIELD(PWRSAV, 9);
REGS_BOOL_FIELD(BYPASS, 10);
REGS_TYPE_FIELD(WIDBUS, 12, 11);
REGS_BOOL_FIELD(NEGEDGE, 13);
REGS_BOOL_FIELD(HWFC_EN, 14);
};
class ARG : public Register32 {
};
class CMD : public Register32 {
public:
using Register32::Register32;
enum class WAITRESP : uint8_t {
None = 0,
Short = 1,
Long = 3
};
REGS_FIELD(CMDINDEX, uint8_t, 5, 0);
REGS_TYPE_FIELD(WAITRESP, 7, 6);
REGS_BOOL_FIELD(WAITINT, 8);
REGS_BOOL_FIELD(WAITPEND, 9);
REGS_BOOL_FIELD(CPSMEN, 10);
};
class RESP : public Register32 {
};
class STA : Register32 {
public:
REGS_BOOL_FIELD_R(CCRCFAIL, 0);
REGS_BOOL_FIELD_R(DCRCFAIL, 1);
REGS_BOOL_FIELD_R(CTIMEOUT, 2);
REGS_BOOL_FIELD_R(DTIMEOUT, 3);
REGS_BOOL_FIELD_R(TXUNDERR, 4);
REGS_BOOL_FIELD_R(RXOVERR, 5);
REGS_BOOL_FIELD_R(CMDREND, 6);
REGS_BOOL_FIELD_R(CMDSENT, 7);
REGS_BOOL_FIELD_R(DATAEND, 8);
REGS_BOOL_FIELD_R(DBCKEND, 10);
REGS_BOOL_FIELD_R(CMDACT, 11);
REGS_BOOL_FIELD_R(TXACT, 12);
REGS_BOOL_FIELD_R(RXACT, 13);
REGS_BOOL_FIELD_R(TXFIFOHE, 14);
REGS_BOOL_FIELD_R(RXFIFOHF, 15);
REGS_BOOL_FIELD_R(TXFIFOF, 16);
REGS_BOOL_FIELD_R(RXFIFOF, 17);
REGS_BOOL_FIELD_R(TXFIFOE, 18);
REGS_BOOL_FIELD_R(RXFIFOE, 19);
REGS_BOOL_FIELD_R(TXDAVL, 20);
REGS_BOOL_FIELD_R(RXDAVL, 21);
REGS_BOOL_FIELD_R(SDIOIT, 22);
};
class ICR : public Register32 {
public:
using Register32::Register32;
REGS_BOOL_FIELD_W(CCRCFAILC, 0);
REGS_BOOL_FIELD_W(DCRCFAILC, 1);
REGS_BOOL_FIELD_W(CTIMEOUTC, 2);
REGS_BOOL_FIELD_W(DTIMEOUTC, 3);
REGS_BOOL_FIELD_W(TXUNDERRC, 4);
REGS_BOOL_FIELD_W(RXOVERRC, 5);
REGS_BOOL_FIELD_W(CMDRENDC, 6);
REGS_BOOL_FIELD_W(CMDSENTC, 7);
REGS_BOOL_FIELD_W(DATAENDC, 8);
REGS_BOOL_FIELD_W(DBCKENDC, 10);
REGS_BOOL_FIELD_W(SDIOITC, 22);
};
constexpr SDIO() {};
REGS_REGISTER_AT(POWER, 0x00);
REGS_REGISTER_AT(CLKCR, 0x04);
REGS_REGISTER_AT(ARG, 0x08);
REGS_REGISTER_AT(CMD, 0x0C);
volatile RESP * RESP(int i ) const { return (class RESP *)(Base() + 0x10+4*i); };
REGS_REGISTER_AT(STA, 0x34);
REGS_REGISTER_AT(ICR, 0x38);
private:
constexpr uint32_t Base() const {
return 0x40012C00;
}
};
constexpr SDIO SDIO;
#endif

45
ion/src/f730/regs/spi.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef REGS_SPI_H
#define REGS_SPI_H
#include "register.h"
class SPI {
public:
class CR1 : Register16 {
public:
REGS_BOOL_FIELD(MSTR, 2);
REGS_BOOL_FIELD(SPE, 6);
REGS_BOOL_FIELD(LSBFIRST, 7);
REGS_BOOL_FIELD(SSI, 8);
REGS_BOOL_FIELD(SSM, 9);
REGS_BOOL_FIELD(RXONLY, 10);
REGS_BOOL_FIELD(DFF, 11);
};
class CR2 : Register16 {
public:
REGS_BOOL_FIELD(RXDMAEN, 0);
};
class SR : Register16 {
public:
REGS_BOOL_FIELD(RXNE, 0);
REGS_BOOL_FIELD(TXE, 1);
};
class DR : public Register16 {
};
constexpr SPI(int i) : m_index(i) {}
constexpr operator int() const { return m_index; }
REGS_REGISTER_AT(CR1, 0x00);
REGS_REGISTER_AT(CR2, 0x04);
REGS_REGISTER_AT(SR, 0x08);
REGS_REGISTER_AT(DR, 0x0C);
private:
constexpr uint32_t Base() const {
return ((uint32_t []){0x40013000, 0x40003800, 0x40003C00})[m_index-1];
};
int m_index;
};
constexpr SPI SPI1(1);
#endif

View File

@@ -0,0 +1,44 @@
#ifndef REGS_SYSCFG_H
#define REGS_SYSCFG_H
#include "register.h"
#include "gpio.h"
class SYSCFG {
public:
class MEMRMP : Register32 {
public:
enum class MemMode : uint8_t {
MainFlashmemory = 0,
SystemFlashmemory = 1,
EmbeddedSRAM = 3
};
REGS_FIELD(MEM_MODE, MemMode, 1, 0);
};
class EXTICR1 : Register32 {
public:
void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*index+3, 4*index, (uint32_t)gpio); }
};
class EXTICR2 : Register32 {
public:
void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-4)+3, 4*(index-4), (uint32_t)gpio); }
};
class EXTICR3 : Register32 {
public:
void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-8)+3, 4*(index-8), (uint32_t)gpio); }
};
constexpr SYSCFG() {};
REGS_REGISTER_AT(MEMRMP, 0x00);
REGS_REGISTER_AT(EXTICR1, 0x08);
REGS_REGISTER_AT(EXTICR2, 0x0C);
REGS_REGISTER_AT(EXTICR3, 0x10);
private:
constexpr uint32_t Base() const {
return 0x40013800;
}
};
constexpr SYSCFG SYSCFG;
#endif

100
ion/src/f730/regs/tim.h Normal file
View File

@@ -0,0 +1,100 @@
#ifndef REGS_TIM_H
#define REGS_TIM_H
#include "register.h"
template <typename RegisterWidth>
class TIM {
public:
class CR1 : Register16 {
public:
REGS_BOOL_FIELD(CEN, 0);
REGS_BOOL_FIELD(ARPE, 7);
};
class CCMR : Register64 {
/* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se,
* it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits
* registers, so one could expect the consolidation to be 32 bits. However,
* both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has
* to be 64 bits wide, even though we'll only use 32 bits out of 64. */
public:
enum class CC1S : uint8_t {
OUTPUT = 0,
INPUT_TI2 = 1,
INPUT_TI1 = 2,
INPUT_TRC = 3
};
enum class OCM : uint8_t {
Frozen = 0,
ActiveOnMatch = 1,
InactiveOnMatch = 2,
Toggle = 3,
ForceInactive = 4,
ForceActive = 5,
PWM1 = 6,
PWM2 = 7
};
typedef OCM OC1M;
typedef OCM OC2M;
typedef OCM OC3M;
typedef OCM OC4M;
REGS_BOOL_FIELD(OC1PE, 3);
REGS_TYPE_FIELD(OC1M, 6, 4);
REGS_BOOL_FIELD(OC2PE, 11);
REGS_TYPE_FIELD(OC2M, 14, 12);
REGS_BOOL_FIELD(OC3PE, 35);
REGS_TYPE_FIELD(OC3M, 38, 36);
REGS_BOOL_FIELD(OC4PE, 43);
REGS_TYPE_FIELD(OC4M, 46, 44);
};
class CCER : Register16 {
public:
REGS_BOOL_FIELD(CC1E, 0);
REGS_BOOL_FIELD(CC2E, 4);
REGS_BOOL_FIELD(CC3E, 8);
REGS_BOOL_FIELD(CC4E, 12);
};
class BDTR : Register16 {
public:
REGS_BOOL_FIELD(MOE, 15);
};
class PSC : public Register16 {};
class ARR : public Register16 {};
class CCR1 : public RegisterWidth {};
class CCR2 : public RegisterWidth {};
class CCR3 : public RegisterWidth {};
class CCR4 : public RegisterWidth {};
constexpr TIM(int i) : m_index(i) {}
REGS_REGISTER_AT(CR1, 0x0);
REGS_REGISTER_AT(CCMR, 0x18);
REGS_REGISTER_AT(CCER, 0x20);
REGS_REGISTER_AT(PSC, 0x28);
REGS_REGISTER_AT(ARR, 0x2C);
REGS_REGISTER_AT(CCR1, 0x34);
REGS_REGISTER_AT(CCR2, 0x38);
REGS_REGISTER_AT(CCR3, 0x3C);
REGS_REGISTER_AT(CCR4, 0x40);
REGS_REGISTER_AT(BDTR, 0x44);
private:
constexpr uint32_t Base() const {
return (m_index == 1 ? 0x40010000 :
(m_index <= 7 ? 0x40000000 + 0x400*(m_index-2) :
(m_index == 8 ? 0x40010400 :
(m_index <= 11 ? 0x40014000 + 0x400*(m_index-9) :
0x40001800 + 0x400*(m_index-12)
)
)
)
);
}
int m_index;
};
constexpr TIM<Register16> TIM3(3);
#endif

50
ion/src/f730/regs/usart.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef REGS_USART_H
#define REGS_USART_H
#include "register.h"
class USART {
public:
class SR : Register32 {
public:
REGS_BOOL_FIELD(RXNE, 5);
REGS_BOOL_FIELD(TXE, 7);
};
class DR : Register32 {
public:
uint16_t get() volatile {
return (uint16_t)getBitRange(8, 0);
}
void set(uint16_t v) volatile {
setBitRange(8, 0, v);
}
};
class BRR : Register32 {
public:
REGS_FIELD(DIV_FRAC, uint8_t, 3, 0);
REGS_FIELD(DIV_MANTISSA, uint16_t, 15, 4);
};
class CR1 : Register32 {
public:
REGS_BOOL_FIELD(UE, 13);
REGS_BOOL_FIELD(TE, 3);
REGS_BOOL_FIELD(RE, 2);
};
constexpr USART(int i) : m_index(i) {}
constexpr operator int() const { return m_index; }
REGS_REGISTER_AT(SR, 0x00);
REGS_REGISTER_AT(DR, 0x04);
REGS_REGISTER_AT(BRR, 0x08);
REGS_REGISTER_AT(CR1, 0x0C);
private:
constexpr uint32_t Base() const {
return ((uint32_t []){0x40011000, 0x40004400, 0x40004800})[m_index-1];
};
int m_index;
};
#endif

95
ion/src/f730/sd_card.cpp Normal file
View File

@@ -0,0 +1,95 @@
#include "sd_card.h"
#include "regs/regs.h"
extern "C" {
#include <assert.h>
}
namespace Ion {
namespace SDCard {
namespace Device {
void init() {
initGPIO();
initCard();
}
void initGPIO() {
// Configure GPIOs to use AF
GPIOA.MODER()->setMode(8, GPIO::MODER::Mode::AlternateFunction);
GPIOB.MODER()->setMode(4, GPIO::MODER::Mode::AlternateFunction);
GPIOB.MODER()->setMode(5, GPIO::MODER::Mode::AlternateFunction);
GPIOB.MODER()->setMode(15, GPIO::MODER::Mode::AlternateFunction);
GPIOC.MODER()->setMode(10, GPIO::MODER::Mode::AlternateFunction);
GPIOD.MODER()->setMode(2, GPIO::MODER::Mode::AlternateFunction);
// More precisely, AF12 which correspond to SDIO alternate functions
GPIOA.AFR()->setAlternateFunction(8, GPIO::AFR::AlternateFunction::AF12);
GPIOB.AFR()->setAlternateFunction(4, GPIO::AFR::AlternateFunction::AF12);
GPIOB.AFR()->setAlternateFunction(5, GPIO::AFR::AlternateFunction::AF12);
GPIOB.AFR()->setAlternateFunction(15, GPIO::AFR::AlternateFunction::AF12);
GPIOC.AFR()->setAlternateFunction(10, GPIO::AFR::AlternateFunction::AF12);
GPIOD.AFR()->setAlternateFunction(2, GPIO::AFR::AlternateFunction::AF12);
}
void initCard() {
// Power on
SDIO.POWER()->setPWRCTRL(SDIO::POWER::PWRCTRL::On);
while (SDIO.POWER()->getPWRCTRL() != SDIO::POWER::PWRCTRL::On) {
}
// Clock set
SDIO.CLKCR()->setCLKDIV(254);
SDIO.CLKCR()->setCLKEN(true);
sendCommand(0, 0);
// CMD8 : 0b0001 = 2.7 - 3.6V
// 0xB7 = Pattern to see back in response
sendCommand(8, 0x1B7);
assert(SDIO.RESP(1)->get() == 0x1B7);
}
void sendCommand(uint32_t cmd, uint32_t arg) {
class SDIO::ICR icr(0);
icr.setCCRCFAILC(true);
icr.setCTIMEOUTC(true);
icr.setCMDRENDC(true);
icr.setCMDSENTC(true);
SDIO.ICR()->set(icr);
SDIO.ARG()->set(arg);
SDIO::CMD::WAITRESP responseType = SDIO::CMD::WAITRESP::Short;
switch (cmd) {
case 0:
responseType = SDIO::CMD::WAITRESP::None;
break;
case 2:
case 9:
case 10:
responseType = SDIO::CMD::WAITRESP::Long;
default:
break;
}
class SDIO::CMD command(0);
command.setCMDINDEX(cmd);
command.setCPSMEN(true);
command.setWAITRESP(responseType);
SDIO.CMD()->set(command);
if (responseType == SDIO::CMD::WAITRESP::None) {
// Wait for timeout or command sent
while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDSENT()) {
}
} else {
while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDREND() && !SDIO.STA()->getCCRCFAIL()) {
}
}
}
}
}
}

33
ion/src/f730/sd_card.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef ION_DEVICE_SD_CARD_H
#define ION_DEVICE_SD_CARD_H
extern "C" {
#include <stdint.h>
}
namespace Ion {
namespace SDCard {
namespace Device {
/* Pin | Role | Mode | Function
* -----+-------------------+-----------------------+----------
* PA8 | SDIO D1 | Alternate Function 12 | SDIO_D1
* PB4 | SDIO D0 | Alternate Function 12 | SDIO_D0
* PB5 | SDIO D3 | Alternate Function 12 | SDIO_D3
* PB15 | SDIO CLK | Alternate Function 12 | SDIO_CK
* PC10 | SDIO D2 | Alternate Function 12 | SDIO_D2
* PD2 | SDIO CMD | Alternate Function 12 | SDIO_CMD
*/
void init();
void initGPIO();
void initCard();
void sendCommand(uint32_t cmd, uint32_t arg);
}
}
}
#endif

10
ion/src/f730/stack.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include <ion.h>
extern const void * _stack_start;
extern const void * _stack_end;
bool Ion::stackSafe() {
volatile int stackDummy;
volatile void * stackPointer = &stackDummy;
return (stackPointer >= &_stack_end && stackPointer <= &_stack_start);
}

24
ion/src/f730/swd.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "swd.h"
#include "regs/regs.h"
namespace Ion {
namespace SWD {
namespace Device {
void init() {
for(const GPIOPin & g : Pins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF0);
}
}
void shutdown() {
for(const GPIOPin & g : Pins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
}
}
}
}
}

28
ion/src/f730/swd.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef ION_DEVICE_SWD_H
#define ION_DEVICE_SWD_H
#include "regs/regs.h"
namespace Ion {
namespace SWD {
namespace Device {
/* Pin | Role | Mode
* -----+-------------------+---------------------
* PA13 | SWDIO | Alternate Function 0
* PA14 | SWCLK | Alternate Function 0
* PB3 | SWO | Alternate Function 0
*/
void init();
void shutdown();
constexpr static GPIOPin Pins[] = {
GPIOPin(GPIOA, 13), GPIOPin(GPIOA, 14), GPIOPin(GPIOB, 3)
};
}
}
}
#endif

36
ion/src/f730/timing.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "timing.h"
namespace Ion {
namespace Timing {
/* TODO: The delay methods 'msleep' and 'usleep' are currently dependent on the
* optimizations chosen by the compiler. To prevent that and to gain in
* precision, we could use the controller cycle counter (Systick). */
void msleep(uint32_t ms) {
for (volatile uint32_t i=0; i<8852*ms; i++) {
__asm volatile("nop");
}
}
void usleep(uint32_t us) {
for (volatile uint32_t i=0; i<9*us; i++) {
__asm volatile("nop");
}
}
uint64_t millis() {
return Ion::Timing::Device::MillisElapsed;
}
}
}
namespace Ion {
namespace Timing {
namespace Device {
volatile uint64_t MillisElapsed = 0;
}
}
}

16
ion/src/f730/timing.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef ION_DEVICE_TIMING_H
#define ION_DEVICE_TIMING_H
#include <ion/timing.h>
namespace Ion {
namespace Timing {
namespace Device {
extern volatile uint64_t MillisElapsed;
}
}
}
#endif

187
ion/src/f730/usb.cpp Normal file
View File

@@ -0,0 +1,187 @@
#include <ion/usb.h>
#include "usb.h"
#include <ion/display.h>
#include "device.h"
#include "display.h"
#include "regs/regs.h"
#include <stdlib.h>
namespace Ion {
namespace USB {
bool isPlugged() {
return Device::VbusPin.group().IDR()->get(Device::VbusPin.pin());
}
bool isEnumerated() {
/* Note: This implementation is not perfect. One would assume isEnumerated to
* return true for as long as the device is enumerated. But the GINTSTS
* register will be cleared in the poll() routine. */
return OTG.GINTSTS()->getENUMDNE();
}
void clearEnumerationInterrupt() {
OTG.GINTSTS()->setENUMDNE(true);
}
void enable() {
// Get out of soft-disconnected state
OTG.DCTL()->setSDIS(false);
}
void disable() {
// Get into soft-disconnected state
OTG.DCTL()->setSDIS(true);
}
}
}
namespace Ion {
namespace USB {
namespace Device {
void init() {
initGPIO();
initOTG();
}
void shutdown() {
shutdownOTG();
shutdownGPIO();
}
static inline void DEBUGTOGGLE() {
bool state = GPIOC.ODR()->get(11);
GPIOC.ODR()->set(11, !state);
}
#include <stdlib.h>
void initGPIO() {
// DEBUG GPIO pin
GPIOC.MODER()->setMode(11, GPIO::MODER::Mode::Output);
GPIOC.ODR()->set(11, false);
/* Configure the GPIO
* The VBUS pin is connected to the USB VBUS port. To read if the USB is
* plugged, the pin must be pulled down. */
// FIXME: Understand how the Vbus pin really works!
#if 0
VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::Input);
VbusPin.group().PUPDR()->setPull(VbusPin.pin(), GPIO::PUPDR::Pull::Down);
#else
VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::AlternateFunction);
VbusPin.group().AFR()->setAlternateFunction(VbusPin.pin(), GPIO::AFR::AlternateFunction::AF10);
#endif
DmPin.group().MODER()->setMode(DmPin.pin(), GPIO::MODER::Mode::AlternateFunction);
DmPin.group().AFR()->setAlternateFunction(DmPin.pin(), GPIO::AFR::AlternateFunction::AF10);
DpPin.group().MODER()->setMode(DpPin.pin(), GPIO::MODER::Mode::AlternateFunction);
DpPin.group().AFR()->setAlternateFunction(DpPin.pin(), GPIO::AFR::AlternateFunction::AF10);
}
void shutdownGPIO() {
constexpr static GPIOPin USBPins[] = {DpPin, DmPin, VbusPin};
for (const GPIOPin & g : USBPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
}
}
void initOTG() {
// Wait for AHB idle
while (!OTG.GRSTCTL()->getAHBIDL()) {
}
/* Core soft reset: Clears the interrupts and many of the CSR register bits,
* resets state machines, flushes the FIFOs and terminates USB transactions.*/
OTG.GRSTCTL()->setCSRST(true);
while (OTG.GRSTCTL()->getCSRST()) {
}
/* Enable the transceiver module of the PHY. It must be done to allow any USB
* operation */
OTG.GCCFG()->setPWRDWN(true);
/* Enable VBUS sensing comparators to detect valid levels for USB operation.
* This is used for instance to end the session if the host is switched off.*/
OTG.GCCFG()->setVBDEN(true);
// Force peripheral only mode
OTG.GUSBCFG()->setFDMOD(true);
// Configure the USB turnaround time, depending on the AHB clock speed (96MHz)
OTG.GUSBCFG()->setTRDT(0x6);
// Clear the interrupts
OTG.GINTSTS()->set(0);
// Full speed device
OTG.DCFG()->setDSPD(OTG::DCFG::DSPD::FullSpeed);
/* RxFIFO size. The value is in terms of 32-bit words.
* According to the reference manual, it should be, at minimum:
* (4 * number of control endpoints + 6)
* To receive SETUP packets on control endpoint
* + ((largest USB packet used / 4) + 1)
* To receive 1 USB packet + 1 packet status
* + (2 * number of OUT endpoints)
* Transfer complete status information
* + 1 for Global NAK
* So, for the calculator: (4*1+6) + (64/4 + 1) + (2*1) + 1 = 30
* As the RAM size is 1.25kB, the size should be at most 320, minus the space
* for the Tx FIFOs.
* However, we tested and found that only values between 40 and 255 actually
* work. We arbitrarily chose 128. */
OTG.GRXFSIZ()->setRXFD(128);
// Unmask the interrupt line assertions
OTG.GAHBCFG()->setGINTMSK(true);
// Restart the PHY clock
OTG.PCGCCTL()->setSTPPCLK(false);
// Pick which interrupts we're interested in
class OTG::GINTMSK intMask(0); // Reset value
intMask.setENUMDNEM(true); // Speed enumeration done
intMask.setRXFLVLM(true); // Receive FIFO non empty
intMask.setIEPINT(true); // IN endpoint interrupt
OTG.GINTMSK()->set(intMask);
// Unmask IN interrupts for endpoint 0 only
OTG.DAINTMSK()->setIEPM(1);
/* Unmask the IN transfer completed interrupt for all endpoints. This
* interrupt warns that a IN transaction happened on the endpoint. */
OTG.DIEPMSK()->setXFRCM(true);
/* To communicate with a USB host, the device still needs to get out of soft-
* disconnected state (SDIS in the DCTL register). We do this when we detect
* that the USB cable is plugged. */
}
void shutdownOTG() {
// Core soft reset
OTG.GRSTCTL()->setCSRST(true);
while (OTG.GRSTCTL()->getCSRST()) {
}
// Get into soft-disconnected state
OTG.DCTL()->setSDIS(true);
// Stop the PHY clock
OTG.PCGCCTL()->setSTPPCLK(true);
// Stop VBUS sensing
OTG.GCCFG()->setVBDEN(false);
// Disable the transceiver module of the PHY
OTG.GCCFG()->setPWRDWN(false);
}
}
}
}

34
ion/src/f730/usb.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef ION_DEVICE_USB_H
#define ION_DEVICE_USB_H
#include "regs/regs.h"
#include "ion.h"
#include "usb/calculator.h"
namespace Ion {
namespace USB {
namespace Device {
/* Pin | Role | Mode | Function
* -----+-------------------+-----------------------+----------
* PA9 | VBUS | Input, pulled down//TODO | Low = unplugged, high = plugged
* PA11 | USB D- | Alternate Function 10 |
* PA12 | USB D+ | Alternate Function 10 |
*/
constexpr static GPIOPin VbusPin = GPIOPin(GPIOA, 9);
constexpr static GPIOPin DmPin = GPIOPin(GPIOA, 11);
constexpr static GPIOPin DpPin = GPIOPin(GPIOA, 12);
void init();
void shutdown();
void initGPIO();
void shutdownGPIO();
void initOTG();
void shutdownOTG();
}
}
}
#endif

67
ion/src/f730/usb/Makefile Normal file
View File

@@ -0,0 +1,67 @@
usb_objs += $(addprefix ion/src/device/usb/, \
calculator.o \
dfu_interface.o\
)
usb_objs += $(addprefix ion/src/device/usb/stack/, \
device.o\
endpoint0.o \
interface.o\
request_recipient.o\
setup_packet.o\
streamable.o\
)
usb_objs += $(addprefix ion/src/device/usb/stack/descriptor/, \
bos_descriptor.o\
configuration_descriptor.o \
descriptor.o\
device_descriptor.o\
device_capability_descriptor.o\
dfu_functional_descriptor.o\
extended_compat_id_descriptor.o \
interface_descriptor.o\
language_id_string_descriptor.o \
microsoft_os_string_descriptor.o\
platform_device_capability_descriptor.o\
string_descriptor.o\
url_descriptor.o\
webusb_platform_descriptor.o\
)
EPSILON_USB_DFU_XIP ?= 0
ifeq ($(EPSILON_USB_DFU_XIP),1)
objs += ion/src/device/usb/dfu_xip.o
objs += $(usb_objs)
else
dfu_objs += liba/src/assert.o
dfu_objs += liba/src/strlen.o
dfu_objs += liba/src/strlcpy.o
dfu_objs += liba/src/memset.o
dfu_objs += liba/src/memcpy.o
dfu_objs += libaxx/src/cxxabi/pure_virtual.o
dfu_objs += ion/src/device/usb/boot.o
dfu_objs += ion/src/device/keyboard.o
dfu_objs += ion/src/device/device.o
dfu_objs += ion/src/device/usb.o
dfu_objs += ion/src/device/base64.o
dfu_objs += ion/src/device/flash.o
dfu_objs += ion/src/device/timing.o
ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld
ion/src/device/usb/dfu.elf: $(usb_objs) $(dfu_objs)
ion/src/device/usb/dfu.o: ion/src/device/usb/dfu.bin
@echo "OBJCOPY $@"
$(Q) $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_ion_src_device_usb_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_ion_src_device_usb_dfu_bin_end=_dfu_bootloader_flash_end $< $@
objs += ion/src/device/usb/dfu.o
objs += ion/src/device/usb/dfu_relocated.o
products += $(usb_objs) $(addprefix ion/src/device/usb/dfu, .elf .bin)
endif

View File

@@ -0,0 +1,2 @@
extern "C" void abort() {
}

View File

@@ -0,0 +1,94 @@
#include "calculator.h"
#include <ion/usb.h>
#include <ion/src/device/regs/regs.h>
#include <ion/src/device/device.h>
#include <ion/src/device/keyboard.h>
namespace Ion {
namespace USB {
namespace Device {
void Calculator::PollAndReset(bool exitWithKeyboard) {
char serialNumber[Ion::Device::SerialNumberLength+1];
Ion::Device::copySerialNumber(serialNumber);
Calculator c(serialNumber);
/* Leave DFU mode if the Back key is pressed, the calculator unplugged or the
* USB core soft-disconnected. */
Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::A6;
uint8_t exitKeyRow = Ion::Keyboard::Device::rowForKey(exitKey);
uint8_t exitKeyColumn = Ion::Keyboard::Device::columnForKey(exitKey);
Ion::Keyboard::Device::activateRow(exitKeyRow);
while (!(exitWithKeyboard && Ion::Keyboard::Device::columnIsActive(exitKeyColumn)) &&
Ion::USB::isPlugged() &&
!c.isSoftDisconnected()) {
c.poll();
}
if (!c.isSoftDisconnected()) {
c.detach();
}
if (c.resetOnDisconnect()) {
/* We don't perform a core reset because at this point in time the USB cable
* is most likely plugged in. Doing a full core reset would be the clean
* thing to do but would therefore result in the device entering the ROMed
* DFU bootloader, which we want to avoid. By performing a jump-reset, we
* will enter the newly flashed firmware. */
Ion::Device::jumpReset();
}
}
Descriptor * Calculator::descriptor(uint8_t type, uint8_t index) {
/* Special case: Microsoft OS String Descriptor should be returned when
* searching for string descriptor at index 0xEE. */
if (type == m_microsoftOSStringDescriptor.type() && index == 0xEE) {
return &m_microsoftOSStringDescriptor;
}
int typeCount = 0;
for (size_t i=0; i<sizeof(m_descriptors)/sizeof(m_descriptors[0]); i++) {
Descriptor * descriptor = m_descriptors[i];
if (descriptor->type() != type) {
continue;
}
if (typeCount == index) {
return descriptor;
} else {
typeCount++;
}
}
return nullptr;
}
bool Calculator::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
if (Device::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) {
return true;
}
if (request->requestType() == SetupPacket::RequestType::Vendor) {
if (request->bRequest() == k_webUSBVendorCode && request->wIndex() == 2) {
// This is a WebUSB, GET_URL request
assert(request->wValue() == k_webUSBLandingPageIndex);
return getURLCommand(transferBuffer, transferBufferLength, transferBufferMaxLength);
}
if (request->bRequest() == k_microsoftOSVendorCode && request->wIndex() == 0x0004) {
// This is a Microsoft OS descriptor, Extended Compat ID request
assert(request->wValue() == 0);
return getExtendedCompatIDCommand(transferBuffer, transferBufferLength, transferBufferMaxLength);
}
}
return false;
}
bool Calculator::getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
*transferBufferLength = m_workshopURLDescriptor.copy(transferBuffer, transferBufferMaxLength);
return true;
}
bool Calculator::getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
*transferBufferLength = m_extendedCompatIdDescriptor.copy(transferBuffer, transferBufferMaxLength);
return true;
}
}
}
}

View File

@@ -0,0 +1,166 @@
#ifndef ION_DEVICE_USB_CALCULATOR_H
#define ION_DEVICE_USB_CALCULATOR_H
#include <stddef.h>
#include <assert.h>
#include "dfu_interface.h"
#include "stack/device.h"
#include "stack/descriptor/bos_descriptor.h"
#include "stack/descriptor/configuration_descriptor.h"
#include "stack/descriptor/descriptor.h"
#include "stack/descriptor/device_descriptor.h"
#include "stack/descriptor/dfu_functional_descriptor.h"
#include "stack/descriptor/extended_compat_id_descriptor.h"
#include "stack/descriptor/interface_descriptor.h"
#include "stack/descriptor/language_id_string_descriptor.h"
#include "stack/descriptor/microsoft_os_string_descriptor.h"
#include "stack/descriptor/string_descriptor.h"
#include "stack/descriptor/url_descriptor.h"
#include "stack/descriptor/webusb_platform_descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
class Calculator : public Device {
public:
static void PollAndReset(bool exitWithKeyboard)
__attribute__((section(".dfu_entry_point"))) // Needed to pinpoint this symbol in the linker script
__attribute__((used)) // Make sure this symbol is not discarded at link time
; // Return true if reset is needed
Calculator(const char * serialNumber) :
Device(&m_dfuInterface),
m_deviceDescriptor(
0x0210, /* bcdUSB: USB Specification Number which the device complies
* to. Must be greater than 0x0200 to use the BOS. */
0, // bDeviceClass: The class is defined by the interface.
0, // bDeviceSUBClass: The subclass is defined by the interface.
0, // bDeviceProtocol: The protocol is defined by the interface.
64, // bMaxPacketSize0: Maximum packet size for endpoint 0
0x0483, // idVendor
0xA291, // idProduct
0x0100, // bcdDevice: Device Release Number
1, // iManufacturer: Index of the manufacturer name string, see m_descriptor
2, // iProduct: Index of the product name string, see m_descriptor
3, // iSerialNumber: Index of the SerialNumber string, see m_descriptor
1), // bNumConfigurations
m_dfuFunctionalDescriptor(
0b0011, /* bmAttributes:
* - bitWillDetach: If true, the device will perform a bus
* detach-attach sequence when it receives a DFU_DETACH
* request. The host must not issue a USB Reset.
* - bitManifestationTolerant: if true, the device is able to
* communicate via USB after Manifestation phase. The
* manifestation phase implies a reset in the calculator, so,
* even if the device is still plugged, it needs to be
* re-enumerated to communicate.
* - bitCanUpload
* - bitCanDnload */
0, /* wDetachTimeOut: Time, in milliseconds, that the device in APP
* mode will wait after receipt of the DFU_DETACH request before
* switching to DFU mode. It does not apply to the calculator.*/
2048, // wTransferSize: Maximum number of bytes that the device can accept per control-write transaction
0x0100),// bcdDFUVersion
m_interfaceDescriptor(
0, // bInterfaceNumber
k_dfuInterfaceAlternateSetting, // bAlternateSetting
0, // bNumEndpoints: Other than endpoint 0
0xFE, // bInterfaceClass: DFU (http://www.usb.org/developers/defined_class)
1, // bInterfaceSubClass: DFU
2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1)
4, // iInterface: Index of the Interface string, see m_descriptor
&m_dfuFunctionalDescriptor),
m_configurationDescriptor(
9 + 9 + 9, // wTotalLength: configuration descriptor + interface descriptor + dfu functional descriptor lengths
1, // bNumInterfaces
k_bConfigurationValue, // bConfigurationValue
0, // iConfiguration: No string descriptor for the configuration
0x80, /* bmAttributes:
* Bit 7: Reserved, set to 1
* Bit 6: Self Powered
* Bit 5: Remote Wakeup (allows the device to wake up the host when the host is in suspend)
* Bit 4..0: Reserved, set to 0 */
0x32, // bMaxPower: half of the Maximum Power Consumption
&m_interfaceDescriptor),
m_webUSBPlatformDescriptor(
k_webUSBVendorCode,
k_webUSBLandingPageIndex),
m_bosDescriptor(
5 + 24, // wTotalLength: BOS descriptor + webusb platform descriptor lengths
1, // bNumDeviceCapabilities
&m_webUSBPlatformDescriptor),
m_languageStringDescriptor(),
m_manufacturerStringDescriptor("NumWorks"),
m_productStringDescriptor("NumWorks Calculator"),
m_serialNumberStringDescriptor(serialNumber),
m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg"),
//m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"),
/* Switch to this descriptor to use dfu-util to write in the SRAM.
* FIXME Should be an alternate Interface. */
m_microsoftOSStringDescriptor(k_microsoftOSVendorCode),
m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "workshop.numworks.com"),
m_extendedCompatIdDescriptor("WINUSB"),
m_descriptors{
&m_deviceDescriptor, // Type = Device, Index = 0
&m_configurationDescriptor, // Type = Configuration, Index = 0
&m_languageStringDescriptor, // Type = String, Index = 0
&m_manufacturerStringDescriptor, // Type = String, Index = 1
&m_productStringDescriptor, // Type = String, Index = 2
&m_serialNumberStringDescriptor, // Type = String, Index = 3
&m_interfaceStringDescriptor, // Type = String, Index = 4
&m_bosDescriptor // Type = BOS, Index = 0
},
m_dfuInterface(this, &m_ep0, k_dfuInterfaceAlternateSetting)
{
}
protected:
virtual Descriptor * descriptor(uint8_t type, uint8_t index) override;
virtual void setActiveConfiguration(uint8_t configurationIndex) override {
assert(configurationIndex == k_bConfigurationValue);
}
virtual uint8_t getActiveConfiguration() override {
return k_bConfigurationValue;
}
bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override;
private:
static constexpr uint8_t k_bConfigurationValue = 1;
static constexpr uint8_t k_dfuInterfaceAlternateSetting = 0;
static constexpr uint8_t k_webUSBVendorCode = 1;
static constexpr uint8_t k_webUSBLandingPageIndex = 1;
static constexpr uint8_t k_microsoftOSVendorCode = 2;
// WebUSB and MicrosoftOSDescriptor commands
bool getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength);
bool getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength);
// Descriptors
DeviceDescriptor m_deviceDescriptor;
DFUFunctionalDescriptor m_dfuFunctionalDescriptor;
InterfaceDescriptor m_interfaceDescriptor;
ConfigurationDescriptor m_configurationDescriptor;
WebUSBPlatformDescriptor m_webUSBPlatformDescriptor;
BOSDescriptor m_bosDescriptor;
LanguageIDStringDescriptor m_languageStringDescriptor;
StringDescriptor m_manufacturerStringDescriptor;
StringDescriptor m_productStringDescriptor;
StringDescriptor m_serialNumberStringDescriptor;
StringDescriptor m_interfaceStringDescriptor;
MicrosoftOSStringDescriptor m_microsoftOSStringDescriptor;
URLDescriptor m_workshopURLDescriptor;
ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor;
Descriptor * m_descriptors[8];
/* m_descriptors contains only descriptors that sould be returned via the
* method descriptor(uint8_t type, uint8_t index), so do not count descriptors
* included in other descriptors or returned by other functions. */
// Interface
DFUInterface m_dfuInterface;
};
}
}
}
#endif

50
ion/src/f730/usb/dfu.ld Normal file
View File

@@ -0,0 +1,50 @@
/* DFU transfers can serve two purposes:
* - Transfering RAM data between the machine and the host, e.g. Python scripts
* - Upgrading the flash memory to perform a software update
*
* The second case raises a huge issue: code cannot be executed from memory that
* is being modified. We're solving this issue by copying the DFU code in RAM.
*
* This linker script will generate some code that expects to be executed from a
* fixed address in RAM. The corresponding instructions will be embedded in the
* main Epsilon ELF file, and copied to that address before execution.
*
* This address needs to live in RAM, and needs to be temporarily overwriteable
* when the program is being run. Epsilon has a large stack to allow deeply
* recursive code to run. But when doing DFU transfers it is safe to assume we
* will need very little stack space. We're therefore using the topmost 8K of
* the stack reserved by Epsilon.
*
* Last but not least, we'll want to jump to a known entry point when running
* the DFU code (namely, Ion::USB::Device::Calculator::Poll). We're simply
* making sure this is the first symbol output. */
EPSILON_STACK_END = 0x20000000 + 256K - 32K;
MEMORY {
RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 8K
}
SECTIONS {
.text : {
. = ALIGN(4);
KEEP(*(.dfu_entry_point))
*(.text)
*(.text.*)
} >RAM_BUFFER
.rodata : {
*(.rodata)
*(.rodata.*)
} >RAM_BUFFER
/DISCARD/ : {
/* For now, we do not need .bss and .data sections. This allows us to simply
* skip any rt0-style initialization and jump straight into the PollAndReset
* routine. */
*(.bss)
*(.bss.*)
*(.data)
*(.data.*)
}
}

View File

@@ -0,0 +1,278 @@
#include "dfu_interface.h"
#include <string.h>
#include <ion/src/device/flash.h>
namespace Ion {
namespace USB {
namespace Device {
static inline uint32_t min(uint32_t x, uint32_t y) { return (x<y ? x : y); }
void DFUInterface::StatusData::push(Channel * c) const {
c->push(m_bStatus);
c->push(m_bwPollTimeout[2]);
c->push(m_bwPollTimeout[1]);
c->push(m_bwPollTimeout[0]);
c->push(m_bState);
c->push(m_iString);
}
void DFUInterface::StateData::push(Channel * c) const {
c->push(m_bState);
}
void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {
if (request->bRequest() == (uint8_t) DFURequest::Download) {
// Handle a download request
if (request->wValue() == 0) {
// The request is a special command
switch (transferBuffer[0]) {
case (uint8_t) DFUDownloadCommand::SetAddressPointer:
setAddressPointerCommand(request, transferBuffer, *transferBufferLength);
return;
case (uint8_t) DFUDownloadCommand::Erase:
eraseCommand(transferBuffer, *transferBufferLength);
return;
default:
m_state = State::dfuERROR;
m_status = Status::errSTALLEDPKT;
return;
}
}
if (request->wValue() == 1) {
m_ep0->stallTransaction();
return;
}
if (request->wLength() > 0) {
// The request is a "real" download. Compute the writing address.
m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer;
// Store the received data until we copy it on the flash.
memcpy(m_largeBuffer, transferBuffer, *transferBufferLength);
m_largeBufferLength = *transferBufferLength;
m_state = State::dfuDNLOADSYNC;
}
}
}
void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {
if (request->bRequest() == (uint8_t) DFURequest::GetStatus) {
// Do any needed action after the GetStatus request.
if (m_state == State::dfuMANIFEST) {
// Leave DFU routine: Leave DFU, reset device, jump to application code
leaveDFUAndReset();
} else if (m_state == State::dfuDNBUSY) {
if (m_largeBufferLength != 0) {
// Here, copy the data from the transfer buffer to the flash memory
writeOnMemory();
}
changeAddressPointerIfNeeded();
eraseMemoryIfNeeded();
m_state = State::dfuDNLOADIDLE;
}
}
}
bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) {
return true;
}
switch (request->bRequest()) {
case (uint8_t) DFURequest::Detach:
m_device->detach();
return true;
case (uint8_t) DFURequest::Download:
return processDownloadRequest(request->wLength(), transferBufferLength);
case (uint8_t) DFURequest::Upload:
return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
case (uint8_t) DFURequest::GetStatus:
return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
case (uint8_t) DFURequest::ClearStatus:
return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
case (uint8_t) DFURequest::GetState:
return getState(transferBuffer, transferBufferLength, transferBufferMaxLength);
case (uint8_t) DFURequest::Abort:
return dfuAbort(transferBufferLength);
}
return false;
}
bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) {
if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) {
m_state = State::dfuERROR;
m_status = Status::errNOTDONE;
m_ep0->stallTransaction();
return false;
}
if (wLength == 0) {
// Leave DFU routine: Reset the device and jump to application code
m_state = State::dfuMANIFESTSYNC;
} else {
// Prepare to receive the download data
m_ep0->clearForOutTransactions(wLength);
m_state = State::dfuDNLOADSYNC;
}
return true;
}
bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) {
m_ep0->stallTransaction();
return false;
}
if (request->wValue() == 0) {
/* The host requests to read the commands supported by the bootloader. After
* receiving this command, the device should returns N bytes representing
* the command codes for :
* Get command / Set Address Pointer / Erase / Read Unprotect
* We no not need it for now. */
return false;
} else if (request->wValue() == 1) {
m_ep0->stallTransaction();
return false;
} else {
/* We decided to never protect Read operation. Else we would have to check
* here it is not protected before reading. */
// Compute the reading address
uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer;
// Copy the requested memory zone into the transfer buffer.
uint16_t copySize = min(transferBufferMaxLength, request->wLength());
memcpy(transferBuffer, (void *)readAddress, copySize);
*transferBufferLength = copySize;
}
m_state = State::dfuUPLOADIDLE;
return true;
}
void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) {
assert(transferBufferLength == 5);
// Compute the new address but change it after the next getStatus request.
m_potentialNewAddressPointer = transferBuffer[1]
+ (transferBuffer[2] << 8)
+ (transferBuffer[3] << 16)
+ (transferBuffer[4] << 24);
m_state = State::dfuDNLOADSYNC;
}
void DFUInterface::changeAddressPointerIfNeeded() {
if (m_potentialNewAddressPointer == 0) {
// There was no address change waiting.
return;
}
// If there is a new address pointer waiting, change the pointer address.
m_addressPointer = m_potentialNewAddressPointer;
m_potentialNewAddressPointer = 0;
m_state = State::dfuDNLOADIDLE;
m_status = Status::OK;
}
void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) {
/* We determine whether the commands asks for a mass erase or which sector to
* erase. The erase must be done after the next getStatus request. */
m_state = State::dfuDNLOADSYNC;
if (transferBufferLength == 1) {
// Mass erase
m_erasePage = Flash::Device::NumberOfSectors;
return;
}
// Sector erase
assert(transferBufferLength == 5);
uint32_t eraseAddress = transferBuffer[1]
+ (transferBuffer[2] << 8)
+ (transferBuffer[3] << 16)
+ (transferBuffer[4] << 24);
m_erasePage = Flash::Device::SectorAtAddress(eraseAddress);
if (m_erasePage < 0) {
// Unrecognized sector
m_state = State::dfuERROR;
m_status = Status::errTARGET;
}
}
void DFUInterface::eraseMemoryIfNeeded() {
if (m_erasePage < 0) {
// There was no erase waiting.
return;
}
if (m_erasePage == Ion::Flash::Device::NumberOfSectors) {
Flash::Device::MassErase();
} else {
Flash::Device::EraseSector(m_erasePage);
}
/* Put an out of range value in m_erasePage to indicate that no erase is
* waiting. */
m_erasePage = -1;
m_state = State::dfuDNLOADIDLE;
m_status = Status::OK;
}
void DFUInterface::writeOnMemory() {
if (m_writeAddress >= k_flashStartAddress && m_writeAddress <= k_flashEndAddress) {
// Write to the Flash memory
Flash::Device::WriteMemory(m_largeBuffer, reinterpret_cast<uint8_t *>(m_writeAddress), m_largeBufferLength);
} else if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) {
// Write on SRAM
// FIXME We should check that we are not overriding the current instructions.
memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength);
} else {
// Invalid write address
m_largeBufferLength = 0;
m_state = State::dfuERROR;
m_status = Status::errTARGET;
return;
}
// Reset the buffer length
m_largeBufferLength = 0;
// Change the interface state and status
m_state = State::dfuDNLOADIDLE;
m_status = Status::OK;
}
bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
// Change the status if needed
if (m_state == State::dfuMANIFESTSYNC) {
m_state = State::dfuMANIFEST;
} else if (m_state == State::dfuDNLOADSYNC) {
m_state = State::dfuDNBUSY;
}
// Copy the status on the TxFifo
*transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength);
return true;
}
bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) {
m_status = Status::OK;
m_state = State::dfuIDLE;
return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength);
}
bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) {
*transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize);
return true;
}
bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) {
m_status = Status::OK;
m_state = State::dfuIDLE;
*transferBufferLength = 0;
return true;
}
void DFUInterface::leaveDFUAndReset() {
m_device->setResetOnDisconnect(true);
m_device->detach();
}
}
}
}

View File

@@ -0,0 +1,172 @@
#ifndef ION_DEVICE_USB_DFU_INTERFACE_H
#define ION_DEVICE_USB_DFU_INTERFACE_H
#include <assert.h>
#include <stddef.h>
#include "stack/device.h"
#include "stack/interface.h"
#include "stack/endpoint0.h"
#include "stack/setup_packet.h"
#include "stack/streamable.h"
namespace Ion {
namespace USB {
namespace Device {
class DFUInterface : public Interface {
public:
DFUInterface(Device * device, Endpoint0 * ep0, uint8_t bInterfaceAlternateSetting) :
Interface(ep0),
m_device(device),
m_status(Status::OK),
m_state(State::dfuIDLE),
m_addressPointer(0),
m_potentialNewAddressPointer(0),
m_erasePage(-1),
m_largeBuffer{0},
m_largeBufferLength(0),
m_writeAddress(0),
m_bInterfaceAlternateSetting(bInterfaceAlternateSetting)
{
}
void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override;
void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override;
protected:
void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override {
assert(interfaceAlternativeIndex == m_bInterfaceAlternateSetting);
}
uint8_t getActiveInterfaceAlternative() override {
return m_bInterfaceAlternateSetting;
}
bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override;
private:
// DFU Request Codes
enum class DFURequest {
Detach = 0,
Download = 1,
Upload = 2,
GetStatus = 3,
ClearStatus = 4,
GetState = 5,
Abort = 6
};
// DFU Download Commmand Codes
enum class DFUDownloadCommand {
GetCommand = 0x00,
SetAddressPointer = 0x21,
Erase = 0x41,
ReadUnprotect = 0x92
};
enum class Status : uint8_t {
OK = 0x00,
errTARGET = 0x01,
errFILE = 0x02,
errWRITE = 0x03,
errERASE = 0x04,
errCHECK_ERASED = 0x05,
errPROG = 0x06,
errVERIFY = 0x07,
errADDRESS = 0x08,
errNOTDONE = 0x09,
errFIRMWARE = 0x0A,
errVENDOR = 0x0B,
errUSBR = 0x0C,
errPOR = 0x0D,
errUNKNOWN = 0x0E,
errSTALLEDPKT = 0x0F
};
enum class State : uint8_t {
appIDLE = 0,
appDETACH = 1,
dfuIDLE = 2,
dfuDNLOADSYNC = 3,
dfuDNBUSY = 4,
dfuDNLOADIDLE = 5,
dfuMANIFESTSYNC = 6,
dfuMANIFEST = 7,
dfuMANIFESTWAITRESET = 8,
dfuUPLOADIDLE = 9,
dfuERROR = 10
};
class StatusData : public Streamable {
public:
StatusData(Status status, State state, uint32_t pollTimeout = 1) :
/* We put a default pollTimeout value of 1ms: if the device is busy, the
* host has to wait 1ms before sending a getStatus Request. */
m_bStatus((uint8_t)status),
m_bwPollTimeout{uint8_t((pollTimeout>>16) & 0xFF), uint8_t((pollTimeout>>8) & 0xFF), uint8_t(pollTimeout & 0xFF)},
m_bState((uint8_t)state),
m_iString(0)
{
}
protected:
void push(Channel * c) const override;
private:
uint8_t m_bStatus; // Status resulting from the execution of the most recent request
uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits
uint8_t m_bState; // State of the device immediately following transmission of this response
uint8_t m_iString;
};
class StateData : public Streamable {
public:
StateData(State state) : m_bState((uint8_t)state) {}
protected:
void push(Channel * c) const override;
private:
uint8_t m_bState; // Current state of the device
};
/* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is
* linked with dfu.ld, so we cannot access the values. */
constexpr static uint32_t k_flashStartAddress = 0x08000000;
constexpr static uint32_t k_flashEndAddress = 0x08100000;
constexpr static uint32_t k_sramStartAddress = 0x20000000;
constexpr static uint32_t k_sramEndAddress = 0x20040000;
// Download and upload
bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength);
bool processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength);
// Address pointer
void setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength);
void changeAddressPointerIfNeeded();
// Access memory
void eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength);
void eraseMemoryIfNeeded();
void writeOnMemory();
void unlockFlashMemory();
void lockFlashMemoryAndPurgeCaches();
// Status
bool getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength);
bool clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength);
// State
bool getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize);
// Abort
bool dfuAbort(uint16_t * transferBufferLength);
// Leave DFU
void leaveDFUAndReset();
Device * m_device;
Status m_status;
State m_state;
uint32_t m_addressPointer;
uint32_t m_potentialNewAddressPointer;
int32_t m_erasePage;
uint8_t m_largeBuffer[Endpoint0::MaxTransferSize];
uint16_t m_largeBufferLength;
uint32_t m_writeAddress;
uint8_t m_bInterfaceAlternateSetting;
};
}
}
}
#endif

View File

@@ -0,0 +1,80 @@
#include <ion/usb.h>
#include <string.h>
#include <assert.h>
#include <ion/src/device/device.h>
extern char _stack_end;
extern char _dfu_bootloader_flash_start;
extern char _dfu_bootloader_flash_end;
namespace Ion {
namespace USB {
typedef void (*PollFunctionPointer)(bool exitWithKeyboard);
void DFU() {
/* DFU transfers can serve two purposes:
* - Transfering RAM data between the machine and a host, e.g. Python scripts
* - Upgrading the flash memory to perform a software update
*
* The second case raises a huge issue: code cannot be executed from memory
* that is being modified. We're solving this issue by copying the DFU code in
* RAM.
*
* The new DFU address in RAM needs to be temporarily overwriteable when the
* program is being run. Epsilon has a large stack to allow deeply recursive
* code to run, but when doing DFU transfers it is safe to assume we will need
* very little stack space. We're therefore using the topmost 8K of the stack
* reserved by Epsilon. */
/* 1- The stack being in reverse order, the end of the stack will be the
* beginning of the DFU bootloader copied in RAM. */
size_t dfu_bootloader_size = &_dfu_bootloader_flash_end - &_dfu_bootloader_flash_start;
char * dfu_bootloader_ram_start = reinterpret_cast<char *>(&_stack_end);
assert(&_stack_end == (void *)(0x20000000 + 256*1024 - 32*1024));
/* 2- Verify there is enough free space on the stack to copy the DFU code. */
char foo;
char * stackPointer = &foo;
if (dfu_bootloader_ram_start + dfu_bootloader_size > stackPointer) {
// There is not enough room on the stack to copy the DFU bootloader.
return;
}
/* 3- Copy the DFU bootloader from Flash to RAM. */
memcpy(dfu_bootloader_ram_start, &_dfu_bootloader_flash_start, dfu_bootloader_size);
/* 4- Disable all interrupts
* The interrupt service routines live in the Flash and could be overwritten
* by garbage during a firmware upgrade opration, so we disable them. */
Device::shutdownSysTick();
/* 5- Jump to DFU bootloader code. We made sure in the linker script that the
* first function we want to call is at the beginning of the DFU code. */
PollFunctionPointer dfu_bootloader_entry = reinterpret_cast<PollFunctionPointer>(dfu_bootloader_ram_start);
/* To have the right debug symbols for the reallocated code, break here and:
* - Get the address of the new .text section
* In a terminal: arm-none-eabi-readelf -a ion/src/device/usb/dfu.elf
* - Delete the current symbol table
* symbol-file
* - Add the new symbol table, with the address of the new .text section
* add-symbol-file ion/src/device/usb/dfu.elf 0x20038000
*/
dfu_bootloader_entry(true);
/* 5- Restore interrupts */
Device::initSysTick();
/* 6- That's all. The DFU bootloader on the stack is now dead code that will
* be overwritten when the stack grows. */
}
}
}

View File

@@ -0,0 +1,12 @@
#include "calculator.h"
#include "../device.h"
namespace Ion {
namespace USB {
void DFU() {
Ion::USB::Device::Calculator::PollAndReset(true);
}
}
}

View File

@@ -0,0 +1,13 @@
#include "../regs/regs.h"
#include "../usb/calculator.h"
#include <ion.h>
void ion_main(int argc, char * argv[]) {
Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00));
while (true) {
Ion::USB::enable();
while (!Ion::USB::isEnumerated()) {
}
Ion::USB::Device::Calculator::PollAndReset(false);
}
}

View File

@@ -0,0 +1,77 @@
/* Create a USB DFU firmware that runs from RAM.
* Flashing using ST's ROMed DFU bootloader is reliable but very slow. To make
* flashing faster, we can leverage ST's bootloader to copy a small "flasher" in
* RAM, and run it from there.
* Caution: ST's bootloader uses some RAM, so we want to stay off of that memory
* region. Per AN2606, section 47, it's using 0x20003000 - 0x2003FFFF; We'll try
* to play safe and avoid the first 32KB of RAM. */
MEMORY {
RAM_BUFFER (rw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K
}
/* The stack is quite large, and should really be equal to Epsilon's. Indeed,
* we're making the Calculator object live on the stack, and it's quite large
* (about 4K just for this single object). Using a stack too small will result
* in some memory being overwritten (for instance, vtables that live in the
* .rodata section). */
STACK_SIZE = 32K;
SECTIONS {
.isr_vector_table ORIGIN(RAM_BUFFER) : {
KEEP(*(.isr_vector_table))
} >RAM_BUFFER
.text : {
. = ALIGN(4);
*(.text)
*(.text.*)
} >RAM_BUFFER
.init_array : {
. = ALIGN(4);
_init_array_start = .;
KEEP (*(.init_array*))
_init_array_end = .;
} >RAM_BUFFER
.rodata : {
. = ALIGN(4);
*(.rodata)
*(.rodata.*)
} >RAM_BUFFER
.data : {
. = ALIGN(4);
*(.data)
*(.data.*)
} >RAM_BUFFER
.bss : {
. = ALIGN(4);
_bss_section_start_ram = .;
*(.bss)
*(.bss.*)
*(COMMON)
_bss_section_end_ram = .;
} >RAM_BUFFER
.stack : {
. = ALIGN(8);
_stack_end = .;
. += (STACK_SIZE - 8);
. = ALIGN(8);
_stack_start = .;
} >RAM_BUFFER
.phony : {
/* We won't do dynamic memory allocation */
_heap_start = .;
_heap_end = .;
/* Effectively bypass copying .data to RAM */
_data_section_start_flash = .;
_data_section_start_ram = .;
_data_section_end_ram = .;
} >RAM_BUFFER
}

View File

@@ -0,0 +1,22 @@
#include "bos_descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
void BOSDescriptor::push(Channel * c) const {
Descriptor::push(c);
c->push(m_wTotalLength);
c->push(m_bNumDeviceCaps);
for (uint8_t i = 0; i < m_bNumDeviceCaps; i++) {
m_deviceCapabilities[i].push(c);
}
}
uint8_t BOSDescriptor::bLength() const {
return Descriptor::bLength() + sizeof(uint16_t) + sizeof(uint8_t);
}
}
}
}

View File

@@ -0,0 +1,36 @@
#ifndef ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H
#define ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H
#include "descriptor.h"
#include "device_capability_descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
class BOSDescriptor : public Descriptor {
public:
constexpr BOSDescriptor(
uint16_t wTotalLength,
uint8_t bNumDeviceCapabilities,
const DeviceCapabilityDescriptor * deviceCapabilities) :
Descriptor(0x0F),
m_wTotalLength(wTotalLength),
m_bNumDeviceCaps(bNumDeviceCapabilities),
m_deviceCapabilities(deviceCapabilities)
{
}
protected:
void push(Channel * c) const override;
virtual uint8_t bLength() const override;
private:
uint16_t m_wTotalLength;
uint8_t m_bNumDeviceCaps;
const DeviceCapabilityDescriptor * m_deviceCapabilities;
};
}
}
}
#endif

View File

@@ -0,0 +1,26 @@
#include "configuration_descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
void ConfigurationDescriptor::push(Channel * c) const {
Descriptor::push(c);
c->push(m_wTotalLength);
c->push(m_bNumInterfaces);
c->push(m_bConfigurationValue);
c->push(m_iConfiguration);
c->push(m_bmAttributes);
c->push(m_bMaxPower);
for (uint8_t i = 0; i < m_bNumInterfaces; i++) {
m_interfaces[i].push(c);
}
}
uint8_t ConfigurationDescriptor::bLength() const {
return Descriptor::bLength() + sizeof(uint16_t) + 5*sizeof(uint8_t);
}
}
}
}

View File

@@ -0,0 +1,48 @@
#ifndef ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H
#define ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H
#include "descriptor.h"
#include "interface_descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
class ConfigurationDescriptor : public Descriptor {
public:
constexpr ConfigurationDescriptor(
uint16_t wTotalLength,
uint8_t bNumInterfaces,
uint8_t bConfigurationValue,
uint8_t iConfiguration,
uint8_t bmAttributes,
uint8_t bMaxPower,
const InterfaceDescriptor * interfaces) :
Descriptor(0x02),
m_wTotalLength(wTotalLength),
m_bNumInterfaces(bNumInterfaces),
m_bConfigurationValue(bConfigurationValue),
m_iConfiguration(iConfiguration),
m_bmAttributes(bmAttributes),
m_bMaxPower(bMaxPower),
m_interfaces(interfaces)
{
}
protected:
void push(Channel * c) const override;
virtual uint8_t bLength() const override;
private:
uint16_t m_wTotalLength;
uint8_t m_bNumInterfaces;
uint8_t m_bConfigurationValue;
uint8_t m_iConfiguration;
uint8_t m_bmAttributes;
uint8_t m_bMaxPower;
const InterfaceDescriptor * m_interfaces;
};
}
}
}
#endif

View File

@@ -0,0 +1,15 @@
#include "descriptor.h"
#include <string.h>
namespace Ion {
namespace USB {
namespace Device {
void Descriptor::push(Channel * c) const {
c->push(bLength());
c->push(m_bDescriptorType);
}
}
}
}

View File

@@ -0,0 +1,32 @@
#ifndef ION_DEVICE_USB_STACK_DESCRIPTOR_H
#define ION_DEVICE_USB_STACK_DESCRIPTOR_H
#include "../streamable.h"
namespace Ion {
namespace USB {
namespace Device {
class InterfaceDescriptor;
class Descriptor : public Streamable {
friend class InterfaceDescriptor;
public:
constexpr Descriptor(uint8_t bDescriptorType) :
m_bDescriptorType(bDescriptorType)
{
}
uint8_t type() const { return m_bDescriptorType; }
protected:
void push(Channel * c) const override;
virtual uint8_t bLength() const { return 2*sizeof(uint8_t); }
private:
uint8_t m_bDescriptorType;
};
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
#include "device_capability_descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
void DeviceCapabilityDescriptor::push(Channel * c) const {
Descriptor::push(c);
c->push(m_bDeviceCapabilityType);
}
uint8_t DeviceCapabilityDescriptor::bLength() const {
return Descriptor::bLength() + sizeof(uint8_t);
}
}
}
}

View File

@@ -0,0 +1,31 @@
#ifndef ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H
#define ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H
#include "descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
class BOSDescriptor;
class DeviceCapabilityDescriptor : public Descriptor {
friend class BOSDescriptor;
public:
constexpr DeviceCapabilityDescriptor(uint8_t bDeviceCapabilityType) :
Descriptor(0x10),
m_bDeviceCapabilityType(bDeviceCapabilityType)
{
}
protected:
void push(Channel * c) const override;
virtual uint8_t bLength() const override;
private:
uint8_t m_bDeviceCapabilityType;
};
}
}
}
#endif

View File

@@ -0,0 +1,29 @@
#include "device_descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
void DeviceDescriptor::push(Channel * c) const {
Descriptor::push(c);
c->push(m_bcdUSB);
c->push(m_bDeviceClass);
c->push(m_bDeviceSubClass);
c->push(m_bDeviceProtocol);
c->push(m_bMaxPacketSize0);
c->push(m_idVendor);
c->push(m_idProduct);
c->push(m_bcdDevice);
c->push(m_iManufacturer);
c->push(m_iProduct);
c->push(m_iSerialNumber);
c->push(m_bNumConfigurations);
}
uint8_t DeviceDescriptor::bLength() const {
return Descriptor::bLength() + sizeof(uint16_t) + 4*sizeof(uint8_t) + 3*sizeof(uint16_t) + 4*sizeof(uint8_t);
}
}
}
}

View File

@@ -0,0 +1,62 @@
#ifndef ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H
#define ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H
#include "descriptor.h"
namespace Ion {
namespace USB {
namespace Device {
class DeviceDescriptor : public Descriptor {
public:
constexpr DeviceDescriptor(
uint16_t bcdUSB,
uint8_t bDeviceClass,
uint8_t bDeviceSubClass,
uint8_t bDeviceProtocol,
uint8_t bMaxPacketSize0,
uint16_t idVendor,
uint16_t idProduct,
uint16_t bcdDevice,
uint8_t iManufacturer,
uint8_t iProduct,
uint8_t iSerialNumber,
uint8_t bNumConfigurations) :
Descriptor(0x01),
m_bcdUSB(bcdUSB),
m_bDeviceClass(bDeviceClass),
m_bDeviceSubClass(bDeviceSubClass),
m_bDeviceProtocol(bDeviceProtocol),
m_bMaxPacketSize0(bMaxPacketSize0),
m_idVendor(idVendor),
m_idProduct(idProduct),
m_bcdDevice(bcdDevice),
m_iManufacturer(iManufacturer),
m_iProduct(iProduct),
m_iSerialNumber(iSerialNumber),
m_bNumConfigurations(bNumConfigurations)
{
}
protected:
void push(Channel * c) const override;
virtual uint8_t bLength() const override;
private:
uint16_t m_bcdUSB;
uint8_t m_bDeviceClass;
uint8_t m_bDeviceSubClass;
uint8_t m_bDeviceProtocol;
uint8_t m_bMaxPacketSize0;
uint16_t m_idVendor;
uint16_t m_idProduct;
uint16_t m_bcdDevice;
uint8_t m_iManufacturer;
uint8_t m_iProduct;
uint8_t m_iSerialNumber;
uint8_t m_bNumConfigurations;
};
}
}
}
#endif

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