From 978c4fe08ff4f5264dbdccb59475eb417e60b392 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 31 Oct 2016 09:59:29 +0100 Subject: [PATCH 01/12] Optimize for minimal code size --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c38435d..622fc63 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,8 @@ FREERTOS = $(SDK_PATH)/third_party/FreeRTOS COMMON = $(SDK_PATH)/example/common CPPFLAGS += $(DEFINES) $(INC) -CFLAGS += -ffunction-sections -fdata-sections -Wall -std=c11 -CXXFLAGS += -ffunction-sections -fdata-sections -Wall +CFLAGS += -Os -ffunction-sections -fdata-sections -Wall -std=c11 +CXXFLAGS += -Os -ffunction-sections -fdata-sections -Wall INC += -I$(SDK_PATH) INC += -I$(SDK_PATH)/inc From be04ef12eb96bc3ba108b685d2022e7f007b98e3 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 31 Oct 2016 15:18:59 +0100 Subject: [PATCH 02/12] Remove unnecessary buffer from IPCMessage queue --- src/IPCQueue.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/IPCQueue.h b/src/IPCQueue.h index 7e4253c..1926aa4 100644 --- a/src/IPCQueue.h +++ b/src/IPCQueue.h @@ -40,8 +40,6 @@ IPCMessageConsume(IPCMessage* aMsg); typedef struct { QueueHandle_t mWaitQueue; - - unsigned char mBuffer[1024]; } IPCMessageQueue; int From aec636ce2108edeae8d3c1ac5b767583f2f376b7 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 31 Oct 2016 15:55:11 +0100 Subject: [PATCH 03/12] Add IPCMessageGetBufferLength() for retrieving buffer length from IPC message The length of the IPC message buffer is stored as part of a larger value. The new helper function extracts the length. --- src/IPCQueue.c | 6 ++++++ src/IPCQueue.h | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/IPCQueue.c b/src/IPCQueue.c index 6ec20cd..87b8217 100644 --- a/src/IPCQueue.c +++ b/src/IPCQueue.c @@ -19,6 +19,12 @@ IPCMessageInit(IPCMessage* aMsg) return 0; } +uint32_t +IPCMessageGetBufferLength(const IPCMessage* aMsg) +{ + return aMsg->mStatus & 0x00ffffff; +} + int IPCMessageProduce(IPCMessage* aMsg) { diff --git a/src/IPCQueue.h b/src/IPCQueue.h index 1926aa4..4a4e7d4 100644 --- a/src/IPCQueue.h +++ b/src/IPCQueue.h @@ -27,6 +27,9 @@ typedef struct int IPCMessageInit(IPCMessage* aMsg); +uint32_t +IPCMessageGetBufferLength(const IPCMessage* aMsg); + int IPCMessageProduce(IPCMessage* aMsg); From afc3a7d6908e82e0da9f9650aae4081d87d7ca35 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 31 Oct 2016 10:59:53 +0100 Subject: [PATCH 04/12] Add protocol for signalling consumption from consumer to producer IPC message can optionally transfer buffers from the producer to the consumer. These buffers are not copied while the message is in transit. Consequently, the consumer must inform the producer when it has finished processing the buffer. This patch adds a monitor variable that the producer uses to wait for the competion of the consumers message handling. Once done, the consumer informs the producer to continue. The reply can also return a buffer back to the consumer. The lifetime of this buffer is managed by a higher-level protocol. --- src/IPCQueue.c | 162 ++++++++++++++++++++++++++++++++++++++++++++----- src/IPCQueue.h | 31 +++++++++- src/Producer.c | 24 ++++++-- 3 files changed, 193 insertions(+), 24 deletions(-) diff --git a/src/IPCQueue.c b/src/IPCQueue.c index 87b8217..10d48df 100644 --- a/src/IPCQueue.c +++ b/src/IPCQueue.c @@ -4,6 +4,14 @@ #include "IPCQueue.h" +typedef struct +{ + uint32_t mDWord0; + uint32_t mDWord1; + uint32_t mStatus; + void* mBuffer; +} IPCMessageReply; + /* * IPCMessage */ @@ -11,14 +19,25 @@ int IPCMessageInit(IPCMessage* aMsg) { + aMsg->mMonitor = xQueueCreate(1, sizeof(IPCMessageReply)); + if (!aMsg->mMonitor) { + return -1; + } + aMsg->mDWord0 = 0; aMsg->mDWord1 = 0; - aMsg->mStatus = 0; + aMsg->mStatus = IPC_MESSAGE_STATE_CLEAR; aMsg->mBuffer = NULL; return 0; } +void +IPCMessageUninit(IPCMessage* aMsg) +{ + vQueueDelete(aMsg->mMonitor); +} + uint32_t IPCMessageGetBufferLength(const IPCMessage* aMsg) { @@ -26,24 +45,109 @@ IPCMessageGetBufferLength(const IPCMessage* aMsg) } int -IPCMessageProduce(IPCMessage* aMsg) +IPCMessageProduce(IPCMessage* aMsg, uint32_t aLength, void* aBuffer) +{ + switch (aMsg->mStatus & 0xf0000000) { + case IPC_MESSAGE_STATE_CLEAR: /* fall through */ + case IPC_MESSAGE_STATE_PRODUCED: /* fall through */ + case IPC_MESSAGE_STATE_ERROR: + /* We're good if the message is currently not in transit. */ + break; + case IPC_MESSAGE_STATE_PENDING: /* fall through */ + default: + /* If the message is currently in transit or the status is + * unknown, we don't produce a new one. Better abort here. */ + return -1; + } + + aMsg->mDWord0 = 0; + aMsg->mDWord1 = 0; + aMsg->mStatus = 0; + aMsg->mStatus |= IPC_MESSAGE_STATE_PRODUCED; + aMsg->mStatus |= aLength; + aMsg->mBuffer = aBuffer; + + return 0; +} + +static int +WaitForConsumption(IPCMessage* aMsg, IPCMessageReply* aReply) +{ + uint32_t state = aMsg->mStatus & 0xf0000000; + if (state != IPC_MESSAGE_STATE_PENDING) { + return -1; + } + BaseType_t ret = xQueueReceive(aMsg->mMonitor, aReply, portMAX_DELAY); + if (ret != pdPASS) { + return -1; + }; + return 0; +} + +int +IPCMessageWaitForReply(IPCMessage* aMsg) { - /* TODO: At some point we have to implement efficient IPC with - * large buffers. IPCMessageProduce() will signal the end of the - * message constrcution **on the producer tast.** A produced - * message can be send over over an IPC queue to a consumer task. - * The consumer calls IPCMessageConsume() after it processed the - * buffer. The producer can then release the buffer. */ + IPCMessageReply reply; + int res = WaitForConsumption(aMsg, &reply); + if (res < 0) { + return -1; + }; + aMsg->mDWord0 = reply.mDWord0; + aMsg->mDWord1 = reply.mDWord1; + aMsg->mStatus = reply.mStatus; + aMsg->mBuffer = reply.mBuffer; return 0; } int -IPCMessageConsume(IPCMessage* aMsg) +IPCMessageWaitForConsumption(IPCMessage* aMsg) { - /* TODO: See IPCMessageProduce() */ + IPCMessageReply reply; + int res = WaitForConsumption(aMsg, &reply); + if (res < 0) { + return -1; + }; + aMsg->mStatus &= 0x0fffffff; /* clear pending status */ + return 0; +} + +static int +ConsumeAndReply(IPCMessage* aMsg, const IPCMessageReply* aReply) +{ + BaseType_t res = xQueueSend(aMsg->mMonitor, &aReply, 0); + if (res != pdPASS) { + return -1; + } return 0; } +int +IPCMessageConsumeAndReply(IPCMessage* aMsg, + uint32_t aDWord0, uint32_t aDWord1, + uint32_t aFlags, uint32_t aLength, + void* aBuffer) +{ + IPCMessageReply reply = { + .mDWord0 = aDWord0, + .mDWord1 = aDWord1, + .mStatus = aFlags | aLength, + .mBuffer = aBuffer + }; + return ConsumeAndReply(aMsg, &reply); +} + +int +IPCMessageConsume(IPCMessage* aMsg) +{ + static const IPCMessageReply sReply = { + .mDWord0 = 0, + .mDWord1 = 0, + .mStatus = IPC_MESSAGE_STATE_CLEAR, + .mBuffer = NULL, + }; + return ConsumeAndReply(aMsg, &sReply); +} + /* * IPCMessageQueue */ @@ -58,14 +162,42 @@ IPCMessageQueueInit(IPCMessageQueue* aMsgQueue) return 0; } +void +IPCMessageQueueUninit(IPCMessageQueue* aMsgQueue) +{ + vQueueDelete(aMsgQueue->mWaitQueue); +} + int IPCMessageQueueConsume(IPCMessageQueue* aMsgQueue, IPCMessage* aMsg) { - BaseType_t res = xQueueSend(aMsgQueue->mWaitQueue, aMsg, 0); - if (res != pdPASS){ - return -1; - } - return 0; + uint32_t status = aMsg->mStatus; + + switch (status & 0xf0000000) { + case IPC_MESSAGE_STATE_PRODUCED: /* fall through */ + /* We're good if the message has been produced correctly. */ + break; + case IPC_MESSAGE_STATE_CLEAR: /* fall through */ + case IPC_MESSAGE_STATE_PENDING: /* fall through */ + case IPC_MESSAGE_STATE_ERROR: /* fall through */ + default: + /* In any other case, the message is probably not ready for + * consumption. Better abort here. */ + return -1; + } + + aMsg->mStatus &= 0x0fffffff; + aMsg->mStatus |= IPC_MESSAGE_STATE_PENDING; + + BaseType_t res = xQueueSend(aMsgQueue->mWaitQueue, aMsg, 0); + if (res != pdPASS){ + goto err_xQueueSend; + } + return 0; + +err_xQueueSend: + aMsg->mStatus = status; + return -1; } int diff --git a/src/IPCQueue.h b/src/IPCQueue.h index 4a4e7d4..7eb3362 100644 --- a/src/IPCQueue.h +++ b/src/IPCQueue.h @@ -13,25 +13,50 @@ * IPCMessage */ +enum IPCMessageStatus { + IPC_MESSAGE_STATE_CLEAR = 0x00000000, + IPC_MESSAGE_STATE_PRODUCED = 0x10000000, + IPC_MESSAGE_STATE_PENDING = 0x20000000, + IPC_MESSAGE_STATE_ERROR = 0x30000000 +}; + typedef struct { + /* Internal monitor for signalling */ + QueueHandle_t mMonitor; + /* Two dword for data transfers. */ uint32_t mDWord0; uint32_t mDWord1; - /* Message flags [31:24] and buffer length [23:0] */ + /* Message state [31:28], flags [27:24], and buffer length [23:0] */ uint32_t mStatus; /* Message buffer */ - const void* mBuffer; + void* mBuffer; } IPCMessage; int IPCMessageInit(IPCMessage* aMsg); +void +IPCMessageUninit(IPCMessage* aMsg); + uint32_t IPCMessageGetBufferLength(const IPCMessage* aMsg); int -IPCMessageProduce(IPCMessage* aMsg); +IPCMessageProduce(IPCMessage* aMsg, uint32_t aLength, void* aBuffer); + +int +IPCMessageWaitForReply(IPCMessage* aMsg); + +int +IPCMessageWaitForConsumption(IPCMessage* aMsg); + +int +IPCMessageConsumeAndReply(IPCMessage* aMsg, + uint32_t aDWord0, uint32_t aDWord1, + uint32_t aFlags, uint32_t aLength, + void* aBuffer); int IPCMessageConsume(IPCMessage* aMsg); diff --git a/src/Producer.c b/src/Producer.c index 42b1742..2605457 100644 --- a/src/Producer.c +++ b/src/Producer.c @@ -19,22 +19,34 @@ Run(ProducerTask* aProducer) "This"," is"," a"," SensorWeb"," device"," by"," Mozilla.\n\r" }; + IPCMessage msg; + int res = IPCMessageInit(&msg); + if (res < 0) { + return; + } + for (unsigned long i = 0;; i = (i + 1) % ArrayLength(sMessage)) { - IPCMessage msg; - int res = IPCMessageInit(&msg); + + res = IPCMessageProduce(&msg, strlen(sMessage[i]) + 1, (void*)sMessage[i]); if (res < 0) { return; } - msg.mBuffer = sMessage[i]; - msg.mStatus = strlen(msg.mBuffer) + 1; - - IPCMessageProduce(&msg); res = IPCMessageQueueConsume(aProducer->mSendQueue, &msg); if (res < 0) { return; } + vTaskDelay(TicksOfMSecs(200)); + + /* While we have been waiting in vTaskDelay(), the consumer + * probably processed our message. Waiting for consumption should + * have the reply ready. + */ + res = IPCMessageWaitForConsumption(&msg); + if (res < 0) { + return; + } } } From 0ebd911101a0e357f8377da6c428f8ef8d931bc5 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 31 Oct 2016 13:34:17 +0100 Subject: [PATCH 05/12] Add NOWAIT flag to prevent reply from consumer The consumer of a message will reply to the producer when it has finished processing a message. This additional IPC can be avoided by setting the flag NOWAIT on the outbound message. Both, consumer and producer, will act as if the reply has been send implicitly. --- src/IPCQueue.c | 29 ++++++++++++++++++++++++++++- src/IPCQueue.h | 2 ++ src/Producer.c | 7 ++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/IPCQueue.c b/src/IPCQueue.c index 10d48df..03df28f 100644 --- a/src/IPCQueue.c +++ b/src/IPCQueue.c @@ -87,6 +87,11 @@ WaitForConsumption(IPCMessage* aMsg, IPCMessageReply* aReply) int IPCMessageWaitForReply(IPCMessage* aMsg) { + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + /* The consumer won't reply to us, so we're + * returning an error here. */ + return -1; + } IPCMessageReply reply; int res = WaitForConsumption(aMsg, &reply); if (res < 0) { @@ -102,6 +107,13 @@ IPCMessageWaitForReply(IPCMessage* aMsg) int IPCMessageWaitForConsumption(IPCMessage* aMsg) { + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + /* The consumer won't send a reply. We simply reset + * the message state and succeed silently. */ + aMsg->mStatus &= 0x0fffffff; + aMsg->mStatus |= IPC_MESSAGE_STATE_CLEAR; + return 0; + } IPCMessageReply reply; int res = WaitForConsumption(aMsg, &reply); if (res < 0) { @@ -127,6 +139,11 @@ IPCMessageConsumeAndReply(IPCMessage* aMsg, uint32_t aFlags, uint32_t aLength, void* aBuffer) { + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + /* We cannot reply because the producer doesn't + * wait for the consumption of the message. */ + return -1; + } IPCMessageReply reply = { .mDWord0 = aDWord0, .mDWord1 = aDWord1, @@ -139,6 +156,9 @@ IPCMessageConsumeAndReply(IPCMessage* aMsg, int IPCMessageConsume(IPCMessage* aMsg) { + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + return 0; /* Silently succeed */ + } static const IPCMessageReply sReply = { .mDWord0 = 0, .mDWord1 = 0, @@ -186,8 +206,15 @@ IPCMessageQueueConsume(IPCMessageQueue* aMsgQueue, IPCMessage* aMsg) return -1; } + /* Usually the consumer will send a reply after the message has + * been processed. Except if we set the NOWAIT flag. In this case + * we reset the message state to CLEAR. */ aMsg->mStatus &= 0x0fffffff; - aMsg->mStatus |= IPC_MESSAGE_STATE_PENDING; + if (aMsg->mStatus & IPC_MESSAGE_FLAG_NOWAIT) { + aMsg->mStatus |= IPC_MESSAGE_STATE_CLEAR; + } else { + aMsg->mStatus |= IPC_MESSAGE_STATE_PENDING; + } BaseType_t res = xQueueSend(aMsgQueue->mWaitQueue, aMsg, 0); if (res != pdPASS){ diff --git a/src/IPCQueue.h b/src/IPCQueue.h index 7eb3362..e2ddc8c 100644 --- a/src/IPCQueue.h +++ b/src/IPCQueue.h @@ -14,6 +14,8 @@ */ enum IPCMessageStatus { + /* Don't wait for consumer and don't signal consumption to producer. */ + IPC_MESSAGE_FLAG_NOWAIT = 0x01000000, IPC_MESSAGE_STATE_CLEAR = 0x00000000, IPC_MESSAGE_STATE_PRODUCED = 0x10000000, IPC_MESSAGE_STATE_PENDING = 0x20000000, diff --git a/src/Producer.c b/src/Producer.c index 2605457..ac1db66 100644 --- a/src/Producer.c +++ b/src/Producer.c @@ -5,7 +5,6 @@ #include "Producer.h" #include -#include #include "IPCQueue.h" #include "Ptr.h" @@ -31,6 +30,8 @@ Run(ProducerTask* aProducer) if (res < 0) { return; } + /* no need to synchronize over static constant buffer */ + msg.mStatus |= IPC_MESSAGE_FLAG_NOWAIT; res = IPCMessageQueueConsume(aProducer->mSendQueue, &msg); if (res < 0) { @@ -40,8 +41,8 @@ Run(ProducerTask* aProducer) vTaskDelay(TicksOfMSecs(200)); /* While we have been waiting in vTaskDelay(), the consumer - * probably processed our message. Waiting for consumption should - * have the reply ready. + * probably processed our message. Waiting for consumption is + * only a formality here, as we set the NOWAIT flag. */ res = IPCMessageWaitForConsumption(&msg); if (res < 0) { From 474726cf0a405ee91479f8cccdd336dff76b0b9f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 2 Nov 2016 11:49:39 +0100 Subject: [PATCH 06/12] Document IPC mechanisms in a tutorial --- src/IPCQueue.h | 133 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/IPCQueue.h b/src/IPCQueue.h index e2ddc8c..83fa4f3 100644 --- a/src/IPCQueue.h +++ b/src/IPCQueue.h @@ -4,6 +4,139 @@ #pragma once +/* + * Inter-Process Communication is one of the building blocks of the + * firmware. + * + * IPC is performed between tasks (not processes) by exchanging IPC + * messages. A task can either send messages, receive messages, or + * both. + * + * IPC messages + * ------------ + * + * An IPC message is represented by the data structure IPCMessage. Each + * message can transfer two 32-bit values and optionally an external + * buffer. + * + * Initialize the message structure by calling IPCMessageInit(). To + * release an initialized message's internal resource, call + * IPCMessageUninit() + * + * IPCMessage msg; + * IPCMessageInit(&msg); + * // do IPC + * IPCMessageUninit(&msg); + * + * The init function returns a negative value on errors, or 0 on + * success. In the examples, we leave out error checking, but don't do + * so in production code. + * + * After you initialized the message, you have to 'produce' it, give + * it to a consumer, and wait for consumption. The produce step is + * performed by IPCMessageProduce(), the waiting step is performed by + * IPCMessageWaitForConsumption(). + * + * const char buf[] = "Hello world"; + * IPCMessage msg; + * IPCMessageInit(&msg); + * IPCMessageProduce(&msg, sizeof(buf), (void*)buf); + * // do message setup and actual IPC with the consumer + * IPCMessageWaitForConsumption(&msg) + * IPCMessageUninit(&msg); + * + * IPCMessageProduce() takes the message as its argument, and an optional + * buffer plus length. The buffer's address is attached to the message and + * received by the consumer. Pass 0 and NULL if you don't want to transfer + * a buffer. IPC messages do not transfer ownership of the message or the + * attached buffer! The message, the buffer and the buffer's content must + * be valid until the consumer has finished processing the message. + * + * Two additional values can be transfered in the IPC message. + * + * msg.mDWord0 = (uint32_t)1ul; + * msg.mDWord1 = (uint32_t)-1l; + * + * After the IPC message has been given to the consumer, which is described + * in the next section, IPCMessageWaitForConsumption() allows to wait for the + * completion of the consumer's side. Instead of only consuming a message, + * producers have the option of returning a reply. Replace the call to + * IPCMessageWaitForConsumption() with IPCMessageWaitForReply() to receive + * the reply. The reply data can contain two 32-bit values and optionally a + * buffer. Again, ownership of the buffer is not transfered. + * + * An initialized IPC message can be used throughout multiple produce- + * consume cyles. There's no requirement to uninitialize and re-initialize + * after consumption. + * + * IPC is performed asynchronously. Both, producer and consumer, continue + * independently. Only calling IPCMessageWaitForConsumption() or + * IPCMessageWaitForReply() will synchronize them. Once these calls return, + * it's safe to release the message, buffer, and buffer content. + * + * There's one shortcut through the produce-consume cycle. If you only want + * to send a message to a consumer and don't have to care about a reply or + * buffer lifetime, you can set NOWAIT on the produced message. + * + * msg.mStatus |= IPC_MESSAGE_FLAG_NOWAIT + * + * NOWAIT is an optimization for these single-shot use cases. The producer + * will not reply after consuming the message, and producers will not wait + * for it. Calling the related functions is safe, but there's no reqirement + * to do so. + * + * Sending a message + * ----------------- + * + * Each consumer task waits for messages on a queue of type IPCMessageQueue. + * To insert an IPC message into the queue, call IPCMessageQueueConsume(). This + * will wake up the waiting consumer. + * + * extern IPCMessageQueue msgQueue; + * + * const char buf[] = "Hello world"; + * IPCMessage msg; + * IPCMessageInit(&msg); + * IPCMessageProduce(&msg, sizeof(buf), (void*)buf); + * // do message setup + * IPCMessageQueueConsume(&msgQueue, &msg); + * IPCMessageWaitForConsumption(&msg) + * IPCMessageUninit(&msg); + * + * Calls to IPCMessageQuueConsume() are meant to complete quickly. But + * if there's lots of contention on the queue, or the consumer is slow, + * the function might block until there's space available at the end of + * the message queue. + * + * Receiving a message + * ------------------- + * + * The consumer task waits for incomming messages on an IPCMessageQueue + * until a message arrives. Message queues are initialized with a call to + * IPCMessageQueueInit(). + * + * IPCMessageQueue msgQueue; + * IPCMessageQueueInit(&msgQueue); + * + * Waiting if performed by IPCMessageQueueWait(). The function's message + * argument returns the received message. After processing the message, call + * IPCMessageConsume() to signal the producer that you're done. + * + * IPCMessageQueue msgQueue; + * IPCMessageQueueInit(&msgQueue); + * + * while (1) { + * IPCMessage msg; + * IPCMessageQueueWait(&msgQueue, &msg); + * // process message + * IPCMessageConsume(&msg); + * } + * + * To send a reply to the producer, replace the call to IPCMessageConsume() + * with IPCMessageReply(). This function allows to transfer two 32-bit values, + * and a buffer to the producer. Again, buffer ownership is not transfered. + */ + #include #include From 2a127bc0e6d46bd6b8eab3ef89890c56ef382d1d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 31 Oct 2016 14:31:09 +0100 Subject: [PATCH 07/12] Add a printf-style interfaces for formatted output --- Makefile | 1 + src/FormattedIO.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++ src/FormattedIO.h | 13 ++++++++++ src/Producer.c | 37 +++----------------------- src/Producer.h | 7 +---- src/Serial.c | 44 +++++++++++++++++++++++++++++-- src/Serial.h | 20 +++++--------- src/main.c | 9 ++----- 8 files changed, 134 insertions(+), 63 deletions(-) create mode 100644 src/FormattedIO.c create mode 100644 src/FormattedIO.h diff --git a/Makefile b/Makefile index 622fc63..9a5defd 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,7 @@ OBJDIR ?= obj OBJ := $(addprefix $(OBJDIR)/src/, \ ApplicationHooks.o \ + FormattedIO.o \ IPCQueue.o \ main.o \ pinmux.o \ diff --git a/src/FormattedIO.c b/src/FormattedIO.c new file mode 100644 index 0000000..579bf5f --- /dev/null +++ b/src/FormattedIO.c @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FormattedIO.h" +#include +#include "Serial.h" + +static int +PrintIPC(uint32_t aLength, void* aBuffer) +{ + IPCMessageQueue* queue = GetSerialOutQueue(); + if (!queue) { + return -1; + } + IPCMessage msg; + int res = IPCMessageInit(&msg); + if (res < 0) { + return -1; + } + res = IPCMessageProduce(&msg, aLength, aBuffer); + if (res < 0) { + goto err; + } + res = IPCMessageQueueConsume(queue, &msg); + if (res < 0) { + goto err; + } + res = IPCMessageWaitForConsumption(&msg); + if (res < 0) { + goto err; + } + IPCMessageUninit(&msg); + return res; + +err: + IPCMessageUninit(&msg); + return -1; +} + +/* + * Libc-like interfaces for easy usage. + */ + +int +Print(const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + int res = VPrint(fmt, ap); + va_end(ap); + + return res; +} + +int +VPrint(const char* fmt, va_list ap) +{ + char buf[128]; + int res = vsnprintf(buf, sizeof(buf), fmt, ap); + if (res < 0) { + return -1; + } + return PrintIPC(res, buf); +} diff --git a/src/FormattedIO.h b/src/FormattedIO.h new file mode 100644 index 0000000..e877e5c --- /dev/null +++ b/src/FormattedIO.h @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include + +int +Print(const char* fmt, ...); + +int +VPrint(const char* fmt, va_list ap); diff --git a/src/Producer.c b/src/Producer.c index ac1db66..a880aef 100644 --- a/src/Producer.c +++ b/src/Producer.c @@ -3,11 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Producer.h" - -#include - -#include "IPCQueue.h" #include "Ptr.h" +#include "FormattedIO.h" #include "Task.h" #include "Ticks.h" @@ -18,36 +15,9 @@ Run(ProducerTask* aProducer) "This"," is"," a"," SensorWeb"," device"," by"," Mozilla.\n\r" }; - IPCMessage msg; - int res = IPCMessageInit(&msg); - if (res < 0) { - return; - } - for (unsigned long i = 0;; i = (i + 1) % ArrayLength(sMessage)) { - - res = IPCMessageProduce(&msg, strlen(sMessage[i]) + 1, (void*)sMessage[i]); - if (res < 0) { - return; - } - /* no need to synchronize over static constant buffer */ - msg.mStatus |= IPC_MESSAGE_FLAG_NOWAIT; - - res = IPCMessageQueueConsume(aProducer->mSendQueue, &msg); - if (res < 0) { - return; - } - + Print("%s", sMessage[i]); vTaskDelay(TicksOfMSecs(200)); - - /* While we have been waiting in vTaskDelay(), the consumer - * probably processed our message. Waiting for consumption is - * only a formality here, as we set the NOWAIT flag. - */ - res = IPCMessageWaitForConsumption(&msg); - if (res < 0) { - return; - } } } @@ -65,9 +35,8 @@ TaskEntryPoint(void* aParam) } int -ProducerTaskInit(ProducerTask* aProducer, IPCMessageQueue* aSendQueue) +ProducerTaskInit(ProducerTask* aProducer) { - aProducer->mSendQueue = aSendQueue; aProducer->mTask = NULL; return 0; diff --git a/src/Producer.h b/src/Producer.h index d909d3e..b588a0e 100644 --- a/src/Producer.h +++ b/src/Producer.h @@ -5,20 +5,15 @@ #pragma once #include -#include #include -#include "IPCQueue.h" - typedef struct { - IPCMessageQueue* mSendQueue; - TaskHandle_t mTask; } ProducerTask; int -ProducerTaskInit(ProducerTask* aProducer, IPCMessageQueue* aSendQueue); +ProducerTaskInit(ProducerTask* aProducer); int ProducerTaskSpawn(ProducerTask* aProducer); diff --git a/src/Serial.c b/src/Serial.c index c1bb989..5f53c24 100644 --- a/src/Serial.c +++ b/src/Serial.c @@ -4,10 +4,23 @@ #include "Serial.h" +#include +#include #include #include "Task.h" +/* + * Serial output + */ + +typedef struct +{ + IPCMessageQueue mRecvQueue; + + TaskHandle_t mTask; +} SerialOutTask; + static void Run(SerialOutTask* aSerialOut) { @@ -39,7 +52,7 @@ TaskEntryPoint(void* aParam) vTaskSuspend(serialOut->mTask); } -int +static int SerialOutTaskInit(SerialOutTask* aSerialOut) { int res = IPCMessageQueueInit(&aSerialOut->mRecvQueue); @@ -51,7 +64,7 @@ SerialOutTaskInit(SerialOutTask* aSerialOut) return 0; } -int +static int SerialOutTaskSpawn(SerialOutTask* aSerialOut) { BaseType_t res = xTaskCreate(TaskEntryPoint, "serial-out", @@ -62,3 +75,30 @@ SerialOutTaskSpawn(SerialOutTask* aSerialOut) } return 0; } + +/* + * Public interfaces + */ + +static SerialOutTask sSerialOutTask; + +int +SerialInit() +{ + /* + * Create the output task + */ + if (SerialOutTaskInit(&sSerialOutTask) < 0) { + return -1; + } + if (SerialOutTaskSpawn(&sSerialOutTask) < 0) { + return -1; + } + return 0; +} + +IPCMessageQueue* +GetSerialOutQueue() +{ + return &sSerialOutTask.mRecvQueue; +} diff --git a/src/Serial.h b/src/Serial.h index 90aa9cd..8438143 100644 --- a/src/Serial.h +++ b/src/Serial.h @@ -4,21 +4,13 @@ #pragma once -#include -#include -#include - #include "IPCQueue.h" -typedef struct -{ - IPCMessageQueue mRecvQueue; - - TaskHandle_t mTask; -} SerialOutTask; - int -SerialOutTaskInit(SerialOutTask* aSerialOut); +SerialInit(void); -int -SerialOutTaskSpawn(SerialOutTask* aSerialOut); +/* Returns the message queue for output of over the serial line. This + * is a singleton. + */ +IPCMessageQueue* +GetSerialOutQueue(void); diff --git a/src/main.c b/src/main.c index 5a7d562..1dc2c2a 100644 --- a/src/main.c +++ b/src/main.c @@ -37,12 +37,7 @@ main(void) * Create the output task */ - static SerialOutTask serialOutTask; - - if (SerialOutTaskInit(&serialOutTask) < 0) { - return EXIT_FAILURE; - } - if (SerialOutTaskSpawn(&serialOutTask) < 0) { + if (SerialInit() < 0) { return EXIT_FAILURE; } @@ -52,7 +47,7 @@ main(void) static ProducerTask producerTask; - if (ProducerTaskInit(&producerTask, &serialOutTask.mRecvQueue) < 0) { + if (ProducerTaskInit(&producerTask) < 0) { return EXIT_FAILURE; } if (ProducerTaskSpawn(&producerTask) < 0) { From 963ace02c31bba2c36e9a066294c707645f9fde8 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 31 Oct 2016 16:04:49 +0100 Subject: [PATCH 08/12] Initialize serial port from main task Instead of initializing the serial port from the serial-out task, it's safer to initialize the serial port from the main task before doing any related work. --- src/Serial.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Serial.c b/src/Serial.c index 5f53c24..cd248b8 100644 --- a/src/Serial.c +++ b/src/Serial.c @@ -24,7 +24,6 @@ typedef struct static void Run(SerialOutTask* aSerialOut) { - InitTerm(); ClearTerm(); for (;;) { @@ -85,6 +84,8 @@ static SerialOutTask sSerialOutTask; int SerialInit() { + InitTerm(); + /* * Create the output task */ From 353606b88dbe8165f200b347931cda0eac09f8d1 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 1 Nov 2016 12:33:29 +0100 Subject: [PATCH 09/12] Provide SerialPut{Char,String}(), which output characters to the serial port The new functions SerialPutChar() and SerialPutString() output charcters directly to the serial port. This is useful for debugging or in ISRs, where IPC is not available. Don't use them in regular code. --- src/Serial.c | 25 +++++++++++++++++++++++-- src/Serial.h | 6 ++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Serial.c b/src/Serial.c index cd248b8..4b41ab5 100644 --- a/src/Serial.c +++ b/src/Serial.c @@ -4,9 +4,15 @@ #include "Serial.h" +#include +#include +#include +#include +#include +#include + #include #include -#include #include "Task.h" @@ -21,6 +27,20 @@ typedef struct TaskHandle_t mTask; } SerialOutTask; +void +SerialPutChar(int c) +{ + MAP_UARTCharPut(CONSOLE, c); +} + +void +SerialPutString(size_t aLength, const char* aString) +{ + for (const char* end = aString + aLength; aString < end; ++aString) { + SerialPutChar(*aString); + } +} + static void Run(SerialOutTask* aSerialOut) { @@ -32,7 +52,8 @@ Run(SerialOutTask* aSerialOut) if (res < 0) { return; } - Report(msg.mBuffer); + + SerialPutString(IPCMessageGetBufferLength(&msg), msg.mBuffer); IPCMessageConsume(&msg); } diff --git a/src/Serial.h b/src/Serial.h index 8438143..3d661e1 100644 --- a/src/Serial.h +++ b/src/Serial.h @@ -9,6 +9,12 @@ int SerialInit(void); +void +SerialPutChar(int c); + +void +SerialPutString(size_t aLength, const char* aString); + /* Returns the message queue for output of over the serial line. This * is a singleton. */ From a71a6fffd05d2abd954927c4261c7841eb1e507a Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 2 Nov 2016 14:08:34 +0100 Subject: [PATCH 10/12] Imported StrPrintf utilities ...from https://github.com/dhylands/projects/commit/8572079daaa10271aeeeed0ebc148fad15b338b5 The imported code is in the Public Domain. --- external/str/StrPrintf.c | 721 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 721 insertions(+) create mode 100644 external/str/StrPrintf.c diff --git a/external/str/StrPrintf.c b/external/str/StrPrintf.c new file mode 100644 index 0000000..dfbca07 --- /dev/null +++ b/external/str/StrPrintf.c @@ -0,0 +1,721 @@ +/**************************************************************************** +* +* Since this code originated from code which is public domain, I +* hereby declare this code to be public domain as well. +* +* Dave Hylands - dhylands@gmail.com +* +****************************************************************************/ +/** +* +* @file StrPrintf.cpp +* +* @brief Implementation of a re-entrant printf function. +* +* Implements a reentrant version of the printf function. Also allows a +* function pointer to be provided to perform the actual output. +* +* This version of printf was taken from +* +* http://www.efgh.com/software/gprintf.htm +* +* This software was posted by the author as being in the "public" domain. +* I've taken the original gprintf.txt and made some minor revisions. +* +****************************************************************************/ + +/** +* @defgroup StrPrintf String Formatting +* @ingroup Str +*/ +/** +* @defgroup StrPrintfInternal String Formatting Internals +* @ingroup StrPrintf +*/ + +/* ---- Include Files ---------------------------------------------------- */ + +#include "Str.h" +#include +#include + +#if defined( AVR ) + +#undef StrPrintf +#undef vStrPrintf + +#undef StrXPrintf +#undef vStrXPrintf + +#define StrPrintf StrPrintf_P +#define vStrPrintf vStrPrintf_P + +#define StrXPrintf StrXPrintf_P +#define vStrXPrintf vStrXPrintf_P + +#else + +#define pgm_read_byte( addr ) *addr + +#endif + +/* ---- Public Variables ------------------------------------------------- */ +/* ---- Private Constants and Types -------------------------------------- */ + +/** + * @addtogroup StrPrintfInternal + * @{ + */ + +/** + * Controls a variety of output options. + */ + +typedef enum +{ + NO_OPTION = 0x00, /**< No options specified. */ + MINUS_SIGN = 0x01, /**< Should we print a minus sign? */ + RIGHT_JUSTIFY = 0x02, /**< Should field be right justified? */ + ZERO_PAD = 0x04, /**< Should field be zero padded? */ + CAPITAL_HEX = 0x08 /**< Did we encounter %X? */ + +} FmtOption; + +/** @def IsOptionSet( p, x ) Determines if an option has been set. */ +/** @def IsOptionClear( p, x ) Determines if an option is not set. */ +/** @def SetOption( p, x ) Sets an option. */ +/** @def ClearOption( p, x ) Unsets an option. */ + +#define IsOptionSet( p, x ) (( (p)->options & (x)) != 0 ) +#define IsOptionClear( p, x ) (( (p)->options & (x)) == 0 ) +#define SetOption( p, x ) (p)->options = (FmtOption)((p)->options | (x)) +#define ClearOption( p, x ) (p)->options = (FmtOption)((p)->options & ~(x)) + +/** + * Internal structure which is used to allow vStrXPrintf() to be reentrant. + */ + +typedef struct +{ + /** Number of characters output so far. */ + int numOutputChars; + + /** Options determined from parsing format specification. */ + FmtOption options; + + /** Minimum number of characters to output. */ + short minFieldWidth; + + /** The exact number of characters to output. */ + short editedStringLen; + + /** The number of leading zeros to output. */ + short leadingZeros; + + /** The function to call to perform the actual output. */ + StrXPrintfFunc outFunc; + + /** Parameter to pass to the output function. */ + void *outParm; + +} Parameters; + +/** + * Internal structure used by vStrPrintf() . + */ +typedef struct +{ + char *str; /**< Buffer to store results into. */ + int maxLen; /**< Maximum number of characters which can be stored. */ + +} StrPrintfParms; + +/* ---- Private Variables ------------------------------------------------ */ +/* ---- Private Function Prototypes -------------------------------------- */ + +static void OutputChar( Parameters *p, int c ); +static void OutputField( Parameters *p, char *s ); +static int StrPrintfFunc( void *outParm, int ch ); + +/** @} */ + +/* ---- Functions -------------------------------------------------------- */ + +/** + * @addtogroup StrPrintf + * @{ + */ + +/***************************************************************************/ +/** +* Writes formatted data into a user supplied buffer. +* +* @param outStr (out) Place to store the formatted string. +* @param maxLen (in) Max number of characters to write into @a outStr. +* @param fmt (in) Format string (see vStrXPrintf() for sull details). +*/ + +int StrPrintf( char *outStr, int maxLen, const char *fmt, ... ) +{ + int rc; + va_list args; + + va_start( args, fmt ); + rc = vStrPrintf( outStr, maxLen, fmt, args ); + va_end( args ); + + return rc; + +} // StrPrintf + +/***************************************************************************/ +/** +* Generic printf function which writes formatted data by calling a user +* supplied function. +* +* @a outFunc will be called to output each character. If @a outFunc returns +* a number >= 0, then StrXPrintf will continue to call @a outFunc with +* additional characters. +* +* If @a outFunc returns a negative number, then StrXPrintf will stop +* calling @a outFunc and will return the non-negative return value. +* +* @param outFunc (in) Pointer to function to call to do the actual output. +* @param outParm (in) Passed to @a outFunc. +* @param fmt (in) Format string (see vStrXPrintf() for sull details). +* +*/ + +int StrXPrintf( StrXPrintfFunc outFunc, void *outParm, const char *fmt, ... ) +{ + int rc; + va_list args; + + va_start( args, fmt ); + rc = vStrXPrintf( outFunc, outParm, fmt, args ); + va_end( args ); + + return rc; + +} // StrxPrintf + +/***************************************************************************/ +/** +* Writes formatted data into a user supplied buffer. +* +* @param outStr (out) Place to store the formatted string. +* @param maxLen (in) Max number of characters to write into @a outStr. +* @param fmt (in) Format string (see vStrXPrintf() for sull details). +* @param args (in) Arguments in a format compatible with va_arg(). +*/ + +int vStrPrintf( char *outStr, int maxLen, const char *fmt, va_list args ) +{ + StrPrintfParms strParm; + + strParm.str = outStr; + strParm.maxLen = maxLen - 1; /* Leave space for temrinating null char */ + + return vStrXPrintf( StrPrintfFunc, &strParm, fmt, args ); + +} // vStrPrintf + +/***************************************************************************/ +/** +* Generic, reentrant printf function. This is the workhorse of the StrPrintf +* functions. +* +* @a outFunc will be called to output each character. If @a outFunc returns +* a number >= 0, then vStrXPrintf will continue to call @a outFunc with +* additional characters. +* +* If @a outFunc returns a negative number, then vStrXPrintf will stop calling +* @a outFunc and will return the non-negative return value. +* +* The format string @a fmt consists of ordinary characters, escape +* sequences, and format specifications. The ordinary characters and escape +* sequences are output in their order of appearance. Format specifications +* start with a percent sign (%) and are read from left to right. When +* the first format specification (if any) is encountered, it converts the +* value of the first argument after @a fmt and outputs it accordingly. +* The second format specification causes the second argument to be +* converted and output, and so on. If there are more arguments than there +* are format specifications, the extra arguments are ignored. The +* results are undefined if there are not enough arguments for all the +* format specifications. +* +* A format specification has optional, and required fields, in the following +* form: +* +* %[flags][width][.precision][l]type +* +* Each field of the format specification is a single character or a number +* specifying a particular format option. The simplest format specification +* contains only the percent sign and a @b type character (for example %s). +* If a percent sign is followed by a character that has no meaning as a +* format field, the character is sent to the output function. For example, +* to print a percent-sign character, use %%. +* +* The optional fields, which appear before the type character, control +* other aspects of the formatting, as follows: +* +* @b flags may be one of the following: +* +* - - (minus sign) left align the result within the given field width. +* - 0 (zero) Zeros are added until the minimum width is reached. +* +* @b width may be one of the following: +* - a number specifying the minimum width of the field +* - * (asterick) means that an integer taken from the argument list will +* be used to provide the width. The @a width argument must precede the +* value being formatted in the argument list. +* +* @b precision may be one of the following: +* - a number +* - * (asterick) means that an integer taken from the argument list will +* be used to provide the precision. The @a precision argument must +* precede the value being formatted in the argument list. +* +* The interpretation of @a precision depends on the type of field being +* formatted: +* - For b, d, o, u, x, X, the precision specifies the minimum number of +* digits that will be printed. If the number of digits in the argument +* is less than @a precision, the output value is padded on the left with +* zeros. The value is not truncated when the number of digits exceeds +* @a prcision. +* - For s, the precision specifies the maximum number of characters to be +* printed. +* +* The optional type modifier l (lowercase ell), may be used to specify +* that the argument is a long argument. This makes a difference on +* architectures where the sizeof an int is different from the sizeof a long. +* +* @b type causes the output to be formatted as follows: +* - b Unsigned binary integer. +* - c Character. +* - d Signed decimal integer. +* - o Unsigned octal integer. +* - s Null terminated character string. +* - u Unsigned Decimal integer. +* - x Unsigned hexadecimal integer, using "abcdef". +* - X Unsigned hexadecimal integer, using "ABCDEF". +* +* @param outFunc (in) Pointer to function to call to output a character. +* @param outParm (in) Passed to @a outFunc. +* @param fmt (in) Format string (ala printf, descrtibed above). +* @param args (in) Variable length list of arguments. +* +* @return The number of characters successfully output, or a negative number +* if an error occurred. +*/ + +int vStrXPrintf +( + StrXPrintfFunc outFunc, + void *outParm, + const char *fmt, + va_list args +) +{ + Parameters p; + char controlChar; + + p.numOutputChars = 0; + p.outFunc = outFunc; + p.outParm = outParm; + + controlChar = pgm_read_byte( fmt++ ); + + while ( controlChar != '\0' ) + { + if ( controlChar == '%' ) + { + short precision = -1; + short longArg = 0; + short base = 0; + + controlChar = pgm_read_byte( fmt++ ); + p.minFieldWidth = 0; + p.leadingZeros = 0; + p.options = NO_OPTION; + + SetOption( &p, RIGHT_JUSTIFY ); + + /* + * Process [flags] + */ + + if ( controlChar == '-' ) + { + ClearOption( &p, RIGHT_JUSTIFY ); + controlChar = pgm_read_byte( fmt++ ); + } + + if ( controlChar == '0' ) + { + SetOption( &p, ZERO_PAD ); + controlChar = pgm_read_byte( fmt++ ); + } + + /* + * Process [width] + */ + + if ( controlChar == '*' ) + { + p.minFieldWidth = (short)va_arg( args, int ); + controlChar = pgm_read_byte( fmt++ ); + } + else + { + while (( '0' <= controlChar ) && ( controlChar <= '9' )) + { + p.minFieldWidth = + p.minFieldWidth * 10 + controlChar - '0'; + controlChar = pgm_read_byte( fmt++ ); + } + } + + /* + * Process [.precision] + */ + + if ( controlChar == '.' ) + { + controlChar = pgm_read_byte( fmt++ ); + if ( controlChar == '*' ) + { + precision = (short)va_arg( args, int ); + controlChar = pgm_read_byte( fmt++ ); + } + else + { + precision = 0; + while (( '0' <= controlChar ) && ( controlChar <= '9' )) + { + precision = precision * 10 + controlChar - '0'; + controlChar = pgm_read_byte( fmt++ ); + } + } + } + + /* + * Process [l] + */ + + if ( controlChar == 'l' ) + { + longArg = 1; + controlChar = pgm_read_byte( fmt++ ); + } + + /* + * Process type. + */ + + if ( controlChar == 'd' ) + { + base = 10; + } + else + if ( controlChar == 'x' ) + { + base = 16; + } + else + if ( controlChar == 'X' ) + { + base = 16; + SetOption( &p, CAPITAL_HEX ); + } + else + if ( controlChar == 'u' ) + { + base = 10; + } + else + if ( controlChar == 'o' ) + { + base = 8; + } + else + if ( controlChar == 'b' ) + { + base = 2; + } + else + if ( controlChar == 'c' ) + { + base = -1; + ClearOption( &p, ZERO_PAD ); + } + else + if ( controlChar == 's' ) + { + base = -2; + ClearOption( &p, ZERO_PAD ); + } + + if ( base == 0 ) /* invalid conversion type */ + { + if ( controlChar != '\0' ) + { + OutputChar( &p, controlChar ); + controlChar = pgm_read_byte( fmt++ ); + } + } + else + { + if ( base == -1 ) /* conversion type c */ + { + char c = (char)va_arg( args, int ); + p.editedStringLen = 1; + OutputField( &p, &c ); + } + else if ( base == -2 ) /* conversion type s */ + { + char *string = va_arg( args, char * ); + + p.editedStringLen = 0; + while ( string[ p.editedStringLen ] != '\0' ) + { + if (( precision >= 0 ) && ( p.editedStringLen >= precision )) + { + /* + * We don't require the string to be null terminated + * if a precision is specified. + */ + + break; + } + p.editedStringLen++; + } + OutputField( &p, string ); + } + else /* conversion type d, b, o or x */ + { + unsigned long x; + + /* + * Worst case buffer allocation is required for binary output, + * which requires one character per bit of a long. + */ + + char buffer[ CHAR_BIT * sizeof( unsigned long ) + 1 ]; + + p.editedStringLen = 0; + if ( longArg ) + { + x = va_arg( args, unsigned long ); + } + else + if ( controlChar == 'd' ) + { + x = va_arg( args, int ); + } + else + { + x = va_arg( args, unsigned ); + } + + if (( controlChar == 'd' ) && ((long) x < 0 )) + { + SetOption( &p, MINUS_SIGN ); + x = - (long) x; + } + + do + { + int c; + c = x % base + '0'; + if ( c > '9' ) + { + if ( IsOptionSet( &p, CAPITAL_HEX )) + { + c += 'A'-'9'-1; + } + else + { + c += 'a'-'9'-1; + } + } + buffer[ sizeof( buffer ) - 1 - p.editedStringLen++ ] = (char)c; + } + while (( x /= base ) != 0 ); + + if (( precision >= 0 ) && ( precision > p.editedStringLen )) + { + p.leadingZeros = precision - p.editedStringLen; + } + OutputField( &p, buffer + sizeof(buffer) - p.editedStringLen ); + } + controlChar = pgm_read_byte( fmt++ ); + } + } + else + { + /* + * We're not processing a % output. Just output the character that + * was encountered. + */ + + OutputChar( &p, controlChar ); + controlChar = pgm_read_byte( fmt++ ); + } + } + return p.numOutputChars; + +} // vStrXPrintf + +/** @} */ + +/** + * @addtogroup StrPrintfInternal + * @{ + */ + +/***************************************************************************/ +/** +* Outputs a single character, keeping track of how many characters have +* been output. +* +* @param p (mod) State information. +* @param c (in) Character to output. +*/ + +static void OutputChar( Parameters *p, int c ) +{ + if ( p->numOutputChars >= 0 ) + { + int n = (*p->outFunc)(p->outParm, c); + + if ( n >= 0 ) + { + p->numOutputChars++; + } + else + { + p->numOutputChars = n; + } + } + +} // OutputChar + +/***************************************************************************/ +/** +* Outputs a formatted field. This routine assumes that the field has been +* converted to a string, and this routine takes care of the width +* options, leading zeros, and any leading minus sign. +* +* @param p (mod) State information. +* @param s (in) String to output. +*/ + +static void OutputField( Parameters *p, char *s ) +{ + short padLen = p->minFieldWidth - p->leadingZeros - p->editedStringLen; + + if ( IsOptionSet( p, MINUS_SIGN )) + { + if ( IsOptionSet( p, ZERO_PAD )) + { + /* + * Since we're zero padding, output the minus sign now. If we're space + * padding, we wait until we've output the spaces. + */ + + OutputChar( p, '-' ); + } + + /* + * Account for the minus sign now, even if we are going to output it + * later. Otherwise we'll output too much space padding. + */ + + padLen--; + } + + if ( IsOptionSet( p, RIGHT_JUSTIFY )) + { + /* + * Right justified: Output the spaces then the field. + */ + + while ( --padLen >= 0 ) + { + OutputChar( p, p->options & ZERO_PAD ? '0' : ' ' ); + } + } + if ( IsOptionSet( p, MINUS_SIGN ) && IsOptionClear( p, ZERO_PAD )) + { + /* + * We're not zero padding, which means we haven't output the minus + * sign yet. Do it now. + */ + + OutputChar( p, '-' ); + } + + /* + * Output any leading zeros. + */ + + while ( --p->leadingZeros >= 0 ) + { + OutputChar( p, '0' ); + } + + /* + * Output the field itself. + */ + + while ( --p->editedStringLen >= 0 ) + { + OutputChar( p, *s++ ); + } + + /* + * Output any trailing space padding. Note that if we output leading + * padding, then padLen will already have been decremented to zero. + */ + + while ( --padLen >= 0 ) + { + OutputChar( p, ' ' ); + } + +} // OutputField + +/***************************************************************************/ +/** +* Helper function, used by vStrPrintf() (and indirectly by StrPrintf()) +* for outputting characters into a user supplied buffer. +* +* @param outParm (mod) Pointer to StrPrintfParms structure. +* @param ch (in) Character to output. +* +* @return 1 if the character was stored successfully, -1 if the buffer +* was overflowed. +*/ + +static int StrPrintfFunc( void *outParm, int ch ) +{ + StrPrintfParms *strParm = (StrPrintfParms *)outParm; + + if ( strParm->maxLen > 0 ) + { + *strParm->str++ = (char)ch; + *strParm->str = '\0'; + strParm->maxLen--; + + return 1; + } + + /* + * Whoops. We ran out of space. + */ + + return -1; + +} // StrPrintfFunc + From 91c5644a14c4ebbf7a45c1e601bdf6a0894b21d0 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 2 Nov 2016 14:18:02 +0100 Subject: [PATCH 11/12] Integrate StrPrintf utilities into build This patch provides a header file for the StrPrint functions and integrates the source file into the build. --- Makefile | 6 ++++++ external/str/StrPrintf.c | 2 +- external/str/StrPrintf.h | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 external/str/StrPrintf.h diff --git a/Makefile b/Makefile index 9a5defd..703d453 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ DRIVERLIB = $(SDK_PATH)/driverlib OSLIB = $(SDK_PATH)/oslib FREERTOS = $(SDK_PATH)/third_party/FreeRTOS COMMON = $(SDK_PATH)/example/common +STR = external/str CPPFLAGS += $(DEFINES) $(INC) CFLAGS += -Os -ffunction-sections -fdata-sections -Wall -std=c11 @@ -63,6 +64,7 @@ INC += -I$(FREERTOS)/source INC += -I$(FREERTOS)/source/include INC += -I$(FREERTOS)/source/portable/GCC/ARM_CM4 INC += -I$(COMMON) +INC += -I$(STR) LIBS = @@ -124,6 +126,10 @@ OBJ += $(addprefix $(OBJDIR)/$(DRIVERLIB)/, \ wdt.o \ ) +OBJ += $(addprefix $(OBJDIR)/$(STR)/, \ + StrPrintf.o \ + ) + .PHONY: all all: $(TARGET) diff --git a/external/str/StrPrintf.c b/external/str/StrPrintf.c index dfbca07..15c05b0 100644 --- a/external/str/StrPrintf.c +++ b/external/str/StrPrintf.c @@ -35,7 +35,7 @@ /* ---- Include Files ---------------------------------------------------- */ -#include "Str.h" +#include "StrPrintf.h" #include #include diff --git a/external/str/StrPrintf.h b/external/str/StrPrintf.h new file mode 100644 index 0000000..0d48d96 --- /dev/null +++ b/external/str/StrPrintf.h @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +#pragma once + +#include + +typedef int (*StrXPrintfFunc)(void *outParm, int c); + +int +StrPrintf(char* outStr, int maxLen, const char* fmt, ...); + +int +StrXPrintf(StrXPrintfFunc outFunc, void* outParm, const char* fmt, ...); + +int +vStrPrintf(char* outStr, int maxLen, const char* fmt, va_list args); + +int +vStrXPrintf(StrXPrintfFunc outFunc,void* outParm, const char* fmt, + va_list args); From 77d9e12fd53b972e900e26aa8be0ab49d0f51f1d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 1 Nov 2016 13:26:46 +0100 Subject: [PATCH 12/12] Add ISR print functions PrintFromISR() and VPrintFromISR() The functions PrintFromISR() and VPrintFromISR() perform formatted output from ISRs. Don't use them outside of an ISR. The functions do not synchronize with the output of the regular print functions of any task. --- src/FormattedIO.c | 85 +++++++++++++++++++++++++++++++++++++++++++++-- src/FormattedIO.h | 14 ++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/FormattedIO.c b/src/FormattedIO.c index 579bf5f..258b508 100644 --- a/src/FormattedIO.c +++ b/src/FormattedIO.c @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "FormattedIO.h" +#include #include #include "Serial.h" @@ -54,13 +55,93 @@ Print(const char* fmt, ...) return res; } +typedef struct +{ + char* mBuf; + size_t mLen; +} StrBuf; + +static int +PutStrBuf(void* aParam, int aChar) +{ + StrBuf* buf = aParam; + + if (!buf->mLen) { + return -1; + } else if (buf->mLen == 1) { + aChar = '\0'; /* always terminate string buffer */ + } + + *buf->mBuf = aChar; + ++buf->mBuf; + --buf->mLen; + + return 1; +} + int VPrint(const char* fmt, va_list ap) { char buf[128]; - int res = vsnprintf(buf, sizeof(buf), fmt, ap); + StrBuf strBuf = { + .mBuf = buf, + .mLen = sizeof(buf) + }; + int res = vStrXPrintf(PutStrBuf, &strBuf, fmt, ap); + if (res < 0) { + return -1; + } + uint32_t len = sizeof(buf) - strBuf.mLen; + res = PrintIPC(len, buf); + if (res < 0) { + return -1; + } + return len; +} + +static int +PutSerial(void* aParam, int aChar) +{ + SerialPutChar(aChar); + return 0; +} + +int +_Print(const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + int res = _VPrint(fmt, ap); + va_end(ap); + + return res; +} + +int +_VPrint(const char* fmt, va_list ap) +{ + int res = vStrXPrintf(PutSerial, NULL, fmt, ap); if (res < 0) { return -1; } - return PrintIPC(res, buf); + return res; +} + +int +PrintFromISR(const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + int res = VPrintFromISR(fmt, ap); + va_end(ap); + + return res; +} + +int +VPrintFromISR(const char* fmt, va_list ap) +{ + return _VPrint(fmt, ap); } diff --git a/src/FormattedIO.h b/src/FormattedIO.h index e877e5c..c9f9f9a 100644 --- a/src/FormattedIO.h +++ b/src/FormattedIO.h @@ -11,3 +11,17 @@ Print(const char* fmt, ...); int VPrint(const char* fmt, va_list ap); + +int +PrintFromISR(const char* fmt, ...); + +int +VPrintFromISR(const char* fmt, va_list ap); + +/* Internal functions for debugging; don't use in production code. */ + +int +_Print(const char* fmt, ...); + +int +_VPrint(const char* fmt, va_list ap);