Synchronized PWMs from two different Timers on ZephyrRTOS (STM32h7) - c

I'm working on a C Project for University where I need to measure a Laser with a TCD1304 CCD-Sensor.
Therefore I am using Zephyr as OS on an STM32-H7(A3ZI-Q).
Since the TCD1304 has to receive different PWM signals to drive it, I am trying to generate PWMS with Zephyr.
So far I have already reached the point where I can generate two PWMs using Timer1 and Timer4.
It should be mentioned that one frequency is 2MHz and the other one is 500kHz, which are multiples of each other.
The different frequencies can be adjusted well, but I can always detect a drift of the two signals to each other.
I have to fix this drift by synchronizing the PWMs of the two timers.
Otherwise it won't be possible to drive the TCD1304.
Here I provide an overview of the used code:
prj.conf
CONFIG_PWM=y
CONFIG_SYS_CLOCK_TICKS_PER_SEC=100
main.c
#define FM_PERIOD 2000000U
#define SH_PERIOD 500000U
void main(){
/* Initialize and start PWM Signals */
const struct device *fM_device = device_get_binding("FM");
const struct device *SH_device = device_get_binding("SH");
if (!fM_device | !SH_device){
printk("TCM1304 - PWM initialize failed.");
return;
}
pwm_set(fM_device, 1, FM_PERIOD, 0.5*FM_PERIOD, 0);
pwm_set(SH_device, 4, SH_PERIOD, 0.5*SH_PERIOD, 0);
}
nucleo_h7a3zi_q.overlay
...
&timers1 {
st,prescaler = <10>;
status = "okay";
pwm1: pwm {
status = "okay";
label = "FM";
pinctrl-names = "default";
pinctrl-0 = <&tim1_ch1_pe9>;
};
};
&timers4 {
st,prescaler = <100>;
status = "okay";
pwm3: pwm {
status = "okay";
label = "SH";
pinctrl-names = "default";
pinctrl-0 = <&tim4_ch4_pd15>;
};
};
...
When generating the two signals by the same timer with different frequencies, I noticed that some periods are cut off at the 2MHz signal.
From this I conclude that different frequencies cannot be created by the same timer?
I assume that this is a common problem for which there must be a solution.
Maybe by assigning the same input clocks to the Timers in the overlay-file (clocks=<XX>-Option) or by using any kind of master-slave principle within Zephyr (Github-Issue).
It seams that my issue is closely related to this Issue, but within Zephyr.
Is there a solution that avoids interrupts in the best possible way to reduce CPU load? I would be very glad to get some help.

Related

STM32 Timer auto-reload preload

The conditions to reproduce:
Here is my real life example that I would like to solve:I am developing an application on an stm32f411RET which needs to dynamically change the period of two PWM's.The two PWM's need to be synced and have exactly the same frequency but because of some pin restrictions I am using two different timers.In my main loop I calculate the period I want and I call:
TIM3->ARR = (uint16_t)period;
LL_TIM_OC_SetCompareCH4(TIM3, period/2);
TIM2->ARR=(uint16_t)period;
LL_TIM_OC_SetCompareCH3(TIM2, period/2);
Everything works great but what is obscure to me is the combination of initialization settings of the two timers :
LL_TIM_InitTypeDef TIM_InitStruct = {0};
LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
NVIC_EnableIRQ(TIM2_IRQn);
TIM_InitStruct.Prescaler = 0;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM_InitStruct.Autoreload = 0;
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
LL_TIM_EnableARRPreload(TIM2); //Important Line!!
LL_TIM_Init(TIM2, &TIM_InitStruct);
LL_TIM_OC_EnablePreload(TIM2, LL_TIM_CHANNEL_CH3);
TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.CompareValue = 0;
TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH3, &TIM_OC_InitStruct);
LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH3);
LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET);
LL_TIM_DisableMasterSlaveMode(TIM2);
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOB);
GPIO_InitStruct.Pin = BBD_R_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_1;
LL_GPIO_Init(BBD_R_GPIO_Port, &GPIO_InitStruct);
This is quite standard for the timer 2 and nearly the same code works for timer 3 with the only exception that LL_TIM_EnableARRPreload(TIM2); changes to LL_TIM_DisableARRPreload(TIM3);.
TLDR The actual question
When I change any of those two initialization functions the timer starts working but changing the frequency make the timer completely die.I have a grasp about what this function does from page 316 of the reference manual and also pages 320 and 321 that contain schematics but still I can't comprehend why this setting can cause the timers to freeze.
P.S.
It might be useful or it might not so I'll leave it here the ARR register of timer 2 is 32 bit long and the ARR of timer 3 is 16 ,that is not obvious from the configurations I posted but I doubt this affects the outcome.
For a start the same initialisation routine should work for both the timers used to generate the PWM signals you want, unless you using one timer in a different configuration to the other.
On thing that stands out is that the TIM_InitStruct.Autoreload is set to 0 during initialisation, the behaviour of the timer in counter mode/pwm mode with ARR set to 0 is undocumented in the reference manual. It would be wise to set the TIM_InitStruct.Autoreload to the UINT32_MAX or UINT16_MAX depending on the timer.
Further, looking at the initialisation routine shown in your question (For timer 2 channel 3), the call LL_TIM_EnableARRPreload enables a change to the ARR value to be buffered. When the ARR changes are buffered the ARR value is only updated on an update event (UEV). When buffered updates are disabled, LL_TIM_DisableARRPreload, the ARR value is updated with a new value immediately. The behaviour with and without buffering are shown by the following figures in the reference manual.
ARR buffered (LL_TIM_EnableARRPreload):
ARR un-buffered (LL_TIM_DisableARRPreload):
Where you are dynamically updating the ARR value (PWM period) and the compare counter value (PWM duty-cycle, CCRn) in a loop, it is generally a good idea to have both updates buffered/preloaded. The CCRn buffering is enabled with LL_TIM_OC_EnablePreload, as shown in your initialisation routine. Buffering the ARR changes, will maintain the integrity of PWM period between ARR updates avoiding any inadvertently long pulses; particularly, should the system find itself in the condition where ARR new < TIMx CNT < ARR old. Note, if you wish to keep the PWM signals in sync, it is important the same ARR preloading configuration is used for both timers.
Note, the following calls are superfluous if the timer hadn't been previously initialised for a different purpose.
LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH3);
LL_TIM_DisableMasterSlaveMode(TIM2);
LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET);
Beyond your question and more to the use-case; depending on how closely you want the PWM signals to be synchronised, you may want to consider the basic configuration of one timer operating as the master (TIMxCR2.MMS=001)and the other as the slave (TIMxSMCR.SMS=100) where the slave timer is enabled when the master is enabled.

How to use I2C GPIO expander's pin instead of RTS to control the RS485 direction, in Linux AUART kernel driver?

I'm creating an embedded system based on i.MX287 processor from NXP(Freescale). I'm using a core processing board which is connected to my evaluation board via a mini PCIe connector.
UARTs 0,3,4 are used as RS232 and UARTs 1,2 as RS485. The core board does not provide the RTS signals in its pinout, so I have to use pins from an I2C GPIO expander to control the RS485 direction. The GPIO expander module is also used for controlling some other devices on the board.
In user-space, I can control the direction pin using libi2c, but my client asked me to put the direction pin control in the UART driver.
Questions:
1- how can I interact with an i2c device inside the auart driver? (is it possible)
2- if it is possible, then how to prevent the i2c-0 bus from being blocked by the kernel? (I also need the userspace calls to the libi2c to work properly)
I googled a lot, but most cases are about how to use the I2C driver or how to activate GPIO pins in the sysfs, and I was able to do all of those.
The libi2c is for userspace so I cannot call it here. I also know that opening a file(/dev/i2c-0) in kernel and reading or writing to it is not a good idea. I am trying to understand what is the best way to handle this problem, without causing any concurrent access issues.
I would appreciate any ideas
P.S. - I don't have a deep understanding of how Linux kernel works, so sorry if my question is a little vague.
Edit 1:
based on #0andriy 's suggestion, I edited the DTS file and added the following to /arch/arm/boot/dts/my_dts_file.dts:
/dts-v1/;
#include "imx28.dtsi"
/ {
// some definitions
apbx#80040000 {
i2c0: i2c#80058000 {
pca8575: gpio#20 {
compatible = "nxp,pca8575";
reg = <0x20>; // PCA8575PW Address -0-0-0
gpio-controller;
#gpio-cells = <2>;
};
};
auart1: serial#8006c000 {
pinctrl-names = "default";
pinctrl-0 = <&auart1_2pins_a>;
linux,rs485-enabled-at-boot-time;
rs485-rts-delay = <0 0>; // in milliseconds
rts-gpios = <&pca8575 4 GPIO_ACTIVE_LOW>;
rs485-rts-active-low;
status = "okay";
};
auart2: serial#8006e000 {
pinctrl-names = "default";
pinctrl-0 = <&auart2_2pins_b>;
linux,rs485-enabled-at-boot-time;
rs485-rts-delay = <0 0>; // in milliseconds
rts-gpios = <&pca8575 5 GPIO_ACTIVE_LOW>;
rs485-rts-active-low;
status = "okay";
};
};
// some definitions
};
and then rebuilt the kernel. I also edited the mxs_auart_init_gpios function in the mxs-auart.c driver to print out the pin description of all the auart GPIOs at boot time. but gpiod = mctrl_gpio_to_gpiod(s->gpios, i) is always NULL.
the pca8575 GPIO controller is not added under /sys/class/gpio/
root# ls /sys/class/gpio
export gpiochip128 gpiochip64 unexport
gpiochip0 gpiochip32 gpiochip96
Edit 2:
auart1_2pins_a and auart2_2pins_b from the imx28.dtsi file :
auart2_2pins_b: auart2-2pins#1 {
reg = <1>;
fsl,pinmux-ids = <
MX28_PAD_AUART2_RX__AUART2_RX
MX28_PAD_AUART2_TX__AUART2_TX
>;
fsl,drive-strength = <MXS_DRIVE_4mA>;
fsl,voltage = <MXS_VOLTAGE_HIGH>;
fsl,pull-up = <MXS_PULL_DISABLE>;
};
auart1_2pins_a: auart1-2pins#0 {
reg = <0>;
fsl,pinmux-ids = <
MX28_PAD_AUART1_RX__AUART1_RX
MX28_PAD_AUART1_TX__AUART1_TX
>;
fsl,drive-strength = <MXS_DRIVE_4mA>;
fsl,voltage = <MXS_VOLTAGE_HIGH>;
fsl,pull-up = <MXS_PULL_DISABLE>;
};
I'm using kernel 4.14.13
the figure below demonstrates what I'm trying to achieve :
I'm not familiar at all with your board so take this answer with a pinch of salt, but I've noticed some funny things on your files.
First off, you need to define the I2C pin you want to use for toggling the direction inside the UART pinmux:
auart2_2pins_b: auart2-2pins#1 {
reg = <1>;
fsl,pinmux-ids = <
MX28_PAD_AUART2_RX__AUART2_RX
MX28_PAD_AUART2_TX__AUART2_TX
MX28_PAD_I2C0_SCL__I2C0_SCL
>;
fsl,drive-strength = <MXS_DRIVE_4mA>;
fsl,voltage = <MXS_VOLTAGE_HIGH>;
fsl,pull-up = <MXS_PULL_DISABLE>;
};
Make sure you double-check the pin name you want to use, I cannot be sure that is the right one.
Then, you seem to be missing the pinctrl for the I2C controller:
i2c0: i2c#80058000 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins_a>;
status = "okay";
pca8575: gpio#20 {
compatible = "nxp,pca8575";
reg = <0x20>; // PCA8575PW Address -0-0-0
gpio-controller;
#gpio-cells = <2>;
};
};
I could not confirm your reg and your pin numbers but I'm assuming you took it from your board's documentation. If you didn't, make sure you find a reliable source for your hardware.
Finally, I'm not sure why you want to have the RTS line active low, most transceivers have a DE/~RE input, which means you need to have the line active high to drive the bus. Maybe your driver is different...
What you are trying to do is documented to be working for other boards so I guess unless there is a bug you should be able to make it work.

Is void setup() code wiped out when deep sleep is used in esp32

I'm doing wireless sensor node using esp32 (slave) and rf24l01 module. My next step is to put my slave in sleep mode (maybe deep sleep). Can I use deep sleep for my project?
They said
everything stored in that memory is wiped out and cannot be accessed.
So is all my void setup() code wiped out? Or just my pack0.temp, humid, soil is wiped out?
My code is attached below
struct package0
{
float temperature = 0;
float humidity = 0;
int soil = 0;
};
typedef struct package0 Package0;
Package0 pack0;
/**********************************/
/**************RF24****************/
RF24 radio(25,26);
RF24Network network(radio);
const uint16_t this_node = 01;
const uint16_t master00 = 00;
const unsigned long interval = 3000;
/**********************************/
void setup() {
dht.begin();
radio.begin();
network.begin(90, this_node);
radio.setPALevel(RF24_PA_MIN);
radio.setDataRate(RF24_250KBPS);
}
void loop() {
// put your main code here, to run repeatedly:
network.update();
unsigned long now = millis();
if (now - last_sent >= interval)
{
last_sent = now;
send();
}
}
void send()
{
pack0.humidity = dht.readHumidity();
pack0.temperature = dht.readTemperature();
pack0.soil = map(analogRead(SOILPIN), 0, 4096, 100, 0);//convert to percentage
if (isnan(pack0.humidity) || isnan(pack0.temperature))
{
Serial.println(F("Failed to read from DHT sensor!"));
return;
}
RF24NetworkHeader header(master00);
bool ok = network.write(header, &pack0, sizeof(pack0));
}
When the ESP32 enters deep sleep, it turns off the processor that's running your code. The contents of memory and the current state of the processor are lost. It costs power to maintain the contents of its memory and the CPU state, and the point of deep sleep is to save as much power as possible, so it stops powering these things.
So when it restarts out of deep sleep it's as if it just powered up. Your setup() function will run again and will need to do any initialization again.
There are a couple of ways to preserve state across sleep cycles.
Obviously you can store data in flash memory using EEPROM or SPIFFS. Writing to flash is slow and costs a lot of power, so this isn't great if you're running off a battery.
You can also store data in the static RAM that's part of the real-time-clock (RTC). This RAM is built into the ESP32 and is maintained during deep sleep. Its contents will be lost or cleared when the ESP32 loses power or is flashed.
You can declare a variable to live in the RTC RAM using RTC_DATA_ATTR. For instance:
RTC_DATA_ATTR unsigned wakeups;
void setup() {
wakeups++;
Serial.begin(115200);
Serial.printf("%u wakeups\n", wakeups);
// do other stuff and enter deep sleep
}
There's only 8KB of static RAM so you can't store huge amounts of data there.
You also need to be careful using it. Storing complex C++ objects in it will almost certainly not work correctly across deep sleep restarts. Storing pointers to data (like a char* pointing to a C string) will not work because the data the pointer pointed to will be lost after restarting from deep sleep.
How deep sleep affects any electronics connected to the ESP32 is difficult to predict. The ESP32 will stop powering its GPIO lines during deep sleep. Whether the devices remain powered and how they react to the GPIO lines floating depends on the device. If they remain powered they may retain their state from the previous cycle. It really depends on the device and the circuitry.
This article is a good tutorial on deep sleep and has more information.

Cortex M0+ returns to wrong position in thread mode after interrupting sleep

I'm not sure if I'm missing something obvious here but has anyone else experienced any issues with the Cortex M0+ (or any of the Cortex M range) returning from an ISR to a somewhat random position in thread mode after being woken up from sleep mode (eg not returning to the line below the WFI instruction). It seems to wake up in the middle of the code, like in the middle of a for loop or the middle of a function. I'm using the ATSAMD218A microcontroller and have been using the debugger in Atmel Studio 7. I've attempted to isolate the problem and have noticed the following:
It only occurs when the device is put to sleep, when the device is interrupted while it is awake the ISR always returns to the right place.
The ISR always returns to the same (incorrect) position in the code, if I comment out that function or piece of code then it begins returning to another incorrect position
I've unsuccessfully inserted delays and NOPs to try determine if it is timing related or clock cycle related but it still always returns to the same (incorrect) position
I have tried implementing interrupts using both direct register access as well as using the Arduino Interrupts library.
It occurs in both idle and standby mode (sleep and deep sleep)
While looking at the assembly instructions during debugging, right before the ISR is exited, the 'bx lr' instruction is called which is supposed to branch to the link register. The link register contains a value called EXC_RETURN which indicates the return behavior, which in my case is 0xFFFFFFF9 (return to thread mode). I can't however find the actual memory address that it returns to anywhere. The memory address isn't in any of the core registers R0-R15.
I have been reading the ARMv6 Architecture Reference Manual as well as the Cortex M0+ Generic User Guide. Not quite sure what's going on and any debugging suggestions would be much appreciated. Does anyone have a better understanding of the exception handling of the Cortex M series and could point me in the right direction to find the memory address that thread mode returns to after an ISR. I could supply code if you'd like but even a simple piece of code that does nothing but count in a loop, sleep and then wake up causes trouble.
EDIT
I've added the relevant code below. It is the most stripped back version (*stripped) that still causes problems. I haven't included the RTC code functions (uses the DS3231RTC library) as I'm fairly certain they don't have any effect. If you think I should upload more let me know.
void configInterrupt(void){
NVIC_DisableIRQ(EIC_IRQn);
NVIC_ClearPendingIRQ(EIC_IRQn);
NVIC_SetPriority(EIC_IRQn, 0);
NVIC_EnableIRQ(EIC_IRQn);
// Enable GCLK for IEC (External Interrupt Controller)
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC));
EIC->WAKEUP.reg |= (1 << 0);
EIC->CONFIG[0].reg |= 0x2; // falling edge
pinConfig(16,INPUT,UP); // custom 'pinMode' function
PORT->Group[0].PINCFG[16].bit.PMUXEN = 1; // enable peripheral muxing
PORT->Group[0].PMUX[8].bit.PMUXE = 0x0; // function A (EIC) = 0x0
EIC->INTENSET.reg = EIC_INTENSET_EXTINT(1 << 0);
EIC->CTRL.bit.ENABLE = 1;
}
void EIC_Handler(void){
RTC_FLAG = 1; // my debug breakpoint is here, at this point the stack has already been pushed and I can see the PC value that will be popped off
int_count++;
EIC->INTFLAG.reg = 1 << 0;
}
void setup() {
configInterrupt();
configureRTC();
RTC_FLAG = 1;
}
void loop() {
if (RTC_FLAG == 1) {
RTC_FLAG = 0;
setNextAlarm();
}
for (int i = 0 ; i <= WINDOW-1 ; i++) {
String data = "";
rawVal = 0; // data gets read from sensor here (stripped)
data += String(rawVal);
data += ",";
distance = 0; // distance calculated from rawVal here (stripped)
data += String(distance);
data += ",";
mean = 0; // mean calculated in a function here (stripped)
data += String(mean);
data += ",";
data += String(int_count); // ISR returns here
// Data gets written to file here (stripped)
}
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__DSB();
__WFI();
}
I'd try to implement the same snippet using Atmel ASF framework, look at the registers the API sets and what order. They will also usually reference errata and implement a work around.
Load the low power example project, it switches between power modes, and wakes on the xplained board button press. If that can hop between modes on your board, you know it's not the part. You might have to move the wake pin to something to match your hardware.

Arduino Drone project, when output is "loaded" (even cap to gnd), input "steering" command starts to glitch

