Skip to content

Commit

Permalink
feat(gpio): working GPIO implementation #20
Browse files Browse the repository at this point in the history
It's still not unit-tested, but at least hello_gpio_irq runs successfully:

```typescript
import * as fs from 'fs';
import { RP2040 } from '../src';
import { bootromB1 } from './bootrom';
import { loadHex } from './intelhex';
import { GPIOPinState } from '../src/gpio-pin';

// Create an array with the compiled code of blink
// Execute the instructions from this array, one by one.
const hex = fs.readFileSync('hello_gpio_irq.hex', 'utf-8');
const mcu = new RP2040();
mcu.logMissingInstructions = false;
mcu.loadBootrom(bootromB1);
loadHex(hex, mcu.flash, 0x10000000);

mcu.gpio[25].addListener((state) => {
  console.log('GPIO 25', GPIOPinState[state]);
});

mcu.uart[0].onByte = (value) => {
  process.stdout.write(new Uint8Array([value]));
};

mcu.PC = 0x10000000;
mcu.execute();

let gpio2Value = false;
setInterval(() => {
  gpio2Value = !gpio2Value;
  console.log('Changing GPIO 2 value to', gpio2Value);
  mcu.gpio[2].setInputValue(gpio2Value);
}, 1000);
```

close #20
  • Loading branch information
urish committed May 25, 2021
1 parent 0de9542 commit 5524b74
Show file tree
Hide file tree
Showing 5 changed files with 411 additions and 14 deletions.
201 changes: 194 additions & 7 deletions src/gpio-pin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,206 @@ export enum GPIOPinState {
InputPullDown,
}

export const FUNCTION_SIO = 5;

export type GPIOPinListener = (state: GPIOPinState, oldState: GPIOPinState) => void;

function applyOverride(value: boolean, overrideType: number) {
switch (overrideType) {
case 0:
return value;
case 1:
return !value;
case 2:
return false;
case 3:
return true;
}
console.error('applyOverride received invalid override type', overrideType);
return value;
}

const IRQ_EDGE_HIGH = 1 << 3;
const IRQ_EDGE_LOW = 1 << 2;
const IRQ_LEVEL_HIGH = 1 << 1;
const IRQ_LEVEL_LOW = 1 << 0;

