looks good! i would suggest that the 3d printed plastic slot bracket is made of two parts, one that goes on the inside and a cap that goes on the outside to cover the esp32 and protect it from shorting. If they screw together it would help clamp down the card as well!
Now onto the software phase. I think first step is to begin the utility on the Mac to select your WiFi network. ESP32 makes listing the nearby APs easy so that oughta be the first command we implement. We can easily try it out once we have the utility.
Should it be a CDEV or a full-fledged app? CDEV is kinda restrictive in what you can do compared to a real app if I recall correctly but it feels more right that the WiFi selector should be a control panel. No need for anything like a WiFi menu; that's nice but it can come later if desired.
Commented on discord, but also saying it here: if nobody else wants to call dibs, I'm up for working on the ESP32 firmware for this project. Also maybe some other bits, maybe the 3D printed bracket, messing with the DeclROM, etc.
Schematic looks nice to me after a brief skim. Is that github repo not public? Getting a 404.
You could put the esp32 module anywhere on the pcb and use an external antenna. The modules can be bought with an IPEX connector on them. Then you just need an rp-sma pigtail and a bracket to hold it. I've added this option to FujiNet and it's working well for those who need longer range.
You could put the esp32 module anywhere on the pcb and use an external antenna. The modules can be bought with an IPEX connector on them. Then you just need an rp-sma pigtail and a bracket to hold it. I've added this option to FujiNet and it's working well for those who need longer range.
Yeah I think the IPEX connector antenna is more reasonable but I am trying the "sticking out the back" approach to see if it's workable. I did reserve room on the board to move the ESP32 module inward and cut the board off at the bracket just in case the current approach doesn't work so well.
Commented on discord, but also saying it here: if nobody else wants to call dibs, I'm up for working on the ESP32 firmware for this project. Also maybe some other bits, maybe the 3D printed bracket, messing with the DeclROM, etc.
Schematic looks nice to me after a brief skim. Is that github repo not public? Getting a 404.
Just made the repo public, sorry about that. Anyway I'm gonna make a basic utility to talk to the ESP32 and then we can get boards made and start on the ESP32 program. Gimme a bit of time and I'll send you an assembled card.
Amazing! I'm gonna try printing it and see how it comes out. The design may need some tweaks as a concession to the 3d-printed construction though. I'm gonna hold off on fabbing the board until we get the bracket design close to final so that we can change the board if necessary.
Yeah 3D printing now. Worse comes to worse I can make the bracket thicker at 1.2mm instead of 0.8mm. I could also make a version that allows you to slot or press in a m3 nut. Kinda like not having to buy extra nuts but I guess would have to buy m3 screws anyhow.
It's interesting that some cards seem to cheat on the bracket design. I have an Asante network card and they place LEDs where the upper bracket loop should be since they ran out of room lol. To be fair that extra loop doesn't really add much to the bracket.
I forgot to mention in the repo that the M3 screws should be 5mm long, The Asante card I have uses ~5.8mm long screws but they are a longer than what's actually needed.
The basic framework for the Mac driver is coming along nicely. I have a rudimentary WiFi control panel which also houses the requisite DRVRs and the INIT to install them:
I can upload source if anyone is interested but I've been developing on my Quadra 660AV (lovely machine to work on) so it's not as easy as a "git push."
One thing I've realized is that there is a missing piece in my concept of the software architecture. The way I saw it, the Ethernet driver on the Mac would communicate with the NuBus card hardware at the behest of client software, e.g. MacTCP. That's how a regular Ethernet card does it and it works well for that application. But for the WiFi card, what happens when the WiFi control panel needs to connect to a different network? The CDEV can't just work the hardware directly since the Ethernet driver could be actively using the card and then the commands and responses for the two clients of the hardware would get mixed up. The CDEV could instantiate the Ethernet driver and then use control/status calls to accomplish the function but I think it would be desirable to have a thinner driver that handles management of the card interface, command/response, etc. separate from the Ethernet piece.
So my next order of business is to define and implement this WiFi card hardware abstraction layer driver which the CDEV will call to connect/disconnect/list networks. This is where the polling data transfer mechanism oughta be implemented in order to keep it separate from the Ethernet stuff. That way we will be able to get the Mac-to-card interface solid before moving on to implementing Ethernet on top. I will make sure that it's easy for clients of the WiFi driver to get function pointers to the various routines so as to avoid going through the Device Manager twice for each call to the Ethernet driver.
edit: One thing that’s kinda confusing about Mac OS Ethernet drivers is the “ENET Driver Shell.” As I understand it, when The client of an Ethernet driver opens the “.ENET” driver, the system actually opens the “ENET Driver Shell,” which behind the scenes is able to open multiple ENET driver instances for different cards and route frames appropriately to each.
It sounds conceptually simple but there is little discussion of the consequences. Like, how are the individual ENET drivers opened? Do they appear in the device driver unit table, or does the ENET shell sorta emulate the Device Manager and call the drivers itself? What happens then if a driver tries to look itself up in the unit table?
This is why I was happy to see the source of Apple’s “.ENET” driver for
the SONIC chip. I can just copy the interface behavior of that.
Another annoying thing about the Ethernet driver lifecycle is that it’s hard to install an Ethernet driver in the system. The ENET driver shell searches the system file, motherboard ROM, and NuBus declaration ROMs for an appropriate driver for the card requested to be used. I think we will be updating the driver frequently so it shouldn’t be in ROM. Therefore we have to install the driver into the system file instead of using an extension. Yuck!
In terms of supporting possibly concurrent access to the card, it seems to me like it would make sense to split the registers/address space between wifi configuration stuff and "ethernet" stuff, which seems like what you're suggesting? So network operations (send/receive) and configuration operations (scan/connect/disconnect) won't be accessing the same things. Otherwise there would need to be like a command queue with locking or something, which seems like overkill.
Another annoying thing about the Ethernet driver lifecycle is that it’s hard to install an Ethernet driver in the system. The ENET driver shell searches the system file, motherboard ROM, and NuBus declaration ROMs for an appropriate driver for the card requested to be used. I think we will be updating the driver frequently so it shouldn’t be in ROM. Therefore we have to install the driver into the system file instead of using an extension. Yuck!
weird thought: a small declaration ROM driver which loads an actual driver (from an extension or whatever, maybe scan for a known filename or resource or creator)? It would be nice to have the whole thing in ROM once the code is stable.
Unfortunately the current hardware doesn't really support that. When the Mac writes to the card, a few address bits are latched as well so the ESP32 can see what location was written to and there can be multiple write streams to the card. But when the Mac reads data, there aren't multiple registers and an output mux, so there is only one response data stream coming back from the card. Unless the data was tagged for multiple streams (reducing the actual data bandwidth), you can't start and complete another command/response in the middle of another one. The "all 7400, no FPGA" design is built out to the max right now (40 chips or whatever) so I don't really think I should add more hardware to solve the problem.
So yeah, there has to be some kind of command queueing mechanism in the WiFi driver. I think that would be necessary even if the hardware supported separate ethernet/config read streams. I'd like to send the packet data to the card at the sorta-interrupt "deferred task" level so that an async ENetWrite call can return pretty quickly while the data is transferred from the Mac to the card. This basically necessitates some degree of blocking/queuing/busywaiting since two programs can do concurrent asynchronous ENetWrite calls.
Thanks for saying this though, you basically made me figure out more specifically what I wanted from the "WiFi driver" layer. It's supposed to do the command queuing piece. Oh and there doesn't have to be "locking," per se, since the concurrency in the Mac OS is just interrupts and process transitions when WaitNextEvent is called. Since I don't think it's allowed to do any of the ENet calls at interrupt time, there is no "true" reentrancy where partway through execution of one function it can get called again. It's just that the Mac can come back to ENetWrite or whatever while the previous write is still trickling out at the deferred task level.
Deferred tasks are executed after interrupt processing before the return back to the current app but with interrupts enabled. Therefore Mac-to-card data transfer can proceed without screwing up the serial, mouse, etc. like during a floppy read/write. And we can put a bound on the time spent transferring data during the deferred task so that if the ESP32 does a task switch and can't accept more data, control can return to the current app while the ESP32 is off doing another task for a few milliseconds.
weird thought: a small declaration ROM driver which loads an actual driver (from an extension or whatever, maybe scan for a known filename or resource or creator)? It would be nice to have the whole thing in ROM once the code is stable.
Yeah it would be good to do it like that. When the driver is finished, we can just put an 'enet' resource (almost identical format to a 'DRVR') in the DeclROM and that's one of the search locations used by the .ENET Shell when it looks for card drivers. The annoying thing is that in the interim, we have to either put the 'enet' resource in the system file or explicitly load our own .ENET driver rather than Apple's .ENET Shell. Evidently you're not supposed to load your own driver called ".ENET" since the .ENET Shell driver is Apple's solution for transparently supporting multiple Ethernet cards, each with a different 'enet' driver resource. Instead, non-NuBus Ethernet hardware is supposed to load itself as .ENET0. All very pesky... maybe we can make an init that temporarily installs the 'enet' driver into the system resource file in memory at boot.
Hello,
I'm looking at the schematics of the NuBus-ESP32 to 'inspire' myself as to how drive the open collector lines (e.g. /NMRQ) in my own NuBus design - 80ma is a lot to drive; 5V-tolerant CPLD and 74LVC can't do it directly.
I was trying to understand the rational behind the resistor value(s) around the MMBT3904, when I noticed a discrepancy; the resistors between the controlling chips and the transistor (e.g. R31 & R32, R10, R22) are labeled as '1k', but reference LCSC C25190, and those are 27 ohms. My guess is that 27 ohms is the correct value (some sort of current limiter?), but I'm no hardware guy so I'd rather ask first
Also, R27 is also labeled as '1k' and reference C21190, which really is a 1k resistor; I assume that's just pull-down ?
Finally, have you built a board already? I'd love to see a picture if so - and have confirmation the interrupt line works as expected before I make an expensive order to Seeed ;-)
Oh, I wouldn’t look at the schematic for this as a good reference on how to design a NuBus card. It’s a work in progress and I have not actually fabricated a board yet. Anyway about the drive strength required to drive the NuNus, it’s actually not as bad as you’re saying. You only need 24 mA DC drive. The 80 mA spec is for AC drive and 74LVC can do it although the edge rate of 74LVC is probably too fast.
Now regarding how I’m driving the NMRQ line, I used the 2N3904 transistors not just because they oughta work but for reasons idiosyncratic to this particular design. The transistors are cheap and they’re also used in the ESP32 programming circuit. If I didn’t have an NPN transistor in the design already then I’d be using a 74LVC chip already in use to drive IRQ so as to minimize the BOM line count. If I were you I would check whether the IRQ line is supposed to be driven open-collector (as I am doing here) or if you can use a push-pull driver. Maybe using the transistor (which just drives low and not high) is not the best strategy if the interrupt line is not shared among all the cards, which I believe it’s not. If NMRQ doesn’t need to be driven open-collector to avoid a bus fight, then maybe the transistor approach is not so good and I should be using a 74LVC chip to drive it to 0 as well as 1. Or even if open-collector is required it would be possible to use (for example) 1/4 of a 74LVC125 with the A input tied low and the OE as the input.
I’m gonna get back to this project soon but like I said, it’s a work-in-progress so I wouldn’t trust any particular detail in the schematic.
Edit: Looking back at it, the circuit for driving NMRQ is quite a hack. There are two ‘3904s which can pull it low. Maybe this wire OR thing going on with them was the only reason I am driving open-collector. Anyway one ‘3904 is driven by U22C/D and one is driven by the ESP32. For the inputs, 1kohm is the right strength for the resistors limiting the base current (R10, R31, R32). R27 is a pulldown but it’s fine for it to be 1k. You only need 0.7v to turn on a transistor so the 1/2 voltage divider formed there doesn’t matter. The pull down isn’t required on the other transistors because the U22 outputs never tristate. Anyway about the base input resistance, it could be 1k or 10k or maybe higher but 22/27 is too low. The base input of a transistor in that configuration looks like a diode to GND so you need more than 27 ohms or else you’ll be wasting a bunch of power driving a short to ground through 27 ohms and 0.7 less volts. Now regarding the U22C and D outputs, R31 and R32 along with the transistor are forming a very cheap OR gate that wastes a milliamp or two when the inputs aren’t equal. And there probably oughta be some resistance in between both of the transistors’ collector pins and the bus NMRQ signal, maybe 22 ohms (or less) is more appropriate there.
24 mA DC is for the address/data and control lines; for now I have a set of 74FCT245 for the A/D and a XC9536XL CPLD for control (which is also suppose to handle stuff like arbitration). This is derived from my SBusFPGA project, where the bus has much lower requirements and I don't even need external drivers, just level-shifters (CB3T) so this is unknown territory for me.
For the open collector lines (/RQST, /ARB*, /NMRQ all are explicitly open collector) DCDMF3 table 5-2 page 109 says 60 mA DC drive, that's the one problematic (...and I just realized I forgot /ARB and won't have enough pins if I split them all R/W...). In the SBusFPGA I have a 74LVC1G07 which does the job perfectly. All of the 74LVC1G07, the 74FCT245 and the CPLD will have trouble to go beyond 24-32 mA. The schematics for TI custom NuBus chips also list an exception for drive strength on those lines, so it might not just be over-specified but actually necessary...
Hence my quest for an alternative solution
Edit: forgot to add a link
Ah! Okay no big deal. You can parallel multiple CMOS outputs (at the expensive of potentially violating the maximum capacitance spec) to get more drive strength. So you can hook up three or four buffers together to get the right drive strength. In general this only works with CMOS though, not LSTTL. Or just use the transistor but yeah 1k is correct for the base resistor. Some would even say that’s too low and I’m overdriving it slightly (no problem but it makes it slightly slower to turn off).
Not an EE by trade, so the transistor solution "feel" more appropriate than just using multiple drivers - not that I could explain why it just seem more elegant, which isn't a very scientific concept
What's the theoretical formula to compute how much inline resistance you need to drive the transistor ? With the two 1k resistor R10 and R27 playing voltage divider, transistor Q4 should see 1/2 the input voltage (around 1.65V for me) on its base - but no idea how much current will go through it or which one is important...
Anyway I've bitten the bullet and moved the CPLD from a VQ44 to a VQ64 package with 72 macrocells, so I can connect all required pins and then some.
Looking at some datasheets at Mouser, how about 74LVT125 ? They seem to have a stronger driver than LVC, and one can handle all /ARB* has they have 4 independent /OE lines. Half another could deal with /NMRQ and /RQST. 74ABT125 are another candidate ; 74BCT125 are way too expensive (about 5-10x as much!). They all top out at 64mA if i read the doc correctly, but I guess that could be enough in practice?
Edit: sorry, there could be redundant question(s) I didn't see you had updated a previous post.
Yeah, good choice too although they draw a bit of static current as opposed to the 'LVC125s which only really draw current when they switch. It's just a few mA though, no big deal. Hmm maybe you can't parallel LVT-series though, being that they're BiCMOS (bipolar+CMOS). I am not sure. But I think a single LVT buffer does 64 mA low drive so that oughta be good.
so the transistor solution "feel" more appropriate than just using multiple drivers
Yeah, I would agree it's a bit of a hack paralleling multiple drivers but I believe the 74xx manufacturers say it's technically allowed. One issue though is that the transistor is sloooooowwww. Takes on the order of a microsecond to turn on and off because the transistor is put into "saturation." This is fine on the /NMRQ line since it's not a synchronous signal but you can't use it anywhere near the speed of an LVC gate. It's like 1000x slower.
What's the theoretical formula to compute how much inline resistance you need to drive the transistor
Oohh, complicated question but part of it is captured by the notion of the "beta factor" aka current gain of the transistor... how much current into the base motivates how much current in the collector. For most transistors it's in the range of 100-1000x, so you put one unit of current into the base and that can motivate 100-1000x more current to be drawn from the collector. So your example 500 ohm (1k || 1k) and 1.65V-0.7V (have to subtract one diode drop) makes 1.9 mA into the base and assuming a very low gain of 100x that oughta be enough to make the transistor sink up to 190 mA. In reality the transistor is capable of more gain than 100x and so 1k is overdriving it a bit and 10k would probably be fine too.
There's a funny phenomenon about the relationship between the transistor turn-off speed and base resistance. If you are putting the transistor in saturation (i.e. you're forcing current into the base well in excess of the gain times the collector current), a moderate resistance turns off the transistor fastest. Too high resistance and you can't move the charge out of the base quickly, but too low resistance and you sorta saturate the transistor even more and it takes even longer to take it out of saturation despite the lower resistance. Lower resistance tends to always turns it on faster though. Too low and you'll burn too much current of course. This is one of the difficulties of "saturating logic." It's slow to take the transistors out of saturation. Many techniques to prevent transistors from going (as much) into saturation were applied in the LSTTL era. Nowadays the solution is to make smaller MOSFETs.
Ouch. That's slow indeed! Even on a slow bus like NuBus it's still ~10 bus cycles, maybe more. That's probably acceptable on the /NMRQ line (which is private to the slot), but it won't do for /ARB* and /RQST. When bus-mastering, you need to sample /RQST to make sure it's not in use already, then assert /RQST and the appropriate /ARB*, then within two cycles resolves the conflict if someone else is also requesting at the same time. So you need to be able to assert the lines in less than a bus cycle but also to de-assert within a bus cycle, if I've understood the protocol correctly.
From your schematics it seems you're not planning on bus-mastering so for you it's irrelevant; but with a FPGA I'd rather have all options open for the future (in the SBusFPGA, the bus mastering is used by the USB OHCI controller and the L/S unit of the crypto engine, both of which are very far-fetched at this stage but not completely impossible).
Okay! After a bit of a hiatus due to procrastinating on the Quadra 660AV 2 MB VRAM mod, I have completed a draft for the sort of low-level driver that allows the Mac to talk to the WiFi card. This solves the issue I was having with the wifi driver... how do I send management commands for listing the wifi networks, getting the MAC address, etc. while the Ethernet driver is also using the card and potentially sending its own commands? There has to be a piece that receives requests from multiple client apps or drivers and makes sure they take turns using the card.
As I wrote before, the hardware is geared toward a command-response sort of thing between the Mac and the ESP32, where the Mac sends commands to the ESP32 and the ESP32 responds. So this new HAL (hardware abstraction layer) implements a simple command queueing scheme that solves the problem of multiple clients trying to use the WiFi card at once. We will implement the Ethernet driver and the WiFi control panel as clients of the WiFi HAL.
On the other side, on the ESP32, we will have to write the reverse piece which will get commands from the Mac, run them, and then deliver the response back. On top of that we will implement the scheme to bridge Ethernet packets onto WiFi (and vice versa) as well as implement the network configuration, etc. commands.
I've got four methods in the WiFi HAL:
C:
// Turns on ESP32 and gets ready to do wifi commands
OSErr wifihal_open(wifihal_t *h, Ptr iobase);
// Sends a command to WiFi card
OSErr wifihal_cmd(wifihal_t *h, wificmdentry_t *cmd);
// Waits for a command to complete
OSErr wifihal_await(wificmdentry_t *h);
// Turns off ESP32
void wifihal_close(wifihal_t *h);
So a wificmdentry refers to an instance in which a wificmd is supposed to be transmitted to the ESP32 and a response received. Wificmd has three parts, a command id (8 bits) and two arguments (one 8 bits and one 16 bits). And then there's a wifiresponse which you get in response to sending a command to the ESP32.
And what's a wifihal? It's a simple structure that stores basic information about the state of the driver:
Basically just the I/O base address an a pointer to the current command executing. Each command entry is a linked list entry with a pointer to the next command in the queue. The last field in the wifihal struct is a pointer to the end of the linked list so that commands can be added without traversing the whole thing.
To explain the various fields of wificmdentry it would be best to go through how to send a command:
Start up the wifi card by instantiating a wifihal struct (probably in the system heap) and calling wifihal_open(...) with a pointer to it and the card's I/O base address as arguments.
Create a wificmdentry, making sure to allocate it somewhere where it won't uhh "go away" until the command is finished executing (i.e. on the heap or on the stack but don't leave that function)
Set the wificmd in the wificmdentry to the command id and arguments desired
Set the txdata pointer to some data to send or null if no payload needs sent. As with the wificmdentry, this needs to stay around until command is done executing.
If txdata not null, set txlength to the data length to be sent.
Set the rxdata pointer to a buffer into which to receive payload data back from the ESP32 or null to ignore received data, if any.
If rxdata not null, set rxlength to the maximum data length to be received (i.e. size of rxdata buffer).
Call wifihal_cmd(...), passing a pointer to the wifihal and wificmdentry just created. This enqueues the command for execution.
Command execution happens at the "deferred task" level so it's asynchronous to program execution, but a program can check the done and phase fields of the wificmdentry to monitor the execution progress of the command. The txlength and rxlength fields will also decrease as txdata and rxdata are sent and received, respectively. Of course, since the Ethernet driver requires this level of concurrency, you can even WaitNextEvent(...) and do a task switch while a command is pending! Alternatively, you can call wificmd_await(...) to busywait until the command is done executing.
Also note that commands queueing for execution are stored in a linked list and since the calling application has responsibility for allocating memory for the command buffers and whatnot, there is basically no limit to the number of commands which can be waiting to be executed.
Now the bad news... unfortunately during the long process of the 660AV VRAM mod, my trusty 7 GB Seagate SCSI disk broke and my small bit of work on the wifi control panel was lost. Oh well! The 660AV was very pleasant to develop on but now I have the new 14" MacBook Pro and that's even more pleasant to develop on lol. Plus I knew I had to switch it to the retro68 toolchain anyway... who wants to actually develop a complex piece of software on the classic Mac?
Source: (I will put it on GitHub eventually)
There's three files, first a header with methods for using the card's register interface: