Author Topic: Porting SPI Slave from AVR to CH32V003  (Read 2085 times)

0 Members and 1 Guest are viewing this topic.

Offline gotofbiTopic starter

  • Contributor
  • Posts: 48
Porting SPI Slave from AVR to CH32V003
« on: October 24, 2025, 08:00:33 am »
Hi, Im trying to port Atmega328p Arduino code into CH32V003 with MounRiver but its not working  |O |O |O


Arduino Code
Code: [Select]
void setup() {
  pinMode(MISO_PIN, OUTPUT);
  pinMode(CLK_PIN, INPUT_PULLUP);
  pinMode(MOSI_PIN, INPUT_PULLUP);
  pinMode(INT_CS_SPI, INPUT_PULLUP);
  pinMode(CS_PIN, INPUT_PULLUP);     
 
  attachInterrupt(digitalPinToInterrupt(INT_CS_SPI), cableselect, RISING);
  SPCR |= _BV(SPE);     // turn on SPI
  SPCR |= _BV(SPIE);    // turn on interrupts
  SPCR |= _BV(CPHA);    // turns on CPHA

  delay(2000);
  Serial.begin(115200);
  Serial.println("Starting the loop");
}
void loop() {
  //something
}
void cableselect() {
  SPCR &= ~_BV(SPIE);     // turn OFF interrupts
  SPDR = data_out[0];     // load first byte into SPDR single-byte's buffer
  isrIdx = 0;             // on next SPI interrupt(SPI_STC_vect), load the 2nd byte
  SPCR |= _BV(SPIE);      // turn on interrupts
}
ISR(SPI_STC_vect) {
  byte c = SPDR;
  data_in[isrIdx] = c;
 
  isrIdx++;
  if (isrIdx >= buffLen) {
    isrIdx = 0;
  }
 
  SPDR = data_out[isrIdx];
}

It is very simple code.
INT_CS_SPI is PB2, CS_PIN is PD2 and they both connected to SPI Master's CS Pin.
Other SPI pins are connected to usual SPI pins.


Now, here is CH32V003 code that I ported.
Code: [Select]
/////////////////////////////////////////////////////
// SPI Transfer Complete Interrupt
void SPI1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SPI1_IRQHandler(void) {
    if (SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) != RESET) {
        // Read received byte
        RxData[isrIndex] = SPI_I2S_ReceiveData(SPI1);
       
        // Load next byte to transmit
        isrIndex++;
        if (isrIndex >= sizeof(TxData)) {
            isrIndex = 0;
        }
        SPI_I2S_SendData(SPI1, TxData[isrIndex]);
    }
}

/////////////////////////////////////////////////////
// External Interrupt for CS Rising Edge (PD1)
void EXTI7_0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI7_0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line1) != RESET) {
        isrIndex = 0;
        EXTI_ClearITPendingBit(EXTI_Line1);
    }
}

/////////////////////////////////////////////////////
// GPIO C Configuration (SPI pins)
void GPIOC_Config(void) {
    GPIO_InitTypeDef GPIO_InitStructure = {0};
   
    // Enable GPIOC clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}

/////////////////////////////////////////////////////
// GPIO D Configuration (CS interrupt pin)
void GPIOD_Config(void) {
    GPIO_InitTypeDef GPIO_InitStructure = {0}; //structure variable used for the GPIO configuration

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); // to Enable the clock for Port D

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // Defines which Pin to configure
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // Defines Output Type
    GPIO_Init(GPIOD, &GPIO_InitStructure);
}

/////////////////////////////////////////////////////
// SPI Slave Configuration
void SPI_Slave_Init(void) {
    SPI_InitTypeDef SPI_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};
   
    // Enable SPI1 clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
   
    // Configure SPI1 as Slave
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;              // Slave mode
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                // CPOL = 0
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;              // CPHA = 1 (SPI Mode 1)
    SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
   
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_I2S_SendData(SPI1, TxData[0]);
   
    // Enable SPI RXNE interrupt
    SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
   
    // Enable SPI
    SPI_Cmd(SPI1, ENABLE);
   
    // Enable SPI1 interrupt in NVIC
    NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

