Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem with adding CH32x035 example (cap_touch_adc) #479

Open
yves5141 opened this issue Dec 21, 2024 · 18 comments
Open

Problem with adding CH32x035 example (cap_touch_adc) #479

yves5141 opened this issue Dec 21, 2024 · 18 comments

Comments

@yves5141
Copy link

yves5141 commented Dec 21, 2024

Hi,
I'm trying to make some of the examples work on a CH32x035G8U6.
But at the moment I'm struggling with the cap_touch_adc example.
I changed line 54 in ch32v003_touch.h, since CH32x035 has a 64-bit counter for SysTick.
For testing, I changed cap_touch_adc.c, in order to get values only from PA0.
However, the program crashes and jumps to line 767 in ch32v003fun.c (while( !DebugPrintfBufferFree() );). I did some debugging, the code works until it gets to line 87 in ch32v003_touch.h, switching to assembly, it happens at 0x00000734:

0x00000734: 1c 44           	lw	a5,8(s0)
0x00000736: 5c 44           	lw	a5,12(s0)
0x00000738: f9 bf           	j	0x716 <main+30>

I'm not sure, how to solve this.
BTW: Not directly related, but I couldn't find a document for the CH32x035 containing the instruction set and listing all the registers. Where can I find that?

ch32v003_touch.h

#ifndef _CH32V003_TOUCH_H
#define _CH32V003_TOUCH_H

/** ADC-based Capactive Touch Control.

	see cap_touch_adc.c for an example.

	// Enable GPIOD, C and ADC
	RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC | RCC_APB2Periph_ADC1;
	InitTouchADC();


	// Then do this any time you want to read some touches.
	sum[0] += ReadTouchPin( GPIOA, 2, 0, iterations );
	sum[1] += ReadTouchPin( GPIOA, 1, 1, iterations );
	sum[2] += ReadTouchPin( GPIOC, 4, 2, iterations );
	sum[3] += ReadTouchPin( GPIOD, 2, 3, iterations );
	sum[4] += ReadTouchPin( GPIOD, 3, 4, iterations );
	sum[5] += ReadTouchPin( GPIOD, 5, 5, iterations );
	sum[6] += ReadTouchPin( GPIOD, 6, 6, iterations );
	sum[7] += ReadTouchPin( GPIOD, 4, 7, iterations );
*/



#define TOUCH_ADC_SAMPLE_TIME 2  // Tricky: Don't change this without a lot of experimentation.

// Can either be 0 or 1.
// If 0: Measurement low and rises high.  So more pressed is smaller number.
// If 1: Higher number = harder press. Good to pair with TOUCH_FLAT.
// If you are doing more prox, use mode 0, otherwise, use mode 1.
#define TOUCH_SLOPE     1

// If you set this to 1, it will glitch the line, so it will only read
// anything reasonable if the capacitance can overcome that initial spike.
// Typically, it seems if you use this you probbly don't need to do
// any pre-use calibration.
#define TOUCH_FLAT      0

// Macro used for force-alingining ADC timing
#define FORCEALIGNADC \
	asm volatile( \
		"\n\
		.balign 4\n\
		andi a2, %[cyccnt], 3\n\
		c.slli a2, 1\n\
		c.addi a2, 12\n\
		auipc a1, 0\n\
		c.add  a2, a1\n\
		jalr a2, 1\n\
		.long 0x00010001\n\
		.long 0x00010001\n\
		"\
		:: [cyccnt]"r"(SysTick->CNTL) : "a1", "a2"\
	);


static void InitTouchADC( );
void InitTouchADC( )
{
	// ADCCLK = 24 MHz => RCC_ADCPRE = 0: divide sys clock by 2
	RCC->CFGR0 &= ~(0x1F<<11);

	// Set up single conversion on chl 2
	ADC1->RSQR1 = 0;
	ADC1->RSQR2 = 0;

	// turn on ADC and set rule group to sw trig
	ADC1->CTLR2 |= ADC_ADON | ADC_EXTSEL;
	
	// Reset calibration
	ADC1->CTLR2 |= ADC_RSTCAL;
	while(ADC1->CTLR2 & ADC_RSTCAL);
	
	// Calibrate
	ADC1->CTLR2 |= ADC_CAL;
	while(ADC1->CTLR2 & ADC_CAL);
}

// Run from RAM to get even more stable timing.
// This function call takes about 8.1uS to execute.
static uint32_t ReadTouchPin( GPIO_TypeDef * io, int portpin, int adcno, int iterations ) __attribute__((noinline, section(".srodata")));
uint32_t ReadTouchPin( GPIO_TypeDef * io, int portpin, int adcno, int iterations )
{
	uint32_t ret = 0;

	__disable_irq();
	FORCEALIGNADC
	ADC1->RSQR3 = adcno;
	ADC1->SAMPTR2 = TOUCH_ADC_SAMPLE_TIME<<(3*adcno);
	__enable_irq();

	uint32_t CFGBASE = io->CFGLR & (~(0xf<<(4*portpin)));
	uint32_t CFGFLOAT = ((GPIO_CFGLR_IN_PUPD)<<(4*portpin)) | CFGBASE;
	uint32_t CFGDRIVE = (GPIO_CFGLR_OUT_2Mhz_PP)<<(4*portpin) | CFGBASE;

	// If we run multiple times with slightly different wait times, we can
	// reduce the impact of the ADC's DNL.


#if TOUCH_FLAT == 1
#define RELEASEIO io->BSHR = 1<<(portpin+16*TOUCH_SLOPE); io->CFGLR = CFGFLOAT;
#else
#define RELEASEIO io->CFGLR = CFGFLOAT; io->BSHR = 1<<(portpin+16*TOUCH_SLOPE);
#endif

#define INNER_LOOP( n ) \
	{ \
		/* Only lock IRQ for a very narrow window. */                           \
		__disable_irq();                                                        \
		FORCEALIGNADC                                                           \
                                                                                \
		/* Tricky - we start the ADC BEFORE we transition the pin.  By doing    \
			this We are catching it onthe slope much more effectively.  */      \
		ADC1->CTLR2 = ADC_SWSTART | ADC_ADON | ADC_EXTSEL;                      \
                                                                                \
		ADD_N_NOPS( n )                                                         \
                                                                                \
		RELEASEIO                                                               \
																			    \
		/* Sampling actually starts here, somewhere, so we can let other        \
			interrupts run */                                                   \
		__enable_irq();                                                         \
		while(!(ADC1->STATR & ADC_EOC));                                        \
		io->CFGLR = CFGDRIVE;                                                   \
		io->BSHR = 1<<(portpin+(16*(1-TOUCH_SLOPE)));                          \
		ret += ADC1->RDATAR;                                                    \
	}

	int i;
	for( i = 0; i < iterations; i++ )
	{
		// Wait a variable amount of time based on loop iteration, in order
		// to get a variety of RC points and minimize DNL.

		INNER_LOOP( 0 );
		INNER_LOOP( 2 );
		INNER_LOOP( 4 );
	}

	return ret;
}

