5.5 CRC Guide

This chapter describes the file composition and the 17 public functions of the CRC driver in the N32G41x standard peripheral library: computing CRC32 and CRC16 checksums, the CRC32 independent data register, the CRC16 clear and endianness configuration, and reading and writing the LRC checksum. The CalCRC example recomputes the hardware results with software algorithms, and the CRC_DMA example moves the data by DMA to complete the checksums. Read the generator polynomials and the CRC32 and CRC16 usage flows together with the “CRC calculation unit” chapter of the user manual, and take the errata sheet as the authority on known chip issues.

5.5.1 Related files

Relative path Purpose
firmware/n32g41x_std_periph_driver/inc/n32g41x_crc.h Defines the four macros for CRC16 clearing, its clear mask, the cleared result value and the endianness, and declares 17 public functions in the four sections CRC32 calculation, CRC16 low-level configuration, CRC16 calculation and CRC16 LRC
firmware/n32g41x_std_periph_driver/src/n32g41x_crc.c Implements those 17 functions: reads and writes the CRC32 and CRC16 data, result and independent data registers, configures the CRC16 control bits, and reads and writes the LRC register
projects/n32g41x_EVAL/examples/CRC/ CRC example root directory, containing the 2 examples CalCRC and CRC_DMA
projects/n32g41x_EVAL/examples/CRC/CalCRC/ Project directory composition of that example: MDK-ARM/, EWARM/, inc/, src/, readme.txt
projects/n32g41x_EVAL/bsp/ Board support files shared by both examples: serial log initialisation and printf redirection (log.h, log.c)

5.5.2 API reference

Search API17 functions

The following 17 public functions are listed in the declaration order of n32g41x_crc.h in 4 groups; each entry gives a summary and the original comment above the function in n32g41x_crc.c.

CRC32 calculation

CRC32_ResetCrc, CRC32_CalcCrc, CRC32_CalcBufCrc, CRC32_GetCrc, CRC32_SetIDat, CRC32_GetIDat

CRC16 low-level configuration

__CRC16_SetLittleEndianFmt, __CRC16_SetBigEndianFmt, __CRC16_SetCleanEnable, __CRC16_SetCleanDisable

CRC16 calculation

__CRC16_CalcCrc, __CRC16_SetCrc, __CRC16_GetCrc, CRC16_CalcBufCrc, CRC16_CalcCRC

CRC16 LRC

__CRC16_SetLRC, __CRC16_GetLRC

void CRC32_ResetCrc(void)Resets the CRC Data register (DAT).
Source comment
name    CRC32_ResetCrc.
fun     Resets the CRC Data register (DAT).
param   none
return  none
uint32_t CRC32_CalcCrc(uint32_t Data)Computes the 32-bit CRC of a given data word(32-bit).
Source comment
name    CRC32_CalcCrc.
fun     Computes the 32-bit CRC of a given data word(32-bit).
param   Data :
          - data word(32-bit) to compute its CRC
return  32-bit CRC
uint32_t CRC32_CalcBufCrc(const uint32_t pBuffer[], uint32_t BufferLength)Computes the 32-bit CRC of a given buffer of data word(32-bit).
Source comment
name    CRC32_CalcBufCrc.
fun     Computes the 32-bit CRC of a given buffer of data word(32-bit).
param   pBuffer :
          - pointer to the buffer containing the data to be computed
param   BufferLength :
          - length of the buffer to be computed
return  32-bit CRC

Use note: The implementation writes one word into CRC->CRC32DAT at a time and returns CRC->CRC32D after the loop; it does not rewrite CRC32CTRL internally, so the initial value is the reset default 0xFFFF_FFFF or the result of the previous calculation.

uint32_t CRC32_GetCrc(void)Returns the current CRC value.
Source comment
name    CRC32_GetCrc.
fun     Returns the current CRC value.
param   none
return  32-bit CRC
void CRC32_SetIDat(uint8_t IDValue)Stores an 8-bit data in the Independent Data(ID) register.
Source comment
name    CRC32_SetIDat.
fun     Stores an 8-bit data in the Independent Data(ID) register.
param   IDValue :
          - 8-bit value to be stored in the ID register
