diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9acbd6a --- /dev/null +++ b/.clang-format @@ -0,0 +1,32 @@ +# Logic style +Language: Cpp +# manually added flags +FixNamespaceComments: 'false' +SortIncludes: 'false' + + +# flags copied from web editor, https://zed0.co.uk/clang-format-configurator/ +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: 'true' +BreakBeforeBraces: Allman +ColumnLimit: '140' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' +ContinuationIndentWidth: '4' +Cpp11BracedListStyle: 'false' +IndentWidth: '4' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +MaxEmptyLinesToKeep: '2' +NamespaceIndentation: All +PointerAlignment: Left +SpaceBeforeParens: Never +SpaceInEmptyParentheses: 'false' +SpacesInCStyleCastParentheses: 'true' +SpacesInParentheses: 'true' +SpacesInSquareBrackets: 'true' +TabWidth: '4' +UseTab: Never diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a116c1b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,71 @@ +name: Build + +on: + push: + branches: [master] + tags: + - '*' + pull_request: + branches: [master] + +jobs: + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: | + cmake -B ${{github.workspace}}/build -A x64 + cmake --build ${{github.workspace}}/build --config Release + - name: Upload windows build + uses: actions/upload-artifact@v2 + with: + name: windows + path: ${{github.workspace}}/build/Analyzers/Release/*.dll + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: | + cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Release + cmake --build ${{github.workspace}}/build + - name: Upload MacOS build + uses: actions/upload-artifact@v2 + with: + name: macos + path: ${{github.workspace}}/build/Analyzers/*.so + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: | + cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Release + cmake --build ${{github.workspace}}/build + - name: Upload Linux build + uses: actions/upload-artifact@v2 + with: + name: linux + path: ${{github.workspace}}/build/Analyzers/*.so + publish: + needs: [windows, macos, linux] + runs-on: ubuntu-latest + steps: + - name: download individual builds + uses: actions/download-artifact@v2 + with: + path: ${{github.workspace}}/artifacts + - name: zip + run: | + cd ${{github.workspace}}/artifacts + zip -r ${{github.workspace}}/analyzer.zip . + - uses: actions/upload-artifact@v2 + with: + name: all-platforms + path: ${{github.workspace}}/artifacts/** + - name: create release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ${{github.workspace}}/analyzer.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6d27ab2..42afabf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1 @@ -Debug/ -Release/ -Debug-Legacy-1.1.14/ -*.VC.db -*.VC.opendb -*.suo -*.sln.ecd -*.vcxproj.user \ No newline at end of file +/build \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d148baa..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "AnalyzerSDK"] - path = AnalyzerSDK - url = https://github.com/saleae/AnalyzerSDK.git -[submodule "LegacyAnalyzerSDK"] - path = LegacyAnalyzerSDK - url = https://github.com/saleae/AnalyzerSDK.git diff --git a/AnalyzerSDK b/AnalyzerSDK deleted file mode 160000 index 560b7cb..0000000 --- a/AnalyzerSDK +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 560b7cb5846aed3db9ae70e2f2ef9b5a559fd144 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ef4a440 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required (VERSION 3.13) + +project(SimpleSerialAnalyzer) + +add_definitions( -DLOGIC2 ) + +# enable generation of compile_commands.json, helpful for IDEs to locate include files. +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# custom CMake Modules are located in the cmake directory. +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +include(ExternalAnalyzerSDK) + +set(SOURCES +src/SimpleSerialAnalyzer.cpp +src/SimpleSerialAnalyzer.h +src/SimpleSerialAnalyzerResults.cpp +src/SimpleSerialAnalyzerResults.h +src/SimpleSerialAnalyzerSettings.cpp +src/SimpleSerialAnalyzerSettings.h +src/SimpleSerialSimulationDataGenerator.cpp +src/SimpleSerialSimulationDataGenerator.h +) + +add_analyzer_plugin(${PROJECT_NAME} SOURCES ${SOURCES}) diff --git a/LegacyAnalyzerSDK b/LegacyAnalyzerSDK deleted file mode 160000 index 160745b..0000000 --- a/LegacyAnalyzerSDK +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 160745b96be27868b5355f384fe797a5cbfa7913 diff --git a/Visual Studio/SimpleSerialAnalyzer.sln b/Visual Studio/SimpleSerialAnalyzer.sln deleted file mode 100644 index 4b1727f..0000000 --- a/Visual Studio/SimpleSerialAnalyzer.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleSerialAnalyzer", "SimpleSerialAnalyzer.vcxproj", "{D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Debug-Legacy-1.1.14|Win32 = Debug-Legacy-1.1.14|Win32 - Debug-Legacy-1.1.14|x64 = Debug-Legacy-1.1.14|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug|Win32.ActiveCfg = Debug|Win32 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug|Win32.Build.0 = Debug|Win32 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug|x64.ActiveCfg = Debug|x64 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug|x64.Build.0 = Debug|x64 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug-Legacy-1.1.14|Win32.ActiveCfg = Debug-Legacy-1.1.14|Win32 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug-Legacy-1.1.14|Win32.Build.0 = Debug-Legacy-1.1.14|Win32 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug-Legacy-1.1.14|x64.ActiveCfg = Debug-Legacy-1.1.14|x64 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Debug-Legacy-1.1.14|x64.Build.0 = Debug-Legacy-1.1.14|x64 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Release|Win32.ActiveCfg = Release|Win32 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Release|Win32.Build.0 = Release|Win32 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Release|x64.ActiveCfg = Release|x64 - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Visual Studio/SimpleSerialAnalyzer.vcxproj b/Visual Studio/SimpleSerialAnalyzer.vcxproj deleted file mode 100644 index 93aca51..0000000 --- a/Visual Studio/SimpleSerialAnalyzer.vcxproj +++ /dev/null @@ -1,255 +0,0 @@ - - - - - Debug-Legacy-1.1.14 - Win32 - - - Debug-Legacy-1.1.14 - x64 - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {D7556E7E-A6BF-4BCE-BDC8-E66D2874C301} - SimpleSerialAnalyzer - Win32Proj - 8.1 - - - - DynamicLibrary - v140 - Unicode - true - - - DynamicLibrary - v140 - Unicode - true - - - DynamicLibrary - v140 - Unicode - - - DynamicLibrary - v140 - Unicode - - - DynamicLibrary - v140 - Unicode - - - DynamicLibrary - v140 - Unicode - - - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>14.0.24720.0 - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - true - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - true - - - true - - - true - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - false - - - false - - - - Disabled - $(ProjectDir)..\AnalyzerSDK\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMPLESERIALANALYZER_EXPORTS;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebugDLL - - Level3 - EditAndContinue - - - Analyzer.lib;%(AdditionalDependencies) - $(ProjectDir)..\AnalyzerSDK\lib;%(AdditionalLibraryDirectories) - true - Windows - MachineX86 - - - - - Disabled - $(ProjectDir)..\LegacyAnalyzerSDK\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMPLESERIALANALYZER_EXPORTS;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebug - - - Level3 - EditAndContinue - - - Analyzer.lib;%(AdditionalDependencies) - $(ProjectDir)..\LegacyAnalyzerSDK\lib;%(AdditionalLibraryDirectories) - true - Windows - MachineX86 - - - - - Disabled - $(ProjectDir)..\AnalyzerSDK\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMPLESERIALANALYZER_EXPORTS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebugDLL - - - Level3 - ProgramDatabase - - - Analyzer64.lib;%(AdditionalDependencies) - $(ProjectDir)..\AnalyzerSDK\lib;%(AdditionalLibraryDirectories) - true - Windows - - - - - Disabled - $(ProjectDir)..\LegacyAnalyzerSDK\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMPLESERIALANALYZER_EXPORTS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - - - Level3 - ProgramDatabase - - - Analyzer64.lib;%(AdditionalDependencies) - $(ProjectDir)..\LegacyAnalyzerSDK\lib;%(AdditionalLibraryDirectories) - true - Windows - - - - - MaxSpeed - true - $(ProjectDir)..\AnalyzerSDK\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;SIMPLESERIALANALYZER_EXPORTS;%(PreprocessorDefinitions) - MultiThreadedDLL - true - - Level3 - ProgramDatabase - - - Analyzer.lib;%(AdditionalDependencies) - $(ProjectDir)..\AnalyzerSDK\lib;%(AdditionalLibraryDirectories) - true - Windows - true - true - MachineX86 - - - - - MaxSpeed - true - $(ProjectDir)..\AnalyzerSDK\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;SIMPLESERIALANALYZER_EXPORTS;%(PreprocessorDefinitions) - MultiThreadedDLL - true - - - Level3 - ProgramDatabase - - - Analyzer64.lib;%(AdditionalDependencies) - $(ProjectDir)..\AnalyzerSDK\lib;%(AdditionalLibraryDirectories) - true - Windows - true - true - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build_analyzer.py b/build_analyzer.py deleted file mode 100644 index 594d9af..0000000 --- a/build_analyzer.py +++ /dev/null @@ -1,120 +0,0 @@ -# Python 3 script to build the analyzer - -import os, glob, platform - -#find out if we're running on mac or linux and set the dynamic library extension -if platform.system().lower() == "darwin": - dylib_ext = ".dylib" -else: - dylib_ext = ".so" - -print("Running on " + platform.system()) - -#make sure the release folder exists, and clean out any .o/.so file if there are any -if not os.path.exists( "release" ): - os.makedirs( "release" ) - -os.chdir( "release" ) -o_files = glob.glob( "*.o" ) -o_files.extend( glob.glob( "*" + dylib_ext ) ) -for o_file in o_files: - os.remove( o_file ) -os.chdir( ".." ) - - -#make sure the debug folder exists, and clean out any .o/.so files if there are any -if not os.path.exists( "debug" ): - os.makedirs( "debug" ) - -os.chdir( "debug" ) -o_files = glob.glob( "*.o" ); -o_files.extend( glob.glob( "*" + dylib_ext ) ) -for o_file in o_files: - os.remove( o_file ) -os.chdir( ".." ) - -#find all the cpp files in /source. We'll compile all of them -os.chdir( "source" ) -cpp_files = glob.glob( "*.cpp" ); -os.chdir( ".." ) - -#specify the search paths/dependencies/options for gcc -include_paths = [ "./AnalyzerSDK/include" ] -link_paths = [ "./AnalyzerSDK/lib" ] -link_dependencies = [ "-lAnalyzer" ] #refers to libAnalyzer.dylib or libAnalyzer.so - -debug_compile_flags = "-O0 -w -c -fpic -g" -release_compile_flags = "-O3 -w -c -fpic" - -def run_command(cmd): - "Display cmd, then run it in a subshell, raise if there's an error" - print(cmd) - if os.system(cmd): - raise Exception("Shell execution returned nonzero status") - -#loop through all the cpp files, build up the gcc command line, and attempt to compile each cpp file -for cpp_file in cpp_files: - - #g++ - command = "g++ " - - #include paths - for path in include_paths: - command += "-I\"" + path + "\" " - - release_command = command - release_command += release_compile_flags - release_command += " -o\"release/" + cpp_file.replace( ".cpp", ".o" ) + "\" " #the output file - release_command += "\"" + "source/" + cpp_file + "\"" #the cpp file to compile - - debug_command = command - debug_command += debug_compile_flags - debug_command += " -o\"debug/" + cpp_file.replace( ".cpp", ".o" ) + "\" " #the output file - debug_command += "\"" + "source/" + cpp_file + "\"" #the cpp file to compile - - #run the commands from the command line - run_command(release_command) - run_command(debug_command) - -#lastly, link -#g++ -command = "g++ " - -#add the library search paths -for link_path in link_paths: - command += "-L\"" + link_path + "\" " - -#add libraries to link against -for link_dependency in link_dependencies: - command += link_dependency + " " - -#make a dynamic (shared) library (.so/.dylib) - -if dylib_ext == ".dylib": - command += "-dynamiclib " -else: - command += "-shared " - -#figgure out what the name of this analyzer is -analyzer_name = "" -for cpp_file in cpp_files: - if cpp_file.endswith( "Analyzer.cpp" ): - analyzer_name = cpp_file.replace( "Analyzer.cpp", "" ) - break - -#the files to create (.so/.dylib files) -if dylib_ext == ".dylib": - release_command = command + "-o release/lib" + analyzer_name + "Analyzer.dylib " - debug_command = command + "-o debug/lib" + analyzer_name + "Analyzer.dylib " -else: - release_command = command + "-o\"release/lib" + analyzer_name + "Analyzer.so\" " - debug_command = command + "-o\"debug/lib" + analyzer_name + "Analyzer.so\" " - -#add all the object files to link -for cpp_file in cpp_files: - release_command += "release/" + cpp_file.replace( ".cpp", ".o" ) + " " - debug_command += "debug/" + cpp_file.replace( ".cpp", ".o" ) + " " - -#run the commands from the command line -run_command(release_command) -run_command(debug_command) diff --git a/cmake/ExternalAnalyzerSDK.cmake b/cmake/ExternalAnalyzerSDK.cmake new file mode 100644 index 0000000..d12b7a4 --- /dev/null +++ b/cmake/ExternalAnalyzerSDK.cmake @@ -0,0 +1,57 @@ +include(FetchContent) + +# Use the C++11 standard +set(CMAKE_CXX_STANDARD 11) + +set(CMAKE_CXX_STANDARD_REQUIRED YES) + +if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY OR NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/) +endif() + +# Fetch the Analyzer SDK if the target does not already exist. +if(NOT TARGET Saleae::AnalyzerSDK) + FetchContent_Declare( + analyzersdk + GIT_REPOSITORY https://github.com/saleae/AnalyzerSDK.git + GIT_TAG alpha + GIT_SHALLOW True + GIT_PROGRESS True + ) + + FetchContent_GetProperties(analyzersdk) + + if(NOT analyzersdk_POPULATED) + FetchContent_Populate(analyzersdk) + include(${analyzersdk_SOURCE_DIR}/AnalyzerSDKConfig.cmake) + + if(APPLE OR WIN32) + get_target_property(analyzersdk_lib_location Saleae::AnalyzerSDK IMPORTED_LOCATION) + if(CMAKE_LIBRARY_OUTPUT_DIRECTORY) + file(COPY ${analyzersdk_lib_location} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) + else() + message(WARNING "Please define CMAKE_RUNTIME_OUTPUT_DIRECTORY and CMAKE_LIBRARY_OUTPUT_DIRECTORY if you want unit tests to locate ${analyzersdk_lib_location}") + endif() + endif() + + endif() +endif() + +function(add_analyzer_plugin TARGET) + set(options ) + set(single_value_args ) + set(multi_value_args SOURCES) + cmake_parse_arguments( _p "${options}" "${single_value_args}" "${multi_value_args}" ${ARGN} ) + + + add_library(${TARGET} MODULE ${_p_SOURCES}) + target_link_libraries(${TARGET} PRIVATE Saleae::AnalyzerSDK) + + set(ANALYZER_DESTINATION "Analyzers") + install(TARGETS ${TARGET} RUNTIME DESTINATION ${ANALYZER_DESTINATION} + LIBRARY DESTINATION ${ANALYZER_DESTINATION}) + + set_target_properties(${TARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${ANALYZER_DESTINATION} + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${ANALYZER_DESTINATION}) +endfunction() \ No newline at end of file diff --git a/docs/Analyzer SDK Setup.md b/docs/Analyzer SDK Setup.md deleted file mode 100644 index d6a148e..0000000 --- a/docs/Analyzer SDK Setup.md +++ /dev/null @@ -1,331 +0,0 @@ -# Setting up an Analyzer SDK Project - -This document details setting up your analyzer project for development on Windows, Mac, and Linux. - -Documentation for the Analyzer SDK code is still in the older Saleae Analyzer SDK.pdf. - -## Initial Setup (All Platforms) - -Before continuing this setup guide, please follow the instructions in the readme file to download, fork or clone this repository, and then run the rename_analyzer.py script. - -## Visual Studio - -To build on Windows, open the visual studio project in the Visual Studio folder, and build. The Visual Studio solution has configurations for 32 bit and 64 bit builds. You will likely need to switch the configuration to 64 bit and build that in order to get the analyzer to load in the Windows software. - -To test your analyzer, you will need to tell the Saleae Logic software where to load find your compiled analyzer dll. - -The four build combinations produce analyzer dll files in the following locations: -``` -.\Visual Studio\Win32\Debug\Analyzer.dll -.\Visual Studio\Win32\Release\Analyzer.dll -.\Visual Studio\x64\Debug\Analyzer.dll -.\Visual Studio\x64\Release\Analyzer.dll -``` - -Instructions to tell the Saleae Logic software where to find your new analyzer dll can be found here: -[How to set the developer directory in the Saleae software to use third party or user created analyzers.](https://support.saleae.com/faq/technical-faq/setting-up-developer-directory) - -Once you have set that directory and restarted the software, your new custom analyzer should appear in the list of analyzers you can add. - -## Debugging an Analyzer with Visual Studio - -Newer versions of the Saleae software cannot be used to debug custom analyzers on Windows. This means that older versions of the software and SDK must be used if you wish to attach a debugger and step through your code. This complicates debugging on Windows, unfortunately, but it can be done. - -*Note: it is impossible to attach a debugger to any version of the software that supports the new products. We are working on a solution to this problem, but for now that means you must rely on the simulation data generator for your analyzer to produce captures you can then debug in the older software.* - -To debug your custom analyzer, you will need to download the 32-bit standalone copy of our older, 1.1.18 software. - -http://downloads.saleae.com/betas/1.1.18/Logic1.1.18BetaWin32Standalone.zip - -This is a sandalone download and does not need to be installed. Just extract the zip file and run the contained Logic.exe. - -Please note - switching between Saleae Logic software versions has a tendency to reset the software's settings. This could cause the analyzer developer directory to get reset. If you no longer see your analyzer in the list, please verify that the analyzer developer directory is still set properly. - -To build and and debug your custom analyzer using the 1.1.14 software, follow these steps: - -- Using Visual Studio, open the solution file in the Visual Studio Folder. -- Select the solution configuration "Debug-Legacy-1.1.14" -- Select the platform "Win32" -- Build the solution -- Launch the 1.1.18 32-bit Logic software. If the analyzer directory is not already configured, set that to the `Visual Studio\Win32\Debug-Legacy-1.1.14` directory, and restart the software. -- The analyzer should now be visible in the list of analyzers you can add. -- In visual studio, open the Debug Menu, and select "Attach to process..." -- Locate Logic.exe in the list, select it, and click the Attach button. -- Add a break point on any line near the top of the WorkerThread() function, such as line 27, mSampleRateHz = GetSampleRate(); -- In the Logic software, add your custom analyzer if you haven't already, and start a simulation. -- The breakpoint should hit - -Optionally you can change the debugger command in visual studio to point to the older Logic.exe binary. Then you will be able to start debugging simply by pressing run in Visual Studio. - -**Common issues on Windows** -- The 'Options' button is missing. - On Logic software version 1.1.18, the 'Options' button is hidden because of the Windows Aero theme. It is still clickable right below the (X) button. More information [here](https://support.saleae.com/faq/technical-faq/why-is-the-options-button-missing). -- The software says "Unable to 'LoadLibrary' on dll ... is not a valid Win32 application" - This is most likely because the analyzer was not built for the same platform architecture as the software running it. In almost all cases, this means the analyzer was compiled for 32 bit instead of 64 bit. Details to switch from 32 bit to 64 bit are included in the Analyzer SDK documentation on page 9. First, [add a x64 target to your project](https://msdn.microsoft.com/en-us/library/ms185328(v=vs.120).aspx). Then, edit the linker settings for the 64 bit configuration. Change the additional dependencies item from Analyzer.dll to Analyzer64.dll. - Note: Only the software versions 1.1.16 and later were built for 64 bit. Previous releases, including 1.1.15, were 32 bit only, which is why no 64 bit analyzer dll was provided. -- The Saleae software crashes on launch when the debugger is attached. - Versions after about 1.1.18 no longer allow debuggers to attach. In these cases, we recommend building with the 32 bit version of the 1.1.18 beta and the 1.1.14 SDK, and debug there. This restriction only applies to Windows. - -## Linux - -*Note: When using git clone, please remember to use --recursive to automatically also clone the AnalyzerSDK submodule* - -After you have cloned, forked, or downloaded the repository, and ran the rename_analyzer.py script, there are two additional steps required to run the analyzer. - -First, if you are using Linux 64 bit, you need to delete the 32 bit libAnalyzer.so file, and rename the libAnalyzer64.so file to just libAnalyzer.so. - -``` -mv AnalyzerSDK/lib/libAnalyzer64.so AnalyzerSDK/lib/libAnalyzer.so -``` - -Then run build_analyzer.py - -``` -python build_analyzer.py -``` - -That's it! To debug the analyzer, you need to first tell the Saleae Logic software where to find your newly compiled *.so file. - -[How to set the developer directory in the Saleae software to use third party or user created analyzers.](https://support.saleae.com/faq/technical-faq/setting-up-developer-directory) - -The two variants of the newly compiled analyzer can be found here: - -``` -debug/libAnalyzer.so -release/libAnalyzer.so -``` - -### Debugging with GDB on Linux - -To debug your analyzer, you will need to attach gdb to the Logic application, something like this: -``` -gdb /Home/YourUserName/Desktop/Logic 1.2.14 (64-bit)/Logic -``` -And then test setting a breakpoint like this: -``` -break MyAnalyzer::WorkerThread -``` -Because your analyzer hasn't been loaded yet, GDB will notify you that it can't find this function, and ask if you want to automatically set this breakpoint if a library with a matching function is loaded in the future. Type y - -Then type run to start the application. Add your custom analyzer and start a simulation. This will trigger the breakpoint. - -## MacOS - -### Build Script Based Project - -*Note: When using git clone, please remember to use --recursive to automatically also clone the AnalyzerSDK submodule* - -After you have cloned, forked, or downloaded the repository, and ran the rename_analyzer.py script, there is one additional step required to run the analyzer. - -run build_analyzer.py - -``` -python build_analyzer.py -``` - -That's it! To debug the analyzer, you need to first tell the Saleae Logic software where to find your newly compiled *.dylib file. - -[How to set the developer directory in the Saleae software to use third party or user created analyzers.](https://support.saleae.com/faq/technical-faq/setting-up-developer-directory) - -``` -debug/libAnalyzer.dylib -release/libAnalyzer.dylib -``` - -### Debugging with GDB on MacOS - -To debug your analyzer, you will need to attach gdb to the Logic application, something like this: -``` -gdb /Applications/Logic.app/Contents/MacOS/Logic -``` -And then test setting a breakpoint like this: -``` -break MyAnalyzer::WorkerThread -``` -Because your analyzer hasn't been loaded yet, GDB will notify you that it can't find this function, and ask if you want to automatically set this breakpoint if a library with a matching function is loaded in the future. Type y - -Then type run to start the application. Add your custom analyzer and start a simulation. This will trigger the breakpoint. - - - -### XCode Based Project - -**Note: This section hasn't yet been updated to describe the proper setup using a fork or clone of the new Github repository for the Sample Analyzer. The instructions will still work, but need improvement to work well with the git repository.** - -This section was written using the 1.1.32 Analyzer SDK, Xcode version 7.2.1, and OSX 10.10.5. however it is likely to work with other versions as well. - -- Start Xcode -- Click "Create a new Xcode project" - -![1](./images/1_-_new_project.png) - -- select "Other" from the left column, and "Empty" from the templates list. -- Click next. - -![2](./images/2_-_empty_project.png) - -- Enter a name for your Xcode Project. - -![2.5](./images/2_5_analyzer_name.png) - -- The location should be set to the analyzer SDK folder recently downloaded, "SaleaeAnalyzerSdk-1.1.32". Do not create a new folder, this will be done for you by Xcode. -- Click "Create" - -![2.75](./images/2_75_-_project_location.png) - -- Back in Finder, copy the file "rename_analyzer.py" and "source" from the downloaded SDK directory into the freshly created folder, which will have the same name as your new analyzer. Shown here the name is "XcodeAnalyzer" - -![3](./images/3_-_copy_files.png) - -- Open a terminal, and browse to the new project folder in the downloaded SDK folder. -- Run the python script with: - - python rename_analyzer.py - -- first, it will prompt you for the class prefix for all of the source files. All classes and files will be re-named with this prefix. If you type "I2C" the classes and files will be named with "I2CAnalyzer". Please avoid using analyzer in this name, as it will get repeated like this: "I2CAnalyzerAnalyzer" -- Second, it will ask you for the name to display to the user in the "Add Analyzers" menu. This should be the user facing name, and can include spaces. - -![4](./images/4_-_rename_analyzer_script.png) - -- Next, we need to add a target to the Xcode project. Be sure that the project is selected in the Project Navigator on the left, and then click the menu highlighted below to add a target. - -![5](./images/5_-_add_target_button.png) - -- This is the target menu. - -![6](./images/6_-_add_target_menu.png) - -- Select "Framework & Library" under "OS X" in the left column, and select "Library" in the main area. -- Click Next. - -![7](./images/7_-_library_target.png) - -- Enter a product name, we recommend the the same name as the project, since there will only be one product. -- Under framework, select "None (Plain C/C++ Library) -- For Type, select "Dynamic" - -![8](./images/8_-_library_settings.png) - -- Next, we need to add the source files. Click File -> Add Files to ""... -- Note: if this is disabled, it is because you do not have the project selected in the Project Navigator. - -![8.5](./images/8_5_-_add_files_menu.png) - -- Browse to the source folder in the Xcode project folder, and select it. Don't select the contents, be sure to select the folder itself. -- select "Create groups" in the "added folders" section. -- Click Add. - -![9](./images/9_-_add_files.png) - -- Verify that the files were automatically added to the build phases "Compile Sources" and "Headers". -- Select the project from the Project Navigator if not already selected. -- Click "Build Phases". -- Expand "Compile Sources" and "Headers" -- Under "Headers", also expand "Project". -- Verify that each has 4 files. - -![9.5](./images/9_5_-_verify_sources_added.png) - -- Click "Build Settings" -- If "Levels" is selected, switch it to "Combined" - -![10](./images/10_-_build_settings_page.png) - -- Expand the section "Search Paths" -- Locate "Header Search Paths" and edit the value. -- Click the "+" button and enter "../include" in the new entry. - -![11](./images/11_-_header_includes_search_path.png) - -- Locate "Library Search Paths" in the same section, and edit its value. -- Click the "+" button and enter "../lib" in the new entry. - -![11.5](./images/11_5_-_add_library_path.png) - -- Return to "Build Phases" and expand the section "Link Binary with Libraries" -- Click the "+" button - -![12](./images/12_-_add_library_part_1.png) - -- Click "Add Other..." - -![13](./images/13_-_add_library_part_2.png) - -- Browse to the original SDK folder which contains our Xcode folder. -- Open the "lib" folder -- Select "libAnalyzer.dylib" -- Click Open. - -![14](./images/14_-_add_library_part_3.png) - - -- At this point, you should build the project, so that the resulting library will be created. -- It's worth mentioning that new Xcode versions do not save build outputs in the project directory. Instead, the default output directory looks like this: - - ~/Library/Developer/Xcode/DerivedData - -- You may want to change it. **The following steps are optional** - -**Optional: change build output folder** - -- Optional step 1: From the file menu, select "Project Settings..." - -![optional 1](./images/optional_-_project_settings_-_edit_products_folder_menu.png) - -- Optional step 2: in the "Derived Data" dropdown, select "Project-relative Location" -- Click "Done" - -![optional 2](./images/optional_-_Project_Settings.png) - -- That's it for the optional steps. - -### Running and Debugging your Analyzer - -- Next we will setup debugging for the project. Be sure to have the latest Saleae Logic Software installed. -- On the Product menu at the top of the screen, select "Scheme" -> "Edit Scheme..." -![15](./images/15_-_edit_scheme.png) - -- Make sure "Run" is selected on the left. -- Under "Executable" select "Other..." -![16](./images/16_-_debug_launch_app_menu.png) - -- Browse to the "Applications" folder (or wherever Logic is installed) and select "Logic.app" -- Click Choose. - -![17](./images/17_-_select_debug_program.png) - - -- Set a breakpoint in the software so that we can test debugging. -- Open "XcodeAnalyzer.cpp" on the left. The name will reflect what you selected as the class prefix in a previous step. In this example, the class prefix was "Xcode". -- In the source file, add a break point to the first line of code in the WorkerThread method. This code runs when the analyzer starts processing data. - - - -![18](./images/18_-_breakpoint_set.png) - -- Before proceeding, see this article with instructions to configure the software to load your new analyzer: https://support.saleae.com/faq/technical-faq/setting-up-developer-directory -- Be sure to select the folder where the debug version of the custom analyzer is is saved. - -- Once the Saleae logic software has been configured, and has been closed, click run from Xcode. -- The Saleae software should launch a few seconds later. Click the "+" button on the analyzers panel, and then select your analyzer. In this case, the user facing name of the analyzer was set by the Python script to "Xcode Analyzer". Yours may be different. -- If your analyzer is missing, it could indicate that the dylib was not created, or that the developer path was not set properly. Please review the previous steps for any possible errors. -- The settings dialog for your custom analyzer will appear. Click "Save". - -![19](./images/19_-_logic_software_add_analyzer_menu.png) - -- Here is your fresh new analyzer, now added to the software. Note that our breakpoint hasn't fired yet. If you had captured data previously, it might fire now, since analyzers will automatically start processing if they are added to an existing analyzer. - -![20](./images/20_-_analyzer_in_Logic.png) - -- Press start to start a simulation. -- Since your analyzer has already been added, the simulator will call your simulation data generator code. The analyzer also starts processing the moment data has been captured, so your breakpoint should fire immediately. - -![21](./images/21_-_logic_software_start_button.png) - -- Here you can see that the debugger was attached and your break point has been hit. You can examine variables, set new break points, or press continue from the debug menu at the bottom. - -![22](./images/22_-_breakpoint_hit.png) - -- Congratulations! You now have an Xcode project for which you can use to develop a custom analyzer for the Saleae software. - -If you have any questions, please contact support. diff --git a/docs/Analyzer_API.md b/docs/Analyzer_API.md new file mode 100644 index 0000000..f284076 --- /dev/null +++ b/docs/Analyzer_API.md @@ -0,0 +1,1401 @@ +# C++ Analyzer API + +This is the original documentation for the Saleae C++ Analyzer API. It has not been updated since the original release, and some parts are out of date. + +Please refer to support, as well as Saleae's other open source protocol analyzers for reference. + +Changes that need to be reflected in this documentation: + +- Packet and Transaction abstraction was not implemented, in favor of [FrameV2 and HLAs.](https://support.saleae.com/saleae-api-and-sdk/protocol-analyzer-sdk/framev2-hla-support-analyzer-sdk) +- LoadSettings and SaveSettings are no longer used, instead the settings interfaces are serialized automatically by the Logic 2 software. +- The `NeedsRerun` function is no longer used. +- Your analyzer must inherit from `Analyzer2`, not `Analyzer`, and implement an additional virtual function `SetupResults`. See other Saleae analyzers for examples. + + +# Writing your Analyzer’s Code + +This second part of the document deals with writing the code for your analyzer. + +There are 4 C++ files and 4 header files that you will implement to create your analyzer. If you followed the procedure in the first part, you already have a working analyzer, and will be modifying that code to suit your needs. + +Conceptually, the analyzer can be broken into 4 main parts – the 4 c++ files. Working on them in a +particular order is highly recommended, and this document describes the procedure in this order. + +First you’ll work on the AnalyzerSettings-derived class. You’ll define the settings your analyzer needs, and create interfaces that’ll allow the Logic software to display a GUI for the settings. You’ll also implement serialization for these settings so they can be saved and recalled from disk. + +Next you implement the SimulationDataGenerator class. Here you’ll generate simulated data that can be later to test your analyzer, or provide an example of what your analyzer expects. + +Third you’ll create your AnalyzerResults-derived class. This class translates saved results into text for a variety of uses. Here you’ll start thinking about the format your results will be saved in. You probably will revisit your this file after implementing your Analyzer. + +Lastly, you’ll implement your Analyzer-derived class. The main thing you’ll do here is translate data +streams into results, based on your protocol. + +Let’s get started! + +---------- + + +# Analyzer Settings + +After setting up your analyzer project, and renaming the source files to match your project, the first step is to implement/modify your analyzer’s *AnalyzerSettings* - derived class. + +**{YourName}AnalyzerSettings.h** + +In this file, you provide a declaration for your *{YourName}AnalyzerSettings* class. This class must inherit from *AnalyzerSettings*, and should include the *AnalyzerSettings.h* header file. + +We’ll start with this + + + #ifndef SIMPLESERIAL_ANALYZER_SETTINGS + #define SIMPLESERIAL_ANALYZER_SETTINGS + + #include + #include + + class SimpleSerialAnalyzerSettings : public AnalyzerSettings + { + public: + SimpleSerialAnalyzerSettings(); + virtual ~SimpleSerialAnalyzerSettings(); + virtual bool SetSettingsFromInterfaces(); + void UpdateInterfacesFromSettings(); + virtual void LoadSettings( const char* settings ); + virtual const char* SaveSettings(); + }; + #endif //SIMPLESERIAL_ANALYZER_SETTINGS + +In addition, your header will define two sets of variables: + +***User-modifiable settings*** + +This will always include at least one variable of the type *Channel* – so the user can specify which input channel to use. This cannot be hard coded, and must be exposed as a setting. ( *Channel* isn’t just an index, it also specifies which Logic device the channel is from). Other possible settings depend on your protocol, and might include: + +-  Bit rate +-  Bits per transfer +-  Bit ordering (MSb first, LSb first) +-  Clock edge (rising, falling) to use +-  Enable line polarity +-  Etc – anything you need for your specific protocol. If you like, start with just the Channel variable(s), and you can add more later to make your analyzer more flexible. + +The variable types can be whatever you like – *std::string*, *double*, *int*, *enum*, etc. Note that these +variables will need to be serialized (saved for later, to a file) so when in doubt, stick to simple types +(rather than custom classes or structs). The SDK provides a means to serialize and store your variables. + +***AnalyzerSettingsInterfaces*** + +One of the services the Analyzer SDK provides is a means for users to edit your settings, with a GUI, with minimal work on your part. To make this possible, each of your settings variables must have a corresponding interface object. Here are the available *AnalyzerSettingsInterface* types: + + +-  AnalyzerSettingInterfaceChannel: Used exclusively for input channel selection. +-  AnalyzerSettingInterfaceNumberList: Used to provide a list of numerical options for the user to choose from. Note that this can be used to select from several enum types as well, as illustrated below. (Each dropdown below is implemented with its own interface object) +-  AnalyzerSettingInterfaceInteger: Allows a user to type an integer into a box. +-  AnalyzerSettingInterfaceText: Allows a user to enter some text into a textbox. +-  AnalyzerSettingInterfaceBool: Provides the user with a checkbox. + +*AnalyzerSettingsInterface* types should be declared as pointers. (We’re using the *std::auto_ptr* type, +which largely acts like a standard (raw) pointer. It’s a simple form of what’s called a “smart pointer” and it automatically calls *delete* on its contents when it goes out of scope.) + +**{YourName}AnalyzerSettings.cpp** + +***The Constructor*** +In your constructor, we’ll first initialize all your settings variables to their default values. Second, we’ll setup each variable’s corresponding interface object. + +Note that if the user has previously entered values for this analyzer, these will be loaded at a later time. Be sure to initialize the variables to values that you want to be defaults. These will show up when a user adds a new instance of your analyzer. + +**Setting up each AnalyzerSettingInterface object** + +First, we create the object (call new) and assign the value to the interface’s pointer. Note that we’re +using *std::auto_ptr*, so this means calling the member function *reset*. For standard (raw pointers), you would do something like: + + mInputChannelInterface = new AnalyzerSettingInterfaceChannel(); + +Next, we call the member function *SetTitleAndTooltip*. The title will appear to the left of the input +element. Note that often times you won’t need a title, but you should use one for *Channels*. The tooltip shows up when hovering over the input element. + + void SetTitleAndTooltip( const char* title, const char* tooltip ); + mInputChannelInterface->SetTitleAndTooltip( "Serial", "Standard Async Serial" ); + +We’ll want to set the value. The interface object is, well, an interface to our settings variables. When setting up the interface, we copy the value from our settings variable to the interface. When the user makes a change, we copy the value in the interface to our settings variable. The function names for this differ depending on the type of interface. + + void SetChannel( const Channel& channel ); + void SetNumber( double number ); + void SetInteger( int integer ); + void SetText( const char* text ); + void SetValue( bool value ); + +We’ll want to specify the allowable options. This depends on the type of interface. + +*AnalyzerSettingInterfaceChannel* + + + void SetSelectionOfNoneIsAllowed( bool is_allowed ); + +Some channels can be optional , but typically they are not. By default, the user must select a channel. + +*AnalyzerSettingInterfaceNumberList* + + + void AddNumber( double number, const char* str, const char* tooltip ); + +Call AddNumber for every item you want in the dropdown. *number* is the value associated with the +selection; it is not displayed to the user. + +*AnalyzerSettingInterfaceInteger* + + + void SetMax( int max ); + void SetMin( int min ); + +You can set the allowable range for the integer the user can enter. + +*AnalyzerSettingInterfaceText* + + + void SetTextType( TextType text_type ); + +By default, this interface just provides a simple textbox for the user to enter text, but you can also +specify that the text should be a path, which will cause a browse button to appear. The options are +*NormalText*, *FilePath*, or *FolderPath*. + +*AnalyzerSettingInterfaceBool* + +There are only two allowable options for the bool interface (checkbox). + +After creating our interfaces (with *new* ), giving them a titles, settings their values, and specifying their allowed options, we need to expose them to the API. We do that with function AddInterface. + + + void AddInterface( AnalyzerSettingInterface* analyzer_setting_interface ); + +**Specifying the export options** + +Analyzers can offer more than one export type. For example txt or csv, or even a wav file or bitmap. If these need special settings, they can be specified as analyzer variables/interfaces as we’ve discussed. + +Export options are assigned an ID. Later, when your function for generating export data is called, this ID will be provided. There are two functions you’ll need to call to specify an export type. Be sure to specify at least one export type (typically text/csv). + + + void AddExportOption( U32 user_id, const char* menu_text ); + void AddExportExtension( U32 user_id, const char * extension_description, const char * + extension ); + AddExportOption( 0, "Export as text/csv file" ); + AddExportExtension( 0, "text", "txt" ); + AddExportExtension( 0, "csv", "csv" ); + +**Specifying which channels are in use** + +The analyzer must indicate which channel(s) it is using. This is done with the *AddChannel* function. +Every time the channel changes (such as when the user changes the channel) the reported channel must be updated. To clear any previous channels that have been set, call *ClearChannels*. + + + void ClearChannels(); + void AddChannel( Channel& channel, const char* channel_label, bool is_used ); + ClearChannels(); + AddChannel( mInputChannel, "Serial", false ); + +Note that in the constructor, we have set *is_used* to *false*. This is because by default our channel is set to *UNDEFINED_CHANNEL.* After the user has set the channel to something other than +*UNDEFINED_CHANNEL* , we would specify *true*. It would always be true, unless the channel was optional, in which case you will need to examine the channel value, and specify false if the channel is set to *UNDEFINED_CHANNEL*. We’ll discuss this later as it comes up. + +***The Destructor*** + +Generally you won’t need to do anything in your *AnalyzerSettings* - derived class’s destructor. However, if you are using standard (raw) pointers for your settings interfaces, you’ll need to delete them here. + +***bool {YourName}AnalyzerSettings::SetSettingsFromInterfaces()*** + +As the name implies, in this function we will copy the values saved in our interface objects to our +settings variables. This function will be called if the user updates the settings. + +We can also examine the values saved in the interface (the user’s selections) and choose to reject +combinations we don’t want to allow. If you want to reject a particular selection, do not assign the +values in the interfaces to your settings variables – use temporary variables so you can choose not to assign them at the last moment. To reject a user’s selections, return *false* ; otherwise return *true*. If you return false (reject the user’s settings), you also need to call *SetErrorText* to indicate why. This will be presented to the user in a popup dialog. + + + void SetErrorText( const char* error_text ); + +For example, when using more than one channel, you would typically want to make sure that all the channels are different. You can use the *AnalyzerHelpers::DoChannelsOverlap* function to make that easier if you like. + +For your analyzer, it’s quite possible that all possible user selections are valid. In that case you can +ignore the above. + +After assigning the interface values to your settings variables, you also need to update the channel(s) the analyzer will report as being used. Below is an example from *SimpleSerialAnalyzerSettings*. + + + bool SimpleSerialAnalyzerSettings::SetSettingsFromInterfaces() + { + mInputChannel = mInputChannelInterface->GetChannel(); + mBitRate = mBitRateInterface->GetInteger(); + ClearChannels(); + AddChannel( mInputChannel, "Simple Serial", true ); + return true; + } + +***void {YourName}AnalyzerSettings::UpdateInterfacesFromSettings()*** + +*UpdateInterfacesFromSettings* goes in the opposite direction. In this function, update all your interfaces with the values from your settings variables. Below is an example from *SimpleSerialAnalyzerSettings*. + + + void SimpleSerialAnalyzerSettings::UpdateInterfacesFromSettings() + { + mInputChannelInterface->SetChannel( mInputChannel ); + mBitRateInterface->SetInteger( mBitRate ); + } + +***void {YourName}AnalyzerSettings::LoadSettings( const char* settings )*** + +In the last to functions of your AnalyzerSettings-derived class, you’ll implement serialization +(persistence) of your settings. It’s pretty straightforward. + +Your settings are saved in, and loaded from, a single string. You can technically serialize all of your +variables into a string anyway you like, including third part libraries like boost, but to keep things simple we provided a mechanism to serialize your variables. We’ll discuss that here. + +First, you’ll need a *SimpleArchive* object. This will perform serialization for us. Use *SetString* to provide the archive with our settings string. This string is passed in as a parameter to *LoadSettings*. + + + class LOGICAPI SimpleArchive + { + public: + SimpleArchive(); + ~SimpleArchive(); + void SetString( const char* archive_string ); + const char* GetString(); + bool operator<<( U64 data ); + bool operator<<( U32 data ); + bool operator<<( S64 data ); + bool operator<<( S32 data ); + bool operator<<( double data ); + bool operator<<( bool data ); + bool operator<<( const char* data ); + bool operator<<( Channel& data ); + bool operator>>( U64& data ); + bool operator>>( U32& data ); + bool operator>>( S64& data ); + bool operator>>( S32& data ); + bool operator>>( double& data ); + bool operator>>( bool& data ); + bool operator>>( char const ** data ); + bool operator>>( Channel& data ); + protected: + struct SimpleArchiveData* mData; + }; + +Next we will use the archive to loaf all of our settings variables, using the overloaded *>>* operator. This operator returns *bool* – it will return *false* if the requested type is not exactly in the right place in the archive. This could happen if you change the settings variables over time, and a user tries to load an old settings string. If loading fails, you can simply not update that settings variable (it will retain its default value). + +Since our channel values may have changed, we will also need to update the channels we’re reporting as using. We need to do this every times settings change. + +Lastly, call *UpdateInterfacesFromSettings.* This will update all our interfaces to reflect the newly loaded values. + +Below is an example from *SimpleSerialAnalyzerSettings*. + + + void SimpleSerialAnalyzerSettings::LoadSettings( const char* settings ) + { + SimpleArchive text_archive; + text_archive.SetString( settings ); + text_archive >> mInputChannel; + text_archive >> mBitRate; + ClearChannels(); + AddChannel( mInputChannel, "Simple Serial", true ); + UpdateInterfacesFromSettings(); + } + +***const char* {YourName}AnalyzerSettings::SaveSettings()*** + +Our last function will save all of our settings variables into a single string. We’ll use *SimpleArchive* to serialize them. +The order in which we serialize our settings variables must be exactly the same order as we extract them, in *LoadSettings*. +When returning, use the *SetReturnString* function, as this will provide a pointer to a string that will not go out of scope when the function ends. + +Bellow is an example from *SimpleSerialAnalyzerSettings*: + + + const char* SimpleSerialAnalyzerSettings::SaveSettings() + { + SimpleArchive text_archive; + text_archive << mInputChannel; + text_archive << mBitRate; + return SetReturnString( text_archive.GetString() ); + } + + +## SimulationDataGenerator + +The next step after creating your *{YourName}AnalyzerSettings* files, is to create your +*SimulationDataGenerator*. + +Your *SimulationDataGenerator* class provides simulated data so that you can test your analyzer against controlled, predictable waveforms. Generally you should make the simulated data match the user settings, so you can easily test under a variety of expected conditions. In addition, simulated data gives end users an example of what to expect when using your analyzer, as well as examples of what the waveforms should look like. + +That said, fully implementing simulated data is not absolutely required to make an analyzer. +{YourName}SimulationDataGenerator.h + +Besides the constructor and destructor, there are only two required functions, and two required +variables. Other functions and variables can be added, to help implement your simulated data. Here is an example starting point, from *SimpleSerialSimulationDataGenerator.h* + + + #ifndef SIMPLESERIAL_SIMULATION_DATA_GENERATOR + #define SIMPLESERIAL_SIMULATION_DATA_GENERATOR + + #include + + class SimpleSerialAnalyzerSettings; + + class SimpleSerialSimulationDataGenerator + { + public: + SimpleSerialSimulationDataGenerator(); + ~SimpleSerialSimulationDataGenerator(); + void Initialize( U32 simulation_sample_rate, SimpleSerialAnalyzerSettings* + settings ); + U32 GenerateSimulationData( U64 newest_sample_requested, U32 sample_rate, + SimulationChannelDescriptor** simulation_channel ); + protected: + SimpleSerialAnalyzerSettings* mSettings; + U32 mSimulationSampleRateHz; + SimulationChannelDescriptor mSerialSimulationData; + }; + #endif //SIMPLESERIAL_SIMULATION_DATA_GENERATOR + +***Overview*** + +The key to the SimulationDataGenerator is the class *SimulationChannelDescriptor*. You will need one of these for every channel you will be simulated (serial, for example, only needs to simulate on one channel). When your *GenerateSimulationData* function is called, your job will be to generate additional simulated data, up to the amount requested. When complete, you provide the caller with a pointer to an array of your *SimulationChannelDescriptor* objects. +We’ll go over this in detail in a minute. + +**{YourName}SimulationDataGenerator.cpp** + +***Constructor/Destructor*** + +You may or may not need anything in your constructor or destructor. For now at least, leave them +empty. At the time we’re constructed, we really have no idea what the settings are or anything else, so there’s not much we can do at this point. + +**void {YourName}SimulationDataGenerator::Initialize( U32 simulation_sample_rate,** +**{YourName}AnalyzerSettings* settings )** + +This function provides you with the state of things as they are going to be when we start simulating. +We’ll need to save this information. + +First, save *simulation_sample_rate* and *settings* to member variables. Notice that we now have a +pointer to our *AnalyzerSettings* - derived class. This is good, now we know what all the settings will be for our simulation – which channel(s) it will be on, as well as any other settings we might need – like if the signal is inverted, etc. + +Next, we’ll want to initialize the state of our *SimulationChannelDescriptor* objects – we need to set what channel it’s for, the sample rate, and the initial bit state (high or low). + +At this point we’ll need to take a step back and discuss some key concepts. + +**BitState** + +*BitState* is a type used often in the SDK. It can be either *BIT_LOW* or BIT_HIGH, and represents a +channel’s logic state. + +**Sample Rate (samples per second)** + +Sample Rate refers to how many samples per second the data is. Typically it refers to how fast we’re collecting data, but for simulation, it refers to how fast we’re generating sample data. + +**Sample Number** + +This is the absolute sample number, starting at sample 0. When a data collection starts, the first sample collected is Sample Number 0. The next sample collected is Sample Number 1, etc. This is the same in simulation. The first sample we’ll provide is Sample Number 0, and so on. + +**SimulationChannelDescriptor** + +We need this object to describe a single channel of data, and what its waveform looks like. We do this in a very simple way: + + +- We provide the initial state of the channel (BIT_LOW, or BIT_HIGH) +- We move forward some number of samples, and then toggle the channel. + - We repeatedly do this + +The initial bit state of the channel never changes. The state (high or low) of a particular sample number can be determined by knowing how many times it has toggled up to that point (an even or odd number of times). + +Put another way: + +-  In the very beginning, we specify the initial state (BIT_LOW or BIT_HIGH). This is the state of Sample Number 0. +-  Then, we move forward (advance) some number of samples. 20 samples, for example. +-  Then, we toggle the channel (low becomes high, high becomes low). +-  Then we move forward (advance) some more. Maybe 100 samples this time. +-  Then we toggle again. +-  Then we move forward again, and then we toggle again, etc. + +Let’s explore the functions used to do this: + +*void Advance( U32 num_samples_to_advance );* + +As you might guess, this is how we move forward in our simulated waveform. Internally, the object +keeps track of what its Sample Number is. The Sample Number starts at 0. After calling *Advance( 10 )* x 3 times, the Sample Number will be 30. + +*void Transition();* + +This toggles the channel. *BIT_LOW* becomes *BIT_HIGH* , *BIT_HIGH* becomes *BIT_LOW*. The current +Sample Number will become the new *BitState* (BIT_LOW or BIT_HIGH), and all samples after that will also be the new *BitState* , until we toggle again. + +*void TransitionIfNeeded( BitState bit_state );* + +Often we don’t want to keep track of the current *BitState* , which toggles every time we call *Transition*. +*TransitionIfNeeded* checks the current *BitState* , and only transitions if the current *BitState* doesn’t match the one we provide. In other words “Change to this *bit_state* , if we’re not already”. + +*BitState GetCurrentBitState();* + +This function lets you directly ask what the current *BitState* is. + +*U64 GetCurrentSampleNumber();* + +This function lets you ask what the current *SampleNumber* is. + +**ClockGenerator** + +A common issue with converting exact timing values into numbers-of-samples, is that you lose some precision. This isn’t always a problem, but it’s nice to have a way to keep track of how much error is building up, and then, just at the right times, add an extra sample in so that on average, the timing is exact. + +*ClockGenerator* is a class provided in *AnalyzerHelpers.h* which will let you enter time values, rather than numbers-of-samples. For example, instead of figuring out how many samples are in 500ns, you can just use *ClockGenerator* to both figure it out and manage the error, so that on average, your timing is perfect. + +Initially we created *ClockGenerator* to create clock-like signals, but now you can use and time value, any time. Here’s how: + +*void Init( double target_frequency, U32 sample_rate_hz );* + +You’ll need to call this before using the class. For *sample_rate_hz* , enter the sample rate we’ll be +generating data at. For *target_frequency* , enter the frequency (in hz) you will most commonly be using. For example, the bit rate of a SPI clock, etc. + +*U32 AdvanceByHalfPeriod( double multiple = 1.0 );* + +This function returns how many samples are needed to move forward by one half of the period (for +example, the low time for a perfect square wave). You can also enter a multiple. For example, to get the number of samples to move forward for a full period, enter 2.0. + +*U32 AdvanceByTimeS( double time_s );* + +This functions provides number of samples needed to advance by the arbitrary time, *time_s*. Note that this is in seconds, so enter 1E-6 for for one microsecond, etc. + +Note that the number of samples for a specific time period may change slightly every once in a while. +This is so that on average, timing will be exact. + +You may want to have a *ClockGenerator* as a member of you class. This makes it easy to use from any helper functions you might create. + +**void {YourName}SimulationDataGenerator::Initialize( U32 simulation_sample_rate,** +**{YourName}AnalyzerSettings* settings )** + +Let’s take another look at the *Initialize* function, now that we have an idea what’s going on. This +example is from *SimpleSerialSimulationDataGenerator.cpp*. + + + void SimpleSerialSimulationDataGenerator::Initialize( U32 simulation_sample_rate, + SimpleSerialAnalyzerSettings* settings ) + { + mSimulationSampleRateHz = simulation_sample_rate; + mSettings = settings; + mSerialSimulationData.SetChannel( mSettings->mInputChannel ); + mSerialSimulationData.SetSampleRate( simulation_sample_rate ); + mSerialSimulationData.SetInitialBitState( BIT_HIGH ); + } + +**U32 {YourName}SimulationDataGenerator::GenerateSimulationData( U64** +**largest_sample_requested, U32 sample_rate, SimulationChannelDescriptor** simulation_channel )** + +This function is repeatedly called to request more simulated data. When it’s called, just keep going +where you left off. In addition, you can generate more data that requested, to make things easy -- that way you don’t have to stop half way in the middle of something and try to pick it back up later exactly where you left off. + +When we leave the function, our Sample Number – in our *SimulationChannelDescriptor* object(s) must be equal to or larger than *largest_sample_requested*. Actually, this number needs to first be adjusted (for technical reasons related to future compatibility). Use the helper function +*AdjustSimulationTargetSample* to do this, as we’ll see in a moment. + +The parameter *simulation_channels* is to provide the caller with a pointer to an array of your +*SimulationChannelDescriptor* objects. We’ll set this pointer at the end of the function. The return value is the number of elements in the array – the number of channels. + +The primary task of the function is to generate the simulation data, which we typically do in a loop – checking until we have generated enough data. A clean way of doing this is to generate a complete piece (possibly a full transaction) of your protocol in a helper function. Then just repeatedly call this function until enough data has been generated. You can also add spacing between the elements of your protocol as you like. + +Here is an example from *SimpleSerialSimulationDataGenerator.cpp*. We’re going to be outputting chars from a string, which we initialized in our constructor as shown. + + + SimpleSerialSimulationDataGenerator::SimpleSerialSimulationDataGenerator() + : mSerialText( "My first analyzer, woo hoo!" ), + mStringIndex( 0 ) + { + } + U32 SimpleSerialSimulationDataGenerator::GenerateSimulationData( U64 + largest_sample_requested, U32 sample_rate, SimulationChannelDescriptor** + simulation_channel ) + { + U64 adjusted_largest_sample_requested = + AnalyzerHelpers::AdjustSimulationTargetSample( largest_sample_requested, sample_rate, + mSimulationSampleRateHz ); + while( mSerialSimulationData.GetCurrentSampleNumber() < + adjusted_largest_sample_requested ) + { + CreateSerialByte(); + } + *simulation_channel = &mSerialSimulationData; + return 1; + } + void SimpleSerialSimulationDataGenerator::CreateSerialByte() + { + U32 samples_per_bit = mSimulationSampleRateHz / mSettings->mBitRate; + U8 byte = mSerialText[ mStringIndex ]; + mStringIndex++; + if( mStringIndex == mSerialText.size() ) + mStringIndex = 0; + //we're currently high + //let's move forward a little + mSerialSimulationData.Advance( samples_per_bit * 10 ); + mSerialSimulationData.Transition(); //low-going edge for start bit + mSerialSimulationData.Advance( samples_per_bit ); //add start bit time + U8 mask = 0x1 << 7; + for( U32 i=0; i<8; i++ ) + { + if( ( byte & mask ) != 0 ) + mSerialSimulationData.TransitionIfNeeded( BIT_HIGH ); + else + mSerialSimulationData.TransitionIfNeeded( BIT_LOW ); + mSerialSimulationData.Advance( samples_per_bit ); + mask = mask >> 1; + } + mSerialSimulationData.TransitionIfNeeded( BIT_HIGH ); //we need to end high + //lets pad the end a bit for the stop bit: + mSerialSimulationData.Advance( samples_per_bit ); + } + +There are a few things we could do to clean this up. First, we could save the *samples_per_bit* as a +member variable, and compute it only once, in the *Initialize* function. If we wanted to be more accurate, we could use the *ClockGenerator* class to pre-populate an array of *samples_per_bit* values, so on average the timing would be perfect. We would use this as a lookup each time we *Advance* one bit. +Another thing we could do is use the *DataExtractor* class to take care of the bit masking/testing. +However, in our simple example what we have works well enough, and it has the advantage of being a bit more transparent. + +**Simulating Multiple Channels** + +Simulating multiple channels requires multiple *SimulationChannelDescriptors*, and they must be in an array. The best way to this is to use the helper class, *SimulationChannelDescriptorGroup*. + +Here is an example of I2C (2 channels)—these are the the member variable definitions in +*I2cSimulationDataGenerator.h* : + + + SimulationChannelDescriptorGroup mI2cSimulationChannels; + SimulationChannelDescriptor* mSda; + SimulationChannelDescriptor* mScl; + +Then, in the *Initialize* function: + + + mSda = mI2cSimulationChannels.Add( settings->mSdaChannel, mSimulationSampleRateHz, + BIT_HIGH ); + mScl = mI2cSimulationChannels.Add( settings->mSclChannel, mSimulationSampleRateHz, + BIT_HIGH ); + +And to provide the array to the caller of *GenerateSimulationData* : + + + *simulation_channels = mI2cSimulationChannels.GetArray(); + return mI2cSimulationChannels.GetCount(); + +You can use each *SimulationChannelDescriptor* object pointer separately, calling *Advance*, *Transition*, etc. on each one, or you can manipulate them as a group, using the *AdvanceAll* method of the *SimulationChannelDescriptorGroup* object. + + + void AdvanceAll( U32 num_samples_to_advance ); + +Before returning from *GenerateSimulationData* , be sure that the Sample Number of *all* of your +*SimulationChannelDescriptor* objects exceed *adjusted_largest_sample_requested*. + +Examples of generating simulation data + +**void SerialSimulationDataGenerator::CreateSerialByte( U64 value )** + + + void SerialSimulationDataGenerator::CreateSerialByte( U64 value ) + { + // assume we start high + mSerialSimulationData.Transition(); // low-going edge for start bit + mSerialSimulationData.Advance( mClockGenerator.AdvanceByHalfPeriod() ); // add + start bit time if( mSettings->mInverted == true ) value = ~value; + U32 num_bits = mSettings->mBitsPerTransfer; + BitExtractor bit_extractor( value, mSettings->mShiftOrder, num_bits ); + for( U32 i = 0; i < num_bits; i++ ) + { + mSerialSimulationData.TransitionIfNeeded( bit_extractor.GetNextBit() ); + mSerialSimulationData.Advance( mClockGenerator.AdvanceByHalfPeriod() ); + } + if( mSettings->mParity == AnalyzerEnums::Even ) + { + if( AnalyzerHelpers::IsEven( AnalyzerHelpers::GetOnesCount( value ) ) == true ) + mSerialSimulationData.TransitionIfNeeded( mBitLow ); // we want to + add a zero bit else mSerialSimulationData.TransitionIfNeeded( mBitHigh ); // we want to + add a one bit mSerialSimulationData.Advance( mClockGenerator.AdvanceByHalfPeriod() ); + } + else if( mSettings->mParity == AnalyzerEnums::Odd ) + { + if( AnalyzerHelpers::IsOdd( AnalyzerHelpers::GetOnesCount( value ) ) == true ) + mSerialSimulationData.TransitionIfNeeded( mBitLow ); // we want to + add a zero bit else mSerialSimulationData.TransitionIfNeeded( mBitHigh ); + mSerialSimulationData.Advance( mClockGenerator.AdvanceByHalfPeriod() ); + } + mSerialSimulationData.TransitionIfNeeded( mBitHigh ); // we need to end high + // lets pad the end a bit for the stop bit: + mSerialSimulationData.Advance( mClockGenerator.AdvanceByHalfPeriod( mSettings - > mStopBits ) ); + } + +Note that above we use a number of helper functions and classes. Let’s discuss BitExtractor briefly. + +*BitExtractor* + + + BitExtractor( U64 data, AnalyzerEnums::ShiftOrder shift_order, U32 num_bits ); + BitState GetNextBit(); + +Some protocols have variable numbers of bits per word, and settings for if the most significant bit is first or last. This can be a pain to manage, so we made the *BitExtractor* class. This can be done by hand of course if you like, but this class tends to tidy up the code quite a bit in our experience. + +Similar, but reversed, is the *DataBuilder* class, but as this generally used for collecting data, we’ll talk more about it then. + +*AnalyzerHelpers* + +Some static helper functions that might be helpful, grouped under the class *AnalyzerHelpers*, include: + + + static bool IsEven( U64 value ); + static bool IsOdd( U64 value ); + static U32 GetOnesCount( U64 value ); + static U32 Diff32( U32 a, U32 b ); + +**void I2cSimulationDataGenerator::CreateBit( BitState bit_state )** + + + void I2cSimulationDataGenerator::CreateBit( BitState bit_state ) + { + if( mScl->GetCurrentBitState() != BIT_LOW ) + AnalyzerHelpers::Assert( "CreateBit expects to be entered with scl low" ); + mI2cSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 0.5 ) ); + mSda->TransitionIfNeeded( bit_state ); + mI2cSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 0.5 ) ); + mScl->Transition(); // posedge + mI2cSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 1.0 ) ); + mScl->Transition(); // negedge + } + void I2cSimulationDataGenerator::CreateI2cByte( U8 data, + I2cResponse reply ) void I2cSimulationDataGenerator::CreateI2cByte( U8 data, + I2cResponse reply ) + { + if( mScl->GetCurrentBitState() == BIT_HIGH ) + { + mI2cSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 1.0 ) ); + mScl->Transition(); + mI2cSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 1.0 ) ); + } + BitExtractor bit_extractor( data, AnalyzerEnums::MsbFirst, 8 ); + for( U32 i = 0; i < 8; i++ ) + { + CreateBit( bit_extractor.GetNextBit() ); + } + if( reply == I2C_ACK ) + CreateBit( BIT_LOW ); + else + CreateBit( BIT_HIGH ); + mI2cSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 4.0 ) ); + } + +*void I2cSimulationDataGenerator::CreateI2cTransaction( U8 address, I2cDirection direction, U8 data )* + + + void I2cSimulationDataGenerator::CreateI2cTransaction( U8 address, I2cDirection direction, U8 data ) + { + U8 command = address << 1; + if( direction == I2C_READ ) + command |= 0x1; + CreateStart(); + CreateI2cByte( command, I2C_ACK ); + CreateI2cByte( data, I2C_ACK ); + CreateI2cByte( data, I2C_NAK ); + CreateStop(); + } + U32 I2cSimulationDataGenerator::GenerateSimulationData( U64 largest_sample_requested, U32 sample_rate, + SimulationChannelDescriptor** simulation_channels ) U32 + I2cSimulationDataGenerator::GenerateSimulationData( U64 largest_sample_requested, U32 sample_rate, + SimulationChannelDescriptor** simulation_channels ) + { + U64 adjusted_largest_sample_requested = + AnalyzerHelpers::AdjustSimulationTargetSample( largest_sample_requested, sample_rate, mSimulationSampleRateHz ); + while( mScl->GetCurrentSampleNumber() < adjusted_largest_sample_requested ) + { + CreateI2cTransaction( 0xA0, I2C_READ, mValue++ ); + mI2cSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 10 .0 ) ); // insert 10 bit-periods of idle + } + *simulation_channels = mI2cSimulationChannels.GetArray(); + return mI2cSimulationChannels.GetCount(); + } + +*void SpiSimulationDataGenerator::OutputWord_CPHA1( U64 mosi_data, U64 miso_data )* + + + void SpiSimulationDataGenerator::OutputWord_CPHA1( U64 mosi_data, U64 miso_data ) + { + BitExtractor mosi_bits( mosi_data, mSettings->mShiftOrder, mSettings - > mBitsPerTransfer ); + BitExtractor miso_bits( miso_data, mSettings->mShiftOrder, mSettings - > mBitsPerTransfer ); + U32 count = mSettings->mBitsPerTransfer; + for( U32 i = 0; i < count; i++ ) + { + mClock->Transition(); // data invalid + mMosi->TransitionIfNeeded( mosi_bits.GetNextBit() ); + mMiso->TransitionIfNeeded( miso_bits.GetNextBit() ); + mSpiSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( .5 ) ); + mClock->Transition(); // data valid + mSpiSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( .5 ) ); + } + mMosi->TransitionIfNeeded( BIT_LOW ); + mMiso->TransitionIfNeeded( BIT_LOW ); + mSpiSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 2.0 ) ); + } + +*void SpiSimulationDataGenerator::CreateSpiTransaction()* + + + void SpiSimulationDataGenerator::CreateSpiTransaction() + { + if( mEnable != NULL ) + mEnable->Transition(); + mSpiSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 2.0 ) ); + if( mSettings->mDataValidEdge == AnalyzerEnums::LeadingEdge ) + { + OutputWord_CPHA0( mValue, mValue + 1 ); + mValue++; + OutputWord_CPHA0( mValue, mValue + 1 ); + mValue++; + OutputWord_CPHA0( mValue, mValue + 1 ); + mValue++; + if( mEnable != NULL ) + mEnable->Transition(); + OutputWord_CPHA0( mValue, mValue + 1 ); + mValue++; + } + else + { + OutputWord_CPHA1( mValue, mValue + 1 ); + mValue++; + OutputWord_CPHA1( mValue, mValue + 1 ); + mValue++; + OutputWord_CPHA1( mValue, mValue + 1 ); + mValue++; + if( mEnable != NULL ) + mEnable->Transition(); + OutputWord_CPHA1( mValue, mValue + 1 ); + mValue++; + } + } + +*U32 SpiSimulationDataGenerator::GenerateSimulationData( U64 largest_sample_requested, U32* +*sample_rate, SimulationChannelDescriptor** simulation_channels )* + + + U32 SpiSimulationDataGenerator::GenerateSimulationData( U64 largest_sample_requested, U32 sample_rate, SimulationChannelDescriptor** simulation_channels ) + { + U64 adjusted_largest_sample_requested = + AnalyzerHelpers::AdjustSimulationTargetSample( largest_sample_requested, sample_rate, mSimulationSampleRateHz ); + while( mClock->GetCurrentSampleNumber() < adjusted_largest_sample_requested ) + { + CreateSpiTransaction(); + mSpiSimulationChannels.AdvanceAll( mClockGenerator.AdvanceByHalfPeriod( 10.0 ) ); // insert 10 bit-periods of idle + } + *simulation_channels = mSpiSimulationChannels.GetArray(); + return mSpiSimulationChannels.GetCount(); + } + +**AnalyzerResults** + +After creating your *SimulationDataGenerator* class, working on your *{YourName}AnalyzerResults* files is the next step. + +*AnalyzerResults* is what we use to transform our results into text for display and as well as exported files, etc. + +Tip: You may end up finalizing may of the details about how your results are saved when you work on your main *Analyzer* file – *{YourName}Analyzer.cpp/.h* ; You can simply implement the bare minimum of the functions in your *{YourName}AnalyzerResults.cpp* file, and come back to it later. + +**{YourName}AnalyzerResults.h** + +In addition to the constructor and destructor, there are 5 functions we’ll need to implement. +*AnalyzerResults* is fairly straightforward, so typically we won’t need much in the way of helper functions or member variables. + +Here’s the *SimpleSerialAnalyzerResults* header file. Yours will like very similar, with the only difference typically being the *enums* and/or *defines* you need. + + #ifndef SIMPLESERIAL_ANALYZER_RESULTS + #define SIMPLESERIAL_ANALYZER_RESULTS + + #include + + class SimpleSerialAnalyzer; + class SimpleSerialAnalyzerSettings; + + class SimpleSerialAnalyzerResults : public AnalyzerResults + { + public: + SimpleSerialAnalyzerResults( SimpleSerialAnalyzer* analyzer, + SimpleSerialAnalyzerSettings* settings ); + virtual ~SimpleSerialAnalyzerResults(); + virtual void GenerateBubbleText( U64 frame_index, Channel& channel, DisplayBase + display_base ); + virtual void GenerateExportFile( const char* file, DisplayBase display_base, U32 + export_type_user_id ); + virtual void GenerateFrameTabularText(U64 frame_index, DisplayBase display_base ); + virtual void GeneratePacketTabularText( U64 packet_id, DisplayBase display_base ); + virtual void GenerateTransactionTabularText( U64 transaction_id, DisplayBase + display_base ); + protected: //functions + protected: //vars + SimpleSerialAnalyzerSettings* mSettings; + SimpleSerialAnalyzer* mAnalyzer; + }; + #endif //SIMPLESERIAL_ANALYZER_RESULTS + +**{YourName}AnalyzerResults.cpp** + +In your constructor, save copies of the *Analyzer* and *Settings* raw pointers provided. There’s generally nothing else to do for the constructor or destructor. Below is an example from +*SimpleSerialAnalyzerResults.cpp:* + + + SimpleSerialAnalyzerResults::SimpleSerialAnalyzerResults( SimpleSerialAnalyzer* analyzer, + SimpleSerialAnalyzerSettings* settings ) + : AnalyzerResults(), + mSettings( settings ), + mAnalyzer( analyzer ) + { + } + +***Frames, Packets, and Transactions*** + +The basic result an analyzer generates is called a *Frame*. This could be byte of serial data, the header of a CAN packet, the MOSI and MISO values from 8-bit of SPI, etc. Smaller elements, such the Start and Stop events in I2C can be saved as *Frames* , are probably better saved as be graphical elements (called *Markers* ) and otherwise ignored. *Collections* of Frames make up *Packets* , and collections of *Packets* make up *Transactions*. + +95% of what you will be concerned about is *Frames*. What exactly a *Frame* represents is your choice, but unless your protocol is fairly complicated (such as USB, CAN, Ethernet) the best bet is to make the Frame your main result element. + +We’ll get into more detail regarding how to save your results when we describe to your *Analyzer* - derived class. + +***Frame*** + +A *Frame* is an object, with fairly generic member variables which can be used to save results. Here is the definition of a *Frame*: + + class LOGICAPI Frame + { + public: + Frame(); + Frame( const Frame& frame ); + ~Frame(); + S64 mStartingSampleInclusive; + S64 mEndingSampleInclusive; + U64 mData1; + U64 mData2; + U8 mType; + U8 mFlags; + }; + +A *Frame* represents a piece of information conveyed by your protocol over an expanse of time. The +member variables *mStartingSampleInclusive* and *mEndingSampleInclusive* are the sample numbers for the beginning and end of the *Frame*. Note that Frames may not overlap; they cannot even share the same sample. For example, if a single clock edge ends one Frame, and starts a new Frame, then you’ll need to add one (+1) to the *mStartingSampleInclusive* of the second frame. + +In addition, the *Frame* can carry two 64-bit numbers as data. For example, in SPI, one of these is used for the MISO result, and the other for the MISO result. Often times you’ll only use one of these variables. + +The *mType* variable is intended to be used to save a custom-defined enum value, representing the type of *Frame*. For example, CAN can have many different types of frames – header, data, CRC, etc. Serial only has one type, and it doesn’t use this member variable. + +*mFlags* is intended to be a holder for custom flags which might apply to frame. Note that this is not intended for use with a custom an enum, but rather for individual bits that can be or’ed together. For example, in Serial, there is a flag for framing-error, and a flag for parity error. + + + #define FRAMING_ERROR_FLAG ( 1 << 0 ) + #define PARITY_ERROR_FLAG ( 1 << 1 ) + +Two flags are reserved by the system, and will produce an error or warning indication on the bubble displaying the *Frame*. + + + #define DISPLAY_AS_ERROR_FLAG ( 1 << 7 ) + #define DISPLAY_AS_WARNING_FLAG ( 1 << 6 ) + +***void {YourName}AnalyzerResults::GenerateBubbleText( U64 frame_index, Channel& channel,*** +***DisplayBase display_base )*** + +*GenerateBubbleText* exists to retrieve text to put in a bubble to be displayed on the screen. If you like you can leave this function empty, and return to it after implementing the rest of your analyzer. + +The *frame_index* is the index to use to get the Frame itself – for example: + + + Frame frame = GetFrame( frame_index ); + +Rarely, an analyzer needs to display results on more than one channel (SPI is the only example of this in an analyzer we make). If so, the channel which is requesting the bubble is specified in the *channel* parameter. In most situations, this can simply be ignored. If you need to use it, just compare it to the channels saved in your *mSettings* object to see which bubble should be generated – for example, for the MISO or MOSI channel. + +*display_base* specifies the radix (hex, decimal, binary) that any numerical values should be displayed in. +There are some helper functions provided so you should never have to deal directly with this issue. + + + enum DisplayBase { Binary, Decimal, Hexadecimal, ASCII }; + AnalyzerHelpers::GetNumberString( U64 number, DisplayBase display_base, U32 + num_data_bits, char* result_string, U32 result_string_max_length ); + +In *GetNumberString*, above, note that *num_data_bits* is the number of bits which are actually part of your result. For sample, for I2C, this is always 8. It will depend on your protocol and possibly on user settings. Providing this will let *GetNumberString* produce a well-formatted number with the right amount of zero-padding for the type of value under consideration. + +Bubbles can display different length strings, depending on how much room is available. You should generate several results strings. The simplest might simply indicate the type of contents (‘D’ for data, for example), longer ones might indicate the full number (“0xFF01”), and longer ones might be very verbose (“Left Channel Audio Data: 0xFF01”). + +To provide strings to the caller, use the *AddStringResult* function. This will make sure that the strings persist after the function has returned. Always call *ClearResultStrings* before adding any string results. + +Note that to easily concatenate multiple strings, simply provide *AddStringResult* with more strings. + + + void ClearResultStrings(); + void AddResultString( const char* str1, const char* str2 = NULL, const char* str3 = NULL, + const char* str4 = NULL, const char* str5 = NULL, const char* str6 = NULL ); //multiple + strings will be concatenated + +Here’s the Serial Analyzer’s *GenerateBubbleText* function: + + + void SerialAnalyzerResults::GenerateBubbleText( U64 frame_index, Channel& /*channel*/, + DisplayBase display_base ) // unreferenced vars commented out to remove warnings. + { + // we only need to pay attention to 'channel' if we're making bubbles for more than + one channel( as set by AddChannelBubblesWillAppearOn ) ClearResultStrings(); + Frame frame = GetFrame( frame_index ); + bool framing_error = false; + if( ( frame.mFlags & FRAMING_ERROR_FLAG ) != 0 ) + framing_error = true; + bool parity_error = false; + if( ( frame.mFlags & PARITY_ERROR_FLAG ) != 0 ) + parity_error = true; + char number_str[ 128 ]; + AnalyzerHelpers::GetNumberString( frame.mData1, display_base, mSettings - > mBitsPerTransfer, number_str, 128 ); + char result_str[ 128 ]; + if( ( parity_error == true ) || ( framing_error == true ) ) + { + AddResultString( "!" ); + sprintf( result_str, "%s (error)", number_str ); + AddResultString( result_str ); + if( parity_error == true && framing_error == false ) + sprintf( result_str, "%s (parity error)", number_str ); + else if( parity_error == false && framing_error == true ) + sprintf( result_str, "%s (framing error)", number_str ); + else + sprintf( result_str, "%s (framing error & parity error)", number_str ); + AddResultString( result_str ); + } + else + { + AddResultString( number_str ); + } + } + +***void {YourName}AnalyzerResults::GenerateExportFile( const char* file, DisplayBase*** +***display_base, U32 export_type_user_id )*** + +This function is called when the user tries to export the analyzer results to a file. If you like, you can leave this function empty, and come back to it after finalizing the rest of your analyzer design. +The *file* parameter is string containing the full path of the file you should create and write to with the analyzer results. + + + std::ofstream file_stream( file, std::ios::out ); + +The *display_base* parameter contains the radix which should be used to display numerical results. (See *GenerateBubbleText* for more detail) + +The *export_type_user_id* parameter is the id associated with the export-type the user selected. You +specify what these options are (there should be at least one) in the constructor of your *AnalyzerSettings* - derived class. If you only have one export option you can ignore this parameter. + +Often times you’ll want to print out the time (in seconds) associated with a particular result. To do this, use the *GetTimeString* helper function. You’ll need the trigger sample number and the sample rate – which can be obtained from your *Analyzer* object pointer. + + + U64 trigger_sample = mAnalyzer->GetTriggerSample(); + U32 sample_rate = mAnalyzer->GetSampleRate(); + static void AnalyzerHelpers::GetTimeString( U64 sample, U64 trigger_sample, U32 + sample_rate_hz, char* result_string, U32 result_string_max_length ); + +Other than that, the implementation is pretty straightforward. Here is an example from +*SerialAnalyzerResults.cpp*: + + + void SerialAnalyzerResults::GenerateExportFile( const char* file, DisplayBase display_base, U32 /*export_type_user_id*/ ) + { + // export_type_user_id is only important if we have more than one export type. + std::ofstream file_stream( file, std::ios::out ); + U64 trigger_sample = mAnalyzer->GetTriggerSample(); + U32 sample_rate = mAnalyzer->GetSampleRate(); + file_stream << "Time [s],Value,Parity Error,Framing Error" << std::endl; + U64 num_frames = GetNumFrames(); + for( U32 i = 0; i < num_frames; i++ ) + { + Frame frame = GetFrame( i ); + // static void GetTimeString( U64 sample, U64 trigger_sample, U32 + sample_rate_hz, char* result_string, U32 result_string_max_length ); + char time_str[ 128 ]; + AnalyzerHelpers::GetTimeString( frame.mStartingSampleInclusive, trigger_sample, sample_rate, time_str, 128 ); + char number_str[ 128 ]; + AnalyzerHelpers::GetNumberString( frame.mData1, display_base, mSettings - > mBitsPerTransfer, number_str, 128 ); + file_stream << time_str << "," << number_str; + if( ( frame.mFlags & FRAMING_ERROR_FLAG ) != 0 ) + file_stream << ",Error,"; + else + file_stream << ","; + if( ( frame.mFlags & FRAMING_ERROR_FLAG ) != 0 ) + file_stream << "Error"; + file_stream << std::endl; + if( UpdateExportProgressAndCheckForCancel( i, num_frames ) == true ) + { + file_stream.close(); + return; + } + } + file_stream.close(); + } + +***void SerialAnalyzerResults::GenerateFrameTabularText( U64 frame_index, DisplayBase*** +***display_base )*** + +*GenerateFrameTabularText* is for producing text for tabular display which is not yet implemented as of 1.1.5. You can safely leave it empty. + +*GenerateFrameTabularText* is almost the same as *GenerateBubbleText* , except that you should generate only one text result. Ideally the string should be concise, and only be a couple inches long or less under normal (non error) circumstances. + +Here is an example from *SerialAnalyzerResults.cpp*: + + + void SerialAnalyzerResults::GenerateFrameTabularText( U64 frame_index, DisplayBase display_base ) + { + Frame frame = GetFrame( frame_index ); + ClearResultStrings(); + bool framing_error = false; + if( ( frame.mFlags & FRAMING_ERROR_FLAG ) != 0 ) + framing_error = true; + bool parity_error = false; + if( ( frame.mFlags & PARITY_ERROR_FLAG ) != 0 ) + parity_error = true; + char number_str[ 128 ]; + AnalyzerHelpers::GetNumberString( frame.mData1, display_base, mSettings - > mBitsPerTransfer, number_str, 128 ); + char result_str[ 128 ]; + if( parity_error == false && framing_error == false ) + { + AddResultString( number_str ); + } + else if( parity_error == true && framing_error == false ) + { + sprintf( result_str, "%s (parity error)", number_str ); + AddResultString( result_str ); + } + else if( parity_error == false && framing_error == true ) + { + sprintf( result_str, "%s (framing error)", number_str ); + AddResultString( result_str ); + } + else + { + sprintf( result_str, "%s (framing error & parity error)", number_str ); + AddResultString( result_str ); + } + } + +***void SerialAnalyzerResults::GeneratePacketTabularText( U64 packet_id, DisplayBase*** +***display_base )*** + +This function is used to produce strings representing packet results for the tabular view. For now, just leave it empty. We’ll be updating the SDK and software to take advantage of this capability later. + +***void SerialAnalyzerResults::GenerateTransactionTabularText ( U64 transaction_id,*** +***DisplayBase display_base )*** + +This function is used to produce strings representing packet results for the tabular view. For now, just leave it empty. We’ll be updating the SDK and software to take advantage of this capability later. + +**Analyzer** + +Your *Analyzer* - derived class is the heart of the analyzer. It’s here were we analyze the bits coming in – in real time – and generate analyzer results. Other than a few other housekeeping things, that’s it. Let’s get started. + +**{YourName}Analyzer.h** + +In addition to the constructor and destructor, here are the functions you’ll need to implement: + + + virtual void WorkerThread(); + virtual U32 GenerateSimulationData( U64 newest_sample_requested, U32 sample_rate, + SimulationChannelDescriptor** simulation_channels ); + virtual U32 GetMinimumSampleRateHz(); + virtual const char* GetAnalyzerName() const; + virtual bool NeedsRerun(); + extern "C" ANALYZER_EXPORT const char* __cdecl GetAnalyzerName(); + extern "C" ANALYZER_EXPORT Analyzer* __cdecl CreateAnalyzer( ); + extern "C" ANALYZER_EXPORT void __cdecl DestroyAnalyzer( Analyzer* analyzer ); + +You’ll also need these member variables: + + + std::auto_ptr< {YourName}AnalyzerSettings > mSettings; + std::auto_ptr< {YourName}AnalyzerResults > mResults; + {YourName}SimulationDataGenerator mSimulationDataGenerator; + bool mSimulationInitialized; + +You’ll also need one *AnalyzerChannelData* raw pointer for each input. For SerialAnalyzer, for example, we need + + AnalyzerChannelData* mSerial; + +As you develop your analyzer, you’ll add additional member variables and helper functions depending on your analysis needs. + +**{YourName}Analyzer.cpp** + +***Constructor*** + +Your constructor will look something like this + + {YourName}Analyzer::{YourName}Analyzer() + : Analyzer(), + mSettings( new {YourName}AnalyzerSettings() ), + mSimulationInitialized( false ) + { + SetAnalyzerSettings( mSettings.get() ); + } + +Note that here you’re calling the base class constructor, *newing* your *AnalyzerSettings* - derived class, and providing the base class with a pointer to your *AnalyzerSettings* - derived object. + +***Destructor*** +This only thing your destructor must do is call *KillThread*. This is a base class member function and will make sure your class destructs in the right order. + +***void {YourName}Analyzer::WorkerThread()*** + +This function the key to everything – it’s where you’ll decode the incoming data. Let’s leave it empty for now, and we’ll discuss in detail once we complete the other housekeeping functions. + +***bool {YourName}Analyzer::NeedsRerun()*** + +Generally speaking, just return *false* in this function. For more detail, read on. + +This function is called when your analyzer has finished analyzing the collected data (this condition is detected from outside your analyzer.) + +This function gives you the opportunity to run the analyzer all over again, on the same data. To do this, simply return *true*. Otherwise, return *false*. The only thing this is currently used for is for our Serial analyzer, for “autobaud”. When using autobaud, we don’t know ahead of time what the serial bit rate will be. If the rate turns out to be significantly different from the rate we ran the analysis at, we return *true* to re-run the analysis. + +If you return *true* , that’s all there is to do. Your analyzer will be re-run automatically. + +***U32 {YourName}Analyzer::GenerateSimulationData( U64 minimum_sample_index, U32*** +***device_sample_rate, SimulationChannelDescriptor***** *simulation_channels )* + +This is the function that gets called to obtain simulated data. We made a dedicated class for handling this earlier – we just need to do some housekeeping here to hook it up. + + + U32 {YourName}Analyzer::GenerateSimulationData( U64 minimum_sample_index, U32 device_sample_rate, + SimulationChannelDescriptor** simulation_channels ) + { + if( mSimulationInitialized == false ) + { + mSimulationDataGenerator.Initialize( GetSimulationSampleRate(), mSettings.get() ); + mSimulationInitialized = true; + } + return mSimulationDataGenerator.GenerateSimulationData( minimum_sample_index, device_sample_rate, simulation_channels ); + } + +***U32 SerialAnalyzer::GetMinimumSampleRateHz()*** + +This function is called to see if the user’s selected sample rate is sufficient to get good results for this analyzer. + +For Serial, for instance, we would like the sample rate to be x4 higher that the serial bit rate. + +For other, typically synchronous, protocols, you may not ask the user to enter the data’s bit rate – +therefore you can’t know ahead of time what sample rate is required. In that case, you can either +return the smallest sample rate (25000), or return a value that will be fast enough for your simulation. +However, your simulation really should adjust its own rate depending on the sample rate – for example, when simulation SPI you should probably make the bit rate something like 4x the sample rate. This will allow the simulation to work perfectly no matter what the sample rate is. + +The rule of thumb is to require oversampling by x4 if you know the data’s bit rate, otherwise just return 25000. + +Here’s what we do in *SerialAnalyzer.cpp* + + + U32 SerialAnalyzer::GetMinimumSampleRateHz() + { + return mSettings->mBitRate * 4; + } + +***const char* {YourName}Analyzer::GetAnalyzerName() const*** + +Simply return the name you would like to see in the “Add Analyzer” drop down. + + + return "Async Serial"; + +***const char* GetAnalyzerName()*** + +Return the same string as in the previous function. + + + return "Async Serial"; + +***Analyzer* CreateAnalyzer()*** + +Return a pointer to a new instance of your Analyzer-derived class. + + + return new {YourName}Analyzer(); + +***void DestroyAnalyzer( Analyzer* analyzer )*** + +Simply call *delete* on the provided pointer. + + + delete analyzer; + +***void {YourName}Analyzer::WorkerThread()*** + +Ok, now that everything else is taken care of, let’s look at the most important part of the analyzer in +detail. + +First, we’ll *new* our *AnalyzerResults* - derived object. + + + mResults.reset( new {YourName}AnalyzerResults( this, mSettings.get() ) ); + +Well provide a pointer to our results to the base class: + + + SetAnalyzerResults( mResults.get() ); + +Let’s indicate which channels we’ll be displaying results on (in the form of bubbles). Usually this will only be one channel. (Except in the case of SPI, where we’ll want to put bubbles on both the MISO and MISO lines.) Only indicate where we will display bubbles – other markup, like tick marks, arrows, etc, are not bubbles, and should not be reported here. + + + mResults->AddChannelBubblesWillAppearOn( mSettings->mInputChannel ); + +We’ll probably want to know (and save in a member variable) the sample rate. + + + mSampleRateHz = GetSampleRate(); + +Now we need to get access to the data itself. We’ll need to get pointers to *AnalyzerChannelData* objects for each channel we’ll need data from. For Serial, we’ll just need one. For SPI, we might need 4. Etc. + + + mSerial = GetAnalyzerChannelData( mSettings->mInputChannel ); + +We’ve now ready to start traversing the data, and recording results. We’ll look at each of these tasks in turn. + +***First, a word of advice*** + +A protocol is typically fairly straightforward, when it behaves exactly as it supposed to. The more your analyzer needs to deal with exceptions to the rule, the more sophisticated it’ll need to be. The best bet is probably to start as simple as possible, and add more “gotchas” as they are discovered, rather than to try and design an elaborate, bulletproof analyzer from the start, especially when you’re new to the API. + +***AnalyzerChannelData*** + +*AnalyzerChannelData* is the class that will give us access to the data from a particular input. This will provide data in a serialized form – we will not have “random access” to any bit in the saved data. +Rather, we will start at the beginning, and move forward as more data becomes available. In fact we’ll never know when we’re at the “end” of the data or not – attempts to move forward in the stream will block until more data becomes available. This will allow our analyzer to process data in a real-time manner. (It may backlog, of course, if it can’t keep up – although generally the collection will end at some point and we’ll be able to finish). + +***AnalyzerChannelData – State*** + +If we’re not sure where are in the stream, or if the input is currently high or low, we can just ask: + + + U64 GetSampleNumber(); + BitState GetBitState(); + +***AnalyzerChannelData – Basic Traversal*** + +We’ll need some ability to move forward in the stream. We have three basic ways to do this. + +*U32 Advance( U32 num_samples );* + +We can move forward in the stream by a specific number of samples. This function will return how +many times the input toggled (changed from a high to a low, or low to a high) to make this move. + +*U32 AdvanceToAbsPosition( U64 sample_number );* + +If we want to move forward to a particular absolute position, we can use this function. It also returns the number of times the input changed during the move. + +*void AdvanceToNextEdge();* + +We also might want to move forward until the state changes. After calling this function you might want to call *GetSampleNumber* to find out how far you’ve come. + +***AnalyzerChannelData – Advanced Traversal (looking ahead without moving)*** + +As you develop your analyzer(s) certain tasks may come up that call for more sophisticated traversal. +Here are some ways of doing it. + +*U64 GetSampleOfNextEdge();* + +This function does not move your position in the stream. Remember, you can not move backward in the stream, so sometimes seeing what’s up ahead without moving can be very important. + +*bool WouldAdvancingCauseTransition( U32 num_samples );* + +This function does not move your position in the stream. Here you find out if moving forward a given number of samples would cause the bit state (low or high) to change. + +*bool WouldAdvancingToAbsPositionCauseTransition( U64 sample_number );* + +This is the same as the prior function, except you provide the absolute position. + +***AnalyzerChannelData – Keeping track of the smallest pulse.*** + +When we were implementing Serial’s “autobaud” it was clear that keeping track of the minimum pulse length over the entire stream was overly cumbersome. If you need this capability for some reason, these functions will provide it for you (it’s turned off by default) + + + void TrackMinimumPulseWidth(); + U64 GetMinimumPulseWidthSoFar(); + +***Filling in and saving Frames*** + +Using the above *AnalyzerChannelData* class, we can now move through a channel’s data and analyze it. +Now let’s discus how to store results. + +We described *Frames* when talking about the *AnalyzerResults* - derived class. A *Frame* is the basic unit results are saved in. *Frames* have: + + +-  starting and ending time (starting and ending sample number), +-  x2 64-bit values to save results in +-  an 8-bit type variable – to specify the type of Frame +-  an 8-bit flags variable – to specify Yes/No types of results. + +When we have analyzed far enough, and now have a complete *Frame* we would like to record, we do it like this: + + + Frame frame; + frame.mStartingSampleInclusive = first_sample_in_frame; + frame.mEndingSampleInclusive = last_sample_in_frame; + frame.mData1 = the_data_we_collected; + //frame.mData2 = some_more_data_we_collected; + //frame.mType = OurTypeEnum; //unless we only have one type of frame + frame.mFlags = 0; + if( such_and_such_error == true ) + frame.mFlags |= SUCH_AND_SUCH_ERROR_FLAG | DISPLAY_AS_ERROR_FLAG; + if( such_and_such_warning == true ) + frame.mFlags |= SUCH_AND_SUCH_WARNING_FLAG | DISPLAY_AS_WARNING_FLAG; + mResults->AddFrame( frame ); + mResults->CommitResults(); + ReportProgress( frame.mEndingSampleInclusive ); + +First we make a *Frame* on the stack. Then we fill in all its values. If there’s a value you don’t need, to save time you can skip setting it. *mFlags* should always be set to zero, however, because certain pre-defined flags will cause the results bubble to indicate a warning or error ( *DISPLAY_AS_WARNING_FLAG*, and *DISPLAY_AS_ERROR_FLAG* ). +Part of the *Frame* is expected to be filled in correctly because it’s used automatically by other systems. In particular, + +-  mStartingSampleInclusive +-  mEndingSampleInclusive +-  mFlags + +should be filled in properly. + +Other parts of the *Frame* are only there so you can create text descriptions or export the data to a +desired format. + +To save a *Frame* , Use *AddFrame* from your *AnalyzerResults* - derived class. Note that frames must be added in-order, and must not overlap. In other words, you can’t add a *Frame* from an earlier time (smaller sample number) after adding a *Frame* form a later time (larger sample number). + +Immediately after adding a *Frame* , call *CommitResults*. This makes the *Frame* accessible to the external system. + +Also call the *Analyzer* base class *ReportProgress*. Provide it with it the largest sample number you have processed. + +***Adding Markers*** + +Makers are visual elements you can place on the waveform to highlight various waveform features as they relate to your protocol. For example, in our asynchronous serial analyzer, we place little white dots at the locations where we sample the input’s state. You can also use markers to indicate where the protocol falls out of specification, a rising or falling clock edge, etc. You specify where to put the marker (the sample number), which channel to display it on, and which graphical symbol to use. + + + void AddMarker( U64 sample_number, MarkerType marker_type, Channel& channel ); + +For example, from *SerialAnalyzer.cpp* : + + mResults->AddMarker( marker_location, AnalyzerResults::Dot, mSettings->mInputChannel ); + +Currently, the available graphical artifacts are + + + enum MarkerType { Dot, ErrorDot, Square, ErrorSquare, UpArrow, DownArrow, X, ErrorX, + Start, Stop, One, Zero }; + +Like *Frames* , you must add *Markers* in order. + +*Markers* are strictly for graphical markup, they can not be used to help generate display text, export files, etc. Only *Frames* are accessible to do that. + +***Packets and Transactions*** + +*Packets* and *Transactions* are only moderately supported as of now, but they will be becoming more prominent in the software. + +*Packets* are sequential collections of *Frames*. Grouping *Frames* into *Packets* as you create them is easy: + + + U64 CommitPacketAndStartNewPacket(); + void CancelPacketAndStartNewPacket(); + +When you add a *Frame* , it will automatically be added to the current *Packet*. When you’ve added all the *Frames* you want in a *Packet*, call *CommitPacketAndStartNewPacket*. In some conditions, especially errors, you will want start a new packet without committing the old one. For this, call +*CancelPacketAndStartNewPacket*. + +Note that *CommitPacketAndStartNewPacket* returns an packet id. You can use this id to assign a +particular packet to a transaction. + + + void AddPacketToTransaction( U64 transaction_id, U64 packet_id ); + +The *transaction_id* is an ID you generate yourself. + +The analyzers created by Saleae do not yet use *Transactions* , and the current Analyzer probably never will. *Transactions* are provided for higher-level protocols, and you may not want to bother, especially since they aren’t used in the Logic software yet. We will use *Transactions* in analyzers for more sophisticated protocols in the future. + +*Packets* on the other hand tend to be fairly applicable for lower level protocols, although not in entirely the same ways. For example: + +-  Serial Analyzer – no packet support makes sense at this level. (there are many more structured protocols that use asynchronous serial where packets would be applicable) +-  SPI Analyzer – packets are used to delimit between periods when the enable line is active. +-  I2C Analyzer – packets are used to delimit periods between a start/restart and a stop. +-  CAN Analyzer – packets are used to represent, well, CAN packets. +-  UNI/O – packets are used to group Frames in a UNI/O sequence. +-  1 - Wire – packets are used to group 1-Wire sequences. +-  I2S/PCM – packets aren’t used. + +Currently, *Packets* are only used when exporting data to text/csv. In the future, analyzer tabular views will support nesting *Frames* into *Packets* , and identifying *Transactions* (ids) associated with particular *Packets*. Generating the textual content to support this is provided in your *AnalyzerResults* - derived class. + +When using Packet ids when exporting data to text/csv, use the GetPacketContainingFrameSequential function, to avoid searching for the packet every time. The GetPacketContainingFrame will do a full search and be much less efficient. + diff --git a/docs/Saleae Analyzer SDK (older).pdf b/docs/Saleae Analyzer SDK (older).pdf deleted file mode 100644 index 2078ef8..0000000 Binary files a/docs/Saleae Analyzer SDK (older).pdf and /dev/null differ diff --git a/docs/images/10_-_build_settings_page.png b/docs/images/10_-_build_settings_page.png deleted file mode 100644 index b91d575..0000000 Binary files a/docs/images/10_-_build_settings_page.png and /dev/null differ diff --git a/docs/images/11_-_header_includes_search_path.png b/docs/images/11_-_header_includes_search_path.png deleted file mode 100644 index 398af73..0000000 Binary files a/docs/images/11_-_header_includes_search_path.png and /dev/null differ diff --git a/docs/images/11_5_-_add_library_path.png b/docs/images/11_5_-_add_library_path.png deleted file mode 100644 index 0c436f2..0000000 Binary files a/docs/images/11_5_-_add_library_path.png and /dev/null differ diff --git a/docs/images/12_-_add_library_part_1.png b/docs/images/12_-_add_library_part_1.png deleted file mode 100644 index 4d31174..0000000 Binary files a/docs/images/12_-_add_library_part_1.png and /dev/null differ diff --git a/docs/images/13_-_add_library_part_2.png b/docs/images/13_-_add_library_part_2.png deleted file mode 100644 index 733b66e..0000000 Binary files a/docs/images/13_-_add_library_part_2.png and /dev/null differ diff --git a/docs/images/14_-_add_library_part_3.png b/docs/images/14_-_add_library_part_3.png deleted file mode 100644 index 1e83898..0000000 Binary files a/docs/images/14_-_add_library_part_3.png and /dev/null differ diff --git a/docs/images/15_-_edit_scheme.png b/docs/images/15_-_edit_scheme.png deleted file mode 100644 index d245599..0000000 Binary files a/docs/images/15_-_edit_scheme.png and /dev/null differ diff --git a/docs/images/16_-_debug_launch_app_menu.png b/docs/images/16_-_debug_launch_app_menu.png deleted file mode 100644 index b016ba1..0000000 Binary files a/docs/images/16_-_debug_launch_app_menu.png and /dev/null differ diff --git a/docs/images/17_-_select_debug_program.png b/docs/images/17_-_select_debug_program.png deleted file mode 100644 index 890ed54..0000000 Binary files a/docs/images/17_-_select_debug_program.png and /dev/null differ diff --git a/docs/images/18_-_breakpoint_set.png b/docs/images/18_-_breakpoint_set.png deleted file mode 100644 index dd6d662..0000000 Binary files a/docs/images/18_-_breakpoint_set.png and /dev/null differ diff --git a/docs/images/19_-_logic_software_add_analyzer_menu.png b/docs/images/19_-_logic_software_add_analyzer_menu.png deleted file mode 100644 index 04f7f99..0000000 Binary files a/docs/images/19_-_logic_software_add_analyzer_menu.png and /dev/null differ diff --git a/docs/images/1_-_new_project.png b/docs/images/1_-_new_project.png deleted file mode 100644 index c1ea629..0000000 Binary files a/docs/images/1_-_new_project.png and /dev/null differ diff --git a/docs/images/20_-_analyzer_in_Logic.png b/docs/images/20_-_analyzer_in_Logic.png deleted file mode 100644 index c49dbf3..0000000 Binary files a/docs/images/20_-_analyzer_in_Logic.png and /dev/null differ diff --git a/docs/images/21_-_logic_software_start_button.png b/docs/images/21_-_logic_software_start_button.png deleted file mode 100644 index 2875323..0000000 Binary files a/docs/images/21_-_logic_software_start_button.png and /dev/null differ diff --git a/docs/images/22_-_breakpoint_hit.png b/docs/images/22_-_breakpoint_hit.png deleted file mode 100644 index 87aa24f..0000000 Binary files a/docs/images/22_-_breakpoint_hit.png and /dev/null differ diff --git a/docs/images/2_-_empty_project.png b/docs/images/2_-_empty_project.png deleted file mode 100644 index 7b14066..0000000 Binary files a/docs/images/2_-_empty_project.png and /dev/null differ diff --git a/docs/images/2_5_analyzer_name.png b/docs/images/2_5_analyzer_name.png deleted file mode 100644 index 2947758..0000000 Binary files a/docs/images/2_5_analyzer_name.png and /dev/null differ diff --git a/docs/images/2_75_-_project_location.png b/docs/images/2_75_-_project_location.png deleted file mode 100644 index 969c94a..0000000 Binary files a/docs/images/2_75_-_project_location.png and /dev/null differ diff --git a/docs/images/3_-_copy_files.png b/docs/images/3_-_copy_files.png deleted file mode 100644 index d3b513e..0000000 Binary files a/docs/images/3_-_copy_files.png and /dev/null differ diff --git a/docs/images/4_-_rename_analyzer_script.png b/docs/images/4_-_rename_analyzer_script.png deleted file mode 100644 index 8d55ef1..0000000 Binary files a/docs/images/4_-_rename_analyzer_script.png and /dev/null differ diff --git a/docs/images/5_-_add_target_button.png b/docs/images/5_-_add_target_button.png deleted file mode 100644 index 5e04968..0000000 Binary files a/docs/images/5_-_add_target_button.png and /dev/null differ diff --git a/docs/images/6_-_add_target_menu.png b/docs/images/6_-_add_target_menu.png deleted file mode 100644 index 37af9f8..0000000 Binary files a/docs/images/6_-_add_target_menu.png and /dev/null differ diff --git a/docs/images/7_-_library_target.png b/docs/images/7_-_library_target.png deleted file mode 100644 index 745a966..0000000 Binary files a/docs/images/7_-_library_target.png and /dev/null differ diff --git a/docs/images/8_-_library_settings.png b/docs/images/8_-_library_settings.png deleted file mode 100644 index 4745720..0000000 Binary files a/docs/images/8_-_library_settings.png and /dev/null differ diff --git a/docs/images/8_5_-_add_files_menu.png b/docs/images/8_5_-_add_files_menu.png deleted file mode 100644 index 129cbb2..0000000 Binary files a/docs/images/8_5_-_add_files_menu.png and /dev/null differ diff --git a/docs/images/9_-_add_files.png b/docs/images/9_-_add_files.png deleted file mode 100644 index 608fe1f..0000000 Binary files a/docs/images/9_-_add_files.png and /dev/null differ diff --git a/docs/images/9_5_-_verify_sources_added.png b/docs/images/9_5_-_verify_sources_added.png deleted file mode 100644 index df41183..0000000 Binary files a/docs/images/9_5_-_verify_sources_added.png and /dev/null differ diff --git a/docs/images/optional_-_Project_Settings.png b/docs/images/optional_-_Project_Settings.png deleted file mode 100644 index cfd0048..0000000 Binary files a/docs/images/optional_-_Project_Settings.png and /dev/null differ diff --git a/docs/images/optional_-_project_settings_-_edit_products_folder_menu.png b/docs/images/optional_-_project_settings_-_edit_products_folder_menu.png deleted file mode 100644 index bf2ef7d..0000000 Binary files a/docs/images/optional_-_project_settings_-_edit_products_folder_menu.png and /dev/null differ diff --git a/readme.md b/readme.md index d468764..1320387 100644 --- a/readme.md +++ b/readme.md @@ -1,16 +1,29 @@ # Saleae Analyzer SDK Sample Analyzer -The Saleae Analyzer SDK is used to create custom plugins for the Saleae Logic software. These plugins are used to decode protocol data from captured waveforms. -The libraries required to build a custom analyzer are stored in another git repository, located here: -[https://github.com/saleae/AnalyzerSDK](https://github.com/saleae/AnalyzerSDK) +- [Saleae Analyzer SDK Sample Analyzer](#saleae-analyzer-sdk-sample-analyzer) + - [Renaming your Analyzer](#renaming-your-analyzer) + - [Cloud Building & Publishing](#cloud-building---publishing) + - [Prerequisites](#prerequisites) + - [Windows](#windows) + - [MacOS](#macos) + - [Linux](#linux) + - [Building your Analyzer](#building-your-analyzer) + - [Windows](#windows-1) + - [MacOS](#macos-1) + - [Linux](#linux-1) + - [Debugging](#debugging) + - [Windows](#windows-2) + - [MacOS](#macos-2) + - [Linux](#linux-2) + - [Updating an Existing Analyzer to use CMake & GitHub Actions](#updating-an-existing-analyzer-to-use-cmake---github-actions) -This repository should be used to create new analyzers for the Saleae software. +The Saleae Analyzer SDK is used to create Low Level Analyzers (LLA) for the Saleae Logic software via a plugin architecture. These plugins are used to decode protocol data from captured waveforms. In many cases you can use a [High Level Analyzer Extension](https://support.saleae.com/extensions/high-level-analyzer-quickstart) to process data from an existing protocol decoder instead of building a LLA. -First, fork, clone or download this repository. Forking is recommended if you plan to use version control or share your custom analyzer publicly. +To build your own protocol decoder plugin, first fork, clone, or download this repository. -Note - This repository contains a submodule. Be sure to include submodules when cloning, for example `git clone --recursive https://github.com/saleae/SampleAnalyzer.git`. If you download the repository from Github, the submodules are not included. In that case you will also need to download the AnalyzerSDK repository linked above and place the AnalyzerSDK folder inside of the SampleAnalyzer folder. +Then, make sure you have the required software installed for development. See the [Prerequisites](#Prerequisites) section below for details. -*Note: an additional submodule is used for debugging on Windows, see section on Windows debugging for more information.* +## Renaming your Analyzer Once downloaded, first run the script rename_analyzer.py. This script is used to rename the sample analyzer automatically. Specifically, it changes the class names in the source code, it changes the text name that will be displayed once the custom analyzer has been loaded into the Saleae Logic software, and it updates the visual studio project. @@ -27,12 +40,255 @@ After that, the script will complete the renaming process and exit. SPI Mark's SPI Analyzer -To build on Windows, open the visual studio project in the Visual Studio folder, and build. The Visual Studio solution has configurations for 32 bit and 64 bit builds. You will likely need to switch the configuration to 64 bit and build that in order to get the analyzer to load in the Windows software. +Once renamed, you're ready to build your analyzer! See the [Building your Analyzer](#Building-your-Analyzer) section below. -To build on Linux or OSX, run the build_analyzer.py script. The compiled libraries can be found in the newly created debug and release folders. +API documentation can be found in [docs/Analyzer_API.md](docs/Analyzer_API.md). - python build_analyzer.py +## Cloud Building & Publishing -To debug on Windows, please first review the section titled `Debugging an Analyzer with Visual Studio` in the included `doc/Analyzer SDK Setup.md` document. +This example repository includes support for GitHub actions, which is a continuous integration service from GitHub. The file located at `.github\workflows\build.yml` contains the configuration. -Unfortunately, debugging is limited on Windows to using an older copy of the Saleae Logic software that does not support the latest hardware devices. Details are included in the above document. \ No newline at end of file +When building in CI, the release version of the analyzer is built for Windows, Linux, and MacOS. The built analyzer files are available for every CI build. Additionally, GitHub releases are automatically created for any tagged commits, making it easy to share pre-built binaries with others once your analyzer is complete. + +Learn how to tag a commit here: https://stackoverflow.com/questions/18216991/create-a-tag-in-a-github-repository + +### Using downloaded analyzer binaries on MacOS + +This section only applies to downloaded pre-built protocol analyzer binaries on MacOS. If you build the protocol analyzer locally, or acquire it in a different way, this section does not apply. + +Any time you download a binary from the internet on a Mac, wether it be an application or a shared library, MacOS will flag that binary for "quarantine". MacOS then requires any quarantined binary to be signed and notarized through the MacOS developer program before it will allow that binary to be executed. + +Because of this, when you download a pre-compiled protocol analyzer plugin from the internet and try to load it in the Saleae software, you will most likely see an error message like this: + +> "libSimpleSerialAnalyzer.so" cannot be opened because th developer cannot be verified. + +Signing and notarizing of open source software can be rare, because it requires an active paid subscription to the MacOS developer program, and the signing and notarization process frequently changes and becomes more restrictive, requiring frequent updates to the build process. + +The quickest solution to this is to simply remove the quarantine flag added by MacOS using a simple command line tool. + +Note - the purpose of code signing and notarization is to help end users be sure that the binary they downloaded did indeed come from the original publisher and hasn't been modified. Saleae does not create, control, or review 3rd party analyzer plugins available on the internet, and thus you must trust the original author and the website where you are downloading the plugin. (This applies to all software you've ever downloaded, essentially.) + +To remove the quarantine flag on MacOS, you can simply open the terminal and navigate to the directory containing the downloaded shared library. + +This will show what flags are present on the binary: + +```sh +xattr libSimpleSerialAnalyzer.so +# example output: +# com.apple.macl +# com.apple.quarantine +``` + +This command will remove the quarantine flag: + +```sh +xattr -r -d com.apple.quarantine libSimpleSerialAnalyzer.so +``` + +To verify the flag was removed, run the first command again and verify the quarantine flag is no longer present. + +## Prerequisites + +### Windows + +Dependencies: + +- Visual Studio 2017 (or newer) with C++ +- CMake 3.13+ + +**Visual Studio 2017** + +_Note - newer versions of Visual Studio should be fine._ + +Setup options: + +- Programming Languages > Visual C++ > select all sub-components. + +Note - if CMake has any problems with the MSVC compiler, it's likely a component is missing. + +**CMake** + +Download and install the latest CMake release here. +https://cmake.org/download/ + +### MacOS + +Dependencies: + +- XCode with command line tools +- CMake 3.13+ + +Installing command line tools after XCode is installed: + +``` +xcode-select --install +``` + +Then open XCode, open Preferences from the main menu, go to locations, and select the only option under 'Command line tools'. + +Installing CMake on MacOS: + +1. Download the binary distribution for MacOS, `cmake-*-Darwin-x86_64.dmg` +2. Install the usual way by dragging into applications. +3. Open a terminal and run the following: + +``` +/Applications/CMake.app/Contents/bin/cmake-gui --install +``` + +_Note: Errors may occur if older versions of CMake are installed._ + +### Linux + +Dependencies: + +- CMake 3.13+ +- gcc 5+ + +Misc dependencies: + +``` +sudo apt-get install build-essential +``` + +## Building your Analyzer + +### Windows + +```bat +mkdir build +cd build +cmake .. -A x64 +cmake --build . +:: built analyzer will be located at SampleAnalyzer\build\Analyzers\Debug\SimpleSerialAnalyzer.dll +``` + +### MacOS + +```bash +mkdir build +cd build +cmake .. +cmake --build . +# built analyzer will be located at SampleAnalyzer/build/Analyzers/libSimpleSerialAnalyzer.so +``` + +### Linux + +```bash +mkdir build +cd build +cmake .. +cmake --build . +# built analyzer will be located at SampleAnalyzer/build/Analyzers/libSimpleSerialAnalyzer.so +``` + +## Debugging + +Although the exact debugging process varies slightly from platform to platform, part of the process is the same for all platforms. + +First, build your analyzer. Then, in the Logic 2 software, load your custom analyzer, and then restart the software. Instructions can be found here: https://support.saleae.com/faq/technical-faq/setting-up-developer-directory + +Once restarted, the software should show your custom analyzer in the list of available analyzers. + +Next, in order to attach your debugger, you will need to find the process ID of the Logic 2 software. To make this easy, we display the process ID of the correct process in the About dialog in the software, which you can open from the main menu. It's the last item in the "Build Info" box, labeled "PID". Note that this is not the correct PID when using an ARM based M1 Mac. (Please contact support for details on debugging on M1 Macs.) + +You will need that PID number for the platform specific steps below. + +Note, we strongly recommend only debugging your analyzer on existing captures, and not while making new recordings. The act of pausing the application with the debugger while recording data will cause the recording to fail once the application is resumed. To make development smooth, we recommend saving the capture you wish to debug with before starting the debugging process, so you can easily re-load it later. + +### Windows + +when `cmake .. -A x64` was run, a Visual Studio solution file was created automatically in the build directory. To debug your analyzer, first open that solution in visual studio. + +Then, open the Debug menu, and select "attach to process...". + +Enter the PID number into the Filter box to find the correct instance of Logic.exe. + +Click attach. + +Next, place a breakpoint somewhere in your analyzer source code. For example, the start of the WorkerThread function. + +Make sure you already have recorded data in the application, and then add an instance of your analyzer. The debugger should pause at the breakpoint. + +### MacOS + +We don't have a clear process for how to debug custom protocol analyzers on MacOS. If you attempt to attach a debugger to the Logic 2 software, you will likely see an error like this: + +> error: attach failed: attach failed (Not allowed to attach to process. Look in the console messages (Console.app), near the debugserver entries, when the attach failed. The subsystem that denied the attach permission will likely have logged an informative message about why it was denied.) + +Checking the output in Console.app, you will likely find logs like this: + +> macOSTaskPolicy: (com.apple.debugserver) may not get the task control port of (Logic2 Helper (R) (pid: 95234): (Logic2 Helper (R) is hardened, (Logic2 Helper (R) doesn't have get-task-allow, (com.apple.debugserver) is a declared debugger(com.apple.debugserver) is not a declared read-only debugger + +This is likely due to our signing and notarization process on MacOS not adding the `get-task-allow` entitlement. If you're in need of MacOS debugging, please contact Saleae support to request it. + +### Linux + +(Note, this section needs to be tested and updated if needed) + +On Linux, you can debug your custom analyzer using GDB. This can be done from the console, however we recommend using a GUI tool like Visual Studio Code, with the C++ extension installed. + +To debug from the command line, once you have loaded your analyzer into the logic software and have checked the process ID, you can attach gdb like so: + +```bash +gdb +attach +``` + +If you see a permissions error like this, you will need to temporarily change the `ptrace_scope` setting on your system. + +> Could not attach to process. [...] ptrace: Operation not permitted. + +You can change the `ptrace_scope` like so, which will then allow you to attach to another process without sudo. (Be sure to `quit` gdb first) + +```bash +sudo sysctl -w kernel.yama.ptrace_scope=0 +``` + +You can learn more about `ptrace_scope` here: https://www.kernel.org/doc/Documentation/security/Yama.txt + + +Next, test setting a breakpoint like this. Be sure to use the correct class name. + +``` +break MyAnalyzer::WorkerThread +``` + +finally, attaching to the process will have paused the Logic application execution. Resume it with the continue command: + +```bash +continue +``` + +If your analyzer hasn't been loaded yet, GDB will notify you that it can't find this function, and ask if you want to automatically set this breakpoint if a library with a matching function is loaded in the future. Type `y ` + +Then return to the application and add your analyzer. This should trigger the breakpoint. + +To verify that symbols for your custom analyzer are loading, check the backtrace with the `bt` command. Example output: + +``` +#0 0x00007f2677dc42a8 in I4CAnalyzer::WorkerThread() () + from /home/build/Downloads/SampleAnalyzer-modernization-2022/build/Analyzers/libI4CAnalyzer.so +#1 0x00007f267046f24a in Analyzer::InitialWorkerThread() () from /tmp/.mount_Logic-0Fyxvr/resources/linux/libAnalyzer.so +#2 0x00007f267263bed9 in ?? () from /tmp/.mount_Logic-0Fyxvr/resources/linux/libgraph_server_shared.so +#3 0x00007f267264688e in ?? () from /tmp/.mount_Logic-0Fyxvr/resources/linux/libgraph_server_shared.so +#4 0x00007f26828e1609 in start_thread (arg=) at pthread_create.c:477 +#5 0x00007f2681136293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 +``` + +## Updating an Existing Analyzer to use CMake & GitHub Actions + +If you maintain an existing C++ analyzer, or wish to fork and update someone else's analyzer, please follow these steps. + +1. Delete the contents of the existing repository, except for the source directory, and the readme. +2. Copy the contents of this sample repository into the existing analyzer, except for the src and docs directories, or the rename_analyzer.py script. +3. Rename the existing source directory to src. +4. In the new CMakeLists.txt file, make the following changes: + +- In the line `project(SimpleSerialAnalyzer)`, replace `SimpleSerialAnalyzer` with the name of the existing analyzer, for example `project(I2CAnalyzer)` +- In the section `set(SOURCES`, replace all of the existing source code file names with the file names of the existing source code. + +5. Update the readme! Feel free to just reference the SampleAnalyzer repository, or copy over the build instructions. +6. Try the build instructions to make sure the analyzer still builds, or commit this to GitHub to have GitHub actions build it for you! +7. Once you're ready to create a release, add a tag to your last commit to trigger GitHub to publish a release. diff --git a/rename_analyzer.py b/rename_analyzer.py index d5c35ab..9a77694 100644 --- a/rename_analyzer.py +++ b/rename_analyzer.py @@ -11,9 +11,9 @@ print("") print("") print("What would you like to call your new analyzer?") print("") -print(">>The files under '/source' will be modified to use it.") +print(">>The files under '/src' will be modified to use it.") print(">>Examples include Serial, MySerial, JoesSerial, Gamecube, Wiimote, 2Wire, etc.") -print(">>Do not inclide the trailing word 'Analyzer' this will be added automaticly.") +print(">>Do not inclide the trailing word 'Analyzer' this will be added automatically.") print("") print("(press CTRL-C to cancel)") print("") @@ -22,7 +22,7 @@ new_analyzer_name = input( "Your new analyzer name: " ) print("") print("") -print("What is the analyzer's title? (as shown in the add new anlayzer drop down)") +print("What is the analyzer's title? (as shown in the add new analyzer drop down)") print("") print(">>Examples include Async Serial, I2C, Joe's Serial, Gamecube, Wiimote, 2Wire, etc.") print("") @@ -33,24 +33,20 @@ new_analyzer_title = input( "Your new analyzer's title: " ) original_name = "SimpleSerial" +#update the CMakeLists.txt project name -vs_project_path = "Visual Studio" -os.chdir( vs_project_path ) -project_files = glob.glob("*.sln") + glob.glob("*.vcxproj") #returns only the file names, no paths. +cmake_file = glob.glob("CMakeLists.txt") -for file in project_files: +for file in cmake_file: contents = open( file, 'r' ).read() contents = contents.replace( original_name + "Analyzer", new_analyzer_name + "Analyzer" ) contents = contents.replace( original_name.upper() + "ANALYZER", new_analyzer_name.upper() + "ANALYZER" ) contents = contents.replace( original_name + "SimulationDataGenerator", new_analyzer_name + "SimulationDataGenerator" ) open( file, 'w' ).write( contents ) -os.rename( glob.glob("*.sln")[0], new_analyzer_name + "Analyzer.sln" ) -os.rename( glob.glob("*.vcxproj")[0], new_analyzer_name + "Analyzer.vcxproj" ) -source_path = "source" -os.chdir( ".." ) +source_path = "src" os.chdir( source_path ) files = dict() diff --git a/source/SimpleSerialAnalyzer.cpp b/src/SimpleSerialAnalyzer.cpp similarity index 100% rename from source/SimpleSerialAnalyzer.cpp rename to src/SimpleSerialAnalyzer.cpp diff --git a/source/SimpleSerialAnalyzer.h b/src/SimpleSerialAnalyzer.h similarity index 100% rename from source/SimpleSerialAnalyzer.h rename to src/SimpleSerialAnalyzer.h diff --git a/source/SimpleSerialAnalyzerResults.cpp b/src/SimpleSerialAnalyzerResults.cpp similarity index 100% rename from source/SimpleSerialAnalyzerResults.cpp rename to src/SimpleSerialAnalyzerResults.cpp diff --git a/source/SimpleSerialAnalyzerResults.h b/src/SimpleSerialAnalyzerResults.h similarity index 100% rename from source/SimpleSerialAnalyzerResults.h rename to src/SimpleSerialAnalyzerResults.h diff --git a/source/SimpleSerialAnalyzerSettings.cpp b/src/SimpleSerialAnalyzerSettings.cpp similarity index 100% rename from source/SimpleSerialAnalyzerSettings.cpp rename to src/SimpleSerialAnalyzerSettings.cpp diff --git a/source/SimpleSerialAnalyzerSettings.h b/src/SimpleSerialAnalyzerSettings.h similarity index 100% rename from source/SimpleSerialAnalyzerSettings.h rename to src/SimpleSerialAnalyzerSettings.h diff --git a/source/SimpleSerialSimulationDataGenerator.cpp b/src/SimpleSerialSimulationDataGenerator.cpp similarity index 100% rename from source/SimpleSerialSimulationDataGenerator.cpp rename to src/SimpleSerialSimulationDataGenerator.cpp diff --git a/source/SimpleSerialSimulationDataGenerator.h b/src/SimpleSerialSimulationDataGenerator.h similarity index 100% rename from source/SimpleSerialSimulationDataGenerator.h rename to src/SimpleSerialSimulationDataGenerator.h