您的位置:网站首页 > 电器维修资料网 > 正文 >
STM32单片机基础学习:从勉强看懂一行程序到IO口研究
来源: 日期:2013-11-29 9:19:06 人气:标签:
刚好勉勉强强看懂一行程序
继续学习中,先把开发板自带一个例子做了些精简,以免看得吓人。。。。
就是这个,让portd上接的4个led分别点亮。
开始研究代码
int main(void)
{
init_all_periph();
。。.。。.
看到这一行,开始跟踪,于是又看到了下面的内容
void init_all_periph(void)
{
rcc_configuration();
。。.。。.
继续跟踪
void rcc_configuration(void)
{
systeminit();
。。.。。.
这行代码在system_stm32f10x.c中找到了。
void systeminit (void)
{
/* reset the rcc clock configuration to the default reset state(for debug purpose) */
/* set hsion bit */
rcc-》cr |= (uint32_t)0x00000001;
/* reset sw, hpre, ppre1, ppre2, adcpre and mco bits */
#ifndef stm32f10x_cl
rcc-》cfgr &= (uint32_t)0xf8ff0000;
#else
rcc-》cfgr &= (uint32_t)0xf0ff0000;
#endif /* stm32f10x_cl */
/* reset hseon, csson and pllon bits */
rcc-》cr &= (uint32_t)0xfef6ffff;
/* reset hsebyp bit */
rcc-》cr &= (uint32_t)0xfffbffff;
/* reset pllsrc, pllxtpre, pllmul and usbpre/otgfspre bits */
rcc-》cfgr &= (uint32_t)0xff80ffff;
#ifndef stm32f10x_cl
/* disable all interrupts and clear pending bits */
rcc-》cir = 0x009f0000;
#else
/* reset pll2on and pll3on bits */
rcc-》cr &= (uint32_t)0xebffffff;
/* disable all interrupts and clear pending bits */
rcc-》cir = 0x00ff0000;
/* reset cfgr2 register */
rcc-》cfgr2 = 0x00000000;
#endif /* stm32f10x_cl */
/* configure the system clock frequency, hclk, pclk2 and pclk1 prescalers */
/* configure the flash latency cycles and enable prefetch buffer */
setsysclock();
}
这一长串的又是什么,如何来用呢?看来,偷懒是不成的了,只能回过头去研究stm32的时钟构成了。
相当的复杂。
系统的时钟可以有3个来源:内部时钟hsi,外部时钟hse,或者pll(锁相环模块)的输出。它们由rcc_cfgr寄存器中的sw来选择。
sw(1:0):系统时钟切换
由软件置’1’或清’0’来选择系统时钟源。 在从停止或待机模式中返回时或直接或间接作为系统时钟的hse出现故障时,由硬件强制选择hsi作为系统时钟(如果时钟安全系统已经启动)
00:hsi作为系统时钟;
01:hse作为系统时钟;
10:pll输出作为系统时钟;
11:不可用。
////////////////////////////////////////////////////////////////////
pll的输出直接送到usb模块,经过适当的分频后得到48m的频率供usb模块使用。
系统时钟的一路被直接送到i2s模块;另一路经过ahb分频后送出,送往各个系统,其中直接送往sdi,fmsc,ahb总线;8分频后作为系统定时器时钟;经过apb1分频分别控制plk1、定时器tim2~tim7;经过apb2分频分别控制plk2、定时器tim1~tim8、再经分频控制adc;
由此可知,stm32f10x芯片的时钟比之于51、avr、pic等8位机要复杂复多,因此,我们立足于对着芯片手册来解读程序,力求知道这些程序代码如何使用,为何这么样使用,如果自己要改,可以修改哪些部分,以便自己使用时可以得心应手。
单步执行,看一看哪些代码被执行了。
/* reset the rcc clock configuration to the default reset state(for debug purpose) */
/* set hsion bit */
rcc-》cr |= (uint32_t)0x00000001;
这是rcc_cr寄存器,由图可见,hsion是其bit 0位。
hsion:内部高速时钟使能
由软件置’1’或清零。
当从待机和停止模式返回或用作系统时钟的外部4-25mhz时钟发生故障时,该位由硬件置’1’来启动内部8mhz的rc振荡器。当内部8mhz时钟被直接或间接地用作或被选择将要作为系统时钟时,该位不能被清零。
0:内部8mhz时钟关闭;
1:内部8mhz时钟开启。
///////////////////////////////////////////////////////////////////////
/* reset sw, hpre, ppre1, ppre2, adcpre and mco bits */
#ifndef stm32f10x_cl
rcc-》cfgr &= (uint32_t)0xf8ff0000;
这是rcc_cfgr寄存器
该行程序清零了mc0[2:0]这三位,和adcpre[1:0],ppre2[2:0],ppre1[2:0],hpre[3:0],sws[1:0]和sw[1:0]这16位。
/*
mco: 微控制器时钟输出,由软件置’1’或清零。
0xx:没有时钟输出;
100:系统时钟(sysclk)输出;
101:内部8mhz的rc振荡器时钟输出;
110:外部4-25mhz振荡器时钟输出;
111:pll时钟2分频后输出。
*/
/* reset hseon, csson and pllon bits */
rcc-》cr &= (uint32_t)0xfef6ffff;
清零了pllon,hsebyp,hserdy这3位。
/* reset hsebyp bit */
rcc-》cr &= (uint32_t)0xfffbffff;
清零了hsebyp位 ///???为什么不一次写??
hsebyp:外部高速时钟旁路,在调试模式下由软件置’1’或清零来旁路外部晶体振荡器。只有在外部4-25mhz振荡器关闭的情况下,才能写入该位。
0:外部4-25mhz振荡器没有旁路;
1:外部4-25mhz外部晶体振荡器被旁路。
所以要先清hseon位,再清该位。
/* reset pllsrc, pllxtpre, pllmul and usbpre/otgfspre bits */
rcc-》cfgr &= (uint32_t)0xff80ffff;
清零了:usbpre,pllmul,pllxtpr,pllsrc共7位
/* disable all interrupts and clear pending bits */
rcc-》cir = 0x009f0000;
////这个暂不解读
setsysclock();
跟踪进入该函数,可见一连串的条件编译:
单步运行,执行的是:
#elif defined sysclk_freq_72mhz
setsysclockto72();
为何执行该行呢,找到sysclk_preq_**的相关定义,如下图所示。
这样就得到了我们所要的一个结论:如果要更改系统工作频率,只需要在这里更改就可以了。
可以继续跟踪进入这个函数来观察如何将工作频率设定为72mhz的。
static void setsysclockto72(void)
{
__io uint32_t startupcounter = 0, hsestatus = 0;
/* sysclk, hclk, pclk2 and pclk1 configuration ---------------------------*/
/* enable hse */
rcc-》cr |= ((uint32_t)rcc_cr_hseon);
//开启hse
/* wait till hse is ready and if time out is reached exit */
do
{
hsestatus = rcc-》cr & rcc_cr_hserdy;
startupcounter++;
} while((hsestatus == 0) && (startupcounter != hsestartup_timeout));
//等待hse确实可用,这有个标志,即rcc_cr寄存器中的hserdy位(bit 17),这个等待不会无限长,有个超时策略,即每循环一次计数器加1,如果计数的次数超过hsestartup_timeout,就退出循环,而这个hsestartup_timeout在stm32f10x.h中定义,
#define hsestartup_timeout ((uint16_t)0x0500) /*!《 time out for hse start up */
///////////////////////////////////////////////////////////////////////////////////////////////
if ((rcc-》cr & rcc_cr_hserdy) != reset)
{
hsestatus = (uint32_t)0x01;
}
else
{
hsestatus = (uint32_t)0x00;
}
///再次判断hserdy标志位,并据此给hsestatus变量赋值。
if (hsestatus == (uint32_t)0x01)
{
/* enable prefetch buffer */
flash-》acr |= flash_acr_prftbe;
/* flash 2 wait state */
flash-》acr &= (uint32_t)((uint32_t)~flash_acr_latency);
flash-》acr |= (uint32_t)flash_acr_latency_2;
/* hclk = sysclk */
rcc-》cfgr |= (uint32_t)rcc_cfgr_hpre_div1;
//找到定义: #define rcc_cfgr_hpre_div1 ((uint32_t)0x00000000) /*!《 sysclk not divided */
/* pclk2 = hclk */
rcc-》cfgr |= (uint32_t)rcc_cfgr_ppre2_div1;
//找到定义:#define rcc_cfgr_ppre2_div1 ((uint32_t)0x00000000) /*!《 hclk not divided */
/* pclk1 = hclk */
rcc-》cfgr |= (uint32_t)rcc_cfgr_ppre1_div2;
//找到定义:#define rcc_cfgr_ppre1_div2 ((uint32_t)0x00000400) /*!《 hclk divided by 2 */
#ifdef stm32f10x_cl
……
#else
/* pll configuration: pllclk = hse * 9 = 72 mhz */
rcc-》cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_pllsrc | rcc_cfgr_pllxtpre |
rcc_cfgr_pllmull));
rcc-》cfgr |= (uint32_t)(rcc_cfgr_pllsrc_hse | rcc_cfgr_pllmull9);
#endif /* stm32f10x_cl */
//以上是设定pll的倍频系数为9,也就是说,这个72m是在外部晶振为8m时得到的。
/* enable pll */
rcc-》cr |= rcc_cr_pllon;
/* wait till pll is ready */
while((rcc-》cr & rcc_cr_pllrdy) == 0)
{
}
/* select pll as system clock source */
rcc-》cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_sw));
rcc-》cfgr |= (uint32_t)rcc_cfgr_sw_pll;
/* wait till pll is used as system clock source */
while ((rcc-》cfgr & (uint32_t)rcc_cfgr_sws) != (uint32_t)0x08)
{
}
}
else
{ /* if hse fails to start-up, the application will have wrong clock
configuration. user can add here some code to deal with this error */
/* go to infinite loop */
while (1)
{
}
}
}
至此,我们可以归纳几条:
(1) 时钟源有3个
(2) 开机时默认是hsi起作用,可以配置为所要求的任意一个时钟
(3) 配置时必须按一定的顺序来打开或都关闭一些位,并且各时钟起作用有一定的时间,因此要利用芯片内部的标志位来判断是否可以执行下一步。
(4) 如果外部时钟、pll输出失效,系统可以自动回复到hsi(开启时钟安全系统)
(5) hsi的频率准确度可以达到+/- 1%,如果有必要时,还可以用程序来调整这个频率,可调的范围大致在200khz左右。
后让我们来感受一下劳动的果实吧--试着改改频率看有何反应。
为查看更改后的效果,先记录更改前的数据。将调试切换到仿真,在第一条:
delay(0xaffff);
指令执行前后,分别记录下status和sec
status:2507 3606995
sec:0.00022749 0.05028982
将振荡频率更改为36mhz,即
。。.
#define sysclk_freq_36mhz 36000000 //去掉该行的注释
/* #define sysclk_freq_48mhz 48000000 */
/* #define sysclk_freq_56mhz 56000000 */
/*#define sysclk_freq_72mhz 72000000*/ //将该行加上注释
再次运行,结果如下:
status:2506 3606994
sec:0.00008478 0.10036276
基本上是延时时间长了一倍。改成硬件仿真,将代码写入板子,可以看到led闪烁的频率明显变慢了。
io研究
前面的例子研究了时钟,接下来就来了解一下引脚的情况
main.c中,有关i/o口的配置代码如下:
void gpio_configuration(void)
{
gpio_inittypedef gpio_initstructure;
/* configure io connected to ld1, ld2, ld3 and ld4 leds *********************/
gpio_initstructure.gpio_pin = gpio_pin_8 | gpio_pin_9 | gpio_pin_10 | gpio_pin_11;
gpio_initstructure.gpio_mode = gpio_mode_out_pp;
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_init(gpiod, &gpio_initstructure);
这几行代码是将gpiod的第8,9,10和11引脚配置成输出,并且还可以设定输出引脚的速度(驱动能力?),这里设定为 50mhz,这应该是常用的,还有可以设置为2mhz的。那么如何将引脚设置成输入呢?查看电路原理图,gpiod.0~gpio.4是接一个摇杆的5个按钮的,因此,下面尝试着将它们设置成为输入端。
gpio_initstructure.gpio_pin=gpio_pin_0|gpio_pin_1|gpio_pin_2|gpio_pin_3|gpio_pin_4;
gpio_initstructure.gpio_mode = gpio_mode_in_floating;
gpio_init(gpiod, &gpio_initstructure);
第1行和第3行完全是照抄,第2行那个gpio_mode_in_floating是在stm32f10x_gpio.h中找到的。
当然是因为这里还有gpio_mode_out_pp,所以猜测应该是它了。至于还有其他那么多的符号就不管了。
定义完成,编译完全通过,那就接下来准备完成下面的代码了。
int main(void)
{
init_all_periph();
while(1)
{ if( gpio_readinputdatabit(gpiod,gpio_pin_0)) //1
{ gpio_resetbits(gpiod, gpio_pin_8);
}
else
{ /* turn on ld1 */
gpio_setbits(gpiod, gpio_pin_8);
/* insert delay */
}
。。.。。.
标号为1的行显然其作用是判断gpiod.0引脚是0还是1。这个函数是在stm32f10x_gpio.c中找到的。
uint8_t gpio_readinputdatabit(gpio_typedef* gpiox, uint16_t gpio_pin)
{
uint8_t bitstatus = 0x00;
/* check the parameters */
assert_param(is_gpio_all_periph(gpiox));
assert_param(is_get_gpio_pin(gpio_pin));
if ((gpiox-》idr & gpio_pin) != (uint32_t)bit_reset)
{
bitstatus = (uint8_t)bit_set;
}
else
{
bitstatus = (uint8_t)bit_reset;
}
return bitstatus;
}
虽然程序还有很多符号看不懂(没有去查),但凭感觉它应该是对某一个引脚的状态进行判断,因为这个函数的类型是uint8_t,估计stm32没有bit型函数(需要验证),所以就用了uint8_t型了),如果是读的端口的值,应该用uint16_t型。这一点在下面也可以得到部分的验证:
uint16_t gpio_readinputdata(gpio_typedef* gpiox)
uint16_t gpio_readoutputdata(gpio_typedef* gpiox)
这些函数是读引脚及输出寄存器的数据的。
再次编译,也是顺利通过,依法炮制,将其他三个引脚输入控制led的代码也写上,为保险起见,先用软件仿真,免得反复擦写flash(顺便说一句,目前还没有搞定将代码写入ram及从ram中执行)
进入仿真后打开外围部件接口,单步执行,果然如同设想那样运作了,单击pins 0后面的勾,再次运行,果然pin8后面的勾没了。做到这里,就感觉到用keil的好处了,这块熟啊,几乎没有花时间在上面,一用就成了。
- 1
- 2
- 下一页
【看看这篇文章在百度的收录情况】
相关文章
- 上一篇: 如何进行开关电器介质损耗的测量?
- 下一篇: 贴片多层陶瓷电容器