olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
5.16k stars 1.05k forks source link

Atmel SAM example #117

Closed Dash095 closed 7 years ago

Dash095 commented 7 years ago

Hello,

I've a question on how to implement the graphics library in a project I'm currently working on.

I've been trying to get the graphics library to work with a Atmel ATSAMD20 microcontroller in combination with a NHD 128x64 OLED screen. From the wiki i found the required constructor for this display to be:

u8g2_Setup_ssd1325_nhd_128x64_1(u8g2, rotation, u8x8_byte_4wire_sw_spi, uC specific)

However, since the Arduino platform is unused, I was unable to find any documentation on how to implement the "uC specific" part which is mentioned. My best guess would be that it would contain the pin mapping/ initializations for the specific uC, but in which format? I also do not know what to do with the "rotation" parameter.

In the documentation folder of the download, the file u8g2.txt mentions something about support for the sam architecture:

Better hardware support: Tested with avr, esp8266 and sam architectures.

I was wondering if this testing/ example code for the sam architecture was available somewhere. Or even an code example of how to implement the callback functions in plain c, without the use of Arduino.

Sorry for asking such trivial beginners questions. Any help would be much appreciated.

Best regards, Dash

olikraus commented 7 years ago

All good questions. I mentioned SAM, because this controller family is also one of the Arduino Architectures (Arduino Due and Arduino Zero)

The rotation argument is something like U8G2_R0. It is actually listed here: https://github.com/olikraus/u8g2/wiki/u8g2reference#carduino-example Of course it should be more clearly documented.

The uC specific part is a callback function, which must handle the low level access such as making a GPIO high or low. Actually the "none-Arduino" part does not require a pinlist. Instead it has to react on a GPIO message. No doubt, this documentation is missing, but I have an example for I2C (SPI should be similar):

https://github.com/olikraus/lpc11u3x-gps-logger/blob/master/u8g2_logo/src/u8x8_lpc11u3x.c

Function prototype is this: uint8_t u8x8_gpio_and_delay_lpc11u3x(u8x8_t u8x8, uint8_t msg, uint8_t arg_int, void arg_ptr)

obviously the name does not matter, but the args and return value is important. Always return 1 of you were able to handle the request otherwise return 0.

uint8_t msg: Messages depend on the low level protocol (such as I2C and SPI), but are all listed here (line 590ff): https://github.com/olikraus/u8g2/blob/master/csrc/u8x8.h

For SPI, you should handle the following messages (see also the example from above): U8X8_MSG_GPIO_AND_DELAY_INIT U8X8_MSG_DELAY_MILLI
U8X8_MSG_DELAY_10MICRO U8X8_MSG_DELAY_100NANO
U8X8_MSG_GPIO_SPI_CLOCK U8X8_MSG_GPIO_SPI_DATA
U8X8_MSG_GPIO_CS
U8X8_MSG_GPIO_DC
U8X8_MSG_GPIO_RESET For the five GPIO messages, arg_int contains 0 or 1 and you should output low or high according to that value. For DELAY_MILLI arg_int contains the milliseconds which must be delayed.

When receiving U8X8_MSG_GPIO_AND_DELAY_INIT you can setup directions of your GPIO ports

arg_ptr is not required in your case

Once you wrote this one function only, you can just startup your display. See here: https://github.com/olikraus/lpc11u3x-gps-logger/blob/master/u8g2_logo/src/main.c Here is line 93-95 from that file: u8g2_Setup_ssd1306_i2c_128x64_noname_2(&u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay_lpc11u3x); // Prepare memory for u8glib u8g2_InitDisplay(&u8g2); // This will emit U8X8_MSG_GPIO_AND_DELAY_INIT and also send the startup code for the display u8g2_SetPowerSave(&u8g2, 0); // this will finally enlight the display, so it might be a clever idea to clean the display RAM first

Dash095 commented 7 years ago

Thanks for the quick and thorough reply.

I've implemented the callback function for the ATSAMD20E15, and it works as expected.

The only downside i found with using the software emulated SPI is that the maximum speed of the bus caps at 1.2MHz, which is a bit slow. I will try write a custom function to use u8g2 with the hardware SPI interface of the chip.

For those who come across this thread with a similar problem, here is the used code. note that it relies on the ASF delay_routines and port drivers:

void displayInit(void){
    u8g2_Setup_ssd1325_nhd_128x64_1(&disp, U8G2_R0, u8x8_byte_4wire_sw_spi, u8g2_gpio_and_delay_samd20); //Configure the display object, this will also initialize a 128 byte frame buffer
    u8g2_InitDisplay(&disp);        //Send initialization code to the display
    u8g2_SetPowerSave(&disp, 0);    
}

void initializeIO(void){
    struct port_config displayPins;
    port_get_config_defaults(&displayPins);
    displayPins.direction  = PORT_PIN_DIR_OUTPUT;

    port_pin_set_config(DISPLAY_SCLK, &displayPins);
    port_pin_set_config(DISPLAY_MOSI, &displayPins);
    port_pin_set_config(DISPLAY_RESET, &displayPins);
    port_pin_set_config(DISPLAY_DC, &displayPins);
    port_pin_set_config(DISPLAY_CS, &displayPins);
}

//SAMD20E15 specific implementation, initializes SPI peripheral and provides low level functions which allows the u8g2 library to communicate with the display 
uint8_t u8g2_gpio_and_delay_samd20(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr){

    switch(msg){
        //Initialize SPI peripheral 
        case U8X8_MSG_GPIO_AND_DELAY_INIT:
            initializeIO();
        break;

        //Function which implements a delay, arg_int contains the amount of ms
        case U8X8_MSG_DELAY_MILLI:
        delay_ms(arg_int);

        break;
        //Function which delays 10us
        case U8X8_MSG_DELAY_10MICRO:
        delay_us(10);

        break;
        //Function which delays 100ns
        case U8X8_MSG_DELAY_100NANO:
        delay_us(0.1);

        break;
        //Function to define the logic level of the clockline
        case U8X8_MSG_GPIO_SPI_CLOCK:
        port_pin_set_output_level(DISPLAY_SCLK, arg_int);

        break;
        //Function to define the logic level of the data line to the display
        case U8X8_MSG_GPIO_SPI_DATA:
        port_pin_set_output_level(DISPLAY_MOSI, arg_int);

        break; 
        // Function to define the logic level of the CS line  
        case U8X8_MSG_GPIO_CS:
        port_pin_set_output_level(DISPLAY_CS, arg_int);

        break;
        //Function to define the logic level of the Data/ Command line
        case U8X8_MSG_GPIO_DC:
        port_pin_set_output_level(DISPLAY_DC, arg_int);

        break;
        //Function to define the logic level of the RESET line
        case U8X8_MSG_GPIO_RESET:
        port_pin_set_output_level(DISPLAY_RESET, arg_int);

        break;
        default:
            return 0; //A message was received which is not implemented, return 0 to indicate an error
    }

    return 1; // command processed successfully. 
}
olikraus commented 7 years ago

Thanks for providing the code for your environment. If you want to use your hardware SPI subsystem, you also need to rewrite u8x8_byte_4wire_sw_spi:

uint8_t u8x8_byte_4wire_sw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
  uint8_t i, b;
  uint8_t *data;
  uint8_t takeover_edge = u8x8_GetSPIClockPhase(u8x8);
  uint8_t not_takeover_edge = 1 - takeover_edge;

  switch(msg)
  {
    case U8X8_MSG_BYTE_SEND:
      data = (uint8_t *)arg_ptr;
      while( arg_int > 0 )
      {
    b = *data;
    data++;
    arg_int--;
    for( i = 0; i < 8; i++ )
    {
      if ( b & 128 )
        u8x8_gpio_SetSPIData(u8x8, 1);
      else
        u8x8_gpio_SetSPIData(u8x8, 0);
      b <<= 1;

      u8x8_gpio_SetSPIClock(u8x8, not_takeover_edge);
      u8x8_gpio_Delay(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->sda_setup_time_ns);
      u8x8_gpio_SetSPIClock(u8x8, takeover_edge);
      u8x8_gpio_Delay(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->sck_pulse_width_ns);
    }    
      }
      break;

    case U8X8_MSG_BYTE_INIT:
      /* disable chipselect */
      u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
      /* no wait required here */

      /* for SPI: setup correct level of the clock signal */
      u8x8_gpio_SetSPIClock(u8x8, u8x8_GetSPIClockPhase(u8x8));
      break;
    case U8X8_MSG_BYTE_SET_DC:
      u8x8_gpio_SetDC(u8x8, arg_int);
      break;
    case U8X8_MSG_BYTE_START_TRANSFER:
      u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level);  
      u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->post_chip_enable_wait_ns, NULL);
      break;
    case U8X8_MSG_BYTE_END_TRANSFER:
      u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->pre_chip_disable_wait_ns, NULL);
      u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
      break;
    default:
      return 0;
  }
  return 1;
}