// Run from RAM to get even more stable timing.
// This function call takes about 8.1uS to execute.
static uint32_t ReadTouchPinSafe( GPIO_TypeDef * io, int portpin, int adcno, int iterations ) __attribute__((noinline, section(".srodata")));
uint32_t ReadTouchPinSafe( GPIO_TypeDef * io, int portpin, int adcno, int iterations )
{
	uint32_t ret = 0;

	ADC1->RSQR3 = adcno;
	ADC1->SAMPTR2 = TOUCH_ADC_SAMPLE_TIME<<(3*adcno);

	// If we run multiple times with slightly different wait times, we can
	// reduce the impact of the ADC's DNL.

#define INNER_LOOP_SAFE( n ) \
	{ \
		/* Only lock IRQ for a very narrow window. */                           \
		__disable_irq();                                                        \
		                                                                        \
		FORCEALIGNADC                                                           \
                                                                                \
		/* Tricky - we start the ADC BEFORE we transition the pin.  By doing    \
			this We are catching it onthe slope much more effectively.  */      \
		ADC1->CTLR2 = ADC_SWSTART | ADC_ADON | ADC_EXTSEL;                      \
                                                                                \
		ADD_N_NOPS( n )                                                         \
                                                                                \
		io->CFGLR = ((GPIO_CFGLR_IN_PUPD)<<(4*portpin)) | (io->CFGLR & (~(0xf<<(4*portpin))));                                                 \
        io->BSHR = 1<<(portpin+16*TOUCH_SLOPE);                                \
																			    \
		/* Sampling actually starts here, somewhere, so we can let other        \
			interrupts run */                                                   \
		__enable_irq();                                                         \
		while(!(ADC1->STATR & ADC_EOC));                                        \
		__disable_irq();                                                        \
		io->CFGLR = (GPIO_CFGLR_OUT_2Mhz_PP)<<(4*portpin) | (io->CFGLR & (~(0xf<<(4*portpin))));                                                  \
		__enable_irq();                                                         \
		io->BSHR = 1<<(portpin+(16*(1-TOUCH_SLOPE)));                          \
		ret += ADC1->RDATAR;                                                    \
	}

	int i;
	for( i = 0; i < iterations; i++ )
	{
		// Wait a variable amount of time based on loop iteration, in order
		// to get a variety of RC points and minimize DNL.

		INNER_LOOP_SAFE( 0 );
		INNER_LOOP_SAFE( 2 );
		INNER_LOOP_SAFE( 4 );
	}

	return ret;
}


#endif

/*
 * MIT License
 * 
 * Copyright (c) 2023 Valve Corporation
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

cap_touch_adc.c

/*
	ADC-aided capacitive touch sensing.

	This demonstrates use of the ADC with cap sense pads. By using the ADC, you
	can get much higher resolution than if you use the exti.  But, you are
	limited by the number of ADC lines, i.e. you can only support a maximum of
	eight (8) touch inputs with this method.

	In exchange for that, typically you can get > 1,000 LSBs per 1 CM^2 and it
	can convert faster.  Carefully written code can be as little as 2us per
	conversion!

	The mechanism of operation for the touch sensing on the CH32V003 is to:
		* Hold an IO low.
		* Start the ADC
		* Use the internal pull-up to pull the line high.
		* The ADC will sample the voltage on the slope.
		* Lower voltage = longer RC respone, so higher capacitance. 
*/

#include "ch32v003fun.h"
#include <stdio.h>

#include "ch32v003_touch.h"

int main()
{
	SystemInit();

	printf("Capacitive Touch ADC example\n");
	
	// Enable GPIOD, C and ADC
	RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1;

	InitTouchADC();
	
	// should be ready for SW conversion now
	while(1)
	{
		uint32_t sum[8] = { 0 };

		uint64_t start = SysTick->CNTL;
		start = start | ((uint64_t)SysTick->CNTH << 32);

		// Sampling all touch pads, 3x should take 6030 cycles, and runs at about 8kHz

		int iterations = 3;/*
		sum[0] += ReadTouchPin( GPIOA, 2, 0, iterations );
		sum[1] += ReadTouchPin( GPIOA, 1, 1, iterations );
		sum[2] += ReadTouchPin( GPIOC, 4, 2, iterations );
		sum[3] += ReadTouchPin( GPIOD, 2, 3, iterations );
		sum[4] += ReadTouchPin( GPIOD, 3, 4, iterations );
		sum[5] += ReadTouchPin( GPIOD, 5, 5, iterations );
		sum[6] += ReadTouchPin( GPIOD, 6, 6, iterations );
		sum[7] += ReadTouchPin( GPIOD, 4, 7, iterations );
		*/
		sum[0] += ReadTouchPin( GPIOA, 0, 0, iterations );

		uint64_t end = SysTick->CNTL;
		end = end | ((uint64_t)SysTick->CNTH << 32);
	
		printf( "%d %d %d %d %d %d %d %d %d\n", (int)sum[0], (int)sum[1], (int)sum[2],
			(int)sum[3], (int)sum[4], (int)sum[5], (int)sum[6], (int)sum[7], (int)(end-start) );
	}
}
@TommyMurphyTM1234
Copy link
Contributor

