[STM32學習記錄-7] AS5047P 旋轉位置感測器/磁性編碼器使用教學

系列:STM32學習記錄 Posted on 2022-04-16

前言

AMS AS5047P 是一款旋轉位置感測器/磁性編碼器。

它擁有包含 SPI、ABI、UVW 及 PWM 的多種使用模式,及 14 位元的高解析度,和 28krpm 的高反應速度,還擁有動態角度誤差補償(Dynamic angle error compensation,DAEC),非常適合搭配馬達進行控制。

本文將以 NUCLEO-F446RE(STM32F446RE)與 STM32 HAL 作為示範,簡單介紹 AS5047P 的用法。本篇的範例程式有放在 GitHub

SPI

AS5047P 透過 SPI 進行通訊。其對 SPI 的要求為:

  • Mode = 1(CPOL = 0,CPHA = 1)
    • 空閒時,SCK 時鐘訊號為低電平(0)。
    • 資料在第二個邊緣取樣(即負緣)。
  • CSn (Chip select)為低電平有效。
  • 資料長度為 16 個位元。其中 MSB 為偶同位(Even parity)位元。
  • 位元順序為 MSB 在前(MSB first)。
  • SCK 最大速度為 10 MHz。
  • 只支援從機模式(Slave operation mode)。

設定範例:

 1static void SPI_Init(void)
 2{
 3  SPI_HandleTypeDef hspi1;
 4  
 5  hspi1.Instance = SPI1;
 6  hspi1.Init.Mode = SPI_MODE_MASTER;
 7  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
 8  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
 9  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
10  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
11  hspi1.Init.NSS = SPI_NSS_SOFT;
12  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
13  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
14  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
15  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
16  hspi1.Init.CRCPolynomial = 10;
17  
18  if (HAL_SPI_Init(&hspi1) != HAL_OK)
19  {
20    Error_Handler();
21  }
22}

通訊格式

AS5047P 有 3 種 SPI 訊框格式。

Command Frame

BitName描述
15PARC偶同位(Even parity),使整個訊框的 1 為偶數個。
14R/W0 代表要寫入。1 代表要讀取。
13:0ADDR要讀寫的暫存器位置。

讀取「NOP (0x0000)」暫存器等同一個 nop(no operation,無操作)指令。

Read Data Frame

BitName描述
15PARC偶同位(Even parity),使整個訊框的 1 為偶數個。
14EF0 代表沒有錯誤發生。1 代表有錯誤發生。
13:0DATA資料。

要讀取資料時,先使用「Command Frame」傳輸要讀取的位置,AS5047P 會在 CS 上拉並重新下拉後的下一個讀取指令時,在 MISO 上傳輸「Read Data Frame」。

Write Data Frame

BitName描述
15PARC偶同位(Even parity),使整個訊框的 1 為偶數個。
140永遠為 0
13:0DATA資料。

要寫入資料時,先使用「Command Frame」傳輸要寫入的位置,再使用「Write Data Frame」傳輸要寫入的資料。

當「Write Data Frame」在 MOSI 上傳輸時,AS5047P 會在 MISO 上傳輸該暫存器目前的值(舊的值),並在下一次的「Command Frame」在 MOSI 上傳輸時,AS5047P 會在 MISO 上傳輸該暫存器實際的值。

程式

完整的程式可以到 GitHub: ziteh/as5047p_driver 查看。

位元操作

1#define BIT_MODITY(src, n, val) ((src) ^= (-(val) ^ (src)) & (1UL << (n)))
2#define BIT_READ(src, n) (((src) >> (n)&1U))
3#define BIT_TOGGLE(src, n) ((src) ^= 1UL << (n))

傳輸「Command Frame」

 1void as5047p_send_command(bool is_read_cmd, uint16_t address)
 2{
 3  uint16_t frame = address & 0x3FFF;
 4
 5  /* R/W: 0 for write, 1 for read. */
 6  BIT_MODITY(frame, 14, is_read_cmd ? 1 : 0);
 7
 8  /* Parity bit(even) calculated on the lower 15 bits. */
 9  if (!is_even_parity(frame))
10  {
11    BIT_TOGGLE(frame, 15);
12  }
13
14  as5047p_spi_transmit(frame);
15}

