Im trying to implement a PWM controller in software.
I started by copying testsuite/klatency into testsuite/kpwm,
and changed all 'latency' to 'pwm', then sliced out the
many things I dont need.
Ive now got something that makes (w a few minor glitches..)
and runs, albeit incorrectly.
soekris:~/ts# ./run
*
*
* Type ^C to stop this application.
*
*
rtai_hal: no version for "struct_module" found: kernel tainted.
Adeos: Domain RTAI registered.
RTAI: hal/x86 loaded.
Adeos: Domain IShield registered.
RTAI: fusion core v0.8.2 (Satch Boogie) started.
RTAI: starting native API services.
kpwm_rt: initialized
RTAI: kpwm: setting alarm: 145000000d
RTAI: kpwm: sending pipe message
RTAI: kpwm: pipe message rcvr not ready
RTAI: kpwm: sending pipe message
RTAI: kpwm: pipe message rcvr not ready
open(/dev/rtp0): Success, fd: 3
got msg from kernel: Thu Jul 7 05:09:10 2005
got msg from kernel: Thu Jul 7 05:09:11 2005
got msg from kernel: Thu Jul 7 05:09:13 2005
got msg from kernel: Thu Jul 7 05:09:14 2005
got msg from kernel: Thu Jul 7 05:09:15 2005
got msg from kernel: Thu Jul 7 05:09:16 2005
got msg from kernel: Thu Jul 7 05:09:18 2005
got msg from kernel: Thu Jul 7 05:09:19 2005
The problem is that the actual time between messages is 55 seconds, not
1 second.
Im sure its a problem with my code, ie some usage error.
More specifically, I suspect that it might be because I dont have a separate
task servicing the alarm. I have 2 reasons for not doing so, maybe both
are wrong..
1. I dont know how to put the alarm-handler into a newly created task
Ive tried several permutations:
a. creating both task and alarm in init, but not starting task
b. starting task
c. moving alarm create,start into handler routine for start-task.
2. its not clear that I need a separate task, afterall, the alarm has a
handler routine,
its presumably run automatically in primary mode.
Since the list doesnt accept attachments (tgzs anyway, I tried) Im
cut-pasting
the file where I think the problem resides. I can send tarball
off-list if its helpful.
Any insight you can prod me towards is appreciated.
jimc
#include <rtai/task.h>
#include <rtai/timer.h>
#include <rtai/pipe.h>
#include <rtai/alarm.h>
#include "kpwm.h"
#include <linux/nsc_gpio.h>
extern struct gpio_access_methods *pc87366_access;
#define NAME "kpwm_rt"
MODULE_LICENSE("GPL");
// 3-state pwm signal generator (ONE technically has sub-state)
#define BOTH 0
#define ONE 1
#define NONE 2
int pwm_state = NONE;
// ONE's sub-state determines which output sig to change
int short_sig; // chooses shorter signal, ie which times out 1st (0,1)
int long_sig; // chooses longer signal, ie which times out 1st (0,1)
#define ONESHOT_LATENCY_BY2 0 // see ONE state in timer_sig_hndlr()
#define TASK_PERIOD_NS 16000000 // 16 mSec
int pwm_period_ns = TASK_PERIOD_NS;
module_param(pwm_period_ns,int,0444);
MODULE_PARM_DESC(pwm_period_ns, "PWM period in ns (default: 16*10e6)");
RT_TASK pwm_task;
RT_PIPE pipe;
RT_ALARM pwm_alarm;
// 1 dwell-timer for each state
RTIME pwm_timer[3] = { 1.5*10e6, 1*10e6, 14.5*10e6 };
// pin minor numbers to write vals to
int pinindex[2] = { 22,23 };
struct _cookie {
int i;
} cookie;
#define MONMSGS 20 // 000
int loopct = 0;
long totct = 0;
void one_shot (RTIME expiration)
{
int err;
xnarch_logerr("kpwm: setting alarm: %lu\n", expiration);
err = rt_alarm_start(&pwm_alarm, expiration, TM_INFINITE);
if (err) {
xnarch_logerr("kpwm: failed to set alarm, code %d\n",err);
}
}
void writepin (int pin, int val)
{
xnarch_logerr("kpwm: writing pin %d\n", pin);
// pc87366_access->gpio_change(pinindex[pin]);
// printk(KERN_INFO " writing %d to %d\n", val, pin);
}
void send_msg(void)
{
struct rtai_kpwm_stat *s;
RT_PIPE_MSG *msg;
msg = rt_pipe_alloc(sizeof(struct rtai_kpwm_stat));
if (!msg) {
xnarch_logerr("kpwm: cannot allocate pipe message\n");
return;
}
s = (struct rtai_kpwm_stat *)P_MSGPTR(msg);
s->dwell_0 = MONMSGS;
s->dwell_1 = MONMSGS;
/* Do not care if the user-space side of the pipe is not yet
open; just enter the next sampling loop then retry. But in
the latter case, we need to free the unsent message by
ourselves. */
xnarch_logerr("kpwm: sending pipe message\n");
if (rt_pipe_send(&pipe,msg,sizeof(*s),0) != sizeof(*s)) {
rt_pipe_free(msg);
xnarch_logerr("kpwm: pipe message rcvr not ready\n");
}
}
void pwm_generator (RT_ALARM *alm, void *cookie)
{
// for (;;) {
xnarch_logerr("pwm: setting alarm\n");
switch (pwm_state) {
case NONE:
pwm_state = BOTH; // transition to next state
one_shot(pwm_timer[BOTH]); // start its timer
// activate both signals
writepin (0, 1);
writepin (1, 1);
break;
case BOTH:
pwm_state = ONE; // transition to next state
/* if both signals should be the 'same' (ex: car is at rest), then
special handling *may* be in order...
Theres no need for the ONE state (of non-zero duration), and
having it enter that state, then set the timer to leave it,
will cause a non-zero dwell-time in that state.
We fix by immediately transitioning out of ONE to NONE, and not
using the timer to do so.
The actual criterion for 'same' is {x-y < ONESHOT_LATENCY / 2},
ie 1/2 the average latency. if x-y is closer to 0 than the
measured latency, we skip the ONE state.
HOWEVER .. be careful here, cuz RTAI does some of its own
calibrating, which could skew/invalidate this.
*/
// drop the shorter signal
writepin (0,0);
if (pwm_timer[ONE] < ONESHOT_LATENCY_BY2) {
writepin (1, 0); // take care of 'our' signal
goto S1; // transition immediately
}
one_shot(pwm_timer[ONE]); // start its timer
break;
case ONE:
S1:
pwm_state = NONE; // transition to next state
one_shot(pwm_timer[NONE]); // start its timer
// drop the other longer signal
writepin (0,0);
break;
default:
pwm_state = NONE; // transition to next state
}
/* do whatever monitor stuff seems useful */
totct++;
if (loopct++ > MONMSGS) {
loopct = 0;
send_msg();
}
}
void start_pwm (void* cookie)
{
int err;
err = rt_alarm_create(&pwm_alarm, "pwm_alarm", &pwm_generator, &cookie);
if (err) {
xnarch_logerr("pwm: cannot create alarm, code %d\n",err);
return;
}
xnarch_logerr("start_pwm called\n");
one_shot (pwm_timer[NONE]);
}
void gpio_init (void)
{
// pc87366_access->gpio_set_low(pinindex[0]);
// pc87366_access->gpio_set_low(pinindex[1]);
// turn on output enable
pc87366_access->gpio_config(pinindex[0], ~0, 1);
pc87366_access->gpio_config(pinindex[1], ~0, 1);
}
int __pwm_init (void)
{
int err;
err = rt_timer_start(TM_ONESHOT);
if (err) {
xnarch_logerr("pwm: cannot start oneshot timer, code %d\n", err);
return 1;
}
err = rt_task_create(&pwm_task,"kpwm",0,99,0);
if (err) {
xnarch_logerr("pwm: failed to create pwm task, code %d\n",err);
return 2;
}
err = rt_pipe_create(&pipe,"kpwmctl",0);
if (err) {
xnarch_logerr("pwm: failed to open real-time pipe, code %d\n",err);
return 3;
}
err = rt_task_start(&pwm_task,&start_pwm,&cookie);
if (err) {
xnarch_logerr("pwm: failed to start pwm task, code %d\n",err);
return 4;
}
printk(KERN_INFO NAME ": initialized\n");
// gpio_init();
send_msg();
for (err=0; err<100000000; err++) ;
send_msg();
return 0;
}
void __pwm_exit (void)
{
int err;
rt_timer_stop();
rt_task_delete(&pwm_task);
err = rt_pipe_delete(&pipe);
if(err)
xnarch_logerr("Warning: could not delete pipe: err=%d.\n",err);
/* turn off output enable on 2 pins */
// pc87366_access->gpio_config(pinindex[0], ~1, 0);
// pc87366_access->gpio_config(pinindex[1], ~1, 0);
printk(KERN_INFO NAME ": exited after %d loops\n", loopct);
}
module_init(__pwm_init);
module_exit(__pwm_exit);
/*
Local variables:
compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
c-basic-offset: 4
End:
*/
In part, this is cuz its not clear