wuhanstudio / u8g2-arm-linux

U8g2 for arm linux - a monochrome graphics library
https://github.com/wuhanstudio/u8g2-arm-linux/wiki
Other
51 stars 24 forks source link

Cannot get CS and DC working on RPi4b #8

Closed karlo922 closed 7 months ago

karlo922 commented 8 months ago

Hi,

I'm trying to use your code to control a HW SPI display connected to:

DC = GPIO2 RES = not connected CS = CE0 (GPIO4) SCK = SPI0 (GPIO11) MOSI = SPI0 (GPIO10)

with following code:

#include "u8g2port.h"

// GPIO chip number for character device
#define GPIO_CHIP_NUM 0
// SPI bus uses upper 4 bits and lower 4 bits, so 0x10 will be /dev/spidev1.0
#define SPI_BUS 0x00
#define OLED_SPI_PIN_RES            U8X8_PIN_NONE
#define OLED_SPI_PIN_DC             2 

// CS pin is controlled by linux spi driver, thus not defined here, but need to be wired
#define OLED_SPI_PIN_CS             U8X8_PIN_NONE

int main(void) {
    u8g2_t u8g2;

    // Initialization
    u8g2_Setup_s1d15300_lm6023_f(&u8g2, U8G2_R0,
            u8x8_byte_arm_linux_hw_spi, u8x8_arm_linux_gpio_and_delay);
    init_spi_hw(&u8g2, GPIO_CHIP_NUM, SPI_BUS, OLED_SPI_PIN_DC,
            OLED_SPI_PIN_RES, OLED_SPI_PIN_CS);

    u8g2_InitDisplay(&u8g2);
    u8g2_ClearBuffer(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);

    u8g2_SetFont(&u8g2, u8g2_font_ncenB08_tr);
    u8g2_DrawStr(&u8g2, 1, 18, "U8g2 HW SPI");

    //u8g2_SetFont(&u8g2, u8g2_font_unifont_t_symbols);
    //u8g2_DrawGlyph(&u8g2, 112, 56, 0x2603);

    u8g2_SendBuffer(&u8g2);

    printf("Initialized ...\n");
    sleep_ms(5000);
    u8g2_SetPowerSave(&u8g2, 1);
    // Close and deallocate SPI resources
    done_spi();
    // Close and deallocate GPIO resources
    done_user_data(&u8g2);
    printf("Done\n");
    return 0;
}

On SCK and MOSI the correct signals come, but my display needs CS to be HIGH instead of low - I fixed this in the c.file of s1d15300_lm6023 but it does not work - CS will be low when activated.

This is the part of the u8g2 lib what I changed - and I know it works as when I use this on arduino, it works. I also check with my oscilloscope and saw that CS is low when active and not high.

static const u8x8_display_info_t u8x8_s1d15300_lm6023_display_info =
{
  /* chip_enable_level = */ 1,
  /* chip_disable_level = */ 0,

  /* post_chip_enable_wait_ns = */ 250, /*  */
  /* pre_chip_disable_wait_ns = */ 120, /*  */
  /* reset_pulse_width_ms = */ 1, 
  /* post_reset_wait_ms = */ 1, 
  /* sda_setup_time_ns = */ 200,        /* */
  /* sck_pulse_width_ns = */ 200,   /* half of cycle time (100ns according to datasheet), AVR: below 70: 8 MHz, >= 70 --> 4MHz clock */
  /* sck_clock_hz = */ 1000000UL,   /* since Arduino 1.6.0, the SPI bus speed in Hz. Should be  1000000000/sck_pulse_width_ns */
  /* spi_mode = */ 0,       /* active high, rising edge */
  /* i2c_bus_clock_100kHz = */ 4,
  /* data_setup_time_ns = */ 200,   /* st7565 datasheet, table 24, tds8 */
  /* write_pulse_width_ns = */ 200, /* st7565 datasheet, table 24, tcclw */
  /* tile_width = */ 13,        /* width of 16*8=128 pixel */
  /* tile_height = */ 4,
  /* default_x_offset = */ 0,
  /* flipmode_x_offset = */ 0,
  /* pixel_width = */ 100,
  /* pixel_height = */ 32
};
karlo922 commented 8 months ago

