diff --git a/red/mod-io2/.gitignore b/red/mod-io2/.gitignore new file mode 100644 index 0000000..5762142 --- /dev/null +++ b/red/mod-io2/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/red/mod-io2/README.txt b/red/mod-io2/README.txt new file mode 100644 index 0000000..235ca95 --- /dev/null +++ b/red/mod-io2/README.txt @@ -0,0 +1,16 @@ +### Install - packages +apt-get install build-essential libnewlib-dev gcc-riscv64-unknown-elf libusb-1.0-0-dev libudev-dev gdb-multiarch + +### Install - Visual Studio Code +https://code.visualstudio.com/docs/setup/linux + +### Install - Platform IO +https://platformio.org/install/ide?install=vscode + +### Install - CH32V-Platform +https://github.com/Community-PIO-CH32V/ch32-pio-projects?tab=readme-ov-file#installing-the-ch32v-platform + +To build firmware.bin and firmware.elf select +> PlatformIO > PROJECT TASKS > Default > Advanced > Verbose build + +firmware.bin and firmware.elf are located in .pio/build/genericCH32V003F4P6/ diff --git a/red/mod-io2/include/README b/red/mod-io2/include/README new file mode 100644 index 0000000..45496b1 --- /dev/null +++ b/red/mod-io2/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/red/mod-io2/lib/README b/red/mod-io2/lib/README new file mode 100644 index 0000000..a10cade --- /dev/null +++ b/red/mod-io2/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/red/mod-io2/platformio.ini b/red/mod-io2/platformio.ini new file mode 100644 index 0000000..f9ca9f4 --- /dev/null +++ b/red/mod-io2/platformio.ini @@ -0,0 +1,16 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:genericCH32V003F4P6] +platform = ch32v +board = genericCH32V003F4P6 +framework = noneos-sdk +build_flags = -D SYSCLK_FREQ_48MHZ_HSI #-D LOD_DEBUG_ENABLE +monitor_speed = 115200 diff --git a/red/mod-io2/src/adc.c b/red/mod-io2/src/adc.c new file mode 100644 index 0000000..a9ec3ac --- /dev/null +++ b/red/mod-io2/src/adc.c @@ -0,0 +1,103 @@ +#include "adc.h" +#include "uptime.h" + +static uint8_t ADC_Ready = 0; + +ADC_Error_Type ADC_Channel_Init(GPIO_TypeDef *GPIO_port, uint16_t GPIO_pin, uint32_t timeoutMS) { + ADC_InitTypeDef ADC_InitStructure = {0}; + + RCC_ADCCLKConfig(RCC_PCLK2_Div2); + + ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; + ADC_InitStructure.ADC_ScanConvMode = DISABLE; + ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; + ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; + ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; + ADC_InitStructure.ADC_NbrOfChannel = 1; + ADC_Init(ADC1, &ADC_InitStructure); + + ADC_Calibration_Vol(ADC1, ADC_CALVOL_75PERCENT); + ADC_Cmd(ADC1, ENABLE); + + return ADC_Calibrate(timeoutMS); +} + +ADC_Error_Type ADC_Calibrate(uint32_t timeoutMS) { + ADC_Ready = 0; + + ADC_ResetCalibration(ADC1); + + uint32_t time = Uptime_Ms(); + while(ADC_GetResetCalibrationStatus(ADC1)) { + if ((uint32_t)(Uptime_Ms() - time) >= timeoutMS) { + // timeout + return ADC_ERROR; + } + } + + ADC_StartCalibration(ADC1); + + time = Uptime_Ms(); + while(ADC_GetCalibrationStatus(ADC1)){ + if ((uint32_t)(Uptime_Ms() - time) >= timeoutMS) { + // timeout + return ADC_ERROR; + } + } + + ADC_Ready = 1; + return ADC_SUCCESS; +} + +ADC_Error_Type Get_ADC_Val(uint8_t ADC_Channel, uint32_t timeoutMS, uint16_t *ADC_Value) { + ADC_Calibrate(timeoutMS); + if (!ADC_Ready) { + return ADC_ERROR; + } + + ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_241Cycles); + ADC_SoftwareStartConvCmd(ADC1, ENABLE); + + uint32_t time = Uptime_Ms(); + while(!ADC_GetFlagStatus( ADC1, ADC_FLAG_EOC )) { + if ((uint32_t)(Uptime_Ms() - time) >= timeoutMS) { + //timeout + ADC_Cmd(ADC1, DISABLE); + return ADC_ERROR; + } + } + + *ADC_Value = ADC_GetConversionValue(ADC1); + ADC_Cmd(ADC1, DISABLE); + + return ADC_SUCCESS; +} + +ADC_Error_Type Get_ADC_Average(uint8_t ADC_channel, uint32_t timeoutMS, uint8_t count, uint16_t *ADC_Value) { + if (!ADC_Ready) { + return ADC_ERROR; + } + + uint32_t tmp_val = 0; + uint16_t val; + + for(uint8_t t = 0; t < count; t++ ){ + if (Get_ADC_Val(ADC_channel, timeoutMS, &val) == ADC_ERROR) { + return ADC_ERROR; + } + tmp_val += val; + Wait_Ms(5); + } + + *ADC_Value = tmp_val / count; + + return ADC_SUCCESS; +} + +uint16_t ADC_Map(uint16_t value, uint16_t minValue, uint16_t maxValue, float minVoltage, float maxVoltage) { + float tmp; + tmp = (maxVoltage - minVoltage) / (maxValue - minValue); + tmp = tmp * (value - minValue) + minVoltage; + tmp = tmp * 1000; + return (uint16_t) tmp; +} diff --git a/red/mod-io2/src/adc.h b/red/mod-io2/src/adc.h new file mode 100644 index 0000000..a77c60e --- /dev/null +++ b/red/mod-io2/src/adc.h @@ -0,0 +1,28 @@ +#ifndef __ADC_H +#define __ADC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef enum { + ADC_SUCCESS = 0, + ADC_ERROR = 1 +} ADC_Error_Type; + +#define ADC_TIMEOUT_MS 100 + +ADC_Error_Type ADC_Channel_Init(GPIO_TypeDef *port, uint16_t pin, uint32_t timeoutMS); +ADC_Error_Type ADC_Calibrate(uint32_t timeoutMS); +ADC_Error_Type Get_ADC_Val(uint8_t ADC_Channel, uint32_t timeoutMS, uint16_t *ADC_Value); +ADC_Error_Type Get_ADC_Average(uint8_t ADC_channel, uint32_t timeoutMS, uint8_t count, uint16_t *ADC_Value); +uint16_t ADC_Map(uint16_t value, uint16_t minValue, uint16_t maxValue, float minVoltage, float maxVoltage); + +#ifdef __cplusplus +} +#endif + + +#endif /* __ADC_H */ diff --git a/red/mod-io2/src/flash.c b/red/mod-io2/src/flash.c new file mode 100644 index 0000000..7236e1d --- /dev/null +++ b/red/mod-io2/src/flash.c @@ -0,0 +1,105 @@ +#include "flash.h" + +#include +#include + +// Disable LOG_DEBUG locally +#undef LOD_DEBUG_ENABLE +#include "log_debug.h" + +/* FLASH Keys */ +#define RDP_Key ((uint16_t)0x00A5) +#define FLASH_KEY1 ((uint32_t)0x45670123) +#define FLASH_KEY2 ((uint32_t)0xCDEF89AB) + +/* Delay definition */ +#define EraseTimeout ((uint32_t)0x000B0000) +#define ProgramTimeout ((uint32_t)0x00002000) + +/* Flash Control Register bits */ +#define CR_PG_Set ((uint32_t)0x00000001) +#define CR_PG_Reset ((uint32_t)0xFFFFFFFE) +#define CR_PER_Set ((uint32_t)0x00000002) +#define CR_PER_Reset ((uint32_t)0xFFFFFFFD) +#define CR_MER_Set ((uint32_t)0x00000004) +#define CR_MER_Reset ((uint32_t)0xFFFFFFFB) +#define CR_OPTPG_Set ((uint32_t)0x00000010) +#define CR_OPTPG_Reset ((uint32_t)0xFFFFFFEF) +#define CR_OPTER_Set ((uint32_t)0x00000020) +#define CR_OPTER_Reset ((uint32_t)0xFFFFFFDF) +#define CR_STRT_Set ((uint32_t)0x00000040) +#define CR_LOCK_Set ((uint32_t)0x00000080) +#define CR_PAGE_PG ((uint32_t)0x00010000) +#define CR_PAGE_ER ((uint32_t)0x00020000) +#define CR_BUF_LOAD ((uint32_t)0x00040000) +#define CR_BUF_RST ((uint32_t)0x00080000) + +static volatile uint16_t *OPTION_BYTES = (uint16_t *)OB_BASE; + +/** + * Default Option bytes values + * + * RDPR 0xA5 + * USER 0x17 + * Data0 0x00 + * Data1 0x00 + * WRPR0 0xFF + * WRPR1 0xFF + */ + +void FLASH_OB_DEBUG() { + LOG_DEBUG( + "RDPR 0x%04X\r\n" + "USER 0x%04X\r\n" + "Data0 0x%04X\r\n" + "Data1 0x%04X\r\n" + "WRPR0 0x%04X\r\n" + "WRPR1 0x%04X\r\n" + "\r\n", + OB->RDPR, + OB->USER, + OB->Data0, + OB->Data1, + OB->WRPR0, + OB->WRPR1 + ); +} + +uint8_t FLASH_OptionByteGet(FLASH_OptionByte_Type byte) { + uint16_t data = OPTION_BYTES[(uint8_t)byte]; + return (uint8_t)(data & 0xFF); +} + +FLASH_Status FLASH_OptionByteSet(FLASH_OptionByte_Type byte, uint8_t data) { + volatile uint16_t buff[OB_COUNT]; + // Keeo old values + for (uint8_t i = 0; i < OB_COUNT; i++) { + buff[i] = OPTION_BYTES[i]; + } + + // Set Data0 + buff[byte] = data; + + // Erase + FLASH_EraseOptionBytes(); + + // Write + FLASH_Status status = FLASH_COMPLETE; + + FLASH->OBKEYR = FLASH_KEY1; + FLASH->OBKEYR = FLASH_KEY2; + status = FLASH_WaitForLastOperation(ProgramTimeout); + + for (uint8_t i = 0; i < OB_COUNT; i++) { + if (status == FLASH_COMPLETE) { + FLASH->CTLR |= CR_OPTPG_Set; + OPTION_BYTES[i] = buff[i]; + status = FLASH_WaitForLastOperation(ProgramTimeout); + if (status != FLASH_TIMEOUT) { + FLASH->CTLR &= CR_OPTPG_Reset; + } + } + } + + return status; +} diff --git a/red/mod-io2/src/flash.h b/red/mod-io2/src/flash.h new file mode 100644 index 0000000..73cc627 --- /dev/null +++ b/red/mod-io2/src/flash.h @@ -0,0 +1,32 @@ +#ifndef __FLASH_H +#define __FLASH_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef enum { + OB_RDPR = 0, + OB_USER, + OB_Data0, + OB_Data1, + OB_WRPR0, + OB_WRPR1, + + OB_COUNT +} FLASH_OptionByte_Type; + +void FLASH_OB_DEBUG(); + +uint8_t FLASH_OptionByteGet(FLASH_OptionByte_Type byte); +FLASH_Status FLASH_OptionByteSet(FLASH_OptionByte_Type byte, uint8_t data); + +#ifdef __cplusplus +} +#endif + +#endif /* __FLASH_H */ diff --git a/red/mod-io2/src/i2c_slave.c b/red/mod-io2/src/i2c_slave.c new file mode 100644 index 0000000..06780f9 --- /dev/null +++ b/red/mod-io2/src/i2c_slave.c @@ -0,0 +1,248 @@ +/* + * Code for using the I2C peripheral in slave mode + * for OLIMEX Neo6502PC-PWR board + * + * Project is based on https://github.com/cnlohr/ch32v003fun/blob/master/examples/i2c_slave/ + * + * MIT License + * + * Copyright (c) 2024 Renze Nicolai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "i2c_slave.h" + +// Disable LOG_DEBUG locally +#undef LOD_DEBUG_ENABLE +#include "log_debug.h" + +static uint8_t _address = 0x00; +static uint8_t _first_write = 1; +static uint8_t _register = 0; +static uint8_t _offset = 0; +static uint8_t _writing = 0; + +static i2c_register_callback_t _register_callback = NULL; +static i2c_write_callback_t _write_callback = NULL; +static i2c_stop_callback_t _stop_callback = NULL; +static i2c_read_callback_t _read_callback = NULL; + +void I2C_Slave_Begin(); +uint8_t I2C_Slave_Remap(uint8_t pos); + +void I2C_Slave_Setup(uint8_t addr) { + _address = addr; + _first_write = 1; + _register = 0; + _offset = 0; + + I2C_Slave_Begin(); +} + +void I2C_Slave_SetOnRegister(i2c_register_callback_t on_register) { + _register_callback = on_register; +} + +void I2C_Slave_SetOnWrite(i2c_write_callback_t on_write) { + _write_callback = on_write; +} + +void I2C_Slave_SetOnRead(i2c_read_callback_t on_read) { + _read_callback = on_read; +} + +void I2C_Slave_SetOnStop(i2c_stop_callback_t on_stop) { + _stop_callback = on_stop; +} + +void I2C_Slave_Begin() { + // SDA, SCL - Open-drain multiplexed output + RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); + GPIO_InitTypeDef GPIO_PortC = {0}; + GPIO_PortC.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2; + GPIO_PortC.GPIO_Mode = GPIO_Mode_AF_OD; + GPIO_PortC.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIOC, &GPIO_PortC); + + // Enable I2C1 + RCC->APB1PCENR |= RCC_APB1Periph_I2C1; + + // Reset I2C1 to init all regs + RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; + RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1; + + I2C1->CTLR1 |= I2C_CTLR1_SWRST; + I2C1->CTLR1 &= ~I2C_CTLR1_SWRST; + + // Set module clock frequency + uint32_t prerate = 2000000; // I2C Logic clock rate, must be higher than the bus clock rate + I2C1->CTLR2 |= (SystemCoreClock / prerate) & I2C_CTLR2_FREQ; + + // Enable interrupts + I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITERREN; + + NVIC_EnableIRQ(I2C1_EV_IRQn); // Event interrupt + NVIC_SetPriority(I2C1_EV_IRQn, 2 << 4); + + NVIC_EnableIRQ(I2C1_ER_IRQn); // Error interrupt + NVIC_SetPriority(I2C1_ER_IRQn, 2 << 4); + + // Set clock configuration + uint32_t clockrate = 1000000; // I2C Bus clock rate, must be lower than the logic clock rate + I2C1->CKCFGR = ((SystemCoreClock / (3 * clockrate)) & I2C_CKCFGR_CCR) | I2C_CKCFGR_FS; // Fast mode 33% duty cycle + // I2C1->CKCFGR = ((SystemCoreClock/ (25 * clockrate)) & I2C_CKCFGR_CCR) | I2C_CKCFGR_DUTY | I2C_CKCFGR_FS; // Fast mode 36% duty cycle + // I2C1->CKCFGR = (SystemCoreClock / (2 * clockrate)) & I2C_CKCFGR_CCR; // Standard mode good to 100kHz + + // Set I2C _address + I2C1->OADDR1 = _address << 1; + I2C1->OADDR2 = 0; + + // Enable I2C + I2C1->CTLR1 |= I2C_CTLR1_PE; + + // Acknowledge bytes when they are received + I2C1->CTLR1 |= I2C_CTLR1_ACK; + + // Disable clock stretch + // I2C1->CTLR1 |= I2C_CTLR1_NOSTRETCH; + + LOG_DEBUG("I2C_Slave: Initialized @ 0x%02X" EOL, _address); +} + +void I2C_Slave_Start_Event() { + // LOG_DEBUG("I2C_Slave: Start" EOL); + _first_write = 1; // Next write will be the _register + _offset = 0; // Reset _offset +} + +void I2C_Slave_Write_Event() { + // LOG_DEBUG("I2C_Slave: Write" EOL); + if (_first_write) { + // First byte written, set the _register + _register = I2C1->DATAR; + _offset = 0; + _first_write = 0; + _writing = 0; + + if (_register_callback != NULL) { + _register_callback(_register); + } + LOG_DEBUG("I2C_Slave: Register 0x%02X" EOL, _register); + } else { + // Normal register write + _writing = 1; + + uint8_t data = I2C1->DATAR; + LOG_DEBUG("I2C_Slave: Write 0x%02X" EOL, data); + + if (_write_callback != NULL) { + _write_callback(_register, _offset, data); + } + _offset++; + } +} + +void I2C_Slave_Read_Event() { + // LOG_DEBUG("I2C_Slave: Read" EOL); + _writing = 0; + + uint8_t data = (_read_callback == NULL ? + 0 + : + _read_callback(_register, _offset) + ); + + I2C1->DATAR = data; + _offset++; + + LOG_DEBUG("I2C_Slave: Read 0x%02X" EOL, data); +} + +void I2C_Slave_Stop_Event() { + LOG_DEBUG("I2C_Slave: Stop" EOL); + if (_stop_callback != NULL) { + _stop_callback(_register, _offset); + } +} + +void I2C1_EV_IRQHandler(void) { + // LOG_DEBUG("I2C_Slave: Event" EOL); + + uint16_t STAR1, STAR2 __attribute__((unused)); + STAR1 = I2C1->STAR1; + STAR2 = I2C1->STAR2; + + if (STAR1 & I2C_STAR1_ADDR) { + // Start event + I2C_Slave_Start_Event(); + } + + if (STAR1 & I2C_STAR1_RXNE) { + // Write event + I2C_Slave_Write_Event(); + } + + if (STAR1 & I2C_STAR1_TXE) { + // Read event + I2C_Slave_Read_Event(); + } + + if (STAR1 & I2C_STAR1_STOPF) { + // Stop event + // Clear stop + I2C1->CTLR1 &= ~(I2C_CTLR1_STOP); + + I2C_Slave_Stop_Event(); + } +} + +void I2C1_ER_IRQHandler(void) { + LOG_DEBUG("I2C_Slave: "); + + uint16_t STAR1 = I2C1->STAR1; + + if (STAR1 & I2C_STAR1_BERR) { + // Bus error + LOG_DEBUG("Bus error" EOL); + + // Clear error + I2C1->STAR1 &= ~(I2C_STAR1_BERR); + } + + if (STAR1 & I2C_STAR1_ARLO) { + // Arbitration lost error + LOG_DEBUG("Arbitration lost error" EOL); + + // Clear error + I2C1->STAR1 &= ~(I2C_STAR1_ARLO); + } + + if (STAR1 & I2C_STAR1_AF) { + if (_writing) { + // Acknowledge failure + LOG_DEBUG("Acknowledge failure" EOL); + } else { + LOG_DEBUG("NAC" EOL); + } + + // Clear error + I2C1->STAR1 &= ~(I2C_STAR1_AF); + } +} diff --git a/red/mod-io2/src/i2c_slave.h b/red/mod-io2/src/i2c_slave.h new file mode 100644 index 0000000..0778b3c --- /dev/null +++ b/red/mod-io2/src/i2c_slave.h @@ -0,0 +1,62 @@ +/* + * Header for using the I2C peripheral in slave mode + * for OLIMEX Neo6502PC-PWR board + * + * Project is based on https://github.com/cnlohr/ch32v003fun/blob/master/examples/i2c_slave/ + * + * MIT License + * + * Copyright (c) 2024 Renze Nicolai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __I2C_SLAVE_H +#define __I2C_SLAVE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void I2C1_EV_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); +void I2C1_EV_IRQHandler(void); + +void I2C1_ER_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); +void I2C1_ER_IRQHandler(void); + + +typedef void (*i2c_register_callback_t)(uint8_t reg); +typedef void (*i2c_write_callback_t)(uint8_t reg, uint8_t offset, uint8_t data); +typedef void (*i2c_stop_callback_t)(uint8_t reg, uint8_t offset); +typedef uint8_t (*i2c_read_callback_t)(uint8_t reg, uint8_t offset); + +void I2C_Slave_Setup(uint8_t addr); +void I2C_Slave_SetOnRegister(i2c_register_callback_t on_register); +void I2C_Slave_SetOnWrite(i2c_write_callback_t on_write); +void I2C_Slave_SetOnStop(i2c_stop_callback_t on_stop); +void I2C_Slave_SetOnRead(i2c_read_callback_t on_read); + +#ifdef __cplusplus +} +#endif + +#endif /* __I2C_SLAVE_H */ diff --git a/red/mod-io2/src/log_debug.c b/red/mod-io2/src/log_debug.c new file mode 100644 index 0000000..faeafea --- /dev/null +++ b/red/mod-io2/src/log_debug.c @@ -0,0 +1,28 @@ +#include + +#include "log_debug.h" + +void LOG_DEBUG_Configure(uint32_t baudrate, uint16_t stop_bits, uint16_t parity) { + #ifdef LOD_DEBUG_ENABLE + GPIO_InitTypeDef GPIO_InitStructure = {0}; + USART_InitTypeDef USART_InitStructure = {0}; + + RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO, ENABLE); + + /* CH32_UART TX-->D5 */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; + GPIO_Init(GPIOD, &GPIO_InitStructure); + + USART_InitStructure.USART_BaudRate = baudrate; + USART_InitStructure.USART_StopBits = stop_bits; + USART_InitStructure.USART_Parity = parity; + USART_InitStructure.USART_WordLength = USART_WordLength_8b; + USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; + USART_InitStructure.USART_Mode = USART_Mode_Tx; + + USART_Init(USART1, &USART_InitStructure); + USART_Cmd(USART1, ENABLE); + #endif +} diff --git a/red/mod-io2/src/log_debug.h b/red/mod-io2/src/log_debug.h new file mode 100644 index 0000000..c9d84b7 --- /dev/null +++ b/red/mod-io2/src/log_debug.h @@ -0,0 +1,35 @@ +#ifndef __LOG_DEBUG_H +#define __LOG_DEBUG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef LOD_DEBUG_ENABLE +#define LOG_DEBUG(f_, ...) do { printf((f_), ##__VA_ARGS__); } while(0) +#else +#define LOG_DEBUG(f_, ...) +#endif + +#define EOL "\r\n" + +#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" +#define BYTE_TO_BINARY(byte) \ + ((byte) & 0x80 ? '1' : '0'), \ + ((byte) & 0x40 ? '1' : '0'), \ + ((byte) & 0x20 ? '1' : '0'), \ + ((byte) & 0x10 ? '1' : '0'), \ + ((byte) & 0x08 ? '1' : '0'), \ + ((byte) & 0x04 ? '1' : '0'), \ + ((byte) & 0x02 ? '1' : '0'), \ + ((byte) & 0x01 ? '1' : '0') + +void LOG_DEBUG_Configure(uint32_t baudrate, uint16_t stop_bits, uint16_t parity); + +#ifdef __cplusplus +} +#endif + +#endif /* __LOG_DEBUG_H */ diff --git a/red/mod-io2/src/main.cpp b/red/mod-io2/src/main.cpp new file mode 100644 index 0000000..f8d4a48 --- /dev/null +++ b/red/mod-io2/src/main.cpp @@ -0,0 +1,45 @@ +#include + +#include "log_debug.h" +#include "uptime.h" + +#include "mod_io2.h" + +#ifdef __cplusplus +extern "C" { +#endif + void NMI_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); + void HardFault_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); +#ifdef __cplusplus +} +#endif + +int main(void) { + NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); + SystemCoreClockUpdate(); + + // Disable GPIO Alternate Functions and extrnal oscilator + // Othewize GPIO PORT A does not work + RCC_HSEConfig(RCC_HSE_OFF); + GPIO_PinRemapConfig(GPIO_Remap_PA1_2, DISABLE); + + Uptime_Init(); + LOG_DEBUG_Configure(115200, USART_StopBits_1, USART_Parity_No); + + LOG_DEBUG(EOL "Sys Clock: %ld" EOL, SystemCoreClock); + + MOD_IO2::Setup(); + while (1) { + } +} + +void NMI_Handler(void) { + LOG_DEBUG("NMI_Handler" EOL); +} + +void HardFault_Handler(void) { + LOG_DEBUG("HardFault_Handler" EOL); + while (1) { + + } +} diff --git a/red/mod-io2/src/mod_io2.cpp b/red/mod-io2/src/mod_io2.cpp new file mode 100644 index 0000000..faf9685 --- /dev/null +++ b/red/mod-io2/src/mod_io2.cpp @@ -0,0 +1,438 @@ +#include "mod_io2.h" + +// Disable LOG_DEBUG locally +#undef LOD_DEBUG_ENABLE +#include "log_debug.h" + +#include "uptime.h" +#include "adc.h" +#include "flash.h" + +void MOD_IO2::Setup() { + i2c_address = FLASH_OptionByteGet(OB_Data0); + Begin(); +} + +void MOD_IO2::Setup(uint8_t addr) { + i2c_address = addr; + Begin(); +} + +void MOD_IO2::Begin() { + // Validate I2C address + if (i2c_address > 0x7F || i2c_address == 0x00) { + i2c_address = MOD_IO2_ADDRESS; + } + + // Enable needed periphery + RCC_APB2PeriphClockCmd( + RCC_APB2Periph_GPIOA | + RCC_APB2Periph_GPIOC | + RCC_APB2Periph_GPIOD | + RCC_APB2Periph_AFIO | + RCC_APB2Periph_ADC1 | + RCC_APB2Periph_TIM1, + ENABLE + ); + RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); + + // GPIO configuration + // GPIOs + for (uint8_t gpio=0; gpio < MOD_IO2_GPIO_COUNT; gpio++) { + GPIOConfig(gpio, GPIO_Mode_IN_FLOATING, true); + } + // Relays + for (uint8_t relay=0; relay < MOD_IO2_RELAY_COUNT; relay++) { + RelayConfig(relay); + } + // PGM1 + GPIO_InitTypeDef cfg = {0}; + cfg.GPIO_Mode = GPIO_Mode_IPU; + cfg.GPIO_Pin = PGM1_JUMPER.pin; + cfg.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(PGM1_JUMPER.port, &cfg); + + // I2C configuration + I2C_Slave_Setup(i2c_address); + + I2C_Slave_SetOnRegister(onRegisterSet); + I2C_Slave_SetOnRead(onRegRead); + I2C_Slave_SetOnWrite(onRegWrite); + + LOG_DEBUG("Initialized." EOL); +} + +void MOD_IO2::onRegisterSet(uint8_t reg) { + i2c_reg = NULL; + for (uint8_t r = 0; r < MOD_IO2_REG_COUNT; r++) { + if (REG_MAP[r].reg == reg) { + i2c_reg = ®_MAP[r]; + return; + } + } +} + +uint8_t MOD_IO2::onRegRead(uint8_t reg, uint8_t offset) { + if (i2c_reg == NULL || i2c_reg->onRead == NULL) { + return 0x00; + } + + return i2c_reg->onRead(reg, offset); +} + +void MOD_IO2::onRegWrite(uint8_t reg, uint8_t offset, uint8_t data) { + if (i2c_reg == NULL || i2c_reg->onWrite == NULL) { + return; + } + + i2c_reg->onWrite(reg, offset, data); +} + +uint8_t MOD_IO2::onGetBoardID(uint8_t reg, uint8_t offset) { + LOG_DEBUG("Board ID 0x%02X" EOL, MOD_IO2_BOARD_ID); + return MOD_IO2_BOARD_ID; +} + +uint8_t MOD_IO2::onGetVersion(uint8_t reg, uint8_t offset) { + uint8_t version = (MOD_IO2_VER_MAJOR << 4) | MOD_IO2_VER_MINOR; + LOG_DEBUG("Version 0x%02X" EOL, version); + return version; +} + +void MOD_IO2::onSetDir(uint8_t reg, uint8_t offset, uint8_t data) { + for (uint8_t gpio = 0; gpio < MOD_IO2_GPIO_COUNT; gpio++) { + uint8_t mask = (1 << gpio); + GPIOConfig( + gpio, + ((data & mask) != 0 ? + // Input + ((gpio_pullup & mask) != 0 ? GPIO_Mode_IPU : GPIO_Mode_IN_FLOATING) + : + // Outout + GPIO_Mode_Out_PP + ) + ); + } + gpio_direction = data; + LOG_DEBUG("SetDir " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(gpio_direction)); +} + +uint8_t MOD_IO2::onGetDir(uint8_t reg, uint8_t offset) { + LOG_DEBUG("GetDir " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(gpio_direction)); + return gpio_direction; +} + +void MOD_IO2::onSetPullUp(uint8_t reg, uint8_t offset, uint8_t data) { + // clear pullup bit for outputs + gpio_pullup = (data & gpio_direction); + + for (uint8_t gpio = 0; gpio < MOD_IO2_GPIO_COUNT; gpio++) { + uint8_t mask = (1 << gpio); + if ((gpio_direction & mask) == 0) { + // Skip outputs + continue; + } + + GPIOConfig(gpio, (gpio_pullup & mask) != 0 ? GPIO_Mode_IPU : GPIO_Mode_IN_FLOATING); + } + LOG_DEBUG("SetPullup " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(gpio_pullup)); +} + +uint8_t MOD_IO2::onGetPullUp(uint8_t reg, uint8_t offset) { + LOG_DEBUG("GetPullup " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(gpio_pullup)); + return gpio_pullup; +} + +void MOD_IO2::onSetLevel(uint8_t reg, uint8_t offset, uint8_t data) { + for (uint8_t gpio = 0; gpio < MOD_IO2_GPIO_COUNT; gpio++) { + uint8_t mask = (1 << gpio); + // Check if GPIO is output + if ((gpio_direction & mask) != 0) { + continue; + } + GPIOSet(gpio, (BitAction)((data & mask) != 0)); + } + LOG_DEBUG("SetGPIO " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(gpio_state)); +} + +uint8_t MOD_IO2::onGetLevel(uint8_t reg, uint8_t offset) { + for (uint8_t gpio = 0; gpio < MOD_IO2_GPIO_COUNT; gpio++) { + GPIOGet(gpio); + } + LOG_DEBUG("GetGPIO " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(gpio_state)); + return gpio_state; +} + +uint8_t MOD_IO2::onAnalogGet(uint8_t reg, uint8_t offset) { + if (offset == 1) { + if (adc_error) { + LOG_DEBUG("ADC 0x%02X n/a" EOL, reg); + } else { + #ifdef MOD_IO2_ADC_VOLTAGE + LOG_DEBUG("ADC 0x%02X %d %d mV" EOL, reg, adc_value, adc_voltage); + #else + LOG_DEBUG("ADC 0x%02X %d" EOL, reg, adc_value); + #endif + } + return ((adc_value >> 8) & 0xFF); + } + + if (offset > 1) { + return 0x00; + } + + uint8_t gpio = reg & 0x0F; + adc_error = false; + + if (GPIO_MAP[gpio].adc == 255) { + adc_error = true; + } else { + GPIOConfig(gpio, GPIO_Mode_AIN); + adc_error |= (ADC_SUCCESS != ADC_Channel_Init(GPIO_MAP[gpio].port, GPIO_MAP[gpio].pin, MOD_IO2_ADC_TIMEOUT)); + adc_error |= (ADC_SUCCESS != Get_ADC_Val(GPIO_MAP[gpio].adc, MOD_IO2_ADC_TIMEOUT, &adc_value)); + } + + if (adc_error) { + adc_value = 0; + adc_voltage = 0; + } + #ifdef MOD_IO2_ADC_VOLTAGE + else { + adc_voltage = ADC_Map(adc_value, 0, 1023, 0.0, 3.3); + } + #endif + + return (adc_value & 0xFF); +} + +void MOD_IO2::onSetRelayState(uint8_t reg, uint8_t offset, uint8_t data) { + for (uint8_t relay = 0; relay < MOD_IO2_RELAY_COUNT; relay++) { + RelaySet(relay, (BitAction)((data & (1 << relay)) != 0)); + } + LOG_DEBUG("SetRelays " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(relay_state)); +} + +uint8_t MOD_IO2::onGetRelayState(uint8_t reg, uint8_t offset) { + LOG_DEBUG("GetRelays " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(relay_state)); + return relay_state; +} + +void MOD_IO2::onRelayOn(uint8_t reg, uint8_t offset, uint8_t data) { + for (uint8_t relay = 0; relay < MOD_IO2_RELAY_COUNT; relay++) { + if (((data & (1 << relay)) != 0)) { + RelaySet(relay, Bit_SET); + } + } + LOG_DEBUG("RelaysOn " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(relay_state)); +} + +void MOD_IO2::onRelayOff(uint8_t reg, uint8_t offset, uint8_t data) { + for (uint8_t relay = 0; relay < MOD_IO2_RELAY_COUNT; relay++) { + if (((data & (1 << relay)) != 0)) { + RelaySet(relay, Bit_RESET); + } + } + LOG_DEBUG("RelaysOff " BYTE_TO_BINARY_PATTERN EOL, BYTE_TO_BINARY(relay_state)); +} + +uint8_t MOD_IO2::onGetRelayOff(uint8_t reg, uint8_t offset) { + return (~relay_state) & MOD_IO2_RELAY_MASK; +} + +void MOD_IO2::onSetPWM(uint8_t reg, uint8_t offset, uint8_t data) { + uint8_t pwm = reg & 0x0F; + if (pwm >= MOD_IO2_PWM_COUNT) { + return; + } + + uint8_t gpio; + if (pwm == 0) { + // Disable PWMx where x = data + if (data == 0 || data >= MOD_IO2_PWM_COUNT) { + return; + } + gpio = PWM_MAP[data].gpio; + PWMDisable(GPIO_MAP[gpio].tim); + GPIOConfig(gpio, GPIO_Mode_IN_FLOATING); + return; + } + + gpio = PWM_MAP[pwm].gpio; + GPIOConfig(gpio, GPIO_Mode_AF_PP); + PWMConfig(GPIO_MAP[gpio].tim, GPIO_MAP[gpio].tch, data); +} + +void MOD_IO2::onSetAddress(uint8_t reg, uint8_t offset, uint8_t data) { + if (GPIO_ReadInputDataBit(PGM1_JUMPER.port, PGM1_JUMPER.pin) == 0) { + // Set new address only if PGM1_JUMPER is closed + Setup(data); + + FLASH_Unlock(); + FLASH_OptionByteSet(OB_Data0, i2c_address); + FLASH_Lock(); + } +} + +void MOD_IO2::GPIOConfig(uint8_t gpio, GPIOMode_TypeDef mode, bool initial) { + // LOG_DEBUG("GPIOConfig(%d)" EOL, gpio); + if (gpio >= MOD_IO2_GPIO_COUNT) { + return; + } + + uint8_t mask = (1 << gpio); + + bool changes_detected = initial || gpio_config[gpio] != mode; + + if (!changes_detected) { + // LOG_DEBUG("GPIO %d - NO CHANGES" EOL, gpio); + return; + } + + // Set registers + if ((mode & 0x10) == 0) { + // input + gpio_direction |= mask; + if (mode == GPIO_Mode_IPU) { + gpio_pullup |= mask; + } else { + gpio_pullup &= ~mask; + } + } else { + // output + gpio_direction &= ~mask; + gpio_pullup &= ~mask; + } + + GPIO_InitTypeDef cfg = {0}; + cfg.GPIO_Mode = mode; + cfg.GPIO_Pin = GPIO_MAP[gpio].pin; + cfg.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIO_MAP[gpio].port, &cfg); + + gpio_config[gpio] = mode; + + LOG_DEBUG( + "GPIO %d - %s %s" EOL, + gpio, + ((gpio_direction & mask) != 0 ? "In" : "Out"), + ((gpio_direction & mask) != 0 ? + (mode == GPIO_Mode_AIN ? + "ADC" + : + ((gpio_pullup & mask) != 0 ? "PU" : "PD") + ) + : + "PP" + ) + ); +} + +void MOD_IO2::GPIOSet(uint8_t gpio, BitAction level) { + GPIO_WriteBit(GPIO_MAP[gpio].port, GPIO_MAP[gpio].pin, level); + if (level) { + gpio_state |= (1 << gpio); + } else { + gpio_state &= ~(1 << gpio); + } +} + +uint8_t MOD_IO2::GPIOGet(uint8_t gpio) { + uint8_t level; + + if (((uint8_t)gpio_config[gpio] & 0x10) == 0) { + level = GPIO_ReadInputDataBit(GPIO_MAP[gpio].port, GPIO_MAP[gpio].pin); + } else { + level = GPIO_ReadOutputDataBit(GPIO_MAP[gpio].port, GPIO_MAP[gpio].pin); + } + + if (level) { + gpio_state |= (1 << gpio); + } else { + gpio_state &= ~(1 << gpio); + } + + return level; +} + +void MOD_IO2::RelayConfig(uint8_t relay) { + LOG_DEBUG("RelayConfig(%d)" EOL, relay); + if (relay >= MOD_IO2_RELAY_COUNT) { + return; + } + + GPIO_InitTypeDef cfg = {0}; + + cfg.GPIO_Mode = GPIO_Mode_Out_PP; + cfg.GPIO_Speed = GPIO_Speed_50MHz; + cfg.GPIO_Pin = RELAY_MAP[relay].pin; + + GPIO_Init(RELAY_MAP[relay].port, &cfg); +} + +void MOD_IO2::RelaySet(uint8_t relay, BitAction level) { + GPIO_WriteBit(RELAY_MAP[relay].port, RELAY_MAP[relay].pin, level); + if (level) { + relay_state |= (1 << relay); + } else { + relay_state &= ~(1 << relay); + } +} + +void MOD_IO2::PWMDisable(TIM_TypeDef *TIM) { + TIM_Cmd(TIM, DISABLE); + TIM_CtrlPWMOutputs(TIM, DISABLE); +} + +void MOD_IO2::PWMConfig(TIM_TypeDef *TIM, uint8_t channel, uint16_t pulse) { + TIM_Cmd(TIM, DISABLE); + TIM_CtrlPWMOutputs(TIM, DISABLE); + + TIM_TimeBaseInitTypeDef TimerConfig; + TimerConfig.TIM_Period = 255; + TimerConfig.TIM_Prescaler = 608; + TimerConfig.TIM_CounterMode = TIM_CounterMode_Up; + TimerConfig.TIM_ClockDivision = TIM_CKD_DIV1; + TIM_TimeBaseInit(TIM, &TimerConfig); + + TIM_OCInitTypeDef TIM_OCConfig={0}; + TIM_OCConfig.TIM_Pulse = pulse; + TIM_OCConfig.TIM_OCMode = TIM_OCMode_PWM1; + TIM_OCConfig.TIM_OutputState = TIM_OutputState_Enable; + TIM_OCConfig.TIM_OCPolarity = TIM_OCPolarity_High; + + switch (channel) { + case 1: + TIM_OC1Init(TIM, &TIM_OCConfig ); + break; + case 2: + TIM_OC2Init(TIM, &TIM_OCConfig ); + break; + case 3: + TIM_OC3Init(TIM, &TIM_OCConfig ); + break; + case 4: + TIM_OC4Init(TIM, &TIM_OCConfig ); + break; + } + + TIM_CtrlPWMOutputs(TIM, ENABLE); + + switch (channel) { + case 1: + TIM_OC1PreloadConfig(TIM, TIM_OCPreload_Disable); + break; + case 2: + TIM_OC2PreloadConfig(TIM, TIM_OCPreload_Disable); + break; + case 3: + TIM_OC3PreloadConfig(TIM, TIM_OCPreload_Disable); + break; + case 4: + TIM_OC4PreloadConfig(TIM, TIM_OCPreload_Disable); + break; + } + + TIM_ARRPreloadConfig(TIM, ENABLE); + + TIM_Cmd(TIM, ENABLE); +} diff --git a/red/mod-io2/src/mod_io2.h b/red/mod-io2/src/mod_io2.h new file mode 100644 index 0000000..d68980e --- /dev/null +++ b/red/mod-io2/src/mod_io2.h @@ -0,0 +1,208 @@ +#ifndef __MOD_IO2_H +#define __MOD_IO2_H + +#include + +#include "i2c_slave.h" + +#define MOD_IO2_ADDRESS 0x21 + +#define MOD_IO2_BOARD_ID 0x23 + +#define MOD_IO2_VER_MAJOR 0x5 +#define MOD_IO2_VER_MINOR 0x0 + +#define MOD_IO2_REG_COUNT 21 + +#define MOD_IO2_GPIO_COUNT 7 + +#define MOD_IO2_RELAY_COUNT 2 +#define MOD_IO2_RELAY_MASK 0x03 + +#define MOD_IO2_ADC_TIMEOUT 100 +//#define MOD_IO2_ADC_VOLTAGE + +#define MOD_IO2_PWM_COUNT 3 + +typedef enum { + MOD_IO2_SET_DIR = 0x01, + MOD_IO2_SET_OUT = 0x02, + MOD_IO2_GET_IN = 0x03, + MOD_IO2_SET_PULL_UP = 0x04, + + MOD_IO2_GET_ANALOG0 = 0x10, + MOD_IO2_GET_ANALOG1 = 0x11, + MOD_IO2_GET_ANALOG2 = 0x12, + MOD_IO2_GET_ANALOG3 = 0x13, + MOD_IO2_GET_ANALOG4 = 0x14, + MOD_IO2_GET_ANALOG5 = 0x15, + MOD_IO2_GET_ANALOG6 = 0x16, + + MOD_IO2_GET_BOARD_ID = 0x20, + MOD_IO2_GET_VERSION = 0x21, + + MOD_IO2_RELAY_STATE = 0x40, + MOD_IO2_RELAY_ON = 0x41, + MOD_IO2_RELAY_OFF = 0x42, + MOD_IO2_RELAY_STATE_GET = 0x43, + + MOD_IO2_SET_PWM_OFF = 0x50, + MOD_IO2_SET_PWM1 = 0x51, + MOD_IO2_SET_PWM2 = 0x52, + + MOD_IO2_SET_ADDRESS = 0xF0, +} MOD_IO2_REG_Type; + +/* MOD-IO2 Reg Mapping */ +typedef struct { + uint8_t reg; + i2c_read_callback_t onRead; + i2c_write_callback_t onWrite; +} MOD_IO2_REG_Map_Type; + +/* MOD-IO2 GPIO Mapping */ +typedef struct { + GPIO_TypeDef* port; + uint16_t pin; + uint8_t adc; + TIM_TypeDef * tim; + uint8_t tch; +} MOD_IO2_GPIO_Map_Type; + +/* MOD-IO2 PWM Mapping */ +typedef struct { + uint8_t gpio; +} MOD_IO2_PWM_Map_Type; + +class MOD_IO2 { +protected: + inline static uint8_t i2c_address = MOD_IO2_ADDRESS; + + static void onSetDir(uint8_t reg, uint8_t offset, uint8_t data); + static uint8_t onGetDir(uint8_t reg, uint8_t offset); + + static void onSetPullUp(uint8_t reg, uint8_t offset, uint8_t data); + static uint8_t onGetPullUp(uint8_t reg, uint8_t offset); + + static void onSetLevel(uint8_t reg, uint8_t offset, uint8_t data); + static uint8_t onGetLevel(uint8_t reg, uint8_t offset); + + static uint8_t onAnalogGet(uint8_t reg, uint8_t offset); + + static uint8_t onGetBoardID(uint8_t reg, uint8_t offset); + static uint8_t onGetVersion(uint8_t reg, uint8_t offset); + + static uint8_t onGetRelayState(uint8_t reg, uint8_t offset); + static void onSetRelayState(uint8_t reg, uint8_t offset, uint8_t data); + + static void onRelayOn(uint8_t reg, uint8_t offset, uint8_t data); + static void onRelayOff(uint8_t reg, uint8_t offset, uint8_t data); + static uint8_t onGetRelayOff(uint8_t reg, uint8_t offset); + + static void onSetPWM(uint8_t reg, uint8_t offset, uint8_t data); + + static void onSetAddress(uint8_t reg, uint8_t offset, uint8_t data); + + inline static const MOD_IO2_REG_Map_Type REG_MAP[MOD_IO2_REG_COUNT] = { + {.reg = MOD_IO2_SET_DIR, .onRead = onGetDir, .onWrite = onSetDir}, + {.reg = MOD_IO2_SET_OUT, .onRead = onGetLevel, .onWrite = onSetLevel}, + {.reg = MOD_IO2_GET_IN, .onRead = onGetLevel, .onWrite = NULL}, + {.reg = MOD_IO2_SET_PULL_UP, .onRead = onGetPullUp, .onWrite = onSetPullUp}, + + // Analog input + {.reg = MOD_IO2_GET_ANALOG0, .onRead = onAnalogGet, .onWrite = NULL}, + {.reg = MOD_IO2_GET_ANALOG5, .onRead = onAnalogGet, .onWrite = NULL}, + {.reg = MOD_IO2_GET_ANALOG6, .onRead = onAnalogGet, .onWrite = NULL}, + + {.reg = MOD_IO2_GET_BOARD_ID, .onRead = onGetBoardID, .onWrite = NULL}, + {.reg = MOD_IO2_GET_VERSION, .onRead = onGetVersion, .onWrite = NULL}, + + {.reg = MOD_IO2_RELAY_STATE, .onRead = onGetRelayState, .onWrite = onSetRelayState}, + {.reg = MOD_IO2_RELAY_ON, .onRead = onGetRelayState, .onWrite = onRelayOn}, + {.reg = MOD_IO2_RELAY_OFF, .onRead = onGetRelayOff, .onWrite = onRelayOff}, + {.reg = MOD_IO2_RELAY_STATE_GET, .onRead = onGetRelayState, .onWrite = NULL}, + + // PWM + {.reg = MOD_IO2_SET_PWM_OFF, .onRead = NULL, .onWrite = onSetPWM}, + {.reg = MOD_IO2_SET_PWM1, .onRead = NULL, .onWrite = onSetPWM}, + {.reg = MOD_IO2_SET_PWM2, .onRead = NULL, .onWrite = onSetPWM}, + + // DAC is NOT available + {.reg = 0x60, .onRead = NULL, .onWrite = NULL}, + + {.reg = MOD_IO2_SET_ADDRESS, .onRead = NULL, .onWrite = onSetAddress}, + }; + + inline static const MOD_IO2_GPIO_Map_Type PGM1_JUMPER = { + .port = GPIOD, .pin = GPIO_Pin_1, .adc = 255, .tim = NULL, .tch = 255 + }; + + // GPIO map + inline static const MOD_IO2_GPIO_Map_Type GPIO_MAP[MOD_IO2_GPIO_COUNT] = { + {.port = GPIOC, .pin = GPIO_Pin_4, .adc = 2, .tim = NULL, .tch = 255}, // GPIO0 - PC4 + {.port = GPIOC, .pin = GPIO_Pin_5, .adc = 255, .tim = NULL, .tch = 255}, // GPIO1 - PC5 + {.port = GPIOC, .pin = GPIO_Pin_6, .adc = 255, .tim = NULL, .tch = 255}, // GPIO2 - PC6 + {.port = GPIOC, .pin = GPIO_Pin_7, .adc = 255, .tim = NULL, .tch = 255}, // GPIO3 - PC7 + {.port = GPIOC, .pin = GPIO_Pin_0, .adc = 255, .tim = NULL, .tch = 255}, // GPIO4 - PC0 + {.port = GPIOD, .pin = GPIO_Pin_3, .adc = 4, .tim = TIM2, .tch = 2}, // GPIO5 - PD3 - A4 - T2CH2 + {.port = GPIOD, .pin = GPIO_Pin_2, .adc = 3, .tim = TIM1, .tch = 1}, // GPIO6 - PD2 - A3 - T1CH1 + }; + + // GPIO cofiguration + inline static GPIOMode_TypeDef gpio_config[MOD_IO2_GPIO_COUNT] = { + GPIO_Mode_IN_FLOATING, // GPIO0 + GPIO_Mode_IN_FLOATING, // GPIO1 + GPIO_Mode_IN_FLOATING, // GPIO2 + GPIO_Mode_IN_FLOATING, // GPIO3 + GPIO_Mode_IN_FLOATING, // GPIO4 + GPIO_Mode_IN_FLOATING, // GPIO5 + GPIO_Mode_IN_FLOATING, // GPIO6 + }; + + // RELAY map + inline static const MOD_IO2_GPIO_Map_Type RELAY_MAP[MOD_IO2_RELAY_COUNT] = { + {.port = GPIOA, .pin = GPIO_Pin_1}, // RELAY1 + {.port = GPIOA, .pin = GPIO_Pin_2}, // RELAY2 + }; + + // PWM map + inline static const MOD_IO2_PWM_Map_Type PWM_MAP[MOD_IO2_PWM_COUNT] = { + {.gpio = 255}, // PWM0 + {.gpio = 6}, // PWM1 + {.gpio = 5}, // PWM2 + }; + + // MOD_IO2 state + inline static const MOD_IO2_REG_Map_Type* i2c_reg = NULL; + + inline static uint8_t gpio_direction = 0x7F; + inline static uint8_t gpio_pullup = 0x00; + inline static uint8_t gpio_state = 0x00; + + inline static bool adc_error = false; + inline static uint16_t adc_value = 0x0000; + inline static uint16_t adc_voltage = 0x0000; + + inline static uint8_t relay_state = 0x00; + + static void Begin(); + + static void GPIOConfig(uint8_t gpio, GPIOMode_TypeDef mode, bool initial = false); + static void GPIOSet(uint8_t gpio, BitAction level); + static uint8_t GPIOGet(uint8_t gpio); + + static void RelayConfig(uint8_t relay); + static void RelaySet(uint8_t relay, BitAction level); + + static void PWMDisable(TIM_TypeDef *TIM); + static void PWMConfig(TIM_TypeDef *TIM, uint8_t channel, uint16_t pulse); + +public: + static void Setup(); + static void Setup(uint8_t addr); + static void onRegisterSet(uint8_t reg); + static uint8_t onRegRead(uint8_t reg, uint8_t offset); + static void onRegWrite(uint8_t reg, uint8_t offset, uint8_t data); +}; + +#endif /* __MOD_IO2_H */ diff --git a/red/mod-io2/src/uptime.c b/red/mod-io2/src/uptime.c new file mode 100644 index 0000000..c00003d --- /dev/null +++ b/red/mod-io2/src/uptime.c @@ -0,0 +1,51 @@ +#include "uptime.h" + +static uint32_t uptime_seconds = 0; + +static uint32_t p_ms = 0; +static uint32_t p_us = 0; + +void Uptime_Init() { + if (p_ms != 0) { + // Already initialized + return; + } + + p_ms = (uint32_t)(SystemCoreClock / 1000); + p_us = (uint32_t)(SystemCoreClock / 1000000); + + // Interrupt every second + NVIC_EnableIRQ(SysTicK_IRQn); + + SysTick->SR &= ~(1 << 0); + SysTick->CMP = SystemCoreClock-1; + SysTick->CNT = 0; + SysTick->CTLR = 0xF; +} + +uint32_t Uptime_S() { + return uptime_seconds; +} + +uint32_t Uptime_Ms() { + return uptime_seconds * 1000 + (uint32_t)(SysTick->CNT / p_ms); +} + +uint32_t Uptime_Us() { + return uptime_seconds * 1000000 + (uint32_t)(SysTick->CNT / p_us); +} + +void Wait_Ms(uint32_t delay) { + uint32_t start = Uptime_Ms(); + while((Uptime_Ms() - start) < delay); +} + +void Wait_Us(uint32_t delay) { + uint32_t start = Uptime_Us(); + while((Uptime_Us() - start) < delay); +} + +void SysTick_Handler(void) { + uptime_seconds++; + SysTick->SR = 0; +} \ No newline at end of file diff --git a/red/mod-io2/src/uptime.h b/red/mod-io2/src/uptime.h new file mode 100644 index 0000000..8a58692 --- /dev/null +++ b/red/mod-io2/src/uptime.h @@ -0,0 +1,29 @@ +#ifndef __UPTIME_H +#define __UPTIME_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void Uptime_Init(); + +uint32_t Uptime_S(); + +uint32_t Uptime_Us(); + +uint32_t Uptime_Ms(); + +void Wait_Us(uint32_t delay); + +void Wait_Ms(uint32_t delay); + +void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); +void SysTick_Handler(void); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/red/mod-io2/test/README b/red/mod-io2/test/README new file mode 100644 index 0000000..b0416ad --- /dev/null +++ b/red/mod-io2/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html