Hot!Combining USART and SPI togetther on PIC18LF45K22 micrcontroller

Page: < 12 Showing page 2 of 2
Author
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/11 15:25:20 (permalink)
0
/*
 * File: main.c
 * Author: VTrivedi
 *
 * Created on November 17, 2017, 1:13 PM
 */


#include <xc.h>
#include <string.h>
#include "mcc_generated_files/mcc.h"

extern bool _send_data;
extern uint8_t myReceiveBuffer[240];
uint8_t myTransmitBuffer[240];
uint8_t uart_count = 0;
uint8_t count = 0;
void USART_putc(unsigned char c); // byte by byte USART write function
void USART_puts(unsigned char *s); // string of bytes USART write function

void USART_putc(unsigned char c)
{
    while(!TXSTA2bits.TRMT2); // wait until transmit shift register is empty
    TXREG2 = c; // write character to TXREG and start transmission
}

void USART_puts(unsigned char *s)
{
    while (*s)
    {
        USART_putc(*s); // send character pointed to by s
        s++; // increase pointer location to the next character
    }
}
void main(void)
{
    uint8_t data = 0x55;
    // Initialize the device
    SYSTEM_Initialize();

    SPI0_IN_EN = DISABLE; // Disable the MSSI Interrupt
    SPI0_IN_FLAG = DISABLE; // Clear the MSSI Interrupt flag
    // Enable high priority global interrupts
    INTERRUPT_GlobalInterruptHighEnable();

    // Enable low priority global interrupts.
    INTERRUPT_GlobalInterruptLowEnable();

    
    USART_puts("Hi This is Test Program\r\n");
    SSP1BUF = 0xFF;
    for(uart_count=0;uart_count<240;uart_count++)
    {
        data = (data) ^ (0xFF);
        myTransmitBuffer[uart_count] = data;
    }
    count = 0;
    Board_TXRX_DONE_SetLow();
    
    SPI0_IN_EN = ENABLE; // Enable the MSSI Interrupt

 while(1)
 {
       /*if(EUSART2_Read())
       {
           USART_puts("Hi We got something from PC\r\n");
       }*/
       if(_send_data)
       {
           _send_data = false;
            count = 0;
            for(int i=0;i<240;i++)
            {
                USART_putc(myReceiveBuffer[i]);
            }
            Board_TXRX_DONE_SetLow();
        }
 }
}


 
/**
  MSSP1 Generated Driver File

  @Company
    Microchip Technology Inc.

  @File Name
    spi1.c

  @Summary
    This is the generated driver implementation file for the MSSP1 driver using PIC10 / PIC12 / PIC16 / PIC18 MCUs

  @Description
    This source file provides APIs for MSSP1.
    Generation Information :
        Product Revision : PIC10 / PIC12 / PIC16 / PIC18 MCUs - 1.45
        Device : PIC18LF45K22
        Driver Version : 2.00
    The generated drivers are tested against the following:
        Compiler : XC8 1.35
        MPLAB : MPLAB X 3.40
*/

/*
    (c) 2016 Microchip Technology Inc. and its subsidiaries. You may use this
    software and any derivatives exclusively with Microchip products.

    THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
    EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
    WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
    PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION
    WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION.

    IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
    INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
    WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
    BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE
    FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN
    ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
    THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.

    MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE
    TERMS.
*/

/**
  Section: Included Files
*/

#include <xc.h>
#include "pin_manager.h"
#include "mcc.h"
#include "spi1.h"

/**
  Section: Macro Declarations
*/

#define SPI_RX_IN_PROGRESS 0x0
#define DATA_SPI 240

volatile unsigned char r; // immediate received byte from ISR

uint8_t myReceiveBuffer[240];
extern uint8_t myTransmitBuffer[240];
extern uint8_t count;
bool _send_data = false;
/**
  Section: Module APIs
*/

void SPI1_Initialize(void)
{
    // Set the SPI1 module to the options selected in the User Interface

    // SMP High Speed; CKE Idle to Active;
    SSP1STAT = 0x00;

    // SSPEN enabled; WCOL no_collision; CKP Idle:High, Active:Low; SSPM SCKx_nSSxenabled; SSPOV no_overflow;
    SSP1CON1 = 0x24;

    // SBCDE disabled; BOEN disabled; SCIE disabled; PCIE disabled; DHEN disabled; SDAHT 100ns; AHEN disabled;
    SSP1CON3 = 0x00;
}


