I’m currently working through the exercises in the latter part of ‘The Art of Electronics’ to learn how to program the CH32V microcontroller. Specifically, I’m struggling to implement an external interrupt on the CH32V203F8P6. I’d like to present the problem and my attempted solutions. My goal is not just to resolve this particular issue, but also to develop a stronger understanding of microcontroller programming principles in general. I’ve started reading ‘Making Embedded Systems’ by Elecia White and ‘Programming Embedded Systems in C and C++’ by Michael Barr to deepen my knowledge. What other learning strategies or resources would you suggest to improve my embedded systems skills? Or if you have a good story of how you first learned how to program chips at a low level that would be great too.
The Problem
What I am trying to implement is a increment and decrement of the pwm being outputted to an LED. In total that would have two external interrupts (the increment and decrement) and one output (the pwm).
The code and hardware is going to be different than the book in my case because I am not using the hardware called for in the book (it was an older outdated chip).
I have only attached one of the two push buttons that are needed. I did that because I wanted to get one working before I did the other.
Code
#include "ch32fun.h"
// Interrupt handler for EXTI line 1
void EXTI1_IRQHandler(void) __attribute__((interrupt));
int main()
{
SystemInit();
// Initialize the port A gpios
RCC->APB2PCENR |= RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1;
// Configure PA 10 for push-pull (PWM Output)
GPIOA->CFGHR &= ~(0xf << (4 * (10 - 8)));
GPIOA->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * (10 - 8));
// using this temporarily because I for some reason
// When I tried to set it myself the pin was sinking
// current.
funPinMode( PA1, GPIO_CFGLR_IN_FLOAT );
// Connect EXTI1 to PA1
AFIO->EXTICR[0] = AFIO_EXTICR1_EXTI1_PA;
// Initialize timer 1 for PWM
RCC->APB2PRSTR |= RCC_APB2Periph_TIM1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;
TIM1->PSC = 719; // Prescaler for 72MHz APB2 clock to 100kHz (adjust as needed)
TIM1->ATRLR = 255; // Auto-reload value for PWM period (adjust as needed)
TIM1->SWEVGR |= TIM_UG;
TIM1->CCER |= TIM_CC3E | TIM_CC3P;
TIM1->CHCTLR2 |= TIM_OC3M_2 | TIM_OC3M_1;
TIM1->BDTR |= TIM_MOE;
TIM1->CH3CVR = 0; // Initial duty cycle set to 0
TIM1->CTLR1 |= TIM_CEN;
// Interrupt setup for EXTI1
EXTI->INTENR = EXTI_INTENR_MR1; // Enable EXTI1 interrupt
EXTI->RTENR = EXTI_RTENR_TR1; // Rising edge trigger
EXTI->FTENR = EXTI_FTENR_TR1; // Falling edge trigger
NVIC_EnableIRQ(EXTI1_IRQn);
EXTI->INTFR = EXTI_Line1;
__enable_irq();
while(1)
{
}
}
void EXTI1_IRQHandler(void)
{
/*
This is only changing the duty cycle because
I wanted to check if it would work.
Later this would increment and decrement it.
*/
TIM1->CH3CVR = 200;
EXTI->INTFR = EXTI_Line1;
}
Now this code again is not doing exactly what I said I was going to get it to do yet because I have been troubleshooting it for a very long time. I pointed out in the comments that the interrupt only changes the brightness to a different brightness instead of incrementing it each time. I wanted to check if my increment was too small of if it was really the interrupt the problem.
Playing around this way did have the benefit that I was able to verify that the pwm was still working properly. I can change the duty cycle in the initiation code and it varies the brightness.
Documentation
Here is the links to the pdf copies of the documentation I have been going over.
Datasheet
Reference manual
Somewhat related to the documentation I was wondering if the register names in the reference manual can be treated like variables or how does a library specify which register is which? I am going to take a look at the ch32fun library myself and try to understand how everything is defined and apologize if it is a somewhat basic question.
Conclusion
I am almost positive that it is some register or flag that I am not thinking of and have not set right. I think my main problem is that I have not got enough experience or knowledge to know what to look for in the documentation. Given that I would be very appreciated to here any feedback on how to tackle learning new chips and perhaps a story on how others first learned to program different chip families.
It isn’t clear to me what the problem is. You said that it is changing the brightness, but are you looking at the output on an oscilloscope to verify it is doing what you want?
In general, when I’m doing something very hardware intensive on a microcontroller, I look at block diagrams to see what the hardware is that I’m interfacing to. In combination with that, I look at the register names that control the items in the block diagram, then look at the corresponding bitfield definitions in the register.
One other thing that makes this much easier is to use a development environment where you can step through code, look at register values, set break points, etc. Do you have all this available? I try not to use development environments that don’t support this level of troubleshooting.
I’m confused as to why you think your code is not working if you are able to change the output PWM duty cycle with a button press. It seems like that is working, based on what you’ve described thus far.
If the question is “how do I make my IRQ handler raise and lower the PWM value?”, then the answer is that you need a variable to keep track of the current PWM value. At each button press, raise or lower the value in the variable and then update the PWM register with that value.
Normally, you would initialize the variable before setting up the IRQ processing so that it is a valid value, and the do the initial loading of the PWM register with that value.
In your IRQ, you usually can manipulate the variable directly unless you allow IRQ pre-emption. So increment/decrement the variable in the handler and write the value to the PWM register. The up and down button ISRs should be able to reference the same variable by making the variable a static variable outside of the IRQ function scope.
Thanks for the answers. I think that I should clarify a few things
@mike.barber@ToyBuilder I think I misspoke somewhere. The brightness is not changing when I click the button, but if I change the code to change the initial duty cycle the brightness changes. I manually changed the code like that to verify the PWM part is functional and the problem is with the interrupt.
@mike.barber I am not using an oscilloscope at the moment, but had thought about it. I also do not currently have the step through ability. I think that it is possible in the set up I am using but I just do not have it set up correctly.
@LukeBeno I have looked through that repository and in fact am even on its discord. I am mostly wanting to set up the external interrupt as a learning exercise so that I can theoretically use it later.
The code I am working with now is setting aside the pwm in favour of just toggling a LED. That code is here:
#include "ch32fun.h"
void EXTI1_IRQHandler( void ) __attribute__((interrupt));
// set state of the LED on PA10
int state = 1;
int main()
{
SystemInit();
// Inititialize the port A gpios
RCC->APB2PCENR |= RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA;
// configure PA 10 for push-pull
GPIOA->CFGHR &= ~(0xf << (4 * (10 - 8))); // Clear configuration bits for PA10
GPIOA->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * (10 - 8)); // Set configuration bits for PA10
// configure PA 1 for floating input
GPIOA->CFGLR &= ~(0xf << (4 * (9 - 8)));
GPIOA->CFGLR |= (GPIO_CNF_IN_FLOATING) << (4 * (9 - 8)); // Set configuration bits for PA1
// Initial set PA10 high
GPIOA->BSHR = (1<<(10));
// Seting the Registers for
// interrupt
AFIO->EXTICR[0] = AFIO_EXTICR1_EXTI0_PA;
//I think something with AFIO ECR register is needed
// Interrupt setup for EXTI1
EXTI->INTENR = EXTI_INTENR_MR1; // Enable EXTI1 interrupt
EXTI->RTENR = EXTI_RTENR_TR1; // disable Rising edge trigger
EXTI->FTENR = EXTI_FTENR_TR1; // Falling edge
// enable toggle interuppt
NVIC_EnableIRQ( 23 ); // it is defined as 23
__enable_irq();
while(1)
{
}
}
void EXTI1_IRQHandler( void ) {
GPIOA->BSHR = (1<<(16+10)); // Set GPIOA pin 10 low
if (EXTI->INTFR & EXTI_Line1) { // Check if update interrupt occurred
if (state == 0)
{
GPIOA->BSHR = (1<<(10)); // Set GPIOA pin 10 high
state = 1;
} else
{
GPIOA->BSHR = (1<<(16+10)); // Set GPIOA pin 10 low
state = 0;
}
EXTI->INTFR = EXTI_Line1; // these may not be set right.
}
}
I think I am going to focus on getting the debug environment set up properly since I think it will be more useful to get a view into what is going on.
Thanks for all the responses. I have figured it out, but first I want to just quickly respond to the responses.
@ToyBuilder It is C code, but I did read through that string. @LukeBeno I did have a look at the Arduino core and may have a closer look at it later. Mostly because I still don’t have debug working.
I do however have my external interrupt working. It turned out that I hadn’t enabled the Alternate Function Clock Bit. I could have sworn that I had at some point but must have removed it during my rapid code changes trying to thrash to an answer. Here is my code that simply toggles an LED on button press based on the external interrupt.
#include "ch32fun.h"
void EXTI1_IRQHandler( void ) __attribute__((interrupt));
// set state of the LED on PA10
int state = 1;
int main()
{
SystemInit();
// Inititialize the port A gpios
RCC->APB2PCENR |= RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA;
RCC->APB2PCENR |= RCC_AFIOEN;
// configure PA 10 for push-pull
GPIOA->CFGHR &= ~(0xf << (4 * (10 - 8))); // Clear configuration bits for PA10
GPIOA->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * (10 - 8)); // Set configuration bits for PA10
// configure PA 1 for floating input
GPIOA->CFGLR &= ~(0xf << (4 * (9 - 8)));
GPIOA->CFGLR |= (GPIO_CNF_IN_FLOATING) << (4 * (9 - 8)); // Set configuration bits for PA1
// Initial set PA10 high
GPIOA->BSHR = (1<<(10));
// Seting the Registers for
// interrupt
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI1_PA;
//I think something with AFIO ECR register is needed
// Interrupt setup for EXTI1
EXTI->INTENR = EXTI_INTENR_MR1; // Enable EXTI1 interrupt
EXTI->RTENR = EXTI_RTENR_TR1; // disable Rising edge trigger
EXTI->FTENR = 0; // Falling edge
// enable toggle interuppt
NVIC_EnableIRQ( EXTI1_IRQn ); // it is defined as 23
EXTI->INTFR = EXTI_Line1;
while(1)
{
}
}
void EXTI1_IRQHandler( void ) {
GPIOA->BSHR = (1<<(16+10)); // Set GPIOA pin 10 low
if (EXTI->INTFR & EXTI_Line1) { // Check if update interrupt occurred
if (state == 0)
{
GPIOA->BSHR = (1<<(10)); // Set GPIOA pin 10 high
state = 1;
} else
{
GPIOA->BSHR = (1<<(16+10)); // Set GPIOA pin 10 low
state = 0;
}
EXTI->INTFR = EXTI_Line1; // these may not be set right.
}
}
The final solution (I do believe many things were wrong at the start) was to add this line.