- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I have an issue regarding a custom board the GPIOs where all the HPS IO’s are set to high impedance no matter to how they are set.
I have tested the following:- GPIO system on linux enabling each of the GPIOs as an output through the controller... and writing values to them... (findings it changes to output state but the value doesn’t alter and reads back high - unchanged)
- GPIO controller address read/write (findings values don’t change)
- Pre-Linux Hardware GPIO Loaning Pins driving a value change on preloader startup to enable a LED (findings no value on GPIO changes value)
Link Copied
31 Replies
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Kyle,
even I was trying to access GPIO using DEO nano but faced issues...did you get any answer??..should we change the high impedance state in Linux boot if so how?? --Ravi- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Ravi,
I've Fixed this issue for both the GPIO and LOAN IO. GPIO Just make sure when you enable the GPIO's in the peripheral pins in Qsys... that you update the Assignment editor page to include your linked pin eg. hps_io_hps_io_gpio_inst_GPIO49 => HPS_GPIO49 -- loans out to the Top level Port HPS_GPIO49 so add this to Assigments > Assignment editor To [HPS_GPIO49] Assignment Name [I/O Standard] Value [Voltage value of output] Recompile the project and the GPIO should be available. LOAN pins are different you need make sure from your Qsys design you export the loanio pins back into your top level design. hps_0_h2f_loan_io_in : out std_logic_vector(66 downto 0); hps_0_h2f_loan_io_out : in std_logic_vector(66 downto 0) := (others => '0'); hps_0_h2f_loan_io_oe : in std_logic_vector(66 downto 0) := (others => '0'); the inputs are assigned to the in... and the outputs to outputs... (when they are output make the oe vector for that pin 1 to enable it as an output) This is pretty much it... happy to help the documentation is brief but pretty much it works well once you do this. Good Luck Kyle- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
" Hi kyle,
when you go to Assignment Editor there are 2 rows you see for each pins GPIO_1[0] Location( This is changed to HPS_IO) and select Enable(Yes) GPIO_1[1] I/O standard 3.3 V LVTTL Once I did this and compiled and flashed the .SOF then Pin 2 of GPIO_1 cannot be accessed. I have a C file which changes the Set bits and Clr bits..But I am not able to take control of the GPIO_1 pin.Is there any jumper which we need to change.also are my configurations correct. Once I get one pin I can map for others. Kindly let me know." regards Ravi- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Kyle,
Thanks for your support...also would like to know if there are any configurations in the Assignments->Device->Deviceandpinoptions->Unused pins (An input Tristated) to access the pin and see the output. I am using a C file which has hps.h through which I assign GPIO1_SWPORTA_DR_ADDR and configuring these registers to get the output. eg: # include <stdio.h> # include <unistd.h> # include <fcntl.h> # include <sys/mman.h> # include "hwlib.h" # include "soc_cv_av/socal/socal.h" # include "soc_cv_av/socal/hps.h" # include "soc_cv_av/socal/alt_gpio.h" # define HW_REGS_BASE ( ALT_STM_OFST ) # define HW_REGS_SPAN ( 0x04000000 ) # define HW_REGS_MASK ( HW_REGS_SPAN - 1 ) # define USER_IO_DIR (0x01000008) # define BIT_LED (0x01000008) # define BUTTON_MASK (0x02000000) int main(int argc, char **argv) { void *virtual_base; int fd; uint32_t scan_input; int i; // map the address space for the LED registers into user space so we can interact with them. // we'll actually map in the entire CSR span of the HPS since we want to access various registers within that span if( ( fd = open( "/dev/mem", ( O_RDWR | O_SYNC ) ) ) == -1 ) { printf( "ERROR: could not open \"/dev/mem\"...\n" ); return( 1 ); } virtual_base = mmap( NULL, HW_REGS_SPAN, ( PROT_READ | PROT_WRITE ), MAP_SHARED, fd, HW_REGS_BASE ); if( virtual_base == MAP_FAILED ) { printf( "ERROR: mmap() failed...\n" ); close( fd ); return( 1 ); } // initialize the pio controller // led: set the direction of the HPS GPIO1 bits attached to LEDs to output alt_clrbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED ); alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR) & ( uint32_t )( HW_REGS_MASK ) ) ), USER_IO_DIR ); so this should work..Let me know get some idea --Ravi- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Ravi,
The Assignment editor needs to be set to Assignment Name [I/O Standard] this is a must is shouldn't be a "location" on the FPGA as there is no location it goes through into the preloader and is linked there. The Value is instead set to the Voltage level [3.3-V LVCMOS] your maybe less depending on the hps powered IO's it could be 2.5V or lower please check this. Enabled is yes too so that would be correct. Please try the linux commands from the HPS command line first (before memory mapping) to make sure that the GPIO's can be set... follow this tutorial for setting the GPIO's If there are LED's then you should be just write a active low or high signal to them from the command line.... Follow this tutorial and see if the LED's light on the gpio then you can start to progress into the memory mapping application code.. http://falsinsoft.blogspot.co.uk/2012/11/access-gpio-from-linux-user-space.html It looks like your code is correct but you need to just make sure that the GPIO is controllable from the drivers in linux the following echo commands will help you enable an GPIO output (number is significant please find out the number of the GPIO from linux and cyclone V handbook for specifying the correct GPIO) here 192 is used as this is from the GPIO controller 2 and is the 1st one your number will be different please check for your correct one. echo 192 > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio192/direction echo 1 > /sys/class/gpio/gpio192/value echo 0 > /sys/class/gpio/gpio192/value The first echo line enables the gpio.... if you get problems here the gpio is already attached to a linux driver and will need removing (look into removing a driver) The second enables that channel to become an output as all gpio's are either in or output the default is always input so this enables you send the gpio as high or low...etc The next 2 lines will write a high or low value to that gpio you should see the light move on and off if it is correctly enabled. Best of luck Kyle Without finding problems you never learn.- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle,
Not able to get the GPIO toggle working for GPIO_1 any suggestions from your side where in assignment editor changes need to be made so that we can toggle the switch??.will really help if you reply.waiting for your response regards Ravi chandran- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle,
thanks for your reply.I tried the way you told using the Linux but this did not work.The GPIO was not accessible.so from linux itself I am not able to control.if we solve this then user space code will work. My Assignment editor looks like this the 2 rows for each pin original To:GPIO_1[1] Assignment name-> Location PIN_Name(value) Enabled(yes) GPIO_1[1] Assignment name-> I/O standard 3.3V LVTTL(value) Enabled(yes) My changed version To:GPIO_1[1] Assignment name-> HPS_IO ON(value) Enabled(yes) GPIO_1[1] Assignment name-> I/O standard 3.3V LVTTL(value) Enabled(yes) Is this correct ??.. Like this I need to enable for all other pins.Is there any thing I missed assigning here???..Let me know.Any configuration changes. In the device option also I have chooseb UNUSED pin as INPUT state TRIGERRED. created the .SOF and programmed and ran the linux command but did not work..Kindly let me know if you see any errors here thanks in advance --Ravi --- Quote Start --- Ravi, The Assignment editor needs to be set to Assignment Name [I/O Standard] this is a must is shouldn't be a "location" on the FPGA as there is no location it goes through into the preloader and is linked there. The Value is instead set to the Voltage level [3.3-V LVCMOS] your maybe less depending on the hps powered IO's it could be 2.5V or lower please check this. Enabled is yes too so that would be correct. Please try the linux commands from the HPS command line first (before memory mapping) to make sure that the GPIO's can be set... follow this tutorial for setting the GPIO's If there are LED's then you should be just write a active low or high signal to them from the command line.... Follow this tutorial and see if the LED's light on the gpio then you can start to progress into the memory mapping application code.. http://falsinsoft.blogspot.co.uk/2012/11/access-gpio-from-linux-user-space.html It looks like your code is correct but you need to just make sure that the GPIO is controllable from the drivers in linux the following echo commands will help you enable an GPIO output (number is significant please find out the number of the GPIO from linux and cyclone V handbook for specifying the correct GPIO) here 192 is used as this is from the GPIO controller 2 and is the 1st one your number will be different please check for your correct one. echo 192 > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio192/direction echo 1 > /sys/class/gpio/gpio192/value echo 0 > /sys/class/gpio/gpio192/value The first echo line enables the gpio.... if you get problems here the gpio is already attached to a linux driver and will need removing (look into removing a driver) The second enables that channel to become an output as all gpio's are either in or output the default is always input so this enables you send the gpio as high or low...etc The next 2 lines will write a high or low value to that gpio you should see the light move on and off if it is correctly enabled. Best of luck Kyle Without finding problems you never learn. --- Quote End ---- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle,
thanks for your reply.I tried the way you told using the Linux but this did not work.The GPIO was not accessible.so from linux itself I am not able to control.if we solve this then user space code will work. My Assignment editor looks like this the 2 rows for each pin original To:GPIO_1[1] Assignment name-> Location PIN_Name(value) Enabled(yes) GPIO_1[1] Assignment name-> I/O standard 3.3V LVTTL(value) Enabled(yes) My changed version To:GPIO_1[1] Assignment name-> HPS_IO ON(value) Enabled(yes) GPIO_1[1] Assignment name-> I/O standard 3.3V LVTTL(value) Enabled(yes) Is this correct ??.. Like this I need to enable for all other pins.Is there any thing I missed assigning here???..Let me know.Any configuration changes. In the device option also I have chooseb UNUSED pin as INPUT state TRIGERRED. created the .SOF and programmed and ran the linux command but did not work..Kindly let me know if you see any errors here thanks in advance --Ravi- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hey Ravi,
Looks like you have too many assignments.... please remove all... For the HPS pin outs... from the GPIO you need to make sure they are selected in the peripheral pin outs as exported GPIO (which you look to have done with GPIO_1). This is then built into the top level file with your pin as GPIO_1: INOUT STD_LOGIC in the port declarations and then mapped to the port map for the qsys ip. (part of the tutorial). Now this map is to hps_io_hps_io_gpio_inst_GPIO1 => GPIO_1 in your case... so the system is linked to the top level pin (specified as INOUT as this is normal for a GPIO). Now in order for the preloader to know you want that top level pin [GPIO_1] enabled you need set it once in the assignment editor... GPIO_1 Assignment name-> I/O standard 3.3V LVTTL(value) Enabled(yes) <- this is the only one you need... i think you might of named it in a vector or something but just trial one first to make sure your doing it right. I cannot help you any more than this. If it still not works out it may still be a hardware failure but unlikely. Best of luck Kyle- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle ,
Really thankful to you.Just tell me where in the tool we can see the peripheral pin outs and top leve file as mentioned and then the mapping. If I get all three then I may get some hint......I cannot see the mapping if I get I might get some idea.. regards Ravi --- Quote Start --- Hey Ravi, Looks like you have too many assignments.... please remove all... For the HPS pin outs... from the GPIO you need to make sure they are selected in the peripheral pin outs as exported GPIO (which you look to have done with GPIO_1). This is then built into the top level file with your pin as GPIO_1: INOUT STD_LOGIC in the port declarations and then mapped to the port map for the qsys ip. (part of the tutorial). Now this map is to hps_io_hps_io_gpio_inst_GPIO1 => GPIO_1 in your case... so the system is linked to the top level pin (specified as INOUT as this is normal for a GPIO). Now in order for the preloader to know you want that top level pin [GPIO_1] enabled you need set it once in the assignment editor... GPIO_1 Assignment name-> I/O standard 3.3V LVTTL(value) Enabled(yes) <- this is the only one you need... i think you might of named it in a vector or something but just trial one first to make sure your doing it right. I cannot help you any more than this. If it still not works out it may still be a hardware failure but unlikely. Best of luck Kyle --- Quote End ---- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Ravi,
I am sorry you need to start with the basics... it involves creating a simple system with your cyclone v soc process (or other processor of your board) with Qsys within quartus, generate the design and create a top level design in either Verilog or VHDL hardware languages. You can then get to grips with it all. Please if you require further help follow the tutorials within this website. Although might not be available for your board you will be able to follow the process accordingly. https://rocketboards.org/ Best Regards Kyle- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle,
Thanks for your reply.I am just a starter in deo nano SOC board.My req is very simple I just wanted to access all the GPIO pins GPIO1 and GPIO0 .since I did it using the C code with HPS_LED and HPS_key I thought I just have to use the existing .SOF.But then since this did not work I generated own .SOF using system builder.But since this said no driver found I had to use qsys tool.This is where I got stuck.I just need a .sof where I can confiure the register and do the toggling and clearing.thats the requirement.so can I use the existing HPS_LED.sof and in that modify the .V file and add the .hps_0_hps_io_hps_io_gpio_inst_GPIO01 ( HPS_GPIO_1 ), // .hps_io_gpio_inst_GPIO01 will this eneble the GPIO1 and can I access the register from the linux user space.?? Need to generate .sof to create this Kindly let me know. Thanks and regards Ravi chandran- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Kyle,
For the exisiting .v files if I has .hps_io_....GPIO01 ==>GPIO_1 and then compile with qsys and then create .sof then I will be able to access the GPIO1 If I get an example file which has this I can add the same later I can extend this to other pins and repeat the same for GPIO0. regards Ravi- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Kyle,
The issue what I am facing in accessing the GPIO is more to do on the linux side.If I do an echo for 222 which is HPS led 53 then I am able to access.But the same is not possible for GPIO 29 to 34 ...this is GPIO1 or GPIO 0(1 to 8).My .SOF is fine and I am able to link to the top level. so what is the change in linux on the board needed to as to access the GPIO??..Kindly help us . regards Ravi chandran --- Quote Start --- Ravi, The Assignment editor needs to be set to Assignment Name [I/O Standard] this is a must is shouldn't be a "location" on the FPGA as there is no location it goes through into the preloader and is linked there. The Value is instead set to the Voltage level [3.3-V LVCMOS] your maybe less depending on the hps powered IO's it could be 2.5V or lower please check this. Enabled is yes too so that would be correct. Please try the linux commands from the HPS command line first (before memory mapping) to make sure that the GPIO's can be set... follow this tutorial for setting the GPIO's If there are LED's then you should be just write a active low or high signal to them from the command line.... Follow this tutorial and see if the LED's light on the gpio then you can start to progress into the memory mapping application code.. http://falsinsoft.blogspot.co.uk/2012/11/access-gpio-from-linux-user-space.html It looks like your code is correct but you need to just make sure that the GPIO is controllable from the drivers in linux the following echo commands will help you enable an GPIO output (number is significant please find out the number of the GPIO from linux and cyclone V handbook for specifying the correct GPIO) here 192 is used as this is from the GPIO controller 2 and is the 1st one your number will be different please check for your correct one. echo 192 > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio192/direction echo 1 > /sys/class/gpio/gpio192/value echo 0 > /sys/class/gpio/gpio192/value The first echo line enables the gpio.... if you get problems here the gpio is already attached to a linux driver and will need removing (look into removing a driver) The second enables that channel to become an output as all gpio's are either in or output the default is always input so this enables you send the gpio as high or low...etc The next 2 lines will write a high or low value to that gpio you should see the light move on and off if it is correctly enabled. Best of luck Kyle Without finding problems you never learn. --- Quote End ---- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Ravi,
It seems you have setup the GPIO's correctly in the .SOF file and are able to access them through linux, so if the GPIO 29 to 34 are linked correctly (or not connected to any HPS controller i.e. SPI, UART etc) and you have doubled checked this. Then your right to assume its a Linux resolution. What I suggest is you perform the following sequence within linux to correctly make sure no other drivers are linked to the device. To see how the GPIO's are setup for the working pins and also duplicate this driver for the non-working pins. These are the steps: cat /sys/kernel/debug/gpio The above command in Linux performs a category display of the kernel gpio pins... if you have any drivers linked to the gpio numbers it will say here. So from this information you need to see if any drivers are linked to the working pins? (e.g. GPIO 222 / HPS_LED 53) - If they are you will need to duplicate this driver by binding it to the correct driver. You can do this in Linux or via the Linux Device Tree for a custom built development it suggest just copy the other GPIOs to to a driver and they are linked. Alternatively if this suggest no drivers are connected to the pins and they are all GPIO output enabled then you need to see if the non-working ones are binded to a driver such as "led-gpio" or alternate drivers dependable on your system configuration. If they are this means they are uncontrollable from this point as they are attached direct to a specific driver and need to be un-binded first. (This can be again done in the linux device tree look at where the GPIO's are set up remove and create a new device tree). Or an alternate method would be just to unbind it from Linux itself using the following command syntax below: echo "leds-gpio" > /sys/bus/platform/drivers/leds-gpio/unbind Please note where it says leds-gpio for both the echo and driver you need to alter for exactly the same driver that is linked to you system you should then be able to read and write to these GPIO's.. Does this makes sense? Best of Luck Kyle- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle,
yes this path really make sense.Now to answer your points I have following observations 1.when I do cat /sys/kernel/debug/gpio I see the following things GPIOs 171-197, /soc/gpio@ff70a000: GPIOs 198-226, /soc/gpio@ff709000: gpio-199 (sysfs ) out hi gpio-222 (sysfs ) out hi GPIOs 227-255, /soc/gpio@ff708000: when I configure for pin 222 ->HPS_53 and 199->GPIO30 following is oberserved GPIOs 198-226, /soc/gpio@ff709000: gpio-199 (sysfs ) out hi gpio-222 (sysfs ) out hi The gpio-222 is linked to HPS_53 and I am able to control through the linux command. value says "hi" here for both but gpio-199 is not seen glowing. 2. while I tried executing your command for unbind echo "leds-gpio" > /sys/bus/platform/drivers/leds-gpio/unbind following error observed:-sh: echo: write error: No such device Any pointers to get over these errors and accessing the pins??? Thanks Ravi- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi ravi,
It seems that there isn't anything bound from your debug list.... only the gpio's are set to the 3 controllers of the GPIO core which is as expected. So from the GPIO point of view... GPIOs 227 to 255 would be linked to GPIOs 0 to 28 GPIOs 198 to 226 would be linked to GPIOs 29 to 57 GPIOs 171 to 197 would be linked to GPIOs 58 to 66 so GPIO 226 = GPIO 57 ---> from your design pin so GPIO 222 = GPIO 53 ---> from your design pin so GPIO 199 = GPIO 30 ---> from your design pin (I think this is not the correct GPIO number from what you have said) So this is the mix up.... your not connected to the correctly controller GPIO number.... So if from your design pin when you want GPIO 0 you need GPIO 227 to be exported... Hope this helps you Many Thanks Kyle- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle ,
even if I export 227 the pin is not glowing this is more to do with unbinding from linux as suggested by you last time while I tried executing your command for unbind echo "leds-gpio" > /sys/bus/platform/drivers/leds-gpio/unbind following error observed:-sh: echo: write error: No such device Any pointers to get over these errors and accessing the pins??? Let me know how I can make the pins unbind....then all the pins shall be accessed. regards Ravi chandran- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi kyle ,
with respect to GPIO 0 also this is what is seen when I do cat /sys/kernel/debug/gpio GPIOs 171-197, /soc/gpio@ff70a000: GPIOs 198-226, /soc/gpio@ff709000: GPIOs 227-255, /soc/gpio@ff708000: gpio-227 (sysfs ) out hi gpio-228 (sysfs ) out lo but 227 and 228 which corresponds to GPIO 0[0] and GPIO_0[1] does not glow....Linux is binding I think ..if I do unbind echo "leds-gpio" > /sys/bus/platform/drivers/leds-gpio/unbind -sh: echo: write error: No such device so I need to get over this problem so as to access GPIO's..what is the way to do this??..any idea. regards Ravi- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi ravi,
They are not bound in your case.. there is nothing obvious on your debug list... except the controllers and this is normal. As your device tree would have something like this included to attach the GPIOs to the memory locations: hps_0_gpio0: gpio@0xff708000 { compatible = "snps,dw-gpio-14.0", "snps,dw-gpio"; reg = < 0xFF708000 0x00001000 >; interrupt-parent = < &hps_0_arm_gic_0 >; interrupts = < 0 164 4 >; clocks = < &l4_mp_clk >; # gpio-cells = < 2 >; gpio-controller; }; //end gpio@0xff708000 (hps_0_gpio0) hps_0_gpio1: gpio@0xff709000 { compatible = "snps,dw-gpio-14.0", "snps,dw-gpio"; reg = < 0xFF709000 0x00001000 >; interrupt-parent = < &hps_0_arm_gic_0 >; interrupts = < 0 165 4 >; clocks = < &l4_mp_clk >; # gpio-cells = < 2 >; gpio-controller; }; //end gpio@0xff709000 (hps_0_gpio1) hps_0_gpio2: gpio@0xff70a000 { compatible = "snps,dw-gpio-14.0", "snps,dw-gpio"; reg = < 0xFF70A000 0x00001000 >; interrupt-parent = < &hps_0_arm_gic_0 >; interrupts = < 0 166 4 >; clocks = < &l4_mp_clk >; # gpio-cells = < 2 >; gpio-controller; }; //end gpio@0xff70a000 (hps_0_gpio2) Have you tried other GPIO's? Perhaps its your circuit or you have included a device on your GPIO's such as a peripheral (UART, SPI or CAN controller) to these pins in Quartus Qys design and is not configurable. Or you have forgotten to add it again in the pin assignments like you did originally with the first GPIO's this could be your issue. regards Kyle
Reply
Topic Options
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page