It does work with your SW-SPI - but is there any chance to use HW-SPI?

wuhanstudio commented 8 months ago

Hi,

For the Linux driver, I replaced the sysfs with gpiod six months ago.

Could you please check if gpiod is supported by the Linux kernel you are using?

The previous port that used sysfs: https://github.com/wuhanstudio/u8g2-arm-linux/tree/31a02e6e22ece76fe9bca45d472ed28c4dea720b

The upstream port: https://github.com/olikraus/u8g2/tree/master/sys/arm-linux

karlo922 commented 8 months ago

Hi, I use the most current operating system in my Raspi4B - how can I test this? I build the code from you with make CPPFLAGS=-DPERIPHERY_GPIO_CDEV_SUPPORT=1 CC=gcc CXX=g++

The one within Olis lib does not compile, it has open includes so I used yours directly.

karlo922 commented 8 months ago

I guess gpiod is included, as it shows "already installed" when trying to install via sudo apt install gpiod and also this output is working:

 sudo gpioinfo
gpiochip0 - 58 lines:
        line   0:     "ID_SDA"       unused   input  active-high
        line   1:     "ID_SCL"       unused   input  active-high
        line   2:       "SDA1"       unused   input  active-high
        line   3:       "SCL1"       unused   input  active-high
        line   4:  "GPIO_GCLK"       unused   input  active-high
        line   5:      "GPIO5"       unused   input  active-high
        line   6:      "GPIO6"       unused   input  active-high
        line   7:  "SPI_CE1_N"   "spi0 CS1"  output   active-low [used]
        line   8:  "SPI_CE0_N"   "spi0 CS0"  output   active-low [used]
        line   9:   "SPI_MISO"       unused   input  active-high
        line  10:   "SPI_MOSI"       unused   input  active-high
        line  11:   "SPI_SCLK"       unused   input  active-high
        line  12:     "GPIO12"       unused   input  active-high
        line  13:     "GPIO13"       unused   input  active-high
        line  14:       "TXD1"       unused   input  active-high
        line  15:       "RXD1"       unused   input  active-high
        line  16:     "GPIO16"       unused   input  active-high
        line  17:     "GPIO17"       unused   input  active-high
        line  18:     "GPIO18"       unused   input  active-high
        line  19:     "GPIO19"       unused   input  active-high
        line  20:     "GPIO20"       unused   input  active-high
        line  21:     "GPIO21"       unused   input  active-high
        line  22:     "GPIO22"       unused   input  active-high
        line  23:     "GPIO23"       unused   input  active-high
        line  24:     "GPIO24"       unused   input  active-high
        line  25:     "GPIO25"       unused   input  active-high
        line  26:     "GPIO26"       unused   input  active-high
        line  27:     "GPIO27"       unused   input  active-high
        line  28: "RGMII_MDIO"       unused   input  active-high
        line  29:  "RGMIO_MDC"       unused   input  active-high
        line  30:       "CTS0"       unused   input  active-high
        line  31:       "RTS0"       unused   input  active-high
        line  32:       "TXD0"       unused   input  active-high
        line  33:       "RXD0"       unused   input  active-high
        line  34:    "SD1_CLK"       unused   input  active-high
        line  35:    "SD1_CMD"       unused   input  active-high
        line  36:  "SD1_DATA0"       unused   input  active-high
        line  37:  "SD1_DATA1"       unused   input  active-high
        line  38:  "SD1_DATA2"       unused   input  active-high
        line  39:  "SD1_DATA3"       unused   input  active-high
        line  40:  "PWM0_MISO"       unused   input  active-high
        line  41:  "PWM1_MOSI"       unused   input  active-high
        line  42: "STATUS_LED_G_CLK" "ACT" output active-high [used]
        line  43: "SPIFLASH_CE_N" unused input active-high
        line  44:       "SDA0"       unused   input  active-high
        line  45:       "SCL0"       unused   input  active-high
        line  46: "RGMII_RXCLK" unused input active-high
        line  47: "RGMII_RXCTL" unused input active-high
        line  48: "RGMII_RXD0"       unused   input  active-high
        line  49: "RGMII_RXD1"       unused   input  active-high
        line  50: "RGMII_RXD2"       unused   input  active-high
        line  51: "RGMII_RXD3"       unused   input  active-high
        line  52: "RGMII_TXCLK" unused input active-high
        line  53: "RGMII_TXCTL" unused input active-high
        line  54: "RGMII_TXD0"       unused   input  active-high
        line  55: "RGMII_TXD1"       unused   input  active-high
        line  56: "RGMII_TXD2"       unused   input  active-high
        line  57: "RGMII_TXD3"       unused   input  active-high
gpiochip1 - 8 lines:
        line   0:      "BT_ON"       unused  output  active-high
        line   1:      "WL_ON"       unused  output  active-high
        line   2: "PWR_LED_OFF" "PWR" output active-low [used]
        line   3: "GLOBAL_RESET" unused output active-high
        line   4: "VDD_SD_IO_SEL" "vdd-sd-io" output active-high [used]
        line   5:   "CAM_GPIO" "cam1_regulator" output active-high [used]
        line   6:  "SD_PWR_ON" "sd_vcc_reg"  output  active-high [used]
        line   7:    "SD_OC_N"       unused   input  active-high

I've read at other places that using SPI with "active high" CS is not easily possible using spidev? But those comments are rather old...

The DC pin works - forget this part of the heading.

wuhanstudio commented 8 months ago

Hi I find a solution that solves this problem by changing the device tree:

https://forums.raspberrypi.com/viewtopic.php?t=304156

fragment@0 {
        target = <&spi0>;
        frag0: __overlay__ {
                    spidev@0 {
                        spi-cs-high;
                    };
                    spidev@1 {
                        spi-cs-high;
                    };
        };
    };
karlo922 commented 8 months ago

But this will force it to be "always" high, if active, correct?

Because the real issue will then be, that I have one display using 2 controllers in master/slave configuration for left and right half of it. With CS = 1 you select the master, with CS = 0 you select the slave one.

The rest of the pins are shared, so in u8g2 I will call 2 constructors for the same pins, but once with a constructor that uses CS high as active and once with CS low as active.

Will this work with this workaround? Or is it fully impossible even with SW-SPI to handle 2 displays with your port like that?

My working arduino code:

U8G2_S1D15300_97X32_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 9, /* dc=*/ 7, /* reset=*/ U8X8_PIN_NONE);
U8G2_S1D15300_100X32I_F_4W_HW_SPI u8g2i(U8G2_R0, /* cs=*/ 9, /* dc=*/ 7, /* reset=*/ U8X8_PIN_NONE);

U8G2 *u8g2_current;
u8g2_uint_t offset;

void setLeftSide() {
  u8g2_current = &u8g2i;
  offset = 0;
}
void setRightSide() {
  u8g2_current = &u8g2;
  offset = 100;
}

void drawStr(u8g2_uint_t x, u8g2_uint_t y, const char *s) {
  u8g2_current->drawStr(x-offset,y,s);
}

void drawWelcome() {
  u8g2_current->setFont(u8g2_font_ncenB10_tr);
  drawStr(5, 22, "Cappuccino, Mausebaer?");
}

void setup(void) {
    u8g2i.begin();
    u8g2.begin();
}

void loop(void) {

    setLeftSide();
    u8g2_current->firstPage();
    do { drawWelcome(); } 
    while (u8g2_current->nextPage());

    setRightSide();
    u8g2_current->firstPage();
    do { drawWelcome(); } 
    while ( u8g2_current->nextPage());
}
wuhanstudio commented 8 months ago

A possible solution is to add an inverter to one of the CS pins.

https://forums.raspberrypi.com/viewtopic.php?t=331853

As for the Linux device tree, the SPI bus can either work in the active-high or active-low mode.

karlo922 commented 8 months ago

