---
title: "Porting ioLibrary_Driver to PIC24 using CCS PCD compiler"
url: "https://maker.wiznet.io/forum/15890"
markdown_url: "https://maker.wiznet.io/forum/15890/md"
type: "Forum topic"
category: "Ethernet Chips"
author: "allenhuffman"
created: "2025-09-25T02:44:02+09:00"
last_activity: "2025-12-18T02:20:32+09:00"
language: "en"
views: 7
replies: 3
source: "WIZnet Makers forum (https://maker.wiznet.io/forum/)"
---

# Porting ioLibrary_Driver to PIC24 using CCS PCD compiler

## Question

Asked by allenhuffman on 2025-09-25 in Ethernet Chips.

Four years ago, we ported the basic ioLibrary_Driver code for W5500 to the PIC24 using the CCS PCD compiler.

I just re-ported the current code. I had to change a few things to work around limitations in the CCS compiler. For example, passing a function pointer does not work. There is a workaround. Each function pointer gets made into a typedef. From the Ethernet library, here are examples:

```plaintext
typedef void(*CRIS_EN)(void);
typedef void(*CRIS_EX)(void);
typedef void(*CS_SEL)(void);
typedef void(*CS_DESEL)(void);
typedef iodata_t(*BUS_RB)(uint32_t addr);
typedef void(*BUS_WB)(uint32_t addr, iodata_t wb);
typedef uint8_t(*SPI_RB)(void);
typedef void(*SPI_WB)(uint8_t wb);
typedef void(*SPI_RBB)(uint8_t* pBuf, uint16_t len);
typedef void(*SPI_WBB)(uint8_t* pBuf, uint16_t len);
typedef void(*VOIDPTR)(void);
typedef void(*BUSBUF_RB)(uint32_t AddrSel, iodata_t* pBuf, int16_t len, uint8_t addrinc);
typedef void(*BUSBUF_WB)(uint32_t AddrSel, iodata_t* pBuf, int16_t len, uint8_t addrinc);
typedef void(*QSPI_RB)(uint8_t opcode, uint16_t addr, uint8_t* pBuf, uint16_t len);
typedef void(*QSPI_WB)(uint8_t opcode, uint16_t addr, uint8_t* pBuf, uint16_t len);
```

Then functions like this:

```plaintext
void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void)) {
```

...get changed into this:

```plaintext
void reg_wizchip_cris_cbfunc(CRIS_EN cris_en, CRIS_EX cris_ex) {
```

This makes the CCS compiler happy. This typedef workaround seems to work without issue on a "real" compiler (I tested it on the Online GDB compiler).

I have started porting the httpServer, but it has many more things to change. First, quoted strings like "This" do not work as function parameters, so the CCS PCD compiler has a way to copy them into RAM automatically and then they work:

```plaintext
#device PASS_STRINGS=IN_RAM
```

I also had to deal with the use of "const" which behaves differently in this compiler/architecture:

```plaintext
*/
    } else if (strcmp((const char *)uri_name, "example.cgi") == 0) {
*/
    } else if (strcmp((char *)uri_name, "example.cgi") == 0) {
```

Removing them allows it to get past the compiler error.

**I have not been able to build** the httpServer yet, and wanted to post this here in case anyone had already figured all this out.

I think my project will need:

- web server

- smtp e-mail sending

The SMTP looks like it wants plaintext/open servers, which our IT department told me we cannot have and they are not supported. This means I may have to do more work to get this going.

Anyone worked on this before?

## Replies

### Reply 1 by allenhuffman, 2025-09-25

Hello, robot. I have a question about #5. The original headers contain prototypes that will not build on the CCS PCD compiler. I had to change those items in wiznet_conf.h and wiznet_conf.c, like this:

```plaintext
// MODIFICATION:
/*
void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void)) {
*/
void reg_wizchip_cris_cbfunc(CRIS_EN cris_en, CRIS_EX cris_ex) {
    if (!cris_en || !cris_ex) {
        WIZCHIP.CRIS._enter = wizchip_cris_enter;
        WIZCHIP.CRIS._exit  = wizchip_cris_exit;
    } else {
        WIZCHIP.CRIS._enter = cris_en;
        WIZCHIP.CRIS._exit  = cris_ex;
    }
}
```

