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!
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