As an inverter would not do the trick using both levels for the master/slave setup, I hope SW SPI should work?

wuhanstudio commented 8 months ago

Yes, SW SPI should work.

Unlike Arduino, the Linux HW SPI is defined in the kernel and configured via device tree, which is beyond u8g2's control.

karlo922 commented 8 months ago

I'll try and let you know here

karlo922 commented 8 months ago

After updating to the most current u8g2 with your auto_sync tool, I tried to convert the working ardiuno code https://github.com/wuhanstudio/u8g2-arm-linux/issues/8#issuecomment-2002553996 like that:

#include "u8g2port.h"

#define GPIO_CHIP_NUM 0
#define OLED_SPI_PIN_MOSI           17
#define OLED_SPI_PIN_SCK            27
#define OLED_SPI_PIN_RES            U8X8_PIN_NONE
#define OLED_SPI_PIN_DC             2
#define OLED_SPI_PIN_CS             3

u8g2_t u8g2i;
u8g2_t u8g2;
u8g2_t u8g2_current;
u8g2_uint_t offset;

void setLeftSide() {
  u8g2_current = u8g2i;
  offset = 0;
}
void setRightSide() {
  u8g2_current = u8g2;
  offset = 100;
}

void drawStr(u8g2_uint_t x, u8g2_uint_t y, const char *s) {
    u8g2_DrawStr(&u8g2_current,x-offset,y,s);
}

void drawWelcome() {
  u8g2_SetFont(&u8g2_current,u8g2_font_ncenB10_tr);
  drawStr(5, 22, "LEFT SIDE AND RIGHT SIDE");
}

int main(void) {

// Initialization
u8g2_Setup_s1d15300_100x32i_f(&u8g2i, U8G2_R0, u8x8_byte_4wire_sw_spi,u8x8_arm_linux_gpio_and_delay);
init_spi_sw(&u8g2i, GPIO_CHIP_NUM, OLED_SPI_PIN_DC, OLED_SPI_PIN_RES,OLED_SPI_PIN_MOSI, OLED_SPI_PIN_SCK, OLED_SPI_PIN_CS, 0);

u8g2_InitDisplay(&u8g2i);
u8g2_ClearDisplay(&u8g2i); 
u8g2_SetPowerSave(&u8g2i, 0);

u8g2_Setup_s1d15300_97x32_1(&u8g2, U8G2_R0, u8x8_byte_4wire_sw_spi,u8x8_arm_linux_gpio_and_delay);
init_spi_sw(&u8g2, U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE, 0);

u8g2_InitDisplay(&u8g2);
u8g2_ClearDisplay(&u8g2); 
u8g2_SetPowerSave(&u8g2, 0);

// Draw
setLeftSide();
u8g2_FirstPage(&u8g2_current);
do { drawWelcome(); } 
while (u8g2_NextPage(&u8g2_current));

setRightSide();
u8g2_FirstPage(&u8g2_current);
do { drawWelcome(); } 
while ( u8g2_NextPage(&u8g2_current));

printf("Initialized ...\n");
sleep_ms(5000);

// Close and deallocate GPIO resources
done_user_data(&u8g2i);
done_user_data(&u8g2);

printf("Done\n");

return 0;
}

But it only shows the left part of the display. I guess because with setting all pins of the "right" constructor to U8X8_PIN_NONE the right constructor u8g2 does not do anything on the hardware. But if I give the rigth controller the same pins like in u8g2i a fault will be triggered:

u8g2_Setup_s1d15300_100x32i_f(&u8g2i, U8G2_R0, u8x8_byte_4wire_sw_spi,u8x8_arm_linux_gpio_and_delay);
init_spi_sw(&u8g2i, GPIO_CHIP_NUM, OLED_SPI_PIN_DC, OLED_SPI_PIN_RES,OLED_SPI_PIN_MOSI, OLED_SPI_PIN_SCK, OLED_SPI_PIN_CS, 0);

u8g2_InitDisplay(&u8g2i);
u8g2_ClearDisplay(&u8g2i); 
u8g2_SetPowerSave(&u8g2i, 0);