Hi, I'm trying to make some of the examples work on a CH32x035G8U6. But at the moment I'm struggling with the cap_touch_adc example. I changed line 54 in ch32v003_touch.h, since CH32x035 has a 64-bit counter for SysTick. For testing, I changed cap_touch_adc.c, in order to get values only from PA0. However, the program crashes and jumps to line 767 in ch32v003fun.c (while( !DebugPrintfBufferFree() );). I did some debugging, the code works until it gets to line 87 in ch32v003_touch.h, switching to assembly, it happens at 0x00000734:

0x00000734: 1c 44           	lw	a5,8(s0)
0x00000736: 5c 44           	lw	a5,12(s0)
0x00000738: f9 bf           	j	0x716 <main+30>

What happens when it tries to execute the lw instruction at 0x00000734?
Does it trap or something?
If so then what are the values of CSRs such as mcause, mepc, mtval etc.?

@yves5141
Copy link
Author

Of course, sorry I forgot: MSTATUS:00000088 MTVAL:300027f3 MCAUSE:00000002 MEPC:00000216

Right after executing 0x00000734 it jumps to while( !DebugPrintfBufferFree() );, prints the values of CSRs (above) and then goes to

	// Infinite Loop
	asm volatile( "1: j 1b" );

@TommyMurphyTM1234
Copy link
Contributor

TommyMurphyTM1234 commented Dec 21, 2024

Of course, sorry I forgot: MSTATUS:00000088 MTVAL:300027f3 MCAUSE:00000002 MEPC:00000216

As far as I can see...

I'm not familiar with the CH32x035 - is there any reason that this instruction would be unsupported on that MCU?

@yves5141
Copy link
Author

I had a similiar issue when trying to use parts the of systick_irq example in one of my projects, it crashed when calling NVIC_EnableIRQ(SysTicK_IRQn);. However, that problem was solved easily by adding extern "C" { ... } in my .cpp file.

@TommyMurphyTM1234
Copy link
Contributor

I had a similiar issue when trying to use parts the of systick_irq example in one of my projects, it crashed when calling NVIC_EnableIRQ(SysTicK_IRQn);. However, that problem was solved easily by adding extern "C" { ... } in my .cpp file.

I don't see how that would help or how that's relevant here.

Did you check your disassembly against what I posted above?
Does your program have the csrrs x15, mstatus, x0 instruction at address 0x00000216 and is this actually where the fault is occurring?
If it does then somehow your target is seeing csrrs x15, mstatus, x0 as an illegal instruction.

@yves5141
Copy link
Author

Well, there is no csrrs in the disassembled file. At address 0x00000216 is:
216: 300027f3 csrr a5,mstatus

The strange thing is, during debugging, it's executing
0x00000732: e7 80 20 8d jalr -1838(ra) # 0x20000000 <ReadTouchPin>,
but it doesn't seem to perform the jump, next thing it does is executing
0x00000736: 1c 44 lw a5,8(s0)
This address does not correspond with the address in the disassembled file. There it is 61a
61a: 441c lw a5,8(s0)

firmeware.txt

@TommyMurphyTM1234
Copy link
Contributor

Well, there is no csrrs in the disassembled file. At address 0x00000216 is: 216: 300027f3 csrr a5,mstatus

csrr is a pseudoinstruction that is actually encoded as csrrs.

