PiSupply/PaPiRus

PaPiRus HAT Pinout / Arduino connection

Opened this issue · 15 comments

Hi everyone,
I found this webpage: https://pinout.xyz/pinout/papirus_hat where I was able to determine which pins are used for what. Can anyone tell me what CS0 and CS1 are used for? Which one is for FLASH_CS and which one for EPD_CS?

Thank you for your help :)

SPI CE0 (BCM GPIO 8, pin 24) is used as the chip select for the SPI interface of the EPD COG(Chip-On-Glass).
SPI CE1 (BCM GPIO 7, pin 26) is not used by PaPiRus-HAT, only wired through to the 40 pin FPC connector for extending the GPIO.
See also the schematic on https://github.com/PiSupply/PaPiRus/blob/master/hardware/PaPiRus%20HAT/Latest%20Version%20-%20v1.9/2014-035-01-Pi-ePaper-circuit_v1_9.pdf

Thank you for your reply! Currently, I'm trying to connect the Display to an Arduino UNO using the Repaper/gratis repo mentioned in the Project description.

According to this https://github.com/repaper/gratis/blob/master/doc/extension_board.md#pin-assignment, the already mentionet pinout (https://pinout.xyz/pinout/papirus_hat) and your hint I came up with this pin-connections:

Description		Arduino (UNO) PIN			Display PIN
3.3V 				3.3V				1
SPI CLK				Digital-13			23
BUSY				Digital-7			22
PWM				Digital-5			12
RESET				Digital-6			18 	(optional for wake on signal)
PANEL_ON			Digital-2			16
DISCHARGE			Digital-4			10
BORDER_CONTROL		        Digital-3			8
SPI_MISO			Digital-12			21 / 19 # not sure about this
SPI_MOSI			Digital-11			19 / 21 # see pin above
FLASH_CS 			Digital-9			24 / 26    # not used
EPD_CS				Digital-8			24 / 24
GND				GND				6		

SW1								36
SW2								37
SW3								38
SW4								40

Do you know whether this will work? If so, do you know which Demo-Code I could use from the RePaper-Repo?

Thank you very much :)

Agree with most of it.
The PWM is not used by the displays with the Papirus HAT, no need to connect it
SPI-MISO (Master In - Slave Out) should be 21
SPI-MOSI (Mater Out - Salve In) should be 19
Arduino or Raspberry Pi is the SPI Master.
FLASH_CS is not needed. It is intended for the Pervasive Displays development board which as on-board flash memory which you can use to load images. Not available on the PaPiRus HAT.

As you probably know the Arduino UNO uses 5V logic. The display only tolerated 3.3V logic.
The HAT has the apropiate voltage converters on-board (see the schematic) so should be no
problem to connect it to a UNO.

Regarding the sketches. Suggest to start with demo in Sketches/demo.
You will need to modify it since the temperature measurement will not work.
The HAT has a different temperature sensor as the development board.
There are also other things to check/change like the LED etc.
Good luck with your project.

Thank you for your reply. I was able to clear the screen and reading the temperature and clock👍!

But displaying stuff seems to be a Problem :(. I'm using an 2.7" Display - and it seems that the UNO does not have enough SRAM for generating a buffer of this size.

Next step: Connecting it to an ESP8266 using the Arduino Libraries and disable the WiFi-functionality for low power consumption :).

Okay, I was able to connect it with an ESP32. But out of the blue - the Display claims that it's broken. Do you have an idea what this could cause? On an RPi the displays works perfectly...

@Donderda No, not directly. The SPI transactions in the begin() function (where the breakage detection is located) for Arduino and Raspberry Pi look identical.
Do not know if the SPI library works the same on ESP32 as on RPi, since any SPI write also returns data. Maybe you have to process write and read in a single call on ESP32.

Thank you for your reply. What makes me wonder is the fact that COG detection above those lines seem to work correctly.

The last time it worked I had to change the SPI_on, SPI_put and SPI_read command to this:

static void SPI_on() {
	SPI.end();
	pinMode(SPI_cs, OUTPUT);
	SPI.begin(SPI_clk, SPI_miso, SPI_mosi, SPI_cs);
	SPI_put(SPI_cs, 0x00);
	SPI_put(SPI_cs, 0x00);
	Delay_us(10);
}
static void SPI_put(uint8_t cs_pin, uint8_t c) {
    ESP_LOGI("SPI_out", "%x", c);
    SPI.beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0)); // added
    digitalWrite(cs_pin, LOW);
    SPI.transfer(c);
    digitalWrite(cs_pin, HIGH);
    SPI.endTransaction(); // added
}

My SPI_read-function currently looks like this:

static uint8_t SPI_read(uint8_t cs_pin, const uint8_t *buffer,
		uint16_t length) {
	// CS low
	digitalWrite(cs_pin, LOW);

	//uint8_t rbuffer[4];
	uint8_t result = 0;

	// send all data
	for (uint16_t i = 0; i < length; ++i) {
		SPI.beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
		result = SPI.transfer(*buffer++);
		ESP_LOGI("SPI_read", "%x", result);
		/* Commented out. It isn't use anyway..
		  
		 if (i < 4) {
			rbuffer[i] = result;
		}
		*/
		SPI.endTransaction();
	}

	// CS high
	digitalWrite(cs_pin, HIGH);
	return result;
}

Here are some more debug infomartion I'm printing:

[I][EPaperPervasive.cpp:214] init(): PRE SPI-READ COG
[I][EPaperPervasive.cpp:863] SPI_read(): ff
[I][EPaperPervasive.cpp:863] SPI_read(): 12
[I][EPaperPervasive.cpp:863] SPI_read(): 0
[I][EPaperPervasive.cpp:863] SPI_read(): 12
[I][EPaperPervasive.cpp:225] init(): POST SPI-READ COG
[I][EPaperPervasive.cpp:830] SPI_put(): 70
[I][EPaperPervasive.cpp:830] SPI_put(): 2
[I][EPaperPervasive.cpp:830] SPI_put(): 72
[I][EPaperPervasive.cpp:830] SPI_put(): 40
[I][EPaperPervasive.cpp:231] init(): PRE SPI-READ breakage
[I][EPaperPervasive.cpp:830] SPI_put(): 70
[I][EPaperPervasive.cpp:830] SPI_put(): f
[I][EPaperPervasive.cpp:863] SPI_read(): 0
[I][EPaperPervasive.cpp:863] SPI_read(): 0
[I][EPaperPervasive.cpp:234] init(): POST SPI-READ breakage

And here is the corresponding snippet from my init-function:

	// wait for COG to become ready
	while (HIGH == digitalRead(this->EPD_Pin_BUSY)) {
		Delay_us(10);
	}
	// read the COG ID
	ESP_LOGI("SPI_read", "PRE SPI-READ COG");

	int cog_id = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x71, 0x00), 2);
	cog_id = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x71, 0x00), 2);

	if (0x02 != (0x0f & cog_id)) {
		this->status = EPD_UNSUPPORTED_COG;
		this->power_off();
		ESP_LOGE("EPaperPervasiv", "UNSUPPORTED COG");
		return;
	}
	ESP_LOGI("SPI_read", "POST SPI-READ COG");

	// Disable OE
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x02), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x40), 2);
	// check breakage
	ESP_LOGI("SPI_read", "PRE SPI-READ breakage");
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x0f), 2);
	int broken_panel = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x73, 0x00), 2);
	ESP_LOGI("SPI_read", "POST SPI-READ breakage");


	if (0x00 == (0x80 & broken_panel)) {
		this->status = EPD_PANEL_BROKEN;
		this->power_off();
		ESP_LOGE("EPaperPervasiv", "EPD_PANEL_BROKEN: %x", broken_panel);
		return;
	}
	Serial.println("AFTER EPD_PANEL_BROKEN CHECK");

Maybe @mrwastl could help? He worked on some ESP32 stuff work the repaper/gratis repository my work is based on...?

@Donderda I assume your SPI_send calls SPI_put twice in code like SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x0f), 2);. When sending the 0x70,0x0f (command header, command index) the CS goes high then low again between the 0x70 and the 0x0f.
The CS should go high first after the 0x0f.
When reading the COG-ID, the 0x71,0x00 is sent without the CS going high between the 0x71 and 0x00.
Make sure the CS does not go high when sending the 2-byte command header, command index.
For the nitty gritty see the datasheet on Pervasive Displays: http://www.pervasivedisplays.com/_literature_198794/COG_Driver_Interface_Timing_for_small_size_G2_V230

@tvoverbeek: THANK YOU! I read the docs the whole day, dived really deep into the docs. But I overread this small detail. You saved me. 💋

Soon I will link an ESP32 example here (as soon as my source code is cleaned up 😇 )

Hey @Donderda, I was wondering if you ever got to clean that code up? Or if you mind posting it as is. I'm trying to hook up the HAT to ESP32, and having some code examples would be awesome! Vielen Dank!

Hey @Donderda, would be great if you could provide it here as well :-D

Hello @Donderda... I''m trying to use papirus zero with ESP32... Is your repo public? Thanks!

If I remember correctly, I changed the functions EPC_CLASS::power_off(), EPD_Class::end() and EPD_Class::init() to the following using higher delays when sending the SPI commands:


void EPD_Class::init() {
	pinMode(this->EPD_Pin_RESET, OUTPUT);
	pinMode(this->EPD_Pin_PANEL_ON, OUTPUT);
	pinMode(this->EPD_Pin_DISCHARGE, OUTPUT);
	pinMode(this->EPD_Pin_BORDER, OUTPUT);
	pinMode(this->EPD_Pin_EPD_CS, OUTPUT);
	pinMode(this->EPD_Pin_BUSY, INPUT);

	digitalWrite(this->EPD_Pin_RESET, LOW);
	digitalWrite(this->EPD_Pin_PANEL_ON, LOW);
	digitalWrite(this->EPD_Pin_DISCHARGE, LOW);
	digitalWrite(this->EPD_Pin_BORDER, LOW);
	digitalWrite(this->EPD_Pin_EPD_CS, LOW);

	// assume ok
	this->status = EPD_OK;

	// power up sequence
	digitalWrite(this->EPD_Pin_RESET, LOW);
	digitalWrite(this->EPD_Pin_PANEL_ON, LOW);
	digitalWrite(this->EPD_Pin_DISCHARGE, LOW);
	digitalWrite(this->EPD_Pin_BORDER, LOW);
	digitalWrite(this->EPD_Pin_EPD_CS, LOW);
	SPI_on();

	Delay_ms(5);
	digitalWrite(this->EPD_Pin_PANEL_ON, HIGH);
	Delay_ms(10);

	digitalWrite(this->EPD_Pin_RESET, HIGH);
	digitalWrite(this->EPD_Pin_BORDER, HIGH);
	digitalWrite(this->EPD_Pin_EPD_CS, HIGH);
	Delay_ms(5);

	digitalWrite(this->EPD_Pin_RESET, LOW);
	Delay_ms(5);

	digitalWrite(this->EPD_Pin_RESET, HIGH);
	Delay_ms(5);

	// wait for COG to become ready
	while (HIGH == digitalRead(this->EPD_Pin_BUSY)) {
		Delay_us(10);
	}
	// read the COG ID

	int cog_id = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x71, 0x00), 2);
	cog_id = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x71, 0x00), 2);

	if (0x02 != (0x0f & cog_id)) {
		this->status = EPD_UNSUPPORTED_COG;
		this->power_off();
		ESP_LOGE("EPaperPervasive", "UNSUPPORTED COG");
		return;
	}

	// Disable OE
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x02), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x40), 2);
	// check breakage
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x0f), 2);
	int broken_panel = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x73, 0x00), 2);

	if (0x00 == (0x80 & broken_panel)) {
		this->status = EPD_PANEL_BROKEN;
		this->power_off();
		ESP_LOGE("EPaperPervasive", "EPD_PANEL_BROKEN. Result: %x", broken_panel);
		return;
	}

	// power saving mode
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x0b), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x02), 2);

	// channel select
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x01), 2);
	SPI_send(this->EPD_Pin_EPD_CS, this->channel_select,
			this->channel_select_length);

	// high power mode osc
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x07), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0xd1), 2);

	// power setting
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x08), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x02), 2);

	// Vcom level
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x09), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0xc2), 2);

	// power setting
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x03), 2);

	// driver latch on
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x03), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

	// driver latch off
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x03), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x00), 2);
	//Serial.println("Booted");

	Delay_ms(5);

	bool dc_ok = false;

	for (int i = 0; i < 4; ++i) {
		// charge pump positive voltage on - VGH/VDL on
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

		Delay_ms(240);

		// charge pump negative voltage on - VGL/VDL on
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x03), 2);

		Delay_ms(40);

		// charge pump Vcom on - Vcom driver on
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x0f), 2);

		Delay_ms(40);
		// check DC/DC
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x0f), 2);
		int dc_state = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x73, 0x00), 2);
		if (0x40 == (0x40 & dc_state)) {
			dc_ok = true;

			break;
		}
	}
	if (!dc_ok) {
		// output enable to disable
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x02), 2);
		SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x40), 2);

		this->status = EPD_DC_FAILED;
		this->power_off();
		return;
	}

	SPI_off();
}