u8g2_Setup_s1d15300_97x32_1(&u8g2, U8G2_R0, u8x8_byte_4wire_sw_spi,u8x8_arm_linux_gpio_and_delay);
init_spi_sw(&u8g2, GPIO_CHIP_NUM, OLED_SPI_PIN_DC, OLED_SPI_PIN_RES,OLED_SPI_PIN_MOSI, OLED_SPI_PIN_SCK, OLED_SPI_PIN_CS, 0);

u8g2_InitDisplay(&u8g2);
u8g2_ClearDisplay(&u8g2); 
u8g2_SetPowerSave(&u8g2, 0);
...
...
gpio_open(): pin 27, Opening output line handle: Device or resource busy [errno 16]
gpio_open(): pin 17, Opening output line handle: Device or resource busy [errno 16]
gpio_open(): pin 3, Opening output line handle: Device or resource busy [errno 16]
gpio_open(): pin 2, Opening output line handle: Device or resource busy [errno 16]

How can I achieve the same I do with the original arduino lib using the two constructors with the same pins with your port of the lib?

wuhanstudio commented 8 months ago

Hi,

Could you please use different gpio ports for the other screen? Because Linux gpio ports are mounted as files, which cannot be opened by two constructors.

karlo922 commented 8 months ago

I cannot use different pins, as the I have only 4 wires to the display. But on the display PCB, two controllers are soldererd in master/slave mode. You control with the one CS line (high: master / low: slave) wich of the two shall do something. They share the same display, the master one controls the left half, the slave one the right half.

similar like this: grafik

So I need to use the exact same pins as the hardware needs me to do that. I have checked this out with olikraus with his lib, and what you see in my "working arduino code example" was the solution.

I have now tested it with always calling done_user_data(&u8g2i); or done_user_data(&u8g2); so in between writing to the display, I free the other controller. It works but may be slow? So you do not have any other idea how I could do this? With arduino itself it was no issue with writing to the same pins.

wuhanstudio commented 8 months ago

One possible solution is to synchronise the file descriptors (all gpio pins) for two u8g2 instances, so that you don’t have to close and reopen it every time.

The file descriptors are saved in user_data->pins[i]

void done_user_data(u8g2_t *u8g2) {
    user_data_t *user_data = u8g2_GetUserPtr(u8g2);
    if (user_data != NULL) {
        // Close all GPIO pins
        for (int i = 0; i < U8X8_PIN_CNT; ++i) {
            if (user_data->pins[i] != NULL) {
                gpio_close(user_data->pins[i]);
                gpio_free(user_data->pins[i]);
            }
        }
        // Free internal buffer
        free(user_data->int_buf);
        // Free user data struct
        free(user_data);
        u8g2_SetUserPtr(u8g2, NULL);
    }
}
karlo922 commented 8 months ago

Thanks for pointing to that although I have not yet got your idea ;) Do you mean to create a kind of "mapper" so I would give dummy pins to the two constructors and merge them somehow to one real pin? Or what did you mean with "synchronise the pins"?

wuhanstudio commented 8 months ago

For example, to avoid always calling done_user_data(&u8g2i):

// Get GPIO file descriptors
user_data_t *user_data = u8g2_GetUserPtr(u8g2);
user_data_t *user_data_i = u8g2_GetUserPtr(u8g2i);

// Copy file descriptors from u8g2 to u8g2i
if ( (user_data != NULL) && (user_data_i != NULL) ){
    for (int i = 0; i < U8X8_PIN_CNT; ++i) {
        if (user_data->pins[i] != NULL) {
            user_data_i->pins[i] = user_data->pins[i];    // Copy the file descriptor
        }
}

// Now, u8g2 and u8g2i share the same file descriptors, so that we don't need to repeatedly close and reopen gpio ports
// At the end of the program, please be careful with `done_user_data(u8g2)` to avoid double free.
karlo922 commented 7 months ago

Thank you, this works with some changes :)

wuhanstudio commented 7 months ago

Cheers !