Projet de TDO Mémoires FLASH et bus de données Etude et comparaison Sophie Sivignon Florent Castelli Groupe 2 Mars 2005 Introduction Actuellement, sans forcément le savoir, nous sommes tous entourés par ce qui est une des dernières révolutions technologiques. Cette révolution a ouvert de nombreuses portes et énormément réduit les coups de fabrication. Celle-ci est la mémoire Flash. La mémoire Flash se retrouve partout : ordinateurs, mémoires pour appareils photos, clés USB, cartes contrôleur ou d’extension et bien d’autres applications. Cette mémoire si répandue de nos jours dépend du périphérique sur lequel elle est connectée. Et ce périphérique communique avec d’autres et échange des informations ; cela se fait par l’intermédiaire d’un bus de données. Tout le monde connaît au moins l’USB. Mais il en existe d’autres qui ont chacun leurs avantages et nous allons en étudier quelques uns dans ce rapport. Nous verrons donc une explication sur le fonctionnement des mémoires Flash. Ensuite nous nous intéresserons au fonctionnement du bus SPI et Microwire. Nous verrons en détail le bus I²C et enfin une comparaison entre ces déférents bus. Introduction _______________________________________________________________ 2 I – Mémoires Flash _________________________________________________________ 3 II – Bus de données _________________________________________________________ 5 1) SPI ______________________________________________________________________ 5 2) Microwire ________________________________________________________________ 6 3) I²C ______________________________________________________________________ 7 Présentation _________________________________________________________________________ 7 Protocole ___________________________________________________________________________ 7 Driver______________________________________________________________________________ 8 4) Comparaison______________________________________________________________ 8 Conclusion _______________________________________________________________ 10 Annexe __________________________________________________________________ 11 Driver I²C pour 6502 __________________________________________________________ 11 2 I – Mémoires Flash La mémoire flash est une mémoire à semi-conducteurs, non volatile et réinscriptible, c'est-àdire une mémoire possédant les caractéristiques d'une mémoire vive mais dont les données ne disparaissent pas lors d'une mise hors tension (mémoire de masse). Ainsi, la mémoire flash stocke les bits de données dans des cellules de mémoire, mais les données sont conservées en mémoire lorsque l'alimentation électrique est coupée. En dépit de sa relative lenteur, la durée de vie et la consommation faible (et même nulle au repos) de la mémoire flash la rendent utile pour de nombreuses applications : appareils photo numériques, téléphones cellulaires, assistants personnels (PDA), ordinateurs portables ou dispositifs de lecture et d'enregistrement sonore comme les baladeurs MP3, clef USB. De plus, ce type de mémoire ne possède pas d'éléments mécaniques, ce qui lui confère une assez grande résistance aux chocs. La mémoire flash est un type d'EEPROM (Electrically Erasable Programmable ReadOnly Memory) qui permet la modification de plusieurs espaces mémoires en une seule opération. La mémoire flash est donc plus rapide lorsque le système doit écrire à plusieurs endroits en même temps. Elle existe sous deux formes: flash NOR et NAND, d'après le type de pont logique utilisé pour chaque cellule de stockage. La mémoire flash NOR est la première à être développée par Atmel. Les temps d'effacement et d'écriture sont longs mais elle possède une interface d'adressage permettant un accès aléatoire à n'importe quelle position. Elle est adaptée à l'enregistrement de programmes informatiques qui sont rarement mis à jour, comme dans les appareils photo numériques ou les organisateurs personnels. Elle peut supporter de 10 000 à 100 000 cycles d'effacement. La mémoire flash NAND fut développée par Toshiba. Elle est plus rapide à l'effacement et à l'écriture, offre une plus grande densité, un coût moins important par bit et une durée de vie dix fois plus importante. Toutefois son interface d'entrée / sortie n'autorise que l'accès séquentiel aux données. Elle est donc utilisée pour le stockage d'informations et est moins utile en tant que mémoire pour ordinateurs. Dans la mémoire flash NOR, chaque cellule de mémoire est similaire à un transistor standard MOSFET, excepté qu'il y a deux portes au lieu d'une. Une des deux portes est le CG (Control Gate) comme dans les autres transistors MOS et la seconde est la FG (Floating Gate) qui est isolée par une couche d'oxyde. Ainsi les électrons, passant au dessus, se retrouvent emprisonnés et stockent l'information. Lorsque les électrons sont sur la FG, ils modifient partiellement le champ magnétique venant du CG qui modifie la tension de la cellule. De ce fait, lorsque la cellule «est lue» une tension spécifique est placée sur le CG, le courant électrique passe ou ne passe pas selon la tension de la cellule qui est, elle, commandée par le nombre d'électrons stockés sur le FG. La présence ou l'absence de courant électrique est traduite par des 0 et des 1, reproduisant ainsi les données stockées. Dans un dispositif à plusieurs niveaux de cellules, qui stocke plus de 1 bit de données par cellule, connaître le flux de passage du courant électrique plutôt que simplement sa présence ou son absence permet de mieux déterminer le nombre d'électrons stockés sur le FG. 3 Une NOR flash cellule est programmée en utilisant un processus appelé hot-electron injection qui consiste à placer un haut voltage sur le CG pour obtenir un champ électrique assez fort pour déplacer le flux d'électrons d'un pôle à un autre. La plupart des composants des mémoires flash NOR modernes sont divisés en segments effaçables appelés blocs ou secteurs. Toutes les cellules mémoires d'un bloc doivent être effacées en même temps. La programmation NOR peut généralement être exécutée à un octet ou mot à la fois. En ce qui concerne la NAND flash, elle utilise le procédé de tunnel injection pour l'écriture et le tunnel release pour l'effacement. 4 II – Bus de données 1) SPI Le « Serial Peripheral Interface Bus » ou bus SPI de Motorola est un standard de contrôle adapté à presque tous les équipements électroniques qui acceptent un flux de bits en série et avec une horloge. SPI est très peu cher car il utilise très peu de fils et donc moins de place sur un circuit intégré et il ne multiplie donc pas les broches sur les composants. On peut aussi l’implémenter en software avec peu de pattes d’entrée / sortie d’un microcontrôleur. Le bus SPI est capable de travailler en duplex et il est donc un très bon choix pour des systèmes de transmission de données. Le bus SPI n’a besoin que de 4 fils sur chaque circuit intégré : - une horloge (SCLK) - une entrée « Master Data Out / Slave Data Input » (MOSI) - une sortie « Master Data Input / Slave Data Output » (MISO) - un « chip select » aussi appelé « slave select » (SS). Organisation Maître / Esclave du bus SPI 1 Dans le cas général, les données arrivent dans le périphérique quand l’horloge est basse et sortent quand l’horloge est haute. Un périphérique pour communiquer doit avoir le chip-select bas. Dans le cas contraire, la plupart des périphériques ont leur sortie avec une haute impédance (désactivée). Tout cela permet à de nombreux appareils de communiquer avec un écouteur unique. La fréquence de l’horloge peut varier de quelques kHz à quelques MHz. Quand il y a plusieurs périphériques esclaves, le périphérique maître possède plusieurs SS et doit donc n’en activer qu’un seul à la fois. Organisation Maître / Esclave du bus SPI 2 5 De nombreuses différences peuvent exister entre différents périphériques SPI. Certains inversent les phases de l’horloge et reçoivent des données quand l’horloge est haute et envoient quand l’horloge est basse. D’autres périphériques peuvent être sélectionné quand le SS est haut. Cela cause de nombreuses incompatibilités entre les périphériques et l’on doit s’assurer quand on couple des périphériques qu’ils utilisent bien tous la même version du bus SPI. Le bus SPI est un bus fait pour échanger des données brutes. Il n’y a pas de mécanisme de communication standard ni de contrôle de flux. Le protocole de communication est laissé libre au concepteur du périphérique. On pourrait voir que les périphériques esclaves ne sont que de simples périphériques d’entrée / sortie. Le bus SPI obtient les meilleures performances quand il n’y a qu’un seul esclave et un seul maître du fait de sa capacité à faire du Full Duplex et de son taux de transfert pouvant atteindre plusieurs mégabits par seconde. 2) Microwire Microwire de National Semiconductor est un bus assez ancien dont le successeur est le bus SPI. Ils fonctionnent de façon assez proche. 6 3) I²C Présentation Le bus I2C (Inter-IC Bus) est caractérisé par une liaison en mode série réalisée à l'aide de 2 Fils. C'est la société Philips qui en à créer le concept au début des années 80. Son succès est lié à sa simplicité. Voici l'architecture type d'un bus I2C, on remarque que plusieurs composants viennent se "greffer" sur le même bus. Les données transitent par les lignes : - SDA : signal de donnée, généré par le Maître ou l'Esclave. - SCL : signal d'horloge généré par le Maître. La communication sur le bus est assez simple. Le Maître envoie sur le bus l'adresse sur 7 bits du composant avec qui il souhaite communiquer, les esclaves ayant une adresse fixe, l'esclave qui reconnaît son adresse répond à son tour par un signal de confirmation, puis le Maître continue la procédure de communication écriture ou lecture. Dans tout les cas, les transactions sont confirmées par un ACK Protocole La condition de repos : Condition dans la quel aucun composants ne communiquent, les lignes de bus sont à l'état Haut La condition de départ : Le Maître force la ligne SDA au niveau bas pour "réveiller" les Esclaves quand SCL est au niveau haut La condition d’arrêt : Les lignes SDA et SCL sont au niveau bas, puis quand SCL passe au niveau haut, le Maître libère la ligne SDA au niveau haut. A cet instant le bus est considéré comme disponible L'acquittement : Une fois les données transmises, un acquittement est opéré par celui qui reçoit les donnés. Cette procédure est réalisée en fin de trame de transmission, soit à la neuvième impulsion. Le récepteur maintien la ligne SDA au niveau bas pendant le front d'horloge 7 La communication par octet : Nous allons prendre comme exemples les cas suivants afin de visualiser à quoi peut ressembler un échange complet sur le bus entre Maître et Esclave : Driver L’élaboration d’un driver pour I²C est assez simple. Le protocole ne nécessite que peu de code pour être fonctionnel dans n’importe quelle architecture. Ci-joint en annexe, le code pour faire fonctionner un périphérique I²C pour un processeur 6502, processeur très simple dont le code assembleur peut être adapté facilement à tout autre processeur IA32, MIPS, PPC, Strong ARM ou autres. Je n’ai pas inclus de code x86 car il est moins facilement portable et moins clair que le code 6502. Le bus I²C est répandu dans tellement d’architectures qu’il est dommage de se restreindre à l’IA32. Une documentation complète du processeur 6502 est disponible à l’adresse http://www.obelisk.demon.co.uk/6502/index.html . Les seuls changements importants pour l’architecture IA32 par exemple sont de prendre en compte l’écriture dans le port de l’interface sur lequel est branché le bus. Par exemple « out 378h, octet » au lieu dans le code présent des « STA I2CPort ». De plus il faut changer quelque peu les temporisations pour que le processeur n’aille pas plus vite que le bus et que l’horloge générée soit ainsi à la bonne fréquence. 4) Comparaison Les mémoires à accès série sont aujourd'hui accessibles par 3 bus séries : 1) le bus SPI de Motorola 2) le bus Microwire de National Semiconductor 3) le bus I²C de Philips Il existe désormais à l'interface parallèle (Centronics) et à l'interface série (RS232), des logiciels du domaine public permettant la constitution de programmateur venant écrire ces mémoires. Aujourd'hui ces mémoires accès série sont placées sur les barrettes de SRAM ou de DRAM pour contenir le pedigree de la barrette tel que sa capacité, le temps d'accès, le fabricant...Autant d'informations nécessaires aux opérations de plug-and-play du PC. Ainsi par 2 fils très particuliers du channel, le microprocesseur connaîtra l'horloge nécessaire pour que les instructions se fassent sans overclockage. 8 Les interfaces synchrones sont caractérisées par la présence d'un signal de transmission/réception destiné à l'horloge. Un procédé « maître » produit généralement un signal d'horloge qui est reçu par tous les procédés « esclaves » qui reçoivent et transmettent les données de façon synchrone. L'avantage est que chaque dispositif travaille en fonction du signal de l'horloge émis par le « maître », indépendamment des variations d'oscillateur de chaque procédé individuel. Ainsi ces interfaces sont très appropriées pour l'usage de puces d'oscillateurs qui ont de grandes variations de fréquence. Exemples d'interfaces synchrones : SPI (Serial Peripheral Interface) développé par Motorola, Microwire développé par National Semiconductor, I²C (Inter Integrated Circuit) développé par Philips mais aussi USART (Universal Synchronous & Asynchronous Receiver Transmitter) qui comme son nom le suggère peut être soit en mode synchrone soit en mode asynchrone. Les interfaces synchrones ont été conçues pour relier des périphériques sur une même carte, comme par exemple la EEPROMS externe ou les convertisseurs Analogique Numérique... Elles sont seulement appropriées aux distances relativement courtes, moins d’un mètre. Mais nous allons nous intéresser d'avantage aux interfaces SPI, Microwire et I²C et faire un petit comparatif: Ainsi SPI est un proche cousin du vieux Microwire. Les deux interfaces sont très simples et fonctionnent principalement sur le principe d'un registre à décalage série de 8bits et pour les dispositifs « maîtres », il s'agit d'une horloge programmable à décalage. Il n'y a aucun moyen d'adresser les dispositifs, les applications typiques se composent d'un dispositif principal « maître », habituellement un microcontrôleur, et d'un ou de multiples dispositifs «esclaves», comme par exemple des fonctions de périphérique comme le convertisseur A/N, EEPROM. Cependant I²C est un petit peu plus complexe que SPI et Microwire, ceci demande donc un plus grand secteur de silicium et entraîne des périphériques légèrement plus chers. Concernant la connexion des périphériques externes, il y a trois raccordements au minimum pour SPI et Microwire : l'horloge, l'entrée des données et la sortie des données. Par conséquent, ces interfaces sont parfois désignées sous le nom d'interfaces à trois fils. Les périphériques interconnectés ont besoin de se partager la borne Vcc et la masse et dans le cas de périphériques connectés multiple, un chip select est nécessaire pour chaque périphérique «esclave». Si on veut connecter N périphériques à un microcontrôleur avec Microwire ou SPI, vous devez sacrifier 3+N pins pour faire le travail. C'est un secteur où I²C a l'avantage, en effet il comporte une adresse de 7bits et il peut donc adresser à 128 périphériques sur le bus sans avoir besoin du signal du chip select. Concernant la vitesse, I²C indiquait au début, une vitesse maximum de 100 KBits/sec, plus tard celle-ci a grimpé à 400 KBits/sec et récemment quelques dispositifs sont montés jusqu'à 1Mbits/sec. Mais ceci est très peu par rapport aux vitesses que peuvent atteindre SPI et Microwire. En effet une EEPROM série peut supporter aujourd'hui jusqu'à 3Mbits/sec pour Microwire et plus de 10Mbits/sec avec SPI. Cette différence de vitesse s'explique par le fait que SPI et Microwire ont des capacités full duplex c'est à dire qu'elles peuvent envoyer et recevoir des données en même temps. Alors que I²C n'a que deux fils, un pour l'horloge et un pour la donnée, il ne peut donc communiquer qu'en semi duplex. La vitesse est très importante pour la consommation courante, tout d'abord, car la majeure partie du temps, les applications du microcontrôleur sont en mode d'économie d'énergie et seulement certaines périodes courtes sont en mode de fonctionnement normal. De plus, les 9 opérations de lecture et d'écriture vers un périphérique doivent être les plus rapides et le temps, où le microcontrôleur est actif, doit être le plus court. Ceci est particulièrement vrai pour les accès à des mémoires externes EEPROM. Systèmes multi maîtres : C'est I²C qui offre un meilleur support pour les systèmes multi-maîtres. L'interface est construite sur le principe d'arbitrage pour détecter les périphériques multiples envoyant sur le bus en même temps et en donnant la priorité à celui qui envoie le premier un « 0 ». SPI supporte ce système grâce à un procédé de « logique par défaut », c'est à dire qu'il peut détecter les requêtes des périphériques pour devenir «maître» via un pin SS (Slave Select). Un inconvénient possible d'I²C est une sensibilité plus forte pour le bruit et avec ceci une intégrité des données inférieure. I²C utilise un bit d'écriture et de lecture qui suit les 7bits d'adresse initiaux pour demander à un périphérique si la donnée doit être lue ou écrite. I²C échantillonne la donnée durant la phase élevée ou basse d'un bit et ce bruit peut altérer le bit d'écriture/lecture. Ainsi si on veut lire une donnée d'une EEPROM externe mais que le bruit transforme ce bit de lecture en bit d'écriture, la mémoire sera corrompue. D'autre part, les périphériques de Microwire et de SPI implémentent les opérations de lecture et d'écriture via des commandes explicites envoyés sur le bus. En conclusion, si on a plusieurs périphériques connectés et plusieurs microcontrôleurs dans le système qui peuvent agir en tant que maître alors l'interface à choisir serait plutôt I²C. Cette interface est très populaire pour les applications vidéo et audio. Si par contre on recherche plutôt des prix réduits, une grande vitesse et une immunité du bruit alors Microwire et SPI sont préférables. Conclusion Il existe donc de nombreuses alternatives face au si répandu bus USB. Elles n’ont pas la même vocation mais permettent d’accomplir des tâches pour lequel il serait trop important. Chaque bus a ses avantages et ses inconvénients et il est donc difficile de faire un choix. La simplicité et de faibles coûts ou une plus grande vitesse mais plus de fils ? Le choix dépend donc de l’application. Mais tous ici peuvent facilement être reliés à des mémoires Flash. Chaque bus est très lié à son constructeur et à ses partisans, mais tout le monde est d’accord sur l’importance des mémoires Flash. Et pour les mémoires Flash, il y a plusieurs constructeurs mais peu de variantes. Elle a su s’imposer rapidement et est devenue notre quotidien. 10 Annexe Driver I²C pour 6502 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Devices are accessed using the following four subroutines. SendAddr This routine sends the slave address to the I2C bus. It can also send any required register address bytes by setting them up as you would data to be sent. No stop is sent so you can either read or write after calling this routine. SendData Send data byte(s) to an addressed device. Set the count in I2cCountL/H and point to the data with TxBuffL/H No stop is sent after calling this routine. ReadData StopI2c Read data byte(s) from an addressed device. Set the count in I2cCountL/H and point to the buffer with RxBuffL/H No stop is sent after calling this routine. generates a stop condition on the i2c bus ; Each device has an 8 bit address, the lowest bit of which is a read/write ; bit. I2CPort inputs RxBuffL TxBuffL RxBuffH TxBuffH ByteBuff = I2cAddr I2cCountL I2cCountH JMP JMP JMP JMP ; ; ; ; ; ; ; = $F121 = = = = $F3 = = = ; bit 0 is data [SDA] ; bit 1 is clock [SLC] ; bits 2 to 7 are unused $F1 ; receive buffer pointer low byte RxBuffL ; the same (can't do both at once!) $F2 ; receive buffer pointer high byte RxBuffH ; the same (can't do both at once!) ; byte buffer for Tx/Rx routines $F4 ; Tx/Rx address $F5 ; Tx/Rx byte count low byte $F6 ; Tx/Rx byte count high byte *= $2000 SendData ReadData SendAddr StopI2c ; i2c bus port, o.c. outputs, tristate ; test vector to send ; test vector to read ; test vector to send ; test vector data data slave address to send stop send the slave address for an i2c device if I2cCountL is non zero then that number of bytes will be sent after the address (to allow register addressing, required on some devices) RxBuff is a pointer, in page zero, to the transmit buffer exits with the clock low and Cb=0 if all ok routine entered with the i2c bus in a stopped state [SDA=SCL=1] SendAddr LDA ORA STA LDA STA LDA I2CPort #$01 I2CPort #$03 I2CPort #$01 ; ; ; ; ; ; get i2c port state release data out to i2c port release clock out to i2c port set for data test WaitAD 11 BIT BEQ LDA I2CPort WaitAD #$02 BIT BEQ JSR LDA JSR BCS LDA BNE RTS I2CPort WaitAC StartI2c I2cAddr ByteOut StopI2c I2cCountL SendData ; test the clock line ; wait for the data to rise ; set for clock test WaitAC ; ; ; ; ; ; ; test the clock line ; wait for the clock to rise ; generate start condition ; get address (including read/write bit) ; send address byte ; branch if no ack ; get byte count ; go send if not zero ; else exit send data to an already addressed i2c I2cCountL/H is the number of bytes to RxBuff is a pointer, in page zero, to exits with Cb=0 if all ok it is assumed at least one byte is to routine entered with the i2c bus in a device send the transmit buffer be sent held state [SCL=0] SendData INC LDY WriteLoop LDA JSR BCS INY BNE INC NoHiWrInc DEC BNE DEC BNE RET ; ; ; ; ; ; I2cCountH #$00 ; increment count high byte ; set index to zero (RxBuffL),Y ByteOut StopI2c NoHiWrInc RxBuffH ; ; ; ; ; ; get byte from buffer send byte to device branch if no ack increment index branch if no rollover else increment pointer high byte I2cCountL WriteLoop I2cCountH WriteLoop ; ; ; ; decrement count loop if not all increment count loop if not all low byte done high byte done get data from already addressed i2c device I2cCountL/H is the number of bytes to get RxBuff is a pointer, in page zero, to the receive buffer exits with Cb=0 if all ok it is assumed at least one byte is to be received routine entered with the i2c bus in a held state [SCL=0] ReadData INC LDY I2cCountH #$00 ; increment count high byte ; set index to zero ReadLoop JSR CLC JSR LDA STA INY BNE INC NoHiRdInc DEC BNE DEC BNE RTS ByteIn NoHiRdInc TxBuffH ; get byte from device ; clear for zero ack ; send ack bit ; get byte from byte buffer ; save in device buffer ; increment index ; branch if no rollover ; else increment pointer high byte I2cCountL ReadLoop I2cCountH ReadLoop ; ; loop if ; ; loop if DoAck ByteBuff (TxBuffL),Y decrement count low byte not all done decrement count high byte not all done ; generate stop condition on i2c bus. it is assumed only that ; the clock is low on entry to this routine. StopI2c 12 ; ; LDA STA NOP LDA STA NOP LDA STA RTS #$00 I2CPort #$02 I2CPort #$03 I2CPort ; ; ; ; ; ; ; ; now hold the data down out to i2c port need this if running >1.9MHz release the clock out to i2c port need this if running >1.9MHz now release the data (stop) out to i2c port ; generate start condition on i2c bus. it is assumed that both ; clock and data are high on entry to this routine. ; note, another condition is A=$02 on entry StartI2c ; STA NOP LDA STA RTS I2CPort #$00 I2CPort ; ; ; ; out to i2c port need this if running >1.9MHz clock low, data low out to i2c port ; output byte to 12c bus, byte is in A. returns Cb=0 if ok ; clock should be low after generating a start or a previously ; sent byte ; exits with clock held low ByteOut OutLoop ; STA LDX ByteBuff #$08 ; save byte for transmit ; 8 bits to do LDA ROL ROL STA NOP ORA STA LDA #$00 ByteBuff A I2CPort ; unshifted clock low ; bit into carry ; get data from carry ; out to i2c port ; need this if running >1.9MHz ; clock line high ; out to i2c port ; set for clock test BIT BEQ LDA AND STA DEX BNE I2CPort WaitT1 I2CPort #$01 I2CPort #$02 I2CPort #$02 WaitT1 OutLoop ; ; ; ; ; ; ; test the clock line wait for the clock to rise get data bit set clock low out to i2c port decrement count branch if not all done ; clock is low, data needs to be released, then the ; clock needs to be released then we need to wait for ; the clock to rise and get the ack bit. GetAck LDA STA LDA STA LDA #$01 I2CPort #$03 I2CPort #$02 ; ; ; ; ; float data out to i2c port float clock, float data out to i2c port set for clock test BIT BEQ LDA LSR LDA STA RTS I2CPort WaitGA I2CPort A #$01 I2CPort ; ; ; ; ; ; test the clock line wait for the clock to rise get data data bit to Cb clock low, data released out to i2c port WaitGA 13 ; input byte from 12c bus, byte is returned in A. entry should ; be with clock low after generating a start or a previously ; sent byte ; exits with clock held low ByteIn LDX LDA STA #$08 #$01 I2CPort ; 8 bits to do ; release data ; out to i2c port LDA STA LDA #$03 I2CPort #$02 ; release clock ; out to i2c port ; set for clock test BIT BEQ LDA ROR ROL LDA STA DEX BNE RTS I2CPort WaitR1 I2CPort A ByteBuff #$01 I2CPort ; test the clock line ; wait for the clock to rise ; get data ; bit into carry ; bit into buffer ; set clock low ; out to i2c port ; decrement count ; branch if not all done InLoop WaitR1 InLoop ; clock is low, ack needs to be set then the clock released ; then we wait for the clock to rise before pulling it low ; and finishing. Ack bit is in Cb DoAck ; LDA ROL STA NOP ORA STA LDA #$00 A I2CPort #$02 I2CPort #$02 ; ; ; ; ; ; ; unshifted clock low get ack from carry out to i2c port need this if running >1.9MHz release clock out to i2c port set for clock test BIT BEQ LDA AND STA RTS I2CPort WaitTA I2CPort #$01 I2CPort ; ; ; ; ; test the clock line wait for the clock to rise get ack back hold clock out to i2c port WaitTA 14