And the prototypes in wiznet_conf.h the same way, after adding the typedefs

```plaintext
// MODIFICATION:
// These typedefs needed to be added for the CCS PIC24 C compiler to deal
// with how function pointers work in that architecture.
// (Must be in code after iodata_t is defined.)
typedef void(*CRIS_EN)(void);
typedef void(*CRIS_EX)(void);
typedef void(*CS_SEL)(void);
typedef void(*CS_DESEL)(void);
typedef iodata_t(*BUS_RB)(uint32_t addr);
typedef void(*BUS_WB)(uint32_t addr, iodata_t wb);
typedef uint8_t(*SPI_RB)(void);
typedef void(*SPI_WB)(uint8_t wb);
typedef void(*SPI_RBB)(uint8_t* pBuf, uint16_t len);
typedef void(*SPI_WBB)(uint8_t* pBuf, uint16_t len);
typedef void(*VOIDPTR)(void);
typedef void(*BUSBUF_RB)(uint32_t AddrSel, iodata_t* pBuf, int16_t len, uint8_t addrinc);
typedef void(*BUSBUF_WB)(uint32_t AddrSel, iodata_t* pBuf, int16_t len, uint8_t addrinc);
typedef void(*QSPI_RB)(uint8_t opcode, uint16_t addr, uint8_t* pBuf, uint16_t len);
typedef void(*QSPI_WB)(uint8_t opcode, uint16_t addr, uint8_t* pBuf, uint16_t len);


// MODIFICATION:
/*
void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void));
*/
void reg_wizchip_cris_cbfunc(CRIS_EN cris_en, CRIS_EX cris_ex);
```

Do you have a different way to approach this that would make rebasing with newer code easier?

### Reply 2 by Aimee0, 2025-09-25

Hello, we recommend **Port Layer Separation**, as it provides the cleanest and most maintainable solution for long-term development.

**Port Layer Separation (Recommended)**

- Do not modify the original ioLibrary. Instead, create `port_ccs.h/.c` files to define CCS-specific typedefs and wrapper functions.

- During maintenance, the scope of changes is limited to the “port files,” reducing conflicts.

Ex)

```plaintext
// port_ccs.h
typedef void(*CRIS_EN)(void);
typedef void(*CRIS_EX)(void);

static inline void reg_wizchip_cris_cbfunc_port(CRIS_EN en, CRIS_EX ex) {
    reg_wizchip_cris_cbfunc((void(*)(void))en, (void(*)(void))ex);
}
```

**Macro/Casting Adapters**

- Instead of changing function pointer types directly, add a conversion layer with macros or casting.

- This preserves the original code but may reduce readability.

Ex)

```plaintext
#define CRIS_EN_CAST(p) ((void(*)(void))(p))
#define CRIS_EX_CAST(p) ((void(*)(void))(p))
```

**Patch Branch Strategy**

- Use the ioLibrary as a git submodule and apply changes only in a dedicated CCS branch.

- When updating upstream, you only need to review and apply the patch, which simplifies management.

### Reply 3 by allenhuffman, 2025-12-18

Thank you, **Aimee0**. At the time of my post, I was unaware of this method. In an earlier project we modified the library and I was doing the same.

I have now created wrapper functions. I began using the CCS compiler, but it was having problems with the HTTPServer example code. To make things easier for that, I switched to trying the MPLAB compiler. That made the process easier for both the I/O library and things like HTTP, SNMP, etc.

For any others doing this ... my wrapper basically looked like this (CCS code commented out, MPLAB code being used. I was never able to get everything running with CCS before I switched.)

Does Wiznet provide any stub file that has a similar wrapper? That would have been a very useful starting point.

```plaintext
#ifndef WIZENETWRAPPER_C
#define WIZENETWRAPPER_C

#include "wizchip_conf.h"

#include "WiznetWrapper.h"

bool WiznetWrapper_init (void)
{
    bool status = false;

    // Reset the Wiznet chip.
    //output_low (W5500_RESET_PIN);   // RSTn LOW
    W5500_RESET_SetLow ();          // RSTn LOW
    
    //delay_ms (1);                   // Min 500us
    __delay_ms (1);                 // Min 500us
    
    //output_high (W5500_RESET_PIN);  // Ensure reset line is not floating.
    W5500_RESET_SetHigh ();         // Ensure reset line is not floating.
    
    //delay_ms (1);                   // 1ms Max to PLOCFK
    __delay_ms (1);                 // 1ms Max to PLOCFK

    // Use our interrupt disable/enable functions.
    reg_wizchip_cris_cbfunc (WiznetWrapper_cris_enter, WiznetWrapper_cris_exit);

    // Use our chip select functions.
    reg_wizchip_cs_cbfunc (WiznetWrapper_cs_select, WiznetWrapper_cs_deselect);

    // Use our read byte/write byte functions.
    reg_wizchip_spi_cbfunc (WiznetWrapper_spi_readbyte, WiznetWrapper_spi_writebyte);

    // Use our read burst/write burst functions.
    reg_wizchip_spiburst_cbfunc (WiznetWrapper_spi_readburst, WiznetWrapper_spi_writeburst);
    
    if (spi1_open(0)) // MODE0 ?
    {
        status = true;
    }
    else // Failed
    {
        status = false;
    }

    return status;
}


void WiznetWrapper_cris_enter (void)
{
    //disable_interrupts (GLOBAL);
    //__builtin_disi(0x3FFF);  // Disable interrupts globally
    INTERRUPT_GlobalDisable ();
}


void WiznetWrapper_cris_exit (void)
{
    //enable_interrupts (GLOBAL);
   // __builtin_disi(0x0000);  // Re-enable interrupts
   INTERRUPT_GlobalEnable ();
}


void WiznetWrapper_cs_select (void)
{
    //printf ("WiznetWrapper_cs_select\r\n");
    // Chip Select
    //output_low (W5500_SPI_SS_PIN); //pin we connected select to
    W5500_CS_SetLow ();
    
    // Delay needed, so we'll do this afterwards.
    //spi_init (W5500_SPI_MODE0_STREAM, TRUE);
    
    //delay_us (40); // Patience... 
    __delay_us (40);
}



void WiznetWrapper_cs_deselect(void)
{
    //printf ("WiznetWrapper_cs_deselect\r\n");
    //output_high (W5500_SPI_SS_PIN);
    W5500_CS_SetHigh ();
}


uint8_t WiznetWrapper_spi_readbyte(void)
{
    //printf ("WiznetWrapper_spi_readbyte\r\n");
    //return spi_xfer(W5500_SPI_MODE0_STREAM, 0, 8);
    return spi1_exchangeByte(0x0);  // Send dummy byte to read
}


void WiznetWrapper_spi_writebyte(uint8_t wb)
{
    //printf ("WiznetWrapper_spi_writebyte (%d)\r\n", wb);
    //spi_xfer(W5500_SPI_MODE0_STREAM, wb, 8);
    spi1_exchangeByte(wb);
}


void WiznetWrapper_spi_readburst(uint8_t* pBuf, uint16_t len)
{
    //printf ("WiznetWrapper_spi_readburst %d\r\n", len);
    // spi_transfer_read(W5500_SPI_MODE0_STREAM, pBuf, (int16) len);
    spi1_exchangeBlock(pBuf, len);  // Sends dummy bytes and fills pBuf
}


void WiznetWrapper_spi_writeburst(uint8_t* pBuf, uint16_t len)
{
    //printf ("WiznetWrapper_spi_writeburst %d\r\n", len);
    //spi_transfer_write(W5500_SPI_MODE0_STREAM, pBuf, (int16) len);
    spi1_writeBlock(pBuf, len);  // Send all bytes, ignore incoming
}


#endif /* WIZENETWRAPPER_C */
```

This greatly simplified the process. Now we can just call the WiznetWrapper_init() and then start using it. Thank you.

---

Source: https://maker.wiznet.io/forum/15890
