前言
为了开发出更好的软件以及避免出现不可预知的BUG,除了在DEBUG的时候查看各种寄存器,更直观的应该是通过开发板上的交互性设备得知芯片的运行状态,得到开发板之后应该是赶紧调通屏幕串口按键LED这类外设。因为本人希望能理解芯片的运行,所以更喜欢采用自建工程的方式进行项目的开发,虽然CubeMX非常方便,也不需要查阅各种手册就能直接生成芯片的驱动代码,但是在初学上难免显得有点浅薄,难以体现某些外设的执行方式以及各种芯片上的不同之处。所以这次评测使用自建工程的方式调通开发板上的交互外设。
PS:
纠正一个错误,上次评测中只是粗略过了一遍开发板的使用手册,并没有细致的研究,错误的把LPUART与STLINK相连,应该是常规的USART1与STLINK相连,不过官方还是给出了一种解决方案。如果需要LPUART与STLINK相连,必须将开发板上的SB7与SB8触点分别接通,然后再更新STLINK的固件就完成。
图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等说明文档
图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,串口接收到任意数据都回传并且统计数据数量。实验现象:
图3 分别按下按键
图4 按键LED实验现象
可以看到LD1/LD2/LD3分别点亮。
图5 串口接收数据
总结
本次评测虽然很简单的调通了串口按键LED三个交互性的外设,但是在这么一个全新芯片摆在我的面前时还是不免得有些难以面对,还好ST官网的资料非常丰富,加上ST在国内早期的布局,玩转一个最新的平台芯片似乎已经不是一个大问题。并且这还是本人自己比较土的原因,如果直接使用CubeMX进行生成,可能一个下午的功夫就完成了我两天的慢悠悠码代码,下次评测将使用STM32WBA52的特色功能蓝牙,也是之前提过的我第一次接触蓝牙协议栈。