1.从GPIO到UART
在前几节中,我们介绍了MCU的启动过程,如何使用GPIO引脚翻转以及如何使用按键触发中断。接下来,我们将介绍最常用的模块之一:串口(UART)。
串口可以说是最古老且最具生命力的通信接口之一。RS485总线更是经受住了时间的考验。尽管串口已经从大多数个人电脑的标配中消失了,但在嵌入式系统中,与上位机通信最常用的方式应该还是通过串口转USB。
我们使用Keil打开以下工程:
STM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
这段代码将串口配置为9600波特率,8位数据位,无校验位,1位停止位。我们将代码编译并下载到MCU上,然后使用串口转USB连接到PC的USB口,在PC端使用串口监视工具来观察MCU发送的数据。
2.UART 的初始化
我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
HAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
HAL库里面对应每个硬件模块有两个函数 例如:
HAL_UART_Init( ) 功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
HAL_UART_MspInit( ) 硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。
3.熟悉 Handle
跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:
typedef struct __MY_TypeDef
{
uint8_t Var1;
uint8_t Var2;
uint16_t Var3;
uint8_t* Var4;
}MY_TypeDef;
MY_TypeDef* MY_VAR;
MY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?
MY_VAR = (MY_TypeDef*) 0x20000018;
需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而
* MY_VAR->Var4 的值是 0x02。
把 MY_VAR 指向另一个地址:
MY_VAR = (MY_TypeDef*) 0x2000001C;
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:
我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:
USART_TypeDef *Instance;
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
UART_InitTypeDef Init;
在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
UART_AdvFeatureInitTypeDef AdvancedInit;
串口扩展功能初始化参数。当前未用到扩展功能。
使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
HAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);
&UartHandle 为 UART1 对应的 Handle 的首地址。
Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。
需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:
GPIO
SYSTICK
NVIC
PWR
RCC
FLASH.
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !