Example code for RX65N Envision dev board with GCC RX compiler

There seems to be little example code around for the RX65N Envision kit in general, and even less when using the GCC-RX compiler. I've seen a request or two here for a simple hello world type application. The example app that Renesas supplies for this board using their compiler is complicated and tied in to Emwin, so I've put together a much simpler one for anyone who's interested. It has touch screen and LCD drivers incorporated, all using direct register access, but neatly abstracted away into a simple API. The app reads touch screen coordinates and plots a box on the screen displaying the touch point screen coordinate inside it as text. The main function is effectively this...

system_clock_config();
touch_init();
lcd_init();

lcd_filled_rectangle(0, 0, 480, 272, RED);

while (true)
{
    touched = touch_get_point(&x, &y);
    if (touched)
    {
        if (x < 450 && y < 240)
        {
            lcd_filled_rectangle(x, y, 30, 32, BLACK);
            itoa(x, text, 10);
            lcd_string(x, y, text, WHITE);
            itoa(y, text, 10);
            lcd_string(x, y + 16, text, WHITE);
        }
    }
}

Colours parameters to the lcd_* functions are in 24 bit (RGB888) format even though the display uses 16 bit (RGB565) format and are downsized in the functions. I know this is inefficient, but in demo code it's more intuitive to deal with RGB888 format colours.

I've created an e2studio project (version 7.2.0) and the GCC RX version used is 4.8.4.201803-GNURX. All the project files and source are in in the EnvisionDemo1 folder in github here...

https://github.com/miniwinwm/RenesasEnvisionGCC

If anyone finds this useful I'll do some more examples projects. I also have it working using the cc-rx compiler if anyone is interested in that.

  • Hi John,

    Thanks for sharing this one. Yes, if you can share more application projects for RX65N, that would be very helpful. Thank you very much.

    JB
    RenesasRulz Forum Moderator

    https://renesasrulz.com/
    https://academy.renesas.com/
    https://en-us.knowledgebase.renesas.com/

  • In reply to JB:

    I've added another simple project which writes and reads from the on-chip data flash memory. This project includes a driver for writing a variable length data structure always to the start of the data flash memory (this can easily be changed in the driver code if required). The driver uses direct register access but has a simple API so that the main program is this...

    system_clock_config();

    non_vol_save((uint8_t *)&test_data_1, sizeof(test_data_t));
    non_vol_load((uint8_t *)&test_data_2, sizeof(test_data_t));

    if (memcmp(&test_data_2, &test_data_1, sizeof(test_data_t)) == 0)
    {
        success = true;
    }
    else
    {
        success = false;
    }

    This one is in the EnvisionDemo2 sub-folder in the same Github repo as above.

    Also I've added in the repo's README.md file instructions on how to create a project for the Envision Kit using GCC RX, including instructions on how to change the default stack size from the default 256 to something larger.

  • In reply to MiniWin:

    There's now a 3rd one up. This one sets up GLCDC to use both display buffers. The primary buffer (2) is configured as before as 16bit RGB565 format, but this time the secondary buffer (1) is configured as 1 bit monochrome.

    On running the app the primary colour one is shown most of the time with a simple moving graphic display, but at the same time the LCD API can be used to write text and scroll it to the secondary monochrome buffer (1). This is not shown until the user button is pressed when the buffer shown flips from primary to secondary when the button is pressed. When the button is released it reverts to showing the primary display.

    Thought this could be useful when developing a UI application - show your UI using the primary buffer but log text output to the secondary buffer and flip to it temporarily at any time by pressing the user button.

    The main loop is this...

    while (true)
    {
        i++;
        if (i > 250)
        {
            i = 0;
        }

        // plot and scroll text on gr1
        lcd_string_1(0, 261, "Test text");
        itoa(i, num_buf, 10);
        lcd_string_1(60, 261, num_buf);
        lcd_scroll_display_up_1(10);
        delay_ms(500);

        // simple moving graphic on gr2
        lcd_filled_rectangle_2(0, 0, 480, 272, RED);
        lcd_filled_rectangle_2(i, i, 20, 20, BLUE);
        delay_ms(500);
    }

    Flipping the displayed buffer is done in the button press interrupt handler...

    void INT_Excep_ICU_IRQ13(void)
    {
        if (PORT0.PIDR.BIT.B5 == 0U)
        {
            lcd_select_buffer(1);
        }
        else
        {
            lcd_select_buffer(2);
        }
    }

    The calls to the LCD driver via the lcd_* functions now have a _1 or _2 at the end of the name to set the buffer the output goes to.

  • In reply to MiniWin:

    Another one today, number 4. This is a port of FreeRTOS version 10.2.0 to run on the Envision Kit. It uses the RX600v2 for GCC port layer available on the FreeRTOS website and bits from the FreeRTOS example project for the RX64M processor.

    The example app creates 3 tasks and does a bit of task synchronization between them. Apart from that it does nothing, but the 3 tasks can be seen running using breakpoints and the debugger.

    This is by no means a tested verified port of FreeRTOS to the RX65N, it's just a bit of amateur tinkering, so should be used as demonstration, hobbyist or prototype code only, and not be used in a commercial product.

    It can be found under EnvisionDemo4 in GitHub here...

    github.com/.../RenesasEnvisionGCC

  • In reply to MiniWin:

    Number 5 - real time clock example.

    The Envision Kit does not have a 32.768 kHz crystal fitted to drive the sub-clock, but the main 12 MHz clock can be used, although it's likely to be less accurate.

    This example has drivers written going directly to registers and provides a simple API for initialising the RTC, settings and getting the time, and a simple main program that uses them that looks like this...

    tm_t t1 = {0};
    tm_t t2 = {14, 11, 17, 22, 2, 118};

    system_clock_config();
    rtc_init();
    rtc_set_time(&t2);

    while (true)
    {
        rtc_get_time(&t1);
        delay_us(1000000);
    }

    The driver code shows an example of setting up a software configurable interrupt which is needed when reading the time from the RTC.

    The git link is here...

    https://github.com/miniwinwm/RenesasEnvisionGCC/tree/master/EnvisionDemo5

  • In reply to MiniWin:

    Number 6 is up. This one gets the open source FAT file system library, FatFS, working on the Envision Kit.

    There are some issues with getting FatFS working on this board. Using CC-RX and FIT libraries it's easy - use the Renesas supplied USB host library, the Mass Storage Class library, and even their own cut down version of FatFS, all integrated for you. Without these libraries for GCC, it's not so easy.

    A first alternative is to use the RX65N SD interface with an on-board SD card reader, for which there is board space but no hardware. Unfortunately, the SD card reader Renesas specified is unusual, single manufacturer, and now discontinued. 

    A third solution, and the one used in this example, is to get an external SD card reader with SPI connections designed for Arduino projects (cheaply available from EBay for less than five $£€) and attach it to the Pmod connector CN14. The SPI interface is the easiest to get going from the 3 communications protocols, if the slowest. It runs at 1Mbit per second, so is fine for small slow speed file access.

    A simplistic synchronous SPI driver has been created for this project, using direct register access, for the SPI connections available on the Pmod connector, SCI2. This has been integrated with the SD interface part of FatFS, which means that the full FatFS API can be used once the hardware has been initialized. The default stack size needs to be increased from 256 bytes to 1024 bytes, and will need increasing again if any FatFS objects are local variables.

    The main program to initialize FatFS, create a file, write to it and close it looks like this...

    sci2_spi_init();
    f_mount(&FatFs, "", 0U);
    if (f_open(&Fil, "miniwin.txt", FA_WRITE | FA_CREATE_ALWAYS) == FR_OK)
    {
        f_write(&Fil, "FatFS for Renesas Envision Kit\r\n", 32U, &bytes_written);
        f_close(&Fil);
    }

    The external SPI SD card reader is connected like this (details in the project readme.txt)...

      

    This is only a simple demonstration project. It's not quick, thread safe or asynchronous, and has no error checking, but might be a useful starting point if further features are required. The project is in github here...

    github.com/.../EnvisionDemo6

  • In reply to MiniWin:

    Three more simple examples up...

    EnvisionDemo7 - Creates a compare/match timer and flashes the user LED 

    EnvisionDemo8 - Reads the on-chip temperature sensor and calculates the chip temperature (this is almost always a few degrees higher than the environment temperature because of the heat generated by the processor).

    EnvisionDemo9 - Simple interrupt on both edges of a GPIO change. Pressing the user button changes the state of the user LED.

     

    All at the usual place in Github...  github.com/.../RenesasEnvisionGCC

  • In reply to MiniWin:

    A couple of PWM examples.

    Number 10 uses the simple 8 bit timer in PWM mode to control P55 that's available on the Pmod connector (CN14). By putting a LED in series with a resistor between this connection and the ground line also available on the Pmod connector the brightness of the LED can be varied.

    Number 11 is also PWM, but a bit more complex. This demonstration shows how to use the Multi-Function Timer Pulse Unit 3 module (MTU3) for PWM generation to control the level of the Envision Kit's display's backlight.

    Although this module can do simple PWM output that's not available on the pin that the backlight enable line is on (P66). This pin is only available for MTU3 PWM using one PWM output line of complementary PWM or reset synchronized PWM. Reset synchronized PWM is simpler, so that is used. The MTU3 knows this output as MTIOC7D, and is one of multiple output lines controlled in reset synchronized PWM. The other lines in this example are unused.

    Reset synchronized PWM requires 2 timers to be used, which for this output pin to be controlled need to be timers 6 and 7.

    Buffering of the PWM timing registers is used to keep the timing and counting consistent, although the cycle period is fixed, so it's only the duty value that is changed.

    The code goes straight to I/O registers but a simple API is available to set the backlight percentage. The code in main to control the backlight looks like this...

    backlight_init();

    while (true)
    {
        duty += 1U;
        if (duty > 100U)
        {
            duty = 0U;
        }

        backlight_set(duty);

        delay_us(10000);

    }

  • In reply to MiniWin:

    Number 12 - a DMA example which uses both peripheral triggered and software triggered DMA to send a bitmap to the Envision kit's display.

    This demonstration shows how to use the DMA to copy from memory to memory. The LCD is set up to show 16 bit RGB565 data. A bitmap is included that is in the same format as the display. DMA is used to copy the bitmap data to the display buffer.

    As the bitmap is less wide than the display, to copy the bitmap to the display buffer, non-contiguous DMA is required. The copy process is split into sections where one section is a horizontal line of the bitmap.

    The copy process is initiated by pressing the user button. DMA channel 0 is initialized to start its transfer on a peripheral interrupt. The user button is initialized to create IRQ13 when it is pressed and the DMA channel is configured to be triggered by this interrupt.

    When a line has been copied by DMA a DMA transfer complete interrupt is generated. This updates the destination address to the next line in the display buffer and the next DMA transfer is initiated by software. All subsequent transfers for the remaining bitmap lines are software initiated.

    The example goes directly to registers. When the application is  running press the user button. On each press the bitmap is copied to the display buffer (for 3 presses after which the display is full).

  • In reply to MiniWin:

    Number 13 - an independent watchdog example.

    This example demonstrates the independent watchdog in window mode. The watchdog cycles is set to approximately 4 seconds and the window in which the watchdog is allowed to be kicked is the middle 2 seconds of that period (after 1s, before 3s). Kicking the watchdog outside of that window, or not kicking it at all, causes a reset. After a reset the reset cause is determined and displayed.

    The watchdog is set up to start running automatically at reset. This requires configuring the watchdog in the Option Setting Memory, in register OFS0. The value of the register is hard coded in the e2studio generated file vects.c near the bottom of the file. This file has been edited manually to set this register value.

    The watchdog in this example is kicked by pressing the user button. The state of the watchdog cycle is shown on the LCD. Pressing the user button at the appropriate time according to the message displayed will allow the application to continue. Pressing the button too early or too late according to the messages will cause a reset and restart.

    The text displayed does not scroll. When it reached the bottom of the display - that's it.

  • In reply to MiniWin:

    Number 14 - Event Link Controller example

    This example demonstrates the event link controller. This module allows events to be communicated between hardware modules independent of interrupts or code.

    This simple example sets up an 8 bit timer, TMR0, and on it's match A value sends an event to the ELC that toggles port B6.

    Port B6 is output on pin 10 of the Envision Kit's Pmod connector. Connecting a scope to this pin will show a square wave output controlled by TMR0 via the ELC.

    The code goes straight to registers.
  • In reply to MiniWin:

    Here's the next one, number 15. This example demonstrates how to put the processor in low power mode, in this example the lowest of them all, deep software standby mode. In addition this example shows how to detect the reason for a reset cycle on wake and counts the number of reset cycles since power-on, saving the count value in one of the deep cycle backup registers.

    It can be found at the link below, along with a readme file which gives more information.

    https://github.com/miniwinwm/RenesasEnvisionGCC/tree/master/EnvisionDemo15

  • In reply to MiniWin:

    Number 16 - a driver for the Macronix MX25L32 QSPI flash chip fitted to the Envision Kit. 

    This example provides a driver for the Envision Kit's on-board flash memory chip which uses a QSPI interface. The driver is at 2 levels, the QSPI level which sets up, reads and writes to the RX65N's QSPI peripheral, and a higher level which prepares and sends commands to the MX25L QSPI flash chip on the Envision Kit board. The test code for this example erases, writes, reads back and compares 3 sections of the flash chip's memory, and looks like this...

        qspi_MX25L_sector_erase(0x3000);

        do
        {
            qspi_MX25L_get_write_in_progress(&write_in_progress);
        }
        while (write_in_progress);

        qspi_MX25L_page_write(0x3000, 256, test_data_write);

        do
        {
            qspi_MX25L_get_write_in_progress(&write_in_progress);
        }
        while (write_in_progress);

        qspi_MX25L_read(0x3000, 256, test_data_read);

    Only a small sub-set of the commands the MX25L supports is implemented in the MX25L layer. It will be easy to extend this for other commands.

    A feature of the MX25L chip to be noted is that writes can be a maximum of 256 bytes at a time and must not cross a 256 page boundary, although apart from that restriction, writes can start at any byte address. For example, writing 10 bytes starting at address 240 is allowed. Writing 20 bytes starting at address 240 is not allowed. The MX25L layer write command enforces this restriction.

    To keep things simple and understandable as an example this code only uses QSPI in single SPI mode and does not use DTC or DMA. However, those extra features can be added if required by building on this example.

    This module does things differently from the FIT QSPI module. The RX QSPI peripheral can control the QSPI chip select line (QSSL) in hardware. This example uses that feature, the FIT module does not, and requires the user to control the QSSL line from software using a GPIO. The reason for this is explained here, and it's to do with the style of the API.

    First, a brief explanation of QSPI as used to communicate with a QSPI flash chip as found on the Envision Kit. QSPI even in single SPI mode is slightly different from standard SPI. In standard SPI the chip select (CS) line really is that. If you only have one SPI slave on your SPI lines you can select CS to asserted and leave it at that. You can even tie it to asserted in hardware.

    In QSPI the QSSL line isn't used only for chip selecting purpose, it's used for signalling the end of transmission as well. A typical QSPI operation is...

    - Assert QSSL
    - Send command details
    - Send data, keep on sending unspecified amount of data until...
    - De-assert QSSL which signals that the operation has ended

    This is shown below with a read command. QSSL# is asserted, a 3 for the read command is sent, then 3 bytes of the address to start reading from (0 48 0), then the data is clocked out until the QSSL# line is de-asserted letting the chip know that the operation has completed.

    Now for how this example's API and the FIT QSPI module's API vary. The example operation described here is to write a page of data. This requires a 4 byte command and 256 bytes of data, 260 bytes in all. The FIT module allows you to write arbitrary sized chunks of that data across multiple calls, for example write 10, 128, 50, 72 bytes to reach the total of 260. This example's API requires one call, giving a pointer to the command, it's length, a pointer to the data, and it's length.

    Given the order of operations above, whether the API is used as an enforced single call or optional multiple calls the QSSL line must be asserted at the beginning and de-asserted at the end. With multiple calls of arbitrary length data sections (as the FIT module allows) - that's a problem, and it's to do with sequences, which control how the data is transferred.

    The RX QSPI module uses the concept of sequences. A sequence defines the size of data to be transferred (byte/short/long) and how many bytes/shorts/longs in the sequence. A complete QSPI operation comprises 1 or more (up to 4) sequences. If a large amount of data is being transferred then the operation should transfer most of it as longs for efficiency, so an operation will comprise a sequence of longs followed by an optional sequence of bytes if there are any bytes left over.

    These sequences must be defined before the QSPI enable flag in the RX QSPI peripheral is asserted which allows transmission to start. Once the QSPI enable flag has been asserted the manual says don't touch these sequences until the operation is finished and the QSPI enable flag is de-asserted. So here's the problem. If you have an API like the FIT module which allows arbitrary length chunks to be sent you cannot configure your sequences once at the start - you have to reconfigure your sequences after every API call to transfer the next chunk of data. That requires the QSPI peripheral to be de-asserted - and that de-asserts QSSL signalling the chip that the transfer has completed, even if more data is to follow. So the only options are give details of all the data in one single API call (as this example does), or require the user to operate the QSSL line via software using a GPIO (as the FIT module does).

    The driver here uses 1 to 3 sequences to implement a complete operation. The first sequence is always present and sends the command in byte chunks. If there is any data following (there may not be, some operations are command only) a following sequence is used. If the amount of data is >= 32 bytes a sequence of longs is used to transfer the data. If there is any data remaining (< 32 bytes) a final sequence of bytes is used. If the data is present but < 32 bytes in total the sequence of longs in the middle is not used.

  • In reply to MiniWin:

    Number 17 - debug i/o redirect.

    This example allows you to redirect normal stdio console input/output to either the Renesas Debug Virtual Console in e2studio or the serial port available on the Pmod connector on the Envision Kit (SCI9). If the serial port on the CN14 Pmod connector is used a small TTL to RS-232 level converter board is required, available on EBay for a few $. A level converter board can be powered from the power lines also available on the Pmod connector.

    All input/output routines available in stdio.h that eventually use puchar() & getchar() can have their i/o redirected using this example code. These include printf(), puts(), scanf(), gets() etc., but not the functions that take a FILE * parameter (i.e. fprintf()). You can write code like this...

        printf("Hello, world\r\n");
        printf("Enter a number...");

        scanf("%d", &i);

        printf("The number you entered was: %d\r\n", i);

    To choose the destination of the redirected input/output modify one of the #defines at the top of debug_io.h...

        #define DEBUG_IO_DESTINATION_SERIAL
        #define DEBUG_IO_DESTINATION_CONSOLE

    This example works by replacing the standard puchar() and getchar() routines in the standard library with versions in debug_io.c. To facilitate this these linker flags are required...

        -fno-builtin-putchar
        -fno-builtin-getchar

    These linker flags need adding in Properties|C/C++ Build|Settings|Linker. Append them to the list of existing flags in the box labeled 'Expert settings: Command line pattern'.

    Some cautions: all the stdio routines used in this example are blocking, not thread safe, not suitable for use in interrupt handlers, and scanf() and gets() are well known as a security risk from buffer overflow. Use only in test or debug code.

    The declarations and code to write to the Renesas Debug Virtual Console is copied from lowlvl.c, part of the Renesas r_bsp package for RX65N processor.

  • In reply to MiniWin:

    Thanks for the #17 debug console solution. As you noted, redefining putchar() and getchar() subverts their normal operation.

    To preserve the normal putchar() and getchar() operation, one could define the low-level write() and read() functions so that they check for stdin, stdout, and stderr, and then route the those streams data to an appropriately defined user function.

    In the Renesas FIT BSPs, the low-level console I/O functions are routed to the locally defined functions, charput() and charget(). The FIT BSP gives the user a configuration option to use the built-in versions of these functions or to define their own. This works well if one is using CC-RX and the project generator wizard , as the low-level stream I/O routing is provided in the file, "lowsrc.c".

    In e2studio V7.3, a GCC project created with the project generator wizard and Smart Configurator does include a GCC compatible FIT-based BSP, which includes the "lowlvl.c" file that defines the charput() and charget functions. But, unfortunately (at this time), doesn't include lowsrc.c. Here is a simplified version, that is sufficient for standard console I/O only. To add file stream I/O, these functions would need to be expanded to check for other file handles and perform the usual routing to locally defined I/O drivers.

    Note that this isn't using the reentrant version functions that are available, so wouldn't be thread safe. You can find the stubs for the Newlib reentrant versions in the GCC distribution folder, "..\GCC for Renesas RX 4.8.4.201801-GNURX-ELF\rx-elf\rx-elf\bin\newlib\libc\syscalls\"

     

    #include <stdint.h>
    #include <stdio.h>

    /* file number */
    #define STDIN  0                    /* Standard input (console)        */
    #define STDOUT 1                    /* Standard output (console)       */
    #define STDERR 2                    /* Standard error output (console) */
     
    /* Special character code */
    #define CR 0x0d                     /* Carriage return */
    #define LF 0x0a                     /* Line feed       */
     
    /* Output one character to standard output */
    extern void charput(unsigned char);
    /* Input one character from standard input */
    extern unsigned char charget(void);
     
    int write (int fd, const void *buf, size_t cnt)
    {
        char *bufptr = (char *)buf;  /* The address of source buffer */
        int  count = cnt;            /* The number of characters to write    */
        unsigned char    c;          /* An output character            */
        if( fd == STDIN )
        {
            return -1;            /* Standard Input     */
        }
        else if((fd == STDOUT) || (fd == STDERR)) /* Standard Error/output   */
        {
        /* WAIT_LOOP */
            for(int i = count; i > 0; --i )
            {
                c = *bufptr++;
                charput(c);
            }
            return count;        /*Return the number of written characters */
        }
        else
        {
            return -1;                  /* Incorrect file number          */
        }
    }
     
    int read(int fd, void *buf, size_t count )
    {
        char *bufptr = (char *)buf;  /* The address of destination buffer */
        if( fd == STDIN )
        {
            /* WAIT_LOOP */
            for(int i = count; i > 0; i--)
            {
                *bufptr = charget();
                if( *bufptr == CR )
                {              /* Replace the new line character */
                   *bufptr = LF;
                }
                bufptr++;
            }
            return count;
            }
            else
            {
            return -1; /* unknown file */
            }
        
    }