第二阶段:Rust编写树莓派串口驱动

此阶段主要关于树莓派如何启用串口。

前置知识一:树莓派 GPIO 功能

引脚介绍

  • 下图为树莓派40个引脚的介绍。

  • 根据官方文档,可知各种 UART 信号如何(包括迷你 UART)映射到通用 I/O (GPIO) 上。
PullALT0ALT1ALT2ALT3ALT4ALT5
GPIO0HighTXD2
GPIO1HighRXD2
GPIO2HighCTS2
GPIO3HighRTS2
GPIO4HighTXD3
GPIO5HighRXD3
GPIO6HighCTS3
GPIO7HighRTS3
GPIO8HighTXD4
GPIO9HighRXD4
GPIO10HighCTS4
GPIO11HighRTS4
GPIO12HighTXD5
GPIO13HighRXD5
GPIO14LowTXD0CTS5
GPIO15LowRXD0RTS5
GPIO16LowCTS0
GPIO17LowRTS0

相关寄存器

GPIO 寄存器基地址:0xfe200000

寄存器简介

  • 根据官方文档,以下表格是部分寄存器的介绍。
OffsetNameDescription
0x00GPFSEL0GPIO Function Select 0
0x04GPFSEL1GPIO Function Select 1
0x08GPFSEL2GPIO Function Select 2
0x0cGPFSEL3GPIO Function Select 3
0x10GPFSEL4GPIO Function Select 4
0x14GPFSEL5GPIO Function Select 5
0xe4GPIO_PUP_PDN_CNTRL_REG0GPIO Pull-up / Pull-down Register 0
0xe8GPIO_PUP_PDN_CNTRL_REG1GPIO Pull-up / Pull-down Register 1
0xecGPIO_PUP_PDN_CNTRL_REG2GPIO Pull-up / Pull-down Register 2
0xf0GPIO_PUP_PDN_CNTRL_REG3GPIO Pull-up / Pull-down Register 3
  • GPFSEL寄存器,主要是控制GPIOxx--GPIOxx的功能选择。

    GPFSEL0控制GPIO0--GPIO9的功能选择;

    GPFSEL1控制GPIO10--GPIO19的功能选择;

    GPFSEL2控制GPIO20--GPIO29的功能选择;

    GPFSEL3控制GPIO30--GPIO39的功能选择;

    GPFSEL4控制GPIO40--GPIO49的功能选择;

    GPFSEL5控制GPIO50--GPIO57的功能选择。

    该寄存器存储32位的二进制数,每3位代表了一个GPIO引脚选择的功能,最后两位保留,其中,每个值的对应关系为

    000 = GPIO Pin ~ is an input
    001 = GPIO Pin ~ is an output
    100 = GPIO Pin ~ takes alternate function 0
    101 = GPIO Pin ~ takes alternate function 1
    110 = GPIO Pin ~ takes alternate function 2
    111 = GPIO Pin ~ takes alternate function 3
    011 = GPIO Pin ~ takes alternate function 4
    010 = GPIO Pin ~ takes alternate function 5
    
  • GPIO_PUP_PDN_CNTRL_REG寄存器,主要用于对GPIOxx--GPIOxx配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻(上拉电阻会将引脚连接到正电源,从而将引脚拉高到高电平;下拉电阻会将引脚连接到地(GND),从而将引脚拉低到低电平)。

    GPIO_PUP_PDN_CNTRL_REG0用于对GPIO0--GPIO15配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻;

    GPIO_PUP_PDN_CNTRL_REG1用于对GPIO16--GPIO31配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻;

    GPIO_PUP_PDN_CNTRL_REG2用于对GPIO32--GPIO47配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻;

    GPIO_PUP_PDN_CNTRL_REG3用于对GPIO48--GPIO57配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻。

    该寄存器存储32位的二进制数,每2位代表了一个GPIO引脚上拉/下拉电阻选择的可能配置,其中,每个值的对应关系为:

    00 = No resistor is selected
    01 = Pull up resistor is selected
    10 = Pull down resistor is selected
    11 = Reserved
    

