From 2fd9e6d4c040d4a54456b153f4523cab05adbf02 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Sun, 1 Dec 2019 15:18:25 +0200 Subject: feat: initial implementation of USART #6 --- demo/src/execute.ts | 8 +++- demo/src/index.css | 4 ++ demo/src/index.html | 1 + demo/src/index.ts | 7 ++++ src/index.ts | 1 + src/usart.spec.ts | 60 ++++++++++++++++++++++++++ src/usart.ts | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/usart.spec.ts create mode 100644 src/usart.ts diff --git a/demo/src/execute.ts b/demo/src/execute.ts index 4568839..f2c7d82 100644 --- a/demo/src/execute.ts +++ b/demo/src/execute.ts @@ -4,9 +4,11 @@ import { CPU, timer0Config, AVRIOPort, + AVRUSART, portBConfig, portCConfig, - portDConfig + portDConfig, + usart0Config } from 'avr8js'; import { loadHex } from './intelhex'; @@ -20,6 +22,8 @@ export class AVRRunner { readonly portB: AVRIOPort; readonly portC: AVRIOPort; readonly portD: AVRIOPort; + readonly usart: AVRUSART; + readonly speed = 16e6; // 16 MHZ private stopped = false; @@ -30,6 +34,7 @@ export class AVRRunner { this.portB = new AVRIOPort(this.cpu, portBConfig); this.portC = new AVRIOPort(this.cpu, portCConfig); this.portD = new AVRIOPort(this.cpu, portDConfig); + this.usart = new AVRUSART(this.cpu, usart0Config, this.speed); } async execute(callback: (cpu: CPU) => void) { @@ -37,6 +42,7 @@ export class AVRRunner { for (;;) { avrInstruction(this.cpu); this.timer.tick(); + this.usart.tick(); if (this.cpu.cycles % 50000 === 0) { callback(this.cpu); await new Promise((resolve) => setTimeout(resolve, 0)); diff --git a/demo/src/index.css b/demo/src/index.css index a3fa8b8..6801204 100644 --- a/demo/src/index.css +++ b/demo/src/index.css @@ -46,3 +46,7 @@ body { margin: 0; white-space: pre-line; } + +#serial-output-text { + color: blue; +} diff --git a/demo/src/index.html b/demo/src/index.html index 935bcff..2a8e924 100644 --- a/demo/src/index.html +++ b/demo/src/index.html @@ -20,6 +20,7 @@

