{"id":26680044,"url":"https://github.com/wagiminator/attiny412-i2c-rotary-encoder","last_synced_at":"2025-08-23T04:06:19.823Z","repository":{"id":127507898,"uuid":"525898444","full_name":"wagiminator/ATtiny412-I2C-Rotary-Encoder","owner":"wagiminator","description":"Rotary Encoder with I²C Interface","archived":false,"fork":false,"pushed_at":"2022-12-13T15:46:15.000Z","size":1666,"stargazers_count":44,"open_issues_count":1,"forks_count":9,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-12T11:42:56.561Z","etag":null,"topics":["attiny","attiny202","attiny212","attiny402","attiny412","avr","i2c","qwiic","rotary-encoder","tinyavr"],"latest_commit_sha":null,"homepage":"https://oshwlab.com/wagiminator/attiny412-i2c-rotary-encoder","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wagiminator.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,"zenodo":null}},"created_at":"2022-08-17T17:49:52.000Z","updated_at":"2025-02-18T02:06:14.000Z","dependencies_parsed_at":"2023-07-18T04:01:11.006Z","dependency_job_id":null,"html_url":"https://github.com/wagiminator/ATtiny412-I2C-Rotary-Encoder","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/wagiminator/ATtiny412-I2C-Rotary-Encoder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny412-I2C-Rotary-Encoder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny412-I2C-Rotary-Encoder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny412-I2C-Rotary-Encoder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny412-I2C-Rotary-Encoder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wagiminator","download_url":"https://codeload.github.com/wagiminator/ATtiny412-I2C-Rotary-Encoder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagiminator%2FATtiny412-I2C-Rotary-Encoder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271740720,"owners_count":24812642,"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","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["attiny","attiny202","attiny212","attiny402","attiny412","avr","i2c","qwiic","rotary-encoder","tinyavr"],"created_at":"2025-03-26T06:19:48.120Z","updated_at":"2025-08-23T04:06:19.814Z","avatar_url":"https://github.com/wagiminator.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rotary Encoder with I²C Interface\r\nRotary encoders are a nice touch for many projects. However, their control requires some pins, interrupts and debouncing. Thanks to this device, a rotary encoder can be easily controlled via the I²C interface, the built-in ATtiny202/212/402/412 does the rest. By assigning different I²C addresses, it is even possible to daisy-chain several rotary encoders. The device is powered via the I²C connection and operates in the voltage range between 2.7V and 5V.\r\n\r\n- Design Files (EasyEDA): https://easyeda.com/wagiminator/attiny412-i2c-rotary-encoder\r\n\r\n![pic1.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny412-I2C-Rotary-Encoder/main/documentation/I2C_RotaryEncoder_pic1.jpg)\r\n\r\n# Hardware\r\n![wiring.png](https://raw.githubusercontent.com/wagiminator/ATtiny412-I2C-Rotary-Encoder/main/documentation/I2C_RotaryEncoder_wiring.png)\r\n\r\n# Software\r\n## Implementation\r\nThe heart of the firmware is the I²C slave implementation. Since the ATtiny supports this in hardware, things are pretty easy. A simple register array is used to read and write the rotary encoder parameters and states. Data transmission and reception are handled via interrupts.\r\n\r\n```c\r\n// I2C slave command macros\r\n#define I2C_complete() TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc\r\n#define I2C_response() TWI0.SCTRLB = TWI_SCMD_RESPONSE_gc\r\n#define I2C_sendACK()  TWI0.SCTRLB = TWI_ACKACT_ACK_gc  | TWI_SCMD_RESPONSE_gc\r\n#define I2C_sendNACK() TWI0.SCTRLB = TWI_ACKACT_NACK_gc | TWI_SCMD_RESPONSE_gc\r\n#define I2C_put(x)     TWI0.SDATA  = (x)\r\n#define I2C_get()      TWI0.SDATA\r\n\r\n// I2C slave status macros\r\n#define I2C_isAddr()  ((TWI0.SSTATUS \u0026 TWI_APIF_bm) \u0026\u0026 (TWI0.SSTATUS \u0026 TWI_AP_bm))\r\n#define I2C_isData()  (TWI0.SSTATUS \u0026 TWI_DIF_bm)\r\n#define I2C_isStop()  ((TWI0.SSTATUS \u0026 TWI_APIF_bm) \u0026\u0026 (~TWI0.SSTATUS \u0026 TWI_AP_bm))\r\n#define I2C_isIn()    (~TWI0.SSTATUS \u0026 TWI_DIR_bm)\r\n#define I2C_isOut()   (TWI0.SSTATUS \u0026 TWI_DIR_bm)\r\n\r\n// I2C slave registers\r\nuint8_t I2C_REG[10];                              // register array\r\nuint8_t I2C_REG_ptr;                              // register pointer\r\nvolatile uint8_t I2C_REG_changed = 0;             // register change flag\r\nvolatile uint8_t I2C_busy = 0;                    // I2C busy flag\r\n\r\n// I2C slave init\r\nvoid I2C_init(void) {\r\n  TWI0.SADDR  = I2C_ADDR \u003c\u003c 1;                    // set address (LSB is R/W bit)\r\n  TWI0.SCTRLA = TWI_DIEN_bm                       // data interrupt enable\r\n              | TWI_APIEN_bm                      // address or stop interrupt enable\r\n              | TWI_PIEN_bm                       // stop interrupt enable\r\n              | TWI_ENABLE_bm;                    // enable I2C slave\r\n}\r\n\r\n// I2C slave interrupt service routine\r\nISR(TWI0_TWIS_vect) { \r\n  // Address match interrupt handler\r\n  if(I2C_isAddr()) {                              // address match?\r\n    I2C_sendACK();                                // send ACK to master\r\n    I2C_REG_ptr = 0;                              // reset register pointer\r\n    I2C_busy = 1;                                 // set I2C busy flag\r\n    return;                                       // quit ISR\r\n  }\r\n  \r\n  // Data interrupt handler\r\n  if(I2C_isData()) {                              // data transmission?\r\n    if(I2C_isOut()) {                             // slave writing to master?\r\n      I2C_put(I2C_REG[I2C_REG_ptr]);              // send register value to master\r\n      I2C_response();                             // no ACK needed here\r\n    } else {                                      // slave reading from master?\r\n      I2C_REG[I2C_REG_ptr] = I2C_get();           // read register value from master\r\n      I2C_sendACK();                              // send ACK to master\r\n      I2C_REG_changed = 1;                        // set register changed flag\r\n    }\r\n    if(++I2C_REG_ptr \u003e= sizeof(I2C_REG))          // increase pointer...\r\n      I2C_REG_ptr = 0;                            // ...or wrap around\r\n    return;                                       // quit ISR\r\n  }\r\n\r\n  // Stop condition interrupt handler\r\n  if(I2C_isStop()) {                              // stop condition?\r\n    I2C_complete();                               // complete transaction\r\n    I2C_busy = 0;                                 // clear I2C busy flag\r\n  }\r\n}\r\n```\r\n\r\nThe I²C address of the device can be set in the firmware parameters at the beginning of the code.\r\n\r\n```c\r\n// Firmware parameters\r\n#define I2C_ADDR      0x36                        // I2C address of the device\r\n```\r\n\r\n## Compiling and Uploading the Firmware\r\n### If using the Arduino IDE\r\n- Open your Arduino IDE.\r\n- Make sure you have installed [megaTinyCore](https://github.com/SpenceKonde/megaTinyCore).\r\n- Go to **Tools -\u003e Board -\u003e megaTinyCore** and select **ATtiny412/402/212/202**.\r\n- Go to **Tools** and choose the following board options:\r\n  - **Chip:**           Choose the chip that is installed on your device\r\n  - **Clock:**          10 MHz internal\r\n  - Leave the rest at the default settings.\r\n- Connect your programmer to your PC and to the UPDI header on the board.\r\n- Go to **Tools -\u003e Programmer** and select your UPDI programmer.\r\n- Go to **Tools -\u003e Burn Bootloader** to burn the fuses.\r\n- Open the sketch and click **Upload**.\r\n\r\n### If using the makefile (Linux/Mac)\r\n- Make sure you have installed the latest [avr-gcc toolchain](http://maxembedded.com/2015/06/setting-up-avr-gcc-toolchain-on-linux-and-mac-os-x/).\r\n- Open a terminal.\r\n- Navigate to the folder with the makefile and the sketch.\r\n- Run `DEVICE=attiny412 PROGRMR=serialupdi PORT=/dev/ttyUSB0 make install` to compile, burn the fuses and upload the firmware (change DEVICE, PROGRMR and PORT accordingly).\r\n\r\n# Operating Instructions\r\nThe device has four 16-bit and two 8-bit registers that can be read and written. The 16-bit registers are signed and the least significant byte is always transmitted first. With each access (reading or writing), the registers are always transferred starting with the first in the following order:\r\n1. Encoder wheel value (16-bit)\r\n2. Encoder switch state (8-bit, 0=switch released, 1=switch pressed)\r\n3. Encoder wheel value loop flag (8-bit, 0=do not loop, 1=loop around)\r\n4. Encoder wheel minimum value (16-bit)\r\n5. Encoder wheel maximum value (16-bit)\r\n6. Encoder wheel value change step (16-bit)\r\n\r\nAn example code for controlling the device is attached. It uses the standard Arduino Wire library, so it should run on almost all supported microcontrollers.\r\n\r\n```c\r\n#include \u003cWire.h\u003e\r\n\r\n#define encoder_addr 0x36\r\n\r\nint16_t value, lastvalue;\r\nboolean pressed, lastpressed;\r\n\r\nvoid setup() {\r\n  Serial.begin(9600);\r\n  Wire.begin();\r\n  encoder_set(-50, 50, 1, 0, 0);\r\n}\r\n\r\nvoid loop() {\r\n  value = encoder_getValue();\r\n  if(value != lastvalue) {\r\n    Serial.println(value);\r\n    lastvalue = value;\r\n  }\r\n  pressed = encoder_isPressed();\r\n  if(pressed != lastpressed) {\r\n    if(pressed) Serial.println(\"Switch was pressed\");\r\n    lastpressed = pressed;\r\n  }\r\n  delay(20);\r\n}\r\n\r\n// Set encoder wheel parameters\r\nvoid encoder_set(int16_t rmin, int16_t rmax, int16_t rstep, int16_t rval, uint8_t rloop) {\r\n  Wire.beginTransmission(encoder_addr);\r\n  Wire.write((uint8_t)(rval \u0026 0xff)); Wire.write((uint8_t)(rval \u003e\u003e 8));\r\n  Wire.write(0); Wire.write(rloop);\r\n  Wire.write((uint8_t)(rmin \u0026 0xff)); Wire.write((uint8_t)(rmin \u003e\u003e 8));\r\n  Wire.write((uint8_t)(rmax \u0026 0xff)); Wire.write((uint8_t)(rmax \u003e\u003e 8));\r\n  Wire.write((uint8_t)(rstep \u0026 0xff)); Wire.write((uint8_t)(rstep \u003e\u003e 8));\r\n  Wire.endTransmission();\r\n}\r\n\r\n// Set encoder wheel value\r\nvoid encoder_setValue(int16_t rval) {\r\n  Wire.beginTransmission(encoder_addr);\r\n  Wire.write((uint8_t)(rval \u0026 0xff)); Wire.write((uint8_t)(rval \u003e\u003e 8));\r\n  Wire.endTransmission();\r\n}\r\n\r\n// Read encoder wheel value\r\nint16_t encoder_getValue() {\r\n  Wire.requestFrom(encoder_addr, 2);\r\n  return((uint16_t)Wire.read() | ((uint16_t)Wire.read() \u003c\u003c 8));\r\n}\r\n\r\n// Read encoder switch state\r\nboolean encoder_isPressed() {\r\n  Wire.requestFrom(encoder_addr, 3);\r\n  Wire.read(); Wire.read();\r\n  return(Wire.read());\r\n}\r\n```\r\n\r\n# References, Links and Notes\r\n1. [ATtiny412 Datasheet](https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATtiny212-214-412-414-416-DataSheet-DS40002287A.pdf)\r\n\r\n![pic2.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny412-I2C-Rotary-Encoder/main/documentation/I2C_RotaryEncoder_pic2.jpg)\r\n![pic3.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny412-I2C-Rotary-Encoder/main/documentation/I2C_RotaryEncoder_pic3.jpg)\r\n\r\n# License\r\n![license.png](https://i.creativecommons.org/l/by-sa/3.0/88x31.png)\r\n\r\nThis work is licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License. \r\n(http://creativecommons.org/licenses/by-sa/3.0/)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwagiminator%2Fattiny412-i2c-rotary-encoder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwagiminator%2Fattiny412-i2c-rotary-encoder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwagiminator%2Fattiny412-i2c-rotary-encoder/lists"}