📜 ⬆️ ⬇️

Implementing a mutex outside the OS using the example of an AVR microcontroller and a TWI bus

Once I decided to build a weather station for myself. The sensors are different there, including on the I2C bus. And as is good, usually at the beginning, he did everything on the flags of waiting. But the path of the real Jedi is different, and it was decided to hang everything up for interruptions. That's where hemorrhoids began. The problem I encountered is the processing of several requests in a row. For example, the pressure sensor BMP085 for further work with it requires you to pull out 11 calibration constants from its EEPROM:


About how I came to the decision and the sequence of thoughts are described below.

Waiting flags

We define the functions get_AC1, get_AC2, ..., get_MD . Each of them receives a corresponding constant from the sensor EEPROM via the I2C bus. The format of the package from the datasheet:


And the sample code for the get_AC1 function:
')
get_AC1
int get_AC1(void) { union { unsigned int Word; unsigned char Byte[2]; } AC1; //  AC1 // ---------------------------------------- TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| //  START  (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWDR = 0xEE; //  SLA+W TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  SLA+W (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWDR = 0xAA; //  0xAA TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  0xAA (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| //  REPEATED START  (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWDR = 0xEF; //  SLA+R TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  SLA+R (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //       ACK (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- AC1.Byte[1] = TWDR; //  MSB TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //       NACK (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- AC1.Byte[0] = TWDR; //  LSB TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)| //  STOP  (0<<TWWC); // ---------------------------------------- return AC1.Word; } 

And here is its waveform:


The use of union union for AC1 is related to the difference in order of bytes. For TWI, this is big-endian, for AVR - little-endian. The get_AC2 function differs only in sending byte 0xAC (we look at the datasheet) instead of 0xAA after the command SLA + W. In all other respects, the functions are absolutely identical. Therefore, we can define one get_Data , which will take the Register address as parameters in accordance with the datasheet:

get_Data
 int get_Data(unsigned char Register_adress) { union { unsigned int Word; unsigned char Byte[2]; } Data; //  Data // ---------------------------------------- TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| //  START  (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWDR = 0xEE; //  SLA+W TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  SLA+W (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWDR = Register_adress; //  Register_adress TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  Register_adress (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| //  REPEATED START  (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWDR = 0xEF; //  SLA+R TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  SLA+R (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //       ACK (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- Data.Byte[1] = TWDR; //  MSB TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //       NACK (0<<TWWC); while (!(TWCR & (1<<TWINT))) //    TWINT ; // ---------------------------------------- Data.Byte[0] = TWDR; //  LSB TWCR = (1<<TWEN)| //  TWI  (0<<TWIE)|(1<<TWINT)| //   (0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)| //  STOP  (0<<TWWC); // ---------------------------------------- return Data.Word; } 

and then just:

 struct { short AC1; short AC2; short AC3; unsigned short AC4; unsigned short AC5; unsigned short AC6; short B1; short B2; short MB; short MC; short MD; } BMP085_EEPROM; ... static void get_EEPROM(void) { BMP085_EEPROM.AC1 = get_Data(0xAA); BMP085_EEPROM.AC2 = get_Data(0xAC); BMP085_EEPROM.AC3 = get_Data(0xAE); BMP085_EEPROM.AC4 = get_Data(0xB0); BMP085_EEPROM.AC5 = get_Data(0xB2); BMP085_EEPROM.AC6 = get_Data(0xB4); BMP085_EEPROM.B1 = get_Data(0xB6); BMP085_EEPROM.B2 = get_Data(0xB8); BMP085_EEPROM.MB = get_Data(0xBA); BMP085_EEPROM.MC = get_Data(0xBC); BMP085_EEPROM.MD = get_Data(0xBE); } 

Here's what happened in the end:


Work on interruptions


Foreword


As can be seen from the figure above, the duration of a single operation is 488 μs , for a bus transfer frequency of 100 kHz , which is 3094 processor clock speeds at a frequency of 8 MHz . Well, this is not in any gate guys. Of course, you can increase the frequency, if the target device allows. For example, for 400 kHz, the duration is 128 μs or 1024 cycles


Waiting for the flag to be raised is an absolutely useless operation. For thousands of clock cycles, the processor can do a lot of useful work, for example, perform the operation of dividing floating-point numbers. Therefore, the only adequate way out of this situation is to use interrupts.

Work on interruptions

Define the structure:
 struct { unsigned char SLA; // Slave address unsigned char *pW; // ? unsigned char nW; // ? unsigned char *pR; // ? unsigned char nR; // ? } TWI; 

and now our get_AC1 function:
 unsigned char Register_address; ... void get_AC1(void) { Register_address = 0xAA; //  Register address TWI.SLA = 0x77; // Slave address  BMP085 TWI.pW = &Register_address; //       TWI.nW = 1; //   ? TWI.pR = &BMP085_EEPROM.AC1; //   ? TWI.nR = 2; //   ? TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| //  START  (0<<TWWC); } 

A couple of words about the interrupt handler. If nW = n , nR = 0 , the frame format is:

if nW = 0 , nR = m :

and if nW = n , nR = m :

By the way the interrupt handler can be anything. The logic of the state machine can be implemented in different ways. An example of mine is shown below:
Interrupt handler
 ISR(TWI_vect) { /* ----------------------------------------------------------------------------------- Jump table which is stored in flash ------------------------------------------------------------------------------------*/ static const void * const twi_list[] PROGMEM = {&&TWI_00, &&TWI_08, &&TWI_10, &&TWI_18, &&TWI_20, &&TWI_28, &&TWI_30, &&TWI_38, &&TWI_40, &&TWI_48, &&TWI_50, &&TWI_58, &&TWI_60, &&TWI_68, &&TWI_70, &&TWI_78, &&TWI_80, &&TWI_88, &&TWI_90, &&TWI_98, &&TWI_A0, &&TWI_A8, &&TWI_B0, &&TWI_B8, &&TWI_C0, &&TWI_C8, &&TWI_F8}; /* ----------------------------------------------------------------------------------- Jump to label, address of which is in twi_list[TWSR>>3] ------------------------------------------------------------------------------------*/ goto *(pgm_read_word(&(twi_list[TWSR>>3]))); /* ----------------------------------------------------------------------------------- Bus error handler ------------------------------------------------------------------------------------*/ TWI_00: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- A START condition has been transmitted A repeated START condition has been transmitted nW = nR = 0: STOP condition will be generated nW > 0, nR - don't care: SLA+W will be send nW = 0, nR > 0: SLA+R will be send ------------------------------------------------------------------------------------*/ TWI_08: TWI_10: if (TWI.nW != 0) // SLA+W will be send TWDR = (TWI.SLA)<<1; else if (TWI.nR != 0) // SLA+R will be send TWDR = (TWI.SLA)<<1 | 1<<0; else // STOP condition will be generated goto STOP; TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  SLA+R/W (0<<TWWC); return; /* ----------------------------------------------------------------------------------- SLA+W has been transmitted; ACK has been received Data byte has been transmitted; ACK has been received nW > 0, nR - don't care: Data byte will be transmitted and ACK or NOT ACK will be received nW = 0, nR > 0: Repeated START will be transmitted nW = nR = 0: STOP condition will be generated ------------------------------------------------------------------------------------*/ TWI_18: TWI_28: if (TWI.nW != 0) { // Data byte will be transmitted and ACK or NOT ACK will be received TWDR = *TWI.pW++; TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //   (0<<TWWC); TWI.nW--; } else if (TWI.nR != 0) // Repeated START will be transmitted TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| //  START  (0<<TWWC); else // STOP condition will be generated goto STOP; return; /* ----------------------------------------------------------------------------------- SLA+W has been transmitted; NOT ACK has been received ------------------------------------------------------------------------------------*/ TWI_20: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- Data byte has been transmitted; NOT ACK has been received ------------------------------------------------------------------------------------*/ TWI_30: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- Arbitration lost in SLA+W or data bytes Arbitration lost in SLA+R or NOT ACK bit ------------------------------------------------------------------------------------*/ TWI_38: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- SLA+R has been transmitted; ACK has been received nR = 1: Data byte will be received and NOT ACK will be returned nR > 1: Data byte will be received and ACK will be returned ------------------------------------------------------------------------------------*/ TWI_40: if (TWI.nR == 1) // Data byte will be received and NOT ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + NACK (0<<TWWC); else // Data byte will be received and ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + ACK (0<<TWWC); return; /* ----------------------------------------------------------------------------------- SLA+R has been transmitted; NOT ACK has been received ------------------------------------------------------------------------------------*/ TWI_48: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- Data byte has been received; ACK has been returned nR = 2: Data byte will be received and NOT ACK will be returned nR > 2: Data byte will be received and ACK will be returned ------------------------------------------------------------------------------------*/ TWI_50: // Read data *TWI.pR++ = TWDR; if (TWI.nR-- == 2) // Data byte will be received and NOT ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + NACK (0<<TWWC); else // Data byte will be received and ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + ACK (0<<TWWC); return; /* ----------------------------------------------------------------------------------- Data byte has been received; NOT ACK has been returned Repeated START will be transmitted STOP condition will be transmitted and TWSTO Flag will be reset STOP condition followed by a START condition will be transmitted and TWSTO Flag will be reset ------------------------------------------------------------------------------------*/ TWI_58: // Read data *TWI.pR = TWDR; TWI_60: TWI_68: TWI_70: TWI_78: TWI_80: TWI_88: TWI_90: TWI_98: TWI_A0: TWI_A8: TWI_B0: TWI_B8: TWI_C0: TWI_C8: TWI_F8: // STOP condition will be transmitted and TWSTO Flag will be reset STOP: TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)| //    + NACK (0<<TWWC); } 

Well, the result of the work:


as we see, the execution time has increased from 488 μs to 538 μs . This is due to the transition to and return from the handler, as well as the calculation of the jump address on the lookup table. But the most important thing is that all transmission is hardware. So, after executing the small function get_AC1 , which lasts only 3.5 microseconds or 28 cycles , we can safely do other things, rather than blunt the wait cycle.


Let's see what happens if we call the get_AC1 , get_AC2 , ..., get_MD functions in order:


Only one will be executed , get_MD , and all because:


The last function loads data into the structure before SLA + W completes, therefore Register_address corresponds to it and is equal to 0xBE . In fact, this is probably the most innocent scenario. After all, if the transfer was a little faster than Register_address would correspond to the get_AC5 function , for example, and we would write it in BMP085_EEPROM.MD . That is, we expect AC1 , we get AC5 stored in MD . And if instead of get_AC5 there would be another, with our SLA, we could send half of the address of one and the other of the other would get NACK, and then interrupt the logic of the interrupt handler, or the program will loop, or throw STOP. There is one obvious way out of this situation. Do not run the next function until the previous one has ended. That is, by running get_AC1, we set the flag to 1, and the handler, after completing STOP, will reset it. And get_AC2 will not start until this flag is cleared. Yes, this is a way out, but all the beauty of interruptions is lost, which in this case is used as much as 0%. It is better flag machine. But there is another beautiful solution.

Analog mutex

Functions get_AC1 , ..., get_MD do not cause data transfer directly. They call another function that puts them in a queue and initializes the transfer itself if the queue is not empty. Rewrite our structure by creating an array of structures:
 #define size 8 ... typedef struct { unsigned char SLA; // Slave address unsigned char *pW; // ? unsigned char nW; // ? unsigned char *pR; // ? unsigned char nR; // ? } twi; twi TWI[size]; 


Let's see how the get_AC1 function will look like now:
 unsigned char buf[size]; ... void get_AC1(void) { volatile twi *pl; //     twi buf[0] = 0xAA; //  Register address pl->SLA = 0x77; // Slave address  BMP085 pl->pW = buf; //       pl->nW = 1; //   ? pl->pR = buf; //   ? pl->nR = 2; //   ? Scheduler(pl); //    } 

Practically the same, only instead of initializing the START state, we call the Scheduler function, passing as a parameter a pointer to the twi structure, which contains all the data necessary for the TWI frame. Consider the basic functions of the scheduler and some changes to the processor:

Do not bother with what is written above. It is better to see a small presentation that will clarify some points of the above:


Scheduler Scheduler Function :
 void Scheduler(twi *pl) { if (tail-head !=1 && head-tail != size-1) //    ,  { twi *pg = &TWI[head]; //      pg->SLA = pl->SLA; //  SLA pg->pW = pl->pW; //  *pW pg->nW = pl->nW; //  nW pg->pR = pl->pR; //  *pR pg->nR = pl->nR; //  nR head = (head+1)&(size-1); //       -  if (!flag.twi_run) //   TWI    (  0) { flag.twi_run = 1; //    1 TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| (0<<TWWC); } } } 

Well, the handler itself:
Redesigned interrupt handler
 ISR(TWI_vect) { twi *p = &TWI[tail]; /* ----------------------------------------------------------------------------------- Jump table which is stored in flash ------------------------------------------------------------------------------------*/ static const void * const twi_list[] PROGMEM = {&&TWI_00, &&TWI_08, &&TWI_10, &&TWI_18, &&TWI_20, &&TWI_28, &&TWI_30, &&TWI_38, &&TWI_40, &&TWI_48, &&TWI_50, &&TWI_58, &&TWI_60, &&TWI_68, &&TWI_70, &&TWI_78, &&TWI_80, &&TWI_88, &&TWI_90, &&TWI_98, &&TWI_A0, &&TWI_A8, &&TWI_B0, &&TWI_B8, &&TWI_C0, &&TWI_C8, &&TWI_F8}; /* ----------------------------------------------------------------------------------- Jump to label, address of which is in twi_list[TWSR>>3] ------------------------------------------------------------------------------------*/ goto *(pgm_read_word(&(twi_list[TWSR>>3]))); /* ----------------------------------------------------------------------------------- Bus error handler ------------------------------------------------------------------------------------*/ TWI_00: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- A START condition has been transmitted A repeated START condition has been transmitted nW = nR = 0: STOP condition will be generated nW > 0, nR - don't care: SLA+W will be send nW = 0, nR > 0: SLA+R will be send ------------------------------------------------------------------------------------*/ TWI_08: TWI_10: if (p->nW != 0) // SLA+W will be send TWDR = p->SLA<<1; else if (p->nR != 0) // SLA+R will be send TWDR = p->SLA<<1 | 1<<0; else // STOP condition will be generated goto STOP; TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //  SLA+R/W (0<<TWWC); return; /* ----------------------------------------------------------------------------------- SLA+W has been transmitted; ACK has been received Data byte has been transmitted; ACK has been received nW > 0, nR - don't care: Data byte will be transmitted and ACK or NOT ACK will be received nW = 0, nR > 0: Repeated START will be transmitted nW = nR = 0: STOP condition will be generated ------------------------------------------------------------------------------------*/ TWI_18: TWI_28: if (p->nW != 0) { // Data byte will be transmitted and ACK or NOT ACK will be received TWDR = *p->pW; p->pW++; TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //   (0<<TWWC); p->nW--; } else if (p->nR != 0) // Repeated START will be transmitted TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| //  START  (0<<TWWC); else // STOP condition will be generated goto STOP; return; /* ----------------------------------------------------------------------------------- SLA+W has been transmitted; NOT ACK has been received ------------------------------------------------------------------------------------*/ TWI_20: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- Data byte has been transmitted; NOT ACK has been received ------------------------------------------------------------------------------------*/ TWI_30: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- Arbitration lost in SLA+W or data bytes Arbitration lost in SLA+R or NOT ACK bit ------------------------------------------------------------------------------------*/ TWI_38: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- SLA+R has been transmitted; ACK has been received nR = 1: Data byte will be received and NOT ACK will be returned nR > 1: Data byte will be received and ACK will be returned ------------------------------------------------------------------------------------*/ TWI_40: if (p->nR == 1) // Data byte will be received and NOT ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + NACK (0<<TWWC); else // Data byte will be received and ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + ACK (0<<TWWC); return; /* ----------------------------------------------------------------------------------- SLA+R has been transmitted; NOT ACK has been received ------------------------------------------------------------------------------------*/ TWI_48: // STOP condition will be generated goto STOP; /* ----------------------------------------------------------------------------------- Data byte has been received; ACK has been returned nR = 2: Data byte will be received and NOT ACK will be returned nR > 2: Data byte will be received and ACK will be returned ------------------------------------------------------------------------------------*/ TWI_50: // Read data *p->pR = TWDR; p->pR++; if (p->nR-- == 2) // Data byte will be received and NOT ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + NACK (0<<TWWC); else // Data byte will be received and ACK will be returned TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)| //    + ACK (0<<TWWC); return; /* ----------------------------------------------------------------------------------- Data byte has been received; NOT ACK has been returned Repeated START will be transmitted STOP condition will be transmitted and TWSTO Flag will be reset STOP condition followed by a START condition will be transmitted and TWSTO Flag will be reset ------------------------------------------------------------------------------------*/ TWI_58: // Read data *p->pR = TWDR; TWI_60: TWI_68: TWI_70: TWI_78: TWI_80: TWI_88: TWI_90: TWI_98: TWI_A0: TWI_A8: TWI_B0: TWI_B8: TWI_C0: TWI_C8: TWI_F8: // STOP condition will be transmitted and TWSTO Flag will be reset STOP: tail = (tail+1)&(size-1); //  tail  1,    ,   if (head != tail) //  head  tail  ,  TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(1<<TWSTA)|(1<<TWSTO)| // - (0<<TWWC); else //  { TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| //  !   (0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)| //  (0<<TWWC); flag.twi_run = 0; //   } } 

Well, the result of the execution of get_AC1 :


Afterword

In fact, if, by analogy with get_AC1, to make get_AC2 and run 2 functions in a row, then only the last one will be executed twice. This is due to the fact that we constantly store data for transmission in buf [0]. To avoid this, we could write data to buf [1] and everything would work as it should. But this is not done. Correctly passing the pointer where to get the data and how much, and some third-party function will put this in the buffer for TWI and return the pointer where in it to get the data in the handler. In general, the code for example:

Code
 unsigned char bufTx[size]; unsigned char pos = 0; ... void get_AC1(void) { volatile twi *pl; //     twi volatile uint8_t buf[] = {0xAA}; //  Register address  buf pl->SLA = 0x77; // Slave address  BMP085 pl->pW = buf; //       pl->nW = 1; //   ? pl->pR = buf; //   ? pl->nR = 2; //   ? Scheduler(pl); //    } // ============================================ void get_AC2(void) { volatile twi *pl; //     twi volatile uint8_t buf[] = {0xAC}; //  Register address  buf pl->SLA = 0x77; // Slave address  BMP085 pl->pW = buf; //       pl->nW = 1; //   ? pl->pR = buf; //   ? pl->nR = 2; //   ? Scheduler(pl); //    } // ============================================ void Scheduler(volatile twi *pl) { if (tail-head !=1 && head-tail != size-1) //    ,  { twi *pg = &TWI[head]; //      pg->SLA = pl->SLA; //  SLA pg->pW = pushToBuf(pl->pW,pl->nW); //  ,    //  buf  bufTX    pg->nW = pl->nW; //  nW pg->pR = pl->pR; //  *pR pg->nR = pl->nR; //  nR head = (head+1)&(size-1); //       -  if (!flag.twi_run) //   TWI    (  0) { flag.twi_run = 1; //    1 TWCR = (1<<TWEN)| //  TWI  (1<<TWIE)|(1<<TWINT)| (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)| (0<<TWWC); } } } // ============================================ unsigned char *pushToBuf(unsigned char *buf, unsigned char n) { unsigned char *p = &bufTx[pos]; //      do { bufTx[pos++] = *buf++; //   buf  bufTx pos &= size-1; //   -  } while (--n); //  n  return p; //   p } 

look at the picture:


Kindly, by analogy, the same function must be done for the receiving buffer. You tell her how much, and she sends you where. You can also modify the TWI structure, for example, passing not pointers, but indexes to optimize memory.
If anyone has a question about why the code is used for zeroing:
 head = (head+1)&(size-1); tail = (tail+1)&(size-1); pos &= size-1; 

I will say this faster and less code. The only condition is that the value be 2 n .
Of the features we should also note the maximum size of the size of the TWI. If it is less than the maximum possible number of consecutive requests, we will certainly lose some of them. Therefore, it is important to highlight this emphasis. Always use size at least 1 more than the maximum number of simultaneous requests .
It would be possible to add a handler of the received data, after working out the function. For our example, this is the processing of the received AC1 and AC2, which we receive in Big-endianne and have to convert to Little-endiann, and then save it in the appropriate place. To do this, in the TWI structure one could also store a pointer to the handler function and figure out how to call it after working out.
A few words should be said about the processes. With reference to my scheme, there are 2 sensors of the number, pressure, 1 time chip and 1 memory chip on the TWI bus. And access to them can be regarded as atomic, for example for a pressure sensor:


if we lose the transmission for uncompressed pressure, the uncompressed temperature obtained is useless and we must start all over again, because we can get non-valid data.
Also, there is no case of failure in the transmission of TWI. Since we are changing the original TWI [i] in the handler, we cannot restart the transfer. To do this, you can declare a global structure of type twi, and before each start, copy data from TWI [tail] into it, and let the handler spoil the copy. In the event of a failure, we can recover the data from the original.

Conclusion


Many will ask: “Why? After all, to store the 16 elements of the structure, you need 16 * (1 + 2 + 1 + 2 + 1) = 112 bytes of SRAM! Why not use RTOS? Let the pier be a structure with 1 element and if it doesn’t fit into it, we queue for n ms. ”I think that this solution is very useful in operating systems. Think how long will such a transfer? If we put in the queue, then each next transmission will be in n ms, and the whole will end in n * m ms. And why bother to queue with unnecessary m instructions, because there is a chance to miss a really important task. And the OS kernel is unloaded without queuing m * (m + 1) / 2 tasks.

Source: https://habr.com/ru/post/193456/


All Articles