From a09aaa22f42481944019f7e277ddc35f3ef54098 Mon Sep 17 00:00:00 2001 From: Rick Lull Date: Mon, 1 Jul 2024 17:04:53 -0400 Subject: [PATCH 1/4] baseline ctc node code. Know working. --- applications/ctcnode/HwInit.cxx | 382 ++++++++++++++++++ applications/ctcnode/Makefile | 56 +++ applications/ctcnode/NodeId.cxx | 9 + applications/ctcnode/config.hxx | 106 +++++ applications/ctcnode/hardware.hxx | 32 ++ applications/ctcnode/main.cxx | 606 +++++++++++++++++++++++++++++ applications/ctcnode/memory_map.ld | 15 + applications/ctcnode/startup.c | 1 + applications/ctcnode/target.ld | 1 + 9 files changed, 1208 insertions(+) create mode 100644 applications/ctcnode/HwInit.cxx create mode 100644 applications/ctcnode/Makefile create mode 100644 applications/ctcnode/NodeId.cxx create mode 100644 applications/ctcnode/config.hxx create mode 100644 applications/ctcnode/hardware.hxx create mode 100644 applications/ctcnode/main.cxx create mode 100644 applications/ctcnode/memory_map.ld create mode 120000 applications/ctcnode/startup.c create mode 120000 applications/ctcnode/target.ld diff --git a/applications/ctcnode/HwInit.cxx b/applications/ctcnode/HwInit.cxx new file mode 100644 index 000000000..5142f8e8d --- /dev/null +++ b/applications/ctcnode/HwInit.cxx @@ -0,0 +1,382 @@ +/** \copyright + * Copyright (c) 2024, Rick Lull + * Based on work by Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file HwInit.cxx + * + * This file represents the hardware initialization for the STM32F303RE Nucelo + * board with the DevKit IO board plugged in. + * + * @author Rick Lull + * @date June 14, 2024 + */ + +#define _DEFAULT_SOURCE + +#include +#include + +#include "stm32l4xx_hal_conf.h" +#include "stm32l4xx_hal.h" + +#include "os/OS.hxx" +#include "Stm32Uart.hxx" +#include "Stm32Can.hxx" +#include "Stm32SPI.hxx" +#include "Stm32I2C.hxx" +#include "Stm32EEPROMEmulation.hxx" +#include "Stm32PWM.hxx" +#include "hardware.hxx" + +/** override stdin */ +const char *STDIN_DEVICE = "/dev/ser0"; + +/** override stdout */ +const char *STDOUT_DEVICE = "/dev/ser0"; + +/** override stderr */ +const char *STDERR_DEVICE = "/dev/ser0"; + +/** UART 0 serial driver instance */ +static Stm32Uart uart0("/dev/ser0", USART2, USART2_IRQn); + +/** CAN 0 CAN driver instance */ +static Stm32Can can0("/dev/can0"); + +/** EEPROM emulation driver. The file size might be made bigger. */ +static Stm32EEPROMEmulation eeprom0("/dev/eeprom", 8192); +// originally 4000 + +/** I2C0 driver instance */ +static Stm32I2C i2c1("/dev/i2c0", I2C1, I2C1_EV_IRQn, I2C1_ER_IRQn); + +/** I2C1 driver instance */ +static Stm32I2C i2c2("/dev/i2c1", I2C2, I2C2_EV_IRQn, I2C2_ER_IRQn); + +/** How many bytes of flash should hold the entire dataset. Must be an integer + * multiple of the minimum erase length (which is the flash page length, for + * the STM32F0 | STM32F3 it is 2 kbytes). The file size maximum is half this value. */ +const size_t EEPROMEmulation::SECTOR_SIZE = 16384; + +/// Recursive mutex for SPI1 peripheral. +OSMutex spi1_lock(true); + +static void noop_cs() { +} + +/// SPI1 driver for io-board peripherals +static Stm32SPI spi1_0( + "/dev/spi1.ioboard", SPI1, SPI1_IRQn, noop_cs, noop_cs, &spi1_lock); + +static void spi1_ext_cs_assert() { + EXT_CS_Pin::set(false); +} + +static void spi1_ext_cs_deassert() { + EXT_CS_Pin::set(true); +} + +/// SPI1 driver for the expansion port. +static Stm32SPI spi1_1("/dev/spi1.ext", SPI1, SPI1_IRQn, &spi1_ext_cs_assert, + &spi1_ext_cs_deassert, &spi1_lock); + +/// SPI2 driver for the onboard input ports. +static Stm32SPI spi2("/dev/spi2", SPI2, SPI2_IRQn, &noop_cs, &noop_cs, nullptr); + +extern "C" { + +/** Blink LED */ +uint32_t blinker_pattern = 0; +static uint32_t rest_pattern = 0; + +void hw_set_to_safe(void) +{ +} + +void reboot() +{ + NVIC_SystemReset(); +} + +void resetblink(uint32_t pattern) +{ + blinker_pattern = pattern; + rest_pattern = pattern ? 1 : 0; + BLINKER_RAW_Pin::set(pattern ? true : false); + /* make a timer event trigger immediately */ +} + +void setblink(uint32_t pattern) +{ + resetblink(pattern); +} + +void i2c1_ev_interrupt_handler(void) +{ + i2c1.event_interrupt_handler(); +} + +void i2c1_er_interrupt_handler(void) +{ + i2c1.error_interrupt_handler(); +} + +void i2c2_ev_interrupt_handler(void) +{ + i2c2.event_interrupt_handler(); +} + +void i2c2_er_interrupt_handler(void) +{ + i2c2.error_interrupt_handler(); +} + +/// TIM17 shares this interrupt with certain features of timer1 +//void tim7_trg_com_interrupt_handler(void) +void tim7_interrupt_handler(void) +{ + // + // Clear the timer interrupt. + // + TIM7->SR = ~TIM_IT_UPDATE; + + // Set output LED. + BLINKER_RAW_Pin::set(rest_pattern & 1); + + // Shift and maybe reset pattern. + rest_pattern >>= 1; + if (!rest_pattern) + { + rest_pattern = blinker_pattern; + } +} + +void diewith(uint32_t pattern) +{ + // vPortClearInterruptMask(0x20); + asm("cpsie i\n"); + + resetblink(pattern); + while (1) + ; +} + +/** CPU clock speed. */ +const unsigned long cm3_cpu_clock_hz = 80000000; +uint32_t SystemCoreClock; +const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; +const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4}; +const uint32_t MSIRangeTable[12] = {100000U, 200000U, 400000U, 800000U, + 1000000U, 2000000U, 4000000U, 8000000U, 16000000U, 24000000U, 32000000U, + 48000000U}; +const uint32_t HSEValue = 8000000; + +/** + * @brief System Clock Configuration + * The system Clock is configured as follow : + * System Clock source = PLL (HSE) + * SYSCLK(Hz) = 80000000 + * HCLK(Hz) = 80000000 + * AHB Prescaler = 1 + * APB1 Prescaler = 1 + * APB2 Prescaler = 1 + * HSE Frequency(Hz) = 800000 + * PLL_M = 1 + * PLL_N = 20 + * PLL_R = 2 + * PLL_P = 7 + * PLL_Q = 4 + * Flash Latency(WS) = 4 + * @param None + * @retval None + */ +static void clock_setup() { + HAL_RCC_DeInit(); + + RCC_OscInitTypeDef RCC_OscInitStruct = {0}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; + + /* Activates PLL with HSE as source, assuming 8 MHz input clock / + * crystal. */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; + RCC_OscInitStruct.MSIState = RCC_MSI_OFF; + RCC_OscInitStruct.LSEState = RCC_LSE_OFF; + RCC_OscInitStruct.HSIState = RCC_HSI_OFF; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; + RCC_OscInitStruct.PLL.PLLM = 1; + RCC_OscInitStruct.PLL.PLLN = 20; // 8 * 20 = 160 MHz VCO + RCC_OscInitStruct.PLL.PLLR = 2; // 80 MHz sysclk + RCC_OscInitStruct.PLL.PLLP = 7; // ? + RCC_OscInitStruct.PLL.PLLQ = 4; // 40 MHz RNG clock. + + HASSERT(HAL_RCC_OscConfig(&RCC_OscInitStruct) == HAL_OK); + + /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 + clocks dividers */ + RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | + RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; + HASSERT(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) == HAL_OK); + + // This will fail if the clocks are somehow misconfigured. +// HASSERT(SystemCoreClock == EXPECTED_CLOCK_SPEED); + HASSERT(SystemCoreClock == cm3_cpu_clock_hz); +} + +/// We don't need the HAL tick configuration code to run. FreeRTOS will take +/// care of that. +HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) +{ + return HAL_OK; +} + +/** Initialize the processor hardware. + */ +void hw_preinit(void) +{ + /* Globally disables interrupts until the FreeRTOS scheduler is up. */ + asm("cpsid i\n"); + + /* these FLASH settings enable opertion at 72 MHz */ + __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); + __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_2); + + /* setup the system clock */ + clock_setup(); + + /* enable peripheral clocks */ + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); +// __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_USART2_CLK_ENABLE(); + __HAL_RCC_CAN1_CLK_ENABLE(); + __HAL_RCC_TIM7_CLK_ENABLE(); +// __HAL_RCC_TIM3_CLK_ENABLE(); + + /* setup pinmux */ + GPIO_InitTypeDef gpio_init; + memset(&gpio_init, 0, sizeof(gpio_init)); + + /* USART1 pinmux on PA9 and PA10 */ +// gpio_init.Mode = GPIO_MODE_AF_PP; +// gpio_init.Pull = GPIO_PULLUP; +// gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; +// gpio_init.Alternate = GPIO_AF7_USART2; +// gpio_init.Pin = GPIO_PIN_2; +// HAL_GPIO_Init(GPIOA, &gpio_init); +// gpio_init.Pin = GPIO_PIN_3; +// HAL_GPIO_Init(GPIOA, &gpio_init); + + /* CAN pinmux on PA11 and PA12 */ + gpio_init.Mode = GPIO_MODE_AF_PP; + // Disables pull-ups because this is a 5V tolerant pin. + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF9_CAN1; + gpio_init.Pin = GPIO_PIN_11; + HAL_GPIO_Init(GPIOA, &gpio_init); + gpio_init.Pin = GPIO_PIN_12; + HAL_GPIO_Init(GPIOA, &gpio_init); + + /* SPI1 pinmux on PB3 and PB5 */ + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF5_SPI1; + gpio_init.Pin = GPIO_PIN_3; + HAL_GPIO_Init(GPIOB, &gpio_init); + gpio_init.Pin = GPIO_PIN_5; + HAL_GPIO_Init(GPIOB, &gpio_init); + + /* SPI2 pinmux on PB13 (SCK), and PB14 (MISO2) */ + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF5_SPI2; + gpio_init.Pin = GPIO_PIN_14; + HAL_GPIO_Init(GPIOB, &gpio_init); + gpio_init.Pin = GPIO_PIN_13; + HAL_GPIO_Init(GPIOB, &gpio_init); + + /* I2C2 pinmux on PB6 (SCL), and PB7 (SDA) */ + gpio_init.Mode = GPIO_MODE_AF_OD; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_LOW; + gpio_init.Alternate = GPIO_AF4_I2C2; + gpio_init.Pin = GPIO_PIN_6; + HAL_GPIO_Init(GPIOB, &gpio_init); + gpio_init.Pin = GPIO_PIN_7; + HAL_GPIO_Init(GPIOB, &gpio_init); + + /* I2C1 pinmux on PB10 (SCL), and PB11 (SDA) */ + gpio_init.Mode = GPIO_MODE_AF_OD; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_LOW; + gpio_init.Alternate = GPIO_AF4_I2C1; + gpio_init.Pin = GPIO_PIN_10; + HAL_GPIO_Init(GPIOB, &gpio_init); + gpio_init.Pin = GPIO_PIN_11; + HAL_GPIO_Init(GPIOB, &gpio_init); + + GpioInit::hw_init(); + + /* Initializes the blinker timer. */ + TIM_HandleTypeDef TimHandle; + memset(&TimHandle, 0, sizeof(TimHandle)); + TimHandle.Instance = TIM7; + TimHandle.Init.Period = configCPU_CLOCK_HZ / 10000 / 8; + TimHandle.Init.Prescaler = 10000; + TimHandle.Init.ClockDivision = 0; + TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP; + TimHandle.Init.RepetitionCounter = 0; + if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK) + { + /* Initialization Error */ + HASSERT(0); + } + if (HAL_TIM_Base_Start_IT(&TimHandle) != HAL_OK) + { + /* Starting Error */ + HASSERT(0); + } + __HAL_DBGMCU_FREEZE_TIM7(); + SetInterruptPriority(TIM7_IRQn, 0); + NVIC_EnableIRQ(TIM7_IRQn); +} + +void usart2_interrupt_handler(void) +{ + Stm32Uart::interrupt_handler(1); +} + +} diff --git a/applications/ctcnode/Makefile b/applications/ctcnode/Makefile new file mode 100644 index 000000000..44c56d695 --- /dev/null +++ b/applications/ctcnode/Makefile @@ -0,0 +1,56 @@ +APP_PATH ?= $(realpath ../..) +-include $(APP_PATH)/config.mk +-include local_config.mk + +OPENMRNPATH ?= $(shell \ +sh -c "if [ \"X`printenv OPENMRNPATH`\" != \"X\" ]; then printenv OPENMRNPATH; \ + elif [ -d /opt/openmrn/src ]; then echo /opt/openmrn; \ + elif [ -d ~/openmrn/src ]; then echo ~/openmrn; \ + elif [ -d ../../../src ]; then echo ../../..; \ + else echo OPENMRNPATH not found; fi" \ +) + +# Find STM32CubeF3 libraries +include $(OPENMRNPATH)/etc/stm32cubel4.mk + +LDFLAGSEXTRA += +SYSLIBRARIESEXTRA += -lfreertos_drivers_stm32cubel431xx +OBJEXTRA += + +CFLAGS += -DSTM32L431xx +CXXFLAGS += -DSTM32L431xx +OPENOCDARGS = -f board/st_nucleo_l4.cfg + +ifndef TARGET +export TARGET := freertos.armv7m +endif +include $(OPENMRNPATH)/etc/prog.mk + +ifndef DEFAULT_ADDRESS +DEFAULT_ADDRESS=0xF0 +endif + +include $(OPENMRNPATH)/etc/node_id.mk + +# How to use: make multibin ADDRESS=0x20 ADDRHIGH=0x45 NUM=3 +# starting address, high bits (user range), count +multibin: + for i in $$(seq 1 $(NUM)) ; do $(MAKE) $(EXECUTABLE).bin ADDRESS=$$(printf 0x%02x $$(($(ADDRESS)+$$i))) ; cp $(EXECUTABLE).bin $(EXECUTABLE).f303.$$(printf %02x%02x $(ADDRHIGH) $$(($(ADDRESS)+$$i-1))).bin ; done + +ifeq ($(call find_missing_deps,OPENOCDPATH OPENOCDSCRIPTSPATH),) +all: $(EXECUTABLE).bin + +flash: $(EXECUTABLE)$(EXTENTION) $(EXECUTABLE).lst + @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi + $(GDB) $< -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "monitor reset halt" -ex "load" -ex "monitor reset init" -ex "monitor reset run" -ex "detach" -ex "quit" + +gdb: + @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi + $(GDB) $(EXECUTABLE)$(EXTENTION) -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "continue" # -ex "monitor reset halt" + +else + +flash gdb: + echo OPENOCD not found ; exit 1 + +endif diff --git a/applications/ctcnode/NodeId.cxx b/applications/ctcnode/NodeId.cxx new file mode 100644 index 000000000..8450178ee --- /dev/null +++ b/applications/ctcnode/NodeId.cxx @@ -0,0 +1,9 @@ +#include "openlcb/If.hxx" +#include "address.h" + +#ifndef NODEID_HIGH_BITS +#define NODEID_HIGH_BITS 0x18 +#endif + +extern const openlcb::NodeID NODE_ID; +const openlcb::NodeID NODE_ID = 0x050101015000ULL | NODEID_LOW_BITS; diff --git a/applications/ctcnode/config.hxx b/applications/ctcnode/config.hxx new file mode 100644 index 000000000..001a86c40 --- /dev/null +++ b/applications/ctcnode/config.hxx @@ -0,0 +1,106 @@ +#ifndef _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ +#define _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ + +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/ConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "openlcb/MultiConfiguredPC.hxx" +#include "openlcb/ServoConsumerConfig.hxx" + +namespace openlcb +{ + +/// Defines the identification information for the node. The arguments are: +/// +/// - 4 (version info, always 4 by the standard +/// - Manufacturer name +/// - Model name +/// - Hardware version +/// - Software version +/// +/// This data will be used for all purposes of the identification: +/// +/// - the generated cdi.xml will include this data +/// - the Simple Node Ident Info Protocol will return this data +/// - the ACDI memory space will contain this data. +extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { + 4, "LCCSignals.com", "US&S CTC Node", + "Rev A", ".1"}; + +#define NUM_OUTPUTS 16 +#define NUM_INPUTS 1 +#define NUM_EXTBOARDS 0 + +/// Declares a repeated group of a given base group and number of repeats. The +/// ProducerConfig and ConsumerConfig groups represent the configuration layout +/// needed by the ConfiguredProducer and ConfiguredConsumer classes, and come +/// from their respective hxx file. +using AllConsumers = RepeatedGroup; +using AllProducers = RepeatedGroup; + +using PortDEConsumers = RepeatedGroup; +using PortABProducers = RepeatedGroup; + +#if NUM_EXTBOARDS == 1 +using Ext0PC = RepeatedGroup; +#elif NUM_EXTBOARDS == 2 +using Ext0PC = RepeatedGroup; +#endif + + +/// Modify this value every time the EEPROM needs to be cleared on the node +/// after an update. +static constexpr uint16_t CANONICAL_VERSION = 0x1188; + +CDI_GROUP(NucleoGroup, Name("Non Column Inputs/Outputs"), Description("These are physically located centrally on the node, not part of any CTC Column. Useful for testing/validation.")); +CDI_GROUP_ENTRY(blue_led, ConsumerConfig, Name("Blue LED"), Description("Blue LED (D?).")); +CDI_GROUP_ENTRY(gold_led, ConsumerConfig, Name("Yellow LED"), Description("Yellow LED (D?).")); +CDI_GROUP_ENTRY(blue_btn, ProducerConfig, Name("Blue Button"), Description("Button with blue cap.")); +CDI_GROUP_ENTRY(gold_btn, ProducerConfig, Name("Gold Button"), Description("Button with yellow cap.")); +CDI_GROUP_END(); + +/// Defines the main segment in the configuration CDI. This is laid out at +/// origin 128 to give space for the ACDI user data at the beginning. +CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128)); +/// Each entry declares the name of the current entry, then the type and then +/// optional arguments list. +CDI_GROUP_ENTRY(internal_config, InternalConfigData); +CDI_GROUP_ENTRY(nucleo_onboard, NucleoGroup); +CDI_GROUP_ENTRY(portde_consumers, PortDEConsumers, Name("Column Indicators"), Description("Line 1-8 is Left Column, Line 9-16 is Right Column"), RepName("Indicator")); +CDI_GROUP_ENTRY(portab_producers, PortABProducers, Name("Column Levers and Buttons"), Description("Line 1-8 is Left Column, Line 9-16 is Right Column"), RepName("Lever Or Button")); +#if NUM_EXTBOARDS > 0 +CDI_GROUP_ENTRY(ext0_pc, Ext0PC, Name("Expansion board 0 lines"), + Description("Line 1-8 is port Even/A, Line 9-16 is port Even/B, Line 17-24 " + "is Odd/A, Line 25-32 is Odd/B"), + RepName("Line")); +#endif +CDI_GROUP_END(); + +/// This segment is only needed temporarily until there is program code to set +/// the ACDI user data version byte. +CDI_GROUP(VersionSeg, Segment(MemoryConfigDefs::SPACE_CONFIG), + Name("Version information")); +CDI_GROUP_ENTRY(acdi_user_version, Uint8ConfigEntry, + Name("ACDI User Data version"), Description("Set to 2 and do not change.")); +CDI_GROUP_END(); + +/// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx +/// to refer to the configuration defined here. +CDI_GROUP(ConfigDef, MainCdi()); +/// Adds the tag with the values from SNIP_STATIC_DATA above. +CDI_GROUP_ENTRY(ident, Identification); +/// Adds an tag. +CDI_GROUP_ENTRY(acdi, Acdi); +/// Adds a segment for changing the values in the ACDI user-defined +/// space. UserInfoSegment is defined in the system header. +CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +/// Adds the main configuration segment. +CDI_GROUP_ENTRY(seg, IoBoardSegment); +/// Adds the versioning segment. +CDI_GROUP_ENTRY(version, VersionSeg); +CDI_GROUP_END(); + +} // namespace openlcb + +#endif // _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ diff --git a/applications/ctcnode/hardware.hxx b/applications/ctcnode/hardware.hxx new file mode 100644 index 000000000..f57748940 --- /dev/null +++ b/applications/ctcnode/hardware.hxx @@ -0,0 +1,32 @@ + +#include "Stm32Gpio.hxx" +#include "utils/GpioInitializer.hxx" +#include "BlinkerGPIO.hxx" +#include "DummyGPIO.hxx" +#include "PWM.hxx" + +// Blue and Gold LEDs +GPIO_PIN(LED_Blue, LedPin, B, 1); +GPIO_PIN(LED_Gold, LedPin, B, 0); + +// Blue and Gold Buttons +GPIO_PIN(BLUE_Btn, GpioInputPU, B, 8); +GPIO_PIN(GOLD_Btn, GpioInputPU, B, 9); + +// Chip Select line for SPI1 IF we had an expansion port, we don't so not actually used. PB2 unconnected +GPIO_PIN(EXT_CS, GpioOutputSafeLow, B, 2); +// Latch line assigned to expansion port, we don't, so not used. PB12 unconnected +GPIO_PIN(EXT_LAT, GpioOutputSafeLow, B, 12); +// Latch line on the onboard outputs +GPIO_PIN(OUT_LAT, GpioOutputSafeLow, B, 4); +// Latch line on the onboard inputs +GPIO_PIN(INP_LAT, GpioOutputSafeLow, B, 15); + +typedef GpioInitializer + GpioInit; + +typedef LED_Blue_Pin BLINKER_RAW_Pin; +typedef BLINKER_Pin LED_Blue; + diff --git a/applications/ctcnode/main.cxx b/applications/ctcnode/main.cxx new file mode 100644 index 000000000..506cfa51c --- /dev/null +++ b/applications/ctcnode/main.cxx @@ -0,0 +1,606 @@ +/** \copyright + * Copyright (c) 2024, Rick Lull + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file main.cxx + * + * CTC Node + * + * @author Rick Lull + * @date 15 June 2024 + */ + +#define _DEFAULT_SOURCE + +#include + +#include "os/os.h" +#include "nmranet_config.h" + +#include "openlcb/SimpleStack.hxx" +#include "openlcb/MultiConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" + +#include "freertos_drivers/st/Stm32Gpio.hxx" +#include "freertos_drivers/common/BlinkerGPIO.hxx" +#include "freertos_drivers/common/DummyGPIO.hxx" +#include "freertos_drivers/common/MCP23017Gpio.hxx" +#include "os/MmapGpio.hxx" +#include "config.hxx" +#include "hardware.hxx" +#include "PWM.hxx" +#include "i2c.h" +#include "i2c-dev.h" + +// These preprocessor symbols are used to select which physical connections +// will be enabled in the main(). See @ref appl_main below. +//#define SNIFF_ON_SERIAL +//#define SNIFF_ON_USB +#define HAVE_PHYSICAL_CAN_PORT + +// Changes the default behavior by adding a newline after each gridconnect +// packet. Makes it easier for debugging the raw device. +OVERRIDE_CONST(gc_generate_newlines, 1); +// Specifies how much RAM (in bytes) we allocate to the stack of the main +// thread. Useful tuning parameter in case the application runs out of memory. +OVERRIDE_CONST(main_thread_stack_size, 2500); + +// Specifies the 48-bit OpenLCB node identifier. This must be unique for every +// hardware manufactured, so in production this should be replaced by some +// easily incrementable method. +extern const openlcb::NodeID NODE_ID; + +// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack +// contains everything needed for a usual peripheral node -- all +// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration +// protocol, ACDI, CDI, a bunch of memory spaces, etc. +openlcb::SimpleCanStack stack(NODE_ID); + +// ConfigDef comes from config.hxx and is specific to the particular device and +// target. It defines the layout of the configuration memory space and is also +// used to generate the cdi.xml file. Here we instantiate the configuration +// layout. The argument of offset zero is ignored and will be removed later. +openlcb::ConfigDef cfg(0); +// Defines weak constants used by the stack to tell it which device contains +// the volatile configuration information. This device name appears in +// HwInit.cxx that creates the device drivers. +extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom"; +// The size of the memory space to export over the above device. +extern const size_t openlcb::CONFIG_FILE_SIZE = + cfg.seg().size() + cfg.seg().offset(); +static_assert(openlcb::CONFIG_FILE_SIZE <= 8192, "Need to adjust eeprom size"); + +// The SNIP user-changeable information in also stored in the above eeprom +// device. In general this could come from different eeprom segments, but it is +// simpler to keep them together. +extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::CONFIG_FILENAME; + +// Instantiates the actual producer and consumer objects for the given GPIO +// pins from above. The ConfiguredConsumer class takes care of most of the +// complicated setup and operation requirements. We need to give it the virtual +// node pointer, the configuration configuration from the CDI definition, and +// the hardware pin definition. The virtual node pointer comes from the stack +// object. The configuration structure comes from the CDI definition object, +// segment 'seg', in which there is a repeated group 'consumers', and we assign +// the individual entries to the individual consumers. Each consumer gets its +// own GPIO pin. +openlcb::ConfiguredConsumer consumer_gold( + stack.node(), cfg.seg().nucleo_onboard().gold_led(), LED_Gold_Pin()); +openlcb::ConfiguredConsumer consumer_blue( + stack.node(), cfg.seg().nucleo_onboard().blue_led(), LED_Blue_Pin()); + +// Similar syntax for the producers. +openlcb::ConfiguredProducer producer_blue( + stack.node(), cfg.seg().nucleo_onboard().gold_btn(), GOLD_Btn_Pin()); +openlcb::ConfiguredProducer producer_gold( + stack.node(), cfg.seg().nucleo_onboard().blue_btn(), BLUE_Btn_Pin()); + +// The producers need to be polled repeatedly for changes and to execute the +// debouncing algorithm. This class instantiates a refreshloop and adds the two +// producers to it. +openlcb::RefreshLoop loop( + stack.node(), {producer_blue.polling(), producer_gold.polling()}); + +class FactoryResetHelper : public DefaultConfigUpdateListener { +public: + UpdateAction apply_configuration(int fd, bool initial_load, + BarrierNotifiable *done) OVERRIDE { + AutoNotify n(done); + return UPDATED; + } + + void factory_reset(int fd) override + { + cfg.userinfo().name().write(fd, "CTC Node"); + cfg.userinfo().description().write( + fd, "LCCSignals.com CTC Node"); + } +} factory_reset_helper; + +/// Input/output handler for shift register based IO expansion. Periodically +/// mirrors bitmaps from memory to and from the external shift registers, +/// manipulating a latch GPIO appropriately. Uses a SPI port device. There is +/// no limit on the number of shift registers daisy chained. The input and +/// output shift registers should have independent daisy chains. It is okay if +/// there is no CS wired up for the shift registers as the latch operation will +/// cause all unrelated transfers to be ignored by the shift registers.. +/// +/// This state flow is blocking the executor during the SPI transfers and +/// should not be used on the main executor. +class SpiIOShiftRegister : public StateFlowBase +{ +public: + /// Constructor. + /// + /// @param dedicated_executor is a serivce that is NOT on the main + /// executor. + /// @param mutex if not null, this (async) mutex will be acquired before + /// the SPI port is touched. Allows mutual exclusion of multiple stateflows + /// of this class. + /// @param latch is the IO pin to operate the latch on the shift + /// registers. The expectation is that output shift registers latch on the + /// low to high edge of this IO, and input shift registers are shifting + /// while high and loading on clocks while low. This matches the 74*595 and + /// 74*166 series' behavior. + /// @param output_storage bit array of the data to output to the shift + /// registers. If nullptr, no output shift registers will be operated. Must + /// be aligned to word boundary, but the individual bytes will be output in + /// address-increasing order, i.e. the first byte to be output is the byte + /// pointed by output_storage. (This is LSB-first on little endian + /// architectures.) + /// @param output_len_bytes how many 8-bit shift registers are daisy + /// chained on the output. + /// @param input_storage bit array where to put the data input from the + /// shift registers. If nullptr, no input shift registers will be + /// operated. Must be aligned to word boundary, but the individual bytes + /// address-increasing order, i.e. the first byte to be output is the byte + /// pointed by output_storage. (This is LSB-first on little endian + /// architectures.) + /// @param input_len_bytes how many 8-bit shift registers are daisy + /// chained on the input. + /// @param delay_msec defines the delay between consecutive refreshes of + /// the shift registers (inversely the refresh rate). + SpiIOShiftRegister(Service *dedicated_executor, + AsyncMutex *mutex, const Gpio *latch, uint32_t *output_storage, + unsigned output_len_bytes, uint32_t *input_storage = nullptr, + unsigned input_len_bytes = 0, unsigned delay_msec = 50) + : StateFlowBase(dedicated_executor) + , asyncMutex_(mutex) + , latch_(latch) + , delayMsec_(delay_msec) + , outputStorage_(output_storage) + , outputLenBytes_(output_len_bytes) + , inputStorage_(input_storage) + , inputLenBytes_(input_len_bytes) + {} + + /// Initializes SPI port. Called via main() to not rely on static init ordering. + /// @param port is the character device name for the SPI port + void init(const char *port) { + fd_ = ::open(port, O_RDWR); + HASSERT(fd_ >= 0); + // test alignment + HASSERT((((uintptr_t)outputStorage_) & 3) == 0); + HASSERT((((uintptr_t)inputStorage_) & 3) == 0); + start_flow(STATE(wait_delay)); + } + + /// Executes a refresh cycle right now. + void trigger() { + timer_.ensure_triggered(); + } + +private: + Action wait_delay() + { + return sleep_and_call( + &timer_, MSEC_TO_NSEC(delayMsec_), STATE(refresh)); + } + + Action refresh() + { + if (asyncMutex_) { + return allocate_and_call(STATE(locked), asyncMutex_); + } else { + return call_immediately(STATE(locked)); + } + } + + Action locked() { + latch_->write(Gpio::CLR); + if (outputStorage_) { + auto rb = ::write(fd_, outputStorage_, outputLenBytes_); + HASSERT((size_t)rb == outputLenBytes_); + } else { + // need to get some clock cycles while lat==low to load the input + // shift register + uint8_t d = 0; + ::write(fd_, &d, 1); + latch_->write(Gpio::CLR); + } + usleep(2); + latch_->write(Gpio::SET); + if (inputStorage_) { + auto rb = ::read(fd_, inputStorage_, inputLenBytes_); + HASSERT((size_t)rb == inputLenBytes_); + } + usleep(2); + latch_->write(Gpio::CLR); + if (asyncMutex_) { + asyncMutex_->Unlock(); + } + return call_immediately(STATE(wait_delay)); + } + + /// Helper structure. + StateFlowTimer timer_{this}; + /// File descriptor for the SPI port. + int fd_; + /// Async mutex if the SPI port needs it + AsyncMutex* asyncMutex_; + /// Latch to trigger activating the output. + const Gpio* latch_; + /// How much time to sleep between two refreshes. + unsigned delayMsec_; + /// Bit stream of the data to be written. This is purposefully aligned. + uint32_t* outputStorage_; + /// How many bytes of data to shift out. + unsigned outputLenBytes_; + /// Bit stream of the data to be read. This is purposefully aligned. + uint32_t* inputStorage_; + /// How many bytes of data to shift in. + unsigned inputLenBytes_; +}; + +Executor<1> io_executor("io_thread", 1, 1300); +Service io_service(&io_executor); + +uint32_t output_register[1] = {0x00000000}; + +SpiIOShiftRegister internal_outputs(&io_service, nullptr, OUT_LAT_Pin::instance(), output_register, 2); + +constexpr const MmapGpio PORTD_LINE1(output_register, 7, true); +constexpr const MmapGpio PORTD_LINE2(output_register, 6, true); +constexpr const MmapGpio PORTD_LINE3(output_register, 5, true); +constexpr const MmapGpio PORTD_LINE4(output_register, 4, true); +constexpr const MmapGpio PORTD_LINE5(output_register, 3, true); +constexpr const MmapGpio PORTD_LINE6(output_register, 2, true); +constexpr const MmapGpio PORTD_LINE7(output_register, 1, true); +constexpr const MmapGpio PORTD_LINE8(output_register, 0, true); + +constexpr const MmapGpio PORTE_LINE1(output_register, 15, true); +constexpr const MmapGpio PORTE_LINE2(output_register, 14, true); +constexpr const MmapGpio PORTE_LINE3(output_register, 13, true); +constexpr const MmapGpio PORTE_LINE4(output_register, 12, true); +constexpr const MmapGpio PORTE_LINE5(output_register, 11, true); +constexpr const MmapGpio PORTE_LINE6(output_register, 10, true); +constexpr const MmapGpio PORTE_LINE7(output_register, 9, true); +constexpr const MmapGpio PORTE_LINE8(output_register, 8, true); + + +constexpr const Gpio *const kPortDEGpio[] = { + &PORTD_LINE1, &PORTD_LINE2, &PORTD_LINE3, &PORTD_LINE4, // + &PORTD_LINE5, &PORTD_LINE6, &PORTD_LINE7, &PORTD_LINE8, // + &PORTE_LINE1, &PORTE_LINE2, &PORTE_LINE3, &PORTE_LINE4, // + &PORTE_LINE5, &PORTE_LINE6, &PORTE_LINE7, &PORTE_LINE8 // +}; + +openlcb::MultiConfiguredConsumer portde_consumers(stack.node(), kPortDEGpio, + ARRAYSIZE(kPortDEGpio), cfg.seg().portde_consumers()); + + +uint32_t input_register[2] = {0}; + +SpiIOShiftRegister internal_inputs(&io_service, nullptr, INP_LAT_Pin::instance(), nullptr, 0, input_register, 3); + +constexpr const MmapGpio PORTB_LINE1(input_register, 0, false); +constexpr const MmapGpio PORTB_LINE2(input_register, 1, false); +constexpr const MmapGpio PORTB_LINE3(input_register, 2, false); +constexpr const MmapGpio PORTB_LINE4(input_register, 3, false); +constexpr const MmapGpio PORTB_LINE5(input_register, 4, false); +constexpr const MmapGpio PORTB_LINE6(input_register, 5, false); +constexpr const MmapGpio PORTB_LINE7(input_register, 6, false); +constexpr const MmapGpio PORTB_LINE8(input_register, 7, false); + +constexpr const MmapGpio PORTA_LINE1(input_register, 8, false); +constexpr const MmapGpio PORTA_LINE2(input_register, 9, false); +constexpr const MmapGpio PORTA_LINE3(input_register, 10, false); +constexpr const MmapGpio PORTA_LINE4(input_register, 11, false); +constexpr const MmapGpio PORTA_LINE5(input_register, 12, false); +constexpr const MmapGpio PORTA_LINE6(input_register, 13, false); +constexpr const MmapGpio PORTA_LINE7(input_register, 14, false); +constexpr const MmapGpio PORTA_LINE8(input_register, 15, false); + +// Similar syntax for the producers. +openlcb::ConfiguredProducer producer_a1( + stack.node(), cfg.seg().portab_producers().entry<0>(), (const Gpio*)&PORTA_LINE1); +openlcb::ConfiguredProducer producer_a2( + stack.node(), cfg.seg().portab_producers().entry<1>(), (const Gpio*)&PORTA_LINE2); +openlcb::ConfiguredProducer producer_a3( + stack.node(), cfg.seg().portab_producers().entry<2>(), (const Gpio*)&PORTA_LINE3); +openlcb::ConfiguredProducer producer_a4( + stack.node(), cfg.seg().portab_producers().entry<3>(), (const Gpio*)&PORTA_LINE4); +openlcb::ConfiguredProducer producer_a5( + stack.node(), cfg.seg().portab_producers().entry<4>(), (const Gpio*)&PORTA_LINE5); +openlcb::ConfiguredProducer producer_a6( + stack.node(), cfg.seg().portab_producers().entry<5>(), (const Gpio*)&PORTA_LINE6); +openlcb::ConfiguredProducer producer_a7( + stack.node(), cfg.seg().portab_producers().entry<6>(), (const Gpio*)&PORTA_LINE7); +openlcb::ConfiguredProducer producer_a8( + stack.node(), cfg.seg().portab_producers().entry<7>(), (const Gpio*)&PORTA_LINE8); + +// Similar syntax for the producers. +openlcb::ConfiguredProducer producer_b1( + stack.node(), cfg.seg().portab_producers().entry<8>(), (const Gpio*)&PORTB_LINE1); +openlcb::ConfiguredProducer producer_b2( + stack.node(), cfg.seg().portab_producers().entry<9>(), (const Gpio*)&PORTB_LINE2); +openlcb::ConfiguredProducer producer_b3( + stack.node(), cfg.seg().portab_producers().entry<10>(), (const Gpio*)&PORTB_LINE3); +openlcb::ConfiguredProducer producer_b4( + stack.node(), cfg.seg().portab_producers().entry<11>(), (const Gpio*)&PORTB_LINE4); +openlcb::ConfiguredProducer producer_b5( + stack.node(), cfg.seg().portab_producers().entry<12>(), (const Gpio*)&PORTB_LINE5); +openlcb::ConfiguredProducer producer_b6( + stack.node(), cfg.seg().portab_producers().entry<13>(), (const Gpio*)&PORTB_LINE6); +openlcb::ConfiguredProducer producer_b7( + stack.node(), cfg.seg().portab_producers().entry<14>(), (const Gpio*)&PORTB_LINE7); +openlcb::ConfiguredProducer producer_b8( + stack.node(), cfg.seg().portab_producers().entry<15>(), (const Gpio*)&PORTB_LINE8); + +#if NUM_EXTBOARDS == 1 + +MCP23017 exp0(&io_executor, 0, 0, 0); +MCP23017 exp1(&io_executor, 0, 0, 1); + +constexpr const MCP23017Gpio IOEXT0_A0(&exp0, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT0_A1(&exp0, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT0_A2(&exp0, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT0_A3(&exp0, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT0_A4(&exp0, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT0_A5(&exp0, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT0_A6(&exp0, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT0_A7(&exp0, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT0_B0(&exp0, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT0_B1(&exp0, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT0_B2(&exp0, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT0_B3(&exp0, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT0_B4(&exp0, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT0_B5(&exp0, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT0_B6(&exp0, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT0_B7(&exp0, MCP23017::PORTB, 7); + +constexpr const MCP23017Gpio IOEXT1_A0(&exp1, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT1_A1(&exp1, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT1_A2(&exp1, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT1_A3(&exp1, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT1_A4(&exp1, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT1_A5(&exp1, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT1_A6(&exp1, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT1_A7(&exp1, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT1_B0(&exp1, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT1_B1(&exp1, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT1_B2(&exp1, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT1_B3(&exp1, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT1_B4(&exp1, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT1_B5(&exp1, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT1_B6(&exp1, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT1_B7(&exp1, MCP23017::PORTB, 7); + +constexpr const Gpio *const kPortExt0[] = { + &IOEXT0_A0, &IOEXT0_A1, &IOEXT0_A2, &IOEXT0_A3, // + &IOEXT0_A4, &IOEXT0_A5, &IOEXT0_A6, &IOEXT0_A7, // + &IOEXT0_B0, &IOEXT0_B1, &IOEXT0_B2, &IOEXT0_B3, // + &IOEXT0_B4, &IOEXT0_B5, &IOEXT0_B6, &IOEXT0_B7, // + &IOEXT1_A0, &IOEXT1_A1, &IOEXT1_A2, &IOEXT1_A3, // + &IOEXT1_A4, &IOEXT1_A5, &IOEXT1_A6, &IOEXT1_A7, // + &IOEXT1_B0, &IOEXT1_B1, &IOEXT1_B2, &IOEXT1_B3, // + &IOEXT1_B4, &IOEXT1_B5, &IOEXT1_B6, &IOEXT1_B7 // +}; + +openlcb::MultiConfiguredPC ext0_pcs( + stack.node(), kPortExt0, ARRAYSIZE(kPortExt0), cfg.seg().ext0_pc()); + +#endif // if num extboards = 1 + +#if NUM_EXTBOARDS == 2 + +MCP23017 exp0(&io_executor, 0, 0, 0); +MCP23017 exp1(&io_executor, 0, 0, 1); +MCP23017 exp10(&io_executor, 0, 1, 0); +MCP23017 exp11(&io_executor, 0, 1, 1); + +constexpr const MCP23017Gpio IOEXT0_A0(&exp0, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT0_A1(&exp0, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT0_A2(&exp0, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT0_A3(&exp0, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT0_A4(&exp0, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT0_A5(&exp0, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT0_A6(&exp0, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT0_A7(&exp0, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT0_B0(&exp0, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT0_B1(&exp0, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT0_B2(&exp0, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT0_B3(&exp0, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT0_B4(&exp0, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT0_B5(&exp0, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT0_B6(&exp0, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT0_B7(&exp0, MCP23017::PORTB, 7); + +constexpr const MCP23017Gpio IOEXT1_A0(&exp1, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT1_A1(&exp1, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT1_A2(&exp1, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT1_A3(&exp1, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT1_A4(&exp1, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT1_A5(&exp1, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT1_A6(&exp1, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT1_A7(&exp1, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT1_B0(&exp1, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT1_B1(&exp1, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT1_B2(&exp1, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT1_B3(&exp1, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT1_B4(&exp1, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT1_B5(&exp1, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT1_B6(&exp1, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT1_B7(&exp1, MCP23017::PORTB, 7); + +constexpr const MCP23017Gpio IOEXT10_A0(&exp10, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT10_A1(&exp10, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT10_A2(&exp10, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT10_A3(&exp10, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT10_A4(&exp10, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT10_A5(&exp10, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT10_A6(&exp10, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT10_A7(&exp10, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT10_B0(&exp10, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT10_B1(&exp10, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT10_B2(&exp10, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT10_B3(&exp10, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT10_B4(&exp10, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT10_B5(&exp10, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT10_B6(&exp10, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT10_B7(&exp10, MCP23017::PORTB, 7); + + +constexpr const MCP23017Gpio IOEXT11_A0(&exp11, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT11_A1(&exp11, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT11_A2(&exp11, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT11_A3(&exp11, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT11_A4(&exp11, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT11_A5(&exp11, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT11_A6(&exp11, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT11_A7(&exp11, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT11_B0(&exp11, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT11_B1(&exp11, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT11_B2(&exp11, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT11_B3(&exp11, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT11_B4(&exp11, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT11_B5(&exp11, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT11_B6(&exp11, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT11_B7(&exp11, MCP23017::PORTB, 7); + + +constexpr const Gpio *const kPortExt0[] = { + &IOEXT0_A0, &IOEXT0_A1, &IOEXT0_A2, &IOEXT0_A3, // + &IOEXT0_A4, &IOEXT0_A5, &IOEXT0_A6, &IOEXT0_A7, // + &IOEXT0_B0, &IOEXT0_B1, &IOEXT0_B2, &IOEXT0_B3, // + &IOEXT0_B4, &IOEXT0_B5, &IOEXT0_B6, &IOEXT0_B7, // + &IOEXT1_A0, &IOEXT1_A1, &IOEXT1_A2, &IOEXT1_A3, // + &IOEXT1_A4, &IOEXT1_A5, &IOEXT1_A6, &IOEXT1_A7, // + &IOEXT1_B0, &IOEXT1_B1, &IOEXT1_B2, &IOEXT1_B3, // + &IOEXT1_B4, &IOEXT1_B5, &IOEXT1_B6, &IOEXT1_B7, // + &IOEXT10_A0, &IOEXT10_A1, &IOEXT10_A2, &IOEXT10_A3, // + &IOEXT10_A4, &IOEXT10_A5, &IOEXT10_A6, &IOEXT10_A7, // + &IOEXT10_B0, &IOEXT10_B1, &IOEXT10_B2, &IOEXT10_B3, // + &IOEXT10_B4, &IOEXT10_B5, &IOEXT10_B6, &IOEXT10_B7, // + &IOEXT11_A0, &IOEXT11_A1, &IOEXT11_A2, &IOEXT11_A3, // + &IOEXT11_A4, &IOEXT11_A5, &IOEXT11_A6, &IOEXT11_A7, // + &IOEXT11_B0, &IOEXT11_B1, &IOEXT11_B2, &IOEXT11_B3, // + &IOEXT11_B4, &IOEXT11_B5, &IOEXT11_B6, &IOEXT11_B7 // + + +}; + +openlcb::MultiConfiguredPC ext0_pcs( + stack.node(), kPortExt0, ARRAYSIZE(kPortExt0), cfg.seg().ext0_pc()); + +#endif // if NUM_EXTBOARDS = 2 + +openlcb::RefreshLoop loopab(stack.node(), + { +#if NUM_EXTBOARDS > 0 + ext0_pcs.polling(), // +#endif + producer_a1.polling(), producer_a2.polling(), // + producer_a3.polling(), producer_a4.polling(), // + producer_a5.polling(), producer_a6.polling(), // + producer_a7.polling(), producer_a8.polling(), // + producer_b1.polling(), producer_b2.polling(), // + producer_b3.polling(), producer_b4.polling(), // + producer_b5.polling(), producer_b6.polling(), // + producer_b7.polling(), producer_b8.polling(), // + }); + +/** Entry point to application. + * @param argc number of command line arguments + * @param argv array of command line arguments + * @return 0, should never return + */ +int appl_main(int argc, char *argv[]) +{ + stack.check_version_and_factory_reset( + cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false); + +#if NUM_EXTBOARDS == 1 + { + int i2cfd = ::open("/dev/i2c0", O_RDWR); + exp0.init(i2cfd); + exp1.init(i2cfd); + } +#elif NUM_EXTBOARDS == 2 + { + int i2cfd = ::open("/dev/i2c0", O_RDWR); + exp0.init(i2cfd); + exp1.init(i2cfd); + exp10.init(i2cfd); + exp11.init(i2cfd); + } +#endif + + internal_outputs.init("/dev/spi1.ioboard"); + internal_inputs.init("/dev/spi2"); + + // The necessary physical ports must be added to the stack. + // + // It is okay to enable multiple physical ports, in which case the stack + // will behave as a bridge between them. For example enabling both the + // physical CAN port and the USB port will make this firmware act as an + // USB-CAN adapter in addition to the producers/consumers created above. + // + // If a port is enabled, it must be functional or else the stack will + // freeze waiting for that port to send the packets out. +#if defined(HAVE_PHYSICAL_CAN_PORT) + stack.add_can_port_select("/dev/can0"); +#endif +#if defined(SNIFF_ON_USB) + stack.add_gridconnect_port("/dev/serUSB0"); +#endif +#if defined(SNIFF_ON_SERIAL) + stack.add_gridconnect_port("/dev/ser0"); +#endif + + // This command donates the main thread to the operation of the + // stack. Alternatively the stack could be started in a separate stack and + // then application-specific business logic could be executed ion a busy + // loop in the main thread. + stack.loop_executor(); + return 0; +} diff --git a/applications/ctcnode/memory_map.ld b/applications/ctcnode/memory_map.ld new file mode 100644 index 000000000..c716a83fe --- /dev/null +++ b/applications/ctcnode/memory_map.ld @@ -0,0 +1,15 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 216K + EEPROMEMU (r) : ORIGIN = 0x08034BC0, LENGTH = 32K + BOOTLOADER (rx) : ORIGIN = 0x0803C8C0, LENGTH = 8K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 48K +} + +__flash_start = ORIGIN(FLASH); +__flash_end = ORIGIN(FLASH) + LENGTH(FLASH); +__eeprom_start = ORIGIN(EEPROMEMU); +__eeprom_end = ORIGIN(EEPROMEMU) + LENGTH(EEPROMEMU); +__bootloader_start = ORIGIN(BOOTLOADER); +__app_header_offset = 0x270; +__bootloader_magic_ptr = ORIGIN(RAM); diff --git a/applications/ctcnode/startup.c b/applications/ctcnode/startup.c new file mode 120000 index 000000000..38a135da2 --- /dev/null +++ b/applications/ctcnode/startup.c @@ -0,0 +1 @@ +../../boards/st-stm32l4-generic/startup.c \ No newline at end of file diff --git a/applications/ctcnode/target.ld b/applications/ctcnode/target.ld new file mode 120000 index 000000000..df750f126 --- /dev/null +++ b/applications/ctcnode/target.ld @@ -0,0 +1 @@ +../../boards/st-stm32l4-generic/target.ld \ No newline at end of file From be09795c7de852ea923bb72fa638d57500748392 Mon Sep 17 00:00:00 2001 From: Rick Lull Date: Sun, 21 Jul 2024 08:55:51 -0400 Subject: [PATCH 2/4] updated initial signal state. CDI fixed for labels. --- applications/ctcnode/config.hxx | 14 ++++++++--- applications/ctcnode/main.cxx | 44 ++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/applications/ctcnode/config.hxx b/applications/ctcnode/config.hxx index 001a86c40..2f6621349 100644 --- a/applications/ctcnode/config.hxx +++ b/applications/ctcnode/config.hxx @@ -39,8 +39,10 @@ extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { using AllConsumers = RepeatedGroup; using AllProducers = RepeatedGroup; -using PortDEConsumers = RepeatedGroup; -using PortABProducers = RepeatedGroup; +using LeftConsumers = RepeatedGroup; +using RightConsumers = RepeatedGroup; +using LeftProducers = RepeatedGroup; +using RightProducers = RepeatedGroup; #if NUM_EXTBOARDS == 1 using Ext0PC = RepeatedGroup; @@ -67,8 +69,12 @@ CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128)); /// optional arguments list. CDI_GROUP_ENTRY(internal_config, InternalConfigData); CDI_GROUP_ENTRY(nucleo_onboard, NucleoGroup); -CDI_GROUP_ENTRY(portde_consumers, PortDEConsumers, Name("Column Indicators"), Description("Line 1-8 is Left Column, Line 9-16 is Right Column"), RepName("Indicator")); -CDI_GROUP_ENTRY(portab_producers, PortABProducers, Name("Column Levers and Buttons"), Description("Line 1-8 is Left Column, Line 9-16 is Right Column"), RepName("Lever Or Button")); +CDI_GROUP_ENTRY(left_consumers, LeftConsumers, Name("Left Column Indicators"), Description("Indicator Order: 3 Traffic - Left, Center, Right; 3 Model Board; Turnout Thrown, Turnout Normal"), RepName("Indicator")); +CDI_GROUP_ENTRY(left_producers, RightProducers, Name("Left Column Levers and Buttons"), Description("Button and Lever Order: Code, Call On, Maintaner Call; Switch Lever Normal, Switch Lever Reverse; Traffic Lever Left, Center, Right"), RepName("Lever Or Button")); +CDI_GROUP_ENTRY(right_consumers, LeftConsumers, Name("Right Column Indicators"), Description("Indicator Order: 3 Traffic - Left, Center, Right; 3 Model Board; Turnout Thrown, Turnout Normal"), RepName("Indicator")); +CDI_GROUP_ENTRY(right_producers, RightProducers, Name("Right Column Levers and Buttons"), Description("Button and Lever Order: Code, Call On, Maintaner Call; Switch Lever Normal, Switch Lever Reverse; Traffic Lever Left, Center, Right"), RepName("Lever Or Button")); + + #if NUM_EXTBOARDS > 0 CDI_GROUP_ENTRY(ext0_pc, Ext0PC, Name("Expansion board 0 lines"), Description("Line 1-8 is port Even/A, Line 9-16 is port Even/B, Line 17-24 " diff --git a/applications/ctcnode/main.cxx b/applications/ctcnode/main.cxx index 506cfa51c..441489a09 100644 --- a/applications/ctcnode/main.cxx +++ b/applications/ctcnode/main.cxx @@ -301,16 +301,20 @@ constexpr const MmapGpio PORTE_LINE7(output_register, 9, true); constexpr const MmapGpio PORTE_LINE8(output_register, 8, true); -constexpr const Gpio *const kPortDEGpio[] = { +constexpr const Gpio *const kPortDGpio[] = { &PORTD_LINE1, &PORTD_LINE2, &PORTD_LINE3, &PORTD_LINE4, // &PORTD_LINE5, &PORTD_LINE6, &PORTD_LINE7, &PORTD_LINE8, // +}; + +constexpr const Gpio *const kPortEGpio[] = { &PORTE_LINE1, &PORTE_LINE2, &PORTE_LINE3, &PORTE_LINE4, // &PORTE_LINE5, &PORTE_LINE6, &PORTE_LINE7, &PORTE_LINE8 // }; -openlcb::MultiConfiguredConsumer portde_consumers(stack.node(), kPortDEGpio, - ARRAYSIZE(kPortDEGpio), cfg.seg().portde_consumers()); - +openlcb::MultiConfiguredConsumer left_consumers(stack.node(), kPortDGpio, + ARRAYSIZE(kPortDGpio), cfg.seg().left_consumers()); +openlcb::MultiConfiguredConsumer right_consumers(stack.node(), kPortEGpio, + ARRAYSIZE(kPortDGpio), cfg.seg().right_consumers()); uint32_t input_register[2] = {0}; @@ -336,39 +340,39 @@ constexpr const MmapGpio PORTA_LINE8(input_register, 15, false); // Similar syntax for the producers. openlcb::ConfiguredProducer producer_a1( - stack.node(), cfg.seg().portab_producers().entry<0>(), (const Gpio*)&PORTA_LINE1); + stack.node(), cfg.seg().left_producers().entry<0>(), (const Gpio*)&PORTA_LINE1); openlcb::ConfiguredProducer producer_a2( - stack.node(), cfg.seg().portab_producers().entry<1>(), (const Gpio*)&PORTA_LINE2); + stack.node(), cfg.seg().left_producers().entry<1>(), (const Gpio*)&PORTA_LINE2); openlcb::ConfiguredProducer producer_a3( - stack.node(), cfg.seg().portab_producers().entry<2>(), (const Gpio*)&PORTA_LINE3); + stack.node(), cfg.seg().left_producers().entry<2>(), (const Gpio*)&PORTA_LINE3); openlcb::ConfiguredProducer producer_a4( - stack.node(), cfg.seg().portab_producers().entry<3>(), (const Gpio*)&PORTA_LINE4); + stack.node(), cfg.seg().left_producers().entry<3>(), (const Gpio*)&PORTA_LINE4); openlcb::ConfiguredProducer producer_a5( - stack.node(), cfg.seg().portab_producers().entry<4>(), (const Gpio*)&PORTA_LINE5); + stack.node(), cfg.seg().left_producers().entry<4>(), (const Gpio*)&PORTA_LINE5); openlcb::ConfiguredProducer producer_a6( - stack.node(), cfg.seg().portab_producers().entry<5>(), (const Gpio*)&PORTA_LINE6); + stack.node(), cfg.seg().left_producers().entry<5>(), (const Gpio*)&PORTA_LINE6); openlcb::ConfiguredProducer producer_a7( - stack.node(), cfg.seg().portab_producers().entry<6>(), (const Gpio*)&PORTA_LINE7); + stack.node(), cfg.seg().left_producers().entry<6>(), (const Gpio*)&PORTA_LINE7); openlcb::ConfiguredProducer producer_a8( - stack.node(), cfg.seg().portab_producers().entry<7>(), (const Gpio*)&PORTA_LINE8); + stack.node(), cfg.seg().left_producers().entry<7>(), (const Gpio*)&PORTA_LINE8); // Similar syntax for the producers. openlcb::ConfiguredProducer producer_b1( - stack.node(), cfg.seg().portab_producers().entry<8>(), (const Gpio*)&PORTB_LINE1); + stack.node(), cfg.seg().right_producers().entry<0>(), (const Gpio*)&PORTB_LINE1); openlcb::ConfiguredProducer producer_b2( - stack.node(), cfg.seg().portab_producers().entry<9>(), (const Gpio*)&PORTB_LINE2); + stack.node(), cfg.seg().right_producers().entry<1>(), (const Gpio*)&PORTB_LINE2); openlcb::ConfiguredProducer producer_b3( - stack.node(), cfg.seg().portab_producers().entry<10>(), (const Gpio*)&PORTB_LINE3); + stack.node(), cfg.seg().right_producers().entry<2>(), (const Gpio*)&PORTB_LINE3); openlcb::ConfiguredProducer producer_b4( - stack.node(), cfg.seg().portab_producers().entry<11>(), (const Gpio*)&PORTB_LINE4); + stack.node(), cfg.seg().right_producers().entry<3>(), (const Gpio*)&PORTB_LINE4); openlcb::ConfiguredProducer producer_b5( - stack.node(), cfg.seg().portab_producers().entry<12>(), (const Gpio*)&PORTB_LINE5); + stack.node(), cfg.seg().right_producers().entry<4>(), (const Gpio*)&PORTB_LINE5); openlcb::ConfiguredProducer producer_b6( - stack.node(), cfg.seg().portab_producers().entry<13>(), (const Gpio*)&PORTB_LINE6); + stack.node(), cfg.seg().right_producers().entry<5>(), (const Gpio*)&PORTB_LINE6); openlcb::ConfiguredProducer producer_b7( - stack.node(), cfg.seg().portab_producers().entry<14>(), (const Gpio*)&PORTB_LINE7); + stack.node(), cfg.seg().right_producers().entry<6>(), (const Gpio*)&PORTB_LINE7); openlcb::ConfiguredProducer producer_b8( - stack.node(), cfg.seg().portab_producers().entry<15>(), (const Gpio*)&PORTB_LINE8); + stack.node(), cfg.seg().right_producers().entry<7>(), (const Gpio*)&PORTB_LINE8); #if NUM_EXTBOARDS == 1 From e0dde7bda0efcda0757a455f7637058f217549ad Mon Sep 17 00:00:00 2001 From: Rick Lull Date: Sun, 21 Jul 2024 13:04:31 -0400 Subject: [PATCH 3/4] Working control panel node. Seperate lamp control; no head funtionality. Lamps come up red as default. Baseline functionality and .2 release candidate. --- applications/controlpanelnode/config.hxx | 116 ++++ applications/controlpanelnode/main.cxx | 653 +++++++++++++++++++++++ 2 files changed, 769 insertions(+) create mode 100644 applications/controlpanelnode/config.hxx create mode 100644 applications/controlpanelnode/main.cxx diff --git a/applications/controlpanelnode/config.hxx b/applications/controlpanelnode/config.hxx new file mode 100644 index 000000000..8cf9da03b --- /dev/null +++ b/applications/controlpanelnode/config.hxx @@ -0,0 +1,116 @@ +#ifndef _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ +#define _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ + +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/ConfiguredConsumer.hxx" +//#include "openlcb/RYGConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "openlcb/MultiConfiguredPC.hxx" +#include "openlcb/ServoConsumerConfig.hxx" + +namespace openlcb +{ + +/// Defines the identification information for the node. The arguments are: +/// +/// - 4 (version info, always 4 by the standard +/// - Manufacturer name +/// - Model name +/// - Hardware version +/// - Software version +/// +/// This data will be used for all purposes of the identification: +/// +/// - the generated cdi.xml will include this data +/// - the Simple Node Ident Info Protocol will return this data +/// - the ACDI memory space will contain this data. +extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { + 4, "LCCSignals.com", "Control Panel Node", + "Rev A", ".2"}; + +#define NUM_OUTPUTS 16 +#define NUM_INPUTS 1 +#define NUM_EXTBOARDS 0 + +/// Declares a repeated group of a given base group and number of repeats. The +/// ProducerConfig and ConsumerConfig groups represent the configuration layout +/// needed by the ConfiguredProducer and ConfiguredConsumer classes, and come +/// from their respective hxx file. +using AllConsumers = RepeatedGroup; +using AllProducers = RepeatedGroup; + +using SignalConsumers = RepeatedGroup; +//using SignalConsumers = RepeatedGroup; +using FrontProducers = RepeatedGroup; + +//using HeadConsumers = RepeatedGroup; + +/// Modify this value every time the EEPROM needs to be cleared on the node +/// after an update. +static constexpr uint16_t CANONICAL_VERSION = 0x1188; + +CDI_GROUP(NucleoGroup, Name("Back of Node"), Description("These are physically located on the back of the node. Useful for testing/validation.")); +CDI_GROUP_ENTRY(blue_led, ConsumerConfig, Name("Blue LED"), Description("Blue LED (D?).")); +CDI_GROUP_ENTRY(gold_led, ConsumerConfig, Name("Yellow LED"), Description("Yellow LED (D?).")); +CDI_GROUP_ENTRY(blue_btn, ProducerConfig, Name("Blue Button"), Description("Button with blue cap.")); +CDI_GROUP_ENTRY(gold_btn, ProducerConfig, Name("Gold Button"), Description("Button with yellow cap.")); +CDI_GROUP_END(); +CDI_GROUP(FrontPanelLEDGroup, Name("Front Indicators"), Description("These are the LEDs above the buttons.")); +CDI_GROUP_ENTRY(a0_led, ConsumerConfig, Name("A0 Green LED"), Description("Leftmost Front LED")); +CDI_GROUP_ENTRY(a1_led, ConsumerConfig, Name("A1 Red LED"), Description("Front LED")); +CDI_GROUP_ENTRY(a2_led, ConsumerConfig, Name("A2 Green LED"), Description("Leftmost Front LED")); +CDI_GROUP_ENTRY(a3_led, ConsumerConfig, Name("A3 Red LED"), Description("Front LED")); +CDI_GROUP_ENTRY(a4_led, ConsumerConfig, Name("A4 Green LED"), Description("Leftmost Front LED")); +CDI_GROUP_ENTRY(a5_led, ConsumerConfig, Name("A5 Red LED"), Description("Front LED")); +CDI_GROUP_ENTRY(a6_led, ConsumerConfig, Name("A6 Green LED"), Description("Leftmost Front LED")); +CDI_GROUP_ENTRY(a7_led, ConsumerConfig, Name("A7 Red LED"), Description("Front LED")); +CDI_GROUP_END(); + +/// Defines the main segment in the configuration CDI. This is laid out at +/// origin 128 to give space for the ACDI user data at the beginning. +CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128)); +/// Each entry declares the name of the current entry, then the type and then +/// optional arguments list. +CDI_GROUP_ENTRY(internal_config, InternalConfigData); +CDI_GROUP_ENTRY(front_panel, FrontPanelLEDGroup); +CDI_GROUP_ENTRY(signal_consumers, SignalConsumers, Name("Signals"), Description("Searchlight Signals"), RepName("Lamp")); +//CDI_GROUP_ENTRY(head_consumers, HeadConsumers, Name("Signal Heads"), Description("Searchlight Head"), RepName("Head")); +CDI_GROUP_ENTRY(right_producers, FrontProducers, Name("Switch Control Buttons"), Description("Buttons on Front for Switch Control"), RepName("Button")); +CDI_GROUP_ENTRY(back_board, NucleoGroup); + +#if NUM_EXTBOARDS > 0 +CDI_GROUP_ENTRY(ext0_pc, Ext0PC, Name("Expansion board 0 lines"), + Description("Line 1-8 is port Even/A, Line 9-16 is port Even/B, Line 17-24 " + "is Odd/A, Line 25-32 is Odd/B"), + RepName("Line")); +#endif +CDI_GROUP_END(); + +/// This segment is only needed temporarily until there is program code to set +/// the ACDI user data version byte. +CDI_GROUP(VersionSeg, Segment(MemoryConfigDefs::SPACE_CONFIG), + Name("Version information")); +CDI_GROUP_ENTRY(acdi_user_version, Uint8ConfigEntry, + Name("ACDI User Data version"), Description("Set to 2 and do not change.")); +CDI_GROUP_END(); + +/// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx +/// to refer to the configuration defined here. +CDI_GROUP(ConfigDef, MainCdi()); +/// Adds the tag with the values from SNIP_STATIC_DATA above. +CDI_GROUP_ENTRY(ident, Identification); +/// Adds an tag. +CDI_GROUP_ENTRY(acdi, Acdi); +/// Adds a segment for changing the values in the ACDI user-defined +/// space. UserInfoSegment is defined in the system header. +CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +/// Adds the main configuration segment. +CDI_GROUP_ENTRY(seg, IoBoardSegment); +/// Adds the versioning segment. +CDI_GROUP_ENTRY(version, VersionSeg); +CDI_GROUP_END(); + +} // namespace openlcb + +#endif // _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ diff --git a/applications/controlpanelnode/main.cxx b/applications/controlpanelnode/main.cxx new file mode 100644 index 000000000..d4c57e053 --- /dev/null +++ b/applications/controlpanelnode/main.cxx @@ -0,0 +1,653 @@ +/** \copyright + * Copyright (c) 2024, Rick Lull + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file main.cxx + * + * Control Panel Node + * + * @author Rick Lull + * @date 7 July 2024 + */ + +#define _DEFAULT_SOURCE + +#include + +#include "os/os.h" +#include "nmranet_config.h" + +#include "openlcb/SimpleStack.hxx" +#include "openlcb/MultiConfiguredConsumer.hxx" +//#include "openlcb/RYGConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" + +#include "freertos_drivers/st/Stm32Gpio.hxx" +#include "freertos_drivers/common/BlinkerGPIO.hxx" +#include "freertos_drivers/common/DummyGPIO.hxx" +#include "freertos_drivers/common/MCP23017Gpio.hxx" +#include "os/MmapGpio.hxx" +#include "config.hxx" +#include "hardware.hxx" +#include "PWM.hxx" +//#include "i2c.h" +//#include "i2c-dev.h" + +// These preprocessor symbols are used to select which physical connections +// will be enabled in the main(). See @ref appl_main below. +//#define SNIFF_ON_SERIAL +//#define SNIFF_ON_USB +#define HAVE_PHYSICAL_CAN_PORT + +// Changes the default behavior by adding a newline after each gridconnect +// packet. Makes it easier for debugging the raw device. +OVERRIDE_CONST(gc_generate_newlines, 1); +// Specifies how much RAM (in bytes) we allocate to the stack of the main +// thread. Useful tuning parameter in case the application runs out of memory. +OVERRIDE_CONST(main_thread_stack_size, 2500); + +// Specifies the 48-bit OpenLCB node identifier. This must be unique for every +// hardware manufactured, so in production this should be replaced by some +// easily incrementable method. +extern const openlcb::NodeID NODE_ID; + +// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack +// contains everything needed for a usual peripheral node -- all +// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration +// protocol, ACDI, CDI, a bunch of memory spaces, etc. +openlcb::SimpleCanStack stack(NODE_ID); + +// ConfigDef comes from config.hxx and is specific to the particular device and +// target. It defines the layout of the configuration memory space and is also +// used to generate the cdi.xml file. Here we instantiate the configuration +// layout. The argument of offset zero is ignored and will be removed later. +openlcb::ConfigDef cfg(0); +// Defines weak constants used by the stack to tell it which device contains +// the volatile configuration information. This device name appears in +// HwInit.cxx that creates the device drivers. +extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom"; +// The size of the memory space to export over the above device. +extern const size_t openlcb::CONFIG_FILE_SIZE = + cfg.seg().size() + cfg.seg().offset(); +static_assert(openlcb::CONFIG_FILE_SIZE <= 8192, "Need to adjust eeprom size"); + +// The SNIP user-changeable information in also stored in the above eeprom +// device. In general this could come from different eeprom segments, but it is +// simpler to keep them together. +extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::CONFIG_FILENAME; + +// Instantiates the actual producer and consumer objects for the given GPIO +// pins from above. The ConfiguredConsumer class takes care of most of the +// complicated setup and operation requirements. We need to give it the virtual +// node pointer, the configuration configuration from the CDI definition, and +// the hardware pin definition. The virtual node pointer comes from the stack +// object. The configuration structure comes from the CDI definition object, +// segment 'seg', in which there is a repeated group 'consumers', and we assign +// the individual entries to the individual consumers. Each consumer gets its +// own GPIO pin. +openlcb::ConfiguredConsumer consumer_gold( + stack.node(), cfg.seg().back_board().gold_led(), LED_Gold_Pin()); +openlcb::ConfiguredConsumer consumer_blue( + stack.node(), cfg.seg().back_board().blue_led(), LED_Blue_Pin()); + +// Similar syntax for the producers. +openlcb::ConfiguredProducer producer_blue( + stack.node(), cfg.seg().back_board().gold_btn(), GOLD_Btn_Pin()); +openlcb::ConfiguredProducer producer_gold( + stack.node(), cfg.seg().back_board().blue_btn(), BLUE_Btn_Pin()); + +// Consumers for the A Port - directly connected LEDs on A0 through A7 +openlcb::ConfiguredConsumer consumer_a0( + stack.node(), cfg.seg().front_panel().a0_led(), LED_A0_Pin()); +openlcb::ConfiguredConsumer consumer_a1( + stack.node(), cfg.seg().front_panel().a1_led(), LED_A1_Pin()); +openlcb::ConfiguredConsumer consumer_a2( + stack.node(), cfg.seg().front_panel().a2_led(), LED_A2_Pin()); +openlcb::ConfiguredConsumer consumer_a3( + stack.node(), cfg.seg().front_panel().a3_led(), LED_A3_Pin()); +openlcb::ConfiguredConsumer consumer_a4( + stack.node(), cfg.seg().front_panel().a4_led(), LED_A4_Pin()); +openlcb::ConfiguredConsumer consumer_a5( + stack.node(), cfg.seg().front_panel().a5_led(), LED_A5_Pin()); +openlcb::ConfiguredConsumer consumer_a6( + stack.node(), cfg.seg().front_panel().a6_led(), LED_A6_Pin()); +openlcb::ConfiguredConsumer consumer_a7( + stack.node(), cfg.seg().front_panel().a7_led(), LED_A7_Pin()); + + +// The producers need to be polled repeatedly for changes and to execute the +// debouncing algorithm. This class instantiates a refreshloop and adds the two +// producers to it. +openlcb::RefreshLoop loop( + stack.node(), {producer_blue.polling(), producer_gold.polling()}); + +class FactoryResetHelper : public DefaultConfigUpdateListener { +public: + UpdateAction apply_configuration(int fd, bool initial_load, + BarrierNotifiable *done) OVERRIDE { + AutoNotify n(done); + return UPDATED; + } + + void factory_reset(int fd) override + { + cfg.userinfo().name().write(fd, "Control Panel Node"); + cfg.userinfo().description().write( + fd, "LCCSignals.com Control Panel Node"); + } +} factory_reset_helper; + +/// Input/output handler for shift register based IO expansion. Periodically +/// mirrors bitmaps from memory to and from the external shift registers, +/// manipulating a latch GPIO appropriately. Uses a SPI port device. There is +/// no limit on the number of shift registers daisy chained. The input and +/// output shift registers should have independent daisy chains. It is okay if +/// there is no CS wired up for the shift registers as the latch operation will +/// cause all unrelated transfers to be ignored by the shift registers.. +/// +/// This state flow is blocking the executor during the SPI transfers and +/// should not be used on the main executor. +class SpiIOShiftRegister : public StateFlowBase +{ +public: + /// Constructor. + /// + /// @param dedicated_executor is a serivce that is NOT on the main + /// executor. + /// @param mutex if not null, this (async) mutex will be acquired before + /// the SPI port is touched. Allows mutual exclusion of multiple stateflows + /// of this class. + /// @param latch is the IO pin to operate the latch on the shift + /// registers. The expectation is that output shift registers latch on the + /// low to high edge of this IO, and input shift registers are shifting + /// while high and loading on clocks while low. This matches the 74*595 and + /// 74*166 series' behavior. + /// @param output_storage bit array of the data to output to the shift + /// registers. If nullptr, no output shift registers will be operated. Must + /// be aligned to word boundary, but the individual bytes will be output in + /// address-increasing order, i.e. the first byte to be output is the byte + /// pointed by output_storage. (This is LSB-first on little endian + /// architectures.) + /// @param output_len_bytes how many 8-bit shift registers are daisy + /// chained on the output. + /// @param input_storage bit array where to put the data input from the + /// shift registers. If nullptr, no input shift registers will be + /// operated. Must be aligned to word boundary, but the individual bytes + /// address-increasing order, i.e. the first byte to be output is the byte + /// pointed by output_storage. (This is LSB-first on little endian + /// architectures.) + /// @param input_len_bytes how many 8-bit shift registers are daisy + /// chained on the input. + /// @param delay_msec defines the delay between consecutive refreshes of + /// the shift registers (inversely the refresh rate). + SpiIOShiftRegister(Service *dedicated_executor, + AsyncMutex *mutex, const Gpio *latch, uint32_t *output_storage, + unsigned output_len_bytes, uint32_t *input_storage = nullptr, + unsigned input_len_bytes = 0, unsigned delay_msec = 50) + : StateFlowBase(dedicated_executor) + , asyncMutex_(mutex) + , latch_(latch) + , delayMsec_(delay_msec) + , outputStorage_(output_storage) + , outputLenBytes_(output_len_bytes) + , inputStorage_(input_storage) + , inputLenBytes_(input_len_bytes) + {} + + /// Initializes SPI port. Called via main() to not rely on static init ordering. + /// @param port is the character device name for the SPI port + void init(const char *port) { + fd_ = ::open(port, O_RDWR); + HASSERT(fd_ >= 0); + // test alignment + HASSERT((((uintptr_t)outputStorage_) & 3) == 0); + HASSERT((((uintptr_t)inputStorage_) & 3) == 0); + start_flow(STATE(wait_delay)); + } + + /// Executes a refresh cycle right now. + void trigger() { + timer_.ensure_triggered(); + } + +private: + Action wait_delay() + { + return sleep_and_call( + &timer_, MSEC_TO_NSEC(delayMsec_), STATE(refresh)); + } + + Action refresh() + { + if (asyncMutex_) { + return allocate_and_call(STATE(locked), asyncMutex_); + } else { + return call_immediately(STATE(locked)); + } + } + + Action locked() { + latch_->write(Gpio::CLR); + if (outputStorage_) { + auto rb = ::write(fd_, outputStorage_, outputLenBytes_); + HASSERT((size_t)rb == outputLenBytes_); + } else { + // need to get some clock cycles while lat==low to load the input + // shift register + uint8_t d = 0; + ::write(fd_, &d, 1); + latch_->write(Gpio::CLR); + } + usleep(2); + latch_->write(Gpio::SET); + if (inputStorage_) { + auto rb = ::read(fd_, inputStorage_, inputLenBytes_); + HASSERT((size_t)rb == inputLenBytes_); + } + usleep(2); + latch_->write(Gpio::CLR); + if (asyncMutex_) { + asyncMutex_->Unlock(); + } + return call_immediately(STATE(wait_delay)); + } + + /// Helper structure. + StateFlowTimer timer_{this}; + /// File descriptor for the SPI port. + int fd_; + /// Async mutex if the SPI port needs it + AsyncMutex* asyncMutex_; + /// Latch to trigger activating the output. + const Gpio* latch_; + /// How much time to sleep between two refreshes. + unsigned delayMsec_; + /// Bit stream of the data to be written. This is purposefully aligned. + uint32_t* outputStorage_; + /// How many bytes of data to shift out. + unsigned outputLenBytes_; + /// Bit stream of the data to be read. This is purposefully aligned. + uint32_t* inputStorage_; + /// How many bytes of data to shift in. + unsigned inputLenBytes_; +}; + +Executor<1> io_executor("io_thread", 1, 1300); +Service io_service(&io_executor); + +uint32_t output_register[1] = {0x00B676DB}; + +SpiIOShiftRegister internal_outputs(&io_service, nullptr, OUT_LAT_Pin::instance(), output_register, 3); + +constexpr const MmapGpio PORTD_LINE1(output_register, 7, true); +constexpr const MmapGpio PORTD_LINE2(output_register, 6, true); +constexpr const MmapGpio PORTD_LINE3(output_register, 5, true); +constexpr const MmapGpio PORTD_LINE4(output_register, 4, true); +constexpr const MmapGpio PORTD_LINE5(output_register, 3, true); +constexpr const MmapGpio PORTD_LINE6(output_register, 2, true); +constexpr const MmapGpio PORTD_LINE7(output_register, 1, true); +constexpr const MmapGpio PORTD_LINE8(output_register, 0, true); + +constexpr const MmapGpio PORTE_LINE1(output_register, 15, true); +constexpr const MmapGpio PORTE_LINE2(output_register, 14, true); +constexpr const MmapGpio PORTE_LINE3(output_register, 13, true); +constexpr const MmapGpio PORTE_LINE4(output_register, 12, true); +constexpr const MmapGpio PORTE_LINE5(output_register, 11, true); +constexpr const MmapGpio PORTE_LINE6(output_register, 10, true); +constexpr const MmapGpio PORTE_LINE7(output_register, 9, true); +constexpr const MmapGpio PORTE_LINE8(output_register, 8, true); + +constexpr const MmapGpio PORTF_LINE1(output_register, 23, true); +constexpr const MmapGpio PORTF_LINE2(output_register, 22, true); +constexpr const MmapGpio PORTF_LINE3(output_register, 21, true); +constexpr const MmapGpio PORTF_LINE4(output_register, 20, true); +constexpr const MmapGpio PORTF_LINE5(output_register, 19, true); +constexpr const MmapGpio PORTF_LINE6(output_register, 18, true); +constexpr const MmapGpio PORTF_LINE7(output_register, 17, true); +constexpr const MmapGpio PORTF_LINE8(output_register, 16, true); + +constexpr const Gpio *const kPortDEGpio[] = { + &PORTD_LINE1, &PORTD_LINE2, &PORTD_LINE3, &PORTD_LINE4, // + &PORTD_LINE5, &PORTD_LINE6, &PORTD_LINE7, &PORTD_LINE8, // + &PORTE_LINE1, &PORTE_LINE2, &PORTE_LINE3, &PORTE_LINE4, // + &PORTE_LINE5, &PORTE_LINE6, &PORTE_LINE7, &PORTE_LINE8, // + &PORTF_LINE1, &PORTF_LINE2, &PORTF_LINE3, &PORTF_LINE4, // + &PORTF_LINE5, &PORTF_LINE6, &PORTF_LINE7, &PORTF_LINE8 // +}; + +/* constexpr const Gpio *const kPortEFGpio[] = { + &PORTE_LINE5, &PORTE_LINE6, &PORTE_LINE7, &PORTE_LINE8, // + &PORTF_LINE1, &PORTF_LINE2, &PORTF_LINE3, &PORTF_LINE4, // + &PORTF_LINE5, &PORTF_LINE6, &PORTF_LINE7, &PORTF_LINE8 // +}; +*/ + +openlcb::MultiConfiguredConsumer signal_consumers(stack.node(), kPortDEGpio, + ARRAYSIZE(kPortDEGpio), cfg.seg().signal_consumers()); +//openlcb::RYGConfiguredConsumer head_consumers(stack.node(), kPortEFGpio, +// ARRAYSIZE(kPortEFGpio), cfg.seg().head_consumers()); + +uint32_t input_register[2] = {0}; + +SpiIOShiftRegister internal_inputs(&io_service, nullptr, INP_LAT_Pin::instance(), nullptr, 0, input_register, 1); + +constexpr const MmapGpio PORTB_LINE1(input_register, 0, false); +constexpr const MmapGpio PORTB_LINE2(input_register, 1, false); +constexpr const MmapGpio PORTB_LINE3(input_register, 2, false); +constexpr const MmapGpio PORTB_LINE4(input_register, 3, false); +constexpr const MmapGpio PORTB_LINE5(input_register, 4, false); +constexpr const MmapGpio PORTB_LINE6(input_register, 5, false); +constexpr const MmapGpio PORTB_LINE7(input_register, 6, false); +constexpr const MmapGpio PORTB_LINE8(input_register, 7, false); + +/* +constexpr const MmapGpio PORTA_LINE1(input_register, 8, false); +constexpr const MmapGpio PORTA_LINE2(input_register, 9, false); +constexpr const MmapGpio PORTA_LINE3(input_register, 10, false); +constexpr const MmapGpio PORTA_LINE4(input_register, 11, false); +constexpr const MmapGpio PORTA_LINE5(input_register, 12, false); +constexpr const MmapGpio PORTA_LINE6(input_register, 13, false); +constexpr const MmapGpio PORTA_LINE7(input_register, 14, false); +constexpr const MmapGpio PORTA_LINE8(input_register, 15, false); +*/ + +// Similar syntax for the producers. +/* +openlcb::ConfiguredProducer producer_a1( + stack.node(), cfg.seg().left_producers().entry<0>(), (const Gpio*)&PORTA_LINE1); +openlcb::ConfiguredProducer producer_a2( + stack.node(), cfg.seg().left_producers().entry<1>(), (const Gpio*)&PORTA_LINE2); +openlcb::ConfiguredProducer producer_a3( + stack.node(), cfg.seg().left_producers().entry<2>(), (const Gpio*)&PORTA_LINE3); +openlcb::ConfiguredProducer producer_a4( + stack.node(), cfg.seg().left_producers().entry<3>(), (const Gpio*)&PORTA_LINE4); +openlcb::ConfiguredProducer producer_a5( + stack.node(), cfg.seg().left_producers().entry<4>(), (const Gpio*)&PORTA_LINE5); +openlcb::ConfiguredProducer producer_a6( + stack.node(), cfg.seg().left_producers().entry<5>(), (const Gpio*)&PORTA_LINE6); +openlcb::ConfiguredProducer producer_a7( + stack.node(), cfg.seg().left_producers().entry<6>(), (const Gpio*)&PORTA_LINE7); +openlcb::ConfiguredProducer producer_a8( + stack.node(), cfg.seg().left_producers().entry<7>(), (const Gpio*)&PORTA_LINE8); +*/ + +// Similar syntax for the producers. +openlcb::ConfiguredProducer producer_b1( + stack.node(), cfg.seg().right_producers().entry<0>(), (const Gpio*)&PORTB_LINE1); +openlcb::ConfiguredProducer producer_b2( + stack.node(), cfg.seg().right_producers().entry<1>(), (const Gpio*)&PORTB_LINE2); +openlcb::ConfiguredProducer producer_b3( + stack.node(), cfg.seg().right_producers().entry<2>(), (const Gpio*)&PORTB_LINE3); +openlcb::ConfiguredProducer producer_b4( + stack.node(), cfg.seg().right_producers().entry<3>(), (const Gpio*)&PORTB_LINE4); +openlcb::ConfiguredProducer producer_b5( + stack.node(), cfg.seg().right_producers().entry<4>(), (const Gpio*)&PORTB_LINE5); +openlcb::ConfiguredProducer producer_b6( + stack.node(), cfg.seg().right_producers().entry<5>(), (const Gpio*)&PORTB_LINE6); +openlcb::ConfiguredProducer producer_b7( + stack.node(), cfg.seg().right_producers().entry<6>(), (const Gpio*)&PORTB_LINE7); +openlcb::ConfiguredProducer producer_b8( + stack.node(), cfg.seg().right_producers().entry<7>(), (const Gpio*)&PORTB_LINE8); + +#if NUM_EXTBOARDS == 1 + +MCP23017 exp0(&io_executor, 0, 0, 0); +MCP23017 exp1(&io_executor, 0, 0, 1); + +constexpr const MCP23017Gpio IOEXT0_A0(&exp0, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT0_A1(&exp0, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT0_A2(&exp0, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT0_A3(&exp0, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT0_A4(&exp0, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT0_A5(&exp0, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT0_A6(&exp0, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT0_A7(&exp0, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT0_B0(&exp0, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT0_B1(&exp0, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT0_B2(&exp0, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT0_B3(&exp0, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT0_B4(&exp0, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT0_B5(&exp0, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT0_B6(&exp0, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT0_B7(&exp0, MCP23017::PORTB, 7); + +constexpr const MCP23017Gpio IOEXT1_A0(&exp1, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT1_A1(&exp1, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT1_A2(&exp1, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT1_A3(&exp1, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT1_A4(&exp1, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT1_A5(&exp1, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT1_A6(&exp1, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT1_A7(&exp1, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT1_B0(&exp1, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT1_B1(&exp1, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT1_B2(&exp1, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT1_B3(&exp1, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT1_B4(&exp1, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT1_B5(&exp1, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT1_B6(&exp1, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT1_B7(&exp1, MCP23017::PORTB, 7); + +constexpr const Gpio *const kPortExt0[] = { + &IOEXT0_A0, &IOEXT0_A1, &IOEXT0_A2, &IOEXT0_A3, // + &IOEXT0_A4, &IOEXT0_A5, &IOEXT0_A6, &IOEXT0_A7, // + &IOEXT0_B0, &IOEXT0_B1, &IOEXT0_B2, &IOEXT0_B3, // + &IOEXT0_B4, &IOEXT0_B5, &IOEXT0_B6, &IOEXT0_B7, // + &IOEXT1_A0, &IOEXT1_A1, &IOEXT1_A2, &IOEXT1_A3, // + &IOEXT1_A4, &IOEXT1_A5, &IOEXT1_A6, &IOEXT1_A7, // + &IOEXT1_B0, &IOEXT1_B1, &IOEXT1_B2, &IOEXT1_B3, // + &IOEXT1_B4, &IOEXT1_B5, &IOEXT1_B6, &IOEXT1_B7 // +}; + +openlcb::MultiConfiguredPC ext0_pcs( + stack.node(), kPortExt0, ARRAYSIZE(kPortExt0), cfg.seg().ext0_pc()); + +#endif // if num extboards = 1 + +#if NUM_EXTBOARDS == 2 + +MCP23017 exp0(&io_executor, 0, 0, 0); +MCP23017 exp1(&io_executor, 0, 0, 1); +MCP23017 exp10(&io_executor, 0, 1, 0); +MCP23017 exp11(&io_executor, 0, 1, 1); + +constexpr const MCP23017Gpio IOEXT0_A0(&exp0, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT0_A1(&exp0, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT0_A2(&exp0, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT0_A3(&exp0, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT0_A4(&exp0, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT0_A5(&exp0, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT0_A6(&exp0, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT0_A7(&exp0, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT0_B0(&exp0, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT0_B1(&exp0, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT0_B2(&exp0, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT0_B3(&exp0, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT0_B4(&exp0, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT0_B5(&exp0, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT0_B6(&exp0, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT0_B7(&exp0, MCP23017::PORTB, 7); + +constexpr const MCP23017Gpio IOEXT1_A0(&exp1, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT1_A1(&exp1, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT1_A2(&exp1, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT1_A3(&exp1, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT1_A4(&exp1, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT1_A5(&exp1, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT1_A6(&exp1, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT1_A7(&exp1, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT1_B0(&exp1, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT1_B1(&exp1, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT1_B2(&exp1, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT1_B3(&exp1, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT1_B4(&exp1, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT1_B5(&exp1, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT1_B6(&exp1, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT1_B7(&exp1, MCP23017::PORTB, 7); + +constexpr const MCP23017Gpio IOEXT10_A0(&exp10, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT10_A1(&exp10, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT10_A2(&exp10, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT10_A3(&exp10, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT10_A4(&exp10, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT10_A5(&exp10, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT10_A6(&exp10, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT10_A7(&exp10, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT10_B0(&exp10, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT10_B1(&exp10, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT10_B2(&exp10, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT10_B3(&exp10, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT10_B4(&exp10, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT10_B5(&exp10, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT10_B6(&exp10, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT10_B7(&exp10, MCP23017::PORTB, 7); + + +constexpr const MCP23017Gpio IOEXT11_A0(&exp11, MCP23017::PORTA, 0); +constexpr const MCP23017Gpio IOEXT11_A1(&exp11, MCP23017::PORTA, 1); +constexpr const MCP23017Gpio IOEXT11_A2(&exp11, MCP23017::PORTA, 2); +constexpr const MCP23017Gpio IOEXT11_A3(&exp11, MCP23017::PORTA, 3); +constexpr const MCP23017Gpio IOEXT11_A4(&exp11, MCP23017::PORTA, 4); +constexpr const MCP23017Gpio IOEXT11_A5(&exp11, MCP23017::PORTA, 5); +constexpr const MCP23017Gpio IOEXT11_A6(&exp11, MCP23017::PORTA, 6); +constexpr const MCP23017Gpio IOEXT11_A7(&exp11, MCP23017::PORTA, 7); + +constexpr const MCP23017Gpio IOEXT11_B0(&exp11, MCP23017::PORTB, 0); +constexpr const MCP23017Gpio IOEXT11_B1(&exp11, MCP23017::PORTB, 1); +constexpr const MCP23017Gpio IOEXT11_B2(&exp11, MCP23017::PORTB, 2); +constexpr const MCP23017Gpio IOEXT11_B3(&exp11, MCP23017::PORTB, 3); +constexpr const MCP23017Gpio IOEXT11_B4(&exp11, MCP23017::PORTB, 4); +constexpr const MCP23017Gpio IOEXT11_B5(&exp11, MCP23017::PORTB, 5); +constexpr const MCP23017Gpio IOEXT11_B6(&exp11, MCP23017::PORTB, 6); +constexpr const MCP23017Gpio IOEXT11_B7(&exp11, MCP23017::PORTB, 7); + + +constexpr const Gpio *const kPortExt0[] = { + &IOEXT0_A0, &IOEXT0_A1, &IOEXT0_A2, &IOEXT0_A3, // + &IOEXT0_A4, &IOEXT0_A5, &IOEXT0_A6, &IOEXT0_A7, // + &IOEXT0_B0, &IOEXT0_B1, &IOEXT0_B2, &IOEXT0_B3, // + &IOEXT0_B4, &IOEXT0_B5, &IOEXT0_B6, &IOEXT0_B7, // + &IOEXT1_A0, &IOEXT1_A1, &IOEXT1_A2, &IOEXT1_A3, // + &IOEXT1_A4, &IOEXT1_A5, &IOEXT1_A6, &IOEXT1_A7, // + &IOEXT1_B0, &IOEXT1_B1, &IOEXT1_B2, &IOEXT1_B3, // + &IOEXT1_B4, &IOEXT1_B5, &IOEXT1_B6, &IOEXT1_B7, // + &IOEXT10_A0, &IOEXT10_A1, &IOEXT10_A2, &IOEXT10_A3, // + &IOEXT10_A4, &IOEXT10_A5, &IOEXT10_A6, &IOEXT10_A7, // + &IOEXT10_B0, &IOEXT10_B1, &IOEXT10_B2, &IOEXT10_B3, // + &IOEXT10_B4, &IOEXT10_B5, &IOEXT10_B6, &IOEXT10_B7, // + &IOEXT11_A0, &IOEXT11_A1, &IOEXT11_A2, &IOEXT11_A3, // + &IOEXT11_A4, &IOEXT11_A5, &IOEXT11_A6, &IOEXT11_A7, // + &IOEXT11_B0, &IOEXT11_B1, &IOEXT11_B2, &IOEXT11_B3, // + &IOEXT11_B4, &IOEXT11_B5, &IOEXT11_B6, &IOEXT11_B7 // + + +}; + +openlcb::MultiConfiguredPC ext0_pcs( + stack.node(), kPortExt0, ARRAYSIZE(kPortExt0), cfg.seg().ext0_pc()); + +// set default state for Signals LEDs?? + + + + +#endif // if NUM_EXTBOARDS = 2 + +openlcb::RefreshLoop loopab(stack.node(), + { +#if NUM_EXTBOARDS > 0 + ext0_pcs.polling(), // +#endif +// producer_a1.polling(), producer_a2.polling(), // +// producer_a3.polling(), producer_a4.polling(), // +// producer_a5.polling(), producer_a6.polling(), // +// producer_a7.polling(), producer_a8.polling(), // + producer_b1.polling(), producer_b2.polling(), // + producer_b3.polling(), producer_b4.polling(), // + producer_b5.polling(), producer_b6.polling(), // + producer_b7.polling(), producer_b8.polling(), // + }); + +/** Entry point to application. + * @param argc number of command line arguments + * @param argv array of command line arguments + * @return 0, should never return + */ +int appl_main(int argc, char *argv[]) +{ + stack.check_version_and_factory_reset( + cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false); + +#if NUM_EXTBOARDS == 1 + { + int i2cfd = ::open("/dev/i2c0", O_RDWR); + exp0.init(i2cfd); + exp1.init(i2cfd); + } +#elif NUM_EXTBOARDS == 2 + { + int i2cfd = ::open("/dev/i2c0", O_RDWR); + exp0.init(i2cfd); + exp1.init(i2cfd); + exp10.init(i2cfd); + exp11.init(i2cfd); + } +#endif + + internal_outputs.init("/dev/spi1.ioboard"); + internal_inputs.init("/dev/spi2"); + + // The necessary physical ports must be added to the stack. + // + // It is okay to enable multiple physical ports, in which case the stack + // will behave as a bridge between them. For example enabling both the + // physical CAN port and the USB port will make this firmware act as an + // USB-CAN adapter in addition to the producers/consumers created above. + // + // If a port is enabled, it must be functional or else the stack will + // freeze waiting for that port to send the packets out. +#if defined(HAVE_PHYSICAL_CAN_PORT) + stack.add_can_port_select("/dev/can0"); +#endif +#if defined(SNIFF_ON_USB) + stack.add_gridconnect_port("/dev/serUSB0"); +#endif +#if defined(SNIFF_ON_SERIAL) + stack.add_gridconnect_port("/dev/ser0"); +#endif + + // This command donates the main thread to the operation of the + // stack. Alternatively the stack could be started in a separate stack and + // then application-specific business logic could be executed ion a busy + // loop in the main thread. + stack.loop_executor(); + return 0; +} From d84e84b57520fbaff72700f6bc7ec8233a2eee49 Mon Sep 17 00:00:00 2001 From: Rick Lull Date: Sun, 21 Jul 2024 13:10:34 -0400 Subject: [PATCH 4/4] Cleaned up unused MCP code in main.cxx. --- applications/controlpanelnode/main.cxx | 187 ------------------------- 1 file changed, 187 deletions(-) diff --git a/applications/controlpanelnode/main.cxx b/applications/controlpanelnode/main.cxx index d4c57e053..979337c36 100644 --- a/applications/controlpanelnode/main.cxx +++ b/applications/controlpanelnode/main.cxx @@ -412,180 +412,9 @@ openlcb::ConfiguredProducer producer_b7( openlcb::ConfiguredProducer producer_b8( stack.node(), cfg.seg().right_producers().entry<7>(), (const Gpio*)&PORTB_LINE8); -#if NUM_EXTBOARDS == 1 - -MCP23017 exp0(&io_executor, 0, 0, 0); -MCP23017 exp1(&io_executor, 0, 0, 1); - -constexpr const MCP23017Gpio IOEXT0_A0(&exp0, MCP23017::PORTA, 0); -constexpr const MCP23017Gpio IOEXT0_A1(&exp0, MCP23017::PORTA, 1); -constexpr const MCP23017Gpio IOEXT0_A2(&exp0, MCP23017::PORTA, 2); -constexpr const MCP23017Gpio IOEXT0_A3(&exp0, MCP23017::PORTA, 3); -constexpr const MCP23017Gpio IOEXT0_A4(&exp0, MCP23017::PORTA, 4); -constexpr const MCP23017Gpio IOEXT0_A5(&exp0, MCP23017::PORTA, 5); -constexpr const MCP23017Gpio IOEXT0_A6(&exp0, MCP23017::PORTA, 6); -constexpr const MCP23017Gpio IOEXT0_A7(&exp0, MCP23017::PORTA, 7); - -constexpr const MCP23017Gpio IOEXT0_B0(&exp0, MCP23017::PORTB, 0); -constexpr const MCP23017Gpio IOEXT0_B1(&exp0, MCP23017::PORTB, 1); -constexpr const MCP23017Gpio IOEXT0_B2(&exp0, MCP23017::PORTB, 2); -constexpr const MCP23017Gpio IOEXT0_B3(&exp0, MCP23017::PORTB, 3); -constexpr const MCP23017Gpio IOEXT0_B4(&exp0, MCP23017::PORTB, 4); -constexpr const MCP23017Gpio IOEXT0_B5(&exp0, MCP23017::PORTB, 5); -constexpr const MCP23017Gpio IOEXT0_B6(&exp0, MCP23017::PORTB, 6); -constexpr const MCP23017Gpio IOEXT0_B7(&exp0, MCP23017::PORTB, 7); - -constexpr const MCP23017Gpio IOEXT1_A0(&exp1, MCP23017::PORTA, 0); -constexpr const MCP23017Gpio IOEXT1_A1(&exp1, MCP23017::PORTA, 1); -constexpr const MCP23017Gpio IOEXT1_A2(&exp1, MCP23017::PORTA, 2); -constexpr const MCP23017Gpio IOEXT1_A3(&exp1, MCP23017::PORTA, 3); -constexpr const MCP23017Gpio IOEXT1_A4(&exp1, MCP23017::PORTA, 4); -constexpr const MCP23017Gpio IOEXT1_A5(&exp1, MCP23017::PORTA, 5); -constexpr const MCP23017Gpio IOEXT1_A6(&exp1, MCP23017::PORTA, 6); -constexpr const MCP23017Gpio IOEXT1_A7(&exp1, MCP23017::PORTA, 7); - -constexpr const MCP23017Gpio IOEXT1_B0(&exp1, MCP23017::PORTB, 0); -constexpr const MCP23017Gpio IOEXT1_B1(&exp1, MCP23017::PORTB, 1); -constexpr const MCP23017Gpio IOEXT1_B2(&exp1, MCP23017::PORTB, 2); -constexpr const MCP23017Gpio IOEXT1_B3(&exp1, MCP23017::PORTB, 3); -constexpr const MCP23017Gpio IOEXT1_B4(&exp1, MCP23017::PORTB, 4); -constexpr const MCP23017Gpio IOEXT1_B5(&exp1, MCP23017::PORTB, 5); -constexpr const MCP23017Gpio IOEXT1_B6(&exp1, MCP23017::PORTB, 6); -constexpr const MCP23017Gpio IOEXT1_B7(&exp1, MCP23017::PORTB, 7); - -constexpr const Gpio *const kPortExt0[] = { - &IOEXT0_A0, &IOEXT0_A1, &IOEXT0_A2, &IOEXT0_A3, // - &IOEXT0_A4, &IOEXT0_A5, &IOEXT0_A6, &IOEXT0_A7, // - &IOEXT0_B0, &IOEXT0_B1, &IOEXT0_B2, &IOEXT0_B3, // - &IOEXT0_B4, &IOEXT0_B5, &IOEXT0_B6, &IOEXT0_B7, // - &IOEXT1_A0, &IOEXT1_A1, &IOEXT1_A2, &IOEXT1_A3, // - &IOEXT1_A4, &IOEXT1_A5, &IOEXT1_A6, &IOEXT1_A7, // - &IOEXT1_B0, &IOEXT1_B1, &IOEXT1_B2, &IOEXT1_B3, // - &IOEXT1_B4, &IOEXT1_B5, &IOEXT1_B6, &IOEXT1_B7 // -}; - -openlcb::MultiConfiguredPC ext0_pcs( - stack.node(), kPortExt0, ARRAYSIZE(kPortExt0), cfg.seg().ext0_pc()); - -#endif // if num extboards = 1 - -#if NUM_EXTBOARDS == 2 - -MCP23017 exp0(&io_executor, 0, 0, 0); -MCP23017 exp1(&io_executor, 0, 0, 1); -MCP23017 exp10(&io_executor, 0, 1, 0); -MCP23017 exp11(&io_executor, 0, 1, 1); - -constexpr const MCP23017Gpio IOEXT0_A0(&exp0, MCP23017::PORTA, 0); -constexpr const MCP23017Gpio IOEXT0_A1(&exp0, MCP23017::PORTA, 1); -constexpr const MCP23017Gpio IOEXT0_A2(&exp0, MCP23017::PORTA, 2); -constexpr const MCP23017Gpio IOEXT0_A3(&exp0, MCP23017::PORTA, 3); -constexpr const MCP23017Gpio IOEXT0_A4(&exp0, MCP23017::PORTA, 4); -constexpr const MCP23017Gpio IOEXT0_A5(&exp0, MCP23017::PORTA, 5); -constexpr const MCP23017Gpio IOEXT0_A6(&exp0, MCP23017::PORTA, 6); -constexpr const MCP23017Gpio IOEXT0_A7(&exp0, MCP23017::PORTA, 7); - -constexpr const MCP23017Gpio IOEXT0_B0(&exp0, MCP23017::PORTB, 0); -constexpr const MCP23017Gpio IOEXT0_B1(&exp0, MCP23017::PORTB, 1); -constexpr const MCP23017Gpio IOEXT0_B2(&exp0, MCP23017::PORTB, 2); -constexpr const MCP23017Gpio IOEXT0_B3(&exp0, MCP23017::PORTB, 3); -constexpr const MCP23017Gpio IOEXT0_B4(&exp0, MCP23017::PORTB, 4); -constexpr const MCP23017Gpio IOEXT0_B5(&exp0, MCP23017::PORTB, 5); -constexpr const MCP23017Gpio IOEXT0_B6(&exp0, MCP23017::PORTB, 6); -constexpr const MCP23017Gpio IOEXT0_B7(&exp0, MCP23017::PORTB, 7); - -constexpr const MCP23017Gpio IOEXT1_A0(&exp1, MCP23017::PORTA, 0); -constexpr const MCP23017Gpio IOEXT1_A1(&exp1, MCP23017::PORTA, 1); -constexpr const MCP23017Gpio IOEXT1_A2(&exp1, MCP23017::PORTA, 2); -constexpr const MCP23017Gpio IOEXT1_A3(&exp1, MCP23017::PORTA, 3); -constexpr const MCP23017Gpio IOEXT1_A4(&exp1, MCP23017::PORTA, 4); -constexpr const MCP23017Gpio IOEXT1_A5(&exp1, MCP23017::PORTA, 5); -constexpr const MCP23017Gpio IOEXT1_A6(&exp1, MCP23017::PORTA, 6); -constexpr const MCP23017Gpio IOEXT1_A7(&exp1, MCP23017::PORTA, 7); - -constexpr const MCP23017Gpio IOEXT1_B0(&exp1, MCP23017::PORTB, 0); -constexpr const MCP23017Gpio IOEXT1_B1(&exp1, MCP23017::PORTB, 1); -constexpr const MCP23017Gpio IOEXT1_B2(&exp1, MCP23017::PORTB, 2); -constexpr const MCP23017Gpio IOEXT1_B3(&exp1, MCP23017::PORTB, 3); -constexpr const MCP23017Gpio IOEXT1_B4(&exp1, MCP23017::PORTB, 4); -constexpr const MCP23017Gpio IOEXT1_B5(&exp1, MCP23017::PORTB, 5); -constexpr const MCP23017Gpio IOEXT1_B6(&exp1, MCP23017::PORTB, 6); -constexpr const MCP23017Gpio IOEXT1_B7(&exp1, MCP23017::PORTB, 7); - -constexpr const MCP23017Gpio IOEXT10_A0(&exp10, MCP23017::PORTA, 0); -constexpr const MCP23017Gpio IOEXT10_A1(&exp10, MCP23017::PORTA, 1); -constexpr const MCP23017Gpio IOEXT10_A2(&exp10, MCP23017::PORTA, 2); -constexpr const MCP23017Gpio IOEXT10_A3(&exp10, MCP23017::PORTA, 3); -constexpr const MCP23017Gpio IOEXT10_A4(&exp10, MCP23017::PORTA, 4); -constexpr const MCP23017Gpio IOEXT10_A5(&exp10, MCP23017::PORTA, 5); -constexpr const MCP23017Gpio IOEXT10_A6(&exp10, MCP23017::PORTA, 6); -constexpr const MCP23017Gpio IOEXT10_A7(&exp10, MCP23017::PORTA, 7); - -constexpr const MCP23017Gpio IOEXT10_B0(&exp10, MCP23017::PORTB, 0); -constexpr const MCP23017Gpio IOEXT10_B1(&exp10, MCP23017::PORTB, 1); -constexpr const MCP23017Gpio IOEXT10_B2(&exp10, MCP23017::PORTB, 2); -constexpr const MCP23017Gpio IOEXT10_B3(&exp10, MCP23017::PORTB, 3); -constexpr const MCP23017Gpio IOEXT10_B4(&exp10, MCP23017::PORTB, 4); -constexpr const MCP23017Gpio IOEXT10_B5(&exp10, MCP23017::PORTB, 5); -constexpr const MCP23017Gpio IOEXT10_B6(&exp10, MCP23017::PORTB, 6); -constexpr const MCP23017Gpio IOEXT10_B7(&exp10, MCP23017::PORTB, 7); - - -constexpr const MCP23017Gpio IOEXT11_A0(&exp11, MCP23017::PORTA, 0); -constexpr const MCP23017Gpio IOEXT11_A1(&exp11, MCP23017::PORTA, 1); -constexpr const MCP23017Gpio IOEXT11_A2(&exp11, MCP23017::PORTA, 2); -constexpr const MCP23017Gpio IOEXT11_A3(&exp11, MCP23017::PORTA, 3); -constexpr const MCP23017Gpio IOEXT11_A4(&exp11, MCP23017::PORTA, 4); -constexpr const MCP23017Gpio IOEXT11_A5(&exp11, MCP23017::PORTA, 5); -constexpr const MCP23017Gpio IOEXT11_A6(&exp11, MCP23017::PORTA, 6); -constexpr const MCP23017Gpio IOEXT11_A7(&exp11, MCP23017::PORTA, 7); - -constexpr const MCP23017Gpio IOEXT11_B0(&exp11, MCP23017::PORTB, 0); -constexpr const MCP23017Gpio IOEXT11_B1(&exp11, MCP23017::PORTB, 1); -constexpr const MCP23017Gpio IOEXT11_B2(&exp11, MCP23017::PORTB, 2); -constexpr const MCP23017Gpio IOEXT11_B3(&exp11, MCP23017::PORTB, 3); -constexpr const MCP23017Gpio IOEXT11_B4(&exp11, MCP23017::PORTB, 4); -constexpr const MCP23017Gpio IOEXT11_B5(&exp11, MCP23017::PORTB, 5); -constexpr const MCP23017Gpio IOEXT11_B6(&exp11, MCP23017::PORTB, 6); -constexpr const MCP23017Gpio IOEXT11_B7(&exp11, MCP23017::PORTB, 7); - - -constexpr const Gpio *const kPortExt0[] = { - &IOEXT0_A0, &IOEXT0_A1, &IOEXT0_A2, &IOEXT0_A3, // - &IOEXT0_A4, &IOEXT0_A5, &IOEXT0_A6, &IOEXT0_A7, // - &IOEXT0_B0, &IOEXT0_B1, &IOEXT0_B2, &IOEXT0_B3, // - &IOEXT0_B4, &IOEXT0_B5, &IOEXT0_B6, &IOEXT0_B7, // - &IOEXT1_A0, &IOEXT1_A1, &IOEXT1_A2, &IOEXT1_A3, // - &IOEXT1_A4, &IOEXT1_A5, &IOEXT1_A6, &IOEXT1_A7, // - &IOEXT1_B0, &IOEXT1_B1, &IOEXT1_B2, &IOEXT1_B3, // - &IOEXT1_B4, &IOEXT1_B5, &IOEXT1_B6, &IOEXT1_B7, // - &IOEXT10_A0, &IOEXT10_A1, &IOEXT10_A2, &IOEXT10_A3, // - &IOEXT10_A4, &IOEXT10_A5, &IOEXT10_A6, &IOEXT10_A7, // - &IOEXT10_B0, &IOEXT10_B1, &IOEXT10_B2, &IOEXT10_B3, // - &IOEXT10_B4, &IOEXT10_B5, &IOEXT10_B6, &IOEXT10_B7, // - &IOEXT11_A0, &IOEXT11_A1, &IOEXT11_A2, &IOEXT11_A3, // - &IOEXT11_A4, &IOEXT11_A5, &IOEXT11_A6, &IOEXT11_A7, // - &IOEXT11_B0, &IOEXT11_B1, &IOEXT11_B2, &IOEXT11_B3, // - &IOEXT11_B4, &IOEXT11_B5, &IOEXT11_B6, &IOEXT11_B7 // - - -}; - -openlcb::MultiConfiguredPC ext0_pcs( - stack.node(), kPortExt0, ARRAYSIZE(kPortExt0), cfg.seg().ext0_pc()); - -// set default state for Signals LEDs?? - - - - -#endif // if NUM_EXTBOARDS = 2 openlcb::RefreshLoop loopab(stack.node(), { -#if NUM_EXTBOARDS > 0 - ext0_pcs.polling(), // -#endif // producer_a1.polling(), producer_a2.polling(), // // producer_a3.polling(), producer_a4.polling(), // // producer_a5.polling(), producer_a6.polling(), // @@ -606,22 +435,6 @@ int appl_main(int argc, char *argv[]) stack.check_version_and_factory_reset( cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false); -#if NUM_EXTBOARDS == 1 - { - int i2cfd = ::open("/dev/i2c0", O_RDWR); - exp0.init(i2cfd); - exp1.init(i2cfd); - } -#elif NUM_EXTBOARDS == 2 - { - int i2cfd = ::open("/dev/i2c0", O_RDWR); - exp0.init(i2cfd); - exp1.init(i2cfd); - exp10.init(i2cfd); - exp11.init(i2cfd); - } -#endif - internal_outputs.init("/dev/spi1.ioboard"); internal_inputs.init("/dev/spi2");