diff --git a/examples/stm32/.gitignore b/examples/stm32/.gitignore new file mode 100644 index 0000000..97d8c9e --- /dev/null +++ b/examples/stm32/.gitignore @@ -0,0 +1,11 @@ +# Auto-generated from *.ioc file +cmake/* +Core/* +Drivers/* +.mxproject +*.s +*.ld +CMakePresets.json + +# Editor setting +.vscode/* \ No newline at end of file diff --git a/examples/stm32/CMakeLists.txt b/examples/stm32/CMakeLists.txt new file mode 100644 index 0000000..ca52b73 --- /dev/null +++ b/examples/stm32/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.22) + +# +# This file is generated only once, +# and is not re-generated if converter is called multiple times. +# +# User is free to modify the file as much as necessary +# + +# Setup compiler settings +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) + + +# Define the build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif() + +# Set the project name +set(CMAKE_PROJECT_NAME stm32f401ceux) + +# Include toolchain file +include("cmake/gcc-arm-none-eabi.cmake") + +# Enable compile command to ease indexing with e.g. clangd +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) + +# Enable CMake support for ASM and C languages +enable_language(C ASM) + +# Core project settings +project(${CMAKE_PROJECT_NAME}) +message("Build type: " ${CMAKE_BUILD_TYPE}) + +# Create an executable object type +add_executable(${CMAKE_PROJECT_NAME}) + +# Add STM32CubeMX generated sources +add_subdirectory(cmake/stm32cubemx) + +# Link directories setup +target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE + # Add user defined library search paths +) + +# Add sources to executable +target_sources(${CMAKE_PROJECT_NAME} PRIVATE + # Add user sources here + nanomodbus_stm32.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../nanomodbus.c +) + +# Add include paths +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE + # Add user defined include paths + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../../ +) + +# Add project symbols (macros) +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + # Add user defined symbols +) + +# Add linked libraries +target_link_libraries(${CMAKE_PROJECT_NAME} + stm32cubemx + + # Add user defined libraries +) diff --git a/examples/stm32/nanomodbus_stm32.c b/examples/stm32/nanomodbus_stm32.c new file mode 100644 index 0000000..229b634 --- /dev/null +++ b/examples/stm32/nanomodbus_stm32.c @@ -0,0 +1,165 @@ +#include +#include "nanomodbus_stm32.h" + +static int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg); +static int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg); + +// Ring buffer structure definition +typedef struct tRingBuf { + uint8_t data[RX_BUF_SIZE]; + uint16_t head; + uint16_t tail; + bool full; + void (*overflow_callback)(struct tRingBuf* rq); +} ringBuf; + +static ringBuf rb; +static void ringbuf_init(ringBuf* rb, void (*overflow_callback)(struct tRingBuf* rq)); +static void ring_buf_overflow_error(ringBuf* rb); + +void nanomodbus_rtu_init(nmbs_t* nmbs) +{ + ring_buf_init(&rb, ring_buf_overflow_error); + + nmbs_platform_conf pf_conf; + + nmbs_platform_conf_create(&pf_conf); + pf_conf.transport = NMBS_TRANSPORT_RTU; + pf_conf.read = read_serial; + pf_conf.write = write_serial; +} + +// Function to initialize the ring buffer +static void ringbuf_init(ringBuf* rb, void (*overflow_callback)(struct tRingBuf* rq)) { + memset(rb->data, 0, sizeof(rb->data)); + rb->head = 0; + rb->tail = 0; + rb->full = false; + rb->overflow_callback = overflow_callback; +} + +// Function to check if the ring buffer is empty +static bool ringbuf_is_empty(ringBuf* rb) { + return (!rb->full && (rb->head == rb->tail)); +} + +// Function to check if the ring buffer is full +static bool ringbuf_is_full(ringBuf* rb) { + return rb->full; +} + +// Function to write multiple bytes to the ring buffer +static void ringbuf_put(ringBuf* rb, const uint8_t* data, uint16_t length) { + for (uint16_t i = 0; i < length; i++) { + rb->data[rb->head] = data[i]; + + if (rb->full) { // If the buffer is full + if (rb->overflow_callback) { + rb->overflow_callback(rb); // Call the overflow callback + } + rb->tail = (rb->tail + 1) % RX_BUF_SIZE; // Move tail to overwrite data + } + + rb->head = (rb->head + 1) % RX_BUF_SIZE; + + rb->full = (rb->head == rb->tail); + } +} + +// Function to read multiple bytes from the ring buffer +static bool ringbuf_get(ringBuf* rb, uint8_t* data, uint16_t length) { + if (ringbuf_is_empty(rb)) { + return false; // Return false if the buffer is empty + } + + for (uint16_t i = 0; i < length; i++) { + if (ringbuf_is_empty(rb)) { + return false; // If no more data, stop reading + } + + data[i] = rb->data[rb->tail]; + rb->tail = (rb->tail + 1) % RX_BUF_SIZE; + rb->full = false; // Buffer is no longer full after reading + } + + return true; +} + +uint16_t ringbuf_size(ringBuf* rb) { + if (rb->full) { + return RX_BUF_SIZE; + } + + if (rb->head >= rb->tail) { + return rb->head - rb->tail; + } else { + return RX_BUF_SIZE + rb->head - rb->tail; + } +} + +// Example callback function to handle buffer overflow +static void ring_buf_overflow_error(ringBuf* rb) +{ + //In here we may check the overflow situation + while(true){} +} + +// Dual buffer setting to isolate dma implementation and ring buffer. +// You may integrate this feature with dma counter register to minimize memory footprint +static uint8_t rx_dma_buf[RX_BUF_SIZE]; + +// RX event callback from dma +void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) +{ + if(huart == &NANOMB_UART) + { + ringbuf_put(&rb, rx_dma_buf, Size); + HAL_UARTEx_ReceiveToIdle_DMA(huart, rx_dma_buf, RX_BUF_SIZE); + } + // You may add your additional uart handler below +} + +static int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) +{ + uint32_t tick_start = HAL_GetTick(); + while(ringbuf_size(&rb) < count) + { + if(HAL_GetTick() - tick_start >= byte_timeout_ms) + { + return NMBS_ERROR_TIMEOUT; + } + } + // Read from ring buffer + if(ringbuf_get(&rb, buf, count)) + { + return NMBS_ERROR_NONE; + } + else + { + return NMBS_ERROR_TRANSPORT; + } +} + + +static int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) +{ + HAL_StatusTypeDef status = HAL_UART_Transmit_DMA(&NANOMB_UART, buf, count); + + switch (status) + { + case HAL_OK: + return NMBS_ERROR_NONE; + break; + case HAL_ERROR: + return NMBS_ERROR_TRANSPORT; + break; + case HAL_BUSY: + return NMBS_ERROR_TRANSPORT; + break; + case HAL_TIMEOUT: + return NMBS_ERROR_TIMEOUT; + break; + } + return NMBS_ERROR_INVALID_ARGUMENT; +} + diff --git a/examples/stm32/nanomodbus_stm32.h b/examples/stm32/nanomodbus_stm32.h new file mode 100644 index 0000000..05ed248 --- /dev/null +++ b/examples/stm32/nanomodbus_stm32.h @@ -0,0 +1,25 @@ +#ifndef NANOMODBUS_CONFIG_H +#define NANOMODBUS_CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define NANOMB_UART huart1 +#define RX_BUF_SIZE 256 +// Other hardware configuration have done in *.ioc file + +// Core name may vary with your hardware +#include "stm32f4xx_hal.h" +// NanoModbus include +#include "nanomodbus.h" + +void nanomodbus_rtu_init(nmbs_t* nmbs); + +extern UART_HandleTypeDef NANOMB_UART; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/stm32/stm32f401ceux.ioc b/examples/stm32/stm32f401ceux.ioc new file mode 100644 index 0000000..035ff67 --- /dev/null +++ b/examples/stm32/stm32f401ceux.ioc @@ -0,0 +1,131 @@ +#MicroXplorer Configuration settings - do not modify +CAD.formats= +CAD.pinconfig= +CAD.provider= +Dma.Request0=USART1_RX +Dma.Request1=USART1_TX +Dma.RequestsNb=2 +Dma.USART1_RX.0.Direction=DMA_PERIPH_TO_MEMORY +Dma.USART1_RX.0.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.USART1_RX.0.Instance=DMA2_Stream2 +Dma.USART1_RX.0.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.USART1_RX.0.MemInc=DMA_MINC_ENABLE +Dma.USART1_RX.0.Mode=DMA_NORMAL +Dma.USART1_RX.0.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.USART1_RX.0.PeriphInc=DMA_PINC_DISABLE +Dma.USART1_RX.0.Priority=DMA_PRIORITY_LOW +Dma.USART1_RX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +Dma.USART1_TX.1.Direction=DMA_MEMORY_TO_PERIPH +Dma.USART1_TX.1.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.USART1_TX.1.Instance=DMA2_Stream7 +Dma.USART1_TX.1.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.USART1_TX.1.MemInc=DMA_MINC_ENABLE +Dma.USART1_TX.1.Mode=DMA_NORMAL +Dma.USART1_TX.1.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.USART1_TX.1.PeriphInc=DMA_PINC_DISABLE +Dma.USART1_TX.1.Priority=DMA_PRIORITY_LOW +Dma.USART1_TX.1.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +File.Version=6 +GPIO.groupedBy= +KeepUserPlacement=false +Mcu.CPN=STM32F401CEU6 +Mcu.Family=STM32F4 +Mcu.IP0=DMA +Mcu.IP1=NVIC +Mcu.IP2=RCC +Mcu.IP3=SYS +Mcu.IP4=USART1 +Mcu.IPNb=5 +Mcu.Name=STM32F401C(D-E)Ux +Mcu.Package=UFQFPN48 +Mcu.Pin0=PA9 +Mcu.Pin1=PA10 +Mcu.Pin2=VP_SYS_VS_Systick +Mcu.PinsNb=3 +Mcu.ThirdPartyNb=0 +Mcu.UserConstants= +Mcu.UserName=STM32F401CEUx +MxCube.Version=6.11.1 +MxDb.Version=DB.6.0.111 +NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.DMA2_Stream2_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true +NVIC.DMA2_Stream7_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true +NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.ForceEnableDMAVector=true +NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false +NVIC.USART1_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true +NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +PA10.Mode=Asynchronous +PA10.Signal=USART1_RX +PA9.Mode=Asynchronous +PA9.Signal=USART1_TX +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=false +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32F401CEUx +ProjectManager.FirmwarePackage=STM32Cube FW_F4 V1.28.1 +ProjectManager.FreePins=false +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=0x200 +ProjectManager.KeepUserCode=true +ProjectManager.LastFirmware=true +ProjectManager.LibraryCopy=0 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain= +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=stm32f401ceux.ioc +ProjectManager.ProjectName=stm32f401ceux +ProjectManager.ProjectStructure= +ProjectManager.RegisterCallBack= +ProjectManager.StackSize=0x400 +ProjectManager.TargetToolchain=CMake +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=false +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_USART1_UART_Init-USART1-false-HAL-true +RCC.48MHZClocksFreq_Value=32000000 +RCC.AHBFreq_Value=64000000 +RCC.APB1CLKDivider=RCC_HCLK_DIV2 +RCC.APB1Freq_Value=32000000 +RCC.APB1TimFreq_Value=64000000 +RCC.APB2Freq_Value=64000000 +RCC.APB2TimFreq_Value=64000000 +RCC.CortexFreq_Value=64000000 +RCC.FCLKCortexFreq_Value=64000000 +RCC.HCLKFreq_Value=64000000 +RCC.HSE_VALUE=25000000 +RCC.HSI_VALUE=16000000 +RCC.I2SClocksFreq_Value=96000000 +RCC.IPParameters=48MHZClocksFreq_Value,AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,CortexFreq_Value,FCLKCortexFreq_Value,HCLKFreq_Value,HSE_VALUE,HSI_VALUE,I2SClocksFreq_Value,LSE_VALUE,LSI_VALUE,PLLCLKFreq_Value,PLLN,PLLQCLKFreq_Value,RTCFreq_Value,RTCHSEDivFreq_Value,SYSCLKFreq_VALUE,SYSCLKSource,VCOI2SOutputFreq_Value,VCOInputFreq_Value,VCOOutputFreq_Value,VcooutputI2S +RCC.LSE_VALUE=32768 +RCC.LSI_VALUE=32000 +RCC.PLLCLKFreq_Value=64000000 +RCC.PLLN=128 +RCC.PLLQCLKFreq_Value=32000000 +RCC.RTCFreq_Value=32000 +RCC.RTCHSEDivFreq_Value=12500000 +RCC.SYSCLKFreq_VALUE=64000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.VCOI2SOutputFreq_Value=192000000 +RCC.VCOInputFreq_Value=1000000 +RCC.VCOOutputFreq_Value=128000000 +RCC.VcooutputI2S=96000000 +USART1.IPParameters=VirtualMode +USART1.VirtualMode=VM_ASYNC +VP_SYS_VS_Systick.Mode=SysTick +VP_SYS_VS_Systick.Signal=SYS_VS_Systick +board=custom