Stel ik heb op een Atmel AVR (2590, 6490 oid) een stel (8) "bij elkaar geraapte" digitale I/O lijnen (als ingang / uitgang), dus niet netjes 8 bits op een en de zelfde poort.
Daar knutsel ik zelf een byte van, dus bijvoorbeeld (poorten zijn even verzonnen) :
D7 D6 D5 D4 D3 D2 D1 D1
P1.0 P4.4 P7.2 P5.2 P3.2 P3.1 P3.0 P0.1En ik heb een teller, 8 bit, van 0-255
Is er dan iets universeels te maken (in C/C++) dat ik mijn teller naar mijn "zelf geknutselde I/O Byte" kan schrijven ?
Of lezen, as er vanaf de buitenkant data aangeboden word.
Arco
Special Member
Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com
Geeft natuurlijk altijd veel onnodige overhead (beter een complete port kiezen...)
In pseudocode zoiets:
Dim Count As Byte
P0.1 = Count.0
P3.0 = Count.1
P3.1 = Count.2
P3.2 = Count.3
P5.2 = Count.4
P7.2 = Count.5
P4.4 = Count.6
P1.0 = Count.7
Met gemixte in/output nog meer overhead...
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
PORTA = count
Edit: Oh, wacht. ik dacht dat ze WEL allemaal in een port zaten.
Arco werkt in basic.
Ik dacht niet dat C deze syntax heeft met .bitnummer .
Ik heb een library gemaakt waarin ik "pins" kan specificeren met portnummer (A=0, B=1, etc) en bitnummer (0..7). Ik heb dan een write_bit.
Ik zou dan:
const uint8_t pins[]={PA1, PC0, PC1, PC2, PE2 ... };
for (int i=0;i<8;i++) write_pin (pins[i], (count >> i)&1);[Bericht gewijzigd door rew op (94%)]
Je zult zo'n bij elkaar geraapte poort niet in een µC instructie cycle kunnen lezen of schrijven, het lezen / schrijven zal altijd sequentieel gebeuren m.a.w. de poort bits zullen nooit gelijtijdig door de µC kunnen worden gelezen of geschreven. Kan evt. problemen geven als de bits op een of andere manier qua I/O timing bij elkaar horen.
Op woensdag 24 juli 2024 12:36:46 schreef Bobosje:
Je zult zo'n bij elkaar geraapte poort niet in een µC instructie cycle kunnen lezen of schrijven, het lezen / schrijven zal altijd sequentieel gebeuren m.a.w. de poort bits zullen nooit gelijtijdig door de µC kunnen worden gelezen of geschreven. Kan evt. problemen geven als de bits op een of andere manier qua I/O timing bij elkaar horen.
Nee dit snap ik, en realiseer me ook dat het veel overhead gaat geven , REW heeft me al een beetje op de goede weg gezet denk ik.
@Rew, is die Library "deelbaar" ?
Ik zoek inderdaad een functie waar ik de teller naartoe kan pass'en en die vervolgens de pinnen wel uitzoekt.
Hoeft zeker niet in 1 cycle.
buckfast_beekeeper
Van Lambiek wordt goede geuze gemaakt.
Een teller kan je dit bezwaarlijk noemen elke io kan alleen 1 of 0 zijn.
#define SETBIT(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define CLEARBIT(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#define FLIPBIT(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))
#define CHECKBIT(ADDRESS,BIT) (ADDRESS & (1<<BIT))Kan eigenlijk alles wat je wil.
Dit lijkt wel te werken, (komt in elk geval door de compiler)
//
// Testing access to IO-Ports from C++
// - Using port address as parameter
//
#include <stdint.h>
#include <avr/io.h>
void DigitalWrite(volatile uint8_t &Port, uint8_t Mask, bool Value)
{ if(Value)
Port |= Mask;
else
Port &= ~Mask;
}
void WriteByte(uint8_t Value)
{ DigitalWrite(PORTA, (1 << 0), Value & 0x01);
DigitalWrite(PORTA, (1 << 2), Value & 0x02);
DigitalWrite(PORTB, (1 << 0), Value & 0x04);
DigitalWrite(PORTB, (1 << 4), Value & 0x08);
DigitalWrite(PORTC, (1 << 2), Value & 0x10);
DigitalWrite(PORTC, (1 << 5), Value & 0x20);
DigitalWrite(PORTC, (1 << 6), Value & 0x40);
DigitalWrite(PORTD, (1 << 0), Value & 0x80);
}
int main()
{ DigitalWrite(PORTA, (1 << 0), 1);
DigitalWrite(PORTA, (1 << 0), 0);
DigitalWrite(PORTB, (1 << 0), 1);
DigitalWrite(PORTB, (1 << 0), 1);
WriteByte(0x3B);
}
[Bericht gewijzigd door Henry S. op (0%)]
Kan ook in C++. En dat geeft minder code en is veel sneller.
Met optimisation -O1 geeft de compiler sbi en cbi instrukties om de bits te manipuleren. Ik was wel verrast toen ik de listing bekeek. Best wel knap van die compiler.
//
// Testing access to IO-Ports from C++
// - Using a C++ approach
// - Define a class to mimic an IO_PORT
// - Define member functions to set bits.
//
#include <stdint.h>
#include <avr/io.h>
// A port maps directly to an actual IO_Port
// - So class members must match port hardware.
// - No additional registers allowed!
//
// Please note:
// - IO_PORT structure differs per processor type.
// - This matches with original AVR processors
// - AtMega32
// - AtMega6490
// - etc
class IO_PORT
{
public:
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
Port |= Mask;
else
Port &= ~Mask;
}
uint8_t Read(uint8_t Mask)
{ return Pin & Mask ? 1 : 0;
}
};
// Map IO_PORTS to actual hardware registers
// - PINx is start-address of the actual port hardware
IO_PORT &PortA = *((IO_PORT *)&PINA);
IO_PORT &PortB = *((IO_PORT *)&PINB);
IO_PORT &PortC = *((IO_PORT *)&PINC);
IO_PORT &PortD = *((IO_PORT *)&PIND);
// Convert BitNr(0 .. 7) to Bitmask(0x01 .. 0x80)
inline constexpr uint8_t Bit(uint8_t BitNr)
{ return 1 << BitNr;
}
void WriteByte(uint8_t Value)
{ PortA.Write(Bit(0), Value & 0x01);
PortA.Write(Bit(2), Value & 0x02);
PortB.Write(Bit(0), Value & 0x04);
PortB.Write(Bit(4), Value & 0x08);
PortC.Write(Bit(2), Value & 0x10);
PortC.Write(Bit(5), Value & 0x20);
PortC.Write(Bit(6), Value & 0x40);
PortD.Write(Bit(0), Value & 0x80);
}
int main()
{ PortA.Write(Bit(0), true);
PortA.Write(Bit(0), false);
PortA.Write(Bit(0), true);
PortA.Write(Bit(0), false);
WriteByte(0x3B);
}
[Bericht gewijzigd door Henry S. op (0%)]
henri62
1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
Hier is weinig echt C++ aan, net als alle andere posts hierboven trouwens, allemaal C met gebruik van de standard libraries (die wel C++ zijn).
Als je dan toch een class maakt, doe het dan goed. Gebruik dan de operator overload '=' voor een assignment ipv een Write method.
Eigenlijk lost die IO_PORT class niks op voor 'bprosman'. De aanroep gebruikt nog steeds writeByte met losse calls naar Port#.Write(...)
Eigenlijk wil je een class hebben met een constructor waar je de bit mapping in meegeeft bij de instantiatie van de call. Zoiets als 'rew' poste met een array bijvoorbeeld, kan op verschillende manieren.
Dan de = operator in de class overloaden en de bit manipulaties IN de class doen adhv de array die meegepassed is (en intern in een private variable array staat).
Op een soortgelijke manier kun je ook de read abstraheren in de class zodat je een byte kunt lezen van een zwik port bits.
Dan kun je gewoon zeggen:
bitmapped_port myport(PA1, PC0, PC1, PC2, PE2 ... ); // Dit is de class instantiatie
myport = counter;
Maar zoals ik altijd zeg met C++ projecten: Er zijn maar weinig projecten die ik gezien heb die C++ op de juiste en efficiënte manier gebruiken.
Oh ja: Op deze manier kun je ook de +, -, |=, en &= overloaden en er gewoon mee tellen, bitmasken alsof het een counter/byte is.
Ik heb nu geen tijd om zo'n class in elkaar te knutselen anders had ik wel een compleet voorbeeld gegeven.
Loont het zich de moeite wel en de bulk aan extra overhead code om een C++ class te maken voor het kunnen lezen/schrijven van slechts een bij elkaar geraapte poort?
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Enerzijds moet je je realiseren dat veel mensen met arduino iets doen op menselijke tijdschaal, dus "secondes". Dan is "performance" van 8 bitjes bij mekaar rapen niet zo'n probleem. Maar je moet ook uitkijken dat het dan makkelijk wordt om die class "algemeen" te gaan toepassen en alle overhead van dien.
Wat ik bedoel is dat je dan in een volgend project een "virtuele poort" maakt die wel allemaal bij mekaar horende dingen heeft, maar toch niet als "getal" gezien moeten worden: Losse signalen.
Als je dan
virtport |= BIT_CS;
virtport &= ~BIT_FLUP;
gaat doen, dan is het heel waarschijnlijk dat dit onderwater dus een haal de hele 8 bits op, reken de neiuwe waarde uit, en schrijf alle 8 bits weer terug naar de poorten. Dat kost je performance, hetgeen als het toch iets sneller moet en (te) vaak gebeurt ooit een probleem gaat vormen.
Ik heb voor een 7-segmenten display ooit de code geschreven die voor STM32 de "switch naar next digit" doet. omdat ik de bitjes niet op een rij kon krijgen, precompute ik dus ALLES. van "1234" naar [bc][abged][abgcd][fgbc] maar ook het "welk bitje van m'n poort". Ik doe dan in de interrupt 1000x per seconde een "clear deze bitjes", "set deze bitjes" en volgende digitlijn.
Je zult ze de kost moeten geven die dan "live" de bitjes gaan uitrekenen en distribueren over de poort.
En DAT is nu juist wat je syntactisch heel makkelijk maakt als je die class zou maken. En dat nodigt dan uit om het te gebruiken.
write_digitline (curdigit, 0);
7segbits = ascii_to_bits [ display [curdigit++]];
curdigit = curdigit & 3;
write_digitline (curdigit, 1);
Leuk en compact, maar dat de tweede statement ineens 16 of 24 instructies is ipv de 1 of 2 die zo'n statement oplevert, dat zie je er niet aan af.
Hier is weinig echt C++ aan, net als alle andere posts hierboven trouwens, allemaal C met gebruik van de standard libraries (die wel C++ zijn).
Hoezo geen C++? Ik gebruik een class die toegang geeft tot de port registers, en member funkties om daar dingen mee te doen. Dat is volgens mij C++ toch? Ik kan die discussies of C++ nu wel of niet "echt" is nooit volgen trouwens. En waar zie jij ergens iets van standaard libraries in bovenstaande voorbeelden?
En natuurlijk kun je zoiets op heel veel manieren aanpakken. Operator overloading kan inderdaad maar dat heeft ook zo zijn voor een nadelen.
Eigenlijk lost die IO_PORT class niks op voor 'bprosman'. De aanroep gebruikt nog steeds writeByte met losse calls naar Port#.Write(...)
en
Maar zoals ik altijd zeg met C++ projecten: Er zijn maar weinig projecten die ik gezien heb die C++ op de juiste en efficiënte manier gebruiken.
Bovenstaande code vertaalt naar 0xE4 bytes code totaal, inclusief vectoren en opstart overhead. De functie-calls worden door de compiler volledig weggewerkt en de port manipulatie gaat met cbi en sbi instrukties. Efficienter kan niet, zowel in code-size als in executie snelheid.
0000007c <WriteByte(unsigned char)>:
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
7c: 80 ff sbrs r24, 0
7e: 02 c0 rjmp .+4 ; 0x84 <WriteByte(unsigned char)+0x8>
Port |= Mask;
80: d8 9a sbi 0x1b, 0 ; 27
82: 01 c0 rjmp .+2 ; 0x86 <WriteByte(unsigned char)+0xa>
else
Port &= ~Mask;
84: d8 98 cbi 0x1b, 0 ; 27
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
86: 81 ff sbrs r24, 1
88: 02 c0 rjmp .+4 ; 0x8e <WriteByte(unsigned char)+0x12>
Port |= Mask;
8a: da 9a sbi 0x1b, 2 ; 27
8c: 01 c0 rjmp .+2 ; 0x90 <WriteByte(unsigned char)+0x14>
else
Port &= ~Mask;
8e: da 98 cbi 0x1b, 2 ; 27
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
90: 82 ff sbrs r24, 2
92: 02 c0 rjmp .+4 ; 0x98 <WriteByte(unsigned char)+0x1c>
Port |= Mask;
94: c0 9a sbi 0x18, 0 ; 24
96: 01 c0 rjmp .+2 ; 0x9a <WriteByte(unsigned char)+0x1e>
else
Port &= ~Mask;
98: c0 98 cbi 0x18, 0 ; 24
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
9a: 83 ff sbrs r24, 3
9c: 02 c0 rjmp .+4 ; 0xa2 <WriteByte(unsigned char)+0x26>
Port |= Mask;
9e: c4 9a sbi 0x18, 4 ; 24
a0: 01 c0 rjmp .+2 ; 0xa4 <WriteByte(unsigned char)+0x28>
else
Port &= ~Mask;
a2: c4 98 cbi 0x18, 4 ; 24
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
a4: 84 ff sbrs r24, 4
a6: 02 c0 rjmp .+4 ; 0xac <WriteByte(unsigned char)+0x30>
Port |= Mask;
a8: aa 9a sbi 0x15, 2 ; 21
aa: 01 c0 rjmp .+2 ; 0xae <WriteByte(unsigned char)+0x32>
else
Port &= ~Mask;
ac: aa 98 cbi 0x15, 2 ; 21
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
ae: 85 ff sbrs r24, 5
b0: 02 c0 rjmp .+4 ; 0xb6 <WriteByte(unsigned char)+0x3a>
Port |= Mask;
b2: ad 9a sbi 0x15, 5 ; 21
b4: 01 c0 rjmp .+2 ; 0xb8 <WriteByte(unsigned char)+0x3c>
else
Port &= ~Mask;
b6: ad 98 cbi 0x15, 5 ; 21
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
b8: 86 ff sbrs r24, 6
ba: 02 c0 rjmp .+4 ; 0xc0 <WriteByte(unsigned char)+0x44>
Port |= Mask;
bc: ae 9a sbi 0x15, 6 ; 21
be: 01 c0 rjmp .+2 ; 0xc2 <WriteByte(unsigned char)+0x46>
else
Port &= ~Mask;
c0: ae 98 cbi 0x15, 6 ; 21
volatile uint8_t Pin;
volatile uint8_t Ddr;
volatile uint8_t Port;
void Write(uint8_t Mask, bool Value)
{ if(Value)
c2: 88 23 and r24, r24
c4: 14 f4 brge .+4 ; 0xca <WriteByte(unsigned char)+0x4e>
Port |= Mask;
c6: 90 9a sbi 0x12, 0 ; 18
c8: 08 95 ret
else
Port &= ~Mask;
ca: 90 98 cbi 0x12, 0 ; 18
cc: 08 95 ret
[Bericht gewijzigd door Henry S. op (0%)]
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Op maandag 29 juli 2024 10:25:59 schreef deKees:
Hoezo geen C++?
C++ is een superset van C. De C++ compiler kan je gewoon C in duwen vind ie ook goed. Dus zolang je geen C++ specifieke features gebruikt zijn er mensen die roepen dat het C en geen C++ is.
Zolang je afblijft van "serial.Begin"... is het C. 
Ik kan me vergissen (??) maar bovenstaande code met class IO_PORT komt echt niet door een C compiler. Ook al wordt er geen Serial.begin() gebruikt 
En speciaal voor henry62
Hierbij een configureerbare virtuele poort met assignment operator. Is dit wat je wil zien?
//
// Testing access to IO-Ports from C++
// - Using a C++ approach
// - Define a class to mimic an IO_PORT
// - Define member functions to set bits.
//
#include <stdint.h>
#include <avr/io.h>
// Convert BitNr(0 .. 7) to Bitmask(0x01 .. 0x80)
inline constexpr uint8_t Bit(uint8_t BitNr)
{ return 1 << BitNr;
}
// We need access to individual bits.
// - So use a configurable bit class
// - with assignemnt operator to set/clear the corresponding bit.
class VIRTUAL_BIT
{
public:
VIRTUAL_BIT(volatile uint8_t &Port, uint8_t BitNr) :
m_Port(Port), m_BitMask(Bit(BitNr))
{
}
volatile uint8_t &m_Port;
uint8_t m_BitMask;
void operator=(bool NewValue) const
{ if(NewValue)
{ m_Port |= m_BitMask;
}
else
{ m_Port &= ~m_BitMask;
}
}
};
// Now we can define a virtual port class.
// Using a pointer to an array of virtual bits as configuration
// - The we can use an assignment operator to write a byte into this virtual port
class VIRTUAL_PORT
{
public:
VIRTUAL_PORT(const VIRTUAL_BIT *pConfig) :
m_pConfig(pConfig)
{
}
void operator= (uint8_t Value)
{ m_pConfig[0] = Value & Bit(0);
m_pConfig[1] = Value & Bit(1);
m_pConfig[2] = Value & Bit(2);
m_pConfig[3] = Value & Bit(3);
m_pConfig[4] = Value & Bit(4);
m_pConfig[5] = Value & Bit(5);
m_pConfig[6] = Value & Bit(6);
m_pConfig[7] = Value & Bit(7);
}
const VIRTUAL_BIT *m_pConfig;
};
// Define an array of virtual bits for our virtual port
const VIRTUAL_BIT Config[] =
{ { PORTA, Bit(0) },
{ PORTA, Bit(2) },
{ PORTB, Bit(0) },
{ PORTB, Bit(4) },
{ PORTC, Bit(2) },
{ PORTC, Bit(5) },
{ PORTC, Bit(6) },
{ PORTD, Bit(0) }
};
// And define our virtual port, using the above config table.
VIRTUAL_PORT MyPort(Config);
// Now we can assign a value to our virtual port:
int main()
{
MyPort = 0x3B;
}
Kan ook, maar geeft veel meer overhead en is veel trager. Je krijgt nu een extra tabel in SRAM, port adressen moeten telkens worden berekend en bit manipulatie gaat via lds or/and sts.
Totale code size nu 0x19A dus bijna 2 keer zo veel.
[Bericht gewijzigd door Henry S. op (0%)]
Op maandag 29 juli 2024 12:00:49 schreef deKees:
Kan ook, maar geeft veel meer overhead en is veel trager. Je krijgt nu een extra tabel in SRAM, port adressen moeten telkens worden berekend en bit manipulatie gaat via lds or/and sts.Totale code size nu 0x19A dus bijna 2 keer zo veel.
Dat is wat ik bedoelde met de bulk aan extra overhead code voor het kunnen lezen/schrijven van een bij elkaar geraapte poort... 
Ik moet zeggen dat de overhead in alle bovenstaande gevallen nog best meevalt. Zeker in vergelijking met bijv de DigitalWrite() van Arduino.
Welke methode je kiest hangt af van je project en je persoonlijke voorkeuren.
Die laatste methode met VIRTUAL_PORT is misschien wel de beste keus als je op zoek bent naar iets wat je in meerdere projecten kunt gebruiken of als je meerdere virtuele poorten wilt gebruiken binnen een project.
De IO_PORT methode zou mijn voorkeur hebben in gevallen waar je de max performance wilt op een enkele virtuele poort.
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Op maandag 29 juli 2024 11:51:42 schreef deKees:
Ik kan me vergissen (??) maar bovenstaande code ...
sorry ik had het over alle code die /daar/voor in dit topic genoemd werd. Jou code stond er nog niet toen ik eea las, gecontroleerd had dat daar gaan C++ in zat en... een tijd later bedacht om dat "geen C++" bericht te schrijven.
henri62
1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
Even een reactie op alle antwoorden.
De intelligentie zit in de functie WriteByte() dit is dus een gewone C-functie. De class heeft geen bijzonder toegevoegde waarde in dit geval.
Alle bits die bij de poort horen worden hard-coded in die functie gemapped naar de juiste port en bit nummer.
Als je daar tevreden mee bent is het goed, wil je iets generieks maken wordt het ALTIJD langere code, daar ontkom je niet aan.
Dan kun je net zo goed die functie zo maken:
void WriteByte(uint8_t Value)
{
PINA &= ~BIT(0);
PINA |= Value & 0x01 ? BIT(0) : 0;
PINA &= ~BIT(2);
PINA |= Value & 0x02 ? BIT(2) : 0;
PINB &= ~BIT(0);
PINB |= Value & 0x04 ? BIT(0) : 0;
PINB &= ~BIT(4);
PINB |= Value & 0x08 ? BIT(4) : 0;
PINC &= ~BIT(2);
PINC |= Value & 0x10 ? BIT(2) : 0;
PINC &= ~BIT(5);
PINC |= Value & 0x20 ? BIT(5) : 0;
PINC &= ~BIT(6);
PINC |= Value & 0x40 ? BIT(6) : 0;
PIND &= ~BIT(0);
PIND |= Value & 0x80 ? BIT(0) : 0;
}
En de C++ class wegmikken.
Ik heb het niet gecompileerd, maar ik sta er niks van te kijken als het even lang is of nog korter.
@deKees : dat laatste is wat ik dus ongeveer bedoel, dat is generiek bruikbaar. De virtual bit class intern is niet strict noodzakelijk.