Autodidact Ambitions 5 – Dive Deep

by | May 21, 2024 | LifeSkills, Pastimes, Technology | 95 comments

So, I’ve gotten a clock wired up with buttons and a switch so the user can set the time. I still have to solder up a final version of the hardware, but the software has been sorted. Time to dive into how that works. This article will unavoidably be technical, as it’s a dissection of C code for a microcontroller. I will try to explain what everything does and why it was done that way.

First, to get everyone’s eyes glazed over – the Code:

#include <msp430.h>
#include <intrinsics.h>

#define SER_OUT BIT0
#define SR_CLK  BIT1
#define R_CLK   BIT2

volatile int outTime[4] = {0,0,1,0};
volatile int seconds = 0;

void update7(void){
    unsigned int digit;
    unsigned int i;
    /*
     * Control Bits:
     * output order
     * 1 : on or off
     * 2-5 Digit to show
     * 6-12 segments of display
     */
    for(digit=0; digit<4; digit++){
        //right to left
        //colon = seconds & BIT0 - SER_OUt is BIT0  Set P1OUT bit0 to BIT0 of seconds
        if(seconds & SER_OUT){
            P1OUT |= SER_OUT;
        }else{
            P1OUT &= ~SER_OUT;
        }
        P1OUT |= SR_CLK;
        P1OUT &= ~SR_CLK;

        for(i=0; i<4; i++){
            if (i==digit){
                //P1OUT &= ~SER_OUT;
                P1OUT |= SER_OUT;
            }else{
                //P1OUT |= SER_OUT;
                P1OUT &= ~SER_OUT;
            }
            P1OUT |= SR_CLK;
            P1OUT &= ~SR_CLK;
        }

        int curBit = 0x01;

        unsigned int digMask[10] = { 0x77, 0x11, 0x6b, 0x3b, 0x1d, 0x3e, 0x7e, 0x13, 0x7f, 0x3f};
        for(i=0; i<7; i++){
            //bits
            if ((digMask[outTime[digit]] & curBit) == 0){
                P1OUT &= ~SER_OUT;
            }else{
                P1OUT |= SER_OUT;
            }
            curBit *= 2;
            P1OUT |= SR_CLK;
            P1OUT &= ~SR_CLK;
        }

        //cycle serial output to put a 0 in the unused bit of the shift register.
        P1OUT &= ~SER_OUT;
        //P1OUT ^= SR_CLK;
        P1OUT |= SR_CLK;
        P1OUT &= ~SR_CLK;
        //don't hit the R_CLK until all twelve bits are pushed
        P1OUT |= R_CLK;
        P1OUT &= ~R_CLK;
    }
}

void tick(void){
    if(seconds > 59){
        seconds=0;
        outTime[0]++;
    }
    if(outTime[0]>=10){
        outTime[0]=0;
        outTime[1]++;
    }
    if (outTime[1] >= 6){
        outTime[2]++;
        outTime[1] = 0;
    }
    if (outTime[2] >= 10){
        outTime[3]++;
        outTime[2]=0;
    }
    if((outTime[3] * 10) + outTime[2] > 12){
        outTime[2]=1;
        outTime[3]=0;
    }
}

int debounce(int pinno){
    //
    int debo=0x00;
    int d=0;
    for(d=100;d>0;d--){
        debo += P1IN & pinno;
    }
    return (debo);
}

/**
 * main.c
 */
int main(void)
{
    WDTCTL = WDTPW | WDTHOLD;   // stop watchdog timer
    BCSCTL3 |= XCAP_3; //set clock caps to 12.5pF
    TACCTL0=CCIE; //Enable timer interrupts.
    TACCR0=4096; //Counter target for timer to get 1s/interrupt
    /*
     * MC_1 - Count up to TACCR0
     * ID_3 - Divide raw timer by 8
     * TASSEL_1 - Use external clock (ACLK)
     * TACLR - clear interrupt bit so no current active interrupt
     * */
    TACTL = MC_1|ID_3|TASSEL_1|TACLR;
    //Clock starts
    //Setup of clock complete

    //Input/Output configuration
    P1DIR = 0x07;    //Set P1.0-P1.2 & P1.6 to output
    P1REN = 0x38;    //Activate pull up/down resistors on P1.3-P1.5
    P1OUT = 0x00;    //set all to 0
    P1IE |= 0x38;    //Enable interrupts for P1.3 & P1.4
    P1IES = 0x00;    //Trigger input interrupts on rising edge signal
    __enable_interrupt();

    while(1){
       update7();
    }
}

#pragma vector=TIMER0_A0_VECTOR;
__interrupt void TA0_ISR (void){
    seconds++;
    tick();
}
#pragma vector=PORT1_VECTOR;
//__interrupt void button_Press(void){
__interrupt void Port_1(void){
    if (P1IN & 0x20){
            if (debounce(0x08) >= 50){
                outTime[0]++;
            }
            if (debounce(0x10) >= 50){
                outTime[2]++;
            }
        tick();
    }
    P1IFG = 0x00;
}

So, what does this all mean?

The first two lines:

#include <msp430.h>
#include <intrinsics.h>

This includes a pair of header files. Header files include a lot of definitions for functions and variables, they prevent us from having to reinvent the wheel, especially when it comes to hardware specific elements. These two were delivered by Texas Instruments and contain the definitions required to successfully use the chip without having to tear your hair out. I could have been more efficient if, instead of msp430.h I’d picked the header for the specific chip I was using. msp430.h includes all of the chip-specific header files, but the compiler throws away unused material anyway, so I left it in a state which would let me test with whichever chip I had on hand at the time. intrinsics.h includes information required for processing interrupts, which are key to the function of the clock. I actually used the contents of these header files as reference material when trying to get the rest of the code to work. The comments might not be verbose, but they contained the information I needed that the internet failed to deliver.

#define SER_OUT BIT0
#define SR_CLK BIT1
#define R_CLK BIT2

volatile int outTime[4] = {0,0,1,0};
volatile int seconds = 0;

Here we are setting up some things that will be used throughout the program. By being here, it lets the compiler know any other part of the program can make use of it. BIT0, BIT1, and BIT2 are defined in the msp430.h header. They’re constants corresponding to the hex value of positions within a byte. I could use 0x01, 0x02 and 0x04, which are the hexidecimal values of having a solitary 1 in the lowest, second lowest, or third lowest bit respectively, but this is more human readable. SER_OUT, SR_CLK and R_CLK are human readable versions I’ve just defined here so that I can reference the output pins for the serial output, the shift register clock and the register clock on the two shift registers controlling the display. Rather than go over the shift registers again, I’ll refer the curious to the article I put together where I picked apart that type of chip.

The last two lines are defining variables. C does not like using variables before they’ve been defined. As all of the program needs to work against these two, I’m defining them here. The integer ‘seconds’ is self-explanatory, it’s a counter for the number of seconds. The ‘volatile’ keyword tells the compiler that this variable might be changed by outside forces, in particular hardware. If we left this off, the compiler will see that nothing in the main program changes the value, and replace it with a hardware constant. Hardware constants are faster than reading from memory, so the optimizer inside the compiler leans on them to make the program footprint as lean as possible. It, however, breaks the code logic in this case.

‘outTime[4] = {0,0,1,0}’ represents a data structure known as an array. It’s a set of variables referenced by the same name and a number. Our array holes the individual digits to be displayed on the output. To save space and not have to keep splitting up the hours and minutes into indivdual digits, I opted to keep track of the time in the output array. The digits as listed are opposite how they show up on the output. The default starting time is 1:00.

void update7(void){
unsigned int digit;
unsigned int i;

Here we’ve started defining functions. These are chunks of code that can get called and executed from other parts of the program. ‘update7’ is the function that refreshes the output display. Here we define two more variables, both of the basic integer type. You may note the ‘unsigned’ keyword. This indicates that the variable will never hold negative numbers. This does give more space for higher values, since the bit that would otherwise be used to indicate a positive or negative number is instead used to hold data. It’s not strictly needed, as the values these variables go through are so small that we never get close to the upper limit for a signed integer. You may note that I didn’t set a starting value for digit or i. That is because these are used in loops, and set to a starting value when needed.

/*
* Control Bits:
* output order
* 1 : on or off
* 2-5 Digit to show
* 6-12 segments of display
*/

This is a comment. There are two ways to indicate a comment in C either bracket it with /* and */, allowing for multiple lines of comments, or indicate the start of the comment with //, which indicates the remainder of the line is a comment. The compiler discards comments, but they are essential to reading code. This particular one was to remind me what order the bits need to be sent to the output.

    for(digit=0; digit<4; digit++){
        //right to left
        //colon = seconds & BIT0 - SER_OUt is BIT0  Set P1OUT bit0 to BIT0 of seconds
        if(seconds & SER_OUT){
            P1OUT |= SER_OUT;
        }else{
            P1OUT &= ~SER_OUT;
        }
        P1OUT |= SR_CLK;
        P1OUT &= ~SR_CLK;

Here we start a loop, this for loop will contain the bulk of the logic, as we are going to repeat the same steps for each of the four digits of the display. We start by setting the variable digit to zero, indicating that we want to run the loop as long as digit is below 4, and that at the end of each loop, we increment digit by 1.

This if statement includes a number of important items that show up over and over in the program. The conditional in the if is doing a bitwise AND comparison with SER_OUT. This is actually a deceptive use of the SER_OUT shorthand. What we’re doing is checking whether the lowest value bit in seconds is a 0 or 1. This is because we want the colon on the display to blink, turning on or off once a second. I don’t really care if it’s off on an even or odd second, so long as it alternates.

P1OUT is a byte defined in the msp430.h header which represents a hardware register on the chip where the first eight output pins can be set low or high. Since each pin only has two values, you only need one bit to set it. Bit zero on P1OUT is attached to the first output pin, which we are using for serial output to the shift registers. So, we want to set the value on that pin. The operators |= and &= are “or equals” and “and equals” which changes specific bits in the value on the left based upon the expression on the right. The expression can get complex, but we’re keeping it simple. We want to set the lowest bit to a specific value. In the |= operation, the bits will be set to a 1 if either the bit is already a 1 OR if the corresponding value on the right is a 1. Since SER_OUT is defined as 1, ‘P1OUT |= SER_OUT’ always sets the lowest bit on P1OUT 1 one, bringing the first pin the a high voltage. (High being 3.3V, since this is a low-power device) In the &= operation, the bits will be set to one if and only if both the bit is already a 1 AND the value on the right is a 1. The ~ is a negation of SER_OUT, so the value is a 0. Thus ‘P1OUT &= ~SER_OUT’ always sets the bit to 0 and thus brings the pin to a low voltage (close to 0V).

On the last two lines, where we repeat these operations only using the SR_CLK constant quickly toggles the second bit on then off again. This is because the shift registers only respond when there is a transition from low to high on their clock pins. With that quick toggle, we’ve loaded that bit into the shift register.

        for(i=0; i<4; i++){
            if (i==digit){
                //P1OUT &= ~SER_OUT;
                P1OUT |= SER_OUT;
            }else{
                //P1OUT |= SER_OUT;
                P1OUT &= ~SER_OUT;
            }
            P1OUT |= SR_CLK;
            P1OUT &= ~SR_CLK;
        }

This short loop piles four more bits into the shift register. Specifically, these four bits will end up controlling which digit of the display we are controlling at this moment. Since the four digits will have independent values displayed on them, we want to manage one at a time. You will note that I commented out two lines, these are the way I’d originally written the ones the followed each. I had initially misunderstood how the TPIC6B595 chip worked with regards to controlling the cathode pins on the display. I’d taken the way I’d wired it to result in an active low situation, but this proved incorrect, so I swapped the values.

        int curBit = 0x01;

        unsigned int digMask[10] = { 0x77, 0x11, 0x6b, 0x3b, 0x1d, 0x3e, 0x7e, 0x13, 0x7f, 0x3f};

What are these magic numbers?

Well, curBit is going to be used in some bitwise math in a moment, and we want to start at 1. As for the array of gibberish, this is needed to translate a binary number from an integer in C to a set of segments that results in a human readable output on a seven segment display. Each of the display segments is controlled from a different pin on the module, which in turn is wired to its own pin on the shift register. Because there is no standardized wiring, I ended up with my own map of which order the segments appear within the byte of data we are composing. Those hex values in the digMask array represent the specific pins wired up to the display segments which result in a digit of 0 through 9. If you wired up the pins differently, you would need a different set up values here to represent the association of pins to segments for your digits.

        for(i=0; i<7; i++){
            //bits
            if ((digMask[outTime[digit]] & curBit) == 0){
                P1OUT &= ~SER_OUT;
            }else{
                P1OUT |= SER_OUT;
            }
            curBit *= 2;
            P1OUT |= SR_CLK;
            P1OUT &= ~SR_CLK;
        }

This loop shoves seven bits into the shift register corresponding to the current value in the outTime array for the digit we’re displaying as translated through the digMask array. the line ‘curBit *= 2’ multiplies curBit by 2 each time we go through this loop. In bit math, that simply marches the 1 up the byte. Now, I could also have done a shift command of ‘curBit << 1’ where I shift the contents of the variable up a bit. These are logically equivalent. We then wrap up the update7 function with these few lines of code.

//cycle serial output to put a 0 in the unused bit of the shift register.
P1OUT &= ~SER_OUT;
P1OUT |= SR_CLK;
P1OUT &= ~SR_CLK;
//don’t hit the R_CLK until all twelve bits are pushed
P1OUT |= R_CLK;
P1OUT &= ~R_CLK;

We pad the shift register contents with a 0 because I left one pin on the first chip unused. That neatly separated the anode connections controlling the individual segments onto the SN74HC595 chip, while the higher voltage cathode connections were on the higher voltage tolerant TPIC6B595.

Lastly, we cycle the R_CLK, moving the data we just put on the shift registers over to the output side, lighting up the new digit. We close out update7 with a few curly brackets to close the loop and the function, then move on.

void tick(void){
    if(seconds > 59){
        seconds=0;
        outTime[0]++;
    }
    if(outTime[0]>=10){
        outTime[0]=0;
        outTime[1]++;
    }
    if (outTime[1] >= 6){
        outTime[2]++;
        outTime[1] = 0;
    }
    if (outTime[2] >= 10){
        outTime[3]++;
        outTime[2]=0;
    }
    if((outTime[3] * 10) + outTime[2] > 12){
        outTime[2]=1;
        outTime[3]=0;
    }
}

All tick does is update the timekeeping variables and process rollover from seconds to minutes to tens of minutes to hours to tens of hours. This has been separated into its own function because it gets called by anything that messes with any of those values.

I’m going to skip the debounce function and return to it when we are talking about handing button presses. Disconnected from that discussion, it wouldn’t make a lot of sense. It is here in the code because functions need to be defined before they are used, so they go in front of the main function.

This article turned out to be a monster, so I’m going to split it here. The next installment will get into the main function and the interrupt handling. It will get even more into the weeds, since this was just C code and that has to deal with the real world.

About The Author

UnCivilServant

UnCivilServant

A premature curmudgeon and IT drone at a government agency with a well known dislike of many things popular among the Commentariat. Also fails at shilling Books

95 Comments

  1. Richard

    That’s pretty straight forward. If I was hiring programmers I’d make you an offer. Your debouncing technique is not one I’ve seen before. After a transition I usually wait a brief time to see if the level is still good.

    • UnCivilServant

      I tried that but the code for the additional timer ran me over the space available in the flash storage.

      • Unreconstructed

        Did you consider a hardware debounce? Usually not done in manufacturing because of the extra component cost, but for small scale or such, it’s still viable.

      • Unreconstructed

        Oops – didn’t mean to read ahead!

  2. CPRM
    • Sean

      I was thinking the same thing.

    • rhywun

      Exactly!

    • Richard

      I can only assume that the code left you speechless.

    • Certified Public Asshat

      I understood this at least.

      • R C Dean

        My thought exactly.

        I confess, I mostly just scrolled down to the comments on this one. I need a Mariko to translate this kind of thing for me.

  3. The Late P Brooks

    The clock in the illustration should read 11:59, in honor of the Doomsday Clock.

    • UnCivilServant

      But it was 6:00 at the time.

  4. Richard

    If anyone finds UCS’ code irresistible, I’ve got a bunch of MSP430 starter kits like this:

    https://www.ebay.com/itm/276383115922

    That I’d be happy to distribute. Please apply to TPTB for my e-mail address.

    • Yusef drives a Kia

      Will do, my iron is bored,
      Thankee

      • Richard

        If you have an e-mail address you’re willing share we could short-circuit the TPTB step. My e-mail address is myrealfirstname@myreallastname.com so I’m hesitant mentioning it here.

  5. The Late P Brooks

    I was flailing my way through a required computer class, long ago. At one point, I was talking to the professor about how deeply mystifying the simplest programming was, and I said, “I can’t read music, either.” He said he’d never heard anybody put it that way before, but it made sense. I passed, but not by much.

    • UnCivilServant

      The difference is written music doesn’t make any sense, and program code is fairly easy.

      • Mojeaux

        Written music is a language you learn like any other. If you like, you can compare it to math, but I prefer to see it as a language.

        If you can learn to read code, you can learn to read music if you want to. It only doesn’t make sense because you don’t know the language.

      • UnCivilServant

        I also can’t tell the difference between one note and another when I hear it.

        Play me a pair of notes and I’ll be lucky to tell that they’re not the same.

      • Mojeaux

        You don’t have to know one note from another by ear. You have to be able to correspond the note on the paper to the finger position on the instrument while listening carefully. That’s how your ear learns.

        Now, if you’ve given it a good try and you still can’t do it, then that’s a different kettle of fish.

      • The Other Kevin

        I can verify that about music. I took piano lessons and had music class as a kid, and I remember some of it. But for a while I was in a church choir, and I would tap out the timing of the notes as we sang, and that’s when I really got it. I had a hard time choosing what note to sing, but I matched the guy next to me (who had great pitch) and that worked.

      • Mojeaux

        Funnily enough, I have been thinking about picking up the piano where I left off 30 years ago or so, but keyboards don’t have the same touch (they feel more like organs) and I don’t want an actual piano. I could get one for free, but they are expensive to move and maintain.

      • Mojeaux

        Sensei, very cute. The opening sounded like a hammered dulcimer.

      • Sensei

        Mojeaux – I played dulcimer as a kid. Weird instrument as the first strings are doubled. Gives you a bit of drone sound.

        I assumed it was a harpsichord. I’m assuming it’s electronic, but no idea. I’ll have to listen again when I’m not at work.

      • Mojeaux

        I LOVE hammered dulcimer and that is definitely not a harpsichord. It may be a strummed dulcimer, though.

      • Mojeaux

        UCS, the spoken word has a rhythm. It has timing, expression, fast runs and slow drawls. It has tone and pitch. When one writes dialogue, one uses punctuation (and in my case, lots of italics) to convey all this plus the emotion behind the words. Language is music and written language has its notation to convey that.

      • Gender Traitor

        Mojeaux, I’m virtually certain there are keyboards with weighted keys for a piano-like feel, but they’re probably pricier. Can inquire with resident expert if you’re interested.

      • Mojeaux

        GT, I would love a rec, thanks! Right now my discretionary funds are pointed at an air fryer for my husband for father’s day. He has been salivating after an air fryer the way all the dudes here salivate over their sous vides and smokers. However, I am open to saving up for a keyboard that has the right touch.

        No, I don’t like playing the organ. I can do it. (Bach Toccata and Fugue in D minor anyone?) But it’s just not my bag, so I don’t want a “piano” keyboard that feels like an organ.

      • Nephilium

        Mojeaux:

        There may be an unused keyboard sitting around the house here. I’ll just need to check with the other half.

      • Mojeaux

        There may be an unused keyboard sitting around the house here. I’ll just need to check with the other half.

        That would be lovely! Thank you!

      • Bobarian LMD

        (they feel more like organs)

        Nobody wants to touch that one? That ball is sitting right there on the tee.

    • Nephilium

      As it was taught to me at a young age, programming a computer is breaking down a job into the smallest steps possible and telling the computer to do them. The syntax changes between languages, and what those smallest steps are will change between languages, but it’s all about very simple directions building to allow complex things to happen.

      • The Other Kevin

        Yep. I was hired for my current job without experience in the programming languages they used. My hiring manager told me if you are experienced with other languages, you should be able to pick up other ones fairly quickly. Which turned out to be the case.

      • Nephilium

        The Other Kevin:

        It’s like that with quite a few systems. I’ve worked on at least five different types of phone systems for call centers, and while they’ll all have their own terms and tricks. But at the end of the day it’s still call comes in, goes through script, waits in queue, gets to agent.

      • Sensei

        gets to agent dropped.

      • Nephilium

        Sensei:

        Sorry, it would have been better to say “contact gets handled”. That can be dumping a call, getting it to an agent, getting it to a VM box, etc. It’s only my issue when it doesn’t do what it should in the script (it usually is doing what the script says, but the people who made the script didn’t think everything through).

    • Sensei

      It’s interesting as my skill in both coding and music is decidedly “mid”. I know enough to know just how much better those with talent are.

      That said I was one of the last to likely formerly be taught COBOL. At the time I took the course I was told it was going to be obsolete within just a few years…

      • UnCivilServant

        I took COBOL as an elective.

        the knowledge has gone dormant, since I haven’t used it in a while

      • Yusef drives a Kia

        You mean like BASIC?
        / both are still in use

      • Sensei

        I haven’t used it since college. Nor the Fortran 77 I took either. Nor the Pascal…

        I was a business double major which ate up most of my business school classes. C and C# were just out and I couldn’t fit the classes into my class load.

        My only coding now is rudimentary Python and MS VB. I’m a hack at best.

      • Mojeaux

        My dad, who was not quite a luddite (wanted to use a computer and was fascinated by them and had great ideas for their application, but wasn’t sure how they worked and never used one himself [my coder brother was his proxy]) suggested that I take STENOGRAPHY in the NINETIES when I had been transcribing from tapes for years by that time. I do not know WTF he was thinking. I did it, but damn.

      • rhywun

        Pascal (in its Delphi form) is still one of the most widely used languages out there (#11 on the Tiobe list for those who are familiar with that).

        FORTRAN is #10. COBOL #20.

        There are a lot of hot new things out there that get less attention from the search engines.

  6. Yusef drives a Kia

    Sheet music is like language in that it shows emotion and tempo as well as notes. The 12 tone system however is pure math, much easier to learn and define.

  7. Yusef drives a Kia

    Cool article by the way,
    Thanks UCS

  8. The Late P Brooks

    Exorbitant rapacity

    In January, Musk posted on X that he’s “uncomfortable growing Tesla to be a leader in AI & robotics without having ~25% voting control.” He assured everyone that he wants enough shares to be “influential but not so much that I can’t be overturned.”

    Musk’s compensation deal is currently valued at around $46 billion.

    Everybody focuses on the monetary value, but at some point it becomes meaningless. I’m willing to believe him when he says the votes are what what he wants.

    I firmly believe founders should be treated differently than hired gun professional managers. If they replaced Musk with some random jackass Ford castoff, compensation should be a pittance, all in cash, and the board of directors should ride him like a rented mule.

  9. Unreconstructed

    I will say that the part that (initially) confused me as I was skimming the code was here:

    if(seconds & SER_OUT){
    P1OUT |= SER_OUT;
    }else{
    P1OUT &= ~SER_OUT;
    }
    P1OUT |= SR_CLK;
    P1OUT &= ~SR_CLK;

    I honestly think that using constants (0x00 and 0x01 or just 0 and 1 if you prefer) would make this easier to read, because that’s what you’re intent is – not to use SER_OUT, SR_CLK, BIT1 or BIT0. I know they’re the same, but I really like to use symbols that relate to what I’m doing in code, even if that means I have “redundant” #defines.

    • UnCivilServant

      It would not be easier to read.

      On the hardware I’m toggling three pins, but the names tell me what those pins are feeding down the line, which is why I used the names instead of the constant number. I’d have been lost if using the constant instead.

      • Unreconstructed

        OK, I can see that for the “action items”. I guess the part that seems less reader-friendly (to me) is the conditional, as there’s not really a relationship there (and you commented on that in the explanation).

      • Richard

        When you pulse P1OUT with SR_CLK how long does the pulse last?

      • UnCivilServant

        @Richard – I don’t know.

        Originally I’d had a delay loop in the middle just in case it cycled too fast for the shift register, but I took it out to save space, and the pulse was good enough to trigger.

  10. The Late P Brooks

    A video flashes in its first 5 seconds with images of Tesla accomplishments — Gigafactories, robots, the Model 3, the Tesla smartphone app, and a Gigapress. The inference is, without Musk, none of these innovations would have been possible.

    That’s crazy. Anybody could have done it. They just didn’t feel like it.

    • R C Dean

      Well, with Musk, they happened at Tesla.

      At all the other car companies out there without Musk, they didn’t.

      Seems pretty straightforward to me.

  11. The Late P Brooks

    The SOC Investment Group has written a letter to the SEC that underscores how Tesla continues to struggle with performance, governance, and human capital management, creating legal, financial, and reputational risks for shareholders and the company at large. “The lack of Board oversight,” the coalition argues, “has effectively enabled Musk to use Tesla as a coffer for himself and his other business endeavors, even if these actions come at Tesla’s expense.”

    Amalgamated Bank and 6 other investment firms that hold a small portion of Tesla stock concur, saying that Musk is distracted by his commitments to the 5 other companies he controls and isn’t serving the EV maker’s best interests, as reported today by Bloomberg.

    So sell out and move on. Or buy Musk out and go hire some Ford reject.

    • OBJ FRANKELSON

      Sorry, Robert MacNamara is dead.

  12. UnCivilServant

    Quasi-related – does anyone have a recommendation for a good resource on how to properly use an oscilliscope? I’m trying to suss out an issue for Article 10, but I’m acutely aware of the fact that I don’t know what I’m doing, so my data could be wrong.

  13. Sensei

    Business Insider
    We asked ChatGPT if Donald Trump is guilty in the hush-money trial
    Jacob Shamsian & Madeline Berg

    We asked ChatGPT if Business Insider is a hack publication

    • rhywun

      LOL. I wonder if that rag was ever reputable?

  14. The Late P Brooks

    We asked ChatGPT if Business Insider is a hack publication

    Does AI understand “garbage in, garbage out”?

    • UnCivilServant

      No, because it’s been fed garbage.

    • The Other Kevin

      This one is amazing. Scott Adams chats with AI (not sure which one) about cheating in elections. He first asks if the 2020 election was legit, and she said of course, there are all these safeguards, blah blah blah.

      Then he asks if he were writing a movie script about someone stealing an election, how would they do it? And she lists things like: someone could put malware on voting machines and then delete it, someone could compromise election officials, someone could run a misinformation campaign. A government agency like the CIA would have the means to do any of this and it would all be very plausible in a movie script.

      Then back to the 2020 election, it was totally secure and had all kinds of safeguards. LOL.

      https://x.com/ScottAdamsSays/status/1792667757278085193

      • The Other Kevin

        Forgot… someone could change the election laws at the last minute.

      • rhywun

        I wish I saved the URL for this amazing article that listed all the things the US looks for when judging the legitimacy of foreign elections – and shows how starting in 2020 every single rule is broken in the US. By our own measures it was fraudulent.

  15. Raven Nation

    OT: Pochettino out at Chelsea.

    • rhywun

      I honestly could not have named Chelsea’s manager. I don’t watch them much.

  16. The Late P Brooks

    someone could change the election laws at the last minute.

    Do you solemnly pinky swear you are a citizen eligible to vote in this election?

    • R C Dean

      Que?

  17. Gender Traitor

    Meanwhile, my idea of fiddly fun this afternoon is trying to create a much-too-complex org chart in PowerPoint. 🙄

  18. Raven Nation

    Welp, left town yesterday. Downpours today. Probably got water in the basement. Hopefully it’ll drain out over the next week or so.

    • R C Dean

      I had a basement once. Never had any urge to buy another house that had one.

      • Mojeaux

        Midwest without a basement is like a TV without a speaker. I mean, slabs do exist, but they’re relatively rare.

      • Nephilium

        R C Dean:

        Then where do you put the bar?

      • pistoffnick (370HSSV)

        Where do you keep your sex slaves?

      • Raven Nation

        @ Mojeaux/RC Dean: yep. And, although I’ve never actually been in the path of a tornado, I’m glad we have the basement just in case.

      • kinnath

        Where do you hide from the tornados if you don’t have a basement?

      • The Last American Hero

        In a culvert with 90’s Helen Hunt?

      • The Artist Formerly Known as Lackadaisical

        I terribly miss my basement. I had an awesome little gym and storage for everything plus all my hobbies… Also alcohol.

        Sigh.

  19. Yusef drives a Kia

    Got a rescue kittah, too young, unweaned, and an injured left hind leg. After a bit of care and love I can say he will be a great asset to my pack,
    Kittah!
    https://photos.app.goo.gl/66PNjhfFwQu5XfQe8

  20. Sensei

    “We’ve done a lot of good things to feed kids here in Butte,” Mr. Marthaller said. But introducing universal free meals, he added, was “probably the best thing we ever did.”

    Never once does the NYT question why this is necessary. Shocking…

    How Free School Meals Went Mainstream
    Over the past decade, many more schools started to offer free meals to all children, regardless of family income.

    https://www.nytimes.com/2024/05/21/headway/how-free-school-meals-went-mainstream.html

  21. The Late P Brooks

    “We’ve done a lot of good things to feed kids here in Butte,” Mr. Marthaller said. But introducing universal free meals, he added, was “probably the best thing we ever did.”

    Free lunch here. Come and get it.

  22. Spartacus

    Good article but you left out one thing–how do you get it to read the punch cards?