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– Edgar Bonet10/24/2017 20:28:24Commented Oct 24, 2017 at 20:28
2 Answers 2
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:
- Set the DATA pin to HIGH or LOW depending on the bit value to send
- Set the CLOCK pin HIGH
- Set the CLOCK pin LOW
- 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– Edgar Bonet10/24/2017 20:26:34Commented Oct 24, 2017 at 20:26
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;
}
-
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– Nic10/24/2017 00:37:02Commented Oct 24, 2017 at 0:37