#include "rtapi.h"
#include "rtapi_app.h"
#include "hal.h"
#define MAX_CHAN 8
MODULE_AUTHOR("John Kasunich");
MODULE_DESCRIPTION("PWM/PDM Generator for EMC HAL");
MODULE_LICENSE("GPL");
#define MAX_OUTPUT_TYPE 2
int output_type[MAX_CHAN] = { -1, -1, -1, -1, -1, -1, -1, -1 };
RTAPI_MP_ARRAY_INT(output_type, 8, "output types for up to 8 channels");
#define PWM_DISABLED 0
#define PWM_PURE 1
#define PWM_DITHER 2
#define PWM_PDM 3
typedef struct {
long period;
long high_time;
long period_timer;
long high_timer;
unsigned char curr_output;
unsigned char output_type;
unsigned char pwm_mode;
unsigned char direction;
hal_bit_t *out[2];
hal_bit_t *enable;
hal_float_t *value;
hal_float_t *scale;
hal_float_t *offset;
double old_scale;
double scale_recip;
hal_float_t *pwm_freq;
double old_pwm_freq;
int periods;
double periods_recip;
hal_bit_t *dither_pwm;
hal_float_t *min_dc;
hal_float_t *max_dc;
hal_float_t *curr_dc;
} pwmgen_t;
static pwmgen_t *pwmgen_array;
#define PWM_PIN 0
#define DIR_PIN 1
#define UP_PIN 0
#define DOWN_PIN 1
static int comp_id;
static int num_chan;
static long periodns;
static int export_pwmgen(int num, pwmgen_t * addr, int output_type);
static void make_pulses(void *arg, long period);
static void update(void *arg, long period);
int rtapi_app_main(void)
{
int n, retval;
for (n = 0; n < MAX_CHAN && output_type[n] != -1 ; n++) {
if ((output_type[n] > MAX_OUTPUT_TYPE) || (output_type[n] < 0)) {
rtapi_print_msg(RTAPI_MSG_ERR,
"PWMGEN: ERROR: bad output type '%i', channel %i\n",
output_type[n], n);
return -1;
} else {
num_chan++;
}
}
if (num_chan == 0) {
rtapi_print_msg(RTAPI_MSG_ERR,
"PWMGEN: ERROR: no channels configured\n");
return -1;
}
periodns = -1;
comp_id = hal_init("pwmgen");
if (comp_id < 0) {
rtapi_print_msg(RTAPI_MSG_ERR, "PWMGEN: ERROR: hal_init() failed\n");
return -1;
}
pwmgen_array = hal_malloc(num_chan * sizeof(pwmgen_t));
if (pwmgen_array == 0) {
rtapi_print_msg(RTAPI_MSG_ERR,
"PWMGEN: ERROR: hal_malloc() failed\n");
hal_exit(comp_id);
return -1;
}
for (n = 0; n < num_chan; n++) {
retval = export_pwmgen(n, &(pwmgen_array[n]), output_type[n]);
if (retval != 0) {
rtapi_print_msg(RTAPI_MSG_ERR,
"PWMGEN: ERROR: pwmgen %d var export failed\n", n);
hal_exit(comp_id);
return -1;
}
}
retval = hal_export_funct("pwmgen.make-pulses", make_pulses,
pwmgen_array, 0, 0, comp_id);
if (retval != 0) {
rtapi_print_msg(RTAPI_MSG_ERR,
"PWMGEN: ERROR: makepulses funct export failed\n");
hal_exit(comp_id);
return -1;
}
retval = hal_export_funct("pwmgen.update", update,
pwmgen_array, 1, 0, comp_id);
if (retval != 0) {
rtapi_print_msg(RTAPI_MSG_ERR,
"PWMGEN: ERROR: update funct export failed\n");
hal_exit(comp_id);
return -1;
}
rtapi_print_msg(RTAPI_MSG_INFO,
"PWMGEN: installed %d PWM/PDM generators\n", num_chan);
hal_ready(comp_id);
return 0;
}
void rtapi_app_exit(void)
{
hal_exit(comp_id);
}
static void make_pulses(void *arg, long period)
{
pwmgen_t *pwmgen;
int n;
periodns = period;
pwmgen = arg;
for (n = 0; n < num_chan; n++) {
switch ( pwmgen->pwm_mode ) {
case PWM_PURE:
if ( pwmgen->curr_output ) {
pwmgen->high_timer += periodns;
if ( pwmgen->high_timer >= pwmgen->high_time ) {
pwmgen->curr_output = 0;
}
}
pwmgen->period_timer += periodns;
if ( pwmgen->period_timer >= pwmgen->period ) {
pwmgen->period_timer = 0;
pwmgen->high_timer = 0;
if ( pwmgen->high_time > 0 ) {
pwmgen->curr_output = 1;
}
}
break;
case PWM_DITHER:
if ( pwmgen->curr_output ) {
pwmgen->high_timer -= periodns;
if ( pwmgen->high_timer <= 0 ) {
pwmgen->curr_output = 0;
}
}
pwmgen->period_timer += periodns;
if ( pwmgen->period_timer >= pwmgen->period ) {
pwmgen->period_timer -= pwmgen->period;
pwmgen->high_timer += pwmgen->high_time;
if ( pwmgen->high_timer > 0 ) {
pwmgen->curr_output = 1;
}
}
break;
case PWM_PDM:
pwmgen->high_timer += pwmgen->high_time;
if ( pwmgen->curr_output ) {
pwmgen->high_timer -= periodns;
}
if ( pwmgen->high_timer > 0 ) {
pwmgen->curr_output = 1;
} else {
pwmgen->curr_output = 0;
}
break;
case PWM_DISABLED:
default:
pwmgen->curr_output = 0;
pwmgen->high_timer = 0;
pwmgen->period_timer = 0;
break;
}
if (pwmgen->output_type < 2) {
*(pwmgen->out[PWM_PIN]) = pwmgen->curr_output;
} else {
*(pwmgen->out[UP_PIN]) = pwmgen->curr_output & ~pwmgen->direction;
*(pwmgen->out[DOWN_PIN]) = pwmgen->curr_output & pwmgen->direction;
}
pwmgen++;
}
}
static void update(void *arg, long period)
{
static long oldperiodns=-1;
pwmgen_t *pwmgen;
int n, high_periods;
unsigned char new_pwm_mode;
double tmpdc, outdc;
pwmgen = arg;
for (n = 0; n < num_chan; n++) {
if ( *(pwmgen->max_dc) > 1.0 ) {
*(pwmgen->max_dc) = 1.0;
}
if ( *(pwmgen->min_dc) > *(pwmgen->max_dc) ) {
*(pwmgen->min_dc) = *(pwmgen->max_dc);
}
if ( *(pwmgen->min_dc) < 0.0 ) {
*(pwmgen->min_dc) = 0.0;
}
if ( *(pwmgen->max_dc) < *(pwmgen->min_dc) ) {
*(pwmgen->max_dc) = *(pwmgen->min_dc);
}
if ( *(pwmgen->scale) != pwmgen->old_scale ) {
pwmgen->old_scale = *(pwmgen->scale);
if ((*(pwmgen->scale) < 1e-20)
&& (*(pwmgen->scale) > -1e-20)) {
*(pwmgen->scale) = 1.0;
}
pwmgen->scale_recip = 1.0 / *(pwmgen->scale);
}
if ( *(pwmgen->enable) == 0 ) {
new_pwm_mode = PWM_DISABLED;
} else if ( *(pwmgen->pwm_freq) == 0 ) {
new_pwm_mode = PWM_PDM;
} else if ( *(pwmgen->dither_pwm) != 0 ) {
new_pwm_mode = PWM_DITHER;
} else {
new_pwm_mode = PWM_PURE;
}
if ( *(pwmgen->pwm_freq) != pwmgen->old_pwm_freq ) {
pwmgen->pwm_mode = PWM_DISABLED;
}
if ( ( pwmgen->pwm_mode != new_pwm_mode )
|| ( periodns != oldperiodns ) ) {
pwmgen->pwm_mode = PWM_DISABLED;
if ( *(pwmgen->pwm_freq) <= 0.0 ) {
*(pwmgen->pwm_freq) = 0.0;
pwmgen->period = periodns;
} else {
if ( *(pwmgen->pwm_freq) < 0.5 ) {
*(pwmgen->pwm_freq) = 0.5;
} else if ( *(pwmgen->pwm_freq) > ((1e9/2.0) / periodns) ) {
*(pwmgen->pwm_freq) = (1e9/2.0) / periodns;
}
if ( new_pwm_mode == PWM_PURE ) {
pwmgen->periods = (( 1e9 / *(pwmgen->pwm_freq) ) / periodns ) + 0.5;
pwmgen->periods_recip = 1.0 / pwmgen->periods;
pwmgen->period = pwmgen->periods * periodns;
*(pwmgen->pwm_freq) = 1.0e9 / pwmgen->period;
} else {
pwmgen->period = 1.0e9 / *(pwmgen->pwm_freq);
}
}
pwmgen->old_pwm_freq = *(pwmgen->pwm_freq);
}
tmpdc = *(pwmgen->value) * pwmgen->scale_recip + *(pwmgen->offset);
if ( pwmgen->output_type == 0 ) {
if ( tmpdc < 0.0 ) {
tmpdc = 0.0;
}
}
if (tmpdc >= 0.0) {
if ( tmpdc > *(pwmgen->max_dc) ) {
tmpdc = *(pwmgen->max_dc);
} else if ( tmpdc < *(pwmgen->min_dc) ) {
tmpdc = *(pwmgen->min_dc);
}
pwmgen->direction = 0;
outdc = tmpdc;
} else {
if ( tmpdc < -*(pwmgen->max_dc) ) {
tmpdc = -*(pwmgen->max_dc);
} else if ( tmpdc > -*(pwmgen->min_dc) ) {
tmpdc = -*(pwmgen->min_dc);
}
pwmgen->direction = 1;
outdc = -tmpdc;
}
if ( new_pwm_mode == PWM_PURE ) {
high_periods = (pwmgen->periods * outdc) + 0.5;
pwmgen->high_time = high_periods * periodns;
if ( tmpdc >= 0 ) {
*(pwmgen->curr_dc) = high_periods * pwmgen->periods_recip;
} else {
*(pwmgen->curr_dc) = -high_periods * pwmgen->periods_recip;
}
} else {
pwmgen->high_time = ( pwmgen->period * outdc ) + 0.5;
*(pwmgen->curr_dc) = tmpdc;
}
if ( pwmgen->output_type == 1 ) {
*(pwmgen->out[DIR_PIN]) = pwmgen->direction;
}
pwmgen->pwm_mode = new_pwm_mode;
pwmgen++;
}
oldperiodns = periodns;
}
static int export_pwmgen(int num, pwmgen_t * addr, int output_type)
{
int retval, msg;
msg = rtapi_get_msg_level();
rtapi_set_msg_level(RTAPI_MSG_WARN);
retval = hal_pin_float_newf(HAL_IO, &(addr->scale), comp_id,
"pwmgen.%d.scale", num);
if (retval != 0) {
return retval;
}
retval = hal_pin_float_newf(HAL_IO, &(addr->offset), comp_id,
"pwmgen.%d.offset", num);
if (retval != 0) {
return retval;
}
retval = hal_pin_bit_newf(HAL_IO, &(addr->dither_pwm), comp_id,
"pwmgen.%d.dither-pwm", num);
if (retval != 0) {
return retval;
}
retval = hal_pin_float_newf(HAL_IO, &(addr->pwm_freq), comp_id,
"pwmgen.%d.pwm-freq", num);
if (retval != 0) {
return retval;
}
retval = hal_pin_float_newf(HAL_IO, &(addr->min_dc), comp_id,
"pwmgen.%d.min-dc", num);
if (retval != 0) {
return retval;
}
retval = hal_pin_float_newf(HAL_IO, &(addr->max_dc), comp_id,
"pwmgen.%d.max-dc", num);
if (retval != 0) {
return retval;
}
retval = hal_pin_float_newf(HAL_OUT, &(addr->curr_dc), comp_id,
"pwmgen.%d.curr-dc", num);
if (retval != 0) {
return retval;
}
retval = hal_pin_bit_newf(HAL_IN, &(addr->enable), comp_id,
"pwmgen.%d.enable", num);
if (retval != 0) {
return retval;
}
*(addr->enable) = 0;
retval = hal_pin_float_newf(HAL_IN, &(addr->value), comp_id,
"pwmgen.%d.value", num);
if (retval != 0) {
return retval;
}
*(addr->value) = 0.0;
if (output_type == 2) {
retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[UP_PIN]), comp_id,
"pwmgen.%d.up", num);
if (retval != 0) {
return retval;
}
*(addr->out[UP_PIN]) = 0;
retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[DOWN_PIN]), comp_id,
"pwmgen.%d.down", num);
if (retval != 0) {
return retval;
}
*(addr->out[DOWN_PIN]) = 0;
} else {
retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[PWM_PIN]), comp_id,
"pwmgen.%d.pwm", num);
if (retval != 0) {
return retval;
}
*(addr->out[PWM_PIN]) = 0;
if ( output_type == 1 ) {
retval = hal_pin_bit_newf(HAL_OUT, &(addr->out[DIR_PIN]), comp_id,
"pwmgen.%d.dir", num);
if (retval != 0) {
return retval;
}
*(addr->out[DIR_PIN]) = 0;
}
}
*(addr->scale) = 1.0;
*(addr->offset) = 0.0;
*(addr->dither_pwm) = 0;
*(addr->pwm_freq) = 0;
*(addr->min_dc) = 0.0;
*(addr->max_dc) = 1.0;
*(addr->curr_dc) = 0.0;
addr->period = 50000;
addr->high_time = 0;
addr->period_timer = 0;
addr->high_timer = 0;
addr->curr_output = 0;
addr->output_type = output_type;
addr->pwm_mode = PWM_DISABLED;
addr->direction = 0;
addr->old_scale = *(addr->scale) + 1.0;
addr->old_pwm_freq = -1;
rtapi_set_msg_level(msg);
return 0;
}