/////////////////////////////////////////////////////
// External Interrupt Configuration for CS (PD1)
void EXTI_Config(void)
{
    EXTI_InitTypeDef EXTI_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    // Enable GPIOD and AFIO clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE);

    // Configure PD1 as input (floating or pull-up depending on your circuit)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    // Connect EXTI Line1 to PD1
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource1);
 
    // Configure EXTI Line1
    EXTI_InitStructure.EXTI_Line = EXTI_Line1;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    // Configure NVIC for EXTI
    NVIC_InitStructure.NVIC_IRQChannel = EXTI7_0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    // Configure GPIO pins - SEPARATE functions for each port
    GPIOC_Config();  // SPI pins
    GPIOD_Config();  // CS interrupt pin
   
    SPI_Slave_Init();

    EXTI_Config();

    printf("Slave Mode\r\n");
    Delay_Ms(2000);

    while(1) {
    }

}

I think I implemented same logic.
PD1 (interrupt pin) is also connected with PC1 (NSS pin) and they are connected to SPI Master's CS Pin.

I am using CH32V003F4P6 which has SPI as far as I know.
Currently testing the code on cheap aliexpress dev board (https://docs.zephyrproject.org/latest/boards/wch/ch32v003f4p6_dev_board/doc/index.html)


Can someone please tell me what im doing wrong?

Thanks!
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Porting SPI Slave from AVR to CH32V003
« Reply #1 on: October 24, 2025, 08:31:05 am »
"It doesn't work" is not useful information.

In what way does it not work?  What have you changed since the last code that worked?

Are you sure HPE is enabled?
 

Offline gotofbiTopic starter

  • Contributor
  • Posts: 48
Re: Porting SPI Slave from AVR to CH32V003
« Reply #2 on: October 24, 2025, 08:41:15 am »
"It doesn't work" is not useful information.

In what way does it not work?  What have you changed since the last code that worked?

Are you sure HPE is enabled?

Honestly, it never worked because I did not test AVR code.
I got the AVR code from github and lots of people claimed that it works fine with their atmega arduino.
So I ported same code I think and hoped that it worked but when I connect CH32V003 to SPI Master, SPI Master device just failed to communicate (dont have code of SPI Master, only know it wokred or not)

Also, I noticed PD1 is connected to on-board LED so I moved to PD2 but still no difference.

Sorry for noob question; How can I make sure HPE is enabled?
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Porting SPI Slave from AVR to CH32V003
« Reply #3 on: October 24, 2025, 08:57:06 am »
Do you know what HPE means and why it is relevant here?

You could alternatively change the code so it doesn't depend on HPE, and see what happens.

Quote
So I ported same code I think and hoped that it worked

In bare metal code, "hope" is not a viable strategy.

Neither is writing your whole program before testing anything.

Start with the simplest possible program, generally "blinky" and get that working. Then change one small thing at a time, testing each change.

 
The following users thanked this post: voltsandjolts

Offline gotofbiTopic starter

  • Contributor
  • Posts: 48
Re: Porting SPI Slave from AVR to CH32V003
« Reply #4 on: October 24, 2025, 09:22:54 am »
Do you know what HPE means and why it is relevant here?

You could alternatively change the code so it doesn't depend on HPE, and see what happens.

Quote
So I ported same code I think and hoped that it worked

In bare metal code, "hope" is not a viable strategy.

Neither is writing your whole program before testing anything.

Start with the simplest possible program, generally "blinky" and get that working. Then change one small thing at a time, testing each change.

Thank you for your reply and I really appreciate your time and effort and telling me to try on blinky first.
I asked noob of question of how to make sure HPE is enabled; not what is HPE.
From my research "__attribute__((interrupt("WCH-Interrupt-fast")));" is a way to enable it for CH32V003, which clearly shows in my code.

Also, the reason that I didnt test AVR code is simply I dont have AVR arduino in my possession.
Plus, I thought simple SPI slave should be easy to port to CH32V003 without your suggested method of "change one small thing at a time"

I thoroughly checked my code with referencing all other examples as well as datasheet of ch32v003 and I thought I translated well.


Really appreciate your kind suggestion, but frankly, i feel like you are just wasting your effort without reading my code.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Porting SPI Slave from AVR to CH32V003
« Reply #5 on: October 24, 2025, 09:48:15 am »
I asked noob of question of how to make sure HPE is enabled; not what is HPE.
From my research "__attribute__((interrupt("WCH-Interrupt-fast")));" is a way to enable it for CH32V003, which clearly shows in my code.

Incorrect.

That's how you annotate you interrupt handlers to work if and only if HPE mode is already enabled. It's not how you enable HPE mode.

Quote
Plus, I thought simple SPI slave should be easy to port to CH32V003 without your suggested method of "change one small thing at a time"

I've been doing bare metal programming for 45 years (6502 at that time), and I would never dream of doing anything except "change one small thing at a time" in my own work. That's not advice to a n00b, it's advice from a professional.

I don't rule out the possibility that you are more experienced or smarter than me.

Quote
Really appreciate your kind suggestion, but frankly, i feel like you are just wasting your effort without reading my code.

I glanced through it, but if you want me to carefully debug it (which apparently you are not willing to do yourself) then you'll have to pay me.
 
The following users thanked this post: abeyer, pcprogrammer

Offline kripton2035

  • Super Contributor
  • ***
  • Posts: 2905
  • Country: fr
    • kripton2035 schematics repository
Re: Porting SPI Slave from AVR to CH32V003
« Reply #6 on: October 24, 2025, 10:00:23 am »
why don't use arduino ide to program the ch32v ? it would be almost the exact same code ...
 

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 6140
  • Country: nl
Re: Porting SPI Slave from AVR to CH32V003
« Reply #7 on: October 24, 2025, 11:05:06 am »
Plus, I thought simple SPI slave should be easy to port to CH32V003 without your suggested method of "change one small thing at a time"

It seems that you clearly thought wrong, otherwise you would not be asking for advice here.

Also expecting forum members to dive in deep to solve your problem is a far stretch. We are non payed volunteers and even though you neatly packed your comment, it does not show real respect for what Bruce is trying to tell you.

HC32V003 is not a MCU I'm familiar with, but like Bruce, I also have a lot of years of experience in doing low level coding and during development and debugging also start small and add functionality as I go along, testing all the time to make sure other parts are not killed by the new code. Makes life a lot easier than writing up a lot of lines of code to just find out it does not work and then trying to solve the problems.

Offline gotofbiTopic starter

  • Contributor
  • Posts: 48
Re: Porting SPI Slave from AVR to CH32V003
« Reply #8 on: October 24, 2025, 12:00:26 pm »
why don't use arduino ide to program the ch32v ? it would be almost the exact same code ...

couple reasons im staying away from arduino.
1. Im trying to stay away from arudino bootloader which would cause small delay of running my code. Its very small delay but its crucial for my purpose (SPI Master tries to communicate with my device real fast and if it fails, its game over until reboot of SPI Master)
2. Only one example were found (https://github.com/openwch/arduino_core_ch32/blob/main/libraries/SPI/examples/SPIFlash/SPIFlash.ino) and its SPI Master example. (Couldnt find SPI Slave example with Arduino with CH32 core)
3. Didn't want to reinvent wheel, Saw few examples from official site (https://www.wch.cn/downloads/CH32V003EVT_ZIP.html)

Plus, I thought simple SPI slave should be easy to port to CH32V003 without your suggested method of "change one small thing at a time"

It seems that you clearly thought wrong, otherwise you would not be asking for advice here.

Also expecting forum members to dive in deep to solve your problem is a far stretch. We are non payed volunteers and even though you neatly packed your comment, it does not show real respect for what Bruce is trying to tell you.

HC32V003 is not a MCU I'm familiar with, but like Bruce, I also have a lot of years of experience in doing low level coding and during development and debugging also start small and add functionality as I go along, testing all the time to make sure other parts are not killed by the new code. Makes life a lot easier than writing up a lot of lines of code to just find out it does not work and then trying to solve the problems.

You are absolutely right. I underestimated my problem and was seeking for simple quick fix.
I initially thought I made small mistake on the code since I thought what could go so wrong for implementing simple spi slave code.
And that is why I posted full code of Arduino as well as ported code.

I did not expect anyone to deep dive into the code nor setup debug environment to find what I really did wrong.
If that was my expectation, I would rather hire and pay professional like bruce, rather than posing on the forum and seeking for help.

I have no intention of disrespecting anyone from forum nor someone do all the work for me.
However, Im not also expected someone to tell me try "blinky" first after sharing all the code that I wrote.


I sincerely respect everyone on the forum and hope my intention was clearly delivered.
 
The following users thanked this post: pcprogrammer

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 6140
  • Country: nl
Re: Porting SPI Slave from AVR to CH32V003
« Reply #9 on: October 24, 2025, 12:31:36 pm »
I took a bit deeper look at your code, and your problem could be that PC1, according to what you wrote, the pin for NSS, is not being setup.

Your SPI NSS (handshake) mode is set to hardware, if I'm interpreting the code correctly.

A slave SPI will only receive data when enabled or selected. On STM32 I can set a slave SPI to software management and not use the NSS pin to make it receive or send for that matter. When not in software management mode the NSS pin has to be set for input and made active to allow for the SPI slave to work.

Offline HwAoRrDk

  • Super Contributor
  • ***
  • Posts: 1933
  • Country: gb
Re: Porting SPI Slave from AVR to CH32V003
« Reply #10 on: October 24, 2025, 10:10:15 pm »
First question: your Arduino code is configured to react to a rising edge on CS, and your CH32V code is also configuring EXTI for a rising edge interrupt. Are you sure that's what you need? Chip-select on SPI is conventionally active-low, with the slave device being selected by a low level on CS, therefore you typically want the slave to react to a falling edge on CS. Do you have some unusual master device that uses active-high polarity on CS?

As pcprogrammer said, in your CH32V code you aren't explicitly configuring PC1, but all GPIO pins are configured as inputs by default, so that probably isn't an issue (but you should always explicitly configure all the GPIOs being used, even if in the same way as default). However, a bigger issue is that if you're using PC1 for CS, and configuring the SPI for hardware NSS, then this only supports active-low CS signals. If you do indeed need active-high CS, then you can't use hardware NSS.

Your apparent requirement for active-high CS also conflicts with how you are configuring GPIO PD1 with GPIO_Mode_IPU, which enables the internal pull-up resistor. This only suitable for active-low CS - you would want GPIO_Mode_IPD for active-high CS.

One other issue I can see in your code is your EXTI7_0_IRQHandler code is flawed for hardware NSS usage. In it you are only setting isrIndex to zero, but you also really need to put the first byte of data into the SPI data register before the master starts outputting SCK. In hardware NSS mode, as soon as the slave SPI peripheral starts receiving clock, it automatically starts transmitting the contents of the data register. But you're only writing TX data to the SPI data register upon each newly-received byte coming in; your first transmitted byte on MISO will be whatever random value happens to be in the SPI data register. You should probably add a SPI_I2S_SendData(SPI1, TxData[isrIndex]) call to EXTI7_0_IRQHandler.

You could probably also do with modifying your SPI interrupt handling. As I said above, you're only writing TX bytes to the SPI data register upon RXNE IRQ, but that is only fired on the last bit of each byte being received. I'm not sure whether that leaves enough time to put new TX data in the SPI data register for the next to go out on MISO. You should probably instead also handle SPI TXE interrupts, and write the next TX byte to the SPI data register there. That way you can be sure the correct data is ready to be sent in plenty of time.

One last thing I'll mention is that you may not need to use an additional GPIO (i.e. PD1) to also have an external interrupt on the CS line. You can probably set up the EXTI on PC1. I don't think there's anything stopping you using PC1 for SPI NSS and enabling an EXTI on it too.

Are you sure HPE is enabled?

FYI, HPE is enabled by default in the WCH standard start-up assembly code. It looks like OP has adapted one of the SPI example projects from the WCH EVT, so will almost certainly be using that.
 
The following users thanked this post: gotofbi

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Porting SPI Slave from AVR to CH32V003
« Reply #11 on: October 24, 2025, 11:43:15 pm »
Are you sure HPE is enabled?

FYI, HPE is enabled by default in the WCH standard start-up assembly code. It looks like OP has adapted one of the SPI example projects from the WCH EVT, so will almost certainly be using that.

I know that. I'm trying to establish whether OP understands their code or vibed it. Sadly, a necessity these days.
 
The following users thanked this post: gotofbi

Offline AVI-crak

  • Regular Contributor
  • *
  • Posts: 151
  • Country: ru
    • Rtos
Re: Porting SPI Slave from AVR to CH32V003
« Reply #12 on: October 25, 2025, 09:55:02 pm »
It is very simple code.
Your code is a single unit = hardware + software (or driver, for that matter) + logic + user level.
To port your code to other platforms, you need to separate the hardware and software layers. Then your idea will work on any hardware.
There's a ton of ready-made hardware and software code for the CH32V. Much of it is included in the project's source files. Unlike STM, you don't need to complete a quest to defeat the peripherals. The CH32V chips are just for a week of experimentation, which is a bit sad.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->