return  none
uint8_t CRC32_GetIDat(void)Returns the 8-bit data stored in the Independent Data(ID) register.
Source comment
name    CRC32_GetIDat.
fun     Returns the 8-bit data stored in the Independent Data(ID) register.
param   none
return  8-bit value of the ID register
void __CRC16_SetLittleEndianFmt(void)Set the calculate data to little endian format.
Source comment
name    __CRC16_SetLittleEndianFmt.
fun     Set the calculate data to little endian format.
param   none
return  none
void __CRC16_SetBigEndianFmt(void)Set the calculate data to big endian format.
Source comment
name    __CRC16_SetBigEndianFmt.
fun     Set the calculate data to big endian format.
param   none
return  none
void __CRC16_SetCleanEnable(void)Enable Clean CRC16 value.
Source comment
name    __CRC16_SetCleanEnable.
fun     Enable Clean CRC16 value.
param   none
return  none
void __CRC16_SetCleanDisable(void)Disable Clean CRC16 value.
Source comment
name    __CRC16_SetCleanDisable.
fun     Disable Clean CRC16 value.
param   none
return  none
uint16_t __CRC16_CalcCrc(uint8_t Data)Computes the 16-bit CRC of a given data word(8-bit).
Source comment
name    __CRC16_CalcCrc.
fun     Computes the 16-bit CRC of a given data word(8-bit).
param   Data :
          - data word(8-bit) to compute its CRC
return  16-bit CRC
void __CRC16_SetCrc(uint8_t Data)Writes an 8-bit data to the CRC16 data register (CRC16DAT).
Source comment
name    __CRC16_SetCrc.
fun     Writes an 8-bit data to the CRC16 data register (CRC16DAT).
param   Data :
          - 8-bit data value to be written to the CRC16 data register
return  none
uint16_t __CRC16_GetCrc(void)Returns the current CRC value.
Source comment
name    __CRC16_GetCrc.
fun     Returns the current CRC value.
param   none
return  16-bit CRC
uint16_t CRC16_CalcBufCrc(const uint8_t pBuffer[], uint32_t BufferLength)Computes the 16-bit CRC of a given buffer of data word(8-bit).
Source comment
name    CRC16_CalcBufCrc.
fun     Computes the 16-bit CRC of a given buffer of data word(8-bit).
param   pBuffer :
          - pointer to the buffer containing the data to be computed
param   BufferLength :
          - length of the buffer to be computed
return  16-bit CRC

Use note: The implementation first writes CRC->CRC16D to CRC_CRC16D_CLEAR_VALUE (0x0000), then writes each byte into CRC->CRC16DAT and finally returns CRC->CRC16D.

uint16_t CRC16_CalcCRC(uint8_t Data)Computes the 16-bit CRC of a given data word(8-bit).
Source comment
name    CRC16_CalcCRC.
fun     Computes the 16-bit CRC of a given data word(8-bit).
param   Data :
          - data word(8-bit) to compute its CRC
return  16-bit CRC
void __CRC16_SetLRC(uint8_t Data)Write LRC initial value.
Source comment
name    __CRC16_SetLRC.
fun     Write LRC initial value.
param   Data :
          - 8-bit data
return  none
uint8_t __CRC16_GetLRC(void)Returns the current LRC value.
Source comment
name    __CRC16_GetLRC.
fun     Returns the current LRC value.
param   none
return  8-bit LRC

5.5.3 Demo descriptions

This section describes the 2 CRC examples: CalCRC computes the CRC32 and CRC16 of the same data set in hardware and in software and compares the results, and CRC_DMA moves the data and reads the results back by DMA to complete the CRC32, CRC16 and LRC checks.

5.5.3.1 CalCRC Hardware and software CRC comparison

Example directory: projects/n32g41x_EVAL/examples/CRC/CalCRC/

Enable the CRC clock and initialise the serial log→compute the CRC32 of the 32-bit data buffer in hardware→recompute it in software with the same polynomial and compare→compute the CRC16 of the 8-bit data buffer in hardware→recompute it in software and compare→print the two verification results over the serial port

Item Source analysis
Function Computes the 32-bit and 16-bit checksums of the same data set in hardware and with software reference algorithms, compares them one by one and prints the comparison results over the serial port.
Source files src/main.c: main, the software reference algorithms GetCRC16_Software and GetCRC32_Software, and the data buffers;
inc/main.h: includes the driver and BSP headers;
src/n32g41x_it.c, inc/n32g41x_it.h: interrupt service routines;
readme.txt: function, environment and usage notes;
MDK-ARM/, EWARM/: Keil and IAR projects.
Key configuration BUFFER_SIZE is 114 and DataBuffer holds 114 32-bit words, the first being 0x00001021;
Buffer holds 8 bytes 0x7e, 0x11, 0x36, 0x4e, 0x55, 0x0a, 0xf1, 0x1a.
The software CRC16 polynomial CRC16_DFE_POLY is 0x8005 and the software CRC32 polynomial CRC32_DFE_POLY is 0x04C11DB7;
the software CRC32 initial value passed is 0xffffffff and the software CRC16 initial value is 0x00.
The system clock is HSI+PLL, 80 MHz for the N32G412 and 96 MHz for the N32G415;
printing uses PA9 at 115200 bps.
Program and data path main first enables the CRC clock and calls log_init to initialise the serial port;
CRC32_CalcBufCrc computes the hardware CRC32 of the 114 words of DataBuffer and GetCRC32_Software recomputes the same buffer, printing CRC32 calculation is correct! when they are equal;
then CRC16_CalcBufCrc computes the hardware CRC16 of the 8 bytes of Buffer and GetCRC16_Software recomputes it, printing CRC16 calculation is correct! when they are equal;
finally it enters an empty while (1).
Main APIs CRC32_CalcBufCrc, CRC16_CalcBufCrc
Verification After reset, observe the serial output: CRC32 calculation is correct! and CRC16 calculation is correct! should appear;
a message ending in error means the hardware and software results differ.
Use note The development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1 on the N32G41x evaluation board;
printing uses PA9 at 115200 bps.
Core-code walkthrough and porting

The example runs the hardware CRC and a software reference algorithm on the same data set and verifies the hardware computation by comparing the two results.

Data path: DataBuffer, Buffer → CRC32_CalcBufCrc, CRC16_CalcBufCrc write CRC->CRC32DAT, CRC->CRC16DAT and read the results back → GetCRC32_Software, GetCRC16_Software recompute bit by bit → the results are compared and printed by printf

1. Define the checksum data and the polynomials. The data size and the two generator polynomials come from the macros at the top of the file, and both the hardware and the software paths use the same data set.

src/main.csrc/main.c
#define BUFFER_SIZE    114
#define CRC16_DFE_POLY 0x8005
#define CRC32_DFE_POLY 0x04C11DB7

When porting to your own data, change BUFFER_SIZE and the element counts of DataBuffer and Buffer together, and change both macros together when the generator polynomial changes.

2. Enable the CRC clock and run the hardware CRC32 first. After enabling the CRC clock and initialising the serial port, main computes the hardware CRC32 and then the software CRC32, both over DataBuffer.

src/main.csrc/main.c · main
    /* Enable CRC clock */
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_CRC, ENABLE);

    log_init();

    /* Compute the 32-bit CRC of DataBuffer using hardware */
    CRC32Value = CRC32_CalcBufCrc((uint32_t*)DataBuffer, BUFFER_SIZE);

    /* Compute the 32-bit CRC of DataBuffer using software */
    CRCValue   = GetCRC32_Software((uint32_t*)DataBuffer, BUFFER_SIZE, 0xffffffff);

CRC32_CalcBufCrc only writes each word into CRC->CRC32DAT and reads CRC->CRC32D back; it does not reset CRC32 internally, so the hardware initial value is the reset value 0xFFFF_FFFF of CRC_CRC32DAT, and the software initial value 0xffffffff must match it for the results to be comparable.

3. Recompute bit by bit with the software reference algorithm. GetCRC32_Software shifts each word left bit by bit and XORs the generator polynomial, producing a CRC32 that matches the hardware.

src/main.csrc/main.c · GetCRC32_Software
    for (i = 0; i < len; i++)
    {
        xbit = 0x80000000;

        for (bits = 0; bits < 32; bits++)
        {
            if (crc32 & 0x80000000)
            {
                crc32 <<= 1;
                crc32 ^= CRC32_DFE_POLY;
            }
            else
            {
                crc32 <<= 1;
            }

            if (buf[i] & xbit)
            {
                crc32 ^= CRC32_DFE_POLY;
            }

            xbit >>= 1;
        }
    }

