mirror of
https://github.com/nodemcu/nodemcu-firmware.git
synced 2025-02-06 21:18:25 +08:00
The PR removed the bulk of non-newlib headers from the NodeMCU source base. app/libc has now been cut down to the bare minimum overrides to shadow the corresponding functions in the SDK's libc. The old c_xyz.h headerfiles have been nuked in favour of the standard <xyz.h> headers, with a few exceptions over in sdk-overrides. Again, shipping a libc.a without headers is a terrible thing to do. We're still living on a prayer that libc was configured the same was as a default-configured xtensa gcc toolchain assumes it is. That part I cannot do anything about, unfortunately, but it's no worse than it has been before. This enables our source files to compile successfully using the standard header files, and use the typical malloc()/calloc()/realloc()/free(), the strwhatever()s and memwhatever()s. These end up, through macro and linker magic, mapped to the appropriate SDK or ROM functions.
392 lines
8.5 KiB
C
392 lines
8.5 KiB
C
/*
|
|
* Module for interfacing with Switec instrument steppers (and
|
|
* similar devices). These are the steppers that are used in automotive
|
|
* instrument panels and the like. Run off 5 volts at low current.
|
|
*
|
|
* Code inspired by:
|
|
*
|
|
* SwitecX25 Arduino Library
|
|
* Guy Carpenter, Clearwater Software - 2012
|
|
*
|
|
* Licensed under the BSD2 license, see license.txt for details.
|
|
*
|
|
* NodeMcu integration by Philip Gladstone, N1DQ
|
|
*/
|
|
|
|
#include "platform.h"
|
|
#include "c_types.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include "driver/switec.h"
|
|
#include "ets_sys.h"
|
|
#include "os_type.h"
|
|
#include "osapi.h"
|
|
#include "hw_timer.h"
|
|
#include "user_interface.h"
|
|
#include "task/task.h"
|
|
|
|
#define N_STATES 6
|
|
//
|
|
// First pin passed to setup corresponds to bit 3
|
|
// On the motor, the pins are arranged
|
|
//
|
|
// 4 1
|
|
//
|
|
// 3 2
|
|
//
|
|
// The direction of rotation can be reversed by reordering the pins
|
|
//
|
|
// State 3 2 1 0 A B Value
|
|
// 0 1 0 0 1 - - 0x9
|
|
// 1 0 0 0 1 . - 0x1
|
|
// 2 0 1 1 1 + . 0x7
|
|
// 3 0 1 1 0 + + 0x6
|
|
// 4 1 1 1 0 . + 0xE
|
|
// 5 1 0 0 0 - . 0x8
|
|
static const uint8_t stateMap[N_STATES] = {0x9, 0x1, 0x7, 0x6, 0xE, 0x8};
|
|
|
|
typedef struct {
|
|
uint8_t current_state;
|
|
uint8_t stopped;
|
|
int8_t dir;
|
|
uint32_t mask;
|
|
uint32_t pinstate[N_STATES];
|
|
uint32_t next_time;
|
|
int16_t target_step;
|
|
int16_t current_step;
|
|
uint16_t vel;
|
|
uint16_t max_vel;
|
|
uint16_t min_delay;
|
|
task_handle_t task_number;
|
|
} DATA;
|
|
|
|
static DATA *data[SWITEC_CHANNEL_COUNT];
|
|
static volatile char timer_active;
|
|
|
|
#define MAXVEL 255
|
|
|
|
// Note that this has to be global so that the compiler does not
|
|
// put it into ROM.
|
|
uint8_t switec_accel_table[][2] = {
|
|
{ 20, 3000 >> 4},
|
|
{ 50, 1500 >> 4},
|
|
{ 100, 1000 >> 4},
|
|
{ 150, 800 >> 4},
|
|
{ MAXVEL, 600 >> 4}
|
|
};
|
|
|
|
static void ICACHE_RAM_ATTR timer_interrupt(os_param_t);
|
|
|
|
#define TIMER_OWNER ((os_param_t) 'S')
|
|
|
|
|
|
// Just takes the channel number
|
|
int switec_close(uint32_t channel)
|
|
{
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
return -1;
|
|
}
|
|
|
|
DATA *d = data[channel];
|
|
|
|
if (!d) {
|
|
return 0;
|
|
}
|
|
|
|
if (!d->stopped) {
|
|
return -1;
|
|
}
|
|
|
|
// Set pins as input
|
|
gpio_output_set(0, 0, 0, d->mask);
|
|
|
|
data[channel] = NULL;
|
|
free(d);
|
|
|
|
// See if there are any other channels active
|
|
for (channel = 0; channel < sizeof(data)/sizeof(data[0]); channel++) {
|
|
if (data[channel]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If not, then disable the interrupt
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
platform_hw_timer_close(TIMER_OWNER);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __attribute__((always_inline)) inline void write_io(DATA *d)
|
|
{
|
|
uint32_t pin_state = d->pinstate[d->current_state];
|
|
|
|
gpio_output_set(pin_state, d->mask & ~pin_state, 0, 0);
|
|
}
|
|
|
|
static __attribute__((always_inline)) inline void step_up(DATA *d)
|
|
{
|
|
d->current_step++;
|
|
d->current_state = (d->current_state + 1) % N_STATES;
|
|
write_io(d);
|
|
}
|
|
|
|
static __attribute__((always_inline)) inline void step_down(DATA *d)
|
|
{
|
|
d->current_step--;
|
|
d->current_state = (d->current_state + N_STATES - 1) % N_STATES;
|
|
write_io(d);
|
|
}
|
|
|
|
static void ICACHE_RAM_ATTR timer_interrupt(os_param_t p)
|
|
{
|
|
// This function really is running at interrupt level with everything
|
|
// else masked off. It should take as little time as necessary.
|
|
//
|
|
(void) p;
|
|
|
|
int i;
|
|
uint32_t delay = 0xffffffff;
|
|
|
|
// Loop over the channels to figure out which one needs action
|
|
for (i = 0; i < sizeof(data) / sizeof(data[0]); i++) {
|
|
DATA *d = data[i];
|
|
if (!d || d->stopped) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t now = system_get_time();
|
|
if (now < d->next_time) {
|
|
int need_to_wait = d->next_time - now;
|
|
if (need_to_wait < delay) {
|
|
delay = need_to_wait;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// This channel is past it's action time. Need to process it
|
|
|
|
// Are we done yet?
|
|
if (d->current_step == d->target_step && d->vel == 0) {
|
|
d->stopped = 1;
|
|
d->dir = 0;
|
|
task_post_low(d->task_number, 0);
|
|
continue;
|
|
}
|
|
|
|
// if stopped, determine direction
|
|
if (d->vel == 0) {
|
|
d->dir = d->current_step < d->target_step ? 1 : -1;
|
|
// do not set to 0 or it could go negative in case 2 below
|
|
d->vel = 1;
|
|
}
|
|
|
|
// Move the pointer by one step in the correct direction
|
|
if (d->dir > 0) {
|
|
step_up(d);
|
|
} else {
|
|
step_down(d);
|
|
}
|
|
|
|
// determine delta, number of steps in current direction to target.
|
|
// may be negative if we are headed away from target
|
|
int delta = d->dir > 0 ? d->target_step - d->current_step : d->current_step - d->target_step;
|
|
|
|
if (delta > 0) {
|
|
// case 1 : moving towards target (maybe under accel or decel)
|
|
if (delta <= d->vel) {
|
|
// time to declerate
|
|
d->vel--;
|
|
} else if (d->vel < d->max_vel) {
|
|
// accelerating
|
|
d->vel++;
|
|
} else {
|
|
// at full speed - stay there
|
|
}
|
|
} else {
|
|
// case 2 : at or moving away from target (slow down!)
|
|
d->vel--;
|
|
}
|
|
|
|
// vel now defines delay
|
|
uint8_t row = 0;
|
|
// this is why vel must not be greater than the last vel in the table.
|
|
while (switec_accel_table[row][0] < d->vel) {
|
|
row++;
|
|
}
|
|
|
|
uint32_t micro_delay = switec_accel_table[row][1] << 4;
|
|
if (micro_delay < d->min_delay) {
|
|
micro_delay = d->min_delay;
|
|
}
|
|
|
|
// Figure out when we next need to take action
|
|
d->next_time = d->next_time + micro_delay;
|
|
if (d->next_time < now) {
|
|
d->next_time = now + micro_delay;
|
|
}
|
|
|
|
// Figure out how long to wait
|
|
int need_to_wait = d->next_time - now;
|
|
if (need_to_wait < delay) {
|
|
delay = need_to_wait;
|
|
}
|
|
}
|
|
|
|
if (delay < 1000000) {
|
|
if (delay < 50) {
|
|
delay = 50;
|
|
}
|
|
timer_active = 1;
|
|
platform_hw_timer_arm_us(TIMER_OWNER, delay);
|
|
} else {
|
|
timer_active = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// The pin numbers are actual platform GPIO numbers
|
|
int switec_setup(uint32_t channel, int *pin, int max_deg_per_sec, task_handle_t task_number )
|
|
{
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
return -1;
|
|
}
|
|
|
|
if (data[channel]) {
|
|
if (switec_close(channel)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
DATA *d = (DATA *) calloc(1, sizeof(DATA));
|
|
if (!d) {
|
|
return -1;
|
|
}
|
|
|
|
if (!data[0] && !data[1] && !data[2]) {
|
|
// We need to stup the timer as no channel was active before
|
|
// no autoreload
|
|
if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, FALSE)) {
|
|
// Failed to get the timer
|
|
free(d);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
data[channel] = d;
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
// Build the mask for the pins to be output pins
|
|
d->mask |= 1 << pin[i];
|
|
|
|
int j;
|
|
// Now build the hi states for the pins according to the 6 phases above
|
|
for (j = 0; j < N_STATES; j++) {
|
|
if (stateMap[j] & (1 << (3 - i))) {
|
|
d->pinstate[j] |= 1 << pin[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
d->max_vel = MAXVEL;
|
|
if (max_deg_per_sec == 0) {
|
|
max_deg_per_sec = 400;
|
|
}
|
|
d->min_delay = 1000000 / (3 * max_deg_per_sec);
|
|
d->task_number = task_number;
|
|
|
|
#ifdef SWITEC_DEBUG
|
|
for (i = 0; i < 4; i++) {
|
|
printf("pin[%d]=%d\n", i, pin[i]);
|
|
}
|
|
|
|
printf("Mask=0x%x\n", d->mask);
|
|
for (i = 0; i < N_STATES; i++) {
|
|
printf("pinstate[%d]=0x%x\n", i, d->pinstate[i]);
|
|
}
|
|
#endif
|
|
|
|
// Set all pins as outputs
|
|
gpio_output_set(0, 0, d->mask, 0);
|
|
|
|
platform_hw_timer_set_func(TIMER_OWNER, timer_interrupt, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// All this does is to assert that the current position is 0
|
|
int switec_reset(uint32_t channel)
|
|
{
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
return -1;
|
|
}
|
|
|
|
DATA *d = data[channel];
|
|
|
|
if (!d || !d->stopped) {
|
|
return -1;
|
|
}
|
|
|
|
d->current_step = d->target_step = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Just takes the channel number and the position
|
|
int switec_moveto(uint32_t channel, int pos)
|
|
{
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
return -1;
|
|
}
|
|
|
|
DATA *d = data[channel];
|
|
|
|
if (!d) {
|
|
return -1;
|
|
}
|
|
|
|
if (pos < 0) {
|
|
// This ensures that we don't slam into the endstop
|
|
d->max_vel = 50;
|
|
} else {
|
|
d->max_vel = MAXVEL;
|
|
}
|
|
|
|
d->target_step = pos;
|
|
|
|
// If the pointer is not moving, setup so that we start it
|
|
if (d->stopped) {
|
|
// reset the timer to avoid possible time overflow giving spurious deltas
|
|
d->next_time = system_get_time() + 1000;
|
|
d->stopped = false;
|
|
|
|
if (!timer_active) {
|
|
timer_interrupt(0);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Get the current position, direction and target position
|
|
int switec_getpos(uint32_t channel, int32_t *pos, int32_t *dir, int32_t *target)
|
|
{
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
return -1;
|
|
}
|
|
|
|
DATA *d = data[channel];
|
|
|
|
if (!d) {
|
|
return -1;
|
|
}
|
|
|
|
*pos = d->current_step;
|
|
*dir = d->stopped ? 0 : d->dir;
|
|
*target = d->target_step;
|
|
|
|
return 0;
|
|
}
|