Compare commits

...

12 Commits

21 changed files with 152239 additions and 53 deletions

13
BOM.csv Normal file
View File

@ -0,0 +1,13 @@
Quantity,Manufacture Part Number,Manufacturer,Description,LCSC Part Number,Package
1,STM32F103C8,STM,Microcontroller ,,
1,SMD-5032_4P8M20pf20ppm,Zhejang Abel Elec,8Mhz Crystal,C133333,SMD-5032
1,DC-470-2.1GP,GANGYUAN,GANGYUAN DC-470-2.1GP ,C194407,
6,XKB5858-W-TP,XKB Enterprise,XKB Enterprise XKB5858-W-TP ,C381091,
4,WF06Q1002BTL,Walsn Tech Corp,10k 0603 Resistor,C305259,SMD0603
2,EWH1HM4R7D11OT,Aihua Group,4u7 leaded cap ,C105327,radial_D5P2
2,ECSS05072R2M101P00,Guangdong TOPAZ Elec Tech,2u2 leaded cap,C156723,radial_D5P2
12,PE105J2A0501,KYET,1u Polyester film cap,C390180,rectangular_7x5P5
2,0603CG200J201NT,Guangdong Fenghua Advanced Tech,20pF crystal cap,C63680,SMD0603
2,0603X106K160NT,Guangdong Fenghua Advanced Tech,10u cap,C70225,SMD0603
7,D-G080508G1-KS2,Wuxi ARK Tech Elec,LED bright green,C107402,SMD0805
1,TSA061G50-250,BRIGHT,switch ,C294501,THT
1 Quantity Manufacture Part Number Manufacturer Description LCSC Part Number Package
2 1 STM32F103C8 STM Microcontroller
3 1 SMD-5032_4P8M20pf20ppm Zhejang Abel Elec 8Mhz Crystal C133333 SMD-5032
4 1 DC-470-2.1GP GANGYUAN GANGYUAN DC-470-2.1GP C194407
5 6 XKB5858-W-TP XKB Enterprise XKB Enterprise XKB5858-W-TP C381091
6 4 WF06Q1002BTL Walsn Tech Corp 10k 0603 Resistor C305259 SMD0603
7 2 EWH1HM4R7D11OT Aihua Group 4u7 leaded cap C105327 radial_D5P2
8 2 ECSS05072R2M101P00 Guangdong TOPAZ Elec Tech 2u2 leaded cap C156723 radial_D5P2
9 12 PE105J2A0501 KYET 1u Polyester film cap C390180 rectangular_7x5P5
10 2 0603CG200J201NT Guangdong Fenghua Advanced Tech 20pF crystal cap C63680 SMD0603
11 2 0603X106K160NT Guangdong Fenghua Advanced Tech 10u cap C70225 SMD0603
12 7 D-G080508G1-KS2 Wuxi ARK Tech Elec LED bright green C107402 SMD0805
13 1 TSA061G50-250 BRIGHT switch C294501 THT

View File

