1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using Antmicro.Renode.Peripherals.SPI;
9 using Antmicro.Renode.Logging;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Peripherals.Wireless.IEEE802_15_4;
12 using System.Linq;
13 
14 namespace Antmicro.Renode.Peripherals.Wireless
15 {
16     public class AT86RF233 : ISPIPeripheral, IRadio, IGPIOReceiver
17     {
AT86RF233()18         public AT86RF233()
19         {
20             IRQ = new GPIO();
21             Reset();
22         }
23 
ReceiveFrame(byte[] frame, IRadio sender)24         public void ReceiveFrame(byte[] frame, IRadio sender)
25         {
26             this.DebugLog("Frame of length {0} received.", frame.Length);
27             if(frame.Length == 0)
28             {
29                 // according to the documentation:
30                 // Received frames with a frame length field set to zero (invalid PHR) are not signaled to the microcontroller.
31                 this.DebugLog("Ignoring an empty frame.");
32                 return;
33             }
34 
35             if(operatingMode == OperatingMode.RxAackOn || operatingMode == OperatingMode.RxOn)
36             {
37                 HandleFrame(frame);
38             }
39             else
40             {
41                 deferredFrameBuffer = frame;
42                 this.DebugLog("Radio is not listening right now - this frame is being deffered.");
43             }
44         }
45 
Reset()46         public void Reset()
47         {
48             autoCrc = true;
49             accessMode = AccessMode.Command;
50             frameBuffer = new byte[0];
51             deferredFrameBuffer = null;
52         }
53 
OnGPIO(int number, bool value)54         public void OnGPIO(int number, bool value)
55         {
56             if(number != 0)
57             {
58                 this.Log(LogLevel.Warning, "Unexpected GPIO: {0}", number);
59                 return;
60             }
61 
62             this.DebugLog("Chip select set to: {0}", value);
63             if(value && accessMode == AccessMode.FrameBufferAccess && accessType == AccessType.ReadAccess)
64             {
65                 accessMode = AccessMode.Command;
66             }
67         }
68 
Transmit(byte data)69         public byte Transmit(byte data)
70         {
71             this.DebugLog("Byte received: 0x{0:X}", data);
72             switch(accessMode)
73             {
74             case AccessMode.Command:
75                 HandleCommandByte(data);
76                 break;
77             case AccessMode.RegisterAccess:
78                 var result = (byte)0;
79                 if(accessType == AccessType.ReadAccess)
80                 {
81                     result = HandleRegisterRead((Registers)context);
82                 }
83                 else
84                 {
85                     HandleRegisterWrite((Registers)context, data);
86                 }
87                 accessMode = AccessMode.Command;
88                 return result;
89             case AccessMode.FrameBufferAccess:
90                 if(accessType == AccessType.ReadAccess)
91                 {
92                     return HandleFrameBufferRead(context++);
93                 }
94                 else
95                 {
96                     HandleFrameBufferWrite(context++, data);
97                 }
98                 break;
99             default:
100                 this.Log(LogLevel.Warning, "Unsupported access mode: {0}", accessMode);
101                 break;
102             }
103             return 0;
104         }
105 
FinishTransmission()106         public void FinishTransmission()
107         {
108         }
109 
110         public int Channel { get; set; }
111 
112         public event Action<IRadio, byte[]> FrameSent;
113 
114         public GPIO IRQ { get; private set; }
115 
HandleCommandByte(byte data)116         private void HandleCommandByte(byte data)
117         {
118             // according to the documentation 'data' byte should be interpreted as follows:
119             // 1 0 A A A A A A - Register read from address A A A A A A
120             // 1 1 A A A A A A - Register write to address A A A A A A
121             // 0 0 1 x x x x x - Frame buffer read
122             // 0 1 0 x x x x x - Frame buffer write
123             // 0 0 0 x x x x x - SRAM read
124             // 0 1 0 x x x x x - SRAM write
125 
126             if((data & (1 << 7)) == 0)
127             {
128                 accessMode = ((data >> 5) & 1) == 0 ? AccessMode.SramAccess : AccessMode.FrameBufferAccess;
129             }
130             else
131             {
132                 accessMode = AccessMode.RegisterAccess;
133             }
134             accessType = (data & (1 << 6)) == 0 ? AccessType.ReadAccess : AccessType.WriteAccess;
135             if(accessMode == AccessMode.RegisterAccess)
136             {
137                 context = (byte)(data & 0x3F);
138                 this.DebugLog("Register 0x{0:X} {1} request", context, accessType == AccessType.ReadAccess ? "read" : "write");
139             }
140             else
141             {
142                 context = -1;
143                 this.DebugLog("Command received: {0} {1}", accessMode.ToString(), accessType.ToString());
144             }
145         }
146 
HandleFrame(byte[] frame)147         private void HandleFrame(byte[] frame)
148         {
149             // fcs (crc) check
150             if(frame.Length >= 2)
151             {
152                 var crc = Frame.CalculateCRC(frame.Take(frame.Length - 2));
153                 if(frame[frame.Length - 2] != crc[0] || frame[frame.Length - 1] != crc[1])
154                 {
155                     this.Log(LogLevel.Warning, "A frame with wrong CRC received - dropping it.");
156                     return;
157                 }
158             }
159             else
160             {
161                 this.Log(LogLevel.Warning, "Short frame (length {0}) received - CRC is not checked.", frame.Length);
162             }
163 
164             if(operatingMode == OperatingMode.RxAackOn && Frame.IsAcknowledgeRequested(frame))
165             {
166                 var frameSent = FrameSent;
167                 if(frameSent != null)
168                 {
169                     var ackFrame = Frame.CreateAckForFrame(frame);
170                     this.DebugLog("Sending automatic ACK for frame sequence number: {0}", ackFrame.DataSequenceNumber);
171                     frameSent(this, ackFrame.Bytes);
172                 }
173             }
174 
175             frameBuffer = frame;
176             this.DebugLog("Setting IRQ");
177             IRQ.Set();
178         }
179 
HandleRegisterRead(Registers register)180         private byte HandleRegisterRead(Registers register)
181         {
182             this.Log(LogLevel.Noisy, "Reading register {0}.", register);
183             switch(register)
184             {
185             case Registers.TrxStatus:
186                 return (byte)((deferredFrameBuffer == null ? 0xC0 : 0x80) + (byte)operatingMode); // CCA_DONE + CCA_STATUS
187             case Registers.TrxState:
188                 return (byte)operatingMode;
189             case Registers.VersionNum:
190                 return 0x1; // Revision A
191             case Registers.ManId0:
192                 return 0x1F; // Atmel JEDEC manufacturer ID
193             case Registers.ManId1:
194                 return 0x0; // Atmel JEDEC manufacturer ID
195             case Registers.TrxCtrl0:
196                 return 0x9; // CLKDM_SHA_SEL + CLKM_CTRL (1Mhz)
197             case Registers.TrxCtrl2:
198                 return 0x20; // OQPSK_SCRAM_EN
199             case Registers.IrqStatus:
200                 IRQ.Unset();
201                 // TODO: this probably should not be hardcoded
202                 return 0x8 + 0x20; // TRX_END + AMI
203             case Registers.PhyCcCca:
204                 return (byte)(0x20 + Channel); // CCA_MODE + Channel
205             case Registers.PhyEdLevel:
206                 return 0x53; // maximum ED level value
207             case Registers.PhyRssi:
208                 return 0x9c; // RxCrcValid + Maximum RSSI value
209             default:
210                 this.Log(LogLevel.Warning, "Read from unexpected register: 0x{0:X}", context);
211                 return 0;
212             }
213         }
214 
HandleRegisterWrite(Registers register, byte data)215         private void HandleRegisterWrite(Registers register, byte data)
216         {
217             this.Log(LogLevel.Noisy, "Writing register {0} with data {1:X}.", register, data);
218             switch(register)
219             {
220             case Registers.TrxState:
221                 data &= 0x1f;
222                 if(!Enum.IsDefined(typeof(OperatingMode), data))
223                 {
224                     this.Log(LogLevel.Warning, "Unknown mode: 0x{0:1}", data);
225                     return;
226                 }
227                 operatingMode = (OperatingMode)data;
228                 this.Log(LogLevel.Info, "Entering {0} mode", operatingMode.ToString());
229                 if(operatingMode == OperatingMode.ForceTrxOff)
230                 {
231                     operatingMode = OperatingMode.TrxOff;
232                     this.Log(LogLevel.Info, "Entering {0} mode", operatingMode.ToString());
233                 }
234                 if((operatingMode == OperatingMode.RxOn || operatingMode == OperatingMode.RxAackOn) && deferredFrameBuffer != null)
235                 {
236                     HandleFrame(deferredFrameBuffer);
237                     deferredFrameBuffer = null;
238                 }
239                 break;
240             case Registers.PhyCcCca:
241                 Channel = data & 0x1F;
242                 this.Log(LogLevel.Info, "Setting channel {0}", Channel);
243                 break;
244             case Registers.TrxCtrl1:
245                 autoCrc = ((data & 0x20) != 0);
246                 this.Log(LogLevel.Info, "Auto CRC turned {0}", autoCrc ? "on" : "off");
247                 break;
248             default:
249                 this.Log(LogLevel.Warning, "Write value 0x{0:X} to unexpected register: 0x{1:X}", data, context);
250                 break;
251             }
252         }
253 
HandleFrameBufferRead(int index)254         private byte HandleFrameBufferRead(int index)
255         {
256             if(index == -1)
257             {
258                 // this means we have to send PHR byte indicating frame length
259                 return (byte)frameBuffer.Length;
260             }
261             if(context < frameBuffer.Length)
262             {
263                 return frameBuffer[index];
264             }
265             if(index == frameBuffer.Length)
266             {
267                 // send LQI
268                 return 0xFF; // the best Link Quality Indication
269             }
270             if(index == frameBuffer.Length + 1)
271             {
272                 // send ED
273                 return 0x53; // maximum Energy Detection level
274             }
275             if(index == frameBuffer.Length + 2)
276             {
277                 accessMode = AccessMode.Command;
278                 // send RX_STATUS
279                 return 0x80; // CRC ok
280             }
281             return 0;
282         }
283 
HandleFrameBufferWrite(int index, byte data)284         private void HandleFrameBufferWrite(int index, byte data)
285         {
286             if(index == -1)
287             {
288                 // this is PHR
289                 frameBuffer = new byte[data];
290             }
291             else
292             {
293                 frameBuffer[index] = data;
294             }
295             if(index == frameBuffer.Length - 1)
296             {
297                 SendPacket();
298                 accessMode = AccessMode.Command;
299             }
300         }
301 
SendPacket()302         private void SendPacket()
303         {
304             var frameSent = FrameSent;
305             if(frameSent == null)
306             {
307                 this.NoisyLog("Could not sent packet as there is no frame handler attached.");
308                 return;
309             }
310 
311             if(autoCrc)
312             {
313                 if(frameBuffer.Length >= 2)
314                 {
315                     // replace buffer's last two bytes with calculated CRC
316                     var frameDataLenght = frameBuffer.Length - 2;
317                     var crc = Frame.CalculateCRC(frameBuffer.Take(frameDataLenght));
318                     Array.Copy(crc, 0, frameBuffer, frameDataLenght, 2);
319                 }
320                 else
321                 {
322                     this.Log(LogLevel.Warning, "Sending short frame (length {0}) - CRC is not calculated.", frameBuffer.Length);
323                 }
324             }
325 
326             frameSent(this, frameBuffer);
327             this.DebugLog("Frame of length {0} sent.", frameBuffer.Length);
328         }
329 
330         private OperatingMode operatingMode;
331         private bool autoCrc;
332         private int context;
333         private AccessMode accessMode;
334         private AccessType accessType;
335         private byte[] frameBuffer;
336         // this is used to hold the last frame received when radio was in TrxOff mode;
337         // conserving energy can lead to disabling radio (going into TrxOff mode) and
338         // turning it on only periodically; during those short period network acticity
339         // is assessed by monitoring channel state (CCA); in the emulator network communication
340         // takes no time so it is problematic to decide if CCA should return true or false;
341         // here comes the trick: if there was any frame received during TrxOff mode CCA
342         // returns channel busy state and only when the radio switches to RxOn mode the frame
343         // is actualy received
344         private byte[] deferredFrameBuffer;
345 
346         private enum OperatingMode : byte
347         {
348             ForceTrxOff = 0x3,
349             RxOn = 0x6,
350             TrxOff = 0x8,
351             PllOn = 0x9,
352             RxAackOn = 0x16,
353             TxAretOn = 0x19
354         }
355 
356         private enum AccessMode
357         {
358             Command,
359             RegisterAccess,
360             FrameBufferAccess,
361             SramAccess
362         }
363 
364         private enum AccessType
365         {
366             ReadAccess,
367             WriteAccess
368         }
369 
370         private enum Registers
371         {
372             TrxStatus = 0x1,
373             TrxState = 0x2,
374             TrxCtrl0 = 0x3,
375             TrxCtrl1 = 0x4,
376             PhyRssi = 0x6,
377             PhyEdLevel = 0x7,
378             PhyCcCca = 0x8,
379             TrxCtrl2 = 0xC,
380             IrqStatus = 0xF,
381             VersionNum = 0x1D,
382             ManId0 = 0x1E,
383             ManId1 = 0x1F
384         }
385     }
386 }
387