HAL_SPI_Transmit'te DataSize Ne İşe Yarıyor ?

Not: Bu yazıdaki kodlar ve register isimleri (TXDR, CFG1, DSIZE) STM32H7 HAL'ine göredir.

SPI ile çalışırken, IOC dosyasında DataSize parametresine 8-bit, 16-bit veya 32-bit gibi değerler verebiliyorum. Fakat veri gönderirken kullandığımız fonksiyonun signature'ı şöyle:

HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)

Burada pData, uint8_t *pData olarak tanımlanmıştır.

Bunu fark edince şu sorular aklıma geldi: DataSize'ı IOC dosyasında neden ayarlıyorum? DataSize tam olarak ne işe yarıyor? Bu durum veri kaybına yol açar mı?

Diyelim ki yapımız şu:

uint16_t TX_Data[] = {1, 2, 3, 4};
HAL_SPI_Transmit_IT(&hspi1, (uint8_t*)TX_Data, 4);

1. uint8_t* cast'i neden veri kaybettirmez?

uint16_t TX_Data[4], memory'de 8 byte'lık ardışık bir alandır. uint16_t* de uint8_t* de aynı address'i gösterir. Pointer type, compiler'a o address'ten kaç byte okunacağını ve ++ ile kaç byte ilerleneceğini söyler.

Yani (uint8_t*)TX_Data cast'i veriyi değiştirmez. Sadece compiler'a "bunu byte byte işle" deriz.

2. HAL pointer'ı tekrar orijinal type'ına cast ediyor

HAL_SPI_Transmit_IT, verdiğimiz uint8_t* pointer'ını hspi->pTxBuffPtr içinde saklar. Transfer başlamadan önce DataSize'a bakarak hangi ISR'ın çalışacağını seçer. 16-bit için seçilen SPI_TxISR_16BIT, pointer'ı tekrar uint16_t* olarak cast eder.

/* Set the function for IT treatment */
if (hspi->Init.DataSize > SPI_DATASIZE_16BIT)
{
  hspi->TxISR = SPI_TxISR_32BIT;
}
else if (hspi->Init.DataSize > SPI_DATASIZE_8BIT)
{
  hspi->TxISR = SPI_TxISR_16BIT;
}
else
{
  hspi->TxISR = SPI_TxISR_8BIT;
}

SPI_TxISR_16BIT içinde işimize yarayan kısım şu:

static void SPI_TxISR_16BIT(SPI_HandleTypeDef *hspi)
{
  /* Transmit data in 16 Bit mode */
  __IO uint16_t *ptxdr_16bits = (__IO uint16_t *)(&(hspi->Instance->TXDR));

  *ptxdr_16bits = *((uint16_t *)hspi->pTxBuffPtr);   // uint16_t* cast'i, 2 byte okunur
  hspi->pTxBuffPtr += sizeof(uint16_t);              // pointer 2 byte ilerler
  hspi->TxXferCount--;                               // Size kaç verildiyse o kadar azalır

  /* ... TxXferCount == 0 olunca TXP interrupt kapatılır ... */
}

Yani:

  • pTxBuffPtr her seferinde 2 byte (sizeof(uint16_t)) ilerler. Böylece bir sonraki transferde dizideki bir sonraki uint16_t elemanı okunur.
  • TxXferCount (bizim verdiğimiz Size) her ISR çağrısında 1 azalır. Yani ISR'ın kaç kez çalışacağını, başka bir deyişle kaç tane 16-bit frame gönderileceğini belirler. Bu değer byte sayısı değildir.

Yani zincir şöyle oluyor:

  1. uint16_t a[], (uint8_t*)a cast'i ile fonksiyona parameter olarak veriliyor.
  2. HAL bunu uint8_t* olarak pTxBuffPtr içinde saklıyor.
  3. HAL_SPI_Transmit_IT içinde DataSize kontrol ediliyor ve ISR seçiliyor.
  4. ISR çalışırken SPI_TxISR_16BIT içinde tekrar uint16_t* cast'i yapılıyor ve o address'ten 2 byte birden okunuyor.

3. Peki DataSize tam olarak ne işe yarıyor?

CubeMX'te seçtiğimiz DataSize, MX_SPI1_Init() içinde şöyle set edilir:

hspi1.Init.DataSize = SPI_DATASIZE_16BIT;

Bu değer, SPI'ın CFG1 register'ındaki DSIZE field'ına yazılır ve üç yerde kullanılır.

3.1. Hardware: Bir frame kaç bit?
Bir frame'de SCK'nın kaç clock pulse üreteceğini belirler. 16-bit seçtiğimizde, CS aktifken 16 clock pulse çıkar ve 16 bit shift edilir.

3.2. Hardware: TXDR access width ve FIFO packing
TXDR'a 8, 16 veya 32-bit access yapılabilir. Access width, FIFO'ya tek seferde kaç bit'lik data girdiğini belirler. Access width frame size'dan küçük olmamalıdır. Büyükse data packing devreye girer: örneğin 16-bit frame'de yapılan 32-bit write, FIFO'ya tek seferde 2 frame koyar. Access width DSIZE ile uyumsuz olursa frame'ler yanlış pack edilir.

3.3. Software: HAL hangi ISR'ı seçecek?
Bölüm 2'deki seçim kodu, DataSize'a bakarak TxISR function pointer'ını set eder. pData'nın uint8_t* olarak gelip ISR içinde uint16_t* olarak cast edilmesinin kararı burada verilir.

4. Peki veri kaybı ne zaman olur?

DataSize, buffer'ın element type'ı ile uyumluysa veri kaybı olmaz. Uyumsuzsa olur. Aynı diziyi DataSize = 8-bit iken gönderdiğimizi düşünelim:

uint16_t TX_Data[] = {1, 2, 3, 4};   // memory'de: 01 00 02 00 03 00 04 00
HAL_SPI_Transmit_IT(&hspi1, (uint8_t*)TX_Data, 4);   // DataSize = 8BIT

Size, byte sayısı değil frame sayısıdır. 8-bit modda 4 frame = 4 byte gider: 01 00 02 00. Yani dizinin sadece ilk iki elemanı gönderilir; 3 ve 4 hiç gitmez.

Kısacası (uint8_t*) cast'i tek başına veri kaybettirmez. Veri kaybına, DataSize'ın buffer type'ı ile uyumsuz olması yol açar.