关于寄存器的代码介绍

位置:rust-raspberrypi-OS-tutorials/06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs

#![allow(unused)]
fn main() {
register_bitfields! {
    u32,

    /// GPIO Function Select 1
    GPFSEL1 [
        /// Pin 15
        FSEL15 OFFSET(15) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART RX
        ],
        /// Pin 14
        FSEL14 OFFSET(12) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART TX
        ],
    ],
    /// GPIO Pull-up/down Register
    /// BCM2837 only.
    GPPUD [
        /// Controls the actuation of the internal pull-up/down control line to ALL the GPIO pins.
        PUD OFFSET(0) NUMBITS(2) [
            Off = 0b00,
            PullDown = 0b01,
            PullUp = 0b10
        ]
    ],
    /// GPIO Pull-up/down Clock Register 0
    /// BCM2837 only.
    GPPUDCLK0 [
        /// Pin 15
        PUDCLK15 OFFSET(15) NUMBITS(1) [
            NoEffect = 0,
            AssertClock = 1
        ],
        /// Pin 14
        PUDCLK14 OFFSET(14) NUMBITS(1) [
            NoEffect = 0,
            AssertClock = 1
        ],
    ],

    /// GPIO Pull-up / Pull-down Register 0
    /// BCM2711 only.
    GPIO_PUP_PDN_CNTRL_REG0 [
        /// Pin 15
        GPIO_PUP_PDN_CNTRL15 OFFSET(30) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ],
        /// Pin 14
        GPIO_PUP_PDN_CNTRL14 OFFSET(28) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ],
    ]
}

register_structs! {
    #[allow(non_snake_case)]
    RegisterBlock {
        (0x00 => _reserved1),
        (0x04 => GPFSEL1: ReadWrite<u32, GPFSEL1::Register>),
        (0x08 => _reserved2),
        (0x94 => GPPUD: ReadWrite<u32, GPPUD::Register>),
        (0x98 => GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>),
        (0x9C => _reserved3),
        (0xE4 => GPIO_PUP_PDN_CNTRL_REG0: ReadWrite<u32, GPIO_PUP_PDN_CNTRL_REG0::Register>),
        (0xE8 => @END),
    }
}
}

根据上述内容,可以知道GPIO14、15要实现串口通信,需要启用ALT0功能。而这对应着关于GPIO的GPFSEL1寄存器,

所以,在代码中,在第12-14位和第15-17位设置为0b100:

#![allow(unused)]
fn main() {
 GPFSEL1 [
        /// Pin 15
        FSEL15 OFFSET(15) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART RX
        ],

        /// Pin 14
        FSEL14 OFFSET(12) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART TX
        ],
 ]

}

还要设置上拉/下拉电阻,在第28、29位和第30、31位设置为0b01:

#![allow(unused)]
fn main() {
 GPIO_PUP_PDN_CNTRL_REG0 [
        /// Pin 15
        GPIO_PUP_PDN_CNTRL15 OFFSET(30) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ],

        /// Pin 14
        GPIO_PUP_PDN_CNTRL14 OFFSET(28) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ]
  ]
}

前置知识二:UART 通信

UART 是一种通用的串行、异步通信总线,该总线有两条数据线,TXD 用于发送数据,RXD用于接收数据,在嵌入式系统中常用于主机与辅助设备之间的通信。

UART连接方式如图:

