owned this note
owned this note
Published
Linked with GitHub
# Motion Sensor Configuration
<!-- {%hackmd hackmd-dark-theme %} -->
> 國立成功大學 資訊工程學系
嵌入式作業系統分析與實作 Analysis and Implementation of Embedded Operating Systems [CSIE7618] 2022 Spring
## LIS3DSH Documentation
- [LIS3DSH: 3-axis digital output accelerometer](https://drive.google.com/file/d/13lmWzrJA0MERsK2inaA48x4zjTfCU25x/view?usp=sharing)
- [[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing)
## Introduction
我們用的motion sensor型號是 `ST MEMS LIS3DSH`,是 STM32F407G-DISC1 開發板上內建的感測器。`ST MEMS LIS3DSH` 是一種 MEMS (Micro-Electro-Mechanical Systems) 加速度計,用來測量物體在三個不同軸上的加速度。
### Registers of LIS3DSH
我們是藉由讀取/寫入 LIS3DSH 的 registers,來達到讀取 LIS3DSH 的data,或者控制 LIS3DSH。
[[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing) 的第六章 $Register\ mapping$ 的 ++Table 15++ 有列出 LIS3DSH 的 registers 的資訊,像是 register name、register address、default value、以及 comment;第七章則有各個 register 的較詳細的描述。
### Serial Peripheral Interface
- [SPI (Serial Peripheral Interface) 串列 (序列) 週邊介面](https://magicjackting.pixnet.net/blog/post/164725144)
- [Introduction to SPI Interface](https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html)
- [序列周邊介面](https://zh.wikipedia.org/zh-tw/%E5%BA%8F%E5%88%97%E5%91%A8%E9%82%8A%E4%BB%8B%E9%9D%A2)
[Discovery kit with STM32F407VG MCU](https://drive.google.com/file/d/1g46_RRT_xp9N05Mal4vouiHfJFGbltRt/view?usp=sharing) 第19頁:

STM32F407VG的microcontroller是透過SPI (Serial Peripheral Interface)向LIS3DSH發送命令和接收數據,以達到對Accelerometer(加速規)的控制。
Serial Peripheral Interface Bus (SPI),是其中一種最廣泛用於 microcontroller 和 peripheral ICs 通訊的 interface (peripheral ICs包括了像是sensors、ADCs、DACs等等)。
SPI讓我們可以在一個主要的裝置 (Master) 和一個或多個次要的裝置 (Slave) 之間進行communication:
- SPI Master:SPI bus上的primary裝置,他控制communication的timing、發送commands和data給一個或多個SPI slave裝置。Master會決定何時啟動和終止communication,也會決定data傳送的速率等等。
- SPI Slave:SPI bus上的secondary裝置,負責接收Master傳送的command和data,並回應相對應的data給master。Slave只能在Master啟動communication時回應,他不能主動發起communication。
在我們的開發板,STM32F407G-DISC1 是 SPI Master,而 LIS3DSH 則是 SPI Slave。透過 SPI,STM32F407G-DISC1 可以向 LIS3DSH 發送命令、讀取sensor的data等。
以下是SPI Master和Slave的內部硬體結構:

(Reference: [SPI (Serial Peripheral Interface) 串列 (序列) 週邊介面](https://magicjackting.pixnet.net/blog/post/164725144))
SPI常見的是3-wire或4-wire,我們開發板的LIS3DSH則是4-wire。上圖的4-wire用的名稱分別是MOSI、MISO、SCLK、SS
- MOSI:Master輸出data,slave接收data
- MISO:Master接收data,slave傳送data
- SCLK:時脈信號,由master產生並控制
- SS:這個signal是用來選擇哪一個slave,由master控制。Slave只有在 /SS 信號是低電位時才會對master的指令有反應

在 [[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing) 的第25頁可以看到,這份documentation用的名稱是CS、SPC、SDI、SDO
- CS (Chip Select):相當於前述的SS,由SPI maseter控制。在transmission一開始的時候,他必須要在低電位,當transmission結束,就回到高電位(如上圖所示)
- SPC:serial port clock,由SPI master控制,用於同步通信,相當於前述的SCLK
- SDI:slave端接收data
- SDO:slave端傳送data

(Reference: [Introduction to SPI Interface](https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html), PS. 此圖中的subnode就是slave, main就是master)

以上是 [[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing) 第25頁之內容
從前述內容可以看到,一次的read/write是16 bits
- 第0個bit表示這是read/write,若是0則代表要write,若是1則代表要read
- 再來的第1到第7個bit,則是register的address
- 最後的第8到第15個bit,則是要寫入的data或是要read的data
### SPI Read
[[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing) 的第26頁:

### SPI Write
[[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing) 的第27頁:

### Short Summary
Communication Step:
1. 當Master啟動communication,他會選定他要communicat的slave,並將 `CS` 設定為低電位
2. Master產生clock signal來做synchronous communication
3. 當communication結束,Master將CS定為高電位
Data Transfer:
- Data的傳送可以是full duplex,也就是 Master 和 Slave 可以同時發送和接收data
- Data transfer是serial,一個bit一個bit進行,從most significant bit到least significant bit
[STM32F407 Reference Manual](https://drive.google.com/file/d/1fatEEbkJzNamSldnF7oRBnQ7XJmMc1l-/view?usp=sharing) P.873也有介紹SPI
## Configuration in STM32CubeIDE
開啟 `.ioc` 檔案,點 `Connectivity`,點 `SPI1`,`Mode` 選 `Full-Duplex Master` (把Data的傳送方設定為Full duplex,且控制板是Master),這時應該會看到 `PA5`, `PA6`, `PA7` 從黃色變綠色

可以看到 `PA5` 是 `SCK`,`PA6` 是 `MISO`,`PA7` 是 `MOSI`

對 `PE0` 點左鍵,點選 `GPIO_EXT10`

對 `PE3` 點左鍵,點選 `GPIO_Output`

`Save` and `Generate code`
:::info
`PE0` & `PE3`

[Discovery kit with STM32F407VG MCU](https://drive.google.com/file/d/1g46_RRT_xp9N05Mal4vouiHfJFGbltRt/view?usp=drive_link) p.26
- PE0
- INT1 通常表示Interrupt pin 1。在 Motion Sensor,Interrupt pin 通常用於通知microcontroller有新的data可用,或者某種事件發生。當 Motion Sensor 檢測到特定的條件時,它可能會發出Interrupt,讓microcontroller知道需要處理某些事情。
- PE3
- CS 代表 Chip Select,I2C/SPI 表示這個pin在 I2C 或 SPI 模式下被用作 Chip Select
- 在 SPI 通信中,CS (Chip Select) 通常用於選擇要進行communication的設備。當 CS 被SPI Master設置為低電位時,表示SPI Master開始與SPI Slave設備進行 SPI 通訊。因此,當 PE3 被用作 SPI 的 CS 時,它可能被設置為低電位以啟動 Motion Sensor 的 SPI 通訊

:::
## Add Some Code
將以下一些助教提供的code加入 `main.c` (完整的 `main.c` 程式碼附在最後)
```cpp
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define LIS3DSH_WHO_AM_I_ADDR 0x0F
#define LIS3DSH_STAT_ADDR 0x18
#define LIS3DSH_CTRL_REG4_ADDR 0x20
#define LIS3DSH_CTRL_REG1_ADDR 0x21
#define LIS3DSH_CTRL_REG2_ADDR 0x22
#define LIS3DSH_CTRL_REG3_ADDR 0x23
#define LIS3DSH_CTRL_REG5_ADDR 0x24
#define LIS3DSH_CTRL_REG6_ADDR 0x25
#define LIS3DSH_STATUS_ADDR 0x27
#define LIS3DSH_OUT_X_L_ADDR 0x28
#define LIS3DSH_OUT_X_H_ADDR 0x29
#define LIS3DSH_OUT_Y_L_ADDR 0x2A
#define LIS3DSH_OUT_Y_H_ADDR 0x2B
#define LIS3DSH_OUT_Z_L_ADDR 0x2C
#define LIS3DSH_OUT_Z_H_ADDR 0x2D
#define LIS3DSH_ST1_1_ADDR 0x40
#define LIS3DSH_ST1_2_ADDR 0x41
#define LIS3DSH_THRS1_1_ADDR 0x57
#define LIS3DSH_MASK1_B_ADDR 0x59
#define LIS3DSH_MASK1_A_ADDR 0x5A
#define LIS3DSH_SETT1_ADDR 0x5B
#define LIS3DSH_OUTS1_ADDR 0x5F
/* USER CODE END PM */
```
:::info
- 這裡是把一些可能會用到的LIS3DSH的register的address寫成mrcro,方便後面使用
- [[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing) 的p.29的Table 15有列出各個register名稱和其address
:::
```cpp
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void MEMS_Write(uint8_t address, uint8_t data){
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &address, 1, 10);
HAL_SPI_Transmit(&hspi1, &data, 1, 10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
}
void MEMS_Read(uint8_t address,uint8_t *data){
address |= 0x80;
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &address, 1, 10);
HAL_SPI_Receive(&hspi1, data, 1, 10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
}
void LED_Task(void *pvParameter)
{
for(;;){
uint8_t data;
MEMS_Read(LIS3DSH_WHO_AM_I_ADDR, &data);
if(data == 0x3F){
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}
vTaskDelay(500/portTICK_RATE_MS);
}
}
/* USER CODE END 0 */
```
:::info
```cpp=
void MEMS_Write(uint8_t address, uint8_t data){
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &address, 1, 10);
HAL_SPI_Transmit(&hspi1, &data, 1, 10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
}
```
從前面的說明可以知道,當SPI Master裝置 (也就是我們的開發板),要和SPI Slave裝置 (也就是LIS3DSH) 進行SPI communication時,SPI Master要先把Chip Select (CS) 設置為低電位,而 `PE3` 就是 SPI 的 CS。當他們的communication結束時,SPI Master就會把 CS 設置為高電位。
```cpp=2
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
```
- 所以這一行code就是說要把GPIOE port的第3個pin,也就是 `PE3` 設置為低電位。
```cpp=3
HAL_SPI_Transmit(&hspi1, &address, 1, 10);
HAL_SPI_Transmit(&hspi1, &data, 1, 10);
```
- 前面 `.ioc` 有設置好並generate code的話應該會自動產出 `SPI_HandleTypeDef hspi1;` 這個SPI的handler。而這兩段code的第一個參數就是決定用 `hspi1` 這個SPI
- `&address` 則是要寫入的register的address;`&data` 則是要寫入的data
- `1` 是指要傳送的data的size是1 byte
- `10` 則是timeout duration (以millisecond為單位)
:::
:::info
```cpp=1
void MEMS_Read(uint8_t address,uint8_t *data){
address |= 0x80;
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &address, 1, 10);
HAL_SPI_Receive(&hspi1, data, 1, 10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
}
```

前面 SPI Read 的部分有提到,若是要 Read 的話,要把bit 0設置為1,剩下的7個bit則是register的address;若是Write的話則是0,所以前面 `MEMS_Write` 沒有去動它,
```cpp=2
address |= 0x80;
```
所以這裡就是把address的最高位bit設置為1
:::
:::info
```cpp=
void LED_Task(void *pvParameter)
{
for(;;){
uint8_t data;
MEMS_Read(LIS3DSH_WHO_AM_I_ADDR, &data);
if(data == 0x3F){
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}
vTaskDelay(500/portTICK_RATE_MS);
}
}
```
```cpp=5
MEMS_Read(LIS3DSH_WHO_AM_I_ADDR, &data);
```
- 這裡就是去讀 `WHO_AM_I` register,並把取得的data存到 `data`
[[**LIS3DSH**] MEMS digital output motion sensor: ultra-low-power high-performance three-axis "nano" accelerometer](https://drive.google.com/file/d/1iu1itVmMhB6YWI6NDVBz3cMH6m32VpMl/view?usp=sharing) 第32頁:

- 可以看到這個register的default value是 `0011 1111`
- `Who_Am_I` register是用來識別裝置用的,他的值是一個固定值,開發人員可以藉由讀取這個register的value來確認他們是否與預期的device進行通訊
- 在 LIS3DSH 的 `Who_Am_I` register的值是 `00111111`(`0x3F`)。所以,藉由讀取這個registr的值,我們可以確保我們正在與我們預期的 LIS3DSH 做communication。
所以可以知道 `LED_Task` 在做的事情就是,若確認從SPI讀到的data是 `0x3F` ,也就是我們正在跟 LIS3DSH 做communication,那綠色LED燈就閃爍
:::
## HAL_SPI_Transmit
HAL的官方user manual [[UM1725 Description of STM32F4 HAL and low-layer drivers]](https://drive.google.com/file/d/1YbZT-6qF25z9Frmt4rmDQ8ANFWGAj36g/view?usp=sharing) 第1037頁:

## HAL_SPI_Receive
HAL的官方user manual [[UM1725 Description of STM32F4 HAL and low-layer drivers]](https://drive.google.com/file/d/1YbZT-6qF25z9Frmt4rmDQ8ANFWGAj36g/view?usp=sharing) 第1037頁:

## State Machine of LIS3DSH
依照 [LIS3DSH: 3-axis digital output accelerometer](https://drive.google.com/file/d/13lmWzrJA0MERsK2inaA48x4zjTfCU25x/view?usp=sharing) 第41頁的內容:


- LIS3DSH 有提供++2個state machine++,這兩個state machine彼此是獨立的,++每個state mahcine最多可以有16個states++
- ++當state machine到達 `End state`,或者是特定的指令被執行,就會發出interrupt++
第45-47頁的內容:


- ++每個state都會有 `NEXT`/`RESET` condition。其中 `RESET` condition被定義在 `ST1_x` 和 `ST2_x` 這兩個registers的MSB的部分;而 `NEXT` condition則被定義在`ST1_x` 和 `ST2_x` 這兩個registers的LSB的部分++
- `ST1` 和 `ST2` 是指state machine 1和2
- 每個state machine有16個state,所以`ST1_x` 和 `ST2_x` 的 `x` 就是指第某個state,所以 `x` = 1~16
- ++從 `START` 開始,會先檢查 `RESET` 的條件有沒有成立,若有成立那就 `RESET`;若沒有成立,那就檢查 `NEXT` 的條件有沒有成立,有的話就到下個state;若 `NEXT` 也沒有成立,那就停在該state等新的資訊++

- 當我們在state `n` 的時候,後續只有可能有三種情況:
- 跳到下一個state,也就是state `n+1`
- 或者是Reset Point所設定的state
- 或者是停留在state `n`
- 當 `RESET` condition成立,就會跳到 Reset Point
- 當 `RESET` condition不成立,`NEXT` 成立,那就會到下一個state
- 當到達了 `END` state,就會發出一個interrupt
### Example of State Machine Configurations - Wake-up
接下來看 [LIS3DSH: 3-axis digital output accelerometer](https://drive.google.com/file/d/13lmWzrJA0MERsK2inaA48x4zjTfCU25x/view?usp=sharing) p.76提供的一個例子,看要怎麼設定registers才能達到特定state machine的效果,這裡看的是 Wake-up 這個state machine,也是lab 3會用到的

- 這個state machine只有 `START` 跟 `END`。所以只要 `RESET` 不成立,`NEXT` 成立,那就會從 `START` 轉換到 `END`,那就會產生一個interrupt
- Lab 3需要的是,只要一晃動板子,就產生interrupt,所以 Wake-up這個state machine很符合我們的需求
- 這個state machine的 `RESET` 是 `NOP`,`NEXT` 則是 `GNTH1`,接下來看Operation Code來了解 `NOP` 跟 `GNTH1` 是什麼
### Operation Codes
第48頁介紹Operation Codes

- Op code分成兩種,第一種是 `RESET/NEXT`,第二種是 `COMMANDS`
以下是 `RESET/NEXT` condition 的OP code:


- `NOP` 的 OP code是 `0x0`,也就是 `0000`
- 可以看到若 `NOP` 在 `RESET` condtion,那就不做事直接跳到 `NEXT` condition

- `GNTH1` 的OP code是 `0x5`,也就是 `0101`
- `GNTH1` 則是若 `any/triggered axis of the data sample set (X, Y, Z, V) is greater than threshold 1 level`,則condition就成立
- 也就是只要 X, Y, Z 這三個軸,其中一個軸的data大於threshold 1的level,就會成立
- Threshold的定義:`THRS1_y` + Hysteresis
- `y` = 1 or 2,是指state machine 1 or state machine 2
- Hysteresis
- State Machine 1:由 `CTRL_REG1` 的以下三個bit決定:`HYST2_1`, `HYST1_1`, 以及 `HYST0_1`
- 由於前面 Wake-up state machine只有設定 `CTRL_REG1`,所以可以知道Wake-up state machine那邊的設定是設定在state machine 1
接下來來看前面設定的7個register
### `CTRL_REG1`
| Register | Address | Value |
| -------- | -------- | -------- |
| `CTRL_REG1` | `0x21` | `0x01` (`0000 0001`) |

- 這裡可以看到,前面 `GNTH1` 的 hysteresis 都被設置為0
- 另外也將 `SM1_EN` 設置為1
### `CTRL_REG3`
| Register | Address | Value |
| -------- | -------- | -------- |
| `CTRL_REG3` | `0x23` | `0x48` (`0100 1000`) |

### `CTRL_REG4`
| Register | Address | Value |
| -------- | -------- | -------- |
| `CTRL_REG4` | `0x20` | `0x67` (`0110 0111`) |


- 將data rate設置為10 Hz,並enable X, Y, Z-axis
### `CTRL_REG5`
| Register | Address | Value |
| -------- | -------- | -------- |
| `CTRL_REG5` | `0x24` | `0x00` |

### `THRS1_1`
| Register | Address | Value |
| -------- | -------- | -------- |
| `THRS1_1` | `0x57` | `0x55` (`0101 0101`) |

(第一次接觸這東西,很多都還沒有很瞭解,不太曉得為什麼這裡要設定為 `0x55` @@)
### `ST1_1` & `ST1_2`
| Register | Address | Value |
| -------- | -------- | -------- |
| `ST1_1` | `0x40` | `0x05` (`0000 0101`) |
| `ST1_2` | `0x41` | `0x11` (`0001 0001`) |

<!-- (因為第一次接觸,不太確定我這部分的理解對不對,有給上下文給ChatGPT確認我的理解對不對,目前他是說我的理解沒錯> <) -->
- `ST1_1`
- 前面有提到 `RESET` condition被定義在 `ST1_x` 和 `ST2_x` 這兩個registers的MSB的部分;而 `NEXT` condition則被定義在 `ST1_x` 和 `ST2_x` 這兩個registers的LSB的部分
- 再加上前面有看到Op code的部分,`0x0` 是 `NOP`,`0x5` 是 `GNTH1`
- 所以將 `ST1_1` 的值設為 `0x05` 就是指 `RESET` 是 `NOP`,而 `NEXT` 是 `GNTH1`
- `ST1_2`
- 前面有提到Op code有分成 `RESET/NEXT` condition 和 `COMMAND`
- 那對照 `COMMAND` 的部分的話,可以知道 `0x11` 是指 `CONT`,見下說明:



:::info
本來想把每個register為什麼這樣設定都弄懂,但因為第一次接觸這主題,覺得document好像也沒寫得很詳細,所以目前就只有理解到這樣Q_Q
:::
## Code for Testing
以下是助教提供的測試用的 `main.c` 的程式碼,若前面設定都沒問題的話,綠色LED燈會閃爍:
```cpp=
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define LIS3DSH_WHO_AM_I_ADDR 0x0F
#define LIS3DSH_STAT_ADDR 0x18
#define LIS3DSH_CTRL_REG4_ADDR 0x20
#define LIS3DSH_CTRL_REG1_ADDR 0x21
#define LIS3DSH_CTRL_REG2_ADDR 0x22
#define LIS3DSH_CTRL_REG3_ADDR 0x23
#define LIS3DSH_CTRL_REG5_ADDR 0x24
#define LIS3DSH_CTRL_REG6_ADDR 0x25
#define LIS3DSH_STATUS_ADDR 0x27
#define LIS3DSH_OUT_X_L_ADDR 0x28
#define LIS3DSH_OUT_X_H_ADDR 0x29
#define LIS3DSH_OUT_Y_L_ADDR 0x2A
#define LIS3DSH_OUT_Y_H_ADDR 0x2B
#define LIS3DSH_OUT_Z_L_ADDR 0x2C
#define LIS3DSH_OUT_Z_H_ADDR 0x2D
#define LIS3DSH_ST1_1_ADDR 0x40
#define LIS3DSH_ST1_2_ADDR 0x41
#define LIS3DSH_THRS1_1_ADDR 0x57
#define LIS3DSH_MASK1_B_ADDR 0x59
#define LIS3DSH_MASK1_A_ADDR 0x5A
#define LIS3DSH_SETT1_ADDR 0x5B
#define LIS3DSH_OUTS1_ADDR 0x5F
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void MEMS_Write(uint8_t address,uint8_t data){
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &address, 1, 10);
HAL_SPI_Transmit(&hspi1, &data, 1, 10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
}
void MEMS_Read(uint8_t address,uint8_t *data){
address |= 0x80;
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &address, 1, 10);
HAL_SPI_Receive(&hspi1, data, 1, 10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
}
void LED_Task(void *pvParameter)
{
for(;;){
uint8_t data;
MEMS_Read(LIS3DSH_WHO_AM_I_ADDR, &data);
if(data == 0x3F){
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}
vTaskDelay(500/portTICK_RATE_MS);
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();
/* USER CODE BEGIN 2 */
xTaskCreate(
LED_Task,
"LED_Task",
128,
NULL,
1,
NULL);
vTaskStartScheduler();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief SPI1 Initialization Function
* @param None
* @retval None
*/
static void MX_SPI1_Init(void)
{
/* USER CODE BEGIN SPI1_Init 0 */
/* USER CODE END SPI1_Init 0 */
/* USER CODE BEGIN SPI1_Init 1 */
/* USER CODE END SPI1_Init 1 */
/* SPI1 parameter configuration*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI1_Init 2 */
/* USER CODE END SPI1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
/*Configure GPIO pin : PE3 */
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/*Configure GPIO pin : PD12 */
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/*Configure GPIO pin : PE0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM7 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM7) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
```