Atmega controlling 4094 shift register - Page 1
Forum section: electronics / general

author: Pferd
author_rank: Contributor
timestamp: September 12, 2021, 09:59:30 pm
content: Hello everyone,I am in need of some programming help, because I am lacking experience and tricks using the AVR programmed in C.I am prototyping a level attenuator, which is using two 4051 cmos ics controlled by a 4094 shift register, to form 64 steps of attenuation. The analog side works as intended, but I have problems controlling the shift register. In the schematic I planned on using 74HC595 registers, but the only thing I had around at the moment just for proof of concept was a 4094. I want to feed it with its data (a simple 6-bit value) via the SPI interface of the Atmega preferably. Some code has already been programmed by me, with plenty of room for improvement, but it is constantly sending out the data. But it should only put out the shifted data once it is done shifting, so it doesn´t transport the spi frequency to my analog signal. It doesn´t need to be fast, since it only has to set the attenuation once through a user interface. (For testing I added two buttons, one to increase and one to decrease the stored value).I do find tutorials on how to use them with an AVR but I can´t get my head around to that, because everybody seems to have their own functions programmed. Is there a clean version, like a clean function to simply transmit serial data to a shift register simply by giving it the value it has to send? Should I use a timer to make it cleaner?For some additional information, the finished product will have 7 or 8 of these attenuators controlled by one SPI bus by cascading all the required shift registers together and sending out a long word of data.I attach some pictures so it is easier to understand what I´m up to.Code:#define F_CPU 16000000UL#include <avr/io.h>#include <util/delay.h>void SPI_MasterInit(void){DDRB |= (1<<2)|(1<<3)|(1<<5);PORTB |= (1<<2);SPCR = (1<<SPIE)|(1<<SPE)|(1<<MSTR)|(1<<CPOL)|(0<<CPHA)|(1<<SPR0)|(1<<SPR1);}void SPI_MasterTransmit(char cData){SPDR = cData;while(!(SPSR & (1<<SPIF)));}int main(void){PORTD = 0x00;DDRD &= 0xFC;PORTD = (1<<0) | (1<<1);PORTC = 0x00;DDRC = 0xff;int data = 0x00;SPI_MasterInit();/* Replace with your application code */while (1){if ((PIND & (1<<0)) == 0){_delay_ms(250);data++;}if ((PIND & (1<<1)) == 0){_delay_ms(250);data--;}if (data >= 0x40){data = 0x00;}PORTC = data;SPI_MasterTransmit(data);}If you need more information let me know.The 4094 is wired up to the MOSI, SCK and SS lines as it is showed in many examples. On my little board it is the three pin headers in the middle and on my AVR board it is the three free pins on the right.Thank you in advance!:)

author: DavidAlfa
author_rank: Super Contributor
timestamp: September 12, 2021, 11:41:15 pm
content: It's simple:Feed spi clock(CP, pin 3) and data (Data, pin 2).Data is sampled at the rising clock edge.When you transmitted the full byte, send a positive pulse to Strobe(pin 1) to transfer the data to the parallel outputs.As SS is active low, it will be set high at the end of the transmission, updating the output buffer.The parallel output can be tri-stated (setting it low) or enabled(High) using OE (pin 15). I guess you're wiring it to VDD?To avoid sending data all the time, only do it when the value changed, using a flag or other checking condition.Ex.Code:int main(void){uin8_t update = 1; // Force initial updatePORTD = 0x00;DDRD &= 0xFC;PORTD = (1<<0) | (1<<1);PORTC = 0x00;DDRC = 0xff;int data = 0x00;SPI_MasterInit();/* Replace with your application code */while (1){if ((PIND & (1<<0)) == 0){update = 1; // Set flagdata++;}if ((PIND & (1<<1)) == 0){update = 1;;data--;}if(update){ // Check flagupdate = 0;_delay_ms(250);if (data >= 0x40){data = 0x00;}PORTC = data;SPI_MasterTransmit(data);}

author: Pferd
author_rank: Contributor
timestamp: September 13, 2021, 05:00:18 pm
content: Thanks a lot! I just tried it and it really works!:) This was exactly the kind of help I needed!I added the SS pin functionality to my code, I included it in the spitransmit function. Is this the way to go or is there a more elegant solution? I don´t know if it is good to have so many delay loops. At the moment for testing purposes it is fine but how do I manag it later for example with the ss pin without using some small delay? Here´s the code:Code:void SPI_MasterInit(void){DDRB |= (1<<2)|(1<<3)|(1<<5);PORTB &= ~(1<<2);SPCR = (1<<SPIE)|(1<<SPE)|(1<<MSTR)|(1<<CPOL)|(0<<CPHA)|(1<<SPR0)|(1<<SPR1);}void SPI_MasterTransmit(char cData){SPDR = cData;while(!(SPSR & (1<<SPIF)));_delay_us(100);PORTB |= (1<<2);//SSpin_delay_us(100);PORTB &= ~(1<<2);//SSpin}The same delay problem goes for the button debouncing. But this might be possible without delay by using a timer. Any ideas? Because later the thing will have 8 buttons and 6 rotary encoders, as it is currently planned.Oh, and one more question: Imagine I have 6 shift registers cascaded, for sending them data I simply use the 8-bit SPItransmit function 6 times in a row, or is there a more efficient trick?Thanks in advance!:)

author: DavidAlfa
author_rank: Super Contributor
timestamp: September 14, 2021, 12:05:31 am
content: Yes, simply send 6 bytes, and done.Not the most efficient way, but for slow updates you don't need more.For the buttons and encoder, a timer is a good way.Encoders can change pretty fast compared to buttons, so I'd d sample them every 1-2ms.The buttons can be sampled every 20ms or so.You don't even need interrupts for something that simple, just set the timer so the overflow happens every 1ms and wait for the flag.Every time, sample and store the encoders, process the data to determine rotation, and do whatever if something changed.For the buttons, do the same but slower, ex. every 20 times the timer overflows (20ms).If analog noise is a problem, slow down the spi clock down to the kHz range, and connect the 4094 interface (spi, ss) through resistors to limit the slew rate, 1Kohm should do nice at low speeds.