From 353819ace0f6b22300048cc5ead2a616e8bcb3b5 Mon Sep 17 00:00:00 2001 From: AlvyneZ Date: Thu, 30 Apr 2026 19:26:48 +0300 Subject: [PATCH 1/3] Adding a blue font for text from firmware console output --- simavr/sim/run_avr.c | 1 + simavr/sim/sim_avr.c | 4 ++-- simavr/sim/sim_core.c | 2 ++ simavr/sim/sim_core.h | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/simavr/sim/run_avr.c b/simavr/sim/run_avr.c index e010eece2..e1886f6eb 100644 --- a/simavr/sim/run_avr.c +++ b/simavr/sim/run_avr.c @@ -38,6 +38,7 @@ static const struct text_colors font_no_color = { .green = "", .red = "", + .blue = "", .normal = "" }; #endif diff --git a/simavr/sim/sim_avr.c b/simavr/sim/sim_avr.c index 1d98c4f6e..cf981172c 100644 --- a/simavr/sim/sim_avr.c +++ b/simavr/sim/sim_avr.c @@ -275,8 +275,8 @@ _avr_io_console_write( { if (v == '\r' && avr->io_console_buffer.buf) { avr->io_console_buffer.buf[avr->io_console_buffer.len] = 0; - AVR_LOG(avr, LOG_OUTPUT, "O:" "%s" "" "\n", - avr->io_console_buffer.buf); + AVR_LOG(avr, LOG_OUTPUT, "%s%s%s\n", + simavr_font.blue, avr->io_console_buffer.buf, simavr_font.normal); avr->io_console_buffer.len = 0; return; } diff --git a/simavr/sim/sim_core.c b/simavr/sim/sim_core.c index e9beb4b10..db69d9874 100644 --- a/simavr/sim/sim_core.c +++ b/simavr/sim/sim_core.c @@ -34,10 +34,12 @@ struct text_colors simavr_font = { #ifdef NO_COLOR .green = "", .red = "", + .blue = "", .normal = "" #else .green = "\e[32m", .red = "\e[31m", + .blue = "\e[34m", .normal = "\e[0m" #endif }; diff --git a/simavr/sim/sim_core.h b/simavr/sim/sim_core.h index 28b9f3924..bbe0c846c 100644 --- a/simavr/sim/sim_core.h +++ b/simavr/sim/sim_core.h @@ -32,6 +32,7 @@ extern "C" { struct text_colors { const char *green; const char *red; + const char *blue; const char *normal; }; From 90f8d9c0ab2fd0314a234483a7ec66165851bfae Mon Sep 17 00:00:00 2001 From: AlvyneZ Date: Thu, 30 Apr 2026 19:29:58 +0300 Subject: [PATCH 2/3] Connecting USI_IRQ_DO to sda pin for two wire mode and start detector delay --- simavr/sim/avr_usi.c | 71 +++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/simavr/sim/avr_usi.c b/simavr/sim/avr_usi.c index fe65edf28..278dde91d 100644 --- a/simavr/sim/avr_usi.c +++ b/simavr/sim/avr_usi.c @@ -69,18 +69,8 @@ static void _avr_usi_set_usidr(struct avr_t * avr, avr_usi_t * p, uint8_t new_va avr_core_watch_write(avr, p->r_usidr, new_val); - switch(avr_regbit_get(avr, p->usiwm)) { - case USI_WM_THREEWIRE: - if (!p->clock_high) - _avr_usi_push_high_bit(p); - break; - case USI_WM_TWOWIRE: - case USI_WM_TWOWIRE_HOLD: - // TODO: this - break; - default: - break; - } + if (!p->clock_high) + _avr_usi_push_high_bit(p); } static void _avr_usi_clock_usidr(struct avr_t * avr, avr_usi_t * p) @@ -106,7 +96,10 @@ static void _avr_usi_disconnect_irqs(struct avr_t * avr, avr_usi_t * p, uint8_t } case USI_WM_TWOWIRE: case USI_WM_TWOWIRE_HOLD: - // TODO: this + avr_ioport_getirq_t req_di = { .bit = p->pin_di }; + if (avr_ioctl(avr, AVR_IOCTL_IOPORT_GETIRQ_REGBIT, &req_di) > 0) { + avr_unconnect_irq(&p->io.irq[USI_IRQ_DO], req_di.irq[0]); + } break; default: @@ -131,7 +124,10 @@ static void _avr_usi_connect_irqs(struct avr_t * avr, avr_usi_t * p, uint8_t new } case USI_WM_TWOWIRE: case USI_WM_TWOWIRE_HOLD: - // TODO: this + avr_ioport_getirq_t req_di = { .bit = p->pin_di }; + if (avr_ioctl(avr, AVR_IOCTL_IOPORT_GETIRQ_REGBIT, &req_di) > 0) { + avr_connect_irq(&p->io.irq[USI_IRQ_DO], req_di.irq[0]); + } break; default: @@ -154,6 +150,32 @@ static void _avr_usi_set_scl_hold(struct avr_t * avr, avr_usi_t * p, bool enable p->io.irq[USI_IRQ_USCK].flags &= ~IRQ_FLAG_STRONG; } +static avr_cycle_count_t _avr_usi_start_det_di_dly(struct avr_t * avr, avr_cycle_count_t when, void * param) +{ + avr_usi_t * p = (avr_usi_t *)param; + + if (!p->io.irq[USI_IRQ_USCK].value) + return 0; // SCL must be high at SDA change + + DBG(printf("USI ------------------- DI start condition detected\n")); + + avr_raise_interrupt(avr, &p->usi_start); + return 0; +} + +static avr_cycle_count_t _avr_usi_stop_det_di_dly(struct avr_t * avr, avr_cycle_count_t when, void * param) +{ + avr_usi_t * p = (avr_usi_t *)param; + + if (!p->io.irq[USI_IRQ_USCK].value) + return 0; // SCL must be high at SDA change + + DBG(printf("USI ------------------- DI stop condition detected\n")); + + avr_core_watch_write(avr, p->r_usisr, avr->data[p->r_usisr] | (1 << p->usipf.bit)); + return 0; +} + // ------------------------------------------------------------------------------------------------- // USISR - status register // ------------------------------------------------------------------------------------------------- @@ -340,25 +362,14 @@ static void _avr_usi_di_changed(struct avr_irq_t * irq, uint32_t value, void * p if (avr_regbit_get(avr, p->usiwm) >= USI_WM_TWOWIRE) { //Start & Stop detection for two wire mode - if (!p->io.irq[USI_IRQ_USCK].value) - return; // SCL must be high at SDA change - avr_ioport_state_t iostate; - uint8_t port = p->port_ioctl & 0xFF; - if (avr_ioctl(avr, AVR_IOCTL_IOPORT_GETSTATE(port), &iostate) < 0) - return; - if (iostate.ddr & (1 << p->pin_di.bit)) - return; // SDA must be in input mode - - up = !irq->value && value; - down = irq->value && !value; - - DBG(printf("USI ------------------- DI %s condition detected\n", - down ? "start" : up ? "stop" : "?")); + value &= 0xff; // Ignore output flag + up = !(irq->value & 0xff) && value; + down = (irq->value & 0xff) && !value; if (down) - avr_raise_interrupt(avr, &p->usi_start); + avr_cycle_timer_register(avr, 1UL, _avr_usi_start_det_di_dly, p); else if (up) - avr_core_watch_write(avr, p->r_usisr, avr->data[p->r_usisr] | (1 << p->usipf.bit)); + avr_cycle_timer_register(avr, 1UL, _avr_usi_stop_det_di_dly, p); } } From c34139933d8e419a672557ef8c1bee90c23baeb7 Mon Sep 17 00:00:00 2001 From: AlvyneZ Date: Sat, 2 May 2026 10:04:28 +0300 Subject: [PATCH 3/3] Adding an example for demonstrating i2c slave using USI --- examples/board_usi_i2cslave/Makefile | 52 ++ examples/board_usi_i2cslave/README | 5 + .../board_usi_i2cslave/attiny85_i2cslave.c | 357 ++++++++++++++ .../board_usi_i2cslave/i2ctest_usislave.c | 455 ++++++++++++++++++ 4 files changed, 869 insertions(+) create mode 100644 examples/board_usi_i2cslave/Makefile create mode 100644 examples/board_usi_i2cslave/README create mode 100644 examples/board_usi_i2cslave/attiny85_i2cslave.c create mode 100644 examples/board_usi_i2cslave/i2ctest_usislave.c diff --git a/examples/board_usi_i2cslave/Makefile b/examples/board_usi_i2cslave/Makefile new file mode 100644 index 000000000..7e54d927c --- /dev/null +++ b/examples/board_usi_i2cslave/Makefile @@ -0,0 +1,52 @@ +# +# Copyright 2008-2012 Michel Pollet +# +# This file is part of simavr. +# +# simavr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# simavr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with simavr. If not, see . + +target= i2ctest_usislave +firm_src = ${wildcard at*${board}.c} +firmware = ${firm_src:.c=.axf} +simavr = ../.. + +IPATH = . +IPATH += ${simavr}/examples/shared +IPATH += ${simavr}/examples/parts +IPATH += ${simavr}/include +IPATH += ${simavr}/simavr/sim + +VPATH = . +VPATH += ${simavr}/examples/shared +VPATH += ${simavr}/examples/parts + + +all: obj ${firmware} ${target} + +include ${simavr}/Makefile.common + +atmega1280_${target}.axf: atmega1280_${target}.c twimaster.c +atmega1280_${target}.axf: ${simavr}/examples/shared/avr_twi_master.c +atmega1280_${target}.axf: ${simavr}/examples/shared/avr_twi_master.h + +board = ${OBJ}/${target}.elf + +${board} : ${OBJ}/${target}.o +${board} : ${OBJ}/i2c_eeprom.o + +${target}: ${board} + @echo $@ done + +clean: clean-${OBJ} + rm -rf *.hex *.a *.axf ${target} *.vcd .*.swo .*.swp .*.swm .*.swn diff --git a/examples/board_usi_i2cslave/README b/examples/board_usi_i2cslave/README new file mode 100644 index 000000000..3e1156220 --- /dev/null +++ b/examples/board_usi_i2cslave/README @@ -0,0 +1,5 @@ + +This example is similar to board_i2cslave, treating the AVR (ATtiny) +as a bus slave, rather than bus master. It hooks directly to the +AVR usi (i2c mode) pins, and bit bangs the I2C protocol as a master, +and then runs a firmware that behaves as a TWI slave to talk to it. diff --git a/examples/board_usi_i2cslave/attiny85_i2cslave.c b/examples/board_usi_i2cslave/attiny85_i2cslave.c new file mode 100644 index 000000000..cf5ce43e4 --- /dev/null +++ b/examples/board_usi_i2cslave/attiny85_i2cslave.c @@ -0,0 +1,357 @@ +#include +#include +#include +#include + +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "attiny85"); +// Use a General Purpose I/O Register as a "virtual UART" +static int v_putchar(char c) +{ + GPIOR0 = c; // simavr can "watch" this register + return 0; +} + +static int v_puts(char *s) +{ + for (char *c = s; (*c) != '\0'; c++) { + v_putchar(*c); + } + return 0; +} + +/******************************************************************************* + * Callbacks and data handling + */ + +typedef enum { + USII2CSLV_WAITING, + USII2CSLV_RECEIVING_ADDRESS, + USII2CSLV_ACKING_ADDRESS_FOR_TX, + USII2CSLV_SENDING_DATA, + USII2CSLV_RECEIVING_ACK_FOR_TX, + USII2CSLV_ACKING_GEN_CALL, + USII2CSLV_ACKING_RX_DATA, + USII2CSLV_RECEIVING_DATA +} UsiI2cSlv_TrfState_t; + +static volatile UsiI2cSlv_TrfState_t i2cStatus; +static const uint8_t i2cAddress = 0x42; + +//Set i2cRxBuffer or i2cTxBuffer to NULL pointers to disable rx from or tx to master +#define BUF_SIZE 25 +static uint8_t mainBuffer[BUF_SIZE * 2], loopBuf[BUF_SIZE]; +static uint8_t *i2cRxBuffer = mainBuffer, *i2cTxBuffer = (mainBuffer + BUF_SIZE); +static volatile uint8_t loopBufDatLen = 0, txCount = 0, rxCount = 0; +static uint8_t rxBufLen = BUF_SIZE, txBufLen = 0; + +static inline void printLoopBuf(void) +{ + v_puts("Got "); + v_putchar('0' + ((loopBufDatLen / 10) % 10)); + v_putchar('0' + (loopBufDatLen % 10)); + v_puts(" bytes: "); + for (int i = 0; i < loopBufDatLen; i++) { + v_putchar('|'); v_putchar(loopBuf[i]); + } + v_putchar('|'); v_putchar('\r'); + loopBufDatLen = 0; +} + +static inline uint8_t loadTxCallback(void) +{ + static uint8_t offset = 'B'; + if (txCount) { //Data was sent out from buffer + v_puts("Tx needs > "); + v_putchar('0' + ((txCount / 10) % 10)); + v_putchar('0' + (txCount % 10)); + v_putchar('\r'); + offset += txCount; + } + for (uint8_t i = 0; i < 4; i++) { //Do not load more than BUF_SIZE + i2cTxBuffer[i] = i + offset; + } + txBufLen = 4; + txCount = 0; //Reset counter + return txBufLen; //The amount of data loaded +} + +static inline void txDoneCallback(void) { + if (txCount) { //Data was sent out from buffer + txBufLen = 0; //To avoid sending the same tx data, by not reusing buffer + v_puts("Tx done "); + v_putchar('0' + ((txCount / 10) % 10)); + v_putchar('0' + (txCount % 10)); + v_putchar('\r'); + txCount = 0; //Reset counter + } +} + +static inline void rcvDoneCallback(void) { + if (rxCount) { //Data available in buffer + if (!loopBufDatLen) { //Last rx data has been consumed + //Copy data into loopBuf for consumption in main loop + for (int i = 0; i < rxCount; i++) { + loopBuf[i] = i2cRxBuffer[i]; + } + loopBufDatLen = rxCount; + } + PORTB ^= (1 << PB1); //Toggle PB1 + rxCount = 0; //Reset counter + } +} + +/******************************************************************************* + * USI I2C slave peripheral handling + */ + +#if defined(__AVR_ATtiny2313__) + # define DDR_USI DDRB + # define PORT_USI PORTB + # define PIN_USI PINB + # define PORT_USI_SDA PB5 + # define PORT_USI_SCL PB7 + # define PIN_USI_SDA PINB5 + # define PIN_USI_SCL PINB7 + # define USI_OVERFLOW_VECTOR USI_OVERFLOW_vect +#elif defined(__AVR_ATtiny84__) | defined(__AVR_ATtiny44__) + # define DDR_USI DDRA + # define PORT_USI PORTA + # define PIN_USI PINA + # define PORT_USI_SDA PORTA6 + # define PORT_USI_SCL PORTA4 + # define PIN_USI_SDA PINA6 + # define PIN_USI_SCL PINA4 + # define USI_OVERFLOW_VECTOR USI_OVF_vect +#elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | \ + defined(__AVR_ATtiny85__) | defined(__AVR_ATtiny261__) | \ + defined(__AVR_ATtiny461__) | defined(__AVR_ATtiny861__) + # define DDR_USI DDRB + # define PORT_USI PORTB + # define PIN_USI PINB + # define PORT_USI_SDA PB0 + # define PORT_USI_SCL PB2 + # define PIN_USI_SDA PINB0 + # define PIN_USI_SCL PINB2 + # define USI_OVERFLOW_VECTOR USI_OVF_vect +#endif +# define USI_START_VECTOR USI_START_vect + +static inline void UsiI2cSlv_InitPins(void) +{ + // Configuring the pins as inputs + DDR_USI &= ~(_BV(PORT_USI_SDA) | _BV(PORT_USI_SCL)); + // Setting the pins pulled-up + PORT_USI |= _BV(PORT_USI_SDA);// | _BV(PORT_USI_SCL); leave SCL out for simulation +} + +static inline void UsiI2cSlv_InitPeri(void) +{ + i2cStatus = USII2CSLV_WAITING; + USICR = (1 << USISIE) | //Enable Start cond interrupt + (0 << USIOIE) | //Disable Overflow interrupt + (1 << USIWM1) | (0 << USIWM0) | //Normal 2 wire mode + (1 << USICS1) | (0 << USICS0) | //External clock source (+ve trigger) + (0 << USICLK) | //Clock Strobe (software trigger) + (0 << USITC); //Software Clock Strobe + // Clear all status flags and reset overflow counter + USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | + (1 << USIDC) | (0x0 << USICNT0); +} + +void UsiI2cSlv_Init(void) +{ + UsiI2cSlv_InitPins(); + UsiI2cSlv_InitPeri(); +} + +static inline void UsiI2cSlv_CheckLastTrf(void) +{ + rcvDoneCallback(); + txDoneCallback(); +} + +/** + * @brief USI Start Condition detected interrupt handler + * @note Start Condition = SDA falling edge when SCL is high + */ +ISR(USI_START_VECTOR) +{ + // Checking for immediate Stop after start + while (PIN_USI & (1 << PIN_USI_SCL)) { + if (PIN_USI & (1 << PIN_USI_SDA)) { + // SDA going high during SCL high means immediate STOP after + UsiI2cSlv_InitPeri(); + goto USI_START_VECTOR_EXIT; + } + } + // Start receiving address from master with clock stretching + i2cStatus = USII2CSLV_RECEIVING_ADDRESS; + USICR = (1 << USISIE) | //Enable Start cond interrupt (for repeated start) + (1 << USIOIE) | //Enable Overflow interrupt (counts 16 SCL edges) + (1 << USIWM1) | (1 << USIWM0) | //Clock-stretching 2 wire mode + (1 << USICS1) | (0 << USICS0) | //External clock source (+ve trigger) + (0 << USICLK) | //Clock Strobe (software trigger) + (0 << USITC); //Software Clock Strobe + // Clear all status flags and reset overflow counter + USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | + (1 << USIDC) | (0x0 << USICNT0); + USI_START_VECTOR_EXIT: + UsiI2cSlv_CheckLastTrf(); +} + +static inline void SET_USI_TO_SEND_ACK(UsiI2cSlv_TrfState_t newStatus) +{ + i2cStatus = newStatus; + USIDR = 0; + PORT_USI&= ~_BV(PORT_USI_SDA); + DDR_USI |= _BV(PORT_USI_SDA); + USISR = (0 << USISIF) | (1 << USIOIF) | (1 << USIPF) | + (1 << USIDC)| (0x0E << USICNT0); +} + +static inline void SET_USI_TO_RECEIVE_ACK() +{ + i2cStatus = USII2CSLV_RECEIVING_ACK_FOR_TX; + DDR_USI &= ~(1 << PORT_USI_SDA); + PORT_USI|= _BV(PORT_USI_SDA); + USIDR = 0x80; + USISR = (0 << USISIF) | (1 << USIOIF) | (1 << USIPF) | + (1 << USIDC) | (0x0E << USICNT0); +} + +static inline void SET_USI_TO_TWI_START_CONDITION_MODE() +{ + i2cStatus = USII2CSLV_WAITING; + DDR_USI &= ~(1 << PORT_USI_SDA); + PORT_USI|= _BV(PORT_USI_SDA); + USICR = (1 << USISIE) | //Enable Start condfition interrupt + (0 << USIOIE) | //DIsable Overflow interrupt + (1 << USIWM1) | (0 << USIWM0) | //Normal 2 wire mode + (1 << USICS1) | (0 << USICS0) | //External clock source (+ve trigger) + (0 << USICLK) | //Clock Strobe (software trigger) + (0 << USITC); //Software Clock Strobe + // Clear all status flags except StartCond and reset overflow counter + USISR = (0 << USISIF) | (1 << USIOIF) | (1 << USIPF) | + (1 << USIDC) | (0x0 << USICNT0); +} + +static inline void SET_USI_TO_SEND_DATA() +{ + i2cStatus = USII2CSLV_SENDING_DATA; + DDR_USI |= (1 << PORT_USI_SDA); + USISR = (0 << USISIF) | (1 << USIOIF) | + (1 << USIPF) | ( 1 << USIDC) | ( 0x0 << USICNT0 ); +} + +static inline void SET_USI_TO_RECEIVE_DATA() +{ + i2cStatus = USII2CSLV_RECEIVING_DATA; + DDR_USI &= ~(1 << PORT_USI_SDA); + PORT_USI|= _BV(PORT_USI_SDA); + USISR = (0 << USISIF) | (1 << USIOIF) | (1 << USIPF) | + (1 << USIDC) | (0x0 << USICNT0); +} + +ISR(USI_OVERFLOW_VECTOR) +{ + switch (i2cStatus) { + case (USII2CSLV_RECEIVING_ADDRESS): + if (USIDR == 0) { // General Call + SET_USI_TO_SEND_ACK(USII2CSLV_ACKING_GEN_CALL); + break; + } else if ((USIDR >> 1) == i2cAddress) { + if (USIDR & 0x01) { //R => Master Reading from slave + if ( + (i2cTxBuffer) && //Tx configured and + ((txBufLen) || //Buffer not sent or + (loadTxCallback())) //Buffer refreshed + ) { + SET_USI_TO_SEND_ACK(USII2CSLV_ACKING_ADDRESS_FOR_TX); + break; + } + } else { //W => Master Writing to slave + if (i2cRxBuffer) { //Rx configured + SET_USI_TO_SEND_ACK(USII2CSLV_ACKING_RX_DATA); + break; + } + } + //At this point NACK is needed, as read or write is not possible + } else {/* Address does not match, so don't touch bus */} + SET_USI_TO_TWI_START_CONDITION_MODE(); + break; + case (USII2CSLV_RECEIVING_ACK_FOR_TX): + if (USIDR) { // Got a NACK from master + txCount--; //Last byte not taken + txDoneCallback(); + SET_USI_TO_TWI_START_CONDITION_MODE(); + break; + } + __attribute__((fallthrough)); + case (USII2CSLV_ACKING_ADDRESS_FOR_TX): + if (i2cTxBuffer) { + if (txCount >= txBufLen) { + loadTxCallback(); + } + if (txCount < txBufLen) { + USIDR = i2cTxBuffer[txCount]; + txCount++; + SET_USI_TO_SEND_DATA(); //st = USII2CSLV_SENDING_DATA + break; + } + } + SET_USI_TO_TWI_START_CONDITION_MODE(); + //This results in HiZ SDA, so master receives 0xFF from bus + break; + case (USII2CSLV_SENDING_DATA): + SET_USI_TO_RECEIVE_ACK(); //st = USII2CSLV_RECEIVING_ACK_FOR_TX + break; + case (USII2CSLV_ACKING_GEN_CALL): + SET_USI_TO_TWI_START_CONDITION_MODE(); + //This results in a NACK, making master stop sending more data + break; + case (USII2CSLV_ACKING_RX_DATA): + SET_USI_TO_RECEIVE_DATA(); //st = USII2CSLV_RECEIVING_DATA + break; + case (USII2CSLV_RECEIVING_DATA): + if (i2cRxBuffer) { //RxInto buffer configured + if (rxCount < rxBufLen) { + i2cRxBuffer[rxCount] = USIDR; + rxCount++; + SET_USI_TO_SEND_ACK(USII2CSLV_ACKING_RX_DATA); + break; + } else { //Buffer is full + rcvDoneCallback(); + } + } + SET_USI_TO_TWI_START_CONDITION_MODE(); + //This results in a NACK, making master stop sending more data + break; + default: + SET_USI_TO_TWI_START_CONDITION_MODE(); + break; + } +} + +/******************************************************************************* + * Main function + */ + +int main(void) +{ + // Set PB1 as output (Toggled on data reception) + DDRB |= (1 << PB1); + + v_puts("Initializing...\r"); + UsiI2cSlv_Init(); + + v_puts("sei\r"); + sei(); + + while (1) { + if (loopBufDatLen) + printLoopBuf(); + } + + return 0; +} diff --git a/examples/board_usi_i2cslave/i2ctest_usislave.c b/examples/board_usi_i2cslave/i2ctest_usislave.c new file mode 100644 index 000000000..172b4fa40 --- /dev/null +++ b/examples/board_usi_i2cslave/i2ctest_usislave.c @@ -0,0 +1,455 @@ +/* + i2ctest.c + + Copyright 2021 Sebastian Koschmieder + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include + +#include "avr_ioport.h" +#include "avr_usi.h" +#include "sim_avr.h" +#include "sim_elf.h" +#include "sim_gdb.h" +#include "sim_vcd_file.h" + +static avr_cycle_count_t awaitCallback(struct avr_t * avr, avr_cycle_count_t when, void * param) { + (void)avr; (void)when; + *((int*)param) = 0; + return 0; +} +static void await_avr_usec(avr_t *avr, uint32_t usec) { + int looping = 1; + if (usec) { + avr_cycle_timer_register_usec(avr, usec, awaitCallback, &looping); + } + else {//Single cycle + avr_cycle_timer_register(avr, 1UL, awaitCallback, &looping); + } + while (looping) { + avr_run(avr); + } +} + +//--------------------------------------- +const char avr_i2c_port = 'B'; +const uint8_t avr_sda_bit = 0; +const uint8_t avr_scl_bit = 2; + +typedef enum { + SDA_SEND_LOW = 0, + SDA_SEND_HIGH = 1, + SDA_LISTENING = 2 +} sda_mode_t; + +typedef struct { + avr_irq_t *slv_sda; + avr_irq_t *slv_scl; +} i2c_bus_t; + +struct msg { + enum { TWI_WRITE, TWI_READ } mode; + uint8_t address; + char *buffer; + size_t size; +}; +struct chain_msgs { + struct msg *msg; + int msg_cnt; +}; + +typedef struct { + avr_t *avr; + i2c_bus_t bus; + avr_irq_t *irq; + uint8_t selected; + struct chain_msgs *msg_chain; + int current_msg; + int cur_msg_byte; +} i2c_master; + +static inline struct msg *msg_current( i2c_master *ma ) { + return &ma->msg_chain->msg[ ma->current_msg ]; +} + +static inline struct msg *msg_preview( i2c_master *ma ) { + return &ma->msg_chain->msg[ ma->current_msg + 1 ]; +} + +static inline int msg_hasNext( i2c_master *ma ) { + return ( ma->current_msg + 1 ) < ma->msg_chain->msg_cnt; +} + +static inline void msg_moveNext( i2c_master *ma ) { + ma->current_msg++; +} + +static inline void msg_free( i2c_master *ma ) { + free( ma->msg_chain->msg ); + ma->msg_chain->msg = NULL; + free( ma->msg_chain ); + ma->msg_chain = NULL; +} + +//------------------------------------------------------------------------------ +static inline uint8_t twi_get_bus_state(i2c_master *ma) { + avr_ioport_state_t iostate; + avr_ioctl(ma->avr, AVR_IOCTL_IOPORT_GETSTATE(avr_i2c_port), &iostate); + uint8_t ret = (iostate.pin & (0x01U << avr_sda_bit)) ? 0b01U : 0; + ret |= (iostate.pin & (0x01U << avr_scl_bit)) ? 0b10U : 0; + return ret; +} + +static void twi_set_sda(i2c_master *ma, sda_mode_t mode) { + avr_ioport_state_t iostate; + + switch (mode) { + case SDA_SEND_LOW: + avr_raise_irq(ma->irq + USI_IRQ_DI, 0); + ma->bus.slv_sda->flags |= IRQ_FLAG_STRONG; + break; + case SDA_SEND_HIGH: + ma->bus.slv_sda->flags &= ~IRQ_FLAG_STRONG; + avr_raise_irq(ma->irq + USI_IRQ_DI, 1); + break; + case SDA_LISTENING: + ma->bus.slv_sda->flags &= ~IRQ_FLAG_STRONG; + avr_ioctl(ma->avr, AVR_IOCTL_IOPORT_GETSTATE(avr_i2c_port), &iostate); + if (!(iostate.ddr & (0x01U << avr_sda_bit))) + avr_raise_irq(ma->irq + USI_IRQ_DI, 1);//pullup only if MCU not outputing already + break; + } +} +static void twi_set_scl(i2c_master *ma, uint8_t high) { + avr_irq_t *usi_scl = avr_io_getirq(ma->avr, AVR_IOCTL_USI_GETIRQ(), USI_IRQ_USCK); + if (high) { + do { //Wait till the User Flag used for stretching is cleared + avr_raise_irq(ma->irq + USI_IRQ_USCK, 1); + await_avr_usec(ma->avr, 0); + } while(usi_scl->value == 0); + } else { + avr_raise_irq(ma->irq + USI_IRQ_USCK, 0); + } +} + +static void twi_send_start(i2c_master *ma) { + //Note if no stop was sent, the start will be a repeated start + switch (twi_get_bus_state(ma)) { + case 0b10: //SCL BUS_LINE_HIGH, SDA BUS_LINE_LOW + await_avr_usec(ma->avr, 1); + twi_set_scl(ma, 0); + FALLTHROUGH + case 0b00: //SCL BUS_LINE_LOW, SDA BUS_LINE_LOW + await_avr_usec(ma->avr, 1); + twi_set_sda(ma, SDA_SEND_HIGH); + FALLTHROUGH + case 0b01: //SCL BUS_LINE_LOW, SDA BUS_LINE_HIGH + await_avr_usec(ma->avr, 1); + twi_set_scl(ma, 1); + FALLTHROUGH + default: //SCL BUS_LINE_HIGH, SDA BUS_LINE_HIGH + await_avr_usec(ma->avr, 1); + break; + } + twi_set_sda(ma, SDA_SEND_LOW); //SDA going low while SCL is high + await_avr_usec(ma->avr, 0); + twi_set_scl(ma, 0); +} + +static void twi_send_stop(i2c_master *ma) { + printf("Sending Stop\n"); + switch (twi_get_bus_state(ma)) { + case 0b11: //SCL BUS_LINE_HIGH, SDA BUS_LINE_HIGH + await_avr_usec(ma->avr, 1); + twi_set_scl(ma, 0); + FALLTHROUGH + case 0b01: //SCL BUS_LINE_LOW, SDA BUS_LINE_HIGH + await_avr_usec(ma->avr, 1); + twi_set_sda(ma, SDA_SEND_LOW); + FALLTHROUGH + case 0b00: //SCL BUS_LINE_LOW, SDA BUS_LINE_LOW + await_avr_usec(ma->avr, 1); + twi_set_scl(ma, 1); + FALLTHROUGH + default: //SCL BUS_LINE_HIGH, SDA BUS_LINE_LOW + await_avr_usec(ma->avr, 1); + break; + } + twi_set_sda(ma, SDA_SEND_HIGH); + await_avr_usec(ma->avr, 0); +} + +static uint8_t twi_send_byte(i2c_master *ma, uint8_t byte) { + if (ma->bus.slv_scl->value) { + await_avr_usec(ma->avr, 0); + twi_set_scl(ma, 0); + } + for (int i = 8; i != 0; --i) { + await_avr_usec(ma->avr, 3); //Changing in the peak of the SCL low period + twi_set_sda(ma, (byte & 0x80U) ? SDA_SEND_HIGH : SDA_SEND_LOW); + await_avr_usec(ma->avr, 3); + twi_set_scl(ma, 1); + await_avr_usec(ma->avr, 6); //Time for slave to read + twi_set_scl(ma, 0); + byte <<= 1; + } + twi_set_sda(ma, SDA_LISTENING); + await_avr_usec(ma->avr, 6); //Time for slave to change pin + twi_set_scl(ma, 1); + await_avr_usec(ma->avr, 3);//Reading in the peak of the SCL high period + register uint8_t ret = !(ma->bus.slv_sda->value & 0xff); + await_avr_usec(ma->avr, 3); + twi_set_scl(ma, 0); + return ret; //1 for Acked, 0 for Nacked +} + +static uint8_t twi_read_byte(i2c_master *ma, uint8_t ack) { + register uint8_t shift_reg = 0; + if (ma->bus.slv_scl->value) { + await_avr_usec(ma->avr, 0); + twi_set_scl(ma, 0); + } + await_avr_usec(ma->avr, 0); + twi_set_sda(ma, SDA_LISTENING); + for (int i = 8; i != 0; --i) { + shift_reg <<= 1; + await_avr_usec(ma->avr, 6); //Time for slave to change the line + twi_set_scl(ma, 1); + await_avr_usec(ma->avr, 3); //Reading in the peak of the SCL high period + shift_reg += !!(ma->bus.slv_sda->value & 0xff); + await_avr_usec(ma->avr, 3); + twi_set_scl(ma, 0); + } + await_avr_usec(ma->avr, 3); //Changing in the peak of the SCL low period + twi_set_sda(ma, (ack) ? SDA_SEND_LOW : SDA_SEND_HIGH); + await_avr_usec(ma->avr, 3); + twi_set_scl(ma, 1); + await_avr_usec(ma->avr, 6); //Time for slave to read + twi_set_scl(ma, 0); + return shift_reg; +} + +void i2c_master_init(avr_t *avr, i2c_master *ma) { + static const char *_master_irq_names[] = { + [USI_IRQ_DI] = "master.sda", + [USI_IRQ_DO] = "", + [USI_IRQ_USCK] = "master.scl", + [USI_IRQ_TIM0_COMP] = "", + }; + ma->avr = avr; + ma->bus.slv_sda = avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(avr_i2c_port), avr_sda_bit); + ma->bus.slv_scl = avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(avr_i2c_port), avr_scl_bit); + ma->irq = avr_alloc_irq(&avr->irq_pool, 0, USI_IRQ_COUNT, _master_irq_names); + ma->selected = 0; +} + +void i2c_master_attach(avr_t *avr, i2c_master *ma) { + avr_connect_irq(ma->irq + USI_IRQ_DI, ma->bus.slv_sda); + avr_connect_irq(ma->irq + USI_IRQ_USCK, ma->bus.slv_scl); + avr_raise_irq(ma->irq + USI_IRQ_DI, 1UL); + avr_raise_irq(ma->irq + USI_IRQ_USCK, 1UL); +} +//--------------------------------------- + +static void twi_startTransmission(i2c_master *ma, struct chain_msgs *msg_chain) { + ma->current_msg = 0; + ma->cur_msg_byte = 0; + ma->msg_chain = msg_chain; + + if( !msg_chain || !msg_chain->msg || !msg_chain->msg_cnt) { + return; + } + int addr_byte, ack = 0; + + do { + ma->selected = msg_current(ma)->address; + addr_byte = ma->selected << 1; + if( msg_current(ma)->mode == TWI_READ ) { + addr_byte |= 0x01U; + } + + printf("Sending start to 0x%02X %c\n", ma->selected, msg_current(ma)->mode?'r':'w'); + twi_send_start(ma); + ack = twi_send_byte(ma, addr_byte); + if (!ack) { + printf("NACKED\n"); + msg_current(ma)->size = 0; //Meaning error in transmission + } else { + printf("ACKED\n"); + char *buf = msg_current(ma)->buffer; + if( msg_current(ma)->mode == TWI_READ ) { + for (int i = msg_current(ma)->size; i != 0; --i) { + *buf = twi_read_byte(ma, 1); + buf++; + } + twi_read_byte(ma, 0); + } else { + for (int i = 0; i < msg_current(ma)->size; ++i) { + ack = twi_send_byte(ma, *buf); + if (!ack) { + printf("NACKED @ byte %d\n", i); + break; + } + buf++; + } + } + } + //Stop here if there is no other message to send + if (!msg_hasNext(ma)) { + twi_send_stop(ma); + break; + } + msg_moveNext(ma); + //Checking if address has changed so STOP is sent + if (ma->selected != msg_current(ma)->address) { + twi_send_stop(ma); + } //Otherwise repeated start will be used + } while(1); +} + +avr_cycle_count_t twi_sendSomething(i2c_master *ma, uint8_t addr) { + static uint8_t adr_buf = '2'; + static char str_buf[] = "Hello AVR!"; + struct msg *msg = malloc( sizeof( struct msg ) * 2 ); + struct chain_msgs *msg_chain = malloc( sizeof( struct chain_msgs ) ); + if(!msg || !msg_chain) + exit(1); + + msg[0].address = addr; + msg[0].mode = TWI_WRITE; + msg[0].buffer = ( char * ) &adr_buf; + msg[0].size = sizeof( uint8_t ); + + msg[1].address = addr; + msg[1].mode = TWI_WRITE; + msg[1].buffer = &str_buf[0]; + msg[1].size = sizeof( str_buf ) - 1; + + msg_chain->msg = msg; + msg_chain->msg_cnt = 2; + + twi_startTransmission(ma, msg_chain); + + return 0; +} + +avr_cycle_count_t twi_receiveSomething(i2c_master *ma, uint8_t addr) { + static uint8_t adr_buf = '0'; + static char str_buf[7]; + struct msg *msg = malloc( sizeof( struct msg ) * 2 ); + struct chain_msgs *msg_chain = malloc( sizeof( struct chain_msgs ) ); + if(!msg || !msg_chain) + exit(1); + + msg[0].address = addr; + msg[0].mode = TWI_WRITE; + msg[0].buffer = ( char * ) &adr_buf; + msg[0].size = sizeof( uint8_t ); + + msg[1].address = addr; + msg[1].mode = TWI_READ; + msg[1].buffer = &str_buf[0]; + msg[1].size = sizeof( str_buf ) - 1; + + msg_chain->msg = msg; + msg_chain->msg_cnt = 2; + + twi_startTransmission( ma, msg_chain); + if (msg[1].size) + printf("Received Data: \e[31m%s\e[0m\n", str_buf); + + return 0; +} + +int main(int argc, char *argv[]) { + elf_firmware_t f = {}; + const char *fname = "attiny85_i2cslave.axf"; + + printf("Firmware pathname is %s\n", fname); + elf_read_firmware(fname, &f); + + printf("firmware %s f=%d mmcu=%s\n", fname, (int)f.frequency, f.mmcu); + + avr_t *avr = NULL; + avr_vcd_t vcd_file; + + avr = avr_make_mcu_by_name(f.mmcu); + if (!avr) { + fprintf(stderr, "%s: AVR '%s' not known\n", argv[0], f.mmcu); + exit(1); + } + avr_init(avr); + avr_load_firmware(avr, &f); + + //Configuring Fuses for 8MHz operation + avr->fuse[AVR_FUSE_LOW] = 0xE2; + avr->fuse[AVR_FUSE_HIGH] = 0xDF; + avr->fuse[AVR_FUSE_EXT] = 0xFF; + + // even if not setup at startup, activate gdb if crashing + avr->gdb_port = 1234; + // avr->log = 4; + if (0) { + avr->state = cpu_Stopped; + avr_gdb_init(avr); + } + avr_set_console_register(avr, 0x31/*GPIOR0*/); + + i2c_master master; + i2c_master_init(avr, &master); + i2c_master_attach(avr, &master); + + /* + * VCD file initialization + * This will allow you to create a "wave" file and display it in gtkwave + */ + avr_irq_t *ddrb_irq = avr_iomem_getirq(avr, 0x37, "DDRB", AVR_IOMEM_IRQ_ALL); + avr_irq_t *usicr_irq = avr_iomem_getirq(avr, 0x2D, "USICR", AVR_IOMEM_IRQ_ALL); + avr_irq_t *usisr_irq = avr_iomem_getirq(avr, 0x2E, "USISR", AVR_IOMEM_IRQ_ALL); + avr_irq_t *usidr_irq = avr_iomem_getirq(avr, 0x2F, "USIDR", AVR_IOMEM_IRQ_ALL); + avr_irq_t *usi_st_vect_irq = avr_get_interrupt_irq(avr, 13); + avr_irq_t *usi_ov_vect_irq = avr_get_interrupt_irq(avr, 14); + avr_vcd_init(avr, "gtkwave_output.vcd", &vcd_file, 100 /* usec */); + avr_vcd_add_signal(&vcd_file, master.bus.slv_sda, 1 /* bits */, "SDA" ); + avr_vcd_add_signal(&vcd_file, master.bus.slv_scl, 1 /* bits */, "SCL" ); + avr_vcd_add_signal(&vcd_file, ddrb_irq , 8 /* bits */, "DDRB"); + avr_vcd_add_signal(&vcd_file, usicr_irq, 8 /* bits */, "USICR"); + avr_vcd_add_signal(&vcd_file, usisr_irq, 8 /* bits */, "USISR"); + avr_vcd_add_signal(&vcd_file, usidr_irq, 8 /* bits */, "USIDR"); + avr_vcd_add_signal(&vcd_file, usi_st_vect_irq, 1 /* bits */, "USIST_vect" ); + avr_vcd_add_signal(&vcd_file, usi_ov_vect_irq, 1 /* bits */, "USIOV_vect" ); + + printf("\nDemo launching:\n"); + avr_vcd_start(&vcd_file); + + await_avr_usec(avr, 1000); + twi_sendSomething(&master, 0x42); + await_avr_usec(avr, 100000); + twi_receiveSomething(&master, 0x42); + await_avr_usec(avr, 100000); + twi_sendSomething(&master, 0x21); + await_avr_usec(avr, 100000); + twi_receiveSomething(&master, 0x21); + await_avr_usec(avr, 200000); + + avr_vcd_close(&vcd_file); + await_avr_usec(avr, 100000); + return 0; +}