+        

       
diff --git a/demo/src/index.ts b/demo/src/index.ts index e51cbbc..7b6f9b1 100644 --- a/demo/src/index.ts +++ b/demo/src/index.ts @@ -10,10 +10,12 @@ const BLINK_CODE = ` // Red LED connected to pin 12. Enjoy! void setup() { + Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); } void loop() { + Serial.println("Blink"); digitalWrite(LED_BUILTIN, HIGH); delay(500); digitalWrite(LED_BUILTIN, LOW); @@ -50,6 +52,7 @@ const stopButton = document.querySelector('#stop-button'); stopButton.addEventListener('click', stopCode); const statusLabel = document.querySelector('#status-label'); const compilerOutputText = document.querySelector('#compiler-output-text'); +const serialOutputText = document.querySelector('#serial-output-text'); function executeProgram(hex: string) { runner = new AVRRunner(hex); @@ -62,6 +65,9 @@ function executeProgram(hex: string) { led12.value = value & D12bit ? true : false; led13.value = value & D13bit ? true : false; }); + runner.usart.onByteTransmit = (value) => { + serialOutputText.textContent += String.fromCharCode(value); + }; runner.execute((cpu) => { const time = formatTime(cpu.cycles / MHZ); statusLabel.textContent = 'Simulation time: ' + time; @@ -73,6 +79,7 @@ async function compileAndRun() { led13.value = false; runButton.setAttribute('disabled', '1'); + serialOutputText.textContent = ''; try { statusLabel.textContent = 'Compiling...'; const result = await buildHex(editor.getModel().getValue()); diff --git a/src/index.ts b/src/index.ts index 094d3a9..d8f4373 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,3 +18,4 @@ export { portKConfig, portLConfig } from './gpio'; +export { AVRUSART, usart0Config } from './usart'; diff --git a/src/usart.spec.ts b/src/usart.spec.ts new file mode 100644 index 0000000..230709f --- /dev/null +++ b/src/usart.spec.ts @@ -0,0 +1,60 @@ +import { CPU } from './cpu'; +import { AVRUSART, usart0Config } from './usart'; + +const FREQ_16MHZ = 16e6; +const FREQ_11_0529MHZ = 11059200; + +describe('USART', () => { + it('should correctly calculate the baudRate from UBRR', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_11_0529MHZ); + cpu.writeData(0xc5, 0); // UBRR0H <- 0 + cpu.writeData(0xc4, 5); // UBRR0L <- 5 + expect(usart.baudRate).toEqual(115200); + }); + + it('should correctly calculate the baudRate from UBRR in double-speed mode', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc5, 3); // UBRR0H <- 3 + cpu.writeData(0xc4, 64); // UBRR0L <- 64 + cpu.writeData(0xc0, 2); // UCSR0A: U2X0 + expect(usart.baudRate).toEqual(2400); + }); + + it('should return 5-bits per byte when UCSZ = 0', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0); + expect(usart.bitsPerChar).toEqual(5); + }); + + it('should return 6-bits per byte when UCSZ = 1', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x2); + expect(usart.bitsPerChar).toEqual(6); + }); + + it('should return 7-bits per byte when UCSZ = 2', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x4); + expect(usart.bitsPerChar).toEqual(7); + }); + + it('should return 8-bits per byte when UCSZ = 3', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x6); + expect(usart.bitsPerChar).toEqual(8); + }); + + it('should return 9-bits per byte when UCSZ = 7', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x6); + cpu.writeData(0xc1, 0x4); + expect(usart.bitsPerChar).toEqual(9); + }); +}); diff --git a/src/usart.ts b/src/usart.ts new file mode 100644 index 0000000..276207e --- /dev/null +++ b/src/usart.ts @@ -0,0 +1,119 @@ +import { CPU } from './cpu'; +import { avrInterrupt } from './interrupt'; +import { u8 } from './types'; + +export interface USARTConfig { + rxCompleteInterrupt: u8; + dataRegisterEmptyInterrupt: u8; + txCompleteInterrupt: u8; + + UCSRA: u8; + UCSRB: u8; + UCSRC: u8; + UBRRL: u8; + UBRRH: u8; + UDR: u8; +} + +export const usart0Config: USARTConfig = { + rxCompleteInterrupt: 0x24, + dataRegisterEmptyInterrupt: 0x26, + txCompleteInterrupt: 0x28, + UCSRA: 0xc0, + UCSRB: 0xc1, + UCSRC: 0xc2, + UBRRL: 0xc4, + UBRRH: 0xc5, + UDR: 0xc6 +}; + +export type USARTTransmitCallback = (value: u8) => void; + +// Register bits +const UCSRA_RXC = 0x80; // USART Receive Complete +const UCSRA_TXC = 0x40; // USART Transmit Complete +const UCSRA_UDRE = 0x20; // USART Data Register Empty +const UCSRA_FE = 0x10; // Frame Error +const UCSRA_DOR = 0x8; // Data OverRun +const UCSRA_UPE = 0x4; // USART Parity Error +const UCSRA_U2X = 0x2; // Double the USART Transmission Speed +const UCSRA_MPCM = 0x1; // Multi-processor Communication Mode +const UCSRB_RXCIE = 0x80; // RX Complete Interrupt Enable +const UCSRB_TXCIE = 0x40; // TX Complete Interrupt Enable +const UCSRB_UDRIE = 0x20; // USART Data Register Empty Interrupt Enable +const UCSRB_RXEN = 0x10; // Receiver Enable +const UCSRB_TXEN = 0x8; // Transmitter Enable +const UCSRB_UCSZ2 = 0x4; // Character Size 2 +const UCSRB_RXB8 = 0x2; // Receive Data Bit 8 +const UCSRB_TXB8 = 0x1; // Transmit Data Bit 8 +const UCSRC_UMSEL1 = 0x80; // USART Mode Select 1 +const UCSRC_UMSEL0 = 0x40; // USART Mode Select 0 +const UCSRC_UPM1 = 0x20; // Parity Mode 1 +const UCSRC_UPM0 = 0x10; // Parity Mode 0 +const UCSRC_USBS = 0x8; // Stop Bit Select +const UCSRC_UCSZ1 = 0x4; // Character Size 1 +const UCSRC_UCSZ0 = 0x2; // Character Size 0 +const UCSRC_UCPOL = 0x1; // Clock Polarity + +export class AVRUSART { + public onByteTransmit: USARTTransmitCallback | null = null; + + constructor(private cpu: CPU, private config: USARTConfig, private freqMHz: number) { + this.cpu.writeHooks[config.UCSRA] = (value, oldValue) => { + this.cpu.data[config.UCSRA] = value | UCSRA_UDRE | UCSRA_TXC; + return true; + }; + this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => { + if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) { + // Enabling the transmission - mark UDR as empty + this.cpu.data[config.UCSRA] |= UCSRA_UDRE; + } + }; + this.cpu.writeHooks[config.UDR] = (value) => { + if (this.onByteTransmit) { + this.onByteTransmit(value); + } + this.cpu.data[config.UCSRA] |= UCSRA_UDRE | UCSRA_TXC; + }; + } + + tick() { + if (this.cpu.interruptsEnabled) { + const ucsra = this.cpu.data[this.config.UCSRA]; + const ucsrb = this.cpu.data[this.config.UCSRB]; + if (ucsra & UCSRA_UDRE && ucsrb & UCSRB_UDRIE) { + avrInterrupt(this.cpu, this.config.dataRegisterEmptyInterrupt); + this.cpu.data[this.config.UCSRA] &= ~UCSRA_UDRE; + } + if (ucsrb & UCSRA_TXC && ucsrb & UCSRB_TXCIE) { + avrInterrupt(this.cpu, this.config.txCompleteInterrupt); + this.cpu.data[this.config.UCSRA] &= ~UCSRA_TXC; + } + } + } + + get baudRate() { + const UBRR = (this.cpu.data[this.config.UBRRH] << 8) | this.cpu.data[this.config.UBRRL]; + const multiplier = this.cpu.data[this.config.UCSRA] & UCSRA_U2X ? 8 : 16; + return Math.floor(this.freqMHz / (multiplier * (1 + UBRR))); + } + + get bitsPerChar() { + const ucsz = + ((this.cpu.data[this.config.UCSRA] & (UCSRC_UCSZ1 | UCSRC_UCSZ0)) >> 1) | + (this.cpu.data[this.config.UCSRB] & UCSRB_UCSZ2); + switch (ucsz) { + case 0: + return 5; + case 1: + return 6; + case 2: + return 7; + case 3: + return 8; + default: // 4..6 are reserved + case 7: + return 9; + } + } +} -- cgit v1.2.3