Skip to content

Commit 7019638

Browse files
committed
docs(chips-api): add 7-segment display tutorial from google docs
This tutorial was taken from the existing Google Docs version, just tidied up and converted into markdown to integrate with the site. Unfortunately the Google Docs version has been defaced - it makes it hard (or impossible) to follow along.
1 parent 1595ff8 commit 7019638

File tree

3 files changed

+264
-1
lines changed

3 files changed

+264
-1
lines changed

docs/chips-api/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Custom Chips are usually written in C, but you can use any language that compile
2222
## Tutorials
2323

2424
- [Video Tutorial](https://youtu.be/yzdCS3A4DvU) - Three chip examples in 15 Minutes
25-
- [Step-by-step blog tutorial](https://link.wokwi.com/chips-api-tutorial) - Create a 7-segment driver chip from scratch
25+
- [Step-by-step blog tutorial](tutorial-7seg.md) - Create a 7-segment driver chip from scratch
2626
- [I2C chip tutorial](https://youtu.be/BS_uTqz3zik) - Coding a PCF8575 I/O expander from scratch
2727

2828
## Getting started

docs/chips-api/tutorial-7seg.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
title: 'Tutorial: 7-segment display'
3+
sidebar_label: 'Tutorial: 7-segement display'
4+
---
5+
6+
# Tutorial: 7-segment display
7+
8+
## Introduction
9+
The Custom Chips API allows you to create new simulation models and behaviors that extend the functionality of Wokwi.
10+
You can create new sensors, displays, memories, testing instruments and even simulate your own hardware.
11+
12+
Custom chips are usually written in C, and have an accompanying JSON file that describes the pinout, as well as any
13+
input values for the chip (e.g. the current temperature for a temperature sensor chip). Other languages are also
14+
available - more on that later.
15+
16+
In this tutorial we'll learn how to get started with the Chips API by implementing a simple 7-segment controller chip.
17+
The chip will get a character (0-9 or A-F) via UART interface, and will display it on a
18+
[7-segment display](https://docs.wokwi.com/parts/wokwi-7segment#using-the-7-segment-display).
19+
20+
Let's get started!
21+
22+
## The pinout
23+
Before we dive into the code, let's define the pinout for the chip:
24+
25+
| Name | Type | Function |
26+
| :- | :- | :- |
27+
| VCC | Power | Supply voltage |
28+
| GND | Power | Ground |
29+
| RX | Input | UART |
30+
| SEG_A | Output | 7-segment |
31+
| SEG_B | Output | 7-segment |
32+
| SEG_C | Output | 7-segment |
33+
| SEG_D | Output | 7-segment |
34+
| SEG_E | Output | 7-segment |
35+
| SEG_F | Output | 7-segment |
36+
| SEG_G | Output | 7-segment |
37+
38+
Our chip will have a total of 10 pins: two power supply pins, one UART input pin (RX), and 7 output pins to drive the
39+
7-segment display. For simplicity, we'll assume that the 7-segment display is a common anode display, which is the
40+
default on Wokwi.
41+
42+
## The chip JSON file
43+
Now we're ready to start writing code! We'll start from an empty ESP32-C3 project:
44+
[wokwi.com/projects/new/esp32-c3](https://wokwi.com/projects/new/esp32-c3).
45+
46+
The first thing we need to do is to create a custom chip. The easiest way to go about this is to press the blue "+"
47+
button and serach for "Custom Chip". After selecting this option, type "sevseg-controller" for the chip name. Select
48+
the "C" language option.
49+
50+
Wokwi will add a green breakout board for your custom chip, and create two new files in your project:
51+
- `sevseg-controller.chip.json` - defines the pinout
52+
- `sevseg-controller.chip.c` - defines the logic for the chip
53+
54+
We'll start by editing the `sevseg-controller.chip.json` as follows:
55+
1. Change the `name` of the chip to "7 Segment Controller"
56+
2. Change the `author` of the chip to your name
57+
3. Change the `pins` array of the chip to include all the pin names:
58+
```json
59+
["VCC", "GND", "RX", "SEG_A", "SEG_B", "SEG_C", "SEG_D", "SEG_E", "SEG_F", "SEG_G"]
60+
```
61+
62+
You'll see the green breakout board updating as you make changes to the JSON file.
63+
64+
## Implementing the chip's logic
65+
Next, go to `sevseg-controller.chip.c`. This file implements the chip logic. The two important parts are:
66+
1. The `chip_state_t` struct - use it to store all the state information of your chip, together with all the objects
67+
that you create for your chips: IO pins, timers, etc.
68+
2. The `chip_init` function - Wokwi will call this function for every instance (copy) of your chip. The function should
69+
allocate memory for a new `chip_state_t` struct, initialize all the IO pins, the chip's state, and create any
70+
relevant objects (we'll see an example in a minute).
71+
72+
The default implementation does not include any state or initialization - it only prints a message saying "Hello from
73+
custom chip!". You will see this message when you start the simulation - it'll appear in a new "Chips Console" tab
74+
below the diagram.
75+
76+
We'll modify `chip_init` to perform the following actions:
77+
1. Initialize all the `SEG_x` pins as outputs
78+
2. Listen for UART data on the `RX` pin, and call a function that will update the `SEG_x` according to the character we
79+
received.
80+
81+
### Initializing the 7-segment outputs
82+
Start by adding a `segment_pins` array to the `chip_state_t` struct. This array will store a reference to the `SEG_x`
83+
output pins, so we'll need to allocate 7 items:
84+
```C
85+
typedef struct {
86+
pin_t segment_pins[7];
87+
} chip_state_t;
88+
```
89+
90+
Next, add the following code to `chip_init` (after the line that defines `chip`):
91+
```C
92+
chip->segment_pins[0] = pin_init("SEG_A", OUTPUT_HIGH);
93+
chip->segment_pins[0] = pin_init("SEG_B", OUTPUT_HIGH);
94+
chip->segment_pins[0] = pin_init("SEG_C", OUTPUT_HIGH);
95+
chip->segment_pins[0] = pin_init("SEG_D", OUTPUT_HIGH);
96+
chip->segment_pins[0] = pin_init("SEG_E", OUTPUT_HIGH);
97+
chip->segment_pins[0] = pin_init("SEG_F", OUTPUT_HIGH);
98+
chip->segment_pins[0] = pin_init("SEG_G", OUTPUT_HIGH);
99+
```
100+
101+
The code initializes each of the segment pins as an output, and sets the initial value to digital high. The 7-segment
102+
display has a common annode, so setting a segment pin high will turn that segment off. You can learn more about the
103+
`pin_init` function in the [GPIO API reference](gpio.md).
104+
105+
### Listening to UART data
106+
Add the following code to `chip_init`, right after the code that initializes the segment pins:
107+
```C
108+
const uart_config_t uart_config = {
109+
.tx = NO_PIN,
110+
.rx = pin_init("RX", INPUT),
111+
.buad_rate = 115200,
112+
.rx_data = on_uart_rx_data,
113+
.user_data = chip,
114+
};
115+
uart_init(&uart_config);
116+
```
117+
118+
The code configures the `RX` pin as an input (in the third line), and sets up a `uart_config_t` structure. This
119+
structure configures the baud rate, as well as a function that will get called whenever there is new data,
120+
`on_uart_rx_data`.
121+
122+
Also note how we set `.user_data` to `chip` - this is important, as this value will be passed as a parameter to the
123+
`on_uart_rx_data` function, providing it access to our chip's state.
124+
125+
In our case, we are only interested in receiving data, so we set `.tx` to the special `NO_PIN` value.
126+
127+
:::tip
128+
129+
To learn more about using UART in Wokwi, check out the [UART API reference](uart.md).
130+
131+
:::
132+
133+
### From UART to 7-segment
134+
For the final part of the show, we'll implement the `on_uart_rx_data` callback. Paste the following code above the
135+
definition of `chip_init`:
136+
```C
137+
const uint8_t font[] = {
138+
['0'] = 0b11000000,
139+
['1'] = 0b11111001,
140+
['2'] = 0b10100100,
141+
['3'] = 0b10110000,
142+
['4'] = 0b10011001,
143+
['5'] = 0b10010010,
144+
['6'] = 0b10000010,
145+
['7'] = 0b11111000,
146+
['8'] = 0b10000000,
147+
['9'] = 0b10010000,
148+
['A'] = 0b10001000,
149+
['B'] = 0b10000011,
150+
['C'] = 0b11000110,
151+
['D'] = 0b10100001,
152+
['E'] = 0b10000110,
153+
['F'] = 0b10001110,
154+
};
155+
156+
static void on_uart_rx_data(void *user_data, uint8_t byte) {
157+
chip_state_t *chip = user_data;
158+
uint8_t font_char = font[byte];
159+
if (font_char) {
160+
for (int bit = 0; bit < 7; bit++) {
161+
uint8_t bit_value = font_char & (1 << bit);
162+
pin_write(chip->segment_pins[bit], bit_value ? HIGH : LOW);
163+
}
164+
}
165+
}
166+
```
167+
168+
This part simply defines the "font" - it maps between a character that we receive from UART and the corresponding
169+
segments that need to be turned on. Our 7-segment display has a common anode, so 0 will ligh a segment, and 1 will turn
170+
it off.
171+
172+
The `on_uart_rx_data` is where the actual magic happens. We use the `font` array to lookup the `byte` we received over
173+
UART. If we find a match (when `font_char` is not 0), we iterate over the bits of the `font_char`, and update each
174+
segment to its corresponding bit in `font_char`.
175+
176+
That's it - we created a simple 7-segment controller chip for Wokwi!
177+
178+
## Testing the chip
179+
You can test the chip by adding a 7-segment display to the diagram, and writing it to the chip. Don't forget to write
180+
the common pin of the 7-segment display to the 3.3V or 5V pin of the ESP32-C3 board!
181+
182+
Next, wire the `RX` pin of the chip to the `TX` pin of the ESP32-C3 board. You can also wire the GND/VCC pins of the
183+
chip for good measures, even though the chip will be functional even without these pins.
184+
185+
Finally, pase the following code into `sketch.ino`:
186+
```C
187+
void setup() {
188+
Serial.begin(115200);
189+
}
190+
191+
int i = 0;
192+
void loop() {
193+
Serial.println(i, HEX);
194+
i++;
195+
if (i > 0xf) {
196+
i = 0;
197+
}
198+
delay(500);
199+
}
200+
```
201+
202+
It's a simple program that outputs all the hexadecimal values between 0 and F to the ESP32-C3's serial port - all the
203+
characters that are included in our custom chip's font. If you prefer plain C code, you can change the first line in
204+
`loop` to use `printf()` instead.
205+
```C
206+
printf("%X\n", i);
207+
```
208+
209+
When you start the simulation, you should see the 7-segment display counting from 0 to F repeatedly. Hooray!
210+
211+
Doesn't work? No worries, here's the link to the final result, so you can compare it with yours:
212+
[wokwi.com/projects/371252876830114817](https://wokwi.com/projects/371252876830114817).
213+
214+
## Under the hood
215+
How do custom chips work? What happens with the C code you write? Behind the scenes, Wokwi takes this code and compiles
216+
it into a Web Assembly module using LLVM (if you are curious, here's the
217+
[Docker container](https://github.com/wokwi/wokwi-builders/blob/main/clang-wasm/Dockerfile) that does all the magic).
218+
219+
When you run the simulation, Wokwi creates an instance of the Web Assembly module, and calls `chip_init` once for
220+
every instance of the chip in your diagram.
221+
222+
Using Web Assembly means you can write your code in a variety of languages. Currently, only C is officially supported,
223+
there are some examples of how to write custom chips with Rust, AssemblyScript and even Zig.
224+
225+
There's even a hack where we use Custom Chips to simulate Verilog: we use Yosys CXXRTL to convert your Verilog code
226+
into C++, and then
227+
[use emscripten to compile](https://github.com/wokwi/wokwi-builders/blob/main/verilog-cxxrtl/project/compile.sh)
228+
the result along with [some glue code](https://github.com/wokwi/wokwi-builders/blob/main/verilog-cxxrtl/project/main.cpp)
229+
into Web Assembly. Scary cool?
230+
231+
## Next steps
232+
If you want to dive deeper into the Custom Chips API, here are some ideas how to build on the chip we created in this
233+
tutorial:
234+
235+
- **Fix the bug!** <br/>
236+
Unfortunately, our code has a bug - some values will cause it to display garbage on the 7-segment display (try sending
237+
it a 'b'). Some bound checking can help!
238+
239+
- **Add support for common cathode 7-segment displays** <br/>
240+
You can use an additional input pin to select between common anode/cathode, or use the [Attributes API](attributes.md)
241+
to allow the user to define the type of display by editing the chip attributes in `diagram.json`.
242+
243+
- **Add another communication protocol** <br/>
244+
You can turn our 7-segment display into an [I2C](i2c.md) or an [SPI](spi.md) device.
245+
246+
- **Support multiple digits** <br/>
247+
Control a two or four digital 7-segment display! Use the [Time API](time.md) to create a timer that will quickly
248+
alternate between the digits.
249+
250+
- **Add analog input** <br/>
251+
Use the [Analog API](analog.md) to read and display an analog input value. This makes the 7-segment controller chips
252+
useful even without a microcontroller - you can connect it directly to a potentiometer or an analog sensor, and
253+
display the reading directly.
254+
255+
- **Share your chip on GitHub** <br/>
256+
By sharing your chip's code on GitHub, you can make it easy for other users to include it in their project. You can
257+
use the [inverter-chip repo](https://github.com/wokwi/inverter-chip) as a starting point - it has a
258+
[GitHub action](https://github.com/wokwi/inverter-chip/blob/main/.github/workflows/build.yaml) that automatically
259+
compiles the chips and creates a release whenever you push a tag.
260+
261+
Here's an [example for a Wokwi project](https://wokwi.com/projects/350946636543820370) that uses this chip. Note the
262+
"dependencies" section in `diagram.json` - it tells Wokwi where to look for the chip implementation on GitHub.

sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ module.exports = {
7676
],
7777
'Chips API': [
7878
'chips-api/getting-started',
79+
'chips-api/tutorial-7seg',
7980
'chips-api/chip-json',
8081
'chips-api/gpio',
8182
'chips-api/analog',

0 commit comments

Comments
 (0)