From 92a184fbf8c2b5671032cfcad8ae2f1c9ee39ca7 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 20 Jun 2024 12:27:48 +0200 Subject: WIP more documentation --- lib/pbdrv/pb-mod.h | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h index ae36d22..c4629a6 100644 --- a/lib/pbdrv/pb-mod.h +++ b/lib/pbdrv/pb-mod.h @@ -3,8 +3,8 @@ /** * \file puzzle bus driver implementation * - * Most \c pb_* functions have a weak implementation, which may be - * overwritten by a custom implementation. This allows you to use the default + * Most \c pb_* functions have a weak implementation, which may be overwritten + * by a custom implementation. This allows you to use the default * implementation where possible, and only implement extensions required for * your puzzle module. Please see spec.adoc for more information about how to * use the puzzle bus driver library. @@ -21,10 +21,42 @@ extern const char * PB_MOD_NAME; //! puzzle module bus address (required) extern const i2c_addr_t PB_MOD_ADDR; +/** + * \brief handle a received message from the I2C bus (puzzle bus) + * + * This function attempts to parse an I2C message as a puzzle bus message, and + * calls the appropriate handler for the message if it is considered valid. + * + * \param buf pointer to message content + * \param sz size of \p buf + * + * \note This function should not be directly called from an ISR. Please use + * FreeRTOS's \c xTimerPendFunctionCallFromISR() or a similar scheduler-based + * deferred function call mechanism instead. + */ void pb_i2c_recv(const uint8_t * buf, size_t sz); +/** + * \brief send a message in master-mode on the I2C bus (puzzle bus) + * + * This function sends an I2C message to the address specified by \p i2c_addr. + * + * \param i2c_addr address of slave controller + * \param buf pointer to message content + * \param sz size of \p buf + */ void pb_i2c_send(i2c_addr_t i2c_addr, const uint8_t * buf, size_t sz); +/** + * \brief global state read hook + * \ingroup hook + * \return current value of global state enum + */ pb_global_state_t pb_hook_mod_state_read(); +/** + * \brief global state write hook + * \ingroup hook + * \param state new value of global state enum + */ void pb_hook_mod_state_write(pb_global_state_t state); /** -- cgit v1.2.3 From 9425826203e1c954491a6a29ae84daf1a5edda2c Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 20 Jun 2024 17:53:58 +0200 Subject: more documentation (WIP design.adoc) --- docs/design.adoc | 96 ++++---- docs/figs.drawio | 396 ++++++++------------------------ docs/img/.gitignore | 1 + docs/img/main-controller-state.svg | 2 +- docs/img/main-controller-top.svg | 2 +- docs/img/power-supply-top.svg | 2 +- docs/img/puzzle-module-common-state.svg | 2 +- docs/img/puzzle-module-top.svg | 2 +- docs/img/software-components.puml | 37 +++ docs/img/style.ipuml | 3 + docs/img/system-bus.svg | 2 +- docs/img/system-top.svg | 2 +- docs/makefile | 5 + lib/pbdrv/pb-types.h | 43 +++- 14 files changed, 232 insertions(+), 363 deletions(-) create mode 100644 docs/img/.gitignore create mode 100644 docs/img/software-components.puml create mode 100644 docs/img/style.ipuml (limited to 'lib/pbdrv') diff --git a/docs/design.adoc b/docs/design.adoc index 5ebbb15..e54c970 100644 --- a/docs/design.adoc +++ b/docs/design.adoc @@ -117,7 +117,7 @@ the following: ** Aggregating game state for all installed puzzle modules. ** Reading and writing game state for all installed puzzle modules. ** Broadcasting updates internally. -* Serve TCP socket connections for— +* Serve a TCP socket for— ** Sending state updates ** Manually updating game state ** Integration with the bomb @@ -146,6 +146,7 @@ The criteria for the main controller are: The requirements document compares various microcontrollers that fit these criteria. After this comparison, the decision was made to utilize the Raspberry Pi Pico W as main controller during development. +// TODO: this was a bad choice [[fig:main-controller-top]] .Main controller top-level block diagram @@ -165,9 +166,22 @@ peripherals is handled through a central I^2^C bus referred to as the 'puzzle bus'. This design was again carried over from the hardware design from the 21-22 group cite:[2122_design]. -The only notable difference made this year was the removal of the -"HarwareInterrupt" line1{empty}footnote:[This is not a typo], which was -connected but not utilized cite:[research]. +The previously specified "HarwareInterrupt" line1{empty}footnote:[This is not a +typo] has been removed this year, as it was connected but not utilized +cite:[research]. + +To optimize for flexibility and extensibility, the puzzle bus should ideally +function as a network (similar to the CAN bus), to allow puzzle modules to send +responses asynchronously. I^2^C was initially chosen for the puzzle bus due to +its widespread availability on microcontrollers and in peripherals, but does +not provide any network-like capabilities. + +To archive network-like capabilities, the puzzle bus is specified to be a +multi-master I^2^C bus, and all puzzle modules are specified to be I^2^C +multi-master controllers that are slave-addressible. The multi-master part is +required to prevent I^2^C transmissions from being corrupted in the event of a +bus collision, and the slave-addressible part is required to both send and +receive messages on the same controller. Address definitions and protocol specifications are further detailed in <>. @@ -202,15 +216,15 @@ image::img/system-bus.svg[] [[sec:lv2]] == Modules -This section elaborates on the top-level specifications from <> with -additional hardware specifications and software design decisions. +This section elaborates on the top-level (hardware) specifications from +<> with software design decisions. === Puzzle Module Framework This subsection defines aspects of the 'puzzle framework' and the interface that allows puzzle modules to integrate with this framework. All communication described within this subsection refers to 'internal' communication between the -main controller and puzzle module. +main controller and puzzle modules on the puzzle bus. The puzzle framework is the foundation of the puzzle box software, and is designed to facilitate the following: @@ -226,56 +240,52 @@ designed to facilitate the following: ==== State All puzzle modules implement the same state machine shown in -<>. Note that the arrows indicate state -transitions that a puzzle module may take on its own. The main controller also -allows the game operator to manually set the current state as one of the states -on the right half of <>, which can be used to -skip a puzzle if a player is stuck (<>) or -reset a game if it is malfunctioning (<>). - -Puzzle modules start in the 'uninitialized' state, where they repeatedly send -messages to the main controller (see <>). The state transition -from 'uninitialized' to 'reset' is forced by the main controller upon -initialization. States on the right half of <> -are used during gameplay. +<>. Note that solid arrows indicate state +transitions that a puzzle module may take on its own, while dashed arrows +indicate state transitions forced by the main controller. The main controller +also allows the game operator to manually set the current state as one of the +states on the right half of <>, which can be +used to skip a puzzle if a player is stuck (<>) +or reset a game if it is malfunctioning (<>). + +Puzzle modules start in the 'uninitialized' state, where they wait until the +main controller sends a SET STATE command. Receiving this command indicates to +the puzzle module that it was successfully registered by the main controller, +and that it may transition from the 'uninitialized' state to the 'reset' state. [[fig:puzzle-module-common-state]] .Global puzzle module state machine image::img/puzzle-module-common-state.svg[] The state machine described in <> is referred -to as the global state. Puzzle modules may also declare and define custom state -variables, which is referred to as auxiliary state. These auxiliary state -variables contain game-specific variables; e.g. the binary state of each button -on the Neotrellis puzzle module, or the last passcode entered on the vault -puzzle module. +to as the global state. Puzzle modules may also declare and define custom +variables, which are referred to as properties. These properties may contain +game-specific variables; e.g. the binary state of each button on the Neotrellis +puzzle module, or the last passcode entered on the vault puzzle module. -Separating the auxiliary state from the generic state allows the main -controller to handle the auxiliary state as an arbitrary blob, which allows for -future expansion without modification of the main controller software. +Separating properties from the global state allows the main controller to +handle these property values as an arbitrary blob, which allows for future +expansion without modification of the main controller software. ==== Commands -The puzzle module framework describes the following commands: +The puzzle module framework describes the following command *types*: + +* ``PROP``: property +* ``MAGIC``: handshake +* ``STATE``: global state -* Read state -* Write state -* Update +Each command also has a specific *action*: -The 'read' and 'write' commands are used to communicate both types of state -defined in <>. +* ``REQ``: request +* ``RES``: response +* ``SET``: (over)write -To avoid issues caused by state synchronization memory consumption on the main -controller and puzzle modules, auxiliary state is only stored on each -respective puzzle module's controller. Only global state is cached on the main -controller to reduce the number of back-and-forth messages required for state -updates. +Not all commands define behavior for all actions (e.g. there is no MAGIC SET +command). -These commands are sufficient to realize the puzzle box, but this means that -the puzzle box would rely heavily on polling-based updates internally. To solve -this, the 'update' command was created. This command is utilized for various -kinds of updates, including registering new puzzle modules and updating global -state. +The Doxygen-generated pages for these command types explain their usage, and +will not be repeated here. === Main Controller diff --git a/docs/figs.drawio b/docs/figs.drawio index 042ce15..d898390 100644 --- a/docs/figs.drawio +++ b/docs/figs.drawio @@ -1,4 +1,4 @@ - + @@ -354,20 +354,20 @@ - + - - + + - + - + @@ -381,28 +381,28 @@ - + - - + + - + - - + + - - + + @@ -415,21 +415,21 @@ - + - - + + - + - - + + @@ -437,465 +437,249 @@ - + - + + + + - + - - + + - + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + - - - - - - - - - - + + - + - - + + - - + - - + + + - - + - + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/docs/img/.gitignore b/docs/img/.gitignore new file mode 100644 index 0000000..afafe8d --- /dev/null +++ b/docs/img/.gitignore @@ -0,0 +1 @@ +software-components.svg diff --git a/docs/img/main-controller-state.svg b/docs/img/main-controller-state.svg index 6e0333b..f82a8c0 100644 --- a/docs/img/main-controller-state.svg +++ b/docs/img/main-controller-state.svg @@ -1,3 +1,3 @@ -
Reset
Playing
Solved
\ No newline at end of file +
idle
playing
solved
\ No newline at end of file diff --git a/docs/img/main-controller-top.svg b/docs/img/main-controller-top.svg index 5bb7cda..601b402 100644 --- a/docs/img/main-controller-top.svg +++ b/docs/img/main-controller-top.svg @@ -1,3 +1,3 @@ -
Main controller
Puzzle bus
Wi-Fi
\ No newline at end of file +
main controller
puzzle bus
Wi-Fi
\ No newline at end of file diff --git a/docs/img/power-supply-top.svg b/docs/img/power-supply-top.svg index 89107a6..1ba4c14 100644 --- a/docs/img/power-supply-top.svg +++ b/docs/img/power-supply-top.svg @@ -1,3 +1,3 @@ -
Power supply
Puzzle bus
Charger
\ No newline at end of file +
power supply
puzzle bus
charger
\ No newline at end of file diff --git a/docs/img/puzzle-module-common-state.svg b/docs/img/puzzle-module-common-state.svg index b5688ef..4b1e1fd 100644 --- a/docs/img/puzzle-module-common-state.svg +++ b/docs/img/puzzle-module-common-state.svg @@ -1,3 +1,3 @@ -
Uninitialized
Reset
Playing
Solved
\ No newline at end of file +
uninitialized
idle
playing
solved
\ No newline at end of file diff --git a/docs/img/puzzle-module-top.svg b/docs/img/puzzle-module-top.svg index 79c16ed..bc65730 100644 --- a/docs/img/puzzle-module-top.svg +++ b/docs/img/puzzle-module-top.svg @@ -1,3 +1,3 @@ -
Puzzle module
Puzzle bus
Puzzle outputs
Puzzle inputs
\ No newline at end of file +
puzzle module
puzzle bus
puzzle outputs
puzzle inputs
\ No newline at end of file diff --git a/docs/img/software-components.puml b/docs/img/software-components.puml new file mode 100644 index 0000000..3803334 --- /dev/null +++ b/docs/img/software-components.puml @@ -0,0 +1,37 @@ +@startuml +!include style.ipuml + +component [arduino-sdk] as "Arduino SDK" + +component pbdrv-mod as "Puzzle bus module driver\n(pbdrv-mod)" { + component pbdrv as "Puzzle bus driver\n(pbdrv)" +} + +[freertos] as "FreeRTOS" +[mpack] as "mpack" + +[pico-sdk] as "RPI Pico SDK\n(pico-sdk)" +[i2ctcp] as "i2c over tcp bridge\n(i2ctcp)" + +node pbc as "puzzle box client\n(pbc)" +node main as "main controller software" +node pm as "puzzle module" + +pm ---> [pbdrv-mod] +pm ----> [freertos] +pm ----> [arduino-sdk] + +main ---> [pbdrv-mod] +main ----> [freertos] +main ----> [pico-sdk] +main ---> [i2ctcp] + +pbc ---> [pbdrv] +pbc ---> [i2ctcp] + +[pbdrv] --> [mpack] + +[i2ctcp] --> [mpack] + +@enduml + diff --git a/docs/img/style.ipuml b/docs/img/style.ipuml new file mode 100644 index 0000000..25bb6d7 --- /dev/null +++ b/docs/img/style.ipuml @@ -0,0 +1,3 @@ +!theme plain +skinparam RoundCorner 0 + diff --git a/docs/img/system-bus.svg b/docs/img/system-bus.svg index 440227a..1b04940 100644 --- a/docs/img/system-bus.svg +++ b/docs/img/system-bus.svg @@ -1,3 +1,3 @@ -
Puzzle bus
(I²C + power)
Main controller
Puzzle module 3
(neotrellis)
Puzzle module 4
(safe)
Puzzle module 1
(software)
Puzzle module 2
(hardware)
Power supply
(battery pack)
\ No newline at end of file +
puzzle bus
(I²C + power)
main controller
neotrellis puzzle module
vault puzzle module
software puzzle module
hardware puzzle module
power supply
(battery pack)
\ No newline at end of file diff --git a/docs/img/system-top.svg b/docs/img/system-top.svg index 0a9e8c0..902fa07 100644 --- a/docs/img/system-top.svg +++ b/docs/img/system-top.svg @@ -1,3 +1,3 @@ -
Puzzle outputs
Puzzle box
Puzzle inputs
Player(s)
Charger
Wi-Fi
Bomb
\ No newline at end of file +
puzzle outputs
puzzle box
puzzle inputs
player(s)
charger
Wi-Fi
bomb
\ No newline at end of file diff --git a/docs/makefile b/docs/makefile index eb54a80..15ea3a4 100644 --- a/docs/makefile +++ b/docs/makefile @@ -20,3 +20,8 @@ ASCIIDOCTOR_ARGS += --backend pdf %.pdf: %.adoc $(PDFDEPS) bundle exec asciidoctor $(ASCIIDOCTOR_ARGS) $< +%.svg: %.puml + plantuml -tsvg $< + +design.pdf: img/software-components.svg + diff --git a/lib/pbdrv/pb-types.h b/lib/pbdrv/pb-types.h index 4d085f9..f5fbb74 100644 --- a/lib/pbdrv/pb-types.h +++ b/lib/pbdrv/pb-types.h @@ -21,9 +21,17 @@ typedef uint16_t i2c_addr_t; //! puzzle bus command types enum pb_cmd_id { - PB_CMD_PROP, //!< puzzle module property - PB_CMD_STATE, //!< global state - PB_CMD_MAGIC, //!< magic (handshake) + /** \brief puzzle module property (REQ, RES, SET) */ + PB_CMD_PROP, + /** \brief puzzle module global state variable (REQ, RES, SET) */ + PB_CMD_STATE, + /** + * \brief magic (handshake) (REQ, RES) + * + * This message is used to distinguish between puzzle modules and regular I2C + * slaves on the puzzle bus. + */ + PB_CMD_MAGIC, }; typedef enum pb_cmd_id pb_cmd_id_t; @@ -51,10 +59,31 @@ static const char pb_cmd_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 }; //! puzzle bus message header (shared by all commands) typedef struct { - pb_cmd_id_t type; //!< command type - pb_action_t action; //!< command action - i2c_addr_t sender; //!< i2c address of sender - void * cmd; //!< command data (type dependant) + /** + * \brief command type + * + * This is used to identify what the message is about. + */ + pb_cmd_id_t type; + /** + * \brief command action + * + * This is used to specify what should happen as a result of this message. + */ + pb_action_t action; + /** + * \brief I2C address of sender + * + * This is used to facilitate the 'network' features, as the sender of an I2C + * write is unknown. + */ + i2c_addr_t sender; + /** + * \brief command data (type dependent) + * + * Struct containing command type-specific data. + */ + void * cmd; } pb_msg_t; //! PB_CMD_PROP data -- cgit v1.2.3 From d57d0cdb11451283fffbc3ac4b4deaacf188e94a Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Fri, 21 Jun 2024 10:51:19 +0200 Subject: add puzzle module init hook + weak implementation --- lib/pbdrv/pb-route.c | 9 ++++++++- lib/pbdrv/pb-route.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/pb-route.c b/lib/pbdrv/pb-route.c index ee47700..df00cd3 100644 --- a/lib/pbdrv/pb-route.c +++ b/lib/pbdrv/pb-route.c @@ -56,8 +56,12 @@ __weak void pb_route_cmd_prop_req(pb_msg_t * msg) {} __weak void pb_route_cmd_prop_res(pb_msg_t * msg) {} __weak void pb_route_cmd_prop_set(pb_msg_t * msg) {} +//! last known global state of last STATE REQ sender (i.e. main controller) static pb_global_state_t _main_state = PB_GS_NOINIT; __weak void pb_hook_main_state_update(pb_global_state_t state) {} +__weak void pb_hook_module_init() { + pb_hook_mod_state_write(PB_GS_IDLE); +} __weak void pb_route_cmd_state_req(pb_msg_t * msg) { pb_global_state_t own_state = pb_hook_mod_state_read(); pb_buf_t buf = pb_send_state_res(own_state); @@ -66,8 +70,11 @@ __weak void pb_route_cmd_state_req(pb_msg_t * msg) { // notify of new global state variable pb_cmd_state_t * cmd = msg->cmd; - if (cmd->state != _main_state) + if (cmd->state != _main_state) { + // first STATE REQ = module init OK + if (_main_state == PB_GS_NOINIT) pb_hook_module_init(); pb_hook_main_state_update(cmd->state); + } _main_state = cmd->state; } diff --git a/lib/pbdrv/pb-route.h b/lib/pbdrv/pb-route.h index 41c009a..967c4a9 100644 --- a/lib/pbdrv/pb-route.h +++ b/lib/pbdrv/pb-route.h @@ -27,6 +27,7 @@ void pb_route_cmd_state_res(pb_msg_t * msg); void pb_route_cmd_state_set(pb_msg_t * msg); void pb_hook_main_state_update(pb_global_state_t state); +void pb_hook_module_init(); void pb_route_cmd_magic_req(pb_msg_t * msg); void pb_route_cmd_magic_res(pb_msg_t * msg); -- cgit v1.2.3 From 6da66e5356eb367e4b766484eab3a94cb009fb9c Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Fri, 21 Jun 2024 10:56:20 +0200 Subject: fix dumb mistake in pbdrv --- lib/pbdrv/pb-route.c | 7 ++++--- lib/pbdrv/pb-send.c | 4 ++-- lib/pbdrv/pb-send.h | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/pb-route.c b/lib/pbdrv/pb-route.c index df00cd3..709fb7f 100644 --- a/lib/pbdrv/pb-route.c +++ b/lib/pbdrv/pb-route.c @@ -64,9 +64,6 @@ __weak void pb_hook_module_init() { } __weak void pb_route_cmd_state_req(pb_msg_t * msg) { pb_global_state_t own_state = pb_hook_mod_state_read(); - pb_buf_t buf = pb_send_state_res(own_state); - pb_send_reply(msg, &buf); - pb_buf_free(&buf); // notify of new global state variable pb_cmd_state_t * cmd = msg->cmd; @@ -76,6 +73,10 @@ __weak void pb_route_cmd_state_req(pb_msg_t * msg) { pb_hook_main_state_update(cmd->state); } _main_state = cmd->state; + + pb_buf_t buf = pb_send_state_res(); + pb_send_reply(msg, &buf); + pb_buf_free(&buf); } __weak void pb_route_cmd_state_res(pb_msg_t * msg) {} diff --git a/lib/pbdrv/pb-send.c b/lib/pbdrv/pb-send.c index 66c43c1..29d81ed 100644 --- a/lib/pbdrv/pb-send.c +++ b/lib/pbdrv/pb-send.c @@ -64,9 +64,9 @@ pb_buf_t pb_send_state_req() { return pb_msg_write(&msg); } -pb_buf_t pb_send_state_res(pb_global_state_t state) { +pb_buf_t pb_send_state_res() { pb_cmd_state_t cmd = { - .state = state, + .state = pb_hook_mod_state_read(), }; pb_msg_t msg = { .type = PB_CMD_STATE, diff --git a/lib/pbdrv/pb-send.h b/lib/pbdrv/pb-send.h index 2f8be1e..679df29 100644 --- a/lib/pbdrv/pb-send.h +++ b/lib/pbdrv/pb-send.h @@ -13,7 +13,7 @@ pb_buf_t pb_send_read_req(uint8_t propid); pb_buf_t pb_send_read_res(uint8_t propid, uint8_t * value, size_t size); pb_buf_t pb_send_write_req(uint8_t propid, uint8_t * value, size_t size); pb_buf_t pb_send_state_req(); -pb_buf_t pb_send_state_res(pb_global_state_t state); +pb_buf_t pb_send_state_res(); pb_buf_t pb_send_state_set(pb_global_state_t state); pb_buf_t pb_send_magic_req(); pb_buf_t pb_send_magic_res(); -- cgit v1.2.3 From 67f076b151d35a36800734cb31a403fac92ffb6d Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Fri, 21 Jun 2024 13:22:18 +0200 Subject: WIP more design document + add TODOs --- docs/design.adoc | 332 +++++++++++++++++------------- docs/handover.adoc | 3 +- docs/img/.gitignore | 2 + docs/img/sequence-puzzle-module-init.puml | 17 ++ docs/img/sequence-puzzle-module-init.svg | 3 - docs/img/style.ipuml | 1 + docs/makefile | 1 + lib/pbdrv/pb.h | 19 +- 8 files changed, 222 insertions(+), 156 deletions(-) create mode 100644 docs/img/sequence-puzzle-module-init.puml delete mode 100644 docs/img/sequence-puzzle-module-init.svg (limited to 'lib/pbdrv') diff --git a/docs/design.adoc b/docs/design.adoc index e54c970..7d9759a 100644 --- a/docs/design.adoc +++ b/docs/design.adoc @@ -219,12 +219,36 @@ image::img/system-bus.svg[] This section elaborates on the top-level (hardware) specifications from <> with software design decisions. -=== Puzzle Module Framework +=== Software module separation -This subsection defines aspects of the 'puzzle framework' and the interface -that allows puzzle modules to integrate with this framework. All communication -described within this subsection refers to 'internal' communication between the -main controller and puzzle modules on the puzzle bus. +[[fig:software-component]] +.Software library components +image::img/software-components.svg[] + +<> shows a software component diagram with an example +Arduino-based puzzle module, the main controller and the puzzle box client. +Notable properties of this architecture include: + +* The Arduino SDK, FreeRTOS, mpack, and RPI Pico SDK are external libraries, + and with the exception of mpack, these have not been modified. +* The puzzle bus driver is split into a (portable) standalone library and a + module-specific driver. +* There is a separate library for (de)serializing I^2^C commands for + transmission over TCP. + +The specific decision to split the puzzle bus driver and create a separate +I^2^C over TCP library was made to avoid writing a command set separate from +internal puzzle bus commands (i.e. one specific to TCP connections). This +architecture allows the puzzle box client to not only control the main +controller, but also directly inspect puzzle bus messages for debugging and +development of future puzzle modules (<>). + +=== Puzzle module framework + +This subsection defines aspects of the 'puzzle framework': the interface that +allows puzzle modules to integrate with the puzzle bus main controller. All +communication described within this subsection refers to 'internal' +communication between the main controller and puzzle modules on the puzzle bus. The puzzle framework is the foundation of the puzzle box software, and is designed to facilitate the following: @@ -240,18 +264,18 @@ designed to facilitate the following: ==== State All puzzle modules implement the same state machine shown in -<>. Note that solid arrows indicate state +<>. Note that continuous arrows indicate state transitions that a puzzle module may take on its own, while dashed arrows indicate state transitions forced by the main controller. The main controller -also allows the game operator to manually set the current state as one of the -states on the right half of <>, which can be -used to skip a puzzle if a player is stuck (<>) -or reset a game if it is malfunctioning (<>). +also allows the game operator to manually set a module''s global state to one of +these states, which can be used to skip a puzzle if a player is stuck +(<>) or reset a game if it is malfunctioning +(<>). Puzzle modules start in the 'uninitialized' state, where they wait until the main controller sends a SET STATE command. Receiving this command indicates to the puzzle module that it was successfully registered by the main controller, -and that it may transition from the 'uninitialized' state to the 'reset' state. +and that it may transition from the 'uninitialized' state to the 'idle' state. [[fig:puzzle-module-common-state]] .Global puzzle module state machine @@ -269,70 +293,124 @@ expansion without modification of the main controller software. ==== Commands -The puzzle module framework describes the following command *types*: +// TODO: cleanup + +All messages sent over the puzzle bus have a similar format. This format is +shown in Table 2. Notable details include: + +The 'subject' field does not have to match the I^2^C address of the message +sender or recipient + +Property 0x00 stores a module's global state + +.Puzzle bus message format +[%autowidth] +|=== +| Field | Content + +| Command | Enum: read, write, update +| Subject | I^2^C address (7-bit) +| Property | Address (8-bit) +| Value | Byte string (variable length) +|=== + +The messages described in Table 2 are (de)serialized using Google's protocol +buffer library. This choice was made after considering various alternative +options for sending structured messages cite:[research]. + +<> shows an example of how messages are +exchanged for the initialization of a puzzle module. + +<> shows an example exchange where the last puzzle +module (A) is solved while (B) is already solved. + +. First, module A sets it's own state to 'solved' and subsequently informs the + main controller of this change. +. As a result of this update notification, the main controller queries puzzle + module A for its new global state. +. Once the main controller has received and confirmed that all puzzle module + global states are set to 'solved', the main controller sets its own state to + 'solved', and broadcasts an update message to all puzzle modules. +. As a result of the update message from the main controller, module B requests + the main controller's new global state, and is able to verify that all puzzle + modules have been solved. + +In this example, module B could be the vault puzzle module, which displays a +code when the entire puzzle box is solved. + +[[fig:sequence-puzzle-finish]] +.Puzzle box finish sequence diagram +image::img/sequence-puzzle-finish.svg[] + +The puzzle module framework describes the following command _types_: * ``PROP``: property * ``MAGIC``: handshake * ``STATE``: global state -Each command also has a specific *action*: +Each command also has a specific _action_: * ``REQ``: request * ``RES``: response * ``SET``: (over)write -Not all commands define behavior for all actions (e.g. there is no MAGIC SET -command). +- Not all commands define behavior for all actions (e.g. there is no MAGIC SET + command). +- A REQ command is always answered by a RES command. +- A SET command does not have a reply. +- All commands are sent as I^2^C writes. The Doxygen-generated pages for these command types explain their usage, and -will not be repeated here. +will not be restated in this document. === Main Controller This subsection defines the function and state of the main controller. +==== Initializing puzzle modules + +The main controller sends a MAGIC REQ command to every I^2^C address on +startup. The MAGIC command effectively serves as a 'secret handshake' (using a +_magic_ value) which is used to distinguish between puzzle modules and +unrelated I^2^C devices. Puzzle modules start in the 'uninitialized' state (see +<>), in which they do nothing. Puzzle modules +in this state are still able to reply to requests, including MAGIC REQ +commands. When the main controller receives a MAGIC RES command, the I^2^C +address of the sender is added to an internal list for puzzle modules. + +After the initial handshake request 'wave' (bus scan), all puzzle modules are +repeatedly asked for their global state using a STATE REQ command. This request +also includes the global state of the requesting puzzle module, which is always +the main controller (under normal circumstances). Upon receiving the first +STATE REQ command, a puzzle module knows it has been registered successfully by +the main controller, and may transition into the 'idle' state. + +[[fig:sequence-puzzle-module-init]] +.Puzzle module initialization sequence diagram +image::img/sequence-puzzle-module-init.svg[] + +(Activated lifeline indicates the module is no longer in 'uninitialized' state) + ==== State -The global state of the main controller is an aggregated version of all -installed puzzle modules and is defined by the state machine shown in +The global state of the main controller is an aggregation of all installed +puzzle modules and is defined by the state machine shown in <>. [[fig:main-controller-state]] .Main controller global state machine image::img/main-controller-state.svg[] -The following list describes when each state is active: +The main controller global state is determined using the following rules: -* If all puzzle modules are in the 'reset' state, the main controller is also - in the 'reset' state. +* If all puzzle modules are in the 'idle' state, the main controller is also in + the 'idle' state. * If all puzzle modules are in the 'solved' state, the main controller is also - in the 'solved' state. + in the 'solved' state. * Else, the main controller is in the 'playing' state. -Because the main controller's state is only dependent on the installed puzzle -modules' state, it is only updated when a puzzle module sends an update -notification. When the global state of the main module changes, an update -broadcast is sent to all puzzle modules. - -To simplify the commands used to control the puzzle box, the list of installed -puzzle modules is stored as an auxiliary state variable of the main controller. - -==== Initializing Puzzle Modules - -Puzzle modules start in the 'uninitialized' state (see -<>). In this state, the puzzle module -repeatedly sends an update command to the main controller. The main controller -responds to this message by sending a 'set state' command with the target state -as 'reset' as reply. Before this response is sent, the main controller -internally adds the bus address of the puzzle module requesting to be -initialized to the list of installed puzzle modules. From the main controller's -point of view, this reply marks the moment the initialization is complete. - -[[fig:sequence-puzzle-module-init]] -.Puzzle module initialization sequence diagram -image::img/sequence-puzzle-module-init.svg[] - -(Activated lifeline indicates the module is no longer in 'uninitialized' state) +Due to the repeated STATE REQ commands, this effectively informs the puzzle +modules when the puzzle box is completely solved. [[sec:main-bridge]] ==== Bridge @@ -343,99 +421,83 @@ The Raspberry Pi 3B+ used as main controller during the 21-22 run of the project set up a Wi-Fi Mesh network cite:[2122_design] to communicate with the puzzle box. This year's main controller (Raspberry Pi Pico W cite:[research]) uses a standard 802.11b/g/n access point instead -(<>). +(<>) as it is a simpler solution. + +On this network, the main controller hosts a server that serves a TCP socket +connection. This socket is used to directly forward all internal messages sent +on the puzzle bus to the puzzle box client bidirectionally (on behalf of the +main controller). + +Due to the separation of the puzzle bus driver code into a standalone library +for reading/writing puzzle bus commands, and a puzzle module-specific code, the +puzzle box client is able to read/write raw I^2^C commands directly. A separate +library was made for serializing I^2^C messages so they can be sent over the +TCP connection. -On this network, the main controller hosts a server that serves TCP socket -connections. These sockets directly forward all internal messages sent to the -main controller bidirectionally (i.e. on behalf of the main controller). Detailed specifications on the TCP socket server are in <>. -==== Operating System +==== Operating system + +TODO -The research document cite:[research] contains a detailed comparison of various -operating systems that are suitable to realize the functionality described in -this section. After this comparison, the decision was made to utilize FreeRTOS -as the operating system on the Rasberry Pi Pico W. +- main controller does tcp and i2c at the same time +- simple scheduler is needed +- curriculum only has FreeRTOS and Zephyr +- Zephyr is overkill +- FreeRTOS it is + +- due to RP2040 limitations, delays are used +- most SDKs I2C drivers directly call I2C message handlers from ISR +- puzzle bus driver functions can no longer be called directly from ISR handlers due to the delay +- FreeRTOS is also used in puzzle modules, though this can likely be removed in the future [[sec:lv2-bus]] -=== Puzzle Bus +=== Puzzle bus This section describes the addresses and communication protocol used on the puzzle bus. These specifications only apply to messages sent internally in the puzzle box, as messages forwarded by the bridge (see <>) are sent on behalf of the main controller. -==== Addresses - -The I^2^C addresses remain mostly unchanged from the 20-21 group's -implementation cite:[2021_design]. Addresses that were modified since the 20-21 -implementation are marked with an asterisk. Table 1 lists these addresses for -reference. These addresses are also used to identify specific puzzle modules. - -.I^2^C address reference -[%autowidth] -|=== -| Peripheral | Address - -| Main controller | 0x10* -| Neotrellis puzzle controller | 0x11* -| Neotrellis button matrix | 0x12* -| Software puzzle controller | 0x03 -| Hardware puzzle controller | 0x04 -| Vault puzzle controller | 0x06 -|=== - -==== Messages - -All messages sent over the puzzle bus have a similar format. This format is -shown in Table 2. Notable details include: - -The 'subject' field does not have to match the I^2^C address of the message -sender or recipient - -Property 0x00 stores a module's global state +==== Guidelines -.Puzzle bus message format -[%autowidth] -|=== -| Field | Content +The following assumptions are made about puzzle modules: -| Command | Enum: read, write, update -| Subject | I^2^C address (7-bit) -| Property | Address (8-bit) -| Value | Byte string (variable length) -|=== +* Puzzle modules do not take initiative to send REQ or SET commands. They only + respond to requests from the main controller. +* Puzzle modules can be distinguished from unrelated I^2^C peripherals using + the MAGIC command. +* Puzzle modules are I^2^C multi-master controllers that are slave-addressable + in master mode. +* The main controller is a puzzle module, but with the following differences: +** The main controller is allowed to initiate REQ and SET commands. +** The main controller's global state is an aggregation of all other puzzle + modules, and ignores STATE SET commands. -The messages described in Table 2 are (de)serialized using Google's protocol -buffer library. This choice was made after considering various alternative -options for sending structured messages cite:[research]. +These guidelines allow the following simplifications: -<> shows an example of how messages are -exchanged for the initialization of a puzzle module. +* Puzzle modules may assume they are always being addressed by the main + controller (this simplifies the message architecture). +* The puzzle bus may be shared with regular I^2^C peripherals without causing + issues. -<> shows an example exchange where the last puzzle -module (A) is solved while (B) is already solved. +==== Addresses -. First, module A sets it's own state to 'solved' and subsequently informs the - main controller of this change. -. As a result of this update notification, the main controller queries puzzle - module A for its new global state. -. Once the main controller has received and confirmed that all puzzle module - global states are set to 'solved', the main controller sets its own state to - 'solved', and broadcasts an update message to all puzzle modules. -. As a result of the update message from the main controller, module B requests - the main controller's new global state, and is able to verify that all puzzle - modules have been solved. +The RPI Pico SDK prohibits the use of I^2^C addresses reserved by the I^2^C +specification. This means different addresses from previous years are used. +These addresses are indexed in the code under a shared header (see +``lib/pbdrv/pb.h``). -In this example, module B could be the vault puzzle module, which displays a -code when the entire puzzle box is solved. +The same I^2^C address may be used by two different puzzle modules, but this +will make it impossible for them to be used simultaniously. -[[fig:sequence-puzzle-finish]] -.Puzzle box finish sequence diagram -image::img/sequence-puzzle-finish.svg[] +The I^2^C addresses are also used to determine the puzzle sequence (i.e. the +order in which puzzle modules are set to the 'playing' state). The sequence is +determined by the main controller on startup, and consists of the connected +puzzle modules' addresses in descending order (i.e. highest address first). -=== NeoTrellis Puzzle +=== NeoTrellis puzzle This subsection defines aspects of the 'NeoTrellis puzzle' module and gives a summary of how the puzzle is meant to be solved. This module will be created to @@ -462,7 +524,7 @@ diagram has been shown in <>. .NeoTrellis puzzle in-out image::img/neotrellis-io.png[] -=== Software Puzzle +=== Software puzzle This subsection defines aspects of the 'software puzzle' module and gives a summary of how the puzzle is meant to be solved. This module will be created to @@ -502,9 +564,9 @@ code. This is shown in <>. .Software puzzle in-out image::img/software-io.png[] -=== Hardware Puzzle +=== Hardware puzzle -==== Hardware Puzzle gameplay +==== Hardware puzzle gameplay The hardware puzzle has a logic gate puzzle engraved on the front plate of the puzzle box. To solve the puzzle, the user must set the toggle switches to the @@ -522,7 +584,7 @@ The inputs and outputs of this puzzle have been taken from the design document of the previous group which worked on this project (21-22). This input and output diagram has been shown in Figure ??. -=== Vault Puzzle +=== Vault puzzle ==== Vault puzzle gameplay @@ -539,35 +601,9 @@ clicked the vault resets and they need to start over from the beginning. .Vault puzzle in-out image::img/vault-io.png[] -=== Bomb device - -==== Bomb device connection - -The bomb connects to a WiFi-network using the 802.11x standard. The hub hosts -an interface that can be used to identify all the devices including the bomb -and also pair it to a puzzlebox. After that the game can be set-up and a given -countdown time and start time will be communicated to the bomb over a TCP -socket connection. The hub generates a code that will be send to both the -puzzlebox and bomb so that both devices know what would be or can be expected. - -The bomb can also use the WiFi connection to sync. the time. - -==== Device inputs & outputs - -[[fig:bomb-io]] -.Bomb device in-out -image::img/bomb-io.png[] - == Components [[sec:lv3-remote-control]] -=== Remote Control -==== Socket Server -==== Socket Commands -=== Neotrellis Puzzle -=== Game state diagrams, activity diagrams (if applicable) -=== Software Puzzle -=== Hardware Puzzle -=== Vault Puzzle +=== Remote control [appendix] == NeoTrellis puzzle example diff --git a/docs/handover.adoc b/docs/handover.adoc index 0e8af5a..6918256 100644 --- a/docs/handover.adoc +++ b/docs/handover.adoc @@ -109,7 +109,8 @@ Our project documentation was originally written in Microsoft Word, but we later transferred all documentation to AsciiDoc because of issues where OneDrive would roll back changes. If possible, use a documentation system or format that allows using an external version control system like Git to avoid -losing content. +losing content. This is also the reason why our documents may contain +formatting/style errors. === Misconceptions diff --git a/docs/img/.gitignore b/docs/img/.gitignore index afafe8d..529fbaa 100644 --- a/docs/img/.gitignore +++ b/docs/img/.gitignore @@ -1 +1,3 @@ software-components.svg +sequence-puzzle-module-init.svg +sequence-puzzle-finish.svg diff --git a/docs/img/sequence-puzzle-module-init.puml b/docs/img/sequence-puzzle-module-init.puml new file mode 100644 index 0000000..3d2fa56 --- /dev/null +++ b/docs/img/sequence-puzzle-module-init.puml @@ -0,0 +1,17 @@ +@startuml +!include style.ipuml + +participant "main controller" as main +participant "puzzle module" as mod + +activate main + +main -> mod : MAGIC REQ +mod -> main : MAGIC RES + +||| + +main -> mod ++: STATE REQ +mod -> main : STATE RES + +@enduml diff --git a/docs/img/sequence-puzzle-module-init.svg b/docs/img/sequence-puzzle-module-init.svg deleted file mode 100644 index 2e8db4f..0000000 --- a/docs/img/sequence-puzzle-module-init.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -
Main
update
A
state := reset
state := reset
power on
update
\ No newline at end of file diff --git a/docs/img/style.ipuml b/docs/img/style.ipuml index 25bb6d7..9ea5170 100644 --- a/docs/img/style.ipuml +++ b/docs/img/style.ipuml @@ -1,3 +1,4 @@ !theme plain skinparam RoundCorner 0 +hide footbox diff --git a/docs/makefile b/docs/makefile index 15ea3a4..d180398 100644 --- a/docs/makefile +++ b/docs/makefile @@ -24,4 +24,5 @@ ASCIIDOCTOR_ARGS += --backend pdf plantuml -tsvg $< design.pdf: img/software-components.svg +design.pdf: img/sequence-puzzle-module-init.svg diff --git a/lib/pbdrv/pb.h b/lib/pbdrv/pb.h index 0f2e9d1..e8037ae 100644 --- a/lib/pbdrv/pb.h +++ b/lib/pbdrv/pb.h @@ -1,23 +1,34 @@ #pragma once +//! I2C bus speed in hertz (100 KHz) #define PB_CLOCK_SPEED_HZ 100000 +//! I2C bus timeout delay in milliseconds #define PB_TIMEOUT_MS 10 +//! I2C bus timeout delay in microseconds #define PB_TIMEOUT_US (1e3 * PB_TIMEOUT_MS) -// Adafruit NeoTrellis modules +//! Adafruit NeoTrellis module 1 I2C address #define PB_ADDR_ADA_NEO_1 0x2E +//! Adafruit NeoTrellis module 2 I2C address #define PB_ADDR_ADA_NEO_2 0x2F +//! Adafruit NeoTrellis module 3 I2C address #define PB_ADDR_ADA_NEO_3 0x30 +//! Adafruit NeoTrellis module 4 I2C address #define PB_ADDR_ADA_NEO_4 0x32 -// Main controller +//! Main controller I2C address #define PB_ADDR_MOD_MAIN 0x08 -// Puzzle modules +//! NeoTrellis puzzle module I2C address #define PB_ADDR_MOD_NEOTRELLIS 0x21 +//! Software puzzle module I2C address #define PB_ADDR_MOD_SOFTWARE 0x22 +//! Hardware puzzle module I2C address #define PB_ADDR_MOD_HARDWARE 0x23 +//! Vault puzzle module I2C address #define PB_ADDR_MOD_VAULT 0x24 -// #define BUSADDR_MOD_AUTOMATION 0x25 +//! Automation puzzle module I2C address +#define BUSADDR_MOD_AUTOMATION 0x25 +//! Dummy puzzle module I2C address #define PB_ADDR_MOD_DUMMY 0x69 -- cgit v1.2.3 From 8a80226e9dce394573e95d68fc9fe04592ad5907 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Fri, 21 Jun 2024 19:29:44 +0200 Subject: more doxygen documentationg --- Doxyfile | 8 +++- lib/pbdrv/index.dox | 39 +++++++++++++++ lib/pbdrv/pb-buf.h | 16 +++++++ lib/pbdrv/pb-mem.h | 55 +++++++++++++++++++++ lib/pbdrv/pb-mod.h | 83 ++++++++++++++++++++++++-------- lib/pbdrv/pb-msg.h | 40 ++++++++++++++++ lib/pbdrv/pb-route.h | 27 +++++++---- lib/pbdrv/pb.h | 9 ++++ lib/pbdrv/spec.adoc | 133 --------------------------------------------------- 9 files changed, 248 insertions(+), 162 deletions(-) create mode 100644 lib/pbdrv/index.dox delete mode 100644 lib/pbdrv/spec.adoc (limited to 'lib/pbdrv') diff --git a/Doxyfile b/Doxyfile index c3623cc..53e75c2 100644 --- a/Doxyfile +++ b/Doxyfile @@ -3,6 +3,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "puzzlebox" OUTPUT_DIRECTORY = doxygen +INPUT += readme.md INPUT += client INPUT += lib/mpack INPUT += lib/i2ctcp @@ -14,8 +15,13 @@ EXCLUDE = lib/mpack/src EXCLUDE_PATTERNS = **/build EXCLUDE_SYMLINKS = YES -FILE_PATTERNS = *.c *.cpp *.h *.hpp *.md +FILE_PATTERNS = *.c *.cpp *.h *.hpp *.md *.dox RECURSIVE = YES GENERATE_LATEX = NO +ALIASES += I2C="I²C" +INPUT_FILTER = "sed -e 's/\\|\2<\/sup>C\>/\\I2C/g'" + +USE_MDFILE_AS_MAINPAGE = readme.md + diff --git a/lib/pbdrv/index.dox b/lib/pbdrv/index.dox new file mode 100644 index 0000000..ad05078 --- /dev/null +++ b/lib/pbdrv/index.dox @@ -0,0 +1,39 @@ +// vim:ft=doxygen +/** + +\defgroup pbdrv pbdrv +\brief Standalone puzzle bus driver + +pbdrv is a standalone portable static library for handling (i.e. +(de)serialization) of puzzle bus messages. + +\defgroup pbdrv-mod pbdrv-mod +\brief Puzzle module driver (superset of \ref pbdrv) + +pbdrv-mod is a superset of pbdrv, and includes functions specific to puzzle bus +modules. pbdrv-mod compiles to an object file instead of a static library +because it may depend on functions that rely on external libraries. pbdrv-mod +is still considered standalone, but requires either using an existing driver, +or (partially) implementing the driver functions. + +\note Most \c pb_* functions have a weak implementation, which may be +overwritten by a custom implementation. This allows you to use the default +implementation where possible, and only implement extensions required for your +puzzle module. + +TODO: where to find drivers +TODO: what are extensions +TODO: what to do if there is no driver / extension + +\{ + +\defgroup hook +\brief Functions for (partially) overriding default behavior + +Hooks are functions with a default (weak) implementation in pbdrv. These +functions can be overwritten by the user to implement custom behavior, without +needing to understand the internals of pbdrv. + +\} + +*/ diff --git a/lib/pbdrv/pb-buf.h b/lib/pbdrv/pb-buf.h index 78ee380..049f516 100644 --- a/lib/pbdrv/pb-buf.h +++ b/lib/pbdrv/pb-buf.h @@ -6,14 +6,30 @@ extern "C" { #endif +/** + * \ingroup pbdrv + * \ingroup pbdrv-mod + * \defgroup pb_buf Buffer + * \brief Binary data buffer type used in pbdrv + * \{ + */ + //! binary buffer struct typedef struct { char * data; //! pointer to data size_t size; //! size of data } pb_buf_t; +/** + * \brief free a \c pb_buf_t + * + * This function calls \c pb_free() on the \c data member of a \c pb_buf_t + * struct if it is not equal to \c NULL. + */ void pb_buf_free(pb_buf_t * buf); +/// \} + #ifdef __cplusplus } #endif diff --git a/lib/pbdrv/pb-mem.h b/lib/pbdrv/pb-mem.h index 72be214..4d0f995 100644 --- a/lib/pbdrv/pb-mem.h +++ b/lib/pbdrv/pb-mem.h @@ -6,13 +6,68 @@ extern "C" { #endif +/** + * \ingroup pbdrv + * \ingroup pbdrv-mod + * \defgroup pb_mem Memory + * \brief Platform-specific memory management functions + * + * \note This header only declares the memory management functions, and it is + * up to \ref pb_ext "extensions" to implement the underlying memory management + * functions. + * + * TODO: ref to extensions + * + * \{ + */ + +/** + * \brief Allocate a contiguous chunk of memory + * \param sz Requested size of memory area + * \return Pointer to memory area, or \c NULL on failure + * \note The allocated memory must be free'd again using \c pb_free() + */ void * pb_malloc(size_t sz); +/** + * \brief Free a chunk of memory previously allocated with \c pb_malloc() + * \param ptr Pointer to memory area + */ void pb_free(void * ptr); +/** + * \brief Resize the memory area \c ptr to size \c sz + * \param ptr Pointer to allocated memory area + * \param sz Requested new size of memory area + * \return Pointer to memory area, or \c NULL on failure + * \warning This function is not available on FreeRTOS, and should be avoided + * if possible. MPack's \c mpack_writer_init_growable() is known to use \c + * realloc(), and should not be used for this reason. + */ void * pb_realloc(void * ptr, size_t sz); +/** + * \brief copy a memory region + * \param dest Pointer to destination memory + * \param src Pointer to source memory + * \param sz Number of bytes to copy + * \return Pointer to \c dest + * + * This function has a portable implementation, and is always available. + */ void * pb_memcpy(void * dest, const void * src, size_t sz); +/** + * \brief compare two memory regions + * \param a Pointer to first memory region + * \param b Pointer to second memory region + * \param sz Number of bytes to compare + * \return 0 if the memory regions are identical, or the difference between the + * first non-matching byte + * + * This function has a portable implementation, and is always available. + */ int pb_memcmp(const void * a, const void * b, size_t sz); +/// \} + #ifdef __cplusplus } #endif diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h index c4629a6..2ff1908 100644 --- a/lib/pbdrv/pb-mod.h +++ b/lib/pbdrv/pb-mod.h @@ -1,31 +1,63 @@ #pragma once -/** - * \file puzzle bus driver implementation - * - * Most \c pb_* functions have a weak implementation, which may be overwritten - * by a custom implementation. This allows you to use the default - * implementation where possible, and only implement extensions required for - * your puzzle module. Please see spec.adoc for more information about how to - * use the puzzle bus driver library. - */ - #include "pb-types.h" #ifdef __cplusplus extern "C" { #endif -//! puzzle module name (optional, default = "???") +/** + * \ingroup pbdrv-mod + * \defgroup pb_mod Module + * \brief Metadata and auxiliary utility functions + * \{ + */ + +/** + * \brief Puzzle module name + * + * Optional to define, default value is "???" + */ extern const char * PB_MOD_NAME; -//! puzzle module bus address (required) +/** + * \brief Puzzle module bus address + * + * **Required** to define + */ extern const i2c_addr_t PB_MOD_ADDR; +/** + * \brief Platform-specific blocking delay function + * + * FIXME: this should be removed (see handover: RP2040 I2C limitations) + */ +void pb_mod_blocking_delay_ms(unsigned long ms); + +/// \} + +/** + * \ingroup pbdrv-mod + * \defgroup pb_i2c I2C + * \brief I2C send/receive handlers + * + * If there is no existing \ref pb_drv "driver" for the microcontroller on + * which you want to use the puzzle bus driver, you may implement the following + * in order to use pbdrv: + * + * - The \c pb_i2c_recv() function must be **called** for every received I2C + * message + * - The \c pb_i2c_send() function must be **implemented** using the + * platform/device-specific I2C write function + * + * \{ + */ + /** * \brief handle a received message from the I2C bus (puzzle bus) * * This function attempts to parse an I2C message as a puzzle bus message, and * calls the appropriate handler for the message if it is considered valid. + * Invalid messages are silently ignored. * * \param buf pointer to message content * \param sz size of \p buf @@ -46,25 +78,36 @@ void pb_i2c_recv(const uint8_t * buf, size_t sz); */ void pb_i2c_send(i2c_addr_t i2c_addr, const uint8_t * buf, size_t sz); +/// \} + +/// \ingroup hook +/// \{ + +/** + * \defgroup pb_hook_mod_state State + * \brief Provide your own global state variable + * + * If your puzzle module defines its own global \c pb_global_state_t, you can + * tell the driver to use it by implementing these functions. These functions + * are also used internally by the driver when creating \c STATE \c REQ and \c + * STATE \c RES commands. + * + * \{ + */ + /** * \brief global state read hook - * \ingroup hook * \return current value of global state enum */ pb_global_state_t pb_hook_mod_state_read(); /** * \brief global state write hook - * \ingroup hook * \param state new value of global state enum */ void pb_hook_mod_state_write(pb_global_state_t state); -/** - * \brief platform-specific blocking delay function - * - * FIXME: this should be removed (see handover: RP2040 I2C limitations) - */ -void pb_mod_blocking_delay_ms(unsigned long ms); +/// \} +/// \} #ifdef __cplusplus } diff --git a/lib/pbdrv/pb-msg.h b/lib/pbdrv/pb-msg.h index f27d4c4..33b697a 100644 --- a/lib/pbdrv/pb-msg.h +++ b/lib/pbdrv/pb-msg.h @@ -7,10 +7,50 @@ extern "C" { #endif +/** + * \ingroup pbdrv + * \ingroup pbdrv-mod + * \defgroup pb_msg Message + * \brief Message (de)serialization + * \{ + */ + +/** + * \brief Serialize a message into a binary buffer + * + * \note This function allocates a \c pb_buf_t that should be free'd using \c + * pb_buf_free() + * + * \param msg Message to serialize + * + * \warning The type of \c msg->cmd is inferred from \c msg->type. If the + * message is not correctly formatted, this function may cause undefined + * behavior. If possible, use functions from \ref pb_send instead. + * + * \return \c pb_buf_t containing the serialized message, or an empty struct if + * serialization failed + */ pb_buf_t pb_msg_write(const pb_msg_t * msg); +/** + * \brief Read a binary buffer and attempt to deserialize it as a puzzle bus + * message + * + * \note This function allocates a \c pb_msg_t pointer that should be free'd + * using \c pb_msg_free() + * + * \param buf Binary data to interpret as puzzle bus message + * + * \return \c pb_msg_t pointer containing the deserialized message, or NULL if + * serialization failed + */ pb_msg_t * pb_msg_read(const pb_buf_t * buf); +/** + * \brief Recursively free fields of a \c pb_msg_t + */ void pb_msg_free(pb_msg_t * msg); +/// \} + #ifdef __cplusplus } #endif diff --git a/lib/pbdrv/pb-route.h b/lib/pbdrv/pb-route.h index 967c4a9..233a087 100644 --- a/lib/pbdrv/pb-route.h +++ b/lib/pbdrv/pb-route.h @@ -8,16 +8,10 @@ extern "C" { void pb_route_msg(pb_msg_t * msg); -bool pb_hook_route_msg(pb_msg_t * msg); - void pb_route_cmd_prop(pb_msg_t * msg); void pb_route_cmd_state(pb_msg_t * msg); void pb_route_cmd_magic(pb_msg_t * msg); -bool pb_hook_route_cmd_prop(pb_msg_t * msg); -bool pb_hook_route_cmd_state(pb_msg_t * msg); -bool pb_hook_route_cmd_magic(pb_msg_t * msg); - void pb_route_cmd_prop_req(pb_msg_t * msg); void pb_route_cmd_prop_res(pb_msg_t * msg); void pb_route_cmd_prop_set(pb_msg_t * msg); @@ -26,11 +20,28 @@ void pb_route_cmd_state_req(pb_msg_t * msg); void pb_route_cmd_state_res(pb_msg_t * msg); void pb_route_cmd_state_set(pb_msg_t * msg); +void pb_route_cmd_magic_req(pb_msg_t * msg); +void pb_route_cmd_magic_res(pb_msg_t * msg); + +/// \ingroup hook +/// \{ + +/// \defgroup hook_route Routing +/// \brief Use a custom message handler +/// \{ + +bool pb_hook_route_msg(pb_msg_t * msg); + +bool pb_hook_route_cmd_prop(pb_msg_t * msg); +bool pb_hook_route_cmd_state(pb_msg_t * msg); +bool pb_hook_route_cmd_magic(pb_msg_t * msg); + +/// \} + void pb_hook_main_state_update(pb_global_state_t state); void pb_hook_module_init(); -void pb_route_cmd_magic_req(pb_msg_t * msg); -void pb_route_cmd_magic_res(pb_msg_t * msg); +/// \} #ifdef __cplusplus } diff --git a/lib/pbdrv/pb.h b/lib/pbdrv/pb.h index e8037ae..cef04d8 100644 --- a/lib/pbdrv/pb.h +++ b/lib/pbdrv/pb.h @@ -1,5 +1,13 @@ #pragma once +/** + * \ingroup pbdrv + * \ingroup pbdrv-mod + * \defgroup pb Bus + * \brief Constants for the puzzle bus hardware + * \{ + */ + //! I2C bus speed in hertz (100 KHz) #define PB_CLOCK_SPEED_HZ 100000 //! I2C bus timeout delay in milliseconds @@ -32,3 +40,4 @@ //! Dummy puzzle module I2C address #define PB_ADDR_MOD_DUMMY 0x69 +/// \} diff --git a/lib/pbdrv/spec.adoc b/lib/pbdrv/spec.adoc deleted file mode 100644 index 3172e84..0000000 --- a/lib/pbdrv/spec.adoc +++ /dev/null @@ -1,133 +0,0 @@ -= Puzzle module specification - -This folder contains an implementation of the puzzle bus protocol -specification, and is targeted at puzzle module developers. This document -describes the required implementation steps for integrating a new game into the -puzzle module framework. - -== The bus - -The puzzle bus carries data over a standard I^2^C bus. Additional details about -this bus can be found in the link:../../docs/design.adoc[Design document]. - -The following details are important to puzzle module developers, as they may -cause unexpected behavior: - -- *Addresses influence the puzzle box's behavior*. The order of puzzles is - determined by the puzzle module address. Two puzzle modules may use the same - address, but this will mean that they cannot be used simultaniously in the - same puzzle box. Known addresses are documented in link:bus.h[]. -- *The read/write bit of an I^2^C frame determines how it's handled*. I^2^C - *read* frames are treated as requests, while *write* frames are treated as - responses. - -== Puzzle bus driver (pbdrv) - -The library in this folder is a partial implementation of the puzzle bus -specification *for puzzle modules*. Most functions in the driver are marked -with the 'weak' attribute, which allows you to override them by providing an -implementation. - -In order to utilize this driver, the following must be done: - -- The ``pbdrv_i2c_recv`` function must be *called* for every received *I^2^C - read* frame -- The ``pbdrv_i2c_send`` function must be *implemented* with the - platform-specific *I^2^C write* function - -This is enough to get the puzzle module registered. You may also want to -implement some of the following integrations: - -- If your game uses the global state variable, you should implement the - <> to point the driver to your own - global state variable, and be notified of reads/writes to it. -- If you want to expose additional game state variables over the puzzle bus, - you should implement the <>. -- If you want to implement custom puzzle bus commands, you can implement the - <>. - -All other kinds of integrations/hooks can likely be realized by overriding the -default implementations, but this is discouraged. - -[[sec:state-global]] -== Global state - -If your puzzle module defines its own global ``enum pb_state``, you can tell -the driver to use it by implementing the ``pbdrv_hook_state_read`` and -``pbdrv_hook_state_write`` functions. These functions are also used by the -default implementation of the read/write commands to address 0 (global state). - -Example: - -```c -pb_state_t global_state = PB_GS_NOINIT; - -pb_state_t pbdrv_hook_mod_state_read() { - return global_state; -} - -void pbdrv_hook_mod_state_write(pb_state_t state) { - global_state = state; -} -``` - -[[sec:state-aux]] -== Auxiliary state - -You can expose additional state variables by implementing the -``pbdrv_hook_read`` and ``pbdrv_hook_write`` functions. These functions should -return ``true`` for state addresses you want to override. - -Example: - -```c -#define CUSTOM_VAR_ADDR 0x01 -uint8_t my_custom_variable = 10; - -bool pbdrv_hook_read(uint16_t i2c_addr, uint8_t addr) { - switch (addr) { - case CUSTOM_VAR_ADDR: { - char res[] = { PB_CMD_READ, addr, my_custom_variable }; - pbdrv_i2c_send(i2c_addr, res, sizeof(res)); - break; - } - default: return false; - } - - return true; -} - -bool pbdrv_hook_write(uint16_t i2c_addr, uint8_t addr, const char * buf, size_t sz) { - switch (addr) { - case CUSTOM_VAR_ADDR: { - if (sz != 1) return false; - my_custom_variable = buf[0]; - break; - } - default: return false; - } - - return true; -} -``` - -[[sec:cmd]] -== Custom commands - -Similar to the auxiliary state, custom commands can be added by implementing -the ``pbdrv_hook_cmd`` function, which should return ``true`` for the -command(s) that you want to overwrite. - -Example: - -```c -bool pbdrv_hook_cmd(uint16_t i2c_addr, enum pb_cmd cmd, const char * buf, size_t sz) { - if (cmd == 0x54) { - printf("custom command received!\n"); - return true; - } - - return false; -} -``` - -- cgit v1.2.3 From 2187c3d6196f2a25bc0b74365358c9874dde3a46 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 22 Jun 2024 10:39:01 +0200 Subject: more code documentation --- Doxyfile | 2 + lib/pbdrv/index.dox | 29 +++++----- lib/pbdrv/pb-buf.h | 2 +- lib/pbdrv/pb-mod.h | 11 +++- lib/pbdrv/pb-route.c | 8 +-- lib/pbdrv/pb-route.h | 161 ++++++++++++++++++++++++++++++++++++++++++++++++--- lib/pbdrv/pb-types.h | 58 ++++++++++++++----- 7 files changed, 228 insertions(+), 43 deletions(-) (limited to 'lib/pbdrv') diff --git a/Doxyfile b/Doxyfile index 53e75c2..ea9692a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -25,3 +25,5 @@ INPUT_FILTER = "sed -e 's/\\|\2<\/sup>C\>/\\I2C/g'" USE_MDFILE_AS_MAINPAGE = readme.md +INTERNAL_DOCS = YES + diff --git a/lib/pbdrv/index.dox b/lib/pbdrv/index.dox index ad05078..e4a6c5f 100644 --- a/lib/pbdrv/index.dox +++ b/lib/pbdrv/index.dox @@ -7,15 +7,6 @@ pbdrv is a standalone portable static library for handling (i.e. (de)serialization) of puzzle bus messages. -\defgroup pbdrv-mod pbdrv-mod -\brief Puzzle module driver (superset of \ref pbdrv) - -pbdrv-mod is a superset of pbdrv, and includes functions specific to puzzle bus -modules. pbdrv-mod compiles to an object file instead of a static library -because it may depend on functions that rely on external libraries. pbdrv-mod -is still considered standalone, but requires either using an existing driver, -or (partially) implementing the driver functions. - \note Most \c pb_* functions have a weak implementation, which may be overwritten by a custom implementation. This allows you to use the default implementation where possible, and only implement extensions required for your @@ -25,14 +16,26 @@ TODO: where to find drivers TODO: what are extensions TODO: what to do if there is no driver / extension +\defgroup pbdrv-mod pbdrv-mod +\brief Puzzle module driver (superset of \ref pbdrv) + +pbdrv-mod is a superset of \ref pbdrv, and includes functions specific to +puzzle bus modules. pbdrv-mod compiles to an object file instead of a static +library because it may depend on functions that rely on external libraries. +pbdrv-mod is still considered standalone, but requires either using an existing +driver, or (partially) implementing the driver functions. + +\copydetails pbdrv + \{ -\defgroup hook +\defgroup pb_hook Hook \brief Functions for (partially) overriding default behavior -Hooks are functions with a default (weak) implementation in pbdrv. These -functions can be overwritten by the user to implement custom behavior, without -needing to understand the internals of pbdrv. +Hooks are functions that allow the user to implement custom behavior (i.e. +extend or conditionally replace the default handlers), without needing to +completely overwrite the built-in handlers or understand the internals of \ref +pbdrv. \} diff --git a/lib/pbdrv/pb-buf.h b/lib/pbdrv/pb-buf.h index 049f516..9ff53fe 100644 --- a/lib/pbdrv/pb-buf.h +++ b/lib/pbdrv/pb-buf.h @@ -10,7 +10,7 @@ extern "C" { * \ingroup pbdrv * \ingroup pbdrv-mod * \defgroup pb_buf Buffer - * \brief Binary data buffer type used in pbdrv + * \brief Binary data buffer type used in \ref pbdrv * \{ */ diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h index 2ff1908..ef2cd77 100644 --- a/lib/pbdrv/pb-mod.h +++ b/lib/pbdrv/pb-mod.h @@ -80,7 +80,7 @@ void pb_i2c_send(i2c_addr_t i2c_addr, const uint8_t * buf, size_t sz); /// \} -/// \ingroup hook +/// \ingroup pb_hook /// \{ /** @@ -89,8 +89,7 @@ void pb_i2c_send(i2c_addr_t i2c_addr, const uint8_t * buf, size_t sz); * * If your puzzle module defines its own global \c pb_global_state_t, you can * tell the driver to use it by implementing these functions. These functions - * are also used internally by the driver when creating \c STATE \c REQ and \c - * STATE \c RES commands. + * are also used internally by the driver. * * \{ */ @@ -98,11 +97,17 @@ void pb_i2c_send(i2c_addr_t i2c_addr, const uint8_t * buf, size_t sz); /** * \brief global state read hook * \return current value of global state enum + * + * The default implementation of this function uses an internal global state + * variable in \ref pbdrv. */ pb_global_state_t pb_hook_mod_state_read(); /** * \brief global state write hook * \param state new value of global state enum + * + * The default implementation of this function uses an internal global state + * variable in \ref pbdrv. */ void pb_hook_mod_state_write(pb_global_state_t state); diff --git a/lib/pbdrv/pb-route.c b/lib/pbdrv/pb-route.c index 709fb7f..f5c32d6 100644 --- a/lib/pbdrv/pb-route.c +++ b/lib/pbdrv/pb-route.c @@ -58,8 +58,8 @@ __weak void pb_route_cmd_prop_set(pb_msg_t * msg) {} //! last known global state of last STATE REQ sender (i.e. main controller) static pb_global_state_t _main_state = PB_GS_NOINIT; -__weak void pb_hook_main_state_update(pb_global_state_t state) {} -__weak void pb_hook_module_init() { +__weak void pb_hook_ev_main_state_update(pb_global_state_t state) {} +__weak void pb_hook_ev_module_init() { pb_hook_mod_state_write(PB_GS_IDLE); } __weak void pb_route_cmd_state_req(pb_msg_t * msg) { @@ -69,8 +69,8 @@ __weak void pb_route_cmd_state_req(pb_msg_t * msg) { pb_cmd_state_t * cmd = msg->cmd; if (cmd->state != _main_state) { // first STATE REQ = module init OK - if (_main_state == PB_GS_NOINIT) pb_hook_module_init(); - pb_hook_main_state_update(cmd->state); + if (_main_state == PB_GS_NOINIT) pb_hook_ev_module_init(); + pb_hook_ev_main_state_update(cmd->state); } _main_state = cmd->state; diff --git a/lib/pbdrv/pb-route.h b/lib/pbdrv/pb-route.h index 233a087..b80e4ec 100644 --- a/lib/pbdrv/pb-route.h +++ b/lib/pbdrv/pb-route.h @@ -6,40 +6,185 @@ extern "C" { #endif +/** + * \ingroup pbdrv-mod + * \defgroup pb_route Routing + * \internal + * \brief Parsed message handler routing + * + * These functions form a tree-shaped call graph, and are used to handle + * specific commands received from \c pb_i2c_recv(). + * + * \{ + */ + +/** + * \brief Handle a message with type {\ref PB_CMD_PROP "PROP", \ref + * PB_CMD_STATE "STATE", \ref PB_CMD_MAGIC "MAGIC"} + * + * Calls the next handler depending on \c msg->type. + */ void pb_route_msg(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_PROP "PROP" message with action {\ref + * pb_route_cmd_prop_req "REQ", \ref pb_route_cmd_prop_res "RES", \ref + * pb_route_cmd_prop_set "SET"} + * + * Calls the next handler depending on \c msg->action. + */ void pb_route_cmd_prop(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_STATE "STATE" message with action {\ref + * pb_route_cmd_state_req "REQ", \ref pb_route_cmd_state_res "RES", \ref + * pb_route_cmd_state_set "SET"} + * + * Calls the next handler depending on \c msg->action. + */ void pb_route_cmd_state(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_MAGIC "MAGIC" message with action {\ref + * pb_route_cmd_magic_req "REQ", \ref pb_route_cmd_magic_res "RES"} + * + * Calls the next handler depending on \c msg->action. + * + * \note Messages with type \c MAGIC and action \c SET will be silently + * ignored, as there is no such command. + */ void pb_route_cmd_magic(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_PROP "PROP" message with action \ref + * PB_ACTION_REQ "REQ" + * + * The default implementation of this function is empty, as puzzle module + * properties are user-defined. + */ void pb_route_cmd_prop_req(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_PROP "PROP" message with action \ref + * PB_ACTION_RES "RES" + * + * The default implementation of this function is empty, as puzzle module + * properties are user-defined. + */ void pb_route_cmd_prop_res(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_PROP "PROP" message with action \ref + * PB_ACTION_SET "SET" + * + * The default implementation of this function is empty, as puzzle module + * properties are user-defined. + */ void pb_route_cmd_prop_set(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_STATE "STATE" message with action \ref + * PB_ACTION_REQ "REQ" + * + * The default implementation of this function does the following: + * - Call \c pb_hook_ev_module_init() if this is the first received \c STATE \c + * REQ command. + * - Call \c pb_hook_ev_main_state_update() if the main controller state has + * changed. + * - Reply with a \c STATE \c RES message. + */ void pb_route_cmd_state_req(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_STATE "STATE" message with action \ref + * PB_ACTION_RES "RES" + * + * The default implementation of this function is empty, as only the main + * controller handles this type of command. + */ void pb_route_cmd_state_res(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_STATE "STATE" message with action \ref + * PB_ACTION_SET "SET" + * + * The default implementation of this function does the following: + * - Write the global state variable using \c pb_hook_mod_state_write(). + */ void pb_route_cmd_state_set(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_MAGIC "MAGIC" message with action \ref + * PB_ACTION_REQ "REQ" + * + * The default implementation of this function does the following: + * - Verify the size of the magic string + * - Verify the content of the magic string + * - Reply with a \c MAGIC \c RES message. + */ void pb_route_cmd_magic_req(pb_msg_t * msg); +/** + * \brief Handle a \ref PB_CMD_MAGIC "MAGIC" message with action \ref + * PB_ACTION_RES "RES" + * + * The default implementation of this function is empty, as only the main + * controller handles this type of command. + */ void pb_route_cmd_magic_res(pb_msg_t * msg); -/// \ingroup hook -/// \{ - -/// \defgroup hook_route Routing -/// \brief Use a custom message handler -/// \{ +/// \} +/** + * \ingroup pb_hook + * \defgroup pb_hook_route Routing + * \brief Conditionally use substitute or extend the built-in message handlers + * \{ + */ + +/** + * \brief \c pb_route_msg() hook + * + * The default implementation of this function immediately returns false. + * + * \return \c true if execution should continue to the default handler, or \c + * false if it should stop (i.e. the message was handled). + */ bool pb_hook_route_msg(pb_msg_t * msg); +//! \c pb_route_cmd_prop() hook \copydetails pb_hook_route_msg bool pb_hook_route_cmd_prop(pb_msg_t * msg); +//! \c pb_route_cmd_state() hook \copydetails pb_hook_route_msg bool pb_hook_route_cmd_state(pb_msg_t * msg); +//! \c pb_route_cmd_magic() hook \copydetails pb_hook_route_msg bool pb_hook_route_cmd_magic(pb_msg_t * msg); /// \} -void pb_hook_main_state_update(pb_global_state_t state); -void pb_hook_module_init(); +/** + * \ingroup pb_hook + * \defgroup pb_hook_ev Events + * \brief Functions called on puzzle bus-related events + * \{ + */ + +/** + * \brief Main controller state update hook + * + * The default implementation of this function is empty. + * + * \param state New state of main controller + * + * \note This function is also called when the first \c STATE \c REQ command is + * received, as the main controller state variable used to check if the state + * actually changed is initialized to \ref PB_GS_NOINIT. In this case, this + * function is called *after* \c pb_hook_ev_module_init(). + */ +void pb_hook_ev_main_state_update(pb_global_state_t state); +/** + * \brief Module initialized hook + * + * The default implementation of this function calls \c + * pb_hook_mod_state_write() with \ref PB_GS_IDLE. + * + * This function is called when the first \c STATE \c REQ command is received, + * indicating this puzzle module has been registered successfully by the main + * controller, and is now part of an active play session. + */ +void pb_hook_ev_module_init(); /// \} diff --git a/lib/pbdrv/pb-types.h b/lib/pbdrv/pb-types.h index f5fbb74..dfd5da9 100644 --- a/lib/pbdrv/pb-types.h +++ b/lib/pbdrv/pb-types.h @@ -8,7 +8,17 @@ extern "C" { #endif +/** + * \ingroup pbdrv + * \ingroup pbdrv-mod + * \defgroup pb_types Types + * \brief Datatypes used within \ref pbdrv + * + * \{ + */ + #ifdef __GNUC__ +//! Mark function as weak (allow user to override implementation) #define __weak __attribute__((weak)) #endif #ifndef __weak @@ -21,15 +31,33 @@ typedef uint16_t i2c_addr_t; //! puzzle bus command types enum pb_cmd_id { - /** \brief puzzle module property (REQ, RES, SET) */ + /** + * \brief puzzle module property (\ref pb_route_cmd_prop_req "REQ", \ref + * pb_route_cmd_prop_res "RES", \ref pb_route_cmd_prop_set "SET") + * + * The \c PROP command type is used for exchanging arbitrary data between + * puzzle modules and/or the puzzle box client (pbc) over the TCP bridge. + * These properties are not used by the puzzle framework. + */ PB_CMD_PROP, - /** \brief puzzle module global state variable (REQ, RES, SET) */ + /** + * \brief puzzle module global state variable (\ref pb_route_cmd_state_req + * "REQ", \ref pb_route_cmd_state_res "RES", \ref pb_route_cmd_state_set + * "SET") + * + * The \c STATE command is used by puzzle modules to inform the main + * controller about their global state. The main controller aggregates the + * states of all connected puzzle modules and exchanges this aggregated state + * with the puzzle modules to indicate when the entire puzzle box is solved. + */ PB_CMD_STATE, /** - * \brief magic (handshake) (REQ, RES) + * \brief magic (handshake) (\ref pb_route_cmd_magic_req "REQ", \ref + * pb_route_cmd_magic_res "RES") * - * This message is used to distinguish between puzzle modules and regular I2C - * slaves on the puzzle bus. + * The \c MAGIC command effectively serves as a 'secret handshake' (using a + * _magic_ value) which is used to distinguish between puzzle modules and + * unrelated I2C devices. */ PB_CMD_MAGIC, }; @@ -57,16 +85,16 @@ static const char pb_cmd_magic_req[] = { 0x70, 0x75, 0x7a, 0x62, 0x75, 0x73 }; //! magic reply from puzzle module back to main controller static const char pb_cmd_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 }; -//! puzzle bus message header (shared by all commands) +//! puzzle bus message header / container (shared by all commands) typedef struct { /** - * \brief command type + * \brief Command type (see \ref pb_cmd_id_t) * * This is used to identify what the message is about. */ pb_cmd_id_t type; /** - * \brief command action + * \brief Command action (see \ref pb_action_t) * * This is used to specify what should happen as a result of this message. */ @@ -79,31 +107,33 @@ typedef struct { */ i2c_addr_t sender; /** - * \brief command data (type dependent) + * \brief Command data (dependent on \p type) * * Struct containing command type-specific data. */ void * cmd; } pb_msg_t; -//! PB_CMD_PROP data +//! \ref PB_CMD_PROP data typedef struct { uint8_t propid; //!< id of state property uint8_t * value; //!< new or current value - size_t _value_size; //!< [META] size of \p value + size_t _value_size; //!< size of \p value } pb_cmd_prop_t; -//! PB_CMD_STATE data +//! \ref PB_CMD_STATE data typedef struct { pb_global_state_t state; //!< global state } pb_cmd_state_t; -//! PB_CMD_MAGIC data +//! \ref PB_CMD_MAGIC data typedef struct { char * magic; //!< magic value - size_t _magic_size; //!< [META] size of \p magic + size_t _magic_size; //!< size of \p magic } pb_cmd_magic_t; +/// \} + #ifdef __cplusplus } #endif -- cgit v1.2.3 From bad32f876ab99fe0820fd310a4826378d0b11fe7 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 22 Jun 2024 11:27:48 +0200 Subject: more docs --- Doxyfile | 1 + lib/pbdrv/pb-buf.h | 4 +- lib/pbdrv/pb-mod.h | 11 +++-- lib/pbdrv/pb-send.c | 10 ++--- lib/pbdrv/pb-send.h | 115 +++++++++++++++++++++++++++++++++++++++++++++++++-- lib/pbdrv/pb-types.h | 35 +++++++++------- 6 files changed, 145 insertions(+), 31 deletions(-) (limited to 'lib/pbdrv') diff --git a/Doxyfile b/Doxyfile index ea9692a..2d600e7 100644 --- a/Doxyfile +++ b/Doxyfile @@ -26,4 +26,5 @@ INPUT_FILTER = "sed -e 's/\\|\2<\/sup>C\>/\\I2C/g'" USE_MDFILE_AS_MAINPAGE = readme.md INTERNAL_DOCS = YES +EXTRACT_STATIC = YES diff --git a/lib/pbdrv/pb-buf.h b/lib/pbdrv/pb-buf.h index 9ff53fe..8b4bb10 100644 --- a/lib/pbdrv/pb-buf.h +++ b/lib/pbdrv/pb-buf.h @@ -16,8 +16,8 @@ extern "C" { //! binary buffer struct typedef struct { - char * data; //! pointer to data - size_t size; //! size of data + char * data; //!< pointer to data + size_t size; //!< size of data } pb_buf_t; /** diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h index ef2cd77..75fcbec 100644 --- a/lib/pbdrv/pb-mod.h +++ b/lib/pbdrv/pb-mod.h @@ -9,27 +9,26 @@ extern "C" { /** * \ingroup pbdrv-mod * \defgroup pb_mod Module - * \brief Metadata and auxiliary utility functions + * \brief Puzzle module metadata and auxiliary utility functions * \{ */ /** * \brief Puzzle module name - * - * Optional to define, default value is "???" + * \note This constant is optional to define, its default value is "???" */ extern const char * PB_MOD_NAME; /** * \brief Puzzle module bus address - * - * **Required** to define + * \warning This variable **must** be defined by the user */ extern const i2c_addr_t PB_MOD_ADDR; /** * \brief Platform-specific blocking delay function * - * FIXME: this should be removed (see handover: RP2040 I2C limitations) + * FIXME: this entire function should be removed (see handover: RP2040 I2C + * limitations) */ void pb_mod_blocking_delay_ms(unsigned long ms); diff --git a/lib/pbdrv/pb-send.c b/lib/pbdrv/pb-send.c index 29d81ed..dc34c44 100644 --- a/lib/pbdrv/pb-send.c +++ b/lib/pbdrv/pb-send.c @@ -2,7 +2,7 @@ #include "pb-mod.h" #include "pb-msg.h" -__weak void pb_send_reply(pb_msg_t * msg, pb_buf_t * reply) { +__weak void pb_send_reply(const pb_msg_t * msg, const pb_buf_t * reply) { return pb_i2c_send(msg->sender, (uint8_t *) reply->data, reply->size); } @@ -21,10 +21,10 @@ pb_buf_t pb_send_read_req(uint8_t propid) { return pb_msg_write(&msg); } -pb_buf_t pb_send_read_res(uint8_t propid, uint8_t * value, size_t size) { +pb_buf_t pb_send_read_res(uint8_t propid, const uint8_t * value, size_t size) { pb_cmd_prop_t cmd = { .propid = propid, - .value = value, + .value = (uint8_t *) value, ._value_size = size, }; pb_msg_t msg = { @@ -36,10 +36,10 @@ pb_buf_t pb_send_read_res(uint8_t propid, uint8_t * value, size_t size) { return pb_msg_write(&msg); } -pb_buf_t pb_send_write_req(uint8_t propid, uint8_t * value, size_t size) { +pb_buf_t pb_send_write_req(uint8_t propid, const uint8_t * value, size_t size) { pb_cmd_prop_t cmd = { .propid = propid, - .value = value, + .value = (uint8_t *) value, ._value_size = size, }; pb_msg_t msg = { diff --git a/lib/pbdrv/pb-send.h b/lib/pbdrv/pb-send.h index 679df29..7e21eda 100644 --- a/lib/pbdrv/pb-send.h +++ b/lib/pbdrv/pb-send.h @@ -7,17 +7,126 @@ extern "C" { #endif -void pb_send_reply(pb_msg_t * msg, pb_buf_t * reply); +/** + * \ingroup pbdrv-mod + * \defgroup pb_send Send + * \brief Functions for directly creating serialized message buffers + * \{ + */ +/** + * \brief Utility function for replying to a message + * + * \param msg Message to reply to + * \param reply Data to send as reply + * + * This function uses \c pb_i2c_send() to send \p reply to \p msg->sender. + */ +void pb_send_reply(const pb_msg_t * msg, const pb_buf_t * reply); + +/** + * \brief Create a serialized \ref PB_CMD_PROP "PROP" \ref PB_ACTION_REQ "REQ" + * message + * + * \param propid Property ID to request + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ pb_buf_t pb_send_read_req(uint8_t propid); -pb_buf_t pb_send_read_res(uint8_t propid, uint8_t * value, size_t size); -pb_buf_t pb_send_write_req(uint8_t propid, uint8_t * value, size_t size); +/** + * \brief Create a serialized \ref PB_CMD_PROP "PROP" \ref PB_ACTION_RES "RES" + * message + * + * \param propid Requested property ID + * \param value Pointer to structured data in property + * \param size Size of \p value + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ +pb_buf_t pb_send_read_res(uint8_t propid, const uint8_t * value, size_t size); +/** + * \brief Create a serialized \ref PB_CMD_PROP "PROP" \ref PB_ACTION_SET "SET" + * message + * + * \param propid Property ID to write + * \param value Pointer to data to write to property + * \param size Size of \p value + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ +pb_buf_t pb_send_write_req(uint8_t propid, const uint8_t * value, size_t size); +/** + * \brief Create a serialized \ref PB_CMD_STATE "STATE" \ref PB_ACTION_REQ + * "REQ" message + * + * The current module's state is obtained using \c pb_hook_mod_state_read(). + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ pb_buf_t pb_send_state_req(); +/** + * \brief Create a serialized \ref PB_CMD_STATE "STATE" \ref PB_ACTION_RES + * "RES" message + * + * The current module's state is obtained using \c pb_hook_mod_state_read(). + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ pb_buf_t pb_send_state_res(); +/** + * \brief Create a serialized \ref PB_CMD_STATE "STATE" \ref PB_ACTION_SET + * "SET" message + * + * \param state Requested new state + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ pb_buf_t pb_send_state_set(pb_global_state_t state); +/** + * \brief Create a serialized \ref PB_CMD_MAGIC "MAGIC" \ref PB_ACTION_REQ + * "REQ" message + * + * The magic string is equal to \ref pb_cmd_magic_req. + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ pb_buf_t pb_send_magic_req(); +/** + * \brief Create a serialized \ref PB_CMD_MAGIC "MAGIC" \ref PB_ACTION_RES + * "RES" message + * + * The magic string is equal to \ref pb_cmd_magic_res. + * + * \return Message buffer + * + * \note The buffer returned by this function must be free'd with \c + * pb_buf_free(). + */ pb_buf_t pb_send_magic_res(); +/// \} + #ifdef __cplusplus } #endif diff --git a/lib/pbdrv/pb-types.h b/lib/pbdrv/pb-types.h index dfd5da9..ef3df54 100644 --- a/lib/pbdrv/pb-types.h +++ b/lib/pbdrv/pb-types.h @@ -18,19 +18,19 @@ extern "C" { */ #ifdef __GNUC__ -//! Mark function as weak (allow user to override implementation) #define __weak __attribute__((weak)) #endif #ifndef __weak #error Could not determine weak attribute for current compiler +//! Mark function as weak (allow user to override implementation) #define __weak #endif //! I2C address (10 or 7 bit) typedef uint16_t i2c_addr_t; -//! puzzle bus command types -enum pb_cmd_id { +//! Puzzle bus command types +typedef enum { /** * \brief puzzle module property (\ref pb_route_cmd_prop_req "REQ", \ref * pb_route_cmd_prop_res "RES", \ref pb_route_cmd_prop_set "SET") @@ -60,29 +60,34 @@ enum pb_cmd_id { * unrelated I2C devices. */ PB_CMD_MAGIC, -}; -typedef enum pb_cmd_id pb_cmd_id_t; +} pb_cmd_id_t; -//! puzzle bus command action types -enum pb_action { +//! Puzzle bus command action types +typedef enum { PB_ACTION_REQ, //!< request PB_ACTION_RES, //!< response PB_ACTION_SET, //!< (over)write -}; -typedef enum pb_action pb_action_t; +} pb_action_t; -//! puzzle bus global states -enum pb_global_state { +//! Puzzle module global states +typedef enum { PB_GS_NOINIT, //!< uninitialized (only used by puzzle modules) PB_GS_IDLE, //!< puzzle not started yet PB_GS_PLAYING, //!< puzzle actively being solved PB_GS_SOLVED, //!< puzzle completed -}; -typedef enum pb_global_state pb_global_state_t; +} pb_global_state_t; -//! magic sent from main controller to puzzle module +/** + * \brief Magic sent from main controller to puzzle module (="puzbus") + * + * The size of this array can be obtained by \c sizeof(pb_cmd_magic_req). + */ static const char pb_cmd_magic_req[] = { 0x70, 0x75, 0x7a, 0x62, 0x75, 0x73 }; -//! magic reply from puzzle module back to main controller +/** + * \brief Magic reply from puzzle module back to main controller (="gaming") + * + * The size of this array can be obtained by \c sizeof(pb_cmd_magic_res). + */ static const char pb_cmd_magic_res[] = { 0x67, 0x61, 0x6d, 0x69, 0x6e, 0x67 }; //! puzzle bus message header / container (shared by all commands) -- cgit v1.2.3 From bb63040692c94ffa662b0af7eb14f3c5951aa6e6 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 22 Jun 2024 13:01:42 +0200 Subject: even more doxygen documentation --- lib/pbdrv/drv/arduino/index.dox | 19 ++++++++++ lib/pbdrv/drv/arduino/mod.cpp | 20 ++++++----- lib/pbdrv/drv/index.dox | 32 +++++++++++++++++ lib/pbdrv/ext/freertos/index.dox | 6 ++++ lib/pbdrv/ext/freertos/pb-mem.c | 5 +++ lib/pbdrv/ext/freertos/pb-mod.c | 1 + lib/pbdrv/ext/index.dox | 24 +++++++++++++ lib/pbdrv/ext/stdlib/index.dox | 6 ++++ lib/pbdrv/ext/stdlib/pb-mem.c | 5 +++ lib/pbdrv/index.dox | 64 +++++++++++++++++++++++++-------- lib/pbdrv/pb-mod.h | 13 ++----- lib/pbdrv/pb-msg.h | 3 ++ lib/pbdrv/pb-serial.h | 76 ++++++++++++++++++++++++++++++++++------ main/pbdrv.h | 56 +++++++++++++++++------------ 14 files changed, 262 insertions(+), 68 deletions(-) create mode 100644 lib/pbdrv/drv/arduino/index.dox create mode 100644 lib/pbdrv/drv/index.dox create mode 100644 lib/pbdrv/ext/freertos/index.dox create mode 100644 lib/pbdrv/ext/index.dox create mode 100644 lib/pbdrv/ext/stdlib/index.dox (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/drv/arduino/index.dox b/lib/pbdrv/drv/arduino/index.dox new file mode 100644 index 0000000..4c74222 --- /dev/null +++ b/lib/pbdrv/drv/arduino/index.dox @@ -0,0 +1,19 @@ +// vim:ft=doxygen +/** +\ingroup pb_drv +\defgroup pb_drv_arduino Arduino +\brief Arduino (Arduino-CMake-Toolchain) driver + +This driver is automatically enabled if the variable \c ARDUINO is defined in +your CMakeLists.txt (it is by default when using Arduino-CMake-Toolchain). + +\note This driver automatically includes the +\ref pb_ext_freertos "FreeRTOS extension" for deferring calls to \c +pb_i2c_recv() from the I2C ISR. + +This driver is known to work with the following MCUs: +- ATmega328P (Arduino Uno) +- ATmega2560 (Arduino Mega) + +*/ + diff --git a/lib/pbdrv/drv/arduino/mod.cpp b/lib/pbdrv/drv/arduino/mod.cpp index 9130334..2eef8d5 100644 --- a/lib/pbdrv/drv/arduino/mod.cpp +++ b/lib/pbdrv/drv/arduino/mod.cpp @@ -42,6 +42,7 @@ static void pb_setup() { Wire.onReceive(recv_event); } +/// \ingroup pb_drv_arduino __weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { Wire.beginTransmission((int) addr); Wire.write(buf, sz); @@ -64,7 +65,16 @@ void loop_task() { } } -//! Application entrypoint +/** + * \ingroup pb_drv_arduino + * \brief Application entrypoint + * + * \note I should really be able to use Arduino's initVariant function for + * this, but I can't seem to get it to link properly using the CMake setup in + * this repository. Overriding the main() function seems to work, and the + * USBCON thing in the default Arduino main() function isn't needed because + * puzzle modules are likely not using USB. + */ int main(void) { init(); // call arduino internal setup setup(); // call regular arduino setup @@ -74,11 +84,3 @@ int main(void) { return 0; } -/** - * \note I should really be able to use Arduino's initVariant function for - * this, but I can't seem to get it to link properly using the CMake setup in - * this repository. Overriding the main() function seems to work, and the - * USBCON thing in the default Arduino main() function isn't needed because - * puzzle modules are likely not using USB. - */ - diff --git a/lib/pbdrv/drv/index.dox b/lib/pbdrv/drv/index.dox new file mode 100644 index 0000000..1fe09e2 --- /dev/null +++ b/lib/pbdrv/drv/index.dox @@ -0,0 +1,32 @@ +// vim:ft=doxygen +/** +\ingroup pbdrv-mod +\defgroup pb_drv Drivers +\brief Platform-specific \ref pbdrv-mod implementations + +Like \ref pb_ext "extensions", drivers provide platform-specific +implementations for various functions used in \ref pbdrv-mod. + +Drivers are automatically included based on your build configuration, and you +only need to ensure \c pbdrv-mod is linked with your final executable in order +to use one of the available drivers: + +```cmake +# include pbdrv +add_subdirectory(lib/pbdrv) + +# link pbdrv-mod +target_link_libraries(main pbdrv-mod) + +``` + +If there is no existing driver for your target, you may implement the following +in order to use \ref pbdrv-mod: + +- The \c pb_i2c_recv() function must be **called** for every received I2C + message +- The \c pb_i2c_send() function must be **implemented** using the + platform/device-specific I2C write function + +*/ + diff --git a/lib/pbdrv/ext/freertos/index.dox b/lib/pbdrv/ext/freertos/index.dox new file mode 100644 index 0000000..dfa45ff --- /dev/null +++ b/lib/pbdrv/ext/freertos/index.dox @@ -0,0 +1,6 @@ +// vim:ft=doxygen +/** +\ingroup pb_ext +\defgroup pb_ext_freertos FreeRTOS +\brief FreeRTOS memory management and scheduler-based delay +*/ diff --git a/lib/pbdrv/ext/freertos/pb-mem.c b/lib/pbdrv/ext/freertos/pb-mem.c index b18d79f..6647f05 100644 --- a/lib/pbdrv/ext/freertos/pb-mem.c +++ b/lib/pbdrv/ext/freertos/pb-mem.c @@ -3,24 +3,29 @@ #include "../../pb-mem.h" #include "../../pb-types.h" +/// \ingroup pb_ext_freertos inline void * pb_malloc(size_t sz) { return pvPortMalloc(sz); } +/// \ingroup pb_ext_freertos inline void pb_free(void * ptr) { vPortFree(ptr); } +/// \ingroup pb_ext_freertos __weak inline void * pb_realloc(void * ptr, size_t sz) { return NULL; // shit out of luck (don't use mpack_writer_init_growable) } +/// \ingroup pb_ext_freertos __weak void * pb_memcpy(void * dest, const void * src, size_t sz) { for (size_t offset = 0; offset < sz; offset++) *((char*) dest + offset) = *((char*) src + offset); return dest; } +/// \ingroup pb_ext_freertos __weak int pb_memcmp(const void * a, const void * b, size_t sz) { for (size_t offset = 0; offset < sz; offset++) { int diff = *((char*) a + offset) - *((char*) b + offset); diff --git a/lib/pbdrv/ext/freertos/pb-mod.c b/lib/pbdrv/ext/freertos/pb-mod.c index 75495be..5c0aa36 100644 --- a/lib/pbdrv/ext/freertos/pb-mod.c +++ b/lib/pbdrv/ext/freertos/pb-mod.c @@ -3,6 +3,7 @@ #include "../../pb-types.h" +/// \ingroup pb_ext_freertos __weak void pb_mod_blocking_delay_ms(unsigned long ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } diff --git a/lib/pbdrv/ext/index.dox b/lib/pbdrv/ext/index.dox new file mode 100644 index 0000000..f7d2bc6 --- /dev/null +++ b/lib/pbdrv/ext/index.dox @@ -0,0 +1,24 @@ +// vim:ft=doxygen +/** +\ingroup pbdrv +\ingroup pbdrv-mod +\defgroup pb_ext Extensions +\brief Platform-specific \ref pbdrv implementations + +Extensions provide platform-specific implementations for various functions used +in \ref pbdrv, and allows \ref pbdrv to remain completely portable. Extensions +are used in both \ref pbdrv and \ref pbdrv-mod. + +In order to use an extension, include the appropriate CMake lists file for your +target platform after the \ref pbdrv include: + +```cmake +# include pbdrv +add_subdirectory(lib/pbdrv) + +# use stdlib extension (for use with C standard library) +include(lib/pbdrv/ext/stdlib/include.cmake) + +``` + +*/ diff --git a/lib/pbdrv/ext/stdlib/index.dox b/lib/pbdrv/ext/stdlib/index.dox new file mode 100644 index 0000000..756af1e --- /dev/null +++ b/lib/pbdrv/ext/stdlib/index.dox @@ -0,0 +1,6 @@ +// vim:ft=doxygen +/** +\ingroup pb_ext +\defgroup pb_ext_stdlib stdlib +\brief C stdlib memory management +*/ diff --git a/lib/pbdrv/ext/stdlib/pb-mem.c b/lib/pbdrv/ext/stdlib/pb-mem.c index b260c2c..328efbb 100644 --- a/lib/pbdrv/ext/stdlib/pb-mem.c +++ b/lib/pbdrv/ext/stdlib/pb-mem.c @@ -3,22 +3,27 @@ #include "../../pb-mem.h" +/// \ingroup pb_ext_stdlib inline void * pb_malloc(size_t sz) { return malloc(sz); } +/// \ingroup pb_ext_stdlib inline void pb_free(void * ptr) { free(ptr); } +/// \ingroup pb_ext_stdlib inline void * pb_realloc(void * ptr, size_t sz) { return realloc(ptr, sz); } +/// \ingroup pb_ext_stdlib void * pb_memcpy(void * dest, const void * src, size_t sz) { return memcpy(dest, src, sz); } +/// \ingroup pb_ext_stdlib int pb_memcmp(const void * a, const void * b, size_t sz) { return memcmp(a, b, sz); } diff --git a/lib/pbdrv/index.dox b/lib/pbdrv/index.dox index e4a6c5f..eb0fd63 100644 --- a/lib/pbdrv/index.dox +++ b/lib/pbdrv/index.dox @@ -4,28 +4,62 @@ \defgroup pbdrv pbdrv \brief Standalone puzzle bus driver -pbdrv is a standalone portable static library for handling (i.e. -(de)serialization) of puzzle bus messages. +\ref pbdrv is a standalone portable static library for handling (i.e. +(de)serialization) of puzzle bus messages. \ref pbdrv is meant for use within +applications that handle puzzle bus messages, but are not puzzle modules +themselves. For a complete puzzle module driver, please see \ref pbdrv-mod. -\note Most \c pb_* functions have a weak implementation, which may be -overwritten by a custom implementation. This allows you to use the default -implementation where possible, and only implement extensions required for your -puzzle module. +If you order to use \ref pbdrv, you need to include this folder in your +CMakeLists.txt file, include the \ref pb_ext "extension" for your target +platform, and link the \c pbdrv library with your executable: + +```cmake +# include pbdrv +add_subdirectory(lib/pbdrv) + +# + +# link with executable +target_link_libraries(main pbdrv) +``` -TODO: where to find drivers -TODO: what are extensions -TODO: what to do if there is no driver / extension \defgroup pbdrv-mod pbdrv-mod \brief Puzzle module driver (superset of \ref pbdrv) pbdrv-mod is a superset of \ref pbdrv, and includes functions specific to -puzzle bus modules. pbdrv-mod compiles to an object file instead of a static -library because it may depend on functions that rely on external libraries. -pbdrv-mod is still considered standalone, but requires either using an existing -driver, or (partially) implementing the driver functions. +puzzle bus modules. \ref pbdrv-mod compiles to an object file instead of a +static library because it may depend on functions that rely on external +libraries. \ref pbdrv-mod is still considered standalone, but requires either +using an existing \ref pb_drv "driver", or (partially) implementing the driver +functions. + +Like \ref pbdrv, \ref pbdrv-mod can be used by including this folder in your +CMakeLists.txt file and linking the library with your executable. A notable +difference with \ref pbdrv-mod is that you do not need to include an extension. +\ref pb_ext "Extensions" are still used by \ref pbdrv-mod, but they are +included automatically by the target platform's \ref pb_drv "driver". The +appropriate \ref pb_drv "driver" to load is also automatically detected. + +Example: + +```cmake +# include pbdrv +add_subdirectory(lib/pbdrv) + +# link with executable +target_link_libraries(main pbdrv-mod) +``` + +\note In most cases, the \ref pb_hook "hooks" should be sufficient to realize +extensions or custom behavior not provided by \ref pbdrv-mod. + +\note Most \c pb_* functions have a weak implementation, which may be +overwritten by a custom implementation. This allows you to use the default +implementation where possible, and only implement extensions required for your +puzzle module. -\copydetails pbdrv +\see pbdrv \{ @@ -35,7 +69,7 @@ driver, or (partially) implementing the driver functions. Hooks are functions that allow the user to implement custom behavior (i.e. extend or conditionally replace the default handlers), without needing to completely overwrite the built-in handlers or understand the internals of \ref -pbdrv. +pbdrv-mod. \} diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h index 75fcbec..3869e55 100644 --- a/lib/pbdrv/pb-mod.h +++ b/lib/pbdrv/pb-mod.h @@ -39,20 +39,11 @@ void pb_mod_blocking_delay_ms(unsigned long ms); * \defgroup pb_i2c I2C * \brief I2C send/receive handlers * - * If there is no existing \ref pb_drv "driver" for the microcontroller on - * which you want to use the puzzle bus driver, you may implement the following - * in order to use pbdrv: - * - * - The \c pb_i2c_recv() function must be **called** for every received I2C - * message - * - The \c pb_i2c_send() function must be **implemented** using the - * platform/device-specific I2C write function - * * \{ */ /** - * \brief handle a received message from the I2C bus (puzzle bus) + * \brief Handle a received message from the I2C bus (puzzle bus) * * This function attempts to parse an I2C message as a puzzle bus message, and * calls the appropriate handler for the message if it is considered valid. @@ -67,7 +58,7 @@ void pb_mod_blocking_delay_ms(unsigned long ms); */ void pb_i2c_recv(const uint8_t * buf, size_t sz); /** - * \brief send a message in master-mode on the I2C bus (puzzle bus) + * \brief Send a message in master-mode on the I2C bus (puzzle bus) * * This function sends an I2C message to the address specified by \p i2c_addr. * diff --git a/lib/pbdrv/pb-msg.h b/lib/pbdrv/pb-msg.h index 33b697a..ff5bcde 100644 --- a/lib/pbdrv/pb-msg.h +++ b/lib/pbdrv/pb-msg.h @@ -46,6 +46,9 @@ pb_buf_t pb_msg_write(const pb_msg_t * msg); pb_msg_t * pb_msg_read(const pb_buf_t * buf); /** * \brief Recursively free fields of a \c pb_msg_t + * + * \note The \p msg pointer itself is also free'd by this function. You should + * set it to NULL afterwards to avoid confusion. */ void pb_msg_free(pb_msg_t * msg); diff --git a/lib/pbdrv/pb-serial.h b/lib/pbdrv/pb-serial.h index d3d0007..79f08d7 100644 --- a/lib/pbdrv/pb-serial.h +++ b/lib/pbdrv/pb-serial.h @@ -8,23 +8,77 @@ extern "C" { #endif -#define __pb_cmd(name) \ - pb_ser_r_t pb_ser_r_##name; \ - pb_ser_w_t pb_ser_w_##name; \ - pb_ser_free_t pb_ser_free_##name; +/** + * \ingroup pbdrv + * \ingroup pbdrv-mod + * \defgroup pb_ser Serial + * \internal + * \brief Internal (de)serialization functions using mpack + * + * \{ + */ +/** + * \brief Write (serialize) message fields using mpack + * + * \param writer Pointer to \c mpack_writer_t instance + * \param msg Pointer to message struct to read from + */ typedef void pb_ser_w_t(mpack_writer_t * writer, const pb_msg_t * msg); -pb_ser_w_t pb_ser_w; - +/** + * \brief Read (deserialize) message fields using mpack + * + * \param reader Pointer to \c mpack_reader_t instance + * \param msg Pointer to message struct to write to + */ typedef void pb_ser_r_t(mpack_reader_t * reader, pb_msg_t * msg); -pb_ser_r_t pb_ser_r; - +/** + * \brief Recursively free message struct fields + * + * \param msg Pointer to message struct to free + */ typedef void pb_ser_free_t(pb_msg_t * msg); + +/** + * \brief Write the \ref pb_msg_t header fields and call another function for + * \p msg->cmd. + * \see pb_ser_w_t + */ +pb_ser_w_t pb_ser_w; +/** + * \brief Read the \ref pb_msg_t header fields and call another function for \p + * msg->cmd. + * \see pb_ser_r_t + */ +pb_ser_r_t pb_ser_r; +/** + * \brief Call another function for \p msg->cmd. + * \see pb_ser_free_t + */ pb_ser_free_t pb_ser_free; -__pb_cmd(cmd_prop) -__pb_cmd(cmd_state) -__pb_cmd(cmd_magic) +//! Write the \ref pb_cmd_prop_t fields \see pb_ser_w_t +pb_ser_w_t pb_ser_w_cmd_prop; +//! Read the \ref pb_cmd_prop_t fields \see pb_ser_r_t +pb_ser_r_t pb_ser_r_cmd_prop; +//! Free the \ref pb_cmd_prop_t fields \see pb_ser_free_t +pb_ser_free_t pb_ser_free_cmd_prop; + +//! Write the \ref pb_cmd_state_t fields \see pb_ser_w_t +pb_ser_w_t pb_ser_w_cmd_state; +//! Read the \ref pb_cmd_state_t fields \see pb_ser_r_t +pb_ser_r_t pb_ser_r_cmd_state; +//! Free the \ref pb_cmd_state_t fields \see pb_ser_free_t +pb_ser_free_t pb_ser_free_cmd_state; + +//! Write the \ref pb_cmd_magic_t fields \see pb_ser_w_t +pb_ser_w_t pb_ser_w_cmd_magic; +//! Read the \ref pb_cmd_magic_t fields \see pb_ser_r_t +pb_ser_r_t pb_ser_r_cmd_magic; +//! Free the \ref pb_cmd_magic_t fields \see pb_ser_free_t +pb_ser_free_t pb_ser_free_cmd_magic; + +/// \} #ifdef __cplusplus } diff --git a/main/pbdrv.h b/main/pbdrv.h index a751000..9496aa9 100644 --- a/main/pbdrv.h +++ b/main/pbdrv.h @@ -3,35 +3,41 @@ #include "pb-types.h" /** - * This is the RP2040 puzzle bus driver. This file is no longer inside - * lib/pb//rp2040 as it is tightly coupled to both the pico-sdk and - * freertos functions. I have tried to get FreeRTOS to play nicely with the - * CMake subproject layout, but the pico-sdk and the rp2040 port of freertos - * both rely on CMake's import() functionality, which makes using FreeRTOS in a - * libary context extremely messy. + * \ingroup pb_drv + * \defgroup pb_drv_rp2040 RP2040 + * \brief Raspberry Pi Pico and Pico W driver * - * The workaround implemented in this driver was already kind of messy, and a - * different microcontroller should be used for the main controller instead. + * \note This file is no longer inside `lib/pbdrv/drv/rp2040` as it is tightly + * coupled to both the pico-sdk and FreeRTOS functions. I have tried to get + * FreeRTOS to play nicely with the CMake subproject layout, but the pico-sdk + * and the RP2040 port of FreeRTOS both rely on CMake's import() functionality, + * which makes using FreeRTOS in a libary context extremely messy. + * + * \warning The workaround implemented in this driver was already kind of + * messy, and **a different microcontroller should be used for the main + * controller instead**. Please see the handover document for more details. + * + * \{ */ #ifdef __cplusplus extern "C" { #endif -//! puzzle bus driver setup +//! Puzzle bus driver setup void pb_setup(); /** - * While the RP2040's datasheet claims it supports multi-master configurations - * by implementing bus arbitration, it does not natively support a mode where - * it is configured as a (multi-)master with a slave address, such that it can - * be addressed by other multi-masters. This function includes a hacky - * workaround that teporarily sets the RP2040 to I2C master mode to send a - * message, and then restores it back to slave mode. + * \note While the RP2040's datasheet claims it supports multi-master + * configurations by implementing bus arbitration, it does not natively support + * a mode where it is configured as a (multi-)master with a slave address, such + * that it can be addressed by other multi-masters. This function includes a + * hacky workaround that teporarily sets the RP2040 to I2C master mode to send + * a message, and then restores it back to slave mode. * - * This approach results in some received frames being (partially) dropped in - * the time period between the invocation of this function and the bus becoming - * idle (and the message is sent). + * \warning This approach results in some received frames being (partially) + * dropped in the time period between the invocation of this function and the + * bus becoming idle (and the message is sent). */ void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz); @@ -39,13 +45,19 @@ void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz); * \brief Scan the bus for I2C slaves, and send handshake messages to ACK-ing * slaves. * - * As a result of the RP2040 hardware limitations detailed at the top of this - * file, this function is also implemented in this file, even through it does - * not belong to the puzzle bus driver. In order to not miss any handshake - * responses, the bus should remain busy during the entire scan. + * \note As a result of the RP2040 hardware limitations, this function is also + * implemented in this file, even though it does not belong to the puzzle bus + * driver. + * + * \warning In order to not miss any handshake responses, the bus should remain + * busy during the entire scan. The \c nostop parameter of the \c + * i2c_write_timeout_us() function from the pico-sdk does not seem to keep the + * bus busy. */ void bus_scan(); +/// \} + #ifdef __cplusplus } #endif -- cgit v1.2.3 From ccdcf5001c47820c41e9b962d8498602870289b5 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 22 Jun 2024 18:32:06 +0200 Subject: WIP RP2040 I2C workaround code --- lib/pbdrv/pb-route.c | 3 --- main/config.def.h | 20 ++++++++++++++------ main/i2c.c | 16 ++++++++++++---- main/init.c | 12 ++++++++---- main/pbdrv.c | 36 +++++++++++------------------------- main/pbdrv.h | 15 --------------- 6 files changed, 45 insertions(+), 57 deletions(-) (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/pb-route.c b/lib/pbdrv/pb-route.c index f5c32d6..5a7bd67 100644 --- a/lib/pbdrv/pb-route.c +++ b/lib/pbdrv/pb-route.c @@ -93,9 +93,6 @@ __weak void pb_route_cmd_magic_req(pb_msg_t * msg) { // // return early if magic doesn't match if (pb_memcmp(cmd->magic, pb_cmd_magic_req, sizeof(pb_cmd_magic_req)) != 0) return; - // FIXME: this should be removed (see handover: RP2040 I2C limitations) - pb_mod_blocking_delay_ms(2000); - pb_buf_t buf = pb_send_magic_res(); pb_send_reply(msg, &buf); pb_buf_free(&buf); diff --git a/main/config.def.h b/main/config.def.h index 0dae608..b3be5ed 100644 --- a/main/config.def.h +++ b/main/config.def.h @@ -72,13 +72,21 @@ * \name I2C configuration * \{ */ -#ifndef CFG_SDA_PIN -//! I2C SDA pin -#define CFG_SDA_PIN 16 +#ifndef CFG_SDA0_PIN +//! I2C 0 SDA pin +#define CFG_SDA0_PIN 16 #endif -#ifndef CFG_SCL_PIN -//! I2C SCL pin -#define CFG_SCL_PIN 17 +#ifndef CFG_SCL0_PIN +//! I2C 0 SCL pin +#define CFG_SCL0_PIN 17 +#endif +#ifndef CFG_SDA1_PIN +//! I2C 1 SDA pin +#define CFG_SDA1_PIN 18 +#endif +#ifndef CFG_SCL1_PIN +//! I2C 1 SCL pin +#define CFG_SCL1_PIN 19 #endif /// \} diff --git a/main/i2c.c b/main/i2c.c index 2503560..0f23a13 100644 --- a/main/i2c.c +++ b/main/i2c.c @@ -16,6 +16,18 @@ i2c_addr_t modules[CFG_PB_MOD_MAX]; size_t modules_size = 0; +static void bus_scan() { + pb_buf_t buf = pb_send_magic_req(); + + // check for all 7-bit addresses + uint16_t addr_max = 1 << 7; + for (uint16_t addr = 0x00; addr < addr_max; addr++) { + pb_i2c_send(addr, (uint8_t *) buf.data, buf.size); + } + + pb_buf_free(&buf); +} + static void state_exchange() { for (size_t i = 0; i < modules_size; i++) { pb_buf_t buf = pb_send_state_req(); @@ -28,10 +40,6 @@ void bus_task() { // do a scan of the bus bus_scan(); - // FIXME: this should be removed (see handover: RP2040 I2C limitations) - // wait for 5 seconds until all handshake responses are received - pb_mod_blocking_delay_ms(5e3); - while(1) { // send my state to all puzzle modules state_exchange(); diff --git a/main/init.c b/main/init.c index 6d29d19..d85c94d 100644 --- a/main/init.c +++ b/main/init.c @@ -29,11 +29,15 @@ static void init_wifi() { } static void init_i2c() { - gpio_set_function(CFG_SDA_PIN, GPIO_FUNC_I2C); - gpio_set_function(CFG_SCL_PIN, GPIO_FUNC_I2C); + gpio_set_function(CFG_SDA0_PIN, GPIO_FUNC_I2C); + gpio_set_function(CFG_SCL0_PIN, GPIO_FUNC_I2C); + gpio_set_function(CFG_SDA1_PIN, GPIO_FUNC_I2C); + gpio_set_function(CFG_SCL1_PIN, GPIO_FUNC_I2C); - gpio_pull_up(CFG_SDA_PIN); - gpio_pull_up(CFG_SCL_PIN); + gpio_pull_up(CFG_SDA0_PIN); + gpio_pull_up(CFG_SCL0_PIN); + gpio_pull_up(CFG_SDA1_PIN); + gpio_pull_up(CFG_SCL1_PIN); pb_setup(); } diff --git a/main/pbdrv.c b/main/pbdrv.c index 0624897..6a89253 100644 --- a/main/pbdrv.c +++ b/main/pbdrv.c @@ -14,7 +14,9 @@ #include #include -#define PB_I2C i2c0 +#define PB_I2C_S i2c0 +#define PB_I2C_M i2c1 + #define BUF_SIZE 256 #define MSGS_MAX 4 @@ -32,6 +34,8 @@ static void async_pb_i2c_recv(void * _msg, uint32_t _) { } static void msg_complete(i2c_msg_buf_t * msg) { + return pb_i2c_recv(msg->data, msg->size); + // defer pb_i2c_recv call to FreeRTOS scheduler as pb_i2c_recv takes // too long to return from an ISR xTimerPendFunctionCallFromISR(async_pb_i2c_recv, msg, 0, NULL); @@ -48,7 +52,7 @@ static void recv_event(i2c_inst_t *i2c, i2c_slave_event_t event) { switch (event) { case I2C_SLAVE_RECEIVE: { if (msg->size == BUF_SIZE) return; - msg->data[msg->size++] = i2c_read_byte_raw(PB_I2C); + msg->data[msg->size++] = i2c_read_byte_raw(PB_I2C_S); break; } case I2C_SLAVE_FINISH: { @@ -60,33 +64,15 @@ static void recv_event(i2c_inst_t *i2c, i2c_slave_event_t event) { } void pb_setup() { - i2c_init(PB_I2C, PB_CLOCK_SPEED_HZ); - i2c_slave_init(PB_I2C, PB_MOD_ADDR, &recv_event); + i2c_init(PB_I2C_S, PB_CLOCK_SPEED_HZ); + i2c_init(PB_I2C_M, PB_CLOCK_SPEED_HZ); + + i2c_slave_init(PB_I2C_S, PB_MOD_ADDR, &recv_event); } __weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { - i2c_set_slave_mode(PB_I2C, false, PB_MOD_ADDR); - // false to write stop condition to i2c bus - i2c_write_timeout_us(PB_I2C, addr, buf, sz, false, PB_TIMEOUT_US); - - i2c_set_slave_mode(PB_I2C, true, PB_MOD_ADDR); -} - -void bus_scan() { - i2c_set_slave_mode(PB_I2C, false, PB_MOD_ADDR); - - pb_buf_t buf = pb_send_magic_req(); - - // check for all 7-bit addresses - uint16_t addr_max = 1 << 7; - for (uint16_t addr = 0x00; addr < addr_max; addr++) { - i2c_write_timeout_us(PB_I2C, addr, (uint8_t *) buf.data, buf.size, false, PB_TIMEOUT_US); - } - - pb_buf_free(&buf); - - i2c_set_slave_mode(PB_I2C, true, PB_MOD_ADDR); + i2c_write_timeout_us(PB_I2C_M, addr, buf, sz, false, PB_TIMEOUT_US); } void pb_mod_blocking_delay_ms(unsigned long ms) { diff --git a/main/pbdrv.h b/main/pbdrv.h index 9496aa9..89a4870 100644 --- a/main/pbdrv.h +++ b/main/pbdrv.h @@ -41,21 +41,6 @@ void pb_setup(); */ void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz); -/** - * \brief Scan the bus for I2C slaves, and send handshake messages to ACK-ing - * slaves. - * - * \note As a result of the RP2040 hardware limitations, this function is also - * implemented in this file, even though it does not belong to the puzzle bus - * driver. - * - * \warning In order to not miss any handshake responses, the bus should remain - * busy during the entire scan. The \c nostop parameter of the \c - * i2c_write_timeout_us() function from the pico-sdk does not seem to keep the - * bus busy. - */ -void bus_scan(); - /// \} #ifdef __cplusplus -- cgit v1.2.3 From 648d87ea98ec39d5745d36a0b5c5078cd9491211 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 23 Jun 2024 13:31:49 +0200 Subject: place rp2040 back in pbdrv --- lib/pbdrv/CMakeLists.txt | 6 +-- lib/pbdrv/drv/arduino/include.cmake | 6 +-- lib/pbdrv/drv/arduino/index.dox | 20 +++++---- lib/pbdrv/drv/arduino/mod.cpp | 42 ++++++------------- lib/pbdrv/drv/index.dox | 13 ------ lib/pbdrv/drv/rp2040/include.cmake | 11 +++++ lib/pbdrv/drv/rp2040/index.dox | 31 ++++++++++++++ lib/pbdrv/drv/rp2040/mod.c | 49 ++++++++++++++++++++++ lib/pbdrv/drv/rp2040/pb-mod.h | 21 ++++++++++ lib/pbdrv/ext/freertos/include.cmake | 2 +- lib/pbdrv/index.dox | 13 +++--- main/CMakeLists.txt | 4 +- main/i2c.c | 1 - main/init.c | 2 +- main/mod.c | 7 ++++ main/pbdrv.c | 81 ------------------------------------ main/pbdrv.h | 49 ---------------------- puzzle/dummy/CMakeLists.txt | 3 +- 18 files changed, 157 insertions(+), 204 deletions(-) create mode 100644 lib/pbdrv/drv/rp2040/include.cmake create mode 100644 lib/pbdrv/drv/rp2040/index.dox create mode 100644 lib/pbdrv/drv/rp2040/mod.c create mode 100644 lib/pbdrv/drv/rp2040/pb-mod.h delete mode 100644 main/pbdrv.c delete mode 100644 main/pbdrv.h (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/CMakeLists.txt b/lib/pbdrv/CMakeLists.txt index 998ed4d..903a3b4 100644 --- a/lib/pbdrv/CMakeLists.txt +++ b/lib/pbdrv/CMakeLists.txt @@ -23,7 +23,7 @@ add_library(pbdrv STATIC pb-buf.c ) target_include_directories(pbdrv SYSTEM INTERFACE .) -target_link_libraries(pbdrv mpack) +target_link_libraries(pbdrv PRIVATE mpack) # puzzle bus *module* specific code add_library(pbdrv-mod OBJECT @@ -32,9 +32,9 @@ add_library(pbdrv-mod OBJECT pb-route.c ) target_include_directories(pbdrv-mod SYSTEM INTERFACE .) -target_link_libraries(pbdrv-mod pbdrv) +target_link_libraries(pbdrv-mod PUBLIC pbdrv) # puzzle bus drivers include(drv/arduino/include.cmake) -# include(drv/rp2040/include.cmake) # please see /main/pbdrv.h +include(drv/rp2040/include.cmake) diff --git a/lib/pbdrv/drv/arduino/include.cmake b/lib/pbdrv/drv/arduino/include.cmake index 1e2ff08..520bfc1 100644 --- a/lib/pbdrv/drv/arduino/include.cmake +++ b/lib/pbdrv/drv/arduino/include.cmake @@ -3,9 +3,5 @@ if(NOT DEFINED ARDUINO) endif() target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/mod.cpp") -target_link_arduino_libraries(pbdrv-mod core Wire) - -# freertos is used to defer the handling of i2c messages outside the receive -# interrupt service routine -include("${CMAKE_CURRENT_LIST_DIR}/../../ext/freertos/include.cmake") +target_link_arduino_libraries(pbdrv-mod PRIVATE core Wire) diff --git a/lib/pbdrv/drv/arduino/index.dox b/lib/pbdrv/drv/arduino/index.dox index 4c74222..1856918 100644 --- a/lib/pbdrv/drv/arduino/index.dox +++ b/lib/pbdrv/drv/arduino/index.dox @@ -2,18 +2,22 @@ /** \ingroup pb_drv \defgroup pb_drv_arduino Arduino -\brief Arduino (Arduino-CMake-Toolchain) driver - -This driver is automatically enabled if the variable \c ARDUINO is defined in -your CMakeLists.txt (it is by default when using Arduino-CMake-Toolchain). - -\note This driver automatically includes the -\ref pb_ext_freertos "FreeRTOS extension" for deferring calls to \c -pb_i2c_recv() from the I2C ISR. +\brief Arduino ATmega (w/ Arduino-CMake-Toolchain) driver This driver is known to work with the following MCUs: - ATmega328P (Arduino Uno) - ATmega2560 (Arduino Mega) +\par Usage +- Link the \c pbdrv-mod library with your main executable +- Load an \ref pb_ext "extension" + +\note This driver is automatically enabled if the variable \c ARDUINO is +defined in your CMakeLists.txt (it is by default when using +Arduino-CMake-Toolchain). + +A complete example for this driver is available in the \ref puzzle/dummy +folder. + */ diff --git a/lib/pbdrv/drv/arduino/mod.cpp b/lib/pbdrv/drv/arduino/mod.cpp index 2eef8d5..d2a6402 100644 --- a/lib/pbdrv/drv/arduino/mod.cpp +++ b/lib/pbdrv/drv/arduino/mod.cpp @@ -6,32 +6,17 @@ #include #include -#include -#include -#include - #include "../../pb.h" #include "../../pb-mod.h" #include "../../pb-types.h" -#include "../../pb-buf.h" -#include "../../pb-mem.h" - -static void async_pb_i2c_recv(void * _msg, uint32_t _) { - pb_buf_t * msg = (pb_buf_t *) _msg; - pb_i2c_recv((uint8_t *) msg->data, msg->size); - pb_buf_free(msg); - pb_free(msg); -} static void recv_event(int bytes) { - pb_buf_t * msg = (pb_buf_t *) pb_malloc(sizeof(pb_buf_t)); - msg->data = (char *) pb_malloc(bytes); - msg->size = 0; + uint8_t data[bytes]; + size_t sz = 0; while (Wire.available()) - msg->data[msg->size++] = Wire.read(); + data[sz++] = Wire.read(); - // defer pb_i2c_recv call - xTimerPendFunctionCallFromISR(async_pb_i2c_recv, msg, 0, NULL); + pb_i2c_recv(data, sz); } static void pb_setup() { @@ -42,7 +27,6 @@ static void pb_setup() { Wire.onReceive(recv_event); } -/// \ingroup pb_drv_arduino __weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { Wire.beginTransmission((int) addr); Wire.write(buf, sz); @@ -57,18 +41,14 @@ extern void loop(void); //! Arduino internal initialization void init(void); -//! FreeRTOS loop task -void loop_task() { - for(;;) { - loop(); - if (serialEventRun) serialEventRun(); - } -} - /** * \ingroup pb_drv_arduino * \brief Application entrypoint * + * This function overrides the default (weak) implementation of the \c main() + * function in the Arduino framework. No additional setup is required to use + * this driver. + * * \note I should really be able to use Arduino's initVariant function for * this, but I can't seem to get it to link properly using the CMake setup in * this repository. Overriding the main() function seems to work, and the @@ -79,8 +59,10 @@ int main(void) { init(); // call arduino internal setup setup(); // call regular arduino setup pb_setup(); // call pbdrv-mod setup - xTaskCreate((TaskFunction_t) loop_task, "loop", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); - vTaskStartScheduler(); // start freertos scheduler + for(;;) { + loop(); + if (serialEventRun) serialEventRun(); + } return 0; } diff --git a/lib/pbdrv/drv/index.dox b/lib/pbdrv/drv/index.dox index 1fe09e2..89b9247 100644 --- a/lib/pbdrv/drv/index.dox +++ b/lib/pbdrv/drv/index.dox @@ -7,19 +7,6 @@ Like \ref pb_ext "extensions", drivers provide platform-specific implementations for various functions used in \ref pbdrv-mod. -Drivers are automatically included based on your build configuration, and you -only need to ensure \c pbdrv-mod is linked with your final executable in order -to use one of the available drivers: - -```cmake -# include pbdrv -add_subdirectory(lib/pbdrv) - -# link pbdrv-mod -target_link_libraries(main pbdrv-mod) - -``` - If there is no existing driver for your target, you may implement the following in order to use \ref pbdrv-mod: diff --git a/lib/pbdrv/drv/rp2040/include.cmake b/lib/pbdrv/drv/rp2040/include.cmake new file mode 100644 index 0000000..6d25e96 --- /dev/null +++ b/lib/pbdrv/drv/rp2040/include.cmake @@ -0,0 +1,11 @@ +if(NOT PICO_PLATFORM STREQUAL "rp2040") + return() +endif() + +target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/mod.c") +target_link_libraries(pbdrv-mod PRIVATE hardware_i2c_headers pico_i2c_slave_headers) +target_include_directories(pbdrv-mod INTERFACE "${CMAKE_CURRENT_LIST_DIR}") + +# pico-stdlib is compatible with C stdlib +include("${CMAKE_CURRENT_LIST_DIR}/../../ext/stdlib/include.cmake") + diff --git a/lib/pbdrv/drv/rp2040/index.dox b/lib/pbdrv/drv/rp2040/index.dox new file mode 100644 index 0000000..eb01d15 --- /dev/null +++ b/lib/pbdrv/drv/rp2040/index.dox @@ -0,0 +1,31 @@ +// vim:ft=doxygen +/** +\ingroup pb_drv +\defgroup pb_drv_rp2040 RP2040 +\brief Raspberry Pi Pico and Pico W driver + +This driver is known to work with the following MCUs: +- Raspberry Pi Pico W + +\note While the RP2040's datasheet claims it supports multi-master +configurations by implementing bus arbitration, it does not natively support +a mode where it is configured as a (multi-)master with a slave address, such +that it can be addressed by other multi-masters. + +\note This driver includes a workaround that uses both I2C peripherals on the +RP2040. To use this driver, make sure you initialize both I2C peripherals, and +connect their respective SDA and SCL pins to the same I2C bus. + +\par Usage +- Link the \c pbdrv-mod library with your main executable +- Call the \c pb_setup() function (from \c ) during setup + +\note This driver is automatically enabled if the variable \c PICO_PLATFORM is +equal to \c "rp2040" in your CMakeLists.txt (it is by default when using the +pico-sdk CMake library). + +\note This driver automatically includes the \ref pb_ext_stdlib +"stdlib extension" as the pico-sdk implements the C standard library. + +*/ + diff --git a/lib/pbdrv/drv/rp2040/mod.c b/lib/pbdrv/drv/rp2040/mod.c new file mode 100644 index 0000000..1535da9 --- /dev/null +++ b/lib/pbdrv/drv/rp2040/mod.c @@ -0,0 +1,49 @@ +#include "pb.h" + +#include "pb.h" +#include "pb-types.h" +#include "pb-mod.h" +#include "pb-send.h" +#include "pb-buf.h" + +#include +#include +#include + +#define PB_I2C_S i2c0 +#define PB_I2C_M i2c1 + +#define BUF_SIZE 256 + +uint8_t i2c_msg_buf[BUF_SIZE]; +size_t i2c_msg_buf_sz = 0; + +// This function is called from the I2C ISR +static void recv_event(i2c_inst_t *i2c, i2c_slave_event_t event) { + switch (event) { + case I2C_SLAVE_RECEIVE: { + if (i2c_msg_buf_sz == BUF_SIZE) return; + i2c_msg_buf[i2c_msg_buf_sz++] = i2c_read_byte_raw(PB_I2C_S); + break; + } + case I2C_SLAVE_FINISH: { + pb_i2c_recv(i2c_msg_buf, i2c_msg_buf_sz); + i2c_msg_buf_sz = 0; + break; + } + default: break; + } +} + +void pb_setup() { + i2c_init(PB_I2C_S, PB_CLOCK_SPEED_HZ); + i2c_init(PB_I2C_M, PB_CLOCK_SPEED_HZ); + + i2c_slave_init(PB_I2C_S, PB_MOD_ADDR, &recv_event); +} + +__weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { + // false to write stop condition to i2c bus + i2c_write_timeout_us(PB_I2C_M, addr, buf, sz, false, PB_TIMEOUT_US); +} + diff --git a/lib/pbdrv/drv/rp2040/pb-mod.h b/lib/pbdrv/drv/rp2040/pb-mod.h new file mode 100644 index 0000000..96edc70 --- /dev/null +++ b/lib/pbdrv/drv/rp2040/pb-mod.h @@ -0,0 +1,21 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \ingroup pb_drv_rp2040 + * \brief Puzzle bus driver setup + * + * This function must be called from a setup/init function to initialize the + * I2C peripherals used by this driver. + */ +void pb_setup(); + +#ifdef __cplusplus +} +#endif + +#include "../../pb-mod.h" + diff --git a/lib/pbdrv/ext/freertos/include.cmake b/lib/pbdrv/ext/freertos/include.cmake index e7ab7fd..70ed090 100644 --- a/lib/pbdrv/ext/freertos/include.cmake +++ b/lib/pbdrv/ext/freertos/include.cmake @@ -1,5 +1,5 @@ target_sources(pbdrv PRIVATE "${CMAKE_CURRENT_LIST_DIR}/pb-mem.c") -target_link_libraries(pbdrv +target_link_libraries(pbdrv PUBLIC freertos_kernel freertos_kernel_include freertos_config diff --git a/lib/pbdrv/index.dox b/lib/pbdrv/index.dox index eb0fd63..8ddcb6a 100644 --- a/lib/pbdrv/index.dox +++ b/lib/pbdrv/index.dox @@ -11,8 +11,9 @@ themselves. For a complete puzzle module driver, please see \ref pbdrv-mod. If you order to use \ref pbdrv, you need to include this folder in your CMakeLists.txt file, include the \ref pb_ext "extension" for your target -platform, and link the \c pbdrv library with your executable: +platform, and link the \c pbdrv library with your executable. +\par Example ```cmake # include pbdrv add_subdirectory(lib/pbdrv) @@ -35,14 +36,10 @@ using an existing \ref pb_drv "driver", or (partially) implementing the driver functions. Like \ref pbdrv, \ref pbdrv-mod can be used by including this folder in your -CMakeLists.txt file and linking the library with your executable. A notable -difference with \ref pbdrv-mod is that you do not need to include an extension. -\ref pb_ext "Extensions" are still used by \ref pbdrv-mod, but they are -included automatically by the target platform's \ref pb_drv "driver". The -appropriate \ref pb_drv "driver" to load is also automatically detected. - -Example: +CMakeLists.txt file and linking the library with your executable. Please see +the \ref pb_drv "drivers" page for target-specific usage instructions. +\par Example ```cmake # include pbdrv add_subdirectory(lib/pbdrv) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index d6cbf33..76b1244 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -10,7 +10,6 @@ include(lib/FreeRTOS-Kernel/portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_impor add_subdirectory(lib/mpack) add_subdirectory(lib/i2ctcp) add_subdirectory(lib/pbdrv) -include(lib/pbdrv/ext/stdlib/include.cmake) # pico-stdlib is compatible with C stdlib project(puzzlebox_main C CXX ASM) @@ -24,7 +23,6 @@ add_executable(main mod.c tasks.c blink.c - pbdrv.c ) pico_enable_stdio_usb(main 1) @@ -34,7 +32,7 @@ pico_add_extra_outputs(main) # include_directories(lib/pico-sdk/lib/lwip/contrib/ports/freertos/include) target_include_directories(main PRIVATE .) -target_link_libraries(main +target_link_libraries(main PUBLIC pico_cyw43_arch_lwip_sys_freertos pico_stdlib pico_i2c_slave diff --git a/main/i2c.c b/main/i2c.c index 0f23a13..db859d8 100644 --- a/main/i2c.c +++ b/main/i2c.c @@ -8,7 +8,6 @@ #include "i2c.h" #include "pb-mod.h" -#include "pbdrv.h" #include "config.h" #include "pb-buf.h" #include "pb-send.h" diff --git a/main/init.c b/main/init.c index d85c94d..25fa5e3 100644 --- a/main/init.c +++ b/main/init.c @@ -7,7 +7,7 @@ #include "config.h" #include "init.h" #include "tasks.h" -#include "pbdrv.h" +#include "pb-mod.h" static void init_stdio() { stdio_init_all(); diff --git a/main/mod.c b/main/mod.c index 11a1bb7..ac9107d 100644 --- a/main/mod.c +++ b/main/mod.c @@ -1,6 +1,13 @@ +#include +#include + #include "pb.h" #include "pb-mod.h" const char * PB_MOD_NAME = "main controller"; const i2c_addr_t PB_MOD_ADDR = PB_ADDR_MOD_MAIN; +void pb_mod_blocking_delay_ms(unsigned long ms) { + vTaskDelay(ms / portTICK_PERIOD_MS); +} + diff --git a/main/pbdrv.c b/main/pbdrv.c deleted file mode 100644 index 6a89253..0000000 --- a/main/pbdrv.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "pb.h" - -#include "pb.h" -#include "pb-types.h" -#include "pb-mod.h" -#include "pb-send.h" - -#include -#include -#include -#include - -#include -#include -#include - -#define PB_I2C_S i2c0 -#define PB_I2C_M i2c1 - -#define BUF_SIZE 256 -#define MSGS_MAX 4 - -typedef struct { - uint8_t data[BUF_SIZE]; - size_t size; -} i2c_msg_buf_t; - -static i2c_msg_buf_t msgs[MSGS_MAX]; -static size_t msg_index = 0; - -static void async_pb_i2c_recv(void * _msg, uint32_t _) { - i2c_msg_buf_t * msg = _msg; - pb_i2c_recv(msg->data, msg->size); -} - -static void msg_complete(i2c_msg_buf_t * msg) { - return pb_i2c_recv(msg->data, msg->size); - - // defer pb_i2c_recv call to FreeRTOS scheduler as pb_i2c_recv takes - // too long to return from an ISR - xTimerPendFunctionCallFromISR(async_pb_i2c_recv, msg, 0, NULL); - - // prepare next message for use - msg_index = (msg_index + 1) % MSGS_MAX; - msgs[msg_index].size = 0; -} - -// This function is called from the I2C ISR -static void recv_event(i2c_inst_t *i2c, i2c_slave_event_t event) { - i2c_msg_buf_t * msg = &msgs[msg_index]; - - switch (event) { - case I2C_SLAVE_RECEIVE: { - if (msg->size == BUF_SIZE) return; - msg->data[msg->size++] = i2c_read_byte_raw(PB_I2C_S); - break; - } - case I2C_SLAVE_FINISH: { - msg_complete(msg); - break; - } - default: break; - } -} - -void pb_setup() { - i2c_init(PB_I2C_S, PB_CLOCK_SPEED_HZ); - i2c_init(PB_I2C_M, PB_CLOCK_SPEED_HZ); - - i2c_slave_init(PB_I2C_S, PB_MOD_ADDR, &recv_event); -} - -__weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { - // false to write stop condition to i2c bus - i2c_write_timeout_us(PB_I2C_M, addr, buf, sz, false, PB_TIMEOUT_US); -} - -void pb_mod_blocking_delay_ms(unsigned long ms) { - vTaskDelay(ms / portTICK_PERIOD_MS); -} - diff --git a/main/pbdrv.h b/main/pbdrv.h deleted file mode 100644 index 89a4870..0000000 --- a/main/pbdrv.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "pb-types.h" - -/** - * \ingroup pb_drv - * \defgroup pb_drv_rp2040 RP2040 - * \brief Raspberry Pi Pico and Pico W driver - * - * \note This file is no longer inside `lib/pbdrv/drv/rp2040` as it is tightly - * coupled to both the pico-sdk and FreeRTOS functions. I have tried to get - * FreeRTOS to play nicely with the CMake subproject layout, but the pico-sdk - * and the RP2040 port of FreeRTOS both rely on CMake's import() functionality, - * which makes using FreeRTOS in a libary context extremely messy. - * - * \warning The workaround implemented in this driver was already kind of - * messy, and **a different microcontroller should be used for the main - * controller instead**. Please see the handover document for more details. - * - * \{ - */ - -#ifdef __cplusplus -extern "C" { -#endif - -//! Puzzle bus driver setup -void pb_setup(); - -/** - * \note While the RP2040's datasheet claims it supports multi-master - * configurations by implementing bus arbitration, it does not natively support - * a mode where it is configured as a (multi-)master with a slave address, such - * that it can be addressed by other multi-masters. This function includes a - * hacky workaround that teporarily sets the RP2040 to I2C master mode to send - * a message, and then restores it back to slave mode. - * - * \warning This approach results in some received frames being (partially) - * dropped in the time period between the invocation of this function and the - * bus becoming idle (and the message is sent). - */ -void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz); - -/// \} - -#ifdef __cplusplus -} -#endif - diff --git a/puzzle/dummy/CMakeLists.txt b/puzzle/dummy/CMakeLists.txt index c2633a1..7edce2b 100644 --- a/puzzle/dummy/CMakeLists.txt +++ b/puzzle/dummy/CMakeLists.txt @@ -14,7 +14,7 @@ set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/lib/Arduino-CMake-Toolchain/Arduino set(ARDUINO_BOARD "Arduino Uno [avr.uno]") # set(ARDUINO_BOARD "Arduino Mega or Mega 2560 [avr.mega]") -# freertos +# freertos (used for memory management only) add_library(freertos_config INTERFACE) target_include_directories(freertos_config SYSTEM INTERFACE .) set(FREERTOS_PORT GCC_ATMEGA) @@ -28,6 +28,7 @@ set(FREERTOS_HEAP 4) project(pb_mod_dummy C CXX) add_subdirectory(lib/pbdrv) +include(lib/pbdrv/ext/freertos/include.cmake) add_subdirectory(lib/FreeRTOS-Kernel) add_executable(main -- cgit v1.2.3 From 46eb2370e53c3a1c7eb593ca99c07944c81ec695 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 23 Jun 2024 13:37:17 +0200 Subject: remove delay function from pbdrv --- lib/pbdrv/ext/freertos/include.cmake | 2 -- lib/pbdrv/ext/freertos/index.dox | 2 +- lib/pbdrv/ext/freertos/pb-mod.c | 10 ---------- lib/pbdrv/pb-mod.h | 8 -------- main/i2c.c | 2 +- main/mod.c | 4 ---- 6 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 lib/pbdrv/ext/freertos/pb-mod.c (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/ext/freertos/include.cmake b/lib/pbdrv/ext/freertos/include.cmake index 70ed090..e205c8f 100644 --- a/lib/pbdrv/ext/freertos/include.cmake +++ b/lib/pbdrv/ext/freertos/include.cmake @@ -5,5 +5,3 @@ target_link_libraries(pbdrv PUBLIC freertos_config ) -target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/pb-mod.c") - diff --git a/lib/pbdrv/ext/freertos/index.dox b/lib/pbdrv/ext/freertos/index.dox index dfa45ff..7d72918 100644 --- a/lib/pbdrv/ext/freertos/index.dox +++ b/lib/pbdrv/ext/freertos/index.dox @@ -2,5 +2,5 @@ /** \ingroup pb_ext \defgroup pb_ext_freertos FreeRTOS -\brief FreeRTOS memory management and scheduler-based delay +\brief FreeRTOS memory management */ diff --git a/lib/pbdrv/ext/freertos/pb-mod.c b/lib/pbdrv/ext/freertos/pb-mod.c deleted file mode 100644 index 5c0aa36..0000000 --- a/lib/pbdrv/ext/freertos/pb-mod.c +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -#include "../../pb-types.h" - -/// \ingroup pb_ext_freertos -__weak void pb_mod_blocking_delay_ms(unsigned long ms) { - vTaskDelay(ms / portTICK_PERIOD_MS); -} - diff --git a/lib/pbdrv/pb-mod.h b/lib/pbdrv/pb-mod.h index 3869e55..91e1d1f 100644 --- a/lib/pbdrv/pb-mod.h +++ b/lib/pbdrv/pb-mod.h @@ -24,14 +24,6 @@ extern const char * PB_MOD_NAME; */ extern const i2c_addr_t PB_MOD_ADDR; -/** - * \brief Platform-specific blocking delay function - * - * FIXME: this entire function should be removed (see handover: RP2040 I2C - * limitations) - */ -void pb_mod_blocking_delay_ms(unsigned long ms); - /// \} /** diff --git a/main/i2c.c b/main/i2c.c index db859d8..93b64db 100644 --- a/main/i2c.c +++ b/main/i2c.c @@ -44,7 +44,7 @@ void bus_task() { state_exchange(); // wait 1 second - pb_mod_blocking_delay_ms(1e3); + vTaskDelay(1e3 / portTICK_PERIOD_MS); } } diff --git a/main/mod.c b/main/mod.c index ac9107d..8650861 100644 --- a/main/mod.c +++ b/main/mod.c @@ -7,7 +7,3 @@ const char * PB_MOD_NAME = "main controller"; const i2c_addr_t PB_MOD_ADDR = PB_ADDR_MOD_MAIN; -void pb_mod_blocking_delay_ms(unsigned long ms) { - vTaskDelay(ms / portTICK_PERIOD_MS); -} - -- cgit v1.2.3 From bfa85b6c313a725afe98bbe6ccb60978de3187a9 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 23 Jun 2024 13:54:02 +0200 Subject: add cross-reference for overwritten pbdrv handlers in main controller software --- lib/pbdrv/pb-route.h | 4 +++- main/i2c.c | 11 +++++++++++ main/i2c.h | 1 + main/index.dox | 7 +++++++ 4 files changed, 22 insertions(+), 1 deletion(-) (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/pb-route.h b/lib/pbdrv/pb-route.h index b80e4ec..2a28c0b 100644 --- a/lib/pbdrv/pb-route.h +++ b/lib/pbdrv/pb-route.h @@ -98,6 +98,7 @@ void pb_route_cmd_state_req(pb_msg_t * msg); * controller handles this type of command. */ void pb_route_cmd_state_res(pb_msg_t * msg); +// TODO: add link to pb_route_cmd_state_res handler in main/i2c.c /** * \brief Handle a \ref PB_CMD_STATE "STATE" message with action \ref * PB_ACTION_SET "SET" @@ -122,7 +123,8 @@ void pb_route_cmd_magic_req(pb_msg_t * msg); * PB_ACTION_RES "RES" * * The default implementation of this function is empty, as only the main - * controller handles this type of command. + * controller handles this type of command. (\ref main_route_cmd_magic_res + * "link") */ void pb_route_cmd_magic_res(pb_msg_t * msg); diff --git a/main/i2c.c b/main/i2c.c index 2503560..f366793 100644 --- a/main/i2c.c +++ b/main/i2c.c @@ -41,6 +41,17 @@ void bus_task() { } } +/** + * \ingroup main_pb_override + * \anchor main_route_cmd_magic_res + * + * This function registers the I2C address of the puzzle module that replied to + * the \c MAGIC \c REQ command into a list of "known puzzle modules", which are + * then periodically updated during gameplay. + * + * \note Up to \ref CFG_PB_MOD_MAX puzzle modules can be registered + * simultaniously. + */ void pb_route_cmd_magic_res(pb_msg_t * msg) { if (modules_size == CFG_PB_MOD_MAX) return; modules[modules_size++] = msg->sender; diff --git a/main/i2c.h b/main/i2c.h index 107a04d..27c0b02 100644 --- a/main/i2c.h +++ b/main/i2c.h @@ -15,3 +15,4 @@ void bus_task(); /// \} + diff --git a/main/index.dox b/main/index.dox index aa2d07a..0fee58a 100644 --- a/main/index.dox +++ b/main/index.dox @@ -4,3 +4,10 @@ \defgroup main_tasks tasks \brief Tasks */ + +/** +\ingroup main +\defgroup main_pb_override overrides +\brief Override functions from \ref pbdrv-mod +*/ + -- cgit v1.2.3 From 07e49bbbd3790a95511d68b94d47bf107a5e24a1 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Mon, 24 Jun 2024 14:04:06 +0200 Subject: re-implement deferred I2C handlers for the Arduino pbdrv drv --- lib/pbdrv/drv/arduino/include.cmake | 4 +++ lib/pbdrv/drv/arduino/index.dox | 4 ++- lib/pbdrv/drv/arduino/mod.cpp | 51 ++++++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 13 deletions(-) (limited to 'lib/pbdrv') diff --git a/lib/pbdrv/drv/arduino/include.cmake b/lib/pbdrv/drv/arduino/include.cmake index 520bfc1..5ec1124 100644 --- a/lib/pbdrv/drv/arduino/include.cmake +++ b/lib/pbdrv/drv/arduino/include.cmake @@ -5,3 +5,7 @@ endif() target_sources(pbdrv-mod PRIVATE "${CMAKE_CURRENT_LIST_DIR}/mod.cpp") target_link_arduino_libraries(pbdrv-mod PRIVATE core Wire) +# freertos is used to defer the handling of i2c messages outside the receive +# interrupt service routine +include("${CMAKE_CURRENT_LIST_DIR}/../../ext/freertos/include.cmake") + diff --git a/lib/pbdrv/drv/arduino/index.dox b/lib/pbdrv/drv/arduino/index.dox index 1856918..03510dd 100644 --- a/lib/pbdrv/drv/arduino/index.dox +++ b/lib/pbdrv/drv/arduino/index.dox @@ -10,12 +10,14 @@ This driver is known to work with the following MCUs: \par Usage - Link the \c pbdrv-mod library with your main executable -- Load an \ref pb_ext "extension" \note This driver is automatically enabled if the variable \c ARDUINO is defined in your CMakeLists.txt (it is by default when using Arduino-CMake-Toolchain). +\note This driver automatically includes the \ref pb_ext_freertos +"FreeRTOS extension" for deferring calls to \c pb_i2c_recv() from the I2C ISR. + A complete example for this driver is available in the \ref puzzle/dummy folder. diff --git a/lib/pbdrv/drv/arduino/mod.cpp b/lib/pbdrv/drv/arduino/mod.cpp index d2a6402..c381077 100644 --- a/lib/pbdrv/drv/arduino/mod.cpp +++ b/lib/pbdrv/drv/arduino/mod.cpp @@ -1,22 +1,33 @@ -#ifndef ARDUINO -#error This driver only works on the Arduino platform! -#endif - #include #include #include +#include +#include +#include + #include "../../pb.h" #include "../../pb-mod.h" #include "../../pb-types.h" +#include "../../pb-buf.h" +#include "../../pb-mem.h" + +static void async_pb_i2c_recv(void * _msg, uint32_t _) { + pb_buf_t * msg = (pb_buf_t *) _msg; + pb_i2c_recv((uint8_t *) msg->data, msg->size); + pb_buf_free(msg); + pb_free(msg); +} static void recv_event(int bytes) { - uint8_t data[bytes]; - size_t sz = 0; + pb_buf_t * msg = (pb_buf_t *) pb_malloc(sizeof(pb_buf_t)); + msg->data = (char *) pb_malloc(bytes); + msg->size = 0; while (Wire.available()) - data[sz++] = Wire.read(); + msg->data[msg->size++] = Wire.read(); - pb_i2c_recv(data, sz); + // defer pb_i2c_recv call + xTimerPendFunctionCallFromISR(async_pb_i2c_recv, msg, 0, NULL); } static void pb_setup() { @@ -27,7 +38,17 @@ static void pb_setup() { Wire.onReceive(recv_event); } +/** + * \ingroup pb_drv_arduino + * \warning This function includes a hard-coded 10ms delay before sending. This + * is to work around a weird issue where the Arduino pulls both SDA and SCL low + * while attempting to initiate an I2C transmission. We were able to verify + * that the Arduino correctly handles bus arbitration under a test scenario + * with 2 Uno's, but ran into issues while integrating the Arduino's with the + * RP2040. + */ __weak void pb_i2c_send(i2c_addr_t addr, const uint8_t * buf, size_t sz) { + vTaskDelay(10 / portTICK_PERIOD_MS); // prevent bus collisions Wire.beginTransmission((int) addr); Wire.write(buf, sz); Wire.endTransmission(true); @@ -41,6 +62,14 @@ extern void loop(void); //! Arduino internal initialization void init(void); +//! FreeRTOS loop task +void loop_task() { + for(;;) { + loop(); + if (serialEventRun) serialEventRun(); + } +} + /** * \ingroup pb_drv_arduino * \brief Application entrypoint @@ -59,10 +88,8 @@ int main(void) { init(); // call arduino internal setup setup(); // call regular arduino setup pb_setup(); // call pbdrv-mod setup - for(;;) { - loop(); - if (serialEventRun) serialEventRun(); - } + xTaskCreate((TaskFunction_t) loop_task, "loop", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); + vTaskStartScheduler(); // start freertos scheduler return 0; } -- cgit v1.2.3