This part handles 32-bit words only; when moving to byte or half-word data, the word length, the bit width and the polynomial must all be adjusted together with the hardware side.

4. Compare the results and print the conclusion. The volatile values are read into local variables and compared; equality prints the correct message and otherwise the error message, and the 16-bit path is compared in the same way.

src/main.csrc/main.c · main
    /* Read volatile values into local variables to avoid undefined access order */
    crc32_hw = CRC32Value;
    crc32_sw = CRCValue;
    if (crc32_hw == crc32_sw)
    {
        printf(" CRC32 calculation is correct! \r\n");
    }
    else
    {
        printf(" CRC32 calculation error! \r\n");
    }

    /* Compute the 16-bit CRC of Buffer using hardware */
    CRC16Value = CRC16_CalcBufCrc((uint8_t*)Buffer, 8);

    /* Compute the 16-bit CRC of Buffer using software */
    CRCValue   = GetCRC16_Software((uint8_t*)Buffer, 8, 0x00);

CRC16_CalcBufCrc first writes CRC->CRC16D to 0x0000 before accumulating, so the software initial value 0x00 matches it; when porting, the length 8 in this block must match the actual element count of Buffer.

Corresponding source files: src/main.c, inc/main.h

5.5.3.2 CRC_DMA Complete the checks by DMA transfer

Example directory: projects/n32g41x_EVAL/examples/CRC/CRC_DMA/

Enable the CRC and DMA clocks and initialise the serial port→reset the CRC32 calculation unit→move the 32-bit data into the CRC32 calculation by DMA→move the 8-bit data into the CRC16 calculation by DMA→read the results back by DMA and compare them with the expected values→print the CRC32, CRC16 and LRC results over the serial port

Item Source analysis
Function Moves the words and bytes from memory into the CRC data registers by a DMA channel, verifies the hardware results of CRC32, CRC16 and LRC, and reads the result registers back into memory by DMA.
Source files src/main.c: main and assert_failed;
src/crc_dma.c: TestCrc32AndDma, TestCrc16AndDma and the two data buffers;
inc/main.h: includes the driver and BSP headers;
src/n32g41x_it.c, inc/n32g41x_it.h: interrupt service routines;
readme.txt: function, environment and usage notes;
MDK-ARM/, EWARM/: Keil and IAR projects.
Key configuration CRC_original_word_data holds 114 32-bit words, the first being 0x00001021;
CRC_original_byte_data holds 456 bytes, one byte per byte of the word data.
DMA uses DMA_CH1 with Mem2Mem set to DMA_M2M_ENABLE, Priority to DMA_PRIORITY_VERY_HIGH and CircularMode to DMA_MODE_NORMAL;
the CRC32 transfer uses MemDataSize DMA_MemoryDataSize_Word and BufSize 114, the CRC16 transfer uses MemDataSize DMA_MemoryDataSize_Byte and BufSize 456.
The CRC16 calculation selects the big-endian format and clears the result beforehand.
The expected values are 0x379E9F06 for CRC32, 0x5268 for CRC16 and 0x00 for LRC.
Program and data path main enables the CRC and DMA clocks, calls log_init and prints CRC DMA test start., calls CRC32_ResetCrc to reset CRC32, then calls TestCrc32AndDma and TestCrc16AndDma in turn;
the former configures DMA_CH1 twice to move memory into the CRC32 data register and the result back into memory and compares it;
the latter first sets the CRC16 and LRC initial values, enables the clear and selects big-endian, then transfers 456 bytes by DMA and reads CRC16D and LRC back to compare.
Main APIs CRC32_ResetCrc, CRC32_GetCrc, __CRC16_SetCrc, __CRC16_SetLRC, __CRC16_SetCleanEnable, __CRC16_SetBigEndianFmt, __CRC16_GetCrc, __CRC16_GetLRC
Verification After reset, observe the serial port: CRC32 should print as 0x379e9f06, CRC16 as 0x5268 and LRC as 0x00, and no message ending in error should appear.
Use note The development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1 on the N32G41x evaluation board;
printing uses PA9 at 115200 bps;
the example depends on the DMA driver.
Core-code walkthrough and porting

