From e32a11f04c5fccb9776693b6a54d9dfcbdde6615 Mon Sep 17 00:00:00 2001 From: Guy Sviry <32539816+guysv@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:12:19 +0300 Subject: [PATCH] feat(spi): DMA support (#131) * initial SPI DMA support * spi dma: add dma channel update points * micropython spi test --------- Co-authored-by: Guy Sviry --- .github/workflows/ci-micropython.yml | 6 +++- demo/micropython-run.ts | 1 + package.json | 3 +- src/peripherals/spi.ts | 32 +++++++++++++++-- src/rp2040.ts | 11 +++++- test/micropython-spi-test.ts | 53 ++++++++++++++++++++++++++++ test/micropython/main-spi.py | 18 ++++++++++ test/mklittlefs.py | 13 ++++--- 8 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 test/micropython-spi-test.ts create mode 100644 test/micropython/main-spi.py diff --git a/.github/workflows/ci-micropython.yml b/.github/workflows/ci-micropython.yml index e9f6807..76235ac 100644 --- a/.github/workflows/ci-micropython.yml +++ b/.github/workflows/ci-micropython.yml @@ -24,6 +24,10 @@ jobs: env: VERSION: ${{ matrix.micropython_version }} - name: Create filesystem - run: python test/mklittlefs.py + run: python test/mklittlefs.py littlefs.img test/micropython/main.py - name: Test Micropython run: timeout 10 npm run start:micropython -- --image micropython.uf2 --expect-text "Hello, MicroPython!" + - name: Create SPI test filesystem + run: python test/mklittlefs.py littlefs-spi.img test/micropython/main-spi.py + - name: Test Micropython SPI + run: timeout 10 npm run test:micropython-spi -- "hello world" "h" "0123456789abcdef0123456789abcdef0123456789abcdef" diff --git a/demo/micropython-run.ts b/demo/micropython-run.ts index e284e3a..515a5a8 100644 --- a/demo/micropython-run.ts +++ b/demo/micropython-run.ts @@ -33,6 +33,7 @@ console.log(`Loading uf2 image ${imageName}`); loadUF2(imageName, mcu); if (fs.existsSync('littlefs.img') && !args.circuitpython) { + console.log(`Loading uf2 image littlefs.img`); loadMicropythonFlashImage('littlefs.img', mcu); } else if (fs.existsSync('fat12.img') && args.circuitpython) { loadCircuitpythonFlashImage('fat12.img', mcu); diff --git a/package.json b/package.json index 1b8da1c..51ede32 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "start:micropython": "ts-node demo/micropython-run.ts", "start:circuitpython": "ts-node demo/micropython-run.ts --circuitpython", "start:gdbdiff": "ts-node debug/gdbdiff.ts", - "test": "jest" + "test": "jest", + "test:micropython-spi": "ts-node test/micropython-spi-test.ts" }, "devDependencies": { "@types/jest": "^27.4.1", diff --git a/src/peripherals/spi.ts b/src/peripherals/spi.ts index c2331d2..69d0097 100644 --- a/src/peripherals/spi.ts +++ b/src/peripherals/spi.ts @@ -1,5 +1,6 @@ import { RP2040 } from '../rp2040'; import { FIFO } from '../utils/fifo'; +import { DREQChannel } from './dma'; import { BasePeripheral, Peripheral } from './peripheral'; const SSPCR0 = 0x000; // Control register 0, SSPCR0 on page 3-4 @@ -58,6 +59,11 @@ const SSPRXINTR = 1 << 2; const SSPRTINTR = 1 << 1; const SSPRORINTR = 1 << 0; +export interface ISPIDMAChannels { + rx: DREQChannel; + tx: DREQChannel; +} + export class RPSPI extends BasePeripheral implements Peripheral { readonly rxFIFO = new FIFO(8); readonly txFIFO = new FIFO(8); @@ -105,8 +111,26 @@ export class RPSPI extends BasePeripheral implements Peripheral { return this.rp2040.clkPeri / (this.clockDivisor * (1 + scr)); } - constructor(rp2040: RP2040, name: string, readonly irq: number) { + private updateDMATx() { + if (this.txFIFO.full) { + this.rp2040.dma.clearDREQ(this.dreq.tx); + } else { + this.rp2040.dma.setDREQ(this.dreq.tx); + } + } + + private updateDMARx() { + if (this.rxFIFO.empty) { + this.rp2040.dma.clearDREQ(this.dreq.rx); + } else { + this.rp2040.dma.setDREQ(this.dreq.rx); + } + } + + constructor(rp2040: RP2040, name: string, readonly irq: number, readonly dreq: ISPIDMAChannels) { super(rp2040, name); + this.updateDMATx(); + this.updateDMARx(); } private doTX() { @@ -148,6 +172,9 @@ export class RPSPI extends BasePeripheral implements Peripheral { if (this.intStatus !== prevStatus) { this.checkInterrupts(); } + + this.updateDMATx(); + this.updateDMARx(); } readUint32(offset: number) { @@ -211,7 +238,8 @@ export class RPSPI extends BasePeripheral implements Peripheral { return; case SSPDR: if (!this.txFIFO.full) { - this.txFIFO.push(value); + // decoded with respect to SSPCR0.DSS + this.txFIFO.push(value & ((1 << this.dataBits) - 1)); this.doTX(); this.fifosUpdated(); } diff --git a/src/rp2040.ts b/src/rp2040.ts index 02e4cb4..0e639d8 100644 --- a/src/rp2040.ts +++ b/src/rp2040.ts @@ -69,7 +69,6 @@ export class RP2040 { }), ]; readonly i2c = [new RPI2C(this, 'I2C0', IRQ.I2C0), new RPI2C(this, 'I2C1', IRQ.I2C1)]; - readonly spi = [new RPSPI(this, 'SPI0', IRQ.SPI0), new RPSPI(this, 'SPI1', IRQ.SPI1)]; readonly pwm = new RPPWM(this, 'PWM_BASE'); readonly adc = new RPADC(this, 'ADC'); @@ -121,6 +120,16 @@ export class RP2040 { new RPPIO(this, 'PIO1', IRQ.PIO1_IRQ0, 1), ]; readonly usbCtrl = new RPUSBController(this, 'USB'); + readonly spi = [ + new RPSPI(this, 'SPI0', IRQ.SPI0, { + rx: DREQChannel.DREQ_SPI0_RX, + tx: DREQChannel.DREQ_SPI0_TX, + }), + new RPSPI(this, 'SPI1', IRQ.SPI1, { + rx: DREQChannel.DREQ_SPI1_RX, + tx: DREQChannel.DREQ_SPI1_TX, + }), + ]; private stopped = true; diff --git a/test/micropython-spi-test.ts b/test/micropython-spi-test.ts new file mode 100644 index 0000000..424a8aa --- /dev/null +++ b/test/micropython-spi-test.ts @@ -0,0 +1,53 @@ +import { GPIOPinState, RP2040 } from '../src'; +import { ConsoleLogger, LogLevel } from '../src/utils/logging'; +import { bootromB1 } from '../demo/bootrom'; +import { loadUF2, loadMicropythonFlashImage } from '../demo/load-flash'; +import fs from 'fs'; +import minimist from 'minimist'; + +const args = minimist(process.argv.slice(2)); + +const mcu = new RP2040(); +mcu.loadBootrom(bootromB1); +mcu.logger = new ConsoleLogger(LogLevel.Error); + +const imageName = 'micropython.uf2'; +console.log(`Loading uf2 image ${imageName}`); +loadUF2(imageName, mcu); + +const littlefs = 'littlefs-spi.img'; + +if (fs.existsSync(littlefs)) { + console.log(`Loading littlefs image ${littlefs}`); + loadMicropythonFlashImage(littlefs, mcu); +} + +let spiBuf = ''; +mcu.gpio[5].addListener((state: GPIOPinState, oldState: GPIOPinState) => { + if (!spiBuf) { + return; + } + + if (state === GPIOPinState.High && oldState === GPIOPinState.Low) { + if (spiBuf !== args._?.shift()) { + console.log('SPI TEST FAILED.'); + process.exit(1); + } else { + console.log('SPI MESSAGE RECEIVED.'); + spiBuf = ''; + } + + if (args._.length === 0) { + console.log('SPI TEST PASSED.'); + process.exit(0); + } + } +}); + +mcu.spi[0].onTransmit = (char) => { + spiBuf += String.fromCharCode(char); + mcu.spi[0].completeTransmit(0); +}; + +mcu.core.PC = 0x10000000; +mcu.execute(); diff --git a/test/micropython/main-spi.py b/test/micropython/main-spi.py new file mode 100644 index 0000000..eccf3cd --- /dev/null +++ b/test/micropython/main-spi.py @@ -0,0 +1,18 @@ +from machine import Pin, SPI + +spi = SPI(0) +cs = Pin(5, Pin.OUT) +cs(1) + +spi.init(baudrate=10 * 1024 * 1024, polarity=0, phase=0) +cs(0) +spi.write(b'hello world') +cs(1) + +cs(0) +spi.write(b'h') +cs(1) + +cs(0) +spi.write(b'0123456789abcdef0123456789abcdef0123456789abcdef') +cs(1) \ No newline at end of file diff --git a/test/mklittlefs.py b/test/mklittlefs.py index df6ea39..56ca781 100644 --- a/test/mklittlefs.py +++ b/test/mklittlefs.py @@ -1,16 +1,19 @@ # Simple script to create a littlefs image for running the MicroPython test from littlefs import LittleFS -from os.path import join +from os.path import basename +from sys import argv -files = ['main.py'] -source_dir = 'test/micropython' -output_image = 'littlefs.img' +files = argv[2:] +# files = ['test/micropython/main.py'] +output_image = argv[1] lfs = LittleFS(block_size=4096, block_count=352, prog_size=256) +main = True for filename in files: - with open(join(source_dir, filename), 'r') as src_file, lfs.open(filename, 'w') as lfs_file: + with open(filename, 'r') as src_file, lfs.open("main.py" if main else basename(filename), 'w') as lfs_file: lfs_file.write(src_file.read()) + main = False with open(output_image, 'wb') as fh: fh.write(lfs.context.buffer)