Arduino drone project, whenever output is "loaded" ("open-circuit" signal-in pin to ESC or even a cap to ground), the input "steering" command starts to glitch (go to really low values << 1000).
The motor speed is a function of both the steering command, as well as the throttle. (In this test-case with just one motor as seen in the code below, unMotorSpeed = unThrottleIn +/- unSteeringIn)
When hooked up to a scope, the physical input signals (steering and throttle coming from the receiver) are great, and I even swapped the input pins to make sure there wasn't a problem between the receiver and the arduino. The problem seems to be coming from the software, but it just doesn't make sense, since when there isn't a "load" attached, the input and output values are all fine and clean. (I put "load" in quotes because sometimes it's essentially an open circuit --> super high impedance input signal to the electronic speed controller (ESC), which I don't even ground to complete a circuit).
Would anyone be able to check out the code and see if I'm missing something?
At this point, a somewhat quick workaround would be to simply not write those new glitchy values to the motor and keep the old speed values for whenever the new speed is significantly lower than the old speed (and these are at over 50khz, so clearly a huge jump in just one step is a bit crazy).
Note: In the code, the total output of unMotorSpeed leaves from the servoThrottle pin. Just the original naming that I didn't end up changing... it's clear if you read the code and see all the variables.
UPDATE: Weird thing is, I just ran my setup without any changes and EVERYTHING WORKED...I reflashed the arduino multiple times to make sure it wasn't some lucky glitch, and it kept on working, with everything set up. Then I dropped my remote on the ground and moved a few of the wires on the breadboard around, and things went back to their wonky ways after putting the setup back together. Idk what to do!
// --> starting code found at: rcarduino.blogspot.com
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
with.html
#include <Servo.h>
// Assign your channel in pins
#define THROTTLE_IN_PIN 3
#define STEERING_IN_PIN 2
// Assign your channel out pins
#define THROTTLE_OUT_PIN 9
//#define STEERING_OUT_PIN 9
// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoThrottle;
//Servo servoSteering;
// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;
// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;
//uint32_t ulAuxStart;
void setup()
{
Serial.begin(9600);
// attach servo objects, these will generate the correct
// pulses for driving Electronic speed controllers, servos or other devices
// designed to interface directly with RC Receivers
servoThrottle.attach(THROTTLE_OUT_PIN);
// using the PinChangeInt library, attach the interrupts
// used to read the channels
attachInterrupt(digitalPinToInterrupt(THROTTLE_IN_PIN), calcThrottle,CHANGE);
attachInterrupt(digitalPinToInterrupt(STEERING_IN_PIN), calcSteering,CHANGE);
}
void loop()
{
// create local variables to hold a local copies of the channel inputs
// these are declared static so that thier values will be retained
// between calls to loop.
static uint16_t unThrottleIn;
static uint16_t unSteeringIn;
static uint16_t difference;
static uint16_t unMotorSpeed; // variable that stores overall motor speed
static uint8_t bUpdateFlags; // local copy of update flags
// check shared update flags to see if any channels have a new signal
if(bUpdateFlagsShared)
{
noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables
// take a local copy of which channels were updated in case we need to use this in the rest of loop
bUpdateFlags = bUpdateFlagsShared;
// in the current code, the shared values are always populated
// so we could copy them without testing the flags
// however in the future this could change, so lets
// only copy when the flags tell us we can.
if(bUpdateFlags & THROTTLE_FLAG)
{
unThrottleIn = unThrottleInShared;
}
if(bUpdateFlags & STEERING_FLAG)
{
unSteeringIn = unSteeringInShared;
}
// clear shared copy of updated flags as we have already taken the updates
// we still have a local copy if we need to use it in bUpdateFlags
bUpdateFlagsShared = 0;
interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
// as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
// service routines own these and could update them at any time. During the update, the
// shared copies may contain junk. Luckily we have our local copies to work with :-)
}
//Serial.println(unSteeringIn);
// do any processing from here onwards
// only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
// variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by
// the interrupt routines and should not be used in loop
// the following code provides simple pass through
// this is a good initial test, the Arduino will pass through
// receiver input as if the Arduino is not there.
// This should be used to confirm the circuit and power
// before attempting any custom processing in a project.
// we are checking to see if the channel value has changed, this is indicated
// by the flags. For the simple pass through we don't really need this check,
// but for a more complex project where a new signal requires significant processing
// this allows us to only calculate new values when we have new inputs, rather than
// on every cycle.
///// if-else chain commented out to determine/prove problem with steering signal --> buggy!
if(unSteeringIn < 1400) // if steering joystick moved left
{
difference = 1400 - unSteeringIn;
if(unThrottleIn - difference >= 0)
unMotorSpeed = unThrottleIn - difference;
}
else if(unSteeringIn > 1550) //if steering joystick moved right (needs to be tweaked, but works for now)
{
difference = unSteeringIn - 1600;
if(unThrottleIn + difference < 2000)
unMotorSpeed = unThrottleIn + difference;
}
else
{
unMotorSpeed = unThrottleIn;
}
//Serial.println(unMotorSpeed);
//Serial.println(unSteeringIn);
//Serial.println(unThrottleIn);
if(bUpdateFlags)
{
//Serial.println(servoThrottle.readMicroseconds());
if(servoThrottle.readMicroseconds() != unMotorSpeed)
{
servoThrottle.writeMicroseconds(unMotorSpeed);
Serial.println(unMotorSpeed);
}
}
bUpdateFlags = 0;
}
// simple interrupt service routine
void calcThrottle()
{
// if the pin is high, its a rising edge of the signal pulse, so lets record its value
if(digitalRead(THROTTLE_IN_PIN) == HIGH)
{
ulThrottleStart = micros();
}
else
{
// else it must be a falling edge, so lets get the time and subtract the time of the rising edge
// this gives use the time between the rising and falling edges i.e. the pulse duration.
unThrottleInShared = (uint16_t)(micros() - ulThrottleStart); // pulse duration
// use set the throttle flag to indicate that a new throttle signal has been received
bUpdateFlagsShared |= THROTTLE_FLAG;
}
}
void calcSteering()
{
if(digitalRead(STEERING_IN_PIN) == HIGH)
{
ulSteeringStart = micros();
}
else
{
unSteeringInShared = (uint16_t)(micros() - ulSteeringStart); // pulse duration
bUpdateFlagsShared |= STEERING_FLAG;
}
}
You should read the documentation of AttachInterrupt() - in the section "About Interrupt Service Routines" it gives information on how certain functions behave when called from an interrupt. For micros() it states:
micros() works initially, but will start behaving erratically after 1-2 ms.
I believe that means after the ISR has been running for more than 1ms, rather than just 1 ms in general, so may not apply in this case, but you might need to consider how you are doing the timing in the ISR. That's a problem with Arduino - terrible documentation!
One definite problem which may be a cause is the fact that unSteeringInShared is non-atomic. It is a 16 bit value on 8 bit hardware so requires multiple instructions to read and write and the process can be interrupted. It is therefore possible to read one byte of the value in the loop() context and then have both bytes changed by the interrupt context before you read the second byte, so you then up with two halves of two different values.
To resolve this problem you could either disable interrupts while reading:
noInterrupts() ;
unSteeringIn = unSteeringInShared ;
interrupts() ;
Or you can spin-lock the read:
do
{
unSteeringIn = unSteeringInShared ;
} while( unSteeringIn != unSteeringInShared ) ;
You should do the same for unThrottleInShared too, although why you do not see any problem with that is unclear - this is perhaps not the problem you are currently observing, but is definitely a problem in any case.
Alternatively if 8 bit resolution is sufficient you could encode the input as an atomic 8 bit value thus:
uint8_t unSteeringInShared ;
...
int32_t timeus = micros() - ulSteeringStart - 1000 ;
if( timeus < 0 )
{
unSteeringInShared = 0 ;
}
else if( timeus > 1000 )
{
unSteeringInShared = 255;
}
else
{
unSteeringInShared = (uint8_t)(time * 255 / 1000) ;
}
Of course changing your scale from 1000 to 2000 to 0 to 255 will need changes to the rest of the code. For example to convert a value x in the range 0 to 255 to a a servo pulse width:
pulsew = (x * 1000 / 256) + 1000 ;

Resources