Shift Register 74HC595 Output Current - c++

I'm testing/debugging a C++ program that uses the 74HC595 shift register (Arduino Teensy3.2 # 3.3V).
I want to connect the 8 outputs of the SR back to 8 input pins on the Arduino board.
My question is: would I need resistors between the output of the shift register and the Arduino input? How to know the current flowing from the SR output?
I did have a look at the datasheet but that just confused me more:
https://www.taydaelectronics.com/datasheets/A-251.pdf
CODE TO SET THE REGISTERS:
void ShiftRegisterOut(uint8_t bitOrder, uint8_t val)
{
for (uint8_t i = 0; i < 8; i++)
{
if (bitOrder == LSBFIRST)
digitalWrite(pinData, !!(val & (1 << i)));
else
digitalWrite(pinData, !!(val & (1 << (7 - i))));
digitalWrite(pinClock, HIGH);
digitalWrite(pinClock, LOW);
}
}

According to the datasheet you linked and the datasheet for uC on the Teensy you should not need resistors between the uC to limit current:
74HC595 maximum output current +/-20mA # 6V
MK20DX256VLH7 maximum input current +/- 25mA
But if you supply the 74HC595 with > 6V you might exceed the maximum input voltage of 5.5V of the MK20DX256VLH7.

We need to differentiate between "µC port is input" and "µC port is output", and how the supply voltages differ.
TL;DR: You should strive to use the same supply voltage on both, and to make sure that the µC never sets the connected port pins as output. This is the first case described, and you will need no resistor.
µC port is input
Supply voltage of 74HC595 is lower than or equal to that of the µC
In this case the input impedance of the µC port is so high that no significant DC current flows.
You don't need any resistor, it will only make the edges slower.
Supply voltage of 74HC595 is higher than that of the µC
If the voltage output by the pins of the 74HC595 makes the clamp diodes of the µC conduct, the resulting current can be too high, depending on too many electrical characteristics to mention here. The data sheet of the 74HC595 states that each output can deliver at least 35mA because this is the allowed maximum output current. This is clearly more then the allowed 25mA of the µC.
There is another limit: the 74HC595 must not provide more than 70mA in total.
So you need a resistor per line. For optimum speed of the edges make it as low as possible.
In example, if you have 6V supply at the 74HC595, you will need at least (6V - 3.3V) / (70mA / 8) = 308Ohm. To be safe I'd use 620Ohm.
µC port is output
In this case the output could drive against each other. You need a resistor for each line. The higher of both supply voltages needs to be taken into account.
The µC has the limit for the sum of all outputs, too: it must not provide more than 100mA in total. But this is higher than that of the 74HC595 so we need to take that.
Supply voltage of 74HC595 is lower than or equal to that of the µC
The critical case is "low" on the 74HC595 and "high" on the µC.
You will need at least 3.3V / (70mA / 8) = 377Ohm. To be safe I'd use 750Ohm.
Supply voltage of 74HC595 is higher than that of the µC
The critical case is "high" on the 74HC595 and "low" on the µC, and it depends on the supply voltage of the 74HC595.
In example, if you have 6V supply at the 74HC595, you will need at least 6V / (70mA / 8) = 686Ohm. To be safe I'd use 1.5kOhm.

Related

SPI Extra Clock Cycle over communication between STM32 Nucleo L432KC and MAX31865 ADC

The setup that I'm working with is a Nucleo L432KC connected to 8 different MAX31865 ADCs to get temperature readings from RTDs (resistive thermal devices). Each of the 8 chip selects is connected to its own pin, but the SDI/SDO of all chips are connected to the same bus, since I only read from one at a time (only 1 chip select is enabled at a time). For now, I am using a 100 ohm base resistor in the Kelvin connection, not an RTD, just to ensure an accurate resistance reading. The read from an RTD comes from calling the function rtd.read_all(). When I read from one and only one RTD, I get an accurate reading and an accurate SPI waveform (pasted below):
correct SPI reading for 1 ADC
(yellow is chip enable, green is clock, blue is miso, purple is mosi)
However, when I read from 2 or more sequentially, the SPI clock for some reason gains an additional unwanted cycle at the start of the read that throws off the transmitted values. It's been having the effect of shifting the clock to the right and bit-shifting my resistance values to the left by 1.
Logic analyzer reading of SPI; clock has additional cycle at start
What could be causing this extra clock cycle? I'm programming in C++ using mbed. I can access the SPI.h file but I can't see the implementation so I'm not sure what might be causing this extra clock cycle at the start. If I need to add the code too, let me know and I'll edit/comment.
rtd.read_all() function:
uint8_t MAX31865_RTD::read_all( )
{
uint16_t combined_bytes;
//SPI clock polarity/phase (CPOL & CPHA) is set to 11 in spi.format (bit 1 = polarity, bit 0 = phase, see SPI.h)
//polarity of 1 indicates that the SPI reading idles high (default setting is 1; polarity of 0 means idle is 0)
//phase of 1 indicates that data is read on the first edge/low-to-high leg (as opposed to phase 0,
//which reads data on the second edge/high-to-low transition)
//see https://deardevices.com/2020/05/31/spi-cpol-cpha/ to understand CPOL/CPHA
//chip select is negative logic, idles at 1
//When chip select is set to 0, the chip is then waiting for a value to be written over SPI
//That value represents the first register that it reads from
//registers to read from are from addresses 00h to 07h (h = hex, so 0x01, 0x02, etc)
//00 = configuration register, 01 = MSBs of resistance value, 02 = LSBs of
//Registers available on datasheet at https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
//The chip then automatically increments to read from the next register
/* Start the read operation. */
nss = 0; //tell the MAX31865 we want to start reading, waiting for starting address to be written
/* Tell the MAX31865 that we want to read, starting at register 0. */
spi.write( 0x00 ); //start reading values starting at register 00h
/* Read the MAX31865 registers in the following order:
Configuration (00)
RTD (01 = MSBs, 02 = LSBs)
High Fault Threshold (03 = MSBs, 04 = LSBs)
Low Fault Threshold (05 = MSBs, 06 = LSBs)
Fault Status (07) */
this->measured_resistance = 0;
this->measured_configuration = spi.write( 0x00 ); //read from register 00
//automatic increment to register 01
combined_bytes = spi.write( 0x00 ) << 8; //8 bit value from register 01, bit shifted 8 left
//automatic increment to register 02, OR with previous bit shifted value to get complete 16 bit value
combined_bytes |= spi.write( 0x00 );
//bit 0 of LSB is a fault bit, DOES NOT REPRESENT RESISTANCE VALUE
//bit shift 16-bit value 1 right to remove fault bit and get complete 15 bit raw resistance reading
this->measured_resistance = combined_bytes >> 1;
//high fault threshold
combined_bytes = spi.write( 0x00 ) << 8;
combined_bytes |= spi.write( 0x00 );
this->measured_high_threshold = combined_bytes >> 1;
//low fault threshold
combined_bytes = spi.write( 0x00 ) << 8;
combined_bytes |= spi.write( 0x00 );
this->measured_low_threshold = combined_bytes >> 1;
//fault status
this->measured_status = spi.write( 0x00 );
//set chip select to 1; chip stops incrementing registers when chip select is high; ends read cycle
nss = 1;
/* Reset the configuration if the measured resistance is
zero or a fault occurred. */
if( ( this->measured_resistance == 0 )
|| ( this->measured_status != 0 ) )
{
//reconfigure( );
// extra clock cycle causes measured_status to be non-zero, so chip will reconfigure even though it doesn't need to. reconfigure commented out for now.
}
return( status( ) );
}
Background:
I took a look at the entire implementation of the MAX31865_RTD class and the thing I find "troubling" is that a MAX31865_RTD instance creates its own SPI instance on construction. If you create multiple instances of this MAX31865_RTD class then there will be a separate SPI instance created and initialized for each of these.
If you have 8 of these chips and you create 8 separate MAX31865_RTD instances to provide one for each of your chips then this also creates 8 SPI instances that all point to the same physical SPI device of the microcontroller.
The problem:
When you call the read_all function on your MAX31865_RTD instance it in turn calls the SPI write functions (as seen in the code you provided). But digging deeper in the call chain you will eventually find that the code of the SPI write method (and others as well) is written in a way that it assumes that there can be multiple SPI instances that are using the same SPI hardware with different parameters (frequency, word length, etc...). In order to actually use the SPI hardware, the SPI class instance must first take ownership of the hardware if it does not have it yet. To do this it "acquires" the hardware for itself which basically means that it reconfigures the SPI hardware to the frequency and word length and mode that this particular SPI instance was set to (This happens regardless of the fact that every instance is set to the same parameters. They don't know about each other. They just see the fact that they have lost ownership and thus have to reacquire it and they also automatically assume that the settings are to be restored.). And this frequency (= clock) reinitialization is the reason that your clock is having a weird artefact/glitch on it. Each time you call the read_all on a different MAX31865_RTD instance the SPI instance of that instance will have to do an acquire (because they steal the ownership from each other on each read_all call) and it will make the clock behave weird.
Why it works if you only have one device:
Because when you have one and only one MAX31865_RTD instance then it has only one SPI instance which is the sole "owner" of the SPI hardware. So no-one is stealing the ownership on each turn. Which means that it does not have to re-acquire it on every read_all call. So in that case the SPI hardware is not reinitialized every time. So you don't get the weird clock pulse and everything works as intended.
My proposed solution #1:
I propose that you change the implementation of the read_all method.
If the version of the SPI class that you use has the select method, then add the
spi.select();
line just before pulling the chip select (nss) low. Basically add the line above this block:
/* Start the read operation. */
nss = 0;
If there is no select function, then just add a
spi.write(0x00);
line in the same place instead of the line with the select.
In essence both of the proposed lines just force the acquire (and the accompanying clock glitch) before the chip select line is asserted. So by the time the chip select is pulled low and the actual data is being written the SPI instance already has ownership and the write methods will not trigger an acquire (nor the clock glitch).
My proposed solution #2:
Another solution is to modify the MAX31865_RTD class to use an SPI instance reference and provide that reference through its constructor. That way you can create one SPI instance explicitly and provide this same SPI instance to all your MAX31865_RTD instances at construction. Now since all of your MAX31865_RTD instances are using a reference to the same and only SPI instance, the SPI hardware ownership never changes since there is only one SPI class instance that is using it. Thus the hardware is never reconfigured and the glitch never happens. I would prefer this solution since it is less of a workaround.
My proposed solution #3:
You could also modify the MAX31865_RTD class to have a "setter" for the nss (= chip select) pin. That way, you could have only one MAX31865_RTD instance for all your 8 devices and only change the nss pin before addressing the next device. Since there is only one MAX31865_RTD instance then there is only one SPI instance which also solves the ownership issue and since no re-acquisition has to be made then no glitch will be triggered.
And of-course there can be any number of other ways to fix this knowing the reason of the problem.
Hope this helps in solving your issue.

Absolute Encoder hexadecimal input

Question: How do I receive input from a absolute encoder that makes it output in hexadecimal/grey code into a Arduino? do I use a digitalRead or analogRead command? I could not find example code/projects with a absolute encoder online.
Overall Objective: I want to use a absolute encoder as a knob for a project. The idea is that the encoder will output its position as a state to be used in a case statement. I.e:
case1: analogRead(absEncdr == 00)
arduino enters idle routine
case2: analogRead(absEncdr == 11)
arduino enters button routine
etc
details: Abs encoder type: 25LB22-G
encoder datasheet is here.
I'm using the hexidecimal/ grey code 4-Bit Binary Code Hexadecimal-16 Position encoder version
Basically, I'm not sure how to read in this abs encoder into my Arduino.
To read that you will need four digital pins. You can read the four pins and bit-shift those values into one byte that you can compare to the table in the data-sheet.
byte position = (digitalRead(pin8) << 3) | (digitalRead(pin4) << 2) | (digitalRead(pin2) << 1) | digitalRead(pin1);
Assuming that the pins are named as they are in that truth table in the datasheet.
We are using the OR operator | to put them together into the same byte.

how to calculate i2c timing for stm32l0

I got a stm32l0 series procesor and i dont know how to calculate i2c timing.
I want to work with Bosh bme680/bme280 sensor. I find only Timming configuration tool for stm32F0/f3. Some1 know how to calculate it?
TLDR: RM0377 Reference manual "Ultra-low-power STM32L0x1 advanced Arm®-based 32-bit
MCUs", page 614 contains examples.
Note: I haven't actually worked with I2C yet. All the following is based on documentation.
UM1749 User Manual "Description of STM32L0 HAL and Low Layer
drivers" (p.
233):
uint32_t I2C_InitTypeDef::Timing Specifies the I2C_TIMINGR_register value. This parameter calculated by referring
to I2C initialization section in Reference manual
RM0377 Reference manual "Ultra-low-power STM32L0x1 advanced Arm®-based 32-bit
MCUs"
(p.641):
Timing register (I2C_TIMINGR)
(...)
PRESC[3:0] : Timing prescaler
This field is used to prescale I2CCLK in order to generate the clock period t_PRESC used for data setup and hold counters (refer
to I2C timings on page 587 ) and for SCL high and low level
counters (refer to I2C master initialization on page 602 ).
t_PRESC = (PRESC+1) x t_I2CCLK
SCLDEL[3:0] : Data setup time
This field is used to generate a delay t_SCLDEL between SDA edge and SCL rising edge. In master mode and in slave mode with NOSTRETCH = 0, the SCL line is stretched low during t_SCLDEL.
t_SCLDEL = (SCLDEL+1) x t_PRESC
Note: t_SCLDEL is used to generate t_SU:DAT timing.
SDADEL[3:0] : Data hold time
This field is used to generate the delay t_SDADEL between SCL falling edge and SDA edge. In master mode and in slave mode with NOSTRETCH = 0, the SCL line is stretched low during t_SDADEL.
t_SDADEL = SDADEL x t_PRESC
Note: SDADEL is used to generate t_HD:DAT timing.
SCLH[7:0] : SCL high period (master mode)
This field is used to generate the SCL high period in master mode.
t_SCLH = (SCLH+1) x t_PRESC
Note: SCLH is also used to generate t_SU:STO and t_HD:STA timing.
SCLL[7:0] : SCL low period (master mode)
This field is used to generate the SCL low period in master mode.
t_SCLL = (SCLL+1) x t_PRESC
Note: SCLL is also used to generate t_BUF and t_SU:STA timings.
More info about prescalers can be found in paragraphs about timers
(p. 433).
RM0377 (p. 581):
The interface is connected to the I2C bus by a data pin (SDA) and by a clock pin (SCL). It can be connected with a standard (up to
100 kHz), Fast-mode (up to 400 kHz) or Fast-mode Plus (up to 1MHz)
I2C bus.
So in order to communicate the I2C clock needs to be have appropriate frequency.
RM0377 (p. 583):
This independent clock source can be selected from the following three clock sources:
PCLK1: APB1 clock (default value)
HSI16: internal 16 MHz RC oscillator
SYSCLK: system clock
Refer to Section 7: Reset and clock control (RCC) for more details.
RM0377 (p. 604):
RM0377 (p. 602):
t_SCL = t_SYNC1 + t_SYNC2 + {[(SCLH+1) + (SCLL+1)] x (PRESC+1) x t_I2CCLK}
The duration of t_SYNC1 depends on these parameters:
SCL falling slope
When enabled, input delay induced by the analog filter.
When enabled, input delay induced by the digital filter: DNF x t_I2CCLK
Delay due to SCL synchronization with I2CCLK clock (2 to 3 I2CCLK periods)
The duration of t_SYNC2 depends on these parameters:
SCL rising slope
When enabled, input delay induced by the analog filter.
When enabled, input delay induced by the digital filter: DNF x t_I2CCLK
Delay due to SCL synchronization with I2CCLK clock (2 to 3 I2CCLK periods)
BME280 Datasheet (p. 30):
All modes (standard, fast, high speed) are supported.
Gathering it all up: Basing on the I2C clock frequency, and the speed you want to use, you need to choose such values of PRESC, SCLDEL, SDADEL, SCLH, SCLL that you comply to the I2C-SMBUS specification clock timings using formula t_SCL = t_SYNC1 + t_SYNC2 + {[(SCLH+1) + (SCLL+1)] x (PRESC+1) x t_I2CCLK}
There are also some I2C_TIMINGR register configuration examples in RM0377 at page 614.

