- Com o objetivo de criar um computador mínimo montável em casa, foi implementado um sistema de cerca de 1 MIPS com 8MB de RAM que roda Debian Linux, vi, gcc e make usando apenas componentes de 8 pinos
- A placa final é composta por STM32G031, 8MB de SPI PSRAM e uma ponte USB-serial PL2303GL, totalizando 3 chips, e por causa da limitação do encapsulamento de 8 pinos há apenas 6 I/Os utilizáveis
- A falta de pinos foi o principal desafio do projeto: a RAM usa SPI comum, o cartão SD usa SDIO de 1 bit compartilhando os pinos da RAM, e a transmissão UART é feita por bit-banging
- O software reutiliza um emulador de MIPS já existente, baseado em assembly ARMv6M, e um bootloader de 8KB atualiza o firmware a partir de
FIRMWARE.BINno sistema de arquivos FAT do cartão SD - O STM32G031 é overclockado além dos 64MHz oficiais com a configuração VOS0 e, com CPU host a 148MHz, inicializa o Debian em cerca de 1 minuto com desempenho equivalente a aproximadamente 1,65MHz de um MIPS R3000
Computador Linux mínimo feito só com chips de 8 pinos
- O objetivo era criar um computador moderno em forma de kit fácil de montar em casa usando apenas chips de 8 pinos
- O critério mínimo para um computador moderno foi a capacidade de executar Debian Linux, vi, gcc e make
- Com base em experimentos anteriores, a especificação mínima necessária para rodar Linux foi definida como 8MB de RAM e uma CPU de 1 MIPS
- O armazenamento usa cartão SD, e a conexão de console usa USB-serial
- A placa tem formato circular pequeno e traz um conector USB-C na borda superior
- O número de componentes e de pinos foi reduzido para que até alguém com quase nenhuma experiência em solda consiga montar usando um ferro de solda de 45W
Escolha dos componentes
- Para a conexão USB foi escolhido o PL2303GL
- É uma ponte USB-serial que funciona sem componentes externos e também fornece saída regulada de 3,3V a 100mA
- Há drivers para os principais sistemas operacionais, e no macOS é preciso instalar pela App Store
- Como alternativa, também foi estudada uma implementação USB com ATTINYx5 e V-USB
- Pela especificação, USB low-speed não pode usar bulk endpoint, mas como os principais sistemas operacionais não impõem isso, uma porta serial ACM funciona
- O V-USB consome muito tempo de CPU, flash e RAM, o que pesa demais para este projeto
- A RAM usa SPI PSRAM em encapsulamento SOIC-8
- ISSI, APMEMORY e Vilsion, entre outras, fabricam componentes desse tipo, e peças de 8MB podem ser encontradas em distribuidores comuns
- Vários fornecedores prometeram chips de 16MB, mas o autor concluiu que eles não chegaram a ser realmente disponibilizados
- Foram comparadas famílias de microcontroladores como PIC16F, RL78, PSoC1, eZ8, S08CPUv2, STM8, MSP430, AVR, PSoC4, MSPM0C, CH32V003, CH570E e STM32G0
- A escolha final foi a família STM32G031J4M6/STM32G031
- Ela oferece núcleo Cortex-M0+, 64MHz oficiais, 32KB de flash e 8KB de RAM
- No encapsulamento de 8 pinos, ela tinha vantagem sobre os outros candidatos em desempenho e memória
- Houve preocupação com a qualidade dos documentos de errata da STM, mas como o projeto usa o mínimo possível de periféricos on-chip, a escolha foi considerada viável
Projeto de hardware encaixado em 6 pinos de I/O
-
UART do console
- UART RX e TX são difíceis de combinar com outras funções
- Se o RX for compartilhado, dados recebidos podem ser perdidos durante outras operações; se o TX for compartilhado, até pulsos low curtos podem aparecer como caracteres no PC
- Por isso, 2 dos 6 I/Os foram dedicados ao console UART
- No mapeamento final de pinos, o pino 8 é usado como USART2 RX e o pino 7 faz o UART TX por bit-banging
- Durante a transmissão UART, toda a execução para, então é usada a maior velocidade possível: 115.200bps
- Enviar um caractere leva cerca de 87 microssegundos, e como a placa passa a maior parte do tempo sem emitir saída, isso foi considerado aceitável
-
Conexão da RAM
- A SPI PSRAM suporta QSPI, mas QSPI exigiria 6 pinos e por isso não pode ser usado
- dual-SPI poderia ser até 2 vezes mais rápido que SPI comum sem usar pinos extras, mas o STM32G031 não suporta dual-SPI
- Foi considerado que fazer dual-SPI por bit-banging na CPU dificilmente superaria a combinação de SPI por hardware com DMA
- Como resultado, a RAM foi conectada por SPI comum, e só essa conexão já consome os 4 pinos restantes
-
Conexão do cartão SD
- Usar o cartão SD em modo SPI exigiria mais um pino de chip select, mas já não havia pinos livres
- Usar o nCS da RAM invertido como nCS do cartão SD causou problemas com alguns cartões e ainda exigia componentes extras
- Também foi estudado compartilhar o pino UART TX com o nCS do cartão SD usando um filtro passa-baixa, mas isso exigiria UART abaixo de 300bps e seria frágil quando o cartão SD estivesse lento
- A solução final foi implementar diretamente o protocolo SDIO de 1 bit do cartão SD
- O nCS da RAM é compartilhado com o SD CLK, o RAM CLK com o SD CMD, e o RAM MOSI com o SD DAT
- Acesso à RAM parece para o cartão SD apenas um bit em idle, enquanto acesso ao SD parece para a RAM apenas repetição de seleção e desseleção, então o arranjo funciona com segurança
- Porém, como acesso à RAM não pode entrar no meio de uma transação SD, leitura/escrita em múltiplos blocos não pode ser usada
- Pelo mapeamento de pinos do STM32G031, não é possível usar SDIO por hardware, então todo o acesso ao SD é feito por bit-banging
- A implementação em assembly atinge algo em torno de 14 ciclos de CPU por bit
Bootloader e fluxo de inicialização do Linux
-
Emulador
- Foi reutilizado o emulador de MIPS do projeto LinuxCard
- O emulador foi escrito em assembly ARMv6M e já conseguia inicializar Linux
- Para executar mais rápido, também foi criado um JIT de MIPS para ARMv6M, mas o código tinha 46KB, grande demais, e com cache de tradução de 6KB o ganho de velocidade não foi suficiente, então ele não foi usado
- Os 32KB de flash do STM32G031 foram divididos em 8KB para o bootloader e 24KB para o código principal
-
Bootloader para atualização de firmware
- Como não sobraram pinos de depuração, era necessário um bootloader que atualizasse o firmware a partir do cartão SD
- O bootloader inclui driver SDIO, driver do sistema de arquivos FAT, código de gravação em flash, logging e código de transmissão UART por bit-banging
- O tamanho real é de cerca de 6,5KB, mas por causa da granularidade dos blocos de flash ocupa uma área de 8KB
- Ele procura por FIRMWARE.BIN no cartão SD e aplica a atualização quando passam as verificações básicas e a versão é maior
- A word no offset 16 da imagem da aplicação é usada como número de versão
- O byte no offset 8 do bootloader é a versão do bootloader e, fora exibir o texto de boot do app principal, não é usado
- O bootloader também procura no sistema de arquivos FAT por arquivos ou diretórios cujo nome começa com CLOCK
- O número seguinte é usado como frequência de clock da aplicação principal; se estiver fora da faixa de 32–200MHz ou não houver valor, usa 132MHz
-
Partições do cartão e carregamento do kernel
- O fluxo de boot foi montado de forma semelhante ao processo de inicialização de um PC
- O primeiro setor do cartão SD é lido para o início da RAM e a execução salta para lá
- O código de primeiro estágio encontra a partição do tipo 0xBB, carrega em 0x80001000 e então salta
- O bootloader de segundo estágio monta em FAT16 a partição marcada como active e carrega o arquivo VMLINUX como ELF
- A linha de comando do kernel fica embutida no bootloader
- O root é
/dev/pvd3e o init é/sbin/uMIPSinit /dev/pvd1é tentado como montagem em/boot- A ordem de partições do projeto é partição FAT, partição do bootloader e rootfs
- Como Windows e macOS montam a primeira partição, a partição FAT facilita colocar e retirar arquivos
- No Linux já inicializado, essa partição também aparece como
/boot
Desempenho e overclock
- A velocidade oficial de operação do STM32G031 é 64MHz, mas foi tentado clock mais alto usando a configuração de tensão interna
- A documentação da STM traz as configurações VOS2 1,0V e VOS1 1,2V, e em VOS1 a estabilidade acima de cerca de 75MHz não é boa
- Usando a configuração VOS0 1,35V descrita em documentação antiga e em documentos de chips semelhantes, a margem para overclock aumenta bastante
- A maioria dos chips funciona bem em 136MHz, e alguns chegam a 180MHz
- Como a memória flash não fica mais rápida, é preciso gerenciar corretamente os flash wait states
- Com CPU host a 148MHz, a CPU MIPS emulada fica próxima de um MIPS R3000 de cerca de 1,65MHz com FPU desativada
- O sistema inicializa em cerca de 1 minuto, e vi, make, objdump e gcc funcionam
- Como é um sistema Debian completo, é possível copiar pacotes
.debpara/boote instalá-los
Montagem e primeira inicialização
-
Fluxo de montagem
- Os arquivos de projeto são fornecidos para que a placa possa ser fabricada diretamente, e o autor está procurando uma empresa para vender kits
- A montagem segue a ordem: soquete do cartão SD, capacitor, resistor, STM32G031 e PL2303GL
- R101, R102, R201 e R202 não devem ser montados inicialmente
- Primeiro, é preciso gravar o bootloader no STM32
- Faz-se uma ponte em R101 e R201 para montar a fiação serial do bootloader ROM
- Esse processo é feito sem inserir o cartão SD e com o chip de RAM ainda sem solda
- Depois de gravar o bootloader, removem-se as pontes R101/R201 e fazem-se pontes em R102/R202
- Depois disso, basta soldar o chip de RAM APS6408 ou VTI7064 na posição U2 para concluir a montagem do hardware
-
Firmware e primeiro boot
- O cartão SD precisa ter pelo menos 1GB, e deve receber a imagem de disco fornecida
- A imagem inclui o bootloader MIPS de primeiro estágio, o bootloader MIPS de segundo estágio, uma partição com o kernel Linux e uma cópia do firmware, além do rootfs Debian
- Colocando FIRMWARE.BIN na partição FAT, o bootloader grava sua própria flash no primeiro boot
- O terminal serial deve ser configurado em 115.200bps, 8N1
- Na primeira execução, os fuses do STM32 são programados, e pode ser necessário desconectar e reconectar o cabo USB-C
- Após cerca de 20 segundos, começam as mensagens de boot do kernel Linux, e a inicialização completa leva cerca de 1 minuto
- Como há apenas 8MB de RAM, é fortemente recomendado executar
swapon /swapfilecomo primeiro comando - Ativar o swap leva dezenas de segundos, mas depois disso é possível executar mais programas
Arquivos para download e uso
- O download principal é uMIPS.8PL.zip
- O arquivo compactado inclui os itens necessários para fabricar a placa e executá-la
schematics: esquemáticosgerbers: arquivos Gerber para fabricação da placasrcs: fontes do emulador e do bootloaderbinaries/SD.img: imagem para gravar no cartão SDBOOTLOADER.BIN: bootloader a ser gravado no chip durante a montagemFIRMWARE.BIN: imagem de firmware pré-compilada
- Depois do boot, o shell padrão é
sh, ebashtambém pode ser executado - Para evitar falta de RAM, é recomendável ativar o swapfile incluído na imagem com
swapon /swapfile - Assumindo o MCU rodando a 120MHz, a velocidade efetiva de CPU é de cerca de 1,5MHz
- Compilar um programa C simples com gcc leva alguns minutos, mas funciona
- Como exemplo, são fornecidos geradores de Mandelbrot em ponto flutuante e ponto fixo, em forma de código-fonte e binário
- Entre as ferramentas instaladas estão vim, make e gcc, e pacotes Debian podem ser adicionados pela partição FAT16 compartilhada do cartão SD
1 comentários
Opiniões no Hacker News
A parte em que, depois de pensar quais pinos poderiam ser combinados com os três pinos do SDIO, a solução encontrada foi usar o nCS da RAM como CLK do cartão SD, o CLK da RAM como CMD do cartão SD e o MOSI da RAM como DAT do cartão SD é realmente um hack genial.
Ao analisar as interações possíveis com cada dispositivo, fica convincente que isso funciona com segurança, e é algo mais do que digno de aparecer no Hacker News.
Sempre acho um pouco decepcionante ver que usar um chip separado para se conectar ao USB acaba sendo a opção padrão.
USB é um protocolo complexo demais; passando do nível básico de V-USB, que roda USB 1.1 de baixa velocidade, em geral parece difícil sem hardware especializado e uma pilha de software bem grande.
Já SPI é absurdamente simples: o hardware mínimo necessário é basicamente um registrador de deslocamento capaz de receber clock rápido o suficiente.
Dá saudade da época em que desktops e notebooks antigos tinham portas seriais/paralelas expostas para esse tipo de comunicação de baixo nível.
Talvez, se periféricos simples usassem UART, I2C e SPI multidrop em curtas distâncias, com alguns clocks padronizados e um conector único, e dispositivos com muitos dados, como monitores ou drives externos, passassem direto para IEEE 802.3 Ethernet, não fosse necessário dar suporte separado a USB e Ethernet, bastando oferecer um link Ethernet.
SPI não considera várias conveniências oferecidas pelo USB, como fornecimento de energia, hotplug, descoberta de dispositivos e erros de bits.
Há valor em desenvolvedores de software entenderem os idiomas de SPI e a forma como projetistas de hardware usam SPI.
Normalmente, SPI é usado para preencher registradores de periféricos, e sua natureza é diferente da comunicação assíncrona de alto nível comum em USB ou Ethernet e nas camadas de abstração acima deles.
Não existe um padrão universal para frames SPI, mas há padrões idiomáticos, e isso foi suficiente para incontáveis aplicações.
Na prática, protocolos simples como SPI e I2C não são suficientes.
Eles não são rápidos, usam sinalização single-ended e por isso são muito sensíveis a ruído, além de não terem correção de erros.
Esses protocolos são muito adequados ao uso pretendido, isto é, conectar CIs entre si sobre uma PCB, mas, se você expõe uma porta sem terminação para o mundo externo, fica difícil garantir qualquer coisa.
Mesmo em PCs modernos, esses protocolos e variantes são muito usados, mas sempre como barramentos internos.
Não examinei a especificação USB em detalhe, mas o principal problema do bit-banging provavelmente é a velocidade exigida.
O microcontrolador não é rápido o bastante para alternar pinos e, ao mesmo tempo, decodificar o protocolo e gerenciar a correção de erros, então é necessário hardware dedicado.
Ao fazer bit-banging de I2C, você pode encontrar o mesmo problema.
Com uma CPU de 20 MHz, o clock máximo obtido é de cerca de 250 KHz, um pouco mais da metade da velocidade máxima comum de 400 KHz, e a versão de 1 MHz é praticamente inviável.
PHYs existem porque transferir protocolos de comunicação para o hardware é esmagadoramente mais barato.
Caso contrário, seria preciso superdimensionar muito mais a CPU para ter recursos para gerenciar manualmente a comunicação, e é por isso que microcontroladores modernos trazem hardware para I2C, SPI e comunicação serial.
Em resumo, protocolos seriais simples como SPI, I2C e UART são uma péssima escolha para periféricos externos.
É difícil fazê-los operar em velocidades adequadas, e eles não suportam cabos longos nem ruído.
Mesmo deixando RS-232 como exceção, já que RS-232 não é UART, a natureza e o projeto desses protocolos não permitem usá-los dessa forma; se a especificação fosse alterada para dar suporte a isso, acabaríamos reinventando o USB.
Sinalização digital ou telas de TV para salas de reunião também costumam oferecer RS-232 para um controle mais flexível do que HDMI-CEC.
Muitas vezes não é necessário um bitrate acima de 9600 bps, e o conector mais comum é um borne de parafuso de 3 pinos com Tx, Rx e GND.
Instalações atuais geralmente têm pelo menos um adaptador RS232-USB em algum lugar e, em salas grandes, fazem bridge de RS232 sobre Ethernet.
Quando entrei nessa área, achei surpreendente, mas faz sentido ao considerar que muitas instalações têm décadas de idade e que os componentes foram sendo substituídos um por um.
Esse chip custa na faixa de US$ 0,10 e tem 2 KB de RAM, 16 KB de Flash, 48 MHz e opera a 1 CPI.
O novo CH570 também custa na faixa de US$ 0,10 em SOIC8, mas traz 100 MHz, 16 KB de RAM, 256 KB de Flash, USB e até rádio de pacotes de 2,4 GHz; já encomendei uma placa de desenvolvimento.
Seria bom também indicar a espessura da placa necessária para quem quiser fabricar a própria placa.
Se me lembro bem, é algo em torno de 0,8 mm, e essa espessura é necessária para que o “conector de borda USB-C” encaixe no plugue.
É um ótimo texto, mas fico imaginando se tudo não teria ficado muito mais simples caso o requisito de 8 pinos fosse um pouco mais flexível.
Com apenas alguns pinos a mais, a complexidade do projeto teria caído bastante, e o tempo de soldagem teria aumentado só um pouquinho.
Há muitos chips bem mais rápidos com USB integrado.
O Allwinner V3s também pode ser soldado à mão, tem RAM integrada e inicializa Linux nativamente muito bem.
O RP2350 também é uma boa opção, com uma excelente interface QSPI RAM com cache integrado e suporte a USB.
É quase um projeto de apenas 2 chips
Um deles é só um IC USB-serial e, se eu não tivesse contado o cartão SD, voltaria a ser 3 incluindo o cartão SD
Como o número total de pinos é tão baixo, dá vontade de tentar montar no estilo dead bug
Um adaptador microSD-para-SD serve muito bem como suporte de microSD soldável
Fazer um truque parecido tendo ele como alvo também parece que seria um hack interessante
Tecnicamente é um projeto muito bacana, mas parece ter levado a ideia ao extremo a ponto de se afastar um pouco do objetivo de criar um novo kit de computador para iniciantes
Para um iniciante, não há muita diferença entre soldar um SOIC8 e um SOIC28
Eu diria que um SOIC28 é tão fácil ou difícil quanto um SOIC8
Usar um chip maior permitiria adicionar som mínimo, teclado e, mais tarde, até uma saída real de monitor no estilo VGA, tornando-o um computador muito mais útil
A dificuldade de soldagem quase não aumentaria, e ele viraria uma base melhor para usuários interessados expandirem
Se quiser fazer algo nessa direção, pode usar o meu código
Eu fiz assim porque achei divertida a restrição artificial de 8 pinos
Dá um impulso estranho de querer eliminar até a placa e fazer isso como uma escultura de circuito
Eu não sou artista nem escultor, então não me atrevi a tentar
Seria bonitinho usar uma SPI Flash de 8 pinos como armazenamento em vez de um cartão SD
Ele disse que “tem alergia a RISC-V por motivos pessoais”, e fico curioso sobre o motivo
Não quero começar uma guerra santa; é só uma opinião pessoal, mas bem forte
O RISC-V foi projetado historicamente tarde o bastante para poder aproveitar muito conhecimento existente, mas, na minha visão, quase não aproveitou
Por isso, várias extensões vêm sendo propostas para consertar coisas que deveriam ter sido feitas direito desde o início
Com o acúmulo de adições, só agora, depois de 10 anos, ele começa aos poucos a se aproximar de algo que faz sentido
Também não compro a desculpa de que era necessário um processo de aprendizado
As informações necessárias já existiam desde o começo, e os erros eram claros para a maioria de nós
Algumas extensões são apenas curativos para problemas fundamentais de projeto
Por exemplo, shadd2 é um curativo para o problema de não haver um modo de endereçamento decente para acesso a arrays
A resposta comum para isso é prometer uma fusão mágica de instruções dentro do core, algo prometido com frequência, mas que não é realmente entregue
Especialmente nos processadores baratos, que parecem ser o único alvo do RISC-V, isso é ainda menos verdade
A ausência de instruções de extração e inserção de bitfields também é um erro amador, e há uma extensão para corrigir isso
Mas deveria ter sido óbvio desde o início que esse tipo de recurso seria necessário
Instruções de desvio condicional com base em um bit específico dentro de um registrador também aparecem com frequência, então eram um recurso óbvio que deveria ter sido considerado desde o começo
Uma análise mínima de software moderno teria mostrado isso
O irritante é que a informação já existia
Sabíamos o que software moderno fazia, mas tudo isso foi ignorado e, no fim, o que recebemos foi basicamente um MIPS-1 levemente atualizado
Agora, com um monte de extensões acrescentadas, a fragmentação ficou séria
Dá para mirar em algo como o RV23, que seria um resultado final razoavelmente racional, mas não há hardware que o implemente; ou então é preciso mirar no mínimo denominador comum, que roda em qualquer lugar, mas funciona pessimamente
Há problemas de projeto ainda mais graves quando se tenta usar RISC-V para computação de alto desempenho real, mas deixo isso para o próximo rant
Outro conjunto de instruções projetado em uma época parecida realmente aproveitou o conhecimento sobre como o software moderno se parece, e o resultado aparece: aarch64
O projeto em si também é muito bacana, mas esta página também é um excelente recurso para obter informações sobre microcontroladores pequenos
A família WLCSP ficou de fora, mas ela também leva à página do emulador de MIPS para ARM https://dmitry.gr/?r=05.Projects&proj=33.%20LinuxCard, que parece bastante interessante