Bootstrap

AB32VG1-在rt-thread中如何使用串口2(uart2)

AB32VG1-在rt-thread中如何使用串口2(uart2)


新的官方bsp已经提供串口2的驱动,直接通过rt thread studio图形化配置界面打开uart2的开关,就可以直接使用。此文仅供学习。
AB32VG1开发板使用的芯片是AB5301A,这个芯片的公开的资料相对比较少。官方公开的资料主要是以下3个:

  • 1.中科蓝讯开发板工程文档使用说明 https://ab32vg1-example.readthedocs.io/zh/latest/index.html
  • 2.文档更新仓库(会不定期更新):https://gitee.com/bluetrum/AB32VG1_DOC
  • 3.RT-Thread 官方论坛关于 AB32VG1 开发板的技术分享 : https://club.rt-thread.org/ask/search.html?module=article&type=new&keyword=ab32vg1
    其他零零散散,也可以搜索到很多网友大神的教程,我重点看了《嵌入式实验楼》的例子。我这里就是以他的例子为基础进行修改的。《 玩转中科蓝讯(AB32VG1)开发板》第6章 音乐播放器

一、串口2(uart2)相关寄存器

1.1 串口2使用的引脚,如何配置GPIO功能复用

查看文档《AB32VG1_Register.pdf》UART2的引脚是PE2、PE3或者PB2、PB1。
在这里插入图片描述
在这里插入图片描述
我选用的是PE3 – TX2G1 ,PE2 – RX2G1.
通过寄存器FUNCMCON1 来配置,看看FUNCMCON1的内容:
在这里插入图片描述
只要在合适的地方插入下面的语句就可以实现配置。

FUNCMCON1 = ((1u << 8)|(1u <<4));

1.2 配置GPIO复用的代码应该放在哪里

串口初始化代码在《ab32vg1_hal_msp.c》文件中,下面是修改后的代码,uart0和uart1是官方提供的,uart2是我增加上去的:

void hal_uart_mspinit(struct uart_handle *huart)
{
    struct gpio_init gpio_init;

    if (huart->instance == UART0_BASE) {
        gpio_init.pin       = GPIO_PIN_7;
        gpio_init.pull      = GPIO_PULLUP;
        gpio_init.dir       = GPIO_DIR_INPUT;
        gpio_init.de        = GPIO_DIGITAL;
        gpio_init.alternate = GPIO_AF_MAP_Gx(UT0TXMAP_AF, GPIO_AF_G1) | UT0RXMAP_TX;
        gpio_init.af_con    = GPIO_AFEN | GPIO_AFCON0 | UT0TXMAP_AF;
        hal_gpio_init(GPIOA_BASE, &gpio_init);
    } else if (huart->instance == UART1_BASE) {
        gpio_init.pin       = GPIO_PIN_4;
        gpio_init.dir       = GPIO_DIR_OUTPUT;
        gpio_init.de        = GPIO_DIGITAL;
        gpio_init.alternate = GPIO_AF_MAP_Gx(UT1TXMAP_AF, GPIO_AF_G2);
        gpio_init.af_con    = GPIO_AFEN | GPIO_AFCON0 | UT1TXMAP_AF;
        hal_gpio_init(GPIOA_BASE, &gpio_init);

        gpio_init.pin       = GPIO_PIN_3;
        gpio_init.pull      = GPIO_PULLUP;
        gpio_init.dir       = GPIO_DIR_INPUT;
        gpio_init.de        = GPIO_DIGITAL;
        gpio_init.alternate = GPIO_AF_MAP_Gx(UT1RXMAP_AF, GPIO_AF_G2);
        gpio_init.af_con    = GPIO_AFEN | GPIO_AFCON0 | UT1RXMAP_AF;
        hal_gpio_init(GPIOA_BASE, &gpio_init);
        /* Interrupt */
    } else if (huart->instance == UART2_BASE) {//   增加对串口2的初始化
        gpio_init.pin       = GPIO_PIN_3; //    PE3 -- TX2G1
        gpio_init.dir       = GPIO_DIR_OUTPUT;
        gpio_init.de        = GPIO_DIGITAL;
        gpio_init.alternate = 0;    //  通过这两个参数配置GPIO复用的逻辑太复杂
        gpio_init.af_con    = 0;    //  一时间没有搞清楚,直接不用
        hal_gpio_init(GPIOE_BASE, &gpio_init);
        gpio_init.pin       = GPIO_PIN_2;   //  PE2 -- RX2G1
        gpio_init.pull      = GPIO_PULLUP;
        gpio_init.dir       = GPIO_DIR_INPUT;
        gpio_init.de        = GPIO_DIGITAL;
        gpio_init.alternate = 0;
        gpio_init.af_con    = 0;
        hal_gpio_init(GPIOE_BASE, &gpio_init);
        FUNCMCON1 = ((1u << 8)|(1u <<4));    //  这个是关键的代码
    }
}

下面给出官方配置GPIO复用的代码,相关的代码分别放在几个文件,这里集中放置,供学有余力的同学参考:

//<ab32vg1_hal_gpio.h>
/* Private constants */
#define FUNCMCONx(x)            *(volatile uint32_t*)(SFR0_BASE + (0x07 + (x))*4)
#define GPIO_AFDIS              (0u << 7)
#define GPIO_AFEN               (1u << 7)
#define GPIO_AFCON0             (0u << 5)     /*!< When using UARTT0 UART1 HSUART SPI0 and SD0 */
#define GPIO_AFCON1             (1u << 5)     /*!< When using LPWM0 LPWM1 LPWM2 LPWM3 SPI1 UART2 and CLKOUT */
#define GPIO_AFCON2             (2u << 5)     /*!< When using IR TIMER3 TIMER4 TIMER5 and IIS */
#define GPIO_AFCON_MASK         (0x3u << 5)
#define GPIO_GET_AFCON(af_con)  (uint8_t)(((af_con) & (GPIO_AFCON_MASK)) >> 5)
//-------SFR0_BASE ---<ab32vg1.h>---
#define SFR0_BASE  (0x00000000 + 0x000)
//---------------<ab32vg1_hal_gpio_ex.h>-------------------------
#define UT1RXMAP_AF             (28u)
#define UT1TXMAP_AF             (24u)
#define HSUTRXMAP_AF            (20u)
#define HSUTTXMAP_AF            (16u)
#define UT0RXMAP_AF             (12u)
#define UT0TXMAP_AF             ( 8u)
#define SPI0MAP_AF              ( 4u)
#define SD0MAP_AF               ( 0u)

#define UT1RXMAP_TX             ((uint32_t)(0x3u << (UT1RXMAP_AF)))
#define UT0RXMAP_TX             ((uint32_t)(0x7u << (UT0RXMAP_AF)))
#define GPIO_AF_MAP_Gx(AF, Gx)  ((uint32_t)((Gx) << (AF)))
#define GPIO_AF_MAP_CLR(AF)     ((uint32_t)(0xfu << (AF)))  

//---------<ab32vg1_hal_gpio.c>---------------------------------------
void hal_gpio_init(hal_sfr_t gpiox, gpio_init_t gpio_init)
{...
gpio_afinit(gpiox, iocurrent, gpio_init->alternate, gpio_init->af_con);
...
}
void gpio_afinit(hal_sfr_t gpiox, uint8_t pin, uint32_t alternate, uint32_t af_con)
{
    uint32_t is_rx_map_tx = (alternate & UT1RXMAP_TX) || (alternate & UT0RXMAP_TX);
    if ((af_con & 0xf0u) != GPIO_AFDIS) {
        gpiox[GPIOxFEN]   |= BIT(pin);

        switch (is_rx_map_tx)
        {
        case UT1RXMAP_TX:
            FUNCMCONx(GPIO_GET_AFCON(af_con)) |= GPIO_AF_MAP_CLR(UT1RXMAP_AF);
            break;
        case UT0RXMAP_TX:
            FUNCMCONx(GPIO_GET_AFCON(af_con)) |= GPIO_AF_MAP_CLR(UT0RXMAP_AF);
            break;
        default:
            break;
        }

        FUNCMCONx(GPIO_GET_AFCON(af_con)) |= GPIO_AF_MAP_CLR(af_con & 0x1f);
        FUNCMCONx(GPIO_GET_AFCON(af_con)) |= alternate;
        HAL_LOG("af_con=0x%X AFCON=%d alternate=%d\n", af_con, GPIO_GET_AFCON(af_con), (af_con & 0x1f));
    }
}

二、串口初始化流程跟踪

1.rtthread_startup()

函数rtthread_startup在《components.c》中,调用了rt_hw_board_init();
component.c位置

2.rt_hw_board_init()

函数rt_hw_board_init在《board.c》中,调用了rt_hw_usart_init();
在这里插入图片描述

3.rt_hw_usart_init()

函数rt_hw_usart_init在《drv_uart.c》中,drv_uart.c里面有很多处需要修改。
在这里插入图片描述
这里贴出drv_uart.c的全部代码,改动有几处:

  1. 增加串口2的枚举
  2. 增加串口2的描述和寄存器基址
  3. 中断服务函数中增加串口2的接收中断服务。
/*
 * Copyright (c) 2020-2021, Bluetrum Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 2020-11-20     greedyhao         first version
 */

#include "board.h"
#include "drv_usart.h"

#ifdef RT_USING_SERIAL

//#define DRV_DEBUG
#define LOG_TAG             "drv.usart"
#include <drv_log.h>

#undef  RT_SERIAL_USING_DMA

enum
{
    UART0_INDEX,
    UART1_INDEX,
    UART2_INDEX,    //  【1】增加串口2的枚举
};

static struct ab32_uart_config uart_config[] =
{
    {
        .name = "uart0",
        .instance = UART0_BASE,
    },
    {
        .name = "uart1",
        .instance = UART1_BASE,
    },//    【2】增加串口2的描述和寄存器基址
    {
        .name = "uart2",
        .instance = UART2_BASE,
    }
};

static struct ab32_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {0};

static rt_err_t ab32_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    struct ab32_uart *uart;
    RT_ASSERT(serial != RT_NULL);
    RT_ASSERT(cfg != RT_NULL);

    uart = rt_container_of(serial, struct ab32_uart, serial);
    uart->handle.instance           = uart->config->instance;
    uart->handle.init.baud          = cfg->baud_rate;
    uart->handle.init.mode          = UART_MODE_TX_RX;

    switch (cfg->data_bits)
    {
    case DATA_BITS_8:
        uart->handle.init.word_len  = UART_WORDLENGTH_8B;
        break;
    case DATA_BITS_9:
        uart->handle.init.word_len  = UART_WORDLENGTH_9B;
        break;
    default:
        uart->handle.init.word_len  = UART_WORDLENGTH_8B;
        break;
    }

    switch (cfg->stop_bits)
    {
    case STOP_BITS_1:
        uart->handle.init.stop_bits = UART_STOPBITS_1;
        break;
    case STOP_BITS_2:
        uart->handle.init.stop_bits = UART_STOPBITS_2;
        break;
    default:
        uart->handle.init.stop_bits = UART_STOPBITS_1;
        break;
    }

#ifdef RT_SERIAL_USING_DMA
    uart->dma_rx.last_index = 0;
#endif

    hal_uart_init(&uart->handle);

    return RT_EOK;
}

static rt_err_t ab32_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    struct ab32_uart *uart;
#ifdef RT_SERIAL_USING_DMA
    rt_ubase_t ctrl_arg = (rt_ubase_t)arg;
#endif

    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct ab32_uart, serial);

    switch (cmd)
    {
    /* disable interrupt */
    case RT_DEVICE_CTRL_CLR_INT:
        hal_uart_control(uart->handle.instance, UART_RXIT_ENABLE, HAL_DISABLE);
        break;
    /* enable interrupt */
    case RT_DEVICE_CTRL_SET_INT:
        hal_uart_clrflag(uart->handle.instance, UART_FLAG_RXPND);
        hal_uart_control(uart->handle.instance, UART_RXIT_ENABLE, HAL_ENABLE);
        break;
    case RT_DEVICE_CTRL_CLOSE:
        hal_uart_deinit(uart->handle.instance);
        break;
    }

    return RT_EOK;
}

static int ab32_putc(struct rt_serial_device *serial, char ch)
{
    struct ab32_uart *uart;
    RT_ASSERT(serial != RT_NULL);

    uart = rt_container_of(serial, struct ab32_uart, serial);
    hal_uart_clrflag(uart->handle.instance,  UART_FLAG_TXPND);
    hal_uart_write(uart->handle.instance, ch);
    while(hal_uart_getflag(uart->handle.instance, UART_FLAG_TXPND) == 0);

    return 1;
}

static int ab32_getc(struct rt_serial_device *serial)
{
    int ch;
    struct ab32_uart *uart;
    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct ab32_uart, serial);

    ch = -1;
    if(hal_uart_getflag(uart->handle.instance, UART_FLAG_RXPND) != HAL_RESET) {
        ch = hal_uart_read(uart->handle.instance);
        hal_uart_clrflag(uart->handle.instance, UART_FLAG_RXPND);
    }

    return ch;
}

static rt_size_t ab32_dma_transmit(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction)
{
    return -1;
}

static void uart_isr(int vector, void *param)
{
    rt_interrupt_enter();

    if(hal_uart_getflag(UART0_BASE, UART_FLAG_RXPND))       //RX one byte finish
    {
        rt_hw_serial_isr(&(uart_obj[UART0_INDEX].serial), RT_SERIAL_EVENT_RX_IND);
    }
    if(hal_uart_getflag(UART1_BASE, UART_FLAG_RXPND))       //RX one byte finish
    {
        rt_hw_serial_isr(&(uart_obj[UART1_INDEX].serial), RT_SERIAL_EVENT_RX_IND);
    }
    if(hal_uart_getflag(UART2_BASE, UART_FLAG_RXPND))       //RX one byte finish
    {
        rt_hw_serial_isr(&(uart_obj[UART2_INDEX].serial), RT_SERIAL_EVENT_RX_IND);
    }   //  【3】中断服务函数中增加串口2的接收中断服务。

    rt_interrupt_leave();
}

static const struct rt_uart_ops ab32_uart_ops =
{
    .configure = ab32_configure,
    .control = ab32_control,
    .putc = ab32_putc,
    .getc = ab32_getc,
    .dma_transmit = ab32_dma_transmit
};

int rt_hw_usart_init(void)
{
    rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct ab32_uart);
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    rt_err_t result = 0;

    rt_hw_interrupt_install(IRQ_UART0_2_VECTOR, uart_isr, RT_NULL, "ut_isr");

    for (int i = 0; i < obj_num; i++)
    {
        /* init UART object */
        uart_obj[i].config          = &uart_config[i];
        uart_obj[i].serial.ops      = &ab32_uart_ops;
        uart_obj[i].serial.config   = config;
        uart_obj[i].serial.config.baud_rate = 1500000;

        /* register UART device */
        result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
                                       RT_DEVICE_FLAG_RDWR
                                       | RT_DEVICE_FLAG_INT_RX
                                       | RT_DEVICE_FLAG_INT_TX
                                       | uart_obj[i].uart_dma_flag
                                       , NULL);
        RT_ASSERT(result == RT_EOK);
    }

   return result;
}

#endif

4. uart_config_all()

函数uart_config_all在《ab32vg1_hal_uart.c》中,这里要对uart的时钟源进行使能。

void uart_config_all(struct uart_handle *huart)
{
    hal_uart_control(huart->instance, UART_MODULE_ENABLE, HAL_DISABLE);

    CLKCON1 |= BIT(14);
    if (huart->instance == UART0_BASE) {
        hal_rcu_periph_clk_enable(RCU_UART0);
    } else if (huart->instance == UART1_BASE) {
        hal_rcu_periph_clk_enable(RCU_UART1);
    }  else if (huart->instance == UART2_BASE) {    //  【1】增加串口2的时钟使能
        hal_rcu_periph_clk_enable(RCU_UART2);
    } else {
        return; /* Not support! */
    }

    hal_uart_setbaud(huart->instance, huart->init.baud);

    if (huart->init.mode != UART_MODE_TX) {
        hal_uart_control(huart->instance, UART_RX_ENABLE, HAL_ENABLE);
    }
    hal_uart_control(huart->instance, UART_MODULE_ENABLE, HAL_ENABLE);
}

至此,驱动部分的改动完毕,下面看看应用部分的改动。

三、串口2应用实例

uart2实例的源代码在《uart2_app.c》
在这里插入图片描述
这个实例是完全按照“音乐播放器”实例中使用串口1的代码《uart_app.c》依样画葫芦。
主要功能在数据解析线程函数 data_uart2_parsing() ,完成以下动作:

  1. 发送字符串“1234”共10次;
  2. uart2数据转发,把收到的数据回传。
    下载程序后,msh>输入 uart2_init 命令,波特率为9600.
#include "uart_app.h"
#include "key_app.h"
#include "led_app.h"

#define SAMPLE_UART2_NAME                 "uart2"

uint8_t tmp;
uint8_t flag1 = 0;