Quadrature signal generation with bit manipulation

I try to generate quadrature signal but with the lowest operation possible. I use a STM32 and GPIO pin B8 and B9 for sending the signal.
couple of pin 8 and 9 have four possible options which are in clock wise:
0/0 1/0 1/1 and 0/1
and counter clockwise
0/0 0/1 1/1 1/0
I can't find the way with bitwise to be able to quickly set or reset the bit for the selected pin.
Moreover, I must be able to go clock or counterclokwise and change sense whenever I want like if it was a rotary or linear encoder.
Thank you for your help
Bit-banging
Bitwise thinking, B9 gets the previous value of B8, and B8 gets the inverse of B9, or the other way round when counting down. You swap the two bits, and exclusive-or with 0x100 or 0x200 depending on the direction.
inline void incB89(int down) {
uint32_t temp;
/* read the current output state */
temp = GPIOB->ODR;
/* modifying the significant bit-pair
don't care about overflow */
temp = (((temp & 0x100) << 1) | ((temp & 0x200) >> 1)) ^ (0x100 << down);
/* Setting the reset bits BR8 and BR9. This has the effect that
bits 8 and 9 will be copied into the ODR, and the rest will
be left alone */
temp |= ((1 << 24) | (1 << 25));
GPIOB->BSRR = temp;
}
Using a timer (or two)
On most STM32 series controllers, TIM4 channels 3 and 4 outputs can be mapped to PB8 and PB9. If you have one of these, this timer can control the outputs autonomously, unaffected by code, memory, or interrupt latency.
Set the GPIO mode and alternate function registers according to the reference manual of your controller.
Configure both channel 3 and 4 to toggle mode, set the OC1M and OC2M bits in TIM4->CCMR1 to 0b011.
Set the input clock, prescaler PSC and reload ARR to achieve twice the desired frequency, because each output will be toggled once in every timer cycle.
Set TIM4->CCR3=0 and TIM4->CCR4=(TIM4->ARR+1)/2 for counting in one direction. Swap them (while the counter is stopped) to reverse direction.
Enable the outputs in TIM4->CCER.
You can start and stop counting by setting or resetting the CEN bit of TIM4->CR1.
To count the cycles, you can to configure an interrupt for toggle or update events in TIM4->DIER, or use another timer as a slave to TIM4.
To use e.g. TIM3 to count:
Set the MMS bits in TIM4->CR2 to 0b010 to output a trigger pulse on each overflow.
Configure TIM3->SMCR to External Clock Mode 1, and select the internal trigger of TIM4.
Set TIM3->ARR to the required number of half-cycles - 1.
Configure an interrupt on update.
Start the counter.
There are some more tricks possible with timers, like using DMA bursts triggered by the slave to update the ARR and CCR registers of the master timer from a table of "wawelength" values.

Serial communication protocol design issues

This is an embedded solution using C++, im reading the changes of brightness from a cellphone screen, from very bright (white) to dark (black).
Using JavaScript and a very simple script im changing the background of a webpage from white to black on 100 milliseconds intervals and reading the result on my brightness sensor, as expected the browser is not very precise on timing, some times it does 100ms sometimes less and sometimes more with a huge deviation at times.
var syncinterval = setInterval(function(){
bytes = "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
bit = bytes[i];
output_bit(bit);
i += 1;
if(i > bytes.length) {
clearInterval(syncinterval);
i = 0;
for (i=0; i < input.length; i++) {
tbits = input[i].charCodeAt(0).toString(2);
while (tbits.length < 8) tbits = '0' + tbits;
bytes += tbits;
}
console.log(bytes);
}
}, sync_speed);
My initial idea, before knowing how the timing was on the browser was to use asynchronous serial communication, with some know "word" to sync the stream of data as RS232 does with his start bit, but on RS232 the clocks are very precise.
I could use a second sensor to read a different part of the screen as a clock, in this case even if the monitor or the browser "decides" to go faster or slower my system will only read when there is a clock signal (this is a similar application were they swipe the sensors instead of making the screen flicks as i need), but this require a more complex hardware system, i would like not to complicate things before searching for a software solution.
I don't need high speeds, the data im trying to send is just about 8 Bytes as much.
With any kind of asynchronous communications, you rely on transmitter sending a new 'bit' of data at a fixed time interval, and the receiver sampling the data at the same (fixed) interval. If the browser isn't accurate on timings, you'll just need to slow the bitrate down until its good enough.
There are a few tricks you can use to help you improve the reliability:-
a : While sending, calculate the required 'start transmit time' of each 'bit' in advance, and modify the delay after each bit has been 'sent', based on current time vs. required time. This means you'll avoid cumulative errors (i.e. if Bit 1 is sent a little 'late', the delay to bit 2 will be reduced to compensate), rather than delaying a constant N microseconds per bit.
b: While receiving, you must sample the incoming data much faster than you expect changes. (UARTS normally use a 16x oversample) This means you can resynchronize with the 'start bit' (the initial change from 1 to 0 in your diagram) and you can then sample each bit at the expected 'centre' of its time period.
In other words, if you're sending data at 1000us intervals, you sample data at ~62us intervals, and when you detect a 'start bit, you wait 500us to put you in the centre of the time period, then take 8 single-bit samples at 1000us intervals to form an 8-bit byte.
You might consider not using a fixed-rate encoding, where each bit is represented as a sequence of the same length, and instead go for a variable-rate encoding:
Time: 0 1 2 3 4
0: _/▔\_
1: _/▔▔▔▔▔\_
This means that when decoding, all you need to do is to measure the time the screen is lit. Short pulses are 0s, long pulses are 1s. It's woefully inefficient, but doesn't require accurate clocking and should be relatively resistant to inaccurate timing. By using some synchronisation pulses (say, an 010 sequence) between bytes you can automatically detect the length of the pulses and so end up not needing a fixed clock at all.