Jump to content
 

N9WXU

Member
  • Content Count

    81
  • Joined

  • Last visited

  • Days Won

    24

N9WXU last won the day on November 27

N9WXU had the most liked content!

Community Reputation

38 Excellent

1 Follower

About N9WXU

  • Rank
    Contributor

Recent Profile Visitors

547 profile views
  1. The two functions you see are both halves of a ring buffer driver. The first function unloads the UART receive buffer and puts the bytes into the array eusart2RXBuffer. This array is indexed by eusart2RXHead. The head is always incremented and it rolls over when it reaches the maximum value. This receiving function creates a basic ring buffer insert that sacrifices error handling for speed. There are four possible errors that can occur. UART framing error. If a bad UART signal arrives the UART will abort reception with a framing error. It can be important to know if framing errors have occurred, and it is critical that the framing error bit be cleared if it gets set. The UART receiver is overrun. This happens if a third byte begins before any bytes are removed from the UART. With an ISR unloading the receiver this is generally not a real threat but if the baudrate is very high, and/or interrupts are disabled for too long, it can be a problem. The ring buffer head overwrites the tail. The oldest bytes will be lost but worse, the tail is not "pushed" ahead so the next read will return the newest data and then the oldest data. That can be a strange bug to sort out. It is better to add a check for head == tail and then increment the tail in that instance. This error is perhaps an extension of #3. The eusart2RxCount variable keeps track of the bytes in the buffer. This makes the while loop at the beginning of the read function much more efficient (probably 2 instructions on a PIC16). However if there is a head-tail collision, the the count variable will be too high which will later cause a undetected underrun in the read function. The second function is to be called from your application to retrieve the data captured by the interrupt service routine. This function will block until data is available. If you do not want to block, there are other functions that indicate the number of bytes available. The read function does have a number of lines of code, but it is a very efficient ring buffer implementation which extends the UART buffer size and helps keep UART receive performance high. That said, not all UART applications require a ring buffer. If you turn off the UART interrupts, you should get simple polling code that blocks for a character but does not add any buffers. The application interface should be identical (read) there will simply be no interrupt or buffers supporting the read function.
  2. Excellent points and I think we are almost completely in agreement. I have only four complaints with these helper macros and two of them are relatively minor. Consider the following snapshot from ATMEL Studio in a SAMD21 project the <CTRL><SPACE> pattern works great when you know the keyword. If you simply start with SERCOM, you get a number of matches that are not UART related. ALL the choices will compile. See the BAUD value placed in the CTRLA register... Some of these choices are used like macros...And others are not (CMODE) Placing an invalid value in these macros is completely reasonable and will compile. MISRA 19.7 disallows function-like macros. (though this is not really an issue because it does not apply in this situation.) So, these constructs are handy because they help prevent a certain sort of bookkeeping error related to sorting out all of these offsets. But the constructs allow range errors and semantic errors. Since we are talking about the SERCOM and I am using the SAMD21 in my example. Here is what START produces. hri_sercomusart_write_CTRLA_reg( SERCOM0, 1 << SERCOM_USART_CTRLA_DORD_Pos /* Data Order: enabled */ | 0 << SERCOM_USART_CTRLA_CMODE_Pos /* Communication Mode: disabled */ | 0 << SERCOM_USART_CTRLA_FORM_Pos /* Frame Format: 0 */ | 0 << SERCOM_USART_CTRLA_SAMPA_Pos /* Sample Adjustment: 0 */ | 0 << SERCOM_USART_CTRLA_SAMPR_Pos /* Sample Rate: 0 */ | 0 << SERCOM_USART_CTRLA_IBON_Pos /* Immediate Buffer Overflow Notification: disabled */ | 0 << SERCOM_USART_CTRLA_RUNSTDBY_Pos /* Run In Standby: disabled */ | 1 << SERCOM_USART_CTRLA_MODE_Pos); /* Operating Mode: enabled */ This is completely different than the other examples provided. This style is defined in hri_sercom_d21.h and a word to the wise, DO NOT BROWSE THIS INSIDE OF ATMEL START, This file must be huge as the page is completely frozen as it populates the source viewer. So I do like the construct, but often it does not help me very much because I must still go through the entire register and decide my values. When I string these values together any mistakes made will be sorted at once. All that aside, I don't really care much about the HW initialization. I want it to be short, to the point, and perfectly clear about what is placed in each register. In a typical project, only 1% of the code is some sort of HW work as you develop the application specific HW interfaces and bring up your board. Once these are tested, you are not likely to spend any more time on them. In a 9 month project, I expect to spend 2 weeks in the HW functions so if a magic value is clear and to the point, use it. If you want to construct your values with logical operations. Go for it.
  3. But in all of your examples you are not telling me why you are doing that bit of work. I cannot possibly determine if there is a bug if I don't know why you are configuring the SERCOM with that particular value. How about simply saying: void configureSerialForMyDataLink(void) { // datalink specifications found in specification 4.3.2 // using SERCOM0 as follows: // - Alternate Pin x,y,z // - 9600 baud // - half duplex // - SamD21 datasheet page 26 for specifics SERCOM0 = <blah blah blah>; } Now you know why. You have a function that has a clear purpose. And if the link is invalid, you can see the intent. The specifics of the bits are in the datasheet and clearly referenced. No magic here. As for the special access mode for performance... inline void SERCOM0_WRITE(uint32_t ControllOffset, uint32_t Value) { // Accessing the SERCOM via DFP offsets for high performance (* (uint32_t*) (0x42000400 + ControlOfset)) = Value; } Now a future engineer has a handy helper and the details are nicely removed. And an interested engineer can debug it because the intent is clear. Obviously you need to be a DFP expert (or have the datasheet) to understand/edit it. But no magic. But the application should NEVER use this helper. It should be buried in the HAL. The first function is much more clear for the HAL because it conveys application level intent. i.e. the Application will rarely care about the SERCOM and will always care about its DataLink. If I port the code to something without a SERCOM, the application will still need a DataLink so this function will simply be refilled with something suitable for the other CPU. The application remains unchanged.
  4. As tempting as it is to duplicate the datasheet in your code, there is much to dislike about this strategy. Single Source of Truth should be the manufacturers datasheet... Not what you copied into the code. The signal to noise ratio of the code will suck, making debugging more challenging. The register description is not always enough so this slippery slope will have you copying the entire peripheral chapter. Your future maintainer will not have your knowledge of the device so they will need the datasheet anyway.
  5. Comments describing the desired affect of the register value is ideal. So here is an example. void startMotorControlPWM(void) { // configuring TMR2 for 8-bit and 8.3khz PWM // See Requirements section 4.3.2.1 T2CON = 0xXX; // TMR2 1:1 prescaler PR2 = 0x4F; // reload at 6-bits (2 bits implied) produces 8-bit PWM CCP1CON = 0xXX; // Using CCP1 for PWM with TMR2 CCPR1L = 0xXX; // initial PWM at 25% for a slow motor rotation. } Note that magic numbers are used... but the explanations make the intent clear. And the necessary requirements are referenced for trace ability. When your future self is debugging, you can verify the intent to the requirements and then verify the magic numbers against the datasheet. Making changes during debugging would be done right here with the magic numbers and that will be clear and easy because the datasheet register maps will be handy. Once this code is working, the top level function name is easy and clear.
  6. Great points. So perhaps there are some unique considerations to the magic number story when dealing directly with HW. I too have frequently seen the comments diverge from the code and your simple "fix" definitely demonstrates that point. Switching to some sort of macro or enumerations and "building" the constant for the register is only a partial help. For example: INTCON = _INTCON_IOCIF_MASK | _INTCON_IOCIE_MASK | _ADCON0_nDONE_MASK; This example is a bit contrived because Microchip now includes the register name and the bit name and the type of constant (mask). However I still see code where various datasheet constants are used but the language provides no guarantee that the constant belongs to a specific register. Note the bug above is ORing in the ADCON0 constant with the INTCON constants. This will compile clean and might even work if the bit happens to be correct. These sorts of constructs in the HW code are really too tactical. We get caught up with the details setting some bit in a register and that should NEVER the goal of the function we are writing. void startPWM(void) { <blah blah blah> } If my function is startPWM, then it is clear what the need is. If I decided to use the CCP as that PWM, or a timer interrupt, either choice will work. Now I have the beginnings of a hardware abstraction layer. After much debate, MCC chose the following pattern: void EPWM1_Initialize (void) { // Set the PWM to the options selected in PIC10 / PIC12 / PIC16 / PIC18 MCUs // CCP1M P1A,P1C: active high; P1B,P1D: active high; DC1B 3; P1M single; CCP1CON = 0x3C; // CCP1ASE operating; PSS1BD0 low; PSS1AC0 low; CCP1AS0 disabled; ECCP1AS = 0x00; // P1RSEN automatic_restart; P1DC0 0; PWM1CON = 0x80; // STR1D P1D_to_port; STR1C P1C_to_port; STR1B P1B_to_port; STR1A P1A_to_CCP1M; STR1SYNC start_at_begin; PSTR1CON = 0x01; // CCPR1L 127; CCPR1L = 0x7F; // CCPR1H 0; CCPR1H = 0x00; // Selecting Timer2 CCPTMRS0bits.C1TSEL = 0x0; } This code has both magic numbers and an explanation. The reasons were as follows: Maximum performance. Slamming in a full value is always the fastest choice. Explain the choice with a comment. Magic values are fast and simple. The machine generated comment will be correct to the datasheet but will not convey "why". We can't know why so this is the next best thing. MCC is NOT a hardware abstraction layer but it does give a quick setup to many common functions. If you are really writing a HAL targeting your application, you will have more application defined interface functions and what happens inside the interface functions is very isolated. I would argue that if you tried to edit those functions WITHOUT the datasheet open, you must really know that part. ONe example is clearing an interrupt flag on a PIC16 is a simple as writing a 0 to the bit. Clearing the interrupt flag on an AVR requires writing a 1 to the bit. No amount of special macros and enumerations will make the code look "correct" to a both AVR freak and a PIChead. Each choice is correct for the architecture for sound technical reasons but you should not expect the HW behavior to be as obvious as which bit to select. So I would argue that even unsupported Magic Numbers in the hardware abstraction layer is perfectly acceptable. Please reference the datasheet chapter & verse so I can see what you were doing to the hardware. A bit of explanation documenting what the function is accomplishing and any clever bits in the HW you were taking advantage of would be very helpful. As a parting note, consider the CLC. There are LOTS of bits and the WHY is critical. Once you have drawn the schematic of the CLC circuit and decided how the bits are going to be set, just put the values in the right spots and don't worry about trying to create special macros. Good Luck. PS: Don't assume that I am 100% for magic numbers in the code. I just don't like avoiding them when they are the shortest, fastest code, and (once the datasheet is open) the easiest to debug.
  7. I have been told many times that we should avoid the use of magic numbers in our code. That is numbers like these: CMCON0 = 0x5d; for(x = 0; x < 29; x ++) { // do something clever with x } The rational is that we don't really know what 0x5D means when it is written to CMCON0 or why we should be stopping at 28 in the for loop. However if you made the code like this: #define CMCON0_PWM_CONFIGURATION <PWM_MAGIC_NUMBERS> // See Datasheet Chapter 3.4 on PWM configuration #define MAXIMUM_PLAYER_COUNT 29 CMCON0 = CMCON0_PWM_CONFIGURATION; for(x = 0; x < MAXIMUM_PLAYER_COUNT; x ++) { // do something with each player } Clearly this code is much more readable. However, there is a time when this practice is simply crazy. For example, If you only configure the PWM in a single place, you may not want to hunt around for the exact value placed in CMCON because you have the datasheet open in front of you so the right place for the exact value is right there on that line. Putting the value in one place to use in another place just makes more work. Or what about this example: // Found in a configuration header file #define ZERO_VALUE 0 #define ONE_VALUE 1 #define TWO_VALUE 2 #define THREE_VALUE 3 #define FOUR_VALUE 4 #define FIVE_VALUE 5 #define SIX_VALUE 6 #define SEVEN_VALUE 7 // Found later in the C code switch(device_number) { case ZERO_VALUE: <blah blah blah> break; case ONE_VALUE: <ETC> } I would LOVE a rational explanation on why doing this is little more than cargo culting a coding style. I have seen this sort of sillyness in many other places. // is this better? delay_ms(1000); // or this delay_ms(ONE_SECOND); // or perhaps this inline void delay_One_Second(void) { delay_ms(1000); } At the end of the day, I want to see numbers where they make sense. The MOST important reason for using the NO_MAGIC_NUMBER pattern is really NOT to clarify the numbers, but really to make the code DRY. For example consider this: #define MESSAGE_TIMEOUT 1000 // later delay_ms(MESSAGE_TIMEOUT); // or even better void delay_message_timeout(void); If you are going to use the MESSAGE_TIMEOUT in a large number of places and especially if you may be tweaking the value, then putting the number in one place is only sane. But the BEST way is to make the function because that is the MOST clear when you read the final code and it creates a very nice point of abstraction for any number of delay methods. The biggest argument FOR the anti-magic number pattern is really about documentation. In a simple way, the proponents of this pattern are trying to follow the "comments are a smell" pattern but usually without realizing it. In this way, a standin for the number can be used that makes the purpose clear. This is a powerful point and should be considered carefully. But, sometimes, the clearest value to put in the code is actually the MAGIC number. AND, if you don't understand that number, you have no business editing that part of the code. As always, be prudent and understand the needs of the programmers who will follow. That programmer is likely to be you. Good Luck.
  8. I am building an integrated audio interface for a Baofeng UV-5R hand-held radio. Primarily this is to get a packet radio APRS system up and running. Traditionally, this seems to be accomplished with a crazy collection of adapters and hand crafted interface cables. As I was looking for a better solution, I discovered the Teensy family of ARM microcontrollers. (actually Arduino high performance ARM's built on NXP Cortex M4's) The important part is actually the library support. Out of the box I was able to get a USB Audio device up and map the ADC and DAC to the input and output. This could all be configured with some simple "patch cord" wiring. So I created the "program" above. This combines the 2 audio channels from USB into a single DAC and also passes the data to an RMS block. This causes the audio to play on the DAC at 44.1khz. It also keeps a running RMS value available for your own code. More on this later. Next the ADC data is duplicated into both channels back through USB and on to the PC (Raspberry Pi). Again an RMS block is present here as well. Now pressing EXPORT produces this little block of "code" Which gets pasted into the Arduino IDE at the beginning of the program (before your setup & main. /* * A simple hardware test which receives audio on the A2 analog pin * and sends it to the PWM (pin 3) output and DAC (A14 pin) output. * * This example code is in the public domain. */ #include <Audio.h> #include <Wire.h> #include <SPI.h> #include <SD.h> #include <SerialFlash.h> #include <Audio.h> #include <Wire.h> #include <SPI.h> #include <SD.h> #include <SerialFlash.h> // GUItool: begin automatically generated code AudioInputUSB usb1; //xy=91,73.00001907348633 AudioInputAnalog adc1; //xy=153.00000381469727,215.00003242492676 AudioMixer4 mixer1; //xy=257.00000762939453,72.00002670288086 AudioAnalyzeRMS rms2; //xy=427.0000114440918,266.000036239624 AudioOutputUSB usb2; //xy=430.00001525878906,217.00003242492676 AudioOutputAnalog dac1; //xy=498.00009536743164,72.00002670288086 AudioAnalyzeRMS rms1; //xy=498.00001525878906,129.0000295639038 AudioConnection patchCord1(usb1, 0, mixer1, 0); AudioConnection patchCord2(usb1, 1, mixer1, 1); AudioConnection patchCord3(adc1, 0, usb2, 0); AudioConnection patchCord4(adc1, 0, usb2, 1); AudioConnection patchCord5(adc1, rms2); AudioConnection patchCord6(mixer1, dac1); AudioConnection patchCord7(mixer1, rms1); // GUItool: end automatically generated code const int LED = 13; void setup() { // Audio connections require memory to work. For more // detailed information, see the MemoryAndCpuUsage example AudioMemory(12); pinMode(LED,OUTPUT); } void loop() { // Do nothing here. The Audio flows automatically if(rms1.available()) { if(rms1.read() > 0.25) { digitalWrite(LED,HIGH); } else { digitalWrite(LED,LOW); } } // When AudioInputAnalog is running, analogRead() must NOT be used. } And Viola!. The PC sees this as an audio device and the LED blinks when the audio starts. PERFECT. Now, the LED will be replaced with the push to talk (PTT) circuit and the audio I/O will connect to the Baofeng through some filters. A single board interface to the radio from a Raspberry Pi that does not require 6 custom cables, and a 3 trips to E-BAY. Now I am waiting for my PCB's from dirtypcb.com This entire bit of work is for my radio system that is being installed on the side of my house. Here is the box: Inside is a Raspberry Pi 3, a Baofeng UV-5R for 2M work, 3 RTL dongles for receiving 1090MHz ADS-B, 978MHz ADS-B, 137MHz Satellite weather, GPS and 1 LoRaWAN 8 channel Gateway. I will write more about the configuration later if there is any interest. Good Luck.
  9. If I had a dollar for every time MPLAB puts a modal dialog box under the main window so everything is "frozen". I would retire. Put your rant below:
  10. It looks like you switched microcontrollers. I noticed the generated code was for an MSSP and now it is for an I2C peripheral. Which MCU are you using with the errors above?
  11. If you look at the MPLAB Code Configurator timeout driver you will find that each "timer" that you want to create has a structure similar to this: struct timeout { int (*callback)(void *argument); struct timeout *next; // other housekeeping things } And an API that takes such a structure. void timeout_addTimer(struct timeout *myTimer); struct timeout myTimer = {myCallback,0}; void main(void) { timeout_addTimer(&myTimer); } Note: the function names are similar in purpose to MCC but not identical. The function addTimer will accept the completely allocated timer, finish initializing the "private" data and insert this into the list of timers. The memory is "owned" and "provided" by the user but the driver will manage it. Of course this driver will also work with a heap if that is desired. void main(void) { struct timeout *myTimer = malloc(sizeof(struct timeout)); myTimer.callback = myCallback; timeout_addTimer(myTimer); // after some time has passed free(myTimer); } In both cases the application is 100% responsible for the memory and can do the most suitable thing for the circumstances. Just to be "complete" here are two other options: The stack: void main(void) { struct timeout myTimer = {myCallback,0}; timeout_addTimer(&myTimer); while(1) { // do my application } } // This is safe because myTimer will never go out of scope. // However, if you use this technique in a function that will exit, // the memory will go out of scope on the stack and there will be // a difficult to debug crash when the timer expires. Static void foo(void) { static struct timeout myTimer = {myCallback,0}; timeout_addTimer(&myTimer); } Now the timer will be in scope because the variable is static. Of course you may have a new problem that addTimer could be called with this timer already in the timer list. That may break the linked list if the list is not pre-scanned for preexisting timers. In EVERY case I do prefer static allocation that is the responsibility of the application. That assures the following: Code fails during compile/link time so it is easier to debug. Memory usage appears in the data segment so it is tracked by ALL compilers as RAM usage giving an accurate measure of RAM. The driver is scale able because it NEVER needs internally allocated memory nor does it need heap access. That said, I just started using the ESP32 and it's memory is fragmented. It has a block of RAM that is used for static variables, and the data segment. And it has a different larger block of RAM used for the heap. If you don't use the heap, you will have very little memory. Good heap hygiene should be a future topic for discussion.
  12. Sometimes I get the sad impression that embedded FW engineers only understand 1 data container, the array. The array is a powerful thing and there are many good reasons to use it but it also has some serious problems. For instance, a number of TCP/IP libraries use arrays to hold a static list of sockets. When you wish to create a new socket the library takes one of the unused sockets from the array and returns a reference (a pointer or index) so the socket can be used as a parameter for the rest of the API. It turns out that sockets are somewhat heavy things (use lots of memory) so you always want to optimize the system to have the smallest number of sockets necessary. Unfortunately, you must "pick" a reasonable number of sockets early in the development process. If you run out of sockets you must go back and recompile the library to utilize the new socket count. Now there is a dependency that is not obvious, only fails at run time and links the feature count of the product with the underlying library. You will see bugs like, "when I am using the app I no longer get notification e-mails". It turns out that this problem can be easily solved with a dynamic container. i.e. one that grows at runtime as you need it to. A brute force method would perhaps be to rely upon the heap to reallocate the array at runtime and simply give the library a pointer to an array. That will work but it inserts a heavy copy operation and the library has to be paused while the old array is migrated to the new array. I propose that you should consider a Linked List. I get a number of concerns from other engineers when I have made this suggestion so just hang tight just one moment. Concerns Allocating the memory requires the heap and my application cannot do that. Traversing the list is complicated and requires recursion. We cannot afford the stack space. A linked list library is a lot of code to solve this problem when a simple array can manage it. The linking pointers use more memory. If you have a new concern, post it below. I will talk about these first. Concern #1, Memory allocation I would argue that a heap is NOT required for a linked list. It is simply the way computer science often teaches the topic. Allocate a block of memory for the data. place the data in the block of memory. Data is often supplied as function parameters. insert the block into the list in the correct place. Computer science courses often teach linked lists and sorting algorithms at the same time so this process forms a powerful association. However, what if the process worked a little differently.j Library Code -> Define a suitable data structure for the user data. Include a pointer for the linked list. User Code -> Create a static instance of the library data structure. Fill it with data. User Code -> Pass a reference to the data structure to the library. Library Code -> insert the data structure into the linked list. If you follow this pattern, the user code can have as many sockets or timers or other widgets as it has memory for. The library will manage the list and operate on the elements. When you delete an element you are simply telling the library to forget but the memory is always owned by the user application. That fixes the data count dependency of the array. Concern #2, Traversing the list is complex and recursive. First, Recursion is always a choice. Just avoid it if that is a rule of your system. Every recursive algorithm can be converted to a loop. .Second, Traversing the list is not much different than an array. The pointer data type is larger so it does take a little longer. struct object_data { int mass; struct object_data *nextObject; }; int findTheMassOfTheObjects(struct object_data *objectList) { thisObject = objectList; while(thisObject) { totalMass += thisObject->mass; thisObject = thisObject->nextObject; } printf("The mass of all the objects is %d grams\n", totalMass); return totalMass; } So here is a quick example. It does have the potential of running across memory if the last object in the list does NOT point at NULL. So that is a potential pitfall. Concern #3, A linked list library is a lot of code Yes it is. Don't do that. A generic library can be done and is a great academic exercise but most of the time the additional next pointers and a few functions to insert and remove objects are sufficient. The "library" should be a collection of code snippets that your developers can copy and paste into the code. This will provide reuse but break the dependency on a common library allowing data types to change, or modifications to be made. Concern #4, A linked list will use more memory It is true that the linked list adds a pointer element to the container data structure. However, this additional memory is probably much smaller than the "just in case" additional memory of unused array elements. It is probably also MUCH better than going back and recompiling an underlying library late in the program and adding a lot more memory so the last bug will not happen again. A little history The linked list was invented by Allen Newell, Cliff Shaw and Herbert Simon. These men were developing IPL (Information Processing Language) and decided that lists were the most suitable solution for containers for IPL. They were eventually awarded a Turing Award for making basic contributions to AI, Psychology of Human Cognition and list processing. Interestingly IPL was developed for a computer called JOHNIAC which had a grand total of 16 kbytes of RAM. Even with only 16KB IPL was very successful and linked lists were determined to be the most suitable design for that problem set. Most of our modern microcontrollers have many times that memory and we are falling back on arrays more and more often. If you are going to insist on an array where a linked list is a better choice, you can rest easy knowing that CACHE memory works MUCH better with arrays simply because you can guarantee that all the data is in close proximity so the entire array is likely living in the cache. Good Luck P.S. - The timeout driver and the TCP library from Microchip both run on 8-bit machines with less than 4KB of RAM and they both use linked lists. Check out the code in MCC for details.
  13. One important rule for good software is Do not Repeat Yourself. This rule is often referred to under the acronym DRY. Most of the time, your code can be made DRY by refactoring repeating blocks of code into a function. This is a good practice but it can lead to a lot of small functions. You undoubtedly will keep your API's tidy by making these functions static and keeping them close to where they are needed but I have recently been working on a lot of C++ code and I have a new tool in my programming toolbox that I would like to share with you. The LAMBDA function. Essentially a lambda is a function that you define inside of another function. This lambda function has function scope so it can only be used in the function that defines it. This can be very useful and will do two things to help keep your code maintainable. It keeps your code readable by forcing you to define a function close to where it is needed. It encourages you to keep the function short & sweet because you will not be tempted to make it a "general purpose solution". Here is an example. I was tasked to implement a serializer where data would be sent on the serial port. This was a binary protocol and it included a special character for start of frame (SOF) and end of frame (EOF). Because the SOF and EOF characters could appear in the actual data, there was an additional data link escape (DLC) character sequence that would expand into the SOF, EOF and DLC. For added fun, there is a checksum that is generated BEFORE the character padding. <SOF><DATA><CHECKSUM><EOF> Here is a function that can decode this message. #define MAXIMUM_MESSAGE_SIZE 255 void dataLink::receiveData(char b) { const char SOF = 0x1A; const char EOF = 0x1B; const char DLC = 0x1C; static enum {findSOF, getData} theState; static int messageIndex=0; static char checksum = 0; static char receivedMessage[MAXIMUM_MESSAGE_SIZE]; switch(theState) { case findSOF: if(b == SOF) { theState = getData; messageIndex = 0; checksum = 0; memset(receivedMessage,0,sizeof(receivedMessage)); } break; case getData: { static bool dlc_last = false; if(dlc_last) { dlc_last = false; switch(b) { case 1: receivedMessage[messageIndex++] = 0x1A; checksum += 0x1A; break; case 2: receivedMessage[messageIndex++] = 0x1B; checksum += 0x1B; break; case 3: receivedMessage[messageIndex++] = 0x1C; checksum += 0x1C; break; } } else { switch(b) { case EOF: theState = findSOF; if(checksum == 0) { //********************* // Do something with the new message //********************* } break; case DLC: dlc_last = true; break; default: receivedMessage[messageIndex++] = b; checksum += b; break; } } break; } } } This function receives a byte and using a few states, creates a checksum validated array of decoded bytes representing the message. I will not explain each line as the details of this function are really not very important. As my code reviewer you should instantly notice that there are 4 sections of nearly identical code that are repeated. In other words, this is not DRY. My first inclination would be to attempt to reorder the decisions so the update of the message array and checksum was done once. This method of course works quite well in this case but I wanted a simple contrived example to show off the lambda function. #define MAXIMUM_MESSAGE_SIZE 255 void dataLink::receiveData(char b) { const char SOF = 0x1A; const char EOF = 0x1B; const char DLC = 0x1C; static enum {findSOF, getData} theState; static int messageIndex=0; static char checksum = 0; static char receivedMessage[MAXIMUM_MESSAGE_SIZE]; auto output_byte = [&](char b) { receivedMessage[messageIndex++] = b; checksum += b; }; switch(theState) { case findSOF: if(b == SOF) { theState = getData; messageIndex = 0; checksum = 0; memset(receivedMessage,0,sizeof(receivedMessage)); } break; case getData: { static bool dlc_last = false; switch(b) { case EOF: theState = findSOF; if(checksum == 0) { //********************* // Do something with the new message //********************* } break; case DLC: dlc_last = true; break; default: if(dlc_last) { dlc_last = false; switch(b) case 1: output_byte(0x1A); break; case 2: output_byte(0x1B); break; case 3: output_byte(0x1C); break; } else { output_byte(b); } break; } break; } } } Now you can see that I moved the work of inserting the byte into the array and updating the checksum into a function called output_byte. This function is defined inside the receiveData function. The syntax has a square bracket followed by parenthesis. The Ampersand inside the brackets indicates that the function has access to all the variables inside receiveData. This makes the function very simple and easy to verify by inspection. Of course you could have made the output_byte function a private member function of the class. But you would have needed to add the checksum and the index variables to the class as well. That increases the complexity of the class. By using the lambda, the function can be made DRY, and readable, and the function does not leak information or variables into any other code. This makes the function much simpler to maintain or potentially refactor in the future. BTW, I tested this function by building the project in the Arduino environment on a SAMD21. The actual Arduino IDE is very limited but when you use VIsual Studio Code and PlatformIO you get full debug on an ATMEL ICE with the Arduino frameworks. This makes developing interesting applications VERY fast. lambda_demo.zip
  14. N9WXU

    How to abstract

    I have seen lots of code that is tightly tied to specific hardware or to specific frameworks. This code is OK because it generally satisfies rule #1 (it must work) but as soon as the HW or framework changes this code becomes very difficult to adapt to the new system. Developers often state that they are abstracted from the hardware by the framework but this is generally never the case because the framework was provided by the hardware vendor. So what is a developer to do? Step #1 Ask the right question. Instead of asking HOW do I do a thing (how do I send bytes over the UART). The developer should ask WHAT do I need to do. Ideally the developer should answer this WHAT question at a pretty high level. WHAT do I need to do? I need to send a packet over RS485. Step #2 Define an API that satisfies the answers to the WHAT questions. If I must send a packet over RS485, then perhaps I need a SendPacket(myPacket) function. In the context of my application this function will be 100% clear to my partner developers. Step #3 Implement a trial of my new API that runs on my PC. This is sufficiently abstract that running my application on my development PC should be trivial. I can access a file, or the network, or the COM ports, or the STDIO and still satisfy the API. Get my partners to kick it around a bit. Repeat #1,#2 & #3 until the API is as clear as possible for THIS application. Step #4 Implement the new API on my HW or framework. This may seem like contributing to Lasagna code.... i.e. just another layer. But in fact this is the true definition of the hardware abstraction layer. ALL details of the HW (or framework) that are not required for THIS application are hidden away and no longer contribute to developer confusion. 100% of what is left is EXACTLY what your application needs. Now you have a chance at producing that mythical self documenting code. You will also find that unit testing the business logic can be more easily accomplished because you will MOCK all functions at this new API layer. Hardware NEVER has to be involved. Good Luck.
  15. I have been working on "STEM" activities with kids of all ages for quite some time. Usually it is with my own kids but often it has been with kids at schools or in the neighborhood. This activity has been very rewarding but there are a few challenges that can quickly make the experience less interesting for the kids and an exercise in frustration for you, the mentor. 1) Don't be spontaneous (but fake it well) - My daughter and I wired a display to a nano and wrote the code to count 0 to 9. This was a perfect bite sized project because I was able to write enough 7-segment abstraction (struct digit { int a:1; int b:1; etc...}; ) to quickly stick a number on the display and I left enough missing code to have her "help" by identifying which segments needed to be active to draw each number. This was a ton of fun and she was suitably engaged. However, on previous occasions we took on too much and the "library" that needed to be thrown together to bring the complexity into reach by a 7 year old was more that I could deliver inside her attention span. So you do need to be prepared for when the kids are motivated to play with electronics... but some of that preparedness might be a stock of ready to go code modules that you can tap into service. 2) Be Prepared with stuff. - I like to keep a pretty well stocked assortment of parts, tools and ingredients for many projects. With prices for components so cheap, I always buy a few extra's for the stock pile to enable a kid with a sudden itch to do something cool. Unfortunately, there are often a few unintended hurdles. For example: I have a large collection of 7-segment displays and a small pile of Arduino Nano's. 3) 3D printers are fun and interesting.... but laser cutters are better and scissors are best. We all like to show off the amazing things you can do with a 3d printer and I have 3 of them. Unfortunately, using a printer requires a few things. a) patience, b) learning to 3d model, c) patience. My kids are quite good at alerting me when my print has turned into a ball of yarn. But none of the kids has developed any interest in 3d modeling for the 3d printer. I also have a fairly large laser cutter. This is FAR more fun and the absolute best tool I have put into my garage. My laster cutter is 130W and cuts about 1.5meters x 1meter or so. We have cut the usual plywood and acrylic. We also cut gingerbread, fabric, paper, and cardboard. (Laser cut gingerbread houses taste bad) I can convert a pencil sketch into a DXF file in a few minutes....BUT the scissors are better for that quick and dirty experiment. which leads to.... 4) Fail Fast and with ZERO effort.... Kids HATE TO WASTE THEIR TIME. Of course what they consider wasted time and what you and I consider wasted time is a different discussion. For example: folding laundry is a waste of time because you will just unfold the laundry to wear the clothes. So it is better to jam everything under the bed. Taking 2 hours to design a 3d model for the laser cutter or 3d printer is a waste of time if the parts don't work when you are done. However, if you can quickly cut something out of cardboard with scissors or a knife, then the time cost is minimal and if it doesn't work out, they are not sad. I have often had a sad kid after an experiment that took a large amount of effort. I thought the experiment worked well and we all learned something but the "wasted effort" was a problem. I have also seen grownups ride a project down in flames because it was "too big to fail" or "we will have wasted all that money if we quit now".. This is the same problem on a grand scale as the kids. So teach them to fail fast and learn from each iteration. As the project develops, the cool tools will be used but not in the first pass. 5) Pick your battles. Guide your charges with small projects that can be "completed" in 30 minutes or so. DO NOT nag them to "finish" if it is not done on the first outing. If the kid finds that project fun, they will hound you to work on it with them. As they develop skills, they will work on parts themselves while you are not around. (watch out for super glue and soldering irons). This is the ideal situation. So you need to do teasers and have fun. They will come back to the fun projects and steer clear of boring ones. So what has worked for me? 1) Rockets. I have bought 12 packs of rockets as classroom kits. I keep a few on stock pile. Once you have a field to fly them you can always get an entire group of kids ready to fly small rockets in an hour or so and they are fun for all ages. 2) Paper Airplanes. Adults and kids can easily burn an afternoon with paper airplanes. Kids by themselves will quickly tire of it so teach them to fold a basic airplane, how to throw and add a little competition. Don't forget to include spectacular crashes as a competition goal because that will keep their spirits up when problems occur. 3) VEX Robotics. I have done FIRST robots, Lego League and VEX robotics. My favorite is VEX IQ because the budget can be reached by a small group of families and the field fits on the back porch. I did have to bribe one daughter who was doing the code with cookies. This started a tradition of "cookies and code". Each task completed earns a cookie. Each bug fixed is a cookie. The rewards are fantastic! 4) Robotics at Home. Robotics are good for kids because they incorporate so many aspects of engineering (Mechanical, Electrical, Software) into one package. You can easily fill in any of these elements while the child explores their interest. One of my daughters likes to build robots. Another likes to program them. I simply remove any technical obstacles, hopefully before they notice them coming. This allows them to keep living in the moment and solving the problems at their level. 5) SCIENCE!. Be prepared to take ANY question they have and turn it into a project. We launched baking soda & vinegar rockets. I did 3d print them so I had to plan ahead. We have also recreated Galileo's gravity experiments in our stairwell. We recorded the difference in the impact of different objects by connecting a microphone to an oscilloscope. We placed the microphone under a piece of wood so the objects would make a sharp noise. We then spent the time trying to release objects at exactly the same time. We used a lever to lift a car!. The child was 5. The lever was a 3 meter steel tube. The car was a small Jeep. We did not lift it very far and we bent the lever but the lesson was learned and will never be forgotten. 6) Change the Oil! Or any other advanced chore. Involve the child in activities that are beyond them but don't leave them stranded. I make sure my new drivers have changed the oil and a tire. I try to involve the younger kids just because they will be underfoot anyway. A child with engineering interests will be make their desires known. In the end you are providing an enriching experience for a child. Keep the experience short & sweet. The objective is to walk away THIS happy. If the experience is positive the child will come back for more. A future lesson can teach ohms law, or software abstraction. The first experiences are just to have fun and do something interesting. Please share your kid project ideas! Include pictures! Good Luck
×
×
  • Create New...