@ -1,27 +1,32 @@
## Wat
![render image](doc/images/render.png)
This pcb is an audio multiplexer, meaning it can switch three analog inputs into one analog output.
<iframe src="https://myhub.autodesk360.com/ue28af833/shares/public/SH56a43QTfd62c1cd96847154d5ed1f7a373?mode=embed" width="640" height="480" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" frameborder="0"></iframe>
## Why
This project contains all design files for a **6:1 audio multiplexer** , meaning it can switch six analog inputs into one analog output.
Sometimes you just need more inputs. And this is how you do it.
It is based on the [BD3491](https://www.digikey.de/product-detail/de/rohm-semiconductor/BD3491FS-E2/BD3491FS-E2TR-ND/5720882) which does everthing related to the Audio signals.
## How
On top of switching the inputs, it is also able to condition the signal. It can
The BD3491 does all the audio switching. It combines one 6 input switch and an equalizer into a simple package.
The STM32 configures the BD3491 to switch to the specified inputs based on pressed buttons or software control.
- Apply Bass-Boosting and Treble-Boosting (essentially a crappy 3-Point Equalizer)
- Amplify the input signal by up to 20dB
- Add attenuation to the individual channels (aka. balance the left and right channel)
# serial control Syntax
Upon connecting the stm32 to a PC using USB it will register itself as a new Serial Port. The following text commands can be issued via said Serial connection (the baudrate is irrelevant):
All of these feature of the chip can be activated with the on-board STM32, which can in turn be controlled
with either on-board buttons or via it's integrated USB port. One can either use the serial protocol directly
or a [GUI written in python](gui/README.md)
- `C[number]\n` will change the input to the specified channel. The stm32 echo the command as it was understood. e.g: When Sending `C0\n` it will respond `C0\r\n` to acknowledge the channel switched to **Input 1**. When issuing a `C10` there will be no response, since 10 is out of range. *NOTE:* when using the buttons on the PCB to switch the input, the stm will also issue a `C[number]\r\n` over the serial port if it is connected to notify the PCB of the external input change.
The Board can be supplied over said USB port as well (if the primary use-case implies it beeing connected
to the PC constantly) or by applying power using a 2.1mm Barrel Jack.
- `G[number]\n` will activate an input gain of [number] in dB. It will also respond with the closest gain it can do. e.g: `G10` will result in the stm responding `G12\r\n` which means it will add a 12db input boost, which was the closest matching _valid_ input gain value. You can check the datasheet of the BD3491 to find the possible gain values if this peaks your interest :)
For more details on the pcb, check the [PCB readme](kicad/README.md)
- `L[number]\n` or `R[number]\n` will activate an attenuator in either the left or the right channel. [number] is once again in dB. It works much like the `G`-command. Setting [number] to 0 will deactivate the Attenuation.
For details on the Serial command syntax and firmware flashing instructions, check the [firmware readme](firmware/README.md)
- `B[number]\n` or `T[number]\n` adds **B**ass or **T**reble gain to the output
- `M[0/1]\n` will either mute or unmute the output
# Why
Because ?
Sometimes you just need more inputs. This is how you do it.
- `S[0/1]\n` will connect all inputs to the output

BIN
case/case.f3z Normal file

Binary file not shown.

72170
case/case.iges Normal file

File diff suppressed because it is too large Load Diff

75262
case/case.step Normal file

File diff suppressed because it is too large Load Diff

4397
doc/audioMux-brd.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 186 KiB

