本帖最后由 xiatianyun 于 2018-6-4 09:42 编辑
刚看到有前人发布自己学习中的持续更新贴作为鞭策自己不断学习的方法,我也做个学习笔记,把过程中的点点滴滴写下来。
其实写笔记我一直使用复制粘贴保存在电脑里,但最近我喜欢上了用笔写在笔记本上,觉得更有价值更有效率。
--------------------------
先来谈谈STM32的常识。
ARM公司其实不生产芯片,靠什么赚钱呢?靠授权。ARM研发内核然后授权给各个芯片厂家,靠授权费用赚钱,这个是个好注意。毕竟研发和生产在环节上很不同,一家科技公司很难占据产品的各个环节,占据了也就作茧自缚了,就像昔日的诺基亚。
但毕竟内核只是芯片的一部分,内核之外叫做片上外设,意思就是芯片上的相对于内核而言的外部设备,比如GPIO。
芯片厂家根据内核自己设计相关的片上外设成为单片机,如果没有统一的标准那么各个厂家的芯片就不能通用。
意法半导体公司就是其中一家授权生产ARM的公司,当然意法的产品不只ARM芯片。
芯片内核也是升级换代的,我以前知道的ARM其实是比较早的内核产品。精英版的STM32是比较新的内核,叫Cortex-M3,手机上的叫Core-A,A7、A8之类,是很先进的东西。
微控制器芯片Cortex-M3、M4、M7,一个比一个先进。
-------------------------------
事实上使用汇编编写程序已经变得很难了,51时代就有使用C来设计51程序了。学校里面曾看到高年级同学使用手操器来下载程序,那种是在纸上写好汇编程序然后自己手动通过手操器(不知道叫什么合适)一条条下载到试验机里面的,连个电脑软件都没有。想来那种手操器兼具汇编和烧制的功能。我是学PLC的,我以前也是这么学习PLC的。C语言是比较底层的语言,用来编写单片机程序就理所当然了。单片机的C语言采用的编译器和普通的C语言不同,可以把单片机的C语言看作是C的一个子集,并不是所有C的特性均支持。
要想用C来写单片机程序当然要做些底层封装,可以自己封装,事实上芯片公司提供了标准封装,ST公司提供的STM32库函数就是这样的封装。
------------------------------
Keil公司是另外一家公司,是软件公司,提供开发单片机的工具,从51到ARM均有。
ST公司也有自己的开发工具,为什么我们都用Keil呢?业有所专嘛。
Keil 提供的STM32开发工具似乎换了个名字,叫MDK,不过装好后还是叫Keil。
Keil不是免费的,以前很难找到破解的Keil,现在我们使用Keil,所以我们都懂了。
Keil是个IDE工具,除了编辑程序外还提供编译器,并且通过简单选择就配置好了开发链路,不用自己配置,类似与其他专有语言开发工具,比如微软的VS。
换句话说如果开发者要自己配置开发链路的话也是可以的,只要够专业。通过查看,安装好的Keil在目录下的ARMARMCCin里就是编译工具,其他 目录里面提供单片机的include、lib。
为什么还要从ST官网下载库呢?官网的比较新嘛。Keil提供的库我想也是ST提供的,不过可能不会同步更新。
Keil的编辑器不好用,不过keil提供从其他编辑器来操作的可定制菜单,看来Keil还是知道自己的短板的。
现在比较不错的编辑器有gvim/sublime text/vs code,不过这些都是普通编辑器,如果要在开发中实现跳转、自动完成的话需要插件,这些都是学习成本,就看值不值了。
补充:
其实开发STM32除了在windows平台外还有Linux平台和Mac平台也可以,这对于极客来说是个好消息。具体看ST公司提供的开发套件,从我了解的信息看是集成在Eclipse的开发环境。
----------------------------------
学习STM32除了软件外其实很重要的还有硬件,电子工程这类。我学习STM32纯属偶然,我在PLC设计中需要使用一块带Modbus RTU通讯的中继板来做扩展,这块中继板设置通讯奇偶校验时只能使用固定的无校验,这让我很郁闷,系统中其他站都要设置成无校验才行。所以有了我是不是可以修改下的冲动。电子工程没弄过,这个是个拦路虎,我想我以后如果中断学习了可能是因为这个。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
配置好时钟后就开始执行用户程序了,在用户程序里面要使用外设首先必须让外设挂载的总线上的该设备进行时钟使能,让时钟驱动外设工作。
这是通过寄存器设置来实现的。
RCC一共有10个寄存器,互联型更多。启动时配置时钟当然要是通过这些寄存器来设置的,不过进入main后使能外设时钟就只用到了三个寄存器:RCC_AHBENR、RCC_APB2ENR、RCC_APB1ENR。寄存器同样是封装在结构体里面的:RCC_TypeDef,里面的寄存器名称去掉了RCC_前缀。既然是使用库函数,我们也就不必太关心这些寄存器封装是如何使用的了,只需关心库提供了哪些函数去操作这些寄存器实现所需的功能了。
RCC库函数在stm32f10x_rcc.h和.c中实现。
RCC地址宏定义,在stm32f10x.h中定义:
[mw_shl_code=c,true]#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)#define RCC ((RCC_TypeDef *) RCC_BASE)
#define RCC ((RCC_TypeDef *) RCC_BASE)
[/mw_shl_code]
RCC寄存器原来是在AHB总线地址空间上的,这个AHB总线有些不平常。AHB、APB1、APB2总线上外设使能的函数分别是:
[mw_shl_code=c,true]void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);[/mw_shl_code]
要使某个外设时钟使能,其实就是使RCC_AHBENR、RCC_APB2ENR、RCC_APB1ENR三个寄存器中对应位置1,那么这是如何在函数中实现的呢?首先是定义了这三个寄存器各个位代表的外设的宏,也就是当置1时寄存器的值。以APB2ENR寄存器各个位代表的外设为例说明:
[mw_shl_code=c,true]#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040)
#define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080)
#define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100)
#define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200)
#define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400)
#define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800)
#define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000)
#define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000)
#define RCC_APB2Periph_USART1 ((uint32_t)0x00004000)
#define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000)
#define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000)
#define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000)
#define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000)
#define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000)
#define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000)
#define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000)
[/mw_shl_code]均是当外设位为1而其他位为0时寄存器的值。stm32中文参考手册V10.0中关于APB2ENR寄存器只有低16位有定义,高16位保留,可是,库函数里面却有高16位的几个定义,这是为什么?
而如果需要使能就使APB2ENR寄存器“或”上该值,关闭则是“与”上该值的取反值。
[mw_shl_code=c,true]void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->APB2ENR |= RCC_APB2Periph;
}
else
{
RCC->APB2ENR &= ~RCC_APB2Periph;
}
}
[/mw_shl_code]
------------------------------------------------
断言assert_param():
看似是一个函数,其实是宏,在stm32f10x_conf.h中定义:
[mw_shl_code=c,true]#ifdef USE_FULL_ASSERT
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */
[/mw_shl_code]
断言用于参数检查,如果说传入的参数符合要求那么宏值为(void)0,否则执行assert_failed()函数,这个函数没有实现。如果需要可以实现assert_failed()函数,指出哪个文件的哪行出了错误。
断言一般用在开发阶段,必须定义USE_FULL_ASSERT宏,否则只是得到(void)0,对于C来说就相当于空语句。开发调试结束取消USE_FULL_ASSERT宏。这里需要注意:宏定义不仅仅用于预编译阶段,它甚至可以用于运行阶段。比如在运行阶段才能决定断言里面的值倒地是true还是false,从而决定断言值是(void)0还是执行assert_failed()函数。断言和错误捕捉显然不同。
---------------------------------
来看看都做了什么检查: assert_param(IS_FUNCTIONAL_STATE(NewState));
[mw_shl_code=c,true]#define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE))
[/mw_shl_code]很清楚了,如果说参数STATE为DISABLE或ENABLE则宏值为true,否则为false。
那么 assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph))呢?[mw_shl_code=c,true]#define IS_RCC_APB2_PERIPH(PERIPH) ((((PERIPH) & 0xFFC00002) == 0x00) && ((PERIPH) != 0x00))
[/mw_shl_code]
这个比较诡异!!
---------------------------------------
总结,void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);中的RCC_APB2Periph参数必须是预定义的宏才能保证是有效值。
比如:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
如果需要同时使能多个同总线上的外设就采用“或”.
如何记忆这个函数:RCC_APB2PeriphClockCmd就是“关于RCC时钟的”挂载在APB2总线上的外设时钟使能函数,参数开头也必须是RCC_APB2Periph_开头。
一周热门 更多>