AVR Errata - Unpublished, and other "Gotchas"

Go To Last Post
84 posts / 0 new

Pages

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The only error is the 3 instead of 2 cycles. I think their use of the word "jump" is just in the general sense meaning "some opcode that gets you from one place to another". Both JMP and RJMP achieve that. I agree that if they'd said "The vector is normally a JMP to the interrupt routine, and this jump takes three clock cycles. " then that would be wrong. (obviously the 3 cycle thing is an error though).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Device: ATtiny167
Datasheet Revision: 8265C-AVR-03/12
Feature: Dynamic Clock Switch

The code section in datasheet page 34 (non-watchdog) & page 36 (with watchdog) has a missing section of code & the CLOCK_AVAILABILITY definition.

The missing definition to be added:

#define CLOCK_AVAILABILITY 0x03	

The missing section of code:

	//NEW Added: Request for Clock Availability
	CLKSELR = ((sut << 4) & 0x30) | (clk_number & 0x0F);
	CLKCSR = 1 << CLKCCE;
	CLKCSR = CLOCK_AVAILABILITY;

Without the above section of code & definition, the CLKRDY bit in CLKCSR will NEVER be set thereby putting your code in endless loop in:

	// Wait for clock validity
	while ((CLKCSR & (1 << CLKRDY)) == 0);

Atmel support has confirmed this error & the above missing section of code was provided by them.

My complete & tested code is below:

#define F_CPU	5000000UL

#include 
#include 
#include 

#define CLOCK_RECOVER 0x05	
#define CLOCK_ENABLE 0x02	
#define CLOCK_SWITCH 0x04	
#define CLOCK_DISABLE 0x01	
#define WD_ARL_ENABLE 0x06	
#define CLOCK_AVAILABILITY 0x03	


#define WD_2048CYCLES 0x07

void ClockSwitching(unsigned char clk_number, unsigned char sut)
{
	
	unsigned char previous_clk, temp;
	// Disable interrupts
	temp = SREG; asm ("cli");

	// Save the current system clock source
	CLKCSR = 1 << CLKCCE;
	CLKCSR = CLOCK_RECOVER;
	previous_clk = CLKSELR & 0x0F;
	
	// Enable the new clock source
	CLKSELR = ((sut << 4 ) & 0x30) | (clk_number & 0x0F);
	CLKCSR = 1 << CLKCCE;
	CLKCSR = CLOCK_ENABLE;
	
	//NEW Added: Request for Clock Availability
	CLKSELR = ((sut << 4) & 0x30) | (clk_number & 0x0F);
	CLKCSR = 1 << CLKCCE;
	CLKCSR = CLOCK_AVAILABILITY;
	
	// Wait for clock validity
	while ((CLKCSR & (1 << CLKRDY)) == 0);
	// Enable the watchdog in automatic reload mode
	WDTCR = (1 << WDCE) | (1 << WDE);
	WDTCR = (1 << WDE ) | WD_2048CYCLES;
	CLKCSR = 1 << CLKCCE;
	CLKCSR = WD_ARL_ENABLE;
	// Switch clock source
	CLKCSR = 1 << CLKCCE;
	CLKCSR = CLOCK_SWITCH;
	// Wait for effective switching
	while (1){
		CLKCSR = 1 << CLKCCE;
		CLKCSR = CLOCK_RECOVER;
		if ((CLKSELR & 0x0F) == (clk_number & 0x0F)) break;
	}
	// Shut down unneeded clock source
	if (previous_clk != (clk_number & 0x0F)) {
		CLKSELR = previous_clk;
		CLKCSR = 1 << CLKCCE;
		CLKCSR = CLOCK_DISABLE;
		CLKCSR = 0x80;	//Errata suggestion
	}
	// Re-enable interrupts
	SREG = temp;
}	


int main(void)
{
	DDRB |= (1 << PORTB0);
	PORTB &= ~(1 << PORTB0);
	
	ClockSwitching(0x0D, 0x02);
	
    while(1)
    {
        PORTB ^= (1 << PORTB0);
		_delay_ms(100);
    }
}

Once an engineer, forever an engineer

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for sharing, it is now saved in my bottomless pit of AVR stuff. :-)

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ATmega644(P) and ATmega1284(P):

High slewrate data on RxD0 disturbs the crystal oscillator in the AVR when it's not set to "Full swing" with the fusebits.
So either reduce the slewrate on RxD (using 10k - 100pF Low pass filter), or set the oscillator to full swing.

All credits to njepsen on the MCSelec-forum
http://www.mcselec.com/index2.ph...

I am just the messenger ;)

Nard

A GIF is worth a thousend words   They are called Rosa, Sylvia, Tricia, and Ulyana. You can find them https://www.linuxmint.com/

Dragon broken ? http://aplomb.nl/TechStuff/Dragon/Dragon.html for how-to-fix tips

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Atmega48A/88A/168A/328A+PA datasheet
doc: 8271G-AVR-02/2013

In section 20.7 there is a bug in the example

The assembly example is:

USART_Receive:
; Wait for data to be received
in r16, UCSRnA
sbrs r16, UDREn
rjmp USART_Receive
; Get and return received data from buffer
in r16, UDRn
ret

Here the USART Data register Empty flag is checked instead of the USART Receive Complete flag.
The fun part is that the C code example below that does have the correct code:

