diff --git a/TinyFrame/TF_Config.h b/TinyFrame/TF_Config.h index fb9f757..f66d408 100644 --- a/TinyFrame/TF_Config.h +++ b/TinyFrame/TF_Config.h @@ -5,6 +5,7 @@ #ifndef TF_CONFIG_H #define TF_CONFIG_H +#include "platform.h" #include //#include // when using with esphttpd @@ -65,7 +66,7 @@ typedef uint8_t TF_COUNT; // Timeout for receiving & parsing a frame // ticks = number of calls to TF_Tick() -#define TF_PARSER_TIMEOUT_TICKS 10 +#define TF_PARSER_TIMEOUT_TICKS 250 //------------------------- End of user config ------------------------------ diff --git a/TinyFrame/TF_Integration.c b/TinyFrame/TF_Integration.c index c858097..4f38df7 100644 --- a/TinyFrame/TF_Integration.c +++ b/TinyFrame/TF_Integration.c @@ -7,6 +7,7 @@ #include "platform.h" #include "task_main.h" +#include "utils/hexdump.h" #include "USB/usbd_cdc_if.h" #include "TinyFrame.h" @@ -20,9 +21,11 @@ void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len) int32_t total = (int32_t) len; while (total > 0) { assert_param(osOK == osSemaphoreWait(semVcomTxReadyHandle, 5000)); - assert_param(USBD_OK == CDC_Transmit_FS((uint8_t *) buff, (uint16_t) MIN(total, CHUNK))); - buff += CHUNK; - total -= CHUNK; + uint16_t chunksize = (uint16_t) MIN(total, CHUNK); + assert_param(USBD_OK == CDC_Transmit_FS((uint8_t *) buff, chunksize)); + + buff += chunksize; + total -= chunksize; } } diff --git a/TinyFrame/TinyFrame.c b/TinyFrame/TinyFrame.c index 4ad41e7..bce258c 100644 --- a/TinyFrame/TinyFrame.c +++ b/TinyFrame/TinyFrame.c @@ -372,6 +372,12 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf) if (res == TF_RENEW) { renew_id_listener(ilst); } + else if (res == TF_CLOSE) { + // Set userdata to NULL to avoid calling user for cleanup + ilst->userdata = NULL; + ilst->userdata2 = NULL; + cleanup_id_listener(tf, i, ilst); + } return; } } @@ -389,8 +395,12 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf) res = tlst->fn(tf, &msg); if (res != TF_NEXT) { - // if it's TF_CLOSE, we assume user already cleaned up userdata - // TF_RENEW doesn't make sense here because type listeners don't expire + // type listeners don't have userdata. + // TF_RENEW doesn't make sense here because type listeners don't expire = same as TF_STAY + + if (res == TF_CLOSE) { + cleanup_type_listener(tf, i, tlst); + } return; } } @@ -404,8 +414,16 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf) res = glst->fn(tf, &msg); if (res != TF_NEXT) { - // if it's TF_CLOSE, we assume user already cleaned up userdata - // TF_RENEW doesn't make sense here because generic listeners don't expire + // generic listeners don't have userdata. + // TF_RENEW doesn't make sense here because generic listeners don't expire = same as TF_STAY + + // note: It's not expected that user will have multiple generic listeners, or + // ever actually remove them. They're most useful as default callbacks if no other listener + // handled the message. + + if (res == TF_CLOSE) { + cleanup_generic_listener(tf, i, glst); + } return; } } @@ -463,10 +481,11 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) #if !TF_USE_SOF_BYTE if (tf->state == TFState_SOF) { - TF_ParsBeginFrame(); - } + TF_ParsBeginFrame(); + } #endif + //@formatter:off switch (tf->state) { case TFState_SOF: if (c == TF_SOF_BYTE) { @@ -495,15 +514,15 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) case TFState_TYPE: CKSUM_ADD(tf->cksum, c); COLLECT_NUMBER(tf->type, TF_TYPE) { -#if TF_CKSUM_TYPE == TF_CKSUM_NONE - tf->state = TFState_DATA; - tf->rxi = 0; -#else - // enter HEAD_CKSUM state - tf->state = TFState_HEAD_CKSUM; - tf->rxi = 0; - tf->ref_cksum = 0; -#endif + #if TF_CKSUM_TYPE == TF_CKSUM_NONE + tf->state = TFState_DATA; + tf->rxi = 0; + #else + // enter HEAD_CKSUM state + tf->state = TFState_HEAD_CKSUM; + tf->rxi = 0; + tf->ref_cksum = 0; + #endif } break; @@ -518,6 +537,7 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) } if (tf->len == 0) { + // if the message has no body, we're done. TF_HandleReceivedMessage(tf); TF_ResetParser(tf); break; @@ -529,7 +549,7 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) CKSUM_RESET(tf->cksum); // Start collecting the payload - if (tf->len >= TF_MAX_PAYLOAD_RX) { + if (tf->len > TF_MAX_PAYLOAD_RX) { // ERROR - frame too long. Consume, but do not store. tf->discard_data = true; } @@ -545,16 +565,16 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) } if (tf->rxi == tf->len) { -#if TF_CKSUM_TYPE == TF_CKSUM_NONE - // All done - TF_HandleReceivedMessage(); - TF_ResetParser(); -#else - // Enter DATA_CKSUM state - tf->state = TFState_DATA_CKSUM; - tf->rxi = 0; - tf->ref_cksum = 0; -#endif + #if TF_CKSUM_TYPE == TF_CKSUM_NONE + // All done + TF_HandleReceivedMessage(); + TF_ResetParser(); + #else + // Enter DATA_CKSUM state + tf->state = TFState_DATA_CKSUM; + tf->rxi = 0; + tf->ref_cksum = 0; + #endif } break; @@ -570,6 +590,7 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) } break; } + //@formatter:on // we get here after finishing HEAD, if no data are to be received - handle and clear if (tf->len == 0 && tf->state == TFState_DATA) { @@ -753,16 +774,19 @@ static bool _TF_FN TF_SendFrame(TinyFrame *tf, TF_Msg *msg, TF_Listener listener } } - // Flush if checksum wouldn't fit in the buffer - if (TF_SENDBUF_LEN - len < sizeof(TF_CKSUM)) { - TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, len); - len = 0; + // Checksum only if message had a body + if (msg->len > 0) { + // Flush if checksum wouldn't fit in the buffer + if (TF_SENDBUF_LEN - len < sizeof(TF_CKSUM)) { + TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, len); + len = 0; + } + + // Add checksum, flush what remains to be sent + len += TF_ComposeTail(tf->sendbuf + len, &cksum); } - // Add checksum, flush what remains to be sent - len += TF_ComposeTail(tf->sendbuf+len, &cksum); TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, len); - TF_ReleaseTx(tf); return true; diff --git a/TinyFrame/TinyFrame.h b/TinyFrame/TinyFrame.h index a4d92df..1a617e7 100644 --- a/TinyFrame/TinyFrame.h +++ b/TinyFrame/TinyFrame.h @@ -3,14 +3,14 @@ /** * TinyFrame protocol library - * - * (c) Ondřej Hruška 2017, MIT License + * + * (c) Ondřej Hruška 2017, MIT License * no liability/warranty, free for any use, must retain this notice & license - * + * * Upstream URL: https://github.com/MightyPork/TinyFrame */ -#define TF_VERSION "2.0.1" +#define TF_VERSION "2.0.4" //--------------------------------------------------------------------------- #include // for uint8_t etc diff --git a/USB/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c b/USB/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c index 58ff55d..56f4f98 100644 --- a/USB/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c +++ b/USB/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c @@ -646,9 +646,12 @@ uint8_t USBD_CDC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum) if(pdev->pClassData2 != NULL) { - - hcdc->TxState = 0; - USBD_CDC_TransmitDone(pdev); + if (epnum == CDC_IN_EP) { + hcdc->TxState = 0; + USBD_CDC_TransmitDone(pdev); + } else if (epnum == CDC_CMD_EP) { + // command EP send done + } return USBD_OK; } diff --git a/USB/usbd_cdc_if.c b/USB/usbd_cdc_if.c index 52d4ce1..736aa62 100644 --- a/USB/usbd_cdc_if.c +++ b/USB/usbd_cdc_if.c @@ -48,6 +48,7 @@ /* Includes ------------------------------------------------------------------*/ #include "platform.h" +#include "tasks/task_msg.h" #include "usbd_cdc_if.h" /* USER CODE BEGIN INCLUDE */ #include "task_main.h" @@ -265,6 +266,7 @@ static int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t length) */ static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len) { + static struct rx_que_item rxitem; /* USER CODE BEGIN 6 */ // this does nothing?! // the buffer was already assigned in the init function and we got it as argument here?! @@ -272,7 +274,11 @@ static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len) assert_param(*Len <= APP_RX_DATA_SIZE); USBD_CDC_ReceivePacket(&hUsbDeviceFS); - TF_Accept(comm, Buf, *Len); + + // Post the data chunk on the RX queue to be handled asynchronously. + rxitem.len = *Len; + memcpy(rxitem.data, Buf, rxitem.len); + assert_param(pdPASS == xQueueSend(queRxDataHandle, &rxitem, 100)); return (USBD_OK); /* USER CODE END 6 */ diff --git a/comm/messages.c b/comm/messages.c index 57c367c..7eda1fa 100644 --- a/comm/messages.c +++ b/comm/messages.c @@ -6,213 +6,57 @@ #include "TinyFrame.h" #include "framework/unit_registry.h" #include "comm/messages.h" -#include "task_sched.h" -TinyFrame tf_; +static TinyFrame tf_; TinyFrame *comm = &tf_; // --------------------------------------------------------------------------- -// Pre-declaring local functions -static TF_Result lst_ping(TinyFrame *tf, TF_Msg *msg); -static TF_Result lst_unit(TinyFrame *tf, TF_Msg *msg); -static TF_Result lst_list_units(TinyFrame *tf, TF_Msg *msg); -static TF_Result lst_default(TinyFrame *tf, TF_Msg *msg); - -void comm_init(void) -{ - TF_InitStatic(comm, TF_SLAVE); - TF_AddTypeListener(comm, MSG_PING, lst_ping); - TF_AddTypeListener(comm, MSG_UNIT_REQUEST, lst_unit); - TF_AddTypeListener(comm, MSG_LIST_UNITS, lst_list_units); - - // fall-through - TF_AddGenericListener(comm, lst_default); -} - -// --------------------------------------------------------------------------- - -void tf_respond_snprintf(TF_TYPE type, TF_ID id, const char *format, ...) -{ -#define ERR_STR_LEN 64 - char buf[ERR_STR_LEN]; - va_list args; - va_start(args, format); - uint32_t len = (uint32_t) fixup_vsnprintf(&buf[0], ERR_STR_LEN, format, args); - va_end(args); - - tf_respond_buf(type, id, (const uint8_t *) buf, len); -} - -void tf_respond_buf(TF_TYPE type, TF_ID id, const uint8_t *buf, uint32_t len) -{ - TF_Msg msg; - TF_ClearMsg(&msg); - { - msg.type = type; - msg.frame_id = id; - msg.data = buf; - msg.len = (TF_LEN) len; - } - TF_Respond(comm, &msg); -} - -void tf_send_buf(TF_TYPE type, const uint8_t *buf, uint32_t len) -{ - TF_Msg msg; - TF_ClearMsg(&msg); - { - msg.type = MSG_UNIT_REPORT; - msg.data = buf; - msg.len = (TF_LEN) len; - msg.type = type; - } - TF_Send(comm, &msg); // no listener -} - -// --------------------------------------------------------------------------- - -static void job_respond_err(Job *job) -{ - tf_respond_str(MSG_ERROR, job->frame_id, job->str); -} - -void sched_respond_err(TF_ID frame_id, const char *message) -{ - dbg("ERR: %s", message); - Job job = { - .cb = job_respond_err, - .frame_id = frame_id, - .str = message - }; - scheduleJob(&job, TSK_SCHED_LOW); -} - -void sched_respond_bad_cmd(TF_ID frame_id) -{ - sched_respond_err(frame_id, "BAD COMMAND"); -} - -void sched_respond_malformed_cmd(TF_ID frame_id) -{ - sched_respond_err(frame_id, "MALFORMED PAYLOAD"); -} - -// --------------------------------------------------------------------------- - -static void job_respond_suc(Job *job) -{ - tf_respond_ok(job->frame_id); -} - -void sched_respond_suc(TF_ID frame_id) -{ - Job job = { - .cb = job_respond_suc, - .frame_id = frame_id - }; - scheduleJob(&job, TSK_SCHED_LOW); -} - -// --------------------------------------------------------------------------- - -static void job_respond_uX(Job *job) -{ - tf_respond_buf(MSG_SUCCESS, job->frame_id, (const uint8_t *) &job->d32, job->len); -} - -void sched_respond_u8(TF_ID frame_id, uint8_t d) -{ - Job job = { - .cb = job_respond_uX, - .frame_id = frame_id, - .d32 = d, - .len = 1 - }; - scheduleJob(&job, TSK_SCHED_HIGH); -} - -void sched_respond_u16(TF_ID frame_id, uint16_t d) -{ - Job job = { - .cb = job_respond_uX, - .frame_id = frame_id, - .d32 = d, - .len = 2 - }; - scheduleJob(&job, TSK_SCHED_HIGH); -} - -void sched_respond_u32(TF_ID frame_id, uint32_t d) -{ - Job job = { - .cb = job_respond_uX, - .frame_id = frame_id, - .d32 = d, - .len = 4 - }; - scheduleJob(&job, TSK_SCHED_HIGH); -} - -// --------------------------------------------------------------------------- - -static void job_ping_reply(Job *job) -{ - tf_respond_snprintf(MSG_SUCCESS, job->frame_id, "%s/%s", GEX_VERSION, GEX_PLATFORM); -} - +/** + * Ping request listener - returns version string and other info about the build + */ static TF_Result lst_ping(TinyFrame *tf, TF_Msg *msg) { - Job job = { - .cb = job_ping_reply, - .frame_id = msg->frame_id - }; - scheduleJob(&job, TSK_SCHED_LOW); - + com_respond_snprintf(msg->frame_id, MSG_SUCCESS, "%s/%s", GEX_VERSION, GEX_PLATFORM); return TF_STAY; } -// ---------------------------------------------------------------------------- - -static void job_unhandled_resp(Job *job) -{ - tf_respond_snprintf(MSG_ERROR, job->frame_id, "UNKNOWN MSG %"PRIu32, job->d32); -} - +/** + * Default listener, fallback for unhandled messages + */ static TF_Result lst_default(TinyFrame *tf, TF_Msg *msg) { - Job job = { - .cb = job_unhandled_resp, - .frame_id = msg->frame_id, - .d32 = msg->type - }; - scheduleJob(&job, TSK_SCHED_LOW); + dbg("!! Unhandled msg type %02"PRIx8", frame_id 0x%04"PRIx16, msg->type, msg->frame_id); + com_respond_snprintf(msg->frame_id, MSG_ERROR, "UNKNOWN MSG %"PRIu8, msg->type); return TF_STAY; } -// ---------------------------------------------------------------------------- - +/** + * Unit request listener, a message targeted at a particular + */ static TF_Result lst_unit(TinyFrame *tf, TF_Msg *msg) { ureg_deliver_unit_request(msg); return TF_STAY; } -// ---------------------------------------------------------------------------- -static void job_list_units(Job *job) +static TF_Result lst_list_units(TinyFrame *tf, TF_Msg *msg) { - ureg_report_active_units(job->frame_id); + ureg_report_active_units(msg->frame_id); + return TF_STAY; } -static TF_Result lst_list_units(TinyFrame *tf, TF_Msg *msg) +// --------------------------------------------------------------------------- + +void comm_init(void) { - Job job = { - .cb = job_list_units, - .frame_id = msg->frame_id, - }; - scheduleJob(&job, TSK_SCHED_LOW); + TF_InitStatic(comm, TF_SLAVE); + TF_AddTypeListener(comm, MSG_PING, lst_ping); + TF_AddTypeListener(comm, MSG_UNIT_REQUEST, lst_unit); + TF_AddTypeListener(comm, MSG_LIST_UNITS, lst_list_units); - return TF_STAY; + // fall-through + TF_AddGenericListener(comm, lst_default); } diff --git a/comm/messages.h b/comm/messages.h index 3aae72f..a4373d8 100644 --- a/comm/messages.h +++ b/comm/messages.h @@ -9,142 +9,43 @@ #include "task_sched.h" #include "TinyFrame.h" -extern TinyFrame *comm; - -/** - * Initialize TinyFrame and set up listeners - */ -void comm_init(void); +#define BULK_LST_TIMEOUT_MS 200 +#define BULKREAD_MAX_CHUNK 512 // this is a static buffer /** * Supported message types (TF_TYPE) */ enum TF_Types_ { // General, low level - MSG_SUCCESS = 0x00, //!< Generic success response; used by default in all responses; payload is transaction-specific - MSG_PING = 0x01, //!< Ping request (or response), used to test connection - MSG_ERROR = 0x02, //!< Generic failure response (when a request fails to execute) - // Unit messages - MSG_UNIT_REQUEST = 0x10, //!< Command addressed to a particular unit - MSG_UNIT_REPORT = 0x11, //!< Spontaneous report from a unit - // System messages - MSG_LIST_UNITS = 0x20, //!< Get all unit call-signs and names -}; - -/** - * Respond to a TF message using printf-like formatting. - * Works synchronously, must be called on a job queue. - * - * @param type - response type byte - * @param frame_id - ID of the original msg - * @param format - printf format - * @param ... - replacements - */ -void __attribute__((format(printf,3,4))) -tf_respond_snprintf(TF_TYPE type, TF_ID frame_id, const char *format, ...); - -/** - * Respond to a TF message with a buffer of fixed length and custom type. - * Works synchronously, must be called on a job queue. - * - * @param type - response type byte - * @param frame_id - ID of the original msg - * @param buf - byte buffer - * @param len - buffer size - */ -void tf_respond_buf(TF_TYPE type, TF_ID frame_id, const uint8_t *buf, uint32_t len); + MSG_SUCCESS = 0x00, //!< Generic success response; used by default in all responses; payload is transaction-specific + MSG_PING = 0x01, //!< Ping request (or response), used to test connection + MSG_ERROR = 0x02, //!< Generic failure response (when a request fails to execute) -/** - * Respond to a TF message with empty body and MSG_SUCCESS type. - * Works synchronously, must be called on a job queue. - * - * @param frame_id - ID of the original msg - */ -static inline void tf_respond_ok(TF_ID frame_id) -{ - tf_respond_buf(MSG_SUCCESS, frame_id, NULL, 0); -} + MSG_BULK_READ_OFFER = 0x03, //!< Offer of data to read. Payload: u32 total len + MSG_BULK_READ_POLL = 0x04, //!< Request to read a previously announced chunk. + MSG_BULK_WRITE_OFFER = 0x05, //!< Offer to receive data in a write transaction. Payload: u32 max size, u32 max chunk + MSG_BULK_DATA = 0x06, //!< Writing a chunk, or sending a chunk to master. + MSG_BULK_END = 0x07, //!< Bulk transfer is done, no more data to read or write. Recipient shall check total len and discard it on mismatch. There could be a checksum ... + MSG_BULK_ABORT = 0x08, //!< Discard the ongoing transfer -/** - * Same like tf_respond_buf(), but used for sending spontaneous reports. - * Works synchronously, must be called on a job queue / timer task etc. - * - * @param type - response type byte - * @param buf - byte buffer - * @param len - buffer size - */ -void tf_send_buf(TF_TYPE type, const uint8_t *buf, uint32_t len); - -/** - * Same like tf_respond_buf(), but the buffer length is measured with strlen. - * Used to sending ASCII string responses. - * Works synchronously, must be called on a job queue. - * - * @param type - response type byte - * @param frame_id - ID of the original msg - * @param str - character buffer, zero terminated - */ -static inline void tf_respond_str(TF_TYPE type, TF_ID frame_id, const char *str) -{ - tf_respond_buf(type, frame_id, (const uint8_t *) str, (uint32_t) strlen(str)); -} - -/** - * Schedule sending an ASCII string error response. - * Schedules a low priority job. - * - * @param frame_id - ID of the original msg - * @param str - character buffer, zero terminated - */ -void sched_respond_err(TF_ID frame_id, const char *str); - -/** - * Variant of sched_respond_err() for reporting bad received command code - * - * @param msg_id - ID of the original msg - */ -void sched_respond_bad_cmd(TF_ID frame_id); - -/** - * Variant of sched_respond_err() for reporting malformed commands (e.g. too short payload) - * - * @param msg_id - ID of the original msg - */ -void sched_respond_malformed_cmd(TF_ID frame_id); + // Unit messages + MSG_UNIT_REQUEST = 0x10, //!< Command addressed to a particular unit + MSG_UNIT_REPORT = 0x11, //!< Spontaneous report from a unit -/** - * Schedule sending an empty response with MSG_SUCCESS type. - * Schedules a low priority job. - * - * @param frame_id - ID of the original msg - */ -void sched_respond_suc(TF_ID frame_id); + // System messages + MSG_LIST_UNITS = 0x20, //!< Get all unit call-signs and names +}; -/** - * Schedule sending a one-byte response with MSG_SUCCESS type. - * Schedules a high priority job. - * - * @param frame_id - ID of the original msg - * @param d - data - */ -void sched_respond_u8(TF_ID frame_id, uint8_t d); +extern TinyFrame *comm; -/** - * Schedule sending a two-byte response with MSG_SUCCESS type. - * Schedules a high priority job. - * - * @param frame_id - ID of the original msg - * @param d - data - */ -void sched_respond_u16(TF_ID frame_id, uint16_t d); +// Must be after the enum because it's used in the header file. +#include "msg_responses.h" +#include "msg_bulkread.h" +#include "msg_bulkwrite.h" /** - * Schedule sending a 4-byte response with MSG_SUCCESS type. - * Schedules a high priority job. - * - * @param frame_id - ID of the original msg - * @param d - data + * Initialize TinyFrame and set up listeners */ -void sched_respond_u32(TF_ID frame_id, uint32_t d); +void comm_init(void); #endif //GEX_MESSAGES_H diff --git a/comm/msg_bulkread.c b/comm/msg_bulkread.c new file mode 100644 index 0000000..9ee55c3 --- /dev/null +++ b/comm/msg_bulkread.c @@ -0,0 +1,98 @@ +// +// Created by MightyPork on 2017/12/23. +// + +#include "platform.h" + +#include + +#include "messages.h" +#include "utils/payload_parser.h" +#include "utils/payload_builder.h" + +/** Buffer for preparing bulk chunks */ +static uint8_t bulkread_buffer[BULKREAD_MAX_CHUNK]; + +/** + * TF listener for the bulk read transaction + */ +static TF_Result bulkread_lst(TinyFrame *tf, TF_Msg *msg) +{ + struct bulk_read *bulk = msg->userdata; + + // this is a final call before timeout, to clean up + if (msg->data == NULL) { + goto close; + } + + assert_param(NULL != bulk); + + if (msg->type == MSG_BULK_ABORT) { + goto close; + } + else if (msg->type == MSG_BULK_READ_POLL) { + // find how much data the reader wants + PayloadParser pp = pp_start(msg->data, msg->len, NULL); + uint32_t chunk = pp_u32(&pp); + + chunk = MIN(chunk, bulk->len - bulk->offset); + chunk = MIN(chunk, BULKREAD_MAX_CHUNK); + + // load data into the buffer + bulk->read(bulk, chunk, bulkread_buffer); + + bool last = (bulk->offset + chunk >= bulk->len); + + TF_Msg resp; + TF_ClearMsg(&resp); + resp.frame_id = bulk->frame_id; + resp.type = (last ? MSG_BULK_END : MSG_BULK_DATA); // the last chunk has the END type + resp.data = bulkread_buffer; + resp.len = (TF_LEN) chunk; + TF_Respond(tf, &resp); + + if (last) goto close; + + // advance the position pointer + bulk->offset += chunk; + } + + return TF_RENEW; + +close: + if (bulk) { + // Ask user to free the bulk and userdata + bulk->read(bulk, 0, NULL); + msg->userdata = NULL; + } + return TF_CLOSE; +} + +/** Start the bulk read flow */ +void bulkread_start(TinyFrame *tf, struct bulk_read *bulk) +{ + assert_param(bulk); + assert_param(bulk->len); + assert_param(bulk->read); + + bulk->offset = 0; + + { + uint8_t buf[8]; + PayloadBuilder pb = pb_start(buf, 4, NULL); + pb_u32(&pb, bulk->len); + pb_u32(&pb, BULKREAD_MAX_CHUNK); + + // We use userdata1 to hold a reference to the bulk transfer + TF_Msg msg = { + .type = MSG_BULK_READ_OFFER, + .frame_id = bulk->frame_id, + .is_response = true, // this ensures the frame_id is not re-generated + .data = buf, + .len = (TF_LEN) pb_length(&pb), + .userdata = bulk, + }; + + TF_Query(tf, &msg, bulkread_lst, BULK_LST_TIMEOUT_MS); + } +} diff --git a/comm/msg_bulkread.h b/comm/msg_bulkread.h new file mode 100644 index 0000000..abe49fb --- /dev/null +++ b/comm/msg_bulkread.h @@ -0,0 +1,45 @@ +// +// Created by MightyPork on 2017/12/23. +// + +#ifndef GEX_F072_MSG_BULKREAD_H +#define GEX_F072_MSG_BULKREAD_H + +#ifndef GEX_MESSAGES_H +#error "Include messages.h instead!" +#endif + +#include + +typedef struct bulk_read BulkRead; + +/** + * Type of the bulk-read listener. Offset within the data can be retrieved from bulk->offset. + * + * If buffer is NULL, this is the last call and the bulk struct must be freed if it was allocated. + * + * @param bulk - a data object passed to the bulkread_start() function. + * @param chunk - size of the chunk to read + * @param buffer - destination buffer to fill with the data. NULL if bulk should be freed. + */ +typedef void (*bulkread_data_cb)(BulkRead *bulk, uint32_t chunk, uint8_t *buffer); + +/** Bulk read structure */ +struct bulk_read { + TF_ID frame_id; //!< ID of the requesting frame, used for the entire bulk read session + bulkread_data_cb read; //!< Read callback + uint32_t len; //!< Total data length + void *userdata; //!< A place for arbitrary userdata + + uint32_t offset; //!< Internal offset counter, will be set to 0 on start. +}; + +/** + * Start a bulk read session + * @param tf - tinyframe instance + * @param bulk - bulk read config structure (malloc'd or static) + */ +void bulkread_start(TinyFrame *tf, struct bulk_read *bulk); + + +#endif //GEX_F072_MSG_BULKREAD_H diff --git a/comm/msg_bulkwrite.c b/comm/msg_bulkwrite.c new file mode 100644 index 0000000..bd42b27 --- /dev/null +++ b/comm/msg_bulkwrite.c @@ -0,0 +1,89 @@ +// +// Created by MightyPork on 2017/12/23. +// + +#include "platform.h" + +#include + +#include "messages.h" +#include "utils/payload_parser.h" +#include "utils/payload_builder.h" + +/** + * TF listener for the bulk write transaction + */ +static TF_Result bulkwrite_lst(TinyFrame *tf, TF_Msg *msg) +{ + struct bulk_write *bulk = msg->userdata; + + // this is a final call before timeout, to clean up + if (msg->data == NULL) { + goto close; + } + + assert_param(NULL != bulk); + + if (msg->type == MSG_BULK_ABORT) { + goto close; + } + else if (msg->type == MSG_BULK_DATA || msg->type == MSG_BULK_END) { + // if past len, say we're done and close + if (bulk->offset >= bulk->len) { + com_respond_err(bulk->frame_id, "WRITE OVERRUN"); + goto close; + } + + bulk->write(bulk, msg->data, msg->len); + + // Return SUCCESS + com_respond_ok(msg->frame_id); + + // advance the position pointer (can be used by the write callback) + bulk->offset += msg->len; + + if (msg->type == MSG_BULK_END) { + // this was the last chunk + goto close; + } + } + + return TF_RENEW; + +close: + if (bulk) { + // Ask user to free the bulk and userdata + bulk->write(bulk, NULL, 0); + msg->userdata = NULL; + } + return TF_CLOSE; +} + +/** Start the bulk write flow */ +void bulkwrite_start(TinyFrame *tf, struct bulk_write *bulk) +{ + assert_param(bulk); + assert_param(bulk->len); + assert_param(bulk->write); + + bulk->offset = 0; + + { + uint8_t buf[8]; + PayloadBuilder pb = pb_start(buf, 8, NULL); + pb_u32(&pb, bulk->len); + pb_u32(&pb, TF_MAX_PAYLOAD_RX); + + // We use userdata1 to hold a reference to the bulk transfer + TF_Msg msg = { + .type = MSG_BULK_WRITE_OFFER, + .frame_id = bulk->frame_id, + .is_response = true, // this ensures the frame_id is not re-generated + .data = buf, + .len = (TF_LEN) pb_length(&pb), + .userdata = bulk, + }; + + TF_Query(tf, &msg, bulkwrite_lst, BULK_LST_TIMEOUT_MS); + } +} diff --git a/comm/msg_bulkwrite.h b/comm/msg_bulkwrite.h new file mode 100644 index 0000000..4391dab --- /dev/null +++ b/comm/msg_bulkwrite.h @@ -0,0 +1,41 @@ +// +// Created by MightyPork on 2017/12/23. +// + +#ifndef GEX_F072_MSG_BULKWRITE_H +#define GEX_F072_MSG_BULKWRITE_H + +#include + +typedef struct bulk_write BulkWrite; + +/** + * Type of the bulk-write listener. Offset within the data can be retrieved from bulk->offset. + * + * If buffer is NULL, this is the last call and the bulk struct must be freed if it was allocated. + * + * @param bulk - a data object passed to the bulkwrite_start() function. + * @param chunk - destination buffer to fill with the data. NULL if bulk should be freed. + * @param len - size of the chunk to write + */ +typedef void (*bulkwrite_data_cb)(BulkWrite *bulk, const uint8_t *chunk, uint32_t len); + +/** Bulk read structure */ +struct bulk_write { + TF_ID frame_id; //!< ID of the requesting frame, used for the entire bulk read session + bulkwrite_data_cb write; //!< Write callback + uint32_t len; //!< Total data length + void *userdata; //!< A place for arbitrary userdata + + uint32_t offset; //!< Internal offset counter, will be set to 0 on start. +}; + +/** + * Start a bulk write session + * @param tf - tinyframe instance + * @param bulk - bulk write config structure (malloc'd or static) + */ +void bulkwrite_start(TinyFrame *tf, struct bulk_write *bulk); + + +#endif //GEX_F072_MSG_BULKWRITE_H diff --git a/comm/msg_responses.c b/comm/msg_responses.c new file mode 100644 index 0000000..3562b02 --- /dev/null +++ b/comm/msg_responses.c @@ -0,0 +1,98 @@ +// +// Created by MightyPork on 2017/12/22. +// + +#include "messages.h" +#include "msg_responses.h" + +void com_respond_snprintf(TF_ID frame_id, TF_TYPE type, const char *format, ...) +{ +#define ERR_STR_LEN 64 + + char buf[ERR_STR_LEN]; + va_list args; + va_start(args, format); + uint32_t len = (uint32_t) fixup_vsnprintf(&buf[0], ERR_STR_LEN, format, args); + va_end(args); + + com_respond_buf(frame_id, type, (const uint8_t *) buf, len); +} + + +void com_respond_buf(TF_ID frame_id, TF_TYPE type, const uint8_t *buf, uint32_t len) +{ + TF_Msg msg; + TF_ClearMsg(&msg); + { + msg.type = type; + msg.frame_id = frame_id; + msg.data = buf; + msg.len = (TF_LEN) len; + } + TF_Respond(comm, &msg); +} + + +void com_respond_ok(TF_ID frame_id) +{ + com_respond_buf(frame_id, MSG_SUCCESS, NULL, 0); +} + + +void com_send_buf(TF_TYPE type, const uint8_t *buf, uint32_t len) +{ + TF_Msg msg; + TF_ClearMsg(&msg); + { + msg.type = MSG_UNIT_REPORT; + msg.data = buf; + msg.len = (TF_LEN) len; + msg.type = type; + } + TF_Send(comm, &msg); // no listener +} + + +void com_respond_str(TF_TYPE type, TF_ID frame_id, const char *str) +{ + com_respond_buf(frame_id, type, (const uint8_t *) str, (uint32_t) strlen(str)); +} + + +// --------------------------------------------------------------------------- + +void com_respond_err(TF_ID frame_id, const char *message) +{ + com_respond_str(MSG_ERROR, frame_id, message); +} + + +void com_respond_bad_cmd(TF_ID frame_id) +{ + com_respond_err(frame_id, "BAD COMMAND"); +} + + +void com_respond_malformed_cmd(TF_ID frame_id) +{ + com_respond_err(frame_id, "MALFORMED PAYLOAD"); +} + +// --------------------------------------------------------------------------- + +void com_respond_u8(TF_ID frame_id, uint8_t d) +{ + com_respond_buf(frame_id, MSG_SUCCESS, (const uint8_t *) &d, 1); +} + + +void com_respond_u16(TF_ID frame_id, uint16_t d) +{ + com_respond_buf(frame_id, MSG_SUCCESS, (const uint8_t *) &d, 2); +} + + +void com_respond_u32(TF_ID frame_id, uint32_t d) +{ + com_respond_buf(frame_id, MSG_SUCCESS, (const uint8_t *) &d, 4); +} diff --git a/comm/msg_responses.h b/comm/msg_responses.h new file mode 100644 index 0000000..8189092 --- /dev/null +++ b/comm/msg_responses.h @@ -0,0 +1,114 @@ +// +// Created by MightyPork on 2017/12/22. +// + +#ifndef GEX_F072_MSG_RESPONSES_H +#define GEX_F072_MSG_RESPONSES_H + +#ifndef GEX_MESSAGES_H +#error "Include messages.h instead!" +#endif + +/** + * Respond to a TF message using printf-like formatting. + * Works synchronously, must be called on a job queue. + * + * @param type - response type byte + * @param frame_id - ID of the original msg + * @param format - printf format + * @param ... - replacements + */ +void __attribute__((format(printf,3,4))) +com_respond_snprintf(TF_ID frame_id, TF_TYPE type, const char *format, ...); + +/** + * Respond to a TF message with a buffer of fixed length and custom type. + * Works synchronously, must be called on a job queue. + * + * @param type - response type byte + * @param frame_id - ID of the original msg + * @param buf - byte buffer + * @param len - buffer size + */ +void com_respond_buf(TF_ID frame_id, TF_TYPE type, const uint8_t *buf, uint32_t len); + +/** + * Respond to a TF message with empty body and MSG_SUCCESS type. + * Works synchronously, must be called on a job queue. + * + * @param frame_id - ID of the original msg + */ +void com_respond_ok(TF_ID frame_id); + +/** + * Same like tf_respond_buf(), but used for sending spontaneous reports. + * Works synchronously, must be called on a job queue / timer task etc. + * + * @param type - response type byte + * @param buf - byte buffer + * @param len - buffer size + */ +void com_send_buf(TF_TYPE type, const uint8_t *buf, uint32_t len); + +/** + * Same like tf_respond_buf(), but the buffer length is measured with strlen. + * Used to sending ASCII string responses. + * Works synchronously, must be called on a job queue. + * + * @param type - response type byte + * @param frame_id - ID of the original msg + * @param str - character buffer, zero terminated + */ +void com_respond_str(TF_TYPE type, TF_ID frame_id, const char *str); + +/** + * Schedule sending an ASCII string error response. + * Schedules a low priority job. + * + * @param frame_id - ID of the original msg + * @param str - character buffer, zero terminated + */ +void com_respond_err(TF_ID frame_id, const char *str); + +/** + * Variant of sched_respond_err() for reporting bad received command code + * + * @param msg_id - ID of the original msg + */ +void com_respond_bad_cmd(TF_ID frame_id); + +/** + * Variant of sched_respond_err() for reporting malformed commands (e.g. too short payload) + * + * @param msg_id - ID of the original msg + */ +void com_respond_malformed_cmd(TF_ID frame_id); + +/** + * Schedule sending a one-byte response with MSG_SUCCESS type. + * Schedules a high priority job. + * + * @param frame_id - ID of the original msg + * @param d - data + */ +void com_respond_u8(TF_ID frame_id, uint8_t d); + +/** + * Schedule sending a two-byte response with MSG_SUCCESS type. + * Schedules a high priority job. + * + * @param frame_id - ID of the original msg + * @param d - data + */ +void com_respond_u16(TF_ID frame_id, uint16_t d); + +/** + * Schedule sending a 4-byte response with MSG_SUCCESS type. + * Schedules a high priority job. + * + * @param frame_id - ID of the original msg + * @param d - data + */ +void com_respond_u32(TF_ID frame_id, uint32_t d); + +#endif //GEX_F072_MSG_RESPONSES_H diff --git a/cortex_handlers.c b/cortex_handlers.c index a0d36e7..752dcb4 100644 --- a/cortex_handlers.c +++ b/cortex_handlers.c @@ -4,6 +4,7 @@ #include #include "platform/debug_uart.h" #include "platform/status_led.h" +#include "utils/stacksmon.h" /* External variables --------------------------------------------------------*/ @@ -29,6 +30,8 @@ void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName) called if a stack overflow is detected. */ PRINTF(tFAULT" RTOS stack overflow! tsk: %s\r\n", (char *) pcTaskName); StatusLed_On(STATUS_FAULT); + + stackmon_dump(); while (1); } diff --git a/framework/unit_registry.c b/framework/unit_registry.c index 5940c01..d6cf730 100644 --- a/framework/unit_registry.c +++ b/framework/unit_registry.c @@ -2,6 +2,7 @@ // Created by MightyPork on 2017/11/26. // +#include #include "platform.h" #include "utils/avrlibc.h" #include "comm/messages.h" @@ -494,11 +495,6 @@ uint32_t ureg_get_num_units(void) return count; } -static void job_nosuch_unit(Job *job) -{ - tf_respond_snprintf(MSG_ERROR, job->frame_id, "NO UNIT @ %"PRIu32, job->d32); -} - /** Deliver message to it's destination unit */ void ureg_deliver_unit_request(TF_Msg *msg) { @@ -513,17 +509,20 @@ void ureg_deliver_unit_request(TF_Msg *msg) if (!pp.ok) { dbg("!! pp not OK!"); } if (callsign == 0 || !pp.ok) { - sched_respond_malformed_cmd(msg->frame_id); + com_respond_malformed_cmd(msg->frame_id); return; } UlistEntry *li = ulist_head; while (li != NULL) { Unit *const pUnit = &li->unit; - if (pUnit->callsign == callsign) { + if (pUnit->callsign == callsign && pUnit->status == E_SUCCESS) { bool ok = pUnit->driver->handleRequest(pUnit, msg->frame_id, command, &pp); + + // send extra SUCCESS confirmation message. + // error is expected to have already been reported. if (ok && confirmed) { - sched_respond_suc(msg->frame_id); + com_respond_ok(msg->frame_id); } return; } @@ -531,48 +530,47 @@ void ureg_deliver_unit_request(TF_Msg *msg) } // Not found - Job job = { - .cb = job_nosuch_unit, - .frame_id = msg->frame_id, - .d32 = callsign - }; - scheduleJob(&job, TSK_SCHED_LOW); + com_respond_snprintf(msg->frame_id, MSG_ERROR, "NO UNIT @ %"PRIu8, callsign); } - +/** Send a response for a unit-list request */ void ureg_report_active_units(TF_ID frame_id) { // count bytes needed - uint32_t needed = 1; // + uint32_t msglen = 1; // for the count byte UlistEntry *li = ulist_head; uint32_t count = 0; while (li != NULL) { - count++; - needed += strlen(li->unit.name)+1; + if (li->unit.status == E_SUCCESS) { + count++; + msglen += strlen(li->unit.name) + 1; + msglen += strlen(li->unit.driver->name) + 1; + } li = li->next; } - needed += count; + msglen += count; // one byte per message for the callsign bool suc = true; - uint8_t *buff = malloc_ck(needed, &suc); - if (!suc) { tf_respond_str(MSG_ERROR, frame_id, "OUT OF MEMORY"); return; } - + uint8_t *buff = malloc_ck(msglen, &suc); + if (!suc) { com_respond_str(MSG_ERROR, frame_id, "OUT OF MEMORY"); return; } { - PayloadBuilder pb = pb_start(buff, needed, NULL); - pb_u8(&pb, (uint8_t) count); // assume we don't have more than 255 + PayloadBuilder pb = pb_start(buff, msglen, NULL); + pb_u8(&pb, (uint8_t) count); // assume we don't have more than 255 units li = ulist_head; while (li != NULL) { - pb_u8(&pb, li->unit.callsign); - pb_string(&pb, li->unit.name); + if (li->unit.status == E_SUCCESS) { + pb_u8(&pb, li->unit.callsign); + pb_string(&pb, li->unit.name); + pb_string(&pb, li->unit.driver->name); + } li = li->next; } assert_param(pb.ok); - tf_respond_buf(MSG_SUCCESS, frame_id, buff, needed); + com_respond_buf(frame_id, MSG_SUCCESS, buff, msglen); } - free(buff); } diff --git a/freertos.c b/freertos.c index f4fc74b..1e0e3fd 100644 --- a/freertos.c +++ b/freertos.c @@ -57,23 +57,34 @@ /* USER CODE END Includes */ /* Variables -----------------------------------------------------------------*/ + +#define STACK_MAIN 160 +#define STACK_MSG 230 +#define STACK_JOBRUNNER 150 + osThreadId tskMainHandle; -uint32_t mainTaskBuffer[ 160 ]; +uint32_t mainTaskBuffer[ STACK_MAIN ]; osStaticThreadDef_t mainTaskControlBlock; -osThreadId tskSchedLPHandle; -uint32_t schedLowBuffer[ 128 ]; -osStaticThreadDef_t schedLowControlBlock; -osThreadId tskSchedHPHandle; -uint32_t schedHighBuffer[ 128 ]; -osStaticThreadDef_t schedHighControlBlock; -osMessageQId queSchedLPHandle; -uint8_t myQueue01Buffer[ 5 * sizeof( struct sched_que_item ) ]; -osStaticMessageQDef_t myQueue01ControlBlock; -osMessageQId queSchedHPHandle; -uint8_t myQueue02Buffer[ 5 * sizeof( struct sched_que_item ) ]; + +osThreadId tskMsgHandle; +uint32_t msgTaskBuffer[ STACK_MSG ]; +osStaticThreadDef_t msgTaskControlBlock; + +osThreadId tskJobRunnerHandle; +uint32_t jobRunnerBuffer[ STACK_JOBRUNNER ]; +osStaticThreadDef_t jobRunnerControlBlock; + +osMessageQId queSchedHandle; +uint8_t myQueue02Buffer[ HP_SCHED_CAPACITY * sizeof( struct sched_que_item ) ]; osStaticMessageQDef_t myQueue02ControlBlock; + +osMessageQId queRxDataHandle; +uint8_t myQueue03Buffer[ RX_QUE_CAPACITY * sizeof( struct rx_que_item ) ]; +osStaticMessageQDef_t myQueue03ControlBlock; + osMutexId mutTinyFrameTxHandle; osStaticMutexDef_t myMutex01ControlBlock; + osSemaphoreId semVcomTxReadyHandle; osStaticSemaphoreDef_t myBinarySem01ControlBlock; @@ -84,7 +95,8 @@ osStaticSemaphoreDef_t myBinarySem01ControlBlock; /* Function prototypes -------------------------------------------------------*/ void TaskMain(void const * argument); extern void TaskSchedLP(void const * argument); -extern void TaskSchedHP(void const * argument); +extern void TaskJobQueue(void const *argument); +extern void TaskMessaging(void const * argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ @@ -125,8 +137,8 @@ void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackTy void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ stackmon_register("Main", mainTaskBuffer, sizeof(mainTaskBuffer)); - stackmon_register("Job Queue Low", schedLowBuffer, sizeof(schedLowBuffer)); - stackmon_register("Job Queue High", schedHighBuffer, sizeof(schedHighBuffer)); + stackmon_register("JobRunner", jobRunnerBuffer, sizeof(jobRunnerBuffer)); + stackmon_register("Messaging", msgTaskBuffer, sizeof(msgTaskBuffer)); /* USER CODE END Init */ /* Create the mutex(es) */ @@ -154,29 +166,30 @@ void MX_FREERTOS_Init(void) { /* Create the thread(s) */ /* definition and creation of tskMain */ - osThreadStaticDef(tskMain, TaskMain, osPriorityHigh, 0, 160, mainTaskBuffer, &mainTaskControlBlock); + osThreadStaticDef(tskMain, TaskMain, osPriorityHigh, 0, STACK_MAIN, mainTaskBuffer, &mainTaskControlBlock); tskMainHandle = osThreadCreate(osThread(tskMain), NULL); - /* definition and creation of tskSchedLP */ - osThreadStaticDef(tskSchedLP, TaskSchedLP, osPriorityLow, 0, 128, schedLowBuffer, &schedLowControlBlock); - tskSchedLPHandle = osThreadCreate(osThread(tskSchedLP), NULL); + /* definition and creation of tskJobRunner */ + osThreadStaticDef(tskJobRunner, TaskJobQueue, osPriorityAboveNormal, 0, STACK_JOBRUNNER, jobRunnerBuffer, &jobRunnerControlBlock); + tskJobRunnerHandle = osThreadCreate(osThread(tskJobRunner), NULL); - /* definition and creation of tskSchedHP */ - osThreadStaticDef(tskSchedHP, TaskSchedHP, osPriorityAboveNormal, 0, 128, schedHighBuffer, &schedHighControlBlock); - tskSchedHPHandle = osThreadCreate(osThread(tskSchedHP), NULL); + /* definition and creation of TaskMessaging */ + osThreadStaticDef(tskMsg, TaskMessaging, osPriorityNormal, 0, STACK_MSG, msgTaskBuffer, &msgTaskControlBlock); + tskMsgHandle = osThreadCreate(osThread(tskMsg), NULL); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ /* USER CODE END RTOS_THREADS */ /* Create the queue(s) */ - /* definition and creation of queSchedLP */ - osMessageQStaticDef(queSchedLP, 5, struct sched_que_item, myQueue01Buffer, &myQueue01ControlBlock); - queSchedLPHandle = osMessageCreate(osMessageQ(queSchedLP), NULL); /* definition and creation of queSchedHP */ - osMessageQStaticDef(queSchedHP, 5, struct sched_que_item, myQueue02Buffer, &myQueue02ControlBlock); - queSchedHPHandle = osMessageCreate(osMessageQ(queSchedHP), NULL); + osMessageQStaticDef(queSchedHP, HP_SCHED_CAPACITY, struct sched_que_item, myQueue02Buffer, &myQueue02ControlBlock); + queSchedHandle = osMessageCreate(osMessageQ(queSchedHP), NULL); + + /* definition and creation of queRxData */ + osMessageQStaticDef(queRxData, RX_QUE_CAPACITY, struct rx_que_item, myQueue03Buffer, &myQueue03ControlBlock); + queRxDataHandle = osMessageCreate(osMessageQ(queRxData), NULL); /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ diff --git a/gex.mk b/gex.mk index a5df107..b42f206 100644 --- a/gex.mk +++ b/gex.mk @@ -7,6 +7,7 @@ GEX_SRC_DIR = \ User/units \ User/units/system \ User/units/neopixel \ + User/units/test \ User/units/pin \ User/TinyFrame \ User/CWPack \ @@ -36,10 +37,6 @@ GEX_INCLUDES = \ -IUser/TinyFrame \ -IUser/vfs \ -IUser/utils \ - -IUser/units \ - -IUser/units/system \ - -IUser/units/neopixel \ - -IUser/units/pin \ -IUser/framework \ -IUser/platform \ -IUser/tasks \ @@ -65,10 +62,14 @@ GEX_CFLAGS = \ -fmerge-constants -fmerge-all-constants \ -fno-exceptions -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -finline-small-functions -findirect-inlining \ -GEX_CDEFS = \ +GEX_CDEFS_BASE = \ -D__weak="__attribute__((weak))" \ -D__packed="__attribute__((__packed__))" \ -DUSE_FULL_LL_DRIVER \ + +# TODO implement debug build choice +ifeq '1' '1' +GEX_CDEFS = $(GEX_CDEFS_BASE) \ -DUSE_FULL_ASSERT=1 \ -DVERBOSE_ASSERT=1 \ -DDEBUG_VFS=0 \ @@ -76,3 +77,13 @@ GEX_CDEFS = \ -DVERBOSE_HARDFAULT=1 \ -DUSE_STACK_MONITOR=1 \ -DUSE_DEBUG_UART=1 +else +GEX_CDEFS = $(GEX_CDEFS_BASE) \ + -DUSE_FULL_ASSERT=0 \ + -DVERBOSE_ASSERT=0 \ + -DDEBUG_VFS=0 \ + -DDEBUG_FLASH_WRITE=0 \ + -DVERBOSE_HARDFAULT=0 \ + -DUSE_STACK_MONITOR=0 \ + -DUSE_DEBUG_UART=0 +endif diff --git a/platform/debug_uart.c b/platform/debug_uart.c index a9f91d6..cc371b9 100644 --- a/platform/debug_uart.c +++ b/platform/debug_uart.c @@ -79,6 +79,7 @@ ssize_t _write_r(struct _reent *rptr, int fd, const void *buf, size_t len) #else // No-uart variant +void DebugUart_PreInit(void) {} void DebugUart_Init(void) {} ssize_t _write_r(struct _reent *rptr, int fd, const void *buf, size_t len) { return len; diff --git a/platform/platform.c b/platform/platform.c index d798e21..f042292 100644 --- a/platform/platform.c +++ b/platform/platform.c @@ -6,11 +6,11 @@ #include "usbd_core.h" #include "USB/usb_device.h" #include "framework/resources.h" - #include "framework/unit_registry.h" #include "units/pin/unit_pin.h" #include "units/neopixel/unit_neopixel.h" +#include "units/test/unit_test.h" // ----- SUPPORTED UNITS ----- @@ -20,6 +20,8 @@ void plat_register_units(void) ureg_add_type(&UNIT_PIN); ureg_add_type(&UNIT_NEOPIXEL); + ureg_add_type(&UNIT_TEST); + // Platform-specific units could be added here } diff --git a/tasks/sched_queue.h b/tasks/sched_queue.h index 4239ede..14662ee 100644 --- a/tasks/sched_queue.h +++ b/tasks/sched_queue.h @@ -5,7 +5,7 @@ #ifndef GEX_SCHED_QUEUE_H #define GEX_SCHED_QUEUE_H -#include +#include typedef struct sched_que_item Job; typedef void (*ScheduledJobCb) (Job *job); @@ -29,4 +29,13 @@ struct sched_que_item { }; }; +// This que is used to stash frames received from TinyFrame for later evaluation on the application thread +struct rx_que_item { + uint32_t len; + uint8_t data[64]; +}; + +#define HP_SCHED_CAPACITY 5 +#define RX_QUE_CAPACITY 16 + #endif //GEX_SCHED_QUEUE_H diff --git a/tasks/task_main.c b/tasks/task_main.c index 60b9e1a..3c148da 100644 --- a/tasks/task_main.c +++ b/tasks/task_main.c @@ -48,7 +48,7 @@ void TaskMain(void const * argument) // Periodically check stacks for overrun stackmon_check_canaries(); // Periodically dump all stacks - for checking levels before critical (to reduce size if not needed) -// if ((cnt%50)==0) stackmon_dump(); + if ((cnt%75)==0) stackmon_dump(); continue; } diff --git a/tasks/task_msg.c b/tasks/task_msg.c new file mode 100644 index 0000000..b4bcc35 --- /dev/null +++ b/tasks/task_msg.c @@ -0,0 +1,31 @@ +// +// Created by MightyPork on 2017/12/22. +// + +#include "platform.h" +#include "comm/messages.h" +#include "utils/hexdump.h" +#include "task_msg.h" +#include "sched_queue.h" + +/** + * Process data received from TinyFrame. + * The queue holds received messages or parts of messages, + * copied there by the USB thread. + * + * Since this is a separate thread that handles TF input and runs the listeners, + * TF functions (send, respond) can be called immediately without the need for an + * intermediate queued job. + */ +void TaskMessaging(const void * argument) +{ + dbg("> Message queue task started!"); + + struct rx_que_item slot; + while (1) { + xQueueReceive(queRxDataHandle, &slot, osWaitForever); + assert_param(slot.len>0 && slot.len<=64); // check the len is within bounds + + TF_Accept(comm, slot.data, slot.len); + } +} \ No newline at end of file diff --git a/tasks/task_msg.h b/tasks/task_msg.h new file mode 100644 index 0000000..ef3ffa6 --- /dev/null +++ b/tasks/task_msg.h @@ -0,0 +1,12 @@ +// +// Created by MightyPork on 2017/12/22. +// + +#ifndef GEX_F072_TASK_MSG_H +#define GEX_F072_TASK_MSG_H + +extern osMessageQId queRxDataHandle; +extern osThreadId tskMsgHandle; +void TaskMessaging(const void * argument); + +#endif //GEX_F072_TASK_MSG_H diff --git a/tasks/task_sched.c b/tasks/task_sched.c index ade3b9c..274c6be 100644 --- a/tasks/task_sched.c +++ b/tasks/task_sched.c @@ -5,23 +5,18 @@ #include "platform.h" #include "task_sched.h" -extern osMessageQId queSchedLPHandle; -extern osMessageQId queSchedHPHandle; +extern osMessageQId queSchedHandle; -volatile uint32_t jobQueHighWaterMarkHP = 0; -volatile uint32_t jobQueHighWaterMarkLP = 0; +volatile uint32_t jobQueHighWaterMark = 0; /** * Schedule a function for later execution in the jobs thread * * @param callback - the callback function - * @param prio - priority, selects which job queue to use - * @param data1 - first data object, NULL if not needed; usually context/handle - * @param data2 - second data object, NULL if not needed; usually data/argument */ -void scheduleJob(Job *job, enum task_sched_prio prio) +void scheduleJob(Job *job) { - QueueHandle_t que = (prio == TSK_SCHED_LOW ? queSchedLPHandle : queSchedHPHandle); + QueueHandle_t que = queSchedHandle; assert_param(que != NULL); assert_param(job->cb != NULL); @@ -44,44 +39,22 @@ void scheduleJob(Job *job, enum task_sched_prio prio) } #if USE_STACK_MONITOR - if (prio == TSK_SCHED_LOW) { - jobQueHighWaterMarkLP = MAX(jobQueHighWaterMarkLP, count); - } else { - jobQueHighWaterMarkHP = MAX(jobQueHighWaterMarkHP, count); - } + jobQueHighWaterMark = MAX(jobQueHighWaterMark, count); #endif } /** - * Low priority task queue handler - * - * @param argument - */ -void TaskSchedLP (const void * argument) -{ - dbg("> Low priority queue task started!"); - - struct sched_que_item job; - while (1) { - xQueueReceive(queSchedLPHandle, &job, osWaitForever); - - assert_param(job.cb != NULL); - job.cb(&job); - } -} - -/** - * High priority task queue handler + * job queue handler (for use in interrupts to do longer stuff on a thread) * * @param argument */ -void TaskSchedHP (const void * argument) +void TaskJobQueue(const void *argument) { dbg("> High priority queue task started!"); struct sched_que_item job; while (1) { - xQueueReceive(queSchedHPHandle, &job, osWaitForever); + xQueueReceive(queSchedHandle, &job, osWaitForever); assert_param(job.cb != NULL); job.cb(&job); diff --git a/tasks/task_sched.h b/tasks/task_sched.h index c9c14b0..0841991 100644 --- a/tasks/task_sched.h +++ b/tasks/task_sched.h @@ -8,22 +8,13 @@ #include "platform.h" #include "sched_queue.h" -enum task_sched_prio { - TSK_SCHED_LOW = 0, - TSK_SCHED_HIGH = 1, -}; - #if USE_STACK_MONITOR -extern volatile uint32_t jobQueHighWaterMarkHP; -extern volatile uint32_t jobQueHighWaterMarkLP; +extern volatile uint32_t jobQueHighWaterMark; #endif -extern osThreadId tskSchedLPHandle; -void TaskSchedLP (const void * argument); - -extern osThreadId tskSchedHPHandle; -void TaskSchedHP (const void * argument); +extern osThreadId tskJobRunnerHandle; +void TaskJobQueue(const void *argument); -void scheduleJob(Job *job, enum task_sched_prio prio); +void scheduleJob(Job *job); #endif //GEX_TASK_SCHED_H diff --git a/units/neopixel/unit_neopixel.c b/units/neopixel/unit_neopixel.c index 0adc320..3610590 100644 --- a/units/neopixel/unit_neopixel.c +++ b/units/neopixel/unit_neopixel.c @@ -173,14 +173,14 @@ static bool Npx_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, Paylo break; default: - sched_respond_bad_cmd(frame_id); + com_respond_bad_cmd(frame_id); return false; } return true; bad_count: - sched_respond_err(frame_id, "BAD PIXEL COUNT"); + com_respond_err(frame_id, "BAD PIXEL COUNT"); return false; } diff --git a/units/pin/unit_pin.c b/units/pin/unit_pin.c index 4b56089..fb5a299 100644 --- a/units/pin/unit_pin.c +++ b/units/pin/unit_pin.c @@ -194,23 +194,23 @@ static bool Pin_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, Paylo case CMD_READ: if (!priv->output) { - sched_respond_u8(frame_id, (bool) LL_GPIO_IsInputPinSet(priv->port, priv->ll_pin)); + com_respond_u8(frame_id, (bool) LL_GPIO_IsInputPinSet(priv->port, priv->ll_pin)); } else goto must_be_input; break; default: - sched_respond_bad_cmd(frame_id); + com_respond_bad_cmd(frame_id); return false; } return true; must_be_output: - sched_respond_err(frame_id, "NOT OUTPUT PIN"); + com_respond_err(frame_id, "NOT OUTPUT PIN"); return false; must_be_input: - sched_respond_err(frame_id, "NOT INPUT PIN"); + com_respond_err(frame_id, "NOT INPUT PIN"); return false; } diff --git a/units/test/unit_test.c b/units/test/unit_test.c new file mode 100644 index 0000000..55e5fdb --- /dev/null +++ b/units/test/unit_test.c @@ -0,0 +1,190 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "comm/messages.h" +#include "unit_base.h" +#include "unit_test.h" + +/** Private data structure */ +struct priv { + uint32_t unused; +}; + +// ------------------------------------------------------------------------ + +/** Load from a binary buffer stored in Flash */ +static void Tst_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + // +} + +/** Write to a binary buffer for storing in Flash */ +static void Tst_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + // +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +static bool Tst_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + // + + return suc; +} + +/** Generate INI file section for the unit */ +static void Tst_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + // +} + +// ------------------------------------------------------------------------ + +/** Allocate data structure and set defaults */ +static bool Tst_preInit(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv), &suc); + CHECK_SUC(); + + // + + return true; +} + +/** Finalize unit set-up */ +static bool Tst_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // + + return true; +} + +/** Tear down the unit */ +static void Tst_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // + + // Free memory + free(unit->data); + unit->data = NULL; +} + +// ------------------------------------------------------------------------ + +enum PinCmd_ { + CMD_PING = 0, + CMD_ECHO = 1, + CMD_BULKREAD = 2, + CMD_BULKWRITE = 3, +}; + +static const char *longtext = "The history of all hitherto existing societies is the history of class struggles.\n\nFreeman and slave, patrician and plebeian, lord and serf, guild-master and journeyman, in a word, oppressor and oppressed, stood in constant opposition to one another, carried on an uninterrupted, now hidden, now open fight, a fight that each time ended, either in a revolutionary re-constitution of society at large, or in the common ruin of the contending classes.\n\nIn the earlier epochs of history, we find almost everywhere a complicated arrangement of society into various orders, a manifold gradation of social rank. In ancient Rome we have patricians, knights, plebeians, slaves; in the Middle Ages, feudal lords, vassals, guild-masters, journeymen, apprentices, serfs; in almost all of these classes, again, subordinate gradations.\n\nThe modern bourgeois society that has sprouted from the ruins of feudal society has not done away with class antagonisms. It has but established new classes, new conditions of oppression, new forms of struggle in place of the old ones. Our epoch, the epoch of the bourgeoisie, possesses, however, this distinctive feature: it has simplified the class antagonisms. Society as a whole is more and more splitting up into two great hostile camps, into two great classes, directly facing each other: Bourgeoisie and Proletariat.\n\nFrom the serfs of the Middle Ages sprang the chartered burghers of the earliest towns. From these burgesses the first elements of the bourgeoisie were developed.\n\nThe discovery of America, the rounding of the Cape, opened up fresh ground for the rising bourgeoisie. The East-Indian and Chinese markets, the colonisation of America, trade with the colonies, the increase in the means of exchange and in commodities generally, gave to commerce, to navigation, to industry, an impulse never before known, and thereby, to the revolutionary element in the tottering feudal society, a rapid development.\n\nThe feudal system of industry, under which industrial production was monopolised by closed guilds, now no longer sufficed for the growing wants of the new markets. The manufacturing system took its place. The guild-masters were pushed on one side by the manufacturing middle class; division of labour between the different corporate guilds vanished in the face of division of labour in each single workshop.\n\nMeantime the markets kept ever growing, the demand ever rising. Even manufacture no longer sufficed. Thereupon, steam and machinery revolutionised industrial production. The place of manufacture was taken by the giant, Modern Industry, the place of the industrial middle class, by industrial millionaires, the leaders of whole industrial armies, the modern bourgeois.\n\nModern industry has established the world-market, for which the discovery of America paved the way. This market has given an immense development to commerce, to navigation, to communication by land. This development has, in its time, reacted on the extension of industry; and in proportion as industry, commerce, navigation, railways extended, in the same proportion the bourgeoisie developed, increased its capital, and pushed into the background every class handed down from the Middle Ages.\n\nWe see, therefore, how the modern bourgeoisie is itself the product of a long course of development, of a series of revolutions in the modes of production and of exchange.\n\nEach step in the development of the bourgeoisie was accompanied by a corresponding political advance of that class. An oppressed class under the sway of the feudal nobility, an armed and self-governing association in the mediaeval commune; here independent urban republic (as in Italy and Germany), there taxable \"third estate\" of the monarchy (as in France), afterwards, in the period of manufacture proper, serving either the semi-feudal or the absolute monarchy as a counterpoise against the nobility, and, in fact, corner-stone of the great monarchies in general, the bourgeoisie has at last, since the establishment of Modern Industry and of the world-market, conquered for itself, in the modern representative State, exclusive political sway. The executive of the modern State is but a committee for managing the common affairs of the whole bourgeoisie."; + +static void br_longtext(struct bulk_read *bulk, uint32_t chunk, uint8_t *buffer) +{ + // clean-up request + if (buffer == NULL) { + free(bulk); + return; + } + + memcpy(buffer, longtext+bulk->offset, chunk); +} + +static void bw_dump(struct bulk_write *bulk, const uint8_t *chunk, uint32_t len) +{ + // clean-up request + if (chunk == NULL) { + free(bulk); + return; + } + + dbg("\r\nBulk write at %d, len %d", (int)bulk->offset, (int)len); + PUTSN((const char *) chunk, len); + PUTS("\r\n"); +} + +/** Handle a request message */ +static bool Tst_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + (void)pp; + + struct priv *priv = unit->data; + + switch (command) { + case CMD_PING: + com_respond_ok(frame_id); + break; + + case CMD_ECHO:; + uint32_t len; + const uint8_t *data = pp_tail(pp, &len); + com_respond_buf(frame_id, MSG_SUCCESS, data, len); + break; + + case CMD_BULKREAD:; + struct bulk_read *br = malloc(sizeof(struct bulk_read)); + assert_param(br); + + br->len = (uint32_t) strlen(longtext); + br->frame_id = frame_id; + br->read = br_longtext; + + bulkread_start(comm, br); + break; + + case CMD_BULKWRITE:; + struct bulk_write *bw = malloc(sizeof(struct bulk_write)); + assert_param(bw); + + bw->len = 1024; + bw->frame_id = frame_id; + bw->write = bw_dump; + + bulkwrite_start(comm, bw); + break; + + default: + com_respond_bad_cmd(frame_id); + return false; + } + + return true; +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_TEST = { + .name = "TEST", + .description = "Test unit", + // Settings + .preInit = Tst_preInit, + .cfgLoadBinary = Tst_loadBinary, + .cfgWriteBinary = Tst_writeBinary, + .cfgLoadIni = Tst_loadIni, + .cfgWriteIni = Tst_writeIni, + // Init + .init = Tst_init, + .deInit = Tst_deInit, + // Function + .handleRequest = Tst_handleRequest, +}; diff --git a/units/test/unit_test.h b/units/test/unit_test.h new file mode 100644 index 0000000..9feb5ba --- /dev/null +++ b/units/test/unit_test.h @@ -0,0 +1,13 @@ +// +// Created by MightyPork on 2017/11/25. +// Testing unit that uses most of the protocol to verify the core functionality +// + +#ifndef U_TEST_H +#define U_TEST_H + +#include "unit.h" + +extern const UnitDriver UNIT_TEST; + +#endif //U_TEST_H diff --git a/utils/build_parser.sh b/utils/build_parser.sh old mode 100755 new mode 100644 diff --git a/utils/macro.h b/utils/macro.h index 6057624..924e529 100644 --- a/utils/macro.h +++ b/utils/macro.h @@ -35,11 +35,11 @@ extern "C" { #define KB(size) ((size) * 1024) #ifndef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif #ifndef MAX -#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) #endif #define ROUND_UP(value, boundary) ((value) + ((boundary) - (value)) % (boundary)) diff --git a/utils/stacksmon.c b/utils/stacksmon.c index 01498e7..12078d8 100644 --- a/utils/stacksmon.c +++ b/utils/stacksmon.c @@ -14,7 +14,7 @@ struct stackhandle { uint32_t len; }; -#define STACK_NUM 16 +#define STACK_NUM 8 static uint32_t nextidx = 0; static struct stackhandle stacks[STACK_NUM]; @@ -64,9 +64,9 @@ void stackmon_dump(void) ); } - PUTS("\033[36m>> QUEUES\033[m\r\n"); - PRINTF(" Used slots: \033[33mHP %"PRIu32", LP %"PRIu32"\033[m\r\n", - jobQueHighWaterMarkHP, jobQueHighWaterMarkLP); + PUTS("\033[36m>> JOB QUEUE\033[m\r\n"); + PRINTF(" Used slots: \033[33m%"PRIu32"\033[m\r\n", + jobQueHighWaterMark); PRINTF("\033[1m---------------------------\033[m\r\n\r\n"); }