export class GPIOPin {
constructor(readonly rp2040: RP2040, readonly index: number) {}
private rawInputValue = false;
private lastValue = this.value;

ctrl: number = 0x1f;
padValue: number = 0b0110110;
irqEnableMask = 0;
irqForceMask = 0;
irqStatus = 0;

private readonly listeners = new Set<GPIOPinListener>();

constructor(readonly rp2040: RP2040, readonly index: number, readonly name = index.toString()) {}

get rawInterrupt() {
return !!((this.irqStatus & this.irqEnableMask) | this.irqForceMask);
}

get isSlewFast() {
return !!(this.padValue & 1);
}

get schmittEnabled() {
return !!(this.padValue & 2);
}

get pulldownEnabled() {
return !!(this.padValue & 4);
}

get pullupEnabled() {
return !!(this.padValue & 8);
}

get driveStrength() {
return (this.padValue >> 4) & 0x3;
}

get inputEnable() {
return !!(this.padValue & 0x40);
}

get outputDisable() {
return !!(this.padValue & 0x80);
}

get functionSelect() {
return this.ctrl & 0x1f;
}

get outputOverride() {
return (this.ctrl >> 8) & 0x3;
}

get outputEnableOverride() {
return (this.ctrl >> 12) & 0x3;
}

get inputOverride() {
return (this.ctrl >> 16) & 0x3;
}

get irqOverride() {
return (this.ctrl >> 28) & 0x3;
}

get rawOutputEnable() {
switch (this.functionSelect) {
case FUNCTION_SIO: {
const { index, rp2040 } = this;
const bitmask = 1 << index;
return !!(rp2040.sio.gpioOutputEnable & bitmask);
}
default:
return false;
}
}

get rawOutputValue() {
switch (this.functionSelect) {
case FUNCTION_SIO: {
const { index, rp2040 } = this;
const bitmask = 1 << index;
return !!(rp2040.sio.gpioValue & bitmask);
}
default:
return false;
}
}

get inputValue() {
return applyOverride(this.rawInputValue, this.inputOverride);
}

get irqValue() {
return applyOverride(this.rawInterrupt, this.irqOverride);
}

get outputEnable() {
return applyOverride(this.rawOutputEnable, this.outputEnableOverride);
}

get outputValue() {
return applyOverride(this.rawOutputValue, this.outputOverride);
}

/**
* Returns the STATUS register value for the pin, as outlined in section 2.19.6 of the datasheet
*/
get status() {
const irqToProc = this.irqValue ? 1 << 26 : 0;
const irqFromPad = this.rawInterrupt ? 1 << 24 : 0;
const inToPeri = this.inputValue ? 1 << 19 : 0;
const inFromPad = this.rawInputValue ? 1 << 17 : 0;
const oeToPad = this.outputEnable ? 1 << 13 : 0;
const oeFromPeri = this.rawOutputEnable ? 1 << 12 : 0;
const outToPad = this.outputValue ? 1 << 9 : 0;
const outFromPeri = this.rawOutputValue ? 1 << 8 : 0;
return (
irqToProc | irqFromPad | inToPeri | inFromPad | oeToPad | oeFromPeri | outToPad | outFromPeri
);
}

get value() {
const { index, rp2040 } = this;
const bitmask = 1 << index;
if (rp2040.sio.gpioOutputEnable & bitmask) {
return rp2040.sio.gpioValue & bitmask ? GPIOPinState.High : GPIOPinState.Low;
if (this.outputEnable) {
return this.outputValue ? GPIOPinState.High : GPIOPinState.Low;
} else {
// TODO account for pullup/pulldown
// TODO: check what happens when we enable both pullup/pulldown
if (this.pulldownEnabled) {
return GPIOPinState.InputPullDown;
}
if (this.pullupEnabled) {
return GPIOPinState.InputPullUp;
}
return GPIOPinState.Input;
}
}

// TODO add a way to listen for value changes
setInputValue(value: boolean) {
this.rawInputValue = value;
const prevIrqValue = this.irqValue;
if (value) {
this.irqStatus |= IRQ_EDGE_HIGH | IRQ_LEVEL_HIGH;
this.irqStatus &= ~IRQ_LEVEL_LOW;
} else {
this.irqStatus |= IRQ_EDGE_LOW | IRQ_LEVEL_LOW;
this.irqStatus &= ~IRQ_LEVEL_HIGH;
}
if (this.irqValue !== prevIrqValue) {
this.rp2040.updateIOInterrupt();
}
}

checkForUpdates() {
const { lastValue, value } = this;
if (value !== lastValue) {
this.lastValue = value;
for (const listener of this.listeners) {
listener(value, lastValue);
}
}
}

updateIRQValue(value: number) {
if (value & IRQ_EDGE_LOW && this.irqStatus & IRQ_EDGE_LOW) {
this.irqStatus &= ~IRQ_EDGE_LOW;
this.rp2040.updateIOInterrupt();
}
if (value & IRQ_EDGE_HIGH && this.irqStatus & IRQ_EDGE_HIGH) {
this.irqStatus &= ~IRQ_EDGE_HIGH;
this.rp2040.updateIOInterrupt();
}
}

addListener(callback: GPIOPinListener) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
}
97 changes: 97 additions & 0 deletions src/peripherals/io.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { RP2040 } from '../rp2040';
import { BasePeripheral, Peripheral } from './peripheral';

const GPIO_CTRL_LAST = 0x0ec;
const INTR0 = 0xf0;
const PROC0_INTE0 = 0x100;
const PROC0_INTF0 = 0x110;
const PROC0_INTS0 = 0x120;
const PROC0_INTS3 = 0x12c;

export class RPIO extends BasePeripheral implements Peripheral {
constructor(rp2040: RP2040, name: string) {
super(rp2040, name);
}

getPinFromOffset(offset: number) {
const gpioIndex = offset >>> 3;
return {
gpio: this.rp2040.gpio[gpioIndex],
isCtrl: !!(offset & 0x4),
};
}

readUint32(offset: number) {
if (offset <= GPIO_CTRL_LAST) {
const { gpio, isCtrl } = this.getPinFromOffset(offset);
return isCtrl ? gpio.ctrl : gpio.status;
}
if (offset >= INTR0 && offset <= PROC0_INTS3) {
const startIndex = (offset & 0xf) * 2;
const register = offset & ~0xf;
const { gpio } = this.rp2040;
let result = 0;
for (let index = 7; index >= 0; index--) {
const pin = gpio[index + startIndex];
if (!pin) {
continue;
}
result <<= 4;
switch (register) {
case INTR0:
result |= pin.irqStatus;
break;
case PROC0_INTE0:
result |= pin.irqEnableMask;
break;
case PROC0_INTF0:
result |= pin.irqForceMask;
break;
case PROC0_INTS0:
result |= (pin.irqStatus & pin.irqEnableMask) | pin.irqForceMask;
break;
}
}
return result;
}
return super.readUint32(offset);
}

writeUint32(offset: number, value: number) {
if (offset <= GPIO_CTRL_LAST) {
const { gpio, isCtrl } = this.getPinFromOffset(offset);
if (isCtrl) {
gpio.ctrl = value;
gpio.checkForUpdates();
}
return;
}
if (offset >= INTR0 && offset <= PROC0_INTS3) {
const startIndex = (offset & 0xf) * 2;
const register = offset & ~0xf;
const { gpio } = this.rp2040;
for (let index = 0; index < 8; index++) {
const pin = gpio[index + startIndex];
if (!pin) {
continue;
}
const pinValue = (value >> (index * 4)) & 0xf;
const pinRawWriteValue = (this.rawWriteValue >> (index * 4)) & 0xf;
switch (register) {
case INTR0:
pin.updateIRQValue(pinRawWriteValue);
break;
case PROC0_INTE0:
pin.irqEnableMask = pinValue;
break;
case PROC0_INTF0:
pin.irqForceMask = pinValue;
break;
}
}
return;
}

super.writeUint32(offset, value);
}
}
59 changes: 59 additions & 0 deletions src/peripherals/pads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { RP2040 } from '../rp2040';
import { BasePeripheral, Peripheral } from './peripheral';

const VOLTAGE_SELECT = 0;
const GPIO_FIRST = 0x4;
const GPIO_LAST = 0x78;

const QSPI_FIRST = 0x4;
const QSPI_LAST = 0x18;

export type IIOBank = 'qspi' | 'bank0';

export class RPPADS extends BasePeripheral implements Peripheral {
voltageSelect = 0;

private readonly firstPadRegister = this.bank === 'qspi' ? QSPI_FIRST : GPIO_FIRST;
private readonly lastPadRegister = this.bank === 'qspi' ? QSPI_LAST : GPIO_LAST;

constructor(rp2040: RP2040, name: string, readonly bank: IIOBank) {
super(rp2040, name);
}

getPinFromOffset(offset: number) {
const gpioIndex = (offset - this.firstPadRegister) >>> 2;
if (this.bank === 'qspi') {
return this.rp2040.qspi[gpioIndex];
} else {
return this.rp2040.gpio[gpioIndex];
}
}

readUint32(offset: number) {
if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) {
const gpio = this.getPinFromOffset(offset);
return gpio.padValue;
}
switch (offset) {
case VOLTAGE_SELECT:
return this.voltageSelect;
}
return super.readUint32(offset);
}

writeUint32(offset: number, value: number) {
if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) {
const gpio = this.getPinFromOffset(offset);
gpio.padValue = value;
gpio.checkForUpdates();
return;
}
switch (offset) {
case VOLTAGE_SELECT:
this.voltageSelect = value & 1;
break;
default:
super.writeUint32(offset, value);
}
}
}
Loading

0 comments on commit 5524b74

Please sign in to comment.