Compare commits

...

35 commits

Author SHA1 Message Date
Liz
c74b1472d1
Merge pull request #9 from jepler/update-umac
Some checks failed
Build pico-mac / bins (push) Has been cancelled
Update umac
2025-08-05 11:00:53 -04:00
e57801a82e Update umac 2025-08-05 09:54:20 -05:00
Limor "Ladyada" Fried
1aa7fad692
Merge pull request #7 from eightycc/psram-qspi-oc
Support PSRAM QSPI CLK up to 133MHz
2025-08-05 10:40:22 -04:00
Limor "Ladyada" Fried
3fa2e069a2
Merge pull request #8 from jepler/fruitjam-revd
Some checks failed
Build pico-mac / bins (push) Has been cancelled
Update for Rev D hardware
2025-08-04 13:30:04 -04:00
ca4463174d Update fetch procedure for rom & boot floppy
the URLs apparently changed(?)
2025-08-04 11:43:51 -05:00
17f4b35e58 Update for Rev D hardware 2025-08-04 11:06:04 -05:00
eightycc
bd9c3e1cae Add support for clocking PSRAM QSPI up to 133MHz. 2025-03-30 07:30:42 -07:00
eightycc
15bb242b02
Merge branch 'adafruit:main' into main 2025-03-28 19:43:47 -07:00
Limor "Ladyada" Fried
1b8203677f
Merge pull request #6 from adafruit/reenable-speaker-set-volume
Some checks failed
Build pico-mac / bins (push) Has been cancelled
Reenable speaker & set volume
2025-03-28 15:03:07 -04:00
1c71dd765a Automute 1/2s after audio finishes playing
Some checks failed
Build pico-mac / bins (push) Has been cancelled
2025-03-28 11:48:47 -05:00
eebab0afa4 mute speaker too 2025-03-28 11:48:47 -05:00
0e46434843 mute headphone when not actively playing audio 2025-03-28 11:48:44 -05:00
Limor "Ladyada" Fried
646926f066
Merge pull request #5 from adafruit/mute-etc
Mute dac when mac is not making noise
2025-03-28 10:54:13 -04:00
ladyada
baef470b95 turn off speaker for now 2025-03-28 10:50:52 -04:00
ladyada
ad1c9265be headphone gain is good now 2025-03-28 10:49:12 -04:00
b1c20d06ea Change the LED to a SD activity light
Some checks failed
Build pico-mac / bins (push) Has been cancelled
2025-03-28 09:35:02 -05:00
41a8e0e9b6 Mute the i2s dac when sound output is inactive 2025-03-28 09:18:22 -05:00
d41ccd1082 ignore built files 2025-03-28 09:18:22 -05:00
05d4c11f56 Fix specifying disc images 2025-03-28 09:18:22 -05:00
ladyada
6965161fec Merge branch 'main' of github.com:adafruit/pico-mac 2025-03-28 10:08:45 -04:00
ladyada
34e184b5fe tweak headset gain to not be so loud. 2025-03-28 10:08:21 -04:00
1b3eb17cee
Update README.md 2025-03-28 08:45:25 -05:00
2593d665b0 Update README 2025-03-28 08:34:27 -05:00
eightycc
873994107f Allow PSRAM overclocking by enabling burst wrap mode. 2025-03-28 06:25:15 -07:00
Limor "Ladyada" Fried
e98a74b69c
Merge pull request #2 from adafruit/update-sound-build-etc
Add overclocking, update sound build etc
2025-03-27 14:53:31 -04:00
bfc9f6f0f0 Add overclocking
.. not currently compatible with PSRAM (sadly), even though I tried
doing the right things with PSRAM clocking.
2025-03-27 12:07:36 -05:00
082f3175f0 Move building the disk image & patching rom into cmake
This fixes a problem I kept causing myself: the patched ROM files
were not per build directory, meaning that I'd build a wrong ROM
and wonder why nothing was working.
2025-03-27 11:47:12 -05:00
7d815b8fd3 finish updating pico-extras submodule 2025-03-27 10:24:18 -05:00
6ed6eae9bc quiet debug messages for i2s init 2025-03-27 10:23:43 -05:00
43fee452a2 update pico-extras 2025-03-27 10:23:40 -05:00
3edf26ed1d
Merge pull request #1 from adafruit/rp2350-fruitjam
Rp2350 fruitjam
2025-03-24 11:21:36 -05:00
c611126e85 actually call i2s dac setup code 2025-03-24 11:14:53 -05:00
f02bb8e260 try to attach files to releases 2025-03-24 10:40:02 -05:00
5e307d8167 Give firmwares distinctive names 2025-03-24 10:36:21 -05:00
a566102759 add more board configs 2025-03-24 10:28:32 -05:00
12 changed files with 468 additions and 464 deletions

View file

@ -36,17 +36,35 @@ jobs:
run: |
./fetch-rom-dsk.sh
./fruitjam-build.sh -m 4096
./fruitjam-build.sh -m 4096 -v
./fruitjam-build.sh -m 4096 -o
./fruitjam-build.sh -m 4096 -v -o
./fruitjam-build.sh -m 400
./fruitjam-build.sh -m 400 -v
./fruitjam-build.sh -m 400 -o
./fruitjam-build.sh -m 400 -v -o
mkdir uf2s
cp build*/*.uf2 uf2s/
mkdir elfs
cp build*/*.elf elfs/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: uf2 files
path: build*/*.uf2
path: uf2s/*
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: elf files
path: build*/*.elf
path: elfs/*
- name: Create release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: build*/*.uf2
fail_on_unmatched_files: true
body: "Select a uf2 from the list below."

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.img
*.bin
os.7z
picotool

View file

