Hi Hendrik. Here is the driver code for Beaglebone Bare Metal UART.
It works with the UART0/1/2/3/4/5.
I think the FIFO init code could be cleaned up further, maybe someone can tackle that and post it.
It is long (500 lines) but it is all there............
//
// clean-code version of uartecho.c
//
// uart0 interrupt driven
//
// study TRM spruh73j for programming the subsystems
//
#include "bbbuart.h"
//
// INTERNAL MACRO DEFINITIONS
//
//#define UART_MODULE_INPUT_CLK (48000000)
//
// GLOBAL VARIABLE DEFINITIONS
//
unsigned int Tx0Thresh = FALSE; // UART0 Tx threshold flag
unsigned int txcmd = 0x0; // Tx command flag
//
// UARTx module clock config
//
// UART0 always enabled, UARTx optional
// DO NOT call unless you want to enable UART1...UART5
// This function enables the system L3 and system L4_WKUP clocks
void UARTModuleClkCfg(unsigned int baseAddUARTx) {
// Configuring L3 Interface Clocks
// Writing to MODULEMODE field of CM_PER_L3_CLKCTRL register
HWREG(SOC_CM_PER_REGS + CM_PER_L3_CLKCTRL) |= CM_PER_L3_CLKCTRL_MODULEMODE_ENABLE;
// Waiting for MODULEMODE field to reflect the written value
while(CM_PER_L3_CLKCTRL_MODULEMODE_ENABLE != (HWREG(SOC_CM_PER_REGS + CM_PER_L3_CLKCTRL) &
CM_PER_L3_CLKCTRL_MODULEMODE));
// Writing to MODULEMODE field of CM_PER_L3_INSTR_CLKCTRL register
HWREG(SOC_CM_PER_REGS + CM_PER_L3_INSTR_CLKCTRL) |= CM_PER_L3_INSTR_CLKCTRL_MODULEMODE_ENABLE;
// Waiting for MODULEMODE field to reflect the written value
while(CM_PER_L3_INSTR_CLKCTRL_MODULEMODE_ENABLE != (HWREG(SOC_CM_PER_REGS + CM_PER_L3_INSTR_CLKCTRL) &
CM_PER_L3_INSTR_CLKCTRL_MODULEMODE));
// Writing to CLKTRCTRL field of CM_PER_L3_CLKSTCTRL register
HWREG(SOC_CM_PER_REGS + CM_PER_L3_CLKSTCTRL) |= CM_PER_L3_CLKSTCTRL_CLKTRCTRL_SW_WKUP;
// Waiting for CLKTRCTRL field to reflect the written value
while(CM_PER_L3_CLKSTCTRL_CLKTRCTRL_SW_WKUP != (HWREG(SOC_CM_PER_REGS + CM_PER_L3_CLKSTCTRL) &
CM_PER_L3_CLKSTCTRL_CLKTRCTRL));
// Writing to CLKTRCTRL field of CM_PER_OCPWP_L3_CLKSTCTRL register
HWREG(SOC_CM_PER_REGS + CM_PER_OCPWP_L3_CLKSTCTRL) |= CM_PER_OCPWP_L3_CLKSTCTRL_CLKTRCTRL_SW_WKUP;
//Waiting for CLKTRCTRL field to reflect the written value
while(CM_PER_OCPWP_L3_CLKSTCTRL_CLKTRCTRL_SW_WKUP != (HWREG(SOC_CM_PER_REGS + CM_PER_OCPWP_L3_CLKSTCTRL) &
CM_PER_OCPWP_L3_CLKSTCTRL_CLKTRCTRL));
// Writing to CLKTRCTRL field of CM_PER_L3S_CLKSTCTRL register
HWREG(SOC_CM_PER_REGS + CM_PER_L3S_CLKSTCTRL) |= CM_PER_L3S_CLKSTCTRL_CLKTRCTRL_SW_WKUP;
//Waiting for CLKTRCTRL field to reflect the written value
while(CM_PER_L3S_CLKSTCTRL_CLKTRCTRL_SW_WKUP != (HWREG(SOC_CM_PER_REGS + CM_PER_L3S_CLKSTCTRL) &
CM_PER_L3S_CLKSTCTRL_CLKTRCTRL));
// Checking fields for necessary values
// Waiting for IDLEST field in CM_PER_L3_CLKCTRL register to be set to 0x0
while((CM_PER_L3_CLKCTRL_IDLEST_FUNC << CM_PER_L3_CLKCTRL_IDLEST_SHIFT)!=
(HWREG(SOC_CM_PER_REGS + CM_PER_L3_CLKCTRL) & CM_PER_L3_CLKCTRL_IDLEST));
// Waiting for IDLEST field in CM_PER_L3_INSTR_CLKCTRL register to attain the desired value.
while((CM_PER_L3_INSTR_CLKCTRL_IDLEST_FUNC << CM_PER_L3_INSTR_CLKCTRL_IDLEST_SHIFT)!=
(HWREG(SOC_CM_PER_REGS + CM_PER_L3_INSTR_CLKCTRL) & CM_PER_L3_INSTR_CLKCTRL_IDLEST));
// Waiting for CLKACTIVITY_L3_GCLK field in CM_PER_L3_CLKSTCTRL register to attain the desired value.
while(CM_PER_L3_CLKSTCTRL_CLKACTIVITY_L3_GCLK != (HWREG(SOC_CM_PER_REGS + CM_PER_L3_CLKSTCTRL) &
CM_PER_L3_CLKSTCTRL_CLKACTIVITY_L3_GCLK));
// Waiting for CLKACTIVITY_OCPWP_L3_GCLK field in CM_PER_OCPWP_L3_CLKSTCTRL register to attain the desired value.
while(CM_PER_OCPWP_L3_CLKSTCTRL_CLKACTIVITY_OCPWP_L3_GCLK != (HWREG(SOC_CM_PER_REGS + CM_PER_OCPWP_L3_CLKSTCTRL) &
CM_PER_OCPWP_L3_CLKSTCTRL_CLKACTIVITY_OCPWP_L3_GCLK));
// Waiting for CLKACTIVITY_L3S_GCLK field in CM_PER_L3S_CLKSTCTRL register to attain the desired value.
while(CM_PER_L3S_CLKSTCTRL_CLKACTIVITY_L3S_GCLK != (HWREG(SOC_CM_PER_REGS + CM_PER_L3S_CLKSTCTRL) &
CM_PER_L3S_CLKSTCTRL_CLKACTIVITY_L3S_GCLK));
// Configuring registers related to Wake-Up region
// Writing to MODULEMODE field of CM_WKUP_CONTROL_CLKCTRL register
HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CONTROL_CLKCTRL) |= CM_WKUP_CONTROL_CLKCTRL_MODULEMODE_ENABLE;
// Waiting for MODULEMODE field to reflect the written value
while(CM_WKUP_CONTROL_CLKCTRL_MODULEMODE_ENABLE != (HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CONTROL_CLKCTRL) &
CM_WKUP_CONTROL_CLKCTRL_MODULEMODE));
// Writing to CLKTRCTRL field of CM_PER_L3S_CLKSTCTRL register
HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CLKSTCTRL) |= CM_WKUP_CLKSTCTRL_CLKTRCTRL_SW_WKUP;
//Waiting for CLKTRCTRL field to reflect the written value
while(CM_WKUP_CLKSTCTRL_CLKTRCTRL_SW_WKUP != (HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CLKSTCTRL) &
CM_WKUP_CLKSTCTRL_CLKTRCTRL));
// Writing to CLKTRCTRL field of CM_L3_AON_CLKSTCTRL register
HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CM_L3_AON_CLKSTCTRL) |= CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKTRCTRL_SW_WKUP;
//Waiting for CLKTRCTRL field to reflect the written value
while(CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKTRCTRL_SW_WKUP != (HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CM_L3_AON_CLKSTCTRL) &
CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKTRCTRL));
// Writing to MODULEMODE field of CM_WKUP_UART0_CLKCTRL register
HWREG(SOC_CM_WKUP_REGS + CM_WKUP_UART0_CLKCTRL) |= CM_WKUP_UART0_CLKCTRL_MODULEMODE_ENABLE;
// Waiting for MODULEMODE field to reflect the written value
while(CM_WKUP_UART0_CLKCTRL_MODULEMODE_ENABLE != (HWREG(SOC_CM_WKUP_REGS + CM_WKUP_UART0_CLKCTRL) &
CM_WKUP_UART0_CLKCTRL_MODULEMODE));
// Verifying if the other bits are set to required settings
//
// Waiting for IDLEST field in CM_WKUP_CONTROL_CLKCTRL register to attain desired value.
while((CM_WKUP_CONTROL_CLKCTRL_IDLEST_FUNC << CM_WKUP_CONTROL_CLKCTRL_IDLEST_SHIFT) !=
(HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CONTROL_CLKCTRL) & CM_WKUP_CONTROL_CLKCTRL_IDLEST));
// Waiting for CLKACTIVITY_L3_AON_GCLK field in CM_L3_AON_CLKSTCTRL register to attain desired value.
while(CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKACTIVITY_L3_AON_GCLK !=
(HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CM_L3_AON_CLKSTCTRL) &
CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKACTIVITY_L3_AON_GCLK));
// Waiting for IDLEST field in CM_WKUP_L4WKUP_CLKCTRL register to attain desired value.
while((CM_WKUP_L4WKUP_CLKCTRL_IDLEST_FUNC << CM_WKUP_L4WKUP_CLKCTRL_IDLEST_SHIFT) !=
(HWREG(SOC_CM_WKUP_REGS + CM_WKUP_L4WKUP_CLKCTRL) & CM_WKUP_L4WKUP_CLKCTRL_IDLEST));
// Waiting for CLKACTIVITY_L4_WKUP_GCLK field in CM_WKUP_CLKSTCTRL register to attain desired value.
while(CM_WKUP_CLKSTCTRL_CLKACTIVITY_L4_WKUP_GCLK != (HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CLKSTCTRL) &
CM_WKUP_CLKSTCTRL_CLKACTIVITY_L4_WKUP_GCLK));
// Waiting for CLKACTIVITY_L4_WKUP_AON_GCLK field in CM_L4_WKUP_AON_CLKSTCTRL register to attain desired value.
while(CM_WKUP_CM_L4_WKUP_AON_CLKSTCTRL_CLKACTIVITY_L4_WKUP_AON_GCLK !=
(HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CM_L4_WKUP_AON_CLKSTCTRL) &
CM_WKUP_CM_L4_WKUP_AON_CLKSTCTRL_CLKACTIVITY_L4_WKUP_AON_GCLK));
// Waiting for CLKACTIVITY_UART0_GFCLK field in CM_WKUP_CLKSTCTRL register to attain desired value.
while(CM_WKUP_CLKSTCTRL_CLKACTIVITY_UART0_GFCLK != (HWREG(SOC_CM_WKUP_REGS + CM_WKUP_CLKSTCTRL) &
CM_WKUP_CLKSTCTRL_CLKACTIVITY_UART0_GFCLK));
// Waiting for IDLEST field in CM_WKUP_UART0_CLKCTRL register to attain desired value.
while((CM_WKUP_UART0_CLKCTRL_IDLEST_FUNC << CM_WKUP_UART0_CLKCTRL_IDLEST_SHIFT) !=
(HWREG(SOC_CM_WKUP_REGS + CM_WKUP_UART0_CLKCTRL) & CM_WKUP_UART0_CLKCTRL_IDLEST));
}
//
// UART pin mux configuration
//
void UARTPinMuxCfg(unsigned int baseAdd) {
if(baseAdd == SOC_UART_0_REGS) {
// RXD
HWREG(SOC_CONTROL_REGS + CONTROL_CONF_UART_RXD(0)) =
(CONTROL_CONF_UART0_RXD_CONF_UART0_RXD_PUTYPESEL |
CONTROL_CONF_UART0_RXD_CONF_UART0_RXD_RXACTIVE);
// TXD
HWREG(SOC_CONTROL_REGS + CONTROL_CONF_UART_TXD(0)) =
CONTROL_CONF_UART0_TXD_CONF_UART0_TXD_PUTYPESEL;
}
}
//
// UART module reset
//
void UARTreset(unsigned int baseAdd) {
// Performing Software Reset of the module
HWREG(baseAdd + UART_SYSC) |= (UART_SYSC_SOFTRESET);
// Wait until the process of Module Reset is complete
while(!(HWREG(baseAdd + UART_SYSS) & UART_SYSS_RESETDONE));
}
//
// UART FIFO configuration
//
static void UartFIFOConfigure(unsigned int baseAdd) {
unsigned int txGra = 0x0; // tx trigger gran = 4
unsigned int rxGra = 0x1; // rx trigger gran = 1
unsigned int txTrig = 0x30; // tx trig lev = 8 (fifo size 64 - fifo space 56)
unsigned int rxTrig = 0x1; // rx trig lev = 1
unsigned int txClr = 0x1; // clear tx fifo
unsigned int rxClr = 0x4; // clear rx fifo
unsigned int dmaEnPath = 0x1; // dma enab thru scr
unsigned int dmaMode = 0x0; // dma mode = 0 (dma disabled)
unsigned int lcrRegValue = 0;
unsigned int enhanFnBitVal = 0;
unsigned int tcrTlrBitVal = 0;
unsigned int tlrValue = 0;
unsigned int fcrValue = 0;
// see TRM - UART Programming Quick Start Procedure
// retain mode value
lcrRegValue = HWREG(baseAdd + UART_LCR);
// switch to Register Configuration Mode B
HWREG(baseAdd + UART_LCR) = (UART_REG_CONFIG_MODE_B);
// retain ENHANCEDEN bit value
enhanFnBitVal = (HWREG(baseAdd + UART_EFR) & UART_EFR_ENHANCED_EN);
// set ENHANCEDEN bit - EFR[4] to 1
HWREG(baseAdd + UART_EFR) |= UART_EFR_ENHANCED_EN;
// switch to Register Configuration Mode A
HWREG(baseAdd + UART_LCR) = (UART_REG_CONFIG_MODE_A);
// retain TCR_TLR bit value - MCR[6]
tcrTlrBitVal = (HWREG(baseAdd + UART_MCR) & UART_MCR_TCR_TLR);
// set TCRTLR bit to 1
HWREG(baseAdd + UART_MCR) |= (UART_MCR_TCR_TLR);
// enable FIFO: fcr[0] = 1
fcrValue |= UART_FCR_FIFO_EN;
// set Receiver FIFO trigger level
if(UART_TRIG_LVL_GRANULARITY_1 != rxGra) {
// clear RXTRIGGRANU1 bit in SCR
HWREG(baseAdd + UART_SCR) &= ~(UART_SCR_RX_TRIG_GRANU1);
// clear RX_FIFO_TRIG_DMA field of TLR register
HWREG(baseAdd + UART_TLR) &= ~(UART_TLR_RX_FIFO_TRIG_DMA);
fcrValue &= ~(UART_FCR_RX_FIFO_TRIG);
// check if 'rxTrig' matches with the RX Trigger level values in FCR
if((UART_FCR_RX_TRIG_LVL_8 == rxTrig) ||
(UART_FCR_RX_TRIG_LVL_16 == rxTrig) ||
(UART_FCR_RX_TRIG_LVL_56 == rxTrig) ||
(UART_FCR_RX_TRIG_LVL_60 == rxTrig)) {
fcrValue |= (rxTrig & UART_FCR_RX_FIFO_TRIG);
} else {
// RX Trig level multiple of 4, set RX_FIFO_TRIG_DMA of TLR
HWREG(baseAdd + UART_TLR) |= ((rxTrig << UART_TLR_RX_FIFO_TRIG_DMA_SHIFT) &
UART_TLR_RX_FIFO_TRIG_DMA);
}
} else { // yes: rxGra = 0x1
// 'rxTrig' contains the 6-bit RX Trigger level value
rxTrig &= 0x003F;
// collect bits rxTrig[5:2]
tlrValue = (rxTrig & 0x003C) >> 2;
// collect bits rxTrig[1:0] and write to 'fcrValue'
fcrValue |= (rxTrig & 0x0003) << UART_FCR_RX_FIFO_TRIG_SHIFT;
// set RX_TRIG_GRANU1 bit of SCR register
HWREG(baseAdd + UART_SCR) |= UART_SCR_RX_TRIG_GRANU1;
// program RX_FIFO_TRIG_DMA field of TLR register
HWREG(baseAdd + UART_TLR) |= (tlrValue << UART_TLR_RX_FIFO_TRIG_DMA_SHIFT);
}
// set Tx FIFO trigger level
if(UART_TRIG_LVL_GRANULARITY_1 != txGra) { // yes txgra = 0x0
// clear TX_TRIG_GRANU1 bit in SCR
HWREG(baseAdd + UART_SCR) &= ~(UART_SCR_TX_TRIG_GRANU1);
// clear TX_FIFO_TRIG_DMA field of TLR register
HWREG(baseAdd + UART_TLR) &= ~(UART_TLR_TX_FIFO_TRIG_DMA);
fcrValue &= ~(UART_FCR_TX_FIFO_TRIG);
// check if 'txTrig' matches with the TX Trigger level values in FCR
if((UART_FCR_TX_TRIG_LVL_8 == (txTrig)) ||
(UART_FCR_TX_TRIG_LVL_16 == (txTrig)) ||
(UART_FCR_TX_TRIG_LVL_32 == (txTrig)) ||
(UART_FCR_TX_TRIG_LVL_56 == (txTrig))) {
fcrValue |= (txTrig & UART_FCR_TX_FIFO_TRIG);
} else {
// TX Trig level a multiple of 4, set TX_FIFO_TRIG_DMA of TLR
HWREG(baseAdd + UART_TLR) |= ((txTrig << UART_TLR_TX_FIFO_TRIG_DMA_SHIFT) &
UART_TLR_TX_FIFO_TRIG_DMA);
}
} else {
// 'txTrig' has the 6-bit TX Trigger level value
txTrig &= 0x003F;
// collect bits txTrig[5:2]
tlrValue = (txTrig & 0x003C) >> 2;
// collect bits txTrig[1:0] and write to 'fcrValue'
fcrValue |= (txTrig & 0x0003) << UART_FCR_TX_FIFO_TRIG_SHIFT;
// Setting the TXTRIGGRANU1 bit of SCR register
HWREG(baseAdd + UART_SCR) |= UART_SCR_TX_TRIG_GRANU1;
// program TX_FIFO_TRIG_DMA field of TLR register
HWREG(baseAdd + UART_TLR) |= (tlrValue << UART_TLR_TX_FIFO_TRIG_DMA_SHIFT);
}
if(UART_DMA_EN_PATH_FCR == dmaEnPath) {
// Configuring the UART DMA Mode through FCR register
HWREG(baseAdd + UART_SCR) &= ~(UART_SCR_DMA_MODE_CTL);
dmaMode &= 0x1;
// clear bit corresponding to the DMA_MODE in 'fcrValue'
fcrValue &= ~(UART_FCR_DMA_MODE);
// set DMA Mode of operation
fcrValue |= (dmaMode << UART_FCR_DMA_MODE_SHIFT);
} else {
dmaMode &= 0x3;
// configure UART DMA Mode through SCR register
HWREG(baseAdd + UART_SCR) |= UART_SCR_DMA_MODE_CTL;
// clear DMAMODE2 field in SCR
HWREG(baseAdd + UART_SCR) &= ~(UART_SCR_DMA_MODE_2);
// program DMAMODE2 field in SCR
HWREG(baseAdd + UART_SCR) |= (dmaMode << UART_SCR_DMA_MODE_2_SHIFT);
}
// program bits which clear the RX and TX FIFOs
fcrValue |= (rxClr << UART_FCR_RX_FIFO_CLEAR_SHIFT);
fcrValue |= (txClr << UART_FCR_TX_FIFO_CLEAR_SHIFT);
// write fcrValue to FCR register
HWREG(baseAdd + UART_FCR) = fcrValue;
// switch to Register Configuration Mode B
HWREG(baseAdd + UART_LCR) = (UART_REG_CONFIG_MODE_B);
// restore ENHANCEDEN bit - EFR[4] to original value
HWREG(baseAdd + UART_EFR) &= ~(UART_EFR_ENHANCED_EN);
HWREG(baseAdd + UART_EFR) |= (enhanFnBitVal & UART_EFR_ENHANCED_EN);
// switch to Register Configuration Mode A
HWREG(baseAdd + UART_LCR) = (UART_REG_CONFIG_MODE_A);
// restore original value of TCRTLR bit in MCR[6]
HWREG(baseAdd + UART_MCR) &= ~(UART_MCR_TCR_TLR);
HWREG(baseAdd + UART_MCR) |= (tcrTlrBitVal & UART_MCR_TCR_TLR);
// restore LCR to original value
HWREG(baseAdd + UART_LCR) = lcrRegValue;
}
//
// set up UART
//
static void UARTsetup(unsigned int baseAdd,
unsigned int baud_rate,
unsigned int UARTparms,
unsigned int UARTmode) {
unsigned int divisorValue = 0;
unsigned int enhanFnBitVal = 0;
unsigned int sleepMdBitVal = 0;
// compute Divisor Value
UARTmode &= UART_MDR1_MODE_SELECT;
switch(UARTmode) { // see TRM spruh73j p3983
case UART16x_OPER_MODE:
divisorValue = (UART_MODULE_INPUT_CLK)/(16 * baud_rate);
break;
case UART13x_OPER_MODE:
divisorValue = (UART_MODULE_INPUT_CLK)/(13 * baud_rate);
break;
default:
divisorValue = (UART_MODULE_INPUT_CLK)/(16 * baud_rate);
break;
}
// disable UART...
// clear MODESELECT field in MDR1
HWREG(baseAdd + UART_MDR1) &= ~(UART_MDR1_MODE_SELECT);
// set MODESELECT field in MDR1 to DISABLED mode = 0x7 & 0x7
HWREG(baseAdd + UART_MDR1) |= (UART_MDR1_MODE_SELECT_DISABLED & UART_MDR1_MODE_SELECT);
// switch to Register Configuration Mode B = 0xBF
HWREG(baseAdd + UART_LCR) = (UART_REG_CONFIG_MODE_B);
// retain ENHANCEDEN bit value
enhanFnBitVal = (HWREG(baseAdd + UART_EFR) & UART_EFR_ENHANCED_EN);
// set ENHANCEDEN bit - EFR[4] to 1
HWREG(baseAdd + UART_EFR) |= UART_EFR_ENHANCED_EN;
// switch to Register operation mode = 0x7F
HWREG(baseAdd + UART_LCR) = (UART_REG_OPERATIONAL_MODE);
// retain current value of IER[4] (SLEEPMODE bit) and clear it
sleepMdBitVal = HWREG(baseAdd + UART_IER) & UART_IER_SLEEP_MODE_IT;
HWREG(baseAdd + UART_IER) &= ~(UART_IER_SLEEP_MODE_IT);
// switch to Register Configuration Mode B = 0xBF
HWREG(baseAdd + UART_LCR) = (UART_REG_CONFIG_MODE_B);
// write to Divisor Latch Low(DLL) register
HWREG(baseAdd + UART_DLL) = (divisorValue & 0x00FF);
// write to Divisor Latch High(DLH) register
HWREG(baseAdd + UART_DLH) = ((divisorValue & 0x3F00) >> 8);
// switch to Register operation mode = 0x7F
HWREG(baseAdd + UART_LCR) = (UART_REG_OPERATIONAL_MODE);
// restore value of IER[4] (SLEEPMODE bit) to original
HWREG(baseAdd + UART_IER) |= sleepMdBitVal;
// switch to Register Configuration Mode B
HWREG(baseAdd + UART_LCR) = (UART_REG_CONFIG_MODE_B);
// restore value of EFR[4] to original value
HWREG(baseAdd + UART_EFR) &= ~(UART_EFR_ENHANCED_EN);
HWREG(baseAdd + UART_EFR) |= enhanFnBitVal;
// set value of LCR Register
HWREG(baseAdd + UART_LCR) = UARTparms;
// clear MODESELECT field in MDR1
HWREG(baseAdd + UART_MDR1) &= ~(UART_MDR1_MODE_SELECT);
// enable UART...
// set MODESELECT field in MDR1 to UART Operating Mode
HWREG(baseAdd + UART_MDR1) |= (UARTmode & UART_MDR1_MODE_SELECT);
return;
}
//
// Interrupt Service Routine for UART0.
//
static void UART0Isr(void) {
unsigned char rxByte = 0;
unsigned int intId = 0;
// get source of UART0 interrupt
intId = (HWREG(SOC_UART_0_REGS + UART_IIR) & UART_IIR_IT_TYPE); // UARTIntIdentityGet()
switch(intId) {
case UART_INTID_TX_THRES_REACH: // UARTIntDisable()
HWREG(SOC_UART_0_REGS + UART_IER) &= ~(UART_INT_THR & 0xFF); // disable IRQ THR
Tx0Thresh = TRUE; // set Tx flag
break;
case UART_INTID_RX_THRES_REACH: // check if data in Rx FIFO, get a byte
if(HWREG(SOC_UART_0_REGS + UART_LSR) & UART_LSR_RX_FIFO_E)
rxByte = HWREG(SOC_UART_0_REGS + UART_RHR); // UARTCharGetNonBlocking()
// check Tx FIFO or TX shift reg empty, send a byte
if(HWREG(SOC_UART_0_REGS + UART_LSR) & (UART_LSR_TX_SR_E | UART_LSR_TX_FIFO_E))
HWREG(SOC_UART_0_REGS + UART_THR) = rxByte; // UARTCharPutNonBlocking()
if(rxByte == 'x') txcmd = 0x1;
break;
default: // skip all error conditions, just dump Rx FIFO
while(HWREG(SOC_UART_0_REGS + UART_LSR) & UART_LSR_RX_FIFO_E) {
rxByte = HWREG(SOC_UART_0_REGS + UART_RHR);
if(HWREG(SOC_UART_0_REGS + UART_LSR) & (UART_LSR_TX_SR_E | UART_LSR_TX_FIFO_E))
HWREG(SOC_UART_0_REGS + UART_THR) = rxByte;
}
break;
}
}
//
// UART Interrupt config
//
static void UartInterruptEnable(unsigned int baseAdd) {
switch(baseAdd) {
case SOC_UART_0_REGS:
// IntRegister() register Interrupt Service Routine
fnRAMVectors[SYS_INT_UART0INT] = UART0Isr;
// IntPrioritySet() set priority for system interrupt in AINTC
HWREG(SOC_AINTC_REGS + INTC_ILR(SYS_INT_UART0INT)) =
((0 << INTC_ILR_PRIORITY_SHIFT) & INTC_ILR_PRIORITY) | AINTC_HOSTINT_ROUTE_IRQ ;
// IntSystemEnable() enable system interrupt in AINTC
__asm(" dsb");
// disable system interrupt in corresponding MIR_CLEAR register ???
HWREG(SOC_AINTC_REGS + INTC_MIR_CLEAR(SYS_INT_UART0INT >> REG_IDX_SHIFT))
= (0x01 << (SYS_INT_UART0INT & REG_BIT_MASK));
break;
default:
break;
}
// UARTIntEnable() enab UART IRQs lean 'n mean
HWREG(baseAdd + UART_IER) |=
((UART_INT_LINE_STAT | UART_INT_THR | UART_INT_RHR_CTI) & 0x0F);
}
//
// default interrupt service routine
//
static void ISRdefault(void)
{
; // do zippo
}
//
// ARM Interrupt Controller Init
//
void AINTCinit(void) {
unsigned int intrNum;
// IntMasterIRQEnable() enable IRQ in CPSR of ARM processor
asm(" mrs r0, CPSR\n\t"
" bic r0, r0, #0x80\n\t"
" msr CPSR_c, r0");
// IntMasterIRQEnable() reset AINTC
HWREG(SOC_AINTC_REGS + INTC_SYSCONFIG) = INTC_SYSCONFIG_SOFTRESET;
// wait for reset to complete
while((HWREG(SOC_AINTC_REGS + INTC_SYSSTATUS)
& INTC_SYSSTATUS_RESETDONE) != INTC_SYSSTATUS_RESETDONE);
// enable any interrupt generation by setting priority threshold
HWREG(SOC_AINTC_REGS + INTC_THRESHOLD) =
INTC_THRESHOLD_PRIORITYTHRESHOLD; // = 0xff disab priority threshold (default)
// register default handler for all interrupts
for(intrNum = 0; intrNum < NUM_INTERRUPTS; intrNum++) {
fnRAMVectors[intrNum] = ISRdefault; // dummy ISR
}
}
//
// buffered UART transmit
//
// 64 bytes max buffer size (presumed FIFO size)
// check (once only) Tx Shift Reg empty or Tx FIFO empty
//
unsigned int UART_tx(unsigned int baseAdd, unsigned char *txbuf, unsigned int nobytes) {
unsigned int i = 0;
if(nobytes > 64) nobytes = 64;
if(HWREG(SOC_UART_0_REGS + UART_LSR) & (UART_LSR_TX_SR_E | UART_LSR_TX_FIFO_E)) {
for(i = 0; i < nobytes; i++) {
HWREG(baseAdd + UART_THR) = *txbuf++; // write to THR
}
}
return i;
}
//
// UART transmit - unlimited buffer
//
unsigned int UART_tx_big(unsigned int baseAdd, unsigned char *TxBuf, unsigned int TxBufSize) {
unsigned int TxCount = 0;
while(TxCount < TxBufSize) { // test if transmission is complete.
TxCount += UART_tx(baseAdd, &TxBuf[TxCount], TxBufSize);
}
return 0;
}
//
// main program
//
// Tx a string then echo all received characters, IRQ driven
//
int main() {
unsigned int UARTparms = 0;
unsigned int masterUART = SOC_UART_0_REGS;
unsigned char TxBuf[] = "1234567890Abcdefghijklmnopqrstuvwxyz1234567890abcdefghij 64BYTES";
// config system clocks - not necessary for UART0, it is always enabled
//UARTModuleClkCfg(SOC_UART_1_REGS);
// config UART0 pin mux
UARTPinMuxCfg(SOC_UART_0_REGS);
// reset UART0 module
UARTreset(SOC_UART_0_REGS);
// config FIFO
UartFIFOConfigure(SOC_UART_0_REGS);
// set up UART for UARTparms see TRM - LCR Register Field Descriptions
UARTparms = 0x3; // p4033 - 8 data bits, 1 stop bit, no parity
UARTsetup(SOC_UART_0_REGS, 115200, UARTparms, UART16x_OPER_MODE);
AINTCinit(); // ARM Interrupt Controller init
UartInterruptEnable(SOC_UART_0_REGS); // ready2rock! enab uart irq
UART_tx(masterUART, TxBuf, sizeof(TxBuf) - 1);
while(1) { // spin loop
if(Tx0Thresh == TRUE) { // tx fifo empty
if(txcmd) {
UART_tx(masterUART, TxBuf, sizeof(TxBuf) - 1);
// UART_tx_big(masterUART, TxBuf, sizeof(TxBuf) - 1);
Tx0Thresh = FALSE;
HWREG(masterUART + UART_IER) |= (UART_INT_THR & 0x0F); // enab UART IRQs
txcmd = 0x0;
}
}
}
}
// FINITO
and the associated header file bbbuart.h :
//
// bbbuart.h - manifests for uartx bare metal project
//
//#include "uart_irda_cir.h"
#define UART_SYSC (0x54)
#define UART_SYSC_SOFTRESET (0x00000002u)
#define UART_SYSS (0x58)
#define UART_SYSS_RESETDONE (0x00000001u)
#define UART_DLL (0x0)
#define UART_RHR (0x0)
#define UART_THR (0x0)
#define UART_DLH (0x4)
#define UART_IER (0x4)
#define UART_EFR (0x8)
#define UART_FCR (0x8)
#define UART_IIR (0x8)
#define UART_LCR (0xC)
#define UART_MCR (0x10)
#define UART_LSR (0x14)
#define UART_TLR (0x1C)
#define UART_MDR1 (0x20)
#define UART_SCR (0x40)
#define UART_REG_CONFIG_MODE_A (0x0080)
#define UART_REG_CONFIG_MODE_B (0x00BF)
#define UART_REG_OPERATIONAL_MODE (0x007F)
#define UART_EFR_ENHANCED_EN (0x00000010u)
#define UART_MCR_TCR_TLR (0x00000040u)
#define UART_FCR_FIFO_EN (0x00000001u)
#define UART_TRIG_LVL_GRANULARITY_1 (0x0001)
#define UART_SCR_RX_TRIG_GRANU1 (0x00000080u)
#define UART_TLR_RX_FIFO_TRIG_DMA (0x000000F0u)
#define UART_TLR_RX_FIFO_TRIG_DMA_SHIFT (0x00000004u)
#define UART_TLR_TX_FIFO_TRIG_DMA (0x0000000Fu)
#define UART_TLR_TX_FIFO_TRIG_DMA_SHIFT (0x00000000u)
#define UART_FCR_RX_FIFO_TRIG (0x000000C0u)
#define UART_FCR_RX_FIFO_TRIG_SHIFT (0x00000006u)
#define UART_FCR_RX_FIFO_TRIG_16CHAR (0x1u)
#define UART_FCR_RX_FIFO_TRIG_56CHAR (0x2u)
#define UART_FCR_RX_FIFO_TRIG_60CHAR (0x3u)
#define UART_FCR_RX_FIFO_TRIG_8CHAR (0x0u)
#define UART_FCR_RX_TRIG_LVL_8 (UART_FCR_RX_FIFO_TRIG_8CHAR << \
UART_FCR_RX_FIFO_TRIG_SHIFT)
#define UART_FCR_RX_TRIG_LVL_16 (UART_FCR_RX_FIFO_TRIG_16CHAR << \
UART_FCR_RX_FIFO_TRIG_SHIFT)
#define UART_FCR_RX_TRIG_LVL_56 (UART_FCR_RX_FIFO_TRIG_56CHAR << \
UART_FCR_RX_FIFO_TRIG_SHIFT)
#define UART_FCR_RX_TRIG_LVL_60 (UART_FCR_RX_FIFO_TRIG_60CHAR << \
UART_FCR_RX_FIFO_TRIG_SHIFT)
#define UART_FCR_TX_TRIG_LVL_8 (UART_FCR_TX_FIFO_TRIG_8SPACES << \
UART_FCR_TX_FIFO_TRIG_SHIFT)
#define UART_FCR_TX_TRIG_LVL_16 (UART_FCR_TX_FIFO_TRIG_16SPACES << \
UART_FCR_TX_FIFO_TRIG_SHIFT)
#define UART_FCR_TX_TRIG_LVL_32 (UART_FCR_TX_FIFO_TRIG_32SPACES << \
UART_FCR_TX_FIFO_TRIG_SHIFT)
#define UART_FCR_TX_TRIG_LVL_56 (UART_FCR_TX_FIFO_TRIG_56SPACES << \
UART_FCR_TX_FIFO_TRIG_SHIFT)
#define UART_FCR_TX_FIFO_TRIG (0x00000030u)
#define UART_FCR_TX_FIFO_TRIG_SHIFT (0x00000004u)
#define UART_FCR_TX_FIFO_TRIG_8SPACES (0x0u)
#define UART_FCR_TX_FIFO_TRIG_16SPACES (0x1u)
#define UART_FCR_TX_FIFO_TRIG_32SPACES (0x2u)
#define UART_FCR_TX_FIFO_TRIG_56SPACES (0x3u)
#define UART_DMA_EN_PATH_FCR (UART_SCR_DMA_MODE_CTL_FCR)
#define UART_FCR_DMA_MODE (0x00000008u)
#define UART_FCR_DMA_MODE_SHIFT (0x00000003u)
#define UART_SCR_DMA_MODE_CTL (0x00000001u)
#define UART_SCR_DMA_MODE_CTL_FCR (0x0u)
#define UART_SCR_DMA_MODE_2 (0x00000006u)
#define UART_SCR_DMA_MODE_2_SHIFT (0x00000001u)
#define UART_SCR_TX_TRIG_GRANU1 (0x00000040u)
#define UART_FCR_RX_FIFO_CLEAR_SHIFT (0x00000001u)
#define UART_FCR_TX_FIFO_CLEAR_SHIFT (0x00000002u)
#define UART_MDR1_MODE_SELECT (0x00000007u)
#define UART_MDR1_MODE_SELECT_UART13X (0x3u)
#define UART_MDR1_MODE_SELECT_UART16X (0x0u)
#define UART16x_OPER_MODE (UART_MDR1_MODE_SELECT_UART16X)
#define UART13x_OPER_MODE (UART_MDR1_MODE_SELECT_UART13X)
#define UART_MDR1_MODE_SELECT_DISABLED (0x7u)
#define UART_IER_SLEEP_MODE_IT (0x00000010u)
#define UART_IER_THR_IT (0x00000002u)
#define UART_IIR_IT_TYPE (0x0000003Eu)
#define UART_IIR_IT_TYPE_SHIFT (0x00000001u)
#define UART_IIR_IT_TYPE_RHRINT (0x2u)
#define UART_IIR_IT_TYPE_THRINT (0x1u)
#define UART_INTID_TX_THRES_REACH (UART_IIR_IT_TYPE_THRINT << UART_IIR_IT_TYPE_SHIFT)
#define UART_INTID_RX_THRES_REACH (UART_IIR_IT_TYPE_RHRINT << UART_IIR_IT_TYPE_SHIFT)
#define UART_INT_LINE_STAT (UART_IER_LINE_STS_IT)
#define UART_INT_THR (UART_IER_THR_IT)
#define UART_INT_RHR_CTI (UART_IER_RHR_IT)
#define UART_LSR_RX_FIFO_E (0x00000001u)
#define UART_LSR_TX_FIFO_E (0x00000020u)
#define UART_LSR_TX_SR_E (0x00000040u)
#define UART_IER_LINE_STS_IT (0x00000004u)
#define UART_IER_RHR_IT (0x00000001u)
//#include "soc_AM335x.h"
#define SOC_PRCM_REGS (0x44E00000)
#define SOC_CM_PER_REGS (SOC_PRCM_REGS + 0)
#define SOC_CM_WKUP_REGS (SOC_PRCM_REGS + 0x400)
#define SOC_UART_0_REGS (0x44E09000)
#define SOC_UART_1_REGS (0x48022000)
#define SOC_UART_2_REGS (0x48024000)
#define SOC_UART_3_REGS (0x481A6000)
#define SOC_UART_4_REGS (0x481A8000)
#define SOC_UART_5_REGS (0x481AA000)
#define SOC_CONTROL_REGS (0x44E10000)
#define SOC_AINTC_REGS (0x48200000)
//#include "interrupt.h"
#define AINTC_HOSTINT_ROUTE_IRQ (0)
#define SYS_INT_UART0INT (72)
#define SYS_INT_UART1INT (73)
#define SYS_INT_UART2INT (74)
#define INTC_ILR(n) (0x100 + ((n) * 0x04))
#define INTC_ILR_PRIORITY (0x000001FCu)
#define INTC_ILR_PRIORITY_SHIFT (0x00000002u)
#define INTC_MIR_CLEAR(n) (0x88 + ((n) * 0x20))
#define INTC_SYSCONFIG (0x10)
#define INTC_SYSCONFIG_SOFTRESET (0x00000002u)
#define INTC_SYSSTATUS (0x14)
#define INTC_SYSSTATUS_RESETDONE (0x00000001u)
#define INTC_THRESHOLD (0x68)
#define INTC_THRESHOLD_PRIORITYTHRESHOLD (0x000000FFu)
//#include "hw_types.h"
#define HWREG(x) (*((volatile unsigned int *)(x)))
#define TRUE 1
#define FALSE 0
// from uart.c
//#include "hw_control_AM335x.h"
#define CONTROL_CONF_UART_RXD(n) (0x970 + ((n) * 0x10))
#define CONTROL_CONF_UART_TXD(n) (0x974 + ((n) * 0x10))
#define CONTROL_CONF_UART0_RXD_CONF_UART0_RXD_RXACTIVE (0x00000020u)
#define CONTROL_CONF_UART0_RXD_CONF_UART0_RXD_PUTYPESEL (0x00000010u)
#define CONTROL_CONF_UART0_TXD_CONF_UART0_TXD_PUTYPESEL (0x00000010u)
//#include "hw_cm_wkup.h"
#define CM_WKUP_CLKSTCTRL (0x0)
#define CM_WKUP_CONTROL_CLKCTRL (0x4)
#define CM_WKUP_CONTROL_CLKCTRL_MODULEMODE (0x00000003u)
#define CM_WKUP_CONTROL_CLKCTRL_MODULEMODE_ENABLE (0x2u)
#define CM_WKUP_CLKSTCTRL_CLKTRCTRL (0x00000003u)
#define CM_WKUP_CLKSTCTRL_CLKTRCTRL_SW_WKUP (0x2u)
#define CM_WKUP_CM_L3_AON_CLKSTCTRL (0x18)
#define CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKTRCTRL (0x00000003u)
#define CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKTRCTRL_SW_WKUP (0x2u)
#define CM_WKUP_CM_L3_AON_CLKSTCTRL_CLKACTIVITY_L3_AON_GCLK (0x00000008u)
#define CM_WKUP_UART0_CLKCTRL (0xb4)
#define CM_WKUP_UART0_CLKCTRL_MODULEMODE (0x00000003u)
#define CM_WKUP_UART0_CLKCTRL_MODULEMODE_ENABLE (0x2u)
#define CM_WKUP_CONTROL_CLKCTRL_IDLEST (0x00030000u)
#define CM_WKUP_CONTROL_CLKCTRL_IDLEST_SHIFT (0x00000010u)
#define CM_WKUP_CONTROL_CLKCTRL_IDLEST_FUNC (0x0u)
#define CM_WKUP_L4WKUP_CLKCTRL_IDLEST (0x00030000u)
#define CM_WKUP_L4WKUP_CLKCTRL_IDLEST_SHIFT (0x00000010u)
#define CM_WKUP_L4WKUP_CLKCTRL_IDLEST_FUNC (0x0u)
#define CM_WKUP_L4WKUP_CLKCTRL (0xc)
#define CM_WKUP_CLKSTCTRL_CLKACTIVITY_L4_WKUP_GCLK (0x00000004u)
#define CM_WKUP_CM_L4_WKUP_AON_CLKSTCTRL_CLKACTIVITY_L4_WKUP_AON_GCLK (0x00000004u)
#define CM_WKUP_CM_L4_WKUP_AON_CLKSTCTRL (0xcc)
#define CM_WKUP_CLKSTCTRL_CLKACTIVITY_UART0_GFCLK (0x00001000u)
#define CM_WKUP_UART0_CLKCTRL_IDLEST (0x00030000u)
#define CM_WKUP_UART0_CLKCTRL_IDLEST_SHIFT (0x00000010u)
#define CM_WKUP_UART0_CLKCTRL_IDLEST_FUNC (0x0u)
//#include "hw_cm_per.h"
#define CM_PER_L3S_CLKSTCTRL (0x4)
#define CM_PER_L3_CLKCTRL (0xe0)
#define CM_PER_L3_CLKSTCTRL (0xc)
#define CM_PER_L3_CLKCTRL_MODULEMODE (0x00000003u)
#define CM_PER_L3_CLKCTRL_MODULEMODE_ENABLE (0x2u)
#define CM_PER_L3_INSTR_CLKCTRL (0xdc)
#define CM_PER_L3_INSTR_CLKCTRL_MODULEMODE (0x00000003u)
#define CM_PER_L3_INSTR_CLKCTRL_MODULEMODE_ENABLE (0x2u)
#define CM_PER_L3_CLKSTCTRL_CLKTRCTRL (0x00000003u)
#define CM_PER_L3_CLKSTCTRL_CLKTRCTRL_SW_WKUP (0x2u)
#define CM_PER_OCPWP_L3_CLKSTCTRL (0x12c)
#define CM_PER_OCPWP_L3_CLKSTCTRL_CLKTRCTRL (0x00000003u)
#define CM_PER_OCPWP_L3_CLKSTCTRL_CLKTRCTRL_SW_WKUP (0x2u)
#define CM_PER_L3S_CLKSTCTRL_CLKTRCTRL (0x00000003u)
#define CM_PER_L3S_CLKSTCTRL_CLKTRCTRL_SW_WKUP (0x2u)
#define CM_PER_L3_CLKCTRL_IDLEST (0x00030000u)
#define CM_PER_L3_CLKCTRL_IDLEST_SHIFT (0x00000010u)
#define CM_PER_L3_CLKCTRL_IDLEST_FUNC (0x0u)
#define CM_PER_L3_INSTR_CLKCTRL_IDLEST (0x00030000u)
#define CM_PER_L3_INSTR_CLKCTRL_IDLEST_SHIFT (0x00000010u)
#define CM_PER_L3_INSTR_CLKCTRL_IDLEST_FUNC (0x0u)
#define CM_PER_L3_CLKSTCTRL_CLKACTIVITY_L3_GCLK (0x00000010u)
#define CM_PER_OCPWP_L3_CLKSTCTRL_CLKACTIVITY_OCPWP_L3_GCLK (0x00000010u)
#define CM_PER_L3S_CLKSTCTRL_CLKACTIVITY_L3S_GCLK (0x00000008u)
#define REG(x)(*((volatile uint32_t *)(x)))
#define BIT(x)(0x1 << x)
#define UART_MODULE_INPUT_CLK (48000000)
// from interrupt.c
#define REG_IDX_SHIFT (0x05)
#define REG_BIT_MASK (0x1F)
#define NUM_INTERRUPTS (128u)
void (*fnRAMVectors[NUM_INTERRUPTS])(void);
Also, use this command file, from one of the starterware examples.
I wasted alot of time with flakey-intermittently failing images that weren't linked/built properly.
None of this is documented for noobs. Copy it into your pwm workspace dir.
/****************************************************************************/
/* LNK32.CMD - v4.5.0 COMMAND FILE FOR LINKING TMS470 32BIS C/C++ PROGRAMS */
/* */
/* Usage: lnk470 <obj files...> -o <out file> -m <map file> lnk32.cmd */
/* cl470 <src files...> -z -o <out file> -m <map file> lnk32.cmd */
/* */
/* Description: This file is a sample command file that can be used */
/* for linking programs built with the TMS470 C/C++ */
/* Compiler. Use it as a guideline; you may want to change */
/* the allocation scheme according to the size of your */
/* program and the memory layout of your target system. */
/* */
/* Notes: (1) You must specify the directory in which run-time support */
/* library is located. Either add a "-i<directory>" line to */
/* this file, or use the system environment variable C_DIR */
/* to specify a search path for libraries. */
/* */
/* (2) If the run-time support library you are using is not */
/* named below, be sure to use the correct name here. */
/* */
/****************************************************************************/
-stack 0x0008 /* SOFTWARE STACK SIZE */
-heap 0x2000 /* HEAP AREA SIZE */
-e Entry
/* Since we used 'Entry' as the entry-point symbol the compiler issues a */
/* warning (#10063-D: entry-point symbol other than "_c_int00" specified: */
/* "Entry"). The CCS Version (5.1.0.08000) stops building from command */
/* line when there is a warning. So this warning is suppressed with the */
/* below flag. */
--diag_suppress=10063
/* SPECIFY THE SYSTEM MEMORY MAP */
MEMORY
{
DDR_MEM : org = 0x80000000 len = 0x7FFFFFF /* RAM */
}
/* SPECIFY THE SECTIONS ALLOCATION INTO MEMORY */
SECTIONS
{
.text:Entry : load > 0x80000000
.text : load > DDR_MEM /* CODE */
.data : load > DDR_MEM /* INITIALIZED GLOBAL AND STATIC VARIABLES */
.bss : load > DDR_MEM /* UNINITIALIZED OR ZERO INITIALIZED */
/* GLOBAL & STATIC VARIABLES */
RUN_START(bss_start)
RUN_END(bss_end)
.const : load > DDR_MEM /* GLOBAL CONSTANTS */
.stack : load > 0x87FFFFF0 /* SOFTWARE SYSTEM STACK */
}
It all looks very large, but this is all self-contained, no hidden functions.
In CCS/Properties/ARM Linker/File Search Path add only 1 library I could not suss-out:
C:\ti\AM335X_StarterWare_02_00_01_01\binary\armv7a\cgt_ccs\am335x\system_config\Debug\system.lib
Maybe someone on the forum can explain this.
You can easily code-in the manifests and eliminate some of the HAL functions to reduce size.
I encourage others in the bare-metal community to break-out the other subsystems on the bone.
We can all benefit and build bullet-proof lightning-fast applications.
The Sitara documentation looks good, but it is a tough cookie.
hackaway.......................dd
↧
BeagleBone Bare Metal UART Driver
↧