Giter Site home page Giter Site logo

rubberazer / jetgpio Goto Github PK

View Code? Open in Web Editor NEW
50.0 5.0 12.0 1.54 MB

C library to manage the GPIO header of the Nvidia Jetson boards

Home Page: https://rubberazer.github.io/JETGPIO

License: The Unlicense

C++ 7.26% C 91.92% Makefile 0.71% Shell 0.11%
gpio gpio-pins jetson-nano jetson-tx1 pwm i2c spi jetson-orin jetson-orin-nano lcd1602

jetgpio's Introduction

JETGPIO library

C library to manage the GPIO header of the Nvidia JETSON boards

ORIN AGX WANTED TO EXPAND THE LIBRARY

FUNCTIONALITY:

  • Supported models:

    • JETSON NANO and TX1

    • JETSON ORIN NANO and ORIN NX

  • GPIO control of all the header pinout as input or output. Low latency, see also The need for speed below for some more information on this

  • Catching rising or falling edges in any header pin working as input. Timestamp of the event in nanoseconds in epoch format is provided

  • PWM (hardware) control on header pins 32, 33 for all models and also 15 for Orin

  • I2C serial communication over pins: 27 (SDA) & 28 (SCL) and 3 (SDA) & 5 (SCL)

  • SPI serial communication over pins: 19 (MOSI), 21 (MISO), 23 (SCK) & 24 (CS0) and 37 (MOSI), 22 (MISO), 13 (SCK) & 18 (CS0)

  • No need of previous kernel configuration, no need to change the device tree, no need to reconfigure via /opt/nvidia/jetson-io/jetson-io.py or the like

INSTALLATION:

Clone/download the content into any folder in your JETSON, cd to that folder and type:

sudo make   
sudo make install                                             

That's it, the library should be installed and ready to be used. To uninstall the library:

sudo make uninstall

HOW TO:

You will find code examples to learn how to use the library in both: EXAMPLES_C & EXAMPLES_C++ folders, the first one contains all the C examples, the second one contains the same examples ready to compile in C++. To compile the examples you will find instructions in the comment section at the top of each of the example files

  • jetgpio_example.c & jetgpio_example.cpp show how to setup and use any of the pins as Inputs/Outputs, this will allow you to toggle pins from 0V (logic 0) to 3.3V (logic 1) or read the pin as an input where 3.3V at the pin is a logic 1. Note that when reading inputs, floating pins will throw unreliable results (as there is no actual input)

  • jetgpio_edge.c & jetgpio_edge.cpp show how to catch rising or falling edges in any pin working as Input, timestamp in epoch format in nanoseconds is provided

  • jetgpio_PWM_example.c & jetgpio_PWM_example.cpp show how to use the PWM funcionality at pin 32

  • test_PWM.c & test_PWM.cpp show again how to use the PWM funcionality with some LEDs connected and also capturing interruptions

  • jetgpio_i2c_example.c & jetgpio_i2c_example.cpp show how to use the i2c comms to talk to a MPU6050 gyroscope connected to i2c0 (pins 27 & 28)

  • lcd_i2c.c & lcd_i2c.cpp minimalistic example of how to show a message on the screen of a Freenove i2c 1602 LCD display connected to i2c1 (pins 3 & 5)

  • spi_loop.c & spi_loop.cpp show how to run a simple loop test on the SPI port(s) by connecting together pins 19 & 21 for port SPI1 and pins 22 & 37 for port SPI2

DOCUMENTATION:

Some doxygen documentation here. As a rule of thumb, the library functions names and usage mimic the ones of the pigpio library (which I recommend if you work with Raspberry Pis). Learnt a lot from that one

THE NEED FOR SPEED:

I created a couple of little programs to measure reaction time e.g. how fast an output pin turns from 0 to 1 (3.3v), or how fast a change to an input pin is detected by the library, a diagram of the physical setup is shown below, basically I set up pin 38 as an output and pin 40 as an input and connect both through a resistor to observe the interaction:

