esp-rs / esp-hal Goto Github PK
View Code? Open in Web Editor NEWno_std Hardware Abstraction Layers for ESP32 microcontrollers
Home Page: https://docs.esp-rs.org/esp-hal/
License: Apache License 2.0
no_std Hardware Abstraction Layers for ESP32 microcontrollers
Home Page: https://docs.esp-rs.org/esp-hal/
License: Apache License 2.0
Link doesn't work for esp32c3-hal: https://docs.rs/esp32c3-hal
There are a number of peripheral drivers already implemented in the esp32-hal repository. Since this crate has been published, we aren't really able to release a new version until we've roughly reached feature-parity with the previous implementation. Because of this, we should begin working on porting this drivers.
These can either just be dumped in the esp32-hal
package for now, making the required changes just to get things building, or can be re-written to be compatible across all supported chips. While the latter method is preferable, the prior is likely the more realistic path forward.
The existing drivers are:
analog/adc
analog/dac
clock_control
see #44delay
dport
dprint
efuse
external_ram
gpio
i2c
interrupt
ledc
(in progress)mem
serial
spi
timer
(partially complete)Currently we use floats in one place:
esp-hal/esp-hal-common/src/timer.rs
Lines 226 to 229 in 907d43e
If possible we should remove that since it adds 2k of code (optimized) for ESP32-C3:
File .text Size Crate Name
0.0% 12.3% 1.2KiB compiler_builtins compiler_builtins::float::div::__divdf3
0.0% 3.0% 292B compiler_builtins __floatundidf
0.0% 1.6% 156B compiler_builtins __floatunsidf
0.0% 1.4% 140B compiler_builtins __gedf2
0.0% 1.4% 140B compiler_builtins __gtdf2
0.0% 1.3% 130B compiler_builtins __fixunsdfdi
It's very convenient that we don't need a memory.x
or build.rs
in binary crates using the HAL
However, we should be able to set things like flash size if it doesn't match our default.
Ideally not by providing custom linker scripts
This implementation is actually not correct, but was "good enough" just to get something in here. See here for details:
https://github.com/esp-rs/esp-hal/blob/main/esp-hal-common/src/delay.rs#L92-L94
This will need to be corrected at some point.
Hello,
the README states that the MSRV is 1.59.0. However, when compiling for esp32 from main with the 1.61.0
toolchain, I get the following error:
Compiling esp-hal-procmacros v0.1.0 (/var/home/ahartmann/repos/mt/playground/esp-hal/esp-hal-procmacros)
error[E0658]: use of unstable library feature 'bool_to_option'
--> /var/home/ahartmann/repos/mt/playground/esp-hal/esp-hal-procmacros/src/lib.rs:228:35
|
228 | (f.sig.inputs.len() == 1).then_some(Ident::new("context", proc_macro2::Span::call_site()));
| ^^^^^^^^^
|
= note: see issue #80967 <https://github.com/rust-lang/rust/issues/80967> for more information
= help: add `#![feature(bool_to_option)]` to the crate attributes to enable
For more information about this error, try `rustc --explain E0658`.
error: could not compile `esp-hal-procmacros` due to previous error
And indeed this language feature was added as part of this PR and landed in Rust 1.62.0. This bumps the MSRV to 1.62.0.
Maybe it would be a good idea to add some github action that compiles the code against the MSRV mentioned in the Readme so it doesn't break along the way, or at least gets recognized automatically?
Currently we only support target mode.
Period mode could be potentially useful e.g. esp-wifi
currently uses period mode of SYSTIMER and since the HAL lacks that functionality it accesses the registers directly.
But according to my logic analyzer it matches perfect on ESP32-C3 and ESP32-S3
Get to the point that at least "hello_world" can be flashed and runs. Ideally also the other examples besides "blinky" (since GPIO will be a bigger task)
We should use a Duration instead of a plain U64 for the timer timeout.
Also we should pass in the clock config from #44 to the constructor and have a way to configure the rate of the timer ticks.
I apparently didn't test this before merging (oops), but if you attempt to initialize the SPI
peripheral without providing all pins it is impossible to build. For example:
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let sck = io.pins.gpio7;
let mosi = io.pins.gpio6;
let spi = Spi::new(
peripherals.SPI2,
sck,
mosi,
None,
None,
16u32.MHz(),
SpiMode::Mode0,
&mut system.peripheral_clock_control,
&clocks,
);
Results in:
error[E0282]: type annotations needed
--> src/main.rs:41:9
|
41 | None,
| ^^^^ cannot infer type of the type parameter `T` declared on the enum `Option`
|
help: consider specifying the generic argument
|
41 | None::<T>,
| +++++
For more information about this error, try `rustc --explain E0282`.
Get to the point that at least "hello_world" can be flashed and runs. Ideally also the other examples besides "blinky" (since GPIO will be a bigger task)
This includes
The table below enumerates all peripherals for each chip supported by esp-hal
. Support in this case does not mean a feature-complete and bug-free driver implementation, only that you are able to interact with the peripheral in some meaningful way.
If a cell contains am em dash (—) this means that the particular peripheral is not present for a chip. A check mark (✓) means that some driver implementation exists.
The table should be complete, though some minor naming changes have been made for consistency. If there are any missing peripherals or errors please comment below indicating such. Since adding support for additional devices it's possible some new peripherals have been introduced which aren't displayed below.
If you would like to contribute to this project, we ask that you please comment below "claiming" a peripheral, just to help ensure people are not stepping on each others' feet.
If you would like to request a feature for an existing peripheral driver, please open a separate issue to do so.
The following peripherals either have no drivers at all, or do not support all devices with that peripheral:
Peripheral | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
---|---|---|---|---|---|---|---|
ATOMIC | — | — | — | — | — | — | |
DEDICATED_GPIO | — | — | — | — | — | — | |
DS | — | — | |||||
ECC | — | — | — | — | |||
EXTMEM | — | — | |||||
GPIOSD | — | — | |||||
HINF | — | — | — | — | — | ||
HMAC | — | — | |||||
HP_APM | — | — | — | — | — | ||
HP_SYS | — | — | — | — | — | ||
LCD_CAM | — | — | — | — | — | — | |
LP_ANA | — | — | — | — | — | ||
LP_AON | — | — | — | — | — | ||
LP_APM | — | — | — | — | — | ||
LP_APM0 | — | — | — | — | — | — | |
LP_CLKRST | — | — | — | — | — | ||
LP_I2C0 | — | — | — | — | — | — | |
LP_I2C_ANA_MST | — | — | — | — | — | — | |
LP_IO | — | — | — | — | — | — | |
LP_PERI | — | — | — | — | — | ||
LP_TEE | — | — | — | — | — | — | |
LP_TIMER | — | — | — | — | — | ||
LP_UART | — | — | — | — | — | — | |
LP_WDT | — | — | — | — | — | ||
MEM_MONITOR | — | — | — | — | — | ||
OTP_DEBUG | — | — | — | — | — | ||
PARL_IO | — | — | — | — | — | ||
PAU | — | — | — | — | — | ||
PERI_BACKUP | — | — | — | — | — | — | |
PMS | — | — | — | — | — | — | |
PMU | — | — | — | — | — | — | |
RTCIO | — | — | — | — | |||
RTC_CNTL | — | — | |||||
RTC_I2C | — | — | — | — | |||
SDMMC | — | — | — | — | — | ||
SENSITIVE | — | — | — | — | |||
SLC | — | — | — | — | — | ||
SLCHOST | — | — | — | — | |||
SOC_ETM | — | — | — | — | — | — | |
TEE | — | — | — | — | — | ||
TRACE | — | — | — | — | — | ||
TWAI0 1 | — | ✓ | ✓ | ||||
TWAI1 1 | — | — | — | — | — | — | |
UHCI0 | — | ||||||
UHCI1 | — | — | — | — | |||
WCL | — | — | — | — | — | — | |
XTS_AES | — | — | — |
1: #341
The following peripherals have some driver implementation for all supported chips, though the drivers may not be feature-complete:
Peripheral | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
---|---|---|---|---|---|---|---|
AES | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
ASSIST_DEBUG | — | ✓ | ✓ | ✓ | ✓ | — | ✓ |
DAC | ✓ | — | — | — | — | ✓ | — |
DMA | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
GPIO | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
I2C0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
I2C1 | ✓ | — | — | — | ✓ | ✓ | ✓ |
I2S0 | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
I2S1 | ✓ | — | — | — | — | — | ✓ |
LEDC | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
PCNT | ✓ | — | — | ✓ | ✓ | ✓ | ✓ |
PWM0 | ✓ | — | — | ✓ | ✓ | — | ✓ |
PWM1 | ✓ | — | — | — | — | — | ✓ |
RMT | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
RNG | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
RSA | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
SARADC | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
SHA | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
SPI2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
SPI3 | ✓ | — | — | — | — | ✓ | ✓ |
SYSTIMER | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
TIMG0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
TIMG1 | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
UART0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
UART1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
UART2 | ✓ | — | — | — | — | — | ✓ |
USB_DEVICE | — | — | ✓ | ✓ | ✓ | — | ✓ |
USB0 | — | — | — | — | — | ✓ | ✓ |
I believe it's still too early to begin work on this, but once a stable release (or at the very least a more stable alpha release) is available we can revisit this topic.
In [email protected]
the traits to implement are:
embedded_hal_async::delay::DelayUs
embedded_hal_async::digital::Wait
embedded_hal_async::i2c::I2c
embedded_hal_async::spi::SpiBus
embedded_hal_async::spi::SpiBusFlush
embedded_hal_async::spi::SpiBusRead
embedded_hal_async::spi::SpiBusWrite
embedded_hal_async::spi::SpiDevice
embedded_hal_async::spi::SpiDeviceRead
embedded_hal_async::spi::SpiDeviceWrite
embedded_hal_async::serial::Write
I tried to use an led strip with my esp32c3, but it seems like there's a hang when using more than one led.
Take the hello_rgb
example and modify it to drive two leds. Here's the diff:
diff --git a/esp32c3-hal/examples/hello_rgb.rs b/esp32c3-hal/examples/hello_rgb.rs
index a4a3b41..2ae38d7 100644
--- a/esp32c3-hal/examples/hello_rgb.rs
+++ b/esp32c3-hal/examples/hello_rgb.rs
@@ -62,7 +62,7 @@ fn main() -> ! {
// We use one of the RMT channels to instantiate a `SmartLedsAdapter` which can
// be used directly with all `smart_led` implementations
- let mut led = <smartLedAdapter!(1)>::new(pulse.channel0, io.pins.gpio8);
+ let mut led = <smartLedAdapter!(2)>::new(pulse.channel0, io.pins.gpio8);
// Initialize the Delay peripheral, and use it to toggle the LED state in a
// loop.
@@ -81,7 +81,7 @@ fn main() -> ! {
color.hue = hue;
// Convert from the HSV color space (where we can easily transition from one
// color to the other) to the RGB color space that we can then send to the LED
- data = [hsv2rgb(color)];
+ data = [hsv2rgb(color); 2];
// When sending to the LED, we do a gamma correction first (see smart_leds
// documentation for details) and then limit the brightness to 10 out of 255 so
// that the output it's not too bright.
The on-board led should show a rainbow pattern, just like before.
The led keeps the color that was active before the start of the program (no updates get through, not even the first one).
As far as I can see, the call to SmartLedAdapter.write()
never returns. I haven't found the root cause for this yet.
Does anyone have an idea what could be going wrong?
I've been playing around with my esp32-c3 dev board, trying to drive the on-board rgb led (WS2812) with the rmt peripheral. Some time ago, I managed to do this using esp-idf functionality (see here), but I wanted to also achieve it "bare-metal" using only esp-hal and the pac crate. After some trial and error, I now have an implementation that correctly configures the rmt peripheral to drive the on-board led. The code is here (see branch 'rmt'):
https://github.com/fkohlgrueber/esp32c3-hello-world/tree/rmt
I'd be interested to contribute a driver for the rmt peripheral and probably also the led driver to esp-hal. I'd like to use this issue to discuss the details of how this could work. Some questions off the top of my head:
I'm sorry for that slightly unstructured brain dump above. The main point is that I wanted to show what I did and that - if time permits - I'd be interested to work towards integrating an rmt driver into esp-hal.
Let me know what you think!
As an MVP, we should have the TIMG
, UART
, and GPIO
peripherals implemented for each chip, with working examples. Presently this is all implemented to some degree for the ESP32-C3.
The remaining chips should be able to use the TIMG
and UART
implementations with little to no modifications. GPIO
will likely have to be device-specific.
TIMG
UART
GPIO
TIMG
UART
GPIO
TIMG
UART
GPIO
TIMG
UART
GPIO
Our current examples do their job in that they give us CI coverage of this feature, and provide some demonstration of how it can be used. We can likely come up with a more interesting/representative example, however.
This feature was added in #6.
For whatever reason this update seems to have broken this driver for some of the chips. I have verified it still works on the ESP32-S2, but it has stopped working on the ESP32-C3 and ESP32-S3 for me. I do not have an ESP32 board with a neopixel on it so I cannot test this.
This obviously needs to be fixed.
I'm specifically interested in the #[ram]
and #[interrupt]
attributes that are generally implemented by the procmacros
crate. An example of this can be seen in the esp32-hal repository.
I imagine this may be tricky to get working for all chips, so it's possible more than one implementation may be necessary. I have not looked into this enough to know whether or not that is the case at this time.
We need to re-export more of esp-hal-common
since that shouldn't be used directly
Background:
When implementing https://github.com/bjoernQ/ps2keyboard-esp32c3 I discovered I need to import a few things that are actually in esp-hal-common
and are not re-exported:
use esp_hal_common::{Event, OpenDrain, Output, Pin};
The stack walking code isn't specific to esp's, only the printing methods. This crate maybe useful to other chip vendors if we had a custom printing feature.
This isn't something we'll implement but maybe a good contribution from the community :).
This is a tracking issue for the remaining communication protocols. With these implemented we should be able to interact with a vast array of sensors and external peripherals.
In the first iteration it would be sufficient to just configure all clocks and freeze them
The Hal currently lacks a generic pin type. Other HAL's such as the nrf-hal use a degrade method to obtain a generic pin. While working on the SpiDevice implementation the lack of a generic pin makes it so that I can't have a method take a type of pin2 and then take a type of pin3 for example. Is there a reason that a generic pin doesn't already exist?
Some of the APIs that just get straight ported from ESP32-HAL have a potential of being used wrong.
e.g. one could try to use ADC without ever calling enable_pin
or for the (not yet merged) LEDC one could call getChannel
with different pins but the same channel
In many places there are runtime checks but we can try to make use of the type-system to turn them into compile-time errors where possible
get_channel
multiple times with the same channel and different PINsAnd we also need to pass in the frozen clock settings we will introduce in #44 for that - not sure if we want or should give the user the freedom to choose the clock source then
That would reduce the PIN usage in https://github.com/bjoernQ/ps2keyboard-esp32c3 from 4 to 2
Maybe relevant
Also interesting:
https://github.com/stm32-rs/stm32f4xx-hal/blob/cb7cd4e4ad63a0b72e6711b720b1d29ff2db3063/src/gpio/hal_02.rs#L69-L78
The code in https://github.com/esp-rs/esp-hal/blob/43c8f34e5f532bf6edd5cc23fef8e27c7a60c571/esp32-hal/examples/gpio_interrupt.rs describes the following setup:
level1_interrupt
function which clears the interrupt and prints to serialThis raises the following questions already:
I have taken the example code and tried to adapt it. However, I couldn't get it to work as I intended.
We have the following board based on the ESP32 D1 mini module:
https://github.com/das-labor/badge-2021
This board has 4 buttons with debounce circuits each (see also the schematics, linked in README). Two are push buttons and two more are part of joysticks. They are routed to GPIOs 0, 2, 5, and 12.
Note: Those buttons work just fine with MicroPython, checking their level in a loop.
I have iterated over multiple attempts, starting from the example. See das-labor/badge-2021-rs@e13aa27 and also the history before that commit.
What I expected to work:
level10_interrupt
So far I can never see the level10_interrupt
executed. However, even if not enabled, I do see the level1_interrupt
executed, and it only works for one button, IO0. If listening to low level instead of listening on edge, it is constantly triggered without pressing the button.
This is all quite confusing. Any ideas would be highly appreciated. 🙏
I wanted to play around with interrupt timers on esp32c3 and I bumped into an issue with the dependency on esp-hal-common.
Following the timer_interrupt.rs example in esp32c3-hal I need to use
esp-hal-common::Timer
. This example being located inside the esp-hal
crate, a path dependency can be easily specified (from Cargo.toml):
[dependencies.esp-hal-common]
path = "../esp-hal-common"
features = ["esp32c3"]
However, from my app that is a different crate, how can I specify this dependency?
Wouldn´t if make sense to have esp32c3-hal
re-export esp-hal-common
? The user does not need to know about the existence of esp-hal-common
I guess.
It's that job nobody wants to do!
This is a long-term project, as there's not much point documenting drivers until their interfaces are relatively stable.
Each HAL package should have top-level documentation at the very least. Peripheral drivers should additionally be documented, preferably even containing simple examples as doc tests.
Beginning with module-level documentation, here are all modules which are currently exported by any of the chip-specific HAL packages:
Our SPI implementation currently expects MISO, and CS pins. Those should be optional
How do we plan on handling time types in this new hal, seems currently we are just using a u32 for frequency etc (perhaps not a bad thing). Historically each hal defined its own time types, but nowadays there are a couple of external crates that will do the job.
Currently we use macros nested multiple levels for implementing GPIO.
We should reduce that since it's not convenient to work with (on the implementation side).
Ideally this
Note: We shouldn't touch this before #55 since that one will introduce one more macro to get rid of
Currently we are defaulting to direct booting over using a second-stage bootloader. The bootloader method can be used by enabling the normalboot
feature.
normalboot
?This feature was added in #6.
Currently smart_leds_adapter
assumes the pulse_control
is configured to 40Mhz - easiest solution would be to take a rate which the user should have configure for the pulse_control
The ESP32-S3 also allows the use of "direct boot" mode, so we should support this like we do for the ESP32-C3.
There is a HAL (more of a POC actually) for the ULP RiscV coprocessor of esp32s2 and (in future) esp32s3, that lives here.
This is a "mini" HAL as it exposes a single peripheral - the RTC pins (and an impl of the embedded-hal
Delay trait), but this is still quite useful as the alternative is either to use unsafe code with hard-coded memory addresses (the ones where the pin-related registers are mapped) or (a bit better but still not perfect) to use the register definitions from the upcoming esp32s2/esp32s3 PAC crates.
The need for a "mini" HAL for ULP (RTC gpio pins + a delay utility really) was recognized a bit by the ESP-IDF here in that they have something similar for C code.
Since the ULP HAL is really bare metal, I think it should either live in this crate (likely feature-gated by a ulp
feature), or in a separate crate - say - esp32-ulp-hal
. Any preferences?
In any case it should depend on the upcoming bare-metal PAC crates for register definitions.
Additionally, the current "mini" HAL does need a startup code in RiscV32 assembly, which also currently lives in the esp-idf-hal
here, as well as a startup code in Rust, that lives here. I'm not really sure where that assembly + Rust code belongs, perhaps in some sort of "rt" crate (esp32-ulp-rt
?), but we need to find it a new home as well.
Finally, none of the above is very urgent, but ideally we should solve it sooner or later.
At least writing to it should be supported
With the upcoming release of critical-section
, default implementations for each arch are being removed. Instead, they should be implemented by the HALs/PAC on a per chip basis. This allows critical sections to be correctly implemented for dual-core chips, and or take advantage of hardware semaphores instead of spin locking.
A note on Xtensa implementations, we should not be disabling interrupts for our critical sections, this means the waiti
instruction will never return. Instead, we should be raising the PS.INTLEVEL
accordingly. See esp-rs/xtensa-lx#20.
The new release isn't out yet, but we should track the implementations we'll need here.
It should be possible for these to coexist with the existing 0.2.7
implementations. I have already begun work on this.
EDIT: This crate has been broken up into multiple crates, the task list below has been updated to reflect this. Not all new crates are included, they will be added as needed.
[email protected]
embedded_hal::delay::DelayUs
embedded_hal::digital::InputPin
embedded_hal::digital::OutputPin
embedded_hal::digital::StatefulOutputPin
embedded_hal::digital::ToggleableOutputPin
embedded_hal::i2c::I2c
embedded_hal::pwm::SetDutyCycle
embedded_hal::serial::Write
embedded_hal::spi::SpiBus
embedded_hal::spi::SpiDevice
[email protected]
embedded_hal::serial::Read
embedded_hal::serial::Write
embedded_hal::spi::FullDuplex
[email protected]
embedded_can::blocking::Can
embedded_can::nb::Can
Currently the feature is always activated as soon as a target is activated (which is always the case).
This feature must be controllable by a feature of the chip-specific HALs and not activated by default.
The example should not be build when the feature is not activated. That should be doable by something like this
[[example]]
name = "hello_rgb"
required-features = ["smartled"]
Currently we place interrupt vectors and handlers in RAM. We should have an optional feature to place them in IRAM
We should have ways to start and run code on the second core on dual-core chips
Most embedded Rust HAL's only need to worry about single core access to peripherals. Even chips with dual core, such as the stm32h7 series only support one of the two cores: https://github.com/stm32-rs/stm32h7xx-hal. What does this mean? Interrupt free critical sections are not enough to guarantee mutual exclusion on multicore systems.
In the original esp32-hal, we used spin locks internally - this is also what esp-idf does too. Perhaps there is a better way, and if there isn't how can we add spin locks cleanly in this new hal.
The current interrupt implementation is quite currently low level. The PAC's we generate also generate a table of Peripheral interrupts with handlers held in static
storage. We should be able to provide a higher level of abstraction such that we could declare an interrupt for a peripheral with a simple proc-macro. For example
#[interrupt] // delcare the following function as a interrupt handler for GPIO
fn GPIO() {
// stuff
}
I have a good idea of how to handle this in Xtensa, as it's something we did in the original esp32-hal. The idea is that internally the hal will claim all interrupt levels from xtensa_lx_rt (here) at which point any interrupt goes to a single handler where the status is checked. From there it's easy to get the interrupt number and call the corresponding peripheral handler.
I am less sure of the best approach on RISCV, something similar to the above could work. Perhaps we reserve 15 CPU interrupts, one for each of the priority levels and do the same approach. This then leaves the other 17 interrupts available for direct use which will result in slightly lower interrupt latency.
I am more than happy to take a stab at implementing this but I would appreciate some feedback on the design.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.