1 UART协议

其实在MCU上面,Hello World一般是点灯,只是我是软件出身,还是把Hello World列为一个里程碑吧。。。

要在Pico上实现输出Hello World,一般还是Uart输出,也就是我们一般说的串口输出。关于Uart,可以参考之前的文章:https://blog.csdn.net/fanged/article/details/140913847

要在Pico上实现Uart,除了了解UART协议本身,还有就是了解在rp2350的配置方法。在数据手册上是在12章。

内容很多,我这方面经验也不多,直接看后面代码吧。

2 代码

代码还是来自之前的大神:https://github.com/carlosftm/RPi-Pico2-Baremetal/tree/main/02_BlockingUART

环境是之前文章所搭建的环境。https://blog.csdn.net/fanged/article/details/155830094

/* Copyright (c) 2024 CarlosFTM
   SPDX-License-Identifier: GPL-3.0-or-later
   (see LICENSE.txt for details)
*/

#include <stdint.h>

/* Define register access function
   To make the code more readable, we make use of these macros */
#define PUT32(address, value) (*((volatile unsigned int *)(address))) = value
#define GET32(address) (*(volatile unsigned int *)(address))

/* Define Atomic Register Access
   See section 2.1.3 "Atomic Register Access" on RP2350 datasheet */
#define WRITE_NORMAL (0x0000)   // normal read write access
#define WRITE_XOR    (0x1000)   // atomic XOR on write
#define WRITE_SET    (0x2000)   // atomic bitmask set on write
#define WRITE_CLR    (0x3000)   // atomic bitmask clear on write

/* Function declaration */
int main( void );
void Default_Handler(void);
void Reset_Handler(void);

/* Type definitions */
typedef void(*vectors_t)(void);
typedef struct {
    uint32_t word0;
    uint32_t word1;
    uint32_t word2;
    uint32_t word3;
    uint32_t word4;
} PicobinBlockItem;

/* Extern Variables */
extern unsigned int __stack_end__;

/* Vector Table */
__attribute__( ( used, section( ".vector_table" ) ) ) vectors_t vectorTable[] =
{
  (vectors_t)(&__stack_end__), // Initial SP
  Reset_Handler,               // Reset
  Default_Handler,             // NMI
  Default_Handler,             // HardFault
  Default_Handler,             // MemManage
  Default_Handler,             // BusFaults
  Default_Handler,             // UsageFault
  0,                           // Reserved
  0,                           // Reserved
  0,                           // Reserved
  0,                           // Reserved.
  Default_Handler,             // SVCall
  Default_Handler,             // DebugMonitor
  0,                           // Reserved
  Default_Handler,             // PendSV
  Default_Handler,             // SysTick
  Default_Handler,             // IRQ0
  Default_Handler,             // IRQ1 ...
};

/* RP2350 Spec - 5.9.5. Minimum Viable Image Metadata
   As we want to work with ARM Arch, then we use the Minimum Arm IMAGE_DEF
*/
PicobinBlockItem picoBinBlockItem __attribute__((section(".picobin_block_item"))) = {
    .word0 = 0xffffded3,    // PICOBIN_BLOCK_MARKER_START (4 byte magic header)
    .word1 = 0x10210142,    // 0x42 PICOBIN_BLOCK_ITEM_1BS_IMAGE_TYPE, 0x01 word in size, 0x1021 image type exe secure, ARM, RP2350
    .word2 = 0x000001ff,    // 0x00 pad, 0x0001 size, 0xff(size_type == 1, item_type_ == PICOBIN_BLOCK_ITEM_2BS_LAST)
    .word3 = 0x00000000,    // loop containing just this block
    .word4 = 0xab123579,    // PICOBIN_BLOCK_MARKER_END (4 byte magic footer)
};

void Default_Handler(void)
{
  while (1)
  {
    asm("nop");
  }
}

void Reset_Handler(void)
{
  uint32_t cpuId = GET32(0xd0000000);
  if (cpuId == 1)
  {
    // Hold Core1 on infinite loop
    while(1)
    {
        asm("nop");
    }
  }
  main();
}

/* Delay Function
   5 instructions are needed to do a loop. Therefore:
   5 loops @ 12MHz = 2398 clock cycles per ms.
*/
void delay(uint32_t millisec)
{
    for(uint32_t i = 0; i < millisec * 2398; i++)
    {
        asm("nop");
    }
}

/* ConfigDevice Function
   Configures the clock and GPIO
*/
void configDevice(void)
{
    // Setup XOC clock to drive the GPIO (Pico2 board as a ABM8-272-T3 crystal that oscillates at 12MHz)
    PUT32((0x40048000 + 0),      0x00000aa0);               //  XOC range 1-15MHz (Crystal Oschillator)
    PUT32((0x40048000 + 0x0c),   0x000000c4);               //  Startup Delay (default = 50,000 cycles aprox.)
    PUT32((0x40048000 + 0x2000), 0x00FAB000);               //  Enable XOC
    while (!(GET32(0x40048000 + 4) & ( 1 << 31 )));         //  Wait for XOC stable

    // Configure source clock for components (see datasheer RP2350 Chapter 8. "Clocks")
    PUT32((0x40010000 + 0x3C), 0 );                         //  CLK SYS CTRL = XOC (for processor, bus frabric & memories)
    PUT32((0x40010000 + 0x48), ((1 << 11) | ( 4 << 5)));    //  CLK_PERI_CTRL = XOC (for perifery UART and SPI) + Enable

    // De-asserts the reset of UART0
    PUT32((0x40020000 + WRITE_SET + 0x0), (1 << 26));       // Set UART0 to reset
    asm("nop");
    asm("nop");
    PUT32((0x40020000 + WRITE_CLR + 0x0), (1 << 26));       // De-assert the reset from UART0 
    while (!(GET32(0x40020000 + 0x08) & (1 << 26)));        // Wait for UART0 to be ready

    // Configure GPIO25 to use function 5 (SIO) to controll the GPIO by software
    PUT32((0x40028000 + 0xcc), 5);                          // IO GPIO25 uses SIO
    PUT32((0x40028000 + 0x04), 2);                          // IO GPIO0 uses UART TX
    PUT32((0x40028000 + 0x0c), 2);                          // IO GPIO1 uses UART RX

    // Enable GPIO out in SIO register
    PUT32((0xd0000000 + WRITE_SET + 0x038), (1 << 25));     // SIO OE (output enable) for Pin25

    // Configure the pad control (new on RP2350)
    PUT32((0x40038000 + WRITE_CLR + 0x68), (1 << 8));       // Remove GPIO25 pad isolation
    PUT32((0x40038000 + WRITE_CLR + 0x04), (1 << 8));       // Remove UART0TX pad isolation
    PUT32((0x40038000 + WRITE_CLR + 0x08), (1 << 8));       // Remove UART0RX pad isolation
    PUT32((0x40038000 + WRITE_SET + 0x08), (1 << 6));       // Enable UART0RX pad for input

    // Configure UART0
    //   Baud: For a baud rate of 115200 with UARTCLK = 12MHz then:
    //   Baud Rate Divisor = 12000000/(16 * 115200) ~= 6.5104
    PUT32((0x40070000 + 0x24), 6);                                         // UARTIBRD_H: Integer part of the baudrate divisor
    PUT32((0x40070000 + 0x28), 5104);                                      // UARTFBRD_L: Decimal part of the baudrate divisor
    PUT32((0x40070000 + 0x2c), (( 0x3 << 5 ) | ( 1 << 4 )));               // UARTLCR_H: Word lenght = 8, FIFO RX/TX enabled
    PUT32((0x40070000 + 0x30), ((   1 << 9 ) | ( 1 << 8 ) | ( 1 << 0 )));  // UARTCR: UART Enabled, Tx enabled, Rx enabled
}


/* Transmits Character over UART
   Writes a character on the UART TX FIFO
*/
void uartTxChar(int8_t txData)
{
    volatile int8_t myData = txData;
    while(GET32(0x40070000 + 0x018) & (1 << 5));        // Wait until UART0 FIFO is empty
    PUT32((0x40070000 + 0x0), myData);                  // UARTDR: Write data to Tx.
}

/* Transmits string over UART
   Writes a character on the UART TX FIFO
*/
void uartTxString(int8_t* txData)
{
    while(*txData != '\0')
    {
        uartTxChar(*txData++);
    }
    uartTxChar('\r');
    uartTxChar('\n');
}

/* Indicates when data is avaiable on UART Rx FIFO 
*/
int uartRxDataAvail(void)
{
    return ((GET32(0x40070000 + 0x018) & (1 << 4)) == 0);   // Wait until UART0 FIFO is no empty
}

/* Receives character over UART
*/
uint8_t uartRxChar(void)
{
    while(uartRxDataAvail() == 0);                          // Wait until UART0 Rx data is available
    return((unsigned char)GET32(0x40070000 + 0x0));         // UARTDR: Write data to Tx.
}

/* -------------
   Main Function
   -------------
*/
int main( void ){
    configDevice();
    delay(1000);
    uartTxString((int8_t*)"-= UART Blocking Example for RP2350 =-\n\n");
    while(1)
    {
        unsigned char i = '0';
        int8_t textString[] = "[ ] Hola Mundo!";
        while(i <= 'Z')
        {
            textString[1] = i++;
            uartTxString(textString);
            PUT32((0xd0000000 + WRITE_SET + 0x028), (1 << 25));    // xor GPIO (toggle pin)
            delay(100);
            if (uartRxDataAvail() != 0)
            {
              uartTxChar(uartRxChar());    // Transmit each received character
            }
        }
    }
    return 0;
}

使用的UART0,也就是右上角两个口,再加上一个GND。再找一个TTL转USB。

接好后直接用PICO插件的串口工具,很快就出结果了。。

3 代码学习

3.1 初始化

就是在configDevice()中操作的。去掉了时钟初始化和LED的部分代码如下:

    // De-asserts the reset of UART0
    PUT32((0x40020000 + WRITE_SET + 0x0), (1 << 26));       // Set UART0 to reset
    asm("nop");
    asm("nop");
    PUT32((0x40020000 + WRITE_CLR + 0x0), (1 << 26));       // De-assert the reset from UART0 
    while (!(GET32(0x40020000 + 0x08) & (1 << 26)));        // Wait for UART0 to be ready
 
    // Configure GPIO25 to use function 5 (SIO) to controll the GPIO by software
    PUT32((0x40028000 + 0x04), 2);                          // IO GPIO0 uses UART TX
    PUT32((0x40028000 + 0x0c), 2);                          // IO GPIO1 uses UART RX
 
    // Configure the pad control (new on RP2350)
    PUT32((0x40038000 + WRITE_CLR + 0x04), (1 << 8));       // Remove UART0TX pad isolation
    PUT32((0x40038000 + WRITE_CLR + 0x08), (1 << 8));       // Remove UART0RX pad isolation
    PUT32((0x40038000 + WRITE_SET + 0x08), (1 << 6));       // Enable UART0RX pad for input
 
    // Configure UART0
    //   Baud: For a baud rate of 115200 with UARTCLK = 12MHz then:
    //   Baud Rate Divisor = 12000000/(16 * 115200) ~= 6.5104
    PUT32((0x40070000 + 0x24), 6);                                         // UARTIBRD_H: Integer part of the baudrate divisor
    PUT32((0x40070000 + 0x28), 5104);                                      // UARTFBRD_L: Decimal part of the baudrate divisor
    PUT32((0x40070000 + 0x2c), (( 0x3 << 5 ) | ( 1 << 4 )));               // UARTLCR_H: Word lenght = 8, FIFO RX/TX enabled
    PUT32((0x40070000 + 0x30), ((   1 << 9 ) | ( 1 << 8 ) | ( 1 << 0 )));  // UARTCR: UART Enabled, Tx enabled, Rx enabled

首先是初始化UART0,操作Rest寄存器的26位。中间使用asm("nop");短暂等待。

之后通过SIO的寄存器,将IO口配置成UART。

然后要显示配置pad isolation,这是树莓派pico安全低功耗的特性,总之要搞一下。

最后就是具体配置UART0,波特率,模式之类的。

3.2 发送和接收

首先是发送。将发送字符串封装成了一个函数。这个没什么好看的。

void uartTxString(int8_t* txData)

{

    while(*txData != '\0')

    {

        uartTxChar(*txData++);

    }

    uartTxChar('\r');

    uartTxChar('\n');

}

然后是发送字符。

void uartTxChar(int8_t txData)

{

    volatile int8_t myData = txData;

    while(GET32(0x40070000 + 0x018) & (1 << 5));        // Wait until UART0 FIFO is empty

    PUT32((0x40070000 + 0x0), myData);                  // UARTDR: Write data to Tx.

}

首先用GET32检查FIFO的指示寄存器是否为空。

然后用PUT32将数据写入UART0的数据寄存器。

这里的UARTDR虽然是32位,0x00到0x03,但是只有低8位是有效数据位。所以一次只能写入一个char。

再看看接收。

/* Indicates when data is avaiable on UART Rx FIFO

*/

int uartRxDataAvail(void)

{

    return ((GET32(0x40070000 + 0x018) & (1 << 4)) == 0);   // Wait until UART0 FIFO is no empty

}

/* Receives character over UART

*/

uint8_t uartRxChar(void)

{

    while(uartRxDataAvail() == 0);                          // Wait until UART0 Rx data is available

    return((unsigned char)GET32(0x40070000 + 0x0));         // UARTDR: Write data to Tx.

}

首先是查看是否有待读的数据。用来判断的是UARTFR Register的第4位。

之后从发送相同的数据寄存器,UARTDR读取数据。虽然只有一个char,但是这里读取也是用的GET32,好像是ARM结构UART的读取只能32位读。。。

这里还有一个问题。0x40070000 + 0x0,之前写操作也是这个地址,查了一下,好像是为了节约寄存器寻址空间,所以做成共用,但是硬件上做了区隔的。

好了,就到这里吧。。这也算比较简单的了,中断,DMA啥的都没有。后面再学习吧。。。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