unsigned char USART_Receive( void )
{
/* Wait for data to be received */
while ( !(UCSRnA & (1<<RXCn)) )
;
/* Get and return received data from buffer */
return UDRn;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AT90CAN128: aborting a pending MOb can leave CONMOB in an unexpected state

I am not sure what kind of bug this is: could be a hardware bug or could be Atmel's library at90CANlib_3_2 being buggy AND misleading.

The library's procedure to abort a MOb that was pending consists on writing 0 to the CONMOB bits. Also, the library depends on those bits being 0 to detect a MOb as being free.

Turns out that a failed MOb (e.g. because of bus errors) will retry its process, and if its CONMOB is overwritten with 0 in that situation there is a rare chance that the CONMOB will jump back to its old value. So any routine testing for CONMOB==0 will consider the MOb busy. Result: a MOb leaked and unusable until reset.

I wrote Atmel about this and their answer was that:

Quote:
If you want to abort CAN communications, I would instead recommend using ABRQ in CANGCON as the means to abort. CANCDMOB is not affected by the abort, so you would still have to manage it.

... but that is not a good solution, because ABRQ aborts all MObs. I only wanted to abort one MOb.

Also they said, ...

Quote:
CANCDMOB really does not indicate that a MOB is "free" in the sense that it is ready for the next transmission - other bits indicate this; the user manual is clear that CONMOB bits are not cleared once communication is performed...

Which is true according to the documentation, which mentions that CANEN is the register to check for availability of MObs. But that means that the library is pretty b0rked, since it never even refers to the CANEN registers.

Also, the manual mentions repeatedly that the CONMOB is not expected to change by itself, not even after a reset. Which makes me think that this is actually a hardware bug (no?).

Anyway, my minimal workaround is: when aborting a MOb, write 0 to CONMOB, check that the corresponding CANEN bit is 0, and rewrite the 0 to CONMOB.
Alternatively, to search for free MObs, stop checking CONMOB and switch to checking CANEN.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ATtiny13A datasheet 8126F-AVR-05/12 chapter 7.5.1.

BODCR - BOD Control Register bitnames are labeled as BODS and BODSE, while in the tn13Adef.inc are labeled as BPDS and BPDSE.

RES

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Typo in t25/45/85 datasheet, page 150, table 20-7. The rightmost column should read "Signature Byte 2" instead of 0.

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

The low power oscillator itself is a bug imho.  I have seen it cause all manner of glitchy, impossible to debug problems, which are magically cured by going back to the full swing oscillator.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

 

Two typing errors in ATmega64M1 datasheet (7647K–AVR–12/13 )

 

 

19.2.2 Current Source for Low Cost Traducer
An external transducer based on variable resistor can be connected to the current source. This ca be for instance:
A thermistor, or temperature-sensitive resistor, used as a temperature sensor
A CdS photoconductive cell, or luminosity-sensitivity resistor, used as a luminosity sensor.
Using the current source with this type of transducer eliminates the need for additional parts otherwise required in resistor
network or Wheatstone bridge.

 

RES

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Many AVR8 datasheets, including modern versions for e.g. Mega48 family and Mega164 family, have the "I/O pin input hysteresis vs. VCC" graph in Typical Characteristics scaled in millivolts (mV).  It should be volts (V), as in e.g. Tiny1634 sheet.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ATtiny4/5/9/10 datasheet.

link: http://www.atmel.com/images/atme...

page 146, section 16.4.2

SST - Serial STore to data space using indirect addressing

 

This binary value is wrong....

"DS[PR] ← data 0110 0000 PR ← PR + 1 Post increment"

 

It should be...

"DS[PR] ← data 0110 0100 PR ← PR + 1 Post increment"

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Device: MEGA644P family

Summary: Datasheet errata

 

Datasheet assembler examples (notably the USART) do not appear to have been updated to reflect that some registers are now outside the 'magic 32' where in and out instructions work and therefore lds and sts need to be sued, and the example code will throw errors.

It's me again...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Datasheet assembler examples (notably the USART) do not appear to have been updated to reflect that some registers are now outside the 'magic 32' where in and out instructions work and therefore lds and sts need to be sued, and the example code will throw errors.

Thank you.

 

Note that the 'magic 32' are for the single-instruction bit-manipulation and bit-testing instructions like SBI/CBI/SBIS/SBIC.  Instructions like IN/OUT will work on all 64 I/O registers.

 

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

stub does your datasheet contain a little note at the bottom of the examples which says:

 

Note: 1. See “About Code Examples” on page 8.

which in turn says

3.2 About Code Examples
This documentation contains simple code examples that briefly show how to use various parts of
the device. Be aware that not all C compiler vendors include bit definitions in the header files
and interrupt handling in C is compiler dependent. Please confirm with the C compiler documentation for more details.
The code examples assume that the part specific header file is included before compilation. For
I/O registers located in extended I/O map, "IN", "OUT", "SBIS", "SBIC", "CBI", and "SBI" instructions must be replaced with instructions that allow access to extended I/O. Typically "LDS" and
"STS" combined with "SBRS", "SBRC", "SBR", and "CBR".

 

Anyway assembler tragics use macros to overcome that sort comings wink

AVR001: Conditional Assembly and portability macros
 

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Device: XMEGA128A1U

Summary: Analog Comparator silicon errata

 

The XMEGA128A1U (and probably XMEGA64A1U) does not make AC1OUT signals available on PA6 and PB6.

 

Not a problem if just migrating from older XMEGA128A1 device. Could bite you if migrating from a different AU device (A4U in my case).

 

Workaround: AC ISR to toggle output pin if you can tolerate the latency.

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

balisong42 wrote:
Workaround: AC ISR to toggle output pin if you can tolerate the latency.
Proposed alternate work-around (transparency : I haven't tried it)

The event system can route an event sourced by AC to be sunk by a port pin.

 


http://www.atmel.com/products/microcontrollers/avr/avr_xmega.aspx?tab=documents

Atmel AVR XMEGA AU Manual
(file size: 7.2MB, 478 pages, revision F, updated: 04/2013)

http://www.atmel.com/Images/Atmel-8331-8-and-16-bit-AVR-Microcontroller-XMEGA-AU_Manual.pdf

13. I/O Ports

(page 146, bottom)

13.10 Clock and Event Output
It is possible to output the peripheral clock and any of the event channels to the port pins (using EVCTRL register). This
can be used to clock, control, and synchronize external functions and hardware to internal device timing. The output port
pin is selectable. If an event occurs, it remains visible on the port pin as long as the event lasts; normally one peripheral
clock cycle.

 

Edit : manual

 

"Dare to be naïve." - Buckminster Fuller

Last Edited: Sat. Mar 11, 2017 - 06:28 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Device: ATmega328PB datasheet (complete) 40001906A, (C) 2017

Summary: PD0 incorrectly listed as RXD1 (USART1 Input Pin) on page 98 under "17.3.3. Alternate Functions of Port D". Correct assignment for PD0 is RXD0 (USART0 Input Pin) as elsewhere in datasheet (including "Table 6-1. PORT Function Multiplexing"). Also makes ATmega328PB Xplained Mini schematic more logical: unlikely to use USART0 for TXD and USART1 for RXD.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Device: ATmega328PB Xplained Mini User Guide 42469B, updated 2015-08

Summary: PD1 and PD0 ATmega328PB pins incorrectly swapped in "Table 2-10 J104 USART Header" on p.17. (ATmega328PB Xplained Mini schematic A09-2523 Rev. 4, 04.01.2017, is correct.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The latest (0856L - Nov '16) revision of the Atmel AVR Instruction Set Manual gives the mistaken impression that an intermediary register will often be needed with the EOR instruction. In section 58.1 (p91), among the operands, the bitmask register Rr is restricted to R0 - R3. You can't use LDI to load a bitmask register with that restriction: LDI requires R16 - R31.

 

The problem is a typo - a missing '1': the restriction on Rr should be R0 - R31. This is hinted at by the example (in 58.2 on the same page) which uses R4 for Rr; that would be wrong if the limit of R3 were correct. More significant, the opcode shows five bits for Rr (R0 - R31) not two as it would if the limit of R3 were correct.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AVR910: In-System Programming APPLICATION NOTE

 

The document states that Programming Enable command ($AC 53 xx yy) gets reply ($zz AC 53 xx).

 

That is not correct. It does not echo AC in the second byte of the reply.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It should. It's usually the input delayed by one byte.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It should. It's usually the input delayed by one byte.

Note that the command in question is "Programming Enable".  The echo-to-MISO likely isn't enabled until programming is enabled.  That happens after the $53 is received, so echoing likely starts on the 3rd byte, echoing the second byte, $53.

 

Note that device datasheets are not definitive, but seem to support this interpretation:

 

 

 

Note that I have not confirmed on hardware what @MASIP has reported above.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Tue. Mar 27, 2018 - 03:45 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The latest complete datasheet (DS40001906C, dated 2018-02-22) still gives conflicting pinouts for serial ISP.

 

"Figure 32-6. Serial Programming and Verify, VCC = 1.8 - 5.5V" on p400 shows MOSI, MISO and SCK as PB5, PB6 and PB7 respectively. That's wrong; it appears to be copied from the datasheet for the ATmega324PB (for which those pins are correct).

 

For the ATmega328PB the correct pins for MOSI, MISO and SCK are PB3, PB4 and PB5 respectively. The datasheet has that correct further down the page, in "Table 32-15. Pin Mapping Serial Programming". (The schematic for the 328PB Xplained Mini board also shows the correct pins.)

 

Below Table 32-15 there also appears this confusing note: "The pin mapping for SPI programming is listed. Not all parts use the SPI pins dedicated for the internal SPI interface." What it means is this: The pin mapping for serial ISP is listed. There are pins dedicated for the internal SPI but not all parts use them for ISP.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I believe there an error in the ATmega48A.../328/P datasheet, dated 2018, DS40002061A.

On page 131, Section 16.9.1, the second paragraph starting "The Input Capture ..." states the need to deal with "resolution of the counter", but I think they mean "range of the counter".

 

Edit: Or am I missing something with respect to the term "resolution"?

Last Edited: Thu. Mar 21, 2019 - 12:44 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

"The Input Capture ..." states the need to deal with "resolution of the counter", but I think they mean "range of the counter".

 

This appears to be what is meant by "resolution."

See sections 15.7.1 and 15.7.2 on page 107.

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2


Device: ATtiny212/ATtiny412

Summary: Wrong mux table in the datasheet

Datasheet: Rev. A - 01/2017 

Here is the right mux table:

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Device: ATmega32U4

Summary: Errors in datasheet 7766J (04/2016), Chapter 32 "Instruction Set Summary"

 

I've tested the CALL and RET instructions, and their #Clocks values are wrong.

 

Since the Program Counter (PC) is 16 bits wide:

- the CALL instruction pushes 2 bytes on the stack,

- the CALL instruction takes only 4 cycles -- instead of 5,

- the RET instruction takes only 4 cycles -- instead of 5.

 

Since the wrong values might come from the copy of data corresponding to a device with a larger memory space,  the values for the RCALL, ICALL, EICALL and RETI instructions should also be checked.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

ATTINY1614

 

Datasheet says on pg337; "When the USART is set in one-wire mode, the transmitter and the receiver share the same RxD I/O pin."; (that is wrong)

One-Wire pin is the TXD pin, NOT THE RXD PIN

Microchip has confirmed that TXD is the correct pin for One-Wire. I'm in the process of trying to get Microchip to update the datasheet.

 

Also not specified in the datasheet for One-Wire mode...

If TX pin is set as output, ODME "Open Drain Mode Enable" does not work.
so TX port must NOT be set as output when using ODME "Open Drain Mode Enable".
This is Not specified in the datasheet.

 

The following statement from pg340 about one wire setup is vague.
For setting the USART in One-Wire mode, the following initialization sequence is recommended:
1) Set the TxD/RxD pin value high, and optionally set the XCK pin low.
*Needs clarification, what does "Set the TxD pin value high" mean, pullup resistors? or set as output?
*testing shows that in open drain mode, pin should NOT be set as output
2) Optionally, write the ODME bit in the USARTn.CTRLB register to '1' for Wired-AND functionality.
3) Set the TxD/RxD and optionally the XCK pin as an output.
4) Select the baud rate and frame format.
5) Select the mode of operation (enables XCK pin output in Synchronous mode).
6) Enable the transmitter or the receiver, depending on the usage

 

