介绍
文档目的
本指南旨在帮助您开始使用STM32及其外设。特别说明,本指南重点介绍用于Nucleo STM32L0评估板的STM32L053R8T6。本指南面向对微控制器及其操作有基本了解的用户。
警告:代码和电路图仅供参考
注意:最新代码将在GitHub上发布。完整代码请参见此处,所有代码片段均引用自GitHub版本。
器件描述
ST公司通过其STM32系列微控制器,提供了一种经济灵活的开发与原型设计方式。与竞争对手相比,ST的Nucleo系列评估板极具成本效益。您只需花费Arduino Uno约一半的价格,就能使用ST的一款具备DSP功能的32位微控制器!Nucleo板还通过采用Arduino外形尺寸及其独有的"Morpho连接器",提供了灵活的原型设计方式。这意味着您可以轻松使用为Arduino设计的扩展板进行原型开发,同时仍可通过Morpho连接器完全访问所有微控制器引脚。Nucleo板上还集成了ST-LINK/V2-1调试器和编程器。
使用的硬件
开发过程中使用的硬件如下所示。如需订购部件,只需点击名称即可跳转至DigiKey部件页面。
设备 | 部件编号 | 描述 | 图片 | |
---|---|---|---|---|
STMicroelectronics NUCLEO-L053R8 | 497-14710-ND | STM32微控制器评估板 | ||
STMicroelectronics X-NUCLEO-IKS01A1 | 497-15062-ND | ST传感器评估板 | ||
Adafruit Ultimate GPS扩展板 | 1528-1045-ND | Arduino用SD卡及GPS扩展板 | ||
XBee Pro S1无线模块 | XBP24-AWI-001-ND | 无线收发器 |
文档资料:
以下列出我在微控制器开发过程中使用过的最实用数据手册。提供这些资料是为了快速查阅,希望能减少您寻找所需信息对应数据手册的时间。
STM32L053xx Nucleo 开发板文档
这是STM32L053R8开发相关的实用数据手册与文档清单。
文档标题 | 描述 |
---|---|
NUCLEO L053R8 原理图 | Nucleo STM32L0评估板电路图 |
STM32L053xx技术数据 | 基础信息、特性参数及功能描述 |
STM32L053xx参考手册 | 寄存器描述与功能说明 |
STM32L053xx入门指南 | 开发板原理图、引脚定位及描述 |
I2C时序配置 | 时序配置工具说明 |
STM32L053xx 运动 MEMS 与环境传感器扩展板文档
这是使用ST生产的部分传感器时的实用数据手册与文档清单。
文档标题 | 描述 |
---|---|
X-Nucleo-ISK01A1用户手册 | 开发板用户手册 |
X-Nucleo-ISK01A1原理图 | 开发板原理图 |
温湿度传感器 | 温湿度传感器数据手册 |
压力传感器 | 压力传感器数据手册 |
磁力计 | 磁力计数据手册 |
加速度计与陀螺仪 | 加速度计与陀螺仪数据手册 |
Adafruit 终极 GPS 记录扩展板文档
以下是使用Adafruit终极GPS扩展板时实用的数据手册和文档列表。
文档标题 | 描述 |
---|---|
Adafruit GPS扩展板原理图 | 终极GPS扩展板原理图 |
FGPMMOPA6B GPS模块数据手册 | 包含GPS基础数据及NMEA报文格式 |
格式与协议 | 详细说明NMEA语句格式 |
PMTK指令集 | 包含发送至GPS模块的PMTK指令 |
XBee 文档
文档标题 | 描述 |
---|---|
XBee Pro使用手册 | 关于XBee您需要了解的一切! |
设置:
开发板设置
警告:若NUCLEO及其扩展板的总电流消耗超过300mA,必须通过E5V或VIN接口连接外部电源供电。
应首先检查Nucleo开发板上的跳线帽是否满足运行需求。该信息也可在《STM32L053xx入门指南》第8页找到。关于JP1和JP5的电源选项设置,请参阅第16页。下表描述了跳线配置。
跳线名称 | 跳线位置 |
---|---|
JP1 | 无跳线 |
JP5 | 连接PWR与U5V |
JP6 | 添加跳线 |
接下来连接USB线缆,LD1和LD3将亮红灯,表示已准备好编程。若LD3未亮红灯,请参照上方警告并阅读第16页。
程序
与STM32L0 Nucleo开发板交互的程序选择众多,本指南将使用Keil提供的工具。进入Keil页面后选择Keil MDK-ARM。Keil提供的IDE名为µVision,是本指南编程环节的核心工具。本指南编写时使用的是Keil MDK-ARM 5.15版本。
驱动与库
还需提及ST公司开发的STM32CubeMX软件。该软件是STM32系列微控制器C代码初始化的图形界面工具。对配置时钟、GPIO、ADC、UART等外设极为便利。未采用该软件的原因是生成的驱动过于臃肿。其HAL(硬件抽象)驱动提供开发板编程所需的全套功能。若希望继续使用Keil µVision免费版,建议避免使用HAL库。
熟悉STM32微控制器的用户可能会疑惑:L0系列的标准外设库去哪了?很遗憾地告知您——根本没有。生成HAL库的STM32CubeMX是唯一选择。
配置 Keil MDK-ARM 与 ST-Link
在Keil官网注册并完成下载安装后,还需完成几个步骤。
- 在 microVision 环境中:更新 STM32L0 系列的软件包安装(通过菜单路径:项目 → 管理 → 包安装器( Project → Manage → Pack installer ))
a. ARM::CMSIS版本:4.3.0及4.2.0
b. KEIL::MDK中间件版本:6.4.0及6.2.0
c. KEIL:STM32L0xx_DFP版本:1.3.0
d. KEIL::STM32NUCLEO板级支持包版本:1.3.0
- ST-Link 驱动安装
a. 若ST-Link未自动安装,可手动运行以下.bat文件
b. 进入C:\keil_v5\ARM\STLink\USBDriver目录,运行名为stlink_winusb_install.bat的批处理文件 - 目标选项配置
a. 前往项目→目标选项
b. 选择设备型号[设备选项卡]
c. 确保时钟设置为32.0 MHz [目标选项卡]
d. 勾选"使用目标对话框的内存布局"[链接器选项卡]
e. 使用ST-Link调试器,并按图示修改调试设置[调试选项卡]
- 至此所有准备工作已完成,现在可以开始编程。除了本页示例代码外,您还可在µVision的包安装器中下载更多示例程序。
复位与时钟控制 (RCC) 示例
系统核心时钟初始化流程详解
所有寄存器及设置的详细说明可参考STM32L053R8参考手册。本部分指南将介绍系统时钟与外设时钟的配置方法。为直观展示最终系统配置,我使用STM32CubeMx软件呈现相关设置。再次强调,这仅是可视化辅助工具,并未用于生成时钟配置代码。
警告:USART1CLK 与 I2C1CLK 的配置可能不准确。仅 HSI RC 到蓝色外设时钟框的路径是确认正确的
如上图所示,我们将使用16MHz内部时钟并将其倍频至32MHz。为此,首先需将系统时钟设置为内部高速时钟(HSI16),接着配置闪存与电源设置,最后通过PLL利用HSIRC时钟实现32MHz倍频。
步骤 1 :启用 HSI16
只需设置RCC_CR 寄存器中的HSI16ON 位。随后通过监测RCC_CR 寄存器的HSI16RDYF位,等待内部高速时钟就绪。
注意:**设备头文件未采用数据手册的命名方式,但代码逻辑正确。例如,**HSI16ON 对应的寄存器位实际命名为 RCC_CR_HSION 而非预期的 RCC_CR_HSI16ON 。
/* Enable HSI */
RCC->CR |= ((uint32_t)RCC_CR_HSION);
/* Wait for HSI to be ready */
while ((RCC->CR & RCC_CR_HSIRDY) == 0){
// Nop
}
步骤 2 :设置 HSI 为系统时钟
启用HSI后,通过配置RCC_CFGR 寄存器的SW[1:0] 位将其设为系统时钟。等待系统时钟就绪。
/* Set HSI as the System Clock */
RCC->CFGR = RCC_CFGR_SW_HSI;
/* Wait for HSI to be used for teh system clock */
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI){
// Nop
}
步骤 3 :配置 FLASH 与 PWR
FLASH->ACR |= FLASH_ACR_PRFTEN; // Enable Prefetch Buffer
FLASH->ACR |= FLASH_ACR_LATENCY; // Flash 1 wait state
RCC->APB1ENR |= RCC_APB1ENR_PWREN; // Enable the PWR APB1 Clock
PWR->CR = PWR_CR_VOS_0; // Select the Voltage Range 1 (1.8V)
while((PWR->CSR & PWR_CSR_VOSF) != 0); // Wait for Voltage Regulator Ready
步骤 4 :配置 PLL
现在需要配置PLL。首先选择RCC_CFGR 中的PLLSRC 为HSI,设置PLLMUL[3:0] 实现4倍频,最后配置PLLDIV[1:0] 进行2分频。
/* PLLCLK = (HSI * 4)/2 = 32 MHz */
RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMUL | RCC_CFGR_PLLDIV); /* Clear */
RCC->CFGR |= (RCC_CFGR_PLLSRC_HSI | RCC_CFGR_PLLMUL4 | RCC_CFGR_PLLDIV2); /* Set */
步骤 5 :设置外设时钟分频器
所有外设分频系数可保持为1。
/* Peripheral Clock divisors */
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // HCLK = SYSCLK
RCC->CFGR |= RCC_CFGR_PPRE1_DIV1; // PCLK1 = HCLK
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // PCLK2 = HCLK
步骤 6 :设置 PLL 为系统时钟
通过设置RCC_CR 寄存器的PLLON 位启用PLL。检测RCC_CR 的PLLRDY 位确认就绪后,通过SW[1:0] 位将其设为系统时钟。
/* Enable PLL */
RCC->CR &= ~RCC_CR_PLLON; /* Disable PLL */
RCC->CR |= RCC_CR_PLLON; /* Enable PLL */
/* Wait until the PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0){
//Nop
}
/* Select PLL as system Clock */
RCC->CFGR &= ~RCC_CFGR_SW; /* Clear */
RCC->CFGR |= RCC_CFGR_SW_PLL; /* Set */
/* Wait for PLL to become system core clock */
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL){
//Nop
}
系统核心时钟初始化代码:
完整代码详见GitHub。以下示例展示关键操作流程。
Timing.c
/* Enable PLL */
RCC->CR &= ~RCC_CR_PLLON; /* Disable PLL */
RCC->CR |= RCC_CR_PLLON; /* Enable PLL */
/* Wait until the PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0){
//Nop
}
/* Select PLL as system Clock */
RCC->CFGR &= ~RCC_CFGR_SW; /* Clear */
RCC->CFGR |= RCC_CFGR_SW_PLL; /* Set */
/* Wait for PLL to become system core clock */
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL){
//Nop
}
GPIO 配置示例
GPIO 初始化流程详解
所有寄存器及设置的详细说明可参考STM32L053R8参考手册。让我们以绿色板载LED和蓝色用户按钮为例,将PORTA引脚5设为输出,PORTC引脚13设为输入。要确认LED和蓝色用户按钮的位置,请参考NUCLEO L053R8原理图。
步骤 1 :设置时钟
必须首先完成这一步,否则寄存器不会受到所做更改的影响!RCC_IOPENR 用于启用不同的I/O时钟,因此我们可以写入IOPAE N位来启用PORTA的时钟。
RCC->IOPENR |= (1UL << 0); // Enable GPIOA clock
步骤 2 :设置模式(输入 / 输出等)
由于我们以绿色LED为例,需要将PORTA引脚5设为输出引脚。下图中标记的MODE0…MODE15可视为Pin0…Pin15。我们可以通过向GPIOx_MODER 寄存器的MODE5 位写入适当的位序列,将引脚5设为输出。
GPIOA->MODER &= ~((3ul << 2*pin)); // write 00 to pin location
GPIOA->MODER |= ((mode << 2*pin)); // Choose mode
步骤 3 :设置输出类型(推挽 / 开漏)
如果愿意,可以不修改此寄存器,因为其复位状态为推挽模式,这正是我们LED所需的。
- 开漏模式: 输出寄存器中的“0”激活N-MOS,而“1”使端口处于高阻态(P-MOS从不激活)。
- 推挽模式: 输出寄存器中的“0”激活N-MOS,而“1”激活P-MOS。
GPIOA->OTYPER |= (0 << 5); //Output type Push/Pull
步骤 4 :设置速度
我为绿色LED选择了中速。
- 极低速: 400kHz
- 低速: 2MHz
- 中速: 10MHz
- 高速: 40MHz
GPIOC->OSPEEDR |= ((3 << 2*5)); //medium speed
步骤 5 :设置上拉 / 下拉寄存器
如果愿意,可以不修改此寄存器,因为其复位状态为推挽模式,这正是我们LED所需的。请参考步骤3中的图25,查看GPIO引脚及上拉和下拉电阻的示意图。
GPIOC->PUPDR |= (pupd << 2*5); //No pull up pull down
同样的流程可用于设置蓝色用户按钮。
初始化 GPIO 代码
完整代码详见GitHub。
GPIO.c
/**
\fn void GPIO_Init(GPIO_TypeDef* GPIOx, struct GPIO_Parameters GPIO)
\brief Initialize GPIO
\param GPIO_TypeDef* GPIOx: Which port to initialize, i.e. GPIOA,GPIOB
\param struct GPIO_Parameters GPIO: Structure containing all GPIO parameters:
* Pin
* Mode
* Output Type
* Output Speed
* Pull up / Pull down
*/
void GPIO_Init(GPIO_TypeDef* GPIOx, struct GPIO_Parameters GPIO){
/* Enable GPIO Clock depending on port */
if(GPIOx == GPIOA) RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Enable GPIOA clock
if(GPIOx == GPIOB) RCC->IOPENR |= RCC_IOPENR_GPIOBEN; // Enable GPIOB clock
if(GPIOx == GPIOC) RCC->IOPENR |= RCC_IOPENR_GPIOCEN; // Enable GPIOC clock
if(GPIOx == GPIOD) RCC->IOPENR |= RCC_IOPENR_GPIODEN; // Enbale GPIOD clock
/* GPIO Mode Init */
GPIOx->MODER &= ~((3ul << 2*GPIO.Pin)); // write 00 to pin location
GPIOx->MODER |= ((GPIO.Mode << 2*GPIO.Pin)); // Choose mode
/* Output Type Init */
GPIOx->OTYPER &= ~((~(GPIO.OType) << GPIO.Pin));
/* GPIO Speed Init */
GPIOx->OSPEEDR |= ((GPIO.Speed << 2*GPIO.Pin));
/* GPIO PULLUP/PULLDOWN Init */
GPIOx->PUPDR |= (GPIO.PuPd << 2*GPIO.Pin);
}
/**
\fn void Button_Initialize (void)
\brief Initialize User Button
*/
void Button_Initialize(void){
/* Set port parameters */
struct GPIO_Parameters GPIO;
GPIO.Pin = Blue_Button;
GPIO.Mode = Input;
GPIO.OType = Push_Pull;
GPIO.PuPd = No_PuPd;
GPIO.Speed = Low_Speed;
/* Initialize button */
GPIO_Init(GPIOC,GPIO);
}
/**
\fn void LED_Init(void)
\brief Initialize LD2
*/
void LED_Init(void){
/* Set port parameters */
struct GPIO_Parameters GPIO;
GPIO.Pin = Green_LED;
GPIO.Mode = Output;
GPIO.OType = Push_Pull;
GPIO.PuPd = No_PuPd;
GPIO.Speed = High_Speed;
/* Initialize the LED */
GPIO_Init(GPIOA,GPIO);
}
GPIO.h
struct GPIO_Parameters
{
int Pin;
int Mode;
int Speed;
int OType;
int PuPd;
};
typedef enum Mode_Choices
{
Input = 0,
Output = 1,
Alternate_Function = 2,
Analog_Mode = 3
}Mode_Choices;
typedef enum OType_Choices
{
Push_Pull = 0,
Open_Drain = 1
}OType_Choices;
typedef enum Speed_Choices
{
Very_Low_Speed = 0,
Low_Speed = 1,
Medium_Speed = 2,
High_Speed = 3
}Speed_Choices;
typedef enum PuPd_Choices
{
No_PuPd = 0,
Pull_Up = 1,
Pull_Down = 2,
}PuPd_Choices;
设置复用功能分步指南
许多情况下您可能需要使用GPIO复用功能。本指南将演示如何在PORTB引脚8和引脚9上分别配置I2C1_SCL和I2C1_SDA复用功能。
步骤 1 :确定正确的复用功能
查看下方可知,我们需要使用AF4将PB8和PB9分别设置为I2C1_SCL和I2C1_SDA
步骤 2 :向 AFRL 和 AFRH 寄存器写入数值
int SCL = 8; //SCL pin on PORTB alt fnc 4
int SDA = 9; //SDA pin on PORTB alt fnc 4
/*Enable Clock for I2C*/
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
/* Enable GPIO Clock */
RCC->IOPENR |= (1UL << 1);
/** GPIOB Setup
* Alternate Mode PORTB pin 8 and pin 9.............(1)
* Set alternate function 4 for both pin 8 an 9.....(2)
*/
GPIOB->MODER = ~((~GPIOB->MODER) | ((1 << 2*SCL) + (1 << 2*SDA))); /*(1)*/
GPIOB->AFR[1] = 0x00000044; /*(2)*/
关于写入AFR寄存器的注意事项:
/**
* Set AFRL to AF_Value on Pin...(1)
* Set AFRH to AF_Value on Pin...(2)
*/
GPIOB->AFR[0] |= (AF_Value << 4*Pin) /* (1) */
GPIOB->AFR[1] |= (AF_Value << 4*(Pin - 8)) /* (2) */
ADC 示例
所有代码均可在GitHub上获取。
ADC 初始化分步指南
步骤 1 :设置时钟
RCC->APB2ENR |= (1UL << 9);
步骤 2 :启用内置校准
必须确保ADC尚未启用,若已启用则需先禁用。随后设置ADCAL位启动校准过程。等待校准完成,然后通过向ISR寄存器的EOCAL位写入1来清除标志。
/* Calibration Setup */
if((ADC1->CR & ADC_CR_ADEN) != 0){
ADC1->CR &= (uint32_t)(~ADC_CR_ADEN);
}
ADC1->CR |= ADC_CR_ADCAL;
while((ADC1->ISR & ADC_ISR_EOCAL) == 0);
ADC1->ISR |= ADC_ISR_EOCAL;
步骤 3 :启用 ADC
通过CR寄存器的ADEN位启用ADC。随后应检查ISR寄存器的ADRDY位确认ADC就绪。
/* Enable ADC */
ADC1->CR |= (1UL << 0);
/* Wait for ISR bit to set */
while((ADC1->ISR & 1) == 0);
步骤 4 :初始化 ADC 引脚
ADC1->CHSELR |= (1UL << pin);
步骤 5 :配置分辨率、对齐方式和模式
/* Enable Continuous mode */
ADC1->CFGR1 |= (1UL << 13);
/* Right Aligned data in DR register */
ADC1->CFGR1 |= (0UL << 5);
/* 12-Bit Resolution */
ADC1->CFGR1 |= (0Ul << 3);
步骤 6 :启动转换并获取数据
/* Start Conversion */
ADC1->CR |= (1UL << 2);
/* Grab the last 12-Bit Data part */
ADC_Conversion = ADC1->DR & 0x00000FFF;
I2C 示例
I2C 初始化分步指南
所有寄存器及设置的详细说明可参考STM32L053R8参考手册。查阅STM32L053xx技术资料也有助于查找引脚的复用功能。本教程假设您已设置好备用功能。若未完成,请参考《设置备用功能教程》。本教程将分别在PB8引脚配置I2C1_SCL,在PB9引脚配置I2C1_SDA。
步骤 1 :设置时钟
必须配置两个时钟:1.启用GPIO时钟;2.启用I2C时钟
/*Enable Clock for I2C*/
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
/* Enable GPIO Clock */
RCC->IOPENR |= (1UL << 1);
步骤二:配置 I2C 时序参数
最简单的配置方法是使用ST公司提供的I2C时序配置工具。该工具基于Excel运行,可根据您的设置自动生成时钟配置。
/** GPIOB Setup
* Standard Mode @100kHz with I2CCLK = 16MHz, rise time = 100ns, fall time = 10ns.(1)
*/
I2C1->TIMINGR = (uint32_t)0x00503D5A; /*(1)*/
步骤三:启用 I2C
最后一步是启用I2C功能。这必须是I2C初始化的最后步骤。
I2C1->CR1 |= I2C_CR1_PE; //Enable I2C1 peripheral
I2C 初始化代码
完整代码详见GitHub。
注意我已注释掉I2C中断使能,选择采用轮询方式而非中断方式与各类传感器通信。
I2C.c
/**
\fn void I2C_Init(void)
\brief I2C initialization
*PORTB-8: SCL
*PORTB-9: SDA
*Digital Noise filter with supression of 1 I2Cclk
*fast Mode @400kHz with I2CCLK = 16MHz, rise time = 100ns, fall time = 10ns
*/
void I2C_Init(void){
int SCL = 8; //SCL pin on PORTB alt fnc 4
int SDA = 9; //SDA pin on PORTB alt fnc 4
/*Enable Clock for I2C*/
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
/* Enable GPIO Clock */
RCC->IOPENR |= (1UL << 1);
/* Don't forget to also enable which interrupts you want in CR1 */
//interrupt init
// NVIC_EnableIRQ(I2C1_IRQn);
// NVIC_SetPriority(I2C1_IRQn,0);
/** GPIOB Setup
* Digital Noise filter with supression of 1 I2Cclk.(1)
* Alternate Mode PORTB pin 8 and pin 9.............(2)
* Set alternate function 4 for both pin 8 an 9.....(3)
*/
I2C1->CR1 |= (1<<8); /*(1)*/
GPIOB->MODER = ~((~GPIOB->MODER) | ((1 << 2*SCL) + (1 << 2*SDA))); /*(2)*/
GPIOB->AFR[1] = 0x00000044; /*(3)*/
/** GPIOB Setup
* Standard Mode @100kHz with I2CCLK = 16MHz, rise time = 100ns, fall time = 10ns.(1)
* Enable I2C1 peripheral.........................................................(2)
*/
I2C1->TIMINGR = (uint32_t)0x00503D5A; /*(1)*/
I2C1->CR1 |= I2C_CR1_PE; /*(2)*/
}
HTS221 温湿度传感器通信教程
所有寄存器及设置的详细说明可参考《温湿度传感器数据手册》。本节将介绍如何与HTS221温湿度传感器通信、配置传感器,并读取温湿度数据。
步骤一:通信时序
以下I2C通信描述均以主设备(本案例中为STM32L053R8)视角展开。
写入时序可分解为:
- 发送起始信号
- 发送从机地址+读写位(读=1,写=0)
- 接收从机应答(应为0)
- 发送子地址(即目标寄存器地址)
- 接收从机应答(应为0)
- 发送待写入寄存器的数据
- 接收从机应答(应为0)
- 发送停止序列
读取序列可更清晰地描述为:
- 发送起始信号
- 发送从机地址+读写位(读=1,写=0)
- 接收从机应答(应为0)
- 发送子地址,指定要写入的寄存器
- 接收从机应答(应为0)
- 再次发送起始序列
- 发送从机地址+读写位(读=1,写=0)
- 接收从机应答(应为0)
- 从子地址接收数据
- 发送无主设备确认信号
- 发送停止序列
以下通信序列展示了以STM32L053R8作为主设备、HTS221作为从设备通过I2C进行读取的过程。
以下是读取寄存器时步骤1-3 的示例。
该序列可分解为:
起始条件 | 从设备地址 | 读 / 写 | 从设备确认 |
---|---|---|---|
高电平到低电平转换 | 1011111 | 0 | 0 |
注:从设备发送的是NACK,实际上表示"非未确认"状态。
以下是步骤4 和 5 的示例。
该序列可分解为:
子地址 | 从设备确认 |
---|---|
00001111 | 0 |
以下是从设备步骤6-10 接收数据的示例。
该序列可分解为:
重启条件 | 从设备地址 | 读 / 写 | 从设备确认 | 子地址 | Nack (主设备) |
---|---|---|---|---|---|
高电平到低电平转换 | 1011111 | 1 | 0 | 10111100 | 1 |
以下是停止序列步骤****11 的示例。
步骤 2 :通信序列代码
完整代码详见GitHub。
I2C.c
/**
\fn uint32_t I2C_Read_Reg(uint32_t Register)
\brief Reads a register, the entire sequence to read (at least for HTS221)
\param uint32_t Device: The slave address of the device
\param uint32_t Register: The Register to read from
\returns uint32_t I2C1_RX_Data: The data read
*/
uint32_t I2C_Read_Reg(uint32_t Device,uint32_t Register){
//Reset CR2 Register
I2C1->CR2 = 0x00000000;
//Check to see if the bus is busy
while((I2C1->ISR & I2C_ISR_BUSY) == I2C_ISR_BUSY);
//Set CR2 for 1-byte transfer for Device
I2C1->CR2 |=(1UL<<16) | (Device<<1);
//Start communication
I2C1->CR2 |= I2C_CR2_START;
//Check Tx empty before writing to it
if((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE)){
I2C1->TXDR = Register;
}
//Wait for transfer to complete
while((I2C1->ISR & I2C_ISR_TC) == 0);
//Clear CR2 for new configuration
I2C1->CR2 = 0x00000000;
//Set CR2 for 1-byte transfer, in read mode for Device
I2C1->CR2 |= (1UL<<16) | I2C_CR2_RD_WRN | (Device<<1);
//Start communication
I2C1->CR2 |= I2C_CR2_START;
//Wait for transfer to complete
while((I2C1->ISR & I2C_ISR_TC) == 0);
//Send Stop Condition
I2C1->CR2 |= I2C_CR2_STOP;
//Check to see if the bus is busy
while((I2C1->ISR & I2C_ISR_BUSY) == I2C_ISR_BUSY);
//Clear Stop bit flag
I2C1->ICR |= I2C_ICR_STOPCF;
return(I2C1_RX_Data);
}
/**
\fn uint32_t I2C_Read_Reg(uint32_t Register)
\brief Reads a register, the entire sequence to read (at least for HTS221)
\param uint32_t Device: The slave address to written to
\param uint32_t Register: The register that you would like to write to
\param uint32_t Data: The data that you would like to write to the register
*/
void I2C_Write_Reg(uint32_t Device,uint32_t Register, uint32_t Data){
//Reset CR2 Register
I2C1->CR2 = 0x00000000;
//Check to see if the bus is busy
while((I2C1->ISR & I2C_ISR_BUSY) == I2C_ISR_BUSY);
//Set CR2 for 2-Byte Transfer, for Device
I2C1->CR2 |= (2UL<<16) | (Device<<1);
//Start communication
I2C1->CR2 |= I2C_CR2_START;
//Check Tx empty before writing to it
if((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE)){
I2C1->TXDR = Register;
}
//Wait for TX Register to clear
while((I2C1->ISR & I2C_ISR_TXE) == 0);
//Check Tx empty before writing to it
if((I2C1->ISR & I2C_ISR_TXE) == I2C_ISR_TXE){
I2C1->TXDR = Data;
}
//Wait for transfer to complete
while((I2C1->ISR & I2C_ISR_TC) == 0);
//Send Stop Condition
I2C1->CR2 |= I2C_CR2_STOP;
//Check to see if the bus is busy
while((I2C1->ISR & I2C_ISR_BUSY) == I2C_ISR_BUSY);
//Clear Stop bit flag
I2C1->ICR |= I2C_ICR_STOPCF;
}
步骤 3 :配置 HTS221
在此配置中,我将HTS221设置为单次模式,这意味着每次仅读取一组数据。这种情况下我们需要关注两个寄存器。
AV_Conf:
该寄存器在精度和功耗之间进行权衡。精度越高,功耗越大。
- 温度平均次数设置为16
- 湿度平均次数设置为32
CTRL_REG1:
务必记住先开启设备,再阻止数据更新。阻止数据更新的原因是数据长度为16位,但存储在8位寄存器中。通过阻止数据更新,您必须在设备更新寄存器中的值之前读取低数据和高数据寄存器。这可以防止您读取低位数据后数据被更新,然后再读取高位数据,导致在一次读取中得到两个不同的数值。
- 向PD写入1
- 向BDU写入1
请在GitHub上查看完整代码。以下是您应执行的操作要点。
HTS221.c
/**
\fn void HTS221_Init(void)
\brief Initialize the HTS221 and check device signature
\returns uint8_t Device_Found - Determines if the device was detected
*/
uint8_t HTS221_Init(void){
//Local variables
uint8_t Device_Found = 0;
uint32_t AV_CONF_Init = 0x1B; /*16 Temp (AVGT) and 32 Hum (AVGT)*/
//Read data from register and check signature
I2C_Read_Reg(HTS221_ADDRESS,HTS221_WHO_AM_I);
//Check if device signature is correct
if (I2C1->RXDR == HTS221_DEVICE_ID){
Device_Found = 1;
}
else Device_Found = 0;
/* Setup HTS221_AV_CONF Register */
if(Device_Found){
//Set to Default Configuration
I2C_Write_Reg(HTS221_ADDRESS,HTS221_AV_CONF,AV_CONF_Init);
//Activate and Block Data Update, this will ensure that both the higher and lower bits are read
I2C_Write_Reg(HTS221_ADDRESS,HTS221_CTRL_REG1,(HTS221_CTRL_REG1_PD | HTS221_CTRL_REG1_BDU));
}
return(Device_Found);
}
步骤 4 :读取温度和湿度数据
这无疑是最奇怪且最困难的部分,主要是因为数据手册并未真正描述您需要做什么。但别担心,我已经根据为ISK01A1板编写的HAL驱动程序全部搞清楚了。有两个关键方程用于计算温度和湿度。
温度插值:
湿度插值:
查看校准寄存器和输出寄存器以找到上述方程中的所有值。
请在GitHub上查看完整代码。以下是您应执行的操作要点。
HTS221.c
/**
\fn HTS221_Temp_Read(void)
\brief Reads the temperature from HTS221 in one-shot mode
\returns float Temperature_In_F: The temperature in Fahrenheit
*/
float HTS221_Temp_Read(void){
/* Local Variables */
uint8_t STATUS_REG = 0;
//T0_degC and T1_degC
uint16_t T0_degC_x8 = 0;
uint16_t T1_degC_x8 = 0;
uint16_t Msb_TO_T1_degC = 0;
float T0_DegC = 0;
float T1_DegC = 0;
//T_OUT
uint16_t T_OUT_L = 0;
uint16_t T_OUT_H = 0;
float T_OUT = 0;
//T0_OUT and T1_OUT
int16_t T0_OUT_L = 0;
int16_t T0_OUT_H = 0;
int16_t T1_OUT_L = 0;
int16_t T1_OUT_H = 0;
float T0_OUT = 0;
float T1_OUT = 0;
//Temperature Variables
float Temperature_In_C = 0;
float Temperature_In_F = 0;
//Start a temperature conversion
I2C_Write_Reg(HTS221_ADDRESS,HTS221_CTRL_REG2,HTS221_CTRL_REG2_ONE_SHOT);
//Wait for Temperature data to be ready
do{
I2C_Read_Reg(HTS221_ADDRESS,HTS221_STATUS_REG);
STATUS_REG = I2C1->RXDR;
}while((STATUS_REG & HTS221_STATUS_REG_TDA) == 0);
//Read Temperature Data and Calibration
I2C_Read_Reg(HTS221_ADDRESS,HTS221_TEMP_OUT_L);
T_OUT_L = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_TEMP_OUT_H);
T_OUT_H = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_TO_OUT_L);
T0_OUT_L = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_T0_OUT_H);
T0_OUT_H = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_T1_OUT_L);
T1_OUT_L = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_T1_OUT_H);
T1_OUT_H = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_T0_degC_x8);
T0_degC_x8 = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_T1_degC_x8);
T1_degC_x8 = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_T1_T0_Msb);
Msb_TO_T1_degC = I2C1->RXDR;
//Process Calibration Registers
T0_DegC = ((float)(((Msb_TO_T1_degC & 0x3) << 8) | (T0_degC_x8))/8.0);
T1_DegC = ((float)(((Msb_TO_T1_degC & 0xC) << 6) | (T1_degC_x8))/8.0); //Value in 3rd and 4th bit so only shift 6
T0_OUT = (float)((T0_OUT_H << 8) | T0_OUT_L);
T1_OUT = (float)((T1_OUT_H << 8) | T1_OUT_L);
T_OUT = (float)((T_OUT_H << 8) | T_OUT_L);
//Calculate Temperatuer using linear interpolation and convert to Fahrenheit
Temperature_In_C = (float)(T0_DegC + ((T_OUT - T0_OUT)*(T1_DegC - T0_DegC))/(T1_OUT - T0_OUT));
Temperature_In_F = (Temperature_In_C*(9.0/5.0)) +32.0;
return(Temperature_In_F);
}
/**
\fn float HTS221_Humidity_Read(void)
\brief Reads the Humidity from HTS221 in one-shot mode
\returns float Humidity_rH: The relative humidity %
*/
float HTS221_Humidity_Read(void){
/* Local Variables */
uint8_t STATUS_REG = 0;
//H0_rH and H1_rH
uint8_t H0_rH_x2 = 0;
float H0_rH = 0;
uint8_t H1_rH_x2 = 0;
float H1_rH = 0;
//H_OUT
float H_OUT = 0;
uint16_t H_OUT_L = 0;
uint16_t H_OUT_H = 0;
//H0_TO_OUT and H1_TO_OUT
float H0_T0_OUT = 0;
float H1_T0_OUT = 0;
uint16_t H0_T0_OUT_L = 0;
uint16_t H0_T0_OUT_H = 0;
uint16_t H1_T0_OUT_L = 0;
uint16_t H1_T0_OUT_H = 0;
//Humidity Variables
float Humidity_rH = 0;
//Start a humidity conversion
I2C_Write_Reg(HTS221_ADDRESS,HTS221_CTRL_REG2,HTS221_CTRL_REG2_ONE_SHOT);
//Wait for Humidity data to be ready
do{
I2C_Read_Reg(HTS221_ADDRESS,HTS221_STATUS_REG);
STATUS_REG = I2C1->RXDR;
}while((STATUS_REG & HTS221_STATUS_REG_HDA) == 0);
//Read Humidity data and Calibration
I2C_Read_Reg(HTS221_ADDRESS,HTS221_H0_rH_x2);
H0_rH_x2 = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_H1_rH_x2);
H1_rH_x2 = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_HUMIDITY_OUT_L);
H_OUT_L = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_HUMIDITY_OUT_H);
H_OUT_H = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_H0_T0_OUT_L);
H0_T0_OUT_L = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_H0_T0_OUT_H);
H0_T0_OUT_H = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_H1_T0_OUT_L);
H1_T0_OUT_L = I2C1->RXDR;
I2C_Read_Reg(HTS221_ADDRESS,HTS221_H1_T0_OUT_H);
H1_T0_OUT_H = I2C1->RXDR;
//Process Calibration Registers
H0_rH = (float)H0_rH_x2/2.0;
H1_rH = (float)H1_rH_x2/2.0;
H_OUT = (float)((H_OUT_H << 8) | H_OUT_L);
H0_T0_OUT = (float)((H0_T0_OUT_H << 8) | H0_T0_OUT_L);
H1_T0_OUT = (float)((H1_T0_OUT_H << 8) | H1_T0_OUT_L);
//Calculate the relative Humidity using linear interpolation
Humidity_rH = ( float )(((( H_OUT - H0_T0_OUT ) * ( H1_rH - H0_rH )) / ( H1_T0_OUT - H0_T0_OUT )) + H0_rH );
return(Humidity_rH);
}
与 LPS25HB 压力传感器通信的逐步指南
所有寄存器和设置的详细参考可在压力传感器数据手册中找到。在本指南的这一部分,我将描述如何与LPS25HB压力传感器通信、设置并读取其数据。
步骤 1 :通信序列
通信序列与HTS221相同。如果您尚未阅读此部分,请点击此处。
步骤 2 :配置 LPS25HB
与HTS221类似,我们将使用单次模式,每次读取一组数据。我们还需要为设备上电,并阻止数据更新以避免获取冲突数据(因为数据是24位,分散在8位寄存器中)。所有代码均可在GitHub上找到,但以下是您需要执行的操作要点。
/**
\fn uint8_t LPS25HB_Init(void))
\brief Initializes the LPS25HB Pressure sensor
\returns uint8_t Device_Found: 1 - Device found, 0 - Device not found
*/
uint8_t LPS25HB_Init(void){
uint8_t Device_Found = 0;
//Read data from register and check signature
I2C_Read_Reg(LPS25HB_ADDRESS,LPS25HB_WHO_AM_I);
//Check if device signature is correct
if (I2C1->RXDR == LPS25HB_DEVICE_ID){
Device_Found = 1;
}
else{
Device_Found = 0;
}
if(Device_Found){
//Power on the device and Block Data Update
I2C_Write_Reg(LPS25HB_ADDRESS,LPS25HB_CTRL_REG1,(LPS25HB_CTRL_REG1_PD | LPS25HB_CTRL_REG1_BDU));
//Configure the resolution for pressure for 16 internal averages
I2C_Write_Reg(LPS25HB_ADDRESS,LPS25HB_RES_CONF,LPS25HB_RES_CONF_AVGP0);
}
return(Device_Found);
}
步骤 3 :读取压力数据
幸运的是,读取压力数据时无需进行线性插值。但在获取压力读数前仍需完成一项操作。
根据数据手册,压力灵敏度为1位/百帕,因此必须对24位值应用以下公式。
另一个必须完成的技巧是将24位二进制补码值转换为32位二进制补码值。可通过检查24位值的最高有效位,然后用适当值扩展剩余8位来实现。
//convert the 2's complement 24 bit to 2's complement 32 bit
if (Raw_Pressure & 0x00800000){
Raw_Pressure |= 0xFF000000;
}
所有代码均可在GitHub上找到,但以下是应执行操作的要点。
LPS25HB.c
/**
\fn void LPS25HB_Configuration(void)
\brief Prints important Configuration registers
\returns float LPS25HB_Pressure: pressure measured in mbar
*/
float LPS25HB_Pressure_Read(void){
//Local Variables
uint8_t PRESS_OUT_XL = 0;
uint8_t PRESS_OUT_L = 0;
uint8_t PRESS_OUT_H = 0;
float Pressure = 0;
int32_t Raw_Pressure = 0;
uint8_t LPS25HB_STATUS = 0;
//Start a temperature conversion
I2C_Write_Reg(LPS25HB_ADDRESS,LPS25HB_CTRL_REG2,LPS25HB_CTRL_REG2_ONE_SHOT);
//Wait for Temperature data to be ready
do{
I2C_Read_Reg(LPS25HB_ADDRESS,LPS25HB_STATUS_REG);
LPS25HB_STATUS = I2C1->RXDR;
}while((LPS25HB_STATUS & LPS25HB_STATUS_REG_PDA) == 0);
//Read the pressure output registers
I2C_Read_Reg(LPS25HB_ADDRESS,LPS25HB_PRESS_OUT_XL);
PRESS_OUT_XL = I2C1->RXDR;
I2C_Read_Reg(LPS25HB_ADDRESS,LPS25HB_PRESS_OUT_L);
PRESS_OUT_L = I2C1->RXDR;
I2C_Read_Reg(LPS25HB_ADDRESS,LPS25HB_PRESS_OUT_H);
PRESS_OUT_H = I2C1->RXDR;
//Read the reference Register
/* Combine pressure into 24 bit value
PRESS_OUT_H is the High bits 23 - 16
PRESS_OUT_L is the mid bits 15 - 8
PRESS_OUT_XL is the lsb 7 - 0
*/
Raw_Pressure = ((PRESS_OUT_H << 16) | (PRESS_OUT_L << 8) | (PRESS_OUT_XL));
//convert the 2's complement 24 bit to 2's complement 32 bit
if (Raw_Pressure & 0x00800000){
Raw_Pressure |= 0xFF000000;
}
//Calculate Pressure in mbar
Pressure = (float)Raw_Pressure/4096.0f;
return(Pressure);
}
将压力转换为海拔高度
压力传感器的用途之一是计算海拔高度。我发现一篇关于海拔与压力关系推导的实用文章。您可以在此下载该文章,若只需获取海拔计算公式,请参考以下内容。
从上述公式可见,大多数数值都是常量。计算海拔时唯一的未知量就是您的压力读数。
/**
\fn ISK01A1_Get_Altitude(void)
\brief Calculates the altitude based on the pressure
\returns float Z: Altitude calculation in meters
*/
float ISK01A1_Get_Altitude(void){
/* Calculation should be good up to 11km */
/* Local Variables */
const float T0 = 288.15; /* Temperatuer at zero altitude, ISA */
float P = 0.0; /* Measured Pressure */
const float P0 = 101325.0; /* Pressure at zero altitude, ISA */
const float g = 9.80655; /* Acceleration due to gravity */
const float L = -0.0065; /* Lapse Rate, ISA */
const float R = 287.053; /* Gas constant for air */
/* Read Pressure */
P = LPS25HB_Pressure_Read()*100.0; /* Convert mbar to Pa */
/* Calculate Altitude in meters */
ISK01A1.Altitude = (T0/L)*(pow((P/P0),((-L*R)/g))-1);
return(ISK01A1.Altitude);
}
LSM6DS0 加速度计 / 陀螺仪传感器通信指南
所有寄存器设置参考均可在加速度计和陀螺仪数据手册中找到。本指南章节将介绍如何与LSM6DS0加速度计/陀螺仪传感器通信、设置设备,并读取横滚角、偏航角、俯仰角以及X/Y/Z轴加速度。
步骤 1 :通信序列
通信序列与HTS221相同。如果您尚未阅读此部分,请点击此处。
步骤 2 :配置 LSM6DS0
该设备与其他传感器略有不同,因其没有单次触发模式,且上电方式也与其他传感器不同。如下图所示,该设备可能处于几种不同状态。
我决定让加速度计和陀螺仪都处于工作状态,因此必须向CTRL_REG1_G的ODR_G[2:0]位写入大于000(复位)的值。通过此操作可退出省电模式并设置输出数据速率。我决定选择 238Hz 作为输出数据速率 (ODR) ,这意味着设备处于正常模式(非低功耗),消耗电流为 4.3 毫安。
与其他传感器类似,我们仍需向CTRL_REG8寄存器中的BDU位写入数据(因为16位数据被拆分为8位寄存器)。所有代码都可以在GitHub上找到,但核心操作要点如下。
/**
\fn uint8_t LSM6DS0_Init(void)
\brief Initialize LSM6DS0 Gyro and accelerometer
\returns uint8_t Device_Found: 1 - Device found, 0 - Device not found
*/
uint8_t LSM6DS0_Init(void){
//Global Variables
uint8_t Device_Found = 0;
//Read data from register and check signature
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_WHO_AM_I);
//Check if device signature is correct
if (I2C1->RXDR == LSM6DS0_DEVICE_ID){
Device_Found = 1;
}
else Device_Found = 0;
if(Device_Found){
//Enable Block Data Update until MSB and LSB read
I2C_Write_Reg(LSM6DS0_ADDRESS,LSM6DS0_CTRL_REG8,LSM6DS0_CTRL_REG8_BDU);
//Activate both the gyro and the accelerometer at the same ODR of 238 Hz
I2C_Write_Reg(LSM6DS0_ADDRESS,LSM6DS0_CTRL_REG1_G,LSM6DS0_CTRL_REG1_G_ODR_G2);
}
return(Device_Found);
}
步骤三:读取传感器数据
这与LPS25HB压力传感器非常相似,我们只需通过简单公式转换原始读数即可。
默认情况下,设备线性加速度量程为FS=±2g,角速度量程为FS=±245度/秒,因此计算公式如下。
所有代码均可在GitHub上找到,但以下是您需要执行的操作要点。
LSM6DS0.c
/**
\fn float LSM6DS0_X_Acceleration_Read(void)
\brief Retrieves X-Direction Acceleration
\returns float Acceleration_X: Acceleration in mg
*/
float LSM6DS0_X_Acceleration_Read(void){
//Local Variables
uint8_t LSM6DS0_STATUS = 0;
uint8_t Out_X_XL_L = 0;
uint8_t Out_X_XL_H = 0;
int16_t Raw_X = 0;
float Acceleration_X = 0;
//Wait for acceleration data to be ready
do{
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_STATUS_REG);
LSM6DS0_STATUS = I2C1->RXDR;
}while((LSM6DS0_STATUS & LSM6DS0_STATUS_REG_XLDA) == 0);
//Read acceleration output registers
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_X_XL_L);
Out_X_XL_L = I2C1->RXDR;
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_X_XL_H);
Out_X_XL_H = I2C1->RXDR;
//Combine Lower and upper bits
Raw_X = ((Out_X_XL_H << 8) | Out_X_XL_L);
//Calculate acceleration based on FS configuration, see datasheet
Acceleration_X = (float)Raw_X*0.061f;
return(Acceleration_X);
}
/**
\fn float LSM6DS0_Y_Acceleration_Read(void)
\brief Retrieves Y-Direction Acceleration
\returns float Acceleration_Y: Acceleration in mg
*/
float LSM6DS0_Y_Acceleration_Read(void){
//Local Variables
uint8_t LSM6DS0_STATUS = 0;
uint8_t Out_Y_XL_L = 0;
uint8_t Out_Y_XL_H = 0;
int16_t Raw_Y = 0;
float Acceleration_Y = 0;
//Wait for acceleration data to be ready
do{
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_STATUS_REG);
LSM6DS0_STATUS = I2C1->RXDR;
}while((LSM6DS0_STATUS & LSM6DS0_STATUS_REG_XLDA) == 0);
//Read acceleration output registers
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Y_XL_L);
Out_Y_XL_L = I2C1->RXDR;
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Y_XL_H);
Out_Y_XL_H = I2C1->RXDR;
//Combine Lower and upper bits
Raw_Y = ((Out_Y_XL_H << 8) | Out_Y_XL_L);
//Calculate acceleration based on FS configuration, see datasheet
Acceleration_Y = (float)Raw_Y*0.061f;
return(Acceleration_Y);
}
/**
\fn float LSM6DS0_Z_Acceleration_Read(void)
\brief Retrieves Z-Direction Acceleration
\returns float Acceleration_Z: Acceleration in mg
*/
float LSM6DS0_Z_Acceleration_Read(void){
//Local Variables
uint8_t LSM6DS0_STATUS = 0;
uint8_t Out_Z_XL_L = 0;
uint8_t Out_Z_XL_H = 0;
int16_t Raw_Z = 0;
float Acceleration_Z = 0;
//Wait for acceleration data to be ready
do{
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_STATUS_REG);
LSM6DS0_STATUS = I2C1->RXDR;
}while((LSM6DS0_STATUS & LSM6DS0_STATUS_REG_XLDA) == 0);
//Read acceleration output registers
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Z_XL_L);
Out_Z_XL_L = I2C1->RXDR;
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Z_XL_H);
Out_Z_XL_H = I2C1->RXDR;
//Combine the lower and upper bits
Raw_Z = ((Out_Z_XL_H << 8) | Out_Z_XL_L);
//Calculate acceleration based on FS configuration, see datasheet
Acceleration_Z = (float)Raw_Z*0.061f;
return(Acceleration_Z);
}
/**
\fn float LSM6DS0_Gyroscope_Roll_Read(void)
\brief Retrieves X-Direction(Roll)
\returns float Roll: Roll in mdps
*/
float LSM6DS0_Gyroscope_Roll_Read(void){
//Local Variables
uint8_t LSM6DS0_STATUS = 0;
uint8_t Roll_L = 0;
uint8_t Roll_H = 0;
int16_t Raw_Roll = 0;
float Roll = 0;
//Wait for roll data to be ready
do{
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_STATUS_REG);
LSM6DS0_STATUS = I2C1->RXDR;
}while((LSM6DS0_STATUS & LSM6DS0_STATUS_REG_GDA) == 0);
//Read Gyroscope output registers
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_X_G_L);
Roll_L = I2C1->RXDR;
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_X_G_H);
Roll_H = I2C1->RXDR;
//Combine lower and upper bits
Raw_Roll = ((Roll_H << 8) | Roll_L);
//Calculate Roll based on FS configuration,see datasheet
Roll = (float)Raw_Roll*8.75f;
return(Roll);
}
/**
\fn float LSM6DS0_Gyroscope_Pitch_Read(void)
\brief Retrieves Y-Direction(Pitch)
\returns float Pitch: Pitch in mdps
*/
float LSM6DS0_Gyroscope_Pitch_Read(void){
//Local Variables
uint8_t LSM6DS0_STATUS = 0;
uint8_t Pitch_L = 0;
uint8_t Pitch_H = 0;
int16_t Raw_Pitch = 0;
float Pitch = 0;
//Wait for pitch data to be ready
do{
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_STATUS_REG);
LSM6DS0_STATUS = I2C1->RXDR;
}while((LSM6DS0_STATUS & LSM6DS0_STATUS_REG_GDA) == 0);
//Read gyroscope output registers
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Y_G_L);
Pitch_L = I2C1->RXDR;
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Y_G_H);
Pitch_H = I2C1->RXDR;
//Combine lower and upper bits
Raw_Pitch = ((Pitch_H << 8) | Pitch_L);
//Calculate Pitch based on FS configuration,see datasheet
Pitch = (float)Raw_Pitch*8.75f;
return(Pitch);
}
/**
\fn float LSM6DS0_Gyroscope_Yaw_Read(void)
\brief Retrieves Z-Direction(Yaw)
\returns float Yaw: Yaw in mdps
*/
float LSM6DS0_Gyroscope_Yaw_Read(void){
//Local Variables
uint8_t LSM6DS0_STATUS = 0;
uint8_t Yaw_L = 0;
uint8_t Yaw_H = 0;
int16_t Raw_Yaw = 0;
float Yaw = 0;
//Wait for Yaw data to be ready
do{
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_STATUS_REG);
LSM6DS0_STATUS = I2C1->RXDR;
}while((LSM6DS0_STATUS & LSM6DS0_STATUS_REG_GDA) == 0);
//Read gyroscope output registers
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Z_G_L);
Yaw_L = I2C1->RXDR;
I2C_Read_Reg(LSM6DS0_ADDRESS,LSM6DS0_OUT_Z_G_H);
Yaw_H = I2C1->RXDR;
//Combine lower and upper bits
Raw_Yaw = ((Yaw_H << 8) | Yaw_L);
//Calculate Yaw based on FS configuration,see datasheet
Yaw = (float)Raw_Yaw*8.75f;
return(Yaw);
}
LIS3MDL 磁力计传感器通信指南
所有寄存器设置参考详见磁力计数据手册。本节将介绍如何与LIS3MDL磁力计传感器通信、配置该传感器,并读取X、Y、Z三个方向的磁场数据。
步骤 1 :通信序列
通信序列与HTS221相同。如果您尚未阅读此部分,请点击此处。
步骤二:配置 LIS3MDL
需要重点关注4个不同的寄存器。
通过CTRL_REG1 寄存器中的OM[1:0] 位序列设置,可调整X、Y方向的功耗模式。下方代码将其设置为中等性能模式。此外,我们还可以通过DO[2:0] 位 序列设置输出数据速率。以下示例中我选择了10Hz的输出数据速率。
//Performance Vs. Power consumption XY (medium), and set data rate to 10Hz
I2C_Write_Reg(LIS3MDL_ADDRESS,LIS3MDL_CTRL_REG1,(LIS3MDL_CTRL_REG1_OM0 | LIS3MDL_CTRL_REG1_DO2));
//Full scale = +/- 4 gauss (Default value) - just in case
I2C_Write_Reg(LIS3MDL_ADDRESS,LIS3MDL_CTRL_REG2,0x0);
//Performance Vs. Power consumption Z (medium)
I2C_Write_Reg(LIS3MDL_ADDRESS,LTS3MDL_CTRL_REG4,LIS3MDL_CTRL_REG4_OMZ0);
//Enable BDU so you ensure MSB and LSB have been read
I2C_Write_Reg(LIS3MDL_ADDRESS,LTS3MDL_CTRL_REG5,LIS3MDL_CTRL_REG5_BDU);
步骤三:读取传感器数据
与其他传感器类似,我们需要对读数进行微小调整。本例中FS=±4,意味着需要将原始读数除以6842。
所有代码均可在GitHub上找到,但以下是您需要执行的操作要点。
LIS3MDL.c
/**
\fn float LIS3MDL_X_Read(void)
\brief Reads the magnetic field from the X axis
\returns float OUT_X: the magnetic field in mG
*/
float LIS3MDL_X_Read(void){
//Local variables
uint8_t LIS3MDL_STATUS = 0;
uint8_t OUT_X_L = 0;
uint8_t OUT_X_H = 0;
float OUT_X = 0;
int16_t Raw_X = 0;
//Set device to continuous conversion mode
I2C_Write_Reg(LIS3MDL_ADDRESS,LTS3MDL_CTRL_REG3,LIS3MDL_CTRL_REG3_MD0);
//Wait for X coordinate data to be ready
do{
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_STATUS_REG);
LIS3MDL_STATUS = I2C1->RXDR;
}while((LIS3MDL_STATUS & LIS3MDL_STATUS_REG_XDA) == 0);
//Read X Axis magnetic field
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_OUT_X_L);
OUT_X_L = I2C1->RXDR;
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_OUT_X_H);
OUT_X_H = I2C1->RXDR;
//Process X coordinates
Raw_X = ((OUT_X_H << 8) | OUT_X_L);
/*
*1/6842 = ~0.146 mG/LSB according to datasheet
*/
OUT_X = (float)Raw_X * 0.146f; // when using +/- 4 gauss
return(OUT_X);
}
/**
\fn float LIS3MDL_Y_Read(void)
\brief Reads the magnetic field from the Y axis
\returns float OUT_Y: the magnetic field in mG
*/
float LIS3MDL_Y_Read(void){
//Local Variables
uint8_t LIS3MDL_STATUS = 0;
uint8_t OUT_Y_L = 0;
uint8_t OUT_Y_H = 0;
float OUT_Y = 0;
int16_t Raw_Y = 0;
//Set device to continuous conversion mode
I2C_Write_Reg(LIS3MDL_ADDRESS,LTS3MDL_CTRL_REG3,LIS3MDL_CTRL_REG3_MD0);
//Wait for X coordinate data to be ready
do{
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_STATUS_REG);
LIS3MDL_STATUS = I2C1->RXDR;
}while((LIS3MDL_STATUS & LIS3MDL_STATUS_REG_YDA) == 0);
//Read Y Axis magnetic field
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_OUT_Y_L);
OUT_Y_L = I2C1->RXDR;
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_OUT_Y_H);
OUT_Y_H = I2C1->RXDR;
/*
1/6842 = ~0.146 mG/LSB according to datasheet
*/
Raw_Y = ((OUT_Y_H << 8) | OUT_Y_L);
OUT_Y = (float)Raw_Y * 0.146f; // when using +/- 4 gauss
return(OUT_Y);
}
/**
\fn float LIS3MDL_Z_Read(void)
\brief Reads the magnetic field from the Z axis
\returns float OUT_Z: the magnetic field in mG
*/
float LIS3MDL_Z_Read(void){
//Local Variables
uint8_t LIS3MDL_STATUS = 0;
uint8_t OUT_Z_L = 0;
uint8_t OUT_Z_H = 0;
float OUT_Z = 0;
int16_t Raw_Z = 0;
//Set device to continuous conversion mode
I2C_Write_Reg(LIS3MDL_ADDRESS,LTS3MDL_CTRL_REG3,LIS3MDL_CTRL_REG3_MD0);
//Wait for X coordinate data to be ready
do{
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_STATUS_REG);
LIS3MDL_STATUS = I2C1->RXDR;
}while((LIS3MDL_STATUS & LIS3MDL_STATUS_REG_ZDA) == 0);
//Read Z Axis magnetic field
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_OUT_Z_L);
OUT_Z_L = I2C1->RXDR;
I2C_Read_Reg(LIS3MDL_ADDRESS,LIS3MDL_OUT_Z_H);
OUT_Z_H = I2C1->RXDR;
Raw_Z = ((OUT_Z_H << 8) | OUT_Z_L);
/*
1/6842 = ~0.146 mG/LSB according to datasheet
*/
OUT_Z = (float)Raw_Z * 0.146f; // when using +/- 4 gauss
return(OUT_Z);
}
USART/LPUART 应用示例
USART/LPUART 初始化指南
所有寄存器及设置的详细说明可参考STM32L053R8参考手册。查阅STM32L053xx技术资料也有助于查找引脚的复用功能。本教程假设您已设置好备用功能。若未完成,请参考《设置备用功能教程》。在本教程中,我们将设置几个UART接口。STM32L053R8拥有多种不同的UART类型。第一种是标准USART(通用同步异步收发器),第二种是低功耗UART(LPUART)。LPUART的设置与USART几乎相同,但为了全面起见我仍会详述。
步骤 1 :配置时钟
只需在RCC_IOPENR寄存器中写入IOPAEN,并通过在RCC_APB2ENR寄存器中写入USART1EN来启用USART时钟。默认时钟源为PCLK。
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; /* Enable GPIOA clock */
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; /* Enable USART#1 clock */
步骤 2 :配置 USART 波特率寄存器
计算波特率时需注意几个公式。这完全取决于你采用的过采样倍数或是否使用LPUART。什么是过采样?即对UART接收的每个比特位进行多次采样。采样完成后,将选取多数值(0或1)作为最终值。
警告:8 倍过采样时不可直接写入计算出的 USARTDIV 值 。
16 倍过采样时(默认)
可见每个比特时间被采样16次以确定数值。
8 倍过采样时
可见每个比特时间被采样8次以确定数值。
本质上需要遵循特定规则才能向寄存器写入正确值。请参阅STM32L053xx参考手册第759页验证我所写的步骤。
- BRR[2:0] = 计算所得USARTDIV[3:0]右移一位后的值
- BRR[3] = 0,始终如此!
- BRR[15:4] = USARTDIV[15:4]。
当使用 LPUART 时
过采样无选择余地,因此这是LPUART唯一可用的计算公式。
USART BRR 代码:
/* Define statements outside function */
#define PCLK 32000000 // Peripheral Clock
#define BAUD 9600 // Baud rate
/* Local variables */
uint16_t USARTDIV = 0;
uint16_t USART_FRACTION = 0;
uint16_t USART_MANTISSA = 0;
/* Check to see if oversampling by 8 or 16 to properly set baud rate*/
if((USART2->CR1 & USART_CR1_OVER8) == 1){
USARTDIV = PCLK/BAUD;
USART_FRACTION = ((USARTDIV & 0x0F) >> 1) & (0xB);
USART_MANTISSA = ((USARTDIV & 0xFFF0) << 4);
USARTDIV = USART_MANTISSA | USART_FRACTION;
USART2->BRR = USARTDIV; /* 9600 Baud with 32MHz peripheral clock 8bit oversampling */
}
else{
USART2->BRR = PCLK/BAUD; /* 9600 Baud with 32MHz peripheral clock 16bit oversampling */
}
LPUART BRR 代码:
由于我们的MCU是32位,最大值仅为2^32-1 = 4294967295,无法处理像256*32,000,000这样的数值。但我们可以用个小技巧,先除后乘,具体实现如下所示。
/* Define statements outside function */
#define PCLK 32000000 // Peripheral Clock
#define BAUD 9600 // Baud rate
LPUART1->BRR = (unsigned long)((256.0f/BAUD)*PCLK); /* 9600 baud @ 32MHz */
步骤 3 :配置 CR1 寄存器
我们需要通过该寄存器实现几个不同的功能。需启用RX和TX,因此同时设置RE 和TE 位 。还需指定字长,通过向M[1:0] 写入相应值实现。请注意M0位于第12位,M1位于第28位。
USART2->CR3 = 0x0000; /* no flow control */
USART2->CR2 = 0x0000; /* 1 stop bit */
/* 1 stop bit, 8 data bits */
USART2->CR1 = ((USART_CR1_RE) | /* enable RX */
(USART_CR1_TE) | /* enable TX */
(USART_CR1_UE) | /* enable USART */
(USART_CR1_RXNEIE)); /* Enable Interrupt */
USART/LPUART 初始化代码(含读写功能!)
所有代码均可在GitHub上找到,但以下是您需要执行的操作要点。
Serial.c
/*------------------------------------------------------------------------------------------------------
* Name: Serial.c
* Purpose: Used to communicat with your computer using USART2
* Date: 7/14/15
* Author: Christopher Jordan - Denny
*------------------------------------------------------------------------------------------------------
* Note(s): SER_PutChar are used to redefine printf function
*----------------------------------------------------------------------------------------------------*/
/*-------------------------------------------------Include Statements---------------------------------*/
#include "stm32l0xx.h" // Specific Device header
#include "Serial.h"
/*-------------------------------------------------Define Statements----------------------------------*/
#define PCLK 32000000 // Peripheral Clock
#define BAUD 9600 // Baud rate
/*-------------------------------------------------Functions------------------------------------------*/
/**
\fn void SER_Initialize (void)
\brief Initializes USART2 to be used with the serial monitor
*/
void SER_Initialize (void){
/* Local variables */
uint16_t USARTDIV = 0;
uint16_t USART_FRACTION = 0;
uint16_t USART_MANTISSA = 0;
RCC->IOPENR |= ( 1ul << 0); /* Enable GPIOA clock */
RCC->APB1ENR |= ( 1ul << 17); /* Enable USART#2 clock */
/* Configure PA3 to USART2_RX, PA2 to USART2_TX */
GPIOA->AFR[0] &= ~((15ul << 4* 3) | (15ul << 4* 2) );
GPIOA->AFR[0] |= (( 4ul << 4* 3) | ( 4ul << 4* 2) );
GPIOA->MODER &= ~(( 3ul << 2* 3) | ( 3ul << 2* 2) );
GPIOA->MODER |= (( 2ul << 2* 3) | ( 2ul << 2* 2) );
/* Check to see if oversampling by 8 or 16 to properly set baud rate*/
if((USART2->CR1 & USART_CR1_OVER8) == 1){
USARTDIV = PCLK/BAUD;
USART_FRACTION = ((USARTDIV & 0x0F) >> 1) & (0xB);
USART_MANTISSA = ((USARTDIV & 0xFFF0) << 4);
USARTDIV = USART_MANTISSA | USART_FRACTION;
USART2->BRR = USARTDIV; /* 9600 Baud with 32MHz peripheral clock 8bit oversampling */
}
else{
USART2->BRR = PCLK/BAUD; /* 9600 Baud with 32MHz peripheral clock 16bit oversampling */
}
USART2->CR3 = 0x0000; /* no flow control */
USART2->CR2 = 0x0000; /* 1 stop bit */
/* 1 stop bit, 8 data bits */
USART2->CR1 = ((USART_CR1_RE) | /* enable RX */
(USART_CR1_TE) | /* enable TX */
(USART_CR1_UE) | /* enable USART */
(USART_CR1_RXNEIE)); /* Enable Interrupt */
}
/**
\fn char SER_PutChar(char ch)
\brief Put character to the serial monitor
\param char ch: Character to send to the serial monitor
\returns char ch: The character that was sent to the serial monitor
*/
char SER_PutChar(char ch){
//Wait for buffer to be empty
while ((USART2->ISR & USART_ISR_TXE) == 0){
//Nop
}
//Send character
USART2->TDR = (ch);
return (ch);
}
/**
\fn int SER_GetChar(void)
\brief Get character from the serial monitor
\returns int USART2->RDR: The value of character from the serial monitor
*/
int SER_GetChar(void){
if (USART2->ISR & USART_ISR_RXNE)
return (USART2->RDR);
return (-1);
}
重定向 Printf
有个巧妙方法可在C语言中通过USART使用printf函数。一如既往,所有代码均可在GitHub上找到。
/*------------------------------------------------------------------------------------------------------
* Name: Retarget.c
* Purpose: Retargets printf C function to be used with USART2
* Date: 7/14/15
* Author: Christopher Jordan - Denny
*------------------------------------------------------------------------------------------------------
* Note(s): This is really just magic
*----------------------------------------------------------------------------------------------------*/
/*--------------------------------------Include Statements--------------------------------------------*/
#include <stdio.h>
#include <rt_misc.h>
#include "Serial.h"
/*--------------------------------------Additional Compiler Info--------------------------------------*/
#pragma import(__use_no_semihosting_swi)
/*--------------------------------------Structure Definitions-----------------------------------------*/
struct __FILE { int handle;};
FILE __stdout;
FILE __stdin;
/*--------------------------------------Functions-----------------------------------------------------*/
int fputc(int c, FILE *f){
return (SER_PutChar(c));
}
int fgetc(FILE *f){
return (SER_GetChar());
}
void _sys_exit(int return_code){
label: goto label; /* endless loop */
}
FGPMMOPA6H GPS 模块通信实战指南
FGPMMOPA6B GPS模块数据手册提供了所有设置和NMEA语句的完整参考。完整命令列表请参阅PMTK命令集。本节将介绍如何与FGPMMOPA6H GPS模块建立通信并进行配置。同时还将讲解接收数据的解析方法。强烈建议结合GitHub上的完整代码学习本节内容。若未配置USART,请先阅读《USART/LPUART初始化指南》。
步骤 1 :设置刷新率
首先需声明位置回显时间,其次声明更新时间。必须同时设置这两项才能更改刷新时间。
#define PMTK_SET_NMEA_UPDATE_200_MILLIHERTZ "$PMTK220,5000*1B\r\n" // Once every 5 seconds, 200 millihertz.
#define PMTK_API_SET_FIX_CTL_200_MILLIHERTZ "$PMTK300,5000,0,0,0,0*18\r\n" // Once every 5 seconds, 200 millihertz.
USART1_Send(PMTK_API_SET_FIX_CTL_200_MILLIHERTZ); /* 5s Position echo time */
USART1_Send(PMTK_SET_NMEA_UPDATE_200_MILLIHERTZ); /* 5s update time */
步骤 2 :设置接收的 GPS 消息类型
默认接收5种消息:$GPGGA、$GPGSA、$GPGSV、$GPRMC和$GPVTG。其中$GPRMC和$GPGGA最为实用。选择这两条消息只需一条简单指令。
// turn on GPRMC and GGA
#define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n"
USART1_Send(PMTK_SET_NMEA_OUTPUT_RMCGGA); /* Output RMC Data and GGA */
步骤 3 :整理接收的 GPS 数据
我制作了流程图来帮助理解如何分类接收到的消息。此操作仅将消息归入对应标签,不解析数据内容。本例中我采用中断机制处理数据接收。
FGPMMOPA6H.c
/*---------------------------------NMEA Output Sentences----------------------------------------------*/
static const char GGA_Tag[] = "$GPGGA";
static const char GSA_Tag[] = "$GPGSA";
static const char GSV_Tag[] = "$GPGSV";
static const char RMC_Tag[] = "$GPRMC";
static const char VTG_Tag[] = "$GPVTG";
/*---------------------------------Globals------------------------------------------------------------*/
volatile int CharIndex = 0; /* Character index of the char array */
const int NMEA_LENGTH = 128; /* The max length of one NMEA line */
char Rx_Data[NMEA_LENGTH] = "0"; /* Rx Sring */
volatile uint8_t Transmission_In_Progress = FALSE; /* Are we in between a $ and \n */
char GGA_Message[128]; /* Original GGA message */
char GSA_Message[128]; /* Original GSA message */
char GSV_Message[128]; /* Original GSV message */
char RMC_Message[128]; /* Original RMC message */
char VTG_Message[128]; /* Original VTG message */
/**
\fn void USART1_IRQHandler(void)
\brief Global interrupt handler for USART1, handles Rx interrupt
*/
void USART1_IRQHandler(void){
if(USART1->ISR & USART_ISR_RXNE){
/* Reads and CLEARS RXNE Flag */
Rx_Data[CharIndex] = USART1->RDR;
/* Data Not Ready */
RMC.New_Data_Ready = FALSE;
GGA.New_Data_Ready = FALSE;
/* If Rx_Data = $, then we are in transmission */
if(Rx_Data[CharIndex] == '$'){
Transmission_In_Progress = TRUE;
}
/* If we are transmitting then save to proper message once complete */
if(Transmission_In_Progress == TRUE){
if(Rx_Data[CharIndex] == '\n'){
if(strncmp(GGA_Tag,Rx_Data,(sizeof(GGA_Tag)-1)) == 0){
strcpy(GGA_Message,Rx_Data);
GGA.New_Data_Ready = TRUE;
}
if(strncmp(GSA_Tag,Rx_Data,(sizeof(GSA_Tag)-1)) == 0){
strcpy(GSA_Message,Rx_Data);
}
if(strncmp(GSV_Tag,Rx_Data,(sizeof(GSV_Tag)-1)) == 0){
strcpy(GSV_Message,Rx_Data);
}
if(strncmp(RMC_Tag,Rx_Data,(sizeof(RMC_Tag)-1)) == 0){
strcpy(RMC_Message,Rx_Data);
RMC.New_Data_Ready = TRUE;
}
if(strncmp(VTG_Tag,Rx_Data,(sizeof(VTG_Tag)-1)) == 0){
strcpy(VTG_Message,Rx_Data);
}
Transmission_In_Progress = FALSE;
CharIndex = 0;
memset(Rx_Data,0,sizeof(Rx_Data));
}
else{
CharIndex++;
}
}
}
}
步骤 4 :解析特定消息数据
GPS数据以逗号分隔,结尾为回车符或换行符。例如$GPRMC消息格式如下所示:
$GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,3.05,W,A*2C
这意味着我们只需按逗号分隔符进行解析。C语言的strtok函数非常适合此操作,其用法稍显特殊,建议深入研究。强烈推荐查看GitHub上的相关代码。核心操作逻辑可参考下方示例。
FGPMMOPA6H.c
/**
\fn void FGPMMOPA6H_Parse_RMC_Data(void)
\brief Parses the RMC Data based on the , delimeter
*/
void FGPMMOPA6H_Parse_RMC_Data(void){
//Local Variables
char RMC_Message_Copy[128] = "";
const char delimeter[2] = ",";
char *token = "";
int i = 0;
char temp[11][12]; /* [11][12]: 11 strings, of length 12 */
//Copy original RMC to a copy in order to not destroy message
strcpy(RMC_Message_Copy,RMC_Message);
//Seperated Message
/* get the first token */
token = strtok(RMC_Message_Copy, delimeter);
/* walk through other tokens */
while( token != NULL )
{
strcpy(temp[i],token);
i++;
token = strtok(NULL, delimeter);
}
//Copy the message into its individual components
strcpy(RMC.Message_ID,temp[0]);
strcpy(RMC.UTC_Time,temp[1]);
strcpy(RMC.Status,temp[2]);
strcpy(RMC.Latitude,temp[3]);
strcpy(RMC.N_S_Indicator,temp[4]);
strcpy(RMC.Longitude,temp[5]);
strcpy(RMC.E_W_Indicator,temp[6]);
strcpy(RMC.Speed_Over_Ground,temp[7]);
strcpy(RMC.Course_Over_Ground,temp[8]);
strcpy(RMC.Date,temp[9]);
strcpy(RMC.Mode,temp[10]);
}
XBee Pro S1 通信配置指南
所有参数设置可参考XBee Pro用户手册。本节将说明如何配置并实现XBee Pro S1的通信。强烈建议查看GitHub上的完整代码。若未配置USART,请先阅读《USART/LPUART初始化指南》。若对串口监视程序不熟悉,请参阅《使用CoolTerm与XBee通信》快速入门教程。
第1步:设置地址
有几个地址需要您关注。
地址类型 | 指令发出 | 目的: |
---|---|---|
MY | ATMY | 读取/写入MY地址,即XBee的源地址 |
PAN | ATID | 个人区域网络 - 读取/写入XBee的16位ID只有同一PAN内的XBee才能通信 |
DH | ATDH | 目标高位 - 读取 / 写入 XBee 的高 32 位目标地址 |
DL | ATDL | 目标低位 - 读取 / 写入 XBee 的低 32 位目标地址 |
CH | ATCH | 读取/写入XBee的工作频道(1) |
(1) XBee频道由以下符合802.15.4标准的公式决定
默认CH = 0x0C或12,表示中心频率=7.405MHz
修改这些值的指令序列如下
接下来我将展示如何在两个XBee之间建立点对点网络的示例重申,如果您不熟悉串行监视程序,请参阅《使用CoolTerm与XBee通信》
您会注意到,每次寄存器成功写入后,XBee都会返回一个OK响应。我们可以利用这一点来确保寄存器已正确设置,并通过中断来处理接收到的数据,如下所示。
XBeePro24.c
/**
\fn void RNG_LPUART1_IRQHandler(void)
\brief Global interrupt handler for LPUART, Currently only handles RX
*/
void RNG_LPUART1_IRQHandler(void){
if((LPUART1->ISR & USART_ISR_RXNE) == USART_ISR_RXNE){
/* Read RX Data */
RX_Data[ChIndex] = LPUART1->RDR;
/* Check for end of recieved data */
if(RX_Data[ChIndex] == '\r'){
/* Compare string to OK to see if Acknowledged */
if(strncmp(OK,RX_Data,(sizeof(OK)-1)) == 0){
Device_Ack_Flag = TRUE;
}
/* Copy XBee message */
strcpy(XBee_Message,RX_Data);
/* set data ready to read flag */
XBee_Ready_To_Read = TRUE;
/* Clear RX_Data */
ChIndex = 0;
memset(RX_Data,0,sizeof(RX_Data));
}else ChIndex++;
}
}
要在TeraTerm中应用我们示例中的设置,可以按照以下步骤操作。
XBeePro24.c
/**
\fn void XBee_Init(void)
\brief Initializes the XBEE
*/
void XBee_Init(void){
/* Enter AT command mode */
Delay(1000);
LPUART1_Send(ENTER_AT_COMMAND_MODE);
Wait_For_OK();
/* MY address = 2 */
LPUART1_Send(SET_ATMY);
Wait_For_OK();
/* PAN = 3001 */
LPUART1_Send(SET_ATID);
Wait_For_OK();
/* Set Destination address high */
LPUART1_Send(SET_ATDH);
Wait_For_OK();
/* Set Destination Address low */
LPUART1_Send(SET_ATDL);
Wait_For_OK();
/* Set Channel */
LPUART1_Send(SET_ATCH);
Wait_For_OK();
/* End AT command mode */
LPUART1_Send(EXIT_AT_COMMAND_MODE);
Wait_For_OK();
printf("##### XBee Initialized #####\r\n");
}
/**
\fn void Wait_For_OK(void)
\brief Waits until the XBEE has sent the OK message
This is done when it has changed its settings
*/
void Wait_For_OK(void){
/* Wait for XBee Acknowledge */
while(Device_Ack_Flag == 0){
//Nop
}
/* Reset Flags */
Device_Ack_Flag = FALSE;
XBee_Ready_To_Read = FALSE;
}
/**
\fn void Wait_For_Data(void)
\brief This waits for the data the XBEE writes to the bus
*/
void Wait_For_Data(void){
/* Wait for data to be copied */
while(XBee_Ready_To_Read == 0){
//Nop
}
/* Reset Flags */
Device_Ack_Flag = FALSE;
XBee_Ready_To_Read = FALSE;
}
第2步:发送数据
正确设置地址后,发送数据就很简单了,只需向XBee写入即可。
/* Send data over the XBEE */
LPUART1_Send(Data);
使用 CoolTerm 与 XBee 通信
这是一份关于如何设置CoolTerm作为串行监视器的简短指南。您可以从此处下载CoolTerm。
第1步:设置串行端口
点击齿轮和扳手图标(称为“选项 ”),您可以使用以下设置与默认配置下的XBee Pro s1进行通信。
第2步:设置终端
在左侧菜单中点击“终端”,然后勾选“本地回显”框。
就这样,您已经准备好通过串行端口监控和与XBee或其他设备通信了,只需点击连接按钮即可。
脉宽调制示例?
初始化 PWM 逐步指南
所有设置的详细参考可以在STM32L053xx参考手册中找到。在本指南的这一部分,我将描述如何使用通用定时器(TIM21/22)启用脉宽调制(PWM)。该信号将用于控制伺服电机。我强烈建议查看GitHub上的完整代码。
第1步:启用时钟
RCC->APB2ENR |= RCC_APB2ENR_TIM22EN;
第2步:设置周期
这段代码将设置PWM的时钟速度、PWM周期以及脉冲长度。在我的示例中,时钟设置为32MHz,因此要获得一个良好的1MHz时钟,我们可以将TIM22_PSC 的值设置为31。这来自于公式:
其中FCK_PSC 为时钟频率(32MHz),PSC[15:0] 是写入TIM22_PSC 寄存器的值(31),CK_CNT 为定时器时钟。下图能最直观地说明这一点。
现在可以设置所需周期,由于我使用PWM控制伺服电机,故选择20ms周期。既然现在时钟为1MHz(周期1µs),向TIM22_ARR 寄存器写入2000即可获得20ms周期。最后需要计算的是脉冲宽度。可通过从TIM22_ARR 寄存器值减去脉冲持续时间,并将结果写入TIM22_CCR1 寄存器实现。
TIM22->PSC = 31; //CK_CNT=Fck_psc/(PSC[15:0]+1), so 32MHz clock becomes 1MHz
TIM22->ARR = 20000; //Clock is 1MHz so period becomes 20ms
/*Pulse Width Calculation*/
TIM22->CCR1 = (TIM22->ARR)-Pulse_Duration;
第3步:更多设置
还需完成几项设置才能完成配置。本案例中我使用了输出比较模式1的PWM模式2。这将使通道1在向上计数时,只要TIMx_CNT<TIMx_CCR1 就保持无效状态。向上计数是默认设置。我还启用了输出比较1预装载功能。接着通过向TIMx_CCER 寄存器写入TIM_CCER_CC1E 位,将OC1设为高电平有效。最后通过向TIMx_CR1 寄存器写入TIM_CR1_CEN 位启用PWM,并通过向TIMx_EGR 写入TIM_EGR_UG 位重新初始化计数器和寄存器。
/*Select PWM 2 on OC1 and enable preload register*/
TIMx->CCMR1 |= TIM_CCMR1_OC1M_2|TIM_CCMR1_OC1M_1|TIM_CCMR1_OC1M_0
|TIM_CCMR1_OC1PE
#if PULSE_WITHOUT_DELAY > 0
|TIM_CCMR1_OC1FE
#endif
;
/*Select active high polarity on OC1*/
TIMx->CCER |= TIM_CCER_CC1E;
/*Enable PWM*/
TIMx->CR1 = TIM_CR1_CEN;
TIMx->EGR = TIM_EGR_UG;
第4步:选择 GPIO
最后一步是选择要使用的引脚。这涉及检查复用功能并配置GPIO。若未阅读《GPIO示例》或《复用功能配置指南》章节,请立即查阅。下文展示了我用于PWM的GPIO配置代码。
/*Initialize GPIO*/
struct GPIO_Parameters GPIO;
GPIO.Pin = Pin;
GPIO.Mode = Alternate_Function;
GPIO.Speed = High_Speed;
GPIO.PuPd = Pull_Down;
GPIO_Init(GPIOx,GPIO);
PWM 初始化代码
完整代码详见GitHub。
PWM.c
void PWM(TIM_TypeDef* TIMx, int Pulse_Duration,GPIO_TypeDef* GPIOx, int Pin){
/*Initialize GPIO*/
struct GPIO_Parameters GPIO;
GPIO.Pin = Pin;
GPIO.Mode = Alternate_Function;
GPIO.Speed = High_Speed;
GPIO.PuPd = Pull_Down;
GPIO_Init(GPIOx,GPIO);
/*Enable TIM clock*/
if(TIMx == TIM22){
RCC->APB2ENR |= RCC_APB2ENR_TIM22EN;
}
if(TIMx == TIM21){
RCC->APB2ENR |= RCC_APB2ENR_TIM21EN;
}
TIMx->PSC = 31; //CK_CNT=Fck_psc/(PSC[15:0]+1), so 32MHz clock becomes 1MHz
TIMx->ARR = 20000; //Clock is 1MHz so period becomes 20ms
/*Pulse Width Calculation*/
TIMx->CCR1 = (TIMx->ARR)-Pulse_Duration;
/*Select PWM 2 on OC1 and enable preload register*/
TIMx->CCMR1 |= TIM_CCMR1_OC1M_2|TIM_CCMR1_OC1M_1|TIM_CCMR1_OC1M_0
|TIM_CCMR1_OC1PE
#if PULSE_WITHOUT_DELAY > 0
|TIM_CCMR1_OC1FE
#endif
;
/*Select active high polarity on OC1*/
TIMx->CCER |= TIM_CCER_CC1E;
/*Enable PWM*/
TIMx->CR1 = TIM_CR1_CEN;
TIMx->EGR = TIM_EGR_UG;
}
欢迎大家在GitHub上使用、修改并优化我的代码。