BIN
doc/images/render.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@ -20,9 +20,9 @@ int bd_set_gain(I2C_HandleTypeDef * handle, uint8_t gain_in_db);
uint8_t bd_set_attenuation(I2C_HandleTypeDef * handle, uint8_t right_channel, uint8_t attenuation_in_db);
// when gain == 0, cut bass boost
void bd_set_bass_boost(I2C_HandleTypeDef * handle, uint8_t gain);
uint8_t bd_set_bass_boost(I2C_HandleTypeDef * handle, uint8_t gain);
// when gain == 0, cut treble boost
void bd_set_treble_boost(I2C_HandleTypeDef * handle, uint8_t gain);
uint8_t bd_set_treble_boost(I2C_HandleTypeDef * handle, uint8_t gain);
enum BD_SOURROUND_GAIN {
BD_SOURROUND_OFF = 0,

View File

@ -37,7 +37,20 @@ extern "C" {
/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */
struct ui_state {
uint8_t oldchannel;
enum {
UI_STATE_NORMAL = 0,
UI_STATE_MUTE,
UI_STATE_SHORT,
} state;
uint8_t gain;
uint8_t bboost, tboost;
uint8_t latt, ratt;
};
extern struct ui_state ui_state; // global ui state
/* USER CODE END ET */
/* Exported constants --------------------------------------------------------*/
@ -54,8 +67,12 @@ extern "C" {
void Error_Handler(void);
void set_input(uint8_t channel);
int get_input();
void set_attenuation(uint8_t left, uint8_t right);
void set_mute(uint8_t mute);
void set_mute(uint8_t mute); //< tries to switch to muted ui state
int is_muted(); //< returns whether the ui is muted
void set_short(uint8_t shorted); //< tries to switch to shorted ui state
int is_shorted();
/* USER CODE BEGIN EFP */

35
firmware/README.md Normal file
View File

@ -0,0 +1,35 @@
# Building the Firmware
In order to build the firmware, [platformio](https://platformio.org/install/cli) must be installed.
To build the firmware, navigate to this folder using the terminal of your choice and type
```bash
platformio run
```
## Flashing the firmware
If you happen to have an stlink compatible interface (e.g the [programmer attached the nucleo-boards](https://jeelabs.org/book/1547a/])),
then (after connecting said interface to the PC and the PCB),
the firmware can be flashed with
```bash
platformio run -t upload
```
# serial control Syntax
Upon connecting the stm32 to a PC using USB it will register itself as a new Serial Port. The following text commands can be issued via said Serial connection (the baudrate is irrelevant):
- `C[number]\n` will change the input to the specified channel. The stm32 echo the command as it was understood. e.g: When Sending `C0\n` it will respond `C0\r\n` to acknowledge the channel switched to **Input 1**. When issuing a `C10` there will be no response, since 10 is out of range. *NOTE:* when using the buttons on the PCB to switch the input, the stm will also issue a `C[number]\r\n` over the serial port if it is connected to notify the PCB of the external input change.
- `G[number]\n` will activate an input gain of [number] in dB. It will also respond with the closest gain it can do. e.g: `G10` will result in the stm responding `G12\r\n` which means it will add a 12db input boost, which was the closest matching _valid_ input gain value. You can check the datasheet of the BD3491 to find the possible gain values if this peaks your interest :)
- `L[number]\n` or `R[number]\n` will activate an attenuator in either the left or the right channel. [number] is once again in dB. It works much like the `G`-command. Setting [number] to 0 will deactivate the Attenuation.
- `B[number]\n` or `T[number]\n` adds **B**ass or **T**reble gain to the output
- `M[0/1]\n` will either mute or unmute the output
- `S[0/1]\n` will connect all inputs to the output

View File

@ -64,19 +64,30 @@ int bd_set_gain(I2C_HandleTypeDef * handle, uint8_t gain_in_db) {
uint8_t bd_set_attenuation(I2C_HandleTypeDef * handle, uint8_t right_channel, uint8_t attenuation_in_db) {
if ((attenuation_in_db&0x7F) > 87)
attenuation_in_db = BD_INF_ATTENUATION;
bd_write_reg(handle, 0x21 + right_channel, attenuation_in_db | 0x80);
if (right_channel)
right_channel = 1;
bd_write_reg(handle, 0x22 - right_channel, attenuation_in_db | 0x80);
return attenuation_in_db;
}
// when gain == 0, cut bass boost
void bd_set_bass_boost(I2C_HandleTypeDef * handle, uint8_t gain) {
uint8_t bd_set_bass_boost(I2C_HandleTypeDef * handle, uint8_t gain) {
if (gain > 0x07) gain = 0x07;
bd_write_reg(handle, 0x51, (gain == 0)?(0x80):(0x00) | (gain&0x07) << 1);
return gain;
}
// when gain == 0, cut treble boost
void bd_set_treble_boost(I2C_HandleTypeDef * handle, uint8_t gain) {
uint8_t bd_set_treble_boost(I2C_HandleTypeDef * handle, uint8_t gain) {
if (gain > 0x07) gain = 0x07;
bd_write_reg(handle, 0x57, (gain == 0)?(0x80):(0x00) | (gain&0x07) << 1);
return gain;
}
void bd_set_sourround(I2C_HandleTypeDef * handle, enum BD_SOURROUND_GAIN gain) {

View File

@ -102,58 +102,92 @@ int test_button_pressed() {
#undef TEST_BUTTON
}
struct ui_state {
uint8_t oldchannel;
uint8_t mute;
} ui_state = {
struct ui_state ui_state = {
.oldchannel = -1,
.mute = 0,
.state = UI_STATE_NORMAL,
};
void set_input(uint8_t channel) {
if (ui_state.oldchannel == channel || channel >= 6)
if (ui_state.state != UI_STATE_NORMAL || ui_state.oldchannel == channel || channel >= 6)
return;
set_input_led(ui_state.oldchannel, 0);
set_input_led(ui_state.oldchannel, 0);
set_input_led(channel, 1);
// do not disturb the mute mode
if (!ui_state.mute)
{
set_input_led(channel, 1);
// invert bd inputs to match the numbers on the case
bd_set_input(&hi2c1, 5-channel);
}
// invert bd inputs to match the numbers on the case
bd_set_input(&hi2c1, 5-channel);
ui_state.oldchannel = channel;
printf("C%d\n", channel);
}
int get_input() {
return ui_state.oldchannel;
}
// only mute if the ui allows to
void set_mute(uint8_t mute) {
if(ui_state.mute == mute)
if(UI_STATE_MUTE == ui_state.state && mute)
// no state change required
return;
if (0 == (ui_state.mute = mute)) {
// figure out the state transition
if (UI_STATE_NORMAL == ui_state.state && mute) {
ui_state.state = UI_STATE_MUTE;
HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, 1);
set_input_led(ui_state.oldchannel, 0);
bd_set_input(&hi2c1, BD_INPUT_MUTE);
}
else if (UI_STATE_MUTE == ui_state.state && !mute)
{
ui_state.state = UI_STATE_NORMAL;
// hack to make reactivation of the last channel easier
uint8_t old_input = ui_state.oldchannel;
ui_state.oldchannel = -1;
set_input(old_input);
HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, 0);
}
else {
HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, 1);
set_input_led(ui_state.oldchannel, 0);
bd_set_input(&hi2c1, BD_INPUT_MUTE);
HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, 0);
}
}
printf("M%d\n", ui_state.mute?1:0);
int is_muted() {
return ui_state.state == UI_STATE_MUTE;
}
void set_short(uint8_t do_short) {
// test for possible state changes
if (UI_STATE_NORMAL == ui_state.state && do_short)
{
ui_state.state = UI_STATE_SHORT;
bd_set_input(&hi2c1, BD_INPUT_ALL);
// visualize that all inputs are shorted
for (int i = 0; i < 6; ++i)
set_input_led(i, 1);
}
else if (UI_STATE_SHORT == ui_state.state && !do_short)
{
ui_state.state = UI_STATE_NORMAL;
for (int i = 0; i < 6; ++i)
set_input_led(i, 0);
// reactivate currently selected input
uint8_t old_input = ui_state.oldchannel;
ui_state.oldchannel = -1;
set_input(old_input);
}
}
int is_shorted() {
return ui_state.state == UI_STATE_SHORT;
}
void set_attenuation(uint8_t left, uint8_t right) {
bd_set_attenuation(&hi2c1, 0, left);
bd_set_attenuation(&hi2c1, 1, right);
ui_state.latt = bd_set_attenuation(&hi2c1, 0, left);
ui_state.ratt = bd_set_attenuation(&hi2c1, 1, right);
}
@ -202,6 +236,7 @@ int main(void)
set_attenuation(0,0);
int count = 0;
int button_timeout = 0;
/* USER CODE END 2 */
/* Infinite loop */
@ -226,7 +261,8 @@ int main(void)
if (1000 < button_timeout) {
// long press, toggle mute
set_mute(!ui_state.mute);
set_mute(!is_muted());
printf("M%d\n", is_muted());
button_timeout = 0;
}

View File

@ -138,16 +138,24 @@ int parse_buffer() {
uint16_t gain;
if (next_arg_unsigned(&gain)) break;
printf("G%d\n", bd_set_gain(&hi2c1, gain));
printf("G%d\n", (ui_state.gain = bd_set_gain(&hi2c1, gain)));
}
break;
case 'L':
case 'R':
{
uint16_t att;
if (next_arg_unsigned(&att)) break;
uint8_t dir = 0;
uint8_t * save = &ui_state.latt;
printf("%c%d\n", temp, bd_set_attenuation(&hi2c1, temp == 'R', att&0xFF));
if (next_arg_unsigned(&att)) break;
if (temp == 'R') {
dir = 1;
save = &ui_state.ratt;
}
printf("%c%d\n", temp, (*save = bd_set_attenuation(&hi2c1, dir, att&0xFF)));
}
break;
case 'M':
@ -156,11 +164,46 @@ int parse_buffer() {
if (next_arg_unsigned(&mute)) break;
set_mute(mute);
printf("M%d\n", is_muted());
}
break;
case 'S':
{
uint16_t shorted;
if (next_arg_unsigned(&shorted)) break;
set_short(shorted);
printf("S%d\n", is_shorted());
}
break;
case 'B':
case 'T':
{
uint16_t boost;
if (next_arg_unsigned(&boost)) break;
printf("B%d\n", (ui_state.bboost = bd_set_bass_boost(&hi2c1, boost)));
}
break;
case 'T':
{
uint16_t boost;
if (next_arg_unsigned(&boost)) break;
printf("T%d\n", (ui_state.tboost = bd_set_treble_boost(&hi2c1, boost)));
}
break;
case '?':
{
// return the system state
printf("M%d\n", is_muted());
printf("S%d\n", is_shorted());
printf("C%d\n", get_input());
printf("B%d\n", ui_state.bboost);
printf("T%d\n", ui_state.tboost);
printf("L%d\n", ui_state.latt);
printf("R%d\n", ui_state.ratt);
}
break;
default:
printf("E\n");
}

3
gui/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"python.pythonPath": "/usr/bin/python"
}

33
gui/app.spec Normal file
View File

@ -0,0 +1,33 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['app.py'],
pathex=['/home/julian/git/audioMux/gui'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='app',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False )

BIN
gui/dist/gui_linux vendored Executable file

Binary file not shown.

77
gui/icon.svg Normal file
View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="10mm"
height="10mm"
viewBox="0 0 10 10"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="icon.svg"
inkscape:export-filename="/home/julian/git/audioMux/gui/icon/1.png"
inkscape:export-xdpi="325.12"
inkscape:export-ydpi="325.12">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="4.446261"
inkscape:cy="56.922155"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1918"
inkscape:window-height="1058"
inkscape:window-x="0"
inkscape:window-y="20"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-287)">
<circle
style="color:#000000;overflow:visible;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#298f29;stroke-width:0.9441849;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path815"
cx="5"
cy="292"
r="4.5279074" />
<text
xml:space="preserve"
style="font-size:4.23333311px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#beb3b3;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
x="2.6935086"
y="294.57178"
id="text819"><tspan
sodipodi:role="line"
id="tspan817"
x="2.6935086"
y="294.57178"
style="font-size:7.05555534px;fill:#000000;fill-opacity:1;stroke:#beb3b3;stroke-width:0.26458332px;stroke-opacity:1;">1</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
gui/icon/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

55
gui/tray.py Normal file
View File

@ -0,0 +1,55 @@
import wx
from wx import adv
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.AppendItem(item)
return item
class Tray(wx.adv.TaskBarIcon):
def __init__(self, parent):
super().__init__()
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.OnLeftDown)
self.SetIcon(wx.Icon("icon/1.png"))
self.parent = parent
def CreatePopupMenu(self):
menu = wx.Menu()
hidem = wx.MenuItem(menu, wx.ID_ANY, 'Show Window')
menu.Bind(wx.EVT_MENU, self.ShowParent, id=hidem.GetId())
menu.Append(hidem)
menu.AppendSeparator()
quitm = wx.MenuItem(menu, wx.ID_ANY, 'Quit')
menu.Bind(wx.EVT_MENU, self.OnExit, id=quitm.GetId())
menu.Append(quitm)
return menu
def CreateSelectionMenu(self):
return None
def OnLeftDown(self, event):
menu = self.CreateSelectionMenu()
if menu:
self.PopupMenu(menu)
def OnExit(self, event):
# close self and parent
wx.CallAfter(self.Destroy)
wx.CallAfter(self.parent.Destroy)
def ShowParent(self, evt):
self.parent.Show()
if __name__ == "__main__":
app = wx.App()
# create an empty frame to keep the main loop running
Tray(wx.Frame(None))
app.MainLoop()

29
kicad/README.md Normal file
View File

@ -0,0 +1,29 @@
![THE PCB](../doc/audioMux-brd.svg)
There are a total of six solderjumpers on the board:
# switching to the barrel jack as voltage supply
*JP3* and *JP4* set the source for the supply voltage.
*JP4* connects the voltage input to the barrel jack, *JP4* connects it to the USB +5V rail.
Per Default JP4 is closed and JP3 is open, making the default way of supplying voltage the USB jack.
If the Barrel Jack should be used, then **JP4 has to be opened first** by cutting the trace which shorts it by default, otherwise **the USB voltage will be shorted with the barrel jack, causing potential damage to the USB port**.
Afterwards, *JP3* can be closed with a small blob of solder.
# activating the equalizer
*JP1* and *JP2* connect the output of the Audio switching part of the Rohm chip.
When left in the default setting, they will connect them to the output jack directly, bypassing the Equalisation part of the chip completely.
By selecting the other possible path, the equalisation circuit becomes usable. For this to work,
*JP7* and *JP8* have to be closed with small solder blobs as well, since those connect the equalizers
output to the output audio jack.
# Assembling the rest
For assembly of the board, check the [documentation folder](../doc). It contains the [interactive bill of materials](../doc/interactive_placement.html),
which indicates what components go where.
Also take a look at the [BOM](../doc/BOM.xlsx) for what components can be sourced where.