Tested and working method for 1 wire.
1) Do NOT set TxD pin as output
2) Enable Pullup Resistor on TxD pin if NOT using external pullup.
3) set bit LBME "Loop-back Mode Enable"
4) set bit ODME "Open Drain Mode Enable"
5) Use TxD pin for Transmit and Receive operations.
6) Enable the transmitter or the receiver, depending on the usage

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Please also publish this in a separate Tiny1614 thread because nobody who needs it (maybe including me) will ever find it here when the time comes.

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

New silicon errata found on ATMEGA324PB: OC1B signal on pin PD4 stops working if USART1 is enabled.

Case 00496661 submitted to Microchip.

This is really annoying, because in my design I can't use my second stepper motor (step signal connected to OC1B) together with the second UART. (I Use 2 steppers and 2 UART's, so I cannot use other channels).

Here is a minimal code example to demonstrate the bug.

 

#include <avr/io.h>
#include <avr/interrupt.h>

// Proof of concept of silicon errata on ATMEGA324PB: OC1B signal on pin PD4 stops working if USART1 is enabled
// Minimal working example, some registers are already set correctly by power on default value
int main()
{
    DDRD |= (1 << 4);         // Set OC1B (PD4) as output
    TCCR1A = (1 << COM1B0);   // Toggle OC1B (PD4) on compare
    TCCR1B = 0x02;            // Start timer (divide by 8)

 

    // Pin OC1B (PD4) is now generating pulses at frequency fOsc / 65536 / 8 / 2 (check with blinking LED)

 

    // Uncomment line below to demonstrate silicon errata on ATMEGA324PB. Pulses on pin OC1B (PD4) will stop after uncommenting.
    // UCSR1B = (1<<RXEN1) | (1<<TXEN1);

 

    // As soon as USART1 is enabled, Pin PD4 is disconnected from OC1B and stops generating pulses.
    // It seems that alternate port function XCK1 is disturbing control of pin PD4
    // This should NOT happen since the USART is NOT in synchronous mode. (UMSEL in TCCR1C is 0 by default)

 

    while(1);
}

Last Edited: Thu. Feb 6, 2020 - 08:46 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

--- USI SPI Compliance ---

The AVR Universal Serial Interface (USI) Three-wire external clock modes are compliant with SPI modes 1 and 3. The datasheet (e.g., ATtiny84A) partially correctly states that the "USI clock modes correspond to SPI data modes 0 and 1". However, the datasheet USI Three-wire Mode Timing Diagram figure and supporting text are ambiguous and/or fundamentally incorrect with regards to how the USI actually functions; most importantly, the described clock edge phase association with DI-sample/DO-change events are either inconsistent or wrong.

 

I have notified Microchip, who said they are looking into it.

 

The following figure summarizes the USI external clock modes functionality:

 

 

The following ATtiny84A program characterizes the USI DI-sample/DO-change timing on a standalone chip without need for an oscilloscope to observe results (although a scope is very useful):

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/cpufunc.h>
#include <util/delay.h>
#include <stdint.h>
#include <stdbool.h>

// Purpose:
// Discover USI Three-wire external clock mode DI and DO timing wrt USCK
// without external connections to device pins (self-acquisition).

// Results summary (USICS1 = 1):
//  USICS0 = 0 with normally high USCK: This USI usage mode looks the same as SPI mode 3 (CPOL=1, CPHA=1)
//  USICS0 = 1 with normally low USCK:  This USI usage mode looks the same as SPI mode 1 (CPOL=0, CPHA=1)
//  USICS0 = 0 with normally low USCK:  This USI usage mode does not look any SPI mode
//  USICS0 = 1 with normally high USCK: This USI usage mode does not look any SPI mode

// Method:
//  1. Manually generate 16 USCK edges for each simulated transfer.
//  2. Use Pin Change interrupts for DO and USCK to capture time, pin state, and USICNT for each pin state change.
//  3. Apply simulated DI pin input, then capture the time when it is sampled by the USI for each Data register bit.

// Detailed observations for all combinations of USICS0 and normal USCK clock states:
#define USICS0_VALUE        0   // USI clock source edge select (0 or 1): 0=="positive edge", 1=="negative edge"
#define NORMAL_USCK_STATE   1   // USI USCK normal (before and after transfer) state (0 or 1): 0==low, 1==high
//  USICS0 = 0 with normally high USCK (This USI usage mode looks the same as SPI mode 3 (CPOL=1, CPHA=1)):
//  DO is changed on leading  (falling) USCK edges 1(MSB),3(b6),5(b5),7(b4),9(b3),11(b2),13(b1),15(LSB); (not changed at write to USIDR)
//  DI is sampled on trailing (rising)  USCK edges 2(MSB),4(b6),6(b5),8(b4),10(b3),12(b2),14(b1),16(LSB)

//#define USICS0_VALUE      1   // USI clock source edge select (0 or 1): 0=="positive edge", 1=="negative edge"
//#define NORMAL_USCK_STATE 0   // USI USCK normal (before and after transfer) state (0 or 1): 0==low, 1==high
//  USICS0 = 1 with normally low USCK (This USI usage mode looks the same as SPI mode 1 (CPOL=0, CPHA=1)):
//  DO is changed on leading  (rising)  USCK edges 1(MSB),3(b6),5(b5),7(b4),9(b3),11(b2),13(b1),15(LSB); (not changed at write to USIDR)
//  DI is sampled on trailing (falling) USCK edges 2(MSB),4(b6),6(b5),8(b4),10(b3),12(b2),14(b1),16(LSB)
//  MSB is sampled (DI) on 2nd USCK edge (USICNT=1 to 2); LSB is sampled (DI) on 16th USCK edge (USICNT=15 to 0)

//#define USICS0_VALUE      0   // USI clock source edge select (0 or 1): 0=="positive edge", 1=="negative edge"
//#define NORMAL_USCK_STATE 0   // USI USCK normal (before and after transfer) state (0 or 1): 0==low, 1==high
//  USICS0 = 0 with normally low USCK (This USI usage mode does not look any SPI mode):
//  DO is changed to MSB at write to USIDR,
//  then on trailing (falling) USCK edges 2(b6),4(b5),6(b4),8(b3),10(b2),12(b1),14(LSB),16(next MSB)
//  DI is sampled on mixed USCK edges; rising 3(MSB),5(b6),7(b5),9(b4),11(b3),13(b2),15(b1), and falling 16(LSB)

//#define USICS0_VALUE      1   // USI clock source edge select (0 or 1): 0=="positive edge", 1=="negative edge"
//#define NORMAL_USCK_STATE 1   // USI USCK normal (before and after transfer) state (0 or 1): 0==low, 1==high
//  USICS0 = 1 with normally high USCK (This USI usage mode does not look any SPI mode):
//  DO is changed to MSB at write to USIDR,
//  then on trailing (rising) USCK edges 2(b6),4(b5),6(b4),8(b3),10(b2),12(b1),14(LSB),16(next MSB)
//  DI is sampled on mixed USCK edges; falling 3(MSB),5(b6),7(b5),9(b4),11(b3),13(b2),15(b1), and rising 16(LSB)

// Hardware abstraction for bit access:
struct bits
{
  uint8_t b0:1;
  uint8_t b1:1;
  uint8_t b2:1;
  uint8_t b3:1;
  uint8_t b4:1;
  uint8_t b5:1;
  uint8_t b6:1;
  uint8_t b7:1;
} __attribute__((__packed__));
#define BIT(portLetter, bitNumber, registerName) ((*(volatile struct bits*)&registerName##portLetter).b##bitNumber)
// portLetter = A-D, bitNumber = 0-7, registerName = "PORT", "PIN", or "DDR"
#define INPUT   0   // DDR bit = input
#define OUTPUT  1   // DDR bit = output

//  Pin usage map:
//  ATtiny84A Port Functions
//  Name    Num FuncA   FuncB   FuncC   FuncD   FuncE   FuncF       Usage
//  ----    --- -----   -----   -----   -----   -----   -----       -----
//  PA7     6   ADC7    OC0B    ICP1    -       -       PCINT7  ==
//  PA6     7   ADC6    DI      SDA     MOSI    OC1A    PCINT6  ==  USI DI
//  PA5     8   ADC5    DO      MISO    OC1B    -       PCINT5  ==  USI DO, PCINT5
//  PA4     9   ADC4    USCK    SCL     T1      -       PCINT4  ==  USI USCK, PCINT4
//  PA3     10  ADC3    T0      -       -       -       PCINT3  ==
//  PA2     11  ADC2    AIN1    -       -       -       PCINT2  ==
//  PA1     12  ADC1    AIN0    -       -       -       PCINT1  ==
//  PA0     13  ADC0    AREF    -       -       -       PCINT0  ==  scope trigger
//
//  PB3     4   RESET   dW      -       -       -       PCINT11 ==  dW
//  PB2     5   INT0    OC0A    CKOUT   -       -       PCINT10 ==
//  PB1     3   XTAL2   -       -       -       -       PCINT9  ==  resonator
//  PB0     2   XTAL1   CLKI    -       -       -       PCINT8  ==  resonator
//
//  Vcc     1
//  GND     14

// Pin assignments
#define USI_DI(r)           BIT(A, 6, r)    // USI 3-wire mode DI
#define USI_DO(r)           BIT(A, 5, r)    // USI 3-wire mode DO
#define USI_USCK(r)         BIT(A, 4, r)    // USI 3-wire mode USCK
#define SCOPE_TRIG(r)       BIT(A, 0, r)    // scope trigger

#define PCINT_DO    PCINT5                  // DO pin change interrupt mask bit
#define PCINT_USCK  PCINT4                  // USCK pin change interrupt mask bit

// Constants
const double        timeTick    = 40e-6;    // time clock tick length (sec)
#define nEdges      16                      // USCK edges per transfer
#define nBits       8                       // data bits per transfer
const uint8_t       endOfTime   = 255;      // time stops here
const uint8_t       delayUsck   = 10;       // delay before first USCK edge (use 10 to "tag" 1st edge for capture readability)
const uint8_t       usckWidth   = 10;       // half of USCK period (use 10 to "tag" edges in multiples of 10 for capture readability)
const uint8_t       txData      = 0xaa;     // DO transmit data--need alternating bit values to force DO change at each update
const double        delayTransfer = 2 * usckWidth * timeTick;   // delay between transfers (sec)

// RAM variables
volatile uint8_t    time;                       // runs from 0 to 255 * timeTick seconds
volatile uint8_t    tNextEdge;                  // scheduled time for USCK edge output change
volatile uint8_t    pinChangeCount;             // DO/USCK pin change counter (pin change capture buffer index)
volatile uint8_t    captureTimeUsck[nEdges];    // capture buffer for time of USCK pin change
volatile uint8_t    captureStateUsck[nEdges];   // capture buffer for USCK pin state at pin change
volatile uint8_t    captureUsicntUsck[nEdges];  // capture buffer for USICNT at USCK pin change
volatile uint8_t    captureTimeDo[nEdges];      // capture buffer for time of DO pin change
volatile uint8_t    captureStateDo[nEdges];     // capture buffer for DO pin state at pin change
volatile uint8_t    captureUsicntDo[nEdges];    // capture buffer for USICNT at DO pin change
volatile uint8_t    captureTimeDi[nBits];       // capture buffer for time of DI pin sample==1 for each USI Data Register Rx bit
volatile uint8_t    delayDi;                    // DI pin stimulus (pin state set to 1) delay
volatile uint8_t    bitDi;                      // DI capture bit number (DI capture buffer index)

static inline void Break(void)
{
    asm volatile("break\n\t"::);    // breakpoint instruction
}

ISR(PCINT0_vect)
{
    // USI DO and USCK pin change interrupt (handle only one at a time)
    if ( PCMSK0 & (1<<PCINT_DO) )
    {
        captureStateDo[pinChangeCount]  = USI_DO(PIN);          // capture DO pin state
        captureTimeDo[pinChangeCount]   = time;                 // absolute timestamp (includes delayUsck)
        captureUsicntDo[pinChangeCount] = USISR & ((1<<USICNT3) | (1<<USICNT2) | (1<<USICNT1) | (1<<USICNT0) ); // capture USI USCK edge counter
    }
    else if ( PCMSK0 & (1<<PCINT_USCK) )
    {
        captureStateUsck[pinChangeCount]    = USI_USCK(PIN);        // capture USCK pin state
        captureTimeUsck[pinChangeCount]     = time;                 // absolute timestamp (includes delayUsck)
        captureUsicntUsck[pinChangeCount]   = USISR & ((1<<USICNT3) | (1<<USICNT2) | (1<<USICNT1) | (1<<USICNT0) ); // capture USI USCK edge counter
    }

    if ( pinChangeCount < nEdges )
    {
        pinChangeCount++;
    }
}

ISR(TIM0_COMPA_vect, ISR_NOBLOCK)   // non-blocking to allow USI DO pin change ISR
{
    // Time clock tick service

    // DI stimulus
    if ( time == delayDi )
    {
        USI_DI(PORT) = 1;       // set USI DI pin
        _NOP(); _NOP();         // ensure sample setup time (input synchronizer delay) is met in case USCK edge is scheduled for same time
    }

    // USCK generator
    if ( (USISR & (1<<USIOIF)) == 0 )
    {
        if ( time == tNextEdge )            // time for next edge?
        {
            USI_USCK(PIN) = 1;              // toggle USCK pin
            tNextEdge = time + usckWidth;   // schedule next edge
        }
    }

    // If no more time, then stop time clock
    if ( time == endOfTime )
    {
        TIMSK0 = 0;
    }

    time++;
}

void RunTransferClockAndDiStim( void )
{
    USI_DI(PORT)    = 1;            // scope trigger
    USI_DI(PORT)    = 0;            // clear USI DI pin
    USIDR           = txData;       // at USIDR write: if USCK(pin) == USICS0_VALUE, then DO is changed to MSB, else DO unchanged

    _delay_us( delayTransfer * 1e6 );

    cli();

    time            = 0;            // reset time
    tNextEdge       = delayUsck;    // schedule first USCK edge
    USISR           = 1<<USIOIF;    // clear USI USCK edge counter overflow flag, and reset count
    TIMSK0          = 1<<OCIE0A;    // enable time interrupt; start transfer

    sei();

    while ( (TIMSK0 & (1<<OCIE0A)) != 0 );      // wait until ISR stops time clock
}

int main(void)
{
    uint8_t i;

    USI_DI(DDR)         = OUTPUT;
    USI_DO(DDR)         = OUTPUT;
    USI_USCK(DDR)       = OUTPUT;
    SCOPE_TRIG(DDR)     = OUTPUT;

    USI_USCK(PORT)      = NORMAL_USCK_STATE;    // when does this need to occur wrt USISR, USICR?
    SCOPE_TRIG(PORT)    = 0;

    _delay_us( 10 * delayTransfer * 1e6 );

    // Configure Timer/Counter 0 (8-bit) for use as time clock
    // Mode 2, Clear Timer on Capture (CTC), TOP = OCR0A
    TCCR0A = 2<<WGM00 | 0<<COM0A0;  // non-PWM, OC0A/OC0B disconnected
    //TCCR0B = 1<<CS00;             // 1x prescale
    //TCCR0B = 2<<CS00;             // 8x prescale
    TCCR0B = 3<<CS00;               // 64x prescale
    //TCCR0B = 4<<CS00;             // 256x prescale
    //TCCR0B = 5<<CS00;             // 1024x prescale
    OCR0A = ((timeTick)*F_CPU)/64 - 1;
    //TIMSK0 = 1<<OCIE0A;           // enable time interrupt

    USICR   = (1<<USIWM0) | (1<<USICS1) | (USICS0_VALUE<<USICS0);   // USI mode = 3-wire; clock source = external

    // Pre-fill capture buffers with invalid data that indicates non-capture
    for ( i = 0; i < nEdges; i++ )
    {
        captureTimeUsck[i]      = 0;
        captureStateUsck[i]     = 255;
        captureUsicntUsck[i]    = 255;
        captureTimeDo[i]        = 0;
        captureStateDo[i]       = 255;
        captureUsicntDo[i]      = 255;
    }

    sei();

    GIMSK   = 1<<PCIE0;
    // Discover USCK change timing/selftest (only need single transfer)
    pinChangeCount = 0;
    PCMSK0  = 1<<PCINT_USCK;        // enable only USI USCK pin change interrupt
    RunTransferClockAndDiStim();    // let pin change ISR capture USCK change timing

    // Discover DO update (USI Data Register shifted out) timing (only need single transfer)
    pinChangeCount = 0;
    PCMSK0  = 1<<PCINT_DO;          // enable only USI DO pin change interrupt
    RunTransferClockAndDiStim();    // let pin change ISR capture DO update timing
    GIMSK   = 0;                    // disable external interrupts

    // Discover DI sample timing for each USI Data Register bit shifted in
    delayDi = endOfTime;        // set DI delay sweep to initial value (sweep from after last (LSB) down to first (MSB) bit)
    for ( bitDi = 0; bitDi < nBits; bitDi++ )   // watch for last bit shifted in (LSB) first
    {
        do
        {
            RunTransferClockAndDiStim();    // let time clock ISR stimulate DI at time==delayDi
            if ( USIBR & (1<<bitDi) )       // did this bit detect DI stimulus in this transfer?
            {
                captureTimeDi[bitDi] = delayDi; // yes, capture DI delay timestamp (absolute, includes delayUsck)
                SCOPE_TRIG(PORT) = 1;
                SCOPE_TRIG(PORT) = 0;
                break;                      // skip to next bit, starting with capture delay of this bit
            }
            else
            {
                delayDi--;                  // no, try next DI stimulus delay for this bit
            }
        }
        while ( delayDi >= 0 );             // should never get to delayDi==0 (should detect MSB before)
    }

    Break();
}

 

The following ATmega328P SPI master and ATtiny84A USI Three-wire mode slave programs demonstrate which USI external clock modes are compliant with SPI modes, and which are not:

 

ATmega328P SPI master code:

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>

// Test data transfer exerciser using SPI in Master mode
#define SPI_MODE_CPOL_CPHA  0       // clock polarity and phase, [CPOL:CPHA] = 0, 1, 2, or 3
// SPI mode 0 (CPOL=0, CPHA=0): SCK idle  low; read MISO on SCK lead  (rise); write MOSI on SCK trail (fall) or half SCK cycle before 1st lead (rise); MOSI normally high (regardless of last bit)
// SPI mode 2 (CPOL=1, CPHA=0): SCK idle high; read MISO on SCK lead  (fall); write MOSI on SCK trail (rise) or half SCK cycle before 1st lead (rise); MOSI normally high (regardless of last bit)
// SPI mode 1 (CPOL=0, CPHA=1): SCK idle  low; read MISO on SCK trail (fall); write MOSI on SCK lead  (rise); MOSI normally same as last bit
// SPI mode 3 (CPOL=1, CPHA=1): SCK idle high; read MISO on SCK trail (rise); write MOSI on SCK lead  (fall); MOSI normally same as last bit

// Hardware abstraction for bit access:
struct bits
{
  uint8_t b0:1;
  uint8_t b1:1;
  uint8_t b2:1;
  uint8_t b3:1;
  uint8_t b4:1;
  uint8_t b5:1;
  uint8_t b6:1;
  uint8_t b7:1;
} __attribute__((__packed__));
#define BIT(portLetter, bitNumber, registerName) ((*(volatile struct bits*)&registerName##portLetter).b##bitNumber)
// portLetter = A-D, bitNumber = 0-7, registerName = "PORT", "PIN", or "DDR"
#define INPUT   0   // DDR bit = input
#define OUTPUT  1   // DDR bit = output

// ATmega328P pin assignments
#define SPI_SS_(r)      BIT(B, 2, r)    // SPI slave select -- output to slave
#define SPI_MOSI(r)     BIT(B, 3, r)    // SPI MOSI -- output to slave
#define SPI_MISO(r)     BIT(B, 4, r)    // SPI MISO -- input from slave
#define SPI_SCK(r)      BIT(B, 5, r)    // SPI SCK -- output to slave
#define ERROR_LED_(r)   BIT(D, 1, r)    // transfer integrity error indicator (note: active-low)

const double    WAIT_SLAVE_SELECT   = 2.0e-6;   // wait time (seconds) for slave to prepare for transfer after select
const double    WAIT_SLAVE_DESELECT = 1.0e-6;   // wait time (seconds) for slave to process Tx data after deselect
const uint8_t   TX_ONLY_MSB_AND_LSB = 0x81;     // Tx only MSB and LSB to highlight failure mode and for better trace readability

static uint8_t  txData;
static uint8_t  rxData;

int main(void)
{
    cli();

    SPI_MISO(DDR)   = INPUT;
    SPI_SS_(DDR)    = OUTPUT;
    SPI_MOSI(DDR)   = OUTPUT;
    SPI_SCK(DDR)    = OUTPUT;
    ERROR_LED_(DDR) = OUTPUT;

    SPCR    = (0<<SPIE) | (1<<SPE) | (0<<DORD) | (1<<MSTR) | ((SPI_MODE_CPOL_CPHA) << CPHA);        // master, MSB-first

    //SPCR  |= 0<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/2
    //SPCR  |= 0<<SPR0;                     // Fsck = Fclk/4
    //SPCR  |= 1<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/8
    SPCR    |= 1<<SPR0;                     // Fsck = Fclk/16
    //SPCR  |= 2<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/32
    //SPCR  |= 2<<SPR0;                     // Fsck = Fclk/64
    //SPCR  |= 3<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/64
    //SPCR  |= 3<<SPR0;                     // Fsck = Fclk/128

    ERROR_LED_(PORT) = 1;                   // (note: active-low error indicator)
    SPI_SS_(PORT) = 1;                      // deselect slave

    // To see failure on scope, trigger on ERROR_LED_ or SPI_SS_ positive-pulse-width-greater-than non-error condition width
    // To see success on scope, trigger on SPI_SS_ positive-pulse-width-less-than error condition width

    // Repeatedly transmit same (fixed) data; expect exact data (echo) received from slave
    // after data pipeline fills/flushes on first transfer.
    txData = TX_ONLY_MSB_AND_LSB;

    while ( 1 )
    {
        SPI_SS_(PORT) = 0;                          // select slave
        _delay_us( WAIT_SLAVE_SELECT * 1.0e6 );     // wait for slave to prepare for transfer
        SPDR = txData;                              // start transfer with Tx data
        while ( (SPSR & (1<<SPIF)) == 0 );          // wait for transfer to finish
        rxData = SPDR;                              // get Rx data
        SPI_SS_(PORT) = 1;                          // deselect slave
        _delay_us( WAIT_SLAVE_DESELECT * 1.0e6 );   // wait for slave to process Tx data

        if ( rxData != txData )                     // check Rx data integrity -- expect echo of previous Tx data
        {
            ERROR_LED_(PORT) = 0;
            // Extend SPI_SS_=1 width for scope trigger on pulse-width-greater-than non-error condition width
            _delay_us( 2.0 * WAIT_SLAVE_DESELECT * 1.0e6 );
            ERROR_LED_(PORT) = 1;
        }
    }
}

 

ATtiny84A USI Three-wire mode slave code:

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>

// Echo received test data using slave USI in Three-wire mode, external clock
#define USICS0_VALUE    0       // clock source edge select (0 or 1): 0=="positive edge", 1=="negative edge"

// Test observations for USI slave (ATtiny84A @20MHz) in both external clock modes, USICS1=1 and USICS0=0/1
// with real SPI master (ATmega328P @16MHz) in modes 0/1/2/3 with Fusck = 1 MHz:
// SPI mode 0 (CPOL=0, CPHA=0): SCK idle  low; read MISO on SCK lead  (rise); write MOSI on SCK trail (fall) or half SCK cycle before 1st lead (rise); MOSI normally high (regardless of last bit)
// SPI mode 2 (CPOL=1, CPHA=0): SCK idle high; read MISO on SCK lead  (fall); write MOSI on SCK trail (rise) or half SCK cycle before 1st lead (rise); MOSI normally high (regardless of last bit)
// SPI mode 1 (CPOL=0, CPHA=1): SCK idle  low; read MISO on SCK trail (fall); write MOSI on SCK lead  (rise); MOSI normally same as last bit
// SPI mode 3 (CPOL=1, CPHA=1): SCK idle high; read MISO on SCK trail (rise); write MOSI on SCK lead  (fall); MOSI normally same as last bit

//  USI slave USICS0=0 / SPI master mode 0 (CPOL=0, CPHA=0):    DI=0x81 DO=0x03     (fail) "usitest_00.bmp"
//  USI slave USICS0=0 / SPI master mode 1 (CPOL=0, CPHA=1):    DI=0x81 DO=various  (fail) "usitest_01.bmp"
//  USI slave USICS0=0 / SPI master mode 2 (CPOL=1, CPHA=0):    DI=0x81 DO=various  (fail) "usitest_02.bmp"
//  USI slave USICS0=0 / SPI master mode 3 (CPOL=1, CPHA=1):    DI=0x81 DO=0x81     (pass) "usitest_03.bmp"

//  USI slave USICS0=1 / SPI master mode 0 (CPOL=0, CPHA=0):    DI=0x81 DO=various  (fail) "usitest_10.bmp"
//  USI slave USICS0=1 / SPI master mode 1 (CPOL=0, CPHA=1):    DI=0x81 DO=0x81     (pass) "usitest_11.bmp"
//  USI slave USICS0=1 / SPI master mode 2 (CPOL=1, CPHA=0):    DI=0x81 DO=0x03     (fail) "usitest_12.bmp"
//  USI slave USICS0=1 / SPI master mode 3 (CPOL=1, CPHA=1):    DI=0x81 DO=various  (fail) "usitest_13.bmp"

// Hardware abstraction for bit access:
struct bits
{
  uint8_t b0:1;
  uint8_t b1:1;
  uint8_t b2:1;
  uint8_t b3:1;
  uint8_t b4:1;
  uint8_t b5:1;
  uint8_t b6:1;
  uint8_t b7:1;
} __attribute__((__packed__));
#define BIT(portLetter, bitNumber, registerName) ((*(volatile struct bits*)&registerName##portLetter).b##bitNumber)
// portLetter = A-D, bitNumber = 0-7, registerName = "PORT", "PIN", or "DDR"
#define INPUT   0   // DDR bit = input
#define OUTPUT  1   // DDR bit = output

// ATtiny84A pin assignments
#define SPI_SS_(r)      BIT(A, 7, r)    // [Pin# 6] SPI slave select -- input from master
#define USI_DI(r)       BIT(A, 6, r)    // [Pin# 7] SPI MOSI -- input from master
#define USI_DO(r)       BIT(A, 5, r)    // [Pin# 8] SPI MISO -- output to master
#define USI_USCK(r)     BIT(A, 4, r)    // [Pin# 9] SPI SCK -- input from master 

int main(void)
{
    cli();

    SPI_SS_(DDR)    = INPUT;
    USI_DI(DDR)     = INPUT;
    USI_USCK(DDR)   = INPUT;
    USI_DO(DDR)     = OUTPUT;

    USICR = (1<<USIWM0) | (1<<USICS1) | (USICS0_VALUE<<USICS0); // USI mode = 3-wire; clock source = external

    while( 1 )
    {
        if ( SPI_SS_(PIN) == 1 )
        {
            USI_DO(DDR) = INPUT;                    // slave is deselected; float DO
            USISR = 1<<USIOIF;                      // synch/reset USCK edge counter, and clear overflow flag
            USIDR = USIBR;                          // pre-load Tx test data
        }
        else
        {
            USI_DO(DDR) = OUTPUT;                   // slave is deselected; drive DO (master waits enough time)
            if ( USISR & (1<<USIOIF) )              // transfer complete? (master supplies clock)
            {
                USISR = 1<<USIOIF;                  // reset USCK edge counter, and clear overflow flag
            }
        }
    }
}

 

 

Last Edited: Sun. Jul 5, 2020 - 10:34 PM

Pages