你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【STM32WBA52CG评测】二、点灯与调试

[复制链接]
Teresa 发布时间:2023-8-12 22:05

前言

为了开发出更好的软件以及避免出现不可预知的BUG,除了在DEBUG的时候查看各种寄存器,更直观的应该是通过开发板上的交互性设备得知芯片的运行状态,得到开发板之后应该是赶紧调通屏幕串口按键LED这类外设。因为本人希望能理解芯片的运行,所以更喜欢采用自建工程的方式进行项目的开发,虽然CubeMX非常方便,也不需要查阅各种手册就能直接生成芯片的驱动代码,但是在初学上难免显得有点浅薄,难以体现某些外设的执行方式以及各种芯片上的不同之处。所以这次评测使用自建工程的方式调通开发板上的交互外设。

PS:

纠正一个错误,上次评测中只是粗略过了一遍开发板的使用手册,并没有细致的研究,错误的把LPUART与STLINK相连,应该是常规的USART1与STLINK相连,不过官方还是给出了一种解决方案。如果需要LPUART与STLINK相连,必须将开发板上的SB7与SB8触点分别接通,然后再更新STLINK的固件就完成。

925FBA2F-326D-4e04-B6D9-B530CF633AE7.png

图1 SB7与SB8

建立工程

既然是自建工程,在keil建立工程首先是选定芯片,根据UM3103 STM32WBA Nucleo-64 board,开发板上使用的是STM32WBA52CGU6芯片,直接选定,然后不需要RTE协助。根据个人习惯建立的工程如下:

Flash//项目名字

--User//存放main.c

--BSP//存放用户编写的各种驱动文件

--MDK-ARM//存放startup_stm32wba52xx.s启动文件

--HAL-Driver//存在HAL库的各种驱动文件

--CMSIS//存放系统文件system_stm32wbaxx.c

--SEGGER/HardFault//存放SEGGER公司的HardFault调试组件

--Doc//存放readme等说明文档

工程树.png

图2 工程树

从ST官网下载固件包en.stm32cubewba-v1-1-0之后,将各种文件添加进入工程,增加main.c并且尝试编译。

LED

首先调通最简单的LED,根据手册三个LED分别是

LD1----PB4

LD2----PB11

LD3----PB8

这非常贴心,只需要启动GPIOB的时钟,再分别给三个IO设置模式就行,这时,我注意到GPIO的速度设置有所不同,只有三个速度模式,可能是因为是低功耗的平台,所以不存在VERY-HIGH的速度模式。

/** @defgroup GPIO_speed GPIO speed
  * @brief GPIO Output Maximum frequency
  * @{
  */
#define GPIO_SPEED_FREQ_LOW             0x00000000u  /*!< Low speed       */
#define GPIO_SPEED_FREQ_MEDIUM          0x00000001u  /*!< Medium speed    */
#define GPIO_SPEED_FREQ_HIGH            0x00000002u  /*!< High speed      */
/**
  * @}
  */

鉴于代码非常简单,就到最后直接看现象吧

按键

开发板上拥有四个按键,分别是B1、B2、B3与B4(reset),分别是

static const X_GPIO_T s_gpio_list[HARD_KEY_NUM] = {
    {GPIOC, GPIO_PIN_13, 0},        /* B1 */
    {GPIOB, GPIO_PIN_6, 0},         /* B2 */
    {GPIOB, GPIO_PIN_7, 0},         /* B3 */
};

对于按键来说,采用了一个按键FIFO的形式,使用一个周期10ms的轮询函数在Systick的中断中轮询按键FIFO指针的状态来达到读取按键的形式。具体方式:将KeyScan10ms()放入Systick中断中每10ms扫描一次按键状态,通过PutKey()将按键状态装填进入FIFO。最后在主循环中采用GetKey()读取按键状态进行一系列的操作。

typedef enum
{
    KEY_NONE = 0,

    KEY_1_DOWN,
    KEY_1_UP,
    KEY_1_LONG,

    KEY_2_DOWN,
    KEY_2_UP,
    KEY_2_LONG,

    KEY_3_DOWN,
    KEY_3_UP,
    KEY_3_LONG,
}KEY_E;

按键的三种状态,按下,松开,长按

void PutKey(uint8_t _KeyCode)
{
    s_tKey.Buf[s_tKey.Write] = _KeyCode;

    if (++s_tKey.Write >= KEY_FIFO_SIZE)
    {
        s_tKey.Write = 0;
    }
}

PutKey

uint8_t GetKey(void)
{
    uint8_t ret;

    if (s_tKey.Read == s_tKey.Write)
    {
        return KEY_NONE;
    }
    else
    {
        ret = s_tKey.Buf[s_tKey.Read];

        if (++s_tKey.Read >= KEY_FIFO_SIZE)
        {
            s_tKey.Read = 0;
        }
        return ret;
    }
}

GetKey

使用了一个大小为10的FIFO完成了按键的判断,不会丢失按键的同时,也不需要通过中断来影响系统。

串口

串口使用了USART1与DMA两个外设实现了通信功能,不过STM32WBA52上的DMA与众不同,是一个低功耗的GPDMA。总体上采用了串口空闲中断与DMA接收数据,发送数据采用了DMA传输与printf阻塞传输,之所以采用Printf阻塞打印还是因为用的顺手。

USART1设置

void UART_Init(uint32_t _baud)
{
#if USART1_IDLE_DMA
    usart1.uart.Instance = USART1;
    usart1.uart.Init.BaudRate = _baud;
    usart1.uart.Init.WordLength = UART_WORDLENGTH_8B;
    usart1.uart.Init.StopBits = UART_STOPBITS_1;
    usart1.uart.Init.Parity = UART_PARITY_NONE;
    usart1.uart.Init.Mode = UART_MODE_TX_RX;
    usart1.uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    usart1.uart.Init.OverSampling = UART_OVERSAMPLING_16;
    usart1.uart.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
    usart1.uart.Init.ClockPrescaler = UART_PRESCALER_DIV1;
    usart1.uart.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;

    HAL_UART_Init(&usart1.uart);
    HAL_UARTEx_SetTxFifoThreshold(&usart1.uart, UART_TXFIFO_THRESHOLD_1_8);
    HAL_UARTEx_SetRxFifoThreshold(&usart1.uart, UART_RXFIFO_THRESHOLD_1_8);
    HAL_UARTEx_DisableFifoMode(&usart1.uart);
    UART_PtrInit();

    __HAL_UART_ENABLE_IT(&usart1.uart, UART_IT_IDLE);
    HAL_UART_Receive_DMA(&usart1.uart, usart1.RxInPtr->start, USART1_RX_MAX);
#endif 
}

USART1采用了常规的8个比特数据长度无校验无硬件流控,波特率这里并没有设置,最终是115200。

UART_PtrInit()设置了软件FIFO的各种初始化,因为使用了软件FIFO,就没有采用ST提供的硬件FIFO

GPDMA1设置

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef GPIO_InitType = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
    #if USART1_IDLE_DMA
        PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
        PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
        if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);
        }
        USART1_CLK_ENABLE();
        USART1_GPDMA_CLK_ENABLE();
        USART1_TX_GPIO_CLK_ENABLE();
        USART1_RX_GPIO_CLK_ENABLE();

        GPIO_InitType.Pin = USART1_TX_PIN;
        GPIO_InitType.Pull = GPIO_PULLUP;
        GPIO_InitType.Mode = GPIO_MODE_AF_PP;
        GPIO_InitType.Speed = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitType.Alternate = USART1_TX_AF;
        HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitType);

        GPIO_InitType.Pin = USART1_RX_PIN;
        GPIO_InitType.Pull = GPIO_PULLUP;
        GPIO_InitType.Mode = GPIO_MODE_AF_PP;
        GPIO_InitType.Speed = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitType.Alternate = USART1_RX_AF;
        HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitType);

        HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);

        usart1.dmatx.Instance = USART1_TX_DMA_CHANNEL;
        usart1.dmatx.Init.Request = GPDMA1_REQUEST_USART1_TX;
        usart1.dmatx.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
        usart1.dmatx.Init.Direction = DMA_MEMORY_TO_PERIPH;
        usart1.dmatx.Init.DestInc = DMA_DINC_FIXED;
        usart1.dmatx.Init.SrcInc = DMA_SINC_INCREMENTED;
        usart1.dmatx.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
        usart1.dmatx.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
        usart1.dmatx.Init.Mode = DMA_NORMAL;
        usart1.dmatx.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
        usart1.dmatx.Init.DestBurstLength = 1;
        usart1.dmatx.Init.SrcBurstLength = 1;
        usart1.dmatx.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT1;
        usart1.dmatx.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;

        __HAL_LINKDMA(huart, hdmatx, usart1.dmatx);

        HAL_DMA_Init(&usart1.dmatx);
        HAL_DMA_ConfigChannelAttributes(&usart1.dmatx, DMA_CHANNEL_NPRIV);

        HAL_NVIC_SetPriority(USART1_TX_DMA_IRQn, 3, 0);
        HAL_NVIC_EnableIRQ(USART1_TX_DMA_IRQn);

        usart1.dmarx.Instance = USART1_RX_DMA_CHANNEL;
        usart1.dmarx.Init.Request = GPDMA1_REQUEST_USART1_RX;
        usart1.dmarx.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
        usart1.dmarx.Init.Direction = DMA_PERIPH_TO_MEMORY;
        usart1.dmarx.Init.DestInc = DMA_DINC_INCREMENTED;
        usart1.dmarx.Init.SrcInc = DMA_SINC_FIXED;
        usart1.dmarx.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
        usart1.dmarx.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
        usart1.dmarx.Init.Mode = DMA_NORMAL;
        usart1.dmarx.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
        usart1.dmarx.Init.DestBurstLength = 1;
        usart1.dmarx.Init.SrcBurstLength = 1;
        usart1.dmarx.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
        usart1.dmarx.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;

        __HAL_LINKDMA(huart, hdmarx, usart1.dmarx);

        HAL_DMA_Init(&usart1.dmarx);
        HAL_DMA_ConfigChannelAttributes(&usart1.dmarx, DMA_CHANNEL_NPRIV);

        HAL_NVIC_SetPriority(USART1_RX_DMA_IRQn, 3, 0);
        HAL_NVIC_EnableIRQ(USART1_RX_DMA_IRQn);
#endif
}

GPDMA1设置中让我很困扰的是抢占通道的各种设置,没有仔细阅读过GPDMA的手册,其实我是不理解的,但是身为工程师,通过参考例程中的设置还是费劲的跑起来了,给我个人的感觉是GPDMA的设置在很大程度上非常的自由,不过在编写BSP驱动的时候没有F4那时候这么无脑,比如GPDMA加入的是目的地址与源地址,而在F4时期使用的是外设地址与内存地址。

串口中断

void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&usart1.uart);

    if (__HAL_UART_GET_FLAG(&usart1.uart, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_IDLEFLAG(&usart1.uart);
        usart1.RxCount += (USART1_RX_MAX - __HAL_DMA_GET_COUNTER(&usart1.dmarx));
        HAL_UART_AbortReceive_IT(&usart1.uart);
    }
}

每当空闲中断触发的时候,就将每次的接收数量存入相应的计数变量。通过HAL_UART_AbortReceive_IT()进行软件FIFO的存储读取等等操作

Printf

void Usart1_printf(char *format,...)
{
    uint8_t tempbuff[256];
    uint16_t i;
    va_list listdata;
    va_start(listdata, format);
    vsprintf((char*)tempbuff, format, listdata);
    va_end(listdata);

    for (i = 0; i <strlen((const char*)tempbuff); i++)
    {
        while(__HAL_UART_GET_FLAG(&usart1.uart, UART_FLAG_TXE) != 1);
        usart1.uart.Instance->TDR = tempbuff[i];
    }

    while(__HAL_UART_GET_FLAG(&usart1.uart, UART_FLAG_TC) != 1);
}

重写了一个属于USART1的普通阻塞Printf函数,在编写时候发现DR寄存器变成了TDR与RDR寄存器,在我一开始上手STM32的时候面对只有一个DR的时候还是楞了一下的,这是便利性的更新

实验现象

通过上面的各种操作,主函数中编写了一个简单的DEMO,通过GetKey()读取按键,按下B1/B2/B3分别点亮LD1/LD2/LD3,串口接收到任意数据都回传并且统计数据数量。实验现象:

image.png

图3 分别按下按键

微信图片_20230812195948.jpg

图4 按键LED实验现象

可以看到LD1/LD2/LD3分别点亮。

image.png

图5 串口接收数据

总结

本次评测虽然很简单的调通了串口按键LED三个交互性的外设,但是在这么一个全新芯片摆在我的面前时还是不免得有些难以面对,还好ST官网的资料非常丰富,加上ST在国内早期的布局,玩转一个最新的平台芯片似乎已经不是一个大问题。并且这还是本人自己比较土的原因,如果直接使用CubeMX进行生成,可能一个下午的功夫就完成了我两天的慢悠悠码代码,下次评测将使用STM32WBA52的特色功能蓝牙,也是之前提过的我第一次接触蓝牙协议栈。

收藏 评论1 发布时间:2023-8-12 22:05

举报

1个回答
y369369 回答时间:2023-8-17 15:18:49
BLE板子有没有做一些无线通信的小玩意9 P  I0 L5 z; w1 k! u
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版