The example uses a DMA channel instead of software register writes to move data between the data buffers and the result registers, and judges the checks from the values read back.

Data path: CRC_original_word_data, CRC_original_byte_data → DMA_Init, DMA_EnableChannel move them into CRC->CRC32DAT, CRC->CRC16DAT → CRC32_GetCrc, __CRC16_GetCrc, __CRC16_GetLRC or the DMA reads the results back → printf prints them and they are compared with the expected values

1. Enable the CRC and DMA clocks and reset CRC32. main enables the AHB clocks of both peripherals, initialises the serial port, resets CRC32 and then enters the two test functions.

src/main.csrc/main.c · main
    /* Enable CRC and DMA clock */
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_CRC | RCC_AHB_PERIPH_DMA, ENABLE);

    log_init();

    printf(" CRC DMA test start.\r\n");

    CRC32_ResetCrc();

    TestCrc32AndDma();
    TestCrc16AndDma();

Both test functions depend on DMA, so the source files of the CRC and DMA drivers must be added to the build together when porting to another project.

2. Move the word data into CRC32 by DMA. The DMA channel moves the 114 words of CRC_original_word_data one by one into the 32-bit CRC data register, and the wait ends on the transfer-complete flag.

src/crc_dma.csrc/crc_dma.c · TestCrc32AndDma
    /* Memory -> CRC32DAT */
    DMA_InitStructure.PeriphAddr     = (uint32_t)&CRC->CRC32DAT;
    DMA_InitStructure.MemAddr        = (uint32_t)CRC_original_word_data;
    DMA_InitStructure.Direction      = DMA_DIR_PERIPH_DST;
    DMA_InitStructure.Mem2Mem        = DMA_M2M_ENABLE;
    DMA_InitStructure.MemDataSize    = DMA_MemoryDataSize_Word;
    DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_WORD;
    DMA_InitStructure.DMA_MemoryInc  = DMA_MEM_INC_ENABLE;
    DMA_InitStructure.PeriphInc      = DMA_PERIPH_INC_DISABLE;
    DMA_InitStructure.CircularMode   = DMA_MODE_NORMAL;
    DMA_InitStructure.Priority       = DMA_PRIORITY_VERY_HIGH;
    DMA_InitStructure.BufSize        = 114;

    DMA_Init(DMA_CH1, &DMA_InitStructure);
    DMA_EnableChannel(DMA_CH1, ENABLE);
    while (!DMA_GetFlagStatus(DMA_FLAG_TC1, DMA));
    DMA_DeInit(DMA_CH1);

The peripheral address stays fixed on the result register while the memory address increments, so each word enters the CRC in turn; change MemAddr and BufSize together when replacing the buffer, and switch to byte width for byte data.

3. Read the CRC32 result back and compare it with the expected value. After the transfer the result register is read with CRC32_GetCrc and compared with the fixed expected value, and a mismatch prints an error.

src/crc_dma.csrc/crc_dma.c · TestCrc32AndDma
    CRC32_Value = CRC32_GetCrc();
    printf("The CRC 32 code after Memory->CRC32DAT DMA transmission is %08x.\r\n", CRC32_GetCrc());

    if (CRC32_Value != 0x379E9F06)
        printf("The CRC 32 code computed through DMA transmission is error, it should be 0x379E9F06.\r\n");

The expected value 0x379E9F06 corresponds to this fixed set of 114 words; the comparison value must be recomputed whenever the data or the polynomial changes.

4. Set the CRC16 initial value, the clear and the big-endian format before transferring. Before the CRC16 transfer the initial value is set, the result is cleared and big-endian is selected, and the 456 bytes are then moved in by byte width.

src/crc_dma.csrc/crc_dma.c · TestCrc16AndDma
    __CRC16_SetCrc(0x00);
    __CRC16_SetLRC(0x00);
    __CRC16_SetCleanEnable();
    __CRC16_SetBigEndianFmt();

    /* Memory -> CRC16DAT */
    DMA_InitStructure.PeriphAddr     = (uint32_t)&CRC->CRC16DAT;
    DMA_InitStructure.MemAddr        = (uint32_t)CRC_original_byte_data;
    DMA_InitStructure.Direction      = DMA_DIR_PERIPH_DST;
    DMA_InitStructure.Mem2Mem        = DMA_M2M_ENABLE;
    DMA_InitStructure.MemDataSize    = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
    DMA_InitStructure.DMA_MemoryInc  = DMA_MEM_INC_ENABLE;
    DMA_InitStructure.PeriphInc      = DMA_PERIPH_INC_DISABLE;
    DMA_InitStructure.CircularMode   = DMA_MODE_NORMAL;
    DMA_InitStructure.Priority       = DMA_PRIORITY_VERY_HIGH;
    DMA_InitStructure.BufSize        = 456;

    DMA_Init(DMA_CH1, &DMA_InitStructure);
    DMA_EnableChannel(DMA_CH1, ENABLE);
    while (!DMA_GetFlagStatus(DMA_FLAG_TC1, DMA));
    DMA_DeInit(DMA_CH1);

__CRC16_SetBigEndianFmt clears the ENDIAN bit so that the data under check is processed from the most significant bit, and __CRC16_SetCleanEnable sets the clear bit so that the result register first takes the default value 0x0000. A different data endianness or initial value changes the CRC16 result directly.

5. Read the CRC16 and LRC results back and compare them. After the transfer the result register and the LRC register are read and compared with the expected values.

src/crc_dma.csrc/crc_dma.c · TestCrc16AndDma
    CRC16_Value = __CRC16_GetCrc();
    LRC_Value = __CRC16_GetLRC();

    printf("The CRC 16 code after Memory->CRC16DAT DMA transmission is %04x.\r\n", __CRC16_GetCrc());
    if (CRC16_Value != 0x5268)
        printf("The CRC 16 code computed through DMA transmission is error, it should be 0x5268.\r\n");

    printf("The LRC code after Memory->CRC16DAT DMA transmission is %02x.\r\n", __CRC16_GetLRC());
    if (LRC_Value != 0x00)
        printf("The LRC code computed through DMA transmission is error, it should be 0x00.\r\n");

The CRC and LRC calculations run at the same time, so reading CRC->LRC gives the LRC checksum of the same data set; for this data the expected values are 0x5268 for CRC16 and 0x00 for LRC.

Corresponding source files: src/main.c, src/crc_dma.c, inc/main.h

5.5.4 Notes and troubleshooting

Scenario Check & action Source
CRC32 initial value and reset CRC_CRC32CTRL.RESET is write-1-only and is cleared automatically by hardware, and setting it makes CRC_CRC32D 0xFFFF_FFFF;
when it is not set the initial value is the result of the previous calculation, so reset or write an initial value yourself before a new check.
User manual
Clearing the CRC16 result and the data endianness CRC_CRC16CTRL.CLEAR is write-1-to-clear and stays set for one cycle only (software always reads 0), and setting it makes the result take the default value 0x0000;
CRC_CRC16CTRL.ENDIAN applies to the data under check only, with 0 for most-significant-bit first and 1 for least-significant-bit first.
User manual
Reading and clearing LRC The CRC and LRC calculations run at the same time, and after reading CRC_LRC software should clear it before the next calculation, otherwise the result carries over from the previous one. User manual
Reserved bits and access width Bits 31:1 of CRC_CRC32CTRL, bits 31:8 of CRC_CRC32IDAT, bits 31:3 and bit 0 of CRC_CRC16CTRL, bits 31:8 of CRC_CRC16DAT, bits 31:16 of CRC_CRC16D and bits 31:8 of CRC_LRC are reserved and must keep their reset values;
the CRC16 registers support 8-bit, 16-bit and 32-bit operations.
User manual

The N32G412 and N32G415 series errata sheet V1.0.0 (2026.6.30) lists a single row for revision A chips with an empty errata link column and gives no CRC-related entries.

Nations Technologies DevKit Docs

Datasheets, SDK, driver APIs and demo guides for Nations Technologies MCU development kits, organized by chip series.

© 2026 Nations Technologies Inc. All rights reserved.粤ICP备11069485号This site uses cookie-free anonymous analytics.