jetson_speed

Compiling and running jetgpio_round_trip.c I am measuring the time from before executing the function that writes a logic 1 (3.3v) to pin 38 until the point when this is detected (by voltage level not edge interrupt) at pin 40. Here the intention is to measure the worst case scenario of a combination of 2 different actions:

  • a pin changes state from 0 to 1 (output)
  • a second pin detects a change on its state from 0 to 1 (input) being this change produced by the output pin

The results that I am getting for the round trip (total time to execute both actions) by running this program are:

Nano Classic Orin Nano
Minimum 1.3 us 3.1 us **
Maximum 1.8 us 4.2 us **

Compiling and running jetgpio_speed_edge.c I am trying to measure the time using a similar setup as described above, the difference here is that I am using the library function: gpioSetISRFunc() which basically goes through the linux gpio driver in order to catch rising and falling edges, the reason to use the linux driver for this has to do with the fact that catching interrupts from user space (this is a library after all) is basically 'problematic' for a number of reasons, in short, if driver performance and/or device tree stuff got in my way I would basically replace the current driver by my own, but that is beyond the scope of this library.

Nano Classic Orin Nano
Minimum 250 us 200 us
Maximum 700 us 1000 us

Note that this doesn't measure individual actions but the total time to execute both (round trip). It is clear that the timestamp produced by the linux driver is the one to blame for the slow reaction on detecting a change on the input pin, still interesting as there is no meaningful cpu waste as the hardware is producing the interrupt for us (no polling)

Compiling and running jetgpio_output.c I am writing high/low to pin 38 on a continuous loop, what I am getting on the oscilloscope are the following results:

Nano Classic Orin Nano
Average 0.6 us 2 us **

**Yes, the new Orin has a slower response/higher latency than the old Jetson Nano, this is due to the fact that writing to some registers is being monitored by an external CPU called BPMP (Boot and Power Management Processor). This CPU is an addition to what is called CPU Complex (the 6 Arm A78A cores that are described on the Orin Nano/NX specs) and is completely independent from the main system, running its own firmware and with an independent device tree. Some of the tasks performed by this CPU are clock and power supply management for peripherals inside the SOM e.g. PWM, GPIO... but it also plays a "firewall" role, in other words before writing to some registers in the standard CPU Complex the writing instructions have to pass through this "firewall" making the whole thing slower. This extra, out of the system security manager can be very useful on some applications but it has obviously a downside. Again sorting this out goes beyond the scope of any user space application (library) and it would imply flashing the Orin after modifying stuff, which is something along with other things like modifying the device tree (and reflashing probably) that I wanted to avoid when I created this library.

freenove_i2c_1602_lcd

JETSON NANO AND ORIN PINOUT:

The library uses the typical 40 pin header numbering, taking the dev kit as reference so for instance pin 3 is I2C_2_SDA on Nano or I2C1_SDA on Orin, pin 1 is 3.3 VDC power and so on. You can check the official NVIDIA pinmux configuration for reference or if not available you can use the ones below:

https://jetsonhacks.com/nvidia-jetson-nano-j41-header-pinout/

https://jetsonhacks.com/nvidia-jetson-orin-nano-gpio-header-pinout/

The library has been tested on a Jetson Nano: tegra210 (TX1) and on a Jetson Orin Nano: tegra234

jetgpio's People

Contributors

littlebluesub avatar pintarj avatar rubberazer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

jetgpio's Issues

segfault on GPIO configure

Jetson Orin Nano

# R35 (release), REVISION: 3.1, GCID: 32827747, BOARD: t186ref, EABI: aarch64, DATE: Sun Mar 19 15:19:21 UTC 2023

The below error is caused when setting the GPIO to an OUTPUT.

[ 1742.458497] ---[ end trace c9b4cbc2ab4c73c3 ]---
[ 1796.382888] CPU:0, Error: cbb-fabric@0x13a00000, irq=25
[ 1796.388290] **************************************
[ 1796.393217] CPU:0, Error:cbb-fabric, Errmon:2
[ 1796.397703] 	  Error Code		: FIREWALL_ERR