void SPI_Write(unsigned char data)
{
  SSP1BUF = data;
}

unsigned char SPI_Read(void)
{
  // Wait for Data Transmit/Receipt complete
  while(!SSP1STATbits.BF);

  return(SSP1BUF);
}

unsigned spiDataReady() //Check whether the data is ready to read
{
    if(SSP1STATbits.BF)
        return 1;
    else
        return 0;
}

void SPI1_ISR(void)
{
// PIR1bits.SSP1IF = 0; // clear interrupt flag
    if(1 == SSP1CON1bits.SSPOV)
    {
        myReceiveBuffer[count++] = SSP1BUF;
        SSP1CON1bits.SSPOV = 0;
    }
    else if(SPI1_HasWriteCollisionOccured())
    {
        SPI1_ClearWriteCollisionStatus();
    }
    else
    {
        GREEN_LED_Toggle();
        r = SPI_Read();
        Board_TXRX_DONE_SetHigh();
        myReceiveBuffer[count++] = r;
        SSP1CON1bits.SSPOV = 0;
        SSP1BUF = myTransmitBuffer[count];
        if(count == 240)
        {
            _send_data = true;
            LED_RED_Toggle();
        }
        else
        {
            //if(!_send_data)
            Board_TXRX_DONE_SetLow();
        }
    }
    PIR1bits.SSP1IF = 0; // clear interrupt flag
}

/**
 End of File
*/
Hi Team,
I tried the suggestions provided by fellow members of forum.
However, now I after modifying the code the issue I am facing is that every alternate byte sent by Master is missed.
When I save the byte into the buffer I see that communication is continuous but every alternate bytes get missed and I believe that is due to the SSP1CON1bits.SSPOV flag getting set.
Can't understand why this flag is getting set as I have also used one of the GPIO line between Master and Slave that will indicate the Master that Slave is busy copying the data into the buffer and loading the SSP1BUF with new value.
Not sure how to attached so have added the source code for main.c and spi.c file.
 
The concept I have tried works well without interrupt but with interrupt it gives above mentioned problems.
Would appreciate if you could point me to the right direction.
#21
Gort2015
Klaatu Barada Nikto
  • Total Posts : 1549
  • Reward points : 0
  • Joined: 2015/04/30 10:49:57
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/11 16:14:43 (permalink)
0
Modify stdout or weak "write" and all text going to uart will go to lcd including printf.
 
void SPI_Write(unsigned char data){
SSP1BUF = data;
}

Give and take.
The Microchip function SPI_Write is a generated template.  You have to fill the rest in.
 
post edited by Gort2015 - 2017/12/11 16:22:12

MPLab X playing up, bug in your code? Nevermind, Star Trek:Discovery will be with us soon.
https://www.youtube.com/watch?v=Iu1qa8N2ID0
+ ST:Continues, "What Ships are Made for", Q's back.
#22
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/12 07:56:30 (permalink)
0
Gort2015
Modify stdout or weak "write" and all text going to uart will go to lcd including printf.
 
But I am not sending any data to lcd infact there is no function for lcd?
void SPI_Write(unsigned char data){
 
SSP1BUF = data;
}

Give and take.
The Microchip function SPI_Write is a generated template.  You have to fill the rest in.
 I am not using the SPI_Write() function too in the ISR. If you see in order to get the next byte ready in the SSP1BUF for slave, I am simply copying the next value from Tx buffer to SSP1BUF.
And as per the protocol whenever, Master sends any data at the same time it will get the data from the slave what is loaded in the SSP1BUF.




#23
Gort2015
Klaatu Barada Nikto
  • Total Posts : 1549
  • Reward points : 0
  • Joined: 2015/04/30 10:49:57
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/12 10:51:07 (permalink)
0
USART_puts("Hi This is Test Program\r\n");
SSP1BUF = 0xFF;

You loaded the spi buffer but didn't wait for busy and read the spi buffer.

