Closed Dash095 closed 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
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.
}
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
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.
}`
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.
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:
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