struct serial_configure config_uart2 = RT_SERIAL_CONFIG_DEFAULT;  /* 初始化配置参数 */

/* 用于接收消息的信号量 */
static struct rt_semaphore rx2_sem;
static rt_device_t serial2;

void analyticald_uart2_data(void)
{
    rt_kprintf("vol=%d\n", tmp);
}

/* 接收数据回调函数 */
static rt_err_t uart2_rx_ind(rt_device_t dev, rt_size_t size)
{
    /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
    if (size > 0)
    {
        rt_sem_release(&rx2_sem);
    }
    return RT_EOK;
}

static char uart2_sample_get_char(void)
{
    uint8_t ch;

    while (rt_device_read(serial2, 0, &ch, 1) == 0)
    {
        rt_sem_control(&rx2_sem, RT_IPC_CMD_RESET, RT_NULL);
        rt_sem_take(&rx2_sem, RT_WAITING_FOREVER);
    }
    return ch;
}

/* 数据解析线程 */
static void data_uart2_parsing(void)
{
    char *  str="1234";
    //  发送10次
    for (tmp = 0; tmp < 10; ++tmp) {
        rt_device_write(serial2, 0, str, 4);
        rt_thread_mdelay(500);
    }
    while (1)
    {
        tmp = uart2_sample_get_char();
        rt_device_write(serial2, 0, &tmp, 1);
        flag1 = 1;
    }
}

int uart2_init(void)
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX];
    //char str[] = "hello RT-Thread!\r\n";

    rt_strncpy(uart_name, SAMPLE_UART2_NAME, RT_NAME_MAX);

    /* 查找系统中的串口设备 */
    serial2 = rt_device_find(uart_name);
    if (!serial2)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }
    /* step2:修改串口配置参数 */
    config_uart2.baud_rate = BAUD_RATE_9600;        //修改波特率为9600
    config_uart2.data_bits = DATA_BITS_8;           //数据位 8
    config_uart2.stop_bits = STOP_BITS_1;           //停止位 1
    config_uart2.bufsz     = 128;                   //修改缓冲区 buff size 为 128
    config_uart2.parity    = PARITY_NONE;           //无奇偶校验位

    /* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */
    rt_device_control(serial2, RT_DEVICE_CTRL_CONFIG, &config_uart2);

    /* 初始化信号量 */
    rt_sem_init(&rx2_sem, "rx2_sem", 0, RT_IPC_FLAG_FIFO);
    /* 以中断接收及轮询发送模式打开串口设备 */
    rt_device_open(serial2, RT_DEVICE_FLAG_INT_RX);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial2, uart2_rx_ind);
    /* 发送字符串 */
    //rt_device_write(serial, 0, str, (sizeof(str) - 1));

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial2", (void (*)(void *parameter))data_uart2_parsing, RT_NULL, 2048, 5, 5);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }

    return ret;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart2_init, uart2 device sample);

四、总结

最后提供源代码,百度盘:
链接:https://pan.baidu.com/s/1tZfSWQ_6LFHhoa2aKie5Ug?pwd=tz3m
提取码:tz3m

五、补充

5.1 第一次按照RT-Thread Studio要手动指定编译工具链

在这里插入图片描述
在这里插入图片描述

5.2 编译出错 宏 RT_SECTION 没有定义

../libraries/hal_drivers/drv_common.c:33:12: error: expected declaration specifiers or '...' before string constant
   33 | RT_SECTION(".irq.uart")

《AB32VG1-001\rt-thread\include\rtdef.h》 定义了rt_section (注意是小写),我们把大写的也一起定义了。
在这里插入图片描述

5.3 ‘struct rt_thread’ 结构体没有成员 ‘name’

../board/board.c: In function 'exception_isr':
../board/board.c:284:66: error: 'struct rt_thread' has no member named 'name'
  284 |     rt_kprintf(stack_info, rt_thread_self()->sp, rt_thread_self()->name);

直接注释掉该行。

RT_SECTION(".irq.err")
void exception_isr(void)
{
#if defined(RT_USING_FINSH) && defined(MSH_USING_BUILT_IN_COMMANDS)
    extern long list_thread(void);
#endif
    sys_error_hook(1);
#ifdef RT_USING_CONSOLE
    rt_console_set_device(RT_NULL);
    //rt_kprintf(stack_info, rt_thread_self()->sp, rt_thread_self()->name);
#endif
    while (1);
}
;