MPLab X playing up, bug in your code? Nevermind, Star Trek:Discovery will be with us soon.
https://www.youtube.com/watch?v=Iu1qa8N2ID0
+ ST:Continues, "What Ships are Made for", Q's back.
#24
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/12 11:24:39 (permalink)
0
I loaded the SPI buffer since that is required as per the datasheet information.
Since this is the Slave PIC it has to have something in the SSP1BUF loaded before Master begins transmission otherwise it will send any garbage value.
That's the reason I loaded the SSP1BUF.
 
And in the ISR I am waiting for the BF flag to get set post which I do the SPI_read() to get the value sent by Master SPI.
 
Is this not the way it is suppose to be done?
 
Vikram
#25
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/12 11:34:44 (permalink)
0
One more thing I found is that if I execute the firmware in debug mode i.e keeping both the PIC18 (Slave) and the MSP432(Master) then I get the data is proper sequence but if I execute in the run mode it misses each alternative data byte.
I have provided enough delay at the Master SPI firmware and also have used the GPIO for handshake that informs Master when to send the next data byte but still every alternate data byte is skipped.
 
Vikram
#26
Aussie Susan
Super Member
  • Total Posts : 2922
  • Reward points : 0
  • Joined: 2008/08/18 22:20:40
  • Location: Melbourne, Australia
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/12 18:44:37 (permalink)
0
In the ISR you do not need to check any flags as the ISR will not be called until after the exchange has completed. Therefore you can always just read from the buffer in the ISR.
I note that you have logic to check for when 'count' reaches 240 in the main part of the ISR but count is also incremented when the SSPOV bit is found to be set but there are no similar checks in that code branch. Probably not related to your issue but this is a good way to have exact match conditionals fail.
Also I can't see where you do the handshaking from the slave to tell the master when to send next. If that is something to do with the 'Board_TXRX_SetHigh()' function call then it is not protecting the code branches that handle errors. (Also what is the 'SPI1_HasWriteCollisionOccurred() function and is it causing your problem in some way?)
Susan
#27
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/13 08:14:01 (permalink)
0
Thank you Susan for the reply.
I have added the flag checking concept in the ISR in order to know due to which reason the interrupt has generated.
You are right in that the count does not need to be incremented when SSPOV flag is set since when SSPOV is set the data in the SSPSR is not updated as per the data sheet. I will change that.
The handshaking is done through the Board_TXRX_SetHigh() function.
I did not understand what you meant by 
Aussie Susan
If that is something to do with the 'Board_TXRX_SetHigh()' function call then it is not protecting the code branches that handle errors. 

protecting code branches that handle errors.
SPI_HasWriteCollisionOccured() function just checks whether the WCOL flag is set or not. If so it clears it.
 
Vikram
#28
Aussie Susan
Super Member
  • Total Posts : 2922
  • Reward points : 0
  • Joined: 2008/08/18 22:20:40
  • Location: Melbourne, Australia
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/13 19:41:24 (permalink)
4 (1)
What I mean by that the overall logic flow through the ISR might no be correct.
The main function sets the line low and so the line will be low when the ISR is called.
If an overflow is detected then the line is left in the low state which I think means that the master can then continue to send the next character straight away.
If no overflow is detected but a write-collision is, then you have the same situation.
Therefore if something goes wrong, then it is quite possible that it will continue to go wrong because the master thinks that all is well.
As for the action of any function, unless you show it to us we have no knowledge of what it does and the side effects it can create.
By the way, in the ISR, you write to the SPI buffer after the 'count' variable has been incremented and before you test for it equaling 240. When the increment does make 'count' 240, you still use 'count' to index the 'myTransmitBuffer' array which will be 1 beyond the end of that array.
Also, as 'r' is only used within the ISR, it does not need to be made volatile. However the '_send_data' value is set within the ISR and read in the main loop and therefore should be declared volatile.
However I don't think that this is the real cause of the problem. Personally I don't like calling functions in an ISR (especially when all they seem to do is a line or two of code that could be executed in line) but I suspect that the ISR is reacting very quickly on the end of the exchange unless you have a very high SPI clock from the master and a very slow slave system clock. If you are missing characters then something else must be causing this to occur. I'm a little suspicious of your 'SPI_Read()' function which checks the BF bit but there should not be an issue there as long as none of the other functions called before that call could impact on the BF bit. (I'm thinking that some other function waits for the BF bit to be high then also reads the SPI buffer. This would cause the 'SPI_Read' function to wait until the *next* character was received before returning (all of this *before* you raise the line to the master telling it to stop) which would result in reading every 2nd character.
Susan
#29
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/14 07:23:13 (permalink)
0
Thank you Susan for making me understand in detail.
I removed the checking of BF flag within the ISR as per your earlier post and now I can see all the data coming in sequence.
However, I had to introduce a delay in the master side code (not in ISR) before I am sending next data byte. The delay is added after checking whether the handshaking line has gone low or not.
So I have one more question. Although the SPI clock rate on master is 1MHz and clock of slave is controlled by master will it impact if the system clock of both master and slave are different or they need to be same?
 