void EPD_Class::end() {

	this->nothing_frame();

	if (EPD_1_44 == this->size || EPD_2_0 == this->size) {
		this->border_dummy_line();
	}

	this->dummy_line();

	if (EPD_2_7 == this->size) {
		// only pulse border pin for 2.70" EPD
		digitalWrite(this->EPD_Pin_BORDER, LOW);
		Delay_ms(200);
		digitalWrite(this->EPD_Pin_BORDER, HIGH);
	}

	SPI_on();

	// check DC/DC
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x0f), 2);
	int dc_state = SPI_read(this->EPD_Pin_EPD_CS, CU8(0x73, 0x00), 2);
	if (0x40 != (0x40 & dc_state)) {
		this->status = EPD_DC_FAILED;
		this->power_off();
		return;
	}

	// latch reset turn on
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x03), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

	// output enable off
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x02), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x05), 2);

	// power off charge pump Vcom
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x03), 2);

	// power off charge pump neg voltage
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

	Delay_ms(240);

	// power off all charge pumps
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x00), 2);

	// turn of osc
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x07), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

	// discharge internal on
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
	SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x83), 2);

	Delay_ms(30);

	// discharge internal off
	//SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
	//SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x00), 2);

	power_off();
}

void EPD_Class::power_off() {

	// turn of power and all signals
	digitalWrite(this->EPD_Pin_RESET, LOW);
	digitalWrite(this->EPD_Pin_PANEL_ON, LOW);
	digitalWrite(this->EPD_Pin_BORDER, LOW);

	// ensure SPI MOSI and CLOCK are Low before CS Low
	SPI_off();
	digitalWrite(this->EPD_Pin_EPD_CS, LOW);

	// pulse discharge pin
	digitalWrite(this->EPD_Pin_DISCHARGE, HIGH);
	Delay_ms(150);
	digitalWrite(this->EPD_Pin_DISCHARGE, LOW);
}

It's been a long time. Please let me know if this changes fix them.

Here's a repo of the project I used the display for: https://github.com/Donderda/doorsign/

Feel free to check the code. It's not well documented, but maybe it helps @rlaltrello