{"id":23333771,"url":"https://github.com/scttnlsn/cmcm","last_synced_at":"2025-06-28T21:38:50.753Z","repository":{"id":142162348,"uuid":"93467962","full_name":"scttnlsn/cmcm","owner":"scttnlsn","description":"Cooperative multitasking for ARM Cortex-M microcontrollers","archived":false,"fork":false,"pushed_at":"2017-06-06T02:56:44.000Z","size":10,"stargazers_count":12,"open_issues_count":2,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-09T18:55:55.279Z","etag":null,"topics":["arm","concurrency","cortex-m","microcontroller","multitasking"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scttnlsn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-06T02:39:24.000Z","updated_at":"2024-09-17T11:45:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"0d7fade2-6bcd-4564-887b-a8bbc90da765","html_url":"https://github.com/scttnlsn/cmcm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/scttnlsn/cmcm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scttnlsn%2Fcmcm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scttnlsn%2Fcmcm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scttnlsn%2Fcmcm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scttnlsn%2Fcmcm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scttnlsn","download_url":"https://codeload.github.com/scttnlsn/cmcm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scttnlsn%2Fcmcm/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262503159,"owners_count":23321182,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arm","concurrency","cortex-m","microcontroller","multitasking"],"created_at":"2024-12-21T00:32:19.620Z","updated_at":"2025-06-28T21:38:50.732Z","avatar_url":"https://github.com/scttnlsn.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CMCM\n\nThis is a very simple implementation of cooperative multitasking (via context switching) for ARM Cortex-M microcontrollers.\n\n**Includes:**\n\n* context switching between multiple tasks (tasks run to completion or yield back to the \"OS\")\n* statically allocated stack space for tasks (configurable)\n* task sleep/wake/delay\n* mutexes\n* queues (receiving tasks block until data is available)\n\n**Does not include:**\n\n* task preemption\n* task priorities\n* basically everything else you'd find in an RTOS\n\nI use this as an alternative to evented, state machine based multitasking since the semantics are *almost* as easily understood and it alleviates the need for a lot of asynchronous code and state management.\n\n## Setup\n\nThere are two configurable values that CMCM expects:\n\n```c\n#ifndef CMCM_MAX_NUM_TASKS\n#define CMCM_MAX_NUM_TASKS 8\n#endif\n\n#ifndef CMCM_STACK_SIZE\n#define CMCM_STACK_SIZE 2048\n#endif\n```\n\nThese values determine the size of the statically allocated memory devoted to task stacks.  You should tune these values for your application so as not to needlessly waste RAM.\n\nCMCM does not automatically hook into any interrupts.  You need to explicitly instruct CMCM to perform a context switch (probably from your `PendSV` interrupt handler):\n\n```c\nvoid pend_sv_handler(void) {\n  cmcm_context_switch();\n}\n```\n\nIn addition, if you choose to use CMCM's delay function, you'll need to periodically increment CMCM's internal tick counter (probably from your `SysTick` handler):\n\n```c\nvoid sys_tick_handler(void) {\n  cmcm_tick();\n}\n```\n\nThe argument to `cmcm_delay(ticks)` is the number of ticks to delay the current task, so if your systick handler runs every millisecond then `cmcm_delay(100)` will put your task to sleep for 100ms.\n\n## Tasks\n\nTasks are functions that can run concurrently, each with their own call stack.\n\nAPI:\n\n```c\n// create a new task, immediately placing it in rotation for context switches\nvoid cmcm_create_task(void (*handler)(void));\n\n// interrupt the current task by triggering a context switch\nvoid cmcm_yield(void);\n\n// delay the current task for the given number of ticks (yields internally)\nvoid cmcm_delay(uint32_t ticks);\n\n// interrupt the current task and put it to sleep until further notice (removes the task from context switch rotation)\nvoid cmcm_sleep(void);\n\n// wake the task with the given ID (puts it back into rotation for context switching)\nvoid cmcm_wake(int task_id);\n\n// returns the ID of the current task\nint cmcm_current_task(void);\n```\n\nHere's an example of a simple task that blinks an LED:\n\n```c\nvoid blink_task(void) {\n  while (1) {\n    led_on();\n    cmcm_delay(100);\n    led_off();\n    cmcm_delay(100);\n  }\n}\n\nint main(void) {\n  // ... hardware initialization, etc.\n\n  cmcm_create_task(blink_task);\n  cmcm_yield(); // passes control to CMCM\n\n  return 0;\n}\n```\n\nTasks can loop forever or eventually return.  If a task returns then its stack space becomes available for another task to be dynamically created via `cmcm_create_task`.  Notice that you need to explicitly yield to CMCM at the end of your `main` function.  After this initial yield, control will never return to your `main` function so all remaining execution needs to be handled by tasks.\n\n## Synchronization\n\n**Mutexes**\n\nUseful for preventing concurrent access to shared resources.  Nothing special here.\n\nAPI:\n\n```c\nvoid cmcm_mutex_lock(cmcm_mutex_t *mutex);\nvoid cmcm_mutex_unlock(cmcm_mutex_t *mutex);\n```\n\nFor example, you could use mutexes to allow multiple tasks to write to the same I2C bus:\n\n```c\nstatic cmcm_mutex_t mutex;\n\nvoid task1(void) {\n  while (1) {\n    cmcm_mutex_lock(\u0026mutex);\n    i2c_write(...);\n    cmcm_mutex_unlock(\u0026mutex);\n    cmcm_yield();\n  }\n}\n\nvoid task2(void) {\n  while (1) {\n    cmcm_mutex_lock(\u0026mutex);\n    i2c_write(...);\n    cmcm_mutex_unlock(\u0026mutex);\n    cmcm_yield();\n  }\n}\n```\n\n**Queues**\n\nFIFO queues for producer/consumer task synchronization.  Useful for waiting on asynchronous events (among other things).  Queues are also statically allocated and have a configurable size:\n\n```c\n#ifndef CMCM_QUEUE_SIZE\n#define CMCM_QUEUE_SIZE 10\n#endif\n```\n\nAPI:\n\n```c\n// initialize a new queue\nvoid cmcm_queue_init(cmcm_queue_t *queue);\n\n// place a message at the end of the queue (never blocks)\nvoid cmcm_queue_put(cmcm_queue_t *queue, cmcm_msg_t msg);\n\n// receive (or wait for) the next message in the queue (blocks until a message is available)\nvoid cmcm_queue_receive(cmcm_queue_t *queue, cmcm_msg_t *msg);\n```\n\nHere's an example of a task waiting for an asynchronous event from an interrupt (say, the completion of an ADC conversion):\n\n```c\nstatic cmcm_queue_t queue;\n\nvoid the_task(void) {\n  cmcm_queue_init(\u0026queue);\n\n  while (1) {\n    // starts an ADC conversion running and returns immediately\n    adc_start_conversion();\n\n    // wait for an ADC value off the queue\n    cmcm_msg_t msg;\n    cmcm_queue_receive(\u0026queue, \u0026msg);\n\n    printf(\"value=%d\\n\", msg.value);\n    cmcm_delay(1000);\n  }\n}\n\n// here's the contrived interrupt handler\nvoid adc_conversion_complete(void) {\n  cmcm_msg_t msg;\n  msg.value = adc_read();\n  cmcm_queue_put(\u0026queue, msg);\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscttnlsn%2Fcmcm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscttnlsn%2Fcmcm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscttnlsn%2Fcmcm/lists"}