mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-03-24 00:00:44 +01:00
[ion] Initial add of the f730 folder
This commit is contained in:
committed by
Ruben Dashyan
parent
85e08f500d
commit
b547f8bfd9
51
ion/src/f730/Makefile
Normal file
51
ion/src/f730/Makefile
Normal 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)
|
||||
85
ion/src/f730/backlight.cpp
Normal file
85
ion/src/f730/backlight.cpp
Normal 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
28
ion/src/f730/backlight.h
Normal 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
48
ion/src/f730/base64.cpp
Normal 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
5
ion/src/f730/base64.h
Normal 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
88
ion/src/f730/battery.cpp
Normal 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
35
ion/src/f730/battery.h
Normal 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
|
||||
21
ion/src/f730/bench/Makefile
Normal file
21
ion/src/f730/bench/Makefile
Normal 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 \
|
||||
)
|
||||
46
ion/src/f730/bench/bench.cpp
Normal file
46
ion/src/f730/bench/bench.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
14
ion/src/f730/bench/bench.h
Normal file
14
ion/src/f730/bench/bench.h
Normal 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
|
||||
27
ion/src/f730/bench/command/adc.cpp
Normal file
27
ion/src/f730/bench/command/adc.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
ion/src/f730/bench/command/backlight.cpp
Normal file
34
ion/src/f730/bench/command/backlight.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
ion/src/f730/bench/command/charge.cpp
Normal file
24
ion/src/f730/bench/command/charge.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
ion/src/f730/bench/command/command.cpp
Normal file
47
ion/src/f730/bench/command/command.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
ion/src/f730/bench/command/command.h
Normal file
42
ion/src/f730/bench/command/command.h
Normal 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
|
||||
74
ion/src/f730/bench/command/display.cpp
Normal file
74
ion/src/f730/bench/command/display.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
ion/src/f730/bench/command/exit.cpp
Normal file
19
ion/src/f730/bench/command/exit.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
ion/src/f730/bench/command/keyboard.cpp
Normal file
26
ion/src/f730/bench/command/keyboard.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
ion/src/f730/bench/command/led.cpp
Normal file
35
ion/src/f730/bench/command/led.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
ion/src/f730/bench/command/mcu_serial.cpp
Normal file
23
ion/src/f730/bench/command/mcu_serial.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
ion/src/f730/bench/command/ping.cpp
Normal file
19
ion/src/f730/bench/command/ping.cpp
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
ion/src/f730/bench/command/print.cpp
Normal file
30
ion/src/f730/bench/command/print.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ion/src/f730/bench/command/suspend.cpp
Normal file
22
ion/src/f730/bench/command/suspend.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
ion/src/f730/bench/command/vblank.cpp
Normal file
26
ion/src/f730/bench/command/vblank.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
ion/src/f730/bench/command_handler.cpp
Normal file
44
ion/src/f730/bench/command_handler.cpp
Normal 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++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
27
ion/src/f730/bench/command_handler.h
Normal file
27
ion/src/f730/bench/command_handler.h
Normal 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
|
||||
22
ion/src/f730/bench/command_list.cpp
Normal file
22
ion/src/f730/bench/command_list.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ion/src/f730/bench/command_list.h
Normal file
22
ion/src/f730/bench/command_list.h
Normal 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
|
||||
2
ion/src/f730/boot/Makefile
Normal file
2
ion/src/f730/boot/Makefile
Normal 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
112
ion/src/f730/boot/flash.ld
Normal 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
129
ion/src/f730/boot/isr.c
Normal 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
16
ion/src/f730/boot/isr.h
Normal 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
97
ion/src/f730/boot/rt0.cpp
Normal 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
76
ion/src/f730/console.cpp
Normal 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
30
ion/src/f730/console.h
Normal 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
325
ion/src/f730/device.cpp
Normal 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
36
ion/src/f730/device.h
Normal 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
376
ion/src/f730/display.cpp
Normal 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
110
ion/src/f730/display.h
Normal 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
93
ion/src/f730/events.cpp
Normal 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
233
ion/src/f730/flash.cpp
Normal 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
28
ion/src/f730/flash.h
Normal 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
112
ion/src/f730/keyboard.cpp
Normal 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
70
ion/src/f730/keyboard.h
Normal 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
133
ion/src/f730/led.cpp
Normal 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
48
ion/src/f730/led.h
Normal 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
15
ion/src/f730/log.cpp
Normal 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
69
ion/src/f730/power.cpp
Normal 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
73
ion/src/f730/regs/adc.h
Normal 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
81
ion/src/f730/regs/cm4.h
Normal 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
27
ion/src/f730/regs/crc.h
Normal 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
63
ion/src/f730/regs/dma.h
Normal 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
34
ion/src/f730/regs/exti.h
Normal 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
56
ion/src/f730/regs/flash.h
Normal 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
79
ion/src/f730/regs/fsmc.h
Normal 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
104
ion/src/f730/regs/gpio.h
Normal 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
32
ion/src/f730/regs/itm.h
Normal 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
71
ion/src/f730/regs/mpu.h
Normal 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
37
ion/src/f730/regs/nvic.h
Normal 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
195
ion/src/f730/regs/otg.h
Normal 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
25
ion/src/f730/regs/pwr.h
Normal 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
149
ion/src/f730/regs/rcc.h
Normal 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
|
||||
61
ion/src/f730/regs/register.h
Normal file
61
ion/src/f730/regs/register.h
Normal 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
25
ion/src/f730/regs/regs.h
Normal 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
33
ion/src/f730/regs/rng.h
Normal 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
112
ion/src/f730/regs/sdio.h
Normal 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
45
ion/src/f730/regs/spi.h
Normal 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
|
||||
44
ion/src/f730/regs/syscfg.h
Normal file
44
ion/src/f730/regs/syscfg.h
Normal 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
100
ion/src/f730/regs/tim.h
Normal 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
50
ion/src/f730/regs/usart.h
Normal 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
95
ion/src/f730/sd_card.cpp
Normal 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
33
ion/src/f730/sd_card.h
Normal 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
10
ion/src/f730/stack.cpp
Normal 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
24
ion/src/f730/swd.cpp
Normal 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
28
ion/src/f730/swd.h
Normal 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
36
ion/src/f730/timing.cpp
Normal 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
16
ion/src/f730/timing.h
Normal 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
187
ion/src/f730/usb.cpp
Normal 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
34
ion/src/f730/usb.h
Normal 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
67
ion/src/f730/usb/Makefile
Normal 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
|
||||
2
ion/src/f730/usb/boot.cpp
Normal file
2
ion/src/f730/usb/boot.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
extern "C" void abort() {
|
||||
}
|
||||
94
ion/src/f730/usb/calculator.cpp
Normal file
94
ion/src/f730/usb/calculator.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
166
ion/src/f730/usb/calculator.h
Normal file
166
ion/src/f730/usb/calculator.h
Normal 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
50
ion/src/f730/usb/dfu.ld
Normal 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.*)
|
||||
}
|
||||
}
|
||||
278
ion/src/f730/usb/dfu_interface.cpp
Normal file
278
ion/src/f730/usb/dfu_interface.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
172
ion/src/f730/usb/dfu_interface.h
Normal file
172
ion/src/f730/usb/dfu_interface.h
Normal 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
|
||||
80
ion/src/f730/usb/dfu_relocated.cpp
Normal file
80
ion/src/f730/usb/dfu_relocated.cpp
Normal 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. */
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
12
ion/src/f730/usb/dfu_xip.cpp
Normal file
12
ion/src/f730/usb/dfu_xip.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "calculator.h"
|
||||
#include "../device.h"
|
||||
|
||||
namespace Ion {
|
||||
namespace USB {
|
||||
|
||||
void DFU() {
|
||||
Ion::USB::Device::Calculator::PollAndReset(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
13
ion/src/f730/usb/flasher.cpp
Normal file
13
ion/src/f730/usb/flasher.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
77
ion/src/f730/usb/flasher.ld
Normal file
77
ion/src/f730/usb/flasher.ld
Normal 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
|
||||
}
|
||||
22
ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp
Normal file
22
ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
36
ion/src/f730/usb/stack/descriptor/bos_descriptor.h
Normal file
36
ion/src/f730/usb/stack/descriptor/bos_descriptor.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
48
ion/src/f730/usb/stack/descriptor/configuration_descriptor.h
Normal file
48
ion/src/f730/usb/stack/descriptor/configuration_descriptor.h
Normal 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
|
||||
15
ion/src/f730/usb/stack/descriptor/descriptor.cpp
Normal file
15
ion/src/f730/usb/stack/descriptor/descriptor.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
32
ion/src/f730/usb/stack/descriptor/descriptor.h
Normal file
32
ion/src/f730/usb/stack/descriptor/descriptor.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
29
ion/src/f730/usb/stack/descriptor/device_descriptor.cpp
Normal file
29
ion/src/f730/usb/stack/descriptor/device_descriptor.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
62
ion/src/f730/usb/stack/descriptor/device_descriptor.h
Normal file
62
ion/src/f730/usb/stack/descriptor/device_descriptor.h
Normal 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
Reference in New Issue
Block a user