The e² studio project for Road test: Renesas Solution Starter Kit for RX23E-A is built for Renesas CCRX toolchain. I'm porting it to GCC. I've done this before for several RX65 projects (see Renesas RX65 Envision Kit - part 3: Port an example from Renesas toolchain to GCC ). This design is now fully working. e²studio project is attached to the end of the article.
Most of this post is easy. Fair warning: One section at the end involves direct memory access (DMA), and giving memory buffers a fixed, aligned address. This is an advanced topic. You 'll only have to touch this when you use DMA functionality. I tried to explain it in an understandable way. Comment if it's not clear. I provide the e² studio project with sources, linker scripts and binaries as attachment. |
Day 1: create project and set up first configuration blocks
Create project
File -> New Renesas Project -> RX -> GCC for Renesas RX /C/C++ Executable Project. rx_32a_thermocouple_gcc
C, GCC for Renesas, no RTOS, Target Board RSSKRX23EA (download it via the link on the dialog box if needed). The device is automatically selected.
Create a debug or release configuration, so that we can create a binary firmware file.
Select Smart Configurator, and Finish.
The following steps will try to exactly take over the configuration of Renesas' original project.
Configure Board and Clock settings
There's not much to do here. Open rx23ea_thermocouple_gcc.scfg. On the board tab, appreciate what's shown:
A bit more work on the clock tab: set VCC to 5 V.
Configure board support packages
On the component tab, select the r_bsp component.
- enable: Initializes C input and output library functions
- set to 5: Interrupt Priority Level When FIT Module Interrupts Are Enabled
- used: SWINT1
Save
Add first component: compare match timer
On the component tab, click the + to add a new component. Select Compare Match Timer, CMT0
- Count clock setting: PCLK/512
- enable CMI0
- disable Priority
We'll copy over some additional code for it on day 4.
Save, then Generate Code for the first time.
Day 2: test the project with a Blinky
Best way to see if this project is valid and actually runs on the board, is by making the blinky.
All activities here, except a few blinkin' lines in the main(), will be reused later.
Enable the led from the board config that we selected earlier:
Write a few macros to toggle that led. All user code has to be entered between placeholder comments. When you do that, the configurator will not touch them when doing round-trip configuration.
Config_PORT.h
/********************************************************************************************************************** Macro definitions *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_LED1_On() * Description : This function turns on LED1 * Arguments : None * Return Value : None *********************************************************************************************************************/ #define R_LED1_On() (PORTH.PODR.BIT.B2 = 0) /********************************************************************************************************************** * Function Name: R_LED1_Off() * Description : This function turns off LED1 * Arguments : None * Return Value : None *********************************************************************************************************************/ #define R_LED1_Off() (PORTH.PODR.BIT.B2 = 1)
Write a little blinky loop in main(), located in rx23ea_thermocouple_gcc.c
void main(void) { R_LED1_Off(); bool state = false; do { state = !state; if (state) { R_LED1_On(); } else { R_LED1_Off(); } // silly wait for (int i = 0; i < 10000000; i++) {}; } while (1); }
As you can see in the GIF above, it works when loading the .mod with Flash Programmer.
e² studio project, including binaries. State: the blinky quick test rx23ea_thermocouple_gcc_20230624.zip
Day 3: add and configure SCI and DMA FIT modules
One of the more complex activities: port the serial port and the DMA mechanism.
Add the serial communication module from the Board tap, then click on the Link button.
It 'll navigate to the SCI1 component.
Configure it:
We'll copy over some additional code for it on day 4.
From the SCI component, add 2 DMA Controllers, for RX and TX
Configure DMA0
Configure DMA3
We'll copy over some additional code for it on day 4.
Day 4: some more user code
The thermocouple developers have put the helper functions for each FIT module in the source files generated by the configurator. Like we did on day 2 for the SCI module: put these in the dedicated section at the end of each file between placeholder comments. When you do that, the configurator will not touch them when doing round-trip configuration.
For the timer, in Config_CMT0.h
/********************************************************************************************************************** * Function Name: R_CMT0_IsTimeout * Description : This function checks for timeout occurrence * Arguments : bool flag * false:CMT0 not stop , true:CMT0 Stop * Return Value : bool * false:Timeout has not occurred, true:Timeout occurred *********************************************************************************************************************/ bool R_CMT0_IsTimeout (bool flag); /********************************************************************************************************************** * Function Name: R_CMT0_CntClear * Description : This function clear the compare match countor of CMT0 * Arguments : None * Return Value : None *********************************************************************************************************************/ void R_CMT0_CntClear (void);
and in Config_CMT0.c
/********************************************************************************************************************** * Function Name: R_CMT0_IsTimeout * Description : This function checks for timeout occurrence. * Arguments : bool flag * false:CMT0 not stop , true:CMT0 Stop * Return Value : bool * false:Timeout has not occurred, true:Timeout occurred *********************************************************************************************************************/ bool R_CMT0_IsTimeout (bool flag) { bool ret = false; /** When a timeout occurs */ if (1U == IR(CMT0, CMI0)) { /** Clear the IR(CMT0, CMI0) */ IR(CMT0, CMI0)= 0U; R_Config_CMT0_Stop(); ret = true; } /** The condition given by the argument is true */ else if(true == flag) { R_Config_CMT0_Stop(); } /** Other */ else { /** Do nothing */ nop(); } return ret; } /********************************************************************************************************************** End of function R_CMT0_IsTimeout *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_CMT0_CntClear * Description : This function clear the compare match countor of CMT0 * Arguments : None * Return Value : None *********************************************************************************************************************/ void R_CMT0_CntClear (void) { /** Clear CMT0 count */ CMT0.CMCNT = 0U; } /********************************************************************************************************************** End of function R_CMT0_CntClear
For DMA0, Config_DMAC0.h
/********************************************************************************************************************** Macro definitions *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_DMAC0_GetDestAddr * Description : This function Get DMAC0 destination address * Arguments : None * Return Value : void * * DMAC0 destination address pointer *********************************************************************************************************************/ #define R_DMAC0_GetDestAddr() (DMAC0.DMDAR) /********************************************************************************************************************** Global functions *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_DMAC0_SetDestAddr * Description : This function set the transfer destination address of DMAC0 * Arguments : void *p_addr * Value to be set in DMAC0.DMDAR * Return Value : None *********************************************************************************************************************/ void R_DMAC0_SetDestAddr (void *p_addr);
And Config_DMAC0.c
/********************************************************************************************************************** * Function Name: R_DMAC0_SetDestAddr * Description : This function set the transfer destination address of DMAC0 * Arguments : void *p_addr * Value to be set in DMAC0.DMDAR * Return Value : None *********************************************************************************************************************/ void R_DMAC0_SetDestAddr (void *p_addr) { /** Set DMAC0.DMDAR */ DMAC0.DMDAR = p_addr; } /********************************************************************************************************************** End of function R_DMAC0_SetDestAddr
For DMA3, Config_DMAC3.h
/********************************************************************************************************************** * Function Name: R_DMAC3_SetSrcAddr * Description : This function set the transfer source address of DMAC3 * Arguments : uint8_t *p_addr * Value to be set in DMAC3.DMSAR * Return Value : None *********************************************************************************************************************/ void R_DMAC3_SetSrcAddr (void *p_addr); /********************************************************************************************************************** * Function Name: R_DMAC3_SetTxCnt * Description : This function set count of DMAC3 transmit. * Arguments : uint32_t cnt * count of DMAC3 transmit * Return Value : None *********************************************************************************************************************/ void R_DMAC3_SetTxCnt (uint32_t cnt);
And Config_DMAC3.c
/********************************************************************************************************************** * Function Name: R_DMAC3_SetSrcAddr * Description : This function set the transfer source address of DMAC3 * Arguments : void *p_addr * Value to be set in DMAC3.DMSAR * Return Value : None *********************************************************************************************************************/ void R_DMAC3_SetSrcAddr (void *p_addr) { /** Set DMAC3.DMSAR */ DMAC3.DMSAR = p_addr; } /********************************************************************************************************************** End of function R_DMAC3_SetSrcAddr *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_DMAC3_SetTxCnt * Description : This function set count of DMAC3 transmit. * Arguments : uint32_t cnt * count of DMAC3 transmit * Return Value : None *********************************************************************************************************************/ void R_DMAC3_SetTxCnt (uint32_t cnt) { /** Set DMAC3.DMCRA */ DMAC3.DMCRA = cnt; } /********************************************************************************************************************** End of function R_DMAC3_SetTxCnt
For SCI1, Config_SCI1.h
/********************************************************************************************************************** * Function Name: R_SCI1_SendStart * Description : This function start transmission of SCI * Arguments : None * Return Value : MD_STATUS * MD_OK *********************************************************************************************************************/ MD_STATUS R_SCI1_SendStart (void); /********************************************************************************************************************** * Function Name: R_SCI1_SendStop * Description : This function stop transmission of SCI * Arguments : None * Return Value : MD_STATUS * MD_OK *********************************************************************************************************************/ MD_STATUS R_SCI1_SendStop (void); /********************************************************************************************************************** * Function Name: R_SCI1_ReceiveStart * Description : This function starts receiving of SCI1. * Arguments : None * Return Value : MD_STATUS * MD_OK *********************************************************************************************************************/ MD_STATUS R_SCI1_ReceiveStart (void); /********************************************************************************************************************** * Function Name: R_SCI1_IsTransferEnd * Description : This function returns the transfer status * Arguments : None * Return Value : bool * 0:Transferring, 1:Transfer end *********************************************************************************************************************/ bool R_SCI1_IsTransferEnd (void);
And Config_SCI1.c
/********************************************************************************************************************** * Function Name: R_SCI1_IsTransferEnd * Description : This function returns the transfer status of SCI1. * Arguments : None * Return Value : bool * false:Transferring, true:Transfer end *********************************************************************************************************************/ bool R_SCI1_IsTransferEnd (void) { /** true:Transmit is end, false:Transmitting */ return (bool) ((1U == SCI1.SSR.BIT.TEND) ? true : false); } /********************************************************************************************************************** End of function R_SCI1_IsTransferEnd() *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_SCI1_SendStart * Description : This function start transmission of SCI1 * Arguments : None * Return Value : MD_STATUS * MD_OK *********************************************************************************************************************/ MD_STATUS R_SCI1_SendStart (void) { /** Disable SCI interrupt request */ SCI1.SCR.BIT.TIE = 0U; /** Disable serial transmit */ SCI1.SCR.BIT.TE = 0U; /** Set TXD1 pin */ PORT2.PMR.BYTE |= 0x40U; /** Clear interrupt flag */ IR(SCI1,TXI1)= 0U; /** Enable SCI interrupt */ IEN(SCI1,TXI1)= 1U; /** Enable SCI interrupt request */ SCI1.SCR.BIT.TIE = 1U; /** Enable serial transmit */ SCI1.SCR.BIT.TE = 1U; return MD_OK; } /********************************************************************************************************************** End of function R_SCI1_SendStart() *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_SCI1_SendStop * Description : This function stop transmission of SCI1 * Arguments : None * Return Value : MD_STATUS * MD_OK *********************************************************************************************************************/ MD_STATUS R_SCI1_SendStop (void) { /** Set TXD1 pin */ PORT2.PMR.BYTE &= 0xBFU; /** Disable serial transmit */ SCI1.SCR.BIT.TE = 0U; /** Disable SCI interrupt request */ SCI1.SCR.BIT.TIE = 0U; /** Disable SCI interrupt */ IEN(SCI1,TXI1)= 0U; /** Clear interrupt flag */ IR(SCI1,TXI1)= 0U; return MD_OK; } /********************************************************************************************************************** End of function R_SCI1_SendStop() *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_SCI1_ReceiveStart * Description : This function starts receiving of SCI1. * Arguments : None * Return Value : MD_STATUS * MD_OK *********************************************************************************************************************/ MD_STATUS R_SCI1_ReceiveStart (void) { /** Clear interrupt flag */ IR(SCI1,RXI1)= 0U; /** Enable SCI interrupt */ IEN(SCI1,RXI1) = 1U; /** Enable SCI interrupt request */ SCI1.SCR.BIT.RIE = 1U; /** Enable serial receive */ SCI1.SCR.BIT.RE = 1U; return MD_OK; }
Day 5: understand the magic DMA - SCI addresses
Today I don't make changes. I try to understand why we have to enter these two magic addressesin the DMA configuration:
From the loader script, we see that this area is called ioRegister
In the RX23E-A Group User's Manual, there's more info that leads us to the answer:
The area is called Peripheral I/O registers. Documentation says that our particular addresses are in the peripheral bis 1: for used by DMAC. And we are configuring DMACs.
...
...
When you follow the path, you see that these 2 addresses are used for SCI1 transmit and receive:
and that makes sense. Those are the two functions we are going to support with the two DMACs.
It doesn't stop here. Read the Day 8 section for another memory related topic, where we'll have to adapt the linker script.
Day 6: configure the ADCs and Analogue Front-end
The ADC and Analog Front End can be added from the board file.
We 'll need to configure channel 0 of both ADCs.
ADC0 for Thermocouple measurements
Config_DSDA0.c
/********************************************************************************************************************** * Function Name: R_DSAD0_IsConversionEnd * Description : This function returns the Conversion status of DSAD0. * Arguments : None * Return Value : bool * false:Conversion, true:Conversion end *********************************************************************************************************************/ bool R_DSAD0_IsConversionEnd (void) { return (bool) ((1U == IR(DSAD0, ADI0)) ? true : false); } /********************************************************************************************************************** End of function R_DSAD0_IsConversionEnd *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_DSAD0_ClearIrFlag * Description : This function clears the IR flag * Arguments : None * Return Value : None *********************************************************************************************************************/ void R_DSAD0_ClearIrFlag (void) { IR(DSAD0, ADI0)= 0U; } /********************************************************************************************************************** End of function R_DSAD0_ClearIrFlag
Config_DSDA0.h
/* Function Name: R_DSAD0_IsConversionEnd * Description : This function returns the Conversion status of DSAD0. * Arguments : None * Return Value : bool * false:Conversion, true:Conversion end *********************************************************************************************************************/ bool R_DSAD0_IsConversionEnd (void); /********************************************************************************************************************** * Function Name: R_DSAD0_ClearIrFlag * Description : This function clears the IR flag * Arguments : None * Return Value : None *********************************************************************************************************************/ void R_DSAD0_ClearIrFlag (void);
ADC1 for RDT compensation measurements
Config_DSDA1.c
/********************************************************************************************************************** * Function Name: R_DSAD1_IsConversionEnd * Description : This function returns the Conversion status of DSAD1. * Arguments : None * Return Value : bool * false:Conversion, true:Conversion end *********************************************************************************************************************/ bool R_DSAD1_IsConversionEnd (void) { return (bool) ((1U == IR(DSAD1, ADI1)) ? true : false); } /********************************************************************************************************************** End of function R_DSAD1_IsConversionEnd *********************************************************************************************************************/ /********************************************************************************************************************** * Function Name: R_DSAD1_ClearIrFlag * Description : This function clears the IR flag * Arguments : None * Return Value : None *********************************************************************************************************************/ void R_DSAD1_ClearIrFlag (void) { IR(DSAD1, ADI1)= 0U; } /********************************************************************************************************************** End of function R_DSAD1_ClearIrFlag
Config_DSDA1.h
/********************************************************************************************************************** * Function Name: R_DSAD1_IsConversionEnd * Description : This function returns the Conversion status of DSAD1. * Arguments : None * Return Value : bool * false:Conversion, true:Conversion end *********************************************************************************************************************/ bool R_DSAD1_IsConversionEnd (void); /********************************************************************************************************************** * Function Name: R_DSAD1_ClearIrFlag * Description : This function clears the IR flag * Arguments : None * Return Value : None *********************************************************************************************************************/ void R_DSAD1_ClearIrFlag (void);
Analogue front end
This doesn't need source updates
Day 7: housekeeping
Housekeeping 1: modules that Renesas uses in several examples:
Renesas uses a set of modules in several of their example. They are source code and I just need to copy them over from the appnote example:
Housekeeping 2: those modules use a Renesas toolchain specific header
When you add them, you will get a compile error, because they rely on a CCRX toolchain specific include: machine.h:
Let's roll our own, then see what definitions of that header file are used.
Because the include is between angle brackets <>, the location needs to be in the include path.
Let's create a source folder port, under src. And create a header file machine.h underneath it.
Then add that location to the includes:
When I compile it, the error "machine.h not found" is gone, but it says that the function nop() isn't known. Doh! all this for the nop.
If I add a declaration in my own machine.h, compilation is successful:
/* * machine.h * * Created on: 26 jun. 2023 * Author: jancu */ #ifndef MACHINE_H_ #define MACHINE_H_ #define nop() asm("nop":) #endif /* MACHINE_H_ */
Housekeeping 3: configure compile defines
The code does some conditional compilation, depending on D_PRV_PC_TOOL_USE. Let's set it to 1, like the origonal project.
Day 8: DMA buffers, and glue all together in main()
We'll have to revisit the GCC linker script and memory lay-out.
The DMA section below isn't obvious. It took me a week to find out why the GCC version did not want to connect to Renesas' PC Tool. Turned out that it's related to the memory alignment of DMA the buffers. |
Adapt the loaders / linker script for DMA buffer alignment
To make DMA FIT modules work, buffer memory has to be at aligned memory spaces. I haven't found the correct allignment yet, but the Renesas toolchain puts these buffers at locations that are 0x1000 aligned. I adapted the GCC linker script to reflect a similar situation. I created 2 fixed position sections, both placed in the RAM memory region.
MEMORY { RAM : ORIGIN = 0x4, LENGTH = 0x7ffc ROM : ORIGIN = 0xFFFC0000, LENGTH = 262144 OFS : ORIGIN = 0xFFFFFF80, LENGTH = 16 } SECTIONS { # ---- .dma1 0x6000 (NOLOAD) : AT(0x6000) { KEEP(*(.dma1)) } >RAM .dma2 0x7000 (NOLOAD) : AT(0x7000) { KEEP(*(.dma2)) } >RAM }
The buffers are 4 KB (write) and 0.5 KB (read). I placed each in a section starting at a 0x1000 address. The NOLOAD attribute takes care that the Loader / Flash Programmer doesn't try to write to these addresses while programming the device.
Adapt the main file to take care that the buffers are allocated to the DMA memory
I tried to avoid changing Renesas' code as much as possible. This is the only occurrence where I can't do that. Assigning variables to memory sections isn't the same in the CC-RX and GCC toolchains. Luckily, there's a set of defines that 'll help us. __CCRX__ is only defined when the project is configured for CC-RX. __GNUC__ is defined when the GCC toolchain is selected. Let's exploit that in the code to use the toolchain specific methods to place the variables at a good location. The code within the __CCRX__ area is the original code you get when downloading Renesas' example project. The code in the __GNUC__ is my port.
#if defined(__CCRX__)
#pragma section B B_DMAC_REPEAT_AREA /** Address alignment for DMAC repeat area */
static uint8_t s_send_ary[D_PRV_SEND_ARY_LEN]; /** DMAC3 transfer source */
static uint8_t s_recv_ary[D_PRV_RECV_ARY_LEN]; /** DMAC0 transfer destination */
#pragma section
#endif
#if defined(__GNUC__)
static uint8_t s_send_ary[D_PRV_SEND_ARY_LEN] __attribute__ ((section (".dma1"))); /** DMAC3 transfer source */
static uint8_t s_recv_ary[D_PRV_RECV_ARY_LEN] __attribute__ ((section (".dma2"))); /** DMAC0 transfer destination */
#endif
The allocation is correct. Here's a view of Renesas' memory use view:
.dma1 is at address 0x6000 and contains the 4 KB buffer.
.dma2 is at address 0x7000, contains the 512 bytes buffer.
I know it doesn't look spectacular, but it took me a week to find out that this was the cause for the PC Tool application giving a "Failed to Connect" error.
This is the GCC project, including binaries you can load with Renesas Flash software: rx23ea_thermocouple_gcc20230717_2.zip