I’m trying to develop a microsecond counter. It would be a part of the driver of a temperature sensor, whose data transmission pattern requires monitoring time intervals on the microsecond order of magnitude, like for example the DHT11 humidity and temperature Sensor. And there is a chance to develop this delay timer with the Pulse Width Modulation (PWM) interrupt system, as recommended in the FE310-G002 Manual.
The basic idea is to count the interrupts associated to the pwmcmp0 register of a 1 MHz frequency PWM. And for debugging purposes there are three led that blink accordingly to the required frequencies.
The variable counter
holds the count of the interrupts served. Everything goes fine with low frequencies. With 1 Hz is easy to count the blinks of the leds and confirm that the value of counter gets 60 in one minute, as expected.
But when trying with 1 MHz everything goes wrong. The system destabilizes and crashes frequently, so much that it requires a reboot. Moreover, it doesn’t count well. The counter is expected to go up by one million every second. However, it does not reach more than 100,000 per second.
Why does it work with low frequencies and not with high ones? I suspect that JTAG Clocking may have something to do with it, or that there is an interrupt priority problem. In any case, I would appreciate any kind of information to be able to continue.
Below is a summarized and commented version of the code used.
Thank you!
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
typedef volatile uint32_t addr_t;
#define MAX_INTERRUPTS 16
// Arrays of pointers to void functions.
void (*interrupt_handler [MAX_INTERRUPTS]) ();
void (*exception_handler [MAX_INTERRUPTS]) ();
// -------- Pulse Width Modulator (PWM) -----------------------
#define FREQ_BASE 16 // MHz
#define PIN_PWM 11 // PWM dedicated pin
// For the time being, we will only work with instance number 2.
#define PWM_BASE_ADDR2 0x10035000U
struct PWM_ADDRS...
struct PWM_ADDRS *PWM02 = (struct PWM_ADDRS*) PWM_BASE_ADDR2;
// -------- General Purpose Input/Output Controller (GPIO) ---
#define GPIO_BASE_ADDR 0x10012000U
struct GPIO_ADDRS...
struct GPIO_ADDRS *GPIO = (struct GPIO_ADDRS*) GPIO_BASE_ADDR;
// -------- Platform-Level Interrupt Controller (PLIC) --------
#define PLIC_ENABLE_REG2 0x0C002004U
#define PLIC_PRIORITY_REG_48 0x0C0000C0U // PWM2 Interrupt source 48 -> 16
#define PLIC_THRESHOLD_REG 0x0C200000U
#define PLIC_CLAIM_COMP_REG 0x0C200004U
#define PLIC_PENDING_REG2 0x0C001004
// -------- Core-Local Interruptor (CLINT) --------------------
#define MTIMECMP (uint64_t *) 0x02004000UL
#define MTIME (uint64_t *) 0x200bff8UL
#define TIMER_FREQ 32768 // 2^15 Hz
#define BLINK_FREQ 4
// Macros for reading and writing the control and status registers (CSRs)
#define read_csr(reg) ({ unsigned long __tmp; asm volatile ("csrr %0, " #reg : "=r"(__tmp)); __tmp; })
#define write_csr(reg, val) ({ asm volatile ("csrw " #reg ", %0" :: "rK"(val)); })
volatile uint64_t counter;
int main(void)
{
// General setup. Three LEDs have been configured to display the frequencies.
// Pin 5 for CLINT timer interrupts. Pin 11 is dedicated to the PWM output.
// Pin 10 will display each time the PLIC handler is called.
// "counter" is the variable in which a counter that tracks the PWM frequency
// is expected to be stored.
pin_setup (5, 'O'); // Configured as Ouput pins.
pin_setup (10, 'O');
counter = 0;
reset_timer (TIMER_FREQ / (2 * BLINK_FREQ));
interrupt_handler [7] = timer_handler;
interrupt_handler [11] = PLIC_handler;
// Registration of the trap handler in mtvec.
write_csr(mtvec, ((unsigned long) handle_trap) & ~(0b11));
// Enabling of the interrupts
write_csr(mstatus, read_csr(mstatus) | (1 << 3));
write_csr(mie, read_csr(mie) | (1 << 7));
write_csr(mie, read_csr(mie) | (1 << 11));
// PLIC setup.
// 1.- Enabling in this case PWM2 Interrupt source 48, Interrupt ID 16
// whitch corresponds to pwmcmp0 of instance number 2 of PWM.
uint32_t *PLIC_I_EN = (uint32_t *) PLIC_ENABLE_REG2;
int val_en = (*PLIC_I_EN >> 16) & 0x1;
if (!val_en) *PLIC_I_EN |= (1 << 16);
// 2.- Priority setup of th interrupt source 48.
uint32_t *PLIC_PRTY = (uint32_t *) PLIC_PRIORITY_REG_48;
*PLIC_PRTY &= 0x0;
*PLIC_PRTY |= 0x1;
// 3.- Threshold setup of the HART.
uint32_t *PLIC_THR = (uint32_t*) PLIC_THRESHOLD_REG;
*PLIC_THR &= 0x0;
// PWM setup. In these conditions pin 10 blinks once per second (1 Hz),
// and pin 11 blinks once every 2 seconds (0.5 Hz).
pwmInit ();
pwm (1, 0.5);
printf ("counter %lld\n", counter);
while(1) {};
return 0;
}
void PLIC_handler (void)
{
// Dummy handler function that simply toggles the LED in pin 10 whenever
// the PLIC interrupt is served, and also increments the counter.
int pin_val = (GPIO->output_val >> 10) & 1;
if (pin_val) gpio_clear_set (10,0);
else gpio_clear_set (10,1);
counter++;
}
void timer_handler (void)
{
// Dummy handler function that simply toggles LED in pin 5 whenever the
// CLINT interrupt is served.
int pin_val = (GPIO->output_val >> 5) & 1;
if (pin_val) gpio_clear_set (5,0);
else gpio_clear_set (5,1);
reset_timer (TIMER_FREQ / (2 * BLINK_FREQ));
}
void handle_trap (void)
{
uint32_t mcause_value = read_csr (mcause);
uint32_t mcause_type = (mcause_value >> 31) & 1;
uint32_t mcause_code = mcause_value & 0x3FF;
uint32_t *plic_id = (uint32_t *) PLIC_CLAIM_COMP_REG;
if (mcause_type == 1)
switch (mcause_code)
{
case 7:
interrupt_handler [7] ();
break;
case 11:
if (*plic_id == 48)
{
interrupt_handler [11] ();
*plic_id = 48;
}
else *plic_id = *plic_id;
break;
}
else
exception_handler [mcause_code] ();
}