Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atmel SAM example #117

Closed
Dash095 opened this issue Dec 22, 2016 · 5 comments
Closed

Atmel SAM example #117

Dash095 opened this issue Dec 22, 2016 · 5 comments
Labels

Comments

@Dash095
Copy link

Dash095 commented Dec 22, 2016

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
Copy link
Owner

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
Copy link
Author

Dash095 commented Dec 23, 2016

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. 
}


@Dash095 Dash095 closed this as completed Dec 23, 2016
@olikraus
Copy link
Owner

olikraus commented Dec 23, 2016

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
Copy link

maunns commented Mar 31, 2023

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
Copy link
Owner

olikraus commented Apr 2, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants