还是原来的风格,找个例子来玩,这回要玩的东西在这里(见下图)。
但是这次的printf这个例子有点不一样,它依赖于ST自己的EV板子,所以要用到的东西多一些了。除了上图所示的文件以外,还要把
这里的stm32_eval.c,stm32_eval.h文件,以及图中所示三个文件夹中任意一个文件夹中的部分文件复制到第一个图所示的文件夹中去,这里我们选择stm3210e_eval这个文件夹。
需要复制的文件是stm3210e_eval.h
如同前面一样建立工程,并且注意修改stm32_eval.h的内容
将//#define USE_STM3210E_EVAL 前的#去掉。
这样,就可以编译并通过文件,用软件仿真,在usart #1窗口显示出
USART Printf Example: retarget the C library printf function to the USART
这样一行字了。
显然,对这样的玩法不令人满意,下面试着去掉与stm32e_eval等相关文件,把这里面需要用到的函数直接复制到main中去,同时,也了解一些串口设置的知识。
学到这里,多少有点明白了,STM提供的库为了达到通用性的要求,用了很多的符号来替代常量,然后七转八拐,有时不知要转多少个弯才能找到最终对寄存器操作的代码。这时,keil提供的符号浏览就很有用处了。方法是在将光标移到需要查看的符号上面,按下F12即可,通常可以直接跳转到所需查看到的符号的出处。如下图
将光标移到USART_BaudRate处,按下F12,即跳转到stm32f10x_uart.h文件中相应的定义处:
如果stm32f10x_uart.h文件没有打开,那么这个动作会自动打开这个文件。
下面我们将eval板相关的函数复制到main函数中,以便丢掉与eval板相关的文件。
(1)打开stm32_eval.h文件,将
typedef enum
{
COM1 = 0,
COM2 = 1
} COM_TypeDef;
复制到main.c中,这是用来选择哪一个串口的,因为我的板子上也有2个串口,所以就把它复制过来,也省得对函数作较大的修改了。
(2)打开stm32_eval.c文件,有一个
void STM_EVAL_COMInit(COM_TypeDef COM, USART_InitTypeDef* USART_InitStruct)
的函数,是用来初始化端口的,我们把它复制到main.c中,并且把它改名为
void STM_COMInit(COM_TypeDef COM, USART_InitTypeDef* USART_InitStruct)
去掉中间的eval。
当然,在main函数中调用这个函数的地方也要做相应的修改。
这个函数中用到了如上图中蓝色框中的一些符号,又是一系列的转换,用刚才所说的跟踪方法,找到这些符号的原始出处,作出修改,最后得到的STM_COMInit函数如下:
void STM_COMInit(COM_TypeDef COM, USART_InitTypeDef* USART_InitStruct)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 打开UART所用到的GPIO引脚的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
/* 打开UART的时钟*/
if (COM == COM1)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
}
else //COM=COM2
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
}
/* 配置TX引脚为推挽式输出 */
if(COM==COM1)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
else
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置RX引脚为浮动输入(高阻?) */
if(COM==COM1)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;
else
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 串行口配置*/
if(COM==COM1)
USART_Init(USART1, USART_InitStruct);
else
USART_Init(USART2, USART_InitStruct);
/* 串口允许*/
if(COM==COM1)
USART_Cmd(USART1, ENABLE);
else
USART_Cmd(USART2, ENABLE);
}
至此,修改基本结束,在工程中移去stm32_eval相关的各个文件,在APP文件夹中将这些文件删除,关闭工程,再重新打开工程,编译通过,运行通过。
下面对上述初始化工作做一些解读,当然,少不了要数据手册的帮忙了。
(1)UART1的时钟来源和其他串口的时钟来源不同,UART1的时钟来源是:APB2,其他串口的时钟来源:APB1。
(2)用于UART通信的引脚不会自动配置,需要手工配置。其中用于输出信号的引脚TX必须配置成为推挽式输出,而RX引脚则配置成浮动型输入。
(3)串口波特率、停止位等参数由库提供的stm32f10x_usart.c中的
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
函数来设定。
观察这个函数的执行,可以看到函数通过对CR2寄存器的操作来设定停止位,如下图蓝色框中所示。
通过对CR1寄存器的设定来确定数据位/奇偶校验位等,这些都只需要找到相应的符号,就能顺利地进行设置,找到符号的方法,当然还是上面的按F12浏览的方法。
还有一个重要的工作是波特率的计算,且看这里是如何来做的。
下面这一段是波特率设置的代码
首先根据usartxbase的值来确定需要配置的是USART1还是USART2
usartxbase = (uint32_t)USARTx;
而USARTx是传入这个函数的一个参数。
然后据此来得到用于USART的时钟频率,这个频率值被变量apbclock记录。
从上面变量的跟踪可以看到apbclock的值是0x44aa200即72000000,也就是72MHz。
接下来的一系列计算式就是根据波特率的值来计算应该传入BRR寄存器的值了,偷点懒,这里就不对算式进行一一分析了(我认为暂时没有这个必要)。
至此,USART的设置工作完成,即完成了其数据位、停止位、奇偶校验位、波特率的设置工作。异步通信的配置工作完成。当然,细细分析,可以发现,初始还按默认方式处理了硬件握手等的处理工作。
除了使用库函数提供的printf等函数外,我们在单片机开发中还经常使用直接对数据寄存器赋值的方法来使用串口。STM32串口的数据寄存器名为DR,因此,我试着在main函数中写入这样一行:
While1()
{ USART1->DR=0x55;
}
一试成功,软件仿真时,在串行窗口出现了大串的字符55.
好了,串口暂时告一段落。
用户425423 2012-9-11 20:59