当前位置:首页 > STM32学习笔记 原创
三、USART1通信实验
明显,在使用USART1前要先对其进行初始化,见以下代码:
void USART1_Config(void) { /* USART1 GPIO config */ /* Configure USART1 Tx (PA.09) as alternate function push-pull */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure USART1 Rx (PA.10) as input floating */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); } /* USART1 mode config */ USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No ; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); /* config USART1 clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; USART_Cmd(USART1, ENABLE); 明显,因为USART的管脚是对GPIO脚的复用,首先要对GPIO进行配置。配置过程和LED流水灯实验相同,只是这里的GPIO的模式不同罢了。查手册可知,USART1采用PA9作UART的TX,PA10作UART的RX。PA9采用推挽式复用模式,PA10采用浮空输入模式,为什么是采用这两种模式还不清楚,速度仍然用50MHz。
然后是对USART作配置。同GPIO配置,在使用初始化库函数USART_Init()前,要建立一个配置结构体,其成员包括串口通信的多项参数包括波特率、停止位、奇偶校验位等,见代码。另外,也可以调用库函数USART_StructInit()进行USART的快速配置。
将GPIO和USART都配置好后,就是调用库函数USART_Cmd()来开启USARTx了,其参数就是要设置的USART号和ENABLE/DISABLE。至此,USART就完全配置好了,接下来就是通过USART_SendData()库函数发送字符了。
本实验通过自己定义的USART1_printf()函数从而实现了stdio.h中的printf的功能,只是打印对象转移到了超级终端上,具体实现可看代码。当然,结合库函数USART_SendData()与等待发送完成的语句:
while (!(USART1->SR & USART_FLAG_TXE)); 即可写出简单的字串发送函数。USART1->SR为USART1的状态寄存器,其USART_FLAG_TXE位意即:当TDR寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。此位可用来判断发送是否OK。此外,也可以用库函数USART_GetFlagStatus()来获取发送是否完成的信息。
需要注意的是,虽然STM32的手册上提到的USART的寄存器没有提到USARTx之分(这与CC2430的参考手册不同),但实际上从USART1到USART3的每一个USART都拥有各自的一组完整的寄存器。这点可以参看stm32f10x.h。以下是其中的几个片段:
#define PERIPH_BASE ((uint32_t)0x40000000) #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000) ?? #define USART2_BASE (APB1PERIPH_BASE + 0x4400) #define USART3_BASE (APB1PERIPH_BASE + 0x4800) #define UART4_BASE (APB1PERIPH_BASE + 0x4C00) #define UART5_BASE (APB1PERIPH_BASE + 0x5000) ?? #define USART1_BASE (APB2PERIPH_BASE + 0x3800) #define ADC3_BASE (APB2PERIPH_BASE + 0x3C00) 从中可看出USARTx所在的位置都是不同的,它们各自有各自的内存区域,各自的寄存器组分布在其中,各寄存器的偏移地址是相同的。同时我们还能看出,仅有USART1是高速时钟外设,而USART2和USART3都是低速时钟的外设。为什么有UART4-UART5,和USARTx有什么关系?
另外,从存储器映像表及USART寄存器映像表中我们也能看到,USARTx拥有各自的基地址,然后各自的寄存器都有相同的偏移地址,这样每个USART都有各自完整的一套寄存器。
关于AHB(先进高性能总线)、APB(先进外设总线)有异同,留到后面再作学习。其各自下辖的外设见中文手册19页。
科普:STM32在数据手册以及头文件中SFR地址查找方式中与16位单片机的不同
也许到这里大家应该发现了STM32在数据手册中对寄存器地址的描述与之前像CC2430或是MSP430的数据手册有所不同,这个不同也反映在了其头文件stm32f10x.h对寄存器地址的映射中。特此在这里说明一下:
首先我们打开中文手册,在阅读器上输入19,Enter,我们可以看到一张寄存器地址映射表。这张表很重要,我们要查所有SFR的地址都首先要经过它。 确定我们要找的SFR属于哪一外设,比如我们要找USART1的DR,则看到这样一行:
这里,0x40013800即为USART1的基址,我们点击“参见??”部分进入。则可看到:
找到DR,可看到其偏移是004h。这是DR在USART1基址下的偏移地址。所以USART1_DR的绝对地址是0x40013804。即二者之和。
经此流程我们可看到STM32将SFR的地址分为了基址与偏移两部分。这同样反映在了SFR映射的头文件里。首先,USART1的基址如下图:
再找到如下结构体。结构体的存储方式相当于给基址加上了偏移地址,从而实现了对其所属的SFR的访问。因为USART1和USART2、USART3内的SFR的分布都一样,所以该结构体归三者统一使用。
正因为采用了结构体,用户在编写代码操作寄存器时语句也和16位单片机编程时有所不同,比如表达USART1的SR为USART1->SR。
科普:关于位带(bit-banding,也叫位段)技术
见手册2.3.2或《CM3权威指南CnR2.pdf》的5.5。位带概念包括位带区(bit-banding region)和别名区(alias region)。简单说,位带区类似于本来的寄存器映射,一个地址内的8位包含了多个位的信息,而别名区相当于是把位带区的每一位对应了4个地址,而每4个地址中只有一个地址对应字节的最后一位是真正有效的。参看下面的对应关系图。将1MB对应到32MB(即1个位对应4字节、1个字而非一个字节)而非8MB的原因,应该和Cortex M3内核的32位总线有关吧,以便进一步提高效率。
因为寄存器操作是按字节来进行的,采用位带技术后在进行位操作时就可以简化本来“读-修改-写”为直接进行修改。而对别名区的修改就和对位带区的对应位进行修改时一样的效果,因此这样就提高了代码效率。
共分享92篇相关文档