Just use this procedure as a template and replace code inside U8X8_MSG_BYTE_SEND: Output the bytes through your local SPI hardware system (arg_int bytes located at arg_ptr). You can use U8X8_MSG_BYTE_INIT to init your hardware SPI subsystem. Then pass your own byte procedure as third argument to u8g2_Setup_ssd1325_nhd_128x64_1().

Oliver

maunns commented 1 year ago

Hi @Dash095

I was able to follow through your sample code for SAMD21 in Atmel studio using Atmel start framework and draw on my screen using Software SPI (bit banging), however it is too slow for my application.

Later on, I tried to follow @olikraus 's suggestion in this and other issues to output the data from *arg_ptr with arg_int bytes to the hardware SPI function call but I had no success so far.

Please suggest if you were able to make it work using hardware SPI on either ASF or Atmel start framework for SAM series?

Thanks

My custom implementation is as follows:- io_write is the Atmel API to write to SPI with parameters, spi handle, buffer, bytes to be written

`uint8_t u8x8_byte_4wire_sw_spi_SAMD21(u8x8_t u8x8, uint8_t msg, uint8_t arg_int, void arg_ptr) { uint8_t i, b; uint8_t *data; uint8_t takeover_edge = u8x8_GetSPIClockPhase(u8x8); uint8_t not_takeover_edge = 1 - takeover_edge;

switch(msg)
{
    case U8X8_MSG_BYTE_SEND:
    data = (uint8_t *)arg_ptr;
    while( arg_int > 0 )
    {
        while(io_write(io_SPI, data, arg_int)!=1)
        {};
        data++;
        arg_int--;
    }
    break;

    case U8X8_MSG_BYTE_INIT:
    /* disable chipselect */
    u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
    /* no wait required here */

    /* for SPI: setup correct level of the clock signal */
    SPI_0_init();

    //u8x8_gpio_SetSPIClock(u8x8, u8x8_GetSPIClockPhase(u8x8));
    break;
    case U8X8_MSG_BYTE_SET_DC:
    u8x8_gpio_SetDC(u8x8, arg_int);
    break;
    case U8X8_MSG_BYTE_START_TRANSFER:
    u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level);
    u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->post_chip_enable_wait_ns, NULL);
    break;
    case U8X8_MSG_BYTE_END_TRANSFER:
    u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->pre_chip_disable_wait_ns, NULL);
    u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);

    break;
    default:
    return 0;
}
return 1;

}`

In the following function, i commented out the cases for Data and clock with a suspision if that was messing up the GPIO lines during SPI write.

`//SAMD20E15 specific implementation, initializes SPI peripheral and provides low level functions which allows the u8g2 library to communicate with the display uint8_t u8g2_gpio_and_delay_samd20(U8X8_UNUSED u8x8_t u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void arg_ptr){

switch(msg){
    //Initialize SPI peripheral
    case U8X8_MSG_GPIO_AND_DELAY_INIT:
    //system_init();
    break;

    //Function which implements a delay, arg_int contains the amount of ms
    case U8X8_MSG_DELAY_MILLI:
    delay_ms(arg_int);

    break;
    //Function which delays 10us
    case U8X8_MSG_DELAY_10MICRO:
    delay_us(10);

    break;
    //Function which delays 100ns
    case U8X8_MSG_DELAY_100NANO:
    delay_us(0.1);

    break;
    //Function to define the logic level of the clockline
    case U8X8_MSG_GPIO_SPI_CLOCK:
    //gpio_set_pin_level(DISPLAY_SCLK, arg_int);

    break;
    //Function to define the logic level of the data line to the display
    case U8X8_MSG_GPIO_SPI_DATA:
    //gpio_set_pin_level(DISPLAY_MOSI, arg_int);

    break;
    // Function to define the logic level of the CS line
    case U8X8_MSG_GPIO_CS:
    gpio_set_pin_level(DISPLAY_CS, arg_int);

    break;
    //Function to define the logic level of the Data/ Command line
    case U8X8_MSG_GPIO_DC:
    gpio_set_pin_level(DISPLAY_MOSI, arg_int);

    break;
    //Function to define the logic level of the RESET line
    case U8X8_MSG_GPIO_RESET:
    gpio_set_pin_level(DISPLAY_RESET, arg_int);

    break;
    default:
    return 0; //A message was received which is not implemented, return 0 to indicate an error
}

return 1; // command processed successfully.

}`

olikraus commented 1 year ago

I dont't know the prototype of your write function, but are you sure you need the while loop at all? It looks like you already transfer all the bytes in your io_write() procedure.