Im Zusammenhang für das Projekt "RN-Netzwerk", wo es ja darum geht, PC und Avr zu verbinden, ist ja natürlich auch eine Integration des I2C-Bus unbedingt erforderlich. Da ich mit einer RNBFRA 1.2 Karte gesegnet bin, sollte natürlich gerade der AT90S2313 (der Co-Prozessor) auch zum Mitspieler werden. Das bisherige "durchrouten" über die RS232 ist ja doch nicht sehr befriedigend.
Inhaltsverzeichnis
Bascom Software I2C Routinen
Das Ziel ist:
- Kommunikation auf der PC-Plattform via IP (Stream-Sockets),
- vom PC zum Atmega32 über COMx und AVR-UART,
- und auf der Mikrocontroller-Ebene dann über I2C, (später ev. auch andere Busse)
I2C auf AT90S2313
In diesem Prozessor (und auch auf dem Tiny2313) gibt's keine Hardwarunterstützung. Und in einer Multimaster-Umgebung war auch mit der I2C.LIB von Bascom nichts zu machen. Denn gerade bei I2CSTART kümmert sich Bascom in keiner Weise darum, ob auf dem Bus gerade irgendwas abläuft. Und dann war ja immer noch das Problem, daß es keine SLAVE-Funktionen gibt (Nur um Geld).
Step 1: Multimasterfähige I2c-Funktionen
Es genügte fürs Erste,
- die Start-Routine durch eine multimasterfähige Funktion zu ersetzen,
$external I2cwaitbus 'wait for free bus & reserve it
- und das Adressen- oder Bytesenden durch eine Funktion, die auch die Arbitrierung kontrolliert.
$external I2cwmaster 'send byte & watch arbitration
- I2crbyte u. I2cstop kann man eigentlich im Moment lassen, da sie nichts böses tun.
Step 2: I2c-Slave-Funktionen
Da gibt es eigentlich nur drei Funktionen:
- warten auf eine Startbedingung und lesen der I2C-Adresse
$external I2c_get_addr 'wait for start-cond & read in I2C address
Und, je nachdem, ob Read oder Write
- Bytes vom Master empfangen
$external I2c_slave_rx_data 'ACK & receive master data
- Bytes zum Master senden
$external I2c_slave_tx_byte 'ACK & send data to master
I2c_get_addr
Ich habe aus mehreren Gründen darauf verzichtet, die Adress-Prüfung zu automatisieren, wie das ja sonst üblich ist:
- Ich möchte in weiterer Folge die Funktionen auch als eine Art I2C-Monitor/Logger verwenden
- Ich seh' nicht ein, warum ein Slave immer sowohl Read als auch Write zulassen soll
- Und ich wollt mir auch die Parametriesierung ersparen, ob auch General-Calls oder nicht akzeptiert werden sollen. Irgendeine Art von "IF" muss der User ja sowieso machen, also soll er auch gleich selbst entscheiden, was er "acked" oder nicht.
d.h. also, wenn die Funktion zurückkommt, ist ein I2C-Start erfolgt und eine Adresse ist gesendet worden. Die steht dann vollständig in Feld "I2C_ADDR"
- NULL ist eine General Call Adresse
- SLA+W ist eine "Slave-Write" Adresse
- SLA+R ist eine "Slave-Read" Adresse
Welche SLA-Adresse seine eigene ist, kann der User nun selbst entscheiden.
I2c_slave_rx_data
wird das aufgerufen, wird ein ACK ausgesendet und es werden Daten vom Master empfangen und in den Buffer geschrieben, der im Feld "I2c_write" angegeben ist. Wieviel das sind, entscheidet der Master durch "STOP" oder "Repeated Start". Einen Begrenzung seitens der Funktion ist nicht vorgesehen, da sich ja die Applikationen im Master und im Slave sowieso irgendwie vereinbart haben müssen, wieviele Bytes wann zu senden sind.
Ist die Routine fertig, steht die Anzahl der empfangenen Bytes im Feld "I2c_cntr"
I2c_slave_tx_byte
wird das aufgerufen, wird ein ACK ausgesendet und es werden die Daten zum Master gesendet, die im Feld "I2c_read" angegeben sind. Beendet wird das vom Master duch ein "NACK". Einen Begrenzung der Anzahl durch die Funktion ist auch hier nicht vorgesehen.
Ist die Routine fertig, steht die Anzahl der gesendeten Bytes im Feld "I2c_cntr", falls das den User interessieren sollte.
DEMO MYI2C.ZIP
Das Demo-Set Bascom_Soft-I2c_Library#Web_Links kann heruntergeladen werden, besteht aus drei Teilen
- 2313.bas Demo Programm
- myi2c.bas Include File
- myi2c.lib eine Bascom library, die gehört zu den anderen Bascom Libraries, irgendwo im Installations-directory.
Beispiel 2313.bas
Das Demo-Programm macht eigentlich nix ausser Bytes zu empfangen oder zu senden. Wurde was empfangen und es ist auch gerade eine Sekunde 'rum, sendet er selbst an das RNBFRA Out-Port (PCF) das erste Byte, das im Empfangsbuffer steht. Damit sind die I2C Routinen prinzipiell gut zu testen.
'---------------------------------------------- ' 2313 oder was anderes als I2C Soft-Slave '---------------------------------------------- ' PicNick was here www.roboternetz.de '---------------------------------------------- ' Das ist ein einfacher Sende- und Empfangs Slave '---------------------------------------------- $crystal = 4000000 ' Quarzfrequenz $baud = 9600 'Baudrate für RS232 (Gewohnheit) $hwstack = 48 $swstack = 16 $framesize = 16 Const Tmr_c_prescale = 64 Const Tmr_c_preload = 191 Const Tmr_c_divis = 1000 Config Timer0 = Timer , Prescale = Tmr_c_prescale 'Timer 1mS On Timer0 Interrupt_ticker ' Timer for Timer Queue Dim Timeout As Byte Dim Timediv As Word '------------------------------------- ' Pin-definition I2C '------------------------------------- Config I2cdelay = 5 Config Scl = Portd.3 'Ports fuer IIC-Bus Config Sda = Portd.2 '--------------------------- Library und definitionen ------------------ $include "MyI2c.BAS" '------------------------------------- ' Address-definition RNBFRA '------------------------------------- Const Pwr_adr = &H74 'I2C Adress Powerport Const Out_adr = &H72 'I2C Adress Out PCF Const Exp_adr = &H7E 'I2C Adress PCF Expansion Const Co1_adr = &H68 'I2C Adress 2313 I Const Co1_adrr = &H69 'I2C Adress 2313 I Const Mn1_adr = &H6A 'I2C Adress Atmega I Const Mn1_adrr = &H6B 'I2C Adress Atmega I Const Co2_adr = &H6C 'I2C Adress 2313 II Const Co2_adrr = &H6D 'I2C Adress 2313 II Const Mn2_adr = &H6E 'I2C Adress Atmega II Const Mn2_adrr = &H6F 'I2C Adress Atmega II Dim Sent_flag As Byte Dim M32_byte(24) As Byte 'I2C Buffer Senden & empfangen Enable Timer0 Enable Interrupts '--------------------------------------------- ' '--------------------------------------------- I2cinit ' Bascom soll auch was tun I2c_write = Varptr(m32_byte(1)); ' Data receive-Buffer I2c_read = Varptr(m32_byte(1)); ' Data send-Buffer Do Loadadr I2c_flag , Z Gosub I2c_get_addr ' I2C Adresse read Select Case I2c_addr Case Co1_adr: Loadadr I2c_flag , Z Gosub I2c_slave_rx_data ' daten empfangen bis Stop/Rep Sent_flag = 1 '------------------------------------ ' WORKOUT Message '------------------------------------ Case Co1_adrr: Loadadr I2c_flag , Z Gosub I2c_slave_tx_byte ' daten senden bis NAK Case Else I2cinit End Select If Sent_flag = 1 And Timeout = 1 Then Sent_flag = 0 Timeout = 0 Gosub Out_transmit End If Loop End '----------------------------------------------------- Interrupt_ticker: Timer0 = Tmr_c_preload Incr Timediv If Timediv >= Tmr_c_divis Then Timeout = 1 Timediv = 0 End If Return '----------------------------------------------------- ' '----------------------------------------------------- Out_transmit: Do Gosub I2cwaitbus ' wait for Bus $asm ldi r17, out_adr !Call I2cwmaster ' send SLA+W & arbitr.Check $end Asm If Err = 0 Then Loadadr M32_byte(1) , X $asm ld r17, x+ !Call I2cwmaster ' send data & arbitr.Check $end Asm End If If Err = 0 Then I2cstop End If Loop Until Err = 0 I2cinit Return
Include-File MyI2c.bas
'---------------------------------------------- ' MyI2C.BAS ' I2c Soft-functions '---------------------------------------------- ' PicNick was here www.roboternetz.de '---------------------------------------------- '--------------------------- Library und definitionen ------------------ $lib "MyI2c.LIB" '--------------------------- Slave receive & Send --------------------- Const I2c_m_start = 1 $external I2c_get_addr 'wait for start-cond & read in I2C address Declare Sub I2c_get_addr $external I2c_slave_rx_data 'ACK & receive master data Declare Sub I2c_slave_rx_data $external I2c_slave_tx_byte 'ACK & send data to master Declare Sub I2c_slave_tx_byte '--------------------------- Multimaster sending --------------------- $external I2cwaitbus 'wait for free bus & reserve it Declare Sub I2cwaitbus $external I2cwmaster 'send byte & watch arbitration Declare Sub I2cwmaster '------------------------------------- ' I2C-STRUCTURE '------------------------------------- Dim I2c_flag As Byte '0 Kontroll-flags Dim I2c_addr As Byte '1 slave adresse Dim I2c_cntr As Byte '2 bei empfang anzahl bytes Dim I2c_write As Word '3/4 adresse empfangsbuffer Dim I2c_read As Word '5/6 adresse sendebuffer '--------------------------------------------- ' '--------------------------------------------- I2cinit
Bascom Library Myi2c.lib
copyright = R.Toegel www = http://www.oldformation.at email = roberttoegel@aon.at comment = BASCOM-AVR I2C Slave / Multimaster library add to I2c.LIB libversion = 1.0.0.1 date = 26 Jun 2006 statement = Based on ATMEL application note history = This lib is an addon to i2c.lib ;----------------------------------------------------- ; I2c_slave_tx_ack Send ACK ; I2c_slave_tx_nak Send NACK ; I2c_release Release Lines ; I2c_wait_start Wait for Start Condition ; I2c_get_addr Get Message Address ; I2c_slave_tx_byte Slave Transmitter ; I2c_slave_rx_data Slave Receiver ; I2c_read_byte Internal ;----------------------------------------------------- ; I2cwaitbus ; I2cwmaster ;----------------------------------------------------- [I2c_slave_tx_ack] I2c_slave_tx_ack: * sbi _sdaDDR,_sda ;Hold SDA LO (ACK) Wt_ack_scl: * in r1, SREG ; save SREG cli ; disable interrupts * cbi _sclDDR,_scl ;SCL Release Wt_scl_hi2: * sbis _sclPIN,_scl ;SCL rjmp wt_scl_hi2 ;wait SCL hi Wt_scl_lo3: * sbic _sclPIN,_scl ;SCL rjmp wt_scl_lo3 ;wait SCL lo * sbi _sclDDR,_scl ;SCL Stretch * cbi _sdaDDR,_sda ;release SDA * out SREG, R1 ret [end] ;----------------------------------------------------- [I2c_slave_tx_nak] I2c_slave_tx_nak: $external I2c_slave_tx_ack * cbi _sdaDDR,_sda ;Release SDA rjmp Wt_ack_scl [end] ;----------------------------------------------------- [I2c_release] I2c_release: * cbi _sdaDDR,_sda ;SDA Release * cbi _sclDDR,_scl ;SCL Release clr r22 std Z + 0, r22 ;clear flag ret [end] ;----------------------------------------------------- [I2c_wait_start] I2c_wait_start: ldi r22, 0 std Z + 0, r22 ; clear I2c_flag Wt_hi_hi: * sbis _sclPIN,_scl ; SCL ? rjmp wt_hi_hi ; wait SCL hi * sbis _sdaPIN,_sda ; SDA ? rjmp wt_hi_hi ; wait SDA hi Wt_sda_lo0: * sbic _sdaPIN,_sda ; SDA rjmp wt_sda_lo0 ; wait SDA lo * sbis _sclPIN,_scl ; SCL start condition ? rjmp wt_hi_hi ; no, repeat the whole sequence Wt_scl_lo0: * sbic _sclPIN,_scl ; SCL rjmp wt_scl_lo0 ; wait SCL lo * sbi _sclDDR,_scl ; SCL Stretch ret [end] ;----------------------------------------------------- [I2c_get_addr] I2c_get_addr: $external I2c_wait_start ldd r22, Z + 0 ; i2c_flag * cpi r22, I2c_m_Start breq rept_s ; start-cond. already set rcall I2c_wait_start ; wait for Bus-start rept_s: * clr r22 std Z + 0, r22 ;clear I2c_flag ldi r24 ,1 ;set 'end'-Bit * in r1, SREG ; save SREG cli ; disable interrupts clc ;clear carry Wta_scl_hi: * cbi _sclDDR,_scl ;SCL Release * sbis _sclPIN,_scl ;SCL rjmp wta_scl_hi ;wait SCL hi * sbic _sdaPIN,_sda ;SDA = 0 ? sec ;SDA = 1 -> set carry Wta_scl_lo: * sbic _sclPIN,_scl ;SCL rjmp wta_scl_lo ;wait SCL lo * sbi _sclDDR,_scl ;SCL Stretch rol r24 ;roll carry in/out brcc Wta_scl_hi ;Bit loop std Z + 1, r24 ;Store i2c address * out SREG, r1 ret [end] ;----------------------------------------------------- [I2c_slave_rx_data] I2c_slave_rx_data: ldd xl, Z + 3 ; Load Buffer Address ldd xh, Z + 4 ; clr r23 std Z + 2, r23 ; clear counter I2c_read_loop: $external I2c_slave_tx_ack rcall I2c_slave_tx_ack ; Send ACK * in r1, SREG ; save SREG cli ; disable interrupts clt ; clear T-Bit rcall I2c_read_byte ; read 8 Bit brtc Rd_sto_ack ; T-Bit clear ? o.k.--> * out SREG, r1 std Z + 2, r23 ; store counter std z + 0, r22 ; set start/stop/none flag ret Rd_sto_ack: * out SREG, r1 st x+, r24 ; store character inc r23 ; data counter++ rjmp I2c_read_loop ; no, cont'd next byte '------------------------------------------- I2c_read_byte: ldi r24 ,1 ;set end-bit clc ;clear carry Rd_loop: * cbi _sclDDR,_scl ;SCL Release Wt_scl_hi: * sbis _sclPIN,_scl ; SCL rjmp wt_scl_hi ; wait SCL hi * sbis _sdaPIN,_sda ; SDA ? rjmp Wt_chk_0 ; SDA Low-Check sec ; SDA Hi-Check Wt_chk_1: * sbis _sclPIN,_scl ; SCL rjmp wt_ok_x ; scl low---> ok, next bit * sbic _sdaPIN,_sda ; SCL Hi & SDA lo --> repeated Start rjmp wt_chk_1 ; SCL & SDA Hi --> cont'd wait Rep_start_cond: * sbic _sclPIN,_scl ; SCL rjmp Rep_start_cond ; wait SCL lo * sbi _sclDDR,_scl ; SCL STRETCH SET ; set t-Bit * ldi r22, I2c_m_Start ; signal REP-Start ret Wt_chk_0: ; SDA is Low * sbis _sclPIN,_scl ; SCL ? rjmp wt_ok_x ; scl low---> ok, next bit * sbis _sdaPIN,_sda ; SDA ? rjmp wt_chk_0 ; SCL Hi & SDA lo --> cont'd wait Stop_cond: : stop-condition SET ; set t-bit ret ; xit Wt_ok_x: * sbi _sclDDR,_scl ; STRETCH rol r24 ; roll carry in/out brcc rd_loop ; next bit ret ; byte done [end] ;----------------------------------------------------- [I2c_slave_tx_byte] I2c_slave_tx_byte: $external I2c_slave_tx_ack rcall I2c_slave_tx_ack ; Send ACK ldd xl, Z + 5 ;Load Buffer Address ldd xh, Z + 6 ; * in r1, SREG ; save SREG cli ; disable interrupts rcall Tx_loop * out SREG, r1 ret ;-------------------------------------------------- Tx_loop: ld r24, x+ ; next character ldi r25, 8 ; 8 Bit Tx_c_loop: Sbrc R24 , 7 ;Data Bit ? rjmp Tx_Bit_1 ;set SDA High ;-------------------------------------------------- ;---------------- send NULL------------------------ * sbi _sdaDDR,_sda ;set SDA Low * cbi _sclDDR,_scl ;SCL Release-------------------- Tx0_scl_hi_0: * sbis _sclPIN,_scl ;SCL rjmp Tx0_scl_hi_0 ;wait scl hi nop nop Tx0_scl_lo_0: * sbic _sclPIN,_scl ;SCL rjmp Tx0_scl_lo_0 ;wait scl lo * sbi _sclDDR,_scl ;SCL Stretch rjmp Tx_step ;-------------------------------------------------- ;---------------- send HIGH------------------------ Tx_bit_1: * cbi _sdaDDR,_sda ;SDA Release High * cbi _sclDDR,_scl ;SCL Release Tx1_scl_hi_0: * sbis _sclPIN,_scl ;SCL rjmp Tx1_scl_hi_0 ;wait scl hi * sbis _sdaPIN,_sda ;SDA HI ? ret ;Bus Lost , anyhow Tx1_scl_lo_0: * sbic _sclPIN,_scl ;SCL rjmp Tx1_scl_lo_0 ;wait scl lo * sbi _sclDDR,_scl ;SCL Stretch Tx_step: rol r24 ;roll carry in/out dec r25 ;count bits brne Tx_c_loop ;next bit ;-------------------------------------------------- ;---------------- get ACK/NAK --------------------- * cbi _sdaDDR,_sda ;SDA Release * cbi _sclDDR,_scl ;SCL Release Tx_scl_hi_1: ;get ack bit * sbis _sclPIN,_scl ;SCL rjmp Tx_scl_hi_1 ;wait scl hi * sbic _sdaPIN,_sda ;SDA (ACK) ? ret ;no-ack --> return Tx_scl_lo_1: * sbic _sclPIN,_scl ;SCL rjmp Tx_scl_lo_1 ;wait scl lo * sbi _sclDDR,_scl ;SCL Stretch rjmp tx_loop ; next [end] ;--------------------------------------------------------- ; MASTER Functions ;--------------------------------------------------------- ; Wait for free Bus ;--------------------------------------------------------- [I2cwaitbus] I2cwaitbus: $EXTERNAL _I2C * in r1, SREG ; save SREG cli ; disable interrupts Ck_1_x: LDI r23,0x0f ; 15 times Ck_1_y: * sbis _sclPIN,_scl ; SCL RJMP Ck_1_x ; wait SCL Hi * sbis _sdaPIN,_sda ; SDA HI ? RJMP Ck_1_x ; wait SDA Hi DEC r23 BRNE Ck_1_y ; (8) hang on * sbi _sdaDDR,_sda ; SDA down for Start * out SREG, r1 Rjmp _i2c_hp_delay ; half period delay [end] ;--------------------------------------------------------- ; Send one Byte R17 & check Arbitration ;--------------------------------------------------------- [I2cwmaster] I2cwmaster: $EXTERNAL _I2C * in r1, SREG ; save SREG cli ; disable interrupts Sec ; set carry flag Rol r17 ; shift in carry and out bit one rcall I2cwmaster_loop * out SREG, r1 ret I2cwmaster_loop: * sbi _sclDDR,_scl ;SCL DOWN BRCC I2cwmaster_low ;carry clear --> send Low ; send High ------------------------------------------ NOP ; filler * cbi _sdaDDR,_sda ; release SDA RCALL _i2c_hp_delay ; wait * cbi _sclDDR,_scl ; SCL Release Tx_1_ck: * sbis _sclPIN,_scl ; SCL RJMP Tx_1_ck ; wait SCL Hi * sbis _sdaPIN,_sda ; SDA still HI ? RJMP Tx_lost ; No, ---> arbitration lost Tx_1_ok: RCALL _i2c_hp_delay ;wait LSL r17 ;2^^7 -->Cy brne I2cwmaster_loop ;more bits ? ;--------------------------------------------------- I2cwmaster_loop_x: ; Get ACK / NACK * sbi _sclDDR,_scl ; SCL DOWN * cbi _sdaDDR,_sda ; release SDA RCALL _i2c_hp_delay ; wait * cbi _sclDDR,_scl ; SCL Release Tx_x_ck: * sbis _sclPIN,_scl ; SCL RJMP Tx_x_ck ; wait Hi CLT ; clear t-bit * sbic _sdaPIN,_sda ; SDA = ACK/NAK ? Tx_lost: SET ; set t-bit BLD r6,2 ; t-Bit -> to Bascom "ERR" RJMP _i2c_hp_delay ; wait & return ' send Low ------------------------------------------ I2cwmaster_low: * sbi _sdaDDR,_sda ;down SDA RCALL _i2c_hp_delay ;wait * cbi _sclDDR,_scl ;SCL Release Tx_0_ck: * sbis _sclPIN,_scl ;SCL RJMP Tx_0_ck ;wait SCL Hi RJMP Tx_1_ok ;continue [end]
Autor
Web Links
http://www.oldformation.at/electronic/download/down.htm