版权声明:本文为博主原创文章,未经博主允许不得转载。
stm32的编程其实就是通过事先定义好的不同类型的指针对内存不同位置(寄存器)的修改,得以进行各种设定。
上面的一大段代码实际上就是对某个寄存器的操控。(这里面USARTx是USART1,tmpreg是之前定义好的变量。出自stm32f4xx_usart.c 255至267行)
这几行代码等价于下面这一行代码。其中“寄存器 &= (类型)~(类型)(十六进制)”是常用的讲寄存器某个位置置零的方法。
“USART1->CR2”是“ *((uint32_t*)(0x40011000+0x10))”。0x40011000是USART1的地址,0x10是CR2这个寄存器的偏移量(手册上有写)。
“寄存器 &= (类型)~(类型)(十六进制)” //将某个位置置零的方法
“寄存器 |= (类型)(十六进制)” // 将某个位置置一的方法
/
因为本人是刚刚开始学习stm32,有些地方不是太清楚。而我的单片机上配的例程是用库函数编写的。让我感觉不适应,像是蒙着一层布。所以我花了一些时间将库函数版本的串口通信尽我所能删减修改成直接对寄存器操作的程序。
这是main.c文件里的一个函数。对于库函数的应用基本上都源于这里。
GPIO_InitTypeDef GPIO_InitStructure; //第21行,定义一个结构体。下面会用对结构体其中变量的赋值修改GPIOA寄存器。
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //第30至37行,对结构体赋值,然后通过GPIO_Init函数将值传递给GPIOA寄存器
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
/* Configure USART Tx as alternate function */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*******************************************************库函数RCC_AHB1PeriphClockCmd***********************************************************************/
打开库函数。
第1094行和第1096行的这两个函数。他们的作用是检测传递参数是否有效,几乎每一个库函数都会用它检测一下。重要的是黑色线框里的代码。
第1097至1104行,使能时钟。也是操作寄存器。
(注意:每个总线(AHB1,AHB2,APB1,APB2)都有各自的使能库函数RCC_AxBxPeriphClockCmd。)
于是上述代码可以再main.c的响应位置替换成
RCC->AHB1ENR |= RCC_AHB1Periph_GPIOA;
RCC_APB2PeriphClockCmd函数同理替换成RCC->APB2ENR |= RCC_APB2Periph_USART1。(每一个模块都要初始化时钟)
/****************************************************************库函数GPIO_PinAFConfig******************************************************************************/
跟其他库函数一样,也用到了assert_param来检测参数。
temp和temp_2是为了下面的数据计算。
temp = ((uint32_t)(GPIO_AF) << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ;
GPIO_PinSource取值为0~15.“((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4))”的作用是将0~15这16个数转换
0 1 2 3 4 5 6 7
8 9 10 11 12 13 14 15
0 4 8 12 16 20 24 28
也就是说0和8转换成0,1和9转换成4,2和10转换成8.。。。。。之所以这样转换,是因为AF有两个寄存器——AFH,AHL。每一个是32位。其中每4位控制一个。
temp的值就是将GPIO_PinSource对应4位组合置为GPIO_AF(0x07)。
例:如果GPIO_PinSource的值是9。则temp = ((uint32_t)(GPIO_AF) << 4,即temp的值变成0x00000070;
///
GPIOx->AFR[GPIO_PinSource >> 0x03] &= ~((uint32_t)0xF << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ;
“GPIOx->AFR[GPIO_PinSource >> 0x03]”的作用是如果GPIO_PinSource的值是0~7,就对AFRL操作,如果是8~15,就对AFRH操作。
这里还用到了常用的清零的方法。“寄存器 &= ~数值”。
最终结果是将寄存器相应的4位组合全部清零。
temp_2 = GPIOx->AFR[GPIO_PinSource >> 0x03] | temp;
GPIOx->AFR[GPIO_PinSource >> 0x03] = temp_2;
其实这部分代码我觉得有点累赘。为什么不写成GPIOx->AFR[GPIO_PinSource>>0x03] |=temp ?求大神指点。
上面已经说过“[GPIO_PinSource >> 0x03]”的作用区分高8位和低8位。这两行代码的意思就是讲已经置零的4位赋值为0111;
至此,GPIO_PinAFConfig函数结束。
根据实际情况,我将main.c中GPIO_PinAFConfig的位置替换成
GPIOA->AFR[1] &= ~0x00000ff0 ;
GPIOA->AFR[1] |= 0x00000770;
/*******************************************************************库函数GPIO_Init*************************************************************************************/
其中变量定义如下
uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;
/
main.c文件中已经将赋值好的结构体传递给这个库函数。
这个函数有一个感觉比较吊的部分。它通过for循环的检测将引脚一个一个赋值。
通常情况下,给一个引脚置高置低要对MODER,OTYPER等寄存器分别操作。而这个库函数是先锁定引脚,这样数据不会被掩盖。比如fun1()是通常情况下的函数,fun2()是先锁定引脚的函数。
例1:fun1(参数1); |例2: fun2(参数1);
fun1(参数2); | fun2(参数2);
在例1里面参数1不会有作用,因为被参数2覆盖了。就相当于MODER=1;MODER=2;最终MODER的值是2。
在例2里面参数1先将0位置一,不影响其他位,然后参数2将1位置一,也是不影响其他位。
因此在main.c文件里就有了两次调用
库函数GPIO_Init的其他代码就是先将引脚在寄存器上对应的特定位置清零,再将结构体所蕴含的数据传送到刚才清零的位置上。
/*****************************************************************************库函数USART_Init**************************************************************************************/
这部分代码跟上面的也都是大同小异。响应位置清零,然后响应位置置一。让我感到费解的是为什么库函数在给寄存器赋值是总是喜欢定义一个变量,然后用变量传值。比如代码中的tmpreg就反复使用。而且由 uint32_t 转化为uint16_t不会数据丢失吗?为什么不直接用uint16_t?
BRR这个寄存器我还没有弄明白。好像是关于波特率的计算。
经过这么一番折腾原本的main.c
改成
/*************************************************************************问题************************************************************************************************/
1 复位部分的知识没有学习。
2 函数重映射。
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART1, (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
{}
return ch;
}
这个函数好像是关于printf的函数重映射。
3 很多寄存器的赋值都会借助于一个中间变量,为什么?这样有助于提高运行效率?(USART1的CR1,CR2,CR3这三个寄存器都用到了tmpreg这个中间变量)
4 在USART1的CR1.CR2.CR3 都是用uint32_t类型的tmpreg而不用uint16_t类型,为什么?用uint16_t类型的变量不行吗?
初学stm32,欢迎指点。