Extending CircuitPython: An Introduction

Similar documents
Feather Waveform Generator in CircuitPython

MicroPython Development Documentation Documentation

Python Debouncer Library for Buttons and Sensors

CircuitPython 101: Functions

Digital Circuits 6: An EPROM Emulator

the gamedesigninitiative at cornell university Lecture 7 C++ Overview

Lab 2: ADT Design & Implementation

Short Notes of CS201

Rule 1-3: Use white space to break a function into paragraphs. Rule 1-5: Avoid very long statements. Use multiple shorter statements instead.

CS201 - Introduction to Programming Glossary By

Here's how you declare a function that returns a pointer to a character:

Makefiles Makefiles should begin with a comment section of the following form and with the following information filled in:

CPSC 427: Object-Oriented Programming

Course Coding Standards

Lecture 7. Log into Linux New documents posted to course webpage

0x0d2C May your signals all trap May your references be bounded All memory aligned Floats to ints round. remember...

CircuitPython 101: Basic Builtin Data Structures

CSE 333 Midterm Exam Cinco de Mayo, 2017 (May 5) Name UW ID#

Proper Debugging of ATSAMD21 Processors

Metro Minimalist Clock

static CS106L Spring 2009 Handout #21 May 12, 2009 Introduction

CSC148 Recipe for Designing Classes

CSE 333 Autumn 2013 Midterm

Auto-Pipe Software Block Interface v2. Interface. Contents. Auto-Pipe Types. From Auto-Pipe Wiki

Lab 12: Lijnenspel revisited

CS32 Summer Intro to Object-Oriented Programming in C++ Victor Amelkin August 12, 2013


7. (2 pts) str( str( b ) ) str '4' will not compile (single, double, or triple quotes

Entity vs. Value, Modules, Hidden Implementation, Interface Specification

University of Illinois at Urbana-Champaign Department of Computer Science. First Examination

Ch. 11: References & the Copy-Constructor. - continued -

Operator overloading

CS 211 Programming Practicum Fall 2018

P.G.TRB - COMPUTER SCIENCE. c) data processing language d) none of the above

Armide Documentation. Release Kyle Mayes

Lab 12 Lijnenspel revisited

CSE 333 Autumn 2014 Midterm Key

Exercise Session 2 Simon Gerber

Assignment 2: Temperature Class

Generalised User Interface for Embedded Applications using an LCD screen and keypad.

EL6483: Brief Overview of C Programming Language

Due Date: See Blackboard

Kurt Schmidt. October 30, 2018

So far, system calls have had easy syntax. Integer, character string, and structure arguments.

Exercise Session 2 Systems Programming and Computer Architecture

Circuit Playground D6 Dice

Princeton University COS 333: Advanced Programming Techniques A Subset of C90

QUIZ. What are 3 differences between C and C++ const variables?

CircuitPython 101: Working with Lists, Iterators and Generators

CS 326 Operating Systems C Programming. Greg Benson Department of Computer Science University of San Francisco

MARKING KEY The University of British Columbia MARKING KEY Computer Science 260 Midterm #1 Examination 12:30 noon, Tuesday, February 14, 2012

Item 4: Extensible Templates: Via Inheritance or Traits?

CS 261 Fall C Introduction. Variables, Memory Model, Pointers, and Debugging. Mike Lam, Professor

CSE 333 Midterm Exam Sample Solution 7/28/14

TDDE18 & 726G77. Functions

the gamedesigninitiative at cornell university Lecture 6 C++: Basics

2.2" TFT Display. Created by lady ada. Last updated on :19:15 PM UTC

CSE 142 Su 04 Computer Programming 1 - Java. Objects

Instantiation of Template class

micro:bit Lesson 1. Using the Built-in Sensors

// Initially NULL, points to the dynamically allocated array of bytes. uint8_t *data;

Lecture 10: building large projects, beginning C++, C++ and structs

Programming in C++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

CircuitPython 101: State Machines, Two Ways

Embedding Python in Your C Programs

CSE 303, Winter 2007, Final Examination 15 March Please do not turn the page until everyone is ready.

Topic 6: A Quick Intro To C. Reading. "goto Considered Harmful" History

MARKING KEY The University of British Columbia MARKING KEY Computer Science 260 Midterm #2 Examination 12:30 noon, Thursday, March 15, 2012

RIS shading Series #2 Meet The Plugins

Have examined process Creating program Have developed program Written in C Source code

CE221 Programming in C++ Part 1 Introduction

Software debouncing of buttons

CSCI-243 Exam 1 Review February 22, 2015 Presented by the RIT Computer Science Community

CS107 Handout 37 Spring 2007 May 25, 2007 Introduction to Inheritance

Lecture 05 I/O statements Printf, Scanf Simple statements, Compound statements

