├── .gitmodules ├── stm32++ ├── include │ └── stm32++ │ │ ├── stdfonts.hpp │ │ ├── dmaPrint.hpp │ │ ├── xassert.hpp │ │ ├── semihosting.hpp │ │ ├── font.hpp │ │ ├── utils.hpp │ │ ├── exti.hpp │ │ ├── common.hpp │ │ ├── printSink.hpp │ │ ├── tsnprintf.hpp │ │ ├── gpio.hpp │ │ ├── tprintf.hpp │ │ ├── test_tostring.cpp │ │ ├── drivers │ │ ├── st756x.hpp │ │ ├── ssd1306.hpp │ │ └── ms5611.hpp │ │ ├── spi.hpp │ │ ├── timeutl.hpp │ │ ├── usart.hpp │ │ ├── i2c.hpp │ │ ├── menu.hpp │ │ ├── dma.hpp │ │ ├── emu │ │ └── lcdemu.hpp │ │ ├── button.hpp │ │ ├── adc.hpp │ │ └── tostring.hpp ├── tests │ ├── flash │ │ ├── CMakeLists.txt │ │ └── flash.cpp │ └── tprintf │ │ ├── CMakeLists.txt │ │ └── main.cpp └── src │ ├── tsnprintf.cpp │ ├── printSink.cpp │ ├── semihosting.cpp │ └── stdfonts.cpp ├── sysroot-baremetal ├── Readme.txt ├── stm32.ld ├── include │ ├── arm_common_tables.h │ └── system_stm32f10x.h └── src │ └── startup_stm32f10x.c ├── LICENSE ├── flash.sh ├── stm32f1_bluepillx.cfg ├── stm32++-emulation.cmake ├── env-stm32.sh ├── ocmd.sh ├── stm32-toolchain.cmake └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libopencm3"] 2 | path = libopencm3 3 | url = https://github.com/libopencm3/libopencm3.git 4 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/stdfonts.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FONTS_H 2 | #define FONTS_H 3 | #include 4 | extern Font Font_5x7; 5 | 6 | /* 7 | extern Font Font_7x10; 8 | extern Font Font_11x18; 9 | extern Font Font_16x26; 10 | */ 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /stm32++/tests/flash/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | include_directories(../../include) 3 | add_definitions(-std=c++14 --sanitize=address) 4 | set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} --sanitize=address) 5 | add_executable(nvstore-test flash.cpp) 6 | -------------------------------------------------------------------------------- /sysroot-baremetal/Readme.txt: -------------------------------------------------------------------------------- 1 | This sysroot directory contains a minimal abstraction layer and a linker script, 2 | which are used only if libopencm3 is not used. When using libopencm3, these are not 3 | used or needed. However, the sysroot directory is still passed to CMake for the 4 | CMAKE_FIND_ROOT_PATH, so it can be used for standard libraries and headers. 5 | 6 | -------------------------------------------------------------------------------- /stm32++/tests/tprintf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | include_directories(../../include) 3 | add_definitions(-std=c++14 --sanitize=address -DSTM32PP_NOT_EMBEDDED) 4 | set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} --sanitize=address) 5 | message(STATUS "${STM32PP_SRCS}") 6 | add_executable(tprintf-test ../../src/tsnprintf.cpp ../../src/tprintf.cpp main.cpp) 7 | -------------------------------------------------------------------------------- /stm32++/src/tsnprintf.cpp: -------------------------------------------------------------------------------- 1 | #include // for size_t 2 | #include 3 | 4 | // Trivial case for the tsnprintf recursion. 5 | char* tsnprintf(char* buf, size_t bufsize, const char* fmtStr) 6 | { 7 | if (!buf) 8 | return nullptr; 9 | 10 | char* bufend = buf+bufsize-1; 11 | while (*fmtStr) 12 | { 13 | if (buf >= bufend) 14 | { 15 | assert(buf == bufend); 16 | *buf = 0; 17 | return nullptr; 18 | } 19 | *(buf++) = *(fmtStr++); 20 | } 21 | *buf = 0; 22 | return buf; 23 | } 24 | -------------------------------------------------------------------------------- /stm32++/src/printSink.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef STM32PP_LOG_VIA_SEMIHOSTING 3 | #include 4 | #else 5 | #include 6 | #endif 7 | 8 | struct DefaultPrintSink: public IPrintSink 9 | { 10 | IPrintSink::BufferInfo* waitReady() { return nullptr; } 11 | void print(const char* str, size_t len, int fd) 12 | { 13 | #ifdef STM32PP_LOG_VIA_SEMIHOSTING 14 | shost::fputs(str, len, fd); 15 | #else 16 | ::write(fd, str, len); 17 | #endif 18 | } 19 | }; 20 | 21 | DefaultPrintSink gDefaultPrintSink; 22 | IPrintSink* gPrintSink = &gDefaultPrintSink; 23 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/dmaPrint.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DMA_PRINT_HPP 2 | #define DMA_PRINT_HPP 3 | 4 | #include "printSink.hpp" 5 | namespace dma 6 | { 7 | template 8 | class PrintSink: public DmaDevice, public AsyncPrintSink 9 | { 10 | virtual IPrintSink::BufferInfo* waitReady() 11 | { 12 | while(DmaDevice::txBusy()); 13 | return &mPrintBuffer; 14 | } 15 | virtual void print(const char *str, size_t len, int bufSize) 16 | { 17 | assert(!DmaDevice::txBusy()); 18 | mPrintBuffer.buf = str; 19 | mPrintBuffer.bufSize = bufSize; 20 | DmaDevice::dmaTxStart((const void*)str, len); 21 | } 22 | }; 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /sysroot-baremetal/stm32.ld: -------------------------------------------------------------------------------- 1 | /* This will work with STM32 type of microcontrollers. * 2 | * The sizes of RAM and flash are specified smaller than * 3 | * what most of the STM32 provide to ensure that the demo * 4 | * program will run on ANY STM32. */ 5 | MEMORY 6 | { 7 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K 8 | rom (rx) : ORIGIN = 0x08000000, LENGTH = 128K 9 | } 10 | 11 | SECTIONS 12 | { 13 | . = 0x0; /* From 0x00000000 */ 14 | .text : 15 | { 16 | *(vectors) /* Vector table */ 17 | *(.text) /* Program code */ 18 | *(.rodata) /* Read only data */ 19 | } >rom 20 | 21 | . = 0x20000000; /* From 0x20000000 */ 22 | .data : 23 | { 24 | *(.data) /* Data memory */ 25 | } >ram AT > rom 26 | 27 | .bss : 28 | { 29 | *(.bss) /* Zero-filled run time allocate data memory */ 30 | } >ram AT > rom 31 | } 32 | 33 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/xassert.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STM32PP_XASSERT_HPP 2 | #define STM32PP_XASSERT_HPP 3 | #include "tprintf.hpp" 4 | 5 | #ifndef NDEBUG 6 | #undef xassert 7 | #define xassert(expr, ...) (expr) ? (void)0 : __xassert_fail(#expr, __FILE__, __LINE__, ##__VA_ARGS__) 8 | static inline void __xassert_fail(const char* expr, const char* file, int line, const char* msg=nullptr) 9 | { 10 | tprintf("========\nAssertion failed: "); 11 | if (msg) { 12 | tprintf("% (%)\n", msg, expr); 13 | } else { 14 | tprintf("assert(%)\n", expr); 15 | } 16 | tprintf("at %:%\n========\n", file, line); 17 | 18 | #ifdef STM32PP_NOT_EMBEDDED 19 | abort(); 20 | #else 21 | for(;;) asm ("wfi"); 22 | #endif 23 | } 24 | // #define dbg(fmtStr,...) tprintf(fmtStr, ##__VA_ARGS__) 25 | #else 26 | // #define dbg(fmtStr,...) 27 | #define xassert(...) 28 | #endif 29 | 30 | #endif // XASSERT_HPP 31 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/semihosting.hpp: -------------------------------------------------------------------------------- 1 | #if !defined(SEMIHOSTING_HPP) && !defined(NOT_EMBEDDED) 2 | #define SEMIHOSTING_HPP 3 | 4 | #include 5 | #include // for size_t 6 | 7 | namespace shost 8 | { 9 | /** @brief Semihosting opcodes */ 10 | enum: uint8_t { 11 | SYS_WRITE = 0x05, 12 | SYS_READ = 0x06, 13 | SYS_READC = 0x07, 14 | SYS_TIME = 0x11 15 | }; 16 | 17 | size_t bkpt(size_t cmd, size_t arg1); 18 | 19 | void fputs(const char* str, size_t len, int fd); 20 | void write(const void* buf, size_t bufsize, int fd=1); 21 | 22 | /** @brief Reads a char from semihosting stdin and returns it. If the stdin 23 | * is not connected, -1 is returned (EOF). 24 | */ 25 | static inline int getchar() { return bkpt(SYS_READC, 0); } 26 | 27 | /** @brief Read data from semihosting stdin. 28 | * @return Number of bytes read 29 | */ 30 | size_t read(void* buf, size_t bufsize, int fd=0); 31 | 32 | /** @brief Returns the unix time from the host system */ 33 | static inline uint32_t time() { return bkpt(SYS_TIME, 0); } 34 | 35 | }; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/font.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FONT_HPP 2 | #define FONT_HPP 3 | #include 4 | 5 | struct Font 6 | { 7 | const uint8_t width; 8 | const uint8_t height; 9 | const uint8_t count; 10 | const uint8_t* widths; 11 | const uint8_t* data; 12 | Font(uint8_t aWidth, uint8_t aHeight, uint8_t aCount, const uint8_t* aWidths, const void* aData) 13 | :width(aWidth), height(aHeight), count(aCount), widths(aWidths), data((uint8_t*)aData) 14 | {} 15 | bool isMono() const { return widths == nullptr; } 16 | const uint8_t* getCharData(uint8_t code) const 17 | { 18 | if (!widths) { 19 | if (code < 32) { 20 | return nullptr; 21 | } 22 | code -= 32; 23 | if (code >= count) { 24 | return nullptr; 25 | } 26 | uint8_t byteHeight = (height + 7) / 8; 27 | return data + (byteHeight * width) * code; 28 | } 29 | else { 30 | uint32_t ofs = 0; 31 | for (int ch = 0; ch < code; ch++) 32 | ofs+=widths[ch]; 33 | return data+ofs; 34 | } 35 | } 36 | }; 37 | 38 | #endif // FONT_HPP 39 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_HPP 2 | #define UTILS_HPP 3 | 4 | template 5 | struct CountOnes { enum: uint8_t { value = (val & 0x01) + CountOnes<(val >> 1)>::value }; }; 6 | 7 | template <> 8 | struct CountOnes<0> { enum: uint8_t { value = 0 }; }; 9 | 10 | template 11 | struct Right0Count{ enum: uint8_t { value = (val & 1) ? 0 : 1+Right0Count<(val >> 1)>::value }; }; 12 | 13 | template <> 14 | struct Right0Count<0>{ enum: uint8_t { value = 0 }; }; 15 | 16 | //returns 0 for 0b00, 1 for 0b01, 4 for 0b1000 17 | template 18 | struct HighestBitIdx { enum: uint8_t { value = 1 + HighestBitIdx<(val >> 1)>::value }; }; 19 | 20 | template<> 21 | struct HighestBitIdx<0> { enum: uint8_t { value = 0 }; }; 22 | 23 | #ifndef STM32PP_NOT_EMBEDDED 24 | #include 25 | 26 | /** @brief Scoped global disable of interrupts */ 27 | struct IntrDisable 28 | { 29 | protected: 30 | bool mWasDisabled; 31 | public: 32 | IntrDisable() 33 | : mWasDisabled(cm_is_masked_interrupts()) 34 | { 35 | if (!mWasDisabled) 36 | cm_disable_interrupts(); 37 | } 38 | ~IntrDisable() 39 | { 40 | if (!mWasDisabled) 41 | cm_enable_interrupts(); 42 | } 43 | }; 44 | 45 | #endif 46 | #endif // UTILS_HPP 47 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/exti.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STM32PP_EXTI_H 2 | #define STM32PP_EXTI_H 3 | 4 | #include "common.hpp" 5 | #include 6 | #include 7 | 8 | #define STM32PP_DEFINE_EXTI_PERIPH_INFO(id, irq) \ 9 | STM32PP_PERIPH_INFO(id) \ 10 | static constexpr uint8_t kIrqn = irq; \ 11 | } 12 | 13 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI0, NVIC_EXTI0_IRQ); 14 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI1, NVIC_EXTI1_IRQ); 15 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI2, NVIC_EXTI2_IRQ); 16 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI3, NVIC_EXTI3_IRQ); 17 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI4, NVIC_EXTI4_IRQ); 18 | 19 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI5, NVIC_EXTI9_5_IRQ); 20 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI6, NVIC_EXTI9_5_IRQ); 21 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI7, NVIC_EXTI9_5_IRQ); 22 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI8, NVIC_EXTI9_5_IRQ); 23 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI9, NVIC_EXTI9_5_IRQ); 24 | 25 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI10, NVIC_EXTI15_10_IRQ); 26 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI11, NVIC_EXTI15_10_IRQ); 27 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI12, NVIC_EXTI15_10_IRQ); 28 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI13, NVIC_EXTI15_10_IRQ); 29 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI14, NVIC_EXTI15_10_IRQ); 30 | STM32PP_DEFINE_EXTI_PERIPH_INFO(EXTI15, NVIC_EXTI15_10_IRQ); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /sysroot-baremetal/include/arm_common_tables.h: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------- 2 | * Copyright (C) 2010 ARM Limited. All rights reserved. 3 | * 4 | * $Date: 11. November 2010 5 | * $Revision: V1.0.2 6 | * 7 | * Project: CMSIS DSP Library 8 | * Title: arm_common_tables.h 9 | * 10 | * Description: This file has extern declaration for common tables like Bitreverse, reciprocal etc which are used across different functions 11 | * 12 | * Target Processor: Cortex-M4/Cortex-M3 13 | * 14 | * Version 1.0.2 2010/11/11 15 | * Documentation updated. 16 | * 17 | * Version 1.0.1 2010/10/05 18 | * Production release and review comments incorporated. 19 | * 20 | * Version 1.0.0 2010/09/20 21 | * Production release and review comments incorporated. 22 | * -------------------------------------------------------------------- */ 23 | 24 | #ifndef _ARM_COMMON_TABLES_H 25 | #define _ARM_COMMON_TABLES_H 26 | 27 | #include "arm_math.h" 28 | 29 | extern const uint16_t armBitRevTable[1024]; 30 | extern const q15_t armRecipTableQ15[64]; 31 | extern const q31_t armRecipTableQ31[64]; 32 | extern const q31_t realCoefAQ31[1024]; 33 | extern const q31_t realCoefBQ31[1024]; 34 | extern const float32_t twiddleCoef[6144]; 35 | extern const q31_t twiddleCoefQ31[6144]; 36 | extern const q15_t twiddleCoefQ15[6144]; 37 | 38 | #endif /* ARM_COMMON_TABLES_H */ 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Alexander Vassilev 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /stm32++/src/semihosting.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | #include 3 | 4 | #ifdef NOT_EMBEDDED 5 | #error NOT_EMBEDDED is set:\n\ 6 | Semihosting can be used only when compiling for embedeed target 7 | #endif 8 | 9 | static_assert(sizeof(size_t) == sizeof(void*), ""); 10 | 11 | namespace shost 12 | { 13 | 14 | /** Single-argument wrapper for the BKPT instruction. Note that some commands 15 | * use a second argument in r2 16 | */ 17 | size_t bkpt(size_t cmd, size_t arg1) 18 | { 19 | size_t ret; 20 | asm volatile( 21 | "mov r0, %[cmd];" 22 | "mov r1, %[arg1];" 23 | "bkpt #0xAB;" 24 | "mov %[ret], r0;" 25 | : [ret] "=r" (ret) //out 26 | : [cmd] "r" (cmd), [arg1] "r" (arg1) //in 27 | : "r0", "r1", "memory" //clobber 28 | ); 29 | return ret; 30 | } 31 | 32 | void write(const void* buf, size_t bufsize, int fd) 33 | { 34 | size_t msg[3] = { (size_t)fd, (size_t)buf, bufsize }; 35 | bkpt(SYS_WRITE, (size_t)msg); 36 | } 37 | 38 | void fputs(const char* str, size_t len, int fd) 39 | { 40 | write(str, len, fd); 41 | } 42 | 43 | size_t read(void* buf, size_t bufsize, int fd) 44 | { 45 | size_t msg[3] = { (size_t)fd, (size_t)buf, bufsize }; 46 | //SYS_READ returns the number of bytes remaining in the buffer, 47 | //i.e. bufsize - actual_bytes_read 48 | size_t ret = bkpt(SYS_READ, (size_t)msg); 49 | return bufsize-ret; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STM32PP_COMMON_HPP 2 | #define STM32PP_COMMON_HPP 3 | 4 | /** 5 | Utility routines, classes and definitions for the STM32++ library 6 | @author Alexander Vassilev 7 | @copyright BSD License 8 | */ 9 | 10 | #include 11 | 12 | // Define a class to check whether a class has a member 13 | #define TYPE_SUPPORTS(ClassName, Expr) \ 14 | template struct ClassName { \ 15 | template static uint16_t check(decltype(Expr)); \ 16 | template static uint8_t check(...); \ 17 | static bool const value = sizeof(check(0)) == sizeof(uint16_t); \ 18 | }; 19 | 20 | 21 | // Unspecialized template for peripheral info classes 22 | // Peripheral headers specialize this (in the global namespace) 23 | // for every peripheral and fill in various 24 | // parameters as enums or constexprs - such as DMA controller, 25 | // DMA rx and tx channels, etc 26 | template 27 | struct PeriphInfo; 28 | 29 | 30 | #define __STM32PP_PERIPH_INFO(periphId) \ 31 | template \ 32 | struct PeriphInfo \ 33 | { \ 34 | enum: uint32_t { kPeriphId = periphId }; \ 35 | static constexpr bool kPinsRemapped = Remap; 36 | 37 | #ifndef NDEBUG 38 | #define STM32PP_PERIPH_INFO(periphId) __STM32PP_PERIPH_INFO(periphId) \ 39 | static constexpr const char* periphName() { return #periphId; } 40 | #else 41 | #define STM32PP_PERIPH_INFO(periphId) __STM32PP_PERIPH_INFO(periphId); 42 | #endif 43 | 44 | #endif // COMMON_HPP 45 | -------------------------------------------------------------------------------- /sysroot-baremetal/include/system_stm32f10x.h: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | Released into the public domain. 4 | This work is free: you can redistribute it and/or modify it under the terms of 5 | Creative Commons Zero license v1.0 6 | 7 | This work is licensed under the Creative Commons Zero 1.0 United States License. 8 | To view a copy of this license, visit http://creativecommons.org/publicdomain/zero/1.0/ 9 | or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, 10 | California, 94105, USA. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | or FITNESS FOR A PARTICULAR PURPOSE. 15 | ****************************************************************************** 16 | */ 17 | 18 | 19 | /** @addtogroup CMSIS 20 | * @{ 21 | */ 22 | 23 | /** @addtogroup stm32f10x_system 24 | * @{ 25 | */ 26 | 27 | /** 28 | * @brief Define to prevent recursive inclusion 29 | */ 30 | #ifndef __SYSTEM_STM32F10X_H 31 | #define __SYSTEM_STM32F10X_H 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | /** @addtogroup STM32F10x_System_Includes 38 | * @{ 39 | */ 40 | 41 | /** 42 | * @} 43 | */ 44 | 45 | 46 | /** @addtogroup STM32F10x_System_Exported_types 47 | * @{ 48 | */ 49 | 50 | extern uint32_t SystemCoreClock; /*!< System Clock Frequency (Core Clock) */ 51 | 52 | /** 53 | * @} 54 | */ 55 | 56 | /** @addtogroup STM32F10x_System_Exported_Constants 57 | * @{ 58 | */ 59 | 60 | /** 61 | * @} 62 | */ 63 | 64 | /** @addtogroup STM32F10x_System_Exported_Macros 65 | * @{ 66 | */ 67 | 68 | /** 69 | * @} 70 | */ 71 | 72 | /** @addtogroup STM32F10x_System_Exported_Functions 73 | * @{ 74 | */ 75 | 76 | extern void SystemInit(void); 77 | extern void SystemCoreClockUpdate(void); 78 | /** 79 | * @} 80 | */ 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif /*__SYSTEM_STM32F10X_H */ 87 | 88 | /** 89 | * @} 90 | */ 91 | 92 | /** 93 | * @} 94 | */ 95 | -------------------------------------------------------------------------------- /flash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @author Alexander Vassilev 3 | # @copyright BSD License 4 | 5 | function printUsage 6 | { 7 | echo -e "\nOpenOCD flash image command. Usage:\n\ 8 | flash.sh [-v|--verify] [-h|--halt] [-o|--offset] \n\ 9 | -v --verify - Verify written image\n\ 10 | -h --halt - Do not reset after flashing the image\n\ 11 | -o --offset - Address at which to start writing\n\ 12 | the image, i.e. 0x08000000\n\ 13 | --help Print this help\n" 14 | } 15 | 16 | if [ "$#" -lt "1" ]; then 17 | printUssage 18 | exit 1 19 | fi 20 | 21 | owndir=`echo "$(cd $(dirname "${BASH_SOURCE[0]}"); pwd)"` 22 | 23 | while [[ $# > 0 ]] 24 | do 25 | key="$1" 26 | case $key in 27 | -v|--verify) 28 | verify="1" 29 | ;; 30 | -h|--halt) 31 | hlt="1" 32 | ;; 33 | -o|--offset) 34 | shift 35 | offset="$1" 36 | ;; 37 | --help) 38 | printUsage 39 | exit 0 40 | ;; 41 | *) 42 | if [ ${1:0:1} != '-' ]; then 43 | if [ ! -z "$fname" ]; then 44 | echo "Only one file name can be specified" 45 | exit 1 46 | fi 47 | fname="$1" 48 | else 49 | echo "Unknown option '$1'" 50 | exit 1 51 | fi 52 | ;; 53 | esac 54 | shift # past argument or value 55 | done 56 | 57 | if [ -z "$fname" ]; then 58 | echo "No file specified" 59 | exit 1 60 | fi 61 | 62 | if [ ! -f "$fname" ]; then 63 | echo "File '$fname' not found" 64 | exit 2 65 | fi 66 | 67 | if [ "$hlt" != "1" ]; then 68 | rst="; reset" 69 | fi 70 | # The 'program' openOCD command prevents semihosting from working after flashing 71 | # (semihosting requests hang), until openOCD is restarted. That's why we don't use that 72 | # command 73 | fname=`readlink -f "$fname"` 74 | 75 | cmd="reset halt; flash write_image erase $fname" 76 | 77 | if [ ! -z "offset" ]; then 78 | cmd="$cmd $offset" 79 | fi 80 | 81 | if [ "$verify" == "1" ]; then 82 | cmd="$cmd; verify_image $fname" 83 | fi 84 | 85 | if [ "$hlt" != "1" ]; then 86 | cmd="$cmd; reset run" 87 | fi 88 | 89 | $owndir/ocmd.sh "$cmd" 90 | -------------------------------------------------------------------------------- /stm32f1_bluepillx.cfg: -------------------------------------------------------------------------------- 1 | # script for stm32f1x family 2 | 3 | # 4 | # stm32 devices support both JTAG and SWD transports. 5 | # 6 | source [find target/swj-dp.tcl] 7 | source [find mem_helper.tcl] 8 | 9 | if { [info exists CHIPNAME] } { 10 | set _CHIPNAME $CHIPNAME 11 | } else { 12 | set _CHIPNAME stm32f1x 13 | } 14 | 15 | set _ENDIAN little 16 | 17 | # Work-area is a space in RAM used for flash programming 18 | # By default use 4kB (as found on some STM32F100s) 19 | if { [info exists WORKAREASIZE] } { 20 | set _WORKAREASIZE $WORKAREASIZE 21 | } else { 22 | set _WORKAREASIZE 0x1000 23 | } 24 | 25 | #jtag scan chain 26 | if { [info exists CPUTAPID] } { 27 | set _CPUTAPID $CPUTAPID 28 | } else { 29 | if { [using_jtag] } { 30 | # See STM Document RM0008 Section 26.6.3 31 | set _CPUTAPID 0x3ba00477 32 | } { 33 | # this is the SW-DP tap id not the jtag tap id 34 | set _CPUTAPID 0x1ba01477 35 | } 36 | } 37 | 38 | swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID 39 | 40 | if {[using_jtag]} { 41 | jtag newtap $_CHIPNAME bs -irlen 5 42 | } 43 | 44 | set _TARGETNAME $_CHIPNAME.cpu 45 | target create $_TARGETNAME cortex_m -endian $_ENDIAN -chain-position $_TARGETNAME 46 | 47 | $_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 48 | 49 | # flash size will be probed 50 | set _FLASHNAME $_CHIPNAME.flash 51 | # flash bank $_FLASHNAME stm32f1x 0x08000000 0 0 0 $_TARGETNAME 52 | flash bank $_FLASHNAME stm32f1x 0x08000000 0x20000 0 0 $_TARGETNAME 53 | 54 | # JTAG speed should be <= F_CPU/6. F_CPU after reset is 8MHz, so use F_JTAG = 1MHz 55 | adapter_khz 1000 56 | 57 | adapter_nsrst_delay 100 58 | if {[using_jtag]} { 59 | jtag_ntrst_delay 100 60 | } 61 | 62 | reset_config srst_nogate 63 | 64 | if {![using_hla]} { 65 | # if srst is not fitted use SYSRESETREQ to 66 | # perform a soft reset 67 | cortex_m reset_config sysresetreq 68 | } 69 | 70 | $_TARGETNAME configure -event examine-end { 71 | # DBGMCU_CR |= DBG_WWDG_STOP | DBG_IWDG_STOP | 72 | # DBG_STANDBY | DBG_STOP | DBG_SLEEP 73 | mmw 0xE0042004 0x00000307 0 74 | } 75 | 76 | $_TARGETNAME configure -event trace-config { 77 | # Set TRACE_IOEN; TRACE_MODE is set to async; when using sync 78 | # change this value accordingly to configure trace pins 79 | # assignment 80 | mmw 0xE0042004 0x00000020 0 81 | } 82 | -------------------------------------------------------------------------------- /stm32++/tests/tprintf/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct MyPrintSink: public IPrintSink 6 | { 7 | const char* expectedString = nullptr; 8 | void print(const char *str, size_t len, int fd) 9 | { 10 | if (strcmp(str, expectedString)) 11 | { 12 | printf("ERROR: Output test mismatch: expected '%s', actual: '%s'\n", expectedString, str); 13 | exit(1); 14 | } 15 | else 16 | { 17 | printf("PASS: %s\n", str); 18 | } 19 | } 20 | }; 21 | 22 | MyPrintSink myPrintSink; 23 | 24 | template 25 | void expect(const char* expected, const char* fmtString, Args... args) 26 | { 27 | auto savedSink = gPrintSink; 28 | gPrintSink = &myPrintSink; 29 | myPrintSink.expectedString = expected; 30 | tprintf(fmtString, args...); 31 | gPrintSink = savedSink; 32 | } 33 | 34 | 35 | int main() 36 | { 37 | expect("this is a float: 123.456700", "this is a float: %", 123.4567); 38 | expect("this is a fmtFp(minDigits: 4): 0123.456700", 39 | "this is a fmtFp(minDigits: 4): %", 40 | fmtFp<6>(123.4567, 4)); 41 | expect("this is an int: ' 001234'", "this is an int: '%'", fmtInt(1234, 6, 8)); 42 | expect("this is a hex8(127): 0x7f", "this is a hex8(127): %", fmtHex(127)); 43 | expect("this is a hex16(32767): 0x7FFF", "this is a hex16(32767): %", fmtHex(32767)); 44 | expect("this is a hex16(32767) no prefix: 7fff", "this is a hex16(32767) no prefix: %", fmtHex(32767)); 45 | 46 | expect("this is an octal: OCT4553207", "this is an octal: %", fmtInt<8>(1234567)); 47 | expect("this is a bin(127): 0b01111111", "this is a bin(127): %", fmtBin(127)); 48 | expect("this is a string: 'test message'", "this is a string: %", "'test message'"); 49 | expect("this is a dollar: $", "this is a dollar: %", '$'); 50 | 51 | tprintf("this is a float: %\n" 52 | "this is a fmtFp(minDigits: 4): %\n" 53 | "this is a hex8(127): %\n" 54 | "this is a hex16(32767): %\n" 55 | "this is a bin(127): %\n" 56 | "this is a string: '%'\n" 57 | "this is a dollar: %\n", 58 | 123.4567, fmtFp<6>(123.4567, 4), 59 | fmtHex8(127), fmtHex16(32767), 60 | fmtBin(127), "test message", '$'); 61 | } 62 | -------------------------------------------------------------------------------- /stm32++-emulation.cmake: -------------------------------------------------------------------------------- 1 | # @author Alexander Vassilev 2 | # @copyright BSD License 3 | 4 | # CMake include file for building stm32++ projects as desktop applications, 5 | # in emulation mode. It can be used to build, test and debug hardware-independent 6 | # code, and provide emulation for hardware devices, such as an LCD display. For that 7 | # purpose, the wxWidgets library is used to provide the GUI 8 | # Usage: 9 | # You need to have sourced the env_stm32.sh script, as usual 10 | # In your CMakeLists.txt file, include this file, and you will have basic facilities 11 | # similar to the ones provided by the arm toolchain file. 12 | # The STM32PP_NOT_EMBEDDED define is provided, so that source code can detect 13 | # when it is cumpiled under this emulation environment. 14 | # Example CMakeLists.txt file: 15 | # 16 | # cmake_minimum_required(VERSION 2.8) 17 | # project(lcdemu) 18 | # include($ENV{STM32_ENV_DIR}/emulation.cmake) 19 | # set(SRCS emu.cpp ${STM32PP_SRCS} ${STM32PP_SRCPATH}/stdfonts.cpp) 20 | # set(imgname "${CMAKE_PROJECT_NAME}.elf") 21 | # add_executable(${imgname} ${SRCS}) 22 | # stm32_create_utility_targets(${imgname}) 23 | 24 | cmake_minimum_required(VERSION 3.0) 25 | 26 | set(ENV_SCRIPTS_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE PATH "") 27 | 28 | # Default to debug build 29 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type") 30 | set_Property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo) 31 | 32 | # Utilities to facilitate user CMakeLists 33 | set(STM32PP_SRCPATH "${ENV_SCRIPTS_DIR}/stm32++/src") 34 | set(STM32PP_SRCS 35 | "${STM32PP_SRCPATH}/printSink.cpp" 36 | "${STM32PP_SRCPATH}/tsnprintf.cpp" 37 | ) 38 | 39 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall" CACHE STRING "") 40 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -std=c++14 -fno-exceptions -fno-rtti -fno-use-cxa-atexit -fno-threadsafe-statics" CACHE STRING "") 41 | add_definitions(-DSTM32PP_NOT_EMBEDDED=1) 42 | 43 | include_directories("${ENV_SCRIPTS_DIR}/stm32++/include") 44 | 45 | # Note that for MinGW users the order of libs is important! 46 | find_package(wxWidgets COMPONENTS core base) 47 | if(wxWidgets_FOUND) 48 | include(${wxWidgets_USE_FILE}) 49 | link_libraries(${wxWidgets_LIBRARIES}) 50 | endif() 51 | 52 | function(stm32_create_utility_targets imgname) 53 | add_custom_target(gdb 54 | gdb -ex 'file ${CMAKE_CURRENT_BINARY_DIR}/${imgname}' 55 | -ex 'directory ${CMAKE_CURRENT_SOURCE_DIR}' 56 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 57 | DEPENDS "${imgname}") 58 | endfunction() 59 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/printSink.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Vassilev 3 | * @copyright BSD License 4 | */ 5 | 6 | #ifndef _PRINT_SINK_H 7 | #define _PRINT_SINK_H 8 | 9 | #include 10 | #include 11 | 12 | struct IPrintSink 13 | { 14 | struct BufferInfo 15 | { 16 | const char* buf = nullptr; 17 | size_t bufSize = 0; 18 | void clear() 19 | { 20 | buf = nullptr; 21 | bufSize = 0; 22 | } 23 | }; 24 | /** 25 | * @brief waitReady Waits till the sink has completed the last print operation, if any 26 | * @return Pointer to the sink's buffer info, if the sink is async. 27 | * Null if the sink is synchronous 28 | */ 29 | virtual BufferInfo* waitReady() = 0; 30 | /** 31 | * @brief print Outputs the specified string 32 | * @param str The string to print 33 | * @param len The length of the string to print 34 | * @param info If this is a synchronous print sink, \c info is the file descriptor number, 35 | * for semihosting support. If this is an async print sink, \c info is the size of 36 | * the buffer, that contains the string (\c len may be less than the buffer size) 37 | */ 38 | virtual void print(const char* str, size_t len, int info) = 0; 39 | }; 40 | 41 | struct AsyncPrintSink: public IPrintSink 42 | { 43 | protected: 44 | BufferInfo mPrintBuffer; 45 | }; 46 | 47 | static inline IPrintSink* setPrintSink(IPrintSink* newSink) 48 | { 49 | extern IPrintSink* gPrintSink; 50 | IPrintSink::BufferInfo* currSinkBufInfo = gPrintSink->waitReady(); 51 | bool isAsync = (currSinkBufInfo != nullptr); 52 | if (isAsync) 53 | { 54 | if (currSinkBufInfo->buf) 55 | { 56 | auto newSinkBufInfo = newSink->waitReady(); 57 | if (newSinkBufInfo) // newSink is async, move current async buffer to it 58 | { 59 | if (newSinkBufInfo->buf) 60 | { // newSink also has a buffer allocated, free it 61 | free((void*)newSinkBufInfo->buf); 62 | } 63 | *newSinkBufInfo = *currSinkBufInfo; 64 | currSinkBufInfo->clear(); 65 | } 66 | else // newSink is synchronous, and we have an async buffer, free it 67 | { 68 | free(currSinkBufInfo); 69 | currSinkBufInfo->clear(); 70 | } 71 | } 72 | } 73 | auto old = gPrintSink; 74 | gPrintSink = newSink; 75 | return old; 76 | } 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/tsnprintf.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Vassilev 3 | * @copyright BSD License 4 | */ 5 | 6 | #ifndef _TSNPRINTF_H 7 | #define _TSNPRINTF_H 8 | 9 | #include "tostring.hpp" 10 | #include 11 | #include 12 | #include 13 | char* tsnprintf(char* buf, size_t bufsize, const char* fmtStr); 14 | 15 | // Returns the address of the terminating null of the written string 16 | template 17 | char* tsnprintf(char* buf, size_t bufsize, const char* fmtStr, Val val, Args... args) 18 | { 19 | assert(buf); 20 | assert(bufsize); 21 | 22 | char* bufend = buf+bufsize-1; //point to last char 23 | do 24 | { 25 | char ch = *fmtStr++; 26 | if (ch == '%') 27 | { 28 | // toString returns: 29 | // - null if it didn't manage to write everything 30 | // - the address of the char after the last written, 31 | // if it managed to write everything. However, that returned 32 | // address may be past the end of the buffer, so we need to check 33 | buf = toString(buf, bufend-buf+1, val); 34 | if (!buf) 35 | { 36 | *bufend = 0; 37 | return nullptr; 38 | } 39 | if (buf >= bufend) 40 | { 41 | *bufend = 0; 42 | if (buf == bufend) 43 | { 44 | return bufend; 45 | } 46 | // toString() just managed to fit everything, without the terminating zero 47 | // but now we don't have space for the terminator 48 | assert(buf - bufend == 1); // make sure we haven't gone past the end of the buffer 49 | // replace last char with terminator, and return null, to signal 50 | // that we didn't have enough space 51 | return nullptr; 52 | } 53 | return tsnprintf(buf, bufend-buf+1, fmtStr, args...); 54 | } 55 | else if (ch == 0) 56 | { 57 | *buf = 0; 58 | return buf; 59 | } 60 | else 61 | { 62 | *(buf++) = ch; 63 | } 64 | } 65 | while (buf < bufend); 66 | // we have copied char by char from the format string till we reached the end 67 | // of the buffer wihtout reaching the terminating zero of the fmtString. 68 | // Terminate the string and return nullptr - not enough buffer space 69 | assert(buf == bufend); 70 | *bufend = 0; 71 | return nullptr; 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /env-stm32.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # @author Alexander Vassilev 4 | # @copyright BSD License 5 | 6 | export STM32_RED="\033[1;31m" 7 | export STM32_GREEN="\033[0;32m" 8 | export STM32_NOMARK="\033[0;0m" 9 | export STM32_BOLD="\033[30;1m" 10 | 11 | owndir=`echo "$(cd $(dirname "${BASH_SOURCE[0]}"); pwd)"` 12 | export STM32_ENV_DIR=$owndir 13 | 14 | if [ "$#" != "1" ]; then 15 | STM32_SYSROOT="$owndir/sysroot" 16 | else 17 | STM32_SYSROOT=`readlink -f $1` 18 | fi 19 | 20 | if [ ! -d "$STM32_SYSROOT" ]; then 21 | echo -e "${STM32_RED}sysroot '$STM32_SYSROOT' dir does not exist${STM32_NOMARK}" 22 | return 2 23 | fi 24 | 25 | function xcmake 26 | { 27 | cmake "-DCMAKE_TOOLCHAIN_FILE=$owndir/stm32-toolchain.cmake" "$@" 28 | } 29 | export -f xcmake 30 | 31 | function ecmake 32 | { 33 | cmake "-DCMAKE_MODULE_PATH=$owndir" "$@" 34 | } 35 | export -f ecmake 36 | 37 | function ocmd 38 | { 39 | $owndir/ocmd.sh "$@" 40 | } 41 | export -f ocmd 42 | 43 | function flash 44 | { 45 | $owndir/flash.sh "$@" 46 | } 47 | export -f flash 48 | 49 | function hc05 50 | { 51 | if [ -z "$1" ]; then 52 | ttyDev=/dev/ttyUSB0 53 | else 54 | ttyDev="$1" 55 | fi 56 | socat "$ttyDev",b38400,raw,echo=0,crnl - 57 | } 58 | export -f hc05 59 | 60 | if [ "$USER" == "root" ]; then 61 | prompt="#" 62 | else 63 | prompt="\$" 64 | fi 65 | 66 | export PS1="[\u@\[\033[0;32m\]\[\033[3m\]stm32\[\033[0m\] \w]$prompt" 67 | 68 | # Convenience alias 69 | alias gdb=arm-none-eabi-gdb 70 | alias objcopy=arm-none-eabi-objcopy 71 | alias gcc=arm-none-eabi-gcc 72 | alias g++=arm-none-eabi-g++ 73 | alias as=arm-none-eabi-as 74 | alias nm=arm-none-eabi-nm 75 | alias strip=arm-none-eabi-strip 76 | 77 | echo -e "\ 78 | =================================================================== 79 | Your environment has been set up for STM32 cross-compilation and emulation. 80 | ${STM32_GREEN}STM32_ENV_DIR${STM32_NOMARK}=$owndir 81 | Use '${STM32_GREEN}xcmake${STM32_NOMARK}' instead of 'cmake' in order to configure project for 82 | cross-compilation 83 | Use '${STM32_GREEN}flash${STM32_NOMARK}' to flash chip, see flash --help for details 84 | Use '${STM32_GREEN}ocmd${STM32_NOMARK} ' to send any command to OpenOCD. 85 | Use '${STM32_GREEN}ecmake${STM32_NOMARK}' instead of 'cmake' in order to configure project for emulation. 86 | The project should '${STM32_GREEN}include(stm32++-emulation)${STM32_NOMARK}'. It will use the native 87 | PC toolchain and build a PC executable. This can be used for development and 88 | debugging of hardware-independent parts of firmware code as a PC application 89 | ${STM32_BOLD}Enjoy programming!${STM32_NOMARK} 90 | ===================================================================" 91 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/gpio.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STM32PP_GPIO_HPP 2 | #define STM32PP_GPIO_HPP 3 | 4 | /** 5 | GPIO classes and definitions for the STM32++ library 6 | @author Alexander Vassilev 7 | @copyright BSD License 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include "exti.hpp" 14 | 15 | namespace nsgpio 16 | { 17 | template 18 | struct Pin 19 | { 20 | enum: uint32_t { kPort = aPort }; 21 | enum: uint16_t { kPin = aPin }; 22 | static const auto kClockId = PeriphInfo::kClockId; 23 | static void enableClock() { ::rcc_periph_clock_enable(kClockId); } 24 | 25 | template 26 | static void setMode(M mode, C config) 27 | { 28 | if (StartClock) { 29 | enableClock(); 30 | } 31 | gpio_set_mode(kPort, mode, config, kPin); 32 | } 33 | template 34 | static void setModeInputPuPd(bool pullUp) 35 | { 36 | setMode(GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN); 37 | if (pullUp) 38 | { 39 | set(); 40 | } 41 | else 42 | { 43 | clear(); 44 | } 45 | } 46 | static void set() { gpio_set(kPort, kPin); } 47 | static void clear() { gpio_clear(kPort, kPin); } 48 | static void toggle() { gpio_toggle(kPort, kPin); } 49 | static uint16_t get() { return GPIO_IDR(kPort) & kPin; } 50 | static void configInterrupt(enum exti_trigger_type trigger) 51 | { 52 | rcc_periph_clock_enable(RCC_AFIO); 53 | exti_select_source(kPin, kPort); 54 | exti_set_trigger(kPin, trigger); 55 | } 56 | static const auto irqn() { return PeriphInfo::kIrqn; } 57 | static void enableInterrupt() 58 | { 59 | exti_enable_request(kPin); 60 | } 61 | static void disableInterrupt() 62 | { 63 | exti_disable_request(kPin); 64 | } 65 | }; 66 | } 67 | 68 | STM32PP_PERIPH_INFO(GPIOA) 69 | static constexpr rcc_periph_clken kClockId = RCC_GPIOA; 70 | }; 71 | 72 | STM32PP_PERIPH_INFO(GPIOB) 73 | static constexpr rcc_periph_clken kClockId = RCC_GPIOB; 74 | }; 75 | 76 | STM32PP_PERIPH_INFO(GPIOC) 77 | static constexpr rcc_periph_clken kClockId = RCC_GPIOC; 78 | }; 79 | 80 | STM32PP_PERIPH_INFO(GPIOD) 81 | static constexpr rcc_periph_clken kClockId = RCC_GPIOD; 82 | }; 83 | 84 | STM32PP_PERIPH_INFO(GPIOE) 85 | static constexpr rcc_periph_clken kClockId = RCC_GPIOE; 86 | }; 87 | 88 | STM32PP_PERIPH_INFO(GPIOF) 89 | static constexpr rcc_periph_clken kClockId = RCC_GPIOF; 90 | }; 91 | 92 | STM32PP_PERIPH_INFO(GPIOG) 93 | static constexpr rcc_periph_clken kClockId = RCC_GPIOG; 94 | }; 95 | 96 | 97 | #endif // COMMON_HPP 98 | -------------------------------------------------------------------------------- /ocmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @author Alexander Vassilev 3 | # @copyright BSD License 4 | 5 | STM_VERSION=1_bluepill 6 | #STM_VERSION=1 7 | 8 | if [ "$#" -lt "1" ]; then 9 | echo -e "OpenOCD command client. Usage:\n\ 10 | ocmd.sh \"\"" 11 | exit 1 12 | fi 13 | 14 | owndir=`echo "$(cd $(dirname "${BASH_SOURCE[0]}"); pwd)"` 15 | in="$owndir/openocd.stdin" 16 | #out="$owndir/openocd.stdout" 17 | 18 | pid=`pidof openocd` 19 | if [ -z $pid ]; then 20 | if [ ! -p "$in" ]; then 21 | echo -e "${STM32_BOLD}Creating named pipe '$in' for openOCD and semihosting stdin${STM32_NOMARK}" 22 | rm -f "$in" 23 | mkfifo "$in" 24 | fi 25 | 26 | echo -e "${STM32_BOLD}OpenOCD not running, starting it and waiting for it to open telnet port...${STM32_NOMARK}" 27 | openocd \ 28 | -f /usr/share/openocd/scripts/interface/stlink-v2.cfg \ 29 | -f /usr/share/openocd/scripts/target/stm32f${STM_VERSION}x.cfg \ 30 | -c 'init; arm semihosting enable' < "$in" & 31 | 32 | # opening fifo pipes blocks until the other end is opened, so openocd 33 | # will not be started unless we open the pipe 34 | # Wait a bit till process initializes and the pipe is opened on its side 35 | sleep 0.1 36 | touch "$in" 37 | 38 | # wait a bit more for openocd to start telnet server 39 | sleep 0.5 40 | 41 | checks=0 42 | while [ `nc -z localhost 4444; echo $?` != "0" ] 43 | do 44 | ((checks++)) 45 | if [ "$checks" -gt "60" ]; then 46 | echo -e "\n${STM32_RED}Timed out waiting for OpenOCD to start${STM32_NOMARK}" 47 | exit 1 48 | else 49 | echo -n '.' 50 | fi 51 | sleep 0.5 52 | done 53 | if [ "$checks" -gt "0" ]; then 54 | echo -e "\n" 55 | fi 56 | 57 | pid=`pidof openocd` 58 | echo -e "${STM32_MARK}OpenOCD telnet port detected, openOCD pid is $pid, proceeding with command(s)${STM32_NOMARK}" 59 | 60 | # Disable openocd outputting stuff to the terminal that started it 61 | echo -e "log_output /dev/null\nexit" | (nc localhost 4444 2>&1) > /dev/null 62 | fi 63 | 64 | # 'exit' should go on another line, because if there is an error in the user 65 | # command, the exit command will be ignored 66 | # The network protocol is telnet and openOCD's response contains a binary header, 67 | # which appears as junk on the console. That's why we skip it with the tail filter 68 | 69 | echo -e "$@\nexit" | (nc localhost 4444) | tail -n +2 70 | 71 | # openocd kills the telnet connection only when it reaches the 'exit' command, 72 | # and netcat blocks until the connection is alive. So, we effectively block until 73 | # the command(s) are executed. Still, openocd may continue printing for a bit after 74 | # it drops the telnet connection. In that case, the shell propmt will appear and 75 | # the openocd output after it, so the user will get confused. To fix that, 76 | # wait a bit before terminating this script and returning to the prompt 77 | sleep 0.1 78 | exit 0 79 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/tprintf.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Vassilev 3 | * @copyright BSD License 4 | */ 5 | 6 | #ifndef _TPRINTF_H 7 | #define _TPRINTF_H 8 | 9 | #include "tsnprintf.hpp" 10 | #include "printSink.hpp" 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef STM32PP_TPRINTF_MAX_DYNAMIC_BUFSIZE 16 | #define STM32PP_TPRINTF_MAX_DYNAMIC_BUFSIZE 10240 17 | #endif 18 | 19 | #ifndef STM32PP_TPRINTF_ASYNC_EXPAND_STEP 20 | #define STM32PP_TPRINTF_ASYNC_EXPAND_STEP 64 21 | #endif 22 | 23 | #ifndef STM32PP_TPRINTF_SYNC_EXPAND_STEP 24 | #define STM32PP_TPRINTF_SYNC_EXPAND_STEP 128 25 | #endif 26 | 27 | template 28 | size_t ftprintf(uint8_t fd, const char* fmtStr, Args... args) 29 | { 30 | extern IPrintSink* gPrintSink; 31 | char* staticBuf; // static buf 32 | char* buf; 33 | size_t bufsize; 34 | 35 | auto async = gPrintSink->waitReady(); 36 | if (async) 37 | { 38 | staticBuf = nullptr; 39 | if (async->buf) 40 | { 41 | buf = (char*)async->buf; 42 | bufsize = async->bufSize; 43 | } 44 | else 45 | { 46 | buf = (char*)malloc(InitialBufSize); 47 | bufsize = InitialBufSize; 48 | } 49 | } 50 | else 51 | { 52 | buf = staticBuf = (char*)alloca(InitialBufSize); 53 | bufsize = InitialBufSize; 54 | } 55 | char* ret; 56 | for(;;) 57 | { 58 | ret = tsnprintf(buf, bufsize, fmtStr, args...); 59 | if (ret) 60 | { 61 | break; 62 | } 63 | // tsnprintf() returned nullptr, have to increase buf size 64 | bufsize += async ? STM32PP_TPRINTF_ASYNC_EXPAND_STEP : STM32PP_TPRINTF_SYNC_EXPAND_STEP; 65 | if (bufsize > STM32PP_TPRINTF_MAX_DYNAMIC_BUFSIZE) 66 | { 67 | //too much, bail out 68 | if ((buf != staticBuf) && !async) // buffer is dynamic and synchronous, free it 69 | { 70 | free(buf); 71 | } 72 | return 0; 73 | } 74 | buf = (buf == staticBuf) 75 | ? (char*)malloc(bufsize) 76 | : (char*)realloc(buf, bufsize); 77 | if (!buf) 78 | { 79 | // If we are async, we did a realloc. When realloc fails, 80 | // it doesn't free the old buffer, so the sink's pointer remains valid 81 | return 0; 82 | } 83 | } 84 | assert(ret >= buf); 85 | size_t size = ret-buf; 86 | if (async) 87 | { 88 | gPrintSink->print(buf, size, bufsize); 89 | } 90 | else 91 | { 92 | gPrintSink->print(buf, size, fd); 93 | if (buf != staticBuf) 94 | { 95 | free(buf); 96 | } 97 | } 98 | return size; 99 | } 100 | 101 | template 102 | uint16_t tprintf(const char* fmtStr, Args... args) 103 | { 104 | return ftprintf(1, fmtStr, args...); 105 | } 106 | 107 | static inline void puts(const char* str, uint16_t len) 108 | { 109 | extern IPrintSink* gPrintSink; 110 | gPrintSink->print((char*)str, len, 1); 111 | } 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /stm32++/src/stdfonts.cpp: -------------------------------------------------------------------------------- 1 | //***************************************************************************** 2 | // 3 | // File Name : 'font5x7.h' 4 | // Title : Graphic LCD Font (Ascii Charaters) 5 | // Author : Pascal Stang 6 | // Date : 10/19/2001 7 | // Revised : 10/19/2001 8 | // Version : 0.1 9 | // Target MCU : Atmel AVR 10 | // Editor Tabs : 4 11 | // 12 | //***************************************************************************** 13 | #include 14 | 15 | // standard ascii 5x7 font 16 | // defines ascii characters 0x20-0x7F (32-127) 17 | static const uint8_t Font5x7_data [] = { 18 | 0x00, 0x00, 0x00, 0x00, 0x00,// (space) 19 | 0x00, 0x00, 0x5F, 0x00, 0x00,// ! 20 | 0x00, 0x07, 0x00, 0x07, 0x00,// " 21 | 0x14, 0x7F, 0x14, 0x7F, 0x14,// # 22 | 0x24, 0x2A, 0x7F, 0x2A, 0x12,// $ 23 | 0x23, 0x13, 0x08, 0x64, 0x62,// % 24 | 0x36, 0x49, 0x55, 0x22, 0x50,// & 25 | 0x00, 0x05, 0x03, 0x00, 0x00,// ' 26 | 0x00, 0x1C, 0x22, 0x41, 0x00,// ( 27 | 0x00, 0x41, 0x22, 0x1C, 0x00,// ) 28 | 0x08, 0x2A, 0x1C, 0x2A, 0x08,// * 29 | 0x08, 0x08, 0x3E, 0x08, 0x08,// + 30 | 0x00, 0x50, 0x30, 0x00, 0x00,// , 31 | 0x08, 0x08, 0x08, 0x08, 0x08,// - 32 | 0x00, 0x60, 0x60, 0x00, 0x00,// . 33 | 0x20, 0x10, 0x08, 0x04, 0x02,// / 34 | 0x3E, 0x51, 0x49, 0x45, 0x3E,// 0 35 | 0x00, 0x42, 0x7F, 0x40, 0x00,// 1 36 | 0x42, 0x61, 0x51, 0x49, 0x46,// 2 37 | 0x21, 0x41, 0x45, 0x4B, 0x31,// 3 38 | 0x18, 0x14, 0x12, 0x7F, 0x10,// 4 39 | 0x27, 0x45, 0x45, 0x45, 0x39,// 5 40 | 0x3C, 0x4A, 0x49, 0x49, 0x30,// 6 41 | 0x01, 0x71, 0x09, 0x05, 0x03,// 7 42 | 0x36, 0x49, 0x49, 0x49, 0x36,// 8 43 | 0x06, 0x49, 0x49, 0x29, 0x1E,// 9 44 | 0x00, 0x36, 0x36, 0x00, 0x00,// : 45 | 0x00, 0x56, 0x36, 0x00, 0x00,// ; 46 | 0x00, 0x08, 0x14, 0x22, 0x41,// < 47 | 0x14, 0x14, 0x14, 0x14, 0x14,// = 48 | 0x41, 0x22, 0x14, 0x08, 0x00,// > 49 | 0x02, 0x01, 0x51, 0x09, 0x06,// ? 50 | 0x32, 0x49, 0x79, 0x41, 0x3E,// @ 51 | 0x7E, 0x11, 0x11, 0x11, 0x7E,// A 52 | 0x7F, 0x49, 0x49, 0x49, 0x36,// B 53 | 0x3E, 0x41, 0x41, 0x41, 0x22,// C 54 | 0x7F, 0x41, 0x41, 0x22, 0x1C,// D 55 | 0x7F, 0x49, 0x49, 0x49, 0x41,// E 56 | 0x7F, 0x09, 0x09, 0x01, 0x01,// F 57 | 0x3E, 0x41, 0x41, 0x51, 0x32,// G 58 | 0x7F, 0x08, 0x08, 0x08, 0x7F,// H 59 | 0x00, 0x41, 0x7F, 0x41, 0x00,// I 60 | 0x20, 0x40, 0x41, 0x3F, 0x01,// J 61 | 0x7F, 0x08, 0x14, 0x22, 0x41,// K 62 | 0x7F, 0x40, 0x40, 0x40, 0x40,// L 63 | 0x7F, 0x02, 0x04, 0x02, 0x7F,// M 64 | 0x7F, 0x04, 0x08, 0x10, 0x7F,// N 65 | 0x3E, 0x41, 0x41, 0x41, 0x3E,// O 66 | 0x7F, 0x09, 0x09, 0x09, 0x06,// P 67 | 0x3E, 0x41, 0x51, 0x21, 0x5E,// Q 68 | 0x7F, 0x09, 0x19, 0x29, 0x46,// R 69 | 0x46, 0x49, 0x49, 0x49, 0x31,// S 70 | 0x01, 0x01, 0x7F, 0x01, 0x01,// T 71 | 0x3F, 0x40, 0x40, 0x40, 0x3F,// U 72 | 0x1F, 0x20, 0x40, 0x20, 0x1F,// V 73 | 0x7F, 0x20, 0x18, 0x20, 0x7F,// W 74 | 0x63, 0x14, 0x08, 0x14, 0x63,// X 75 | 0x03, 0x04, 0x78, 0x04, 0x03,// Y 76 | 0x61, 0x51, 0x49, 0x45, 0x43,// Z 77 | 0x00, 0x00, 0x7F, 0x41, 0x41,// [ 78 | 0x02, 0x04, 0x08, 0x10, 0x20,// "\" 79 | 0x41, 0x41, 0x7F, 0x00, 0x00,// ] 80 | 0x04, 0x02, 0x01, 0x02, 0x04,// ^ 81 | 0x40, 0x40, 0x40, 0x40, 0x40,// _ 82 | 0x00, 0x01, 0x02, 0x04, 0x00,// ` 83 | 0x20, 0x54, 0x54, 0x54, 0x78,// a 84 | 0x7F, 0x48, 0x44, 0x44, 0x38,// b 85 | 0x38, 0x44, 0x44, 0x44, 0x20,// c 86 | 0x38, 0x44, 0x44, 0x48, 0x7F,// d 87 | 0x38, 0x54, 0x54, 0x54, 0x18,// e 88 | 0x08, 0x7E, 0x09, 0x01, 0x02,// f 89 | 0x08, 0x14, 0x54, 0x54, 0x3C,// g 90 | 0x7F, 0x08, 0x04, 0x04, 0x78,// h 91 | 0x00, 0x44, 0x7D, 0x40, 0x00,// i 92 | 0x20, 0x40, 0x44, 0x3D, 0x00,// j 93 | 0x00, 0x7F, 0x10, 0x28, 0x44,// k 94 | 0x00, 0x41, 0x7F, 0x40, 0x00,// l 95 | 0x7C, 0x04, 0x18, 0x04, 0x78,// m 96 | 0x7C, 0x08, 0x04, 0x04, 0x78,// n 97 | 0x38, 0x44, 0x44, 0x44, 0x38,// o 98 | 0x7C, 0x14, 0x14, 0x14, 0x08,// p 99 | 0x08, 0x14, 0x14, 0x18, 0x7C,// q 100 | 0x7C, 0x08, 0x04, 0x04, 0x08,// r 101 | 0x48, 0x54, 0x54, 0x54, 0x20,// s 102 | 0x04, 0x3F, 0x44, 0x40, 0x20,// t 103 | 0x3C, 0x40, 0x40, 0x20, 0x7C,// u 104 | 0x1C, 0x20, 0x40, 0x20, 0x1C,// v 105 | 0x3C, 0x40, 0x30, 0x40, 0x3C,// w 106 | 0x44, 0x28, 0x10, 0x28, 0x44,// x 107 | 0x0C, 0x50, 0x50, 0x50, 0x3C,// y 108 | 0x44, 0x64, 0x54, 0x4C, 0x44,// z 109 | 0x00, 0x08, 0x36, 0x41, 0x00,// { 110 | 0x00, 0x00, 0x7F, 0x00, 0x00,// | 111 | 0x00, 0x41, 0x36, 0x08, 0x00,// } 112 | 0x08, 0x08, 0x2A, 0x1C, 0x08,// -> 113 | 0x08, 0x1C, 0x2A, 0x08, 0x08 // <- 114 | }; 115 | Font Font_5x7(5, 7, 96, nullptr, Font5x7_data); 116 | 117 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/test_tostring.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | enum { kConsoleWidth = 80, kStatusWidth = 12 }; 7 | 8 | int gFails = 0; 9 | const char* COLOR_GREEN = isatty(1) ? "\e[1;32m" : ""; 10 | const char* COLOR_RED = isatty(1) ? "\e[1;31m" : ""; 11 | const char* COLOR_NORMAL = isatty(1) ? "\e[0m" : ""; 12 | const char* spaces = " "; 13 | const char* border = "======================================================================================================"; 14 | int summary() 15 | { 16 | auto borderLen = (kConsoleWidth - kStatusWidth - 17 + 2) / 2; 17 | if (gFails) { 18 | printf("%.*s %2d tests %sFAILED%s %.*s\n", borderLen, border, gFails, COLOR_RED, COLOR_NORMAL, borderLen, border); 19 | return 1; 20 | } 21 | else { 22 | printf("%.*s All test %sPASSED%s %.*s\n", borderLen, border, COLOR_GREEN, COLOR_NORMAL, borderLen, border); 23 | return 0; 24 | } 25 | } 26 | 27 | template 28 | void verify(const char* testName, const char* expected, Args... args) 29 | { 30 | int lenExpected = strlen(expected); 31 | int lenOutBuf = std::max(lenExpected, 128) + 1; 32 | char* buf = (char*)alloca(lenOutBuf); 33 | int lineLen = lenExpected + strlen(testName) + 4; 34 | int numSpaces = kConsoleWidth - kStatusWidth - lineLen; 35 | if (numSpaces < 0) { 36 | numSpaces = 0; 37 | } 38 | printf("%s:%.*s\"%s\" ", testName, numSpaces, spaces, expected); 39 | auto result = toString(buf, lenOutBuf, args...); 40 | if (!result) { 41 | printf("%sNULL-return%s\n", COLOR_RED, COLOR_NORMAL); 42 | gFails++; 43 | return; 44 | } 45 | if (strcmp(buf, expected)) { 46 | printf("%sMISMATCH%s\n", COLOR_RED, COLOR_NORMAL); 47 | printf("%.*sinstead got: \"%s\"\n", (int)strlen(testName) + numSpaces + lenExpected - (int)strlen(buf)- 12, spaces, buf); 48 | gFails++; 49 | } else { 50 | printf("%sOK%s\n", COLOR_GREEN, COLOR_NORMAL); 51 | } 52 | } 53 | 54 | int main() 55 | { 56 | verify("simple string", "Test message", "Test message"); 57 | verify("char", "x", 'x'); 58 | verify("repeat char", "xxxxxxxxxx", rptChar('x', 10)); 59 | verify("repeat char once", "x", rptChar('x', 1)); 60 | verify("repeat char zero times", "", rptChar('x', 0)); 61 | verify("decimal+", "12345678", 12345678); 62 | verify("decimal-", "-567890", -567890); 63 | verify("bigDecimal+", "1234567890123456789", 1234567890123456789LL); 64 | verify("bigDecimal-", "-1234567890123456789", -1234567890123456789LL); 65 | 66 | verify("hex(+)", "12abcde", fmtHex(0x12abcde)); 67 | verify("hex(+) w/prefix", "0x12abcde", fmtHex(0x12abcde)); 68 | verify("hex16(-1)", "ffff", fmtHex16(-1)); 69 | verify("hex32(-1)", "ffffffff", fmtHex32(-1)); 70 | verify("hex32(+)", "deadbeef", fmtHex32(0xdeadbeef)); 71 | 72 | verify("bigHex(+)", "0xdeadbeefcafebabe", fmtHex(0xdeadbeefcafebabeLL)); 73 | verify("bigHex(-)", "-0x7eadbeefcafebabe", fmtHex(-0x7eadbeefcafebabeLL)); 74 | 75 | verify("bin(-1)", "-1", fmtBin(-1)); 76 | verify("bin(-1) w/prefix", "-0b1", fmtBin(-1)); 77 | verify("bin8(-1)", "11111111", fmtBin8(-1)); 78 | verify("bin16(-1)", "1111111111111111", fmtBin16(-1)); 79 | verify("bin32(-1)", "11111111111111111111111111111111", fmtBin32(-1)); 80 | verify("bin32(-1) w/prefix", "0b11111111111111111111111111111111", fmtBin((uint32_t)-1)); 81 | verify("bin32(+)", "10101110111101111010000001011001", fmtBin(0b10101110111101111010000001011001)); 82 | verify("bin32(+) w/prefix", "0b10101110111101111010000001011001", fmtBin(0b10101110111101111010000001011001)); 83 | 84 | verify("float single dec", "44.9", fmtFp<1>(44.9f)); 85 | verify("float round-up", "44.1", fmtFp<1>(44.09f)); 86 | verify("float round-up", "45.0", fmtFp<1>(44.95f)); 87 | verify("float1 round-down .4", "44.4", fmtFp<1>(44.44)); 88 | verify("float6 round-down .4", "44.112233", fmtFp<6>(44.1122334)); 89 | 90 | verify("float1 round-up 44.45", "44.5", fmtFp<1>(44.45)); 91 | verify("float6 round-up 44.1122335", "44.112234", fmtFp<6>(44.1122335)); 92 | 93 | // 4.1122345 is represented as 4.112234499999999, which causes down-rounding on .5 94 | // This happens also with printf("%.6f") with this value 95 | verify("float6 round-up fail rounding error: 4.1122345", "4.112234", fmtFp<6>(4.1122345)); 96 | // Doesn't occur on 1.112235 97 | verify("float6 round-up 1.1122345", "1.112235", fmtFp<6>(1.1122345)); 98 | //==== 99 | verify("float round-down", "44.9", fmtFp<1>(44.94f)); 100 | verify("float round-up", "-45.0", fmtFp<1>(-44.95f)); 101 | verify("float round-down", "-44.9", fmtFp<1>(-44.94f)); 102 | return summary(); 103 | } 104 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/drivers/st756x.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STM32PP_ST756x_H 2 | #define STM32PP_ST756x_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define ST756x_LCD_CMD_DISPLAY_OFF 0xAE 12 | #define ST756x_LCD_CMD_DISPLAY_ON 0xAF 13 | 14 | #define ST756x_LCD_CMD_SET_DISP_START_LINE 0x40 15 | #define ST756x_LCD_CMD_SET_PAGE 0xB0 16 | 17 | #define ST756x_LCD_CMD_SET_COLUMN_UPPER 0x10 18 | #define ST756x_LCD_CMD_SET_COLUMN_LOWER 0x00 19 | 20 | #define ST756x_LCD_CMD_SET_SEG_NORMAL 0xA0 21 | #define ST756x_LCD_CMD_SET_SEG_REVERSE 0xA1 22 | 23 | #define ST756x_LCD_CMD_SET_COM_NORMAL 0xC0 24 | #define ST756x_LCD_CMD_SET_COM_REVERSE 0xC8 25 | 26 | #define ST756x_LCD_CMD_SET_DISP_NORMAL 0xA6 27 | #define ST756x_LCD_CMD_SET_DISP_INVERSE 0xA7 28 | 29 | #define ST756x_LCD_CMD_SET_ALLPTS_NORMAL 0xA4 30 | #define ST756x_LCD_CMD_SET_ALLPTS_ON 0xA5 31 | 32 | #define ST756x_LCD_CMD_SET_BIAS_9 0xA2 33 | #define ST756x_LCD_CMD_SET_BIAS_7 0xA3 34 | 35 | #define ST756x_LCD_CMD_POWER_ON 0x28 | 0b0111 // = 0x2f 36 | #define ST756x_LCD_CMD_POWER_OFF 0x28 37 | 38 | #define ST756x_LCD_CMD_SET_VREG_RATIO 0x20 39 | #define ST756x_LCD_CMD_SET_EV 0x81 40 | #define ST756x_LCD_CMD_RESET 0xE2 41 | 42 | #define ST756x_LCD_CMD_RMW 0xE0 43 | #define ST756x_LCD_CMD_RMW_CLEAR 0xEE 44 | /* 45 | #define ST7565_LCD_CMD_SET_STATIC_OFF 0xAC 46 | #define ST7565_LCD_CMD_SET_STATIC_ON 0xAD 47 | #define ST7565_LCD_CMD_SET_STATIC_REG 0x0 48 | #define ST7565_LCD_CMD_SET_BOOSTER_FIRST 0xF8 49 | #define ST7565_LCD_CMD_SET_BOOSTER_234 0 50 | #define ST7565_LCD_CMD_SET_BOOSTER_5 1 51 | #define ST7565_LCD_CMD_SET_BOOSTER_6 3 52 | #define ST7565_LCD_CMD_NOP 0xE3 53 | #define ST7565_LCD_CMD_TEST 0xF0 54 | */ 55 | 56 | template 57 | class ST7567_Driver 58 | { 59 | protected: 60 | IO& mIo; 61 | enum: uint16_t { kBufSize = Width * Height / 8 }; 62 | uint8_t mBuf[kBufSize]; 63 | public: 64 | ST7567_Driver(IO& io): mIo(io) {} 65 | uint8_t* rawBuf() { return mBuf; } 66 | static int16_t width() { return Width; } 67 | static int16_t height() { return Height; } 68 | void cmd(uint8_t byte) { mIo.send(byte); } 69 | void setContrast(uint8_t val) 70 | { 71 | cmd(ST756x_LCD_CMD_SET_EV); // set EV command 72 | cmd(val); // EV value 73 | } 74 | void powerOn() { cmd(ST756x_LCD_CMD_POWER_ON); } 75 | void powerOff() { cmd(ST756x_LCD_CMD_POWER_OFF); } 76 | void displayOn() { cmd(ST756x_LCD_CMD_DISPLAY_ON); } 77 | void displayOff() { cmd(ST756x_LCD_CMD_DISPLAY_OFF); } 78 | void updateScreen(void) 79 | { 80 | int ofs = 0; 81 | for (int page = 0; page < Height / 8; page++) 82 | { 83 | cmd(ST756x_LCD_CMD_SET_PAGE | page); // set page address 84 | cmd(ST756x_LCD_CMD_SET_COLUMN_UPPER); // set column hi nibble 0 85 | cmd(0x00); // set column low nibble 0 86 | nsDelay(500); 87 | DtCmdPin::set(); 88 | 89 | int end = ofs + Width; 90 | while(ofs < end) 91 | { 92 | mIo.send(mBuf[ofs++]); 93 | } 94 | nsDelay(500); 95 | DtCmdPin::clear(); 96 | } 97 | } 98 | bool init() 99 | { 100 | static_assert(Height % 8 == 0); 101 | memset(mBuf, 0x00, 1024); // clear display buffer 102 | RstPin::enableClockAndSetMode(GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL); 103 | if (DtCmdPin::kClockId != RstPin::kClockId) 104 | { 105 | rcc_periph_clock_enable(DtCmdPin::kClockId); 106 | } 107 | DtCmdPin::setMode(GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL); 108 | 109 | DtCmdPin::clear(); 110 | RstPin::clear(); 111 | usDelay(10); 112 | RstPin::set(); 113 | usDelay(10); 114 | 115 | /* Send Commands */ 116 | cmd(ST756x_LCD_CMD_SET_BIAS_7); /* Setup 1/7th Bias Level */ 117 | cmd(ST756x_LCD_CMD_SET_SEG_NORMAL); /* Horizontal (SEG) direction */ 118 | cmd(ST756x_LCD_CMD_SET_COM_REVERSE); /* Vertical (COM) direction */ 119 | cmd(ST756x_LCD_CMD_SET_VREG_RATIO | 0x2); /* Set LCD operating voltage */ 120 | setContrast(0x18); 121 | powerOn(); 122 | msDelay(10); 123 | displayOn(); 124 | updateScreen(); 125 | return true; 126 | } 127 | }; 128 | template 129 | using ST7567 = DisplayGfx>; 130 | 131 | #endif 132 | -------------------------------------------------------------------------------- /stm32++/tests/flash/flash.cpp: -------------------------------------------------------------------------------- 1 | #define STM32PP_FLASH_SIMULATE_POWER_LOSS 2 | #include 3 | #include 4 | using namespace flash; 5 | 6 | uint16_t page1[512]; 7 | uint16_t page2[512]; 8 | std::string dumpPage(uint8_t* page) 9 | { 10 | std::string ret; 11 | auto start = page; 12 | auto end = page + 1024; 13 | for (int col = 0; page < end; page++, col++) 14 | { 15 | if (col >= 20) 16 | { 17 | ret.append(" ").append(std::to_string(page - start)).append("\r\n"); 18 | col = 0; 19 | } 20 | auto ch = *page; 21 | if (ch >= 32 && ch < 129) 22 | { 23 | ret.append(" ") += ch; 24 | } 25 | else 26 | { 27 | char buf[4]; 28 | snprintf(buf, 4, "%02x", ch); 29 | ret.append(" ").append(buf); 30 | } 31 | } 32 | return ret; 33 | } 34 | std::string dumpPage(uint16_t* page) 35 | { 36 | return dumpPage((uint8_t*) page); 37 | } 38 | 39 | FlashValueStore<> store; 40 | std::string values[] = { 41 | "this is a test message", 42 | "new value", 43 | "I am having a huge list of items (15000) to be populated on the items drop down in the front end.", 44 | "Hence I have made an AJAX call (triggered upon a selection of a Company) and this AJAX call to made", 45 | "to an action method in the Controller and this action method populates the list of service items and", 46 | "returns it back to the AJAX call via response. This is where my AJAX call is failing.", 47 | "If i have about 100 - 500 items, the ajax call works. How do I fix this issue?", 48 | "Here, you specify the length as an int argument to printf(), which treats the '*' in the format as a request to get the length from an argument.", 49 | "shouldn't be necessary unless the compiler is far more broken than not implicitly converting char arguments to int.", 50 | "suggests that it is in fact not doing the conversion, and picking up the other 8 bits from trash on the stack or left over in a register", 51 | "Incorrect. Since printf is a variadic function, arguments of type char or unsigned char are promoted to int", 52 | "unsigned char gets promoted to int because printf() is a variadic function (assuming is included). If the header isn't included, then (a) it should be and (b) you don't have a prototype in scope so the unsigned char will still be promoted to int", 53 | "Here, you specify the length as an int argument to printf(), which treats the '*' in the format as a request to get the length from an argument.", 54 | "shouldn't be necessary unless the compiler is far more broken than not implicitly converting char arguments to int.", 55 | 56 | "to an action method in the Controller and this action method populates the list of service items and", 57 | "returns it back to the AJAX call via response. This is where my AJAX call is failing.", 58 | "If i have about 100 - 500 items, the ajax call works. How do I fix this issue?", 59 | "Here, you specify the length as an int argument to printf(), which treats the '*' in the format as a request to get the length from an argument.", 60 | "shouldn't be necessary unless the compiler is far more broken than not implicitly converting char arguments to int.", 61 | "suggests that it is in fact not doing the conversion, and picking up the other 8 bits from trash on the stack or left over in a register", 62 | 63 | }; 64 | bool setString(uint8_t key, const std::string& val) 65 | { 66 | if (val.size() > 255) 67 | { 68 | printf("WARN: setString: string length is more than 255 bytes"); 69 | } 70 | return store.setValue(key, val.c_str(), val.size(), false); 71 | } 72 | std::string getString(uint8_t key) 73 | { 74 | uint8_t len; 75 | uint8_t* data = store.getRawValue(key, len); 76 | if (!data) { 77 | assert(len == 0); // len is a fatal error 78 | return std::string(); 79 | } else { 80 | return std::string((const char*)data, len); 81 | } 82 | } 83 | int32_t DefaultFlashDriver::failAtWriteNum = 0x7fffffff; 84 | 85 | int main() 86 | { 87 | for (int32_t failWord = 0; failWord < 1400; failWord++) 88 | { 89 | printf("==== %d press any key ====", failWord); 90 | getchar(); 91 | std::string lastWrittenOk; 92 | try 93 | { 94 | DefaultFlashDriver::failAtWriteNum = failWord; 95 | store.init((size_t)page1, (size_t)page2); 96 | for (auto& str: values) { 97 | setString(0xab, str); 98 | lastWrittenOk = str; 99 | } 100 | } 101 | catch(int) 102 | { 103 | auto pageToDump = store.activePage(); 104 | if (!pageToDump) { 105 | pageToDump = (uint8_t*)page1; 106 | } 107 | if (pageToDump == (uint8_t*)page1) { 108 | printf("exception %d caught, page1 is active:\n%s\n", failWord, dumpPage(pageToDump).c_str()); 109 | } else { 110 | printf("exception %d caught, page2 is active:\n%s\n", failWord, dumpPage(pageToDump).c_str()); 111 | //printf("page1:\n%s\n", dumpPage(page1).c_str()); 112 | } 113 | DefaultFlashDriver::failAtWriteNum = 0x7fffffff; 114 | store.init((size_t)page1, (size_t)page2); 115 | auto val = getString(0xab); 116 | if (val == lastWrittenOk) 117 | { 118 | printf("val = last written ok(%s)\n", val.c_str()); 119 | } else 120 | { 121 | printf("val = %s\n", val.c_str()); 122 | } 123 | DefaultFlashDriver::erasePage((uint8_t*)page1); 124 | DefaultFlashDriver::erasePage((uint8_t*)page2); 125 | } 126 | } 127 | return 0; 128 | } 129 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/drivers/ssd1306.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: Alexander Vassilev 3 | * @copyright BSD License 4 | */ 5 | #ifndef SSD1306_HPP_INCLUDED 6 | #define SSD1306_HPP_INCLUDED 7 | 8 | //#include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define SSD1306_SETCONTRAST 0x81 15 | #define SSD1306_DISPLAYALLON_RESUME 0xA4 16 | #define SSD1306_DISPLAYALLON 0xA5 17 | #define SSD1306_NORMALDISPLAY 0xA6 18 | #define SSD1306_INVERTDISPLAY 0xA7 19 | #define SSD1306_DISPLAYOFF 0xAE 20 | #define SSD1306_DISPLAYON 0xAF 21 | 22 | #define SSD1306_SETDISPLAYOFFSET 0xD3 23 | #define SSD1306_SETCOMPINS 0xDA 24 | 25 | #define SSD1306_SETVCOMDETECT 0xDB 26 | 27 | #define SSD1306_SETDISPLAYCLOCKDIV 0xD5 28 | #define SSD1306_SETPRECHARGE 0xD9 29 | 30 | #define SSD1306_SETMULTIPLEX 0xA8 31 | 32 | #define SSD1306_SETLOWCOLUMN 0x00 33 | #define SSD1306_SETHIGHCOLUMN 0x10 34 | 35 | #define SSD1306_SETSTARTLINE 0x40 36 | 37 | #define SSD1306_MEMORYMODE 0x20 38 | #define SSD1306_COLUMNADDR 0x21 39 | #define SSD1306_PAGEADDR 0x22 40 | 41 | #define SSD1306_COMSCANINC 0xC0 42 | #define SSD1306_COMSCANDEC 0xC8 43 | 44 | #define SSD1306_SEGREMAP 0xA0 45 | 46 | #define SSD1306_CHARGEPUMP 0x8D 47 | 48 | #define SSD1306_EXTERNALVCC 0x1 49 | #define SSD1306_SWITCHCAPVCC 0x2 50 | 51 | // Scrolling #defines 52 | #define SSD1306_ACTIVATE_SCROLL 0x2F 53 | #define SSD1306_DEACTIVATE_SCROLL 0x2E 54 | #define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3 55 | #define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26 56 | #define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27 57 | #define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 58 | #define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A 59 | 60 | enum { kOptExternVcc = 1 }; 61 | template 62 | class SSD1306_Driver 63 | { 64 | protected: 65 | /* SSD1306 data buffer */ 66 | enum: uint16_t { kBufSize = W * H / 8 }; 67 | uint8_t mBuf[kBufSize]; 68 | IO& mIo; 69 | uint8_t mAddr; 70 | constexpr static uint16_t mkType(uint8_t w, uint8_t h) { return (w << 8) | h; } 71 | public: 72 | enum: uint16_t { 73 | kType = mkType(W, H), 74 | SSD1306_128_32 = mkType(128, 32), 75 | SSD1306_128_64 = mkType(128, 64), 76 | SSD1306_96_16 = mkType(96,16) 77 | }; 78 | uint8_t* rawBuf() { return mBuf; } 79 | static int16_t width() { return W; } 80 | static int16_t height() { return H; } 81 | SSD1306_Driver(IO& intf, uint8_t addr=0x3C): mIo(intf), mAddr(addr) {} 82 | bool init() 83 | { 84 | /* Check if LCD connected to I2C */ 85 | if (!mIo.isDeviceConnected(mAddr)) 86 | return false; 87 | /* LCD needs some time after initial power up */ 88 | 89 | // Init sequence 90 | cmd(SSD1306_DISPLAYOFF); // cmd 0xAE 91 | cmd(SSD1306_SETDISPLAYCLOCKDIV, 0xf0); // cmd 0xD5, the suggested ratio 0x80 92 | 93 | cmd(SSD1306_SETMULTIPLEX, H - 1); // cmd 0xA8 94 | cmd(SSD1306_SETDISPLAYOFFSET, 0x0); // cmd 0xD3, no offset 95 | cmd(SSD1306_SETSTARTLINE | 0x0); // line #0 96 | cmd(SSD1306_MEMORYMODE, 0x00); // cmd 0x20, 0x0 horizontal then vertical increment, act like ks0108 97 | cmd(SSD1306_SEGREMAP | 0x1); 98 | cmd(SSD1306_COMSCANDEC); 99 | if (kType == SSD1306_128_32) 100 | { 101 | cmd(SSD1306_SETCOMPINS, 0x02); // 0xDA 102 | } 103 | else if (kType == SSD1306_128_64) 104 | { 105 | cmd(SSD1306_SETCOMPINS, 0x12); 106 | } 107 | else if (kType == SSD1306_96_16) 108 | { 109 | cmd(SSD1306_SETCOMPINS, 0x02); 110 | } 111 | 112 | cmd(SSD1306_SETPRECHARGE, Opts&kOptExternVcc ? 0x22 : 0xF1); // 0xd9 113 | cmd(SSD1306_SETVCOMDETECT, 0x40); // 0xDB 114 | cmd(SSD1306_DISPLAYALLON_RESUME); // 0xA4 115 | cmd(SSD1306_NORMALDISPLAY); // 0xA6 116 | 117 | cmd(SSD1306_DEACTIVATE_SCROLL); 118 | setContrast(0x8F); 119 | cmd(SSD1306_CHARGEPUMP, Opts&kOptExternVcc ? 0x10 : 0x14); // 0x8D 120 | cmd(SSD1306_DISPLAYON); //--turn on oled panel 121 | return true; 122 | } 123 | void setContrast(uint8_t val) 124 | { 125 | cmd(SSD1306_SETCONTRAST, val); // 0x81 126 | } 127 | template ::value> 128 | typename std::enable_if::type sendBuffer() 129 | { 130 | mIo.dmaTxStart(mBuf, sizeof(mBuf)); 131 | } 132 | template ::value> 133 | typename std::enable_if::type sendBuffer() 134 | { 135 | mIo.blockingSend(mBuf, W*H/8); 136 | mIo.stop(); 137 | } 138 | void updateScreen() 139 | { 140 | cmd(SSD1306_COLUMNADDR, 0, W-1); 141 | cmd(SSD1306_PAGEADDR, 0, H/8-1); 142 | 143 | mIo.startSend(mAddr, false); 144 | mIo.sendByte(0x40); 145 | sendBuffer(); 146 | } 147 | template ::value> 148 | typename std::enable_if::type waitTxComplete() 149 | { 150 | while (mIo.txBusy()); 151 | } 152 | template ::value> 153 | typename std::enable_if::type waitTxComplete() 154 | {} 155 | template 156 | void cmd(Args... args) 157 | { 158 | waitTxComplete(); 159 | mIo.startSend(mAddr); 160 | mIo.sendByte(0); 161 | mIo.sendByte(args...); 162 | mIo.stop(); 163 | } 164 | void powerOn() 165 | { 166 | cmd(SSD1306_CHARGEPUMP, 0x14); 167 | cmd(0xAF); 168 | } 169 | void powerOff() 170 | { 171 | cmd(0xAE); 172 | cmd(SSD1306_CHARGEPUMP, 0x10); 173 | } 174 | }; 175 | 176 | template 177 | using SSD1306 = DisplayGfx>; 178 | #endif 179 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/spi.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Vassilev 3 | * @copyright BSD License 4 | */ 5 | 6 | #ifndef STM32PP_SPI_H 7 | #define STM32PP_SPI_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | namespace nsspi 18 | { 19 | struct Baudrate 20 | { 21 | uint32_t mRate; 22 | Baudrate(uint32_t rate): mRate(rate) {} 23 | }; 24 | 25 | uint8_t clockRatioToCode(uint8_t ratio); 26 | uint16_t codeToClockRatio(uint8_t code); 27 | 28 | uint32_t clockPrescaler(Baudrate rate, uint32_t apbFreq) 29 | { 30 | return clockRatioToCode((apbFreq + rate.mRate - 1) / rate.mRate); // round up 31 | } 32 | uint32_t clockPrescaler(uint8_t ratio, uint32_t apbFreq) 33 | { 34 | return clockRatioToCode(ratio); 35 | } 36 | 37 | enum: uint32_t 38 | { 39 | kDisableOutput = 1, 40 | kDisableInput = 2, 41 | kHardwareNSS = 4, 42 | kSoftwareNSS = 0, 43 | kIdleClockIsLow = 8, 44 | kIdleClockIsHigh = 0, 45 | k16BitFrame = 16, 46 | k8BitFrame = 0, 47 | kFirstClockTransition = 32, 48 | kSecondClockTransition = 0, 49 | kLsbFirst = 64, 50 | kMsbFirst = 0 51 | }; 52 | 53 | template 54 | class SpiMaster: public PeriphInfo 55 | { 56 | public: 57 | template 58 | void init(S speed, uint32_t config) 59 | { 60 | rcc_periph_clock_enable(this->kClockId); 61 | rcc_periph_clock_enable(PeriphInfokPortId>::kClockId); 62 | 63 | uint32_t outputPins = this->kPinSck; 64 | if ((config & kDisableOutput) == 0) { 65 | outputPins |= this->kPinMosi; 66 | } 67 | gpio_set_mode(this->kPortId, GPIO_MODE_OUTPUT_50_MHZ, 68 | GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, outputPins); 69 | 70 | if (config & kHardwareNSS) { 71 | gpio_set_mode(this->kPortId, GPIO_MODE_OUTPUT_50_MHZ, 72 | GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, this->kPinNss); 73 | spi_enable_ss_output(SPI); 74 | } 75 | 76 | if ((config & kDisableInput) == 0) { 77 | gpio_set_mode(this->kPortId, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, 78 | this->kPinMiso); 79 | } 80 | /* Reset SPI, SPI_CR1 register cleared, SPI is disabled */ 81 | spi_reset(SPI); 82 | 83 | spi_init_master(SPI, clockPrescaler(speed, this->apbFreq()), 84 | (config & kIdleClockIsLow) ? SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE : SPI_CR1_CPOL_CLK_TO_1_WHEN_IDLE, 85 | (config & kFirstClockTransition) ? SPI_CR1_CPHA_CLK_TRANSITION_1 : SPI_CR1_CPHA_CLK_TRANSITION_2, 86 | (config & k16BitFrame) ? SPI_CR1_DFF_16BIT : SPI_CR1_DFF_8BIT, 87 | (config & kLsbFirst) ? SPI_CR1_LSBFIRST : SPI_CR1_MSBFIRST); 88 | 89 | spi_enable(SPI); 90 | } 91 | uint32_t baudrate() const 92 | { 93 | return this->apbFreq() / codeToClockRatio(SPI_CR1(SPI) & (0b111 << 3)); 94 | } 95 | void send(uint16_t data) 96 | { 97 | spi_send(SPI, data); 98 | } 99 | uint16_t recv() 100 | { 101 | return spi_read(SPI); 102 | } 103 | bool isBusy() const { return (SPI_SR(SPI) & SPI_SR_BSY) != 0; } 104 | void waitComplete() const { while (SPI_SR(SPI) & SPI_SR_BSY); } 105 | uint8_t dmaWordSize() const 106 | { 107 | return ((SPI_CR1(SPI) & SPI_CR1_DFF) == SPI_CR1_DFF_16BIT) ? 2 : 1; 108 | } 109 | void dmaStartPeripheralTx() { spi_enable_tx_dma(SPI); } 110 | void dmaStartPeripheralRx() { spi_enable_rx_dma(SPI); } 111 | //WARNING: The dmaStopPerpheralXX can be called from an ISR 112 | void dmaStopPeripheralTx() 113 | { 114 | waitComplete(); 115 | spi_disable_tx_dma(SPI); 116 | this->stop(); 117 | } 118 | void dmaStopPeripheralRx() 119 | { 120 | waitComplete(); 121 | spi_disable_rx_dma(SPI); 122 | } 123 | }; 124 | 125 | uint8_t clockRatioToCode(uint8_t ratio) 126 | { 127 | if (ratio <= 2) { 128 | return SPI_CR1_BAUDRATE_FPCLK_DIV_2; 129 | } else if (ratio <= 4) { 130 | return SPI_CR1_BAUDRATE_FPCLK_DIV_4; 131 | } else if (ratio <= 8) { 132 | return SPI_CR1_BAUDRATE_FPCLK_DIV_8; 133 | } else if (ratio <= 16) { 134 | return SPI_CR1_BAUDRATE_FPCLK_DIV_16; 135 | } else if (ratio <= 32) { 136 | return SPI_CR1_BAUDRATE_FPCLK_DIV_32; 137 | } else if (ratio <= 64) { 138 | return SPI_CR1_BAUDRATE_FPCLK_DIV_64; 139 | } else if (ratio <= 128) { 140 | return SPI_CR1_BAUDRATE_FPCLK_DIV_128; 141 | } else { 142 | return SPI_CR1_BAUDRATE_FPCLK_DIV_256; 143 | } 144 | } 145 | 146 | uint16_t codeToClockRatio(uint8_t code) 147 | { 148 | switch (code) 149 | { 150 | case SPI_CR1_BAUDRATE_FPCLK_DIV_2: return 2; 151 | case SPI_CR1_BAUDRATE_FPCLK_DIV_4: return 4; 152 | case SPI_CR1_BAUDRATE_FPCLK_DIV_8: return 8; 153 | case SPI_CR1_BAUDRATE_FPCLK_DIV_16: return 16; 154 | case SPI_CR1_BAUDRATE_FPCLK_DIV_32: return 32; 155 | case SPI_CR1_BAUDRATE_FPCLK_DIV_64: return 64; 156 | case SPI_CR1_BAUDRATE_FPCLK_DIV_128: return 128; 157 | case SPI_CR1_BAUDRATE_FPCLK_DIV_256: return 256; 158 | default: __builtin_trap(); 159 | } 160 | } 161 | } 162 | 163 | STM32PP_PERIPH_INFO(SPI1) 164 | enum: uint32_t { kPortId = GPIOA }; 165 | enum: uint16_t { kPinSck = GPIO_SPI1_SCK, kPinNss = GPIO_SPI1_NSS, 166 | kPinMosi = GPIO_SPI1_MOSI, kPinMiso = GPIO_SPI1_MISO }; 167 | static constexpr rcc_periph_clken kClockId = RCC_SPI1; 168 | static uint32_t apbFreq() { return rcc_apb2_frequency; } 169 | // DMA info 170 | enum: uint32_t { kDmaTxId = DMA1, kDmaRxId = DMA1 }; 171 | enum: uint8_t { 172 | kDmaTxChannel = DMA_CHANNEL3, 173 | kDmaRxChannel = DMA_CHANNEL2 174 | }; 175 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&SPI1_DR); } 176 | static const uint32_t dmaTxDataRegister() { return (uint32_t)(&SPI1_DR); } 177 | }; 178 | 179 | STM32PP_PERIPH_INFO(SPI2) 180 | enum: uint32_t { kPortId = GPIOB }; 181 | enum: uint16_t { kPinSck = GPIO_SPI2_SCK, kPinNss = GPIO_SPI2_NSS, 182 | kPinMosi = GPIO_SPI2_MOSI, kPinMiso = GPIO_SPI2_MISO }; 183 | static constexpr rcc_periph_clken kClockId = RCC_SPI2; 184 | static uint32_t apbFreq() { return rcc_apb1_frequency; } 185 | 186 | enum: uint32_t { kDmaTxId = DMA1, kDmaRxId = DMA1 }; 187 | enum: uint8_t { 188 | kDmaTxChannel = DMA_CHANNEL5, 189 | kDmaRxChannel = DMA_CHANNEL4 190 | }; 191 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&SPI2_DR); } 192 | static const uint32_t dmaTxDataRegister() { return (uint32_t)(&SPI2_DR); } 193 | }; 194 | 195 | #endif 196 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/timeutl.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | Timing utilities for STM32 3 | * @author Alexander Vassilev 4 | * @copyright BSD license 5 | */ 6 | #ifndef _TIME_UTILS_H 7 | #define _TIME_UTILS_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | class DwtCounter 14 | { 15 | public: 16 | typedef uint32_t Word; 17 | static volatile uint32_t get() { return (volatile uint32_t)DWT_CYCCNT; } 18 | static volatile uint32_t ticks() { return (volatile uint32_t)DWT_CYCCNT; } 19 | 20 | template 21 | static W ticksToNs(W ticks) 22 | { return (ticks * 1000) / (rcc_ahb_frequency/1000000); } 23 | 24 | template 25 | static W ticksToUs(W ticks) 26 | { return ticks / (rcc_ahb_frequency/1000000); } 27 | 28 | template 29 | static W ticksToMs(W ticks) 30 | { return ticks / (rcc_ahb_frequency/1000); } 31 | 32 | template 33 | static W ticksTo10Ms(W ticks) 34 | { return ticks / (rcc_ahb_frequency/100); } 35 | 36 | template 37 | static W ticksTo100Ms(W ticks) 38 | { return ticks / (rcc_ahb_frequency/10); } 39 | 40 | template 41 | static W ticksToSec(W ticks) 42 | { return ticks / rcc_ahb_frequency; } 43 | 44 | template 45 | static volatile void delay(uint32_t t) // not accurate below ~400ns 46 | { 47 | #ifndef NDEBUG 48 | enum { kCycleOverhead = 160 + Corr}; //in debug build, the func call overhead is quite high 49 | #else 50 | enum { kCycleOverhead = 32 + Corr}; 51 | #endif 52 | uint32_t now = get(); 53 | uint32_t ticks = t * (rcc_ahb_frequency/1000) / Div; 54 | if (ticks > kCycleOverhead) 55 | ticks -= kCycleOverhead; 56 | else 57 | return; 58 | register uint32_t tsEnd = now + ticks; 59 | if (tsEnd < now) //will wrap 60 | { 61 | while(tsEnd < get()); // wait for get() to also wrap 62 | } 63 | while(get() < tsEnd); 64 | } 65 | }; 66 | 67 | // Should never be actually instantiated with negative values, 68 | // but satisfies compiler checks. 69 | template 70 | typename std::enable_if<(Count <= 0), void>::type nop() 71 | { 72 | } 73 | 74 | template 75 | typename std::enable_if<(Count == 1), void>::type nop() 76 | { 77 | asm volatile("nop;"); 78 | } 79 | 80 | // WARNING: for Count >=30, there is huge random overhead, probably result of 81 | // cache pre-fetch, as the nop sequence does not fit in the cache anymore 82 | template 83 | typename std::enable_if<(Count > 1), void>::type nop() 84 | { 85 | asm volatile("nop;"); 86 | nop(); 87 | } 88 | 89 | template 90 | typename std::enable_if<(Ns <= 100), void>::type nsDelayPrecise() 91 | { 92 | nop<(Ns * (Freq / 1000)) / 1000000>(); 93 | } 94 | 95 | template 96 | typename std::enable_if<(Ns > 100), void>::type nsDelayPrecise() 97 | { 98 | 99 | //Loop takes 5 cycles if clock is 72 MHz, and 3 cycles if clock is 24 MHz 100 | enum: uint32_t 101 | { 102 | kOverhead = 2, 103 | kTicks = ((Ns * (Freq / 1000)) + 500000) / 1000000, 104 | kTicksPerLoop = (Freq > 24000000) ? 6 : 3 105 | }; 106 | 107 | if (kTicks <= kOverhead) 108 | return; 109 | 110 | nop<(kTicks-kOverhead) % kTicksPerLoop>(); 111 | enum: uint32_t { kLoops = (kTicks-kOverhead) / kTicksPerLoop }; 112 | if (kLoops == 0) 113 | return; 114 | register uint32_t loops = kLoops; 115 | asm volatile("0:" "SUBS %[count], 1;" "BNE 0b;" :[count]"+r"(loops)); 116 | } 117 | 118 | template 119 | void nsDelay(uint32_t ns) { DwtCounter::delay<1000000, TickCorr>(ns); } 120 | template 121 | void usDelay(uint32_t us) { DwtCounter::delay<1000, TickCorr>(us); } 122 | template 123 | static inline void msDelay(uint32_t ms) { DwtCounter::delay<1, TickCorr>(ms); } 124 | 125 | /* 64-bit timestamp clock. Uses T as the actual time source, and implements 126 | * wrapping protection 127 | * @param Intr - whether to implement concurrency guard if used in interrupts. 128 | * Necessary because we have internal state that may be updated in the get() 129 | * method 130 | */ 131 | template 132 | class TimeClockNoWrap; 133 | 134 | template 135 | class TimeClockNoWrap: public T 136 | { 137 | typename T::Word mHighWord = 0; 138 | typename T::Word mLastCount; 139 | protected: 140 | using T::get; // hide the get() method if the original source 141 | public: 142 | TimeClockNoWrap(): mLastCount(T::get()){} 143 | int64_t nanotime() { return T::ticksToNs(this->ticks()); } 144 | int64_t microtime() { return T::ticksToUs(this->ticks()); } 145 | int64_t millitime() { return T::ticksToMs(this->ticks()); } 146 | int64_t ticks() 147 | { 148 | typename T::Word now = T::get(); 149 | if (now < mLastCount) 150 | mHighWord++; 151 | mLastCount = now; 152 | return ((int64_t)mHighWord << (sizeof(typename T::Word)*8)) | now; 153 | } 154 | }; 155 | 156 | template 157 | class TimeClockNoWrap: public TimeClockNoWrap 158 | { 159 | typedef TimeClockNoWrap Base; 160 | public: 161 | int64_t ticks() 162 | { 163 | bool disabled = cm_is_masked_interrupts(); 164 | if (disabled) 165 | { 166 | return Base::ticks(); 167 | } 168 | 169 | cm_disable_interrupts(); 170 | int64_t result = Base::ticks(); 171 | cm_enable_interrupts(); 172 | return result; 173 | } 174 | }; 175 | 176 | /* Class to measure elapsed time 177 | * T is the timestamp source 178 | */ 179 | template > 180 | class GenericElapsedTimer: public T 181 | { 182 | protected: 183 | volatile int64_t mStart; 184 | public: 185 | GenericElapsedTimer(): mStart(T::ticks()){} 186 | volatile void reset() { mStart = T::ticks(); } 187 | int64_t tsStart() const { return mStart; } 188 | volatile int64_t ticksElapsed() 189 | { 190 | auto result = T::ticks() - mStart - 17; // still do minimal compensation 191 | return (result < 0) ? 0 : result; 192 | } 193 | volatile int64_t ticksElapsedCompensated() 194 | { 195 | auto t = T::ticks(); 196 | // WARNING: The compensation is tuned for optimized code, i.e. will be very 197 | // inaccurate in debug builds 198 | int overheadClocks; 199 | // Tuned for STM32F103 200 | if (rcc_ahb_frequency >= 72000000) { 201 | overheadClocks = 27; // tuned for 72 MHz (hse) 202 | } else if (rcc_ahb_frequency <= 24000000) { 203 | overheadClocks = 17; // tuned for 24 MHz (hse) 204 | } else { 205 | overheadClocks = 21; // tuned for 48 MHz (hsi) 206 | } 207 | //compensate for our own overhead 208 | auto result = t - mStart - overheadClocks; 209 | return (result < 0) ? 0 : result; 210 | } 211 | template 212 | volatile int64_t nsElapsed() { return T::ticksToNs(Comp ? ticksElapsedCompensated() : ticksElapsed()); } 213 | volatile int64_t usElapsed() { return T::ticksToUs(ticksElapsed()); } 214 | volatile int64_t msElapsed() { return T::ticksToMs(ticksElapsed()); } 215 | }; 216 | typedef GenericElapsedTimer<> ElapsedTimer; 217 | 218 | #endif 219 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/usart.hpp: -------------------------------------------------------------------------------- 1 | /** @author Alexander Vassilev 2 | * @copyright BSD License 3 | */ 4 | 5 | #ifndef _USART_HPP_INCLUDED 6 | #define _USART_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "gpio.hpp" 14 | #include "tprintf.hpp" 15 | #include "dma.hpp" 16 | #include 17 | #ifdef STM32PP_USART_DEBUG 18 | #define STM32PP_USART_LOG(fmtString,...) tprintf("%: " fmtString "\n", Self::periphName(), ##__VA_ARGS__) 19 | #else 20 | #define STM32PP_USART_LOG(fmtString,...) 21 | #endif 22 | 23 | #define STM32PP_LOG_DEBUG(fmtString,...) STM32PP_USART_LOG(fmtString, ##__VA_ARGS__) 24 | 25 | STM32PP_PERIPH_INFO(USART1) 26 | enum: uint32_t { kPort = GPIOA }; 27 | enum: uint16_t { kPinTx = GPIO_USART1_TX, kPinRx = GPIO_USART1_RX }; 28 | static constexpr rcc_periph_clken kClockId = RCC_USART1; 29 | enum: uint32_t { kDmaTxId = DMA1, kDmaRxId = DMA1 }; 30 | enum: uint8_t { 31 | kDmaTxChannel = DMA_CHANNEL4, 32 | kDmaRxChannel = DMA_CHANNEL5 33 | }; 34 | static const uint8_t dmaWordSize() { return 1; } 35 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&USART1_DR); } 36 | static const uint32_t dmaTxDataRegister() { return (uint32_t)(&USART1_DR); } 37 | }; 38 | 39 | STM32PP_PERIPH_INFO(USART2) 40 | enum: uint32_t { kPort = GPIOA }; 41 | enum: uint16_t { kPinTx = GPIO_USART2_TX, kPinRx = GPIO_USART2_RX }; 42 | static constexpr rcc_periph_clken kClockId = RCC_USART2; 43 | enum: uint32_t { kDmaTxId = DMA1, kDmaRxId = DMA1 }; 44 | enum: uint8_t { 45 | kDmaTxChannel = DMA_CHANNEL7, 46 | kDmaRxChannel = DMA_CHANNEL6 47 | }; 48 | static const uint8_t dmaWordSize() { return 1; } 49 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&USART2_DR); } 50 | static const uint32_t dmaTxDataRegister() { return (uint32_t)(&USART2_DR); } 51 | }; 52 | 53 | STM32PP_PERIPH_INFO(USART3) 54 | enum: uint32_t { kPort = GPIOB }; 55 | enum: uint16_t { kPinTx = GPIO_USART3_TX, kPinRx = GPIO_USART3_RX }; 56 | static constexpr rcc_periph_clken kClockId = RCC_USART3; 57 | enum: uint32_t { kDmaTxId = DMA1, kDmaRxId = DMA1 }; 58 | enum: uint8_t { 59 | kDmaTxChannel = DMA_CHANNEL2, 60 | kDmaRxChannel = DMA_CHANNEL3 61 | }; 62 | static const uint8_t dmaWordSize() { return 1; } 63 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&USART3_DR); } 64 | static const uint32_t dmaTxDataRegister() { return (uint32_t)(&USART3_DR); } 65 | }; 66 | 67 | namespace nsusart 68 | { 69 | enum: uint8_t 70 | { 71 | kOptEnableRx = 1, 72 | kOptEnableTx = 2 73 | }; 74 | 75 | template 76 | class Usart: public PeriphInfo 77 | { 78 | protected: 79 | typedef Usart Self; 80 | void enableTx() 81 | { 82 | gpio_set_mode(Self::kPort, GPIO_MODE_OUTPUT_50_MHZ, 83 | GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, Self::kPinTx); 84 | STM32PP_USART_LOG("Enabled TX pin"); 85 | } 86 | public: 87 | void enableTxInterrupt() 88 | { 89 | USART_CR1(Self::kPeriphId) |= USART_CR1_TXEIE; 90 | STM32PP_USART_LOG("Enabled TX interrupt"); 91 | } 92 | void sendBlocking(const char* buf, size_t size) 93 | { 94 | const char* bufend = buf+size; 95 | for(; buf < bufend; buf++) 96 | { 97 | usart_send_blocking(Self::kPeriphId, *buf); 98 | } 99 | } 100 | void sendBlocking(const char* str) 101 | { 102 | while(*str) 103 | { 104 | usart_send_blocking(Self::kPeriphId, *str); 105 | str++; 106 | } 107 | } 108 | protected: 109 | void dmaStartPeripheralTx() 110 | { 111 | usart_enable_tx_dma(Self::kPeriphId); 112 | } 113 | void dmaStopPeripheralTx() 114 | { 115 | usart_disable_tx_dma(Self::kPeriphId); 116 | } 117 | // Rx part 118 | void enableRx() 119 | { 120 | gpio_set_mode(Self::kPort, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, 121 | Self::kPinRx); 122 | STM32PP_USART_LOG("Enabled TX pin"); 123 | } 124 | void dmaStartPeripheralRx() 125 | { 126 | usart_enable_rx_dma(Self::kPeriphId); 127 | } 128 | void dmaStopPeripheralRx() 129 | { 130 | usart_disable_rx_dma(Self::kPeriphId); 131 | } 132 | public: 133 | static void enableRxInterrupt() 134 | { 135 | USART_CR1(Self::kPeriphId) |= USART_CR1_RXNEIE; 136 | STM32PP_USART_LOG("Enabled RX interrupt"); 137 | } 138 | void recvBlocking(char* buf, size_t bufsize) 139 | { 140 | void* end = buf+bufsize; 141 | while(buf < end) 142 | { 143 | *(buf++) = usart_recv_blocking(Self::kPeriphId); 144 | } 145 | } 146 | size_t recvLine(char* buf, size_t bufsize) 147 | { 148 | char* end = buf+bufsize-1; 149 | char* ptr = buf; 150 | while(ptr < end) 151 | { 152 | char ch = usart_recv_blocking(Self::kPeriphId); 153 | if ((ch == '\r') || (ch == '\n')) 154 | { 155 | *ptr = 0; 156 | return ptr-buf; 157 | } 158 | *(ptr++) = ch; 159 | } 160 | *ptr = 0; 161 | return (size_t)-1; 162 | } 163 | void init(uint8_t flags, uint32_t baudRate, uint32_t parity=USART_PARITY_NONE, 164 | uint32_t stopBits=USART_STOPBITS_1) 165 | { 166 | rcc_periph_clock_enable(PeriphInfo::kClockId); 167 | rcc_periph_clock_enable(Self::kClockId); 168 | usart_disable(USART); 169 | STM32PP_USART_LOG("Enabled clocks for port and USART peripheral"); 170 | 171 | bool rx = (flags & kOptEnableRx); 172 | if (rx) 173 | { 174 | this->enableRx(); 175 | } 176 | bool tx = (flags & kOptEnableTx); 177 | if (tx) 178 | { 179 | this->enableTx(); 180 | } 181 | 182 | /* Setup UART parameters. */ 183 | uint32_t mode = rx ? USART_MODE_RX : 0; 184 | if (tx) 185 | { 186 | mode |= USART_MODE_TX; 187 | } 188 | usart_set_mode(Self::kPeriphId, mode); 189 | usart_set_baudrate(Self::kPeriphId, baudRate); 190 | usart_set_databits(Self::kPeriphId, 9); 191 | usart_set_stopbits(Self::kPeriphId, stopBits); 192 | usart_set_parity(Self::kPeriphId, parity); 193 | usart_set_flow_control(Self::kPeriphId, USART_FLOWCONTROL_NONE); 194 | 195 | /* Finally enable the USART. */ 196 | usart_enable(Self::kPeriphId); 197 | STM32PP_USART_LOG("Enabled with baudrate: %, databits: 8, stop bits: %, parity: %, no flow control", 198 | baudRate, stopBits, parity); 199 | } 200 | void powerOff() 201 | { 202 | usart_disable(Self::kPeriphId); 203 | rcc_periph_clock_disable(Self::kClockId); 204 | } 205 | /* This is only needed after powerOff() has been called. 206 | * init() powers on the peripheral on implicitly */ 207 | void powerOn() 208 | { 209 | usart_enable(Self::kPeriphId); 210 | rcc_periph_clock_enable(Self::kClockId); 211 | } 212 | }; 213 | 214 | template 215 | class PrintSink: public UsartDevice, public IPrintSink 216 | { 217 | virtual IPrintSink::BufferInfo* waitReady() { return nullptr; } 218 | virtual void print(const char *str, size_t len, int info) 219 | { 220 | UsartDevice::sendBlocking(str, len); 221 | } 222 | }; 223 | } 224 | 225 | #endif 226 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/drivers/ms5611.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MS5611_HPP_INCLUDED 2 | #define MS5611_HPP_INCLUDED 3 | 4 | #include 5 | #include //for memcpy 6 | #include 7 | 8 | template 9 | class MS5611 10 | { 11 | protected: 12 | enum: uint8_t 13 | { 14 | kCmdReset = 0x1e, 15 | kCmdPromReadBase = 0xA0, 16 | kCmdConvertD1Base = 0x40, 17 | kCmdConvertD2Base = 0x50, 18 | kCmdAdcRead = 0x00 19 | }; 20 | enum: uint8_t 21 | { 22 | kCalPressSens = 0, 23 | kCalPressOffs = 1, 24 | kCalPressSensTCoef = 2, 25 | kCalPressOffsTCoef = 3, 26 | kCalTRef = 4, 27 | kCalTCoeff = 5 28 | }; 29 | public: 30 | MS5611(IO& io, uint8_t addr=0x77): mIo(io), mAddr(addr){} 31 | bool init() 32 | { 33 | (volatile bool)mIo.isDeviceConnected(mAddr); 34 | // usDelay(10); 35 | if (!reset()) 36 | return false; 37 | if (!loadCalibrationData()) 38 | return false; 39 | return true; 40 | } 41 | bool reset() 42 | { 43 | if (!mIo.startSend(mAddr, true)) 44 | return false; 45 | if (!mIo.sendByteTimeout(kCmdReset)) 46 | return false; 47 | mIo.stop(); 48 | msDelay(3); 49 | return true; 50 | } 51 | int32_t temp() const { return mTemp; } 52 | int32_t pressure() const { return mPressure; } 53 | protected: 54 | IO& mIo; 55 | uint8_t mAddr; 56 | uint16_t mCalData[6]; 57 | int32_t mTemp = 0; 58 | int32_t mPressure = 0; 59 | bool sendCmd(uint8_t cmd) 60 | { 61 | if (!mIo.startSend(mAddr, true)) 62 | return false; 63 | bool ret = mIo.sendByteTimeout(cmd); 64 | mIo.stop(); 65 | if (ret) 66 | return true; 67 | return false; 68 | } 69 | bool loadCalibrationData() 70 | { 71 | uint16_t reserved; 72 | uint16_t crc; 73 | uint8_t* ptr = (uint8_t*)mCalData; 74 | for (uint8_t idx = 0; idx < 8; idx++) 75 | { 76 | if (!sendCmd(kCmdPromReadBase + (idx * 2))) 77 | return false; 78 | if (!mIo.startRecv(mAddr, true)) 79 | return false; 80 | uint8_t* dest; 81 | if (idx == 0) 82 | { 83 | dest = (uint8_t*)(&reserved); 84 | } 85 | else if (idx == 7) 86 | { 87 | dest = (uint8_t*)(&crc); 88 | } 89 | else 90 | { 91 | dest = ptr; 92 | ptr += 2; 93 | } 94 | //sensor sends data in big endian format 95 | uint16_t ret = mIo.recvByteTimeout(); 96 | if (ret == 0xffff) 97 | return false; 98 | dest[1] = ret; 99 | ret = mIo.recvByteTimeout(); 100 | if (ret == 0xffff) 101 | return false; 102 | dest[0] = ret; 103 | if (!mIo.stopTimeout()) 104 | return false; 105 | } 106 | assert(ptr == (uint8_t*)mCalData+12); 107 | uint16_t crcBuf[8]; 108 | memcpy(crcBuf+1, mCalData, sizeof(mCalData)); 109 | crcBuf[0] = reserved; 110 | crcBuf[7] = crc; 111 | return crc4(crcBuf); 112 | } 113 | uint16_t usNeededForOsr(uint8_t osr) 114 | { 115 | switch(osr) 116 | { 117 | case 0: return 600; 118 | case 2: return 1200; 119 | case 4: return 2500; 120 | case 6: return 4600; 121 | case 8: 122 | default: 123 | return 9100; 124 | } 125 | } 126 | uint32_t getRawMeasurement(uint8_t baseCmd, uint8_t osr) 127 | { 128 | if (!sendCmd(baseCmd+osr)) 129 | return 0; 130 | mIo.stop(); 131 | usDelay(usNeededForOsr(osr)); 132 | if (!sendCmd(kCmdAdcRead)) 133 | return 0; 134 | 135 | if (!mIo.startRecv(mAddr, true)) 136 | return 0; 137 | 138 | uint32_t result = 0; 139 | result |= (((uint32_t)mIo.recvByte()) << 16); 140 | result |= (((uint32_t)mIo.recvByte()) << 8); 141 | result |= (((uint32_t)mIo.recvByte())); 142 | mIo.stop(); 143 | return result; 144 | } 145 | public: 146 | uint32_t getRawTemp(uint8_t osr) 147 | { 148 | return getRawMeasurement(kCmdConvertD2Base, osr); 149 | } 150 | uint32_t getRawPressure(uint8_t osr) 151 | { 152 | return getRawMeasurement(kCmdConvertD1Base, osr); 153 | } 154 | void sample(uint8_t osr=8) 155 | { 156 | uint32_t rawTemp = getRawTemp(osr); 157 | uint32_t rawPress = getRawPressure(osr); 158 | int64_t dt = rawTemp - ((int64_t)mCalData[kCalTRef] * (1<<8)); 159 | mTemp = 2000 + (dt * mCalData[kCalTCoeff])/(1<<23); 160 | int64_t off2, sens2; 161 | bool corr2; 162 | if (mTemp < 2000) 163 | { 164 | corr2 = true; 165 | uint32_t t2 = (dt*dt) / (1 << 31); 166 | uint32_t m2000 = (mTemp-2000); 167 | off2 = 5*(m2000*m2000) / 2; 168 | sens2 = off2 / 2; 169 | if (mTemp < -15) 170 | { 171 | uint32_t p1500 = mTemp+1500; 172 | p1500*=p1500; 173 | off2 = off2 + 7*p1500; 174 | sens2 = sens2 + 11*p1500/2; 175 | } 176 | mTemp = mTemp - t2; 177 | } 178 | else 179 | { 180 | corr2 = false; 181 | } 182 | int64_t off = ((int64_t)mCalData[kCalPressOffs]) * (1<<16) + (mCalData[kCalPressOffsTCoef]*dt) / (1<<7); 183 | int64_t sens = ((int64_t)mCalData[kCalPressSens]) * (1<<15) + (mCalData[kCalPressSensTCoef]*dt) / (1<<8); 184 | if (corr2) 185 | { 186 | off -= off2; 187 | sens -= sens2; 188 | } 189 | mPressure = (((int64_t)rawPress * sens) / (1<<21) - off) / (1<<15); 190 | } 191 | static bool crc4(uint16_t n_prom[]) 192 | { 193 | uint16_t n_rem = 0x00; 194 | uint16_t crc_read = n_prom[7]; //save read CRC 195 | n_prom[7] &= 0xFF00; //CRC byte is replaced by 0 196 | for (int cnt = 0; cnt < 16; cnt++) // operation is performed on bytes 197 | { // choose LSB or MSB 198 | if (cnt & 1) 199 | n_rem ^= n_prom[cnt>>1] & 0x00FF; 200 | else 201 | n_rem ^= n_prom[cnt>>1] >> 8; 202 | 203 | for (uint8_t n_bit = 8; n_bit > 0; n_bit--) 204 | { 205 | if (n_rem & (0x8000)) 206 | n_rem = (n_rem << 1) ^ 0x3000; 207 | else 208 | n_rem = (n_rem << 1); 209 | } 210 | } 211 | n_rem >>= 12; // final 4-bit reminder is CRC code 212 | n_prom[7]=crc_read; // restore the crc_read to its original place 213 | return ((0x000F & crc_read) == n_rem); 214 | } 215 | }; 216 | /* 217 | Usage: 218 | #include 219 | 220 | int main() 221 | { 222 | rcc_clock_setup_in_hse_8mhz_out_72mhz(); 223 | dwt_enable_cycle_counter(); 224 | nsi2c::I2c i2c; 225 | i2c.init(); 226 | MS5611> sens(i2c); 227 | sens.init(); 228 | usDelay(1000); 229 | sens.sample(); 230 | float pres = sens.pressure(); 231 | for(;;) 232 | { 233 | sens.sample(); 234 | pres = (pres*9 + sens.pressure()) / 10; 235 | tprintf("temp = %, press = %\n", 236 | fmtFp<2>(float((sens.temp()))/100), 237 | fmtFp<2>(pres/100)); 238 | } 239 | } 240 | */ 241 | #endif 242 | -------------------------------------------------------------------------------- /stm32-toolchain.cmake: -------------------------------------------------------------------------------- 1 | # @author Alexander Vassilev 2 | # @copyright BSD License 3 | 4 | # CMake toolchain for arm-none-eabi-gcc and STM32 5 | 6 | cmake_minimum_required(VERSION 3.0) 7 | set(CMAKE_SYSTEM_NAME Generic) 8 | set(CMAKE_SYSTEM_PROCESSOR arm) 9 | set(ENV_SCRIPTS_DIR "${CMAKE_CURRENT_LIST_DIR}") 10 | 11 | set(CMAKE_C_COMPILER arm-none-eabi-gcc) 12 | set(CMAKE_CXX_COMPILER arm-none-eabi-g++) 13 | set(CMAKE_ASM_COMPILER arm-none-eabi-as) 14 | set(CMAKE_LINKER arm-none-eabi-ld) 15 | 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-common -mcpu=cortex-m3 -mthumb -nostartfiles -Wall" CACHE STRING "") 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -std=c++14 -fno-exceptions -fno-rtti -fno-use-cxa-atexit -fno-threadsafe-statics" CACHE STRING "") 18 | set(CMAKE_C_FLAGS_DEBUG "-g -O0" CACHE STRING "") 19 | set(CMAKE_CXX_FLAGS_DEBUG "-g -O0" CACHE STRING "") 20 | 21 | #defaults to 1 but can be overridden by commandline, in which case 22 | #this statement has no effect 23 | set(optUseOpencm3 1 CACHE BOOL "Use the libopencm3 platform") 24 | if (optUseOpencm3) 25 | message(STATUS "Using OpenCM3 framework") 26 | set(CMAKE_SYSROOT "${CMAKE_CURRENT_LIST_DIR}/sysroot") 27 | set(optUseOpencm3 1 CACHE BOOL "Use the libopencm3 platform" FORCE) 28 | set(optChipFamily STM32F1 CACHE STRING "Chip family for opencm3") 29 | set_property(CACHE optChipFamily PROPERTY STRINGS STM32F0 STM32F1 STM32F2 30 | STM32F3 STM32F4 STM32F7 STM32L0 STM32L1 STM32L4) 31 | string(SUBSTRING "${optChipFamily}" 5 2 stm32model_uc) 32 | string(TOLOWER "${stm32model_uc}" stm32model) 33 | 34 | set(ldscriptBaseDir "${CMAKE_CURRENT_LIST_DIR}/libopencm3/lib/stm32") 35 | set(modeldir "${ldscriptBaseDir}/${stm32model}") 36 | file(GLOB ldscripts "${modeldir}/stm32*.ld") 37 | set(optLinkScript "${ldscriptBaseDir}/f1/stm32f103xb.ld" CACHE STRING "Linker script") 38 | set_property(CACHE optLinkScript PROPERTY STRINGS ${ldscripts} "${CMAKE_CURRENT_SOURCE_DIR}/stm32.ld") 39 | set(linkDirs "-L${CMAKE_CURRENT_LIST_DIR}/libopencm3/lib") 40 | link_libraries("opencm3_stm32${stm32model}") 41 | # We cannot use link_directories because of a CMake quirk - it does not 42 | # get applied in most cases (but it does in some, probably if it is 43 | # executed during compiler instrospection CMake decides that the path is 44 | # built into the compiler driver and does not apply it). 45 | # see https://public.kitware.com/Bug/view.php?id=16074 46 | include_directories("${CMAKE_CURRENT_LIST_DIR}/libopencm3/include") 47 | else() 48 | set(optUseOpencm3 0 CACHE BOOL "Use the libopencm3 platform" FORCE) 49 | set(CMAKE_SYSROOT "${CMAKE_CURRENT_LIST_DIR}/sysroot-baremetal") 50 | set(defaultLinkScript "${CMAKE_CURRENT_LIST_DIR}/sysroot/stm32.ld") 51 | set(optChipFamily STM32F10X_MD CACHE STRING "Chip family for platform headers") 52 | set_property(CACHE optChipFamily PROPERTY STRINGS STM32F10X_LD STM32F10X_LD_VL 53 | STM32F10X_MD STM32F10X_MD_VL STM32F10X_HD STM32F10X_HD_VL STM32F10X_XL STM32F10X_CL) 54 | set(optLinkScript "${defaultLinkScript}" CACHE PATH "Linker script" FORCE) 55 | endif() 56 | 57 | set(optStdioLibcInDebug 0 CACHE BOOL 58 | "In DEBUG mode, link to a version of the standard C library that has stdio and printf(), and supports semihosting.\n\ 59 | This has a somewhat large footprint - if you just need printing via semihosting,\n\ 60 | you can just use tprint()") 61 | set(optStdioLibcInRelease 0 CACHE BOOL 62 | "In RELEASE mode, use the stdio-enabled standard C library") 63 | set(optStdioLibcInMinSizeRel 0 CACHE BOOL 64 | "In MinSizeRelease mode, use the stdio-enabled standard C library") 65 | 66 | set(optCustomOffset 0 CACHE STRING "Custom offset of the .text section") 67 | 68 | add_definitions(-DCHIP_TYPE=STM32 -D${optChipFamily}) 69 | include_directories("${CMAKE_CURRENT_LIST_DIR}/stm32++/include") 70 | if (optCustomOffset) 71 | set(linkerOffsetArg "-Ttext=${optCustomOffset}") 72 | endif() 73 | 74 | set(exeLinkerFlags "-nostartfiles -T${optLinkScript} ${linkerOffsetArg} ${linkDirs}") 75 | 76 | if (NOT "${exeLinkerFlags}" STREQUAL "${CMAKE_EXE_LINKER_FLAGS}") 77 | message(STATUS "EXE linker flags changed") 78 | UNSET(CMAKE_EXE_LINKER_FLAGS CACHE) 79 | set(CMAKE_EXE_LINKER_FLAGS "${exeLinkerFlags}" CACHE STRING "") 80 | endif() 81 | 82 | if (optStdioLibcInDebug) 83 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "--specs=rdimon.specs -lc" CACHE STRING "" FORCE) 84 | else() 85 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "--specs=nosys.specs" CACHE STRING "" FORCE) 86 | endif() 87 | 88 | if (optStdioLibcInRelease) 89 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "--specs=rdimon.specs -lc" CACHE STRING "" FORCE) 90 | else() 91 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "--specs=nosys.specs" CACHE STRING "" FORCE) 92 | endif() 93 | 94 | if (optStdioLibcInMinSizeRel) 95 | set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "--specs=rdimon.specs -lc" CACHE STRING "" FORCE) 96 | else() 97 | set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "--specs=nosys.specs" CACHE STRING "" FORCE) 98 | endif() 99 | 100 | set(CMAKE_FIND_ROOT_PATH "${CMAKE_SYSROOT}") 101 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 102 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 103 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 104 | 105 | # Default to debug build 106 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type") 107 | set_Property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo) 108 | 109 | # Utilities to facilitate user CMakeLists 110 | if (optUseOpencm3) 111 | set(STM32PP_SRCPATH "${ENV_SCRIPTS_DIR}/stm32++/src") 112 | set(STM32PP_SRCS 113 | "${STM32PP_SRCPATH}/semihosting.cpp" 114 | "${STM32PP_SRCPATH}/printSink.cpp" 115 | "${STM32PP_SRCPATH}/tsnprintf.cpp" 116 | ) 117 | endif() 118 | 119 | function(stm32_create_utility_targets imgname) 120 | set_target_properties(${imgname} PROPERTIES LINK_DEPENDS "${optLinkScript}") 121 | 122 | string(REGEX REPLACE "\\.[^.]*$" "" imgnameNoExt "${imgname}") 123 | if (optCustomOffset) 124 | set(binName "${imgnameNoExt}_${optCustomOffset}.bin") 125 | else() 126 | set(binName "${imgnameNoExt}.bin") 127 | endif() 128 | add_custom_target(bin bash -c "arm-none-eabi-objcopy -O binary ./${imgname} ./${binName}" DEPENDS "${imgname}") 129 | set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "./${binName}") 130 | 131 | if (optCustomOffset) 132 | add_custom_target(flash bash -c "${ENV_SCRIPTS_DIR}/flash.sh -o ${optCustomOffset} ./${binName}" DEPENDS bin) 133 | else() 134 | add_custom_target(flash bash -c "${ENV_SCRIPTS_DIR}/flash.sh ./${imgname}" DEPENDS "${imgname}") 135 | endif() 136 | # remove all derived firmware files before rebuilding (or before linking if PRE_BUILD is not supported) 137 | add_custom_command(TARGET ${imgname} PRE_BUILD COMMAND rm -vf "./${imgnameNoExt}.bin" "./${imgnameNoExt}_0x????????.bin") 138 | # debugger support 139 | add_custom_target(gdb 140 | arm-none-eabi-gdb -ex 'file ${CMAKE_CURRENT_BINARY_DIR}/${imgname}' 141 | -ex 'directory ${CMAKE_CURRENT_SOURCE_DIR}' 142 | -ex 'target remote localhost:3333' 143 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 144 | DEPENDS "${imgname}") 145 | add_custom_target(wflash 146 | COMMAND echo "Flashing over network (verify OFF), please wait..." 147 | COMMAND curl -i "http://stm32.local:80/fileflash?file=${imgnameNoExt}.bin&verify=0" -H "Content-Type: application/octet-stream" -H "Expect:" --data-binary @./${imgnameNoExt}.bin --silent --output /dev/null --show-error --fail 148 | VERBATIM 149 | DEPENDS bin) 150 | add_custom_target(wflashv 151 | COMMAND echo "Flashing over network (verify ON), please wait..." 152 | COMMAND curl -i "http://stm32.local:80/fileflash?file=${imgnameNoExt}.bin&verify=1" -H "Content-Type: application/octet-stream" -H "Expect:" --data-binary @./${imgnameNoExt}.bin --silent --output /dev/null --show-error --fail 153 | VERBATIM 154 | DEPENDS bin) 155 | endfunction() 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stm32-env 2 | 3 | This project aims to create a more or less complete STM32 development environment, 4 | based on gcc-none-eabi, OpenOCD for interfacing with the controller, 5 | CMake for building, optionally libopencm3 for hardware abstraction. On top of 6 | the libopencm3 hardware abstraction, the stm32++ C++ library is provided, 7 | which harnesses the power of C++11 to implement a simple, hardware-abstracted 8 | programming API for STM32 development. It makes heavy use of C++ templates in 9 | order to move as much programming logic and state as possible to the compile stage, 10 | rather than runtime. This results in faster code and less RAM usage. stm32++ 11 | also includes drivers for some popular hardware such as displays and sensors. 12 | The library is located in the stm32++ subdirectory and has a dedicated Readme file. 13 | Please have in mind that the library is a work in progress, it is not perfect and 14 | there may be bugs. 15 | 16 | Scripts are provided for: 17 | - setting up the shell environment: `env-stm32.sh` 18 | - executing OpenOCD commands and starting it if it's not running: `ocmd.sh` 19 | - flash an image, using ocmd.sh: `flash.sh` 20 | 21 | Additionally, the environment can work in a minimalistic fashion, without 22 | any base libraries, but just `STM32F10x.h`, a minimal set of CMSIS headers and a minimal 23 | link script (`stm32.ld`). This mode can be suitable for writing bootloaders. 24 | Each component is described in more detail below: 25 | 26 | ## env-stm32.sh 27 | 28 | It must be _sourced_ (and not run in a subshell). What it does is to provide a 29 | thin shell around the cmake command that passes to it the `stm32-toolchain.cmake` 30 | toolchain. The actual work of setting up the build is done by the toolchain file. 31 | The env script also sets up convenience aliases for the gcc toolchain, so that for 32 | example gcc maps to arm-none-eabi-gcc, etc. It also alters the shell propmt to easily 33 | identify shells that have the environment setup. 34 | After sourcing this script in the shell, instead of running the command `cmake`, 35 | you should run `xcmake` with any options you would normally pass to cmake. 36 | This automatically picks up the STM32 toolhain. 37 | IMPORTANT: If you want to setup the project to use libopencm3, the option -DoptUseOpencm3 38 | *must* be provided at the initial run of xcmake. If it is not, modifying it later 39 | currently causes inconsistency of the build flags and the build will not work 40 | properly. If you need to change that option, currently you need to delete the 41 | cmake cache and re-configure the project from scratch. 42 | The env script also provides convenience aliases for: 43 | - the toolchain executables, removing the necessity to type the arm-none-eabi- prefix. 44 | That is, gdb will map to arm-none-eabi-gdb, gcc will map to arm-none-eabi-gcc, etc 45 | - the `ocmd.sh` and `flash.sh` scripts, so they can be invoked as shell functions rather 46 | than specifying path and filename. That is, instead of running 47 | 48 | `/path/to/ocmd.sh ` 49 | you can run: 50 | `ocmd ` 51 | 52 | ## stm32-toolchain.cmake 53 | This is the cmake toolchain that will be used by CMake. It: 54 | 55 | - Sets up the CMake sysroot for the toolchain to point to the appropriate location. 56 | The CMake sysroot directory is intentended to contain system headers and libraries, 57 | and is the default search location when CMake is instructed to use a library. 58 | On Unix-like systems, the sysroot is usually the /usr directory, where system-wide 59 | libs and headers are installed. 60 | Two versions of the sysroot directory are provided: 61 | - `./sysroot` - when libopencm3 and stm32++ 62 | - `./sysroot-baremetal` - when not using any libraries, programming baremetal. 63 | 64 | - Based on the option to use or not libopencm3 (`optUseOpenCm3`), and on the type 65 | of chip used (`optChipFamily`), sets up a global define for the chip type and 66 | path to a linker script. The toolchain script automatically detects the supported 67 | by libopencm3 STM32 families(F1, F2, ...) and, based on the selected one, detects 68 | the supported models and fills up the possible linker script paths. 69 | For the barebones mode (no libopencm3), stm32.ld is used, which may be too 70 | minimalistic and the user may want to copy and extend it. In barebones mode, the 71 | script path can be manually set. This is unlike the opencm3 mode, where the linker 72 | script path is a drop down list with the libopencm3-provided ones, for the selected 73 | chip family. You should choose the one that matches the exact model of your chip. 74 | 75 | - `stdio` 76 | If `optStdioLibcInDebug` or `optStdioLibcInRelease` are enabled, the linker 77 | is directed to use a version of the standard C library that has an implementation 78 | of stdio, with semihosting support (`--specs=rdimon.specs -lc`). This enables the 79 | use of file descriptors, printf() and similar standard functions for standard I/O 80 | output. The file descriptors don't currespond to real files, but transparently direct 81 | the I/O streams to OpenOCD, which in turn can direct the I/O to console or files. 82 | This mechanism is called 'semihosting'. OpenOCD has to be configured to support this. 83 | The `ocmd.sh` script always enables that support. The controller side of the semihosting 84 | is very simple to implement - just with a few lines of assembly code that call the 85 | BKPT / SVC ARM instruction. It is not necessary to use the stdio-enabled C 86 | standard lib, if the only thing needed is simple console output for logging. 87 | Support for this is built into the stm32++ library, which provides a very fast and 88 | lightweight `printf`-like formatting facility - `tprintf`. It is implemented 89 | using C++ templates and is type-safe, unlike the classic printf. The argument type 90 | and format detection is static, at compile time, which greatly speeds up 91 | the parsing of the format string at runtime. See the documentation of the stm32++ 92 | library for more details. 93 | - Convenience make targets - the toolchain can define the following convenience make targets: 94 | - `make flash` - Build (if necessary) the firmware, flash it to the chip, using 95 | ocmd.sh, and reset the chip. 96 | - `make gdb` - Start gdb and configure it to look at the project's source code dir, 97 | load the firmware file, and connect to openOCD via the debuging target interface. 98 | 99 | - CMake conveneice defines and functions, as follows: 100 | - `STM32PP_SRCS` - The .cpp source files of the stm32++ library. Add this to 101 | your application's source file list if you want to use stm32++ 102 | - `stm32_create_utility_targets(imgname)` - Call this function in order to have 103 | the convenience `flash` and `gdb` make targets automatically created and set up. 104 | 105 | ## ocmd.sh 106 | Sends a command to OpenOCD via the telnet port. First the script checks if 107 | OpenOCD is running. If not, it is started and the script waits till it starts 108 | accepting telnet connections. Multiple commands can be sent in one call, but they 109 | must all be placed in quotes and separated by semicolons. 110 | To shut down OpenOCD you can send the `shutdown` command. Any call to ocmd.sh will 111 | then first bring up OpenOCD again and execute the command. For help on usage, 112 | you can call the scripts without arguments. 113 | 114 | ## flash.sh 115 | Flashes the specified file to the chip and either resets or halts it, depending on 116 | a command line option. For help on usage, you can call the script without arguments. 117 | 118 | ## Example session 119 | 120 | ``` 121 | // Install the environment 122 | $ source ../../stm32-env/env-stm32.sh 123 | // Create project directory 124 | $ mkdir myproject 125 | $ cd myproject 126 | // Create CMakeLists.txt file that describes how to build the project 127 | $ echo -e \ 128 | "cmake_minimum_required(VERSION 2.8)\n\ 129 | \n\ 130 | project(myproject)\n\ 131 | set(SRCS main.cpp ${STM32PP_SRCS})\n\ 132 | set(imgname "${CMAKE_PROJECT_NAME}.elf")\n\ 133 | add_executable(${imgname} ${SRCS})\n\ 134 | stm32_create_utility_targets(${imgname})\n\"\ 135 | > ./CMakeLists.txt 136 | 137 | // Create a .cpp source file with the main() function 138 | $ echo "int main() { for (;;); return 0; }" > ./main.cpp 139 | 140 | // Create build directory 141 | $ mkdir build 142 | $ cd build 143 | 144 | // Initialize cmake configuration 145 | $ xcmake .. 146 | 147 | // Edit cmake configuration, check and set options 148 | $ ccmake . 149 | 150 | // Build the application, flash it to the chip and run it. 151 | // The ST-Link must be connected 152 | $ make flash 153 | 154 | // reset the chip 155 | $ ocmd reset 156 | ``` 157 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/i2c.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Vassilev 3 | * @copyright BSD License 4 | */ 5 | #ifndef STM32PP_I2C_H 6 | #define STM32PP_I2C_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | namespace dma 17 | { 18 | } 19 | 20 | namespace nsi2c 21 | { 22 | /* Private definitions */ 23 | /** @brief Timeout for ACK of sent data. Used also for timeout for device 24 | * response in \c inDeviceConnected() */ 25 | enum { kTimeoutMs = 10 }; 26 | 27 | /* Private defines */ 28 | enum: bool { kTxMode = true, kRxMode = false, 29 | kAckEnable = true, kAckDisable = false }; 30 | 31 | template 32 | class I2c: public PeriphInfo 33 | { 34 | public: 35 | void init(bool fastMode=true, uint8_t ownAddr=0x15) 36 | { 37 | rcc_periph_clock_enable(PeriphInfokPortId>::kClockId); 38 | /* Set alternate functions for the SCL and SDA pins of I2C1. */ 39 | gpio_set_mode(this->kPortId, GPIO_MODE_OUTPUT_50_MHZ, 40 | GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, 41 | this->kPinSda | this->kPinScl); 42 | 43 | rcc_periph_clock_enable(this->kClockId); 44 | i2c_reset(I2C); 45 | /* Disable the I2C before changing any configuration. */ 46 | i2c_peripheral_disable(I2C); 47 | i2c_set_clock_frequency(I2C, rcc_apb1_frequency / 1000000); 48 | /* 400KHz */ 49 | if (fastMode) 50 | { 51 | // Datasheet suggests 0x1e. 52 | i2c_set_fast_mode(I2C); 53 | uint32_t clockRatio = rcc_apb1_frequency / 400000; 54 | i2c_set_ccr(I2C, (clockRatio*2+3)/6); //round clockRatio/3 55 | i2c_set_dutycycle(I2C, I2C_CCR_DUTY_DIV2); 56 | /* 57 | * rise time for 400kHz => 300ns and 100kHz => 1000ns; 300ns/28ns = 10; 58 | * Incremented by 1 -> 11. 59 | */ 60 | uint32_t clocks = 300 / ((2000000000+rcc_apb1_frequency)/(rcc_apb1_frequency*2)) + 1; 61 | i2c_set_trise(I2C, clocks); 62 | } 63 | else 64 | { 65 | i2c_set_standard_mode(I2C); 66 | uint32_t clockRatio = rcc_apb1_frequency / 100000; 67 | i2c_set_ccr(I2C, clockRatio/2); 68 | uint32_t clocks = 1000 / ((2000000000+rcc_apb1_frequency)/(rcc_apb1_frequency*2)) + 1; 69 | i2c_set_trise(I2C, clocks); 70 | } 71 | 72 | /* 73 | * This is our slave address - needed only if we want to receive from 74 | * other masters. 75 | */ 76 | i2c_set_own_7bit_slave_address(I2C, ownAddr); 77 | i2c_disable_ack(I2C); 78 | /* If everything is configured -> enable the peripheral. */ 79 | i2c_peripheral_enable(I2C); 80 | } 81 | 82 | void blockingSend(uint8_t* data, uint16_t count) 83 | { 84 | uint8_t* end = data+count; 85 | while (data < end) 86 | { 87 | sendByte(*(data++)); 88 | } 89 | } 90 | 91 | /** Dummy function to prevent compile errors of 92 | * \code if (hasDma) dmaSend(...) 93 | * even when the class has no DMA support. If an attempt is made to actually 94 | * call dmaSend(), this will result in a link error because there is no 95 | * implementation of \c dmaSend(), only a prototype 96 | */ 97 | void dmaSend(uint8_t* data, uint16_t count, void* freeFunc=nullptr); 98 | 99 | bool startSend(uint8_t address, bool ack=false) 100 | { 101 | return start(address, kTxMode, ack); 102 | } 103 | bool startRecv(uint8_t address, bool ack=false) 104 | { 105 | return start(address, kRxMode, ack); 106 | } 107 | 108 | /* Private functions */ 109 | bool start(uint8_t address, bool tx, bool ack) 110 | { 111 | /* Generate I2C start pulse */ 112 | i2c_send_start(I2C); 113 | ElapsedTimer timer; 114 | 115 | /* Waiting for START to be sent and switched to master mode. */ 116 | while (!(I2C_SR1(I2C) & I2C_SR1_SB)) 117 | { 118 | if (timer.msElapsed() > kTimeoutMs) 119 | return false; 120 | } 121 | xassert(I2C_SR2(I2C) & I2C_SR2_MSL); 122 | 123 | if (ack) 124 | i2c_enable_ack(I2C); 125 | else 126 | i2c_disable_ack(I2C); 127 | 128 | /* Send destination address. */ 129 | i2c_send_7bit_address(I2C, address, tx ? I2C_WRITE : I2C_READ); 130 | 131 | /* Waiting for address to be transferred. */ 132 | while (!(I2C_SR1(I2C) & I2C_SR1_ADDR)) 133 | { 134 | if (timer.msElapsed() > kTimeoutMs) 135 | return false; 136 | } 137 | xassert(!(I2C_SR1(I2C) & I2C_SR1_SB)); 138 | (volatile uint32_t)I2C_SR2(I2C); 139 | 140 | #ifndef NDEBUG 141 | uint32_t sr2 = I2C_SR2(I2C); 142 | if (tx) 143 | xassert(sr2 & I2C_SR2_TRA); 144 | else 145 | xassert(!(sr2 & I2C_SR2_TRA)); 146 | #endif 147 | 148 | xassert((I2C_SR1(I2C) & I2C_SR1_ADDR) == 0); 149 | return true; 150 | } 151 | 152 | bool sendByteTimeout(uint8_t data) 153 | { 154 | if (!(I2C_SR1(I2C) & I2C_SR1_TxE)) 155 | { 156 | ElapsedTimer timer; 157 | while (!(I2C_SR1(I2C) & I2C_SR1_TxE)) 158 | { 159 | if (timer.msElapsed() > kTimeoutMs) 160 | return false; 161 | } 162 | } 163 | i2c_send_data(I2C, data); 164 | return true; 165 | } 166 | 167 | template 168 | bool sendByteTimeout(uint8_t byte, Args... args) 169 | { 170 | if (!sendByteTimeout(byte)) 171 | return false; 172 | return sendByteTimeout(args...); 173 | } 174 | 175 | void sendByte(uint8_t data) 176 | { 177 | while (!(I2C_SR1(I2C) & I2C_SR1_TxE)); 178 | i2c_send_data(I2C, data); 179 | 180 | } 181 | 182 | template 183 | void sendByte(uint8_t byte, Args... args) 184 | { 185 | sendByte(byte); 186 | sendByte(args...); 187 | } 188 | 189 | template 190 | bool vsendTimeout(T data) 191 | { 192 | uint8_t* end = ((uint8_t*)&data)+sizeof(data); 193 | for (uint8_t* ptr = (uint8_t*)&data; ptr 201 | bool vsendTimeout(T val, Args... args) 202 | { 203 | bool ret = (sizeof(T) == 1) 204 | ? sendByteTimeout(val) 205 | : sendTimeout(val); 206 | if (!ret) 207 | return false; 208 | return sendTimeout(args...); 209 | } 210 | template 211 | void vsend(T data) 212 | { 213 | uint8_t* end = ((uint8_t*)&data)+sizeof(data); 214 | for (uint8_t* ptr = (uint8_t*)&data; ptr 221 | void vsend(T val, Args... args) 222 | { 223 | if (sizeof(T) == 1) 224 | sendByte(val); 225 | else 226 | send(val); 227 | send(args...); 228 | } 229 | 230 | uint16_t recvByteTimeout() 231 | { 232 | ElapsedTimer timer; 233 | while((I2C_SR1(I2C) & I2C_SR1_RxNE) == 0) 234 | { 235 | if (timer.msElapsed() > kTimeoutMs) 236 | return 0xffff; 237 | } 238 | return I2C_DR(I2C); 239 | } 240 | 241 | uint8_t recvByte() 242 | { 243 | while((I2C_SR1(I2C) & I2C_SR1_RxNE) == 0); 244 | return I2C_DR(I2C); 245 | 246 | } 247 | 248 | bool recvTimeout(uint8_t* buf, size_t count) 249 | { 250 | uint8_t* end = buf+count; 251 | while(buf < end) 252 | { 253 | ElapsedTimer timer; 254 | while ((I2C_SR1(I2C) & I2C_SR1_RxNE) == 0) 255 | { 256 | if (timer.msElapsed() > kTimeoutMs) 257 | return false; 258 | } 259 | *(buf++) = I2C_DR(I2C); 260 | } 261 | } 262 | 263 | void recv(uint8_t* buf, size_t count) 264 | { 265 | uint8_t* end = buf+count; 266 | while(buf < end) 267 | { 268 | while ((I2C_SR1(I2C) & I2C_SR1_RxNE) == 0); 269 | *(buf++) = I2C_DR(I2C); 270 | } 271 | } 272 | 273 | void stop() 274 | { 275 | //wait transfer complete 276 | #ifndef NDEBUG 277 | ElapsedTimer timer; 278 | while (!(I2C_SR1(I2C) & (I2C_SR1_BTF | I2C_SR1_TxE))) 279 | { 280 | if (timer.msElapsed() > kTimeoutMs) 281 | { 282 | xassert(false && "stop(): Timeout waiting for output flush"); 283 | for(;;); 284 | } 285 | } 286 | #else 287 | while (!(I2C_SR1(I2C) & (I2C_SR1_BTF | I2C_SR1_TxE))); 288 | #endif 289 | /* Send STOP condition. */ 290 | i2c_send_stop(I2C); 291 | } 292 | 293 | bool stopTimeout() 294 | { 295 | //wait transfer complete 296 | ElapsedTimer timer; 297 | while (!(I2C_SR1(I2C) & (I2C_SR1_BTF | I2C_SR1_TxE))) 298 | { 299 | if (timer.msElapsed() > kTimeoutMs) 300 | return false; 301 | } 302 | i2c_send_stop(I2C); 303 | return true; 304 | } 305 | 306 | bool isDeviceConnected(uint8_t address) 307 | { 308 | /* Try to start, function will return 0 in case device will send ACK */ 309 | bool connected = start(address, kTxMode, kAckEnable); 310 | if (!connected) 311 | return false; 312 | stop(); 313 | return true; 314 | } 315 | 316 | uint8_t findFirstDevice(uint8_t from=0) 317 | { 318 | for (uint8_t i = from; i < 128; i++) 319 | if (isDeviceConnected(i)) 320 | return i; 321 | return 0xff; 322 | } 323 | void dmaStartPeripheralTx() { i2c_enable_dma(I2C); } 324 | void dmaStartPeripheralRx() { i2c_enable_dma(I2C); } 325 | //WARNING: The dmaStopPerpheralXX can be called from an ISR 326 | void dmaStopPeripheralTx() 327 | { 328 | i2c_disable_dma(I2C); 329 | this->stop(); 330 | } 331 | void dmaStopPeripheralRx() 332 | { 333 | i2c_disable_dma(I2C); 334 | this->stop(); 335 | } 336 | }; 337 | } 338 | STM32PP_PERIPH_INFO(I2C1) 339 | enum: uint32_t { kPortId = GPIOB }; 340 | enum: uint16_t { kPinScl = GPIO_I2C1_SCL, kPinSda = GPIO_I2C1_SDA }; 341 | static constexpr rcc_periph_clken kClockId = RCC_I2C1; 342 | enum: uint32_t { kDmaTxId = DMA1, kDmaRxId = DMA1 }; 343 | enum: uint8_t { 344 | kDmaTxChannel = DMA_CHANNEL6, 345 | kDmaRxChannel = DMA_CHANNEL7, 346 | kDmaWordSize = 1 347 | }; 348 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&I2C1_DR); } 349 | static const uint32_t dmaTxDataRegister() { return (uint32_t)(&I2C1_DR); } 350 | }; 351 | 352 | STM32PP_PERIPH_INFO(I2C2) 353 | enum: uint32_t { kPortId = GPIOB }; 354 | enum: uint16_t { kPinScl = GPIO_I2C2_SCL, kPinSda = GPIO_I2C2_SDA }; 355 | static constexpr rcc_periph_clken kClockId = RCC_I2C2; 356 | enum: uint32_t { kDmaTxId = DMA1, kDmaRxId = DMA1 }; 357 | enum: uint8_t { 358 | kDmaTxChannel = DMA_CHANNEL4, 359 | kDmaRxChannel = DMA_CHANNEL5, 360 | kDmaWordSize = 1 361 | }; 362 | static const uint32_t dmaTxDataRegister() { return (uint32_t)(&I2C2_DR); } 363 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&I2C2_DR); } 364 | }; 365 | 366 | #endif 367 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/menu.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STM32PP_MENU_HPP 2 | #define STM32PP_MENU_HPP 3 | #include 4 | #include 5 | 6 | namespace nsmenu 7 | { 8 | enum Event: uint8_t { 9 | kEventLeave=1, 10 | kEventBtnUp=2, kEventBtnDown=3, kEventBtnOk=4, kEventBtnBack=5 11 | }; 12 | struct Item 13 | { 14 | enum Flags: uint8_t { kIsMenu = 1 }; 15 | const char* text; 16 | uint8_t flags; 17 | Item(const char* aText, uint8_t aFlags=0): text(aText), flags(aFlags){} 18 | virtual ~Item() {} 19 | }; 20 | 21 | struct IValue: public Item 22 | { 23 | using Item::Item; 24 | virtual const char* strValue() = 0; 25 | virtual void* binValue(uint8_t& size) const = 0; 26 | virtual const char* onEvent(Event evt) = 0; 27 | }; 28 | 29 | template 30 | struct Value: public IValue 31 | { 32 | enum: uint8_t { kValueId = Id }; 33 | T value; 34 | Value(const char* aText, T aValue): IValue(aText), value(aValue) {} 35 | virtual void* binValue(uint8_t& size) const 36 | { 37 | size = sizeof(T); 38 | return (void*)&value; 39 | } 40 | }; 41 | 42 | template 43 | struct DefaultStep 44 | { 45 | template 46 | static constexpr typename std::enable_if::value, X>::type value() 47 | { 48 | return 1; 49 | } 50 | template 51 | static constexpr typename std::enable_if::value, X>::type value() 52 | { 53 | return 0.1; 54 | } 55 | }; 56 | 57 | template ::value(), 60 | T Min=std::numeric_limits::min(), T Max=std::numeric_limits::max()> 61 | struct NumValue: public Value 62 | { 63 | enum: uint8_t { kEditBufSize = 18 }; 64 | char* mEditBuf = nullptr; 65 | NumValue(const char* aText, T defValue) 66 | : Value(aText, defValue){} 67 | virtual const char* strValue() 68 | { 69 | if (!mEditBuf) { 70 | mEditBuf = new char[kEditBufSize]; 71 | toString(mEditBuf, kEditBufSize, this->value); 72 | } 73 | return mEditBuf; 74 | } 75 | virtual const char* onEvent(Event evt) 76 | { 77 | xassert(mEditBuf); 78 | switch(evt) 79 | { 80 | case kEventLeave: 81 | delete[] mEditBuf; 82 | mEditBuf = nullptr; 83 | return nullptr; 84 | case kEventBtnUp: 85 | return onButtonUp(); 86 | case kEventBtnDown: 87 | return onButtonDown(); 88 | default: 89 | __builtin_trap(); 90 | return nullptr; 91 | } 92 | } 93 | const char* onButtonUp() 94 | { 95 | if (this->value >= Max) { 96 | return nullptr; 97 | } 98 | if (!(void*)ChangeHandler) { 99 | this->value += Step; 100 | } else { 101 | auto newVal = this->value + Step; 102 | if (ChangeHandler(newVal)) { 103 | this->value = newVal; 104 | } else { 105 | return nullptr; 106 | } 107 | } 108 | return toString(mEditBuf, kEditBufSize, this->value); 109 | } 110 | const char* onButtonDown() 111 | { 112 | if (this->value <= Min) { 113 | return nullptr; 114 | } 115 | if (!(void*)ChangeHandler) { 116 | this->value -= Step; 117 | } else { 118 | auto newVal = this->value - Step; 119 | if (!ChangeHandler(newVal)) { 120 | return nullptr; 121 | } else { 122 | this->value = newVal; 123 | } 124 | } 125 | return toString(mEditBuf, kEditBufSize, this->value); 126 | } 127 | }; 128 | 129 | template 130 | struct EnumValue: public Value 131 | { 132 | uint8_t max; 133 | const char** enames; 134 | EnumValue(const char* aText, uint8_t aValue, std::initializer_list names) 135 | : Value(aText, aValue) 136 | { 137 | enames = new const char*[names.size()]; 138 | max = names.size()-1; 139 | int ctr = 0; 140 | for (auto name: names) { 141 | enames[ctr++] = name; 142 | } 143 | xassert(aValue <= max); 144 | } 145 | virtual const char* strValue() 146 | { 147 | return enames[this->value]; 148 | } 149 | virtual const char* onEvent(Event evt) 150 | { 151 | switch(evt) 152 | { 153 | case kEventLeave: 154 | return nullptr; 155 | case kEventBtnUp: 156 | return onButtonUp(); 157 | case kEventBtnDown: 158 | return onButtonDown(); 159 | default: 160 | __builtin_trap(); 161 | return nullptr; 162 | } 163 | } 164 | const char* onButtonUp() 165 | { 166 | auto newVal = (this->value == max) ? 0 : this->value+1; 167 | if ((void*)ChangeHandler) { 168 | if (!ChangeHandler(newVal)) { 169 | return nullptr; 170 | } 171 | } 172 | this->value = newVal; 173 | return enames[this->value]; 174 | } 175 | const char* onButtonDown() 176 | { 177 | auto newVal = (this->value == 0) ? max : this->value-1; 178 | if ((void*)ChangeHandler) { 179 | if (!ChangeHandler(newVal)) { 180 | return nullptr; 181 | } 182 | } 183 | this->value = newVal; 184 | return enames[this->value]; 185 | } 186 | }; 187 | 188 | template 189 | struct BoolValue: public EnumValue 190 | { 191 | BoolValue(const char* aText, uint8_t aValue, std::initializer_list names={"yes", "no"}) 192 | : EnumValue(aText, aValue, names){} 193 | }; 194 | 195 | struct Menu: public Item 196 | { 197 | Menu* parentMenu; 198 | std::vector items; 199 | Menu(Menu* aParent, const char* aText) 200 | : Item(aText, kIsMenu), parentMenu(aParent) 201 | {} 202 | template 203 | void addValue(Args... args) 204 | { 205 | items.push_back(new T(args...)); 206 | } 207 | template 208 | void addEnum(const char* text, uint8_t val, std::initializer_listnames={"yes", "no"}) 209 | { 210 | items.push_back(new EnumValue(text, val, names)); 211 | } 212 | 213 | Menu* submenu(const char* name) 214 | { 215 | auto item = new Menu(this, name); 216 | items.push_back(item); 217 | return item; 218 | } 219 | ~Menu() 220 | { 221 | for (auto item: items) { 222 | delete item; 223 | } 224 | items.clear(); 225 | } 226 | 227 | }; 228 | 229 | enum: uint8_t { kMenuNoBackButton = 1 }; 230 | template 231 | struct MenuSystem: public Menu 232 | { 233 | LCD& lcd; 234 | int16_t mTop; 235 | int16_t mHeight; 236 | int8_t mFontHeight; 237 | Menu* mCurrentMenu = this; 238 | int8_t mSelIdx = 0; 239 | int8_t mScrollOffset = 0; 240 | int8_t mMaxItems; 241 | uint8_t mConfig; 242 | int8_t mCurrLine = -1; 243 | int8_t screenSelPos() const 244 | { 245 | return mSelIdx - mScrollOffset; 246 | } 247 | MenuSystem(LCD& aLcd, const char* title, int16_t y=0, int16_t height=-1, 248 | uint8_t aConfig=kMenuNoBackButton) 249 | : Menu(nullptr, title), lcd(aLcd), mTop(y), mConfig(aConfig) 250 | { 251 | if (height < 0) { 252 | mHeight = lcd.height() - mTop; 253 | } else { 254 | xassert(mTop + mHeight <= lcd.height()); 255 | mHeight = height; 256 | } 257 | if (aConfig & kMenuNoBackButton) { 258 | items.push_back(nullptr); 259 | } 260 | } 261 | void onButtonUp() 262 | { 263 | if (mSelIdx == 0) { 264 | return; 265 | } 266 | if (mSelIdx == mScrollOffset) { // topmost position, scroll down 267 | mScrollOffset = mSelIdx = mScrollOffset-1; 268 | render(); 269 | } else { 270 | drawSelection(); 271 | mSelIdx--; 272 | drawSelection(); 273 | } 274 | lcd.updateScreen(); 275 | } 276 | void onButtonDown() 277 | { 278 | if (mSelIdx >= items.size()-1) { 279 | return; 280 | } 281 | if (screenSelPos() >= mMaxItems-1) { // topmost position, scroll down 282 | mScrollOffset++; 283 | mSelIdx++; 284 | render(); 285 | } else { 286 | drawSelection(); 287 | mSelIdx++; 288 | drawSelection(); 289 | } 290 | lcd.updateScreen(); 291 | } 292 | void render() 293 | { 294 | xassert(lcd.hasFont()); 295 | mFontHeight = lcd.font().height; 296 | mMaxItems = (mHeight - mTop - mFontHeight - 5) / (mFontHeight + 1); 297 | printf("maxItems = %d\n", mMaxItems); 298 | lcd.clear(); 299 | auto y = mTop; 300 | /* 301 | * [ Title ] 302 | * 2 px space 303 | * ----------------- 304 | * 2 px space 305 | * 1-st menu item 306 | */ 307 | lcd.putsCentered(y, text); 308 | y += lcd.font().height + 2; 309 | lcd.hLine(0, lcd.width()-1, y); 310 | y += 2; 311 | uint8_t endItem = mScrollOffset + mMaxItems; 312 | if (items.size() < endItem) { 313 | endItem = items.size(); 314 | } 315 | for (int8_t i = mScrollOffset; i < endItem; i++) { 316 | lcd.gotoXY(0, y); 317 | auto item = items[i]; 318 | if (!item) { 319 | lcd.puts("< Back"); 320 | } else { 321 | lcd.puts(item->text); 322 | if (item->flags & Item::kIsMenu) { 323 | lcd.puts(" -->"); 324 | } else { 325 | lcd.putsRAligned(y, static_cast(item)->strValue()); 326 | } 327 | } 328 | y += lcd.font().height + 1; 329 | } 330 | drawSelection(); 331 | } 332 | void drawSelection() 333 | { 334 | int16_t screenIdx; 335 | if (mSelIdx < 0) { 336 | mSelIdx = mScrollOffset; 337 | } else { 338 | screenIdx = mSelIdx - mScrollOffset; 339 | if (screenIdx < 0) { 340 | mSelIdx = mScrollOffset; 341 | } else if (screenIdx > mMaxItems) { 342 | mSelIdx = mScrollOffset + mMaxItems - 1; 343 | } 344 | } 345 | int16_t top = mTop + mFontHeight + 3 + (screenIdx * (mFontHeight+1)); 346 | lcd.invertRect(0, top, lcd.width(), mFontHeight + 2); 347 | } 348 | }; 349 | } 350 | 351 | #endif 352 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/dma.hpp: -------------------------------------------------------------------------------- 1 | /** @author Alexander Vassilev 2 | * @copyright BSD Licence 3 | * 4 | */ 5 | 6 | #ifndef DMA_HPP 7 | #define DMA_HPP 8 | #include 9 | #include 10 | #include 11 | #include "xassert.hpp" 12 | #include "common.hpp" 13 | 14 | //#define DMA_ENABLE_DEBUG 15 | 16 | #if defined(DMA_ENABLE_DEBUG) || !defined(NDEBUG) 17 | #include "tprintf.hpp" 18 | #define DMA_LOG_DEBUG(fmt,...) tprintf("%(%): " fmt "\n", Base::periphName(), DmaInfo::periphName(), ##__VA_ARGS__) 19 | #else 20 | #define DMA_LOG_DEBUG(fmt,...) 21 | #endif 22 | 23 | TYPE_SUPPORTS(HasTxDma, &std::remove_reference::type::dmaTxStop); 24 | TYPE_SUPPORTS(HasRxDma, &std::remove_reference::type::dmaRxStop); 25 | 26 | namespace dma 27 | { 28 | constexpr uint32_t periphSizeCode(uint8_t size) 29 | { 30 | switch(size) 31 | { 32 | case 1: return DMA_CCR_PSIZE_8BIT; 33 | case 2: return DMA_CCR_PSIZE_16BIT; 34 | case 4: return DMA_CCR_PSIZE_32BIT; 35 | default: __builtin_trap(); 36 | } 37 | } 38 | constexpr uint32_t memSizeCode(uint8_t size) 39 | { 40 | switch(size) 41 | { 42 | case 1: return DMA_CCR_MSIZE_8BIT; 43 | case 2: return DMA_CCR_MSIZE_16BIT; 44 | case 4: return DMA_CCR_MSIZE_32BIT; 45 | default: __builtin_trap(); 46 | } 47 | } 48 | enum: uint8_t { 49 | // DMA channel priority 50 | kPrioMask = 0x3, //bits 0 and 1 denote DMA interrupt priority 51 | kPrioShift = 0, 52 | kPrioVeryHigh = 3, 53 | kPrioHigh = 2, 54 | kPrioMedium = 1, 55 | kPrioLow = 0, 56 | // DMA IRQ priority 57 | kIrqPrioMask = 0x3 << 2, 58 | kIrqPrioShift = 2, 59 | kIrqPrioVeryHigh = 0 << 2, 60 | kIrqPrioHigh = 1 << 2, 61 | kIrqPrioMedium = 2 << 2, 62 | kIrqPrioLow = 3 << 2, 63 | kDmaDontEnableClock = 0x10, // In case the DMA controller clock is already enabled 64 | kDmaNoDoneIntr = 0x20, // Disable dma complete interrupt 65 | kDmaCircularMode = 0x40, 66 | kDefaultOpts = kIrqPrioMedium | kPrioMedium, 67 | kAllMaxPrio = kPrioVeryHigh | kIrqPrioVeryHigh 68 | }; 69 | 70 | bool dmaChannelIsBusy(uint32_t dma, uint8_t chan) 71 | { 72 | return (DMA_CCR(dma, chan) & DMA_CCR_EN); 73 | } 74 | 75 | /** Mixin to support Tx DMA. Base is a peripheral, which is derived from 76 | * PeriphInfo, where Periph is the actual peripheral id (such as ADC1) 77 | * for which DMA is to be supported. No method should conflict with one in dma::Rx 78 | */ 79 | template 80 | class Tx: public Base 81 | { 82 | private: 83 | typedef Tx Self; 84 | typedef PeriphInfo DmaInfo; 85 | typedef void(*FreeFunc)(void*); 86 | volatile bool mTxBusy = false; 87 | protected: 88 | enum: uint8_t { kDmaTxIrq = DmaInfo::dmaIrqForChannel(Base::kDmaTxChannel) }; 89 | public: 90 | template 91 | void init(Args... args) 92 | { 93 | enum: uint8_t { chan = Self::kDmaTxChannel }; 94 | enum: uint32_t { dma = Self::kDmaTxId }; 95 | 96 | Base::init(args...); 97 | DMA_LOG_DEBUG("Tx: Initializing channel %, irq %, opts: %", 98 | (int)Base::kDmaTxChannel, (int)kDmaTxIrq, fmtHex(Opts)); 99 | 100 | if (!HasRxDma::value) 101 | { 102 | rcc_periph_clock_enable(DmaInfo::kClockId); 103 | DMA_LOG_DEBUG("Tx: Enabled clock"); 104 | } 105 | 106 | dma_channel_reset(dma, chan); 107 | dma_set_peripheral_address(dma, chan, Base::dmaTxDataRegister()); 108 | dma_set_peripheral_size(dma, chan, periphSizeCode(Base::kDmaWordSize)); 109 | dma_disable_peripheral_increment_mode(dma, chan); 110 | 111 | dma_set_read_from_memory(dma, chan); 112 | dma_enable_memory_increment_mode(dma, chan); 113 | dma_set_priority(dma, chan, ((Opts & kPrioMask) >> kPrioShift) << DMA_CCR_PL_SHIFT); 114 | if ((Opts & kDmaNoDoneIntr) == 0) 115 | { 116 | nvic_set_priority(kDmaTxIrq, (Opts & kIrqPrioMask) >> kIrqPrioShift); 117 | DMA_LOG_DEBUG("Tx: Enabled transfer complete interrupt"); 118 | } 119 | } 120 | /** @brief Initiates a DMA transfer of the buffer specified 121 | * by the \c data and \c size paremeters. 122 | * When the transfer is complete and the specified \c freeFunc 123 | * is not \c nullptr, that function will be called with the \c data 124 | * param to free it. It can be used also as a completion callback. 125 | * @note Note that \c freeFunc will be called from an interrupt. 126 | * If there is already a transfer in progress, \c dmaWrite() blocks until 127 | * the previous transfer completes (and the previous buffer is freed, 128 | * in case \c freeFunc was provided for the previous transfer). 129 | */ 130 | template 131 | void dmaTxStart(const void* data, uint16_t size, Args... args) 132 | { 133 | enum: uint8_t { chan = Self::kDmaTxChannel }; 134 | enum: uint32_t { dma = Self::kDmaTxId }; 135 | xassert(size % Base::kDmaWordSize == 0); 136 | 137 | while(mTxBusy); 138 | mTxBusy = true; 139 | 140 | dma_set_memory_address(dma, chan, (uint32_t)data); 141 | dma_set_number_of_data(dma, chan, size / Base::kDmaWordSize); 142 | dma_set_memory_size(dma, chan, memSizeCode(Base::kDmaWordSize)); 143 | if ((Opts & kDmaNoDoneIntr) == 0) 144 | { 145 | dma_enable_transfer_complete_interrupt(dma, chan); 146 | nvic_enable_irq(kDmaTxIrq); 147 | } 148 | dma_enable_channel(dma, chan); 149 | //have to enable DMA for peripheral at the upper level and the transfer should start 150 | Base::dmaStartPeripheralTx(args...); 151 | } 152 | volatile bool txBusy() const { return mTxBusy; } 153 | void dmaTxIsr() 154 | { 155 | // check if transfer complete flag is set 156 | // if it is not set, assume something is wrong and bail out 157 | if ((DMA_ISR(Base::kDmaTxId) & DMA_ISR_TCIF(Base::kDmaTxChannel)) == 0) 158 | return; 159 | 160 | // TODO: Maybe move clearing the TCIF flag just before starting a new transfer 161 | // and use it for txBusy() status 162 | 163 | // Clear transfer-complete interrupt flag 164 | DMA_IFCR(Base::kDmaTxId) |= DMA_IFCR_CTCIF(Base::kDmaTxChannel); 165 | dmaTxStop(); 166 | } 167 | void dmaTxStop() // this is called from an ISR 168 | { 169 | dma_disable_transfer_complete_interrupt(Base::kDmaTxId, Base::kDmaTxChannel); 170 | Base::dmaStopPeripheralTx(); 171 | dma_disable_channel(Base::kDmaTxId, Base::kDmaTxChannel); 172 | mTxBusy = false; 173 | } 174 | }; 175 | /** Mixin to support Rx DMA. Base is derived from DmaInfo, 176 | * where Periph is the actual peripheral for which DMA is to be supported 177 | */ 178 | template 179 | class Rx: public Base 180 | { 181 | private: 182 | volatile bool mRxBusy = false; 183 | typedef Rx Self; 184 | typedef PeriphInfo DmaInfo; 185 | public: 186 | enum: uint8_t { kDmaRxIrq = DmaInfo::dmaIrqForChannel(Self::kDmaRxChannel) }; 187 | volatile bool dmaRxBusy() const { return mRxBusy; } 188 | template 189 | void init(Args... args) 190 | { 191 | Base::init(args...); 192 | DMA_LOG_DEBUG("Rx: Initializing channel %, irq %, opts: %", 193 | (int)Base::kDmaRxChannel, (int)kDmaRxIrq, fmtHex(Opts)); 194 | 195 | enum: uint8_t { chan = Base::kDmaRxChannel }; 196 | enum: uint32_t { dma = Base::kDmaRxId }; 197 | 198 | if (!HasTxDma::value) 199 | { 200 | rcc_periph_clock_enable(DmaInfo::kClockId); 201 | DMA_LOG_DEBUG("Rx: Enabled clock"); 202 | } 203 | 204 | dma_disable_channel(dma, chan); 205 | dma_channel_reset(dma, chan); 206 | dma_set_peripheral_address(dma, chan, (uint32_t)Base::dmaRxDataRegister()); 207 | dma_set_peripheral_size(dma, chan, periphSizeCode(Base::kDmaWordSize)); 208 | dma_disable_peripheral_increment_mode(dma, chan); 209 | 210 | dma_enable_memory_increment_mode(dma, chan); 211 | dma_set_read_from_peripheral(dma, chan); 212 | dma_set_priority(dma, chan, ((Opts & kPrioMask) >> kPrioShift) << DMA_CCR_PL_SHIFT); 213 | if (Opts & kDmaCircularMode) 214 | { 215 | dma_enable_circular_mode(dma, chan); 216 | } 217 | else if ((Opts & kDmaNoDoneIntr) == 0) // Interrupt when transfer complete 218 | { 219 | nvic_set_priority(kDmaRxIrq, (Opts & kIrqPrioMask) >> kIrqPrioShift); 220 | DMA_LOG_DEBUG("Rx: Enabled transfer complete interrupt"); 221 | } 222 | } 223 | template 224 | void dmaRxStart(const void* data, uint16_t size, Args... args) 225 | { 226 | xassert(size % Base::kDmaWordSize == 0); 227 | enum: uint32_t { dma = Base::kDmaRxId }; 228 | enum: uint8_t { chan = Base::kDmaRxChannel }; 229 | while(mRxBusy); 230 | mRxBusy = true; 231 | 232 | dma_set_memory_address(dma, chan, (uint32_t)data); 233 | dma_set_memory_size(dma, chan, memSizeCode(Base::kDmaWordSize)); 234 | dma_set_number_of_data(dma, chan, size / Base::kDmaWordSize); 235 | dma_enable_channel(dma, chan); 236 | if ((Opts & kDmaNoDoneIntr) == 0) 237 | { 238 | dma_enable_transfer_complete_interrupt(dma, chan); 239 | nvic_enable_irq(kDmaRxIrq); 240 | } 241 | Base::dmaStartPeripheralRx(args...); 242 | } 243 | void dmaRxIsr() 244 | { 245 | if ((DMA_ISR(Base::kDmaRxId) & DMA_ISR_TCIF(Self::kDmaRxChannel)) == 0) 246 | { 247 | return; 248 | } 249 | DMA_IFCR(Base::kDmaRxId) |= DMA_IFCR_CTCIF(Self::kDmaRxChannel); 250 | dmaRxStop(); 251 | } 252 | void dmaRxStop() 253 | { 254 | nvic_disable_irq(kDmaRxIrq); 255 | dma_disable_transfer_complete_interrupt(Base::kDmaRxId, Base::kDmaRxChannel); 256 | Base::dmaStopPeripheralRx(); 257 | dma_disable_channel(Base::kDmaRxId, Base::kDmaRxChannel); 258 | mRxBusy = false; 259 | } 260 | }; 261 | } 262 | 263 | /** Peripheral definitions */ 264 | STM32PP_PERIPH_INFO(DMA1) 265 | static constexpr rcc_periph_clken kClockId = RCC_DMA1; 266 | static constexpr uint8_t dmaIrqForChannel(const uint8_t chan) 267 | { 268 | switch (chan) 269 | { 270 | case 1: return NVIC_DMA1_CHANNEL1_IRQ; 271 | case 2: return NVIC_DMA1_CHANNEL2_IRQ; 272 | case 3: return NVIC_DMA1_CHANNEL3_IRQ; 273 | case 4: return NVIC_DMA1_CHANNEL4_IRQ; 274 | case 5: return NVIC_DMA1_CHANNEL5_IRQ; 275 | case 6: return NVIC_DMA1_CHANNEL6_IRQ; 276 | case 7: return NVIC_DMA1_CHANNEL7_IRQ; 277 | default: __builtin_trap(); 278 | } 279 | } 280 | }; 281 | 282 | STM32PP_PERIPH_INFO(DMA2) 283 | static constexpr rcc_periph_clken kClockId = RCC_DMA2; 284 | static constexpr uint8_t dmaIrqForChannel(const uint8_t chan) 285 | { 286 | switch (chan) 287 | { 288 | case 1: return NVIC_DMA2_CHANNEL1_IRQ; 289 | case 2: return NVIC_DMA2_CHANNEL2_IRQ; 290 | case 3: return NVIC_DMA2_CHANNEL3_IRQ; 291 | case 4: return NVIC_DMA2_CHANNEL4_5_IRQ; 292 | case 5: return NVIC_DMA2_CHANNEL5_IRQ; 293 | default: __builtin_trap(); 294 | } 295 | } 296 | }; 297 | 298 | #endif // DMA_HPP 299 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/emu/lcdemu.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LCDEMU_HPP 2 | #define LCDEMU_HPP 3 | /** 4 | Emulation of an LCD display and a button panel, using wxWidgets 5 | @author: Alexander Vassilev 6 | @copyright BSD License 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | class BtnDriver; 17 | struct IButtonHandler 18 | { 19 | protected: 20 | uint16_t mPort = 0; 21 | public: 22 | uint16_t port() const { return mPort; } 23 | virtual bool onKeyEvent(bool down, int code) = 0; 24 | }; 25 | struct ButtonAppBase: wxApp 26 | { 27 | IButtonHandler* keyHandler = nullptr; 28 | }; 29 | 30 | template 31 | class ButtonPanel: public wxPanel, public IButtonHandler 32 | { 33 | public: 34 | typedef ButtonPanel Self; 35 | btn::Buttons<0, Pins, RptPins, Flags, 127, DebncDelay, BtnDriver> buttons; 36 | ButtonPanel(wxWindow* parent, btn::EventCb handler, void* userp) 37 | : wxPanel(parent) 38 | { 39 | auto sizer = new wxBoxSizer(wxHORIZONTAL); 40 | for (uint8_t pos = 0; pos < 16; pos++) { 41 | uint16_t mask = 1 << pos; 42 | if ((Pins & mask) == 0) { 43 | continue; 44 | } 45 | auto btn = new wxToggleButton(this, wxID_HIGHEST + 100 + pos, wxString::Format("%d", pos)); 46 | btn->SetMinSize(wxSize(1, 30)); 47 | sizer->Add(btn, 1, wxEXPAND); 48 | Bind(wxEVT_TOGGLEBUTTON, &Self::onButton, this, btn->GetId()); 49 | } 50 | SetSizer(sizer); 51 | static_cast(wxTheApp)->keyHandler = this; 52 | buttons.init(handler, userp); 53 | } 54 | virtual void onButton(wxCommandEvent& evt) 55 | { 56 | auto n = evt.GetId() - wxID_HIGHEST-100; 57 | uint16_t mask = (1 << n); 58 | assert(Pins & mask); 59 | if (evt.IsChecked()) { 60 | mPort |= mask; 61 | } else { 62 | mPort &= ~mask; 63 | } 64 | } 65 | virtual bool onKeyEvent(bool down, int code) 66 | { 67 | if (code < '0' || code > '9') { 68 | return false; 69 | } 70 | int n = code - '0'; 71 | uint16_t mask = (1 << n); 72 | if ((Pins & mask) == 0) { 73 | return false; 74 | } 75 | auto btn = (wxToggleButton*)wxWindow::FindWindowById(wxID_HIGHEST + 100 + n); 76 | if (!btn) { 77 | return false; 78 | } 79 | auto val = (mPort & mask) != 0; 80 | if (down == val) { 81 | return true; 82 | } 83 | if (down) { 84 | mPort |= mask; 85 | } else { 86 | mPort &= ~mask; 87 | } 88 | btn->SetValue(down); 89 | return true; 90 | } 91 | }; 92 | extern uint16_t pinStates; 93 | 94 | struct BtnDriver 95 | { 96 | static uint32_t now() 97 | { 98 | return wxGetUTCTimeMillis().GetValue() & 0xffffffff; 99 | } 100 | static uint32_t ms10ElapsedSince(uint32_t sinceTicks) 101 | { 102 | auto now = BtnDriver::now(); 103 | if (now >= sinceTicks) 104 | { 105 | return (now - sinceTicks) / 10; 106 | } 107 | else // DwtCounter wrap 108 | { 109 | return (((uint64_t)now + 0xffffffff) - sinceTicks) / 10; 110 | } 111 | } 112 | static uint32_t ticksToMs(uint32_t ticks) { return ticks; } 113 | static bool isIrqEnabled(uint8_t irqn) { return false; } 114 | static void enableIrq(uint8_t irqn) { } 115 | static void disableIrq(uint8_t irqn) { } 116 | static void gpioSetPuPdInput(uint32_t port, uint16_t pins, int pullUp) {} 117 | static void gpioSetFloatInput(uint32_t port, uint16_t pins) {} 118 | static uint16_t gpioRead(uint32_t port) 119 | { 120 | return static_cast(wxTheApp)->keyHandler->port(); 121 | } 122 | }; 123 | 124 | template 125 | class LcdDriver : public wxPanel 126 | { 127 | public: 128 | enum: uint8_t { kNumPages = Height / 8 }; 129 | enum { kBufSize = kNumPages * Width }; // LCD Driver API 130 | enum: uint8_t { kFrameWidth = 4, kLcdBorderWidth = 8, kBorderWidth = kFrameWidth+kLcdBorderWidth }; 131 | uint8_t mBuf[kBufSize]; 132 | wxColor mPixelColor = wxColor(0x50, 0x50, 0x50); 133 | bool mIsARLocked = true; 134 | // LCD driver API 135 | static int16_t width() { return Width; } 136 | static int16_t height() { return Height; } 137 | uint8_t* rawBuf() { return mBuf; } 138 | void updateScreen() 139 | { 140 | Refresh(); 141 | Update(); 142 | //wxClientDC dc(this); 143 | //render(dc); 144 | } 145 | void setContrast(uint8_t val) 146 | { 147 | val = 255 - val; 148 | mPixelColor.Set(0, 0, 0, val); 149 | } 150 | bool init() 151 | { 152 | memset(mBuf, 0, sizeof(mBuf)); 153 | return true; 154 | } 155 | //==== 156 | static constexpr wxSize minSize() 157 | { 158 | return wxSize(Width + 6*kBorderWidth, Height + 6*kBorderWidth); 159 | } 160 | static constexpr wxSize sizeInc() { return wxSize(Width, Height); } 161 | 162 | LcdDriver(wxWindow* parent): wxPanel(parent) 163 | { 164 | static_assert(Height % 8 == 0); 165 | // connect event handlers 166 | Connect(wxEVT_PAINT, wxPaintEventHandler(LcdDriver::paintEvent)); 167 | } 168 | void paintEvent(wxPaintEvent & evt) 169 | { 170 | wxPaintDC dc(this); 171 | render(dc); 172 | } 173 | void render(wxDC& dc) 174 | { 175 | auto canvSize = dc.GetSize(); 176 | auto pixelWidth = (canvSize.GetWidth() - kBorderWidth * 2) / Width; 177 | if (pixelWidth < 1) { 178 | return; 179 | } 180 | auto pixelHeight = (canvSize.GetHeight() - kBorderWidth * 2) / Height; 181 | if (pixelHeight < 1) { 182 | return; 183 | } 184 | if (mIsARLocked) { 185 | pixelHeight = pixelWidth = std::min(pixelWidth, pixelHeight); 186 | } 187 | auto imageWidth = Width * pixelWidth; 188 | auto imageHeight = Height * pixelHeight; 189 | 190 | auto hPad = (canvSize.GetWidth() - imageWidth) / 2; 191 | auto vPad = (canvSize.GetHeight() - imageHeight) / 2; 192 | dc.SetPen(wxPen(wxColor(0x00, 0x20, 0x20), kFrameWidth)); 193 | dc.DrawRectangle(hPad-kLcdBorderWidth - kFrameWidth/2, vPad-kLcdBorderWidth-kFrameWidth/2, 194 | imageWidth+kBorderWidth*2-kFrameWidth, imageHeight+kBorderWidth*2-kFrameWidth); 195 | dc.GradientFillLinear( 196 | wxRect(hPad-kLcdBorderWidth, vPad-kLcdBorderWidth, imageWidth+kLcdBorderWidth*2, imageHeight+kLcdBorderWidth*2), 197 | wxColor(0xf1, 0xf7, 0xde), wxColor(0xcf, 0xd9, 0xb0), wxBOTTOM 198 | ); 199 | 200 | dc.SetBrush(wxBrush(mPixelColor)); 201 | dc.SetPen(wxPen(mPixelColor, 1)); 202 | 203 | auto pixPtr = mBuf; 204 | for (uint8_t page = 0; page < kNumPages; page++) { 205 | for (int16_t x = 0; x < Width; x++) { 206 | auto pixels = *pixPtr++; 207 | uint8_t mask = 0x01; 208 | for (uint8_t bit = 0; bit < 8; bit++) { 209 | if (pixels & mask) { 210 | dc.DrawRectangle( 211 | hPad + x * pixelWidth, vPad + (page * 8 + bit) * pixelHeight, 212 | pixelWidth, pixelHeight); 213 | } 214 | mask <<= 1; 215 | } 216 | } 217 | } 218 | } 219 | }; 220 | 221 | template 223 | class LcdPanel: public wxPanel 224 | { 225 | public: 226 | typedef DisplayGfx> LcdDisplay; 227 | typedef ButtonPanel BtnPanel; 228 | LcdDisplay* mLcd; 229 | BtnPanel* mBtnPanel; 230 | typedef LcdPanel Self; 231 | LcdPanel(wxWindow* parent, btn::EventCb handler) 232 | : wxPanel(parent) 233 | { 234 | mLcd = new LcdDisplay(this); 235 | auto sizer = new wxBoxSizer(wxVERTICAL); 236 | sizer->Add(mLcd, 1, wxEXPAND|wxALL); 237 | mBtnPanel = new BtnPanel(this, handler, wxTheApp); 238 | sizer->Add(mBtnPanel, 0, wxEXPAND|wxALL); 239 | SetSizer(sizer); 240 | } 241 | wxSize minSize() const 242 | { 243 | auto minSize = mLcd->minSize(); 244 | return wxSize(minSize.GetWidth(), minSize.GetHeight() + mBtnPanel->GetSize().GetHeight()); 245 | } 246 | }; 247 | 248 | template 250 | class ButtonFrame: public wxFrame 251 | { 252 | public: 253 | typedef ButtonFrame Self; 254 | typedef LcdPanel Panel; 255 | App& mApp; 256 | Panel* mPanel; 257 | ButtonFrame(App& app, const wxString& title, const wxPoint& pos, const wxSize& size) 258 | : wxFrame(NULL, wxID_ANY, title, pos, size), mApp(app) 259 | { 260 | mPanel = new Panel(this, App::buttonHandler); 261 | SetSizeHints(mPanel->minSize(), wxDefaultSize, mPanel->mLcd->sizeInc()); 262 | Connect(wxEVT_SHOW, wxShowEventHandler(Self::onShow)); 263 | } 264 | void onShow(wxShowEvent& evt) 265 | { 266 | Connect(wxEVT_COMMAND_ENTER, wxCommandEventHandler(Self::onStart)); 267 | AddPendingEvent(wxCommandEvent(wxEVT_COMMAND_ENTER)); 268 | } 269 | void onStart(wxCommandEvent& evt) 270 | { 271 | mApp.onStart(); 272 | } 273 | }; 274 | template 276 | class ButtonApp: public ButtonAppBase 277 | { 278 | public: 279 | typedef ButtonApp Self; 280 | typedef ButtonFrame Frame; 281 | enum {kTimerId = wxID_HIGHEST + 1}; 282 | Frame* mFrame; 283 | decltype(mFrame->mPanel->mLcd) lcd; 284 | decltype(mFrame->mPanel->mBtnPanel->buttons)* buttons; 285 | wxTimer* mTimer = nullptr; 286 | int FilterEvent(wxEvent& event) 287 | { 288 | auto type = event.GetEventType(); 289 | if (type == wxEVT_KEY_DOWN) 290 | { 291 | auto code = ((wxKeyEvent&)event).GetKeyCode(); 292 | return keyHandler->onKeyEvent(true, code) ? Event_Processed : Event_Skip; 293 | } 294 | else if (type == wxEVT_KEY_UP) 295 | { 296 | auto code = ((wxKeyEvent&)event).GetKeyCode(); 297 | return keyHandler->onKeyEvent(false, code) ? Event_Processed : Event_Skip; 298 | } 299 | else 300 | { 301 | return Event_Skip; 302 | } 303 | } 304 | virtual bool OnInit() 305 | { 306 | mFrame = new Frame((App&)*this, "STM32 LCD Emulator", wxDefaultPosition, wxSize(500, 300)); 307 | lcd = mFrame->mPanel->mLcd; 308 | buttons = &mFrame->mPanel->mBtnPanel->buttons; 309 | 310 | mFrame->CenterOnScreen(); 311 | mFrame->Show(true); 312 | return true; 313 | } 314 | void startTimer(int ms) 315 | { 316 | if (mTimer) { 317 | printf("Timer already started\n"); 318 | return; 319 | } 320 | mTimer = new wxTimer(this, kTimerId); 321 | mTimer->Start(ms); 322 | 323 | //Connect(wxID_ANY, wxEVT_IDLE, wxIdleEventHandler(Self::onIdle)); 324 | Connect(kTimerId, wxEVT_TIMER, wxTimerEventHandler(Self::onTimer)); 325 | } 326 | void stopTimer() 327 | { 328 | if (!mTimer) { 329 | printf("Timer has not been started\n"); 330 | return; 331 | } 332 | mTimer->Stop(); 333 | Disconnect(kTimerId, wxEVT_TIMER, wxTimerEventHandler(Self::onTimer)); 334 | delete mTimer; 335 | mTimer = nullptr; 336 | } 337 | virtual void onTimer(wxTimerEvent& evt) 338 | { 339 | buttons->poll(); 340 | buttons->process(); 341 | } 342 | }; 343 | 344 | #endif // LCDEMU_HPP 345 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/button.hpp: -------------------------------------------------------------------------------- 1 | /** @author Alexander Vassilev 2 | * @copyright BSD License 3 | */ 4 | 5 | #ifndef BUTTON_HPP_INCLUDED 6 | #define BUTTON_HPP_INCLUDED 7 | 8 | #ifndef STM32PP_NOT_EMBEDDED 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #endif 17 | 18 | #include 19 | #include 20 | 21 | namespace btn 22 | { 23 | /** @brief Button event type */ 24 | enum: uint8_t 25 | { 26 | kEventUp = 0, //< Button up 27 | kEventDown = 1, //< Button down 28 | kEventHold = 2, //< Button held 29 | kEventRepeat = 3 //< Button repeat, generated when the button is held pressed 30 | }; 31 | /** @brief Option flags. They apply to all pins that the Button 32 | * class manages (the APins mask) 33 | */ 34 | enum: uint8_t 35 | { 36 | /** The button is pressed when the pin is low. If internal pull 37 | * is enabled, this flag determines which one of the pull modes 38 | * (pull-up or pull-down) is set on the button pins. 39 | */ 40 | kOptActiveLow = 1, 41 | /** Don't activate internal pull-up/pull-down for button pins */ 42 | kOptNoInternalPuPd = 2 43 | }; 44 | 45 | enum: uint8_t { kNoIrq = 127 }; 46 | 47 | /** @brief Button handler callback. 48 | * Called when a button event occurs 49 | * @param btn - The bit position of the button pin within the GPIO port, 50 | * i.e. 0 for GPIO0, 1 for GPIO1 etc 51 | * @param event - The type of event that occurred for the button 52 | * @param arg - The user pointer that was passed to the Buttons instance 53 | * at construction or set via \c setUserp() 54 | */ 55 | typedef void(*EventCb)(uint8_t btn, uint8_t event, void* userp); 56 | 57 | class HwDriver; 58 | 59 | /** @brief Button handler class 60 | * There is one methods that has to be called periodically on the 61 | * Buttons object - \c poll(). It polls the state of the pins, 62 | * handles debounce delays and queues events. This method is suitable 63 | * for calling from an ISR that is executed periodically, i.e. the systick 64 | * interrupt or a timer. The queued events are processed when the \c process() 65 | * method is called. This is normally done in the \c main() loop. 66 | * @param APort The GPIO port of the button pins 67 | * @param APins Mask of the pins of the port that are connected to buttons 68 | * @param ARpt Mask of the button pins that have repeat enabled. Must be 69 | * a subset of APins 70 | * @param AFlags Option flags 71 | * @param APollIrqN The IRQ number of an interrupt that calls the poll() 72 | * function. This is needed to temporarily disable the interrupt that 73 | * does the polling, to prevent re-entrancy. If polling is done in main(), 74 | * this should be set to kNoIrq (127). 75 | * @param ADebounceDly Debounce interval in milliseconds. If the pin maintains the 76 | * same state within at least this period, then its state is considered 77 | * stable. 78 | */ 79 | template 81 | class Buttons 82 | { 83 | public: 84 | enum: uint32_t { Port = APort }; 85 | enum: uint16_t { Pins = APins, RepeatPins = ARpt }; 86 | enum: uint8_t { Flags = AFlags, DebounceMs = ADebounceDly }; 87 | protected: 88 | //these are accessed only in the isr, via poll() 89 | volatile uint16_t mDebouncing = 0; 90 | volatile uint32_t mDebounceStartTs = 0; 91 | volatile uint16_t mLastPollState; 92 | //mState and mChanged are shared between the isr and the main thread 93 | volatile uint16_t mState; 94 | volatile uint16_t mChanged = 0; 95 | volatile uint32_t mLastObtainedTs; 96 | //=== 97 | EventCb mHandler; 98 | void* mHandlerUserp; 99 | //repeat stuff 100 | enum { kRptCount = CountOnes::value, 101 | kRptShift = Right0Count::value 102 | }; 103 | struct RepeatState 104 | { 105 | enum { kInitialPeriodMs10 = 20, kAccelIntervalMs10 = 40 }; //x10 milliseconds 106 | uint32_t mLastTs; // the time of the last event 107 | uint8_t mDelayToHoldEventMs10 = 33; // 1 second by default, can be configured per button 108 | uint8_t mTimeToNextMs10; 109 | uint8_t mRepeatCnt; 110 | }; 111 | RepeatState mRptStates[kRptCount]; 112 | public: 113 | Buttons() {} 114 | void init(EventCb aCb, void* aUserp) 115 | { 116 | mLastObtainedTs = Driver::now(); 117 | mHandler = aCb; 118 | mHandlerUserp = aUserp; 119 | //=== 120 | static_assert((RepeatPins & ~Pins) == 0, "RepeatPins specifies pins that are not in Pins"); 121 | if ((Flags & kOptNoInternalPuPd) == 0) 122 | { 123 | Driver::gpioSetPuPdInput(Port, Pins, Flags & kOptActiveLow); 124 | } 125 | else 126 | { 127 | Driver::gpioSetFloatInput(Port, Pins); 128 | } 129 | mState = Driver::gpioRead(Port); 130 | mLastPollState = mState; 131 | } 132 | /** @brief Polls the state of the button pins and queues events 133 | * for processing by process(). \c poll() is suitable for calling 134 | * from a periodic ISR 135 | */ 136 | void poll() 137 | { 138 | // may be called from an ISR 139 | // scan pins and detect changes 140 | uint16_t newState = Driver::gpioRead(Port); 141 | uint16_t changedPins = (mLastPollState ^ newState) & Pins; 142 | mLastPollState = newState; 143 | 144 | if (changedPins) 145 | { 146 | mDebounceStartTs = Driver::now(); //reset debounce timer 147 | mDebouncing |= changedPins; //add pins to the ones currently debounced 148 | } 149 | //check for end of debounce period 150 | if (mDebouncing 151 | && (Driver::ticksToMs(Driver::now() - mDebounceStartTs) >= DebounceMs)) 152 | { 153 | // debounce ended, read pins 154 | if (Flags & kOptActiveLow) 155 | newState = ~newState; 156 | // mask with mDebouncing rather than with Pins 157 | mChanged |= ((mState ^ newState) & mDebouncing); 158 | // update the state with only the debounced pins 159 | mState = (mState & ~mDebouncing) | (newState & mDebouncing); 160 | mDebouncing = 0; 161 | } 162 | } 163 | /** @brief Processes queued button events, by calling the user-supplied 164 | * event handler callback 165 | */ 166 | void process() 167 | { 168 | uint32_t now = Driver::now(); 169 | // atomically make a snapshot of the current button state and change flags 170 | bool intsWereEnabled; 171 | if (APollIrqN != kNoIrq) 172 | { 173 | intsWereEnabled = Driver::isIrqEnabled(APollIrqN); 174 | if (intsWereEnabled) 175 | Driver::disableIrq(APollIrqN); 176 | } 177 | uint16_t state = mState; 178 | uint16_t changed = mChanged; 179 | mChanged = 0; 180 | if ((APollIrqN != kNoIrq) && intsWereEnabled) 181 | { 182 | Driver::enableIrq(APollIrqN); 183 | } 184 | for (uint8_t idx=Right0Count::value; idx < HighestBitIdx::value; idx++) 185 | { 186 | uint16_t mask = 1 << idx; 187 | uint16_t pinState = state & mask; 188 | if (changed & mask) 189 | { 190 | uint8_t event; 191 | if (pinState) //just pressed 192 | { 193 | event = kEventDown; 194 | if (kRptCount && (pinState & RepeatPins)) 195 | { 196 | // record timestamp for newly pressed repeatable buttons 197 | auto& rptState = mRptStates[idx-kRptShift]; 198 | rptState.mLastTs = now; 199 | rptState.mTimeToNextMs10 = rptState.mDelayToHoldEventMs10; 200 | rptState.mRepeatCnt = 0; 201 | } 202 | } 203 | else 204 | { 205 | event = kEventUp; 206 | } 207 | mHandler(idx, event, mHandlerUserp); 208 | } 209 | else // button state didn't change 210 | { 211 | if (!kRptCount) //repeat not enabled for any button 212 | continue; 213 | if ((pinState & RepeatPins) == 0) //repeat not enabled for this button, or button not pressed 214 | continue; 215 | 216 | auto& rptState = mRptStates[idx-kRptShift]; 217 | // must be called at leat once per ~40 seconds in case of cpu frequency <= 100 MHz 218 | uint32_t ms10 = Driver::ms10ElapsedSince(rptState.mLastTs); 219 | if (ms10 < rptState.mTimeToNextMs10) 220 | { 221 | continue; //too early for next event 222 | } 223 | // time for next event has come 224 | rptState.mLastTs = now; 225 | if (rptState.mTimeToNextMs10 == rptState.mDelayToHoldEventMs10) 226 | { 227 | // The event that has come is "hold". Switch to repeat by setting 228 | // the time to next event to the initial repeat period 229 | rptState.mTimeToNextMs10 = RepeatState::kInitialPeriodMs10; 230 | rptState.mRepeatCnt = 0; 231 | mHandler(idx, kEventHold, mHandlerUserp); 232 | } 233 | else // The event that has come is "repeat" 234 | { 235 | // Calculate how much time we have spent at this repeat frequency 236 | // NOTE: When we have reached the maximum repeat frequency (mTimeToNextMs reaches 1) 237 | // mRepeatCnt will eventually wrap, because we don't reset it anymore. 238 | // That's ok, as we only need it to trigger next increase in rpt frequency, 239 | // which we can't anymore 240 | uint16_t durMs10 = (++rptState.mRepeatCnt) * rptState.mTimeToNextMs10; 241 | if (durMs10 > RepeatState::kAccelIntervalMs10) 242 | { 243 | if (rptState.mTimeToNextMs10 >= 2) 244 | { 245 | rptState.mTimeToNextMs10 >>= 1; 246 | rptState.mRepeatCnt = 0; 247 | } 248 | } 249 | mHandler(idx, kEventRepeat, mHandlerUserp); 250 | } 251 | } 252 | } 253 | } 254 | /** @brief Sets the user button event handler and its user pointer */ 255 | void setHandler(EventCb h, void* userp) 256 | { 257 | mHandler = h; 258 | mHandlerUserp = userp; 259 | } 260 | void setHoldDelayFor(uint16_t pin, uint16_t timeMs) 261 | { 262 | assert(pin & ARpt); 263 | uint8_t idx = 0; 264 | for (; idx < 16; idx++) 265 | { 266 | if ((1 << idx) == pin) 267 | break; 268 | } 269 | idx -= kRptShift; 270 | auto& state = mRptStates[idx]; 271 | state.mDelayToHoldEventMs10 = (timeMs + 5) / 10; 272 | } 273 | /** @brief Sets the user pointer that is passed to the event handler */ 274 | void setHandlerUserp(void* userp) { mHandlerUserp = userp; } 275 | }; 276 | 277 | #ifndef STM32PP_NOT_EMBEDDED 278 | class HwDriver 279 | { 280 | protected: 281 | typedef HwDriver Self; 282 | public: 283 | static uint32_t now() 284 | { 285 | return DwtCounter::get(); 286 | } 287 | static uint32_t ms10ElapsedSince(uint32_t sinceTicks) 288 | { 289 | auto now = Self::now(); 290 | if (now >= sinceTicks) 291 | { 292 | return (now - sinceTicks) / (rcc_ahb_frequency / 100); 293 | } 294 | else // DwtCounter wrap 295 | { 296 | return (((uint64_t)now + 0xffffffff) - sinceTicks) / (rcc_ahb_frequency / 100); 297 | } 298 | } 299 | static uint32_t ticksToMs(uint32_t ticks) { return DwtCounter::ticksToMs(ticks); } 300 | static bool isIrqEnabled(uint8_t irqn) { return nvic_get_irq_enabled(irqn); } 301 | static void enableIrq(uint8_t irqn) { nvic_enable_irq(irqn); } 302 | static void disableIrq(uint8_t irqn) { nvic_disable_irq(irqn); } 303 | static void gpioSetPuPdInput(uint32_t port, uint16_t pins, int pullUp) 304 | { 305 | gpio_set_mode(port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, pins); 306 | if (pullUp) 307 | { 308 | gpio_set(port, pins); 309 | } 310 | else 311 | { 312 | gpio_clear(port, pins); 313 | } 314 | } 315 | static void gpioSetFloatInput(uint32_t port, uint16_t pins) 316 | { 317 | gpio_set_mode(port, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, pins); 318 | } 319 | static uint16_t gpioRead(uint32_t port) 320 | { 321 | return GPIO_IDR(port); 322 | } 323 | }; 324 | #endif 325 | } 326 | 327 | /* 328 | Usage: 329 | void handler(uint8_t button, uint8_t event, void* arg) 330 | { 331 | gpio_toggle(GPIOC, GPIO13); 332 | } 333 | 334 | Buttons 335 | buttons(handler, nullptr); 336 | 337 | extern "C" void sys_tick_handler() 338 | { 339 | buttons.poll(); 340 | } 341 | 342 | int main(void) 343 | { 344 | rcc_clock_setup_in_hse_8mhz_out_72mhz(); 345 | dwt_enable_cycle_counter(); 346 | 347 | cm_enable_interrupts(); 348 | rcc_periph_clock_enable(RCC_GPIOC); 349 | gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, 350 | GPIO_CNF_OUTPUT_PUSHPULL, GPIO13); 351 | GPIOC_ODR = 0; 352 | systick_set_clocksource(STK_CSR_CLKSOURCE_AHB_DIV8); 353 | systick_set_reload(89999); 354 | 355 | systick_interrupt_enable(); 356 | 357 | // Start counting. 358 | systick_counter_enable(); 359 | for (;;) 360 | { 361 | buttons.process(); 362 | msDelay(20); 363 | } 364 | return 0; 365 | } 366 | */ 367 | #endif 368 | -------------------------------------------------------------------------------- /sysroot-baremetal/src/startup_stm32f10x.c: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file startup_stm32f10x_md.c 4 | * @author MCD Application Team, modified by Martin Thomas 5 | * @version V3.0.0-mthomas4 6 | * @date 19. Mar. 2010 7 | * @brief STM32F10x Medium Density Devices vector table for GNU toolchain. 8 | * This module performs: 9 | * - Set the initial SP 10 | * - Set the initial PC == Reset_Handler, 11 | * - Set the vector table entries with the exceptions ISR address 12 | * - Configure the clock system 13 | * - Branches to main in the C library (which eventually 14 | * calls main()). 15 | * After Reset the Cortex-M3 processor is in Thread mode, 16 | * priority is Privileged, and the Stack is set to Main. 17 | ******************************************************************************* 18 | * @copy 19 | * 20 | * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS 21 | * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE 22 | * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY 23 | * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING 24 | * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE 25 | * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. 26 | * 27 | *

© COPYRIGHT 2009 STMicroelectronics, 2010 M.Thomas

28 | */ 29 | 30 | 31 | /* Includes ------------------------------------------------------------------*/ 32 | /* Private typedef -----------------------------------------------------------*/ 33 | /* Private typedef -----------------------------------------------------------*/ 34 | typedef void( *const intfunc )( void ); 35 | 36 | /* Private define ------------------------------------------------------------*/ 37 | #define WEAK __attribute__ ((weak)) 38 | 39 | /* start address for the initialization values of the .data section. 40 | defined in linker script */ 41 | extern unsigned long _sidata; 42 | 43 | /* start address for the .data section. defined in linker script */ 44 | extern unsigned long _sdata; 45 | 46 | /* end address for the .data section. defined in linker script */ 47 | extern unsigned long _edata; 48 | 49 | /* start address for the .bss section. defined in linker script */ 50 | extern unsigned long _sbss; 51 | 52 | /* end address for the .bss section. defined in linker script */ 53 | extern unsigned long _ebss; 54 | 55 | /* init value for the stack pointer. defined in linker script */ 56 | extern unsigned long _estack; 57 | 58 | /* Private variables ---------------------------------------------------------*/ 59 | 60 | /* Private function prototypes -----------------------------------------------*/ 61 | void Reset_Handler(void) __attribute__((__interrupt__)); 62 | void __Init_Data_and_BSS(void); 63 | void Default_Handler(void); 64 | 65 | /* External function prototypes ----------------------------------------------*/ 66 | extern int main(void); /* Application's main function */ 67 | extern void SystemInit(void); /* STM's system init */ 68 | extern void __libc_init_array(void); /* calls CTORS of static objects */ 69 | 70 | 71 | /******************************************************************************* 72 | * 73 | * Forward declaration of the default fault handlers. 74 | * 75 | *******************************************************************************/ 76 | //mthomas void WEAK Reset_Handler(void); 77 | void WEAK NMI_Handler(void); 78 | void WEAK HardFault_Handler(void); 79 | void WEAK MemManage_Handler(void); 80 | void WEAK BusFault_Handler(void); 81 | void WEAK UsageFault_Handler(void); 82 | //mthomas void WEAK MemManage_Handler(void); 83 | void WEAK SVC_Handler(void); 84 | void WEAK DebugMon_Handler(void); 85 | void WEAK PendSV_Handler(void); 86 | void WEAK SysTick_Handler(void); 87 | 88 | /* External Interrupts */ 89 | void WEAK WWDG_IRQHandler(void); 90 | void WEAK PVD_IRQHandler(void); 91 | void WEAK TAMPER_IRQHandler(void); 92 | void WEAK RTC_IRQHandler(void); 93 | void WEAK FLASH_IRQHandler(void); 94 | void WEAK RCC_IRQHandler(void); 95 | void WEAK EXTI0_IRQHandler(void); 96 | void WEAK EXTI1_IRQHandler(void); 97 | void WEAK EXTI2_IRQHandler(void); 98 | void WEAK EXTI3_IRQHandler(void); 99 | void WEAK EXTI4_IRQHandler(void); 100 | void WEAK DMA1_Channel1_IRQHandler(void); 101 | void WEAK DMA1_Channel2_IRQHandler(void); 102 | void WEAK DMA1_Channel3_IRQHandler(void); 103 | void WEAK DMA1_Channel4_IRQHandler(void); 104 | void WEAK DMA1_Channel5_IRQHandler(void); 105 | void WEAK DMA1_Channel6_IRQHandler(void); 106 | void WEAK DMA1_Channel7_IRQHandler(void); 107 | void WEAK ADC1_2_IRQHandler(void); 108 | void WEAK USB_HP_CAN1_TX_IRQHandler(void); 109 | void WEAK USB_LP_CAN1_RX0_IRQHandler(void); 110 | void WEAK CAN1_RX1_IRQHandler(void); 111 | void WEAK CAN1_SCE_IRQHandler(void); 112 | void WEAK EXTI9_5_IRQHandler(void); 113 | void WEAK TIM1_BRK_IRQHandler(void); 114 | void WEAK TIM1_UP_IRQHandler(void); 115 | void WEAK TIM1_TRG_COM_IRQHandler(void); 116 | void WEAK TIM1_CC_IRQHandler(void); 117 | void WEAK TIM2_IRQHandler(void); 118 | void WEAK TIM3_IRQHandler(void); 119 | void WEAK TIM4_IRQHandler(void); 120 | void WEAK I2C1_EV_IRQHandler(void); 121 | void WEAK I2C1_ER_IRQHandler(void); 122 | void WEAK I2C2_EV_IRQHandler(void); 123 | void WEAK I2C2_ER_IRQHandler(void); 124 | void WEAK SPI1_IRQHandler(void); 125 | void WEAK SPI2_IRQHandler(void); 126 | void WEAK USART1_IRQHandler(void); 127 | void WEAK USART2_IRQHandler(void); 128 | void WEAK USART3_IRQHandler(void); 129 | void WEAK EXTI15_10_IRQHandler(void); 130 | void WEAK RTCAlarm_IRQHandler(void); 131 | void WEAK USBWakeUp_IRQHandler(void); 132 | 133 | /* Private functions ---------------------------------------------------------*/ 134 | /****************************************************************************** 135 | */ 136 | 137 | __attribute__ ((section(".isr_vectorsflash"))) 138 | void (* const g_pfnVectors[])(void) = 139 | { 140 | (intfunc)((unsigned long)&_estack), /* The stack pointer after relocation */ 141 | Reset_Handler, /* Reset Handler */ 142 | NMI_Handler, /* NMI Handler */ 143 | HardFault_Handler, /* Hard Fault Handler */ 144 | MemManage_Handler, /* MPU Fault Handler */ 145 | BusFault_Handler, /* Bus Fault Handler */ 146 | UsageFault_Handler, /* Usage Fault Handler */ 147 | 0, /* Reserved */ 148 | 0, /* Reserved */ 149 | 0, /* Reserved */ 150 | 0, /* Reserved */ 151 | SVC_Handler, /* SVCall Handler */ 152 | DebugMon_Handler, /* Debug Monitor Handler */ 153 | 0, /* Reserved */ 154 | PendSV_Handler, /* PendSV Handler */ 155 | SysTick_Handler, /* SysTick Handler */ 156 | 157 | /* External Interrupts */ 158 | WWDG_IRQHandler, /* Window Watchdog */ 159 | PVD_IRQHandler, /* PVD through EXTI Line detect */ 160 | TAMPER_IRQHandler, /* Tamper */ 161 | RTC_IRQHandler, /* RTC */ 162 | FLASH_IRQHandler, /* Flash */ 163 | RCC_IRQHandler, /* RCC */ 164 | EXTI0_IRQHandler, /* EXTI Line 0 */ 165 | EXTI1_IRQHandler, /* EXTI Line 1 */ 166 | EXTI2_IRQHandler, /* EXTI Line 2 */ 167 | EXTI3_IRQHandler, /* EXTI Line 3 */ 168 | EXTI4_IRQHandler, /* EXTI Line 4 */ 169 | DMA1_Channel1_IRQHandler, /* DMA1 Channel 1 */ 170 | DMA1_Channel2_IRQHandler, /* DMA1 Channel 2 */ 171 | DMA1_Channel3_IRQHandler, /* DMA1 Channel 3 */ 172 | DMA1_Channel4_IRQHandler, /* DMA1 Channel 4 */ 173 | DMA1_Channel5_IRQHandler, /* DMA1 Channel 5 */ 174 | DMA1_Channel6_IRQHandler, /* DMA1 Channel 6 */ 175 | DMA1_Channel7_IRQHandler, /* DMA1 Channel 7 */ 176 | ADC1_2_IRQHandler, /* ADC1 & ADC2 */ 177 | USB_HP_CAN1_TX_IRQHandler, /* USB High Priority or CAN1 TX */ 178 | USB_LP_CAN1_RX0_IRQHandler, /* USB Low Priority or CAN1 RX0 */ 179 | CAN1_RX1_IRQHandler, /* CAN1 RX1 */ 180 | CAN1_SCE_IRQHandler, /* CAN1 SCE */ 181 | EXTI9_5_IRQHandler, /* EXTI Line 9..5 */ 182 | TIM1_BRK_IRQHandler, /* TIM1 Break */ 183 | TIM1_UP_IRQHandler, /* TIM1 Update */ 184 | TIM1_TRG_COM_IRQHandler, /* TIM1 Trigger and Commutation */ 185 | TIM1_CC_IRQHandler, /* TIM1 Capture Compare */ 186 | TIM2_IRQHandler, /* TIM2 */ 187 | TIM3_IRQHandler, /* TIM3 */ 188 | TIM4_IRQHandler, /* TIM4 */ 189 | I2C1_EV_IRQHandler, /* I2C1 Event */ 190 | I2C1_ER_IRQHandler, /* I2C1 Error */ 191 | I2C2_EV_IRQHandler, /* I2C2 Event */ 192 | I2C2_ER_IRQHandler, /* I2C2 Error */ 193 | SPI1_IRQHandler, /* SPI1 */ 194 | SPI2_IRQHandler, /* SPI2 */ 195 | USART1_IRQHandler, /* USART1 */ 196 | USART2_IRQHandler, /* USART2 */ 197 | USART3_IRQHandler, /* USART3 */ 198 | EXTI15_10_IRQHandler, /* EXTI Line 15..10 */ 199 | RTCAlarm_IRQHandler, /* RTC Alarm through EXTI Line */ 200 | USBWakeUp_IRQHandler, /* USB Wakeup from suspend */ 201 | 0,0,0,0,0,0,0, 202 | (intfunc)0xF108F85F /* @0x108. This is for boot in RAM mode for 203 | STM32F10x Medium Density devices. */ 204 | }; 205 | 206 | /** 207 | * @brief This is the code that gets called when the processor first 208 | * starts execution following a reset event. Only the absolutely 209 | * necessary set is performed, after which the application 210 | * supplied main() routine is called. 211 | * @param None 212 | * @retval : None 213 | */ 214 | 215 | void Reset_Handler(void) 216 | { 217 | 218 | /* Initialize data and bss */ 219 | __Init_Data_and_BSS(); 220 | 221 | /* Call CTORS of static objects, not needed for "pure C": */ 222 | /* __libc_init_array(); */ 223 | /* if ( __libc_init_array ) 224 | __libc_init_array() 225 | } */ 226 | 227 | /* Setup the microcontroller system. Initialize the Embedded Flash Interface, 228 | initialize the PLL and update the SystemFrequency variable. */ 229 | SystemInit(); 230 | 231 | /* Call the application's entry point.*/ 232 | main(); 233 | 234 | while(1) { ; } 235 | } 236 | 237 | /** 238 | * @brief initializes data and bss sections 239 | * @param None 240 | * @retval : None 241 | */ 242 | 243 | void __Init_Data_and_BSS(void) 244 | { 245 | unsigned long *pulSrc, *pulDest; 246 | 247 | /* Copy the data segment initializers from flash to SRAM */ 248 | pulSrc = &_sidata; 249 | pulDest = &_sdata; 250 | if ( pulSrc != pulDest ) 251 | { 252 | for(; pulDest < &_edata; ) 253 | { 254 | *(pulDest++) = *(pulSrc++); 255 | } 256 | } 257 | /* Zero fill the bss segment. */ 258 | for(pulDest = &_sbss; pulDest < &_ebss; ) 259 | { 260 | *(pulDest++) = 0; 261 | } 262 | } 263 | 264 | /******************************************************************************* 265 | * 266 | * Provide weak aliases for each Exception handler to the Default_Handler. 267 | * As they are weak aliases, any function with the same name will override 268 | * this definition. 269 | * 270 | *******************************************************************************/ 271 | #pragma weak HardFault_Handler = Default_Handler 272 | #pragma weak NMI_Handler = Default_Handler 273 | #pragma weak MemManage_Handler = Default_Handler 274 | #pragma weak BusFault_Handler = Default_Handler 275 | #pragma weak UsageFault_Handler = Default_Handler 276 | #pragma weak SVC_Handler = Default_Handler 277 | #pragma weak DebugMon_Handler = Default_Handler 278 | #pragma weak PendSV_Handler = Default_Handler 279 | #pragma weak SysTick_Handler = Default_Handler 280 | #pragma weak WWDG_IRQHandler = Default_Handler 281 | #pragma weak PVD_IRQHandler = Default_Handler 282 | #pragma weak TAMPER_IRQHandler = Default_Handler 283 | #pragma weak RTC_IRQHandler = Default_Handler 284 | #pragma weak FLASH_IRQHandler = Default_Handler 285 | #pragma weak RCC_IRQHandler = Default_Handler 286 | #pragma weak EXTI0_IRQHandler = Default_Handler 287 | #pragma weak EXTI1_IRQHandler = Default_Handler 288 | #pragma weak EXTI2_IRQHandler = Default_Handler 289 | #pragma weak EXTI3_IRQHandler = Default_Handler 290 | #pragma weak EXTI4_IRQHandler = Default_Handler 291 | #pragma weak DMA1_Channel1_IRQHandler = Default_Handler 292 | #pragma weak DMA1_Channel2_IRQHandler = Default_Handler 293 | #pragma weak DMA1_Channel3_IRQHandler = Default_Handler 294 | #pragma weak DMA1_Channel4_IRQHandler = Default_Handler 295 | #pragma weak DMA1_Channel5_IRQHandler = Default_Handler 296 | #pragma weak DMA1_Channel6_IRQHandler = Default_Handler 297 | #pragma weak DMA1_Channel7_IRQHandler = Default_Handler 298 | #pragma weak ADC1_2_IRQHandler = Default_Handler 299 | #pragma weak USB_HP_CAN1_TX_IRQHandler = Default_Handler 300 | #pragma weak USB_LP_CAN1_RX0_IRQHandler = Default_Handler 301 | #pragma weak CAN1_RX1_IRQHandler = Default_Handler 302 | #pragma weak CAN1_SCE_IRQHandler = Default_Handler 303 | #pragma weak EXTI9_5_IRQHandler = Default_Handler 304 | #pragma weak TIM1_BRK_IRQHandler = Default_Handler 305 | #pragma weak TIM1_UP_IRQHandler = Default_Handler 306 | #pragma weak TIM1_TRG_COM_IRQHandler = Default_Handler 307 | #pragma weak TIM1_CC_IRQHandler = Default_Handler 308 | #pragma weak TIM2_IRQHandler = Default_Handler 309 | #pragma weak TIM3_IRQHandler = Default_Handler 310 | #pragma weak TIM4_IRQHandler = Default_Handler 311 | #pragma weak I2C1_EV_IRQHandler = Default_Handler 312 | #pragma weak I2C1_ER_IRQHandler = Default_Handler 313 | #pragma weak I2C2_EV_IRQHandler = Default_Handler 314 | #pragma weak I2C2_ER_IRQHandler = Default_Handler 315 | #pragma weak SPI1_IRQHandler = Default_Handler 316 | #pragma weak SPI2_IRQHandler = Default_Handler 317 | #pragma weak USART1_IRQHandler = Default_Handler 318 | #pragma weak USART2_IRQHandler = Default_Handler 319 | #pragma weak USART3_IRQHandler = Default_Handler 320 | #pragma weak EXTI15_10_IRQHandler = Default_Handler 321 | #pragma weak RTCAlarm_IRQHandler = Default_Handler 322 | #pragma weak USBWakeUp_IRQHandler = Default_Handler 323 | 324 | /** 325 | * @brief This is the code that gets called when the processor receives an 326 | * unexpected interrupt. This simply enters an infinite loop, preserving 327 | * the system state for examination by a debugger. 328 | * 329 | * @param None 330 | * @retval : None 331 | */ 332 | 333 | void Default_Handler(void) 334 | { 335 | /* Go into an infinite loop. */ 336 | while (1) 337 | { 338 | } 339 | } 340 | 341 | /******************* (C) COPYRIGHT 2009 STMicroelectronics *****END OF FILE****/ 342 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/adc.hpp: -------------------------------------------------------------------------------- 1 | /** @author Alexander Vassilev 2 | * @copyright BSD License 3 | */ 4 | 5 | /*TODO: 6 | - Implement adc resulution and word width selection 7 | - Interleaved mode 8 | */ 9 | 10 | #ifndef STM32PP_ADC_HPP 11 | #define STM32PP_ADC_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include //needed for the custom system clocks setup that allows max sample rate 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | //#define ADC_ENABLE_DEBUG 25 | #ifdef ADC_ENABLE_DEBUG 26 | #define ADC_LOG_DEBUG(fmt,...) tprintf("adc: " fmt "\n", ##__VA_ARGS__) 27 | #else 28 | #define ADC_LOG_DEBUG(fmt,...) 29 | #endif 30 | 31 | namespace nsadc 32 | { 33 | enum: uint16_t { 34 | kOptScanMode = 1, 35 | kOptContConv = 2, 36 | kOptNoVref = 4, 37 | kOptNoCalibrate = 8 38 | }; 39 | 40 | // To differentiate the type of value when passing to setChannels() 41 | struct ClockCnt 42 | { 43 | public: 44 | uint8_t value; 45 | ClockCnt(uint8_t aVal): value(aVal){} 46 | }; 47 | struct NanoTime 48 | { 49 | public: 50 | uint8_t value; 51 | NanoTime(uint8_t aVal): value(aVal){} 52 | }; 53 | 54 | 55 | template 56 | class AdcNoDma: public PeriphInfo 57 | { 58 | protected: 59 | typedef AdcNoDma Self; 60 | enum { kOptNotInitialized = 0x8000 }; 61 | uint16_t mInitOpts = kOptNotInitialized; 62 | uint32_t mClockFreq = 0; 63 | uint32_t currentClockFreq() const 64 | { 65 | uint32_t code = (RCC_CFGR & RCC_CFGR_ADCPRE) >> RCC_CFGR_ADCPRE_SHIFT; 66 | return rcc_apb2_frequency / codeToClockRatio(code); 67 | } 68 | uint8_t sampleNanosecToCode(uint32_t nanosec) 69 | { 70 | return sampleCyclesToCode(nanosec/(1000000000/mClockFreq)); 71 | } 72 | uint8_t sampleFreqToCode(uint32_t freq) 73 | { 74 | return sampleCyclesToCode(mClockFreq / freq); //must multiply cycles x10 75 | } 76 | uint8_t sampleTimeFreqToCode(uint32_t freq) { return sampleFreqToCode(freq); } 77 | uint8_t sampleTimeFreqToCode(ClockCnt clocks) { return sampleCyclesToCode(clocks.value()); } //Clock is x10, because it contains .5 units 78 | uint8_t sampleTimeFreqToCode(NanoTime nano) { return sampleNanosecToCode(nano.value()); } 79 | 80 | void enableVrefAsync() 81 | { 82 | static_assert(ADC == ADC1, "ADC device is not ADC1"); 83 | /* We want to read the temperature sensor, so we have to enable it. */ 84 | adc_enable_temperature_sensor(); 85 | //17100 nanoseconds sample time required for temperature sensor 86 | setChanSampleTime(ADC_CHANNEL_TEMP, 18000); 87 | setChanSampleTime(ADC_CHANNEL_VREF, 18000); 88 | ADC_LOG_DEBUG("Enabled reference and temperature channels"); 89 | } 90 | public: 91 | uint32_t clockFreq() const { return mClockFreq; } 92 | bool isInitialized() const { return (mInitOpts & kOptNotInitialized) == 0; } 93 | void init(uint8_t opts, uint32_t adcClockFreq=12000000) 94 | { 95 | ADC_LOG_DEBUG("Initializing with options %", opts); 96 | xassert(adcClockFreq > 0 && adcClockFreq <= 14000000); 97 | // calculate rounded ratio - adc is clocked by dividing apb2 clock 98 | uint32_t ratio = ((rcc_apb2_frequency << 1) + adcClockFreq) / (adcClockFreq << 1); 99 | if (ratio & 1) //must be an even number 100 | { 101 | ratio++; 102 | } 103 | int8_t divCode = clockRatioToCode(ratio); 104 | if (divCode < 0) //could not find exact match 105 | { 106 | divCode = -divCode; 107 | ratio = codeToClockRatio(divCode); 108 | } 109 | rcc_periph_clock_enable(Self::kClockId); 110 | ADC_LOG_DEBUG("Enabled clock"); 111 | 112 | /* Make sure the ADC doesn't run during config. */ 113 | adc_power_off(ADC); 114 | rcc_periph_reset_pulse(Self::kResetBit); 115 | rcc_set_adcpre(divCode); 116 | mClockFreq = currentClockFreq(); 117 | 118 | adc_set_right_aligned(ADC); 119 | adc_set_dual_mode(ADC_CR1_DUALMOD_IND); 120 | 121 | if (opts & kOptContConv) 122 | { 123 | adc_set_continuous_conversion_mode(ADC); 124 | ADC_LOG_DEBUG("Set continuous conversion mode"); 125 | } 126 | else 127 | { 128 | adc_set_single_conversion_mode(ADC); 129 | ADC_LOG_DEBUG("Set single conversion mode"); 130 | } 131 | if (opts & kOptScanMode) 132 | { 133 | adc_enable_scan_mode(ADC); 134 | ADC_LOG_DEBUG("Set scan mode"); 135 | 136 | } 137 | else 138 | { 139 | adc_disable_scan_mode(ADC); 140 | ADC_LOG_DEBUG("Disabled scan mode"); 141 | } 142 | 143 | mInitOpts = opts & ~kOptNotInitialized; 144 | if ((opts & kOptNoVref) == 0) 145 | { 146 | enableVrefAsync(); 147 | } 148 | usDelay((opts & kOptNoVref) ? 3 : 10); 149 | ADC_LOG_DEBUG("Init complete: requested clock: %Hz, actual clock: = %Hz", adcClockFreq, mClockFreq); 150 | } 151 | void enableExtTrigRegular(uint32_t trig) 152 | { 153 | adc_enable_external_trigger_regular(ADC, trig); 154 | } 155 | template 156 | void setChannels(uint8_t* chans, uint8_t count, T timeFreq) 157 | { 158 | adc_set_regular_sequence(ADC, count, chans); 159 | uint8_t code = sampleTimeFreqToCode(timeFreq); 160 | for (uint8_t i = 0; i < count; i++) 161 | { 162 | adc_set_sample_time(ADC, chans[i], code); 163 | } 164 | } 165 | template 166 | void setChannels(uint8_t* chans, uint8_t count, T* timeFreqs) 167 | { 168 | adc_set_regular_sequence(ADC, count, chans); 169 | for (uint8_t i = 0; i < count; i++) 170 | { 171 | adc_set_sample_time(ADC, chans[i], sampleTimeFreqToCode(timeFreqs[i])); 172 | } 173 | } 174 | /** 175 | * Sets the sampling time for a channel. If the time is specified as an 176 | * integer, it is treated as nanoseconds. If it is a Clocks value, then 177 | * the sample time is set to this number of ADC clocks. The same is valid 178 | * for all versions of the setChannel() method 179 | */ 180 | template 181 | uint8_t setChanSampleTime(uint8_t chan, T timeFreq) 182 | { 183 | uint8_t code = sampleTimeFreqToCode(timeFreq); 184 | adc_set_sample_time(ADC, chan, code); 185 | return code; 186 | } 187 | float sampleTimeCodeToFreq(uint8_t code) 188 | { 189 | auto cycles = codeToSampleCycles(code); 190 | return (float)mClockFreq / (float)cycles; 191 | } 192 | uint32_t sampleTimeCodeToNs(uint8_t code) 193 | { 194 | return (1000000000 / mClockFreq) * codeToSampleCycles(code); 195 | } 196 | bool isRunning() const { return (ADC_CR2(ADC) & ADC_CR2_ADON) != 0; } 197 | void powerOn(uint32_t trig=ADC_CR2_EXTSEL_SWSTART) 198 | { 199 | xassert(!isRunning()); 200 | adc_enable_external_trigger_regular(ADC, trig); 201 | ADC_LOG_DEBUG("Enabled external trigger %", fmtHex(trig)); 202 | adc_power_on(ADC); 203 | ADC_LOG_DEBUG("Powered on"); 204 | //at least 2 clock cycles after power on, before calibration 205 | uint32_t dly = 4000000000 / mClockFreq; 206 | usDelay(dly); 207 | adc_reset_calibration(ADC); 208 | adc_calibrate(ADC); 209 | usDelay(dly); 210 | ADC_LOG_DEBUG("Calibrated"); 211 | } 212 | void start(uint32_t trig=ADC_CR2_EXTSEL_SWSTART) 213 | { 214 | if (!isRunning()) 215 | { 216 | powerOn(); 217 | } 218 | if (trig == ADC_CR2_EXTSEL_SWSTART) 219 | { 220 | adc_start_conversion_regular(ADC); //sets ADC_CR2_SWSTART 221 | ADC_LOG_DEBUG("Started conversion by software"); 222 | } 223 | } 224 | void powerOff() 225 | { 226 | adc_power_off(ADC); 227 | } 228 | void enableVref() 229 | { 230 | enableVrefAsync(); 231 | usDelay(10); 232 | } 233 | void disableVref() 234 | { 235 | adc_disable_temperature_sensor(); 236 | ADC_LOG_DEBUG("Disabled reference and temperature channels"); 237 | } 238 | protected: 239 | void dmaStartPeripheralRx(uint32_t trig=ADC_CR2_EXTSEL_SWSTART) 240 | { 241 | adc_enable_dma(ADC); 242 | ADC_LOG_DEBUG("Enabled DMA"); 243 | start(trig); 244 | } 245 | void dmaStopPeripheralRx() 246 | { 247 | powerOff(); 248 | adc_disable_dma(ADC); 249 | } 250 | public: 251 | int8_t clockRatioToCode(uint32_t ratio) 252 | { 253 | switch (ratio) 254 | { 255 | case 2: return 0; 256 | case 4: return 1; 257 | case 6: return 2; 258 | case 8: return 3; 259 | default: return -3; 260 | } 261 | } 262 | static uint8_t codeToClockRatio(uint8_t code) 263 | { 264 | switch (code) 265 | { 266 | case 0: return 2; 267 | case 1: return 4; 268 | case 2: return 6; 269 | case 3: return 8; 270 | default: xassert(false); return 0; //silence no return warning 271 | } 272 | } 273 | static uint8_t sampleCyclesToCode(int16_t cycles) 274 | { 275 | // Total conversion time = sample_time + 12.5 cycles 276 | // sample_time is what we set in the register, and cycles is the 277 | // total conversion time 278 | cycles = (cycles * 10) - 125; 279 | if (cycles <= 15) 280 | return ADC_SMPR_SMP_1DOT5CYC; 281 | else if (cycles <= 75) 282 | return ADC_SMPR_SMP_7DOT5CYC; 283 | else if (cycles <= 135) 284 | return ADC_SMPR_SMP_13DOT5CYC; 285 | else if (cycles <= 285) 286 | return ADC_SMPR_SMP_28DOT5CYC; 287 | else if (cycles <= 415) 288 | return ADC_SMPR_SMP_41DOT5CYC; 289 | else if (cycles <= 555) 290 | return ADC_SMPR_SMP_55DOT5CYC; 291 | else if (cycles <= 715) 292 | return ADC_SMPR_SMP_71DOT5CYC; 293 | else 294 | return ADC_SMPR_SMP_239DOT5CYC; 295 | } 296 | static uint16_t codeToSampleCycles(uint8_t code) 297 | { 298 | // total sample cycles are sample_time + 12.5 299 | switch (code) 300 | { 301 | case ADC_SMPR_SMP_1DOT5CYC: return 14; 302 | case ADC_SMPR_SMP_7DOT5CYC: return 20; 303 | case ADC_SMPR_SMP_13DOT5CYC: return 26; 304 | case ADC_SMPR_SMP_28DOT5CYC: return 41; 305 | case ADC_SMPR_SMP_41DOT5CYC: return 54; 306 | case ADC_SMPR_SMP_55DOT5CYC: return 68; 307 | case ADC_SMPR_SMP_71DOT5CYC: return 84; 308 | case ADC_SMPR_SMP_239DOT5CYC: return 252; 309 | default: __builtin_trap(); 310 | } 311 | } 312 | template 313 | uint8_t useSingleChannel(uint8_t chan, T timeFreq) 314 | { 315 | xassert(chan < 18); 316 | // assert(isInitialized() && 317 | // ((mInitOpts & (kOptContConv|kOptScanMode)) == 0)); 318 | uint8_t code = sampleTimeFreqToCode(timeFreq); 319 | adc_set_regular_sequence(ADC, 1, &chan); 320 | adc_set_sample_time(ADC, chan, code); 321 | ADC_LOG_DEBUG("useSingleChannel: chan %, sample freq: %Hz (%ns, code: %)", 322 | chan, sampleTimeCodeToFreq(code), sampleTimeCodeToNs(code), code); 323 | return code; 324 | } 325 | uint16_t convertSingle() 326 | { 327 | adc_start_conversion_direct(ADC); 328 | /* Wait for end of conversion. */ 329 | while (!(adc_eoc(ADC))); 330 | return adc_read_regular(ADC); 331 | } 332 | }; 333 | 334 | 335 | 336 | /** @brief Clock setup for maximum ADC sample rate of 1 MHz. 337 | * Based on libopencm3 clock setup function for CPU clock at 72 MHz 338 | */ 339 | void rcc_clock_setup_in_hse_8mhz_out_56mhz() 340 | { 341 | /* Enable internal high-speed oscillator. */ 342 | rcc_osc_on(RCC_HSI); 343 | rcc_wait_for_osc_ready(RCC_HSI); 344 | 345 | /* Select HSI as SYSCLK source. */ 346 | rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSICLK); 347 | 348 | /* Enable external high-speed oscillator 8MHz. */ 349 | rcc_osc_on(RCC_HSE); 350 | rcc_wait_for_osc_ready(RCC_HSE); 351 | rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSECLK); 352 | 353 | /* 354 | * Set prescalers for AHB, ADC, ABP1, ABP2. 355 | * Do this before touching the PLL (TODO: why?). 356 | */ 357 | rcc_set_hpre(RCC_CFGR_HPRE_SYSCLK_NODIV); /* Set. 56MHz Max. 72MHz */ 358 | rcc_set_adcpre(RCC_CFGR_ADCPRE_PCLK2_DIV4); /* Set. 14MHz Max. 14MHz */ 359 | rcc_set_ppre1(RCC_CFGR_PPRE1_HCLK_DIV2); /* Set. 28MHz Max. 36MHz */ 360 | rcc_set_ppre2(RCC_CFGR_PPRE2_HCLK_NODIV); /* Set. 56MHz Max. 72MHz */ 361 | 362 | /* 363 | * Sysclk runs with 72MHz -> 2 waitstates. 364 | * 0WS from 0-24MHz 365 | * 1WS from 24-48MHz 366 | * 2WS from 48-72MHz 367 | */ 368 | 369 | flash_set_ws(FLASH_ACR_LATENCY_2WS); 370 | 371 | /* 372 | * Set the PLL multiplication factor to 9. 373 | * 8MHz (external) * 9 (multiplier) = 72MHz 374 | */ 375 | rcc_set_pll_multiplication_factor(RCC_CFGR_PLLMUL_PLL_CLK_MUL7); 376 | 377 | /* Select HSE as PLL source. */ 378 | rcc_set_pll_source(RCC_CFGR_PLLSRC_HSE_CLK); 379 | 380 | /* 381 | * External frequency undivided before entering PLL 382 | * (only valid/needed for HSE). 383 | */ 384 | rcc_set_pllxtpre(RCC_CFGR_PLLXTPRE_HSE_CLK); 385 | 386 | /* Enable PLL oscillator and wait for it to stabilize. */ 387 | rcc_osc_on(RCC_PLL); 388 | rcc_wait_for_osc_ready(RCC_PLL); 389 | 390 | /* Select PLL as SYSCLK source. */ 391 | rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_PLLCLK); 392 | 393 | /* Set the peripheral clock frequencies used */ 394 | rcc_ahb_frequency = 56000000; 395 | rcc_apb1_frequency = 28000000; 396 | rcc_apb2_frequency = 56000000; 397 | } 398 | 399 | template 400 | class Adc: public dma::Rx, dma::kAllMaxPrio> 401 | { 402 | }; 403 | } 404 | 405 | STM32PP_PERIPH_INFO(ADC1) 406 | static constexpr rcc_periph_clken kClockId = RCC_ADC1; 407 | enum: uint32_t { kDmaRxId = DMA1 }; 408 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&ADC1_DR); } 409 | enum: uint8_t { kDmaRxChannel = DMA_CHANNEL1, kDmaWordSize = 2 }; 410 | static constexpr rcc_periph_rst kResetBit = RST_ADC1; 411 | }; 412 | 413 | STM32PP_PERIPH_INFO(ADC2) 414 | static constexpr rcc_periph_clken kClockId = RCC_ADC2; 415 | static constexpr rcc_periph_rst kResetBit = RST_ADC2; 416 | // ADC2 has no own DMA support. 417 | }; 418 | 419 | STM32PP_PERIPH_INFO(ADC3) 420 | static constexpr rcc_periph_clken kClockId = RCC_ADC3; 421 | enum: uint32_t { kDmaRxId = DMA2 }; 422 | static const uint32_t dmaRxDataRegister() { return (uint32_t)(&ADC3_DR); } 423 | enum: uint8_t { kDmaRxChannel = DMA_CHANNEL5, kDmaWordSize = 2 }; 424 | static constexpr rcc_periph_rst kResetBit = RST_ADC3; 425 | }; 426 | 427 | #endif 428 | -------------------------------------------------------------------------------- /stm32++/include/stm32++/tostring.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Vassilev 3 | * @copyright BSD License 4 | */ 5 | 6 | #ifndef _TOSTRING_H 7 | #define _TOSTRING_H 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include //for padding calculation we need log10 14 | 15 | static_assert(sizeof(size_t) == sizeof(void*), "size_t is not same size as void*"); 16 | static_assert(sizeof(size_t) == sizeof(ptrdiff_t), "size_t is not same size as ptrdiff_t"); 17 | static_assert(std::is_unsigned::value, "size_t is not unsigned"); 18 | 19 | /** Various flags that specify how a value is converted to string. 20 | * Lower 8 bits are reserved for the numeric system base (for integer numbers) 21 | * or precision (for floating point numbers) 22 | */ 23 | enum: uint16_t { 24 | kFlagsBaseMask = 0xff, 25 | kFlagsPrecMask = 0xff, 26 | kLowerCase = 0x0, kUpperCase = 0x1000, 27 | kDontNullTerminate = 0x0200, kNumPrefix = 0x0400, 28 | kFlagsMaskGlobal = kDontNullTerminate 29 | }; 30 | 31 | typedef uint16_t Flags; 32 | constexpr Flags globalFlags(Flags flags) { return flags & kFlagsMaskGlobal; } 33 | constexpr Flags localFlags(Flags flags) { return flags &~kFlagsMaskGlobal; } 34 | 35 | constexpr uint8_t baseFromFlags(Flags flags) 36 | { 37 | uint8_t base = flags & 0xff; 38 | return base ? base : 10; 39 | } 40 | constexpr uint8_t precFromFlags(Flags flags) 41 | { 42 | uint8_t prec = flags & 0xff; 43 | return prec ? prec : 6; 44 | } 45 | 46 | template 47 | struct DigitConverter; 48 | 49 | template 50 | struct DigitConverter<10, flags> 51 | { 52 | enum { digitsPerByte = 3, prefixLen = 0 }; 53 | static char* putPrefix(char* buf) { return buf; } 54 | static char toDigit(uint8_t digit) { return '0'+digit; } 55 | }; 56 | 57 | template 58 | struct DigitConverter<16, flags> 59 | { 60 | enum { digitsPerByte = 2, prefixLen = 2 }; 61 | static char* putPrefix(char* buf) { buf[0] = '0', buf[1] = 'x'; return buf+2; } 62 | static char toDigit(uint8_t digit) 63 | { 64 | if (digit < 10) 65 | return '0'+digit; 66 | else 67 | return (flags & kUpperCase ? 'A': 'a')+(digit-10); 68 | } 69 | }; 70 | 71 | template 72 | struct DigitConverter<2, flags> 73 | { 74 | enum { digitsPerByte = 8, prefixLen = 2 }; 75 | static char* putPrefix(char *buf) { buf[0] = '0'; buf[1] = 'b'; return buf+2; } 76 | static char toDigit(uint8_t digit) { return '0'+digit; } 77 | }; 78 | 79 | template 80 | struct DigitConverter<8, flags> 81 | { 82 | enum { digitsPerByte = 3, prefixLen = 3 }; 83 | static char* putPrefix(char *buf) 84 | { buf[0] = 'O'; buf[1] = 'C'; buf[2] = 'T'; return buf+3; } 85 | static char toDigit(uint8_t digit) { return '0'+digit; } 86 | }; 87 | 88 | template 89 | typename std::enable_if::value 90 | && std::is_integral::value 91 | && !std::is_same::value, char*>::type 92 | toString(char* buf, size_t bufsize, Val val, uint8_t minDigits=0, uint16_t minLen=0) 93 | { 94 | assert(buf); 95 | assert(bufsize); 96 | 97 | if ((flags & kDontNullTerminate) == 0) { 98 | bufsize--; 99 | } 100 | if (bufsize < minLen) { 101 | *buf = 0; 102 | return nullptr; 103 | } 104 | enum: uint8_t { base = baseFromFlags(flags) }; 105 | DigitConverter digitConv; 106 | char stagingBuf[digitConv.digitsPerByte * sizeof(Val)]; 107 | char* writePtr = stagingBuf; 108 | do 109 | { 110 | Val digit = val % base; 111 | *(writePtr++) = digitConv.toDigit(digit); 112 | val /= base; 113 | } while(val); 114 | 115 | size_t numDigits = writePtr - stagingBuf; 116 | size_t padLen; 117 | if (minDigits && (numDigits < minDigits)) 118 | { 119 | padLen = minDigits - numDigits; 120 | } 121 | else 122 | { 123 | padLen = 0; 124 | } 125 | size_t totalLen; 126 | if ((flags & kNumPrefix) && (digitConv.prefixLen != 0)) 127 | { 128 | totalLen = digitConv.prefixLen+padLen+numDigits; 129 | if (bufsize < totalLen) 130 | { 131 | *buf = 0; 132 | return nullptr; 133 | } 134 | buf = digitConv.putPrefix(buf); 135 | } 136 | else 137 | { 138 | totalLen = padLen + numDigits; 139 | if (bufsize < totalLen) 140 | { 141 | *buf = 0; 142 | return nullptr; 143 | } 144 | } 145 | 146 | while (totalLen < minLen) 147 | { 148 | *(buf++) = ' '; 149 | totalLen++; 150 | } 151 | 152 | for(;padLen; padLen--) 153 | { 154 | *(buf++) = '0'; 155 | } 156 | // numDigits is at least one 157 | do 158 | { 159 | *(buf++) = *(--writePtr); 160 | numDigits--; 161 | 162 | } while(numDigits); 163 | 164 | 165 | if ((flags & kDontNullTerminate) == 0) { 166 | *buf = 0; 167 | } 168 | return buf; 169 | } 170 | 171 | template 172 | typename std::enable_if::value 173 | && std::is_signed::value 174 | && !std::is_same::value, char*>::type 175 | toString(char* buf, size_t bufsize, Val val, uint8_t minDigits=0, uint8_t minLen=0) 176 | { 177 | typedef typename std::make_unsigned::type UVal; 178 | if (val < 0) 179 | { 180 | if (bufsize < 2) 181 | { 182 | if (bufsize) { 183 | *buf = 0; 184 | } 185 | return nullptr; 186 | } 187 | *buf = '-'; 188 | return toString(buf+1, bufsize-1, -val, minDigits, minLen); 189 | } 190 | else 191 | { 192 | return toString(buf, bufsize, val, minDigits, minLen); 193 | } 194 | } 195 | 196 | template 197 | struct is_char_ptr 198 | { 199 | enum: bool {value = false}; 200 | }; 201 | 202 | template 203 | struct is_char_ptr::value 205 | && std::is_same< 206 | typename std::remove_const::type>::type,char>::value 207 | ,void>::type> 208 | { 209 | enum: bool { value = true }; 210 | }; 211 | 212 | template 213 | struct IntFmt 214 | { 215 | constexpr static Flags flags = localFlags(aFlags); 216 | T value; 217 | uint8_t minDigits; 218 | uint8_t minLen; 219 | explicit IntFmt(T aVal, uint8_t aMinDigits=0, uint8_t aMinLen=0) 220 | : value(aVal), minDigits(aMinDigits), minLen(aMinLen) {} 221 | }; 222 | 223 | template 224 | struct NumLenForBase 225 | { 226 | enum: uint8_t { value = sizeof(T)*(uint8_t)(log10f(256)/log10f(base)+0.9) }; 227 | }; 228 | /** 229 | * @param flags 230 | * - The lower 8 bits are the numeric base for the conversion, i.e. 231 | * 10 for decimal format, 16 for hex, 2 for binary. Arbitrary values are supported 232 | * - \c kUpperCase = 0x1000 - use upper case where applicable 233 | * - \c kDontNullTerminate = 0x0200 - don't null-terminate the resulting string 234 | * - \c kNumPrefix = 0x0400 - include the numberic system prefix, i.e. 0x for hex 235 | * @param minDigits The minimum number of digits to display. If the actual digits 236 | * are less, zeroes are prepended to the number 237 | * @param minLen The minimum number of chars in the resulting number string. 238 | * If the actual chars are fewer after applying \c minDigits, spaces are appended to the string. 239 | */ 240 | template 241 | IntFmt fmtInt(T aVal, uint8_t minDigits=0, uint8_t minLen=0) 242 | { return IntFmt(aVal, minDigits, minLen); } 243 | 244 | template 245 | auto fmtHex(T aVal, uint8_t minDigits=0, uint8_t minLen=0) 246 | { return IntFmt(aVal, minDigits, minLen); } 247 | 248 | template 249 | auto fmtHex8(T aVal, uint8_t minDigits=2) 250 | { return IntFmt(aVal, minDigits); } 251 | 252 | template 253 | auto fmtHex16(T aVal, uint8_t minDigits=4) 254 | { return IntFmt(aVal, minDigits); } 255 | 256 | template 257 | auto fmtHex32(T aVal, uint8_t minDigits=8) 258 | { return IntFmt(aVal, minDigits); } 259 | 260 | template 261 | auto fmtPtr(Ptr ptr) { return fmtHex(ptr, sizeof(void*) * 2); } 262 | 263 | template 264 | auto fmtBin(T aVal, uint8_t minDigits=0, uint8_t minLen=0) 265 | { return IntFmt(aVal, minDigits, minLen); } 266 | 267 | template 268 | auto fmtBin8(T aVal, uint8_t minDigits=8) 269 | { return IntFmt(aVal, minDigits); } 270 | 271 | template 272 | auto fmtBin16(T aVal, uint8_t minDigits=16) 273 | { return IntFmt(aVal, minDigits); } 274 | 275 | template 276 | auto fmtBin32(T aVal, uint8_t minDigits=32) 277 | { return IntFmt(aVal, minDigits); } 278 | 279 | template 280 | typename std::enable_if::value && !is_char_ptr

::value, char*>::type 281 | toString(char *buf, size_t bufsize, P ptr) 282 | { 283 | return toString(buf, bufsize, fmtPtr(ptr)); 284 | } 285 | 286 | template 287 | char* toString(char *buf, size_t bufsize, IntFmt num) 288 | { 289 | return toString(buf, bufsize, num.value, num.minDigits, num.minLen); 290 | } 291 | 292 | template 293 | typename std::enable_if<(flags & kDontNullTerminate) == 0, char*>::type 294 | toString(char* buf, size_t bufsize, const char* val) 295 | { 296 | if (!bufsize) 297 | return nullptr; 298 | auto bufend = buf+bufsize-1; //reserve space for the terminating null 299 | while(*val) 300 | { 301 | if(buf >= bufend) 302 | { 303 | assert(buf == bufend); 304 | *buf = 0; 305 | return nullptr; 306 | } 307 | *(buf++) = *(val++); 308 | } 309 | *buf = 0; 310 | return buf; 311 | } 312 | 313 | template 314 | typename std::enable_if<(flags & kDontNullTerminate), char*>::type 315 | toString(char* buf, size_t bufsize, const char* val) 316 | { 317 | auto bufend = buf+bufsize; 318 | while(*val) 319 | { 320 | if(buf >= bufend) 321 | return nullptr; 322 | *(buf++) = *(val++); 323 | } 324 | return buf; 325 | } 326 | 327 | template 328 | typename std::enable_if::value 329 | && (flags & kDontNullTerminate), char*>::type 330 | toString(char* buf, size_t bufsize, Val val) 331 | { 332 | if(!bufsize) 333 | return nullptr; 334 | *(buf++) = val; 335 | return buf; 336 | } 337 | 338 | template 339 | typename std::enable_if::value 340 | && (flags & kDontNullTerminate) == 0, char*>::type 341 | toString(char* buf, size_t bufsize, Val val) 342 | { 343 | if (bufsize >= 2) 344 | { 345 | *(buf++) = val; 346 | *buf = 0; 347 | return buf; 348 | } 349 | else if (bufsize == 1) 350 | { 351 | *buf = 0; 352 | return nullptr; 353 | } 354 | else 355 | { 356 | return nullptr; 357 | } 358 | } 359 | template 360 | struct Pow 361 | { enum: size_t { value = base * Pow::value }; }; 362 | 363 | template 364 | struct Pow 365 | { enum: size_t { value = base }; }; 366 | 367 | template 368 | typename std::enable_if::value, char*>::type 369 | toString(char* buf, size_t bufsize, Val val, uint8_t minDigits=0, uint8_t minLen=0) 370 | { 371 | enum: uint8_t { prec = precFromFlags(flags) }; 372 | if (!bufsize) { 373 | return nullptr; 374 | } 375 | char* bufRealEnd = buf+bufsize; 376 | if ((flags & kDontNullTerminate) == 0) 377 | bufsize--; 378 | 379 | char* bufend = buf+bufsize; 380 | if (val < 0) 381 | { 382 | if (bufsize < 4) //at least '-0.0' 383 | { 384 | *buf = 0; 385 | return nullptr; 386 | } 387 | *(buf++) = '-'; 388 | val = -val; 389 | } 390 | else 391 | { 392 | if (bufsize < 3) 393 | { 394 | *buf = 0; 395 | return nullptr; 396 | } 397 | } 398 | if (std::numeric_limits::has_infinity && (val == std::numeric_limits::infinity())) 399 | { 400 | *(buf++) = 'i'; 401 | *(buf++) = 'n'; 402 | *(buf++) = 'f'; 403 | if (!(flags & kDontNullTerminate)) 404 | { 405 | *buf = 0; 406 | } 407 | return buf; 408 | } 409 | 410 | 411 | size_t whole = (size_t)(val); 412 | 413 | // value to multiply the fractional part so that it becomes an int 414 | enum: uint32_t { mult = Pow<10, prec>::value }; 415 | size_t fractional = (val - whole) * mult + 0.5; 416 | if (fractional >= mult) //the part after the dot overflows to >= 1 due to rounding 417 | { 418 | //move the overflowed unit to the whole part and subtract it from 419 | //the decimal 420 | whole++; 421 | fractional -= mult; 422 | } 423 | if (minLen > prec) { 424 | minLen -= (prec + 1); 425 | } 426 | //we have some minimum space for null termination even if buffer is not enough 427 | auto originalBuf = buf; 428 | buf = toString(buf, bufRealEnd-buf, whole, minDigits, minLen); 429 | if (!buf) 430 | { 431 | assert(*originalBuf == 0); //assert null termination 432 | return nullptr; 433 | } 434 | assert(buf < bufRealEnd); 435 | if (bufend-buf < 2) //must have space at least for '.0' and optional null terminator 436 | { 437 | *originalBuf = 0; 438 | return nullptr; 439 | } 440 | *(buf++) = '.'; 441 | return toString(buf, bufRealEnd-buf, fractional, prec); 442 | } 443 | 444 | template 445 | struct FpFmt 446 | { 447 | constexpr static Flags flags = localFlags(aFlags); 448 | T value; 449 | uint8_t minDigits; 450 | uint8_t minLen; 451 | FpFmt(T aVal, uint8_t aMinDigits, uint8_t minLen) 452 | : value(aVal), minDigits(aMinDigits), minLen(minLen) {} 453 | }; 454 | 455 | /** 456 | * Specifies that a number must be formatted as floating point 457 | * @param aFlags - the formatting flags, where the low 8 bits specify the floating 458 | * point precision, i.e. the minimum number of digits after the decimal point. 459 | * If the actual digits are fewer, then zeroes are appended 460 | * @param minDigits - the minimum number of digits for the whole part of the number 461 | * (before the decimal point). If the actual digits are fewer, zeroes are prepended 462 | * to the whole part of the number. 463 | *@param minLen - the minimum length of the whole floating point string. If the actual 464 | * output is shorter, then spaces are prepended before the integer part 465 | */ 466 | template 467 | auto fmtFp(T val, uint8_t minDigits=0, uint8_t minLen=0) 468 | { 469 | return FpFmt(val, minDigits, minLen); 470 | } 471 | 472 | template 473 | char* toString(char *buf, size_t bufsize, FpFmt fp) 474 | { 475 | // Get local formatting flags from fp, and merge the global flags from the toString call 476 | return toString(buf, bufsize, fp.value, fp.minDigits, fp.minLen); 477 | } 478 | 479 | template 480 | struct RptChar 481 | { 482 | char mChar; 483 | uint16_t mCount; 484 | public: 485 | RptChar(char ch, uint16_t count): mChar(ch), mCount(count){} 486 | char ch() const { return mChar; } 487 | uint16_t count() const { return mCount; } 488 | }; 489 | 490 | template 491 | RptChar rptChar(char ch, uint16_t count) 492 | { 493 | return RptChar(ch, count); 494 | } 495 | 496 | template 497 | char* toString(char* buf, size_t bufsize, RptChar val) 498 | { 499 | if (!bufsize) 500 | return nullptr; 501 | if ((aFlags & kDontNullTerminate) == 0) 502 | bufsize--; 503 | if (val.count() > bufsize) 504 | return nullptr; 505 | char* end = buf + val.count(); 506 | char ch = val.ch(); 507 | while (buf < end) 508 | { 509 | *(buf++) = ch; 510 | } 511 | if ((aFlags & kDontNullTerminate) == 0) 512 | *buf = 0; 513 | return buf; 514 | } 515 | 516 | #endif 517 | --------------------------------------------------------------------------------