Hey there,
recently got my Duo and started playing around a bit. I was curious how I could blink the LED from a FreeRTOS task instead of running it in Linux.
Turns out it’s not super hard. I’m not a RTOS pro, so I just started writing to the registers and got it working. There might be better ways to do it, but I was really just curious to check out how Linux runs on one core and an IO task on the second core.
In order to make this work, follow the RTOS setup from the other threads. Specifically you have to edit freertos/cvitek/kernel/include/riscv64/FreeRTOSConfig.h
and disable
#define configUSE_TICK_HOOK 0
Next edit freertos/cvitek/task/comm/src/riscv64/comm_main.c
/* Standard includes. */
#include <stdio.h>
/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "mmio.h"
#include "delay.h"
/* cvitek includes. */
#include "printf.h"
#include "rtos_cmdqu.h"
#include "cvi_mailbox.h"
#include "intr_conf.h"
#include "top_reg.h"
#include "memmap.h"
#include "comm.h"
#include "cvi_spinlock.h"
//#define __DEBUG__
#ifdef __DEBUG__
#define debug_printf printf
#else
#define debug_printf(...)
#endif
/****************************************************************************
* Function prototypes
****************************************************************************/
void pinMode( uint8_t port, uint8_t pin, uint8_t value );
void writePin( uint8_t port, uint8_t pin, uint8_t value );
uint8_t readPin( uint8_t port, uint8_t pin );
void app_task( void *param );
/****************************************************************************
* Global parameters
****************************************************************************/
#define TOP_BASE 0x03000000
// GPIO Register base
#define XGPIO (TOP_BASE + 0x20000 )
// GPIO Port offset
#define GPIO_SIZE 0x1000
// Port A external port register (read from here when configured as input)
#define GPIO_EXT_PORTA 0x50
// Port A data register
#define GPIO_SWPORTA_DR 0x00
// Port A data direction register
#define GPIO_SWPORTA_DDR 0x04
#define PORT_A 0
#define PORT_B 1
#define PORT_C 2
#define PORT_D 3
#define GPIO_INPUT 0
#define GPIO_OUTPUT 1
#define GPIO_LOW 0
#define GPIO_HIGH 1
#define PINMUX_BASE (TOP_BASE + 0x1000)
#define FMUX_GPIO_FUNCSEL_BASE 0xd4
#define FMUX_GPIO_FUNCSEL_MASK 0x7
#define FMUX_GPIO_FUNCSEL_GPIOC 3
#define FUNCSEL(port, pin) (FMUX_GPIO_FUNCSEL_BASE + port * 0x20u + pin)
#define BIT(x) (1UL << (x))
/* mailbox parameters */
volatile struct mailbox_set_register *mbox_reg;
volatile struct mailbox_done_register *mbox_done_reg;
volatile unsigned long *mailbox_context; // mailbox buffer context is 64 Bytess
/****************************************************************************
* Function definitions
****************************************************************************/
void pinMode( uint8_t port, uint8_t pin, uint8_t value ) {
mmio_clrsetbits_32(
XGPIO + GPIO_SIZE * port + GPIO_SWPORTA_DDR,
BIT( pin ), // erase Bit of PIN_NO( LED )
BIT( pin ) // set Bit of PIN_NO( LED )
);
}
void writePin( uint8_t port, uint8_t pin, uint8_t value ) {
uint32_t base_addr = XGPIO + GPIO_SIZE * port;
uint32_t reg_val = mmio_read_32( base_addr + GPIO_SWPORTA_DR );
reg_val = ( value == GPIO_HIGH ? ( reg_val | BIT(pin) ) : ( reg_val & (~BIT(pin)) ) );
mmio_write_32( base_addr + GPIO_SWPORTA_DR, reg_val );
}
uint8_t readPin( uint8_t port, uint8_t pin ) {
uint32_t base_addr = XGPIO + GPIO_SIZE * port;
uint32_t reg_val = 0;
// let's find out if this pin is configured as GPIO and INPUT or OUTPUT
uint32_t func = mmio_read_32( PINMUX_BASE + FUNCSEL( port, pin ) );
if( func == FMUX_GPIO_FUNCSEL_GPIOC ) {
uint32_t dir = mmio_read_32( base_addr + GPIO_SWPORTA_DDR );
if( dir & BIT( pin ) ) {
reg_val = mmio_read_32( GPIO_SWPORTA_DR + base_addr );
} else {
reg_val = mmio_read_32( GPIO_EXT_PORTA + base_addr );
}
} else {
printf( "%d not configured as GPIO\n", pin );
}
return( ( reg_val >> pin ) & 1 );
}
void app_task(void *param) {
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
// PortC Pin 24
uint8_t DUO_LED_PIN = 24;
uint8_t DUO_LED_PORT = PORT_C;
printf( "Start blink LED on port %d, pin %d\n", DUO_LED_PORT, DUO_LED_PIN );
// Show the pinmux (should be set to 3 to be a GPIO)
printf(
"PINMUX[%d,%d][0x%x]: 0x%x\n",
DUO_LED_PORT, DUO_LED_PIN,
PINMUX_BASE + FUNCSEL( DUO_LED_PORT, DUO_LED_PIN ),
mmio_read_32( PINMUX_BASE + FUNCSEL( DUO_LED_PORT, DUO_LED_PIN ) )
);
// set direction to output
pinMode( DUO_LED_PORT, DUO_LED_PIN, GPIO_OUTPUT );
while( 1 ) {
writePin( DUO_LED_PORT, DUO_LED_PIN, GPIO_HIGH );
vTaskDelay( xDelay );
writePin( DUO_LED_PORT, DUO_LED_PIN, GPIO_LOW );
vTaskDelay( xDelay );
}
}
DEFINE_CVI_SPINLOCK(mailbox_lock, SPIN_MBOX);
void main_cvirtos( void ) {
printf( "create cvi task\n" );
/* Start the tasks and timer running. */
xTaskCreate( app_task, "app_task", 1024, NULL, 1, NULL );
vTaskStartScheduler();
/* If all is well, the scheduler will now be running, and the following
line will never be reached. If the following line does execute, then
there was either insufficient FreeRTOS heap memory available for the idle
and/or timer tasks to be created, or vTaskStartScheduler() was called from
User mode. See the memory management section on the FreeRTOS web site for
more details on the FreeRTOS heap http://www.freertos.org/a00111.html. The
mode from which main() is called is set in the C start up code and must be
a privileged mode (not user mode). */
printf( "cvi task end\n" );
for( ;; )
;
}
You can either just build the entire image now or what I prefer, just run the build_uboot
target which will create the fip.bin
. This file can simply be copied to the boot-partition on the SD card. Which is super handy since you don’t need to burn the whole SD card each time you make changes to your FreeRTOS application. I haven’t found out how to load the fip.bin
during runtime. So I still run a reboot
after each new file.
In order to compile just uboot (which will have FreeRTOS as a dependency) you can run
export MILKV_BOARD=milkv-duo
source milkv/boardconfig-milkv-duo.sh
source build/milkvsetup.sh
defconfig cv1800b_milkv_duo_sd
build_uboot
The resulting file can be found in fsbl/build/cv1800b_milkv_duo_sd/fip.bin
From here I usually login to my Duo and run the following commands to update the file in the boot partition:
mkdir /root/boot
mount /dev/mmcblk0p1 /root/boot
scp user@your-build-machine:duo-buildroot-sdk/fsbl/build/cv1800b_milkv_duo_sd/fip.bin boot/fip.bin
umount /root/boot
reboot
You should also make sure you disable the blink.sh
on the Linux side. I don’t think anything bad would happen, since each core would just overwrite the GPIO register. But you want to make sure FreeRTOS is in charge and not Linux:
cd /mnt/system
mv blink.sh blink.sh.bak
Let me know if this was interesting. Next I want to try to get some communication going between Linux and FreeRTOS. Looks like the mailbox system is the way to go.