流行IDE
-
Keil
老牌编译器,结合SourceInsight用应该是大众所常用的开发方式吧!
-
IAR
老牌编译器,这个还是以前在学校用MSP430时接触的。一样功能比较强大,特别一点,编译出来的hex比其它编译器要小。
-
sw4stm32
基于Eclipse开发的一个STM32专用IDE,似乎是官网推荐。好处是免费,结合STM32Cube的插件使用,开发STM32程序即为方便。
缺点是资料较少,使用的人群没有Keil、IAR多。
-
Atollic TrueSTUDIO(仅仅测过一下)
仅了解一下,这也是基于Eclipse开发的嵌入式IDE,支持很多MCU,不止STM32一种。分为免费版本和Pro版。有兴趣的童鞋可以详细了解一下。
STM32开发方式
官网提供了两套开发STM32的代码:
-
其一为stdperiph_lib,即我们所说的标准库;
-
其二为STM32CubeMx,这是一个基于HAL库的工具,如下图所示;
这种方式极为简化了STM32的开发,可以通过一些简单的配置,快速的生成代码。而且HAL库兼容了STM32各个系列,使得硬件改版后,软件代码的可移植性大大提高。
当然HAL也有不足之处,那便是过于复杂,看过其代码就知道其复杂性远非其它库所比。
它的复杂性带来的便是系统级的兼容。如你在用到嵌入式系统时,并不需要额外的参数及代码来保证各线程的冲突。HAL内部自带互斥、硬件读写锁。
比较建议的开发组合为:HAL+自己参考标准库写的一些硬件操作函数。下面简略介绍一下HAL库的常用开发方式。
基于HAL库开发
使用STM32CubeMx(或Eclipse的插件)生成工程后,其包含了所有的初始化代码及时钟配置代码。你只需要在工程中添加自己的逻辑代码即可。
如果包含系统(STM32Cube默认使用FreeRTOS),则可以删除src中的freertos.c文件,然后新建一个源码目录users,将freertos中的初始化函数“void MX_FREERTOS_Init()”,在自己的c文件中,重新定义一个即可。
这样做的好处是:以后再调整STM32Cube工程后,只需要删除freertos.c,不用更改一处代码即可正常编译执行。
下面说一说“HAL库的正确使用方式”(这部分基于FreeRTOS,无系统的代码大同小异),这主要包含两部分:
外设初始化
生成的初始化代码有三个函数:
void MX_I2C1_Init(void);
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle);
void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle);
通过这三个函数调用,可以初始化外设以及功耗控制。
读写操作
HAL库提供三种读写方式:一是超时读写;二是中断读写;三是DMA读写。详见“stm32fxxx_hal_i2c.h”。
-
超时读写
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);
-
中断读写
HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Master_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Master_Abort_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress); HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c); HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c);
-
DMA读写
HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);
如何得到返回的读写数据长度呢?这个可以根据I2C_HandleTypeDef结构中的RxXferCount/RxXferSize、TxXferCount/TxXferSize做差得到。每发送或者接收一个数据,Count值会减1,而Size则是你传入的一个长度,Size减去Count即可得到返回的读写数据长度。
中断回调
HAL库不止提供了读写操作函数,还提供了一系列的中断回调。比如发送、接收、错误中断。
void HAL_I2C_EV_IRQHandler(I2C_HandleTypeDef *hi2c);
void HAL_I2C_ER_IRQHandler(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode);
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c);
注意:这些函数均在stm32fxxx_hal_i2c.c中有实现。如果需要某个中断回调,则需要查看这个文件中的函数是否有__weak修饰。有__weak修饰的函数表示这是一个虚拟函数,你可以在外部重新定义其实现。如果没有__weak修饰,如HAL_I2C_EV_IRQHandler,则表示这个函数被HAL库实现。
总结
总得来说,STM32开发建议使用如下方式:sw4stm32IDE+STM32Cube的Eclipse插件进行开发。如果不能满足需求,则读写、中断可以从标准库中拷贝过来,自己实现。
简单说就是:
- 使用STM32Cube的函数进行时钟和外设的初始化及其使能控制。
- 使用从标准库移植的代码实现外设的读写、中断操作。
由于HAL库和标准库有部分冲突,所以两个库并不能直接在一个工程中使用。除非修改库的代码。