bit-banging - это тоже, что
дерганье за ниточки
Используя познания из предыдущего поста: "Введение в Bit-banging: "режимы работы GPIO микроконтроллеров AVR, организация последовательной шины" можно сделать что нибудь полезное. "Софтовая" реализация I2C шины будет не зависеть от модели микроконтроллера, будет работать на тех пинах которые вы зададите, и на мой взгляд, что самое важное, может служить примером протокола для взаимодействия между несколькими различными микроконтроллерами.
Иллюстрация I2C протокола, взятая из AppNoteAVR315: Using the TWI module as I2C master представлена ниже:
Waveform showing the clock and data timing for an I2C message.
Несмотря на то, что с первого взгляда может показаться сложновато, то обстоятельство, что время каждого такта может быть произвольным, делает этот протокол доволько удобным и простым в отладке. Можно прикрутить тактовую кнопку к протоколу, пару светодиодов на линии SDA и SCL, и олаживать его в ручном режиме без всякого осцилографа.
Сейчас можно написать пробную программу, для опроса I2C шины, на ниличие полюченных устройств. Алгоритм такой: в цикле от нуля до 255 будем выдавать последовательности: START -- ADDRESS -- STOP. Если на адрес будем получать ACK, значит такое устройство подключено к шине.
Сканнер I2C шины писал для модуля DS1307. На нем установленно два устройства: сам ds1307 и eeprom AT24. Даннай модуль сам формирует линию. Если подключить его к питанию и мультиметром замерить напряжение на контактах SDA и SCL, то там будет высокий уровень. Эти контакты находятся в pull-up режиме. Тогда замыкая SDA или SC на землю, мы будем иметь низкий уровень, а "отпуская" их линия будет возваращаться в высокий.
Тогда, если при инициализации запишем нули в PORT, переключать состояния линий можно будет регистром DDR. Записывая в DDR ноль будем "прижимать" линию за землю, это будет низкий уровень. Записывая в DDR единицу будем "отпускать" линию, это будет высокий уровень.
За основу я взял программу из поста: "UART+AVR: функция форматного вывода printf()", добавил bit-banging и получилась такая штука
/* for ATmega8/168/328 site: http://countzero.weebly.com avr-gcc -mmcu=atmega8 -Wall -Os -o i2c.elf i2c.c avr-objcopy -O ihex i2c.elf i2c.hex avrdude -patmega8 -carduino -P/dev/ttyUSB0 -b19200 -D -Uflash:w:./i2c.hex:i */ #define F_CPU 16000000UL #define LEN 32 #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> #include <compat/deprecated.h> #include <stdio.h> // Clock Line Port #define SCLPORT PORTD #define SCLDDR DDRD #define SCLPIN PIND // Data Line Port #define SDAPORT PORTD #define SDADDR DDRD #define SDAPIN PIND #define SCL PD2 #define SDA PD3 #define QDEL _delay_us(5) // i2c quarter-bit delay #define HDEL _delay_us(10) // i2c half-bit delay #define SDA_I2C_LO sbi(SDADDR, SDA) #define SDA_I2C_HI cbi(SDADDR, SDA) #define SCL_I2C_LO sbi(SCLDDR, SCL); #define SCL_I2C_HI cbi(SCLDDR, SCL); #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI; HDEL; SCL_I2C_LO; #define INIT_I2C cbi(SDAPORT, SDA); cbi(SCLPORT,SCL); char buffer[LEN]; register unsigned char IT asm("r16"); volatile unsigned char done; volatile unsigned char IDX; static inline void clearStr(char* str) { for(IT=0;IT<LEN;IT++) str[IT]=0; } static uint8_t uart_putchar(char c, FILE *stream) { if (c == '\n') uart_putchar('\r', stream); loop_until_bit_is_set(UCSRA, UDRE); UDR = c; return 0; } ISR(USART_RXC_vect) { char bf= UDR; buffer[IDX]=bf; IDX++; if (bf == ':' || IDX >= LEN) { IDX=0; done=1; } } static void blink13(uint8_t count) { PORTB |= (1<<PB5); count =(count <<1);count--; //count=(count*2)-1; for (IT=0;IT<count;IT++) { _delay_ms(500); PORTB ^= (1<<PB5); }; }; static uint8_t start_i2c() { uint8_t ret; ret=64; // attempts uint8_t valueSDA, valueSCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=SDAPIN & (1<<SDA); valueSCL=SCLPIN & (1<<SCL); ret--; } while ((valueSDA == 0 || valueSCL == 0) && ret >0); if (ret == 0) return 0; SDA_I2C_LO; QDEL; SCL_I2C_LO; QDEL; return ret; } static uint8_t stop_i2c() { SDA_I2C_LO; SCL_I2C_LO; HDEL; SCL_I2C_HI; QDEL; SDA_I2C_HI; QDEL; if ((SDAPIN & (1<<SDA)) == 0 || (SCLPIN & (1<<SCL)) == 0) return 1; else return 0; } static uint8_t send_i2c(uint8_t value) { uint8_t bits; bits=8; while (bits > 0) { bits--; SCL_I2C_LO; QDEL; while((SCLPIN &(1<<SCL)) != 0){}; if (value & (1<<bits)) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; } // get ACK QDEL; SDA_I2C_HI; QDEL; SCL_I2C_HI; uint8_t ack; ack = (SDAPIN & (1<<SDA)); QDEL; SCL_I2C_LO; HDEL; return ack; }; static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE); int main(void) { // USART init UBRRL=103; UCSRB=(1<<TXEN)|(1<<RXEN)|(1<<RXCIE); UCSRC=(1<<URSEL)|(3<<UCSZ0); DDRB |= (1<<PB5); // pinMode(13,OUTPUT); blink13(3); //ready indication IDX=0; done=0; sei(); stdout = &mystdout; printf("Start I2C\n"); INIT_I2C; uint8_t k; for(k=1; k < 256;k++) { if (start_i2c()) { uint8_t ask; ask=send_i2c(k); if (ask == 0) printf("Found device: %d\n", k); stop_i2c(); } else printf("Failure start I2C on: %d\n", k); _delay_ms(300); }; for (;;){}; return 0; }
Результат работы программы:
Найденое устройство 160 это 0xA0 EEPROM AT24
Устройство 208 это 0xD0 RTC DS1307
Часто, в скетчах Arduino можно увидеть адрес DS1307 как 0x68, но это адрес без младшего бита R/W. Т.е. 0x68<<1 = 0xD0. Достаточно будет обраться к официальной документации что бы развеять сомнения: DS1307 64 x 8, Serial, I2C Real-Time Clock