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();
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的全部代码,改动有几处:
- 增加串口2的枚举
- 增加串口2的描述和寄存器基址
- 中断服务函数中增加串口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() ,完成以下动作:
- 发送字符串“1234”共10次;
- 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);
}