2024-12-22 23 21 07

The strange thing is, during debugging, it's executing 0x00000732: e7 80 20 8d jalr -1838(ra) # 0x20000000 <ReadTouchPin>, but it doesn't seem to perform the jump, next thing it does is executing 0x00000736: 1c 44 lw a5,8(s0) This address does not correspond with the address in the disassembled file. There it is 61a 61a: 441c lw a5,8(s0)

Are you sure that you're compiling the program with something like -g -Og in order to facilitate debugging and accurate line/instruction tracking?

@yves5141
Copy link
Author

I added g -Og -v to the flags. The address changed, but the behaviour is the same.
DEAD MSTATUS:00000088 MTVAL:300027f3 MCAUSE:00000002 MEPC:20000006
When running the code step-by-step, the program halts at
736: 441c lw a5,8(s0)
and then is handling the exception and throwing the informations above.

firmeware.txt

@yves5141
Copy link
Author

yves5141 commented Dec 24, 2024

Found this thread: https://github.com/eugene-tarassov/vivado-risc-v/issues/181
Might not be directly related to my problem, but this user has a similiar issue when trying to read CSR with mstatus. In his case, the problem is, he is running an OS so he has not enough privileges for mstatus and has to use ustatus.
However, this should not apply to my problem, since I'm running my code bare metal. How does the CPU decide, if code is running in machine mode or usermode?
Replacing mstatus with ustatus avoid mcause 2, but it leads to mcause 6, but this has something to do with the rest of the code which depends on working interrupts. Trying to access CSR with ustatus does nothing, I tried and it is not possible to change any values in CSR using ustatus.

EDIT: Looking at the reference manuals of ch32v003 and ch32x035, I found out, ch32v003 doesn't seem to have a PMP, thus this code works for ch32v003, but not for ch32x035 since it introduces memory access restrictions.
No I will have to find out, how to access CSR from lower privileged mode or switching to machine mode.
The latter would be sufficient for the moment, but I would be interested in how to do it "in the correct way".

@TommyMurphyTM1234
Copy link
Contributor

How does the CPU decide, if code is running in machine mode or usermode?

RISC-V boots into machine mode and will only enter a lower privilege mode, such as user mode, if there is (usually boot) code to do this. As you say this would normally only be an issue when using an [RT]OS that runs the kernel in machine mode and user threads/programs in user mode. I presume that that's not relevant in your case? (Does the CH32x035 MCU even support modes other than machine mode? I don't know and can't seem to access the WCH datasheet page lately).

Replacing mstatus with ustatus

I think that you're going down the wrong track here to be honest.

If the csrrs x15, mstatus, x0 is definitely the instruction that is causing the illegal instruction exception then you need to figure out why this is the case.

@yves5141
Copy link
Author

yves5141 commented Dec 24, 2024

I presume that that's not relevant in your case? (Does the CH32x035 MCU even support modes other than machine mode? I don't know and can't seem to access the WCH datasheet page lately).

Good question. Since the Ch32x035 has a PMP, it seems it can work in different modes. See reference manual on page 51.
However, when using the ch32v003fun framework, everything should work in machine mode, right?

If the csrrs x15, mstatus, x0 is definitely the instruction that is causing the illegal instruction exception then you need to figure out why this is the case.

Well, I'm working on it.

The datasheet says: "[...]the PMP unit is always in effect in user mode, and optionally in machine mode,
and will generate a system exception interrupt (EXC) if the current memory restrictions are violated."
And this is exactly what I experience.
I think I need to place some code at an address space, where code can be executed in machine mode, in order to disable PMP.

CH32X035DS0.PDF
CH32X035RM.PDF

@yves5141
Copy link
Author

yves5141 commented Dec 25, 2024

I ended up adding:

#if defined ( CH32X03x )
asm volatile(
"	li t0, 0x1888\n\
	csrw mstatus, t0\n\
");
#endif