基本特性和工作原理:

  1. 异步性: UART是异步通信协议,发送和接收设备之间不需要共享时钟信号。相反,它使用起始位(Start Bit)和停止位(Stop Bit)来标识每个数据字节的开始和结束,以及一个或多个数据位组成的数据字节。

  2. 数据帧: UART通信的数据传输以数据帧为单位。每个数据帧通常包括一个起始位、8位数据(通常是8位,但也可以是其他位数)、一个可选的奇偶校验位和一个或多个停止位。起始位和停止位提供了数据的边界,确保接收端能够准确地识别每个字节。

  3. 波特率: 波特率是UART通信中的数据传输速度,通常以每秒位数(bps,bits per second)为单位。发送和接收设备必须使用相同的波特率进行通信,以确保数据的正确传输。 波特率计算公式:波特率=时钟频率/16*(IBRD+(FBRD)/64)

  4. 起始位和停止位: 起始位指示数据传输的开始,停止位标志着数据传输的结束。当接收端检测到起始位时,它开始接收数据位,然后是奇偶校验位(如果启用),最后是停止位。接收端通过检测停止位来确定整个数据帧的结束。

  5. 奇偶校验: UART通信可以使用奇偶校验位来验证数据的准确性。奇偶校验可以选择奇数或偶数校验。发送端计算数据位中1的数量,如果选择奇校验,发送端会确保总位数(包括校验位)为奇数;如果选择偶校验,发送端会确保总位数为偶数。

相关寄存器

寄存器简介

根据官方文档,以下是关于部分寄存器的介绍。

OffsetNameDescription
0x00DRData Register
0x04RSRECR
0x18FRFlag register
0x24IBRDInteger Baud rate divisor
0x28FBRDFractional Baud rate divisor
0x2cLCRHLine Control register
0x30CRControl register
0x44ICRInterrupt Clear Register
0x48DMACRDMA Control Register
  • DR寄存器,数据寄存器,用于存储将要发送的数据或接收到的数据。

  • FR寄存器,标志寄存器。

  • IBRD寄存器,整数波特率除数寄存器。

  • FBRD寄存器,分数波特率除数寄存器。

IBRD和FBRD寄存器决定了波特率的数值。

  • LCR_H寄存器,线控制寄存器,用于配置 UART 的数据格式,包括数据位数、停止位数、奇偶校验设置等。

  • CR寄存器,控制寄存器,控制UART的启用,控制发送和接收数据的启用。

  • ICR寄存器,中断清除寄存器,用于清除 UART 中断标志,以便在处理中断后复位相应的中断状态。具体来说,当某个中断发生时,相应的标志位会被设置。通过写入 ICR 寄存器,可以清除这些标志位。

关于寄存器的代码部分

位置:rust-raspberrypi-OS-tutorials/06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs

#![allow(unused)]
fn main() {
register_bitfields! {
    u32,

    /// Flag Register.
    FR [
        /// Transmit FIFO empty. The meaning of this bit depends on the state of the FEN bit in the
        /// Line Control Register, LCR_H.
        ///
        /// - If the FIFO is disabled, this bit is set when the transmit holding register is empty.
        /// - If the FIFO is enabled, the TXFE bit is set when the transmit FIFO is empty.
        /// - This bit does not indicate if there is data in the transmit shift register.
        TXFE OFFSET(7) NUMBITS(1) [],

        /// Transmit FIFO full. The meaning of this bit depends on the state of the FEN bit in the
        /// LCR_H Register.
        ///
        /// - If the FIFO is disabled, this bit is set when the transmit holding register is full.
        /// - If the FIFO is enabled, the TXFF bit is set when the transmit FIFO is full.
        TXFF OFFSET(5) NUMBITS(1) [],

        /// Receive FIFO empty. The meaning of this bit depends on the state of the FEN bit in the
        /// LCR_H Register.
        ///
        /// - If the FIFO is disabled, this bit is set when the receive holding register is empty.
        /// - If the FIFO is enabled, the RXFE bit is set when the receive FIFO is empty.
        RXFE OFFSET(4) NUMBITS(1) [],

        /// UART busy. If this bit is set to 1, the UART is busy transmitting data. This bit remains
        /// set until the complete byte, including all the stop bits, has been sent from the shift
        /// register.
        ///
        /// This bit is set as soon as the transmit FIFO becomes non-empty, regardless of whether
        /// the UART is enabled or not.
        BUSY OFFSET(3) NUMBITS(1) []
    ],

    /// Integer Baud Rate Divisor.
    IBRD [
        /// The integer baud rate divisor.
        BAUD_DIVINT OFFSET(0) NUMBITS(16) []
    ],

    /// Fractional Baud Rate Divisor.
    FBRD [
        ///  The fractional baud rate divisor.
        BAUD_DIVFRAC OFFSET(0) NUMBITS(6) []
    ],

    /// Line Control Register.
    LCR_H [
        /// Word length. These bits indicate the number of data bits transmitted or received in a
        /// frame.
        #[allow(clippy::enum_variant_names)]
        WLEN OFFSET(5) NUMBITS(2) [
            FiveBit = 0b00,
            SixBit = 0b01,
            SevenBit = 0b10,
            EightBit = 0b11
        ],

        /// Enable FIFOs:
        ///
        /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become 1-byte-deep holding
        /// registers.
        ///
        /// 1 = Transmit and receive FIFO buffers are enabled (FIFO mode).
        FEN  OFFSET(4) NUMBITS(1) [
            FifosDisabled = 0,
            FifosEnabled = 1
        ]
    ],

    /// Control Register.
    CR [
        /// Receive enable. If this bit is set to 1, the receive section of the UART is enabled.
        /// Data reception occurs for either UART signals or SIR signals depending on the setting of
        /// the SIREN bit. When the UART is disabled in the middle of reception, it completes the
        /// current character before stopping.
        RXE OFFSET(9) NUMBITS(1) [
            Disabled = 0,
            Enabled = 1
        ],

        /// Transmit enable. If this bit is set to 1, the transmit section of the UART is enabled.
        /// Data transmission occurs for either UART signals, or SIR signals depending on the
        /// setting of the SIREN bit. When the UART is disabled in the middle of transmission, it
        /// completes the current character before stopping.
        TXE OFFSET(8) NUMBITS(1) [
            Disabled = 0,
            Enabled = 1
        ],

        /// UART enable:
        ///
        /// 0 = UART is disabled. If the UART is disabled in the middle of transmission or
        /// reception, it completes the current character before stopping.
        ///
        /// 1 = The UART is enabled. Data transmission and reception occurs for either UART signals
        /// or SIR signals depending on the setting of the SIREN bit
        UARTEN OFFSET(0) NUMBITS(1) [
            /// If the UART is disabled in the middle of transmission or reception, it completes the
            /// current character before stopping.
            Disabled = 0,
            Enabled = 1
        ]
    ],

    /// Interrupt Clear Register.
    ICR [
        /// Meta field for all pending interrupts.
        ALL OFFSET(0) NUMBITS(11) []
    ]
}

register_structs! {
    #[allow(non_snake_case)]
    pub RegisterBlock {
        (0x00 => DR: ReadWrite<u32>),
        (0x04 => _reserved1),
        (0x18 => FR: ReadOnly<u32, FR::Register>),
        (0x1c => _reserved2),
        (0x24 => IBRD: WriteOnly<u32, IBRD::Register>),
        (0x28 => FBRD: WriteOnly<u32, FBRD::Register>),
        (0x2c => LCR_H: WriteOnly<u32, LCR_H::Register>),
        (0x30 => CR: WriteOnly<u32, CR::Register>),
        (0x34 => _reserved3),
        (0x44 => ICR: WriteOnly<u32, ICR::Register>),
        (0x48 => @END),
    }
}

impl PL011UartInner {
    
    ... ...

    pub fn init(&mut self) {
        
        self.flush();

        // Turn the UART off temporarily.
        self.registers.CR.set(0);

        // Clear all pending interrupts.
        self.registers.ICR.write(ICR::ALL::CLEAR);

        // Set new baud rate = 115200
        // (48_000_000 / 16) / 115200 = 26.0416667
        // INTEGER((0.0416667 * 64) + 0.5) = 3.16666688
        self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26));
        self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3));

        self.registers
            .LCR_H
            .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled);

        // Turn the UART on.
        self.registers
            .CR
            .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
    }

    ... ...
}
}

要将波特率设置为115200,所以,在代码中:

self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26));
self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3));

要启用FIFO,并且将发送或接收的数据位数设置为8位,所以:

self.registers
            .LCR_H
            .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled);

还要启用表示UART启用,同时启用发送和接收数据,所以:

self.registers
    .CR
    .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);