Data Structures. Home

11.1 Modular Organization and makefiles.

Remember, this question was mis-worded: you could also add quoted words and sentences in the blanks below. This allowed for a solution to [4] below.

SYSC 2006 C Winter String Processing in C. D.L. Bailey, Systems and Computer Engineering, Carleton University

G52CPP C++ Programming Lecture 9

MITOCW watch?v=flgjisf3l78

Program Translation. text. text. binary. binary. C program (p1.c) Compiler (gcc -S) Asm code (p1.s) Assembler (gcc or as) Object code (p1.

Chapter 10 :: Data Abstraction and Object Orientation

A Fast Review of C Essentials Part I

just a ((somewhat) safer) dialect.

Building CircuitPython

The Stack, Free Store, and Global Namespace

DDMD AND AUTOMATED CONVERSION FROM C++ TO D

The Design Process. General Development Issues. C/C++ and OO Rules of Thumb. Home

C Coding standard. Raphael kena Poss. July Introduction 2. 3 File system layout 2

COMP 202 Java in one week

CMSC445 Compiler design Blaheta. Project 2: Lexer. Due: 15 February 2012

MODEL-BASED DEVELOPMENT -TUTORIAL

Introduction to Programming in C Department of Computer Science and Engineering. Lecture No. #29 Arrays in C

Agenda. The main body and cout. Fundamental data types. Declarations and definitions. Control structures

PIC 10A Objects/Classes

Better variadic functions in C

Ar r ays and Pointer s

CSCI 104 Exceptions. Mark Redekopp David Kempe

Disclaimer. A Bit About Forth. No Syntax. Outline. I don't know Forth Typical Forth tutorial. Dave Eckhardt

Transcription:

Extending CircuitPython: An Introduction Created by Dave Astels Last updated on 2018-11-15 11:08:03 PM UTC

Guide Contents Guide Contents Overview How-To A Simple Example shared-module shared-bindings ports/atmel-samd Makefile mpconfigport.h In Action A Debouncer Module Implementation Interface Going On From Here 2 3 3 4 4 5 9 10 10 10 11 12 15 21 Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 2 of 21

Overview CircuitPython (like MicroPython from which it derives) is implemented in C. While fundamentally different and far more complex than a typical Arduino sketch, it's still the same language; if you are comfortable work in C/C++ in the context of Arduino, CircuitPython internals should look somewhat familiar. While it's true that we can do pretty much anything in Python, there are a variety of reasons we might want to implement a module in C as part of the virtual machine. 1. We're running out of RAM for our Python code 2. We have some code that needs to run faster 3. We have some C code that we want to use. 4. We want finer, more direct control over some aspects of the hardware. 5. We have some functionality that is so fundamental that we always want available. digitalio is a prime example of this. How-To To make this happen, not only do we need to implement a Python module in C, we have to connect it to the runtime as well as add it to the build system. In this guide we will explore how to do this. We'll start with a simple example to show what's needed, and follow that up with a more practical example of an input pin debouncer. Both of these examples are generic CircuitPython extensions, requiring no port specific code. We'll explore that in a future guide. There isn't much documentation to be found on how to extend the runtime. The best approach after working thought this guide is to look around at the other modules defined this way and see how things are done there. You will need to be set up and comfortable building CircuitPython for whatever board(s) you have. There's a guide to get up to speed on that (https://adafru.it/bfu). Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 3 of 21

A Simple Example There are three places in the VM codebase that we'll be working: the implementation, the connection into the virtual machine, and integration into the build. We'll start by exploring this with a very simple example. shared-module This is where the implementation goes. We need to add a directory here named for our module: mymodule. In this directory we need to add an init.c file that contains module level functions. We also need to add a header and source file for each class in the module. In this case that means MyClass.h and MyClass.c. init.c As mentioned, the definition of any module level functions go here. This example has none, so it's empty, with a "this space left intentionally blank" comment. // No module functions MyClass.h In this file we define the structure that holds the instance variables of our class. In this simple example, all we need to to keep track of whether the instance has been disposed of. Normally there will be other ways of telling this state, and we won't need something specific just for it. The other variable is required: base provides storage for the basic information for a Python class. #ifndef MICROPY_INCLUDED_MYMODULE_MYCLASS_H #define MICROPY_INCLUDED_MYMODULE_MYCLASS_H #include "py/obj.h" typedef struct { mp_obj_base_t base; bool deinited; mymodule_myclass_obj_t; #endif // MICROPY_INCLUDED_MYMODULE_MYCLASS_H Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 4 of 21

MyClass.c This file contains the methods of MyClass. It begins by including the corresponding header file as well as some basic runtime support. Then there's the standard constructor that is used to initialize new instances. This example is simple and the constructor takes no arguments. This will typically NOT be the case. The next example shows a constructor with arguments. #include "py/runtime.h" #include "MyClass.h" void shared_module_mymodule_myclass_construct(mymodule_myclass_obj_t* self) { self->deinited = 0; The deinit related methods handle disposal of instances as well as determining whether an instance has been disposed of. In classes that aren't this trivial, one will usually have a way to identify a valid object inherent to the object itself. bool shared_module_mymodule_myclass_deinited(mymodule_myclass_obj_t* self) { return self->deinited; void shared_module_mymodule_myclass_deinit(mymodule_myclass_obj_t* self) { self->deinited = 1; The remaining functions implement the class methods and properties. This example is simple: just two read-only properties that return a fixed value, and nothing has parameters. The question property is defined in shared_module_mymodule_myclass_get_question. This naming convention is the convention used. Just Do It. Sticking with the established conventions is the safest way to go. You never know when it's depended on. The CircuitPython runtime is complex enough that you don't want to take chances. Note that these function return native C types. Conversion to CircuitPython runtime types are done in the interface functions below. const char * shared_module_mymodule_myclass_get_question(mymodule_myclass_obj_t* self) { return "Tricky..."; mp_int_t shared_module_mymodule_myclass_get_answer(mymodule_myclass_obj_t* self) { return 42; shared-bindings In this directory we place the interface for our module. It's the plumbing that connects the implementation to the CircuitPython runtime. Start by adding a directory named after the module as we did for the implementation, e.g. mymodule. We need a couple general files, as well a pair for each class. So at a minimum we need the following. Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 5 of 21

init.h Even if you don't have anything for this file, it has to be present as shown below. #ifndef MICROPY_INCLUDED_SHARED_BINDINGS_MYMODULE INIT H #define MICROPY_INCLUDED_SHARED_BINDINGS_MYMODULE INIT H #include "py/obj.h" // Nothing now. #endif // MICROPY_INCLUDED_SHARED_BINDINGS_MYMODULE INIT H init.c This file takes care of hooking up the globals provided by the module. In this case it's just the name of the module and the class. #include <stdint.h> #include "py/obj.h" #include "py/runtime.h" #include "shared-bindings/mymodule/ init.h" #include "shared-bindings/mymodule/myclass.h" STATIC const mp_rom_map_elem_t mymodule_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR name ), MP_ROM_QSTR(MP_QSTR_mymodule), { MP_ROM_QSTR(MP_QSTR_MyClass), MP_ROM_PTR(&mymodule_myclass_type), ; STATIC MP_DEFINE_CONST_DICT(mymodule_module_globals, mymodule_module_globals_table); const mp_obj_module_t mymodule_module = {.base = { &mp_type_module,.globals = (mp_obj_dict_t*)&mymodule_module_globals, ; MyClass.h The class header here declares the functions from the implementation. They are declared extern which tells the compiler that they are defined elsewhere and will be available when all the files are linked together. Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 6 of 21

#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_MYMODULE_MYCLASS_H #define MICROPY_INCLUDED_SHARED_BINDINGS_MYMODULE_MYCLASS_H #include "shared-module/mymodule/myclass.h" extern const mp_obj_type_t mymodule_myclass_type; extern void shared_module_mymodule_myclass_construct(mymodule_myclass_obj_t* self); extern void shared_module_mymodule_myclass_deinit(mymodule_myclass_obj_t* self); extern bool shared_module_mymodule_myclass_deinited(mymodule_myclass_obj_t* self); extern char * shared_module_mymodule_myclass_get_question(mymodule_myclass_obj_t* self); extern mp_int_t shared_module_mymodule_myclass_get_answer(mymodule_myclass_obj_t* self); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_MYMODULE_MYCLASS_H MyClass.c As usual, we start with some includes: one for your class, and a handful of runtime support headers. #include <stdint.h> #include <string.h> #include "lib/utils/context_manager_helpers.h" #include "py/objproperty.h" #include "py/runtime.h" #include "py/runtime0.h" #include "shared-bindings/mymodule/myclass.h" #include "shared-bindings/util.h" Now we come to the life-cycle functions, including the constructor support. Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 7 of 21

.. currentmodule:: mymodule :class:`myclass` -- The great question (and answer to it) of life, the universe, and everything. ==================================================================================== Provides the great question (and answer to it) of life, the universie, and everything... class:: MyClass() Create an object. STATIC mp_obj_t mymodule_myclass_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp mp_arg_check_num(n_args, n_kw, 0, 0, true); mymodule_myclass_obj_t *self = m_new_obj(mymodule_myclass_obj_t); self->base.type = &mymodule_myclass_type; shared_module_mymodule_myclass_construct(self); return MP_OBJ_FROM_PTR(self);.. method:: deinit() Deinitializes the Meaning and releases any hardware resources for reuse. STATIC mp_obj_t mymodule_myclass_deinit(mp_obj_t self_in) { shared_module_mymodule_myclass_deinit(self_in); return mp_const_none; STATIC MP_DEFINE_CONST_FUN_OBJ_1(mymodule_myclass_deinit_obj, mymodule_myclass_deinit);.. method:: enter () No-op used by Context Managers. // Provided by context manager helper... method:: exit () Automatically deinitializes the hardware when exiting a context. See :ref:`lifetime-and-contextmanagers` for more info. STATIC mp_obj_t mymodule_myclass_obj exit (size_t n_args, const mp_obj_t *args) { shared_module_mymodule_myclass_deinit(args[0]); return mp_const_none; STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mymodule_myclass exit obj, 4, 4, mymodule_myclass_obj ex Now the actual properties. These simply call the implementation functions. In the case of answer, the implementation Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 8 of 21

.. attribute:: question The question of life, the universe and everything STATIC mp_obj_t mymodule_myclass_obj_get_question(mp_obj_t self_in) { char *str = shared_module_mymodule_myclass_get_question(self_in); return mp_obj_new_str(str, strlen(str)); MP_DEFINE_CONST_FUN_OBJ_1(mymodule_myclass_get_question_obj, mymodule_myclass_obj_get_question);.. attribute:: answer The answer to the question of life, the universe and everything STATIC mp_obj_t mymodule_myclass_obj_get_answer(mp_obj_t self_in) { return mp_obj_new_int(shared_module_mymodule_myclass_get_answer(self_in)); MP_DEFINE_CONST_FUN_OBJ_1(mymodule_myclass_get_answer_obj, mymodule_myclass_obj_get_answer); Finally there's code that defines the class locals. This usually serves to bind the method names to the interface functions defines above. const mp_obj_property_t mymodule_myclass_question_obj = {.base.type = &mp_type_property,.proxy = {(mp_obj_t)&mymodule_myclass_get_question_obj, (mp_obj_t)&mp_const_none_obj, ; const mp_obj_property_t mymodule_myclass_answer_obj = {.base.type = &mp_type_property,.proxy = {(mp_obj_t)&mymodule_myclass_get_answer_obj, (mp_obj_t)&mp_const_none_obj, ; STATIC const mp_rom_map_elem_t mymodule_myclass_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mymodule_myclass_deinit_obj), { MP_ROM_QSTR(MP_QSTR enter ), MP_ROM_PTR(&default enter obj), { MP_ROM_QSTR(MP_QSTR exit ), MP_ROM_PTR(&mymodule_myclass exit obj), { MP_ROM_QSTR(MP_QSTR_question), MP_ROM_PTR(&mymodule_myclass_question_obj), { MP_ROM_QSTR(MP_QSTR_answer), MP_ROM_PTR(&mymodule_myclass_answer_obj), ; STATIC MP_DEFINE_CONST_DICT(mymodule_myclass_locals_dict, mymodule_myclass_locals_dict_table); const mp_obj_type_t mymodule_myclass_type = { { &mp_type_type,.name = MP_QSTR_Meaning,.make_new = mymodule_myclass_make_new,.locals_dict = (mp_obj_dict_t*)&mymodule_myclass_locals_dict, ; ports/atmel-samd We'll need to edit two files to hook our new module into the build: Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 9 of 21

Makefile We need to add the c files we added to common-hal to the SRC_SHARED_MODULE list: mymodule/ init.c \ mymodule/myclass.c \ Note the reverse slash at the end of the lines. This is C's line continuation which is needed when defining a multi-line macro. mpconfigport.h There are two places in this file that need an addition. First we need to add our new module. Look for a comment very similar to // extra built in modules to add to the list of known ones Add a line to the list immediately following it, similar to the rest. The difference in what you add will be that it mentions your new module: extern const struct _mp_obj_module_t mymodule; The second thing to do is add your module to the EXTRA_BUILTIN_MODULES macro, with a line like the others there: { MP_OBJ_NEW_QSTR(MP_QSTR_mymodule), (mp_obj_t)&mymodule_module, \ Don't forget that reverse slash at the end of the line. In Action Now build CircuitPython for your board and try it out: >>> import mymodule >>> dir(mymodule) [' class ', ' name ', 'MyClass'] >>> dir(mymodule.myclass) [' class ', ' enter ', ' exit ', ' name ', 'answer', 'deinit', 'question'] >>> m = mymodule.myclass() >>> m.answer 42 >>> m.question 'Tricky...' Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 10 of 21

A Debouncer Module Let's do something more involved now that we know the general layout. A debouncer is a good next step. Here's a CircuitPython implementation that's been used in a few of the author's guides. import time import digitalio class Debouncer(object): """Debounce an input pin""" DEBOUNCED_STATE = 0x01 UNSTABLE_STATE = 0x02 CHANGED_STATE = 0x04 def init (self, pin, interval=0.010): """Make am instance. :param int pin: the pin (from board) to debounce :param int mode: digitalio.pull.up or.down (default is no pull up/down) :param int interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds) """ self.state = 0x00 self.pin = digitalio.digitalinout(pin) self.pin.direction = digitalio.direction.input self.pin.pull = digitalio.pull.up if self.pin.value: self. set_state(debouncer.debounced_state Debouncer.UNSTABLE_STATE) self.previous_time = 0 if interval is None: self.interval = 0.010 else: self.interval = interval def set_state(self, bits): self.state = bits def unset_state(self, bits): self.state &= ~bits def toggle_state(self, bits): self.state ^= bits Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 11 of 21

def get_state(self, bits): return (self.state & bits)!= 0 def update(self): """Update the debouncer state. Must be called before using any of the properties below""" self. unset_state(debouncer.changed_state) current_state = self.pin.value if current_state!= self. get_state(debouncer.unstable_state): self.previous_time = time.monotonic() self. toggle_state(debouncer.unstable_state) else: if time.monotonic() - self.previous_time >= self.interval: if current_state!= self. get_state(debouncer.debounced_state): self.previous_time = time.monotonic() self. toggle_state(debouncer.debounced_state) self. set_state(debouncer.changed_state) @property def value(self): """Return the current debounced value of the input.""" return self. get_state(debouncer.debounced_state) @property def rose(self): """Return whether the debounced input went from low to high at the most recent update.""" return self. get_state(self.debounced_state) and self. get_state(self.changed_state) @property def fell(self): """Return whether the debounced input went from high to low at the most recent update.""" return (not self. get_state(self.debounced_state)) and self. get_state(self.changed_state) Implementation We'll start with the implementation in shared-module/debounce. First the header that defines the data side of the Python class: Debouncer.h #ifndef MICROPY_INCLUDED_DEBOUNCE_DEBOUNCER_H #define MICROPY_INCLUDED_DEBOUNCE_DEBOUNCER_H #include "shared-bindings/digitalio/digitalinout.h" #include "py/obj.h" typedef struct { mp_obj_base_t base; uint8_t state; digitalio_digitalinout_obj_t pin; uint64_t previous_time; uint64_t interval; debounce_debouncer_obj_t; #endif // MICROPY_INCLUDED_DEBOUNCE_DEBOUNCER_H We'll go through the C file ( Debouncer.c ) a section at a time. First, the includes, constants, and external declarations. Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 12 of 21

The latter is the way we can refer to a function or variable that is defined elsewhere, but which we want to use in this file. We don't have to identify where it is, the linker will figure that out during the build. In this case, we want the function that implements Python's time.monotonic function. #include "common-hal/microcontroller/pin.h" #include "shared-bindings/digitalio/pull.h" #include "shared-bindings/digitalio/digitalinout.h" #include "py/runtime.h" #include "supervisor/shared/translate.h" #include "Debouncer.h" #define DEBOUNCED_STATE (0x01) #define UNSTABLE_STATE (0x02) #define CHANGED_STATE (0x04) extern uint64_t common_hal_time_monotonic(void); Next, we have the equivalent of the private methods in the Python version (prefixed by ). In C, we simply have to define them here and not make them available up to the runtime. void set_state(debounce_debouncer_obj_t* self, uint8_t bits) { self->state = bits; void unset_state(debounce_debouncer_obj_t* self, uint8_t bits) { self->state &= ~bits; void toggle_state(debounce_debouncer_obj_t* self, uint8_t bits) { self->state ^= bits; uint8_t get_state(debounce_debouncer_obj_t* self, uint8_t bits) { return (self->state & bits)!= 0; Next, let's turn to the constructor and lifecycle functions. This time the constructor takes two parameters: the pin to debounce, and the debounce interval (how long to let the input settle before accepting it's value). Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 13 of 21

void shared_module_debounce_debouncer_construct(debounce_debouncer_obj_t* self, mcu_pin_obj_t* pin, mp_int_t interval) { digitalinout_result_t result = common_hal_digitalio_digitalinout_construct(&self->pin, pin); if (result!= DIGITALINOUT_OK) { return; common_hal_digitalio_digitalinout_switch_to_input(&self->pin, PULL_UP); self->state = 0x00; if (common_hal_digitalio_digitalinout_get_value(&self->pin)) { set_state(self, DEBOUNCED_STATE UNSTABLE_STATE); self->interval = interval; bool shared_module_debounce_debouncer_deinited(debounce_debouncer_obj_t* self) { return common_hal_digitalio_digitalinout_deinited(&self->pin); void shared_module_debounce_debouncer_deinit(debounce_debouncer_obj_t* self) { if (shared_module_debounce_debouncer_deinited(self)) { return; common_hal_digitalio_digitalinout_deinit(&self->pin); Notice that we have something related to the implementation to tell when the instance is valid: the pin. The deinited function checks for a valid pin, and the deinit function releases the pin and invalidates it. The update function does the work in this code. When it's called, the function first grabs the current time and clears the changed state. Then it checks to see if the the pin is different from last time. If so, it restarts the settling timer and updates the state (to be checked against next time). If the pin hasn't changed since last time, it checks to see if the settling timeout has expired. If so, it's done unless the pin is now different than the most recent debounced state. If the pin is different, the time and debounced state are updated, and the change is noted. This change flag is used in the rose and fell properties which key off a change in the pin's debounced value. Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 14 of 21

void shared_module_debounce_debouncer_update(debounce_debouncer_obj_t* self) { if (shared_module_debounce_debouncer_deinited(self)) { return; uint64_t now = common_hal_time_monotonic(); unset_state(self, CHANGED_STATE); bool current_state = common_hal_digitalio_digitalinout_get_value(&self->pin); if (current_state!= get_state(self, UNSTABLE_STATE)) { self->previous_time = now; toggle_state(self, UNSTABLE_STATE); else { if (now - self->previous_time >= self->interval) { if (current_state!= get_state(self, DEBOUNCED_STATE)) { self->previous_time = now; toggle_state(self, DEBOUNCED_STATE); set_state(self, CHANGED_STATE); Finally, we have the property getter functions: value - the most recent debounced state rose - whether the debounced state is high and got there during the most recent call to update fell - whether the debounced state is low and got there during the most recent call to update bool shared_module_debounce_debouncer_get_value(debounce_debouncer_obj_t* self) { return get_state(self, DEBOUNCED_STATE); bool shared_module_debounce_debouncer_get_rose(debounce_debouncer_obj_t* self) { return get_state(self, DEBOUNCED_STATE) && get_state(self, CHANGED_STATE); bool shared_module_debounce_debouncer_get_fell(debounce_debouncer_obj_t* self) { return!get_state(self, DEBOUNCED_STATE) && get_state(self, CHANGED_STATE);; Interface Again, there are no exposed method level functions, so shared-bindings/debounce/ init.h is simply: #ifndef MICROPY_INCLUDED_SHARED_BINDINGS_DEBOUNCE INIT H #define MICROPY_INCLUDED_SHARED_BINDINGS_DEBOUNCE INIT H #include "py/obj.h" // Nothing now. #endif // MICROPY_INCLUDED_SHARED_BINDINGS_DEBOUNCE INIT H The file shared-bindings/debounce/ init.c ties in the Debouncer class: Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 15 of 21

#include <stdint.h> #include "py/obj.h" #include "py/runtime.h" #include "shared-bindings/debounce/ init.h" #include "shared-bindings/debounce/debouncer.h" STATIC const mp_rom_map_elem_t debounce_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR name ), MP_ROM_QSTR(MP_QSTR_debounce), { MP_ROM_QSTR(MP_QSTR_Debouncer), MP_ROM_PTR(&debounce_debouncer_type), ; STATIC MP_DEFINE_CONST_DICT(debounce_module_globals, debounce_module_globals_table); const mp_obj_module_t debounce_module = {.base = { &mp_type_module,.globals = (mp_obj_dict_t*)&debounce_module_globals, ; shared-bindings/debounce/debouncer.h declares the exposed functions in the implementation (above) as extern. Remember this just tells the compiler that these functions will be available; it's up to the linker to find them and make the connections. #ifndef MICROPY_INCLUDED_SHARED_BINDINGS_DEBOUNCE_DEBOUNCER_H #define MICROPY_INCLUDED_SHARED_BINDINGS_DEBOUNCE_DEBOUNCER_H #include "shared-module/debounce/debouncer.h" extern const mp_obj_type_t debounce_debouncer_type; extern void shared_module_debounce_debouncer_construct(debounce_debouncer_obj_t* self, mcu_pin_obj_t* pin extern void shared_module_debounce_debouncer_deinit(debounce_debouncer_obj_t* self); extern bool shared_module_debounce_debouncer_deinited(debounce_debouncer_obj_t* self); extern void shared_module_debounce_debouncer_update(debounce_debouncer_obj_t* self); extern bool shared_module_debounce_debouncer_get_fell(debounce_debouncer_obj_t* self); extern bool shared_module_debounce_debouncer_get_rose(debounce_debouncer_obj_t* self); extern bool shared_module_debounce_debouncer_get_value(debounce_debouncer_obj_t* self); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_DEBOUNCE_DEBOUNCER_H As before (but more complex this time) the interface source file ( shared-bindings/debounce/debouncer.c ) does the job of plumbing the implementation code into the CircuitPython runtime. This is still fairly straightforward as only the constructor has parameters. #include <stdint.h> #include "lib/utils/context_manager_helpers.h" #include "py/objproperty.h" #include "py/runtime.h" #include "py/runtime0.h" #include "shared-bindings/microcontroller/pin.h" #include "shared-bindings/debounce/debouncer.h" #include "shared-bindings/util.h" Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 16 of 21

.. currentmodule:: debounce :class:`debouncer` -- Debounce an input pin ==================================================================================== Debouncer cleans up an input pin and provides value/rise/fall properties... class:: Debouncer(pin, mode, interval) Create a Debouncer object associated with the given pin. It tracks the value of the pin over time, allowing it to settle before acknowledging transitions. :param ~microcontroller.pin pin: Pin to debounce :param ~int interval: debounce interval in milliseconds For example:: import debounce import board deb = bebounce.debounce(board.d10, 10) while True: deb.update() if deb.fell print("pressed") STATIC mp_obj_t debounce_debouncer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_arg_check_num(n_args, n_kw, 1, 2, true); mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args); enum { ARG_pin, ARG_interval ; static const mp_arg_t allowed_args[] = { { MP_QSTR_pin_, MP_ARG_REQUIRED MP_ARG_OBJ, { MP_QSTR_interval, MP_ARG_INT, ; mp_arg_val_t args[mp_array_size(allowed_args)]; mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); assert_pin(args[arg_pin].u_obj, false); mcu_pin_obj_t* pin = MP_OBJ_TO_PTR(args[ARG_pin].u_obj); mp_int_t interval = 10; if (n_args == 2) { interval = args[arg_interval].u_int; debounce_debouncer_obj_t *self = m_new_obj(debounce_debouncer_obj_t); self->base.type = &debounce_debouncer_type; shared_module_debounce_debouncer_construct(self, pin, interval); return MP_OBJ_FROM_PTR(self);.. method:: deinit() Deinitializes the debouncer and releases any hardware resources for reuse. STATIC mp_obj_t debounce_debouncer_deinit(mp_obj_t self_in) { Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 17 of 21

debounce_debouncer_obj_t *self = MP_OBJ_TO_PTR(self_in); shared_module_debounce_debouncer_deinit(self); return mp_const_none; STATIC MP_DEFINE_CONST_FUN_OBJ_1(debounce_debouncer_deinit_obj, debounce_debouncer_deinit);.. method:: update() Do an update cycle it it's time to. STATIC mp_obj_t debounce_debouncer_obj_update(size_t n_args, const mp_obj_t *args) { (void)n_args; shared_module_debounce_debouncer_update(args[0]); return mp_const_none; STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(debounce_debouncer_update_obj, 1, 1, debounce_debouncer_obj_up.. method:: exit () Automatically deinitializes the hardware when exiting a context. See :ref:`lifetime-and-contextmanagers` for more info. STATIC mp_obj_t debounce_debouncer_obj exit (size_t n_args, const mp_obj_t *args) { (void)n_args; shared_module_debounce_debouncer_deinit(args[0]); return mp_const_none; STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(debounce_debouncer exit obj, 4, 4, debounce_debouncer_obj_.. attribute:: value The current debounced value STATIC mp_obj_t debounce_debouncer_obj_get_value(mp_obj_t self_in) { debounce_debouncer_obj_t *self = MP_OBJ_TO_PTR(self_in); raise_error_if_deinited(shared_module_debounce_debouncer_deinited(self)); return mp_obj_new_bool(shared_module_debounce_debouncer_get_value(self)); MP_DEFINE_CONST_FUN_OBJ_1(debounce_debouncer_get_value_obj, debounce_debouncer_obj_get_value);.. attribute:: rose Whether the input transitioned low to high since last update STATIC mp_obj_t debounce_debouncer_obj_get_rose(mp_obj_t self_in) { debounce_debouncer_obj_t *self = MP_OBJ_TO_PTR(self_in); raise_error_if_deinited(shared_module_debounce_debouncer_deinited(self)); return mp_obj_new_bool(shared_module_debounce_debouncer_get_rose(self)); MP_DEFINE_CONST_FUN_OBJ_1(debounce_debouncer_get_rose_obj, debounce_debouncer_obj_get_rose);.. attribute:: value Whether the input transitioned high to low since last update Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 18 of 21

Whether the input transitioned high to low since last update STATIC mp_obj_t debounce_debouncer_obj_get_fell(mp_obj_t self_in) { debounce_debouncer_obj_t *self = MP_OBJ_TO_PTR(self_in); raise_error_if_deinited(shared_module_debounce_debouncer_deinited(self)); return mp_obj_new_bool(shared_module_debounce_debouncer_get_fell(self)); MP_DEFINE_CONST_FUN_OBJ_1(debounce_debouncer_get_fell_obj, debounce_debouncer_obj_get_fell); const mp_obj_property_t debounce_debouncer_value_obj = {.base.type = &mp_type_property,.proxy = {(mp_obj_t)&debounce_debouncer_get_value_obj, (mp_obj_t)&mp_const_none_obj, ; const mp_obj_property_t debounce_debouncer_rose_obj = {.base.type = &mp_type_property,.proxy = {(mp_obj_t)&debounce_debouncer_get_rose_obj, (mp_obj_t)&mp_const_none_obj, ; const mp_obj_property_t debounce_debouncer_fell_obj = {.base.type = &mp_type_property,.proxy = {(mp_obj_t)&debounce_debouncer_get_fell_obj, (mp_obj_t)&mp_const_none_obj, ; STATIC const mp_rom_map_elem_t debounce_debouncer_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&debounce_debouncer_deinit_obj), { MP_ROM_QSTR(MP_QSTR enter ), MP_ROM_PTR(&default enter obj), { MP_ROM_QSTR(MP_QSTR exit ), MP_ROM_PTR(&debounce_debouncer exit obj), { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&debounce_debouncer_update_obj), { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&debounce_debouncer_value_obj), { MP_ROM_QSTR(MP_QSTR_rose), MP_ROM_PTR(&debounce_debouncer_rose_obj), { MP_ROM_QSTR(MP_QSTR_fell), MP_ROM_PTR(&debounce_debouncer_fell_obj), ; STATIC MP_DEFINE_CONST_DICT(debounce_debouncer_locals_dict, debounce_debouncer_locals_dict_table); const mp_obj_type_t debounce_debouncer_type = { { &mp_type_type,.name = MP_QSTR_Debouncer,.make_new = debounce_debouncer_make_new,.locals_dict = (mp_obj_dict_t*)&debounce_debouncer_locals_dict, ; Notice how each function has a form similar to the following: STATIC mp_obj_t debounce_debouncer_obj_get_rose(mp_obj_t self_in) { debounce_debouncer_obj_t *self = MP_OBJ_TO_PTR(self_in); raise_error_if_deinited(shared_module_debounce_debouncer_deinited(self)); return mp_obj_new_bool(shared_module_debounce_debouncer_get_rose(self)); MP_DEFINE_CONST_FUN_OBJ_1(debounce_debouncer_get_rose_obj, debounce_debouncer_obj_get_rose); Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 19 of 21

Particularly, notice the final line: MP_DEFINE_CONST_FUN_OBJ_1 is what does the plumbing. The final part of the file defines what is known about the Python class (notice we use plain C to define the Python classes): STATIC const mp_rom_map_elem_t debounce_debouncer_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&debounce_debouncer_deinit_obj), { MP_ROM_QSTR(MP_QSTR enter ), MP_ROM_PTR(&default enter obj), { MP_ROM_QSTR(MP_QSTR exit ), MP_ROM_PTR(&debounce_debouncer exit obj), { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&debounce_debouncer_update_obj), { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&debounce_debouncer_value_obj), { MP_ROM_QSTR(MP_QSTR_rose), MP_ROM_PTR(&debounce_debouncer_rose_obj), { MP_ROM_QSTR(MP_QSTR_fell), MP_ROM_PTR(&debounce_debouncer_fell_obj), ; STATIC MP_DEFINE_CONST_DICT(debounce_debouncer_locals_dict, debounce_debouncer_locals_dict_table); const mp_obj_type_t debounce_debouncer_type = { { &mp_type_type,.name = MP_QSTR_Debouncer,.make_new = debounce_debouncer_make_new,.locals_dict = (mp_obj_dict_t*)&debounce_debouncer_locals_dict, ; Changes need to be made to Makefile and mpconfigport.h similar to those on the previous page, except that they reference the debounce module. Adafruit Industries https://learn.adafruit.com/extending-circuitpython Page 20 of 21

Going On From Here The CircuitPython runtime provides a wealth of support for adding modules in C. As mentioned, documentation is sparse and the best way to learn what you need is to explore the modules that are already present. This guide provides an overview of what's involved in writing a port independant module in C. You are not limited in what you can add in this way. CircuitPython provides what you need to interface with most hardware you will want, but puts limits on the performance of your code. There are times you will want to use algorithms that benefit from very high performance. Implementing them in C and exposing them as Python modules is a way to get that level of performance. There is also a way to add modules that rely on specific board capabilities. We'll explore that in a future quide. Adafruit Industries Last Updated: 2018-11-15 11:08:02 PM UTC Page 21 of 21