寫入資料到指定的暫存器

 1void as5047p_send_data(uint16_t address, uint16_t data)
 2{
 3  uint16_t frame = data & 0x3FFF;
 4
 5  /* Data frame bit 14 always low(0). */
 6  BIT_MODITY(frame, 14, 0);
 7
 8  /* Parity bit(even) calculated on the lower 15 bits. */
 9  if (!is_even_parity(frame))
10  {
11    BIT_TOGGLE(frame, 15);
12  }
13
14  as5047p_send_command(false, address);
15  as5047p_spi_transmit(frame);
16}

讀取資料自指定的暫存器

1uint16_t as5047p_read_data(uint16_t address)
2{
3  as5047p_send_command(true, address);
4  uint16_t received_data = as5047p_spi_receive();
5  return received_data;
6}

讀取角度資訊,可選擇是否啟用動態角度誤差補償(DAEC)

讀取「ANGLECOM (0x3FFF)」可取得有 DAEC 的角度數值,讀取「ANGLEUNC (0x3FFE)」可取得無 DAEC 的角度資訊。

 1int as5047p_get_angle(bool with_daec, float *angle_degree)
 2{
 3  uint16_t address;
 4  if (with_daec)
 5  {
 6    /* Measured angle WITH dynamic angle error compensation(DAEC). */
 7    address = AS5047P_ANGLECOM;
 8  }
 9  else
10  {
11    /* Measured angle WITHOUT dynamic angle error compensation(DAEC). */
12    address = AS5047P_ANGLEUNC;
13  }
14
15  uint16_t data = as5047p_read_data(address);
16  if (BIT_READ(data, 14) == 0)
17  {
18    *angle_degree = (data & 0x3FFF) * (360.0 / 0x4000);
19    return 0; /* No error occurred. */
20  }
21  return -1; /* Error occurred. */
22}

偶同位計算

 1bool is_even_parity(uint16_t data)
 2{
 3  uint8_t shift = 1;
 4  while (shift < (sizeof(data) * 8))
 5  {
 6    data ^= (data >> shift);
 7    shift <<= 1;
 8  }
 9  return !(data & 0x1);
10}

SPI 通訊

 1void as5047p_spi_transmit(uint16_t data)
 2{
 3  delay(T_CSN_DELAY);
 4  as5047p_spi_select();
 5  as5047p_spi_send(data);
 6  as5047p_spi_deselect();
 7}
 8
 9uint16_t as5047p_spi_receive(void)
10{
11  delay(T_CSN_DELAY);
12  as5047p_spi_select();
13  uint16_t data = as5047p_spi_read();
14  as5047p_spi_deselect();
15  return data;
16}
17
18void delay(volatile uint16_t t)
19{
20  while (t--)
21  {
22    __asm__("nop"); /* Do nothing. */
23  }
24}
25
26void as5047p_spi_send(uint16_t data)
27{
28  HAL_SPI_Transmit(&hspi1, (uint8_t *)&data, 1, HAL_MAX_DELAY);
29}
30
31uint16_t as5047p_spi_read(void)
32{
33  uint16_t data = 0;
34  HAL_SPI_Receive(&hspi1, (uint8_t *)&data, 1, HAL_MAX_DELAY);
35  return data;
36}
37
38void as5047p_spi_select(void)
39{
40  HAL_GPIO_WritePin(AS5047P_SS_GPIO_Port, AS5047P_SS_Pin, GPIO_PIN_RESET);
41}
42
43void as5047p_spi_deselect(void)
44{
45  HAL_GPIO_WritePin(AS5047P_SS_GPIO_Port, AS5047P_SS_Pin, GPIO_PIN_SET);
46}

若要在 STM32 HAL 以外的平臺使用的話,只需要修改 as5047p_spi_send()as5047p_spi_read()void as5047p_spi_select()as5047p_spi_deselect() 這 4 個函式的實作就好了。

後記

最近在做馬達的閉迴路位置控制,因此買了這個 AS5047P 來用,就順便寫了本篇文章做記錄。而此程式我也有放在 GitHub 上:ziteh/as5047p_driver

若有問題或內容有誤還請告知,謝謝!

相關連結



留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)

comments powered by Disqus