[ 1796.403372] 	  Error Code		: FIREWALL_ERR
[ 1796.407497] 	  MASTER_ID		: CCPLEX
[ 1796.410993] 	  Address		: 0x32e0000
[ 1796.414580] 	  Cache			: 0x0 -- Device Non-Bufferable
[ 1796.419771] 	  Protection		: 0x2 -- Unprivileged, Non-Secure, Data Access
[ 1796.426747] 	  Access_Type		: Read
[ 1796.430238] 	  Access_ID		: 0x15
[ 1796.430242] 	  Fabric		: cbb-fabric
[ 1796.437136] 	  Slave_Id		: 0x35
[ 1796.440356] 	  Burst_length		: 0x0
[ 1796.443845] 	  Burst_type		: 0x1
[ 1796.447157] 	  Beat_size		: 0x2
[ 1796.450373] 	  VQC			: 0x0
[ 1796.453149] 	  GRPSEC		: 0x7e
[ 1796.456195] 	  FALCONSEC		: 0x0
[ 1796.459425] 	**************************************
[ 1796.464467] ------------[ cut here ]------------
[ 1796.464486] WARNING: CPU: 0 PID: 0 at drivers/soc/tegra/cbb/tegra234-cbb.c:577 tegra234_cbb_isr+0x130/0x170
[ 1796.474494] Modules linked in: nvidia_modeset(OE) fuse(E) lzo_rle(E) lzo_compress(E) zram(E) ramoops(E) reed_solomon(E) loop(E) bnep(E) snd_soc_tegra186_asrc(E) snd_soc_tegra186_dspk(E) snd_soc_tegra210_ope(E) snd_soc_tegra210_mvc(E) snd_soc_tegra210_iqc(E) snd_soc_tegra186_arad(E) snd_soc_tegra210_afc(E) snd_soc_tegra210_dmic(E) snd_soc_tegra210_admaif(E) snd_soc_tegra210_adx(E) snd_soc_tegra210_amx(E) snd_soc_tegra_pcm(E) snd_soc_tegra210_mixer(E) snd_soc_tegra210_i2s(E) snd_soc_tegra210_sfc(E) aes_ce_blk(E) crypto_simd(E) cryptd(E) aes_ce_cipher(E) ghash_ce(E) rtl8822ce(E) r8168(E) sha2_ce(E) sha256_arm64(E) sha1_ce(E) rtk_btusb(E) btusb(E) snd_soc_tegra210_adsp(E) btrtl(E) btbcm(E) btintel(E) snd_soc_tegra_machine_driver(E) snd_soc_spdif_tx(E) snd_soc_tegra_utils(E) snd_hda_codec_hdmi(E) snd_soc_simple_card_utils(E) snd_soc_tegra210_ahub(E) r8169(E) realtek(E) userspace_alert(E) fusb301(E) tegra_bpmp_thermal(E) nvadsp(E) snd_hda_tegra(E) tegra210_adma(E) cfg80211(E) snd_hda_codec(E)
[ 1796.474603]  nv_imx219(E) snd_hda_core(E) spi_tegra114(E) nvidia(OE) binfmt_misc(E) ina3221(E) pwm_fan(E) nvgpu(E) nvmap(E) ip_tables(E) x_tables(E) [last unloaded: mtd]
[ 1796.474630] CPU: 0 PID: 0 Comm: swapper/0 Tainted: G        W  OE     5.10.104-tegra #1
[ 1796.474632] Hardware name: NVIDIA Orin NX Developer Kit (DT)
[ 1796.474636] pstate: 60400089 (nZCv daIf +PAN -UAO -TCO BTYPE=--)
[ 1796.474638] pc : tegra234_cbb_isr+0x130/0x170
[ 1796.474640] lr : tegra234_cbb_isr+0x10c/0x170
[ 1796.474642] sp : ffff800010003e10
[ 1796.474643] x29: ffff800010003e10 x28: ffffd05082682680 
[ 1796.474646] x27: 0000000000000001 x26: 0000000000000080 
[ 1796.474649] x25: ffffd050820a7778 x24: ffffd050829ebe40 
[ 1796.474652] x23: ffffd05082387000 x22: 0000000000000019 
[ 1796.474655] x21: ffffd0508280ef20 x20: 0000000000000002 
[ 1796.474657] x19: ffffd0508280ef10 x18: 0000000000000010 
[ 1796.474660] x17: 0000000000000000 x16: ffffd050809e2d90 
[ 1796.474662] x15: ffffd05082682bf0 x14: ffffffffffffffff 
[ 1796.474665] x13: ffff800090003917 x12: ffff80001000391f 
[ 1796.474667] x11: 0101010101010101 x10: 7f7f7f7f7f7f7f7f 
[ 1796.474670] x9 : ffff800010003c30 x8 : 2a2a2a2a2a2a2a2a 
[ 1796.474672] x7 : 2a2a2a2a2a2a2a09 x6 : c0000000ffffefff 
[ 1796.474675] x5 : ffff7787a8377958 x4 : ffffd05082697968 
[ 1796.474677] x3 : 0000000000000001 x2 : ffffd05080b77460 
[ 1796.474679] x1 : ffffd05082682680 x0 : 0000000100010001 
[ 1796.474683] Call trace:
[ 1796.474685]  tegra234_cbb_isr+0x130/0x170
[ 1796.474694]  __handle_irq_event_percpu+0x60/0x2a0
[ 1796.474697]  handle_irq_event_percpu+0x3c/0xa0
[ 1796.474699]  handle_irq_event+0x4c/0xf0
[ 1796.474701]  handle_fasteoi_irq+0xbc/0x170
[ 1796.474705]  generic_handle_irq+0x3c/0x60
[ 1796.474707]  __handle_domain_irq+0x6c/0xc0
[ 1796.474712]  gic_handle_irq+0x64/0x130
[ 1796.474713]  el1_irq+0xd0/0x180
[ 1796.474720]  cpuidle_enter_state+0xb4/0x400
[ 1796.474722]  cpuidle_enter+0x3c/0x50
[ 1796.474726]  call_cpuidle+0x40/0x70
[ 1796.474727]  do_idle+0x1fc/0x260
[ 1796.474729]  cpu_startup_entry+0x28/0x70
[ 1796.474737]  rest_init+0xd8/0xe4
[ 1796.474744]  arch_call_rest_init+0x14/0x1c
[ 1796.474746]  start_kernel+0x50c/0x540
[ 1796.474748] ---[ end trace c9b4cbc2ab4c73c4 ]---

