knihovna pro I2C komunikaci pro ATTINY85

Wiring, C++, C, Java, ...
Pravidla fóra
Toto subfórum slouží k řešení obecných otázek kolem programování (konstrukce, knihovny, alokace paměti, ...)
Odpovědět
Uživatelský avatar
SkullKeep
Příspěvky: 303
Registrován: 23 srp 2017, 18:51
Bydliště: Brno
Kontaktovat uživatele:

knihovna pro I2C komunikaci pro ATTINY85

Příspěvek od SkullKeep » 28 led 2025, 18:50

Poslední 2 dny jsem s AI na chatgpt řešil minimalistický I2C protokol pro ATTINY85. Arduino IDE to prošlo a tu je výsledek:
TinyI2C.cpp

Kód: Vybrat vše

// TinyI2C.cpp - Minimal Software I2C Library for ATtiny85
#include "TinyI2C.h"
#include <avr/io.h>
#include <util/delay.h>

#define I2C_SCL PB2
#define I2C_SDA PB0
#define I2C_DDR DDRB
#define I2C_PORT PORTB
#define I2C_PIN PINB

void TinyI2C::begin() {
    I2C_DDR &= ~((1 << I2C_SCL) | (1 << I2C_SDA)); // Set SCL and SDA as inputs
    I2C_PORT |= (1 << I2C_SCL) | (1 << I2C_SDA);  // Enable pull-ups
}

void TinyI2C::beginTransmission(uint8_t address) {
    _address = (address << 1);
    i2c_start();
    i2c_write(_address);
}

uint8_t TinyI2C::endTransmission() {
    i2c_stop();
    return 0; // Success
}

uint8_t TinyI2C::requestFrom(uint8_t address, uint8_t quantity) {
    _address = (address << 1) | 1;
    _bufferIndex = 0;
    _bufferLength = quantity;

    i2c_start();
    i2c_write(_address);

    for (uint8_t i = 0; i < quantity; i++) {
        _buffer[i] = i2c_read(i == quantity - 1);
    }

    i2c_stop();
    return quantity;
}

void TinyI2C::write(uint8_t data) {
    i2c_write(data);
}

uint8_t TinyI2C::read() {
    if (_bufferIndex < _bufferLength) {
        return _buffer[_bufferIndex++];
    }
    return 0;
}

void TinyI2C::i2c_start() {
    I2C_DDR |= (1 << I2C_SDA); // Set SDA as output
    _delay_us(4);
    I2C_DDR |= (1 << I2C_SCL); // Set SCL as output
    _delay_us(4);
}

void TinyI2C::i2c_stop() {
    I2C_DDR |= (1 << I2C_SDA); // Set SDA as output
    _delay_us(4);
    I2C_DDR &= ~(1 << I2C_SCL); // Release SCL
    _delay_us(4);
    I2C_DDR &= ~(1 << I2C_SDA); // Release SDA
    _delay_us(4);
}

void TinyI2C::i2c_write(uint8_t data) {
    for (uint8_t i = 0; i < 8; i++) {
        if (data & 0x80) I2C_DDR &= ~(1 << I2C_SDA); // Release SDA for high
        else I2C_DDR |= (1 << I2C_SDA); // Pull SDA low
        _delay_us(2);
        I2C_DDR &= ~(1 << I2C_SCL); // Release SCL
        _delay_us(4);
        I2C_DDR |= (1 << I2C_SCL); // Pull SCL low
        data <<= 1;
    }
    I2C_DDR &= ~(1 << I2C_SDA); // Release SDA for ACK
    _delay_us(2);
    I2C_DDR &= ~(1 << I2C_SCL);
    _delay_us(4);
    I2C_DDR |= (1 << I2C_SCL);
}

uint8_t TinyI2C::i2c_read(uint8_t ack) {
    uint8_t data = 0;
    for (uint8_t i = 0; i < 8; i++) {
        data <<= 1;
        I2C_DDR &= ~(1 << I2C_SCL);
        _delay_us(4);
        if (I2C_PIN & (1 << I2C_SDA)) data |= 1;
        I2C_DDR |= (1 << I2C_SCL);
        _delay_us(4);
    }
    if (ack) I2C_DDR |= (1 << I2C_SDA);
    else I2C_DDR &= ~(1 << I2C_SDA);
    _delay_us(2);
    I2C_DDR &= ~(1 << I2C_SCL);
    _delay_us(4);
    I2C_DDR |= (1 << I2C_SCL);
    return data;
}
TinyI2C.h

Kód: Vybrat vše

// TinyI2C.h - Header file for the minimal Software I2C Library for ATtiny85

#ifndef TINYI2C_H
#define TINYI2C_H

#include <inttypes.h>

class TinyI2C {
public:
    void begin();                    // Initializes the I2C bus
    void beginTransmission(uint8_t address);  // Starts an I2C transmission to a device
    uint8_t endTransmission();        // Ends the I2C transmission
    uint8_t requestFrom(uint8_t address, uint8_t quantity);  // Requests data from an I2C device
    void write(uint8_t data);         // Writes a byte of data to the I2C bus
    uint8_t read();                   // Reads a byte of data from the I2C bus

private:
    void i2c_start();                 // Sends a start condition on the I2C bus
    void i2c_stop();                  // Sends a stop condition on the I2C bus
    void i2c_write(uint8_t data);     // Writes a byte to the I2C bus
    uint8_t i2c_read(uint8_t ack);    // Reads a byte from the I2C bus

    uint8_t _address;                 // I2C device address
    uint8_t _buffer[32];              // Buffer to store read data
    uint8_t _bufferIndex;             // Index for buffer
    uint8_t _bufferLength;            // Length of the buffer
};

#endif
Keywords.txt

Kód: Vybrat vše

##################################################################
# Syntax Coloring Map 
##################################################################
# Datatypes (KEYWORD1)

TinyI2C				KEYWORD1

# Methods (KEYWORD2)

TinyI2C				KEYWORD2
begin           	KEYWORD2
beginTransmission	KEYWORD2
endTransmission		KEYWORD2
requestFrom			KEYWORD2
write				KEYWORD2
read				KEYWORD2
:twisted: :?: :arrow: :geek: P. Q. M.

Uživatelský avatar
Caster
Příspěvky: 447
Registrován: 11 zář 2019, 09:02

Re: knihovna pro I2C komunikaci pro ATTINY85

Příspěvek od Caster » 29 led 2025, 00:25

Zajímavé, ale určitě bych si ověřil, zda program funguje. ChatGPT není neomylný a dělá chyby ;) . Pro pomoc s programováním složitých věcí, matematické operace s desítkami milionů cifer (např. Karatsuba násobení) v C++ či zpracování obrazu v reálném čase (RPi4 ultralytic Yolo 11 Python) používám placený openai o1 preview ale i tomu musím domluvit, aby nevymyslel kraviny a program důkladně vyzkoušet.

Pro práci s MCU Microchip 8/32 bit (ATtiny202, SAMD21, SAMR34) používám MPLAB X IDE Harmony, FreeRTOS a C++ ;) . Pro většinu jednoduchých věcí si vystačím s ATtiny202 např. laserová hračka se dvěma servy SG90 pro kočku. Program náhodně pohybuje laserovým paprskem po koberci a kočka za ním běhá. Po přípomínce bráchy, že laser někdy svítí na Kačku jsem program upravil pro RPi4 s kamerou a rozpoznáním kočky ve videu tak, že se nový bod pro laser nesmí překrývat s obrazem kočky ;) .

Funkční program Laser Cat Toy pro ATtiny202 (programuji pomocí programátoru Microchip MPLAB Snap).

Kód: Vybrat vše

/*
 * File:   main.c
 * Author: Dan
 *
 * Created on 16. prosince 2022, 22:28
 */

#include <avr/io.h>
//#include <xc.h>

#define F_CPU 10000000
//#define __DELAY_BACKWARD_COMPATIBLE__

#define ms10  14220  // NOP loop

#define min_x  5
#define max_x 50
#define min_y  5
#define max_y 35
#define min_freeze   60     // 60 * 10 ms = 600 ms
#define max_freeze  100     // 300 * 10 ms = 3 seconds
#define minimal_movement 5


#include <avr/cpufunc.h>
#include <math.h>
#include <stdlib.h>
#include <util/delay.h>

uint16_t random_delay;
float x_position;
float y_position;
uint8_t x_old_position;
uint8_t y_old_position;
uint8_t x_new_position;
uint8_t y_new_position;
float x_speed;
float y_speed;
uint8_t movement_time;

int rnd(int min, int max) {
    int result = min + rand() % (max + 1 - min);
    return result;
}

inline void SYSCLK_init(void) {
    // SYSCLK 10MHz
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm);
    //    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc);
}

inline void TCA0_init(void) {
    //    PORTMUX.CTRLC = PORTMUX_TCA00_ALTERNATE_gc;
    //    PORTMUX.CTRLC = PORTMUX_TCA00_DEFAULT_gc;
    TCA0.SINGLE.PER = 49999; // PWM 20 ms
    TCA0.SINGLE.CMP1 = 3750; // PWM 1.0 ms @WO1, PA1
    TCA0.SINGLE.CMP2 = 3750; // PWM 2.0 ms @WO2, PA2
    TCA0.SINGLE.CTRLB |= TCA_SINGLE_CMP1EN_bm | TCA_SINGLE_CMP2EN_bm | TCA_SINGLE_ALUPD_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
    //    TCA0.SINGLE.DBGCTRL = TCA_SINGLE_DBGRUN_bm;
    TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV4_gc | TCA_SINGLE_ENABLE_bm;
}

void delay_nop(uint16_t tin10ms) {
    uint32_t n_cnt = (uint32_t) tin10ms * ms10;
    while (n_cnt > 0) { // 19881~ 10 ms
        asm("nop");
        n_cnt--;
    }
}

inline void PIN_init(void) {
    PORTA.DIR |= PIN1_bm | PIN2_bm; // WO1 @PIN1, WO2 @PIN2
}

int main(void) {

    SYSCLK_init();
    PIN_init();
    TCA0_init();

    while (1) {
        // X servo angle will stay in [min_x, max_x] range
        // Y servo angle will stay in [min_y, max_y] range
        // to be ajsuted to the size of your living room

        // Servo Init
        x_position = min_x + (max_x - min_x) / 2;
        y_position = min_y + (max_y - min_y) / 2;
        x_old_position = x_position;
        y_old_position = y_position;

        while (1) {
            movement_time = rnd(10, 40);
            random_delay = rnd(min_freeze, max_freeze);
            x_new_position = rnd(min_x + minimal_movement, max_x - minimal_movement);
            y_new_position = rnd(min_y + minimal_movement, max_y - minimal_movement);

            if ((y_new_position > y_old_position) && (y_new_position - y_old_position < 5)) {
                y_new_position += minimal_movement;
            } else if ((y_new_position < y_old_position) && (y_old_position - y_new_position < 5)) {
                y_new_position -= minimal_movement;
            }

            if ((x_new_position > x_old_position) && (x_new_position - x_old_position < 5)) {
                x_new_position += minimal_movement;
            } else if ((x_new_position < x_old_position) && (x_old_position - x_new_position < 5)) {
                x_new_position -= minimal_movement;
            }

            x_speed = ((float) x_new_position - (float) x_old_position) / (float) movement_time;
            y_speed = ((float) y_new_position - (float) y_old_position) / (float) movement_time;
            for (int pos = 0; pos < movement_time; pos += 1) {
                if (pos < movement_time - 1) {
                    x_position += x_speed;
                    y_position += y_speed;
                } else {
                    x_position = (float) x_new_position;    // In order to maintain an accurate integer position for further laser movement
                    y_position = (float) y_new_position;    // and avoid accumulation of errors caused by float calculations over time
                }
                // TCA0.SINGLE.CMP1BUF = x_position / 90 * 1250 + 3750;
                // TCA0.SINGLE.CMP1BUF = y_position / 90 * 1250 + 3750;
                TCA0.SINGLE.CMP1BUF = ((uint32_t) (x_position + 0.5) * 3556) / 256 + 3750;
                TCA0.SINGLE.CMP2BUF = ((uint32_t) (y_position + 0.5) * 3556) / 256 + 3750;
                // TCA0.SINGLE.CMP1BUF = ((uint16_t) x_position * 111) / 8 + 3750;  // For unknown reason need more Flash space and didn't compile than uint32_t above
                // TCA0.SINGLE.CMP2BUF = ((uint16_t) y_position * 111) / 8 + 3750;
                delay_nop(1); // 10 ms
            }
            x_old_position = x_new_position;
            y_old_position = y_new_position;
            delay_nop(random_delay);
        }
    }
}

Uživatelský avatar
SkullKeep
Příspěvky: 303
Registrován: 23 srp 2017, 18:51
Bydliště: Brno
Kontaktovat uživatele:

Re: knihovna pro I2C komunikaci pro ATTINY85

Příspěvek od SkullKeep » 29 led 2025, 07:42

No popravdě toto je 4 verze knihovny páč potřebuji ušetřit paměť v attiny85 a při testu kompilace jsem to s AI ladil cca 3 hodiny. Ale úspora paměti jen s knihovnou je cca 50% oproti klasické verzi WIRE pokud se dá věřit testu při kompilaci.
:twisted: :?: :arrow: :geek: P. Q. M.

Uživatelský avatar
SkullKeep
Příspěvky: 303
Registrován: 23 srp 2017, 18:51
Bydliště: Brno
Kontaktovat uživatele:

Re: knihovna pro I2C komunikaci pro ATTINY85

Příspěvek od SkullKeep » 29 led 2025, 18:42

Jedna větší změna upraveno pro ATtiny24/44/84 a ATtiny25/45/85 s automatickým zjištěním typu při stejně nízkém využití paměti :
ATTinyI2C.h

Kód: Vybrat vše

// ATTinyI2C.h - Header file for the minimal Software I2C Library for ATtiny85

#ifndef ATTinyI2C_H
#define ATTinyI2C_H
#include <inttypes.h>

#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
#define I2C_SCL PA4
#define I2C_SDA PA6
#define I2C_DDR DDRA
#define I2C_PORT PORTA
#define I2C_PIN PINA
#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
 #define I2C_SCL PB2
 #define I2C_SDA PB0
 #define I2C_DDR DDRB
 #define I2C_PORT PORTB
 #define I2C_PIN PINB
#else
  #error "tinySPI does not support this microcontroller."
#endif

class ATTinyI2C {
public:
    void begin();                    // Initializes the I2C bus
    void beginTransmission(uint8_t address);  // Starts an I2C transmission to a device
    uint8_t endTransmission();        // Ends the I2C transmission
    uint8_t requestFrom(uint8_t address, uint8_t quantity);  // Requests data from an I2C device
    void write(uint8_t data);         // Writes a byte of data to the I2C bus
    uint8_t read();                   // Reads a byte of data from the I2C bus

private:
    void i2c_start();                 // Sends a start condition on the I2C bus
    void i2c_stop();                  // Sends a stop condition on the I2C bus
    void i2c_write(uint8_t data);     // Writes a byte to the I2C bus
    uint8_t i2c_read(uint8_t ack);    // Reads a byte from the I2C bus

    uint8_t _address;                 // I2C device address
    uint8_t _buffer[32];              // Buffer to store read data
    uint8_t _bufferIndex;             // Index for buffer
    uint8_t _bufferLength;            // Length of the buffer
};

#endif

// ATTinyI2C.cpp

Kód: Vybrat vše

// ATTinyI2C.cpp - Minimal Software I2C Library for ATtiny85
#include "ATTinyI2C.h"
#include <avr/io.h>
#include <util/delay.h>



void ATTinyI2C::begin() {
    I2C_DDR &= ~((1 << I2C_SCL) | (1 << I2C_SDA)); // Set SCL and SDA as inputs
    I2C_PORT |= (1 << I2C_SCL) | (1 << I2C_SDA);  // Enable pull-ups
}

void ATTinyI2C::beginTransmission(uint8_t address) {
    _address = (address << 1);
    i2c_start();
    i2c_write(_address);
}

uint8_t ATTinyI2C::endTransmission() {
    i2c_stop();
    return 0; // Success
}

uint8_t ATTinyI2C::requestFrom(uint8_t address, uint8_t quantity) {
    _address = (address << 1) | 1;
    _bufferIndex = 0;
    _bufferLength = quantity;

    i2c_start();
    i2c_write(_address);

    for (uint8_t i = 0; i < quantity; i++) {
        _buffer[i] = i2c_read(i == quantity - 1);
    }

    i2c_stop();
    return quantity;
}

void ATTinyI2C::write(uint8_t data) {
    i2c_write(data);
}

uint8_t ATTinyI2C::read() {
    if (_bufferIndex < _bufferLength) {
        return _buffer[_bufferIndex++];
    }
    return 0;
}

void ATTinyI2C::i2c_start() {
    I2C_DDR |= (1 << I2C_SDA); // Set SDA as output
    _delay_us(4);
    I2C_DDR |= (1 << I2C_SCL); // Set SCL as output
    _delay_us(4);
}

void ATTinyI2C::i2c_stop() {
    I2C_DDR |= (1 << I2C_SDA); // Set SDA as output
    _delay_us(4);
    I2C_DDR &= ~(1 << I2C_SCL); // Release SCL
    _delay_us(4);
    I2C_DDR &= ~(1 << I2C_SDA); // Release SDA
    _delay_us(4);
}

void ATTinyI2C::i2c_write(uint8_t data) {
    for (uint8_t i = 0; i < 8; i++) {
        if (data & 0x80) I2C_DDR &= ~(1 << I2C_SDA); // Release SDA for high
        else I2C_DDR |= (1 << I2C_SDA); // Pull SDA low
        _delay_us(2);
        I2C_DDR &= ~(1 << I2C_SCL); // Release SCL
        _delay_us(4);
        I2C_DDR |= (1 << I2C_SCL); // Pull SCL low
        data <<= 1;
    }
    I2C_DDR &= ~(1 << I2C_SDA); // Release SDA for ACK
    _delay_us(2);
    I2C_DDR &= ~(1 << I2C_SCL);
    _delay_us(4);
    I2C_DDR |= (1 << I2C_SCL);
}

uint8_t ATTinyI2C::i2c_read(uint8_t ack) {
    uint8_t data = 0;
    for (uint8_t i = 0; i < 8; i++) {
        data <<= 1;
        I2C_DDR &= ~(1 << I2C_SCL);
        _delay_us(4);
        if (I2C_PIN & (1 << I2C_SDA)) data |= 1;
        I2C_DDR |= (1 << I2C_SCL);
        _delay_us(4);
    }
    if (ack) I2C_DDR |= (1 << I2C_SDA);
    else I2C_DDR &= ~(1 << I2C_SDA);
    _delay_us(2);
    I2C_DDR &= ~(1 << I2C_SCL);
    _delay_us(4);
    I2C_DDR |= (1 << I2C_SCL);
    return data;
}
Keywords

Kód: Vybrat vše

##################################################################
# Syntax Coloring Map 
##################################################################
# Datatypes (KEYWORD1)

ATtinyI2C			KEYWORD1

# Methods (KEYWORD2)

ATtinyI2C			KEYWORD2
begin      		     	KEYWORD2
beginTransmission		KEYWORD2
endTransmission			KEYWORD2
requestFrom			KEYWORD2
write				KEYWORD2
read				KEYWORD2
:twisted: :?: :arrow: :geek: P. Q. M.

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 1 host