Vikram
#30
qɥb
Monolothic Member
  • Total Posts : 170
  • Reward points : 0
  • Joined: 2017/09/09 05:07:30
  • Location: Jupiter
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/14 12:10:16 (permalink)
0
VikramTrivedi
So I have one more question. Although the SPI clock rate on master is 1MHz and clock of slave is controlled by master will it impact if the system clock of both master and slave are different or they need to be same?

The system clocks do NOT matter, so long as the Slave is able to run fast enough to read/write SSPBUF before the next transfer from the Master occurs.
 
#31
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/14 12:15:02 (permalink)
0
Thank you for the verification.
But when you Slave is able to run fast enough does it not refer to again the system clock at which Slave is running?
Or is there any another way to make the Slave run fast enough than master transfers?
 
Vikram
#32
qɥb
Monolothic Member
  • Total Posts : 170
  • Reward points : 0
  • Joined: 2017/09/09 05:07:30
  • Location: Jupiter
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/14 12:20:24 (permalink)
0
That comes down to how many instructions the Master executes between each transfer (including any delay you may add), or if you have some other handshaking mechanism.
 
#33
Aussie Susan
Super Member
  • Total Posts : 2922
  • Reward points : 0
  • Joined: 2008/08/18 22:20:40
  • Location: Melbourne, Australia
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/14 17:54:54 (permalink)
4 (1)
Keep in mind that there are two separate clocks here.
The SCK line for the SPI exchange is driven by the master and basically tells the slave when each of the 8 bits is ready. Therefore it drives the speed of the exchange.
The other clock is the system clock (for both the master and the slave but we are really only concerned with the slave system clock here). The system clock determines how fast the slave's instructions are performed. AS the slave is a PIC18 device, there are 4 system clock cycles per instruction.
When the SPI exchange completes and the IF bit is set, the slave needs to complete any current instruction, respond to the interrupt, save any registers that are used in the ISR, execute your code up to the point where the SSPBUF is read and a new value made ready for the next exchange, and then complete your ISR code, restore the saved registers and return to the next instruction from before the interrupt.
To get an idea of how long all of this will take, have a look at the assembler code that the compiler generates for your ISR and add a couple of 'instructions' (for the interrupt trigger and return). Multiply by 4 and that will tell you how many system clock cycles will be required. Take whatever system clock your slave uses and work out how long all of that takes. 
Of course the exchange can start anytime after you have written to the SSPBUF but the above should give you some idea of how long the slave will take to process for each character.
The other thing to consider is how much gap the master needs after completing one exchange before it can start another. If this is too short, then the master will think the slave is ready for the next exchange while it is still processing the last one. 
Just thinking about this: does the master use the fact that the GPIO line is low to know that it can send a character, or the fact that there is a 'high to low' transition on the line? If the former, then you need to get that line high as quickly as you can after the exchange completes (or at least before the master looks at it again). If it is the 'edge' trigger then you have a lot more time for the slave to process the character.
Susan
#34
VikramTrivedi
New Member
  • Total Posts : 15
  • Reward points : 0
  • Joined: 2017/11/30 09:20:39
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/15 10:16:18 (permalink)
0
Thanks a lot Susan this is the explanation I was looking for. Much appreciated.
I believe the same hold true for any controller in terms of finding how my cycles are consumed per instruction. Os my understanding correct?
 
In regards to the GPIO line yes the master checks the level of the GPIO line to see if the slave is ready for next byte of data or not.
So earlier when I had not inserted any delay at the Master side even before the line use to go high after the exchange completed, the master would have already come to the step ready to send next data byte.
Only when I used to execute in the debug mode it used to get more time before getting the next data transfer.
I will check if it is edge triggered or level triggered.
#35
Gort2015
Klaatu Barada Nikto
  • Total Posts : 1549
  • Reward points : 0
  • Joined: 2015/04/30 10:49:57
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/15 10:45:33 (permalink)
0
You don't need to include another i/o line.
Time is irelavent, the master clock controls the slave.
You can take as long as you like.  If M is busy, S waits.  If S is busy, M waits.
 
The Slave acts on commands that you send it.  You are bypassing that protocol.
 
//send a command to read 4 bytes starting at 0.
 
do
 
    spi_write(READ_ADDR,0,4);
 
while (spi_busy());
 
*data++=spi_read();
 
*data++=spi_read();
 
*data++=spi_read();
 
*data++=spi_read();

post edited by Gort2015 - 2017/12/15 10:48:54

MPLab X playing up, bug in your code? Nevermind, Star Trek:Discovery will be with us soon.
https://www.youtube.com/watch?v=Iu1qa8N2ID0
+ ST:Continues, "What Ships are Made for", Q's back.
#36
Jerry Messina
Super Member
  • Total Posts : 251
  • Reward points : 0
  • Joined: 2003/11/07 12:35:12
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/15 11:26:50 (permalink)
0
Additionally I also tried providing a GPIO line between master and slave that is output on slave side and input on master side. So once the character is received I pull the line high so as to inform master not to send the next data buffer and only once the data is transfered to USART I pull the line low again. This even did not work.

I typically do handshaking like this with hardware using a CCP module. Use the CCP output as the slave "GPIO" handshake signal, and connect the SPI CLK to the CCP TMRIN setup to external clock mode.

Set the CCP module to compare mode and set it to count one event and assert the CCPx output. That way as soon as you get an SPI clock the handshake line asserts automatically for you (signalling not ready).

Once the slave reads the SPI byte from the master it can reset the CCP output and the master knows it can continue.


#37
Gort2015
Klaatu Barada Nikto
  • Total Posts : 1549
  • Reward points : 0
  • Joined: 2015/04/30 10:49:57
  • Location: 0
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/15 15:51:22 (permalink)
0
Why have an addional line when you have chip select.
The slave is a listener.
 
Only the master initiates the exchange, that is why time does not matter.  If slave has data it puts it in the buffer.  If the slave buffer is full then an overflow error happens because the data has not been exchanged by the master clock.  The M clock controls everything.  Slow clock down then put it on your scope, you'll see 8 clocks if 8bit and the sdo/sdi bits.
The data is set half a clock pulse before the clock to ensure they don't happen at the same time.  Slave checks busy before loading buffer.
 
You are Doctor Who when you are the Master.
(crap spi joke, ctrl time)

MPLab X playing up, bug in your code? Nevermind, Star Trek:Discovery will be with us soon.
https://www.youtube.com/watch?v=Iu1qa8N2ID0
+ ST:Continues, "What Ships are Made for", Q's back.
#38
Jerry Messina
Super Member
  • Total Posts : 251
  • Reward points : 0
  • Joined: 2003/11/07 12:35:12
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/16 04:37:19 (permalink)
4 (1)
Only the master initiates the exchange, that is why time does not matter

That's exactly why time DOES matter, and why doing an spi slave is more difficult than it first appears.
 
If the slave can't keep up with the master (a very likely scenario) then things go south fast.
 
#39
qɥb
Monolothic Member
  • Total Posts : 170
  • Reward points : 0
  • Joined: 2017/09/09 05:07:30
  • Location: Jupiter
  • Status: offline
Re: Combining USART and SPI togetther on PIC18LF45K22 micrcontroller 2017/12/16 05:01:46 (permalink)
0
Gort2015
If M is busy, S waits.

Agree

  If S is busy, M waits.

Disagree.
Without some other form of handshaking, the Master does not know the slave is busy.
 
#40
Page: < 12 Showing page 2 of 2
Jump to:
© 2017 APG vNext Commercial Version 4.5