Close

weee! 'bithandling' and now 'gpio_port'

A project log for commonCode (not exclusively for AVRs)

A shit-ton of things that are useful for a shit-ton of projects. (and, Think 'apt-get' for reusable project-code)

eric-hertzEric Hertz 09/06/2016 at 05:049 Comments

So the "thing", of the last post, was porting of https://github.com/ericwazhung/grbl-abstracted such that grbl can now be more-easily ported to a variety of architectures.

Now (actually, months-ago) functioning and configurable for either a PIC32 or the original AVR (while remaining Byte-For-Byte identical to the original!). If you're curious, check out the "diff" at that page.

----------

Today, as a move toward porting #sdramThingZero - 133MS/s 32-bit Logic Analyzer to its next architecture (originally AVR, now PIC32, and, again, with all intention of making it completely "architecture-agnostic" (where did I get that phrase stuck in my head...? It doesn't seem quite right...)... as a means to that end, I've begun the first completely new version of my first go-to 'commonThing,' 'bithandling,' since its origins about a decade ago.

----------

I've jumped from v0.99 to v1.99, because, frankly, it's darn-near entirely different, and darn-near a complete rewrite, with new function/macro-names and more standardization...

E.G. a big one that should make it a million times more intuitive:

all argument-lists are now arranged such that the variable to be modified is listed *first*.

Apparently, I was pretty much completely arbitrary about that in the previous versions... and, despite having made *TONS* of use of it over the years, that was always a big trip-up...

e.g. writeMasked2(variable, mask, value) rather'n... oh jeeze, what was that order again?

E.G.2. a big one that might make it a million times less intuitive:

'bithandling' has been stripped of most of its GPIO-related stuff, that stuff has now been moved to 'gpio_port' (that's a "more intuitive")... but in-so-doing I also changed quite a few macro-names... e.g. whereas previously all the port-related macro-names were *very closely* related to the associated 'bithandling' names (e.g. setpinPORT() ~ setbit()) now they're NOT AT ALL similar. E.G. setpinPORT -> gpioPORT_ohJeezeI'veAlreadyForgotten.

gpioPORT_makeOutputPinHigh(), or, rather, gpioPORT_outputOneToPin(), I think.

So, where *reading* the code is downright self-explanatory, *writing* the code seems to result in lots of reference to gpio_port.h

That, maybe, is more a result of just not yet being familiar with it...

It was pretty bad, before, as well... e.g. there was setinPORT() and setPORTin()... one would set a pin on a port to an input, the other would set the entire port to inputs (all bits).

These *ideas* came about mostly as a result of prepping these macros for grbl-abstracted, and, to a much lesser-extent, much of it was implemented there... and I'm pretty content with most of the changes, but there's reason I went to 1.99, rather than 2.00, as I'm obviously not 100% on a few things. (But, surely, it was *WAY* too large a change to go from merely 0.99 -> 1.00)

So, having written the whole blasted thing with those crazy-long function-names ala how you're supposed to choose a difficult-to-crack, but allegedly-easy-to-remember password:

https://xkcd.com/936/... well, anyways, you're *supposed* to be able to remember those things... but these things are a bit more wonky... Did I choose "correct" or "right", "One" or "High" this time 'round...? Yahknow?

...I find I might just write a whole new set of wrapper-macros or otherwise rename 'em back to the old names I've somewhat arbitrarily-memorized, but with a "2" at the end so I know that the version-2 macro-names *always* have the arguments listed with the *variable first* (or *PORT*).

We'll see.

Maybe I'll write a shell-script to automatically change e.g. setinPORT2() to gpioPORT_configurePinAsInput() for whenever I might decide to distribute something, just so it's easier for me to write/remember, while also being easier for newbs to digest. It's a thought.

--------------

Other additions:

Because bithandling is pretty much the first thing to go into any of my projects, regardless of the architecture, it handles a few architecture-related-things, like making it possible to easily add architecture-specific code to other 'commonThings' *when necessary*... Which, of course, is *highly* necessary for 'gpio_port'... So far, there's gpio_port_avr.h and gpio_port_pic32.h... So, there's a #include in gpio_port.h that looks, quite literally, like:

#include CONCAT_HEADER(gpio_port_,__MCU_ARCH__)
which equates, hopefully obviously, to (when compiled for an AVR):
#include "gpio_port_avr.h"
This method, again, was largely developed for grbl-abstracted, and now finally moved to commonCode.

And... a sorta late-in-the-game realization that I'll probably make use of in the near-future...

Part of the point of gpio_port's functionality is that it makes darn-near everything one can do with a GPIO port/pin available as its own macro... SUCH THAT, A) it's easy to read, B) it compiles (via optimizer) as, usually, the bare-minimum instructions that'd be required to accomplish the goal (no switch-statements to look up which pin is on which port, or which functionality to perform, etc.)...

BUT: again, that can lead to a bit of confusion with the naming... because there are a *lot* of names.

And, even worse, it means that *all those things* have to be reimplemented each time a new architecture is added (if you want full architecture-...-independent support for whatever code makes use of it). That's a lot of work.

SO... it occurred to me...

There are A LOT of things that *can* be handled quite-simply, by making use of each other... E.G.

configurePinAsAnOutput()

configureAllPinsAsOutputs()

configureMaskedPinsAsOutputs()

ALL of these could be handled, simply, by different "calls" to configureMaskedPinsAsOutputs()

The question, then, is... Is it reliable to think that most architectures' optimizers would be smart enough to recognize the most-optimal optimization of all those masked-cases...?

I know for a fact AVR's optimizer (at -Os) has been known to recognize e.g.

PORTA |= (1<<7)

as nothing more than an sbi() instruction

Pretty awesome, really... as it could much more easily have done a read-modify-write as the code was written. (Props to that optimizer team!)

BUT: at, e.g., -O1, I think it should be pretty much guaranteed to use the read-modify-write, as-written... which I don't see a use for, and would really bog things down in some other, more sophisticated, cases. And... are other architectures' optimizers as smart as avr-gcc's? Further, e.g. xc32-gcc, the free license, doesn't allow -Os, so would generate some pretty bloated code that, likely, wouldn't be any better as macros than function-calls. Maybe worse.

So, then, How could I reduce the "entry barrier" for new architectures...?

How's about e.g. using configureMaskedPinsAsOutputs() for *all* cases *except* when that case is explicitly-defined in the architecture-specific file e.g. gpio_port_pic32.h.

Then, it's likely, the majority of stuff could easily be implemented at an early-stage (and functional, really) with only a few explicitly-architecture-specific macros... And, the remainder could be manually "optimized" whenever one sees fit...

And... That's where it gets a little less "optimal"... Some architectures would be quite happy with writeMasked style stuff... PIC32 has _SET and _CLR registers for every PORT register... writeMasked could easily be implemented with two register-writes, no read-modify-write necessary. That is *just as* efficient for all the cases shown. But, e.g. on an AVR, where *three* bits are to be written, it would *definitely* require a read-modify-write, and probably some bit-shifts. (and, again, when optimization is turned off, it'd result in the same, even if only one bit was toggled and an sbi() *could've* replaced it

I've already, long-since, optimized *most* of those macros for AVR, and *many* for PIC32... but when a new architecture is added... would it be better to use writeMasked() as default for all cases, or is another method more general-purpose-optimal... (and... maybe... it'd be worth it to just implement writeMasked as a function-call in those cases...?)

So, that's where I'm at, at the moment.

And... admittedly... some amount of pondering whether having *all those macros* is such a great idea, at all... Yeah... I think it's better'n e.g. writing PORT |= (1<<7) everywhere, ain't no question whether that's architecture-specific. ("Wait, do I write LAT on this architecture, or PORT? Can I read it back for an R-M-W?") Yeah... these help... a lot.

And, again, when hand-optimized ONCE, in the associated gpio_port_<mcu>.h file, they generally result in the lowest instruction-count possible... which is generally *FAR* fewer than a function-call. So that's also quite handy. (Kinda the point, really).

...........

I guess, at some point, one might wonder whether *so much* effort should be placed on GPIOs at all, when darn-near everything these days *could* be handled by peripherals... right?

Not sure... E.G. I once implemented a UART on a device whose only serial-peripheral was synchronous, via an example from the manufacturer. Admittedly a great hack that they came up with a way to use a synchronous serial peripheral to implement asynchronous communication. And, frankly, I was pretty proud to have accomplished the same, from their example.

But, as I recall I think it was almost as much CPU-load (if not more?) than bit-banging the blasted thing. Nevermind being something that really only applies to a handful of parts, even from the same line of controllers, as it made use of a whole bunch of features available only to that style of synchronous serial peripheral, and other available features on that specific device (Again, a "Great Hack" worthy of admiration, and a great learning-experience). But, that guy was half-duplex, and bitbanging a UART in full-duplex while running other software, without even having interrupts isn't really *that* difficult for a RISC processor running at even a lowly 4MHz to handle.

Write that gpio-based software *once* (commonCode's 'polled_uar' and 'polled_uat'), and implement as many UARTs as you need on GPIOs on nearly any architecture... Not *ideal* in most-cases, (especially where a dedicated UART exists) but certainly easy to get a project started, when it's already been written, and easier to throw a debug-port on a device whose uart-peripherals are already in use, or otherwise not-yet-understood.

It's like anything, a tool, with pros-and-cons. Definitely not a one-size-fits-all, but handy to have at the right time. And, using 'bithandling' (and now 'gpio_port'), once set up *once* for your architecture, it doesn't care any more about your architecture-specifics than how to toggle a pin in the same way you'd blink an LED, the same way you'd bit-bang 32-bit SPI on an 8-bit architecture, interface with an SD-card, or SDRAM, or handle something as complex as timing acceleration of stepper-motors ala grbl on a processor most people would consider under-spec'd (props to its creator, definitely some wizardry in making that possible!). It would, however, be a bit more of a stretch to use these macros for, e.g., the bitbanged-implementations of USB on e.g. an AVR, where every clock-cycle counts and most of the *surrounding* code has to be written in assembly, so couldn't be ported to another architecture by merely abstracting the I/O routines. This "abstracted-gpio" method, despite intending to be *as fast as* direct register-writes and/or the associated-assembly, definitely has its limitations.

But, as it stands, *most* of those PIC32 I/O routines have already been "added to the bank," as-of grbl-abstracted, and several other projects. So, the current project, porting of #sdramThingZero - 133MS/s 32-bit Logic Analyzer from its current early-experiments on the AVR-host is little more than a matter of "transferring between accounts" (e.g moving the associated code from grbl-abstracted into commonCode's new 'gpio_port')...

Well, that... and soldering up a TQFP breakout-board, figuring out pinouts, etc. WEEE!

---------

Oh, and I've been meaning to throw in there that...

Another *big* part of the older versions of 'bithandling' (0.xx) and now 'gpio_port' is the fact that all port-related macros make use of *only* references to the associated "PORT" register.

That's kinda a big deal...

E.G. on AVR you have:

WHEREAS: E.G. on a PIC32 you have an *entirely* different scenario:

(And this list is a bit simplified)

OK, so why all this info...?

Well, there are a few things that are supported by both architectures (and I don't pretend to know enough about the myriad of architectures out there to claim these options exist on *all* architectures, but are probably pretty common)...

So, let's look at the AVR again... It basically has *four* GPIO pin-states, controlled by its registers:

The PIC32 can implement all these, as well... (and more, e.g. [Input] pulled-down)

But, doing-so on *both* architectures, requires writing to a myriad of different registers. And, Not Only do those registers have different names on different architectures, not only do they have different *functionality*, but setting some of those configurations on *one* architecture might require multiple registers to be written, plausibly even with read-modify-writes, while setting that same configuration on another architecture might only require writing one register.

So, obviously, setting up different pin-configurations on these different architectures requires not only different *code*, but *also* different register-names.

BUT:

The cool thing about these architectures (and maybe this is true amongst most?) is that these registers are all located *relative* to each other, within the devices' memory-maps.

E.G. On the AVR (for *all* AVRs? I've only worked with a dozen or so varieties):

On the PIC32 (for *all* PIC32s? At least the MX-series...?)

SO... This knowledge makes pinoutting *perty-durn-easy* while *still* being architecture-unspecific...

Every GPIO pin, on both architectures (and probably most?), can be uniquely-referenced by its PORT-register and its bit-number...

Thus, e.g.

#define LED_BIT   3
#define LED_PORT  PORTA
...are the only two things yah-needs-ta-list for any LED pin attached to any port on (any?) both architectures (this happens to correspond to pin PA3, on an AVR, or RPA3 on a PIC32).

The remainder can be accomplished by nothing more than the macros in 'gpio_port'.

So, say you want to set the LED-pin as an output and turn it on (active-high):

gpioPORT_configurePinAsOutput(LED_PORT, LED_PIN);
gpioPORT_outputOneToPin(LED_PORT, LED_PIN);

But, again, for an AVR that would require, otherwise, keeping track of LED_BIT, LED_PORT, and LED_DDR.

For a PIC32 that would require, otherwise, keeping track of LED_BIT, LED_LAT, and LED_TRIS. (And, actually, LED_ANSEL, as well)

...

If we had to keep track of all that for *every* pin on *every* architecture, then feed the appropriate register to the appropriate macro... Yeah, talk about NON-architecture-unspecific.

So, just use _BIT and _PORT, and let the rest be handled by macros... and, in the end, in most cases, the math should be handled during compile time via the optimizer (or maybe even the preprocessor?), thus they'll result in *direct* writes to the *actual* register, rather than doing pointer-arithmetic during *every* call to determine *which* register to write.

(Again, these are *macros*, not *functions*, and their arguments are *expected to be* constants. (You could get away with non-constants, it'd function, but it would *definitely* be much slower))

Perty Handy... and at least a bit more architecture-unspecific.

And, having yet to meet many other architectures, I can only imagine some might have different predefined register names than, e.g., "PORTA" (maybe "port_reg0"?)... hey, as long as they have their various register-offsets in a predefined order (like DDRx = PORTx - 1, etc.) then it'd be just as easy to implement the associated macros for that architecture...

Sure, your LED_PORT wouldn't be as pretty as "PORTA", instead looking like "port_reg0", but functionally, it'd all be the same...

And, really, how often you think you're gonna use the same pinout on two *entirely* different architectures...? Hey, at least all ya's gots to do is find/replace a *couple* #defines for each pin, and can still use the same macros and the same arguments in the calls to those macros.

Discussions

James Newton wrote 09/06/2016 at 17:11 point

Also interesting comments on referencing IO... Here is my thought: The effort expended needs to match the time saved. If you really spend hours and hours in total messing about with chip specific io setup, or minutes per project times many many projects, then spending hours and hours making that issue "go away" is worth it. I've regretted the time I've spent trying to standardize IO setup because it got in the way of my getting projects done and "good enough". I used to get hung up on doing things "right"... I'm learning to just get things done. 

  Are you sure? yes | no

Eric Hertz wrote 09/07/2016 at 10:58 point

It's a mixed-bag. People reinvent the wheel repeatedly... Thousands of people. Is that any better than one person's getting-er-done?

  Are you sure? yes | no

James Newton wrote 09/06/2016 at 16:58 point

https://github.com/ericwazhung/grbl-abstracted 
is amazing... how cool that the code can be re-targeted and come out the same for the original target. Is there any chance that your changes could be pulled back into the original so that future development rests on your work? It's sad to think that they would ignore such a gift.

I don't suppose there is any hope of that compiling and functioning on an 8 bit PIC e.g. a 16F14K22? I have a motion control system that runs on one of those... not written by me... but running on a PCB I designed.

http://www.piclist.com/techref/microchip/bobmotion-gcode-vp.htm

  Are you sure? yes | no

James Newton wrote 09/06/2016 at 17:18 point

For example, I once built up these macros to get the stupid PIC C compiler... this was years back, can't remember which version... to let me define IO pins in ONE line e.g. if the red LED was on pin 1 of port B:

#define LED_RED(p) p(B,1)

would then allow things like PPinOut(LED_RED); PPinHigh(LED_RED); but in the long run, I dropped that because the new C compiler didn't work that way and it wasn't worth my time to figure out how to re-do it. And it wasn't that complex... just not worth screwing with when there was code to write. Let me see if I can find it... ah... here:

#define concat(x,y) x ## y

// ;these macros allow a single define for the pin port and pin number to
// ;become all the registers used to control that pin. For example...
// #define LCD_E(p) p(C,4)
// ...sets up the 4th pin on the C port as the LCD Enable pin. Then...
// PPinOutput(LCD_E) 
// ...sets that as an output and...
// PPin(LCD_E) = 1
// ...makes that output high. 
#define PPinDIR_(port,pin) (TRIS ## port ## pin)
#define PPinPDIR_(port,pin) (TRIS ## port)
#define PPinOUT_(port,pin)    (R ## port ## pin)
#define PPinLAT_(port,pin)    (LAT ## port ## pin)
#define PPinPUL_(port,pin)    (WPU ## port ## pin)
#define PPinPOR_(port,pin)    (PORT ## port)
#define PPinPIN_(port,pin)    (pin)
#define PPinDIR(pin) pin(PPinDIR_)
#define DIR_INPUT 1
#define DIR_OUTPUT 0
#define PPinIn(pin) pin(PPinDIR_)=DIR_INPUT
#define PPinOut(pin) pin(PPinDIR_)=DIR_OUTPUT
#define PPin(pin) pin(PPinOUT_)
#define PPinHigh(pin) pin(PPinOUT_)=1
#define PPinLow(pin)  pin(PPinOUT_)=0
#define PPinLatch(pin)  pin(PPinLAT_)
#define PPinPull(pin) pin(PPinPUL_)
#define PPinPort(pin) pin(PPinPOR_)
#define PPinPortDIR(pin) pin(PPinPDIR_)
#define PPinPin(pin) pin(PPinPIN_)

  Are you sure? yes | no

James Newton wrote 09/06/2016 at 17:35 point

Now I tend to spend my time making the code better able to adjust to changes that I know I might make. E.g. I wasn't sure what sample rate and clock speed I could get to work reliably in the BOBPID code, so I put some work into making the setup of all the stuff that depends on the clock speed automagically reconboobulate itself, and that saved me gobs of time as I fiddled and tweeked the code to get higher and higher sample rates. 

#define SAMPLE_TIME .005 //Seconds
#define SAMPLE_FREQ (1/SAMPLE_TIME) //Hz

// Frequency will be (FOSC/4)/((65536-X)*Prescale) in 16 bit mode. 
// Solving for X we get  X = 655536 - Hz * Prescale * FOSC/4
 #define TIMER0_PRESCALE 256
 #define TIMER0_COUNT (65536 - (OSC_FREQ / (4UL*SAMPLE_FREQ*TIMER0_PRESCALE)))
 #if TIMER0_COUNT < 0 || TIMER0_COUNT > 65535
  #error TIMER0 count out of range
  #endif

/**** SETUP INTERRUPTS ****/
T0CON=0 // Timer 0 Control
| 0b10000000 // Enable timer 0=disabled
#if OSC_FREQ <= 16000000L
| 0b01000000 // 8 bit counter 0=16 bit
#endif
// | 0b00100000 // External clock 0=internal 1=external
// | 0b00010000 // Increment on falling edge 0=rising
// | 0b00001000 // Disable Prescaling 0=Enabled 1=disabled
#if 256 == TIMER0_PRESCALE
|      0b111 // 1:256 prescale value
#elif 128 == TIMER0_PRESCALE
   |      0b110 // 1:128 prescale value
#elif 64 == TIMER0_PRESCALE
   |      0b101 // 1:64 prescale value
#elif 32 == TIMER0_PRESCALE
   |      0b100 // 1:32 prescale value
#elif 16 == TIMER0_PRESCALE
   |      0b011 // 1:16 prescale value
#elif 8 == TIMER0_PRESCALE
   |      0b010 // 1:8 prescale value
#elif 4 == TIMER0_PRESCALE
   |      0b001 // 1:4 prescale value
#elif 2 == TIMER0_PRESCALE
   |      0b000 // 1:2 prescale value
#else
#error Invalid Timer0 Prescale
#endif
;

  Are you sure? yes | no

Eric Hertz wrote 09/07/2016 at 08:11 point

Great way to do it, Combining both the port and the pin number in a single #define. I think I attempted that early-on and ran into difficulties later down the road, as well. Am thinking, nowadays, it's probably more than doable in a reliably-standard way within C99.

The only hesitation I have is the direct use of ##, which works great when your port is immediately-defined, but might be an issue if, e.g. you have #define LED_PORT PORTA then have e.g. #define RED_LED p(LED_PORT, 1)... the use of ## is well-defined, but hard to wrap my head around in any sort of retainable-sense...

I wrote these macros for that purpose:

//GLUE_THESE(a,b) -> ab
#define GLUE_THESE( a, b ) a##b
//GLUE_THESE_VALUES(a,b) ->a_valb_val
// (when #define a a_val #define b b_val)
#define GLUE_THESE_VALUES( a, b ) GLUE_THESE( a, b )

Definitely some stuff in there worthy of consideration before moving on to v2.00, thanks!

  Are you sure? yes | no

Eric Hertz wrote 09/07/2016 at 10:52 point

We've gotten too deep for replies-to-comments... so your latest-comment regarding clock-speed-adjustments:

YEAH!

I tried to come up with a 'commonThing' called 'timerCommon' for AVR doing very similar things... Adjusting the clock-rate (and divisors!) based on F_CPU... That's something that's useful, quite a bit, for e.g. switching CPU-frequencies while maintaining Baud-Rates, and many other things...

But, in the case of 'timerCommon' ran into SO MANY issues with different timer-peripherals having different register-bit-definitions, and whatnot... It got a bit difficult. But that's not really relevant.

Auto-Recombobulating is quite handy... Those settings, you've shown, are those that are *constant* for each build/system, so there's no reason to force your microcontroller to calculate them in realtime... requiring a lot of program-space and a lot of processing-time (repeatedly!). Personally, I consider that a great use of the preprocessor... So many folks these days seem to think the preprocessor a bad idea, but what's the alternative? Are we really going to have a *variable* for F_CPU, when the only way it can possibly know the F_CPU is when you compile it...? And, once you've used up those 4 bytes to store something that *never* changes, then you have to *access* those four bytes *from memory* every time you make use of it... and, then, on an 8-bit processor, that means *four* memory-reads, nevermind a tremendous amount of math and switch-statements! every time you're making use of it... Yeah, personally, I dig the preprocessor.


  Are you sure? yes | no

James Newton wrote 09/07/2016 at 17:18 point

Wrote a semi-high level language in the pre-processor for an assembler once. 

http://techref.massmind.org/techref/scenix/keymacs.src

  Are you sure? yes | no

Eric Hertz wrote 09/07/2016 at 06:18 point

Great board! 

I don't see why it *couldn't* run on an 8-bit PIC with enough memory, etc., assuming there's a C-Compiler that can handle it. Though, there is a bit of work to be done making sure it'd fit in with all the peripherals (serial I/O, Timers running back-to-back, a few other things). 

I could be mistaken, but I find it utterly amazing the grbl dude managed to handle all that on an AVR, what with all the floating-point, etc. Nevermind so much of it being written at such a high level in C. 

You'll have to try it and let me know :)

  Are you sure? yes | no