Giter Site home page Giter Site logo

pslab-firmware's Introduction

PSLab Firmware

This repository contains firmware for the Pocket Science Lab (PSLab) open hardware platform. Hardware version 5 and 6 are supported.

Build Status Gitter Mailing List Twitter Follow

Pocket Science Lab

The PSLab provides an array of test and measurement instruments for doing science and engineering experiments. Its built-in instruments include an oscilloscope, a waveform generator, a frequency counter, programmable voltage and current sources, and a logic analyzer. The PSLab also has UART, I2C, and SPI buses, via which external devices can be connected and controlled.

The PSLab is a fully open device, and FOSSASIA provides a complete hardware and software stack under open source licenses:

Buy

Get in touch

Dependencies

The following tools are required to build the firmware:

  • xc16 compiler
  • cmake

Building

This project is built with CMake. After cloning this repository, you must first initialize and update the toolchain submodule:

git submodule init
git submodule update

This will populate the external/cmake-microchip directory, after which the firmware can be built:

mkdir build
cd build
cmake ..
make

This will create a build artifact in the build directory: pslab-firmware.hex.

Flashing

The firmware can be flashed over USB or by using a programmer such as the PICkit3.

Over USB

Firmware can be flashed over USB if the device already has the bootloader installed.

Flashing the firmware requires the pslab-python library. See pslab-python for installation instructions.

Follow these steps to flash new firmware:

  1. If using PSLab v5, see Entering bootloader mode on PSLab v5

  2. Press and hold the 'BOOT' button

  3. Press the 'RESET' button

    1. The 'Status' LED should start blinking, indicating that the device is in bootloader mode

    2. Release the 'BOOT' button

  4. Run pslab flash --port <portname> firmware.hex

  5. After flashing is complete, reset or power cycle the device

Using a programmer

Warning If your device contains a bootloader, flashing just the firmware HEX with a programmer will OVERWRITE the bootloader. If for some reason you are unable to flash over USB, it is a better idea to first create a combined HEX file containing both the bootloader and the firmware and flash that, rather than flashing the pure firmare HEX. See the bootloader repository for instructions on how to create a combined HEX.

Flashing with a programmer requires the mdb.sh script, which is distributed as part of Microchip's MPLAB-X software suite. On Linux, the default installation path for mdb.sh is /opt/microchip/mplabx/<version>/mplab_platform/bin/mdb.sh. This script is used to run the file flash.mdbscript, located in the repository root. Before following the below steps, you may need to modify flash.mdbscript depending on which programmer you are using and the location of the firmware HEX.

  1. Disconnect the device from any power source
  2. Connect the programmer to the device's ICSP header
  3. Power on the device via USB
  4. Run mdb.sh flash.mdbscript
  5. Disconnect the programmer

Entering bootloader mode on PSLab v5

The PSLab v5 lacks the BOOT button which is used to enter bootloader mode on the v6. The pin which is connected to the BOOT button on the v6 is present, however. It is therefore possible to enter bootloader mode on the v5 by following these steps.

Note The PSLab v5 does not come with the bootloader preinstalled. These steps will have no effect unless you have already installed the bootloader as described here.

  1. With the USB port to the top left of the board, the 5:th pin on the MCU's left side is the BOOT pin, counting from the top. Immediately below it (6:th from the top) is a conveniently located GND pin: How to enter bootloader on PSLab v5

  2. Bridge these pins by touching both simultaneously with a small piece of metal, such as the tip of a jump wire or a paper clip.

  3. Reset or power cycle the device. The v5 lacks a RESET button, but you can soft-reset it through pslab-python:

    import pslab
    pslab.ScienceLab().reset()
  4. The BOOT and GND pins must be bridged when the reset / power cycle happens. If you did it right the SYSTEM LED will start blinking, indicating that the PSLab is in bootloader mode.

Repository structure

📦pslab-firmware
 ┣ 📂src                        # PSLab firmware source code
 ┃ ┣ 📂bus                      # Communication specific source files
 ┃ ┃ ┣ 📜 ...
 ┃ ┃ ┗ 📜i2c.c
 ┃ ┣ 📂helpers                  # Supplementary functions
 ┃ ┃ ┣ 📜 ...
 ┃ ┃ ┗ 📜version.c
 ┃ ┣ 📂instruments              # Instrument specific source files
 ┃ ┃ ┣ 📜 ...
 ┃ ┃ ┗ 📜multimeter.c
 ┃ ┣ 📂registers                # PIC specific register entry files
 ┃ ┃ ┣ 📂comparators
 ┃ ┃ ┃ ┣ 📜 ...
 ┃ ┃ ┃ ┗ 📜ic1.c
 ┃ ┃ ┣ 📂 ...                   # includes converters, memory, system
 ┃ ┃ ┣ 📂timers
 ┃ ┃ ┃ ┣ 📜 ...
 ┃ ┃ ┃ ┗ 📜tmr1.c
 ┃ ┣ 📂sdcard                   # SD Card specific file handling source files
 ┃ ┣ 📜 ...
 ┃ ┣ 📜main.c                   # Entry point to PSLab Core
 ┃ ┣ 📜commands.c               # Entry point to function implementations
 ┣ 📂external
 ┃ ┣ 📂cmake-microchip          # Toolchain submodule
 ┣ 📜CMakeLists.txt
 ┣ 📜flash.mdbscript
 ┣ 📜LICENSE
 ┗ 📜README.md

pslab-firmware's People

Contributors

bessman avatar cloudypadmal avatar hrushi20 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

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

pslab-firmware's Issues

Port TIMING functions

  • GET_TIMING (#126)
  • TIMING_MEASUREMENTS (#126)
  • INTERVAL_MEASUREMENTS (#126)
  • CONFIGURE_COMPARATOR
  • START_ONE_CHAN_LA (#122)
  • START_ALTERNATE_ONE_CHAN_LA (#122)
  • START_TWO_CHAN_LA (#122)
  • START_THREE_CHAN_LA (#122)
  • START_FOUR_CHAN_LA (#122)
  • STOP_LA (#122)
  • GET_INITIAL_DIGITAL_STATES (#122)
  • FETCH_LONG_DMA_DATA (#122)
  • FETCH_INT_DMA_DATA (#122)

API Documentation

Considering the possibility of using a proper API documentation tool to improve firmware documentation.

  • doxygen
  • readthedocs
  • swagger.io
  • ...

Implement an unlock code for flash writing

In order to prevent inadvertent writes to non-volatile flash storage in the program memory area, and unlock sequence such as 0xAA55AA or some such string must be prepended.

Compatibility issues with MPLab IDE v5.45

Deprecated packages had been now removed from MPLab IDE and the current build fails. The error log is as follows.

PSLAB_UART.c: In function 'initUART':
PSLAB_UART.c:50:5: warning: implicit declaration of function 'Nop'
/tmp/ccukwRIO.00008891.s: Assembler messages:
/tmp/ccukwRIO.00008891.s:310: Error: rcall cannot follow a REPEAT.
/tmp/ccukwRIO.00008891.s:581: Error: rcall cannot follow a REPEAT.
make[2]: *** [nbproject/Makefile-default.mk:169: build/default/production/PSLAB_UART.o] Error 255
make[2]: *** Waiting for unfinished jobs....
proto2_main.c: In function 'main':
proto2_main.c:111:25: warning: '_flash_helper1' is deprecated (declared at /opt/microchip/xc16/v1.60/bin/bin/../../support/generic/h/libpic30.h:393): consider migrating to Microchip Code Configurator https://www.microchip.com/mcc
proto2_main.c:116:29: warning: '_flash_helper10' is deprecated (declared at /opt/microchip/xc16/v1.60/bin/bin/../../support/generic/h/libpic30.h:411): consider migrating to Microchip Code Configurator https://www.microchip.com/mcc
proto2_main.c:1095:31: warning: assignment makes pointer from integer without a cast
proto2_main.c:1102:31: warning: assignment makes pointer from integer without a cast
PSLAB_NRF.c: In function 'nRF_Setup':
PSLAB_NRF.c:39:5: warning: implicit declaration of function 'Nop'
Function.c: In function 'read_all_from_flash':
Function.c:308:5: warning: implicit declaration of function 'Nop'
Function.c: In function 'load_to_flash':
Function.c:331:5: warning: '_flash_helper1' is deprecated (declared at /opt/microchip/xc16/v1.60/bin/bin/../../support/generic/h/libpic30.h:393): consider migrating to Microchip Code Configurator https://www.microchip.com/mcc
Function.c:336:9: warning: '_flash_helper10' is deprecated (declared at /opt/microchip/xc16/v1.60/bin/bin/../../support/generic/h/libpic30.h:411): consider migrating to Microchip Code Configurator https://www.microchip.com/mcc
Measurements.c: In function 'set_cap_voltage':
Measurements.c:22:5: warning: implicit declaration of function 'Nop'
proto2_main.c: At top level:
proto2_main.c:1383:1: warning: '_FUID0' definition has been deprecated: consider migrating to #pragma config
proto2_main.c:1383:1: warning: '_FUID1' definition has been deprecated: consider migrating to #pragma config
proto2_main.c:1383:1: warning: '_FUID2' definition has been deprecated: consider migrating to #pragma config
proto2_main.c:1383:1: warning: '_FUID3' definition has been deprecated: consider migrating to #pragma config
make[1]: *** [nbproject/Makefile-default.mk:91: .build-conf] Error 2
make: *** [nbproject/Makefile-impl.mk:39: .build-impl] Error 2
make[2]: Leaving directory '/home/Padmal/FOSSASIA/pslab-firmware/PSLab_Original'
make[1]: Leaving directory '/home/Padmal/FOSSASIA/pslab-firmware/PSLab_Original'

BUILD FAILED (exit value 2, total time: 4s)

CH1/CH2 gain not working

OSCILLOSCOPE_SetPGAGain takes the channel number as its first parameter over serial, with 1 referring to CH1 and 2 referring to CH2. The received value is currently interpreted directly as a tSPI_CS, which is incorrect. tSPI_CS maps:

1 -> SPI_CH2
2 -> SPI_PS (power source)

OSCILLOSCOPE_SetPGAGain therefore selects the wrong chip.

When reading the channel from serial, OSCILLOSCOPE_SetPGAGain should choose the correct tSPI_CS member, i.e.:

1 -> SPI_CH1
2 -> SPI_CH2

Logic analyzer: Trigger mode is not applied until next capture

trigger_mode can be one of

    EVERY_SIXTEENTH_RISING_EDGE = 5
    EVERY_FOURTH_RISING_EDGE = 4
    EVERY_RISING_EDGE = 3
    EVERY_FALLING_EDGE = 2
    EVERY_EDGE = 1
    DISABLED = 0

However, when calling e.g. start_1chan_LA with trigchan & 7 equal to any of these, the capture does not always trigger on the correct edge type. Subsequent captures do trigger on the correct edge type. The same is true for start_2chan_la and start_4chan_LA.

For clarity, this is only the case for trigger_mode, which controls when to start capturing edges, not mode, which controls which type of logic event to capture. The latter works correctly, as far as I can tell.

Pulse counter does not reset timer prescaler before it starts counting

The pulse counter uses timer2 to count pulses on a digital input. Some other functions also use timer2, notably the logic analyzer in four channel mode, which supports setting a prescaler to slow down the timer.

If a prescaler is set by the logic analyzer, the pulse counter will count slower than expected. It should reset the timer2 prescaler before it starts counting.

PSLab v5: Device does not finish boot while PV1 or PV2 is connected to VOL

My PSLab v5 does not boot properly as long as either pin PV1 (Voltage 1) or pin PV2 (Voltage 2) is connected to pin VOL (Voltmeter). The green SYSTEM LED does not light up. The computer recognizes the device, but it is not possible to open a connection, e.g. via the Python library ("Device not found.")

Booting the device finishes as soon the connection is removed (no power cycle required). The SYSTEM LED lights up and it is possible to connect to the device.

I am not sure if it is a firmware issue or hardware issue.

Oscilloscope: Wait indefinitely for trigger

Moving fossasia/pslab-python#61 here since it is a feature request that requires firmware support.

asitava1998

Description
We can add the various types of trigger modes found in the oscilloscopes used in Labs. They are as follows:-
A. Auto - This trigger mode allows the oscilloscope to acquire a waveform even when it does not detect a trigger condition. If no trigger condition occurs while the oscilloscope waits for a specific period (as determined by the time-base setting), it will force itself to trigger.
B. Normal - The Normal mode allows the oscilloscope to acquire a waveform only when it is triggered. If no trigger occurs, the oscilloscope will not acquire a new waveform, and the previous waveform, if any, will remain on the display.
C. Single - The Single mode allows the oscilloscope to acquire one waveform each time you press the RUN button, and the trigger condition is detected.
D. Scan – The Scan mode continuously sweeps waveform from left to right.
I can implement these in the oscilloscope UI and these would help to make it more elaborate.

jithinbp

Currently, the indefinite wait-for-trigger option is not available. Which means only the No-trigger, Auto-trigger and scan modes are available.
I will raise this as a firmware issue.

asitava1998

@jithinbp Yes sure. Even I was facing a problem when I was testing my code with a function generator and the PSLab as an oscilloscope.

Logic analyzer: DMA puts values in buffer before being triggered

First, some nomenclature:

Trigger condition refers to a logic level change which is used to trigger the logic analyzer to start collecting data. It can be, for example, "rising edge" or "falling edge".

Event condition refers to a logic level change which the logic analyzer collects, nominally after being triggered. It can be, for example, "every edge", "rising edge", "falling edge", or "four rising edges".

Currently, the event condition will be detected even if it occurs before the trigger condition, and the current value of the timer (zero) will be copied to the buffer. This means that the first captured timestamp may be erroneous.

This can happen if, for example the trigger condition is "falling edge" and the event condition is "any edge". If a rising edge occurs before a falling edge, the captured timestamps will begin with two zeros, whereas only one of them should be there.

Feature: SD Card support library

Expected: Firmware needs to support SD Card read/write functions in PSLab-v6 design

Actual: Current firmware doesn't have any functionality to support this feature

I would like to add required library files and I will refer this open source FatFS library which comes with a GNU GPL licence. Since I don't have the device with an SD card mounted, I would try out the code with existing PSLab device and an external SD Card Module connected with SPI protocol. Once that is done, @CloudyPadmal, could you please check it with the new prototype and provide feedback?

research on FreeRTOS

For richer applications such as autonomous data logging, it might make sense to define abstraction layers for PSLab's specific features and wrap them in FreeRTOS, while offering another adapter for use on desktop (i.e., using the current command API). It might even be possible to have both at the same time.

See https://github.com/MicrochipTech/freeRTOS-PIC24-dsPIC-PIC32MM for Microchip's FreeRTOS demos.

In addition, we could drive the ESP Wi-Fi module through a serial driver such as https://github.com/jameswalmsley/FreeRTOS/tree/master/FreeRTOS/Demo/PIC24_MPLAB/serial.

Rename device firmware to PSLab

The current device ID is CSpark-SE.P.1.0. This needs to be changed to PSLab.

There will be issues in PSLab desktop app when changing the name as the older devices will still have CSpark-SE.P.1.0 as their device ID and the byte size is different in two names.

Implement entry point for functional blocks

When user turns on PSLab device, we should have two paths for them to move forward

  • Normal mode
  • Standalone mode

Implement a logical pathway to manage these two path entries depending on requirements.

  • Normal mode: UART communication
  • Standalone mode: read from SD card and set instruments

Logic analyzer: Cannot trigger on ID1 in single channel mode

In single channel logic analyzer mode, the trigger is configured via two values:

  • trigger_channel selects which channel to trigger on. It is 0 for ID1, 1 for ID2, 2 for ID3, etc.
  • trigger_mode selects the type of logic event to trigger on. 0 disables the trigger, 1 triggers on falling edge, 2 triggers on rising edge.
    These values are sent to the device packed into a single byte, as ((trigger channel << 4) | trigger mode).

However, due to a bug in the firmware code, it is currently not possible to trigger on ID1:

if (location & 7) {

((trigger_channel << 4) | trigger_mode) & 7 is 0 for trigger_channel == 0. Since the integer referring to ID1 is 0, the above line will evaluate as False if ID1 is selected as trigger channel.

bootloader and upgrade feature

PSLab bootloader

prerequisites

Microchip already offers source code for a bootloader. See their release notes: https://ww1.microchip.com/downloads/en/DeviceDoc/release_notes_bootloader_v1_18_4.pdf

regions/sections in flash

This is only an example. The exact sizes depend on the sizes of bootloader and
the application.

Questions:

  • What is the size of the current application? How is the memory arranged?
    => determines the space left for the bootloader
  • What size can we expect from the bootloader? It should be small and leave as
    much space to the application as possible.

0x000000...0x01ffff 0x020000...0x04ffff

[--- bootloader ---][------ application ------]

functionality

  • loader: load and run application from specific section in flash
  • recovery: drop to loading application from UART in case of boot failure
  • upgrade: in application, offer a command to reflash from UART

Reflashing through PICKit is always possible, regardless.

possible ideas

  • trigger recovery through a hardware button / switch => done in application
  • hardware switch always triggers recovery on boot (press + hold button when plugging in)

Power supply not working

Problem Description

The power supply pins (PCS, PV1, PV2, PV3) on the new prototype are not working. I recall we changed the chip driving the power supply, correct? Do we need to update the firmware to make the new driver chip work?

Port COMMON functions

  • GET_CTMU_VOLTAGE (#123)
  • GET_CAP_RANGE (#135)
  • START_CTMU (#123)
  • STOP_CTMU (#123)
  • GET_CAPACITANCE (#104)
  • START_COUNTING (#115)
  • FETCH_COUNT (#115)
  • GET_HIGH_FREQUENCY (#126)
  • GET_ALTERNATE_HIGH_FREQUENCY (#126)
  • GET_FREQUENCY (#126)
  • GET_VERSION (#75)
  • RETRIEVE_BUFFER (#101)
  • CLEAR_BUFFER (#123)
  • FILL_BUFFER (#123)
  • SETRGB (#125)
  • SETRGB2 (#125)
  • SETRGB3 (#125)
  • READ_PROGRAM_ADDRESS
  • WRITE_PROGRAM_ADDRESS
  • READ_DATA_ADDRESS (#114)
  • WRITE_DATA_ADDRESS (#114)
  • READ_LOG
  • RESTORE_STANDALONE (#114)

GitHub Actions

Integrate CI into this repository to build the firmware changes when a PR is made and merged into main branch (for now it will be bootloader branch)

Port ADC functions

  • CAPTURE_12BIT
  • CAPTURE_ONE (#101)
  • CAPTURE_TWO (#107)
  • CAPTURE_THREE (#107)
  • CAPTURE_FOUR (#107)
  • SET_HI_CAPTURE
  • SET_LOW_CAPTURE
  • MULTIPOINT_CAPACITANCE
  • PULSE_TRAIN
  • CAPTURE_DMASPEED (#110)
  • SET_CAP (#104)
  • CONFIGURE_TRIGGER (#108)
  • GET_CAPTURE_STATUS (#101)
  • SET_PGA_GAIN (#108)
  • SELECT_PGA_CHANNEL (Not used in python. There's no good use to select a PGA channel to say.)
  • GET_VOLTAGE (#79)
  • GET_VOLTAGE_SUMMED (#92)
  • GET_CAPTURE_CHANNEL (Not used in python. Can use BUFFER_FetchLong and BUFFER_FetchInt instead)

Add info/documentation to Readme and addition docs in folder /docs and documentation in folder /docs/img/

Let's follow the same structure of documentation as in our software projects.

  • Please add additional information into Readme.md e.g. chat channel, communication, project structure, best practices and images.
  • Also add detailed information how to compile and upload the firmware (How to get people started who have never done this before)
  • Rename reference docs to docs and provide pdf data in md files.
  • Keep documentation images for the project in /docs/img/.
  • Add information on the goal of the different hardware version and state meaning of abreviations (what does MPLab stand for)

A good example for documentation in a repository is here: https://github.com/fossasia/open-event-orga-server

functions.c file is not visible in project tree

functions.c file is not available in the project tree. The variables and prototype methods need to be adjusted accordingly before adding it to the configuration.xml to avoid build errors.

Use #pragma in clock definition bits

Currently the oscillator selection bits settings are depreciated and they need to be set using #prama tags.

/* PLL using external oscillator. */
_FOSCSEL(FNOSC_FRC & IESO_OFF); //Start up using internal oscillator
_FOSC(FCKSM_CSECMD & OSCIOFNC_OFF & POSCMD_XT); // enable failsafe osc, use external crystal
//_FOSC(FCKSM_CSECMD & OSCIOFNC_ON & POSCMD_EC ); // For Non-passive , driving clocks.

Oscilloscope: 12-bit trigger support

In the old firmware there is an attempt to implement 12-bit resolution for multichannel capture, but it doesn't work for an unknown reason. It should be possible to get it working in the new firmware.

Additional bus functions

  • SEND_SPI8_BULK (#137)

    Currently we only have SEND_SPI8 which send a single (8-bit) word. Sending array of words need sending repeated SPI_HEADER and SEND_SPI8 before each word.
    Like [SPI_HEADER][SEND_SPI8][word_out_0][word_in_0][ACK][SPI_HEADER][SEND_SPI8][word_out_1][word_in_1][ACK]...
    By SEND_SPI8_BULK we can avoid sending repeated SPI_HEADER and SEND_SPI8.
    Like [SPI_HEADER][SEND_SPI8_BULK][count][word_out_0][word_in_0][word_out_1][word_in_1]...

  • SEND_SPI16_BULK (#141)

    Same as SEND_SPI8_BULK with (16-bit) word.

  • WRITE_SPI8_BULK (#137)

    For writing only, we can also avoid [word_in_\d+].
    Like [SPI_HEADER][WRITE_SPI8_BULK][count][word_0][word_1]....

  • WRITE_SPI16_BULK (#141)

    Same as WRITE_SPI8_BULK with (16-bit) word.

  • READ_SPI8_BULK (#137)

    For reading only, we can avoid [word_out_\d+].
    Like [SPI_HEADER][READ_SPI8_BULK][count][word_0][word_1]....

  • READ_SPI16_BULK (#141)

    Same as READ_SPI8_BULK with (16-bit) word.

  • I2C_SEND_BULK (Addressed in #121)

    Currently we only have I2C_SEND_BURST which send a single byte. Sending array of bytes need sending repeated I2C_HEADER and I2C_SEND_BURST before each byte.
    Like [I2C_HEADER][I2C_SEND_BURST][data_0][I2C_HEADER][I2C_SEND_BURST][data_1]...
    By I2C_SEND_BULK we can avoid sending repeated I2C_HEADER and I2C_SEND_BURST.
    Like [I2C_HEADER][I2C_SEND_BULK][count][data_0][data_1]...

  • I2C_READ (Addressed in #121)

    Currently we use I2C_READ_MORE and I2C_READ_END which reads byte by byte.
    By using I2C_READ we can avoid sending repeated I2C_HEADER, I2C_READ_MORE and I2C_READ_END.
    Like [I2C_HEADER][I2C_READ][count][data_0][data_1]...

  • START_SPI (#137)
  • STOP_SPI (#137)
  • SET_SPI_PARAMETERS (#137)

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.