to ch32v003fun.c in the handle_reset() method. According to https://github.com/WuxiProject-offical/CH32X035-HelperLibrary/blob/main/startup_file/startup_ch32x035_highcode_machine.S this enters machine mode and enables global interrupts.

Seems to work, at least I don't get MCAUSE:00000002, now I get MCAUSE:00000006 when leaving the for-loop in ch32v003_touch.h for some reason, but this is another problem.

@TommyMurphyTM1234
Copy link
Contributor

TommyMurphyTM1234 commented Dec 25, 2024

I doubt that the device boots into anything other than machine mode and, in any case, the startup/reset code in question is writing to mstatus which can only be done in machine mode so I'm not sure that the analysis/change here is actually thoroughly understood.

FWIW this is what the WCH EVT does at startup:

/* Enable global interrupt and configure privileged mode */
   	li t0, 0x88           
   	csrw mstatus, t0

@yves5141
Copy link
Author

I doubt that the device boots into anything other than machine mode and, in any case, the startup/reset code in question is writing to mstatus which can only be done in machine mode so I'm not sure that the analysis/change here is actually thoroughly understood.

Well, I can only draw conclusions from what the official documentation tells me. Assumptions lead nowhere.
Anyway, I don't claim my solution to be the best solution. Maybe there is something else missing.

FWIW this is what the WCH EVT does at startup:

* https://github.com/openwch/ch32x035/blob/ed56d28751ca2749db1f829307b434e4682b7de4/EVT/EXAM/SRC/Startup/startup_ch32x035.S#L214-L216
/* Enable global interrupt and configure privileged mode */
   	li t0, 0x88           
   	csrw mstatus, t0

But where is the ch32v003fun framework doing this?
If the reference manual is right, the standard mode is user mode. If that's the case, there are areas in flash, which can execute code in machine mode by default. The reset handler seems to belong to this area, since there CSR instructions can be executed without raising exceptions.

According to https://github.com/WuxiProject-offical/CH32X035-HelperLibrary/blob/main/startup_file/startup_ch32x035_highcode_machine.S this enters machine mode and enables global interrupts.

Link doesn't work. I presume that you meant this?

* https://github.com/WuxiProject-offical/CH32X035-HelperLibrary/blob/e6ebd99c2cd487d9bceac6caabe9a09ee0ef2207/startup_file/startup_ch32x035_highcode_machine.S#L232-L235

That's it.

@cnlohr
Copy link
Owner

cnlohr commented Dec 26, 2024

  1. Have you enabled interrupts in this project or not? If not at all, then yes, let's dig in more. If so, please disable them and debug from there.

  2. If you are going to use the examples, please do not use C++. You can use C++ only after you have all the basic stuff working. You must take care when trying to use interrupts in C++.

  3. I haven't tested cap_touch_adc on the x035. It wasn't specifically designed to work with it, but should in principle.

@TommyMurphyTM1234 Re: mstatus, we set it right here: https://github.com/cnlohr/ch32v003fun/blob/master/ch32v003fun/ch32v003fun.c#L1012-L1020 - what is the issue there?

In general, why are there so many links here to WuxiProject?

I am available now, but I am really confused, what is the current issue you are having?

@TommyMurphyTM1234
Copy link
Contributor

@TommyMurphyTM1234 Re: mstatus, we set it right here: https://github.com/cnlohr/ch32v003fun/blob/master/ch32v003fun/ch32v003fun.c#L1012-L1020 - what is the issue there?

I haven't asserted that there's any issue with what ch32v003fun does with mstatus.

but I am really confused

Me too. :⁠-⁠(

@cnlohr
Copy link
Owner

cnlohr commented Dec 29, 2024

@yves5141 I still can't make heads or tails of what you're saying. ch32v003fun performs this operation https://github.com/cnlohr/ch32v003fun/blob/master/ch32v003fun/ch32v003fun.c#L1012-L1020

I don't think you're looking at ch32v003fun.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants