3

Arduino Mega 2560 board and SN74HC595 shift register(s). I'm working on a school assignment and having issues getting the shift register(s) working without using Arduino libraries.

I know with the libraries you can use functions such as digitalWrite(....); and shiftOut(dataPin, clockPin, MSBFIRST, bitsToSend);.

For my project, I've set up my individual pins as inputs and outputs, but I am having trouble converting the library code into basic code.

IE:

digitalWrite(latchPin, LOW);

can be rewritten as

DDRB &= ~(1 << DDB1);

where latchPin is the SCK pin.

At this stage in the project, we're just trying to get 8 LEDs lit up using 1 shift register, then 16 using 2 shift registers. Any help in understanding what I need to do or direction in which to look would be appreciated.

Thank you,

Nic

Edit:

This is what I have converted from the void setup from https://www.arduino.cc/en/Tutorial/ShftOut12 :

void setup() {
  //Clear the shift registers prev data, Need a low input to reset registers
  //PORTL0 to first SReg SRCLR, PORTL2 to second SReg SRCLR
  //* See data sheet for sn74hc595 shift registers
  DDRL &= ~((1 << PORTL0) | (1 << PORTL2)); //Pg 100 - 21.2.3
  //Use port L0/L2 for SRCLR pin on SReg
  PORTL |= (1 << PORTL0) | (1 << PORTL2); //Pg 100 - 21.2.3

  //Set OE (Enable) to 0 until after last bit is sent
  DDRL &= ~(1 << PORTL4); //Pg 100 - 21.2.3
  //TODO SPI DELAY HERE USING SPDR pg 199

  //Enable SCK as output to drive RCLK and SRCLK on SR
  DDRB &= ~(1 << DDB1); //Pg 96 - 13.4.6
  PORTB |= (1 << PORTB1); //Pg 96 - 13.4.5

  //Enable MOSI as output to drive SER Input on SR
  DDRB &= ~(1 << DDB0); //Pg 96 - 13.4.6
  PORTB |= (1 << PORTB0); //Pg 96 - 13.4.5

  Serial.begin(9600);
  Serial.println("reset");
}
  • You mention “SPI” in the title, but you are not using the SPI port at all: you are bit-banging. Is this by choice? – Edgar Bonet Oct 24 '17 at 20:28
3

The main problem you seem to have is how to implement shiftOut. Well, that's actually quite simple once you understand how shiftOut actually works.

You already know how to set a pin HIGH or LOW - all you lack from that is the concept of time.

All shiftOut does is set the clock and data pins HIGH and LOW at the right time.

It's simply a case of:

  1. Set the DATA pin to HIGH or LOW depending on the bit value to send
  2. Set the CLOCK pin HIGH
  3. Set the CLOCK pin LOW
  4. Go back to 1 for 8 iterations total.

Getting the bit value from a byte can be done with boolean operators & and <<

The basic shiftOut code looks like this:

void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val)
{
    uint8_t i;

    for (i = 0; i < 8; i++)  {
        if (bitOrder == LSBFIRST)
            digitalWrite(dataPin, !!(val & (1 << i)));
        else
            digitalWrite(dataPin, !!(val & (1 << (7 - i))));

        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
    }
}

Since your bit order is fixed you can reduce that down to this:

void myShiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t val)
{
    uint8_t i;

    for (i = 0; i < 8; i++)  {
        digitalWrite(dataPin, !!(val & (1 << (7 - i))));    
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
    }
}

And then you can replace your digitalWrite calls with direct register manipulation.

void myShiftOut(uint8_t val)
{
    uint8_t i;

    for (i = 0; i < 8; i++)  {
        if (val & (1 << (7 - i))) {
            PORTB |= (1 << PORTB0);  // Data 1
        } else {
            PORTB &= ~(1 << PORTB0); // Data 0
        }
        PORTB |= (1 << PORTB1);      // Clock HIGH
        PORTB &= ~(1 << PORTB1);     // Clock LOW
    }
}

Note that this code will now run considerably faster than the bloaty digitalWrite based version - so you may find it's now too fast. You may need to include some short delays around the clock HIGH and clock LOW lines. Depending on how long they need to be you could write a little delay loop to introduce a short delay:

void delayLoop(uint32_t iterations) {
    while ((--iterations) > 0) {
        asm volatile("nop");
    }
}

That will delay for iterations clock cycles plus the time taken to do the loop and call the function. For very short delays it may be better to just insert a few nop instructions:

PORTB |= (1 << PORTB1);      // Clock HIGH
asm volatile("nop");
asm volatile("nop");
asm volatile("nop");
PORTB &= ~(1 << PORTB1);     // Clock LOW
asm volatile("nop");
asm volatile("nop");
asm volatile("nop");
  • For delaying, you can also #include <util/delay.h> and then _delay_us(0.25) for example. – Edgar Bonet Oct 24 '17 at 20:26
3

My strategy: Write the sketch using the standard Arduino library. The goal is have the LED lit up, as you need.

Then, change all standard function calls in your sketch (like digitalWrite) with your own (called it digitalWrite2).

Now implement digitalWrite2 using register manipulation.

Pros:

  • Divide and conquer. You know have two independent smaller problems (LED and registers).
  • When is time to implement digitalWrite2 you can learn from your classmate how to do that. I bet that they will start with the hard part (register manipulation).
  • It makes for a easier debuging. You can compare your own implementation against the standard implementation just by deleting/adding a single "2" in the call.

Of course, taking a look at the Arduino source code will made things trivial :-) Is that cheating? No, we learn from others, we learn reading other people's code.

Edit: Step by Step Example

Let use Blink to show how to do it:

Original

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

For the new version I will replace pinMode and digitalWrite for the my own implementation them. How?

Go to the directory where you put your Arduino binaries, which include the source code. There is a subdirectory with the sources (mine is ~/bin/arduino-1.8.3/hardware/arduino/avr/cores/arduino).

There is a file (wiring_digital.c) where I found the source code for pinMode and digitalWrite. Without any shame, I copy-paste and rename.

Note: the original includes a call to turnOffPWM, which I simply comment out, because we will not use it here and I want to cut it short.

That's all.

New version

void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode2(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite2(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite2(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

void pinMode2(uint8_t pin, uint8_t mode)
{
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *reg, *out;

  if (port == NOT_A_PIN) return;

  // JWS: can I let the optimizer do this?
  reg = portModeRegister(port);
  out = portOutputRegister(port);

  if (mode == INPUT) { 
    uint8_t oldSREG = SREG;
                cli();
    *reg &= ~bit;
    *out &= ~bit;
    SREG = oldSREG;
  } else if (mode == INPUT_PULLUP) {
    uint8_t oldSREG = SREG;
                cli();
    *reg &= ~bit;
    *out |= bit;
    SREG = oldSREG;
  } else {
    uint8_t oldSREG = SREG;
                cli();
    *reg |= bit;
    SREG = oldSREG;
  }
}

void digitalWrite2(uint8_t pin, uint8_t val)
{
  uint8_t timer = digitalPinToTimer(pin);
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *out;

  if (port == NOT_A_PIN) return;

  // If the pin that support PWM output, we need to turn it off
  // before doing a digital write.
  // if (timer != NOT_ON_TIMER) turnOffPWM(timer); By Look Alterno

  out = portOutputRegister(port);

  uint8_t oldSREG = SREG;
  cli();

  if (val == LOW) {
    *out &= ~bit;
  } else {
    *out |= bit;
  }

  SREG = oldSREG;
}

Pablo Picasso

  • I understand what you are saying, but this is the issue I am having. Maybe I just don't fully understand the proper way to go about this. As an example, here is a tutorial I found from the Arduino site: arduino.cc/en/Tutorial/ShftOut12. But how would I go about writing this code in a way that doesn't use Arduino libraries? I've edited my original post to show what code I already have. – Nic Oct 24 '17 at 0:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.