For starters I want to set the stage again. My development environment is based on the MounRiver Studios IDE running on a Linux Mint PC. As I detailed in the previous hack on this subject I created some bash scripts to make things easier. In this project I have updated MounRiver to version 1.80. I also came across some anomalies in the Example programs given on the WCH github. For an unknown reason the individual examples given tend to exclude some references to the libraries for other peripherals. This can easily be solved by editing the .cproject file.... more on this later.
My aim here is to take a deeper dive into several peripherals covering more advanced concepts such as interrupts, as well as fixing some of the oversights in the github examples. The idea is to go beyond "Blinky", but we will be blinking the LED - as well as running a NEOpixel strip.
Getting a project up and going in Moun River
- this can be a challenge under Linux - there is no simple way to associate the IDE program with a ".wvproj" file. Anyway, even if we could, we would still have to do some manual intervention to get our project to be totally usable. So here is the procedure that I use:
- I like to create a separate directory with a "git clone" of the entire ch5xx repository to keep as reference.
- create a separate directory to use as a Moun River workspace.
- Open Moun River using the directory just created as workspace.
- select the "Create a new MounRiver Project" option, selecting the CH592F board.
- Exit Moun River.
- Add my files from the Files section into the CH592F/User directory in the workspace, overwriting existing files where necessary.
- You will then need to edit the "CH592F/.cproject" file to liberate all of the peripheral drivers.
Although I developed this project on a CH582 board I specified the CH592 above because there is no repo for the CH582 board, and the CH583 repo does not include samples for spi0. The 583 has 2 spi I/Fs but the 582 only has one.
At this point it is a good idea to close the IDE and edit the ".cproject" file from outside of the IDE. It should be located in your_workspace/CH592F. We will need to change the word "excluding" in line 263 where it says "<entry excluding="CH59x_adc.c|C..." to "including". This will liberate the excluded drivers in the "StdPeriphDriver" directory so we can use them later.
Now we can re-start the IDE and get to work!
Pin Re-Mapping:
Most CH5xx development modules use the smaller 'F' series MCUs which have a constrained set of I/O pins brought out on the chip. This may be exacerbated by board designs which may or not bring out all of the available pins. For this reason I wanted to include examples of how to select alternate pins. I found that I needed to do so after reviewing the I/O that I wanted to use for this project and closely examining the documentation for the MCU I am using. My constraints were not to use PA8 which is where the on-board LED is connected on many boards as well as being able to use spi0 for the NEOpixels. I also want to try to use a WCH-LinkE programmer on my own board designs.
Serial Example:
We begin with a serial example derived from the example automatically generated when we created the Moun River project. The IDE is nice enough to provide us with some "stdio" functionality for the serial ports. Although we see some examples using a PRINT() function, and other using a non-formatted function, printf() is available once the serial port is set up using the included driver. I've been writing in 'C' for too long and can't live without printf. Most of the CH5xx chips offer up to 4 serial ports with an alternate pin layout available for each. That said - the CH582F module that I used to develop this project left me with only 1 choice for the serial demo. I ended up with UART0 on the default pins. I wanted to include an example of selecting alternate pins in my code. (there is an unused function in the UartPins.c file where I select alt pins for UART3.)
The default UART used by the printf function is specified in the compiler instructions given in the ".cproject" file. You will see my change on line 128: value="DEBUG=0". This only affects the UART selection itself. Selecting alternate pins is done in our setup code.
So, in the end, we will only be able to learn the use of interrupts as far as more advanced functions for the UART Serial peripheral. I use them to receive characters to drive a simple Menu interface. Unlike printf, scanf is not available.
Serial Setup:
We only need to setup the TxD and RxD pins as follows:
My Uart0Pins_std() function:
void Uart0Pins_std() { // Setup standard UART0 pins
GPIOB_SetBits( bTXD0 ); // Set TxD high to avoid noise at boot time
GPIOB_ModeCfg( bTXD0, GPIO_ModeOut_PP_5mA); // TXD0=PB7, OutPut
GPIOB_ModeCfg( bRXD0, GPIO_ModeIN_PU); // RXD0=PB4, Input with Pull-Up
}
This need to be followed with some library function that set up baud rate (115200) and bits per char etc as well as the Interrupt on received characters.
UART0_DefInit(); // baudrate, 7-bit etc
UART0_INTCfg( ENABLE, RB_IER_RECV_RDY ); // Interrupt on Rcvd chars
PFIC_EnableIRQ(UART0_IRQn); // Enable UART0 Interrupts
after this we can use printf for the application and/or debugging.
EEPROM:
The CH5xx MCUs do not have true EEPROM capability but the libraries provide utilities to use Data Flash for the same purpose. You will see these used in various parts of my example code. My little menu system allows the setting of various application parameters, as well as, a method to save them and a method to clear the EEPROM.
void WriteValidEE() {
Control |= 0xA5000000; // add in validation key
EEPROM_WRITE( 0, &Control, 4 ); // write 4 bytes to adr 0
printf(" Control = 0x%08X\n", Control );
}
void ClearEE() {
EEPROM_WRITE( 0, &Zero, 4 ); // write 4 zeros
}
Timer 0 with Interrupt to blink the LED
The CH5xx chips contain four 26bit timers with GPIO output facilities. It would have been convenient to use a timer GPIO to flash the LED, but the LED is on pin A8 on the board I am using which has no connection to any of the timers. A8 is the UART1 RxD pin which is one of the reasons I chose to use UART0.
To make things interesting we are going to offer a Menu option to change the duty cycle of the LED blinker. We do this in the interrupt service routine by re-initializing the timer on each interrupt. We could have used the PWM mode of the timer or even one of the separate PWM peripherals although the latter is not real good at low frequency operations due to it being only 8 bit. Drop me a line in the comments if you would like to see a deeper dive into Timers and PWM in another project.
We only need 3 lines of code to setup the timer. The first line just sets the high water mark of the timer's counter. This will cause an interrupt to be generated. The other 2 lines configure just that condition to be the interrupt source and then enables interrupts for Timer 0.
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeOut_PP_5mA ); // A8 = LED, set to OutPut
TMR0_TimerInit( FREQ_SYS / ( Control & 15) ); // 100ms
TMR0_ITCfg(ENABLE, TMR0_3_IT_CYC_END); // IRQ at Count End
PFIC_EnableIRQ(TMR0_IRQn); // Enable IRQ
The Interrupt Service Routine is somewhat complex because we are changing the time count every IRQ to achieve the effect of a variable duty cycle. I recommend examining the source code given in the Files section for a closer look.
SPI setup for NeoPixels
We are going to use SPI0 to generate the pulse stream needed to do some lighting effects on the NEOpixel strip. I recommend you check out one of my other blogs about using SPI for NEOpixels. The SPI setup is quite simple, we only need to do the Default SPI Master Initialization and set the clock frequency. In this case I determined the frequency empirically using the Logic Analyzer.
SPI0_MasterDefInit(); SPI0_CLKCfg( 21 ); GPIOA_SetBits( bMOSI ); GPIOA_ModeCfg( ( bMOSI | bSCS ), GPIO_ModeOut_PP_5mA);
We are using the MOSI pin to output the pulse stream to the NEOpixel strip so need to set MOSI to output. In this example I am also setting the CS pin to output but only for the benefit of the Logic Analyzer.
I have written up a couple of routines to light up some pixels in a meaningful way. The Show routine just displays a buffer of pixel colours. The Bop routine takes each of the colours from the previous array and runs them down the strip for an attractive effect.
I hope you found our deeper dive into the CH5xx family of RISC-V MCUs to be interesting and informative. Check the External Links for this blog for future links to the podcast and video of this project. Keep in touch via the comments letting me know where else we should go diving. There are more peripherals to discover.