Проверка самописного драйвера USI

Здравствуйте. У меня есть проект на тини2313 (думаю, основной код не нужен), и я там использую I2c, то есть USI. А так как USI даёт только сдвиговый регистр данных, а всю логику I2c нужно реализовывать практически программно. Поэтому я решил не заморачиваться и использовать тяжёлую библиотеку tinywireM. И весь код занял ровно 100% памяти, 2048 байт. Но теперь мне нужно добавить отладку ввиде писка бипера, и даже на такую простую задачу памяти не хватает. Поэтому я наконец решил написать свой драйвер. Вот его код:

‘’’

#define ACK() USIDR = 0; USICR |= (1<<USITC)

#define NACK() USIDR = 255; USICR |= (1<<USITC)

inline void USI_begin(){

DDRB |= (1<<SDA)|(1<<SCL);

};

inline uint8_t USI_transmit(uint8_t addr){

PORTB |= (1<<SDA)|(1<<SCL);

PORTB &= \~(1<<SDA);

PORTB &= \~(1<<SCL);

USICR = 0x12; //USI enable

USIDR = addr;

USISR |= (1<<USIOIF);

while(!(USISR & (1<<USIOIF))){

    USICR |= (1<<USITC);

};

if(PINB & (1<<SDA)) return 1; //NACK error

return 0; //success

};

void USI_send(uint8_t data){

USIDR = data;

USISR |= (1<<USIOIF);

while(!(USISR & (1<<USIOIF))){

    USICR |= (1<<USITC);

};

};

uint8_t micro_buffer[4];

uint32_t USI_readbytes(uint8_t quantity){

for(uint8_t \_ = 0; \_ < quantity; \_++){

    USISR |= (1<<USIOIF);

    while(!(USISR & (1<<USIOIF))){

        USICR |= (1<<USITC);

    };

    micro_buffer\[\_\] = USIDR;

    ACK();

};    

NACK();

};

void USI_stop(){

USICR = 0; //USI disable

PORTB &= \~((1<<SDA)|(1<<SCL));

\_delay_ms(1); //для стабилизации

PORTB |= (1<<SCL);

\_delay_us(100);

PORTB |= (1<<SDA);

};

‘’’

Пожалуйста, извините, что использую форум не по назначению, но пожалуйста, подскажите мне места, в которых есть критические ошибки. Я пробовал спросить про это у ИИ, но он то видит ошибки, где их нет, то не замечает их, поэтому приходится спросить тут, пожалуйста, извините.

Чтобы увидеть ошибки, - надо увидеть код, а вы его не предоставили!
А так, здесь была реализация IIC даже на тини13 :slightly_smiling_face:

сколько раз было говорено - подбирайте контролер под задачу, а не наоборот!

Лучше всего - на практике проверить. Возьмите, к примеру I2C микросхему EEPROM, и, записывайте на неё, затем - считывайте/сравнивайте данные.

Я предоставил код своего драйвера. Или Вы имеете ввиду основной код?

Извините, я думал, что те, которые я ввёл, и есть обратные, а кнопку в меню на нашёл.

Я имею ввиду только это!

Я подбирал, и выбрал именно 2313, а USI - это не такая большая проблема (до этого вообще использовал библиотеку), просто я сейчас сомневаюсь в правильности моего кода и прошу его проверить.

Сначала надо самому проверить. Потом показать как проверяли, и что именно не получается.
А так выглядит - мне лень проверять - проверьте за меня.
P.S.
Если не на чем проверить в железе - есть же Proteus…

если можно без проверки то вот!

/*
 * I2C драйвер для ATtiny2313 с использованием USI
 * Совместим с Arduino IDE
 * 
 * Пины: SDA = PB0, SCL = PB1
 * Частота: 8 МГц (по умолчанию)
 */

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

// ============================================================
// НАСТРОЙКИ (ИЗМЕНИТЕ ПОД СВОЮ СХЕМУ)
// ============================================================

#define F_CPU 8000000UL          // Частота МК (8 МГц)
#define I2C_ADDR 0x50            // Адрес вашего I2C устройства (пример)
#define REG_ADDR 0x00            // Адрес регистра для чтения

// Пины I2C
#define SDA PB0
#define SCL PB1
#define SDA_BIT (1<<SDA)
#define SCL_BIT (1<<SCL)

// Пины для отладки
#define BUZZER PB2               // Пин бипера (опционально)
#define BUZZER_BIT (1<<BUZZER)

// ============================================================
// МАКРОСЫ I2C
// ============================================================

#define SDA_HIGH() PORTB |= SDA_BIT
#define SDA_LOW()  PORTB &= ~SDA_BIT
#define SCL_HIGH() PORTB |= SCL_BIT
#define SCL_LOW()  PORTB &= ~SCL_BIT
#define SDA_READ() (PINB & SDA_BIT)

#define I2C_DELAY() _delay_us(5)  // 100 кГц

// ============================================================
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
// ============================================================

uint8_t micro_buffer[4];

// ============================================================
// ФУНКЦИИ I2C
// ============================================================

// Инициализация I2C
void I2C_init(void) {
    DDRB |= SDA_BIT | SCL_BIT;   // Пины как выходы
    SDA_HIGH();
    SCL_HIGH();
}

// Формирование START условия
void I2C_start(void) {
    SDA_HIGH();
    SCL_HIGH();
    I2C_DELAY();
    SDA_LOW();
    I2C_DELAY();
    SCL_LOW();
    I2C_DELAY();
}

// Формирование STOP условия
void I2C_stop(void) {
    SDA_LOW();
    SCL_HIGH();
    I2C_DELAY();
    SDA_HIGH();
    I2C_DELAY();
}

// Отправка байта (возвращает 0 = ACK, 1 = NACK)
uint8_t I2C_write_byte(uint8_t data) {
    // Загружаем данные в USI
    USIDR = data;
    USISR = (1 << USIOIF);       // Сбрасываем флаг
    
    // Тактируем 8 бит через USI
    for (uint8_t i = 0; i < 8; i++) {
        USICR = (1 << USIWM0) | (1 << USICS1) | (1 << USICLK) | (1 << USITC);
        while (!(USISR & (1 << USIOIF))) {
            USICR |= (1 << USITC);
        }
        USISR = (1 << USIOIF);
    }
    
    // Переключаемся в режим приема для ACK бита
    SDA_HIGH();                  // Освобождаем SDA
    DDRB &= ~SDA_BIT;            // SDA как вход
    
    // Тактируем ACK бит
    SCL_HIGH();
    I2C_DELAY();
    uint8_t ack = SDA_READ();    // 0 = ACK, 1 = NACK
    SCL_LOW();
    I2C_DELAY();
    
    DDRB |= SDA_BIT;             // Снова выход
    return ack;
}

// Чтение байта (send_ack: 1 = отправить ACK, 0 = отправить NACK)
uint8_t I2C_read_byte(uint8_t send_ack) {
    uint8_t data = 0;
    
    DDRB &= ~SDA_BIT;            // SDA как вход
    SDA_HIGH();                  // Подтяжка
    
    // Читаем 8 бит
    for (uint8_t i = 0; i < 8; i++) {
        USISR = (1 << USIOIF);
        SCL_HIGH();
        I2C_DELAY();
        data = (data << 1) | (SDA_READ() ? 1 : 0);
        SCL_LOW();
        I2C_DELAY();
    }
    
    DDRB |= SDA_BIT;             // Снова выход
    
    // Отправляем ACK или NACK
    if (send_ack) {
        SDA_LOW();               // ACK
    } else {
        SDA_HIGH();              // NACK
    }
    SCL_HIGH();
    I2C_DELAY();
    SCL_LOW();
    I2C_DELAY();
    
    return data;
}

// ============================================================
// ФУНКЦИИ ВЫСОКОГО УРОВНЯ
// ============================================================

// Начать передачу (возвращает 0 = успех, 1 = ошибка)
uint8_t I2C_start_transmission(uint8_t addr) {
    I2C_start();
    uint8_t error = I2C_write_byte(addr << 1);
    if (error) {
        I2C_stop();
        return 1;
    }
    return 0;
}

// Отправить байт данных
void I2C_write(uint8_t data) {
    I2C_write_byte(data);
}

// Прочитать байт (last: 1 = последний байт)
uint8_t I2C_read(uint8_t last) {
    return I2C_read_byte(last ? 0 : 1);
}

// Завершить передачу
void I2C_end_transmission(void) {
    I2C_stop();
}

// ============================================================
// ПОЛЕЗНЫЕ ФУНКЦИИ ДЛЯ РАБОТЫ С УСТРОЙСТВАМИ
// ============================================================

// Чтение нескольких байт из устройства
uint8_t I2C_read_bytes(uint8_t dev_addr, uint8_t reg_addr, uint8_t quantity) {
    if (quantity > 4) quantity = 4;  // Защита от переполнения буфера
    
    // Отправляем адрес регистра
    if (I2C_start_transmission(dev_addr)) return 1;
    I2C_write(reg_addr);
    I2C_stop();
    
    // Читаем данные
    if (I2C_start_transmission(dev_addr | 0x01)) return 1;  // Бит чтения
    
    for (uint8_t i = 0; i < quantity; i++) {
        micro_buffer[i] = I2C_read(i == quantity - 1);
    }
    
    I2C_stop();
    return 0;
}

// Запись одного байта в устройство
uint8_t I2C_write_byte_to_reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) {
    if (I2C_start_transmission(dev_addr)) return 1;
    I2C_write(reg_addr);
    I2C_write(data);
    I2C_stop();
    return 0;
}

// ============================================================
// ФУНКЦИИ ДЛЯ БИПЕРА (ОТЛАДКА)
// ============================================================

// Простой писк (использует _delay, НЕ использует таймер)
void beep_simple(uint16_t duration_ms) {
    DDRB |= BUZZER_BIT;          // Пин как выход
    
    for (uint16_t i = 0; i < duration_ms * 10; i++) {
        PORTB ^= BUZZER_BIT;     // Переключаем состояние
        _delay_us(100);          // Частота ~5 кГц
    }
    
    PORTB &= ~BUZZER_BIT;        // Выключаем
}

// Короткий писк для отладки
void beep_short(void) {
    beep_simple(50);             // 50 мс
}

// Длинный писк для ошибки
void beep_error(void) {
    for (uint8_t i = 0; i < 3; i++) {
        beep_simple(100);
        _delay_ms(100);
    }
}

// ============================================================
// АРДУИНО-СТИЛЬ ФУНКЦИИ
// ============================================================

void setup() {
    // Инициализация I2C
    I2C_init();
    
    // Пищим при старте (индикация запуска)
    beep_short();
    _delay_ms(200);
    beep_short();
    
    // ===== ВАШ КОД ЗДЕСЬ =====
    // Пример: читаем 4 байта с устройства
    
    uint8_t result = I2C_read_bytes(I2C_ADDR, REG_ADDR, 4);
    
    if (result == 0) {
        // Успешно прочитали!
        // Теперь данные в micro_buffer[0] ... micro_buffer[3]
        
        // Например: пищим если первый байт = 0xAA
        if (micro_buffer[0] == 0xAA) {
            beep_simple(200);
        }
    } else {
        // Ошибка I2C
        beep_error();
    }
    
    // ===== КОНЕЦ ВАШЕГО КОДА =====
}

void loop() {
    // Основной цикл (можно оставить пустым или добавить мигание)
    
    // Например: мигаем светодиодом на PB3 каждую секунду
    // static uint8_t led_state = 0;
    // if (++led_state & 1) {
    //     PORTB |= (1<<PB3);
    // } else {
    //     PORTB &= ~(1<<PB3);
    // }
    // _delay_ms(1000);
    
    // Или просто экономим энергию
    // Можно уйти в сон, если ничего не делаем
}

// ============================================================
// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ ОТЛАДКИ
// ============================================================

// Сканирование I2C шины (поиск устройств)
void I2C_scan(void) {
    for (uint8_t addr = 0x01; addr < 0x7F; addr++) {
        I2C_start();
        uint8_t error = I2C_write_byte(addr << 1);
        I2C_stop();
        
        if (!error) {
            // Нашли устройство - пищим
            beep_simple(20);
            _delay_ms(100);
        }
    }
}

а то ядра тини нет…

Боюсь Вас расстроить (я бы тоже расстроился), но у Микрочипа есть готовый код для этого и совсем не тяжёлый. Преимущество в том, что это всё-таки код от производителя чипа. В общем, код состоит из четырёх файлов

  1. USI_TWI_Master.c
  2. USI_TWI_Master.h
  3. USI_TWI_Slave.c
  4. USI_TWI_Slave.h

Имеется также документация:

  1. AVR310: Using the USI module as a I2C Master
  2. AVR312: Using the USI Module as a I2C Slave

Я пользуюсь и горя не знаю.

Лучше скачать у них, а то я давно не обновлял, мож там чего поправили. Но, если проблема, скажите, я открою доступ, скачаете у меня..

Это да, но за ради тренировки, сначала свой неплохо-бы доделать ИМХО))

А что, они уже опять разрешили скачивать для Российских IP?

Я бы из принципа остался на своём :slight_smile: Нашёл бы чем он мне удобнее и убедил бы себя этой мыслью :slight_smile: Благо, я нашёл этот до того, как написал свой. Искал собсна для Tiny85, там тоже USI.

Ну, мы же не знаем какой IP у ТС, правда? У меня вот от Межгалактической Империи – Дарт Вейдер подогнал, когда я для его истребителя прошивку писал, мы уже это обсуждали.

Эх…
А у меня наш, местный. Из суходрищенска. С него не дають…

Када “не дают” нада брать самому, слава богу пока не карается