About PWM

Hi,
I have a Jetson Nano Developer Kit and I want to drive an SG90 servo motor (TowerPro Mİcro Serve 9g) using PWM. I can partially drive it. I downloaded and installed your library. However, I encountered an interesting issue. When I set the frequency to 50 Hz with the 'gpioSetPWMfrequency' function, I observe 45 Hz on the oscilloscope, and when I set it to 55 Hz, I see 50 Hz. What could be the reason for this? Have you encountered or seen any similar issues before? I am connecting the pins directly without using a resistor in between. I would greatly appreciate your help. Thank you in advance.

Thank you for this library

This is not an issue, but a thank you.

I am new to working with Jetson, starting with some experience with the Raspberry Pi using wiringPi.

Thank you very much for creating and sharing this library. It was the only C library I could find that worked on both the NVIDIA developer carrier board and the Seeed Studio A203 carrier board I am using for my projects.

Thank you again

Graham Briggs

gpioTerminate sets all pins back to their original state

gpioTerminate( ) will set all pins back to their original state.

I am sure this is by design. I suggest this is a bad design, because it means you can not reliably have more than one program using this library at the same time.

Consider this scenario

  1. Program A starts, initializes the state of all pins
  2. Program A is using pins 38 and 40, so sets those to desired mode
  3. Program B starts, initializes state of all pins
  4. Program B is using pins 29 and 30, so sets those pins to desired mode
  5. Program A exits, it will reset pins 29 and 30 to their original state, which might not be desired state of program B, which is still running. Chaos will ensue.

Suggest that gpioTerminate should only reset the state of pins that have been initialized through call to pinMode in this instance of the program. Alternatively, do not reset pin mode state at all and make this a deliberate choice of the app using the library what to do with pins when it shuts down.

latency measurement

I didnt find, but maybe its already on docs, can you share the latency of JetGPIO lib comparing gpiod?

gpioRead read does not work with output pins

gpioRead( ) does not work when the pin is in output mode.

This may be by design, but it would be nice option if it could. For what it is worth, the wiringPi library returns the state of the pin when you call digital read for both input and output mode pins.

The code could look something like this:


int gpioRead(unsigned gpio)
{
	int level = 0;
	switch (gpio) 
	{
	case 3:
		if (*pinmux3 == PINMUX_IN)
			level = (pin3->IN[0]) >> 3 & 1;
		else
			level = (pin3->OUT[0]) >> 3 & 1;
		break;
	case 5:
		if (*pinmux5 == PINMUX_IN)
			level = (pin5->IN[0]) >> 2 & 1;
		else
			level = (pin5->OUT[0]) >> 2 & 1;
		break;
	case 7:
		if (*pinmux7 == PINMUX_IN)
			level = pin7->IN[0] & 1;
		else
			level = pin7->OUT[0] & 1;
		break;
	case 8:
		if (*pinmux8 == PINMUX_IN)
			level = pin8->IN[0] & 1;
		else
			level = pin8->OUT[0] & 1;
		break;
	case 10:
		if (*pinmux10 == PINMUX_IN)
			level = (pin10->IN[0]) >> 1 & 1;
		else
			level = (pin10->OUT[0]) >> 1 & 1;
		break;

Orin NX PWM minimum freq?

I'm running this on an Orin NX and I'm confused as to why the minimum PWM frequency is 400 Hz. As far as I'm aware, this locks me out of most servo and motor control (since those are usually 50-100Hz). Would it be possible to go around whatever limitation this is to enable said servo and motor control? Thanks!

cant run on boot

hi,
i used your library , its wonderful and work but i have some diffucult for example
i run it in terminal with sudo ./myApp its worked but when i did with myapp.service its not running. path is correct. permission is chmod +x myApp and i try chmod 777 myApp. can you try work on boot or do you have any idea.
i tried that, with desktop file, with service file, with .sh file . it not worked. but other app did works fine

thanks for your help
regards

i have solition just now, may be need someone.
make StartUp Application comman in ubuntu menu .sh file and your app in same folder
like this. /path/your/myAppsh.sh
myAppsh.sh:
#!/bin/bash
echo <your_pass> | sudo -S ./myApp

thats all.
thank you for library

Makefile was missing call to: -lpthread -lrt

Corrected stupid mistake on the github makefile, everything should be working fine now. Unfortunately versions downloaded from the 22nd October to the 5th of November need to add this correction, if not the library is going to fail
ANOBODY WHO DOWNLOADED BETWEEN THE 22ND OCTOBER AND THE 5TH OF NOVEMBER SHOULD CLONE/DOWNLOAD AGAIN

Qt and jetgpio_example.cpp problem

hi, first of all thank you for pair for code..
my problem that, i am using Qt and i make copy code in jetgpio_example.cpp to mainwindow.cpp but editor show error
undefined reference to gpioInitialise' undefined reference to gpioSetMode'
and other function, i did add in header #include <jetgpio.h> and read installation doc. make and make install, by the way , if i run jetgpio_example.cpp in with this way
Compile with: g++ -Wall -o jetgpio_example jetgpio_example.cpp -ljetgpio

  • Execute with: sudo ./jetgpio_example
    everything is okey. but in Qt doesnt work.
    have you got any idea..or solition

#include <stdint.h>
#include <stdlib.h>
#include <jetgpio.h>

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
int Init;

Init = gpioInitialise();
if (Init < 0)
{
   /* jetgpio initialisation failed */
   printf("Jetgpio initialisation failed. Error code:  %d\n", Init);
   exit(Init);
}
else
{
   /* jetgpio initialised okay*/
   printf("Jetgpio initialisation OK. Return code:  %d\n", Init);
}

// Setting up pin 3 as OUTPUT and 7 as INPUT

int stat1 = gpioSetMode(3, JET_OUTPUT);
if (stat1 < 0)
{
   /* gpio setting up failed */
   printf("gpio setting up failed. Error code:  %d\n", stat1);
   exit(Init);
}
else
{
   /* gpio setting up okay*/
   printf("gpio setting up okay. Return code:  %d\n", stat1);
}

int stat2 = gpioSetMode(7, JET_INPUT);
if (stat2 < 0)
{
   /* gpio setting up failed */
   printf("gpio setting up failed. Error code:  %d\n", stat2);
   exit(Init);
}
else
{
   /* gpio setting up okay*/
   printf("gpio setting up okay. Return code:  %d\n", stat2);

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{

ui->setupUi(this);
int Init;

Init = gpioInitialise();
if (Init < 0)
{
   /* jetgpio initialisation failed */
   printf("Jetgpio initialisation failed. Error code:  %d\n", Init);
   exit(Init);
}
else
{
   /* jetgpio initialised okay*/
   printf("Jetgpio initialisation OK. Return code:  %d\n", Init);
}

// Setting up pin 3 as OUTPUT and 7 as INPUT

int stat1 = gpioSetMode(3, JET_OUTPUT);
if (stat1 < 0)
{
   /* gpio setting up failed */
   printf("gpio setting up failed. Error code:  %d\n", stat1);
   exit(Init);
}
else
{
   /* gpio setting up okay*/
   printf("gpio setting up okay. Return code:  %d\n", stat1);
}

int stat2 = gpioSetMode(7, JET_INPUT);
if (stat2 < 0)
{
   /* gpio setting up failed */
   printf("gpio setting up failed. Error code:  %d\n", stat2);
   exit(Init);
}
else
{
   /* gpio setting up okay*/
   printf("gpio setting up okay. Return code:  %d\n", stat2);
}

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
int Init;

Init = gpioInitialise(); //**_<---- problem this line,**_
if (Init < 0)
{
   /* jetgpio initialisation failed */
   printf("Jetgpio initialisation failed. Error code:  %d\n", Init);
   exit(Init);
}
else
{
   /* jetgpio initialised okay*/
   printf("Jetgpio initialisation OK. Return code:  %d\n", Init);
}

// Setting up pin 3 as OUTPUT and 7 as INPUT

int stat1 = gpioSetMode(3, JET_OUTPUT);
if (stat1 < 0)
{
   /* gpio setting up failed */
   printf("gpio setting up failed. Error code:  %d\n", stat1);
   exit(Init);
}
else
{
   /* gpio setting up okay*/
   printf("gpio setting up okay. Return code:  %d\n", stat1);
}

int stat2 = gpioSetMode(7, JET_INPUT);
if (stat2 < 0)
{
   /* gpio setting up failed */
   printf("gpio setting up failed. Error code:  %d\n", stat2);
   exit(Init);
}
else
{
   /* gpio setting up okay*/
   printf("gpio setting up okay. Return code:  %d\n", stat2);
}

///-------------------------------------------------------------------///

thank you...
assyhgit

I2C does not allow for multiple devices on the same bus

As a user, I need to have multiple I2C devices on the same bus.

In the function jetgpio.c / i2cOpen( )

This code (around line 1969) prevents more than one device on the same bus:

if (i2cInfo[i2cBus].state == I2C_CLOSED) {
slot = i2cBus;
i2cInfo[slot].state = I2C_RESERVED;
}
else { printf("i2c bus already open\n");
return -4;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.