diff --git a/apps/hwtest/lowlevel/Makefile b/apps/hwtest/lowlevel/Makefile new file mode 100644 index 000000000..85e1b6960 --- /dev/null +++ b/apps/hwtest/lowlevel/Makefile @@ -0,0 +1,5 @@ +objs += $(addprefix apps/hwtest/lowlevel/,\ + lowlevel.o \ +) + +apps/hwtest/lowlevel/lowlevel.o: SFLAGS += -O3 diff --git a/apps/hwtest/lowlevel/lowlevel.cpp b/apps/hwtest/lowlevel/lowlevel.cpp new file mode 100644 index 000000000..ff466c7cb --- /dev/null +++ b/apps/hwtest/lowlevel/lowlevel.cpp @@ -0,0 +1,195 @@ +#include +#include +#include + +typedef void (*CommandFunction)(const char * input); + +void command_ping(const char * input); +void command_mcu_serial(const char * input); + +class CommandHandler { +public: + constexpr CommandHandler(const char * name, CommandFunction function) : + m_name(name), m_function(function) {} + bool valid() const; + bool handle(const char * command) const; +private: + bool matches(const char * command) const; + const char * m_name; + CommandFunction m_function; +}; + +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++; + } +} + +class CommandList { +public: + constexpr CommandList(const CommandHandler * handlers) : m_handlers(handlers) {} + void dispatch(const char * command) const; +private: + const CommandHandler * m_handlers; +}; + +void CommandList::dispatch(const char * command) const { + const CommandHandler * handler = m_handlers; + while (handler->valid()) { + if (handler->handle(command)) { + return; + } + handler++; + } + Ion::Console::writeLine("NOT_FOUND"); +} + +static const char * sSyntaxError = "SYNTAX_ERROR"; + +void command_ping(const char * input) { + if (input != nullptr) { + Ion::Console::writeLine(sSyntaxError); + return; + } + Ion::Console::writeLine("PONG"); +} + +void command_mcu_serial(const char * input) { + if (input != nullptr) { + Ion::Console::writeLine(sSyntaxError); + return; + } + char response[11+24+1] = {'M', 'C', 'U', '_', 'S', 'E', 'R', 'I', 'A', 'L', '=', 0}; + strlcpy(response+11, Ion::serialNumber(), 25); + Ion::Console::writeLine(response); +} + +static inline int8_t hexChar(char c) { + if (c >= '0' && c <= '9') { + return (c - '0'); + } + if (c >= 'A' && c <= 'F') { + return (c - 'A') + 0xA; + } + return -1; +} +static inline bool isHex(char c) { return hexChar(c) >= 0; } +static inline uint32_t hexNumber(const char * s) { + uint32_t result = 0; + while (*s != NULL) { + result = (result << 4) | hexChar(*s++); + } + return result; +} + +void command_led(const char * input) { + // Input must be of the form "0xAABBCC" + 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("OK"); +} + +void command_backlight(const char * input) { + // Input must be of the form "0xAA" + if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || input[4] != NULL) { + Ion::Console::writeLine(sSyntaxError); + return; + } + uint32_t brightness = hexNumber(input+2); + Ion::Backlight::setBrightness(brightness); + Ion::Console::writeLine("OK"); +} + +void command_adc(const char * input) { + if (input != nullptr) { + Ion::Console::writeLine(sSyntaxError); + return; + } + float result = Ion::Battery::voltage(); + constexpr int precision = 8; + constexpr int bufferSize = Poincare::Complex::bufferSizeForFloatsWithPrecision(precision); + char responseBuffer[bufferSize+4] = {'A', 'D', 'C', '='}; // ADC= + Poincare::Complex::convertFloatToText(result, responseBuffer+4, bufferSize, precision); + Ion::Console::writeLine(responseBuffer); +} + +void command_charge(const char * input) { + if (input != nullptr) { + Ion::Console::writeLine(sSyntaxError); + return; + } + if (Ion::Battery::isCharging()) { + Ion::Console::writeLine("CHARGE=ON"); + } else { + Ion::Console::writeLine("CHARGE=OFF"); + } +} + +void command_keyboard(const char * input) { + if (input != nullptr) { + Ion::Console::writeLine(sSyntaxError); + return; + } + char result[9+Ion::Keyboard::NumberOfKeys+1] = { 'K', 'E', 'Y', 'B', 'O', 'A', 'R', 'D', '=' }; + for (uint8_t i=0; i #include #include +#include #include #include #include @@ -26,6 +27,8 @@ namespace Ion { void msleep(long ms); void usleep(long us); +const char * serialNumber(); + /* CAUTION: This is a complete reset! */ void reset(); diff --git a/ion/include/ion/console.h b/ion/include/ion/console.h new file mode 100644 index 000000000..4f9600999 --- /dev/null +++ b/ion/include/ion/console.h @@ -0,0 +1,15 @@ +#ifndef ION_CONSOLE_H +#define ION_CONSOLE_H + +namespace Ion { +namespace Console { + +// The lines are NULL-terminated + +void writeLine(const char * line); +void readLine(char * line, int maxLineLength); + +} +} + +#endif diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 986970daf..2a3870052 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -7,6 +7,7 @@ objs += $(addprefix ion/src/shared/, \ objs += $(addprefix ion/src/device/, \ backlight.o \ battery.o\ + console.o \ device.o\ display.o\ keyboard.o\ @@ -25,6 +26,7 @@ objs += $(addprefix ion/src/device/, \ 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 endif endif diff --git a/ion/src/device/battery.cpp b/ion/src/device/battery.cpp index 6f66d20c4..02a5fab94 100644 --- a/ion/src/device/battery.cpp +++ b/ion/src/device/battery.cpp @@ -50,6 +50,11 @@ void init() { // 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 shutdown() { diff --git a/ion/src/device/console.cpp b/ion/src/device/console.cpp new file mode 100644 index 000000000..435b69231 --- /dev/null +++ b/ion/src/device/console.cpp @@ -0,0 +1,81 @@ +#include +#include "console.h" + +/* This file implements a serial console. + * We use a 9600 8N1 serial port */ + +namespace Ion { +namespace Console { + +void writeLine(const char * line) { + while (*line != NULL) { + Device::sendChar(*line++); + } + Device::sendChar('\r'); + Device::sendChar('\n'); +} + +void readLine(char * line, int maxLineLength) { + if (maxLineLength <= 0) { + return; + } + char * cursor = line; + char * last = line+maxLineLength-1; + while (true) { + *cursor = Device::recvChar(); + if (*cursor == '\r' || cursor == last) { + *cursor = 0; + return; + } + cursor++; + } +} + +} +} + +namespace Ion { +namespace Console { +namespace Device { + +void init() { + RCC.APB2ENR()->setUSART1EN(true); + + GPIOB.MODER()->setMode(3, GPIO::MODER::Mode::AlternateFunction); + GPIOB.MODER()->setMode(6, GPIO::MODER::Mode::AlternateFunction); + + GPIOB.AFR()->setAlternateFunction(3, GPIO::AFR::AlternateFunction::AF7); + GPIOB.AFR()->setAlternateFunction(6, GPIO::AFR::AlternateFunction::AF7); + + UARTPort.CR1()->setUE(true); + UARTPort.CR1()->setTE(true); + UARTPort.CR1()->setRE(true); + + // Set the Baud rate + // base clock = 16 MHz + // Baud rate = fAPB2/(16*USARTDIV) + // USARTDIV = 104.16667 + // + // DIV_Fraction = 16*0.16666667 + // = 2.666667 -> 3 + // DIV_Mantissa = 104 = 0x68 + // USART_BRR = 0x683 + UARTPort.BRR()->setDIV_FRAC(3); + UARTPort.BRR()->setDIV_MANTISSA(104); +} + +char recvChar() { + while (UARTPort.SR()->getRXNE() == 0) { + } + return (char)UARTPort.DR()->get(); +} + +void sendChar(char c) { + while (UARTPort.SR()->getTXE() == 0) { + } + UARTPort.DR()->set(c); +} + +} +} +} diff --git a/ion/src/device/console.h b/ion/src/device/console.h new file mode 100644 index 000000000..107fdf5ce --- /dev/null +++ b/ion/src/device/console.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_CONSOLE_H +#define ION_DEVICE_CONSOLE_H + +#include +#include "regs/regs.h" + +namespace Ion { +namespace Console { +namespace Device { + +/* Pin | Role | Mode + * -----+-------------------+-------------------- + * PC11 | UART1 RX | Alternate Function + * PD8 | UART1 TX | Alternate Function + */ + +void init(); +void shutdown(); + +constexpr USART UARTPort = USART(1); +constexpr static GPIOPin RxPin = GPIOPin(GPIOB, 14); +constexpr static GPIOPin TxPin = GPIOPin(GPIOE, 9); + +void sendChar(char c); +char recvChar(); + +} +} +} + +#endif diff --git a/ion/src/device/device.cpp b/ion/src/device/device.cpp index c87a2c142..960c17302 100644 --- a/ion/src/device/device.cpp +++ b/ion/src/device/device.cpp @@ -10,6 +10,7 @@ extern "C" { #include "battery.h" #include "sd_card.h" #include "backlight.h" +#include "console.h" #define USE_SD_CARD 0 @@ -61,6 +62,27 @@ void Ion::reset() { CM4.AIRCR()->requestReset(); } +static inline char hex(uint8_t d) { + if (d > 9) { + return 'A'+d; + } + return '0'+d; +} + +const char * Ion::serialNumber() { + static char serialNumber[25] = {0}; + if (serialNumber[0] == 0) { + uint8_t * rawUniqueID = (uint8_t *)0x1FFF7A10; + for (int i=0; i<24; i++) { + uint8_t d = *rawUniqueID++; + serialNumber[2*i] = hex(d & 0xF); + serialNumber[2*i+1] = hex(d >> 4); + } + serialNumber[24] = 0; + } + return serialNumber; +} + // Private Ion::Device methods namespace Ion { @@ -101,6 +123,7 @@ void initPeripherals() { #if USE_SD_CARD SDCard::Device::init(); #endif + Console::Device::init(); } void shutdownPeripherals() { diff --git a/ion/src/device/regs/rcc.h b/ion/src/device/regs/rcc.h index 805b91341..561ba9f41 100644 --- a/ion/src/device/regs/rcc.h +++ b/ion/src/device/regs/rcc.h @@ -74,6 +74,7 @@ public: class APB1ENR : Register32 { public: REGS_BOOL_FIELD(TIM3EN, 1); + REGS_BOOL_FIELD(SPI3EN, 15); REGS_BOOL_FIELD(PWREN, 28); }; @@ -81,6 +82,7 @@ public: 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(SYSCFGEN, 14); diff --git a/ion/src/device/regs/regs.h b/ion/src/device/regs/regs.h index 9375e1ed7..3fe399ad0 100644 --- a/ion/src/device/regs/regs.h +++ b/ion/src/device/regs/regs.h @@ -13,7 +13,9 @@ #include "rcc.h" #include "rng.h" #include "sdio.h" +#include "spi.h" #include "syscfg.h" #include "tim.h" +#include "usart.h" #endif diff --git a/ion/src/device/regs/spi.h b/ion/src/device/regs/spi.h new file mode 100644 index 000000000..a817a1371 --- /dev/null +++ b/ion/src/device/regs/spi.h @@ -0,0 +1,35 @@ +#ifndef REGS_SPI_H +#define REGS_SPI_H + +#include "register.h" + +class SPI { +public: + class CR1 : Register16 { + public: + REGS_BOOL_FIELD(SPE, 6); + REGS_BOOL_FIELD(LSBFIRST, 7); + REGS_BOOL_FIELD(SSI, 8); + REGS_BOOL_FIELD(SSM, 9); + }; + 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(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; +}; + +#endif diff --git a/ion/src/device/regs/usart.h b/ion/src/device/regs/usart.h new file mode 100644 index 000000000..cb454c2d6 --- /dev/null +++ b/ion/src/device/regs/usart.h @@ -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