@ -55,6 +55,8 @@ set(HSTX_D0P 15 CACHE STRING "HSTX D0+ PIN")
set(HSTX_D1P 17 CACHE STRING "HSTX D1+ PIN")
set(HSTX_D2P 19 CACHE STRING "HSTX D2+ PIN")
option(OVERCLOCK "Overclock to 264MHz (known incompatible with psram)" OFF)
# Options for analog VGA output
option(USE_VGA_RES "Video uses VGA (640x480) resolution" OFF)
set(VIDEO_PIN 18 CACHE STRING "VGA Video GPIO base pin (followed by VS, CLK, HS)")
@ -71,12 +73,55 @@ set(PIN_AUDIO_PWM 41 CACHE STRING "Pin for PWM audio")
# See below, -DMEMSIZE=<size in KB> will configure umac's memory size,
# overriding defaults.
set(MEMSIZE 128 CACHE STRING "Memory size, in KB")
set(DISC_IMAGE ${CMAKE_CURRENT_SOURCE_DIR}/umac0ro.img CACHE FILEPATH "Built-in disk image")
if (USE_HSTX)
add_compile_definitions(USE_VGA_RES=1)
add_compile_definitions(HSTX_CKP=${HSTX_CKP} HSTX_D0P=${HSTX_D0P} HSTX_D1P=${HSTX_D1P} HSTX_D2P=${HSTX_D2P})
set(VIDEO_SRC src/video_hstx.c)
else()
add_compile_definitions(GPIO_VID_BASE=${VIDEO_PIN})
set(VIDEO_SRC src/video_vga.c)
endif()
if (OVERCLOCK)
add_compile_definitions(OVERCLOCK=1)
set(OPT_OC "-oc")
else()
set(OPT_OC "")
endif()
if (USE_VGA_RES)
add_compile_definitions(USE_VGA_RES=1)
add_compile_definitions(DISP_WIDTH=640)
add_compile_definitions(DISP_HEIGHT=480)
set(RES "640x480")
set(RESFLAG "-v")
else()
add_compile_definitions(DISP_WIDTH=512)
add_compile_definitions(DISP_HEIGHT=342)
set(RES "512x342")
set(RESFLAG "")
endif()
if (USE_PSRAM)
add_compile_definitions(PIN_PSRAM_CS=${PSRAM_CS} USE_PSRAM=1)
set(OPT_PSRAM "-psram")
else()
add_compile_definitions(USE_PSRAM=0)
set(OPT_PSRAM "")
endif()
set(FIRMWARE "pico-mac-${PICO_BOARD}-${MEMSIZE}k-${RES}${OPT_PSRAM}${OPT_OC}")
# initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project()
include(pico_sdk_import.cmake)
project(firmware)
project(${FIRMWARE})
# initialize the Raspberry Pi Pico SDK
pico_sdk_init()
@ -103,9 +148,9 @@ set(UMAC_SOURCES
${UMAC_MUSASHI_PATH}/softfloat/softfloat.c
)
set(MEMSIZE 128 CACHE STRING "Memory size, in KB")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ggdb -g3 -O3 -DPICO -DMUSASHI_CNF=\\\"../include/m68kconf.h\\\" -DUMAC_MEMSIZE=${MEMSIZE}")
if (USE_SD)
add_compile_definitions(USE_SD=1)
set(FF_DISABLE_RTC ${PICO_RP2350}) # RP2350 doesn't have RTC, so disable it
@ -115,23 +160,6 @@ if (USE_SD)
add_compile_definitions(SD_TX=${SD_TX} SD_RX=${SD_RX} SD_SCK=${SD_SCK} SD_CS=${SD_CS} SD_MHZ=${SD_MHZ})
endif()
if (USE_HSTX)
add_compile_definitions(USE_VGA_RES=1)
add_compile_definitions(HSTX_CKP=${HSTX_CKP} HSTX_D0P=${HSTX_D0P} HSTX_D1P=${HSTX_D1P} HSTX_D2P=${HSTX_D2P})
set(VIDEO_SRC src/video_hstx.c)
else()
add_compile_definitions(GPIO_VID_BASE=${VIDEO_PIN})
set(VIDEO_SRC src/video_vga.c)
endif()
if (USE_VGA_RES)
add_compile_definitions(USE_VGA_RES=1)
add_compile_definitions(DISP_WIDTH=640)
add_compile_definitions(DISP_HEIGHT=480)
else()
add_compile_definitions(DISP_WIDTH=512)
add_compile_definitions(DISP_HEIGHT=342)
endif()
add_compile_definitions(PIN_USB_HOST_DP=${PIN_USB_HOST_DP})
add_compile_definitions(PIN_USB_HOST_DM=${PIN_USB_HOST_DM})
add_compile_definitions(PICO_DEFAULT_PIO_USB_DP_PIN=${PIN_USB_HOST_DP})
@ -147,26 +175,21 @@ if (NOT UART_RX STREQUAL "")
add_compile_definitions(PICO_DEFAULT_UART_RX_PIN=${UART_RX})
endif()
if (USE_PSRAM)
add_compile_definitions(PIN_PSRAM_CS=${PSRAM_CS} USE_PSRAM=1)
else()
add_compile_definitions(USE_PSRAM=0)
endif()
if (USE_AUDIO)
add_subdirectory(external/pico-extras/src/rp2_common/pico_audio_i2s)
add_subdirectory(external/pico-extras/src/common/pico_audio)
add_subdirectory(external/pico-extras/src/common/pico_util_buffer)
add_compile_definitions(ENABLE_AUDIO=1 PICO_AUDIO_I2S_PIO=1 PICO_AUDIO_I2S_DMA_IRQ=0 PICO_AUDIO_I2S_DATA_PIN=24 PICO_AUDIO_I2S_CLOCK_PIN_BASE=25 PICO_AUDIO_I2S_MONO_INPUT=1 PICO_AUDIO_I2S_SWAP_CLOCK=1)
add_compile_definitions(ENABLE_AUDIO=1 PICO_AUDIO_I2S_PIO=1 PICO_AUDIO_I2S_DMA_IRQ=0 PICO_AUDIO_I2S_DATA_PIN=24 PICO_AUDIO_I2S_CLOCK_PIN_BASE=26 PICO_AUDIO_I2S_MONO_INPUT=1)
set(EXTRA_AUDIO_LIB pico_util_buffer pico_audio pico_audio_i2s hardware_i2c)
endif()
if (TARGET tinyusb_device)
add_executable(firmware
add_executable(${FIRMWARE}
src/main.c
${VIDEO_SRC}
src/kbd.c
src/hid.c
src/clocking.c
${EXTRA_SD_SRC}
${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c
@ -178,6 +201,21 @@ if (TARGET tinyusb_device)
)
# The umac sources need to prepare Musashi (some sources are generated):
add_custom_command(OUTPUT incbin/umac-rom.h
COMMAND echo "*** Patching ROM ***"
COMMAND set -xe && mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/incbin && make -C ${UMAC_PATH} patcher && ${UMAC_PATH}/patcher ${RESFLAG} -m ${MEMSIZE} -r "${CMAKE_CURRENT_LIST_DIR}/rom.bin" -w ${CMAKE_CURRENT_BINARY_DIR}/incbin/umac-rom.h
)
add_custom_target(prepare_rom
DEPENDS incbin/umac-rom.h
)
add_custom_command(OUTPUT incbin/umac-disc.h
COMMAND echo "DISC_IMAGE is ${DISC_IMAGE}" && mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/incbin && xxd -i < "${DISC_IMAGE}" > ${CMAKE_CURRENT_BINARY_DIR}/incbin/umac-disc.h
)
add_custom_target(prepare_disc
DEPENDS incbin/umac-disc.h
)
add_custom_command(OUTPUT ${UMAC_MUSASHI_PATH}/m68kops.c
COMMAND echo "*** Preparing umac source ***"
COMMAND make -C ${UMAC_PATH} prepare
@ -185,9 +223,9 @@ if (TARGET tinyusb_device)
add_custom_target(prepare_umac
DEPENDS ${UMAC_MUSASHI_PATH}/m68kops.c
)
add_dependencies(firmware prepare_umac)
add_dependencies(${FIRMWARE} prepare_umac prepare_rom prepare_disc)
target_link_libraries(firmware
target_link_libraries(${FIRMWARE}
pico_stdlib
pico_multicore
tinyusb_host
@ -199,24 +237,24 @@ if (TARGET tinyusb_device)
${EXTRA_AUDIO_LIB}
)
target_include_directories(firmware PRIVATE
target_include_directories(${FIRMWARE} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/include
${PICO_TINYUSB_PATH}/hw
${PICO_TINYUSB_PATH}/src
${UMAC_INCLUDE_PATHS}
${PIOUSB_PATH}/src
incbin
${CMAKE_CURRENT_BINARY_DIR}/incbin
${CMAKE_CURRENT_LIST_DIR}
)
if (NOT USE_HSTX)
pico_generate_pio_header(firmware ${CMAKE_CURRENT_LIST_DIR}/src/pio_video.pio)
pico_generate_pio_header(${FIRMWARE} ${CMAKE_CURRENT_LIST_DIR}/src/pio_video.pio)
endif()
pico_enable_stdio_uart(firmware 1)
pico_enable_stdio_uart(${FIRMWARE} 1)
# Needed for UF2:
pico_add_extra_outputs(firmware)
pico_add_extra_outputs(${FIRMWARE})
elseif(PICO_ON_DEVICE)
message(WARNING "not building firmware because TinyUSB submodule is not initialized in the SDK")

311
README.md
View file

@ -1,36 +1,44 @@
# Pico Micro Mac (pico-umac)
v0.21-fruitjam 22 March 2025
v0.21-fruitjam 28 March 2025
I (@jepler) have run roughshod across the code, breaking things willy-nilly and adding
* 512x342 & 640x480 digital output on HSTX
* PIO USB
* PSRAM support
* Some Sound support
* To enable that, some VIA timer 2 support
The two main variants offered are the "400kB" mac with a 640x480 resolution & a
4MB mac with 512x342 resolution (presented centered on a 640x480 display).
* 512x342 & 640x480 digital output on HSTX
* PIO USB
* PSRAM support
* Some Sound support on the onboard I2S DAC (speaker and headphones)
For now, I2S is on pins A1 (data) A2 (LRCK) A3 (bit clock). With any luck it'll be moved to the on-board I2S soon.
Several pre-compiled variants are offered:
* 400kB or 4096kB (the latter uses PSRAM, and may perform slower overall but can run more software)
* 512x342 or 640x480 desktop resolution (512x342 is more compatible but has black screen margins)
* overclocked or not (overclocked may run faster but may be less reliable)
What works?
* System beep
* A fair amount of hypercard, though not playing melodies with 'play "Boing" "a b c"'
* Hypercard 'play "Boing"' does play audio though (as does 'beep')
* Dark Castle including audio
* After Dark screensavers including audio
* System beep
* Dark Castle including audio
* After Dark screensavers including audio
* Glider works, but without sound
What almost works
* Glider was working, but my sound changes made it boot with an error about missing coprocessor?? (appears linked to the timer2 implementation)
What doesn't work?
* Hypercard "play" and some hypercard screen transitions
There are artifacts that you can grab from the latest Actions build, at least until they expire.
Some of the software I tested with:
* https://archive.org/details/HyperCardBootSystem7
* https://archive.org/details/mac\_DarkCastle\_1\_2
* https://archive.org/details/AfterDark2
Plug mouse & keyboard into the USB ports of the fruit jam.
Put the software (a mac HFS volume with no additional headers or metadata) on a
SD card as "umac0w.img" (if you want to be able to write files) or
"umac0ro.img" (if you want the drive to be read only) and press the reset
button to start.
Some good Mac software:
* https://archive.org/details/HyperCardBootSystem7
* https://archive.org/details/mac_DarkCastle_1_2
* https://archive.org/details/AfterDark2
**Important note on overclocking:** The "oc" uf2 files overclock your RP2 chip to 264MHz. Simply including the `<Adafruit_dvhstx.h>` header enables this overclocking, separate from the option in the Arduino Tools menu.
Just like PC overclocking, theres some risk of reduced component lifespan, though the extent (if any) cant be precisely quantified and could vary from one chip to another.
Proceed at your own discretion.
v0.21 20 December 2024
@ -72,98 +80,23 @@ couple of cheap components.
* git submodules
- Clone the repo with `--recursive`, or `git submodule update --init --recursive`
* Install/set up the [Pico/RP2040 SDK](https://github.com/raspberrypi/pico-sdk)
## Build umac
Install and build `umac` first. It'll give you a preview of the fun
to come, plus is required to generate a patched ROM image.
If you want to use a non-default memory size (i.e. >128K) you will
need to build `umac` with a matching `MEMSIZE` build parameter, for
example:
```
cd external/umac
make MEMSIZE=208
```
This is because `umac` is used to patch the ROM, and when using
unsupported sizes between 128K and 512K the RAM size can't be probed
automatically, so the size needs to be embedded.
This is also the case for altering the video resolution, because the ROM
must be patched for this. Build `umac` with `DISP_WIDTH=640 DISP_HEIGHT=480`
when you intend to use the `USE_VGA_RES` option. For example:
```
cd external/umac
make MEMSIZE=208 DISP_WIDTH=640 DISP_HEIGHT=480
```
* Get a ROM & disc image with `sh fetch-rom-dsk.sh` (needs curl & 7z (debian package p7zip-full))
## Build pico-umac
Do the initial Pico SDK `cmake` setup into an out-of-tree build dir,
providing config options if required.
From the top-level `pico-umac` directory:
Run the configure-and-build script:
```
mkdir build
(cd build ; PICO_SDK_PATH=/path/to/sdk cmake .. <options>)
$ ./fruitjam-build.sh -h
Usage: ./fruitjam-build.sh [-v] [-m KiB] [-d diskimage]
-v: Use framebuffer resolution 640x480 instead of 512x342
-m: Set memory size in KiB (over 400kB requires psram)
-d: Specify disc image to include
-o: Overclock to 264MHz (known to be incompatible with psram)
PSRAM is automatically set depending on memory & framebuffer details
```
Options are required if you want SD support, more than the default 128K of memory,
higher resolution, to change pin configs, etc.:
* `-DUSE_SD=true`: Include SD card support. The GPIOs default to
`spi0` running at 5MHz, and GPIOs 2,3,4,5 for
`SCK`/`TX`/`RX`/`CS` respectively. These can be overridden for
your board/setup:
- `-DSD_TX=<gpio pin>`
- `-DSD_RX=<gpio pin>`
- `-DSD_SCK=<gpio pin>`
- `-DSD_CS=<gpio pin>`
- `-DSD_MHZ=<integer speed in MHz>`
* `-DMEMSIZE=<size in KB>`: The maximum practical size is about
208KB, but values between 128 and 208 should work on a RP2040.
Note that although apps and Mac OS seem to gracefully detect free
memory, these products never existed and some apps might behave
strangely.
- With the `Mac Plus` ROM, a _Mac 128K_ doesn't quite have
enough memory to run _MacPaint_. So, 192 or 208 (and a
writeable boot volume on SD) will allow _MacPaint_ to run.
- **NOTE**: When this option is used, the ROM image must be
built with an `umac` build with a corresponding `MEMSIZE`
* `-DUSE_VGA_RES=1`: Use 640x480 screen resolution instead of the
native 512x342. This uses an additional 16KB of RAM, so this
option makes a _Mac 128K_ configuration virtually unusable.
It is recommended only to use this when configuring >208K
using the option above.
* `-DVIDEO_PIN=<GPIO pin>`: Move the video output pins; defaults
to the pinout shown below.
Tip: `cmake` caches these variables, so if you see weird behaviour
having built previously and then changed an option, delete the `build`
directory and start again.
## ROM image
The flow is to use `umac` built on your workstation (e.g. Linux,
but WSL may work too) to prepare a patched ROM image.
`umac` is passed the 4D1F8172 MacPlusv3 ROM, and `-W` to write the
post-patching binary out:
```
./external/umac/main -r '4D1F8172 - MacPlus v3.ROM' -W rom.bin
```
Note: Again, remember that if you are using the `-DMEMSIZE` option to
increase the `pico-umac` memory, or the `-DUSE_VGA_RES` option to
increase the `pico-umac` screen resolution, you will need to create
this ROM image with a `umac` built with the corresponding
`MEMSIZE`/`DISP_WIDTH`/`DISP_HEIGHT` options, as above.
## Disc image
If you don't build SD support, an internal read-only disc image is
@ -188,147 +121,15 @@ into _one_ of the following files in the root of the card:
* `umac0.img`: A normal read/write disc image
* `umac0ro.img`: A read-only disc image
## Putting it together, and building
Given the `rom.bin` prepared above and a `disc.bin` destinated for
flash, you can now generate includes from them and perform the build:
```
mkdir incbin
xxd -i < rom.bin > incbin/umac-rom.h
# When using an internal disc image:
xxd -i < disc.bin > incbin/umac-disc.h
# OR, if using SD and if you do _not_ want an internal image:
echo > incbin/umac-disc.h
make -C build
```
You'll get a `build/firmware.uf2` out the other end. Flash this to
your Pico: e.g. plug it in with button held/drag/drop. (When
iterating/testing during development, unplugging the OTG cable each
time is a pain I ended up moving to SWD probe programming.)
The LED should flash at about 2Hz once powered up.
# Hardware contruction
It's a simple circuit in terms of having few components: just the
Pico, with three series resistors and a VGA connection, and DC power.
However, if you're not comfortable soldering then don't choose this as
your first project: I don't want you to zap your mouse, keyboard,
monitor, SD cards...
Disclaimer: This is a hardware project with zero warranty. All due
care has been taken in design/docs, but if you choose to build it then
I disclaim any responsibility for your hardware or personal safety.
With that out of the way...
## Theory of operation
Three 3.3V GPIO pins are driven by PIO to give VSYNC, HSYNC, and video
out signals.
The syncs are in many similar projects driven directly from GPIO, but
here I suggest a 66Ω series resistor on each in order to keep the
voltages at the VGA end (presumably into 75Ω termination?) in the
correct range.
For the video output, one GPIO drives R,G,B channels for mono/white
output. A 100Ω resistor gives roughly 0.7V (max intensity) into 3*75Ω
signals.
That's it... power in, USB adapter.
## Pinout and circuit
Parts needed:
* Pico/RP2040 board
* USB OTG micro-B to A adapter
* USB keyboard, mouse (and hub, if not integrated)
* 5V DC supply (600mA+), and maybe a DC jack
* 100Ω resistor
* 2x 66Ω resistors
* VGA DB15 connector, or janky chopped VGA cable
* (optional) SD card breakout, SD card
If you want to get fancy with an SD card, you will need some kind of
SD card SPI breakout adapter. (There are a lot of these around, but
many seem to have a buffer/level-converter for 5V operation. Find one
without, or modify your adapter for a 3.3V supply. Doing so, and
finding an SD card that works well with SPI is out of scope of this
doc.)
Pins are given for a RPi Pico board, but this will work on any RP2040
board with 2MB+ flash as long as all required GPIOs are pinned out:
| GPIO/pin | Pico pin | Usage |
| ------------ | ------------ | -------------- |
| GP0 | 1 | UART0 TX |
| GP1 | 2 | UART0 RX |
| GP18 | 24 | Video output % |
| GP19 | 25 | VSYNC |
| GP21 | 27 | HSYNC |
| Gnd | 23, 28 | Video ground |
| VBUS (5V) | 40 | +5V supply |
| Gnd | 38 | Supply ground |
%: The video pins default here, but can be moved by building with the
`-DVIDEO_PIN` option. This sets the position of the Video pin,
which is immediately followed by VSYNC, then a gap, then HSYNC.
For example, `-DVIDEO_PIN=20` configures the Video pin at 20,
VSYNC at 21, HSYNC at 23.
Method:
* Wire 5V supply to VBUS/Gnd
* Video output --> 100Ω --> VGA RGB (pins 1,2,3) all connected together
* HSYNC --> 66Ω --> VGA pin 13
* VSYNC --> 66Ω --> VGA pin 14
* Video ground --> VGA grounds (pins 5-8, 10)
If you don't have exactly 100Ω, using slightly more is OK but display
will be dimmer. If you don't have 66Ω for the syncs, connecting them
directly is "probably OK", but YMMV.
If you are including an SD card, the default pinout is as follows
(this can be changed at build time, above):
| GPIO/pin | Pico pin | Usage |
| ------------ | ------------ | -------------- |
| GP2 | 4 | SPI0 SCK |
| GP3 | 5 | SPI0 TX (MOSI) |
| GP4 | 6 | SPI0 RX (MISO) |
| GP5 | 7 | SPI0 /CS |
(The SD card needs a good ground, e.g. Pico pin 8 nearby, and 3.3V
supply from Pico pin 36.)
If your SD breakout board is "raw", i.e. has no buffer or series
resistors on-board, you may find adding a 66Ω resistor in series on
all of the four signal lines will help. Supply decoupling caps will
also be important (e.g. 1uF+0.1uF) to keep the SD card happy. _Keep
SD card wiring short._ The default SPI clock (5MHz) is
conservative/slow, but I suggest verifying the circuit/SD card works
before increasing it.
Test your connections: the key part is not getting over 0.7V into your
VGA connector's signals, or shorting SD card pins.
Connect USB mouse, and keyboard if you like, and power up.
# Software
Both CPU cores are used, and are overclocked (blush) to 250MHz so that
Both CPU cores are used, and are optionally overclocked (blush) to 264MHz so that
Missile Command is enjoyable to play.
The `umac` emulator and video output runs on core 1, and core 0 deals
with USB HID input. Video DMA is initialised pointing to the
framebuffer in the Mac's RAM.
framebuffer in the Mac's RAM, or to a mirrored region in SRAM depending
on the configuration.
Other than that, it's just a main loop in `main.c` shuffling things
into `umac`.
@ -345,32 +146,6 @@ VT220 emulator!).
The USB HID code is largely stolen from the TinyUSB example, but shows
how in practice you might capture keypresses/deal with mouse events.
## Video
The video system is pretty good and IMHO worth stealing for other
projects: It uses one PIO state machine and 3 DMA channels to provide
a rock-solid bitmapped 1BPP 640x480 video output. The Mac 512x342
framebuffer is centred inside this by using horizontal blanking
regions (programmed into the line scan-out) and vertical blanking
areas from a dummy "always black" mini-framebuffer.
It supports (at build time) flexible resolutions/timings. The one
caveat (or advantage?) is that it uses an HSYNC IRQ routine to
recalculate the next DMA buffer pointer; doing this at scan-time costs
about 1% of the CPU time (on core 1). However, it could be used to
generate video on-the-fly from characters/tiles without a true
framebuffer.
I'm considering improvements to the video system:
* Supporting multiple BPP/colour output
* Implement the rest of `DE`/display valid strobe support, making
driving LCDs possible.
* Using a video DMA address list and another DMA channel to reduce
the IRQ frequency (CPU overhead) to per-frame, at the cost of a
couple of KB of RAM.
# Licence
`hid.c` and `tusb_config.h` are based on code from the TinyUSB

@ -1 +1 @@
Subproject commit c73f2f3a2b1ef65dc50113a22aadc5a262a92e0f
Subproject commit 601b1bfdb1089068f8515a623622fcb38648417a

2
external/umac vendored

@ -1 +1 @@
Subproject commit ce55830a1babd681d0e9a639c9b23f10cd9e3c96
Subproject commit c265fb4e1eaaaed15776ac97f5b16a56cf5c454d

View file

@ -1,21 +1,12 @@
#!/bin/sh
mkdir -p incbin
set -xe
if ! [ -f rom.bin ]; then
if ! [ -f '4D1F8172 - MacPlus v3.ROM' ]; then
curl -L 'https://ia902205.us.archive.org/view_archive.php?archive=/18/items/mac_rom_archive_-_as_of_8-19-2011/mac_rom_archive_-_as_of_8-19-2011.zip&file=4D1F8172%20-%20MacPlus%20v3.ROM' > '4D1F8172 - MacPlus v3.ROM'
fi
make -C external/umac clean
make -C external/umac DISP_WIDTH=512 DISP_HEIGHT=342
./external/umac/main -r '4D1F8172 - MacPlus v3.ROM' -W rom.bin
curl -L 'https://archive.org/download/mac_rom_archive_-_as_of_8-19-2011/mac_rom_archive_-_as_of_8-19-2011.zip/4D1F8172%20-%20MacPlus%20v3.ROM' > rom.bin
fi
xxd -i < rom.bin > incbin/umac-rom.h
if ! [ -f umac0ro.img ]; then
curl -L 'https://archive.org/download/apple-mac-os-system-3.2-finder-5.3-system-tools-1.0-512-ke-jun-1986-3.5-800k.-7z/Apple%20Mac%20OS%20%28System%203.2%20Finder%205.3%29%20%28System%20Tools%201.0%20Mac%20128%2C%20512K%29%20%28Jun%201986%29%20%283.5-400k%29.7z' > 'Apple Mac OS (System 3.2 Finder 5.3) (System Tools 1.1 Mac Plus) (Jun 1986) (3.5-800k).7z'
7z x -so 'Apple Mac OS (System 3.2 Finder 5.3) (System Tools 1.1 Mac Plus) (Jun 1986) (3.5-800k).7z' 'Apple Mac OS (System 3.2 Finder 5.3) (System Tools 1.0 Mac 128, 512K) (Jun 1986) (3.5-400k)/System Installation.img' > umac0ro.img
curl -L 'https://archive.org/download/mac_MacOS_6.0.8/MacOS_6.0.8_System_Startup.img' > 'umac0ro.img'
fi
xxd -i < umac0ro.img > incbin/umac-disc.h
sha256sum -c roms.sha256sum

View file

@ -10,11 +10,15 @@ set -e
DISP_WIDTH=512
DISP_HEIGHT=342
MEMSIZE=400
DISK_IMAGE=""
DISC_IMAGE=
CMAKE_ARGS=""
OVERCLOCK=0
while getopts "hvd:m:" o; do
while getopts "hovd:m:" o; do
case "$o" in
(o)
OVERCLOCK=1
;;
(v)
DISP_WIDTH=640
DISP_HEIGHT=480
@ -24,14 +28,15 @@ while getopts "hvd:m:" o; do
MEMSIZE=$OPTARG
;;
(d)
DISK_IMAGE=$OPTARG
DISC_IMAGE=$OPTARG
;;
(h|?)
echo "Usage: $0 [-v] [-m KiB] [-d diskimage]"
echo ""
echo " -v: Use framebuffer resolution 640x480 instead of 512x342"
echo " -m: Set memory size in KiB"
echo " -d: Specify disk image to include"
echo " -m: Set memory size in KiB (over 400kB requires psram)"
echo " -d: Specify disc image to include"
echo " -o: Overclock to 264MHz (known to be incompatible with psram)"
echo ""
echo "PSRAM is automatically set depending on memory & framebuffer details"
exit
@ -44,6 +49,9 @@ shift $((OPTIND-1))
TAG=fruitjam_${DISP_WIDTH}x${DISP_HEIGHT}_${MEMSIZE}k
PSRAM=$((MEMSIZE > 400))
if [ $PSRAM -ne 0 ] ; then
if [ $OVERCLOCK -ne 0 ]; then
echo "*** Overclock + PSRAM is known not to work. You have been warned."
fi
TAG=${TAG}_psram
CMAKE_ARGS="$CMAKE_ARGS -DUSE_PSRAM=1"
fi
@ -54,22 +62,18 @@ if [ "$MIRROR_FRAMEBUFFER" -eq 0 ]; then
fi
# Append disk name to build directory if disk image is specified
if [ -n "$DISK_IMAGE" ] && [ -f "$DISK_IMAGE" ]; then
if [ -n "$DISC_IMAGE" ] && [ -f "$DISC_IMAGE" ]; then
# Extract filename without extension
DISK_NAME=$(basename "$DISK_IMAGE" | sed 's/\.[^.]*$//')
TAG=${TAG}_${DISK_NAME}
CMAKE_ARGS="$CMAKE_ARGS -DDISC_IMAGE=${DISC_IMAGE}"
DISC_IMAGE=$(basename "$DISC_IMAGE" | sed 's/\.[^.]*$//')
TAG=${TAG}_${DISC_IMAGE}
fi
if [ $OVERCLOCK -ne 0 ]; then
TAG=${TAG}_overclock
fi
set -x
make -C external/umac clean
make -C external/umac DISP_WIDTH=${DISP_WIDTH} DISP_HEIGHT=${DISP_HEIGHT} MEMSIZE=${MEMSIZE}
rm -f rom.bin
./external/umac/main -r '4D1F8172 - MacPlus v3.ROM' -W rom.bin || true
[ -f rom.bin ]
xxd -i < rom.bin > incbin/umac-rom.h
if [ -n "$DISK_IMAGE" ] && [ -f "$DISK_IMAGE" ]; then
xxd -i < "$DISK_IMAGE" > incbin/umac-disc.h
fi
rm -rf build_${TAG}
cmake -S . -B build_${TAG} \
-DPICO_SDK_PATH=../pico-sdk \
@ -80,5 +84,7 @@ cmake -S . -B build_${TAG} \
-DSD_TX=35 -DSD_RX=36 -DSD_SCK=34 -DSD_CS=39 -DUSE_SD=1 \
-DUART_TX=44 -DUART_RX=45 -DUART=0 \
-DBOARD_FILE=boards/adafruit_fruit_jam.c \
-DSD_MHZ=16 \
-DOVERCLOCK=${OVERCLOCK} \
${CMAKE_ARGS} "$@"
make -C build_${TAG} -j$(nproc)

11
include/clocking.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <stdint.h>
enum clk_sys_speed {
CLK_SYS_264MHZ = 2,
CLK_SYS_176MHZ = 3,
CLK_SYS_132MHZ = 4,
};
extern void overclock(enum clk_sys_speed clk_sys_div, uint32_t bit_clk_hz);

2
roms.sha256sum Normal file
View file

@ -0,0 +1,2 @@
dd908e2b65772a6b1f0c859c24e9a0d3dcde17b1c6a24f4abd8955846d7895e7 *rom.bin
464ff9b8a55b0a8fe10d394b411f9c753fcd9d477c0d2f1118ce9927b19bee54 *umac0ro.img

180
src/clocking.c Normal file
View file

@ -0,0 +1,180 @@
#include "clocking.h"
#include <stdio.h>
#include "pico.h"
#include "pico/stdio.h"
#include "hardware/clocks.h"
#include "hardware/pll.h"
#include "hardware/structs/ioqspi.h"
#include "hardware/structs/qmi.h"
#include "hardware/sync.h"
#include "hardware/vreg.h"
static void __no_inline_not_in_flash_func(set_qmi_timing)() {
// Make sure flash is deselected - QMI doesn't appear to have a busy flag(!)
while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS)
;
qmi_hw->m[0].timing = 0x40000202;
//qmi_hw->m[0].timing = 0x40000101;
// Force a read through XIP to ensure the timing is applied
volatile uint32_t* ptr = (volatile uint32_t*)0x14000000;
(void) *ptr;
}
#ifndef RP2350_PSRAM_MAX_SELECT_FS64
#define RP2350_PSRAM_MAX_SELECT_FS64 (125000000)
#endif
#ifndef RP2350_PSRAM_MIN_DESELECT_FS
#define RP2350_PSRAM_MIN_DESELECT_FS (50000000)
#endif
#ifndef RP2350_PSRAM_RX_DELAY_FS
#define RP2350_PSRAM_RX_DELAY_FS (3333333)
#endif
#ifndef RP2350_PSRAM_MAX_SCK_HZ
#define RP2350_PSRAM_MAX_SCK_HZ (133000000)
#endif
#define SEC_TO_FS 1000000000000000ll
static void __no_inline_not_in_flash_func(set_psram_timing)(void) {
// Get secs / cycle for the system clock - get before disabling interrupts.
uint32_t sysHz = (uint32_t)clock_get_hz(clk_sys);
// Calculate the clock divider - goal to get clock used for PSRAM <= what
// the PSRAM IC can handle - which is defined in RP2350_PSRAM_MAX_SCK_HZ
volatile uint8_t clockDivider = (sysHz + RP2350_PSRAM_MAX_SCK_HZ - 1) / RP2350_PSRAM_MAX_SCK_HZ;
uint32_t intr_stash = save_and_disable_interrupts();
// Get the clock femto seconds per cycle.
uint32_t fsPerCycle = SEC_TO_FS / sysHz;
uint32_t fsPerHalfCycle = fsPerCycle / 2;
// the maxSelect value is defined in units of 64 clock cycles
// So maxFS / (64 * fsPerCycle) = maxSelect = RP2350_PSRAM_MAX_SELECT_FS64/fsPerCycle
volatile uint8_t maxSelect = RP2350_PSRAM_MAX_SELECT_FS64 / fsPerCycle;
// minDeselect time - in system clock cycle
// Must be higher than 50ns (min deselect time for PSRAM) so add a fsPerCycle - 1 to round up
// So minFS/fsPerCycle = minDeselect = RP2350_PSRAM_MIN_DESELECT_FS/fsPerCycle
volatile uint8_t minDeselect = (RP2350_PSRAM_MIN_DESELECT_FS + fsPerCycle - 1) / fsPerCycle;
// RX delay (RP2350 datasheet 12.14.3.1) delay between between rising edge of SCK and
// the start of RX sampling. Expressed in 0.5 system clock cycles. Smallest value
// >= 3.3ns.
volatile uint8_t rxDelay = (RP2350_PSRAM_RX_DELAY_FS + fsPerHalfCycle - 1) / fsPerHalfCycle;
printf("syshz=%u\n", sysHz);
printf("Max Select: %d, Min Deselect: %d, RX delay: %d, clock divider: %d\n", maxSelect, minDeselect, rxDelay, clockDivider);
printf("PSRAM clock rate %.1fMHz\n", (float)sysHz / clockDivider / 1e6);
qmi_hw->m[1].timing = QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | // Break between pages.
3 << QMI_M1_TIMING_SELECT_HOLD_LSB | // Delay releasing CS for 3 extra system cycles.
rxDelay << QMI_M1_TIMING_RXDELAY_LSB | // Delay between SCK and RX sampling
1 << QMI_M1_TIMING_COOLDOWN_LSB | 1 << QMI_M1_TIMING_RXDELAY_LSB |
maxSelect << QMI_M1_TIMING_MAX_SELECT_LSB | minDeselect << QMI_M1_TIMING_MIN_DESELECT_LSB |
clockDivider << QMI_M1_TIMING_CLKDIV_LSB;
restore_interrupts(intr_stash);
}
static void __no_inline_not_in_flash_func(clock_init)(int sys_clk_div) {
uint32_t intr_stash = save_and_disable_interrupts();
// Before messing with clock speeds ensure QSPI clock is nice and slow
hw_write_masked(&qmi_hw->m[0].timing, 6, QMI_M0_TIMING_CLKDIV_BITS);
// We're going to go fast, boost the voltage a little
vreg_set_voltage(VREG_VOLTAGE_1_15);
// Force a read through XIP to ensure the timing is applied before raising the clock rate
volatile uint32_t* ptr = (volatile uint32_t*)0x14000000;
(void) *ptr;
// Before we touch PLLs, switch sys and ref cleanly away from their aux sources.
hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS);
while (clocks_hw->clk[clk_sys].selected != 0x1)
tight_loop_contents();
hw_write_masked(&clocks_hw->clk[clk_ref].ctrl, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, CLOCKS_CLK_REF_CTRL_SRC_BITS);
while (clocks_hw->clk[clk_ref].selected != 0x4)
tight_loop_contents();
// Stop the other clocks so we don't worry about overspeed
clock_stop(clk_usb);
clock_stop(clk_adc);
clock_stop(clk_peri);
clock_stop(clk_hstx);
// Set USB PLL to 528MHz
pll_init(pll_usb, PLL_COMMON_REFDIV, 1584 * MHZ, 3, 1);
const uint32_t usb_pll_freq = 528 * MHZ;
// CLK SYS = PLL USB 528MHz / sys_clk_div = 264MHz, 176MHz, or 132MHz
clock_configure(clk_sys,
CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
usb_pll_freq, usb_pll_freq / sys_clk_div);
// CLK PERI = PLL USB 528MHz / 4 = 132MHz
clock_configure(clk_peri,
0, // Only AUX mux on ADC
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
usb_pll_freq, usb_pll_freq / 4);
// CLK USB = PLL USB 528MHz / 11 = 48MHz
clock_configure(clk_usb,
0, // No GLMUX
CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
usb_pll_freq,
USB_CLK_KHZ * KHZ);
// CLK ADC = PLL USB 528MHz / 11 = 48MHz
clock_configure(clk_adc,
0, // No GLMUX
CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
usb_pll_freq,
USB_CLK_KHZ * KHZ);
// Now we are running fast set fast QSPI clock and read delay
set_qmi_timing();
restore_interrupts(intr_stash);
}
void overclock(enum clk_sys_speed clk_sys_div, uint32_t bit_clk_khz) {
clock_init(clk_sys_div);
stdio_init_all();
set_psram_timing();
#define SHOW_CLK(i) printf("clk_get_hz(%s) -> %u\n", #i, clock_get_hz(i));
SHOW_CLK(clk_ref);
SHOW_CLK(clk_sys);
SHOW_CLK(clk_peri);
SHOW_CLK(clk_hstx);
SHOW_CLK(clk_usb);
SHOW_CLK(clk_adc);
const uint32_t dvi_clock_khz = bit_clk_khz >> 1;
printf("bit_clk_khz = %u dvi_clock_khz = %u\n", bit_clk_khz, dvi_clock_khz);
uint vco_freq, post_div1, post_div2;
if (!check_sys_clock_khz(dvi_clock_khz, &vco_freq, &post_div1, &post_div2))
panic("System clock of %u kHz cannot be exactly achieved", dvi_clock_khz);
const uint32_t freq = vco_freq / (post_div1 * post_div2);
// Set the sys PLL to the requested freq
pll_init(pll_sys, PLL_COMMON_REFDIV, vco_freq, post_div1, post_div2);
// CLK HSTX = Requested freq
clock_configure(clk_hstx,
0,
CLOCKS_CLK_HSTX_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
freq, freq);
}

View file

@ -45,6 +45,7 @@
#include "tusb.h"
#include "umac.h"
#include "clocking.h"
#if USE_SD
#include "f_util.h"
@ -64,6 +65,8 @@
uint8_t *audio_base;
static void audio_setup();
static bool audio_poll();
static void set_mute_state(bool new_state);
static absolute_time_t automute_time;
#endif
////////////////////////////////////////////////////////////////////////////////
@ -103,15 +106,11 @@ static void io_init()
static void poll_led_etc()
{
static int led_on = 0;
static absolute_time_t last = 0;
absolute_time_t now = get_absolute_time();
if (absolute_time_diff_us(last, now) > 500*1000) {
last = now;
//led_on ^= 1;
//gpio_put(GPIO_LED_PIN, led_on);
}
}
@ -156,6 +155,12 @@ static void poll_umac()
int64_t p_1hz = absolute_time_diff_us(last_1hz, now);
int64_t p_vsync = absolute_time_diff_us(last_vsync, now);
bool pending_vsync = p_vsync > 16667;
#if ENABLE_AUDIO
if (automute_time < now) {
automute_time = at_the_end_of_time;
set_mute_state(false);
}
#endif
#if ENABLE_AUDIO
pending_vsync |= audio_poll();
#endif
@ -204,10 +209,12 @@ static void poll_umac()
#if USE_SD
static int disc_do_read(void *ctx, uint8_t *data, unsigned int offset, unsigned int len)
{
gpio_put(GPIO_LED_PIN, 1);
FIL *fp = (FIL *)ctx;
f_lseek(fp, offset);
unsigned int did_read = 0;
FRESULT fr = f_read(fp, data, len, &did_read);
gpio_put(GPIO_LED_PIN, 0);
if (fr != FR_OK || len != did_read) {
printf("disc: f_read returned %d, read %u (of %u)\n", fr, did_read, len);
return -1;
@ -217,10 +224,12 @@ static int disc_do_read(void *ctx, uint8_t *data, unsigned int offset, unsi
static int disc_do_write(void *ctx, uint8_t *data, unsigned int offset, unsigned int len)
{
gpio_put(GPIO_LED_PIN, 1);
FIL *fp = (FIL *)ctx;
f_lseek(fp, offset);
unsigned int did_write = 0;
FRESULT fr = f_write(fp, data, len, &did_write);
gpio_put(GPIO_LED_PIN, 0);
if (fr != FR_OK || len != did_write) {
printf("disc: f_write returned %d, read %u (of %u)\n", fr, did_write, len);
return -1;
@ -394,14 +403,25 @@ static void __no_inline_not_in_flash_func(setup_psram)(void) {
}
// RESETEN, RESET and quad enable
for (uint8_t i = 0; i < 3; i++) {
for (uint8_t i = 0; i < 4; i++) {
qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS;
if (i == 0) {
qmi_hw->direct_tx = 0x66;
} else if (i == 1) {
qmi_hw->direct_tx = 0x99;
} else {
qmi_hw->direct_tx = 0x35;
switch (i) {
case 0:
// RESETEN
qmi_hw->direct_tx = 0x66;
break;
case 1:
// RESET
qmi_hw->direct_tx = 0x99;
break;
case 2:
// Quad enable
qmi_hw->direct_tx = 0x35;
break;
case 3:
// Toggle wrap boundary mode
qmi_hw->direct_tx = 0xc0;
break;
}
while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) {
}
@ -471,11 +491,39 @@ static void __no_inline_not_in_flash_func(setup_psram)(void) {
int main()
{
// set_sys_clock_khz(250*1000, true);
setup_psram();
#if OVERCLOCK
overclock(CLK_SYS_264MHZ, 252000);
#endif
stdio_init_all();
printf("psram size %u\n", _psram_size);
#ifndef RAM_TEST
#define RAM_TEST (0)
#endif
#if RAM_TEST
for(int pass=0; pass<10; pass++) {
uint8_t acc = 1;
for(int i=0; i < _psram_size; i++) {
umac_ram[i] = acc;
acc = acc * 13 + 21;
}
acc = 1;
for(int i=0; i < _psram_size; i++) {
uint8_t ri = umac_ram[i];
if(ri != acc) {
panic("[%08x] Stored %02x read %02x", i, acc, ri);
}
acc = acc * 13 + 21;
}
printf("ram test pass %d OK\n", pass);
}
#endif
io_init();
#if ENABLE_AUDIO
@ -525,7 +573,6 @@ void writeRegister(uint8_t reg, uint8_t value) {
printf("res=%d\n", res);
panic("i2c_write_timeout failed: res=%d\n", res);
}
printf("Write Reg: %d = 0x%x\n", reg, value);
}
uint8_t readRegister(uint8_t reg) {
@ -533,28 +580,23 @@ uint8_t readRegister(uint8_t reg) {
buf[0] = reg;
int res = i2c_write_timeout_us(i2c0, I2C_ADDR, buf, sizeof(buf), /* nostop */ true, 1000);
if (res != 1) {
printf("res=%d\n", res);
panic("i2c_write_timeout failed: res=%d\n", res);
}
res = i2c_read_timeout_us(i2c0, I2C_ADDR, buf, sizeof(buf), /* nostop */ false, 1000);
if (res != 1) {
printf("res=%d\n", res);
panic("i2c_read_timeout failed: res=%d\n", res);
}
uint8_t value = buf[0];
printf("Read Reg: %d = 0x%x\n", reg, value);
return value;
}
void modifyRegister(uint8_t reg, uint8_t mask, uint8_t value) {
uint8_t current = readRegister(reg);
printf("Modify Reg: %d = [Before: 0x%x] with mask 0x%x and value 0x%x\n", reg, current, mask, value);
uint8_t new_value = (current & ~mask) | (value & mask);
writeRegister(reg, new_value);
}
void setPage(uint8_t page) {
printf("Set page %d\n", page);
writeRegister(0x00, page);
}
@ -567,9 +609,9 @@ void Wire_begin() {
static void setup_i2s_dac() {
gpio_init(22);
gpio_set_dir(22, true);
gpio_put(22, true); // allow i2s to come out of reset
gpio_init(22);
gpio_set_dir(22, true);
gpio_put(22, true); // allow i2s to come out of reset
Wire_begin();
sleep_ms(1000);
@ -612,9 +654,9 @@ gpio_put(22, true); // allow i2s to come out of reset
modifyRegister(0x05, 0x80, 0x80);
// Headset and GPIO Config
setPage(1);
modifyRegister(0x2e, 0xFF, 0x0b);
setPage(0);
setPage(1);
modifyRegister(0x2e, 0xFF, 0x0b);
setPage(0);
modifyRegister(0x43, 0x80, 0x80); // Headset Detect
modifyRegister(0x30, 0x80, 0x80); // INT1 Control
modifyRegister(0x33, 0x3C, 0x14); // GPIO1
@ -631,113 +673,32 @@ setPage(0);
// DAC Volume Control
setPage(0);
modifyRegister(0x40, 0x0C, 0x00);
writeRegister(0x41, 0x28); // Left DAC Vol
writeRegister(0x42, 0x28); // Right DAC Vol
// ADC Setup
modifyRegister(0x51, 0x80, 0x80);
modifyRegister(0x52, 0x80, 0x00);
writeRegister(0x53, 0x68); // ADC Volume
writeRegister(0x41, 0x0); // Left DAC Vol, 0dB
writeRegister(0x42, 0x0); // Right DAC Vol, 0dB
// Headphone and Speaker Setup
setPage(1);
modifyRegister(0x1F, 0xC0, 0xC0); // HP Driver
modifyRegister(0x28, 0x04, 0x04); // HP Left Gain
modifyRegister(0x29, 0x04, 0x04); // HP Right Gain
writeRegister(0x24, 0x0A); // Left Analog HP
writeRegister(0x25, 0x0A); // Right Analog HP
modifyRegister(0x1F, 0xC0, 0xC0); // HP Driver Powered
modifyRegister(0x28, 0x04, 0x04); // HP Left not muted
modifyRegister(0x29, 0x04, 0x04); // HP Right not muted
writeRegister(0x24, 50); // Left Analog HP, -26 dB
writeRegister(0x25, 50); // Right Analog HP, -26 dB
modifyRegister(0x28, 0x78, 0x40); // HP Left Gain
modifyRegister(0x29, 0x78, 0x40); // HP Right Gain
modifyRegister(0x28, 0x78, 0x00); // HP Left Gain, 0 db
modifyRegister(0x29, 0x78, 0x00); // HP Right Gain, 0 db
// Speaker Amp
modifyRegister(0x20, 0x80, 0x80);
modifyRegister(0x2A, 0x04, 0x04);
modifyRegister(0x2A, 0x18, 0x08);
writeRegister(0x26, 0x0A);
modifyRegister(0x20, 0x80, 0x80); // Amp enabled (0x80) disable with (0x00)
modifyRegister(0x2A, 0x04, 0x04); // Not muted (0x04) mute with (0x00)
modifyRegister(0x2A, 0x18, 0x08); // 0 dB gain
writeRegister(0x26, 40); // amp gain, -20.1 dB
// Return to page 0
setPage(0);
printf("Initialization complete!\n");
// Read all registers for verification
printf("Reading all registers for verification:\n");
setPage(0);
readRegister(0x00); // AIC31XX_PAGECTL
readRegister(0x01); // AIC31XX_RESET
readRegister(0x03); // AIC31XX_OT_FLAG
readRegister(0x04); // AIC31XX_CLKMUX
readRegister(0x05); // AIC31XX_PLLPR
readRegister(0x06); // AIC31XX_PLLJ
readRegister(0x07); // AIC31XX_PLLDMSB
readRegister(0x08); // AIC31XX_PLLDLSB
readRegister(0x0B); // AIC31XX_NDAC
readRegister(0x0C); // AIC31XX_MDAC
readRegister(0x0D); // AIC31XX_DOSRMSB
readRegister(0x0E); // AIC31XX_DOSRLSB
readRegister(0x10); // AIC31XX_MINI_DSP_INPOL
readRegister(0x12); // AIC31XX_NADC
readRegister(0x13); // AIC31XX_MADC
readRegister(0x14); // AIC31XX_AOSR
readRegister(0x19); // AIC31XX_CLKOUTMUX
readRegister(0x1A); // AIC31XX_CLKOUTMVAL
readRegister(0x1B); // AIC31XX_IFACE1
readRegister(0x1C); // AIC31XX_DATA_OFFSET
readRegister(0x1D); // AIC31XX_IFACE2
readRegister(0x1E); // AIC31XX_BCLKN
readRegister(0x1F); // AIC31XX_IFACESEC1
readRegister(0x20); // AIC31XX_IFACESEC2
readRegister(0x21); // AIC31XX_IFACESEC3
readRegister(0x22); // AIC31XX_I2C
readRegister(0x24); // AIC31XX_ADCFLAG
readRegister(0x25); // AIC31XX_DACFLAG1
readRegister(0x26); // AIC31XX_DACFLAG2
readRegister(0x27); // AIC31XX_OFFLAG
readRegister(0x2C); // AIC31XX_INTRDACFLAG
readRegister(0x2D); // AIC31XX_INTRADCFLAG
readRegister(0x2E); // AIC31XX_INTRDACFLAG2
readRegister(0x2F); // AIC31XX_INTRADCFLAG2
readRegister(0x30); // AIC31XX_INT1CTRL
readRegister(0x31); // AIC31XX_INT2CTRL
readRegister(0x33); // AIC31XX_GPIO1
readRegister(0x3C); // AIC31XX_DACPRB
readRegister(0x3D); // AIC31XX_ADCPRB
readRegister(0x3F); // AIC31XX_DACSETUP
readRegister(0x40); // AIC31XX_DACMUTE
readRegister(0x41); // AIC31XX_LDACVOL
readRegister(0x42); // AIC31XX_RDACVOL
readRegister(0x43); // AIC31XX_HSDETECT
readRegister(0x51); // AIC31XX_ADCSETUP
readRegister(0x52); // AIC31XX_ADCFGA
readRegister(0x53); // AIC31XX_ADCVOL
setPage(1);
readRegister(0x1F); // AIC31XX_HPDRIVER
readRegister(0x20); // AIC31XX_SPKAMP
readRegister(0x21); // AIC31XX_HPPOP
readRegister(0x22); // AIC31XX_SPPGARAMP
readRegister(0x23); // AIC31XX_DACMIXERROUTE
readRegister(0x24); // AIC31XX_LANALOGHPL
readRegister(0x25); // AIC31XX_RANALOGHPR
readRegister(0x26); // AIC31XX_LANALOGSPL
readRegister(0x27); // AIC31XX_RANALOGSPR
readRegister(0x28); // AIC31XX_HPLGAIN
readRegister(0x29); // AIC31XX_HPRGAIN
readRegister(0x2A); // AIC31XX_SPLGAIN
readRegister(0x2B); // AIC31XX_SPRGAIN
readRegister(0x2C); // AIC31XX_HPCONTROL
readRegister(0x2E); // AIC31XX_MICBIAS
readRegister(0x2F); // AIC31XX_MICPGA
readRegister(0x30); // AIC31XX_MICPGAPI
readRegister(0x31); // AIC31XX_MICPGAMI
readRegister(0x32); // AIC31XX_MICPGACM
setPage(3);
readRegister(0x10); // AIC31XX_TIMERDIVIDER
printf("Audio I2C Initialization complete!\n");
}
static int volscale;
@ -746,9 +707,10 @@ static int volscale;
int16_t audio[SAMPLES_PER_BUFFER];
void umac_audio_trap() {
static int led_on;
led_on ^= 1;
gpio_put(GPIO_LED_PIN, 1);
set_mute_state(volscale != 0);
if(volscale) {
automute_time = make_timeout_time_ms(500);
}
int32_t offset = 128;
uint16_t *audiodata = (uint16_t*)audio_base;
int scale = volscale;
@ -776,7 +738,6 @@ const struct audio_i2s_config config =
{
.data_pin = PICO_AUDIO_I2S_DATA_PIN,
.clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE,
.clock_pin_swapped = true,
.pio_sm = 0,
.dma_channel = 3
};
@ -787,6 +748,7 @@ static struct audio_buffer_format producer_format = {
};
static void audio_setup() {
setup_i2s_dac();
const struct audio_format *output_format = audio_i2s_setup(&audio_format, &config);
assert(output_format);
if (!output_format) {
@ -802,14 +764,31 @@ static void audio_setup() {
static bool audio_poll() {
audio_buffer_t *buffer = take_audio_buffer(producer_pool, false);
if (!buffer) return false;
gpio_put(GPIO_LED_PIN, 0);
memcpy(buffer->buffer->bytes, audio, sizeof(audio));
buffer->sample_count = SAMPLES_PER_BUFFER;
give_audio_buffer(producer_pool, buffer);
return true;
}
static bool mute_state = false;
static void set_mute_state(bool new_state) {
if(mute_state == new_state) return;
mute_state = new_state;
setPage(1);
if(mute_state) {
modifyRegister(0x28, 0x04, 0x04); // HP Left not muted
modifyRegister(0x29, 0x04, 0x04); // HP Right not muted
modifyRegister(0x2A, 0x04, 0x04); // Speaker not muted
} else {
modifyRegister(0x28, 0x04, 0x0); // HP Left muted
modifyRegister(0x29, 0x04, 0x0); // HP Right muted
modifyRegister(0x2A, 0x04, 0x0); // Speaker muted
}
}
void umac_audio_cfg(int volume, int sndres) {
volscale = sndres ? 0 : 65536 * volume / 7;
set_mute_state(volscale != 0);
}
#endif