Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Apollo3 Implementation #205

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

csonsino
Copy link

@csonsino csonsino commented Sep 9, 2019

This change adds initial Apollo3 support.

The majority of the functionality is contained in the new apollo3.cpp file, but some minor modifications were made to Adafruit_NeoPixel.cpp in the setPin() function (support for Apollo3 fast GPIO configuration). Function prototypes were added to Adafruit_NeoPixel.h.

Changes to existing code have been wrapped in a AM_PART_APOLLO3 definition check, so existing platforms should not be affected.

I would note that this is a very initial implementation, using the noop timing hack. I would prefer to have a more robust timing implementation (interrupts, DMA, etc). We should be able to put this out there for folks to use in the short term, and transparently swap out the timing implementation when a better solution is in place.

This code was tested using a SparkFun RedBoard Artemis Nano, with the simple and strandtest example apps.

@ladyada
Copy link
Member

ladyada commented Sep 9, 2019

@oclyke please test and review this PR, thank you!

@ladyada
Copy link
Member

ladyada commented Sep 13, 2019

@oclyke @santaimpersonator hiya please let me know the right person at sparkfun or ambiq to review this PR, thank you :)

Copy link

@oclyke oclyke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good, but the mapping between Arduino "pin" and Apollo3 "pad" needs to be implemented.

pin = p;
#if defined(AM_PART_APOLLO3)
apollo3SetPin(pin);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After updating the pin variable either pin or p can be used to refer to the new pin number. The usage in the standard pinMode call uses p while the specialized call for Apollo3 uses pin. This may be more confusing/harder to maintain in the future. Because p is already in use my rec. would be to use p in the specialized call as well

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, although now I'm modifying the value of p to the Apollo3 mapped pad, which might also be slightly confusing.

apollo3.cpp Outdated
@brief Set the NeoPixel output pin number.
@param p Arduino pin number (-1 = no pin).
*/
void Adafruit_NeoPixel::apollo3SetPin(uint16_t pin) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As written this function will likely confuse users b/c NeoPixel output may be mapped incorrectly. The input parameter here is the Arduino pin while the calls to am_hal_gpio_xxx expect the Apollo3 pad number. The mapping is dependent on the board variant and uses ap3_gpio_pad_t ap3_gpio_pin2pad(ap3_gpio_pin_t pin); which is available wherever "Arduino.h" is included.

This applies to any/all cases in this file where the pin is passed directly into an Ambiq HAL function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to apply the pin-to-Apollo3 pad mapping in the Adafruit_NeoPixel.cpp setPin function. That way, the mapping only needs to be performed a single time instead of every Ambiq HAL function, and we can avoid that extra function call in every show().

@oclyke
Copy link

oclyke commented Sep 13, 2019

@ladyada thanks for the reminder :)

@csonsino
Copy link
Author

@oclyke I found a pretty slick way to implement the timing using a ctimer in PWM mode (see apollo3.cpp in my apollo3_ctimer_pwm branch). The last part that I'm struggling with is how to configure the ctimer based upon the selected pin (pad).

Table 818: Counter/Timer Pad Configuration in the Apollo3 Blue Datasheet shows the possible pad-to-timer configurations:

Pad  (FNCSEL)  ctimer output signal  Output Selection (REG_CTIMER_INCFG)
                  2      3      4      5      6      7
PAD4 (6) CT17 ... A4OUT2 B7OUT  A4OUT  A1OUT2 A6OUT2 A7OUT2
PAD5 (7) CT8  ... A2OUT  A3OUT2 A4OUT2 B6OUT  A6OUT2 A7OUT2
PAD6 (5) CT10 ... B2OUT  B3OUT2 B4OUT2 A6OUT  A6OUT2 A7OUT2
PAD7 (7) CT19 ... B4OUT2 A2OUT  B4OUT  B1OUT2 A6OUT2 A7OUT2

I admittedly do not fully grasp the ctimer configuration, and I cannot seem to get interrupts when I use any of the *OUT2 configurations. I'm not exactly sure what I'm doing wrong, but all of the *OUT configurations work fine. I thought that I might need to pass AM_HAL_CTIMER_OUTPUT_SECONDARY (instead of AM_HAL_CTIMER_OUTPUT_NORMAL) into the am_hal_ctimer_output_config function, but that didn't seem to fix the issue. But that's probably a tangential problem.

I see a bunch of possibly useful stuff with the CTx_tbl and outcfg_tbl in am_hal_ctimer.c, but there's really no good way to access that. Is there?

I guess I'm just wondering if you have any suggestions on how I might automatically configure the ctimer properly when the apollo3SetPad function is called? There's got to be a better way than hardcoding this mapping again.

@csonsino
Copy link
Author

I'm going to look into a different strategy. Even though I really like this ctimer mechanism, it requires that I attach a am_ctimer_isr handler directly, which then conflicts with BLE functionality (that also wants to define a am_ctimer_isr handler). If I try to use am_hal_ctimer_int_register for my interrupt handler to "play nice" with the BLE ctimer ISR, it seems to be too slow with the fastest clock mode (12MHz).

@oclyke
Copy link

oclyke commented Oct 14, 2019

@csonsino excellent work! It shows that you've taken the time to develop a good solution. Thanks!

I too have struggled with the PWM peripherals... I'll see if I can convey what I learned.

Automatic Configuration Based on Pad

You're spot-on that the tables in am_hal_ctimer.c would be very helpful and that they are basically impossible to rely on without (gasp) duplicating them. So..... the good news is that I can save you the shame of duplicating those tables yourself because I've already done it in the analog library.

Now of course the Apollo3 Arduino core is still pretty alpha and I can't guarantee anything but what I'd suggest doing is trying to see if you can use ap3_pwm_output() to generate the required signals. If that function isn't what you need we could also collaborate to add something else to the core that you could leverage in this implementation.

It is also possible that I could make those copies of the tables public and that you (and others in the future) could use them how you please. I'm not sure yet if there is any major reason not to do that.

Secondary Outputs

First of all there are only 32 PWM capable pads on the Apollo3 so the user will already need to make sure they are connecting to a supported pin - but it turns out that only 16 of those outputs can be completely independent of one another. It took me a lot of read-throughs of the ctimer section of the datasheet to understand this but effectively the secondary output has to share a common period with the primary output. This makes it very challenging (impossible?) to write kitchen sink code that allows a user to configure any PWM pad for any PWM output. I don't have an answer for a particularly good way around this problem. I also don't remember the intricacies of interrupt generation using the secondary output.

BLE Interference

Okay so obviously we will need to keep track of where all our dedicated interrupt vectors are headed (defined) but that's another problem for another day. Do you know what the BLE code is using the ctimer for (I can't look into it RN and I only remember seeing it as a cursory glance). The reason I ask is because I am working on porting mbed os for the Apollo3 and depending on the resolution needed for BLE the ctimer could possibly be replaced by a software timer / thread.

Keep up the great work and let me know if there is anything useful that we could collaborate on within the core itself.

@csonsino
Copy link
Author

Great, I think that the code in the ap3_pwm_output function could come in handy, and it may make sense to split some of it out into a separate function. But I don't think that I can use ap3_pwm_output as-is. It looks like it's intended to set up a simple duty-cycled repeating PWM waveform, whereas the Neopixel waveform is a very custom signal that must be constructed with the "hi" time varying to represent the necessary 1's & 0's (the duty cycle must change after each frame width).

The big problem that I keep running into is clock speed. I almost need access to the full 48MHz clock in order to schedule things appropriately. With the 12MHz clock, it's tough to do anything else when I need to schedule an interrupt 5 clock cycles away. Half a microsecond timing is not easy. With such few cycles to spare, even function call overhead becomes significant. Forget trying to also process BLE stuff.

I'm crossing my fingers that the mbed os port solves some of the timing issues. It's possible that multithreading would make things work better together.

As far as the current BLE example code, if I'm interpreting it correctly I believe that it's using the ctimer as the reference for the wsf framework (Wicentric Software Foundation BLE stack). That stack might be using the timer to keep track of all BLE related functions- beacons, connection intervals, etc.

@oclyke
Copy link

oclyke commented Oct 22, 2019

I haven't gotten to take a look at your data output method yet. How exactly are you utilizing the CTimer module? Are you shifting out the data one bit at a time? Is there possibly a way to schedule a series of bit sequences all in hardware? If you could give me a quick overview of your method that would be helpful.

I'm not sure that the mbed port will make this problem any easier - especially if the data output is not handled primarily in HW. I only see it adding a lot of overhead in task switching.

There's a method of driving one-wire LEDs that I have wanted to try out for quite some time but have never gotten around to -- it might be worth looking into here. That is to use a SPI port and send out a pattern on MOSI that matches the necessary timing.

For example WS2812 LEDs have T1H, T1L, T0H, and T0L timing to account for. The GCD of those times is 0.05 us, which is the width of a single bit on a 20 MHz SPI transmission. The WS2812 datasheet says that TH+TL is roughly constant for 1's and 0's so we might assume that they should be 1.2 us wide each. Then each 1 or 0 code would be comprised of 24 bits of SPI data.

At 20 MHz:
Code 1: 0xFF 0xF8 0x00
Code 0: 0xF8 0x00 0x00
(some approximations taken here)

So then one byte of LED data would take 3*8 = 24 bytes of data to be sent out over SPI. At first this seems like far too much overhead but the advantage is that the SPI transfer can be run by DMA. A small-ish buffer (say 240 bytes or so) could be pre-filled with the necessary codes to control 10 LEDs, then while that data is transferred out a second buffer could be filled with data for the next 10 and so on. Filling buffers would not take long provided the desired LED data.

Let me know what you think - I've wanted to try this method out for quite some time but never got around to it.

@ernestocurty
Copy link

I tried to run in an Artemis ATP with Adafruit Neopixel 16 led ring (and a logic level shifter) without success. Compiles without errors but leds don't lit up.

@csonsino
Copy link
Author

I'm not sure what the ATP is doing, but from my experience if the application loop is doing anything other than controlling the LEDs, then they won't light up.

I have implemented the show() function several different ways - the raw noop method, ctimer with interrupts, and am_hal_flash_delay(). They're all in my apollo3_flash_delay branch. But they all bitbang the LED control and basically need every clock cycle. If the CPU tries to do something else, then the timing gets thrown off.

I've been trying to find the time to investigate the solution suggested by @oclyke, but haven't gotten to it yet.

Other microcontrollers with slower clocks can deal with LED control and BLE at the same time (my particular need), so this seems like it should be a solvable problem.

@ernestocurty
Copy link

ernestocurty commented Nov 18, 2019

I'm not sure what the ATP is doing, but from my experience if the application loop is doing anything other than controlling the LEDs, then they won't light up.

I have implemented the show() function several different ways - the raw noop method, ctimer with interrupts, and am_hal_flash_delay(). They're all in my apollo3_flash_delay branch. But they all bitbang the LED control and basically need every clock cycle. If the CPU tries to do something else, then the timing gets thrown off.

I've been trying to find the time to investigate the solution suggested by @oclyke, but haven't gotten to it yet.

Other microcontrollers with slower clocks can deal with LED control and BLE at the same time (my particular need), so this seems like it should be a solvable problem.

Well, I just installed the library from your forked repo and tried to run the "simple" example, using a 16 LED ring from adafruit. The version I installed is the one with the noop method.

I saw a couple of libraries that implemented a method of sending data using DMA and hardware SPI. They implemented a conversion where the "1 bits where replaced by "110" and the "0" bits to "100", then send these stream over SPI. Since the neopixels use HI pulses for one and zero they could adjust the spi clock about 3 times the led clock and let the SPI chip do the heavy lifting. The drawback is that it uses 3 times more RAM. Do you think we can implement something similar in the artemis board?

@oclyke
Copy link

oclyke commented Nov 21, 2019

@ernestocurty Yes I think that is a worthwhile pursuit. Using 3x the ram would be acceptable if you could use a small(ish) buffer to push out the next N led's data. (see here)

I won't personally be able to get to this for quite a while. In the meantime if you or anyone else could tack onto this PR that would be great!

@ernestocurty
Copy link

@oclyke, I believe this is task is above my humble coding skills, but with some support (e.g. some references to read) I may be able to embrace it.

For the immediate needs of my project I'll test the APA102 LEDs. Since they run in standard SPI protocol, they may be easier to get running
Best,
Ernesto

@ernestocurty
Copy link

@oclyke, I apologize for missing your explanation for the same theme in the previous answer.

@oclyke
Copy link

oclyke commented Nov 22, 2019

@ernestocurty Don't worry 'bout it! I'll keep an eye out here to see how it goes. I don't have any fantastic resources to share above/beyond the respective datasheets but if you get your feet wet with SPI on the APA102s then that would be a good foundation for the WS2812s I think.

@ankur608
Copy link

ankur608 commented Jan 1, 2020

This change adds initial Apollo3 support.

The majority of the functionality is contained in the new apollo3.cpp file, but some minor modifications were made to Adafruit_NeoPixel.cpp in the setPin() function (support for Apollo3 fast GPIO configuration). Function prototypes were added to Adafruit_NeoPixel.h.

Changes to existing code have been wrapped in a AM_PART_APOLLO3 definition check, so existing platforms should not be affected.

I would note that this is a very initial implementation, using the noop timing hack. I would prefer to have a more robust timing implementation (interrupts, DMA, etc). We should be able to put this out there for folks to use in the short term, and transparently swap out the timing implementation when a better solution is in place.

This code was tested using a SparkFun RedBoard Artemis Nano, with the simple and strandtest example apps.

Unable to compile for ArtemisATP....need help for pin definition

@nakanaela
Copy link

I came across this(https://github.com/konkers/light_stick) and thought it might help anyone working on trying to adapt the Apollo3 to use SPI to improve the timing issues. After I have a good rest, I may try to see if I can make this work with what @csonsino has done as I'd love to get my artemis thing plus running some audio reactive projects.

@ladyada
Copy link
Member

ladyada commented Oct 30, 2020

@oclyke @PaulZC can this PR be merged?

@csonsino
Copy link
Author

Hmmm. I don't know if I would merge that. It's a proof of concept, and I never quite got it working in an application that included any other functionality.

A while back I attempted @oclyke's SPI MOSI suggestion but I didn't have much success. I'll see if I can dig up that code & take a look with an o-scope.

@PaulZC
Copy link

PaulZC commented Oct 31, 2020

Hi @ladyada ,
I'm not sure... I'll let @oclyke decide as he was looking after this. I had some success adding support for Apollo3 in FastLED but it looks like that might now be failing with the latest (Mbed) version of the core.
Happy Halloween!
Paul

@ladyada
Copy link
Member

ladyada commented Oct 31, 2020

okidoke, just checkin in on some old PRs :) please let me know when it could be ready for merging. we could try adding apollo3 to our CI as well once ready

@wzqvip
Copy link

wzqvip commented Jul 26, 2023

Is there any progress? I still failed to light a ws2812 on apollo3 board. Neopixel with

#define Interrupts() (void)am_hal_interrupt_master_disable()
#define noInterrupts() (void)am_hal_interrupt_master_enable()

Does not light it. FastLED get only white and green(?).
Or is there an example on how to use the unoffical apollo3? I failed to compile.
Compiling .pio\build\ATP_SERIAL\libeaa\Adafruit_NeoPixel-apollo3\apollo3.cpp.o lib\Adafruit_NeoPixel-apollo3\Adafruit_NeoPixel.cpp: In member function 'void Adafruit_NeoPixel::show()': lib\Adafruit_NeoPixel-apollo3\Adafruit_NeoPixel.cpp:222:3: error: 'noInterrupts' was not declared in this scope 222 | noInterrupts(); // Need 100% focus on instruction timing | ^~~~~~~~~~~~ lib\Adafruit_NeoPixel-apollo3\Adafruit_NeoPixel.cpp:2205:3: error: 'interrupts' was not declared in this scope 2205 | interrupts(); | ^~~~~~~~~~ lib\Adafruit_NeoPixel-apollo3\Adafruit_NeoPixel.cpp: In member function 'void Adafruit_NeoPixel::setPin(uint16_t)': lib\Adafruit_NeoPixel-apollo3\Adafruit_NeoPixel.cpp:2226:7: error: 'ap3_gpio_pin2pad' was not declared in this scope; did you mean 'ap3_gpio_pad_t'? 2226 | p = ap3_gpio_pin2pad(p); | ^~~~~~~~~~~~~~~~ | ap3_gpio_pad_t *** [.pio\build\ATP_SERIAL\libeaa\Adafruit_NeoPixel-apollo3\Adafruit_NeoPixel.cpp.o] Error 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants